v0.15.8
This commit is contained in:
Generated
+1
-1
@@ -719,7 +719,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gree-controller"
|
||||
version = "0.15.7"
|
||||
version = "0.15.8"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"aes-gcm",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "gree-controller"
|
||||
version = "0.15.7"
|
||||
version = "0.15.8"
|
||||
edition = "2021"
|
||||
authors = ["GREE Controller contributors"]
|
||||
description = "Standalone local GREE HVAC controller with Web UI, SQLite and Home Assistant sensor support"
|
||||
|
||||
+16
-16
@@ -1,14 +1,14 @@
|
||||
de0793949cf01d27d903653226ecc9b5f72523d8711f20bcab022cf42c4c4ed0 ./.env.example
|
||||
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
|
||||
bc531fc3bd260aa91bc4b3f000768d740222f6f80cd780cf2bbd4522d82d8841 ./Cargo.lock
|
||||
8555c5b50711059f76fdbaae495a6a56cae16813af2efd7c69a7fde34762949d ./Cargo.toml
|
||||
ddda584a029e67d057e3afbe93246795e6579d7efd2f3e9ebe4cfd2bb164e907 ./Cargo.lock
|
||||
d09e3c8438adcfac6aa9d16de9aade898a1fa6f64f7edf17bcd70afb714db12f ./Cargo.toml
|
||||
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
||||
4d648f23faf4e57d2900f898ce5416dd0b2be9b842f18e67e8f6b31312c511dc ./README.md
|
||||
54c111977ab409876fd32101fedac6757f3026106d71b187b34c4c36b53424e9 ./SOURCE_MANIFEST.sha256
|
||||
5b5586d2e3e7b80c0ffe2d241517f8f7a8c4d7e80169a6cb3cf46221024f54e9 ./README.md
|
||||
54e2716acb43f0813c6b979df802b8a0f7e3f8f03b1e42b45c746945ed21d098 ./SOURCE_MANIFEST.sha256
|
||||
29e9821e5ed216ea3565dd6d8bc0b174ed3cf9fa72c4ccebbfa99e98db3ad8f9 ./build.rs
|
||||
fe1bd7a84b16782f9ba2c3c301592b52b194698d7e71f0ba806a88ed3c5a9688 ./docs/API.md
|
||||
a881ba0808dd3f189d92038f6163c39d6b6208f6dbff5c32c0ea1a2628f3fbb4 ./docs/FLOW.md
|
||||
f70388d4fd9a5b35c1e32c43a9a4b4599adc6bd458e388ac571684a9cb1dc20d ./docs/openapi.json
|
||||
70a8217b46a12ae47e6b461f6f619fa19facb0b422fd8d850208b55237abc752 ./docs/API.md
|
||||
c6b1aded8ef6f4c840a2b1f2ffa3f0b22eddc080a4750adb34ee950f12f64382 ./docs/FLOW.md
|
||||
9debc0d7d3f663c2a46ab17fd385a37aa732757e6bb7c75d6d60281ea89092b5 ./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
|
||||
396c1e2aa1dce367bd1b773ff2e9b80e09729ca58900dd5141085bf30a47f8f0 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json
|
||||
00fc79f5195f43cd8d076b3d9a7a782263f0bde2a6b58b310c2d7a8b307a9f88 ./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
|
||||
861a1df8677b0539720978aa0e84e7ffc7dea96d37e6d99c6adca2ced0851a44 ./ha-addon/repository/gree-controller/CHANGELOG.md
|
||||
2976822c9663e505d22297e2749192b4ca119bb4e46ccc1c29c813522c84b3c5 ./ha-addon/repository/gree-controller/CHANGELOG.md
|
||||
50b8ba050c6b4919e4d057f7b38c1e23d3913b5b7c87552626452e9695d37251 ./ha-addon/repository/gree-controller/DOCS.md
|
||||
e3834bf712d4cd2acc715d8c85db96dd6e2fb3fbb758dac537f6034c337b3745 ./ha-addon/repository/gree-controller/README.md
|
||||
40d18f2415fb84fe83d79ec19a9c9ca20232d751e02e5713786bac4d38f715a1 ./ha-addon/repository/gree-controller/config.yaml
|
||||
5f0bd0d1ea4aca47844f9e870e4ba1676ad8b90369577db75a1bb0d00228a9da ./ha-addon/repository/gree-controller/README.md
|
||||
f43723e7e356c7cc637b36b5ade0e34cfb69c50c4cf653cca7fc013e926cd7c9 ./ha-addon/repository/gree-controller/config.yaml
|
||||
3aae6cdae4c3aaab7786a9575e7093b7e2c5b69288167075f4b7475611691a6b ./ha-addon/repository/gree-controller/topologia-ha-vlan-gree-pl.png
|
||||
0d0b42a7639b128946eec379175436fa1f150294f39d2dfd169077bfa87fd14d ./ha-addon/repository/gree-controller/topology-ha-vlan-gree-en.png
|
||||
2272740b185e9520e948f74b8f45cf98b8b5fd28c93bdee760da626f91fe5c2a ./ha-addon/repository/gree-controller/translations/en.yaml
|
||||
@@ -85,7 +85,7 @@ fdcd9a5055d08037278b842e7ab69265345c5811f0a06867136c511d140bb191 ./presets/wind
|
||||
804f22123cd3e8db0fac791826c8dd9f758fb866c8e3b5655fb6d25d259dccf1 ./presets/workday_comfort.json
|
||||
fd7390d64a2378352c09382accf6efff43903d952d111b22a982f06c5b2a3b4b ./regenerate-sha.sh
|
||||
c64b1c6deeb24f20af662bfce7fa3683703914987241107ce664fa2f6d1a9dcb ./scripts/README.md
|
||||
9083c4a8c602698a53d926864f2fe7e8c9658379f3d82fcb4171f44ec946a7ad ./scripts/api_dev_test.py
|
||||
3a3287a4ca516f91a1b1bf14257aeef7b1b3027d592200c0a6da1cc70afa9c32 ./scripts/api_dev_test.py
|
||||
5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh
|
||||
6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh
|
||||
1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh
|
||||
@@ -107,7 +107,7 @@ b53e1afab342634fedc218ea4517446f19a5ba7985dee599e191b37d9cc8058a ./src/api.rs
|
||||
ca21f6d5a407e2e3532899d53ba4e8682c2fe961a46d8cd870ddc459721513ef ./src/api/device_groups.rs
|
||||
8f92909dc575ea6238f4a003f8bd4056c2752fa3247b9ab226e47f875c80b916 ./src/api/devices.rs
|
||||
4b5b784670ae761a6848b9a33fe3fb391bebd39d65e87936907428711e96ca44 ./src/api/events.rs
|
||||
2ef2f64b77886e2abd6b410adf8de91a2333d3ebdb8fcee8376ba566e5bba029 ./src/api/flows.rs
|
||||
ca5bc21e051222dcaf0c507e84cb3e60f52a0b612a6056847fa0aee3a4d1a672 ./src/api/flows.rs
|
||||
82c02fa135132ea3ebdb4d88dd1210dc536d22f987151d7f82595ee3d3d03440 ./src/api/gree_cloud.rs
|
||||
b61b0b4f196c5e72e47cf06ca0c14df3cf8eedbdef0242a9d2af33623663489f ./src/api/groups.rs
|
||||
b6d586bc42b166ac05648f3da7d9aeb88efde2e9eaab5642685eb4d9cf15f95a ./src/api/history.rs
|
||||
@@ -129,7 +129,7 @@ a8fe3636ac79df191e2f2c9d8c1eec1e5181e048ef8c9036cfa18f84aef1a462 ./src/db/confi
|
||||
4c20289c08b0cb9c3c4324d9dbc6a47c7dd953aff6fd1746f04b6024043d3bc5 ./src/db/device_history.rs
|
||||
7bc79d9d33726fbdd33b485c8c2e839cedbb8fe29fee872611da978ed149d6a0 ./src/db/energy_history.rs
|
||||
96aac6510ddda8c18fd7e29802a937b96e8a74badf504980846c80cd7933a4c6 ./src/db/events_tokens.rs
|
||||
bb5048ee60053f39ce3c1203dc1f24c7047310538d2357f105a052c016d21ec4 ./src/db/flows.rs
|
||||
bba6e3fb9f4800ae06d0ca67772b06a8c389d308243306719442d0c1f2623420 ./src/db/flows.rs
|
||||
5f4b567c6bc866d0674b3b0c66f4aeccf561774623f575b872ff25bf015b91c2 ./src/db/ha_history.rs
|
||||
4fc007c08304e96818ad1d78997a5b2458e31aa9c48ad68b5be2a007533924bb ./src/db/network_history.rs
|
||||
d6b9b70a04c774173c97f7426487d5e2932d7713e12ae3fb7f64cf2b4e0b3c10 ./src/db/schedules_automations.rs
|
||||
@@ -199,7 +199,7 @@ f13f1be3d5789539eba3fc1b59c14835f8c681eca7634e118fa3763a53aeee2b ./src/queries/
|
||||
8c34b70fb5f80e3249c2e0c63686e60ce1697cf44a7e6f11b3ad6f689ce17792 ./src/state.rs
|
||||
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
|
||||
544a31cb2e5374227026afd6abd0d910381f3fca1638154ebd74e3c3d96a35d1 ./web/404.html
|
||||
0b783d6cea599e53fed4ed82fa95e2048c493057c6d57b0dfa423e7cfa10a7ad ./web/css/styles.css
|
||||
1043dbcfb24a474ce088acb6aa86479e84db74688fb4802febeb3f3f564ca65f ./web/css/styles.css
|
||||
183d122ba366303d9952ac0e68aff4bced4e0d9a468fbe51316c13f15227402b ./web/custom-chart.html
|
||||
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
|
||||
cc1509fd76f0b0bd4c069db63e1e104210c2bed05f32a3eebac7642f94ef8a99 ./web/index.html
|
||||
@@ -210,7 +210,7 @@ e74bb06dba646f298a4d118b5c2dca6ba24f1179ce8ea8d53c1acd6000ca332c ./web/js-dynam
|
||||
752b3c3b59558b42d867d4a15329238f0213f354b76f7011c10e31b4e6dbb311 ./web/js-dynamic/dashboard.js
|
||||
6b166a9311e30fe948bd3236189436eea769c238f963ba00acec5cdb51ff1dd7 ./web/js-dynamic/entities.js
|
||||
b4204241eecf387256364f4230b23ed50d947a362f4ceb1e528d69494e93d7cf ./web/js-dynamic/events.js
|
||||
8e0f294ceeec1d258edb56271469a8a05381e6443a475f76fbeded6637d17503 ./web/js-dynamic/flows.js
|
||||
879a92e95575ace4775b2f9a7f189e78ad0082ca5c12d012ef456e6477010886 ./web/js-dynamic/flows.js
|
||||
3ae714de6b71c068ba665c81a7689f994b1716284e8d937db669ad500f12cf63 ./web/js-dynamic/forms.js
|
||||
92d013bb5d64f64aa8c55380704540a5f72732bde7ccd16554965e401daf50ea ./web/js-dynamic/history.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.
|
||||
|
||||
**Version: 0.15.7**
|
||||
**Version: 0.15.8**
|
||||
|
||||
Version 0.15.7 fixes mixed V1/V2 Local discovery: scan-envelope encryption is no longer mistaken for the device protocol, Auto binding verifies the real protocol, and V2-capable modules advertised through legacy discovery are classified correctly from their discovery metadata.
|
||||
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.
|
||||
|
||||
> [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`.
|
||||
@@ -271,7 +271,7 @@ Schedules select profiles or a custom target for selected ISO weekdays (`1=Monda
|
||||
|
||||
The reusable Flow preset library is stored as standalone JSON files in `presets/`. Each file contains its name, description and complete Flow graph, while the UI loads the embedded preset catalog dynamically instead of hard-coding graphs in JavaScript. The library has category tabs, search, local favorites/recent presets, a graph preview and requirement checks before applying a preset.
|
||||
|
||||
Available blocks include weekday/time/date ranges, the application Night mode, outdoor/device/zone temperature, house/device/zone/group state, arbitrary Home Assistant state/numeric/attribute/availability checks, a constant diagnostic source, AND/OR/NOT, thermostat actions, direct GREE actions and group actions. The GREE action uses the existing `DeviceCommand` fields (power, mode, target, fan, swing, quiet, turbo, light, air, xfan, health and sleep). Multiple branches can feed an action; the compiled runtime preserves the graph logic.
|
||||
Available blocks include weekday/time/date ranges, the application Night mode, outdoor/device/zone temperature, house/device/zone/group state, arbitrary Home Assistant state/numeric/attribute/availability checks, reusable Shared Inputs, a constant diagnostic source, AND/OR/NOT, thermostat actions, direct GREE actions and group actions. Flow exports keep the existing format version and carry definitions of referenced Shared Inputs, so import can create missing sources or safely remap ID collisions. The GREE action uses the existing `DeviceCommand` fields (power, mode, target, fan, swing, quiet, turbo, light, air, xfan, health and sleep). Multiple branches can feed an action; the compiled runtime preserves the graph logic. The editor keeps the Interpretation / Flow cycle details collapsed until requested.
|
||||
|
||||
Quick preset/setpoint overrides normally hand control back at the next schedule boundary. Temporary Quick Thermostat adds explicit start/finish rules such as duration, exact time, temperature reached/stable or next schedule boundary.
|
||||
|
||||
|
||||
+15
-15
@@ -1,13 +1,13 @@
|
||||
de0793949cf01d27d903653226ecc9b5f72523d8711f20bcab022cf42c4c4ed0 ./.env.example
|
||||
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
|
||||
bc531fc3bd260aa91bc4b3f000768d740222f6f80cd780cf2bbd4522d82d8841 ./Cargo.lock
|
||||
8555c5b50711059f76fdbaae495a6a56cae16813af2efd7c69a7fde34762949d ./Cargo.toml
|
||||
ddda584a029e67d057e3afbe93246795e6579d7efd2f3e9ebe4cfd2bb164e907 ./Cargo.lock
|
||||
d09e3c8438adcfac6aa9d16de9aade898a1fa6f64f7edf17bcd70afb714db12f ./Cargo.toml
|
||||
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
||||
4d648f23faf4e57d2900f898ce5416dd0b2be9b842f18e67e8f6b31312c511dc ./README.md
|
||||
5b5586d2e3e7b80c0ffe2d241517f8f7a8c4d7e80169a6cb3cf46221024f54e9 ./README.md
|
||||
29e9821e5ed216ea3565dd6d8bc0b174ed3cf9fa72c4ccebbfa99e98db3ad8f9 ./build.rs
|
||||
fe1bd7a84b16782f9ba2c3c301592b52b194698d7e71f0ba806a88ed3c5a9688 ./docs/API.md
|
||||
a881ba0808dd3f189d92038f6163c39d6b6208f6dbff5c32c0ea1a2628f3fbb4 ./docs/FLOW.md
|
||||
f70388d4fd9a5b35c1e32c43a9a4b4599adc6bd458e388ac571684a9cb1dc20d ./docs/openapi.json
|
||||
70a8217b46a12ae47e6b461f6f619fa19facb0b422fd8d850208b55237abc752 ./docs/API.md
|
||||
c6b1aded8ef6f4c840a2b1f2ffa3f0b22eddc080a4750adb34ee950f12f64382 ./docs/FLOW.md
|
||||
9debc0d7d3f663c2a46ab17fd385a37aa732757e6bb7c75d6d60281ea89092b5 ./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
|
||||
396c1e2aa1dce367bd1b773ff2e9b80e09729ca58900dd5141085bf30a47f8f0 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json
|
||||
00fc79f5195f43cd8d076b3d9a7a782263f0bde2a6b58b310c2d7a8b307a9f88 ./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
|
||||
861a1df8677b0539720978aa0e84e7ffc7dea96d37e6d99c6adca2ced0851a44 ./ha-addon/repository/gree-controller/CHANGELOG.md
|
||||
2976822c9663e505d22297e2749192b4ca119bb4e46ccc1c29c813522c84b3c5 ./ha-addon/repository/gree-controller/CHANGELOG.md
|
||||
50b8ba050c6b4919e4d057f7b38c1e23d3913b5b7c87552626452e9695d37251 ./ha-addon/repository/gree-controller/DOCS.md
|
||||
e3834bf712d4cd2acc715d8c85db96dd6e2fb3fbb758dac537f6034c337b3745 ./ha-addon/repository/gree-controller/README.md
|
||||
40d18f2415fb84fe83d79ec19a9c9ca20232d751e02e5713786bac4d38f715a1 ./ha-addon/repository/gree-controller/config.yaml
|
||||
5f0bd0d1ea4aca47844f9e870e4ba1676ad8b90369577db75a1bb0d00228a9da ./ha-addon/repository/gree-controller/README.md
|
||||
f43723e7e356c7cc637b36b5ade0e34cfb69c50c4cf653cca7fc013e926cd7c9 ./ha-addon/repository/gree-controller/config.yaml
|
||||
3aae6cdae4c3aaab7786a9575e7093b7e2c5b69288167075f4b7475611691a6b ./ha-addon/repository/gree-controller/topologia-ha-vlan-gree-pl.png
|
||||
0d0b42a7639b128946eec379175436fa1f150294f39d2dfd169077bfa87fd14d ./ha-addon/repository/gree-controller/topology-ha-vlan-gree-en.png
|
||||
2272740b185e9520e948f74b8f45cf98b8b5fd28c93bdee760da626f91fe5c2a ./ha-addon/repository/gree-controller/translations/en.yaml
|
||||
@@ -84,7 +84,7 @@ fdcd9a5055d08037278b842e7ab69265345c5811f0a06867136c511d140bb191 ./presets/wind
|
||||
804f22123cd3e8db0fac791826c8dd9f758fb866c8e3b5655fb6d25d259dccf1 ./presets/workday_comfort.json
|
||||
fd7390d64a2378352c09382accf6efff43903d952d111b22a982f06c5b2a3b4b ./regenerate-sha.sh
|
||||
c64b1c6deeb24f20af662bfce7fa3683703914987241107ce664fa2f6d1a9dcb ./scripts/README.md
|
||||
9083c4a8c602698a53d926864f2fe7e8c9658379f3d82fcb4171f44ec946a7ad ./scripts/api_dev_test.py
|
||||
3a3287a4ca516f91a1b1bf14257aeef7b1b3027d592200c0a6da1cc70afa9c32 ./scripts/api_dev_test.py
|
||||
5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh
|
||||
6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh
|
||||
1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh
|
||||
@@ -106,7 +106,7 @@ b53e1afab342634fedc218ea4517446f19a5ba7985dee599e191b37d9cc8058a ./src/api.rs
|
||||
ca21f6d5a407e2e3532899d53ba4e8682c2fe961a46d8cd870ddc459721513ef ./src/api/device_groups.rs
|
||||
8f92909dc575ea6238f4a003f8bd4056c2752fa3247b9ab226e47f875c80b916 ./src/api/devices.rs
|
||||
4b5b784670ae761a6848b9a33fe3fb391bebd39d65e87936907428711e96ca44 ./src/api/events.rs
|
||||
2ef2f64b77886e2abd6b410adf8de91a2333d3ebdb8fcee8376ba566e5bba029 ./src/api/flows.rs
|
||||
ca5bc21e051222dcaf0c507e84cb3e60f52a0b612a6056847fa0aee3a4d1a672 ./src/api/flows.rs
|
||||
82c02fa135132ea3ebdb4d88dd1210dc536d22f987151d7f82595ee3d3d03440 ./src/api/gree_cloud.rs
|
||||
b61b0b4f196c5e72e47cf06ca0c14df3cf8eedbdef0242a9d2af33623663489f ./src/api/groups.rs
|
||||
b6d586bc42b166ac05648f3da7d9aeb88efde2e9eaab5642685eb4d9cf15f95a ./src/api/history.rs
|
||||
@@ -128,7 +128,7 @@ a8fe3636ac79df191e2f2c9d8c1eec1e5181e048ef8c9036cfa18f84aef1a462 ./src/db/confi
|
||||
4c20289c08b0cb9c3c4324d9dbc6a47c7dd953aff6fd1746f04b6024043d3bc5 ./src/db/device_history.rs
|
||||
7bc79d9d33726fbdd33b485c8c2e839cedbb8fe29fee872611da978ed149d6a0 ./src/db/energy_history.rs
|
||||
96aac6510ddda8c18fd7e29802a937b96e8a74badf504980846c80cd7933a4c6 ./src/db/events_tokens.rs
|
||||
bb5048ee60053f39ce3c1203dc1f24c7047310538d2357f105a052c016d21ec4 ./src/db/flows.rs
|
||||
bba6e3fb9f4800ae06d0ca67772b06a8c389d308243306719442d0c1f2623420 ./src/db/flows.rs
|
||||
5f4b567c6bc866d0674b3b0c66f4aeccf561774623f575b872ff25bf015b91c2 ./src/db/ha_history.rs
|
||||
4fc007c08304e96818ad1d78997a5b2458e31aa9c48ad68b5be2a007533924bb ./src/db/network_history.rs
|
||||
d6b9b70a04c774173c97f7426487d5e2932d7713e12ae3fb7f64cf2b4e0b3c10 ./src/db/schedules_automations.rs
|
||||
@@ -198,7 +198,7 @@ f13f1be3d5789539eba3fc1b59c14835f8c681eca7634e118fa3763a53aeee2b ./src/queries/
|
||||
8c34b70fb5f80e3249c2e0c63686e60ce1697cf44a7e6f11b3ad6f689ce17792 ./src/state.rs
|
||||
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
|
||||
544a31cb2e5374227026afd6abd0d910381f3fca1638154ebd74e3c3d96a35d1 ./web/404.html
|
||||
0b783d6cea599e53fed4ed82fa95e2048c493057c6d57b0dfa423e7cfa10a7ad ./web/css/styles.css
|
||||
1043dbcfb24a474ce088acb6aa86479e84db74688fb4802febeb3f3f564ca65f ./web/css/styles.css
|
||||
183d122ba366303d9952ac0e68aff4bced4e0d9a468fbe51316c13f15227402b ./web/custom-chart.html
|
||||
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
|
||||
cc1509fd76f0b0bd4c069db63e1e104210c2bed05f32a3eebac7642f94ef8a99 ./web/index.html
|
||||
@@ -209,7 +209,7 @@ e74bb06dba646f298a4d118b5c2dca6ba24f1179ce8ea8d53c1acd6000ca332c ./web/js-dynam
|
||||
752b3c3b59558b42d867d4a15329238f0213f354b76f7011c10e31b4e6dbb311 ./web/js-dynamic/dashboard.js
|
||||
6b166a9311e30fe948bd3236189436eea769c238f963ba00acec5cdb51ff1dd7 ./web/js-dynamic/entities.js
|
||||
b4204241eecf387256364f4230b23ed50d947a362f4ceb1e528d69494e93d7cf ./web/js-dynamic/events.js
|
||||
8e0f294ceeec1d258edb56271469a8a05381e6443a475f76fbeded6637d17503 ./web/js-dynamic/flows.js
|
||||
879a92e95575ace4775b2f9a7f189e78ad0082ca5c12d012ef456e6477010886 ./web/js-dynamic/flows.js
|
||||
3ae714de6b71c068ba665c81a7689f994b1716284e8d937db669ad500f12cf63 ./web/js-dynamic/forms.js
|
||||
92d013bb5d64f64aa8c55380704540a5f72732bde7ccd16554965e401daf50ea ./web/js-dynamic/history.js
|
||||
511c4e7cc4c48fbbdab64558a9d1c27e6759c9b6dca255d11f301b9708e955c7 ./web/js-dynamic/main.js
|
||||
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
# GREE Controller API reference
|
||||
|
||||
HTTP and WebSocket API for GREE Controller **0.15.7**.
|
||||
HTTP and WebSocket API for GREE Controller **0.15.8**.
|
||||
|
||||
[← Main documentation](../README.md)
|
||||
|
||||
@@ -241,7 +241,7 @@ Response:
|
||||
{
|
||||
"status": "ok",
|
||||
"name": "gree-controller",
|
||||
"version": "0.15.7",
|
||||
"version": "0.15.8",
|
||||
"uptime_seconds": 1234,
|
||||
"control_ready": true,
|
||||
"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_revision": 42,
|
||||
"system": {
|
||||
"version": "0.15.7",
|
||||
"version": "0.15.8",
|
||||
"uptime_seconds": 1234,
|
||||
"auth_required": false,
|
||||
"control_ready": true,
|
||||
@@ -1711,9 +1711,9 @@ Cancellation suppresses the same pending intent until a new explicit thermostat/
|
||||
|
||||
### Flow portability and diagnostics
|
||||
|
||||
`GET /api/flows/:id/export` returns a versioned `gree-controller-flow` JSON document containing only the source graph. Generated schedules/automations are not exported.
|
||||
`GET /api/flows/:id/export` returns a versioned `gree-controller-flow` JSON document containing the source graph plus definitions of every referenced Shared Input. Generated schedules/automations are not exported. The existing envelope may include an optional top-level `shared_inputs` array while leaving the `flow` object unchanged.
|
||||
|
||||
`POST /api/flows/import` accepts either that envelope or a direct Flow source payload, creates a new Flow ID/revision, and preserves draft state. Executable imports are validated and recompiled; draft imports remain disabled with no generated outputs.
|
||||
`POST /api/flows/import` accepts either that envelope or a direct Flow source payload, creates a new Flow ID/revision, and preserves draft state. Executable imports are validated and recompiled; draft imports remain disabled with no generated outputs. When the envelope contains `shared_inputs`, the importer creates missing Shared Inputs automatically. If an imported Shared Input ID collides with a different local definition, an equivalent local source is reused when available; otherwise a new `shared-<uuid>` ID is created and the imported Flow node is remapped. The Flow and newly created Shared Inputs are saved in one database transaction.
|
||||
|
||||
`POST /api/flows/simulate` accepts `{ flow, flow_id?, at?, overrides?, log? }`. `at` is RFC3339. `overrides` maps Flow node IDs to simulated values. The endpoint validates and compiles the graph, evaluates every action and returns per-node traces plus `matched`, `would_execute` and `blocked_reason` (for example disabled zone/device, manual/local/temporary thermostat ownership, thermostat-output conflict or disabled Flow). It never mutates thermostat/device/group/schedule/automation state. Home Assistant read/attribute/parse failures evaluate safely as false and are visible in the trace instead of accidentally satisfying `NOT`/`neq` logic.
|
||||
|
||||
|
||||
+2
-2
@@ -54,7 +54,7 @@ The Flow editor exposes:
|
||||
- per-node trace,
|
||||
- `would_execute` and ownership/block reason,
|
||||
- Flow-scoped execution/dry-run logs,
|
||||
- import/export of versioned `.flow.json` source graphs.
|
||||
- import/export of versioned `.flow.json` source graphs. Flow exports include the definitions of referenced Shared Inputs; import creates missing inputs automatically and remaps ID collisions without overwriting a different local input.
|
||||
|
||||
Dry-run never mutates thermostats, devices, groups, schedules or automations. For an existing Flow it clones persisted stateful runtime data, so timers, last-observed values, rate-limit history and rolling/oscillation windows are evaluated against the same saved state without writing it back.
|
||||
|
||||
@@ -68,7 +68,7 @@ Actions include thermostat zone, full GREE device command, single GREE function,
|
||||
|
||||
The editor ships 37 editable presets grouped into seven categories: Comfort, Energy & cost, Protection & safety, Night, Reliability, Home Assistant / heat sources and Advanced. The library UI also provides search, Favorites, Recent, a mini graph preview and requirement checks before applying a preset. The library includes workday/weekend comfort, weather-aware demand, morning boost, presence and energy-price eco modes, peak-power limiting, open-window/humidity/overheat/frost guards, night profiles, sensor/device resilience, dual heating/cooling thresholds, multi-room group control, nested AND/OR/NOT Home Assistant scenarios, and gas-boiler/external-heat-source coordination including off, reduced-target, boost and fallback heating patterns.
|
||||
|
||||
Shared Flow inputs show the Flows that reference them and link directly to those editors. Home Assistant-backed shared inputs can be tested live in the input modal. Shared inputs report the current raw value/attribute. Each Flow reference defines its own comparison without changing the shared source. Home Assistant temperature entities remain normal HA entities: room-temperature sensors are configured per zone, while outdoor temperature has one global `outdoor_entity_id` plus an optional per-zone `ha_outdoor_entity_id` override. Their aliases are shared with the rest of the UI, outdoor overrides are recorded in sensor metrics/history, and all configured entities are offered as suggestions in Home Assistant Flow blocks; selecting a zone sensor does not create a separate shared input.
|
||||
Shared Flow inputs show the Flows that reference them and link directly to those editors. The Flow editor keeps the natural-language Interpretation / Flow cycle panel collapsed by default on every viewport and exposes it through a disclosure button. Home Assistant-backed shared inputs can be tested live in the input modal. Shared inputs report the current raw value/attribute. Each Flow reference defines its own comparison without changing the shared source. Home Assistant temperature entities remain normal HA entities: room-temperature sensors are configured per zone, while outdoor temperature has one global `outdoor_entity_id` plus an optional per-zone `ha_outdoor_entity_id` override. Their aliases are shared with the rest of the UI, outdoor overrides are recorded in sensor metrics/history, and all configured entities are offered as suggestions in Home Assistant Flow blocks; selecting a zone sensor does not create a separate shared input.
|
||||
|
||||
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`.
|
||||
|
||||
|
||||
+39
-4
@@ -2,7 +2,7 @@
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "GREE Controller API",
|
||||
"version": "0.15.7",
|
||||
"version": "0.15.8",
|
||||
"summary": "Local HTTP/WebSocket API for GREE Controller",
|
||||
"description": "Local API used by the GREE Controller Web UI and Home Assistant integration.",
|
||||
"license": {
|
||||
@@ -2635,7 +2635,7 @@
|
||||
"Flows"
|
||||
],
|
||||
"summary": "Export Flow source",
|
||||
"description": "Exports a versioned `gree-controller-flow` document containing only the source graph, never generated schedules/automations.",
|
||||
"description": "Exports a versioned `gree-controller-flow` document containing the source graph plus definitions of referenced Shared Inputs, never generated schedules/automations.",
|
||||
"operationId": "exportFlow",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -2686,7 +2686,7 @@
|
||||
"Flows"
|
||||
],
|
||||
"summary": "Import Flow source",
|
||||
"description": "Imports either a `gree-controller-flow` envelope or a direct Flow source payload. A new Flow ID/revision is created; executable imports are validated and recompiled, drafts remain disabled.",
|
||||
"description": "Imports either a `gree-controller-flow` envelope or a direct Flow source payload. Flow envelopes may include Shared Input definitions; missing inputs are created automatically and conflicting IDs are safely remapped. A new Flow ID/revision is created; executable imports are validated and recompiled, drafts remain disabled.",
|
||||
"operationId": "importFlow",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
@@ -5967,7 +5967,7 @@
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"example": "0.15.7"
|
||||
"example": "0.15.8"
|
||||
},
|
||||
"uptime_seconds": {
|
||||
"type": "integer",
|
||||
@@ -8026,6 +8026,41 @@
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"shared_inputs": {
|
||||
"type": "array",
|
||||
"description": "Portable definitions for Shared Inputs referenced by the Flow. Import creates missing inputs and safely remaps ID collisions.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"example": "shared-7bdc11d1-2d98-48c2-ba9f-ed2f13cd867b"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"example": "Room humidity"
|
||||
},
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"example": "ha_numeric"
|
||||
},
|
||||
"config": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"example": {
|
||||
"entity_id": "sensor.room_humidity"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"kind",
|
||||
"config"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"flow": {
|
||||
"$ref": "#/components/schemas/FlowInput"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"domain": "gree_controller",
|
||||
"name": "GREE Controller",
|
||||
"version": "0.15.7",
|
||||
"version": "0.15.8",
|
||||
"config_flow": true,
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# Changelog
|
||||
|
||||
## 0.15.8
|
||||
|
||||
- Visual Flow exports now include the Shared Input definitions referenced by the graph.
|
||||
- Flow import creates missing Shared Inputs automatically, reuses equivalent local sources and safely remaps conflicting IDs without overwriting local definitions; Flow and imported inputs are persisted atomically without changing the Flow format version.
|
||||
- Flow export/import remains on format version 1; exported graphs may carry the Shared Input definitions they reference.
|
||||
- The Flow editor keeps the Interpretation / Flow cycle section collapsed by default on desktop, tablet and mobile, with details available through the existing disclosure button.
|
||||
|
||||
## 0.15.7
|
||||
|
||||
- Fixes mixed V1/V2 Local discovery: legacy/plain discovery envelopes are no longer treated as proof that a unit is V1.
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
For VLAN deployments, configure VLAN interfaces on the Home Assistant OS host. The add-on uses `host_network: true`; with multiple directly attached subnets, `gree_interface` may remain empty. Run broadcast discovery separately for each VLAN/subnet.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: "GREE Controller"
|
||||
version: "0.15.7"
|
||||
version: "0.15.8"
|
||||
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/"
|
||||
|
||||
+10
-4
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Developer smoke/integration tests for GREE Controller API 0.15.7.
|
||||
"""Developer smoke/integration tests for GREE Controller API 0.15.8.
|
||||
|
||||
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.7"
|
||||
DEFAULT_EXPECTED_VERSION = "0.15.8"
|
||||
|
||||
SETTINGS_PATHS: dict[str, set[str]] = {
|
||||
"/api/settings/application": {"simulator_enabled"},
|
||||
@@ -534,7 +534,13 @@ def test_flow_read_subresources(client: ApiClient) -> str:
|
||||
flow_id = urllib.parse.quote(item["id"], safe="")
|
||||
export_resp = client.get(f"/api/flows/{flow_id}/export")
|
||||
assert_status(export_resp, 200)
|
||||
export_resp.json()
|
||||
exported = assert_json_object(export_resp)
|
||||
if exported.get("format") != "gree-controller-flow":
|
||||
raise AssertionError(f"unexpected Flow export format: {exported.get('format')!r}")
|
||||
if exported.get("version") != 1:
|
||||
raise AssertionError(f"unexpected Flow export version: {exported.get('version')!r}")
|
||||
if "shared_inputs" in exported and not isinstance(exported["shared_inputs"], list):
|
||||
raise AssertionError("Flow export shared_inputs must be an array")
|
||||
logs_resp = client.get(f"/api/flows/{flow_id}/logs")
|
||||
assert_status(logs_resp, 200)
|
||||
logs_resp.json()
|
||||
@@ -593,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.7 contract", lambda: test_openapi(client, args.expected_version))
|
||||
runner.run("OpenAPI 0.15.8 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))
|
||||
|
||||
|
||||
+200
-28
@@ -53,6 +53,109 @@ fn flow_u8(config: &Value, key: &str) -> Option<u8> {
|
||||
fn flow_bool(config: &Value, key: &str) -> Option<bool> {
|
||||
config.get(key).and_then(Value::as_bool)
|
||||
}
|
||||
|
||||
fn flow_referenced_shared_input_ids(
|
||||
nodes: &[crate::models::FlowNode],
|
||||
) -> std::collections::HashSet<String> {
|
||||
nodes
|
||||
.iter()
|
||||
.filter(|node| node.kind == "shared_input")
|
||||
.filter_map(|node| flow_string(&node.config, "input_id"))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn same_shared_input_source(
|
||||
left: &crate::models::FlowSharedInput,
|
||||
right: &crate::models::FlowSharedInput,
|
||||
) -> bool {
|
||||
left.kind == right.kind && left.config == right.config
|
||||
}
|
||||
|
||||
fn prepare_imported_shared_inputs(
|
||||
input: &mut FlowInput,
|
||||
imported: Vec<crate::models::FlowSharedInput>,
|
||||
current: &[crate::models::FlowSharedInput],
|
||||
) -> Result<(Vec<crate::models::FlowSharedInput>, bool), AppError> {
|
||||
let mut imported_ids = std::collections::HashSet::new();
|
||||
let mut normalized_imported = Vec::with_capacity(imported.len());
|
||||
for mut item in imported {
|
||||
item.id = item.id.trim().chars().take(120).collect();
|
||||
item.name = item.name.trim().chars().take(100).collect();
|
||||
item.kind = item.kind.trim().to_string();
|
||||
if item.id.is_empty() || item.name.is_empty() {
|
||||
return Err(AppError::BadRequest(
|
||||
"imported shared Flow input requires id and name".into(),
|
||||
));
|
||||
}
|
||||
if !imported_ids.insert(item.id.clone()) {
|
||||
return Err(AppError::BadRequest(
|
||||
"imported shared Flow input IDs must be unique".into(),
|
||||
));
|
||||
}
|
||||
normalized_imported.push(item);
|
||||
}
|
||||
|
||||
let referenced_import_ids = flow_referenced_shared_input_ids(&input.nodes);
|
||||
let mut next = current.to_vec();
|
||||
let mut remap = std::collections::HashMap::<String, String>::new();
|
||||
let mut changed = false;
|
||||
|
||||
for item in normalized_imported
|
||||
.into_iter()
|
||||
.filter(|item| referenced_import_ids.contains(&item.id))
|
||||
{
|
||||
if let Some(existing) = next.iter().find(|existing| existing.id == item.id) {
|
||||
if same_shared_input_source(existing, &item) {
|
||||
remap.insert(item.id.clone(), existing.id.clone());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(existing) = next
|
||||
.iter()
|
||||
.find(|existing| same_shared_input_source(existing, &item))
|
||||
{
|
||||
remap.insert(item.id.clone(), existing.id.clone());
|
||||
continue;
|
||||
}
|
||||
|
||||
let original_id = item.id.clone();
|
||||
let mut to_add = item;
|
||||
if next.iter().any(|existing| existing.id == to_add.id) {
|
||||
to_add.id = format!("shared-{}", Uuid::new_v4());
|
||||
}
|
||||
remap.insert(original_id, to_add.id.clone());
|
||||
next.push(to_add);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
for node in &mut input.nodes {
|
||||
if node.kind != "shared_input" {
|
||||
continue;
|
||||
}
|
||||
let Some(input_id) = flow_string(&node.config, "input_id") else {
|
||||
continue;
|
||||
};
|
||||
if let Some(mapped) = remap.get(&input_id) {
|
||||
if mapped != &input_id {
|
||||
let config = node.config.as_object_mut().ok_or_else(|| {
|
||||
AppError::BadRequest("shared Flow input block config must be an object".into())
|
||||
})?;
|
||||
config.insert("input_id".into(), Value::String(mapped.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for input_id in flow_referenced_shared_input_ids(&input.nodes) {
|
||||
if !next.iter().any(|item| item.id == input_id) {
|
||||
return Err(AppError::BadRequest(format!(
|
||||
"shared Flow input block references missing input {input_id}; export the Flow again with shared input definitions or create the input first"
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok((next, changed))
|
||||
}
|
||||
fn flow_louver_position(config: &Value, key: &str) -> Option<u8> {
|
||||
match config.get(key) {
|
||||
Some(Value::Bool(value)) => Some(u8::from(*value)),
|
||||
@@ -604,6 +707,7 @@ fn validate_text_comparison(config: &Value) -> Result<(), AppError> {
|
||||
fn validate_condition(
|
||||
condition: &crate::models::FlowCondition,
|
||||
state: &AppState,
|
||||
shared_inputs_override: Option<&[crate::models::FlowSharedInput]>,
|
||||
) -> Result<(), AppError> {
|
||||
match condition.kind.as_str() {
|
||||
"weekday" => {
|
||||
@@ -1032,20 +1136,25 @@ fn validate_condition(
|
||||
let input_id = flow_string(&condition.config, "input_id").ok_or_else(|| {
|
||||
AppError::BadRequest("shared Flow input block needs input_id".into())
|
||||
})?;
|
||||
let settings = state
|
||||
.db
|
||||
.load_runtime_settings()?
|
||||
.ok_or_else(|| AppError::BadRequest("runtime settings are unavailable".into()))?;
|
||||
let shared = settings
|
||||
.home_assistant
|
||||
.flow_inputs
|
||||
.iter()
|
||||
.find(|item| item.id == input_id)
|
||||
.ok_or_else(|| {
|
||||
AppError::BadRequest(
|
||||
"shared Flow input block references a missing input".into(),
|
||||
)
|
||||
})?;
|
||||
let shared = if let Some(inputs) = shared_inputs_override {
|
||||
inputs.iter().find(|item| item.id == input_id).cloned()
|
||||
} else {
|
||||
state
|
||||
.db
|
||||
.load_runtime_settings()?
|
||||
.and_then(|settings| {
|
||||
settings
|
||||
.home_assistant
|
||||
.flow_inputs
|
||||
.into_iter()
|
||||
.find(|item| item.id == input_id)
|
||||
})
|
||||
}
|
||||
.ok_or_else(|| {
|
||||
AppError::BadRequest(format!(
|
||||
"shared Flow input block references missing input {input_id}"
|
||||
))
|
||||
})?;
|
||||
validate_shared_input_source(&shared.kind, &shared.config, state)?;
|
||||
if shared_input_comparison_kind(&shared.kind) {
|
||||
let operator = flow_string(&condition.config, "operator").ok_or_else(|| {
|
||||
@@ -1072,7 +1181,7 @@ fn validate_condition(
|
||||
config,
|
||||
inputs: Vec::new(),
|
||||
};
|
||||
validate_condition(&resolved, state)?;
|
||||
validate_condition(&resolved, state, shared_inputs_override)?;
|
||||
} else if flow_string(&condition.config, "operator").is_some()
|
||||
|| condition.config.get("value").is_some()
|
||||
{
|
||||
@@ -1104,6 +1213,7 @@ fn validate_flow_comparison(config: &Value) -> Result<(), AppError> {
|
||||
fn compile_flow(
|
||||
state: &AppState,
|
||||
mut flow: crate::models::Flow,
|
||||
shared_inputs_override: Option<&[crate::models::FlowSharedInput]>,
|
||||
) -> Result<(crate::models::Flow, Vec<Schedule>, Vec<Automation>), AppError> {
|
||||
let mut schedules = Vec::new();
|
||||
let mut automations = Vec::new();
|
||||
@@ -1137,7 +1247,7 @@ fn compile_flow(
|
||||
.iter()
|
||||
.filter(|condition| flow_condition_kind(&condition.kind))
|
||||
{
|
||||
validate_condition(condition, state)?;
|
||||
validate_condition(condition, state, shared_inputs_override)?;
|
||||
}
|
||||
let schedule_leaves: Vec<_> = conditions
|
||||
.iter()
|
||||
@@ -1572,7 +1682,7 @@ async fn create_flow(
|
||||
let (flow, schedules, automations) = if flow.draft {
|
||||
(prepare_draft_flow(flow), vec![], vec![])
|
||||
} else {
|
||||
compile_flow(&state, flow)?
|
||||
compile_flow(&state, flow, None)?
|
||||
};
|
||||
state
|
||||
.db
|
||||
@@ -1629,7 +1739,7 @@ async fn update_flow(
|
||||
let (flow, schedules, automations) = if flow.draft {
|
||||
(prepare_draft_flow(flow), vec![], vec![])
|
||||
} else {
|
||||
compile_flow(&state, flow)?
|
||||
compile_flow(&state, flow, None)?
|
||||
};
|
||||
state
|
||||
.db
|
||||
@@ -1689,10 +1799,22 @@ async fn export_flow(
|
||||
.db
|
||||
.get_flow(&id)?
|
||||
.ok_or_else(|| AppError::NotFound(format!("flow {id}")))?;
|
||||
let referenced = flow_referenced_shared_input_ids(&flow.nodes);
|
||||
let shared_inputs: Vec<_> = state
|
||||
.settings
|
||||
.read()
|
||||
.await
|
||||
.home_assistant
|
||||
.flow_inputs
|
||||
.iter()
|
||||
.filter(|item| referenced.contains(&item.id))
|
||||
.cloned()
|
||||
.collect();
|
||||
Ok(Json(json!({
|
||||
"format": "gree-controller-flow",
|
||||
"version": 1,
|
||||
"exported_at": Utc::now(),
|
||||
"shared_inputs": shared_inputs,
|
||||
"flow": {
|
||||
"name": flow.name,
|
||||
"enabled": flow.enabled,
|
||||
@@ -1715,28 +1837,74 @@ async fn import_flow(
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(version) = document.get("version").and_then(Value::as_u64) {
|
||||
if !matches!(version, 1 | 2) {
|
||||
return Err(AppError::BadRequest(format!(
|
||||
"unsupported Flow import version {version}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
let imported_shared_inputs: Vec<crate::models::FlowSharedInput> = document
|
||||
.get("shared_inputs")
|
||||
.cloned()
|
||||
.map(serde_json::from_value)
|
||||
.transpose()
|
||||
.map_err(|err| AppError::BadRequest(format!("invalid shared Flow inputs: {err}")))?
|
||||
.unwrap_or_default();
|
||||
let payload = document.get("flow").cloned().unwrap_or(document);
|
||||
let mut input: FlowInput = serde_json::from_value(payload)
|
||||
.map_err(|err| AppError::BadRequest(format!("invalid Flow import: {err}")))?;
|
||||
input.expected_revision = None;
|
||||
|
||||
let _configuration_guard = state.lock_configuration_operation().await;
|
||||
let _automation_guard = state.lock_automation_operation().await;
|
||||
let _schedule_guard = state.lock_schedule_operation().await;
|
||||
let _cycle_guard = state.lock_zone_control_cycle().await;
|
||||
|
||||
let mut next_settings = state.settings.read().await.clone();
|
||||
let (flow_inputs, shared_inputs_changed) = prepare_imported_shared_inputs(
|
||||
&mut input,
|
||||
imported_shared_inputs,
|
||||
&next_settings.home_assistant.flow_inputs,
|
||||
)?;
|
||||
next_settings.home_assistant.flow_inputs = flow_inputs;
|
||||
validate_flow_shared_inputs(&mut next_settings.home_assistant, &state)?;
|
||||
|
||||
if input.draft {
|
||||
validate_flow_draft_graph(&input)?;
|
||||
} else {
|
||||
validate_flow_graph(&input)?;
|
||||
}
|
||||
let _configuration_guard = state.lock_configuration_operation().await;
|
||||
let _automation_guard = state.lock_automation_operation().await;
|
||||
let _schedule_guard = state.lock_schedule_operation().await;
|
||||
let _cycle_guard = state.lock_zone_control_cycle().await;
|
||||
|
||||
let flow = flow_from_input(Uuid::new_v4().to_string(), input, Utc::now(), 1);
|
||||
let (flow, schedules, automations) = if flow.draft {
|
||||
(prepare_draft_flow(flow), vec![], vec![])
|
||||
} else {
|
||||
compile_flow(&state, flow)?
|
||||
compile_flow(
|
||||
&state,
|
||||
flow,
|
||||
Some(&next_settings.home_assistant.flow_inputs),
|
||||
)?
|
||||
};
|
||||
state
|
||||
.db
|
||||
.replace_flow_outputs(&flow, &schedules, &automations)?;
|
||||
|
||||
if shared_inputs_changed {
|
||||
state.db.replace_flow_outputs_with_runtime_settings(
|
||||
&flow,
|
||||
&schedules,
|
||||
&automations,
|
||||
&next_settings,
|
||||
)?;
|
||||
*state.settings.write().await = next_settings.clone();
|
||||
let payload = home_assistant_settings(&next_settings);
|
||||
state.broadcast(
|
||||
"settings.home_assistant.updated",
|
||||
serde_json::to_value(&payload)?,
|
||||
);
|
||||
} else {
|
||||
state
|
||||
.db
|
||||
.replace_flow_outputs(&flow, &schedules, &automations)?;
|
||||
}
|
||||
for zone_id in schedules
|
||||
.iter()
|
||||
.map(|s| s.zone_id.as_str())
|
||||
@@ -1748,7 +1916,11 @@ async fn import_flow(
|
||||
"info",
|
||||
"flow.imported",
|
||||
&format!("Imported Flow {}", flow.name),
|
||||
json!({"flow_id": flow.id, "revision": flow.revision}),
|
||||
json!({
|
||||
"flow_id": flow.id,
|
||||
"revision": flow.revision,
|
||||
"shared_inputs_imported": shared_inputs_changed
|
||||
}),
|
||||
);
|
||||
state.broadcast("flow.created", serde_json::to_value(&flow)?);
|
||||
state.wake_zone_control();
|
||||
@@ -1896,7 +2068,7 @@ async fn simulate_flow(
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
};
|
||||
let (compiled, schedules, automations) = compile_flow(&state, preview)?;
|
||||
let (compiled, schedules, automations) = compile_flow(&state, preview, None)?;
|
||||
let devices = state.db.list_devices()?;
|
||||
let mut actions = Vec::new();
|
||||
for action in input
|
||||
|
||||
@@ -45,6 +45,50 @@ impl Db {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn replace_flow_outputs_with_runtime_settings(
|
||||
&self,
|
||||
flow: &Flow,
|
||||
schedules: &[Schedule],
|
||||
automations: &[Automation],
|
||||
settings: &RuntimeSettings,
|
||||
) -> Result<()> {
|
||||
let mut conn = self.lock()?;
|
||||
let tx = conn.transaction()?;
|
||||
tx.execute(queries::DELETE_SCHEDULES_BY_FLOW_ID, [flow.id.as_str()])?;
|
||||
tx.execute(queries::DELETE_AUTOMATIONS_BY_FLOW_ID, [flow.id.as_str()])?;
|
||||
for schedule in schedules {
|
||||
let payload = Self::to_json(schedule)?;
|
||||
tx.execute(
|
||||
queries::UPSERT_SCHEDULE,
|
||||
params![
|
||||
schedule.id,
|
||||
schedule.zone_id,
|
||||
payload,
|
||||
schedule.updated_at.to_rfc3339()
|
||||
],
|
||||
)?;
|
||||
}
|
||||
for item in automations {
|
||||
let payload = Self::to_json(item)?;
|
||||
tx.execute(
|
||||
queries::UPSERT_AUTOMATION,
|
||||
params![item.id, payload, item.updated_at.to_rfc3339()],
|
||||
)?;
|
||||
}
|
||||
let flow_payload = Self::to_json(flow)?;
|
||||
tx.execute(
|
||||
queries::UPSERT_FLOW,
|
||||
params![flow.id, flow_payload, flow.updated_at.to_rfc3339()],
|
||||
)?;
|
||||
let settings_payload = Self::to_json(settings)?;
|
||||
tx.execute(
|
||||
queries::UPSERT_RUNTIME_SETTINGS,
|
||||
params![settings_payload, Utc::now().to_rfc3339()],
|
||||
)?;
|
||||
tx.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_flow(&self, id: &str) -> Result<bool> {
|
||||
let mut conn = self.lock()?;
|
||||
let tx = conn.transaction()?;
|
||||
|
||||
+65
-1
@@ -10342,4 +10342,68 @@ select.select-native-proxy[aria-invalid="true"]+.custom-select .custom-select-tr
|
||||
.public-custom-chart-wrap {
|
||||
min-height: 140px;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Keep Flow interpretation compact on every viewport. Details are opt-in. */
|
||||
.flow-natural-preview {
|
||||
display: block;
|
||||
min-height: 0;
|
||||
max-height: none;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.flow-preview-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
padding: 8px 14px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.flow-preview-toggle strong {
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
.flow-preview-toggle-icon {
|
||||
color: var(--muted);
|
||||
font-size: 1rem;
|
||||
transition: transform .15s ease;
|
||||
}
|
||||
|
||||
.flow-preview-details {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 8px;
|
||||
max-height: 34dvh;
|
||||
padding: 9px 14px 11px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.flow-natural-preview.is-expanded .flow-preview-details {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flow-natural-preview.is-expanded .flow-preview-toggle {
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.flow-natural-preview.is-expanded .flow-preview-toggle-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.flow-natural-preview .flow-preview-runtime {
|
||||
justify-content: start;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.flow-natural-preview .flow-preview-runtime span {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
+12
-1
@@ -731,6 +731,17 @@ function flowSourcePayload() {
|
||||
};
|
||||
}
|
||||
|
||||
function flowExportSharedInputs() {
|
||||
if (!app.flowDraft) return [];
|
||||
const ids = new Set((app.flowDraft.nodes || [])
|
||||
.filter(node => node.kind === 'shared_input')
|
||||
.map(node => node.config?.input_id)
|
||||
.filter(Boolean));
|
||||
return (app.flowSharedInputs || [])
|
||||
.filter(item => ids.has(item.id))
|
||||
.map(item => JSON.parse(JSON.stringify(item)));
|
||||
}
|
||||
|
||||
function downloadJsonFile(filename, data) {
|
||||
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob); const a = document.createElement('a');
|
||||
@@ -743,7 +754,7 @@ async function exportFlow() {
|
||||
try {
|
||||
const envelope = app.flowDraft.id && !app.flowDirty
|
||||
? await api(`/api/flows/${encodeURIComponent(app.flowDraft.id)}/export`)
|
||||
: { format: 'gree-controller-flow', version: 1, exported_at: new Date().toISOString(), flow: flowSourcePayload() };
|
||||
: { format: 'gree-controller-flow', version: 1, exported_at: new Date().toISOString(), shared_inputs: flowExportSharedInputs(), flow: flowSourcePayload() };
|
||||
const safe = (flowSourcePayload()?.name || 'flow').replace(/[^a-z0-9_-]+/gi, '-').replace(/^-+|-+$/g, '').toLowerCase() || 'flow';
|
||||
downloadJsonFile(`${safe}.flow.json`, envelope); toast(tr('flow.exported'));
|
||||
} catch (error) { toast(error.message, true); }
|
||||
|
||||
Reference in New Issue
Block a user