v0.14.25
This commit is contained in:
Generated
+1
-1
@@ -700,7 +700,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gree-controller"
|
name = "gree-controller"
|
||||||
version = "0.15.0"
|
version = "0.14.25"
|
||||||
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.0"
|
version = "0.14.25"
|
||||||
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"
|
||||||
|
|||||||
+41
-41
@@ -1,39 +1,39 @@
|
|||||||
de0793949cf01d27d903653226ecc9b5f72523d8711f20bcab022cf42c4c4ed0 ./.env.example
|
de0793949cf01d27d903653226ecc9b5f72523d8711f20bcab022cf42c4c4ed0 ./.env.example
|
||||||
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
|
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
|
||||||
c3c16d0c76fbb80ce5752b28a1736177940670b10f44235eac5997c4e195c634 ./Cargo.lock
|
7fc984bbc6ee597fb9bb036f1c359f60aa8a3fd100daf9dfb41a43eb34f2da62 ./Cargo.lock
|
||||||
ad513e247ab5dbed3c91a6463cf410cf1a4ac73c0b30422bad40afb93cee0159 ./Cargo.toml
|
13d12e35a4919efad82747707c5f42c0fafe242d304e04629dde0cf159487d2f ./Cargo.toml
|
||||||
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
||||||
db052d6c978b067a31d752f293fc8c02702d50137ba7b84bce87371089972f11 ./README.md
|
db95805ae6dfd20d9c83d97fda5977672432ea8f2bce9ae023808b35f7047b3f ./README.md
|
||||||
84bae184c72909d968eeece0a092dbb4fe7a8a071927b29cc188ac6fb6a450f0 ./SOURCE_MANIFEST.sha256
|
5771a9d3361ab936c7df33999fcbd57cc0f76777ff584f714456e4afb608c979 ./SOURCE_MANIFEST.sha256
|
||||||
29e9821e5ed216ea3565dd6d8bc0b174ed3cf9fa72c4ccebbfa99e98db3ad8f9 ./build.rs
|
29e9821e5ed216ea3565dd6d8bc0b174ed3cf9fa72c4ccebbfa99e98db3ad8f9 ./build.rs
|
||||||
8a96630c36cfac5c392837c8872b20d39d72fe3ec99c7c9e09ec78f2228fd262 ./docs/API.md
|
3031d65ac45a9189a7087bc80b7ae340d906a8da8c7db35ae34e50ce299b1e09 ./docs/API.md
|
||||||
58b79297607303151c1de2da74344cd4f6277bc53d80ebe04ed4567d906c963b ./docs/FLOW.md
|
8b548d02dbdf0563f2be617a1d677459bd1959bed93a1799a01bf61bb5aed405 ./docs/FLOW.md
|
||||||
525b03af649d0215e3b81e52ce63ebf8651f47b49503c30d0177b3e0b2714ac4 ./docs/openapi.json
|
26af934fe981ac1f3f42b86d1fbe8afb2883de46dfab78a49808893c96ffde4e ./docs/openapi.json
|
||||||
c65aa543d2e99f8f7d52736307b07ebd182597d80124661d53a16bb23f6f1118 ./ha-addon/.env.example
|
c65aa543d2e99f8f7d52736307b07ebd182597d80124661d53a16bb23f6f1118 ./ha-addon/.env.example
|
||||||
0993076ca45b6d31859680835204cdc111b3f9bdbf460d50b4efec56cd5e685b ./ha-addon/Dockerfile
|
0993076ca45b6d31859680835204cdc111b3f9bdbf460d50b4efec56cd5e685b ./ha-addon/Dockerfile
|
||||||
adf7ff248d782a3b1cba82b8789c57b6849ddaaaf5fbc108af75e18b1534a885 ./ha-addon/README.md
|
adf7ff248d782a3b1cba82b8789c57b6849ddaaaf5fbc108af75e18b1534a885 ./ha-addon/README.md
|
||||||
dd9291b12afade6fc20d66e80a5513251270e18e4c265260b2bbbe393a5a94c4 ./ha-addon/build.sh
|
dd9291b12afade6fc20d66e80a5513251270e18e4c265260b2bbbe393a5a94c4 ./ha-addon/build.sh
|
||||||
4061846cec4d829e4101b4021df7f07dc2e0293ccfd11e91589f49733ec7c5b4 ./ha-addon/home-assistant/README.md
|
082dfeb6326f3e1b9841ccf7f2954117ecdf0aa1a90b0aef1e6a1960dacdecbf ./ha-addon/home-assistant/README.md
|
||||||
f8e8559fe10fe523ac5bc9aac25c6e26e862f679d502e8f3c39f38a0a8e40911 ./ha-addon/home-assistant/custom_components/gree_controller/__init__.py
|
f8e8559fe10fe523ac5bc9aac25c6e26e862f679d502e8f3c39f38a0a8e40911 ./ha-addon/home-assistant/custom_components/gree_controller/__init__.py
|
||||||
3f6ef15ef58456376ac53fde7cace1ef359d6a6f7a64c5b575ad77ea6e55ccc4 ./ha-addon/home-assistant/custom_components/gree_controller/api.py
|
3f6ef15ef58456376ac53fde7cace1ef359d6a6f7a64c5b575ad77ea6e55ccc4 ./ha-addon/home-assistant/custom_components/gree_controller/api.py
|
||||||
da2a0a9390ab75cef150cd281bc329fa409d0d28f51382b36bd8de3a73325c16 ./ha-addon/home-assistant/custom_components/gree_controller/climate.py
|
0c008329aad15f5af6328380c1afe1f49db186b37b3d1cafec4b6f4088386e8e ./ha-addon/home-assistant/custom_components/gree_controller/climate.py
|
||||||
5e4aef2143e81bedb5a15dd4be5c71b64a3ab448ec6d33a20851edd098e5f529 ./ha-addon/home-assistant/custom_components/gree_controller/config_flow.py
|
5e4aef2143e81bedb5a15dd4be5c71b64a3ab448ec6d33a20851edd098e5f529 ./ha-addon/home-assistant/custom_components/gree_controller/config_flow.py
|
||||||
b7f0873109c52be9d7f09bea3dffc416103c50085e1f0680d11661a969479898 ./ha-addon/home-assistant/custom_components/gree_controller/const.py
|
b7f0873109c52be9d7f09bea3dffc416103c50085e1f0680d11661a969479898 ./ha-addon/home-assistant/custom_components/gree_controller/const.py
|
||||||
6509286c7b7de638dcc5d40ce53181e65d1eddc5dc36a13306dc6d87220fedbb ./ha-addon/home-assistant/custom_components/gree_controller/coordinator.py
|
41a8958a6fe6d10e5679c95ac34832583a2a15ecfd67d09b4e094a83d06f5c47 ./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
|
||||||
d2a18bc3de3207b9f9025507981ad3742ddc3f3728fa223baf84c7ecbb05973e ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json
|
2c933d0774037f487e7c927af9688bb79f2ec5876f0ad4f42603430f18e54354 ./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
|
||||||
338a42662e77f91215150851e55bbd39a5c77f5612cc77f3e037c06a6e6bdadb ./ha-addon/home-assistant/custom_components/gree_controller/switch.py
|
338a42662e77f91215150851e55bbd39a5c77f5612cc77f3e037c06a6e6bdadb ./ha-addon/home-assistant/custom_components/gree_controller/switch.py
|
||||||
a745cc458c235158d5d70b6a33d8000fd8b40ff93bfe2f2796af0f4b3a9eef9b ./ha-addon/home-assistant/custom_components/gree_controller/translations/en.json
|
6bddb7b4620021ecd2099a86a77ef5c7f2c2dcd3d07d5db4e7b4c4ce6d3e8c03 ./ha-addon/home-assistant/custom_components/gree_controller/translations/en.json
|
||||||
50b7d6fe6329ad4ea6bee9612ab141c2f73f8382fcc2af0bb076a8e8d4dd4465 ./ha-addon/home-assistant/custom_components/gree_controller/translations/pl.json
|
13f30e2dcdcedbd1b6c3f99c2335e0487108fd72c8e86922368b84f2fa2038ae ./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
|
||||||
b3ee553cc7dbc68c3bb7cdd6dbb9b5ca8646df113cb499ca5bd9d3d0e380db73 ./ha-addon/repository/gree-controller/CHANGELOG.md
|
b207451e12368ac448998cf2f6a98cdf435a391f9bc5b973d89e5ff9c24a090a ./ha-addon/repository/gree-controller/CHANGELOG.md
|
||||||
1b5599195329cc3e643c23aa1caadcec80f133e8e515eec7d92b9f3cbda5b80b ./ha-addon/repository/gree-controller/DOCS.md
|
1b5599195329cc3e643c23aa1caadcec80f133e8e515eec7d92b9f3cbda5b80b ./ha-addon/repository/gree-controller/DOCS.md
|
||||||
c3db302f38d26dc9f8caf37a1a8a07a1334cc07ae301d452f2940a5ec5915719 ./ha-addon/repository/gree-controller/README.md
|
c3db302f38d26dc9f8caf37a1a8a07a1334cc07ae301d452f2940a5ec5915719 ./ha-addon/repository/gree-controller/README.md
|
||||||
f7e5325e34b2785d0d0bea657c882202d7557d313cbdc464dcdb91f702d36391 ./ha-addon/repository/gree-controller/config.yaml
|
42ab24948d58622cd9c51f2bd766058f1b4b15dd36adce8186b6e9b4d72da87c ./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
|
||||||
1bb256c32c07b8366751bbc79e8ceb284a267f3b389115263b2c67cfd73223e3 ./lang/en.json
|
744bfd0d8eeaacf4fa4ac748b2d71554727fd00e0887b5aa058482695d1b531d ./lang/en.json
|
||||||
826362354203c7f66af8acdfc367d2b10868add09b59dc5d3c756f6102692e77 ./lang/pl.json
|
491c9ee92c3a5518ebcab30abb5ce25ee71819fa96bb6239b894add888792312 ./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
|
||||||
@@ -92,7 +92,7 @@ c64b1c6deeb24f20af662bfce7fa3683703914987241107ce664fa2f6d1a9dcb ./scripts/READ
|
|||||||
3fe88e64e43d380c56955f577992ea5b0252c05280f6e9dc3cd0bbcc0bf00896 ./scripts/generate_ha_migration.py
|
3fe88e64e43d380c56955f577992ea5b0252c05280f6e9dc3cd0bbcc0bf00896 ./scripts/generate_ha_migration.py
|
||||||
e4849261fd9ed1f01df96c0637c439c0c4eff8fa317b2918026167bba343af79 ./scripts/install-lxc.sh
|
e4849261fd9ed1f01df96c0637c439c0c4eff8fa317b2918026167bba343af79 ./scripts/install-lxc.sh
|
||||||
bb7cd2c5b27c9dceec1d1d2846fbad9600df9c08e0ad07d13fcde97af533091c ./scripts/install.sh
|
bb7cd2c5b27c9dceec1d1d2846fbad9600df9c08e0ad07d13fcde97af533091c ./scripts/install.sh
|
||||||
50cc08e2aeff052a47c68212524f4584e9201cfc535d71803f276cdc223d5216 ./scripts/live_realtime_test.js
|
b3c22df4f6012a1c3210bd613a263624eb92f636b303c538f473fabdd6845b2c ./scripts/live_realtime_test.js
|
||||||
e00d211e3885e30d7fed1e43b44e6fdad40a67019060156c0641816a93e3365f ./scripts/network-debug.sh
|
e00d211e3885e30d7fed1e43b44e6fdad40a67019060156c0641816a93e3365f ./scripts/network-debug.sh
|
||||||
01952aa92b217f8eae2493b88870e2dec595100cd15c4d561ff11ae2b936c46f ./scripts/regenerate-sha.sh
|
01952aa92b217f8eae2493b88870e2dec595100cd15c4d561ff11ae2b936c46f ./scripts/regenerate-sha.sh
|
||||||
81345b6a0b51736bdbc98fd23199b62e4c721b4e7437e02dab7ea79b97dff29a ./scripts/service.sh
|
81345b6a0b51736bdbc98fd23199b62e4c721b4e7437e02dab7ea79b97dff29a ./scripts/service.sh
|
||||||
@@ -102,13 +102,13 @@ bd7e0455c6a1501f761f0d4c00ff74833eecbd75078af9168235f90a2d9485b3 ./src/api.rs
|
|||||||
0db0d81da4b8161004ddcbed2096812c285ecec9d944771670e6c8edf0775175 ./src/api/assets.rs
|
0db0d81da4b8161004ddcbed2096812c285ecec9d944771670e6c8edf0775175 ./src/api/assets.rs
|
||||||
b24092f34a46626d64628b510eba82193f0b203fd097f6a621b91af5d91eb571 ./src/api/auth.rs
|
b24092f34a46626d64628b510eba82193f0b203fd097f6a621b91af5d91eb571 ./src/api/auth.rs
|
||||||
9e4fc675306e111ed3db7af9822e2925d88109c31b9e309851665a975c0bd83c ./src/api/automations.rs
|
9e4fc675306e111ed3db7af9822e2925d88109c31b9e309851665a975c0bd83c ./src/api/automations.rs
|
||||||
27a788efe385ad7180cb7fc7ec66c34ebd5e6062d9cdf35b32fb461e52a42863 ./src/api/configuration.rs
|
a4222a29cdc90e75dffd5fb5e35c11e3556924a6489b1b31272cd81d0b141821 ./src/api/configuration.rs
|
||||||
19bc141286a336ad67e99079496eb0ae5126f986ab28be62b5f97758e5f6969c ./src/api/debug_tokens.rs
|
19bc141286a336ad67e99079496eb0ae5126f986ab28be62b5f97758e5f6969c ./src/api/debug_tokens.rs
|
||||||
ca21f6d5a407e2e3532899d53ba4e8682c2fe961a46d8cd870ddc459721513ef ./src/api/device_groups.rs
|
ca21f6d5a407e2e3532899d53ba4e8682c2fe961a46d8cd870ddc459721513ef ./src/api/device_groups.rs
|
||||||
5245d5a70c4a1919d8b7757db399e1caf692c8b3a8af0343d8e462bf90373137 ./src/api/devices.rs
|
2d487e274c777cbde081c5803311bec449097f45bd2d021117cfe10677b39efb ./src/api/devices.rs
|
||||||
4b5b784670ae761a6848b9a33fe3fb391bebd39d65e87936907428711e96ca44 ./src/api/events.rs
|
4b5b784670ae761a6848b9a33fe3fb391bebd39d65e87936907428711e96ca44 ./src/api/events.rs
|
||||||
b9a9a68e47fdaa577c28063fa68bf95075bc355bff81addca4ee448ef70dd818 ./src/api/flows.rs
|
d59b080b392b574ec94276c7b485b455d0d1abacc7de2ef29dcbfcf7d86dbdb0 ./src/api/flows.rs
|
||||||
82c02fa135132ea3ebdb4d88dd1210dc536d22f987151d7f82595ee3d3d03440 ./src/api/gree_cloud.rs
|
a17ab0b6d683d285441be15f2af33172d7caa9a57ec8b32e59107379631a377e ./src/api/gree_cloud.rs
|
||||||
b61b0b4f196c5e72e47cf06ca0c14df3cf8eedbdef0242a9d2af33623663489f ./src/api/groups.rs
|
b61b0b4f196c5e72e47cf06ca0c14df3cf8eedbdef0242a9d2af33623663489f ./src/api/groups.rs
|
||||||
3cc64b9c992805f3fbf01c0785cf29ce3121750bf30bd6420ed6e5cf2f8df422 ./src/api/history.rs
|
3cc64b9c992805f3fbf01c0785cf29ce3121750bf30bd6420ed6e5cf2f8df422 ./src/api/history.rs
|
||||||
e8edfbb725d6de650442444b0a929ad2be0bd2c2d7d227d2b3fdd99d05c94f94 ./src/api/house.rs
|
e8edfbb725d6de650442444b0a929ad2be0bd2c2d7d227d2b3fdd99d05c94f94 ./src/api/house.rs
|
||||||
@@ -135,8 +135,8 @@ bb5048ee60053f39ce3c1203dc1f24c7047310538d2357f105a052c016d21ec4 ./src/db/flows
|
|||||||
d6b9b70a04c774173c97f7426487d5e2932d7713e12ae3fb7f64cf2b4e0b3c10 ./src/db/schedules_automations.rs
|
d6b9b70a04c774173c97f7426487d5e2932d7713e12ae3fb7f64cf2b4e0b3c10 ./src/db/schedules_automations.rs
|
||||||
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
|
75f4f139cb268b2dfda73966b52624735380724e73b99930bffe36328620f111 ./src/engine.rs
|
||||||
76dbf72600a48a6ba5b2273777bd8f2c8a6bd212aa236d419cc59eedcb8ab22b ./src/engine/automations.rs
|
ed5c9a0c61c6e597acb33f58593ebe02b562ddc68fa61271e1c1cd436c1003d3 ./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
|
||||||
@@ -146,13 +146,13 @@ e350aa63f3bb14ca887eaaa7555edbeae00fb584cbcaf552cc972f0c8918f5d8 ./src/engine/g
|
|||||||
4ec11426ef860811348f2bf1b458bc5713fe8a7794fb4c8045dd637652e8e696 ./src/engine/history.rs
|
4ec11426ef860811348f2bf1b458bc5713fe8a7794fb4c8045dd637652e8e696 ./src/engine/history.rs
|
||||||
41b7045877771b7c352328998cb45ce2433e7f9631fad3eb1a882ab4897dc31f ./src/engine/local_thermostat.rs
|
41b7045877771b7c352328998cb45ce2433e7f9631fad3eb1a882ab4897dc31f ./src/engine/local_thermostat.rs
|
||||||
f684e6755d023478882d1689833f307f3b4cd2c66abf45f8af8315cee94d36ef ./src/engine/ownership.rs
|
f684e6755d023478882d1689833f307f3b4cd2c66abf45f8af8315cee94d36ef ./src/engine/ownership.rs
|
||||||
76c1594f31af64c9a34e2555ac015e8719d42273c687b4c85b9c8f306b3c69a2 ./src/engine/polling.rs
|
56d64614d8ca85cec7098b1f5d2963ecdcbfb7d0a3d4f22e4424a653895b5a16 ./src/engine/polling.rs
|
||||||
c73691c3637465ab0f79aecf7f5428d0317799a8ab5329ed564cf57963bbe86e ./src/engine/runtime.rs
|
c73691c3637465ab0f79aecf7f5428d0317799a8ab5329ed564cf57963bbe86e ./src/engine/runtime.rs
|
||||||
48a496c3935f886a90b90461c38a7aa31ebc40c6482d6feaeb2be47bc2387625 ./src/engine/schedules.rs
|
48a496c3935f886a90b90461c38a7aa31ebc40c6482d6feaeb2be47bc2387625 ./src/engine/schedules.rs
|
||||||
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
|
2e7349862beb5f844aea0f1c4cd96ab6e53d3500559d7b2562bd93da595d7c41 ./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
|
||||||
@@ -166,7 +166,7 @@ da4068295f37c23222bb13bebac022d88ba7729604a49ad25438c55e45458a49 ./src/influxdb
|
|||||||
920e028d96a14cca9f74a964bb296e68a109cfead314d3164c180201e606d151 ./src/models/automation.rs
|
920e028d96a14cca9f74a964bb296e68a109cfead314d3164c180201e606d151 ./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
|
f3439a154fad8a0111db26918160901dd04fe47dd56d529490e75deaf0a78b43 ./src/models/device.rs
|
||||||
aeab5b5acf35d38a54fd1d82e964fe56635ade69c0149ad423df317095d01555 ./src/models/flow.rs
|
aeab5b5acf35d38a54fd1d82e964fe56635ade69c0149ad423df317095d01555 ./src/models/flow.rs
|
||||||
6e44df6bde2f73c282095c80ac17287e5bef77d9fc1d344e440d863dae5f5031 ./src/models/history.rs
|
6e44df6bde2f73c282095c80ac17287e5bef77d9fc1d344e440d863dae5f5031 ./src/models/history.rs
|
||||||
1fee89f939018bf1f3686c15f25434f01ab96b3239164108db7f348a1f1f8d82 ./src/models/integrations.rs
|
1fee89f939018bf1f3686c15f25434f01ab96b3239164108db7f348a1f1f8d82 ./src/models/integrations.rs
|
||||||
@@ -176,20 +176,20 @@ c4de6101de85d9d7e6814b2504b87d9216561e3d9e274054f081f505fb152140 ./src/models/t
|
|||||||
bc2818ce8d0dae1edfb1b98e5d1c00b193f03eeaa28e343d3532f6023288738c ./src/models/zone.rs
|
bc2818ce8d0dae1edfb1b98e5d1c00b193f03eeaa28e343d3532f6023288738c ./src/models/zone.rs
|
||||||
4c1f676eaf0b6da45b4d4b090ae3ac862fb97c1f9e53284a1640767ae9591d8f ./src/notifications.rs
|
4c1f676eaf0b6da45b4d4b090ae3ac862fb97c1f9e53284a1640767ae9591d8f ./src/notifications.rs
|
||||||
cca742bb2b500eb9269f69385fcf88053cd08aabf6a98b7669b8a7a2fd67df13 ./src/protocol/crypto.rs
|
cca742bb2b500eb9269f69385fcf88053cd08aabf6a98b7669b8a7a2fd67df13 ./src/protocol/crypto.rs
|
||||||
daca14e9a4dd65394add7c7753c8ae5e7e43e991323a5ca23d8788aae4428925 ./src/protocol/gree.rs
|
cd0612021f8ed6c9e0823d63883c0790db09aa485583f3f3fc9f2579f2c28ea3 ./src/protocol/gree.rs
|
||||||
7907c33e6396c7e0d61705b735b21c84d6ed784012fb61022b872c6745540f1d ./src/protocol/gree/binding.rs
|
7907c33e6396c7e0d61705b735b21c84d6ed784012fb61022b872c6745540f1d ./src/protocol/gree/binding.rs
|
||||||
91d82c3ecae21615e4edad31b7ea95dca9b578650348666a2b61475b5a644669 ./src/protocol/gree/commands.rs
|
fcf5e67539c419f3e7e4533d49f41f963fa7779f099bd33c1b210fac32a78949 ./src/protocol/gree/commands.rs
|
||||||
4e419ba3b86ca9d154e8c04445cf005965175c54041093b2daad3666d4d852bd ./src/protocol/gree/core.rs
|
4e419ba3b86ca9d154e8c04445cf005965175c54041093b2daad3666d4d852bd ./src/protocol/gree/core.rs
|
||||||
668e3bfcc4be84d1a86909bfc18e0d3816e0343cdc0d9b43189f7b0c6e0666d4 ./src/protocol/gree/discovery.rs
|
05fa2e4ec133a61c23a3f4ab2ce792bbfab93feaea9f0ce0d4ccc67fc479833d ./src/protocol/gree/discovery.rs
|
||||||
0ab094f609edb99715402d8eeaa7e38cbfd2d271036aec787dcbb3be3e005720 ./src/protocol/gree/merge.rs
|
0ab094f609edb99715402d8eeaa7e38cbfd2d271036aec787dcbb3be3e005720 ./src/protocol/gree/merge.rs
|
||||||
ff33db623f8f1bf30015b4a00e1f247a99de713da3d297c672e42c0795ec845c ./src/protocol/gree/network.rs
|
ff33db623f8f1bf30015b4a00e1f247a99de713da3d297c672e42c0795ec845c ./src/protocol/gree/network.rs
|
||||||
1076cb80950be00ca19f9e2370c73040902d1483b0e80c0ca349c7830d38c9f7 ./src/protocol/gree/polling.rs
|
0a901ff258a74072af3812c5417196da1a9b8daa8a2eff30c96d038558601739 ./src/protocol/gree/polling.rs
|
||||||
e0555fac67d1dd0129ecfea6d1fd2fbeb1a73ac89ad95d1b74a7094e50e29ae4 ./src/protocol/gree/tests.rs
|
e0555fac67d1dd0129ecfea6d1fd2fbeb1a73ac89ad95d1b74a7094e50e29ae4 ./src/protocol/gree/tests.rs
|
||||||
3c93e5b254a2a40a07854532cb5fc07e493ad65d9ba2cf28093cff4a4a4e451c ./src/protocol/gree/transport.rs
|
3c93e5b254a2a40a07854532cb5fc07e493ad65d9ba2cf28093cff4a4a4e451c ./src/protocol/gree/transport.rs
|
||||||
297983059c915057869fbb4f6bcfe752f3d5bd1e93c3a2651a1bf883c739ed29 ./src/protocol/gree_cloud.rs
|
297983059c915057869fbb4f6bcfe752f3d5bd1e93c3a2651a1bf883c739ed29 ./src/protocol/gree_cloud.rs
|
||||||
b4b1bad9cad889dd69681b2afc3e6a09ef19e19b2a1f2900fd417c4b1f55ec2c ./src/protocol/gree_cloud_mqtt.rs
|
b4b1bad9cad889dd69681b2afc3e6a09ef19e19b2a1f2900fd417c4b1f55ec2c ./src/protocol/gree_cloud_mqtt.rs
|
||||||
6258ee3888a5f9a4f8bb6cfbbac97f2621de07f9da6bcb2fa596d452f47f2e49 ./src/protocol/mod.rs
|
6258ee3888a5f9a4f8bb6cfbbac97f2621de07f9da6bcb2fa596d452f47f2e49 ./src/protocol/mod.rs
|
||||||
2a08099b456847f24a828d711c4a0aba3fc372936d9a53f0be931f6706226cc5 ./src/provider.rs
|
a78d73571fbd983660449fcbd7857064a1325ca12adc07e61c2f50a3208adf37 ./src/provider.rs
|
||||||
830621fa75265da7288b8135704c0ce27e0687f87d55abf3cdbe40a6e2f3d2b1 ./src/queries.rs
|
830621fa75265da7288b8135704c0ce27e0687f87d55abf3cdbe40a6e2f3d2b1 ./src/queries.rs
|
||||||
7fac65a9dcbcecee09816f5447c7792dc94f736b1efb66ac85629db75f6280be ./src/queries/device_history.rs
|
7fac65a9dcbcecee09816f5447c7792dc94f736b1efb66ac85629db75f6280be ./src/queries/device_history.rs
|
||||||
580917b91441fe9b3276cf0cfc31536ba7954b6a86f4080347b93f9367a5b1ac ./src/queries/entities.rs
|
580917b91441fe9b3276cf0cfc31536ba7954b6a86f4080347b93f9367a5b1ac ./src/queries/entities.rs
|
||||||
@@ -200,26 +200,26 @@ 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
|
||||||
c184023f9f8a766529e047fb2798296472456a5859828e527cc3a5f180ed2b1e ./web/css/styles.css
|
5cf9a9c26eb2aceee852af05d66a7ce409e375e94d3b59299acda452c6254895 ./web/css/styles.css
|
||||||
183d122ba366303d9952ac0e68aff4bced4e0d9a468fbe51316c13f15227402b ./web/custom-chart.html
|
183d122ba366303d9952ac0e68aff4bced4e0d9a468fbe51316c13f15227402b ./web/custom-chart.html
|
||||||
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
|
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
|
||||||
263d5554e62d2dd26b251f2c43a7e4ec14c8d8d790fa67e55c9bebf16f9d55c0 ./web/index.html
|
784e2c28effb9872b582298292d3e2a6b8dd6140207342452193894d210e2f45 ./web/index.html
|
||||||
9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js-dynamic/README.md
|
9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js-dynamic/README.md
|
||||||
e74bb06dba646f298a4d118b5c2dca6ba24f1179ce8ea8d53c1acd6000ca332c ./web/js-dynamic/bootstrap.js
|
27eff2821c40395369be15e197fa830535d2327fcb27c93a0e8e65a1c62de3ad ./web/js-dynamic/bootstrap.js
|
||||||
5d57253a013fd1cb79e4b8701d4be6e394e3f2acbce73dfcd726a86e5878f1a1 ./web/js-dynamic/charts.js
|
5d57253a013fd1cb79e4b8701d4be6e394e3f2acbce73dfcd726a86e5878f1a1 ./web/js-dynamic/charts.js
|
||||||
aa97e4c67a6f8fe8f473e95e678d7cb79b5756fd915270ceaae59827e6bf7ae1 ./web/js-dynamic/core.js
|
6bef6daa9908520e85055e2372e04dd69b29c1e58d36f0645d258f56fb0b42b7 ./web/js-dynamic/core.js
|
||||||
752b3c3b59558b42d867d4a15329238f0213f354b76f7011c10e31b4e6dbb311 ./web/js-dynamic/dashboard.js
|
752b3c3b59558b42d867d4a15329238f0213f354b76f7011c10e31b4e6dbb311 ./web/js-dynamic/dashboard.js
|
||||||
0e1992a7b93767f1fe4634121ae5aa43900d99f8c2614c2a4cbd5770a49b87c1 ./web/js-dynamic/entities.js
|
0f975542ae1d9bd7216123dd3718be6e9a25dbe643eb899c67024d293940fae5 ./web/js-dynamic/entities.js
|
||||||
eeeef31f767f0aa3efb78bc44a2fea2b9eb86f4d4c50cf1ccae69b5ca30cdfda ./web/js-dynamic/events.js
|
7a4d02bb1ebfa3a5962c7a0b567338d460b99f30ec582fda3a7fec9f6fe54840 ./web/js-dynamic/events.js
|
||||||
22da18288de3282245f88f9f525d9ad29ec027571e7ea893720a9470170eccd2 ./web/js-dynamic/flows.js
|
1865ef1ed93f04fe323338ac44ef06c9b19ad6ec222d47085ce51cb4c03fe13d ./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
|
||||||
86031d4f7f7d1b46412c8fbb09ff1ad1b2b4cd84eb949bc749b27663f1dc27fa ./web/js-dynamic/navigation.js
|
86031d4f7f7d1b46412c8fbb09ff1ad1b2b4cd84eb949bc749b27663f1dc27fa ./web/js-dynamic/navigation.js
|
||||||
799613c6d465cb2f92ecb57cb082d0c33729a03d88ab4ca7fa12a3cbd7e38e75 ./web/js-dynamic/realtime.js
|
2dfeb55897960c48e4fdb9f2fac7ee2956015582e1e3d25b09653ee83e9a07e6 ./web/js-dynamic/realtime.js
|
||||||
1b667f0a8bce824b27f56a8d2f44ff8feef9a011289591dc629ee288db70d137 ./web/js-dynamic/router.js
|
1b667f0a8bce824b27f56a8d2f44ff8feef9a011289591dc629ee288db70d137 ./web/js-dynamic/router.js
|
||||||
c2710fd9c1f2b31878c1e25ceb02d74ae30456c1bbb400e69fcf7619ecc72bc2 ./web/js-dynamic/select-ui.js
|
c2710fd9c1f2b31878c1e25ceb02d74ae30456c1bbb400e69fcf7619ecc72bc2 ./web/js-dynamic/select-ui.js
|
||||||
bc42b4a0dd0d20239ce22ea1dff09b3f94e8c0e1395e01251a4585a051968a8f ./web/js-dynamic/settings-ui.js
|
f7245f0e6cb5011196704f201675ebc6c19d902b8253ffcdbf9ead46cb1c6f86 ./web/js-dynamic/settings-ui.js
|
||||||
672e404470bc6331aa6d658523b221659ec64222af9cc9c17ced978f725790da ./web/js-dynamic/settings.js
|
672e404470bc6331aa6d658523b221659ec64222af9cc9c17ced978f725790da ./web/js-dynamic/settings.js
|
||||||
489ab0208c86f7a2bb0d721106f01fb5d4f39f41e94749b440a6c58e13c4de07 ./web/js/custom-chart.js
|
489ab0208c86f7a2bb0d721106f01fb5d4f39f41e94749b440a6c58e13c4de07 ./web/js/custom-chart.js
|
||||||
6ae536b18db756bf5eda9adb1b8a550d5a02bfc80f0b7da27fa146039f22e884 ./web/js/lang-init.js
|
6ae536b18db756bf5eda9adb1b8a550d5a02bfc80f0b7da27fa146039f22e884 ./web/js/lang-init.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.0**
|
**Version: 0.14.25**
|
||||||
|
|
||||||
Version 0.15.0 adds full vertical/horizontal louver positioning across direct control, thermostat views, automations, Visual Flow and Home Assistant, while keeping legacy boolean swing payloads compatible. Direct add-on access also shows an invalid-token error only after an explicit token submission. The bundled Home Assistant climate cards also localize louver/fan/profile labels without changing their stable technical service values.
|
Version 0.14.25 improves WebSocket connection status handling: transient disconnects show a yellow reconnecting state, persistent outages become connection errors after 15 seconds, and successful reconnects restore the connected state.
|
||||||
|
|
||||||
> [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.
|
||||||
|
|
||||||
|
|||||||
+40
-40
@@ -1,38 +1,38 @@
|
|||||||
de0793949cf01d27d903653226ecc9b5f72523d8711f20bcab022cf42c4c4ed0 ./.env.example
|
de0793949cf01d27d903653226ecc9b5f72523d8711f20bcab022cf42c4c4ed0 ./.env.example
|
||||||
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
|
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
|
||||||
c3c16d0c76fbb80ce5752b28a1736177940670b10f44235eac5997c4e195c634 ./Cargo.lock
|
7fc984bbc6ee597fb9bb036f1c359f60aa8a3fd100daf9dfb41a43eb34f2da62 ./Cargo.lock
|
||||||
ad513e247ab5dbed3c91a6463cf410cf1a4ac73c0b30422bad40afb93cee0159 ./Cargo.toml
|
13d12e35a4919efad82747707c5f42c0fafe242d304e04629dde0cf159487d2f ./Cargo.toml
|
||||||
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
||||||
db052d6c978b067a31d752f293fc8c02702d50137ba7b84bce87371089972f11 ./README.md
|
db95805ae6dfd20d9c83d97fda5977672432ea8f2bce9ae023808b35f7047b3f ./README.md
|
||||||
29e9821e5ed216ea3565dd6d8bc0b174ed3cf9fa72c4ccebbfa99e98db3ad8f9 ./build.rs
|
29e9821e5ed216ea3565dd6d8bc0b174ed3cf9fa72c4ccebbfa99e98db3ad8f9 ./build.rs
|
||||||
8a96630c36cfac5c392837c8872b20d39d72fe3ec99c7c9e09ec78f2228fd262 ./docs/API.md
|
3031d65ac45a9189a7087bc80b7ae340d906a8da8c7db35ae34e50ce299b1e09 ./docs/API.md
|
||||||
58b79297607303151c1de2da74344cd4f6277bc53d80ebe04ed4567d906c963b ./docs/FLOW.md
|
8b548d02dbdf0563f2be617a1d677459bd1959bed93a1799a01bf61bb5aed405 ./docs/FLOW.md
|
||||||
525b03af649d0215e3b81e52ce63ebf8651f47b49503c30d0177b3e0b2714ac4 ./docs/openapi.json
|
26af934fe981ac1f3f42b86d1fbe8afb2883de46dfab78a49808893c96ffde4e ./docs/openapi.json
|
||||||
c65aa543d2e99f8f7d52736307b07ebd182597d80124661d53a16bb23f6f1118 ./ha-addon/.env.example
|
c65aa543d2e99f8f7d52736307b07ebd182597d80124661d53a16bb23f6f1118 ./ha-addon/.env.example
|
||||||
0993076ca45b6d31859680835204cdc111b3f9bdbf460d50b4efec56cd5e685b ./ha-addon/Dockerfile
|
0993076ca45b6d31859680835204cdc111b3f9bdbf460d50b4efec56cd5e685b ./ha-addon/Dockerfile
|
||||||
adf7ff248d782a3b1cba82b8789c57b6849ddaaaf5fbc108af75e18b1534a885 ./ha-addon/README.md
|
adf7ff248d782a3b1cba82b8789c57b6849ddaaaf5fbc108af75e18b1534a885 ./ha-addon/README.md
|
||||||
dd9291b12afade6fc20d66e80a5513251270e18e4c265260b2bbbe393a5a94c4 ./ha-addon/build.sh
|
dd9291b12afade6fc20d66e80a5513251270e18e4c265260b2bbbe393a5a94c4 ./ha-addon/build.sh
|
||||||
4061846cec4d829e4101b4021df7f07dc2e0293ccfd11e91589f49733ec7c5b4 ./ha-addon/home-assistant/README.md
|
082dfeb6326f3e1b9841ccf7f2954117ecdf0aa1a90b0aef1e6a1960dacdecbf ./ha-addon/home-assistant/README.md
|
||||||
f8e8559fe10fe523ac5bc9aac25c6e26e862f679d502e8f3c39f38a0a8e40911 ./ha-addon/home-assistant/custom_components/gree_controller/__init__.py
|
f8e8559fe10fe523ac5bc9aac25c6e26e862f679d502e8f3c39f38a0a8e40911 ./ha-addon/home-assistant/custom_components/gree_controller/__init__.py
|
||||||
3f6ef15ef58456376ac53fde7cace1ef359d6a6f7a64c5b575ad77ea6e55ccc4 ./ha-addon/home-assistant/custom_components/gree_controller/api.py
|
3f6ef15ef58456376ac53fde7cace1ef359d6a6f7a64c5b575ad77ea6e55ccc4 ./ha-addon/home-assistant/custom_components/gree_controller/api.py
|
||||||
da2a0a9390ab75cef150cd281bc329fa409d0d28f51382b36bd8de3a73325c16 ./ha-addon/home-assistant/custom_components/gree_controller/climate.py
|
0c008329aad15f5af6328380c1afe1f49db186b37b3d1cafec4b6f4088386e8e ./ha-addon/home-assistant/custom_components/gree_controller/climate.py
|
||||||
5e4aef2143e81bedb5a15dd4be5c71b64a3ab448ec6d33a20851edd098e5f529 ./ha-addon/home-assistant/custom_components/gree_controller/config_flow.py
|
5e4aef2143e81bedb5a15dd4be5c71b64a3ab448ec6d33a20851edd098e5f529 ./ha-addon/home-assistant/custom_components/gree_controller/config_flow.py
|
||||||
b7f0873109c52be9d7f09bea3dffc416103c50085e1f0680d11661a969479898 ./ha-addon/home-assistant/custom_components/gree_controller/const.py
|
b7f0873109c52be9d7f09bea3dffc416103c50085e1f0680d11661a969479898 ./ha-addon/home-assistant/custom_components/gree_controller/const.py
|
||||||
6509286c7b7de638dcc5d40ce53181e65d1eddc5dc36a13306dc6d87220fedbb ./ha-addon/home-assistant/custom_components/gree_controller/coordinator.py
|
41a8958a6fe6d10e5679c95ac34832583a2a15ecfd67d09b4e094a83d06f5c47 ./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
|
||||||
d2a18bc3de3207b9f9025507981ad3742ddc3f3728fa223baf84c7ecbb05973e ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json
|
2c933d0774037f487e7c927af9688bb79f2ec5876f0ad4f42603430f18e54354 ./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
|
||||||
338a42662e77f91215150851e55bbd39a5c77f5612cc77f3e037c06a6e6bdadb ./ha-addon/home-assistant/custom_components/gree_controller/switch.py
|
338a42662e77f91215150851e55bbd39a5c77f5612cc77f3e037c06a6e6bdadb ./ha-addon/home-assistant/custom_components/gree_controller/switch.py
|
||||||
a745cc458c235158d5d70b6a33d8000fd8b40ff93bfe2f2796af0f4b3a9eef9b ./ha-addon/home-assistant/custom_components/gree_controller/translations/en.json
|
6bddb7b4620021ecd2099a86a77ef5c7f2c2dcd3d07d5db4e7b4c4ce6d3e8c03 ./ha-addon/home-assistant/custom_components/gree_controller/translations/en.json
|
||||||
50b7d6fe6329ad4ea6bee9612ab141c2f73f8382fcc2af0bb076a8e8d4dd4465 ./ha-addon/home-assistant/custom_components/gree_controller/translations/pl.json
|
13f30e2dcdcedbd1b6c3f99c2335e0487108fd72c8e86922368b84f2fa2038ae ./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
|
||||||
b3ee553cc7dbc68c3bb7cdd6dbb9b5ca8646df113cb499ca5bd9d3d0e380db73 ./ha-addon/repository/gree-controller/CHANGELOG.md
|
b207451e12368ac448998cf2f6a98cdf435a391f9bc5b973d89e5ff9c24a090a ./ha-addon/repository/gree-controller/CHANGELOG.md
|
||||||
1b5599195329cc3e643c23aa1caadcec80f133e8e515eec7d92b9f3cbda5b80b ./ha-addon/repository/gree-controller/DOCS.md
|
1b5599195329cc3e643c23aa1caadcec80f133e8e515eec7d92b9f3cbda5b80b ./ha-addon/repository/gree-controller/DOCS.md
|
||||||
c3db302f38d26dc9f8caf37a1a8a07a1334cc07ae301d452f2940a5ec5915719 ./ha-addon/repository/gree-controller/README.md
|
c3db302f38d26dc9f8caf37a1a8a07a1334cc07ae301d452f2940a5ec5915719 ./ha-addon/repository/gree-controller/README.md
|
||||||
f7e5325e34b2785d0d0bea657c882202d7557d313cbdc464dcdb91f702d36391 ./ha-addon/repository/gree-controller/config.yaml
|
42ab24948d58622cd9c51f2bd766058f1b4b15dd36adce8186b6e9b4d72da87c ./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
|
||||||
1bb256c32c07b8366751bbc79e8ceb284a267f3b389115263b2c67cfd73223e3 ./lang/en.json
|
744bfd0d8eeaacf4fa4ac748b2d71554727fd00e0887b5aa058482695d1b531d ./lang/en.json
|
||||||
826362354203c7f66af8acdfc367d2b10868add09b59dc5d3c756f6102692e77 ./lang/pl.json
|
491c9ee92c3a5518ebcab30abb5ce25ee71819fa96bb6239b894add888792312 ./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
|
||||||
@@ -91,7 +91,7 @@ c64b1c6deeb24f20af662bfce7fa3683703914987241107ce664fa2f6d1a9dcb ./scripts/READ
|
|||||||
3fe88e64e43d380c56955f577992ea5b0252c05280f6e9dc3cd0bbcc0bf00896 ./scripts/generate_ha_migration.py
|
3fe88e64e43d380c56955f577992ea5b0252c05280f6e9dc3cd0bbcc0bf00896 ./scripts/generate_ha_migration.py
|
||||||
e4849261fd9ed1f01df96c0637c439c0c4eff8fa317b2918026167bba343af79 ./scripts/install-lxc.sh
|
e4849261fd9ed1f01df96c0637c439c0c4eff8fa317b2918026167bba343af79 ./scripts/install-lxc.sh
|
||||||
bb7cd2c5b27c9dceec1d1d2846fbad9600df9c08e0ad07d13fcde97af533091c ./scripts/install.sh
|
bb7cd2c5b27c9dceec1d1d2846fbad9600df9c08e0ad07d13fcde97af533091c ./scripts/install.sh
|
||||||
50cc08e2aeff052a47c68212524f4584e9201cfc535d71803f276cdc223d5216 ./scripts/live_realtime_test.js
|
b3c22df4f6012a1c3210bd613a263624eb92f636b303c538f473fabdd6845b2c ./scripts/live_realtime_test.js
|
||||||
e00d211e3885e30d7fed1e43b44e6fdad40a67019060156c0641816a93e3365f ./scripts/network-debug.sh
|
e00d211e3885e30d7fed1e43b44e6fdad40a67019060156c0641816a93e3365f ./scripts/network-debug.sh
|
||||||
01952aa92b217f8eae2493b88870e2dec595100cd15c4d561ff11ae2b936c46f ./scripts/regenerate-sha.sh
|
01952aa92b217f8eae2493b88870e2dec595100cd15c4d561ff11ae2b936c46f ./scripts/regenerate-sha.sh
|
||||||
81345b6a0b51736bdbc98fd23199b62e4c721b4e7437e02dab7ea79b97dff29a ./scripts/service.sh
|
81345b6a0b51736bdbc98fd23199b62e4c721b4e7437e02dab7ea79b97dff29a ./scripts/service.sh
|
||||||
@@ -101,13 +101,13 @@ bd7e0455c6a1501f761f0d4c00ff74833eecbd75078af9168235f90a2d9485b3 ./src/api.rs
|
|||||||
0db0d81da4b8161004ddcbed2096812c285ecec9d944771670e6c8edf0775175 ./src/api/assets.rs
|
0db0d81da4b8161004ddcbed2096812c285ecec9d944771670e6c8edf0775175 ./src/api/assets.rs
|
||||||
b24092f34a46626d64628b510eba82193f0b203fd097f6a621b91af5d91eb571 ./src/api/auth.rs
|
b24092f34a46626d64628b510eba82193f0b203fd097f6a621b91af5d91eb571 ./src/api/auth.rs
|
||||||
9e4fc675306e111ed3db7af9822e2925d88109c31b9e309851665a975c0bd83c ./src/api/automations.rs
|
9e4fc675306e111ed3db7af9822e2925d88109c31b9e309851665a975c0bd83c ./src/api/automations.rs
|
||||||
27a788efe385ad7180cb7fc7ec66c34ebd5e6062d9cdf35b32fb461e52a42863 ./src/api/configuration.rs
|
a4222a29cdc90e75dffd5fb5e35c11e3556924a6489b1b31272cd81d0b141821 ./src/api/configuration.rs
|
||||||
19bc141286a336ad67e99079496eb0ae5126f986ab28be62b5f97758e5f6969c ./src/api/debug_tokens.rs
|
19bc141286a336ad67e99079496eb0ae5126f986ab28be62b5f97758e5f6969c ./src/api/debug_tokens.rs
|
||||||
ca21f6d5a407e2e3532899d53ba4e8682c2fe961a46d8cd870ddc459721513ef ./src/api/device_groups.rs
|
ca21f6d5a407e2e3532899d53ba4e8682c2fe961a46d8cd870ddc459721513ef ./src/api/device_groups.rs
|
||||||
5245d5a70c4a1919d8b7757db399e1caf692c8b3a8af0343d8e462bf90373137 ./src/api/devices.rs
|
2d487e274c777cbde081c5803311bec449097f45bd2d021117cfe10677b39efb ./src/api/devices.rs
|
||||||
4b5b784670ae761a6848b9a33fe3fb391bebd39d65e87936907428711e96ca44 ./src/api/events.rs
|
4b5b784670ae761a6848b9a33fe3fb391bebd39d65e87936907428711e96ca44 ./src/api/events.rs
|
||||||
b9a9a68e47fdaa577c28063fa68bf95075bc355bff81addca4ee448ef70dd818 ./src/api/flows.rs
|
d59b080b392b574ec94276c7b485b455d0d1abacc7de2ef29dcbfcf7d86dbdb0 ./src/api/flows.rs
|
||||||
82c02fa135132ea3ebdb4d88dd1210dc536d22f987151d7f82595ee3d3d03440 ./src/api/gree_cloud.rs
|
a17ab0b6d683d285441be15f2af33172d7caa9a57ec8b32e59107379631a377e ./src/api/gree_cloud.rs
|
||||||
b61b0b4f196c5e72e47cf06ca0c14df3cf8eedbdef0242a9d2af33623663489f ./src/api/groups.rs
|
b61b0b4f196c5e72e47cf06ca0c14df3cf8eedbdef0242a9d2af33623663489f ./src/api/groups.rs
|
||||||
3cc64b9c992805f3fbf01c0785cf29ce3121750bf30bd6420ed6e5cf2f8df422 ./src/api/history.rs
|
3cc64b9c992805f3fbf01c0785cf29ce3121750bf30bd6420ed6e5cf2f8df422 ./src/api/history.rs
|
||||||
e8edfbb725d6de650442444b0a929ad2be0bd2c2d7d227d2b3fdd99d05c94f94 ./src/api/house.rs
|
e8edfbb725d6de650442444b0a929ad2be0bd2c2d7d227d2b3fdd99d05c94f94 ./src/api/house.rs
|
||||||
@@ -134,8 +134,8 @@ bb5048ee60053f39ce3c1203dc1f24c7047310538d2357f105a052c016d21ec4 ./src/db/flows
|
|||||||
d6b9b70a04c774173c97f7426487d5e2932d7713e12ae3fb7f64cf2b4e0b3c10 ./src/db/schedules_automations.rs
|
d6b9b70a04c774173c97f7426487d5e2932d7713e12ae3fb7f64cf2b4e0b3c10 ./src/db/schedules_automations.rs
|
||||||
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
|
75f4f139cb268b2dfda73966b52624735380724e73b99930bffe36328620f111 ./src/engine.rs
|
||||||
76dbf72600a48a6ba5b2273777bd8f2c8a6bd212aa236d419cc59eedcb8ab22b ./src/engine/automations.rs
|
ed5c9a0c61c6e597acb33f58593ebe02b562ddc68fa61271e1c1cd436c1003d3 ./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
|
||||||
@@ -145,13 +145,13 @@ e350aa63f3bb14ca887eaaa7555edbeae00fb584cbcaf552cc972f0c8918f5d8 ./src/engine/g
|
|||||||
4ec11426ef860811348f2bf1b458bc5713fe8a7794fb4c8045dd637652e8e696 ./src/engine/history.rs
|
4ec11426ef860811348f2bf1b458bc5713fe8a7794fb4c8045dd637652e8e696 ./src/engine/history.rs
|
||||||
41b7045877771b7c352328998cb45ce2433e7f9631fad3eb1a882ab4897dc31f ./src/engine/local_thermostat.rs
|
41b7045877771b7c352328998cb45ce2433e7f9631fad3eb1a882ab4897dc31f ./src/engine/local_thermostat.rs
|
||||||
f684e6755d023478882d1689833f307f3b4cd2c66abf45f8af8315cee94d36ef ./src/engine/ownership.rs
|
f684e6755d023478882d1689833f307f3b4cd2c66abf45f8af8315cee94d36ef ./src/engine/ownership.rs
|
||||||
76c1594f31af64c9a34e2555ac015e8719d42273c687b4c85b9c8f306b3c69a2 ./src/engine/polling.rs
|
56d64614d8ca85cec7098b1f5d2963ecdcbfb7d0a3d4f22e4424a653895b5a16 ./src/engine/polling.rs
|
||||||
c73691c3637465ab0f79aecf7f5428d0317799a8ab5329ed564cf57963bbe86e ./src/engine/runtime.rs
|
c73691c3637465ab0f79aecf7f5428d0317799a8ab5329ed564cf57963bbe86e ./src/engine/runtime.rs
|
||||||
48a496c3935f886a90b90461c38a7aa31ebc40c6482d6feaeb2be47bc2387625 ./src/engine/schedules.rs
|
48a496c3935f886a90b90461c38a7aa31ebc40c6482d6feaeb2be47bc2387625 ./src/engine/schedules.rs
|
||||||
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
|
2e7349862beb5f844aea0f1c4cd96ab6e53d3500559d7b2562bd93da595d7c41 ./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
|
||||||
@@ -165,7 +165,7 @@ da4068295f37c23222bb13bebac022d88ba7729604a49ad25438c55e45458a49 ./src/influxdb
|
|||||||
920e028d96a14cca9f74a964bb296e68a109cfead314d3164c180201e606d151 ./src/models/automation.rs
|
920e028d96a14cca9f74a964bb296e68a109cfead314d3164c180201e606d151 ./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
|
f3439a154fad8a0111db26918160901dd04fe47dd56d529490e75deaf0a78b43 ./src/models/device.rs
|
||||||
aeab5b5acf35d38a54fd1d82e964fe56635ade69c0149ad423df317095d01555 ./src/models/flow.rs
|
aeab5b5acf35d38a54fd1d82e964fe56635ade69c0149ad423df317095d01555 ./src/models/flow.rs
|
||||||
6e44df6bde2f73c282095c80ac17287e5bef77d9fc1d344e440d863dae5f5031 ./src/models/history.rs
|
6e44df6bde2f73c282095c80ac17287e5bef77d9fc1d344e440d863dae5f5031 ./src/models/history.rs
|
||||||
1fee89f939018bf1f3686c15f25434f01ab96b3239164108db7f348a1f1f8d82 ./src/models/integrations.rs
|
1fee89f939018bf1f3686c15f25434f01ab96b3239164108db7f348a1f1f8d82 ./src/models/integrations.rs
|
||||||
@@ -175,20 +175,20 @@ c4de6101de85d9d7e6814b2504b87d9216561e3d9e274054f081f505fb152140 ./src/models/t
|
|||||||
bc2818ce8d0dae1edfb1b98e5d1c00b193f03eeaa28e343d3532f6023288738c ./src/models/zone.rs
|
bc2818ce8d0dae1edfb1b98e5d1c00b193f03eeaa28e343d3532f6023288738c ./src/models/zone.rs
|
||||||
4c1f676eaf0b6da45b4d4b090ae3ac862fb97c1f9e53284a1640767ae9591d8f ./src/notifications.rs
|
4c1f676eaf0b6da45b4d4b090ae3ac862fb97c1f9e53284a1640767ae9591d8f ./src/notifications.rs
|
||||||
cca742bb2b500eb9269f69385fcf88053cd08aabf6a98b7669b8a7a2fd67df13 ./src/protocol/crypto.rs
|
cca742bb2b500eb9269f69385fcf88053cd08aabf6a98b7669b8a7a2fd67df13 ./src/protocol/crypto.rs
|
||||||
daca14e9a4dd65394add7c7753c8ae5e7e43e991323a5ca23d8788aae4428925 ./src/protocol/gree.rs
|
cd0612021f8ed6c9e0823d63883c0790db09aa485583f3f3fc9f2579f2c28ea3 ./src/protocol/gree.rs
|
||||||
7907c33e6396c7e0d61705b735b21c84d6ed784012fb61022b872c6745540f1d ./src/protocol/gree/binding.rs
|
7907c33e6396c7e0d61705b735b21c84d6ed784012fb61022b872c6745540f1d ./src/protocol/gree/binding.rs
|
||||||
91d82c3ecae21615e4edad31b7ea95dca9b578650348666a2b61475b5a644669 ./src/protocol/gree/commands.rs
|
fcf5e67539c419f3e7e4533d49f41f963fa7779f099bd33c1b210fac32a78949 ./src/protocol/gree/commands.rs
|
||||||
4e419ba3b86ca9d154e8c04445cf005965175c54041093b2daad3666d4d852bd ./src/protocol/gree/core.rs
|
4e419ba3b86ca9d154e8c04445cf005965175c54041093b2daad3666d4d852bd ./src/protocol/gree/core.rs
|
||||||
668e3bfcc4be84d1a86909bfc18e0d3816e0343cdc0d9b43189f7b0c6e0666d4 ./src/protocol/gree/discovery.rs
|
05fa2e4ec133a61c23a3f4ab2ce792bbfab93feaea9f0ce0d4ccc67fc479833d ./src/protocol/gree/discovery.rs
|
||||||
0ab094f609edb99715402d8eeaa7e38cbfd2d271036aec787dcbb3be3e005720 ./src/protocol/gree/merge.rs
|
0ab094f609edb99715402d8eeaa7e38cbfd2d271036aec787dcbb3be3e005720 ./src/protocol/gree/merge.rs
|
||||||
ff33db623f8f1bf30015b4a00e1f247a99de713da3d297c672e42c0795ec845c ./src/protocol/gree/network.rs
|
ff33db623f8f1bf30015b4a00e1f247a99de713da3d297c672e42c0795ec845c ./src/protocol/gree/network.rs
|
||||||
1076cb80950be00ca19f9e2370c73040902d1483b0e80c0ca349c7830d38c9f7 ./src/protocol/gree/polling.rs
|
0a901ff258a74072af3812c5417196da1a9b8daa8a2eff30c96d038558601739 ./src/protocol/gree/polling.rs
|
||||||
e0555fac67d1dd0129ecfea6d1fd2fbeb1a73ac89ad95d1b74a7094e50e29ae4 ./src/protocol/gree/tests.rs
|
e0555fac67d1dd0129ecfea6d1fd2fbeb1a73ac89ad95d1b74a7094e50e29ae4 ./src/protocol/gree/tests.rs
|
||||||
3c93e5b254a2a40a07854532cb5fc07e493ad65d9ba2cf28093cff4a4a4e451c ./src/protocol/gree/transport.rs
|
3c93e5b254a2a40a07854532cb5fc07e493ad65d9ba2cf28093cff4a4a4e451c ./src/protocol/gree/transport.rs
|
||||||
297983059c915057869fbb4f6bcfe752f3d5bd1e93c3a2651a1bf883c739ed29 ./src/protocol/gree_cloud.rs
|
297983059c915057869fbb4f6bcfe752f3d5bd1e93c3a2651a1bf883c739ed29 ./src/protocol/gree_cloud.rs
|
||||||
b4b1bad9cad889dd69681b2afc3e6a09ef19e19b2a1f2900fd417c4b1f55ec2c ./src/protocol/gree_cloud_mqtt.rs
|
b4b1bad9cad889dd69681b2afc3e6a09ef19e19b2a1f2900fd417c4b1f55ec2c ./src/protocol/gree_cloud_mqtt.rs
|
||||||
6258ee3888a5f9a4f8bb6cfbbac97f2621de07f9da6bcb2fa596d452f47f2e49 ./src/protocol/mod.rs
|
6258ee3888a5f9a4f8bb6cfbbac97f2621de07f9da6bcb2fa596d452f47f2e49 ./src/protocol/mod.rs
|
||||||
2a08099b456847f24a828d711c4a0aba3fc372936d9a53f0be931f6706226cc5 ./src/provider.rs
|
a78d73571fbd983660449fcbd7857064a1325ca12adc07e61c2f50a3208adf37 ./src/provider.rs
|
||||||
830621fa75265da7288b8135704c0ce27e0687f87d55abf3cdbe40a6e2f3d2b1 ./src/queries.rs
|
830621fa75265da7288b8135704c0ce27e0687f87d55abf3cdbe40a6e2f3d2b1 ./src/queries.rs
|
||||||
7fac65a9dcbcecee09816f5447c7792dc94f736b1efb66ac85629db75f6280be ./src/queries/device_history.rs
|
7fac65a9dcbcecee09816f5447c7792dc94f736b1efb66ac85629db75f6280be ./src/queries/device_history.rs
|
||||||
580917b91441fe9b3276cf0cfc31536ba7954b6a86f4080347b93f9367a5b1ac ./src/queries/entities.rs
|
580917b91441fe9b3276cf0cfc31536ba7954b6a86f4080347b93f9367a5b1ac ./src/queries/entities.rs
|
||||||
@@ -199,26 +199,26 @@ 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
|
||||||
c184023f9f8a766529e047fb2798296472456a5859828e527cc3a5f180ed2b1e ./web/css/styles.css
|
5cf9a9c26eb2aceee852af05d66a7ce409e375e94d3b59299acda452c6254895 ./web/css/styles.css
|
||||||
183d122ba366303d9952ac0e68aff4bced4e0d9a468fbe51316c13f15227402b ./web/custom-chart.html
|
183d122ba366303d9952ac0e68aff4bced4e0d9a468fbe51316c13f15227402b ./web/custom-chart.html
|
||||||
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
|
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
|
||||||
263d5554e62d2dd26b251f2c43a7e4ec14c8d8d790fa67e55c9bebf16f9d55c0 ./web/index.html
|
784e2c28effb9872b582298292d3e2a6b8dd6140207342452193894d210e2f45 ./web/index.html
|
||||||
9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js-dynamic/README.md
|
9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js-dynamic/README.md
|
||||||
e74bb06dba646f298a4d118b5c2dca6ba24f1179ce8ea8d53c1acd6000ca332c ./web/js-dynamic/bootstrap.js
|
27eff2821c40395369be15e197fa830535d2327fcb27c93a0e8e65a1c62de3ad ./web/js-dynamic/bootstrap.js
|
||||||
5d57253a013fd1cb79e4b8701d4be6e394e3f2acbce73dfcd726a86e5878f1a1 ./web/js-dynamic/charts.js
|
5d57253a013fd1cb79e4b8701d4be6e394e3f2acbce73dfcd726a86e5878f1a1 ./web/js-dynamic/charts.js
|
||||||
aa97e4c67a6f8fe8f473e95e678d7cb79b5756fd915270ceaae59827e6bf7ae1 ./web/js-dynamic/core.js
|
6bef6daa9908520e85055e2372e04dd69b29c1e58d36f0645d258f56fb0b42b7 ./web/js-dynamic/core.js
|
||||||
752b3c3b59558b42d867d4a15329238f0213f354b76f7011c10e31b4e6dbb311 ./web/js-dynamic/dashboard.js
|
752b3c3b59558b42d867d4a15329238f0213f354b76f7011c10e31b4e6dbb311 ./web/js-dynamic/dashboard.js
|
||||||
0e1992a7b93767f1fe4634121ae5aa43900d99f8c2614c2a4cbd5770a49b87c1 ./web/js-dynamic/entities.js
|
0f975542ae1d9bd7216123dd3718be6e9a25dbe643eb899c67024d293940fae5 ./web/js-dynamic/entities.js
|
||||||
eeeef31f767f0aa3efb78bc44a2fea2b9eb86f4d4c50cf1ccae69b5ca30cdfda ./web/js-dynamic/events.js
|
7a4d02bb1ebfa3a5962c7a0b567338d460b99f30ec582fda3a7fec9f6fe54840 ./web/js-dynamic/events.js
|
||||||
22da18288de3282245f88f9f525d9ad29ec027571e7ea893720a9470170eccd2 ./web/js-dynamic/flows.js
|
1865ef1ed93f04fe323338ac44ef06c9b19ad6ec222d47085ce51cb4c03fe13d ./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
|
||||||
86031d4f7f7d1b46412c8fbb09ff1ad1b2b4cd84eb949bc749b27663f1dc27fa ./web/js-dynamic/navigation.js
|
86031d4f7f7d1b46412c8fbb09ff1ad1b2b4cd84eb949bc749b27663f1dc27fa ./web/js-dynamic/navigation.js
|
||||||
799613c6d465cb2f92ecb57cb082d0c33729a03d88ab4ca7fa12a3cbd7e38e75 ./web/js-dynamic/realtime.js
|
2dfeb55897960c48e4fdb9f2fac7ee2956015582e1e3d25b09653ee83e9a07e6 ./web/js-dynamic/realtime.js
|
||||||
1b667f0a8bce824b27f56a8d2f44ff8feef9a011289591dc629ee288db70d137 ./web/js-dynamic/router.js
|
1b667f0a8bce824b27f56a8d2f44ff8feef9a011289591dc629ee288db70d137 ./web/js-dynamic/router.js
|
||||||
c2710fd9c1f2b31878c1e25ceb02d74ae30456c1bbb400e69fcf7619ecc72bc2 ./web/js-dynamic/select-ui.js
|
c2710fd9c1f2b31878c1e25ceb02d74ae30456c1bbb400e69fcf7619ecc72bc2 ./web/js-dynamic/select-ui.js
|
||||||
bc42b4a0dd0d20239ce22ea1dff09b3f94e8c0e1395e01251a4585a051968a8f ./web/js-dynamic/settings-ui.js
|
f7245f0e6cb5011196704f201675ebc6c19d902b8253ffcdbf9ead46cb1c6f86 ./web/js-dynamic/settings-ui.js
|
||||||
672e404470bc6331aa6d658523b221659ec64222af9cc9c17ced978f725790da ./web/js-dynamic/settings.js
|
672e404470bc6331aa6d658523b221659ec64222af9cc9c17ced978f725790da ./web/js-dynamic/settings.js
|
||||||
489ab0208c86f7a2bb0d721106f01fb5d4f39f41e94749b440a6c58e13c4de07 ./web/js/custom-chart.js
|
489ab0208c86f7a2bb0d721106f01fb5d4f39f41e94749b440a6c58e13c4de07 ./web/js/custom-chart.js
|
||||||
6ae536b18db756bf5eda9adb1b8a550d5a02bfc80f0b7da27fa146039f22e884 ./web/js/lang-init.js
|
6ae536b18db756bf5eda9adb1b8a550d5a02bfc80f0b7da27fa146039f22e884 ./web/js/lang-init.js
|
||||||
|
|||||||
+9
-18
@@ -1,6 +1,6 @@
|
|||||||
# GREE Controller API reference
|
# GREE Controller API reference
|
||||||
|
|
||||||
HTTP and WebSocket API for GREE Controller **0.15.0**.
|
HTTP and WebSocket API for GREE Controller **0.14.25**.
|
||||||
|
|
||||||
[← Main documentation](../README.md)
|
[← Main documentation](../README.md)
|
||||||
|
|
||||||
@@ -240,7 +240,7 @@ Response:
|
|||||||
{
|
{
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"name": "gree-controller",
|
"name": "gree-controller",
|
||||||
"version": "0.15.0",
|
"version": "0.14.25",
|
||||||
"uptime_seconds": 1234,
|
"uptime_seconds": 1234,
|
||||||
"control_ready": true,
|
"control_ready": true,
|
||||||
"time": "2026-08-30T06:54:00Z"
|
"time": "2026-08-30T06:54:00Z"
|
||||||
@@ -303,7 +303,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.0",
|
"version": "0.14.25",
|
||||||
"uptime_seconds": 1234,
|
"uptime_seconds": 1234,
|
||||||
"auth_required": false,
|
"auth_required": false,
|
||||||
"control_ready": true,
|
"control_ready": true,
|
||||||
@@ -360,8 +360,7 @@ A device response contains:
|
|||||||
| `mode` | string | `auto`, `cool`, `dry`, `fan`, `heat`. |
|
| `mode` | string | `auto`, `cool`, `dry`, `fan`, `heat`. |
|
||||||
| `target_temperature` | number | Last known unit setpoint. |
|
| `target_temperature` | number | Last known unit setpoint. |
|
||||||
| `fan_speed` | integer | `0..5`; `0` is Auto. |
|
| `fan_speed` | integer | `0..5`; `0` is Auto. |
|
||||||
| `swing_vertical` | integer | Vertical louver (`SwUpDn`): `0` off/default, `1` full range, `2..6` fixed positions, `7..11` partial swing ranges. |
|
| `swing_vertical`, `swing_horizontal` | boolean | Swing state. |
|
||||||
| `swing_horizontal` | integer | Horizontal louver (`SwingLfRig`): `0` off/default, `1` full range, `2..6` fixed positions. |
|
|
||||||
| `quiet`, `turbo`, `light`, `air`, `xfan`, `health`, `sleep` | boolean | Optional GREE features. |
|
| `quiet`, `turbo`, `light`, `air`, `xfan`, `health`, `sleep` | boolean | Optional GREE features. |
|
||||||
| `supports_*` | boolean/null | Capability learned from device status. |
|
| `supports_*` | boolean/null | Capability learned from device status. |
|
||||||
| `current_temperature` | number/null | GREE indoor temperature. |
|
| `current_temperature` | number/null | GREE indoor temperature. |
|
||||||
@@ -485,8 +484,8 @@ All fields optional; at least one meaningful field should be sent:
|
|||||||
"mode": "cool",
|
"mode": "cool",
|
||||||
"target_temperature": 22,
|
"target_temperature": 22,
|
||||||
"fan_speed": 3,
|
"fan_speed": 3,
|
||||||
"swing_vertical": 2,
|
"swing_vertical": true,
|
||||||
"swing_horizontal": 1,
|
"swing_horizontal": false,
|
||||||
"quiet": false,
|
"quiet": false,
|
||||||
"turbo": false,
|
"turbo": false,
|
||||||
"light": true,
|
"light": true,
|
||||||
@@ -497,8 +496,6 @@ All fields optional; at least one meaningful field should be sent:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Louver command values follow the raw GREE positions. For backward compatibility, legacy booleans are still accepted (`false` = `0`, `true` = `1`).
|
|
||||||
|
|
||||||
Rules:
|
Rules:
|
||||||
|
|
||||||
- modes: `auto`, `cool`, `dry`, `fan`, `heat`,
|
- modes: `auto`, `cool`, `dry`, `fan`, `heat`,
|
||||||
@@ -1694,17 +1691,11 @@ Additional Flow condition blocks are `house_mode`, `device_state`, `zone_state`,
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Granular louver positions — 0.15.0
|
|
||||||
|
|
||||||
Vertical (`swing_vertical`) and horizontal (`swing_horizontal`) controls now carry the raw GREE louver position instead of only on/off. Vertical supports `0..11`; horizontal supports `0..6`. The first values map to Off, Full range (Auto) and the fixed positions shown by GREE remotes/apps; vertical `7..11` additionally represent partial swing ranges.
|
|
||||||
|
|
||||||
Manual unit control and thermostat cards use position dropdowns. Legacy direct-device automations, `device_action` / `zone_thermostat` Flow actions and `device_state` conditions expose the same positions. The bundled Home Assistant direct and zone climate entities expose granular swing modes while retaining `off` and `on` as the first two modes for compatibility. Louver changes remain auxiliary one-shot commands and do not take thermostat ownership.
|
|
||||||
|
|
||||||
The command API continues accepting legacy boolean swing payloads (`false` = `0`, `true` = `1`) so existing clients can migrate without an immediate break.
|
|
||||||
|
|
||||||
## Swing control across manual, thermostat and automation surfaces — 0.14.2
|
## Swing control across manual, thermostat and automation surfaces — 0.14.2
|
||||||
|
|
||||||
Version 0.14.2 introduced independent vertical (`swing_vertical`) and horizontal (`swing_horizontal`) boolean swing controls across direct control, thermostat zones, legacy automations, Visual Flow and Home Assistant zone climates. Version 0.15.0 extends those fields to granular louver positions while preserving boolean command compatibility.
|
Local device control exposes vertical (`swing_vertical`) and horizontal (`swing_horizontal`) swing independently. Thermostat zone UI and the bundled Home Assistant zone climate entity expose the same auxiliary controls when the assigned device reports support; changing swing does not hand thermostat ownership to manual control.
|
||||||
|
|
||||||
|
Legacy direct-device automations accept optional `swing_vertical` and `swing_horizontal` action fields. Visual Flow exposes both axes in `device_action` and `zone_thermostat` actions and in device-state conditions. A `zone_thermostat` Flow using swing compiles to an automation rather than a native schedule so the auxiliary command is preserved. Swing remains outside the thermostat's continuously regulated fan/temperature outputs.
|
||||||
|
|
||||||
## Split/multisplit installations and energy comparison — 0.14.1
|
## Split/multisplit installations and energy comparison — 0.14.1
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -4,7 +4,7 @@ Flow is an authoring/orchestration layer. It does not replace the thermostat, sc
|
|||||||
|
|
||||||
## Compilation
|
## Compilation
|
||||||
|
|
||||||
- `weekday + time_range -> zone_thermostat` with schedule-compatible settings compiles to a native `Schedule`; selecting a vertical or horizontal louver position makes the action compile as an `Automation` so the louver command is not lost.
|
- `weekday + time_range -> zone_thermostat` with schedule-compatible settings compiles to a native `Schedule`; selecting vertical or horizontal swing makes the action compile as an `Automation` so the swing command is not lost.
|
||||||
- Richer DAGs compile to native `Automation` rows with `trigger_kind = "flow"`.
|
- Richer DAGs compile to native `Automation` rows with `trigger_kind = "flow"`.
|
||||||
- Generated rows keep `flow_id` and `flow_node_id`, use stable `flow-<stable-unique-id>` names, and are read-only in the legacy editors.
|
- Generated rows keep `flow_id` and `flow_node_id`, use stable `flow-<stable-unique-id>` names, and are read-only in the legacy editors.
|
||||||
- The Flow source graph plus all of its generated schedule/automation rows are replaced in one SQLite transaction.
|
- The Flow source graph plus all of its generated schedule/automation rows are replaced in one SQLite transaction.
|
||||||
@@ -14,11 +14,11 @@ Flow is an authoring/orchestration layer. It does not replace the thermostat, sc
|
|||||||
|
|
||||||
## Existing control domains remain authoritative
|
## Existing control domains remain authoritative
|
||||||
|
|
||||||
- `zone_thermostat` writes the existing persistent Zone intent and wakes the normal thermostat cycle. It can also issue one-shot granular vertical/horizontal louver commands to the assigned unit; these auxiliary fields do not take over thermostat ownership.
|
- `zone_thermostat` writes the existing persistent Zone intent and wakes the normal thermostat cycle. It can also issue one-shot vertical/horizontal swing commands to the assigned unit; these auxiliary fields do not take over thermostat ownership.
|
||||||
- `group_action` calls the existing `control_group` path, including custom group targets.
|
- `group_action` calls the existing `control_group` path, including custom group targets.
|
||||||
- `device_action` uses the existing `DeviceCommand` / automatic-device path.
|
- `device_action` uses the existing `DeviceCommand` / automatic-device path.
|
||||||
- Thermostat hysteresis, compressor protection, schedule hand-back, manual/local ownership and Temporary Quick Thermostat keep their existing priority.
|
- Thermostat hysteresis, compressor protection, schedule hand-back, manual/local ownership and Temporary Quick Thermostat keep their existing priority.
|
||||||
- GREE fan/quiet/sleep and dry/fan HVAC modes are suppressed on thermostat-assigned devices when they would fight the regulator. Device-only features such as light/turbo/louver/air/xfan/health remain available through the normal command path.
|
- GREE fan/quiet/sleep and dry/fan HVAC modes are suppressed on thermostat-assigned devices when they would fight the regulator. Device-only features such as light/turbo/swing/air/xfan/health remain available through the normal command path.
|
||||||
|
|
||||||
## Concurrency invariants
|
## Concurrency invariants
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ Dry-run never mutates thermostats, devices, groups, schedules or automations. Fo
|
|||||||
|
|
||||||
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, 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.
|
||||||
|
|
||||||
Actions include thermostat zone, GREE device, 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.
|
Actions include thermostat zone, GREE device, 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 swing, quiet, turbo, light, air, xfan, health and sleep.
|
||||||
|
|
||||||
## Built-in presets
|
## Built-in presets
|
||||||
|
|
||||||
|
|||||||
+21
-71
File diff suppressed because one or more lines are too long
@@ -8,7 +8,7 @@ ha-addon/home-assistant/custom_components/gree_controller/
|
|||||||
|
|
||||||
It creates HA entities for physical units, thermostat zones, whole-house controls and climate groups, while sending every command to the standalone Rust controller. Home Assistant therefore remains a client and UDP/AES GREE communication stays outside HA.
|
It creates HA entities for physical units, thermostat zones, whole-house controls and climate groups, while sending every command to the standalone Rust controller. Home Assistant therefore remains a client and UDP/AES GREE communication stays outside HA.
|
||||||
|
|
||||||
The climate proxy supports power/turn on/off, HVAC modes, target temperature, fan mode, granular vertical louver positions and granular horizontal louver positions. `off`/`on` remain the first two swing modes for compatibility, followed by fixed positions and the supported vertical partial-swing ranges. The card UI translates those modes using the Home Assistant language while the underlying mode IDs (for example `fixed_upper`) remain unchanged for services, scripts and automations.
|
The climate proxy supports power/turn on/off, HVAC modes, target temperature, fan mode, vertical swing and horizontal swing.
|
||||||
|
|
||||||
Automation plan:
|
Automation plan:
|
||||||
|
|
||||||
|
|||||||
@@ -39,40 +39,6 @@ HA_TO_MODE = {value: key for key, value in MODE_TO_HA.items()}
|
|||||||
FAN_TO_NAME = {0: "auto", 1: "low", 2: "medium_low", 3: "medium", 4: "medium_high", 5: "high"}
|
FAN_TO_NAME = {0: "auto", 1: "low", 2: "medium_low", 3: "medium", 4: "medium_high", 5: "high"}
|
||||||
NAME_TO_FAN = {value: key for key, value in FAN_TO_NAME.items()}
|
NAME_TO_FAN = {value: key for key, value in FAN_TO_NAME.items()}
|
||||||
|
|
||||||
VERTICAL_SWING_TO_VALUE = {
|
|
||||||
SWING_OFF: 0,
|
|
||||||
SWING_ON: 1,
|
|
||||||
"fixed_upper": 2,
|
|
||||||
"fixed_upper_middle": 3,
|
|
||||||
"fixed_middle": 4,
|
|
||||||
"fixed_lower_middle": 5,
|
|
||||||
"fixed_lower": 6,
|
|
||||||
"swing_upper": 7,
|
|
||||||
"swing_upper_middle": 8,
|
|
||||||
"swing_middle": 9,
|
|
||||||
"swing_lower_middle": 10,
|
|
||||||
"swing_lower": 11,
|
|
||||||
}
|
|
||||||
HORIZONTAL_SWING_TO_VALUE = {
|
|
||||||
SWING_OFF: 0,
|
|
||||||
SWING_ON: 1,
|
|
||||||
"fixed_left": 2,
|
|
||||||
"fixed_left_middle": 3,
|
|
||||||
"fixed_middle": 4,
|
|
||||||
"fixed_right_middle": 5,
|
|
||||||
"fixed_right": 6,
|
|
||||||
}
|
|
||||||
VALUE_TO_VERTICAL_SWING = {value: mode for mode, value in VERTICAL_SWING_TO_VALUE.items()}
|
|
||||||
VALUE_TO_HORIZONTAL_SWING = {value: mode for mode, value in HORIZONTAL_SWING_TO_VALUE.items()}
|
|
||||||
|
|
||||||
|
|
||||||
def _louver_mode(raw: Any, modes: dict[int, str]) -> str:
|
|
||||||
try:
|
|
||||||
value = int(raw)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
value = 0
|
|
||||||
return modes.get(value, SWING_OFF)
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@@ -160,15 +126,14 @@ class GreeControllerClimate(CoordinatorEntity[GreeControllerCoordinator], Climat
|
|||||||
"""Home Assistant climate entity controlled through the Rust service."""
|
"""Home Assistant climate entity controlled through the Rust service."""
|
||||||
|
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
_attr_translation_key = "direct_control"
|
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
_attr_min_temp = 8.0
|
_attr_min_temp = 8.0
|
||||||
_attr_max_temp = 30.0
|
_attr_max_temp = 30.0
|
||||||
_attr_target_temperature_step = 1.0
|
_attr_target_temperature_step = 1.0
|
||||||
_attr_hvac_modes = [HVACMode.OFF, HVACMode.AUTO, HVACMode.COOL, HVACMode.HEAT, HVACMode.DRY, HVACMode.FAN_ONLY]
|
_attr_hvac_modes = [HVACMode.OFF, HVACMode.AUTO, HVACMode.COOL, HVACMode.HEAT, HVACMode.DRY, HVACMode.FAN_ONLY]
|
||||||
_attr_fan_modes = list(NAME_TO_FAN)
|
_attr_fan_modes = list(NAME_TO_FAN)
|
||||||
_attr_swing_modes = list(VERTICAL_SWING_TO_VALUE)
|
_attr_swing_modes = [SWING_OFF, SWING_ON]
|
||||||
_attr_swing_horizontal_modes = list(HORIZONTAL_SWING_TO_VALUE)
|
_attr_swing_horizontal_modes = [SWING_OFF, SWING_ON]
|
||||||
_attr_supported_features = (
|
_attr_supported_features = (
|
||||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
| ClimateEntityFeature.FAN_MODE
|
| ClimateEntityFeature.FAN_MODE
|
||||||
@@ -235,11 +200,11 @@ class GreeControllerClimate(CoordinatorEntity[GreeControllerCoordinator], Climat
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def swing_mode(self) -> str:
|
def swing_mode(self) -> str:
|
||||||
return _louver_mode(self._device.get("swing_vertical", 0), VALUE_TO_VERTICAL_SWING)
|
return SWING_ON if self._device.get("swing_vertical", False) else SWING_OFF
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def swing_horizontal_mode(self) -> str:
|
def swing_horizontal_mode(self) -> str:
|
||||||
return _louver_mode(self._device.get("swing_horizontal", 0), VALUE_TO_HORIZONTAL_SWING)
|
return SWING_ON if self._device.get("swing_horizontal", False) else SWING_OFF
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extra_state_attributes(self) -> dict[str, Any]:
|
def extra_state_attributes(self) -> dict[str, Any]:
|
||||||
@@ -254,8 +219,6 @@ class GreeControllerClimate(CoordinatorEntity[GreeControllerCoordinator], Climat
|
|||||||
"air": bool(device.get("air", False)),
|
"air": bool(device.get("air", False)),
|
||||||
"health": bool(device.get("health", False)),
|
"health": bool(device.get("health", False)),
|
||||||
"sleep": bool(device.get("sleep", False)),
|
"sleep": bool(device.get("sleep", False)),
|
||||||
"vertical_louver_position": int(device.get("swing_vertical", 0) or 0),
|
|
||||||
"horizontal_louver_position": int(device.get("swing_horizontal", 0) or 0),
|
|
||||||
"last_seen": device.get("last_seen"),
|
"last_seen": device.get("last_seen"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,20 +250,15 @@ class GreeControllerClimate(CoordinatorEntity[GreeControllerCoordinator], Climat
|
|||||||
await self._command({"fan_speed": NAME_TO_FAN[fan_mode]})
|
await self._command({"fan_speed": NAME_TO_FAN[fan_mode]})
|
||||||
|
|
||||||
async def async_set_swing_mode(self, swing_mode: str) -> None:
|
async def async_set_swing_mode(self, swing_mode: str) -> None:
|
||||||
value = VERTICAL_SWING_TO_VALUE.get(swing_mode)
|
await self._command({"swing_vertical": swing_mode == SWING_ON})
|
||||||
if value is not None:
|
|
||||||
await self._command({"swing_vertical": value})
|
|
||||||
|
|
||||||
async def async_set_swing_horizontal_mode(self, swing_horizontal_mode: str) -> None:
|
async def async_set_swing_horizontal_mode(self, swing_horizontal_mode: str) -> None:
|
||||||
value = HORIZONTAL_SWING_TO_VALUE.get(swing_horizontal_mode)
|
await self._command({"swing_horizontal": swing_horizontal_mode == SWING_ON})
|
||||||
if value is not None:
|
|
||||||
await self._command({"swing_horizontal": value})
|
|
||||||
|
|
||||||
class GreeControllerZoneClimate(CoordinatorEntity[GreeControllerCoordinator], ClimateEntity):
|
class GreeControllerZoneClimate(CoordinatorEntity[GreeControllerCoordinator], ClimateEntity):
|
||||||
"""Full climate entity for a controller thermostat zone."""
|
"""Full climate entity for a controller thermostat zone."""
|
||||||
|
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
_attr_translation_key = "zone_thermostat"
|
|
||||||
_attr_name = "Thermostat"
|
_attr_name = "Thermostat"
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
_attr_min_temp = 8.0
|
_attr_min_temp = 8.0
|
||||||
@@ -308,8 +266,8 @@ class GreeControllerZoneClimate(CoordinatorEntity[GreeControllerCoordinator], Cl
|
|||||||
_attr_target_temperature_step = 0.5
|
_attr_target_temperature_step = 0.5
|
||||||
_attr_hvac_modes = [HVACMode.OFF, HVACMode.AUTO, HVACMode.COOL, HVACMode.HEAT]
|
_attr_hvac_modes = [HVACMode.OFF, HVACMode.AUTO, HVACMode.COOL, HVACMode.HEAT]
|
||||||
_attr_preset_modes = ["auto", "comfort", "sleep", "away"]
|
_attr_preset_modes = ["auto", "comfort", "sleep", "away"]
|
||||||
_attr_swing_modes = list(VERTICAL_SWING_TO_VALUE)
|
_attr_swing_modes = [SWING_OFF, SWING_ON]
|
||||||
_attr_swing_horizontal_modes = list(HORIZONTAL_SWING_TO_VALUE)
|
_attr_swing_horizontal_modes = [SWING_OFF, SWING_ON]
|
||||||
|
|
||||||
def __init__(self, coordinator: GreeControllerCoordinator, zone_id: str) -> None:
|
def __init__(self, coordinator: GreeControllerCoordinator, zone_id: str) -> None:
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
@@ -385,11 +343,11 @@ class GreeControllerZoneClimate(CoordinatorEntity[GreeControllerCoordinator], Cl
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def swing_mode(self) -> str:
|
def swing_mode(self) -> str:
|
||||||
return _louver_mode(self._device.get("swing_vertical", 0), VALUE_TO_VERTICAL_SWING)
|
return SWING_ON if self._device.get("swing_vertical", False) else SWING_OFF
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def swing_horizontal_mode(self) -> str:
|
def swing_horizontal_mode(self) -> str:
|
||||||
return _louver_mode(self._device.get("swing_horizontal", 0), VALUE_TO_HORIZONTAL_SWING)
|
return SWING_ON if self._device.get("swing_horizontal", False) else SWING_OFF
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extra_state_attributes(self) -> dict[str, Any]:
|
def extra_state_attributes(self) -> dict[str, Any]:
|
||||||
@@ -420,8 +378,6 @@ class GreeControllerZoneClimate(CoordinatorEntity[GreeControllerCoordinator], Cl
|
|||||||
"actual_mode": zone.get("actual_mode"),
|
"actual_mode": zone.get("actual_mode"),
|
||||||
"actual_setpoint": zone.get("actual_setpoint"),
|
"actual_setpoint": zone.get("actual_setpoint"),
|
||||||
"current_schedule": zone.get("current_schedule_name"),
|
"current_schedule": zone.get("current_schedule_name"),
|
||||||
"vertical_louver_position": int(self._device.get("swing_vertical", 0) or 0),
|
|
||||||
"horizontal_louver_position": int(self._device.get("swing_horizontal", 0) or 0),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||||
@@ -450,15 +406,17 @@ class GreeControllerZoneClimate(CoordinatorEntity[GreeControllerCoordinator], Cl
|
|||||||
|
|
||||||
async def async_set_swing_mode(self, swing_mode: str) -> None:
|
async def async_set_swing_mode(self, swing_mode: str) -> None:
|
||||||
device_id = str(self._zone.get("device_id") or "").strip()
|
device_id = str(self._zone.get("device_id") or "").strip()
|
||||||
value = VERTICAL_SWING_TO_VALUE.get(swing_mode)
|
if device_id:
|
||||||
if device_id and value is not None:
|
await self.coordinator.async_device_command(
|
||||||
await self.coordinator.async_device_command(device_id, {"swing_vertical": value})
|
device_id, {"swing_vertical": swing_mode == SWING_ON}
|
||||||
|
)
|
||||||
|
|
||||||
async def async_set_swing_horizontal_mode(self, swing_horizontal_mode: str) -> None:
|
async def async_set_swing_horizontal_mode(self, swing_horizontal_mode: str) -> None:
|
||||||
device_id = str(self._zone.get("device_id") or "").strip()
|
device_id = str(self._zone.get("device_id") or "").strip()
|
||||||
value = HORIZONTAL_SWING_TO_VALUE.get(swing_horizontal_mode)
|
if device_id:
|
||||||
if device_id and value is not None:
|
await self.coordinator.async_device_command(
|
||||||
await self.coordinator.async_device_command(device_id, {"swing_horizontal": value})
|
device_id, {"swing_horizontal": swing_horizontal_mode == SWING_ON}
|
||||||
|
)
|
||||||
|
|
||||||
async def async_turn_on(self) -> None:
|
async def async_turn_on(self) -> None:
|
||||||
await self.coordinator.async_zone_control(self._zone_id, {"power": True})
|
await self.coordinator.async_zone_control(self._zone_id, {"power": True})
|
||||||
|
|||||||
@@ -67,10 +67,6 @@ class GreeControllerCoordinator(DataUpdateCoordinator[dict[str, dict]]):
|
|||||||
expected["target_temperature"] = float(int(value + 0.5))
|
expected["target_temperature"] = float(int(value + 0.5))
|
||||||
if "fan_speed" in expected:
|
if "fan_speed" in expected:
|
||||||
expected["fan_speed"] = min(5, max(0, int(expected["fan_speed"])))
|
expected["fan_speed"] = min(5, max(0, int(expected["fan_speed"])))
|
||||||
if "swing_vertical" in expected:
|
|
||||||
expected["swing_vertical"] = min(11, max(0, int(expected["swing_vertical"])))
|
|
||||||
if "swing_horizontal" in expected:
|
|
||||||
expected["swing_horizontal"] = min(6, max(0, int(expected["swing_horizontal"])))
|
|
||||||
return expected
|
return expected
|
||||||
|
|
||||||
def _overlay_pending_device_commands(self, devices: dict[str, dict[str, Any]]) -> None:
|
def _overlay_pending_device_commands(self, devices: dict[str, dict[str, Any]]) -> None:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"domain": "gree_controller",
|
"domain": "gree_controller",
|
||||||
"name": "GREE Controller",
|
"name": "GREE Controller",
|
||||||
"version": "0.15.0",
|
"version": "0.14.25",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
|
|||||||
@@ -18,91 +18,5 @@
|
|||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "GREE Controller is already configured"
|
"already_configured": "GREE Controller is already configured"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"entity": {
|
|
||||||
"climate": {
|
|
||||||
"direct_control": {
|
|
||||||
"name": "Direct control",
|
|
||||||
"state_attributes": {
|
|
||||||
"fan_mode": {
|
|
||||||
"state": {
|
|
||||||
"auto": "Auto",
|
|
||||||
"low": "Low",
|
|
||||||
"medium_low": "Medium-low",
|
|
||||||
"medium": "Medium",
|
|
||||||
"medium_high": "Medium-high",
|
|
||||||
"high": "High"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"swing_mode": {
|
|
||||||
"state": {
|
|
||||||
"off": "Off",
|
|
||||||
"on": "Full range (Auto)",
|
|
||||||
"fixed_upper": "Fixed: Top",
|
|
||||||
"fixed_upper_middle": "Fixed: Upper-middle",
|
|
||||||
"fixed_middle": "Fixed: Middle",
|
|
||||||
"fixed_lower_middle": "Fixed: Lower-middle",
|
|
||||||
"fixed_lower": "Fixed: Bottom",
|
|
||||||
"swing_upper": "Swing: Top",
|
|
||||||
"swing_upper_middle": "Swing: Upper-middle",
|
|
||||||
"swing_middle": "Swing: Middle",
|
|
||||||
"swing_lower_middle": "Swing: Lower-middle",
|
|
||||||
"swing_lower": "Swing: Bottom"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"swing_horizontal_mode": {
|
|
||||||
"state": {
|
|
||||||
"off": "Off",
|
|
||||||
"on": "Full range (Auto)",
|
|
||||||
"fixed_left": "Fixed: Left",
|
|
||||||
"fixed_left_middle": "Fixed: Left-middle",
|
|
||||||
"fixed_middle": "Fixed: Middle",
|
|
||||||
"fixed_right_middle": "Fixed: Middle-right",
|
|
||||||
"fixed_right": "Fixed: Right"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"zone_thermostat": {
|
|
||||||
"name": "Thermostat",
|
|
||||||
"state_attributes": {
|
|
||||||
"preset_mode": {
|
|
||||||
"state": {
|
|
||||||
"auto": "Auto schedule",
|
|
||||||
"comfort": "Comfort",
|
|
||||||
"sleep": "Sleep",
|
|
||||||
"away": "Away"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"swing_mode": {
|
|
||||||
"state": {
|
|
||||||
"off": "Off",
|
|
||||||
"on": "Full range (Auto)",
|
|
||||||
"fixed_upper": "Fixed: Top",
|
|
||||||
"fixed_upper_middle": "Fixed: Upper-middle",
|
|
||||||
"fixed_middle": "Fixed: Middle",
|
|
||||||
"fixed_lower_middle": "Fixed: Lower-middle",
|
|
||||||
"fixed_lower": "Fixed: Bottom",
|
|
||||||
"swing_upper": "Swing: Top",
|
|
||||||
"swing_upper_middle": "Swing: Upper-middle",
|
|
||||||
"swing_middle": "Swing: Middle",
|
|
||||||
"swing_lower_middle": "Swing: Lower-middle",
|
|
||||||
"swing_lower": "Swing: Bottom"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"swing_horizontal_mode": {
|
|
||||||
"state": {
|
|
||||||
"off": "Off",
|
|
||||||
"on": "Full range (Auto)",
|
|
||||||
"fixed_left": "Fixed: Left",
|
|
||||||
"fixed_left_middle": "Fixed: Left-middle",
|
|
||||||
"fixed_middle": "Fixed: Middle",
|
|
||||||
"fixed_right_middle": "Fixed: Middle-right",
|
|
||||||
"fixed_right": "Fixed: Right"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,91 +18,5 @@
|
|||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "GREE Controller jest już skonfigurowany"
|
"already_configured": "GREE Controller jest już skonfigurowany"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"entity": {
|
|
||||||
"climate": {
|
|
||||||
"direct_control": {
|
|
||||||
"name": "Sterowanie bezpośrednie",
|
|
||||||
"state_attributes": {
|
|
||||||
"fan_mode": {
|
|
||||||
"state": {
|
|
||||||
"auto": "Automatyczny",
|
|
||||||
"low": "Niski",
|
|
||||||
"medium_low": "Średnio-niski",
|
|
||||||
"medium": "Średni",
|
|
||||||
"medium_high": "Średnio-wysoki",
|
|
||||||
"high": "Wysoki"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"swing_mode": {
|
|
||||||
"state": {
|
|
||||||
"off": "Wyłączony",
|
|
||||||
"on": "Pełny zakres (Auto)",
|
|
||||||
"fixed_upper": "Stała: Góra",
|
|
||||||
"fixed_upper_middle": "Stała: Środek-góra",
|
|
||||||
"fixed_middle": "Stała: Środek",
|
|
||||||
"fixed_lower_middle": "Stała: Środek-dół",
|
|
||||||
"fixed_lower": "Stała: Dół",
|
|
||||||
"swing_upper": "Ruch: Góra",
|
|
||||||
"swing_upper_middle": "Ruch: Środek-góra",
|
|
||||||
"swing_middle": "Ruch: Środek",
|
|
||||||
"swing_lower_middle": "Ruch: Środek-dół",
|
|
||||||
"swing_lower": "Ruch: Dół"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"swing_horizontal_mode": {
|
|
||||||
"state": {
|
|
||||||
"off": "Wyłączony",
|
|
||||||
"on": "Pełny zakres (Auto)",
|
|
||||||
"fixed_left": "Stała: Lewo",
|
|
||||||
"fixed_left_middle": "Stała: Lewo-środek",
|
|
||||||
"fixed_middle": "Stała: Środek",
|
|
||||||
"fixed_right_middle": "Stała: Środek-prawo",
|
|
||||||
"fixed_right": "Stała: Prawo"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"zone_thermostat": {
|
|
||||||
"name": "Termostat",
|
|
||||||
"state_attributes": {
|
|
||||||
"preset_mode": {
|
|
||||||
"state": {
|
|
||||||
"auto": "Harmonogram (Auto)",
|
|
||||||
"comfort": "Komfort",
|
|
||||||
"sleep": "Sen",
|
|
||||||
"away": "Poza domem"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"swing_mode": {
|
|
||||||
"state": {
|
|
||||||
"off": "Wyłączony",
|
|
||||||
"on": "Pełny zakres (Auto)",
|
|
||||||
"fixed_upper": "Stała: Góra",
|
|
||||||
"fixed_upper_middle": "Stała: Środek-góra",
|
|
||||||
"fixed_middle": "Stała: Środek",
|
|
||||||
"fixed_lower_middle": "Stała: Środek-dół",
|
|
||||||
"fixed_lower": "Stała: Dół",
|
|
||||||
"swing_upper": "Ruch: Góra",
|
|
||||||
"swing_upper_middle": "Ruch: Środek-góra",
|
|
||||||
"swing_middle": "Ruch: Środek",
|
|
||||||
"swing_lower_middle": "Ruch: Środek-dół",
|
|
||||||
"swing_lower": "Ruch: Dół"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"swing_horizontal_mode": {
|
|
||||||
"state": {
|
|
||||||
"off": "Wyłączony",
|
|
||||||
"on": "Pełny zakres (Auto)",
|
|
||||||
"fixed_left": "Stała: Lewo",
|
|
||||||
"fixed_left_middle": "Stała: Lewo-środek",
|
|
||||||
"fixed_middle": "Stała: Środek",
|
|
||||||
"fixed_right_middle": "Stała: Środek-prawo",
|
|
||||||
"fixed_right": "Stała: Prawo"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 0.15.0
|
## 0.14.25
|
||||||
|
|
||||||
- Adds full vertical and horizontal louver positions to manual unit control and thermostat cards, with compact dropdowns that preserve the existing view layout.
|
- WebSocket disconnects now show a yellow reconnecting state instead of an immediate application-style error.
|
||||||
- Extends legacy automations, Visual Flow and the bundled Home Assistant climate entities with granular louver positions; legacy boolean swing API payloads remain accepted.
|
- A persistent WebSocket outage becomes a connection error after 15 seconds while automatic reconnect attempts and HTTP fallback continue unchanged.
|
||||||
- Direct add-on access no longer shows `Invalid access token` on initial page load; that validation message appears only after a token is submitted.
|
- Successful reconnects clear the outage state immediately and stale socket callbacks no longer overwrite the active connection status.
|
||||||
- Home Assistant climate cards localize direct-control names, fan modes, thermostat presets and all vertical/horizontal louver positions while keeping stable technical mode IDs for services and automations.
|
|
||||||
|
|
||||||
## 0.14.24
|
## 0.14.24
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: "GREE Controller"
|
name: "GREE Controller"
|
||||||
version: "0.15.0"
|
version: "0.14.25"
|
||||||
slug: "gree_controller"
|
slug: "gree_controller"
|
||||||
description: "Local GREE HVAC controller with Web UI and Home Assistant integration"
|
description: "Local GREE HVAC controller with Web UI and Home Assistant integration"
|
||||||
url: "https://git.linuxiarz.pl/gru/gree-controller-ha-addon/"
|
url: "https://git.linuxiarz.pl/gru/gree-controller-ha-addon/"
|
||||||
|
|||||||
+6
-23
@@ -8,6 +8,7 @@
|
|||||||
"translations": {
|
"translations": {
|
||||||
"meta.description": "Local GREE air conditioner controller",
|
"meta.description": "Local GREE air conditioner controller",
|
||||||
"status.connecting": "Connecting…",
|
"status.connecting": "Connecting…",
|
||||||
|
"status.reconnecting": "Reconnecting…",
|
||||||
"status.connected": "Connected",
|
"status.connected": "Connected",
|
||||||
"status.disconnected": "Disconnected",
|
"status.disconnected": "Disconnected",
|
||||||
"status.connectionError": "Connection error",
|
"status.connectionError": "Connection error",
|
||||||
@@ -1000,7 +1001,7 @@
|
|||||||
"flow.available": "available",
|
"flow.available": "available",
|
||||||
"flow.haAvailableHint": "This condition is true only when the Home Assistant entity returns a state other than unknown/unavailable. Connection errors fail closed as false. Useful as a guard before NOT and safety logic.",
|
"flow.haAvailableHint": "This condition is true only when the Home Assistant entity returns a state other than unknown/unavailable. Connection errors fail closed as false. Useful as a guard before NOT and safety logic.",
|
||||||
"flow.deviceOptions": "Advanced GREE options",
|
"flow.deviceOptions": "Advanced GREE options",
|
||||||
"flow.deviceOptionsHint": "These fields use the existing DeviceCommand. Louver positions are one-shot auxiliary settings; fan/quiet/sleep and dry/fan HVAC modes are blocked on a thermostat-assigned device when they would fight the regulator; use the Thermostat action there instead.",
|
"flow.deviceOptionsHint": "These fields use the existing DeviceCommand. Fan/quiet/sleep and dry/fan HVAC modes are blocked on a thermostat-assigned device when they would fight the regulator; use the Thermostat action there instead.",
|
||||||
"settings.haConnection": "Home Assistant connection",
|
"settings.haConnection": "Home Assistant connection",
|
||||||
"settings.haConnectionHint": "Server address and credentials used by all Home Assistant features.",
|
"settings.haConnectionHint": "Server address and credentials used by all Home Assistant features.",
|
||||||
"settings.haThermostatSources": "Thermostat temperature sources",
|
"settings.haThermostatSources": "Thermostat temperature sources",
|
||||||
@@ -1327,28 +1328,10 @@
|
|||||||
"energy.maxTargets": "You can select up to 8 devices or installations.",
|
"energy.maxTargets": "You can select up to 8 devices or installations.",
|
||||||
"energy.period": "Data period",
|
"energy.period": "Data period",
|
||||||
"energy.periodHint": "Time range used for energy bars and totals.",
|
"energy.periodHint": "Time range used for energy bars and totals.",
|
||||||
"devices.swingVertical": "Vertical louver",
|
"devices.swingVertical": "Vertical swing",
|
||||||
"devices.swingHorizontal": "Horizontal louver",
|
"devices.swingHorizontal": "Horizontal swing",
|
||||||
"louver.off": "Off",
|
"flow.thermostatDeviceOptions": "Unit swing control",
|
||||||
"louver.fullRangeAuto": "Full range (Auto)",
|
"flow.thermostatDeviceOptionsHint": "Vertical and horizontal swing are sent to the unit as independent settings and do not change thermostat logic.",
|
||||||
"louver.advancedSwingRanges": "Additional swing ranges",
|
|
||||||
"louver.vertical.fixedTop": "Fixed: Top",
|
|
||||||
"louver.vertical.fixedUpperMiddle": "Fixed: Upper-middle",
|
|
||||||
"louver.vertical.fixedMiddle": "Fixed: Middle",
|
|
||||||
"louver.vertical.fixedLowerMiddle": "Fixed: Lower-middle",
|
|
||||||
"louver.vertical.fixedBottom": "Fixed: Bottom",
|
|
||||||
"louver.vertical.swingTop": "Swing: Top",
|
|
||||||
"louver.vertical.swingUpperMiddle": "Swing: Upper-middle",
|
|
||||||
"louver.vertical.swingMiddle": "Swing: Middle",
|
|
||||||
"louver.vertical.swingLowerMiddle": "Swing: Lower-middle",
|
|
||||||
"louver.vertical.swingBottom": "Swing: Bottom",
|
|
||||||
"louver.horizontal.fixedLeft": "Fixed: Left",
|
|
||||||
"louver.horizontal.fixedLeftMiddle": "Fixed: Left-middle",
|
|
||||||
"louver.horizontal.fixedMiddle": "Fixed: Middle",
|
|
||||||
"louver.horizontal.fixedRightMiddle": "Fixed: Right-middle",
|
|
||||||
"louver.horizontal.fixedRight": "Fixed: Right",
|
|
||||||
"flow.thermostatDeviceOptions": "Unit louver control",
|
|
||||||
"flow.thermostatDeviceOptionsHint": "Vertical and horizontal louver positions are sent to the unit independently and do not change thermostat logic.",
|
|
||||||
"history.tabNetwork": "Pings",
|
"history.tabNetwork": "Pings",
|
||||||
"history.networkTarget": "Endpoint",
|
"history.networkTarget": "Endpoint",
|
||||||
"history.allNetworkTargets": "All endpoints",
|
"history.allNetworkTargets": "All endpoints",
|
||||||
|
|||||||
+5
-22
@@ -8,6 +8,7 @@
|
|||||||
"translations": {
|
"translations": {
|
||||||
"meta.description": "Lokalny sterownik klimatyzatorów GREE",
|
"meta.description": "Lokalny sterownik klimatyzatorów GREE",
|
||||||
"status.connecting": "Łączenie…",
|
"status.connecting": "Łączenie…",
|
||||||
|
"status.reconnecting": "Ponowne łączenie…",
|
||||||
"status.connected": "Połączono",
|
"status.connected": "Połączono",
|
||||||
"status.disconnected": "Rozłączono",
|
"status.disconnected": "Rozłączono",
|
||||||
"status.connectionError": "Błąd połączenia",
|
"status.connectionError": "Błąd połączenia",
|
||||||
@@ -1000,7 +1001,7 @@
|
|||||||
"flow.available": "dostępna",
|
"flow.available": "dostępna",
|
||||||
"flow.haAvailableHint": "Warunek jest prawdziwy tylko wtedy, gdy encja Home Assistant odpowiada stanem innym niż unknown/unavailable. Błąd połączenia jest traktowany bezpiecznie jako false. Przydatne jako bramka przed NOT i logiką bezpieczeństwa.",
|
"flow.haAvailableHint": "Warunek jest prawdziwy tylko wtedy, gdy encja Home Assistant odpowiada stanem innym niż unknown/unavailable. Błąd połączenia jest traktowany bezpiecznie jako false. Przydatne jako bramka przed NOT i logiką bezpieczeństwa.",
|
||||||
"flow.deviceOptions": "Zaawansowane opcje GREE",
|
"flow.deviceOptions": "Zaawansowane opcje GREE",
|
||||||
"flow.deviceOptionsHint": "Te pola używają istniejącego DeviceCommand. Pozycje żaluzji są jednorazowymi ustawieniami pomocniczymi; fan/quiet/sleep oraz tryby dry/fan są blokowane dla urządzenia przypisanego do termostatu, gdy mogłyby walczyć z regulatorem; użyj wtedy akcji Termostat.",
|
"flow.deviceOptionsHint": "Te pola używają istniejącego DeviceCommand. Fan/quiet/sleep oraz tryby dry/fan są blokowane dla urządzenia przypisanego do termostatu, gdy mogłyby walczyć z regulatorem; użyj wtedy akcji Termostat.",
|
||||||
"settings.haConnection": "Połączenie z Home Assistant",
|
"settings.haConnection": "Połączenie z Home Assistant",
|
||||||
"settings.haConnectionHint": "Adres serwera i dane dostępu używane przez wszystkie funkcje Home Assistant.",
|
"settings.haConnectionHint": "Adres serwera i dane dostępu używane przez wszystkie funkcje Home Assistant.",
|
||||||
"settings.haThermostatSources": "Źródła temperatury sterowania",
|
"settings.haThermostatSources": "Źródła temperatury sterowania",
|
||||||
@@ -1327,28 +1328,10 @@
|
|||||||
"energy.maxTargets": "Możesz wybrać maksymalnie 8 urządzeń lub instalacji.",
|
"energy.maxTargets": "Możesz wybrać maksymalnie 8 urządzeń lub instalacji.",
|
||||||
"energy.period": "Okres danych",
|
"energy.period": "Okres danych",
|
||||||
"energy.periodHint": "Zakres czasu używany do słupków i sum energii.",
|
"energy.periodHint": "Zakres czasu używany do słupków i sum energii.",
|
||||||
"devices.swingVertical": "Żaluzja pionowa",
|
"devices.swingVertical": "Swing pionowy",
|
||||||
"devices.swingHorizontal": "Żaluzja pozioma",
|
"devices.swingHorizontal": "Swing poziomy",
|
||||||
"louver.off": "Wyłączony",
|
|
||||||
"louver.fullRangeAuto": "Pełny zakres (Auto)",
|
|
||||||
"louver.advancedSwingRanges": "Dodatkowe zakresy ruchu",
|
|
||||||
"louver.vertical.fixedTop": "Stała: Góra",
|
|
||||||
"louver.vertical.fixedUpperMiddle": "Stała: Środek-góra",
|
|
||||||
"louver.vertical.fixedMiddle": "Stała: Środek",
|
|
||||||
"louver.vertical.fixedLowerMiddle": "Stała: Środek-dół",
|
|
||||||
"louver.vertical.fixedBottom": "Stała: Dół",
|
|
||||||
"louver.vertical.swingTop": "Ruch: Góra",
|
|
||||||
"louver.vertical.swingUpperMiddle": "Ruch: Środek-góra",
|
|
||||||
"louver.vertical.swingMiddle": "Ruch: Środek",
|
|
||||||
"louver.vertical.swingLowerMiddle": "Ruch: Środek-dół",
|
|
||||||
"louver.vertical.swingBottom": "Ruch: Dół",
|
|
||||||
"louver.horizontal.fixedLeft": "Stała: Lewo",
|
|
||||||
"louver.horizontal.fixedLeftMiddle": "Stała: Lewo-środek",
|
|
||||||
"louver.horizontal.fixedMiddle": "Stała: Środek",
|
|
||||||
"louver.horizontal.fixedRightMiddle": "Stała: Środek-prawo",
|
|
||||||
"louver.horizontal.fixedRight": "Stała: Prawo",
|
|
||||||
"flow.thermostatDeviceOptions": "Sterowanie żaluzjami jednostki",
|
"flow.thermostatDeviceOptions": "Sterowanie żaluzjami jednostki",
|
||||||
"flow.thermostatDeviceOptionsHint": "Pozycje żaluzji pionowej i poziomej są wysyłane do jednostki niezależnie i nie zmieniają logiki termostatu.",
|
"flow.thermostatDeviceOptionsHint": "Swing pionowy i poziomy są wysyłane do jednostki jako niezależne ustawienia i nie zmieniają logiki termostatu.",
|
||||||
"history.tabNetwork": "Pingi",
|
"history.tabNetwork": "Pingi",
|
||||||
"history.networkTarget": "Punkt pomiaru",
|
"history.networkTarget": "Punkt pomiaru",
|
||||||
"history.allNetworkTargets": "Wszystkie punkty",
|
"history.allNetworkTargets": "Wszystkie punkty",
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const app = {
|
|||||||
settings: { house_mode: 'cool', debug: { overlay_enabled: false }, home_assistant: {} },
|
settings: { house_mode: 'cool', debug: { overlay_enabled: false }, home_assistant: {} },
|
||||||
system: {}, outdoorTemperature: null, controlPlan: null, controlPlanRevision: null,
|
system: {}, outdoorTemperature: null, controlPlan: null, controlPlanRevision: null,
|
||||||
controlPlanPushReady: false, controlPlanTimer: null, currentView: 'dashboard',
|
controlPlanPushReady: false, controlPlanTimer: null, currentView: 'dashboard',
|
||||||
sensorAliases: {}, flowSharedInputs: [], flowDraft: null, ws: null, wsTimer: null,
|
sensorAliases: {}, flowSharedInputs: [], flowDraft: null, ws: null, wsTimer: null, wsErrorTimer: null, wsOutageStartedAt: 0,
|
||||||
token: '', debugBacklogLoaded: false,
|
token: '', debugBacklogLoaded: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -278,7 +278,21 @@ async function main() {
|
|||||||
socket.readyState = FakeWebSocket.CLOSED;
|
socket.readyState = FakeWebSocket.CLOSED;
|
||||||
socket.onclose();
|
socket.onclose();
|
||||||
assert.equal(app.controlPlanPushReady, false);
|
assert.equal(app.controlPlanPushReady, false);
|
||||||
assert(timers.length >= beforeCloseTimers + 2, 'close must schedule fallback and reconnect');
|
assert.equal(calls['connection:reconnecting'], 1, 'short WS outage must show reconnecting state');
|
||||||
|
assert(timers.length >= beforeCloseTimers + 3, 'close must schedule fallback, reconnect and delayed error state');
|
||||||
|
const errorTimer = timers.find(item => !item.cleared && item.delay === 15000);
|
||||||
|
assert(errorTimer, 'persistent WS outage must schedule a delayed connection-error state');
|
||||||
|
errorTimer.fn();
|
||||||
|
assert.equal(calls['connection:connectionError'], 1, 'persistent WS outage must become a connection error');
|
||||||
|
|
||||||
|
const reconnectTimer = timers.find(item => !item.cleared && item.delay === 3000);
|
||||||
|
assert(reconnectTimer, 'WS close must schedule reconnect');
|
||||||
|
reconnectTimer.fn();
|
||||||
|
const reconnectedSocket = FakeWebSocket.instances[FakeWebSocket.instances.length - 1];
|
||||||
|
reconnectedSocket.readyState = FakeWebSocket.OPEN;
|
||||||
|
reconnectedSocket.onopen();
|
||||||
|
assert.equal(calls['connection:connected'], 2, 'successful reconnect must restore connected state');
|
||||||
|
assert.equal(app.wsOutageStartedAt, 0, 'successful reconnect must clear outage tracking');
|
||||||
|
|
||||||
console.log(`Live realtime test OK (${apiCalls.length} API fallback/resync calls observed)`);
|
console.log(`Live realtime test OK (${apiCalls.length} API fallback/resync calls observed)`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -594,8 +594,8 @@ fn sanitize_imported_device(device: &mut Device, now: chrono::DateTime<Utc>) {
|
|||||||
device.mode = "cool".into();
|
device.mode = "cool".into();
|
||||||
device.target_temperature = 23.0;
|
device.target_temperature = 23.0;
|
||||||
device.fan_speed = 0;
|
device.fan_speed = 0;
|
||||||
device.swing_vertical = 0;
|
device.swing_vertical = false;
|
||||||
device.swing_horizontal = 0;
|
device.swing_horizontal = false;
|
||||||
device.quiet = false;
|
device.quiet = false;
|
||||||
device.turbo = false;
|
device.turbo = false;
|
||||||
device.light = false;
|
device.light = false;
|
||||||
|
|||||||
+2
-2
@@ -116,8 +116,8 @@ async fn add_device(
|
|||||||
mode: "cool".into(),
|
mode: "cool".into(),
|
||||||
target_temperature: 24.0,
|
target_temperature: 24.0,
|
||||||
fan_speed: 0,
|
fan_speed: 0,
|
||||||
swing_vertical: 0,
|
swing_vertical: false,
|
||||||
swing_horizontal: 0,
|
swing_horizontal: false,
|
||||||
quiet: false,
|
quiet: false,
|
||||||
quiet_wire_value: None,
|
quiet_wire_value: None,
|
||||||
turbo: false,
|
turbo: false,
|
||||||
|
|||||||
+8
-22
@@ -53,15 +53,6 @@ fn flow_u8(config: &Value, key: &str) -> Option<u8> {
|
|||||||
fn flow_bool(config: &Value, key: &str) -> Option<bool> {
|
fn flow_bool(config: &Value, key: &str) -> Option<bool> {
|
||||||
config.get(key).and_then(Value::as_bool)
|
config.get(key).and_then(Value::as_bool)
|
||||||
}
|
}
|
||||||
fn flow_louver_position(config: &Value, key: &str) -> Option<u8> {
|
|
||||||
match config.get(key) {
|
|
||||||
Some(Value::Bool(value)) => Some(u8::from(*value)),
|
|
||||||
Some(value) => value
|
|
||||||
.as_u64()
|
|
||||||
.and_then(|raw| u8::try_from(raw).ok()),
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generated_flow_name(flow_id: &str, action_node_id: &str) -> String {
|
fn generated_flow_name(flow_id: &str, action_node_id: &str) -> String {
|
||||||
let digest = Sha256::digest(format!("{flow_id}:{action_node_id}").as_bytes());
|
let digest = Sha256::digest(format!("{flow_id}:{action_node_id}").as_bytes());
|
||||||
@@ -1117,8 +1108,8 @@ fn compile_flow(
|
|||||||
&& preset_for_schedule != "auto"
|
&& preset_for_schedule != "auto"
|
||||||
&& mode_for_schedule == "auto"
|
&& mode_for_schedule == "auto"
|
||||||
&& flow_bool(&action_node.config, "power").is_none()
|
&& flow_bool(&action_node.config, "power").is_none()
|
||||||
&& flow_louver_position(&action_node.config, "swing_vertical").is_none()
|
&& flow_bool(&action_node.config, "swing_vertical").is_none()
|
||||||
&& flow_louver_position(&action_node.config, "swing_horizontal").is_none();
|
&& flow_bool(&action_node.config, "swing_horizontal").is_none();
|
||||||
if schedule_only {
|
if schedule_only {
|
||||||
let zone_id = flow_string(&action_node.config, "zone_id")
|
let zone_id = flow_string(&action_node.config, "zone_id")
|
||||||
.ok_or_else(|| AppError::BadRequest("thermostat block needs a zone".into()))?;
|
.ok_or_else(|| AppError::BadRequest("thermostat block needs a zone".into()))?;
|
||||||
@@ -1296,11 +1287,8 @@ fn compile_flow(
|
|||||||
}
|
}
|
||||||
item.action.mode = Some(mode);
|
item.action.mode = Some(mode);
|
||||||
}
|
}
|
||||||
item.action.swing_vertical =
|
item.action.swing_vertical = flow_bool(&action_node.config, "swing_vertical");
|
||||||
flow_louver_position(&action_node.config, "swing_vertical");
|
item.action.swing_horizontal = flow_bool(&action_node.config, "swing_horizontal");
|
||||||
item.action.swing_horizontal =
|
|
||||||
flow_louver_position(&action_node.config, "swing_horizontal");
|
|
||||||
engine::validate_command(&item.action)?;
|
|
||||||
}
|
}
|
||||||
"device_action" => {
|
"device_action" => {
|
||||||
let id = flow_string(&action_node.config, "device_id")
|
let id = flow_string(&action_node.config, "device_id")
|
||||||
@@ -1316,10 +1304,8 @@ fn compile_flow(
|
|||||||
item.action.target_temperature =
|
item.action.target_temperature =
|
||||||
flow_f64(&action_node.config, "target_temperature");
|
flow_f64(&action_node.config, "target_temperature");
|
||||||
item.action.fan_speed = flow_u8(&action_node.config, "fan_speed");
|
item.action.fan_speed = flow_u8(&action_node.config, "fan_speed");
|
||||||
item.action.swing_vertical =
|
item.action.swing_vertical = flow_bool(&action_node.config, "swing_vertical");
|
||||||
flow_louver_position(&action_node.config, "swing_vertical");
|
item.action.swing_horizontal = flow_bool(&action_node.config, "swing_horizontal");
|
||||||
item.action.swing_horizontal =
|
|
||||||
flow_louver_position(&action_node.config, "swing_horizontal");
|
|
||||||
item.action.quiet = flow_bool(&action_node.config, "quiet");
|
item.action.quiet = flow_bool(&action_node.config, "quiet");
|
||||||
item.action.turbo = flow_bool(&action_node.config, "turbo");
|
item.action.turbo = flow_bool(&action_node.config, "turbo");
|
||||||
item.action.light = flow_bool(&action_node.config, "light");
|
item.action.light = flow_bool(&action_node.config, "light");
|
||||||
@@ -1747,8 +1733,8 @@ fn dry_run_block_reason(
|
|||||||
command.mode = flow_string(&action.config, "mode");
|
command.mode = flow_string(&action.config, "mode");
|
||||||
command.target_temperature = flow_f64(&action.config, "target_temperature");
|
command.target_temperature = flow_f64(&action.config, "target_temperature");
|
||||||
command.fan_speed = flow_u8(&action.config, "fan_speed");
|
command.fan_speed = flow_u8(&action.config, "fan_speed");
|
||||||
command.swing_vertical = flow_louver_position(&action.config, "swing_vertical");
|
command.swing_vertical = flow_bool(&action.config, "swing_vertical");
|
||||||
command.swing_horizontal = flow_louver_position(&action.config, "swing_horizontal");
|
command.swing_horizontal = flow_bool(&action.config, "swing_horizontal");
|
||||||
command.quiet = flow_bool(&action.config, "quiet");
|
command.quiet = flow_bool(&action.config, "quiet");
|
||||||
command.turbo = flow_bool(&action.config, "turbo");
|
command.turbo = flow_bool(&action.config, "turbo");
|
||||||
command.light = flow_bool(&action.config, "light");
|
command.light = flow_bool(&action.config, "light");
|
||||||
|
|||||||
@@ -300,8 +300,8 @@ async fn add_gree_cloud_device(
|
|||||||
mode: "cool".into(),
|
mode: "cool".into(),
|
||||||
target_temperature: 24.0,
|
target_temperature: 24.0,
|
||||||
fan_speed: 0,
|
fan_speed: 0,
|
||||||
swing_vertical: 0,
|
swing_vertical: false,
|
||||||
swing_horizontal: 0,
|
swing_horizontal: false,
|
||||||
quiet: false,
|
quiet: false,
|
||||||
quiet_wire_value: None,
|
quiet_wire_value: None,
|
||||||
turbo: false,
|
turbo: false,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ use crate::{
|
|||||||
models::{
|
models::{
|
||||||
Automation, AutomationPlanRule, ClimateGroup, ConnectionStatus, ConnectionType,
|
Automation, AutomationPlanRule, ClimateGroup, ConnectionStatus, ConnectionType,
|
||||||
ControlPlan, ControlPlanEvent, Device, DeviceCommand, DeviceGroup, EnergyReading,
|
ControlPlan, ControlPlanEvent, Device, DeviceCommand, DeviceGroup, EnergyReading,
|
||||||
HORIZONTAL_SWING_MAX, VERTICAL_SWING_MAX,
|
|
||||||
EnergySourcePreference, GroupControlPatch, HaReading, NetworkReading, NightModeSettings,
|
EnergySourcePreference, GroupControlPatch, HaReading, NetworkReading, NightModeSettings,
|
||||||
Reading, RuntimeSettings, Schedule, TemporaryQuickThermostat, Zone, ZoneControlPlan,
|
Reading, RuntimeSettings, Schedule, TemporaryQuickThermostat, Zone, ZoneControlPlan,
|
||||||
ZoneReading,
|
ZoneReading,
|
||||||
|
|||||||
@@ -264,18 +264,6 @@ fn flow_compare_value(actual: &Value, operator: &str, expected: &Value) -> bool
|
|||||||
if operator == "neq" { !equal } else { equal }
|
if operator == "neq" { !equal } else { equal }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flow_louver_compat_value(field: &str, value: Value) -> Value {
|
|
||||||
if !matches!(field, "swing_vertical" | "swing_horizontal") {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
match value {
|
|
||||||
Value::Bool(value) => json!(u8::from(value)),
|
|
||||||
Value::String(value) if value.eq_ignore_ascii_case("true") => json!(1),
|
|
||||||
Value::String(value) if value.eq_ignore_ascii_case("false") => json!(0),
|
|
||||||
value => value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flow_device_state_value(device: &Device, field: &str) -> Option<Value> {
|
fn flow_device_state_value(device: &Device, field: &str) -> Option<Value> {
|
||||||
Some(match field {
|
Some(match field {
|
||||||
"enabled" => json!(device.enabled),
|
"enabled" => json!(device.enabled),
|
||||||
@@ -615,14 +603,8 @@ async fn flow_leaf_observation(
|
|||||||
"device_state" => {
|
"device_state" => {
|
||||||
let id = c.get("device_id").and_then(Value::as_str).unwrap_or("");
|
let id = c.get("device_id").and_then(Value::as_str).unwrap_or("");
|
||||||
let field = c.get("field").and_then(Value::as_str).unwrap_or("");
|
let field = c.get("field").and_then(Value::as_str).unwrap_or("");
|
||||||
let actual = flow_louver_compat_value(
|
let actual = override_value.unwrap_or_else(|| devices.iter().find(|device| device.id == id).and_then(|device| flow_device_state_value(device, field)).unwrap_or(Value::Null));
|
||||||
field,
|
let expected = c.get("value").cloned().unwrap_or(Value::Null);
|
||||||
override_value.unwrap_or_else(|| devices.iter().find(|device| device.id == id).and_then(|device| flow_device_state_value(device, field)).unwrap_or(Value::Null)),
|
|
||||||
);
|
|
||||||
let expected = flow_louver_compat_value(
|
|
||||||
field,
|
|
||||||
c.get("value").cloned().unwrap_or(Value::Null),
|
|
||||||
);
|
|
||||||
(flow_compare_value(&actual, c.get("operator").and_then(Value::as_str).unwrap_or("eq"), &expected), actual)
|
(flow_compare_value(&actual, c.get("operator").and_then(Value::as_str).unwrap_or("eq"), &expected), actual)
|
||||||
}
|
}
|
||||||
"zone_state" => {
|
"zone_state" => {
|
||||||
|
|||||||
@@ -392,20 +392,6 @@ pub(crate) fn validate_command(command: &DeviceCommand) -> Result<(), AppError>
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(value) = command.swing_vertical {
|
|
||||||
if value > VERTICAL_SWING_MAX {
|
|
||||||
return Err(AppError::BadRequest(format!(
|
|
||||||
"vertical louver position must be between 0 and {VERTICAL_SWING_MAX}"
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(value) = command.swing_horizontal {
|
|
||||||
if value > HORIZONTAL_SWING_MAX {
|
|
||||||
return Err(AppError::BadRequest(format!(
|
|
||||||
"horizontal louver position must be between 0 and {HORIZONTAL_SWING_MAX}"
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(value) = &command.mode {
|
if let Some(value) = &command.mode {
|
||||||
if !matches!(value.as_str(), "auto" | "cool" | "dry" | "fan" | "heat") {
|
if !matches!(value.as_str(), "auto" | "cool" | "dry" | "fan" | "heat") {
|
||||||
return Err(AppError::BadRequest("unsupported HVAC mode".into()));
|
return Err(AppError::BadRequest("unsupported HVAC mode".into()));
|
||||||
|
|||||||
+2
-2
@@ -708,8 +708,8 @@ mod tests {
|
|||||||
));
|
));
|
||||||
assert!(!automation_action_conflicts_with_thermostat(
|
assert!(!automation_action_conflicts_with_thermostat(
|
||||||
&DeviceCommand {
|
&DeviceCommand {
|
||||||
swing_vertical: Some(1),
|
swing_vertical: Some(true),
|
||||||
swing_horizontal: Some(0),
|
swing_horizontal: Some(false),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|||||||
+10
-74
@@ -89,41 +89,6 @@ fn default_temperature_step() -> f64 {
|
|||||||
1.0
|
1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const VERTICAL_SWING_MAX: u8 = 11;
|
|
||||||
pub const HORIZONTAL_SWING_MAX: u8 = 6;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Deserialize)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
enum LouverPositionCompat {
|
|
||||||
Bool(bool),
|
|
||||||
Number(u8),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn louver_position_compat(value: LouverPositionCompat) -> u8 {
|
|
||||||
match value {
|
|
||||||
LouverPositionCompat::Bool(value) => u8::from(value),
|
|
||||||
LouverPositionCompat::Number(value) => value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_louver_position<'de, D>(deserializer: D) -> Result<u8, D::Error>
|
|
||||||
where
|
|
||||||
D: serde::Deserializer<'de>,
|
|
||||||
{
|
|
||||||
Ok(louver_position_compat(LouverPositionCompat::deserialize(
|
|
||||||
deserializer,
|
|
||||||
)?))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_optional_louver_position<'de, D>(
|
|
||||||
deserializer: D,
|
|
||||||
) -> Result<Option<u8>, D::Error>
|
|
||||||
where
|
|
||||||
D: serde::Deserializer<'de>,
|
|
||||||
{
|
|
||||||
Ok(Option::<LouverPositionCompat>::deserialize(deserializer)?.map(louver_position_compat))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum EnergySourcePreference {
|
pub enum EnergySourcePreference {
|
||||||
@@ -211,12 +176,10 @@ pub struct Device {
|
|||||||
pub target_temperature: f64,
|
pub target_temperature: f64,
|
||||||
#[serde(default = "default_fan")]
|
#[serde(default = "default_fan")]
|
||||||
pub fan_speed: u8,
|
pub fan_speed: u8,
|
||||||
/// Raw GREE `SwUpDn` louver position. 0=off/default, 1=full swing, 2..6=fixed positions, 7..11=partial swing ranges.
|
#[serde(default)]
|
||||||
#[serde(default, deserialize_with = "deserialize_louver_position")]
|
pub swing_vertical: bool,
|
||||||
pub swing_vertical: u8,
|
#[serde(default)]
|
||||||
/// Raw GREE `SwingLfRig` louver position. 0=off/default, 1=full swing, 2..6=fixed positions.
|
pub swing_horizontal: bool,
|
||||||
#[serde(default, deserialize_with = "deserialize_louver_position")]
|
|
||||||
pub swing_horizontal: u8,
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub quiet: bool,
|
pub quiet: bool,
|
||||||
/// Raw non-zero value last reported by the unit for the GREE `Quiet` property.
|
/// Raw non-zero value last reported by the unit for the GREE `Quiet` property.
|
||||||
@@ -327,8 +290,8 @@ impl Device {
|
|||||||
mode: "cool".into(),
|
mode: "cool".into(),
|
||||||
target_temperature: 23.0,
|
target_temperature: 23.0,
|
||||||
fan_speed: 0,
|
fan_speed: 0,
|
||||||
swing_vertical: 0,
|
swing_vertical: false,
|
||||||
swing_horizontal: 0,
|
swing_horizontal: false,
|
||||||
quiet: false,
|
quiet: false,
|
||||||
quiet_wire_value: None,
|
quiet_wire_value: None,
|
||||||
turbo: false,
|
turbo: false,
|
||||||
@@ -426,12 +389,8 @@ pub struct DeviceCommand {
|
|||||||
pub mode: Option<String>,
|
pub mode: Option<String>,
|
||||||
pub target_temperature: Option<f64>,
|
pub target_temperature: Option<f64>,
|
||||||
pub fan_speed: Option<u8>,
|
pub fan_speed: Option<u8>,
|
||||||
/// Raw GREE `SwUpDn` value. Legacy boolean payloads remain accepted (`false`=0, `true`=1).
|
pub swing_vertical: Option<bool>,
|
||||||
#[serde(default, deserialize_with = "deserialize_optional_louver_position")]
|
pub swing_horizontal: Option<bool>,
|
||||||
pub swing_vertical: Option<u8>,
|
|
||||||
/// Raw GREE `SwingLfRig` value. Legacy boolean payloads remain accepted (`false`=0, `true`=1).
|
|
||||||
#[serde(default, deserialize_with = "deserialize_optional_louver_position")]
|
|
||||||
pub swing_horizontal: Option<u8>,
|
|
||||||
pub quiet: Option<bool>,
|
pub quiet: Option<bool>,
|
||||||
pub turbo: Option<bool>,
|
pub turbo: Option<bool>,
|
||||||
pub light: Option<bool>,
|
pub light: Option<bool>,
|
||||||
@@ -517,10 +476,10 @@ impl DeviceCommand {
|
|||||||
device.fan_speed = v.min(5);
|
device.fan_speed = v.min(5);
|
||||||
}
|
}
|
||||||
if let Some(v) = self.swing_vertical {
|
if let Some(v) = self.swing_vertical {
|
||||||
device.swing_vertical = v.min(VERTICAL_SWING_MAX);
|
device.swing_vertical = v;
|
||||||
}
|
}
|
||||||
if let Some(v) = self.swing_horizontal {
|
if let Some(v) = self.swing_horizontal {
|
||||||
device.swing_horizontal = v.min(HORIZONTAL_SWING_MAX);
|
device.swing_horizontal = v;
|
||||||
}
|
}
|
||||||
if let Some(v) = self.quiet {
|
if let Some(v) = self.quiet {
|
||||||
device.quiet = v;
|
device.quiet = v;
|
||||||
@@ -585,27 +544,4 @@ mod connection_type_tests {
|
|||||||
let migrated: Device = serde_json::from_value(value).expect("deserialize legacy device");
|
let migrated: Device = serde_json::from_value(value).expect("deserialize legacy device");
|
||||||
assert_eq!(migrated.connection_type, ConnectionType::Local);
|
assert_eq!(migrated.connection_type, ConnectionType::Local);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn legacy_boolean_swing_values_migrate_to_positions() {
|
|
||||||
let device = Device::simulated_default();
|
|
||||||
let mut value = serde_json::to_value(device).expect("serialize device");
|
|
||||||
let object = value.as_object_mut().expect("device object");
|
|
||||||
object.insert("swing_vertical".into(), serde_json::Value::Bool(true));
|
|
||||||
object.insert("swing_horizontal".into(), serde_json::Value::Bool(false));
|
|
||||||
let migrated: Device = serde_json::from_value(value).expect("deserialize legacy device");
|
|
||||||
assert_eq!(migrated.swing_vertical, 1);
|
|
||||||
assert_eq!(migrated.swing_horizontal, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn legacy_boolean_swing_command_is_accepted() {
|
|
||||||
let command: DeviceCommand = serde_json::from_value(serde_json::json!({
|
|
||||||
"swing_vertical": true,
|
|
||||||
"swing_horizontal": false
|
|
||||||
}))
|
|
||||||
.expect("deserialize legacy command");
|
|
||||||
assert_eq!(command.swing_vertical, Some(1));
|
|
||||||
assert_eq!(command.swing_horizontal, Some(0));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
use super::crypto::{
|
use super::crypto::{
|
||||||
decrypt_v1, decrypt_v2, encrypt_v1, encrypt_v2, GENERIC_GREE_V1_KEY, GENERIC_GREE_V2_KEY,
|
decrypt_v1, decrypt_v2, encrypt_v1, encrypt_v2, GENERIC_GREE_V1_KEY, GENERIC_GREE_V2_KEY,
|
||||||
};
|
};
|
||||||
use crate::models::{
|
use crate::models::{ApiEvent, ConnectionStatus, ConnectionType, Device, DeviceCommand};
|
||||||
ApiEvent, ConnectionStatus, ConnectionType, Device, DeviceCommand, HORIZONTAL_SWING_MAX,
|
|
||||||
VERTICAL_SWING_MAX,
|
|
||||||
};
|
|
||||||
use anyhow::{anyhow, bail, Context, Result};
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
|||||||
@@ -191,11 +191,11 @@ impl GreeClient {
|
|||||||
}
|
}
|
||||||
if let Some(v) = command.swing_vertical {
|
if let Some(v) = command.swing_vertical {
|
||||||
opt.push("SwUpDn");
|
opt.push("SwUpDn");
|
||||||
values.push(json!(v.min(VERTICAL_SWING_MAX)));
|
values.push(json!(if v { 1 } else { 0 }));
|
||||||
}
|
}
|
||||||
if let Some(v) = command.swing_horizontal {
|
if let Some(v) = command.swing_horizontal {
|
||||||
opt.push("SwingLfRig");
|
opt.push("SwingLfRig");
|
||||||
values.push(json!(v.min(HORIZONTAL_SWING_MAX)));
|
values.push(json!(if v { 1 } else { 0 }));
|
||||||
}
|
}
|
||||||
if let Some(v) = command.quiet {
|
if let Some(v) = command.quiet {
|
||||||
opt.push("Quiet");
|
opt.push("Quiet");
|
||||||
|
|||||||
@@ -201,8 +201,8 @@ impl GreeClient {
|
|||||||
mode: "cool".into(),
|
mode: "cool".into(),
|
||||||
target_temperature: 24.0,
|
target_temperature: 24.0,
|
||||||
fan_speed: 0,
|
fan_speed: 0,
|
||||||
swing_vertical: 0,
|
swing_vertical: false,
|
||||||
swing_horizontal: 0,
|
swing_horizontal: false,
|
||||||
quiet: false,
|
quiet: false,
|
||||||
quiet_wire_value: None,
|
quiet_wire_value: None,
|
||||||
turbo: false,
|
turbo: false,
|
||||||
|
|||||||
@@ -193,20 +193,8 @@ impl GreeClient {
|
|||||||
}
|
}
|
||||||
next.fan_speed = raw as u8;
|
next.fan_speed = raw as u8;
|
||||||
}
|
}
|
||||||
"SwUpDn" => {
|
"SwUpDn" => next.swing_vertical = status_i64(name, value)? != 0,
|
||||||
let raw = status_i64(name, value)?;
|
"SwingLfRig" => next.swing_horizontal = status_i64(name, value)? != 0,
|
||||||
if !(0..=i64::from(VERTICAL_SWING_MAX)).contains(&raw) {
|
|
||||||
bail!("invalid GREE vertical louver value for {name}: {raw}")
|
|
||||||
}
|
|
||||||
next.swing_vertical = raw as u8;
|
|
||||||
}
|
|
||||||
"SwingLfRig" => {
|
|
||||||
let raw = status_i64(name, value)?;
|
|
||||||
if !(0..=i64::from(HORIZONTAL_SWING_MAX)).contains(&raw) {
|
|
||||||
bail!("invalid GREE horizontal louver value for {name}: {raw}")
|
|
||||||
}
|
|
||||||
next.swing_horizontal = raw as u8;
|
|
||||||
}
|
|
||||||
"Quiet" => {
|
"Quiet" => {
|
||||||
let raw = status_i64(name, value)?;
|
let raw = status_i64(name, value)?;
|
||||||
if raw < 0 {
|
if raw < 0 {
|
||||||
|
|||||||
+6
-11
@@ -1,7 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
models::{
|
models::{
|
||||||
ApiEvent, ConnectionStatus, ConnectionType, Device, DeviceCommand, GreeCloudSettings,
|
ApiEvent, ConnectionStatus, ConnectionType, Device, DeviceCommand, GreeCloudSettings,
|
||||||
HORIZONTAL_SWING_MAX, VERTICAL_SWING_MAX,
|
|
||||||
},
|
},
|
||||||
protocol::{
|
protocol::{
|
||||||
crypto::{decrypt_v1, decrypt_v2, encrypt_v1, encrypt_v2},
|
crypto::{decrypt_v1, decrypt_v2, encrypt_v1, encrypt_v2},
|
||||||
@@ -604,9 +603,9 @@ impl GreeCloudProvider {
|
|||||||
if let Some(value) = command.swing_vertical {
|
if let Some(value) = command.swing_vertical {
|
||||||
simple.push((
|
simple.push((
|
||||||
"SwUpDn",
|
"SwUpDn",
|
||||||
json!(value.min(VERTICAL_SWING_MAX)),
|
json!(if value { 1 } else { 0 }),
|
||||||
DeviceCommand {
|
DeviceCommand {
|
||||||
swing_vertical: Some(value.min(VERTICAL_SWING_MAX)),
|
swing_vertical: Some(value),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
@@ -614,9 +613,9 @@ impl GreeCloudProvider {
|
|||||||
if let Some(value) = command.swing_horizontal {
|
if let Some(value) = command.swing_horizontal {
|
||||||
simple.push((
|
simple.push((
|
||||||
"SwingLfRig",
|
"SwingLfRig",
|
||||||
json!(value.min(HORIZONTAL_SWING_MAX)),
|
json!(if value { 1 } else { 0 }),
|
||||||
DeviceCommand {
|
DeviceCommand {
|
||||||
swing_horizontal: Some(value.min(HORIZONTAL_SWING_MAX)),
|
swing_horizontal: Some(value),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
@@ -1676,14 +1675,10 @@ pub fn apply_cloud_properties(device: &mut Device, props: &BTreeMap<String, Valu
|
|||||||
device.fan_speed = value.clamp(0, 5) as u8;
|
device.fan_speed = value.clamp(0, 5) as u8;
|
||||||
}
|
}
|
||||||
if let Some(value) = props.get("SwUpDn").and_then(value_i64) {
|
if let Some(value) = props.get("SwUpDn").and_then(value_i64) {
|
||||||
if (0..=i64::from(VERTICAL_SWING_MAX)).contains(&value) {
|
device.swing_vertical = value != 0;
|
||||||
device.swing_vertical = value as u8;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if let Some(value) = props.get("SwingLfRig").and_then(value_i64) {
|
if let Some(value) = props.get("SwingLfRig").and_then(value_i64) {
|
||||||
if (0..=i64::from(HORIZONTAL_SWING_MAX)).contains(&value) {
|
device.swing_horizontal = value != 0;
|
||||||
device.swing_horizontal = value as u8;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if let Some(value) = props.get("Quiet").and_then(value_i64) {
|
if let Some(value) = props.get("Quiet").and_then(value_i64) {
|
||||||
device.quiet = value != 0;
|
device.quiet = value != 0;
|
||||||
|
|||||||
+2
-43
@@ -621,33 +621,6 @@ h3 {
|
|||||||
background: color-mix(in srgb, var(--accent) 12%, var(--surface));
|
background: color-mix(in srgb, var(--accent) 12%, var(--surface));
|
||||||
}
|
}
|
||||||
|
|
||||||
.louver-controls {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: minmax(0, 1fr);
|
|
||||||
gap: 7px;
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.louver-control {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: minmax(96px, .72fr) minmax(0, 1.28fr);
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.louver-control > span {
|
|
||||||
color: var(--muted);
|
|
||||||
font-size: 11px;
|
|
||||||
line-height: 1.15;
|
|
||||||
}
|
|
||||||
|
|
||||||
.louver-control select,
|
|
||||||
.louver-control .custom-select {
|
|
||||||
width: 100%;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-footer {
|
.card-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -2918,7 +2891,8 @@ button.outside-pill:focus-visible {
|
|||||||
box-shadow: 0 0 0 3px rgba(239, 91, 91, .14), 0 0 10px rgba(239, 91, 91, .24);
|
box-shadow: 0 0 0 3px rgba(239, 91, 91, .14), 0 0 10px rgba(239, 91, 91, .24);
|
||||||
}
|
}
|
||||||
|
|
||||||
.connection-dot.connecting {
|
.connection-dot.connecting,
|
||||||
|
.connection-dot.reconnecting {
|
||||||
background: #e2a93b;
|
background: #e2a93b;
|
||||||
box-shadow: 0 0 0 3px rgba(226, 169, 59, .14);
|
box-shadow: 0 0 0 3px rgba(226, 169, 59, .14);
|
||||||
animation: connection-pulse 1.25s ease-in-out infinite;
|
animation: connection-pulse 1.25s ease-in-out infinite;
|
||||||
@@ -4054,10 +4028,6 @@ body.simulation-standalone [data-view="simulation"] {
|
|||||||
padding: 2px 0 5px;
|
padding: 2px 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-control-card .quick-control-row-2 {
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
.quick-control-card .quick-control-row-3 {
|
.quick-control-card .quick-control-row-3 {
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
@@ -4128,17 +4098,6 @@ body.simulation-standalone [data-view="simulation"] {
|
|||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-control-card .louver-controls {
|
|
||||||
margin: 1px 0 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quick-control-card .louver-control .custom-select-trigger,
|
|
||||||
.quick-control-card .louver-control select {
|
|
||||||
min-height: 34px;
|
|
||||||
padding-block: 5px;
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quick-thermostat-control .zone-quick-actions {
|
.quick-thermostat-control .zone-quick-actions {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
|||||||
+6
-21
@@ -1760,30 +1760,15 @@
|
|||||||
name="action_target_temperature"></label><label><span data-i18n="common.cooldownSeconds">Cooldown
|
name="action_target_temperature"></label><label><span data-i18n="common.cooldownSeconds">Cooldown
|
||||||
(s)</span><input type="number" min="30" name="cooldown_seconds" value="300"></label></div>
|
(s)</span><input type="number" min="30" name="cooldown_seconds" value="300"></label></div>
|
||||||
<div class="two" id="automationDeviceSwingOptions">
|
<div class="two" id="automationDeviceSwingOptions">
|
||||||
<label><span data-i18n="devices.swingVertical">Vertical louver</span><select name="action_swing_vertical">
|
<label><span data-i18n="devices.swingVertical">Vertical swing</span><select name="action_swing_vertical">
|
||||||
<option value="" data-i18n="actions.noChange">No change</option>
|
<option value="" data-i18n="actions.noChange">No change</option>
|
||||||
<option value="0" data-i18n="louver.off">Off</option>
|
<option value="true" data-i18n="actions.turnOn">Turn on</option>
|
||||||
<option value="1" data-i18n="louver.fullRangeAuto">Full range (Auto)</option>
|
<option value="false" data-i18n="actions.turnOff">Turn off</option>
|
||||||
<option value="2" data-i18n="louver.vertical.fixedTop">Fixed: Top</option>
|
|
||||||
<option value="3" data-i18n="louver.vertical.fixedUpperMiddle">Fixed: Upper-middle</option>
|
|
||||||
<option value="4" data-i18n="louver.vertical.fixedMiddle">Fixed: Middle</option>
|
|
||||||
<option value="5" data-i18n="louver.vertical.fixedLowerMiddle">Fixed: Lower-middle</option>
|
|
||||||
<option value="6" data-i18n="louver.vertical.fixedBottom">Fixed: Bottom</option>
|
|
||||||
<option value="7" data-i18n="louver.vertical.swingTop">Swing: Top</option>
|
|
||||||
<option value="8" data-i18n="louver.vertical.swingUpperMiddle">Swing: Upper-middle</option>
|
|
||||||
<option value="9" data-i18n="louver.vertical.swingMiddle">Swing: Middle</option>
|
|
||||||
<option value="10" data-i18n="louver.vertical.swingLowerMiddle">Swing: Lower-middle</option>
|
|
||||||
<option value="11" data-i18n="louver.vertical.swingBottom">Swing: Bottom</option>
|
|
||||||
</select></label>
|
</select></label>
|
||||||
<label><span data-i18n="devices.swingHorizontal">Horizontal louver</span><select name="action_swing_horizontal">
|
<label><span data-i18n="devices.swingHorizontal">Horizontal swing</span><select name="action_swing_horizontal">
|
||||||
<option value="" data-i18n="actions.noChange">No change</option>
|
<option value="" data-i18n="actions.noChange">No change</option>
|
||||||
<option value="0" data-i18n="louver.off">Off</option>
|
<option value="true" data-i18n="actions.turnOn">Turn on</option>
|
||||||
<option value="1" data-i18n="louver.fullRangeAuto">Full range (Auto)</option>
|
<option value="false" data-i18n="actions.turnOff">Turn off</option>
|
||||||
<option value="2" data-i18n="louver.horizontal.fixedLeft">Fixed: Left</option>
|
|
||||||
<option value="3" data-i18n="louver.horizontal.fixedLeftMiddle">Fixed: Left-middle</option>
|
|
||||||
<option value="4" data-i18n="louver.horizontal.fixedMiddle">Fixed: Middle</option>
|
|
||||||
<option value="5" data-i18n="louver.horizontal.fixedRightMiddle">Fixed: Right-middle</option>
|
|
||||||
<option value="6" data-i18n="louver.horizontal.fixedRight">Fixed: Right</option>
|
|
||||||
</select></label>
|
</select></label>
|
||||||
</div>
|
</div>
|
||||||
<p class="field-note" id="automationGroupHint" hidden data-i18n="automations.groupHint">For a group, Auto mode
|
<p class="field-note" id="automationGroupHint" hidden data-i18n="automations.groupHint">For a group, Auto mode
|
||||||
|
|||||||
Vendored
-1
@@ -66,7 +66,6 @@ async function loadBootstrap(showMessage = false) {
|
|||||||
app.loading = true;
|
app.loading = true;
|
||||||
try {
|
try {
|
||||||
const data = await api('/api/bootstrap');
|
const data = await api('/api/bootstrap');
|
||||||
app.authTokenSubmitted = false;
|
|
||||||
const hasControlPlan = applyBootstrapSnapshot(data);
|
const hasControlPlan = applyBootstrapSnapshot(data);
|
||||||
renderAll();
|
renderAll();
|
||||||
if (!hasControlPlan) scheduleControlPlanLoad(0);
|
if (!hasControlPlan) scheduleControlPlanLoad(0);
|
||||||
|
|||||||
+2
-47
@@ -26,7 +26,7 @@ const preferredTheme = ['system', 'light', 'dark'].includes(getCookie('gree_cont
|
|||||||
|
|
||||||
const app = {
|
const app = {
|
||||||
devices: [], zones: [], groups: [], deviceGroups: [], deviceGroupEnergy: {}, schedules: [], automations: [], flows: [], accessTokens: [], settings: null, system: {}, outdoorTemperature: null,
|
devices: [], zones: [], groups: [], deviceGroups: [], deviceGroupEnergy: {}, schedules: [], automations: [], flows: [], accessTokens: [], settings: null, system: {}, outdoorTemperature: null,
|
||||||
token: localStorage.getItem('gree_controller_token') || '', authTokenSubmitted: false, ws: null, wsTimer: null,
|
token: localStorage.getItem('gree_controller_token') || '', ws: null, wsTimer: null, wsErrorTimer: null, wsOutageStartedAt: 0,
|
||||||
currentView: 'dashboard', loading: false, bootstrapReloadPending: false, language: preferredLanguage, theme: preferredTheme, connectionStatus: 'connecting',
|
currentView: 'dashboard', loading: false, bootstrapReloadPending: false, language: preferredLanguage, theme: preferredTheme, connectionStatus: 'connecting',
|
||||||
languages: [], translations: {}, locales: {}, defaultLanguage: DEFAULT_LANGUAGE,
|
languages: [], translations: {}, locales: {}, defaultLanguage: DEFAULT_LANGUAGE,
|
||||||
historyTab: 'overview', historyData: { zones: [], devices: [], sensors: [] }, historyCounts: {},
|
historyTab: 'overview', historyData: { zones: [], devices: [], sensors: [] }, historyCounts: {},
|
||||||
@@ -128,50 +128,6 @@ const deviceCommandFieldLabel = key => {
|
|||||||
}[key];
|
}[key];
|
||||||
return translationKey ? tr(translationKey) : key;
|
return translationKey ? tr(translationKey) : key;
|
||||||
};
|
};
|
||||||
const VERTICAL_LOUVER_OPTIONS = Object.freeze([
|
|
||||||
[0, 'louver.off'],
|
|
||||||
[1, 'louver.fullRangeAuto'],
|
|
||||||
[2, 'louver.vertical.fixedTop'],
|
|
||||||
[3, 'louver.vertical.fixedUpperMiddle'],
|
|
||||||
[4, 'louver.vertical.fixedMiddle'],
|
|
||||||
[5, 'louver.vertical.fixedLowerMiddle'],
|
|
||||||
[6, 'louver.vertical.fixedBottom'],
|
|
||||||
[7, 'louver.vertical.swingTop'],
|
|
||||||
[8, 'louver.vertical.swingUpperMiddle'],
|
|
||||||
[9, 'louver.vertical.swingMiddle'],
|
|
||||||
[10, 'louver.vertical.swingLowerMiddle'],
|
|
||||||
[11, 'louver.vertical.swingBottom'],
|
|
||||||
]);
|
|
||||||
const HORIZONTAL_LOUVER_OPTIONS = Object.freeze([
|
|
||||||
[0, 'louver.off'],
|
|
||||||
[1, 'louver.fullRangeAuto'],
|
|
||||||
[2, 'louver.horizontal.fixedLeft'],
|
|
||||||
[3, 'louver.horizontal.fixedLeftMiddle'],
|
|
||||||
[4, 'louver.horizontal.fixedMiddle'],
|
|
||||||
[5, 'louver.horizontal.fixedRightMiddle'],
|
|
||||||
[6, 'louver.horizontal.fixedRight'],
|
|
||||||
]);
|
|
||||||
const normalizedLouverPosition = value => {
|
|
||||||
if (value === null || value === undefined || value === '') return null;
|
|
||||||
if (value === true || String(value).toLowerCase() === 'true') return 1;
|
|
||||||
if (value === false || String(value).toLowerCase() === 'false') return 0;
|
|
||||||
const numeric = Number(value);
|
|
||||||
return Number.isFinite(numeric) ? numeric : null;
|
|
||||||
};
|
|
||||||
const louverPositionLabel = (axis, value) => {
|
|
||||||
const options = axis === 'horizontal' ? HORIZONTAL_LOUVER_OPTIONS : VERTICAL_LOUVER_OPTIONS;
|
|
||||||
const normalized = normalizedLouverPosition(value);
|
|
||||||
const item = options.find(([raw]) => raw === normalized);
|
|
||||||
return item ? tr(item[1]) : String(value ?? '');
|
|
||||||
};
|
|
||||||
const louverOptions = (axis, selected, { includeNoChange = false } = {}) => {
|
|
||||||
const options = axis === 'horizontal' ? HORIZONTAL_LOUVER_OPTIONS : VERTICAL_LOUVER_OPTIONS;
|
|
||||||
const selectedValue = normalizedLouverPosition(selected);
|
|
||||||
const render = items => items.map(([value, key]) => `<option value="${value}" ${selectedValue === value ? 'selected' : ''}>${esc(tr(key))}</option>`).join('');
|
|
||||||
const noChange = includeNoChange ? `<option value="" ${selectedValue === null ? 'selected' : ''}>${esc(tr('actions.noChange'))}</option>` : '';
|
|
||||||
if (axis !== 'vertical') return `${noChange}${render(options)}`;
|
|
||||||
return `${noChange}${render(options.slice(0, 7))}<optgroup label="${esc(tr('louver.advancedSwingRanges'))}">${render(options.slice(7))}</optgroup>`;
|
|
||||||
};
|
|
||||||
const dateTime = value => value ? new Intl.DateTimeFormat(locale(), { dateStyle: 'short', timeStyle: 'short' }).format(new Date(value)) : '—';
|
const dateTime = value => value ? new Intl.DateTimeFormat(locale(), { dateStyle: 'short', timeStyle: 'short' }).format(new Date(value)) : '—';
|
||||||
const localResumeSeconds = value => {
|
const localResumeSeconds = value => {
|
||||||
const timestamp = value ? new Date(value).getTime() : NaN;
|
const timestamp = value ? new Date(value).getTime() : NaN;
|
||||||
@@ -457,10 +413,9 @@ async function api(path, options = {}) {
|
|||||||
}
|
}
|
||||||
const response = await fetch(withBase(path), { ...options, headers, body });
|
const response = await fetch(withBase(path), { ...options, headers, body });
|
||||||
if (response.status === 401) {
|
if (response.status === 401) {
|
||||||
const invalidToken = app.authTokenSubmitted && Boolean(app.token);
|
const invalidToken = Boolean(app.token);
|
||||||
const message = invalidToken ? tr('auth.invalid') : tr('auth.required');
|
const message = invalidToken ? tr('auth.invalid') : tr('auth.required');
|
||||||
showTokenDialog(invalidToken ? message : '');
|
showTokenDialog(invalidToken ? message : '');
|
||||||
app.authTokenSubmitted = false;
|
|
||||||
const error = new Error(message);
|
const error = new Error(message);
|
||||||
error.status = response.status;
|
error.status = response.status;
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -130,16 +130,6 @@ function powerIconMarkup() {
|
|||||||
return uiIcon('power', 'power-icon');
|
return uiIcon('power', 'power-icon');
|
||||||
}
|
}
|
||||||
|
|
||||||
function deviceLouverControls(device, { disabled = false } = {}) {
|
|
||||||
if (!device) return '';
|
|
||||||
const caps = device.capabilities || {};
|
|
||||||
const disabledAttr = disabled ? ' disabled' : '';
|
|
||||||
const controls = [];
|
|
||||||
if (caps.vertical_swing !== false) controls.push(`<label class="louver-control"><span>${esc(tr('devices.swingVertical'))}</span><select data-louver-field="swing_vertical" data-device="${esc(device.id)}"${disabledAttr}>${louverOptions('vertical', device.swing_vertical)}</select></label>`);
|
|
||||||
if (caps.horizontal_swing !== false) controls.push(`<label class="louver-control"><span>${esc(tr('devices.swingHorizontal'))}</span><select data-louver-field="swing_horizontal" data-device="${esc(device.id)}"${disabledAttr}>${louverOptions('horizontal', device.swing_horizontal)}</select></label>`);
|
|
||||||
return controls.length ? `<div class="louver-controls">${controls.join('')}</div>` : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function manualLocalDeviceCard(device) {
|
function manualLocalDeviceCard(device) {
|
||||||
const caps = device.capabilities || {};
|
const caps = device.capabilities || {};
|
||||||
const modes = ['auto', 'cool', 'dry', 'fan', 'heat'];
|
const modes = ['auto', 'cool', 'dry', 'fan', 'heat'];
|
||||||
@@ -163,8 +153,9 @@ function manualLocalDeviceCard(device) {
|
|||||||
${compressorQueuePanel(managedZone)}
|
${compressorQueuePanel(managedZone)}
|
||||||
<div class="mode-row quick-control-row quick-control-row-5">${modes.map(mode => `<button class="${device.mode === mode ? 'active' : ''}" data-action="mode" data-value="${mode}" data-device="${esc(device.id)}">${esc(modeLabel(mode))}</button>`).join('')}</div>
|
<div class="mode-row quick-control-row quick-control-row-5">${modes.map(mode => `<button class="${device.mode === mode ? 'active' : ''}" data-action="mode" data-value="${mode}" data-device="${esc(device.id)}">${esc(modeLabel(mode))}</button>`).join('')}</div>
|
||||||
<div class="fan-row quick-control-row quick-control-row-4">${fans.map(fan => `<button class="${Number(device.fan_speed) === fan ? 'active' : ''}" data-action="fan" data-value="${fan}" data-device="${esc(device.id)}">${esc(fanLabel(fan))}</button>`).join('')}</div>
|
<div class="fan-row quick-control-row quick-control-row-4">${fans.map(fan => `<button class="${Number(device.fan_speed) === fan ? 'active' : ''}" data-action="fan" data-value="${fan}" data-device="${esc(device.id)}">${esc(fanLabel(fan))}</button>`).join('')}</div>
|
||||||
${deviceLouverControls(device)}
|
<div class="device-toggles quick-control-row quick-control-row-4">
|
||||||
<div class="device-toggles quick-control-row quick-control-row-2">
|
${caps.vertical_swing === false ? '' : `<button class="${device.swing_vertical ? 'active' : ''}" data-action="toggle" data-field="swing_vertical" data-device="${esc(device.id)}">${esc(tr('devices.swingVertical'))}</button>`}
|
||||||
|
${caps.horizontal_swing === false ? '' : `<button class="${device.swing_horizontal ? 'active' : ''}" data-action="toggle" data-field="swing_horizontal" data-device="${esc(device.id)}">${esc(tr('devices.swingHorizontal'))}</button>`}
|
||||||
${device.supports_quiet === false ? '' : `<button class="${device.quiet ? 'active' : ''}" data-action="toggle" data-field="quiet" data-device="${esc(device.id)}">${esc(tr('devices.quiet'))}</button>`}
|
${device.supports_quiet === false ? '' : `<button class="${device.quiet ? 'active' : ''}" data-action="toggle" data-field="quiet" data-device="${esc(device.id)}">${esc(tr('devices.quiet'))}</button>`}
|
||||||
${device.supports_turbo === false ? '' : `<button class="${device.turbo ? 'active' : ''}" data-action="toggle" data-field="turbo" data-device="${esc(device.id)}">${esc(tr('devices.turbo'))}</button>`}
|
${device.supports_turbo === false ? '' : `<button class="${device.turbo ? 'active' : ''}" data-action="toggle" data-field="turbo" data-device="${esc(device.id)}">${esc(tr('devices.turbo'))}</button>`}
|
||||||
</div>
|
</div>
|
||||||
@@ -201,8 +192,9 @@ function manualCloudDeviceCard(device) {
|
|||||||
${compressorQueuePanel(managedZone)}
|
${compressorQueuePanel(managedZone)}
|
||||||
${modes.length ? `<div class="mode-row quick-control-row quick-control-row-5">${modes.map(mode => `<button class="${device.mode === mode ? 'active' : ''}" data-action="mode" data-value="${mode}" data-device="${esc(device.id)}">${esc(modeLabel(mode))}</button>`).join('')}</div>` : ''}
|
${modes.length ? `<div class="mode-row quick-control-row quick-control-row-5">${modes.map(mode => `<button class="${device.mode === mode ? 'active' : ''}" data-action="mode" data-value="${mode}" data-device="${esc(device.id)}">${esc(modeLabel(mode))}</button>`).join('')}</div>` : ''}
|
||||||
${fans.length ? `<div class="fan-row quick-control-row quick-control-row-${Math.min(6, fans.length)}">${fans.map(fan => `<button class="${Number(device.fan_speed) === fan ? 'active' : ''}" data-action="fan" data-value="${fan}" data-device="${esc(device.id)}">${esc(fanLabel(fan))}</button>`).join('')}</div>` : ''}
|
${fans.length ? `<div class="fan-row quick-control-row quick-control-row-${Math.min(6, fans.length)}">${fans.map(fan => `<button class="${Number(device.fan_speed) === fan ? 'active' : ''}" data-action="fan" data-value="${fan}" data-device="${esc(device.id)}">${esc(fanLabel(fan))}</button>`).join('')}</div>` : ''}
|
||||||
${deviceLouverControls(device)}
|
<div class="device-toggles quick-control-row quick-control-row-4">
|
||||||
<div class="device-toggles quick-control-row quick-control-row-2">
|
${caps.vertical_swing === false ? '' : `<button class="${device.swing_vertical ? 'active' : ''}" data-action="toggle" data-field="swing_vertical" data-device="${esc(device.id)}">${esc(tr('devices.swingVertical'))}</button>`}
|
||||||
|
${caps.horizontal_swing === false ? '' : `<button class="${device.swing_horizontal ? 'active' : ''}" data-action="toggle" data-field="swing_horizontal" data-device="${esc(device.id)}">${esc(tr('devices.swingHorizontal'))}</button>`}
|
||||||
${device.supports_quiet === false ? '' : `<button class="${device.quiet ? 'active' : ''}" data-action="toggle" data-field="quiet" data-device="${esc(device.id)}">${esc(tr('devices.quiet'))}</button>`}
|
${device.supports_quiet === false ? '' : `<button class="${device.quiet ? 'active' : ''}" data-action="toggle" data-field="quiet" data-device="${esc(device.id)}">${esc(tr('devices.quiet'))}</button>`}
|
||||||
${device.supports_turbo === false ? '' : `<button class="${device.turbo ? 'active' : ''}" data-action="toggle" data-field="turbo" data-device="${esc(device.id)}">${esc(tr('devices.turbo'))}</button>`}
|
${device.supports_turbo === false ? '' : `<button class="${device.turbo ? 'active' : ''}" data-action="toggle" data-field="turbo" data-device="${esc(device.id)}">${esc(tr('devices.turbo'))}</button>`}
|
||||||
</div>
|
</div>
|
||||||
@@ -572,7 +564,7 @@ function zoneCard(zone, detailed = true) {
|
|||||||
${['auto', 'comfort', 'sleep', 'away'].map(preset => `<button class="${manual === preset ? 'active' : ''}" data-action="zone-preset" data-id="${esc(zone.id)}" data-value="${preset}">${esc(preset === 'sleep' ? tr('zones.sleepNow') : zonePresetLabel(preset))}</button>`).join('')}
|
${['auto', 'comfort', 'sleep', 'away'].map(preset => `<button class="${manual === preset ? 'active' : ''}" data-action="zone-preset" data-id="${esc(zone.id)}" data-value="${preset}">${esc(preset === 'sleep' ? tr('zones.sleepNow') : zonePresetLabel(preset))}</button>`).join('')}
|
||||||
</div>
|
</div>
|
||||||
<div class="mode-row zone-mode-row quick-control-row quick-control-row-3"><button class="${mode === 'house' && globalModeAvailable ? 'active' : ''}" data-action="zone-mode" data-id="${esc(zone.id)}" data-value="house"${globalModeDisabled}>${esc(tr('zones.followHouseShort'))}</button><button class="${mode === 'heat' ? 'active' : ''}" data-action="zone-mode" data-id="${esc(zone.id)}" data-value="heat">${esc(modeLabel('heat'))}</button><button class="${mode === 'cool' ? 'active' : ''}" data-action="zone-mode" data-id="${esc(zone.id)}" data-value="cool">${esc(modeLabel('cool'))}</button></div>
|
<div class="mode-row zone-mode-row quick-control-row quick-control-row-3"><button class="${mode === 'house' && globalModeAvailable ? 'active' : ''}" data-action="zone-mode" data-id="${esc(zone.id)}" data-value="house"${globalModeDisabled}>${esc(tr('zones.followHouseShort'))}</button><button class="${mode === 'heat' ? 'active' : ''}" data-action="zone-mode" data-id="${esc(zone.id)}" data-value="heat">${esc(modeLabel('heat'))}</button><button class="${mode === 'cool' ? 'active' : ''}" data-action="zone-mode" data-id="${esc(zone.id)}" data-value="cool">${esc(modeLabel('cool'))}</button></div>
|
||||||
${device ? deviceLouverControls(device, { disabled: deviceUnavailable }) : ''}
|
${device ? `<div class="device-toggles thermostat-swing-row quick-control-row quick-control-row-2">${device.capabilities?.vertical_swing === false ? '' : `<button class="${device.swing_vertical ? 'active' : ''}" data-action="zone-swing" data-field="swing_vertical" data-device="${esc(device.id)}"${deviceUnavailable ? ' disabled' : ''}>${esc(tr('devices.swingVertical'))}</button>`}${device.capabilities?.horizontal_swing === false ? '' : `<button class="${device.swing_horizontal ? 'active' : ''}" data-action="zone-swing" data-field="swing_horizontal" data-device="${esc(device.id)}"${deviceUnavailable ? ' disabled' : ''}>${esc(tr('devices.swingHorizontal'))}</button>`}</div>` : ''}
|
||||||
<div class="zone-state-line"><span class="zone-runtime-status" title="${esc(runtimeStatus)}">${esc(runtimeStatus)}${controlGroup ? ` <b class="group-control-tag">${esc(controlGroup.name)}</b>` : ''}</span><span>${esc(override)}</span></div>
|
<div class="zone-state-line"><span class="zone-runtime-status" title="${esc(runtimeStatus)}">${esc(runtimeStatus)}${controlGroup ? ` <b class="group-control-tag">${esc(controlGroup.name)}</b>` : ''}</span><span>${esc(override)}</span></div>
|
||||||
<div class="zone-state-line control-owner-line"><span><strong>${esc(tr('zones.controlOwner'))}:</strong> ${esc(zoneControlOwnerLabel(zone))}</span><span>${esc(zoneControlOwnerMeta(zone))}</span></div>
|
<div class="zone-state-line control-owner-line"><span><strong>${esc(tr('zones.controlOwner'))}:</strong> ${esc(zoneControlOwnerLabel(zone))}</span><span>${esc(zoneControlOwnerMeta(zone))}</span></div>
|
||||||
${compressorQueuePanel(zone)}
|
${compressorQueuePanel(zone)}
|
||||||
|
|||||||
@@ -8,15 +8,6 @@ document.addEventListener('keydown', event => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
document.addEventListener('change', async event => {
|
|
||||||
const select = event.target.closest?.('[data-louver-field][data-device]');
|
|
||||||
if (!select) return;
|
|
||||||
const value = Number(select.value);
|
|
||||||
if (!Number.isInteger(value)) return;
|
|
||||||
await sendDeviceCommand(select.dataset.device, { [select.dataset.louverField]: value });
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener('click', async event => {
|
document.addEventListener('click', async event => {
|
||||||
const inlineTarget = event.target.closest('[data-temperature-kind]');
|
const inlineTarget = event.target.closest('[data-temperature-kind]');
|
||||||
if (inlineTarget && !event.target.closest('button')) { beginInlineTemperatureEdit(inlineTarget); return; }
|
if (inlineTarget && !event.target.closest('button')) { beginInlineTemperatureEdit(inlineTarget); return; }
|
||||||
@@ -203,6 +194,7 @@ document.addEventListener('click', async event => {
|
|||||||
if (action === 'cancel-compressor-task') return cancelCompressorTask(button.dataset.id);
|
if (action === 'cancel-compressor-task') return cancelCompressorTask(button.dataset.id);
|
||||||
if (action === 'cancel-all-compressor-tasks') { if (!confirm(tr('zones.queueCancelAllConfirm', { count: app.zones.filter(zone => !!zone.compressor_pending_action).length }))) return; return cancelAllCompressorTasks(); }
|
if (action === 'cancel-all-compressor-tasks') { if (!confirm(tr('zones.queueCancelAllConfirm', { count: app.zones.filter(zone => !!zone.compressor_pending_action).length }))) return; return cancelAllCompressorTasks(); }
|
||||||
if (action === 'zone-device-power') return sendZoneLocalPower(button.dataset.id, button.dataset.value === 'true');
|
if (action === 'zone-device-power') return sendZoneLocalPower(button.dataset.id, button.dataset.value === 'true');
|
||||||
|
if (action === 'zone-swing' && device) return sendDeviceCommand(device.id, current => ({ [button.dataset.field]: !current?.[button.dataset.field] }));
|
||||||
if (action === 'zone-open-temporary') return populateTemporaryThermostat(button.dataset.id);
|
if (action === 'zone-open-temporary') return populateTemporaryThermostat(button.dataset.id);
|
||||||
if (action === 'zone-temperature') { const zone = app.zones.find(v => v.id === button.dataset.id); if (zone) { const base = Number(zone.manual_setpoint ?? zone.effective_setpoint ?? zone.setpoint); queueZoneTemperature(zone, base + Number(button.dataset.delta)); } return; }
|
if (action === 'zone-temperature') { const zone = app.zones.find(v => v.id === button.dataset.id); if (zone) { const base = Number(zone.manual_setpoint ?? zone.effective_setpoint ?? zone.setpoint); queueZoneTemperature(zone, base + Number(button.dataset.delta)); } return; }
|
||||||
if (action === 'zone-mode') return sendZoneControl(button.dataset.id, { mode: button.dataset.value });
|
if (action === 'zone-mode') return sendZoneControl(button.dataset.id, { mode: button.dataset.value });
|
||||||
@@ -357,7 +349,6 @@ $('#tokenForm').addEventListener('submit', async event => {
|
|||||||
setFormBusy(form, true, 'auth.connecting');
|
setFormBusy(form, true, 'auth.connecting');
|
||||||
app.token = new FormData(form).get('token').trim();
|
app.token = new FormData(form).get('token').trim();
|
||||||
localStorage.setItem('gree_controller_token', app.token);
|
localStorage.setItem('gree_controller_token', app.token);
|
||||||
app.authTokenSubmitted = true;
|
|
||||||
if (app.ws) app.ws.close();
|
if (app.ws) app.ws.close();
|
||||||
try { await loadBootstrap(); }
|
try { await loadBootstrap(); }
|
||||||
finally { setFormBusy(form, false); }
|
finally { setFormBusy(form, false); }
|
||||||
@@ -643,7 +634,7 @@ $('#scheduleForm').addEventListener('submit', async event => {
|
|||||||
$('#automationForm').addEventListener('submit', async event => {
|
$('#automationForm').addEventListener('submit', async event => {
|
||||||
event.preventDefault(); const form = event.currentTarget, raw = Object.fromEntries(new FormData(form)); const id = raw.id;
|
event.preventDefault(); const form = event.currentTarget, raw = Object.fromEntries(new FormData(form)); const id = raw.id;
|
||||||
const groupTarget = raw.action_target_kind === 'group';
|
const groupTarget = raw.action_target_kind === 'group';
|
||||||
const action = {}; if (raw.action_power !== '') action.power = raw.action_power === 'true'; if (raw.action_mode) action.mode = raw.action_mode; if (!groupTarget && raw.action_target_temperature !== '') action.target_temperature = parseDecimal(raw.action_target_temperature); if (!groupTarget && raw.action_swing_vertical !== '') action.swing_vertical = Number(raw.action_swing_vertical); if (!groupTarget && raw.action_swing_horizontal !== '') action.swing_horizontal = Number(raw.action_swing_horizontal);
|
const action = {}; if (raw.action_power !== '') action.power = raw.action_power === 'true'; if (raw.action_mode) action.mode = raw.action_mode; if (!groupTarget && raw.action_target_temperature !== '') action.target_temperature = parseDecimal(raw.action_target_temperature); if (!groupTarget && raw.action_swing_vertical !== '') action.swing_vertical = raw.action_swing_vertical === 'true'; if (!groupTarget && raw.action_swing_horizontal !== '') action.swing_horizontal = raw.action_swing_horizontal === 'true';
|
||||||
const body = { name: raw.name, enabled: form.enabled.checked, trigger_kind: raw.trigger_kind, trigger_device_id: raw.trigger_device_id || null, threshold: raw.threshold === '' ? null : parseDecimal(raw.threshold), at_time: raw.at_time || null, action_device_id: groupTarget ? '' : raw.action_device_id, action_group_id: groupTarget ? (raw.action_group_id || null) : null, action_preset: groupTarget ? (raw.action_preset || null) : null, action, cooldown_seconds: Number(raw.cooldown_seconds) };
|
const body = { name: raw.name, enabled: form.enabled.checked, trigger_kind: raw.trigger_kind, trigger_device_id: raw.trigger_device_id || null, threshold: raw.threshold === '' ? null : parseDecimal(raw.threshold), at_time: raw.at_time || null, action_device_id: groupTarget ? '' : raw.action_device_id, action_group_id: groupTarget ? (raw.action_group_id || null) : null, action_preset: groupTarget ? (raw.action_preset || null) : null, action, cooldown_seconds: Number(raw.cooldown_seconds) };
|
||||||
await runFormTask(form, async () => {
|
await runFormTask(form, async () => {
|
||||||
await api(id ? `/api/automations/${encodeURIComponent(id)}` : '/api/automations', { method: id ? 'PUT' : 'POST', body }); form.closest('dialog').close(); form.reset(); await loadBootstrap(); toast(tr('common.saved'));
|
await api(id ? `/api/automations/${encodeURIComponent(id)}` : '/api/automations', { method: id ? 'PUT' : 'POST', body }); form.closest('dialog').close(); form.reset(); await loadBootstrap(); toast(tr('common.saved'));
|
||||||
|
|||||||
+10
-59
@@ -42,13 +42,6 @@ function sharedFlowInputRequiresComparison(kind) {
|
|||||||
return ['outdoor_temperature', 'device_temperature', 'zone_temperature', 'ha_state', 'ha_numeric', 'ha_attribute', 'house_mode', 'device_state', 'zone_state', 'group_state'].includes(kind);
|
return ['outdoor_temperature', 'device_temperature', 'zone_temperature', 'ha_state', 'ha_numeric', 'ha_attribute', 'house_mode', 'device_state', 'zone_state', 'group_state'].includes(kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sharedFlowInputLouverAxis(item) {
|
|
||||||
if (item?.kind !== 'device_state') return null;
|
|
||||||
if (item.config?.field === 'swing_vertical') return 'vertical';
|
|
||||||
if (item.config?.field === 'swing_horizontal') return 'horizontal';
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sharedFlowReferenceComparisonDefaults(item) {
|
function sharedFlowReferenceComparisonDefaults(item) {
|
||||||
const kind = item?.kind || '';
|
const kind = item?.kind || '';
|
||||||
if (['outdoor_temperature', 'device_temperature', 'zone_temperature'].includes(kind)) return { operator: 'lt', value: 20 };
|
if (['outdoor_temperature', 'device_temperature', 'zone_temperature'].includes(kind)) return { operator: 'lt', value: 20 };
|
||||||
@@ -56,7 +49,6 @@ function sharedFlowReferenceComparisonDefaults(item) {
|
|||||||
if (kind === 'ha_state') return { operator: 'eq', value: 'on' };
|
if (kind === 'ha_state') return { operator: 'eq', value: 'on' };
|
||||||
if (kind === 'ha_attribute') return { operator: 'eq', value: '' };
|
if (kind === 'ha_attribute') return { operator: 'eq', value: '' };
|
||||||
if (kind === 'house_mode') return { operator: 'eq', value: 'cool' };
|
if (kind === 'house_mode') return { operator: 'eq', value: 'cool' };
|
||||||
if (sharedFlowInputLouverAxis(item)) return { operator: 'eq', value: 0 };
|
|
||||||
if (kind === 'device_state') return { operator: 'eq', value: 'true' };
|
if (kind === 'device_state') return { operator: 'eq', value: 'true' };
|
||||||
if (kind === 'zone_state') return { operator: 'eq', value: 'true' };
|
if (kind === 'zone_state') return { operator: 'eq', value: 'true' };
|
||||||
if (kind === 'group_state') return { operator: 'eq', value: 'true' };
|
if (kind === 'group_state') return { operator: 'eq', value: 'true' };
|
||||||
@@ -233,8 +225,6 @@ function flowSharedInputObservation(item) {
|
|||||||
function flowSharedInputCurrentText(item, observation = flowSharedInputObservation(item)) {
|
function flowSharedInputCurrentText(item, observation = flowSharedInputObservation(item)) {
|
||||||
if (!observation?.hasValue) return '—';
|
if (!observation?.hasValue) return '—';
|
||||||
const value = observation.value;
|
const value = observation.value;
|
||||||
const louverAxis = sharedFlowInputLouverAxis(item);
|
|
||||||
if (louverAxis) return louverPositionLabel(louverAxis, value);
|
|
||||||
if (['outdoor_temperature', 'device_temperature', 'zone_temperature'].includes(item.kind)) return fmtTemp(value);
|
if (['outdoor_temperature', 'device_temperature', 'zone_temperature'].includes(item.kind)) return fmtTemp(value);
|
||||||
if (item.kind === 'ha_numeric') {
|
if (item.kind === 'ha_numeric') {
|
||||||
const numeric = Number(value);
|
const numeric = Number(value);
|
||||||
@@ -331,11 +321,7 @@ function flowNodeSummary(node) {
|
|||||||
if (node.kind === 'ha_attribute') return `${c.entity_id ? haSensorLabel(c.entity_id) : 'entity_id'}.${c.attribute || 'attribute'} ${flowOperatorLabel(c.operator)} ${c.value ?? '—'}`;
|
if (node.kind === 'ha_attribute') return `${c.entity_id ? haSensorLabel(c.entity_id) : 'entity_id'}.${c.attribute || 'attribute'} ${flowOperatorLabel(c.operator)} ${c.value ?? '—'}`;
|
||||||
if (node.kind === 'ha_available') return `${c.entity_id ? haSensorLabel(c.entity_id) : 'entity_id'} · ${tr('flow.available')}`;
|
if (node.kind === 'ha_available') return `${c.entity_id ? haSensorLabel(c.entity_id) : 'entity_id'} · ${tr('flow.available')}`;
|
||||||
if (node.kind === 'house_mode') return `${tr('flow.houseMode')} ${flowOperatorLabel(c.operator)} ${c.value || '—'}`;
|
if (node.kind === 'house_mode') return `${tr('flow.houseMode')} ${flowOperatorLabel(c.operator)} ${c.value || '—'}`;
|
||||||
if (node.kind === 'device_state') {
|
if (node.kind === 'device_state') return `${app.devices.find(d => d.id === c.device_id)?.name || tr('common.noDevice')} · ${c.field || 'state'} ${flowOperatorLabel(c.operator)} ${c.value ?? '—'}`;
|
||||||
const axis = c.field === 'swing_vertical' ? 'vertical' : c.field === 'swing_horizontal' ? 'horizontal' : null;
|
|
||||||
const value = axis ? louverPositionLabel(axis, c.value) : (c.value ?? '—');
|
|
||||||
return `${app.devices.find(d => d.id === c.device_id)?.name || tr('common.noDevice')} · ${deviceCommandFieldLabel(c.field || 'state')} ${flowOperatorLabel(c.operator)} ${value}`;
|
|
||||||
}
|
|
||||||
if (node.kind === 'zone_state') return `${app.zones.find(z => z.id === c.zone_id)?.name || tr('common.noZone')} · ${c.field || 'state'} ${flowOperatorLabel(c.operator)} ${c.value ?? '—'}`;
|
if (node.kind === 'zone_state') return `${app.zones.find(z => z.id === c.zone_id)?.name || tr('common.noZone')} · ${c.field || 'state'} ${flowOperatorLabel(c.operator)} ${c.value ?? '—'}`;
|
||||||
if (node.kind === 'group_state') return `${app.groups.find(g => g.id === c.group_id)?.name || tr('groups.group')} · ${c.field || 'power_enabled'} ${flowOperatorLabel(c.operator)} ${c.value ?? '—'}`;
|
if (node.kind === 'group_state') return `${app.groups.find(g => g.id === c.group_id)?.name || tr('groups.group')} · ${c.field || 'power_enabled'} ${flowOperatorLabel(c.operator)} ${c.value ?? '—'}`;
|
||||||
if (node.kind === 'night_mode') return tr('flow.nightModeActive');
|
if (node.kind === 'night_mode') return tr('flow.nightModeActive');
|
||||||
@@ -344,10 +330,8 @@ function flowNodeSummary(node) {
|
|||||||
const item = (app.flowSharedInputs || []).find(value => value.id === c.input_id);
|
const item = (app.flowSharedInputs || []).find(value => value.id === c.input_id);
|
||||||
if (!item) return tr('flow.sharedInputMissing');
|
if (!item) return tr('flow.sharedInputMissing');
|
||||||
if (sharedFlowInputRequiresComparison(item.kind)) {
|
if (sharedFlowInputRequiresComparison(item.kind)) {
|
||||||
const axis = sharedFlowInputLouverAxis(item);
|
|
||||||
const value = axis ? louverPositionLabel(axis, c.value) : (c.value ?? '—');
|
|
||||||
return c.operator
|
return c.operator
|
||||||
? `${item.name} · ${sharedFlowInputSourceSummary(item)} · ${flowOperatorLabel(c.operator)} ${value}`
|
? `${item.name} · ${sharedFlowInputSourceSummary(item)} · ${flowOperatorLabel(c.operator)} ${c.value ?? '—'}`
|
||||||
: `${item.name} · ${tr('flow.selectOperator')}`;
|
: `${item.name} · ${tr('flow.selectOperator')}`;
|
||||||
}
|
}
|
||||||
return `${item.name} · ${sharedFlowInputSourceSummary(item)}`;
|
return `${item.name} · ${sharedFlowInputSourceSummary(item)}`;
|
||||||
@@ -497,14 +481,12 @@ function sharedFlowReferenceComparisonFields(item, config) {
|
|||||||
if (!item || !sharedFlowInputRequiresComparison(item.kind)) return '';
|
if (!item || !sharedFlowInputRequiresComparison(item.kind)) return '';
|
||||||
const defaults = sharedFlowReferenceComparisonDefaults(item);
|
const defaults = sharedFlowReferenceComparisonDefaults(item);
|
||||||
const selected = config.operator || '';
|
const selected = config.operator || '';
|
||||||
const louverAxis = sharedFlowInputLouverAxis(item);
|
|
||||||
const numeric = ['outdoor_temperature', 'device_temperature', 'zone_temperature', 'ha_numeric'].includes(item.kind);
|
const numeric = ['outdoor_temperature', 'device_temperature', 'zone_temperature', 'ha_numeric'].includes(item.kind);
|
||||||
const fullOperators = numeric || item.kind === 'ha_attribute';
|
const fullOperators = numeric || item.kind === 'ha_attribute';
|
||||||
const operatorOptions = `${!selected ? `<option value="" selected disabled>${esc(tr('flow.selectOperator'))}</option>` : ''}${fullOperators ? flowOperatorOptions(selected) : [['eq', '='], ['neq', '≠']].map(([value, label]) => `<option value="${value}" ${value === selected ? 'selected' : ''}>${label}</option>`).join('')}`;
|
const operatorOptions = `${!selected ? `<option value="" selected disabled>${esc(tr('flow.selectOperator'))}</option>` : ''}${fullOperators ? flowOperatorOptions(selected) : [['eq', '='], ['neq', '≠']].map(([value, label]) => `<option value="${value}" ${value === selected ? 'selected' : ''}>${label}</option>`).join('')}`;
|
||||||
const value = config.value ?? defaults.value ?? '';
|
const value = config.value ?? defaults.value ?? '';
|
||||||
let valueField = `<input data-flow-config="value" value="${esc(value)}">`;
|
let valueField = `<input data-flow-config="value" value="${esc(value)}">`;
|
||||||
if (louverAxis) valueField = `<select data-flow-config="value">${louverOptions(louverAxis, value)}</select>`;
|
if (numeric) valueField = `<input type="number" step="0.1" data-flow-config="value" value="${Number(value ?? 0)}">`;
|
||||||
else if (numeric) valueField = `<input type="number" step="0.1" data-flow-config="value" value="${Number(value ?? 0)}">`;
|
|
||||||
else if (item.kind === 'house_mode') valueField = `<select data-flow-config="value">${['cool', 'heat', 'off'].map(v => `<option value="${v}" ${String(value) === v ? 'selected' : ''}>${esc(v === 'off' ? tr('common.off') : tr(`mode.${v}`))}</option>`).join('')}</select>`;
|
else if (item.kind === 'house_mode') valueField = `<select data-flow-config="value">${['cool', 'heat', 'off'].map(v => `<option value="${v}" ${String(value) === v ? 'selected' : ''}>${esc(v === 'off' ? tr('common.off') : tr(`mode.${v}`))}</option>`).join('')}</select>`;
|
||||||
return `<p class="field-note">${esc(tr('flow.sharedInputFlowComparisonHint'))}</p><div class="two"><label><span>${esc(tr('flow.operator'))}</span><select data-flow-config="operator">${operatorOptions}</select></label><label><span>${esc(tr('flow.value'))}</span>${valueField}</label></div>`;
|
return `<p class="field-note">${esc(tr('flow.sharedInputFlowComparisonHint'))}</p><div class="two"><label><span>${esc(tr('flow.operator'))}</span><select data-flow-config="operator">${operatorOptions}</select></label><label><span>${esc(tr('flow.value'))}</span>${valueField}</label></div>`;
|
||||||
}
|
}
|
||||||
@@ -534,7 +516,7 @@ function renderFlowInspector() {
|
|||||||
else if (node.kind === 'ha_attribute') fields = `<label><span>entity_id</span><input data-flow-config="entity_id" list="haEntitySuggestions" value="${esc(c.entity_id || '')}" placeholder="climate.living_room"></label><label><span>${esc(tr('flow.attribute'))}</span><input data-flow-config="attribute" value="${esc(c.attribute || '')}" placeholder="hvac_action"></label>${flowTextComparisonFields(c, true)}`;
|
else if (node.kind === 'ha_attribute') fields = `<label><span>entity_id</span><input data-flow-config="entity_id" list="haEntitySuggestions" value="${esc(c.entity_id || '')}" placeholder="climate.living_room"></label><label><span>${esc(tr('flow.attribute'))}</span><input data-flow-config="attribute" value="${esc(c.attribute || '')}" placeholder="hvac_action"></label>${flowTextComparisonFields(c, true)}`;
|
||||||
else if (node.kind === 'ha_available') fields = `<label><span>entity_id</span><input data-flow-config="entity_id" list="haEntitySuggestions" value="${esc(c.entity_id || '')}" placeholder="binary_sensor.window"></label><p class="field-note">${esc(tr('flow.haAvailableHint'))}</p>`;
|
else if (node.kind === 'ha_available') fields = `<label><span>entity_id</span><input data-flow-config="entity_id" list="haEntitySuggestions" value="${esc(c.entity_id || '')}" placeholder="binary_sensor.window"></label><p class="field-note">${esc(tr('flow.haAvailableHint'))}</p>`;
|
||||||
else if (node.kind === 'house_mode') fields = `<div class="two"><label><span>${esc(tr('flow.operator'))}</span><select data-flow-config="operator"><option value="eq" ${c.operator !== 'neq' ? 'selected' : ''}>=</option><option value="neq" ${c.operator === 'neq' ? 'selected' : ''}>≠</option></select></label><label><span>${esc(tr('flow.houseMode'))}</span><select data-flow-config="value">${['cool', 'heat', 'off'].map(v => `<option value="${v}" ${c.value === v ? 'selected' : ''}>${esc(v === 'off' ? tr('common.off') : tr(`mode.${v}`))}</option>`).join('')}</select></label></div>`;
|
else if (node.kind === 'house_mode') fields = `<div class="two"><label><span>${esc(tr('flow.operator'))}</span><select data-flow-config="operator"><option value="eq" ${c.operator !== 'neq' ? 'selected' : ''}>=</option><option value="neq" ${c.operator === 'neq' ? 'selected' : ''}>≠</option></select></label><label><span>${esc(tr('flow.houseMode'))}</span><select data-flow-config="value">${['cool', 'heat', 'off'].map(v => `<option value="${v}" ${c.value === v ? 'selected' : ''}>${esc(v === 'off' ? tr('common.off') : tr(`mode.${v}`))}</option>`).join('')}</select></label></div>`;
|
||||||
else if (node.kind === 'device_state') fields = `<label><span>${esc(tr('common.device'))}</span><select data-flow-config="device_id">${flowSelectOptions(app.devices, c.device_id)}</select></label><label><span>${esc(tr('flow.field'))}</span><select data-flow-config="field">${['enabled', 'online', 'power', 'mode', 'fan_speed', 'swing_vertical', 'swing_horizontal', 'quiet', 'turbo', 'light', 'air', 'xfan', 'health', 'sleep'].map(v => `<option value="${v}" ${c.field === v ? 'selected' : ''}>${esc(deviceCommandFieldLabel(v))}</option>`).join('')}</select></label>${['swing_vertical', 'swing_horizontal'].includes(c.field) ? flowLouverComparisonFields(c, c.field === 'swing_horizontal' ? 'horizontal' : 'vertical') : flowTextComparisonFields(c)}`;
|
else if (node.kind === 'device_state') fields = `<label><span>${esc(tr('common.device'))}</span><select data-flow-config="device_id">${flowSelectOptions(app.devices, c.device_id)}</select></label><label><span>${esc(tr('flow.field'))}</span><select data-flow-config="field">${['enabled', 'online', 'power', 'mode', 'fan_speed', 'swing_vertical', 'swing_horizontal', 'quiet', 'turbo', 'light', 'air', 'xfan', 'health', 'sleep'].map(v => `<option value="${v}" ${c.field === v ? 'selected' : ''}>${esc(deviceCommandFieldLabel(v))}</option>`).join('')}</select></label>${flowTextComparisonFields(c)}`;
|
||||||
else if (node.kind === 'zone_state') fields = `<label><span>${esc(tr('common.zone'))}</span><select data-flow-config="zone_id">${flowSelectOptions(app.zones, c.zone_id)}</select></label><label><span>${esc(tr('flow.field'))}</span><select data-flow-config="field">${['enabled', 'mode', 'active_preset', 'demand', 'control_owner', 'device_manual_override', 'local_thermostat_power'].map(v => `<option value="${v}" ${c.field === v ? 'selected' : ''}>${esc(v)}</option>`).join('')}</select></label>${flowTextComparisonFields(c)}`;
|
else if (node.kind === 'zone_state') fields = `<label><span>${esc(tr('common.zone'))}</span><select data-flow-config="zone_id">${flowSelectOptions(app.zones, c.zone_id)}</select></label><label><span>${esc(tr('flow.field'))}</span><select data-flow-config="field">${['enabled', 'mode', 'active_preset', 'demand', 'control_owner', 'device_manual_override', 'local_thermostat_power'].map(v => `<option value="${v}" ${c.field === v ? 'selected' : ''}>${esc(v)}</option>`).join('')}</select></label>${flowTextComparisonFields(c)}`;
|
||||||
else if (node.kind === 'group_state') fields = `<label><span>${esc(tr('groups.group'))}</span><select data-flow-config="group_id">${flowSelectOptions(app.groups, c.group_id)}</select></label><label><span>${esc(tr('flow.field'))}</span><select data-flow-config="field"><option value="power_enabled" selected>power_enabled</option></select></label>${flowTextComparisonFields(c)}`;
|
else if (node.kind === 'group_state') fields = `<label><span>${esc(tr('groups.group'))}</span><select data-flow-config="group_id">${flowSelectOptions(app.groups, c.group_id)}</select></label><label><span>${esc(tr('flow.field'))}</span><select data-flow-config="field"><option value="power_enabled" selected>power_enabled</option></select></label>${flowTextComparisonFields(c)}`;
|
||||||
else if (node.kind === 'night_mode') fields = `<p class="field-note">${esc(tr('flow.nightModeHint'))}</p>`;
|
else if (node.kind === 'night_mode') fields = `<p class="field-note">${esc(tr('flow.nightModeHint'))}</p>`;
|
||||||
@@ -548,7 +530,7 @@ function renderFlowInspector() {
|
|||||||
else if (node.kind === 'logic_and') fields = `<p class="field-note">${esc(tr('flow.andHint'))}</p>`;
|
else if (node.kind === 'logic_and') fields = `<p class="field-note">${esc(tr('flow.andHint'))}</p>`;
|
||||||
else if (node.kind === 'logic_or') fields = `<p class="field-note">${esc(tr('flow.orHint'))}</p>`;
|
else if (node.kind === 'logic_or') fields = `<p class="field-note">${esc(tr('flow.orHint'))}</p>`;
|
||||||
else if (node.kind === 'logic_not') fields = `<p class="field-note">${esc(tr('flow.notHint'))}</p>`;
|
else if (node.kind === 'logic_not') fields = `<p class="field-note">${esc(tr('flow.notHint'))}</p>`;
|
||||||
else if (node.kind === 'zone_thermostat') fields = `<label><span>${esc(tr('common.zone'))}</span><select data-flow-config="zone_id">${flowSelectOptions(app.zones, c.zone_id)}</select></label><div class="two"><label><span>${esc(tr('schedules.profile'))}</span><select data-flow-config="preset">${['auto', 'comfort', 'sleep', 'away', 'custom'].map(v => `<option value="${v}" ${c.preset === v ? 'selected' : ''}>${esc(zonePresetLabel(v))}</option>`).join('')}</select></label><label><span>${esc(tr('common.targetC'))}</span><input type="number" min="8" max="30" step="0.1" data-flow-config="setpoint" value="${Number(c.setpoint ?? 21)}"></label></div><div class="two"><label><span>${esc(tr('common.mode'))}</span><select data-flow-config="mode"><option value="auto" ${c.mode === 'auto' ? 'selected' : ''}>Auto</option><option value="heat" ${c.mode === 'heat' ? 'selected' : ''}>${esc(tr('mode.heat'))}</option><option value="cool" ${c.mode === 'cool' ? 'selected' : ''}>${esc(tr('mode.cool'))}</option></select></label><label><span>${esc(tr('common.power'))}</span><select data-flow-config="power"><option value="" ${c.power == null ? 'selected' : ''}>${esc(tr('actions.noChange'))}</option><option value="true" ${c.power === true ? 'selected' : ''}>${esc(tr('common.on'))}</option><option value="false" ${c.power === false ? 'selected' : ''}>${esc(tr('common.off'))}</option></select></label></div><details class="flow-device-options"><summary>${esc(tr('flow.thermostatDeviceOptions'))}</summary><div class="two">${flowOptionalLouverField(c, 'swing_vertical', 'vertical')}${flowOptionalLouverField(c, 'swing_horizontal', 'horizontal')}</div><p class="field-note">${esc(tr('flow.thermostatDeviceOptionsHint'))}</p></details><label><span>${esc(tr('common.cooldownSeconds'))}</span><input type="number" min="30" data-flow-config="cooldown_seconds" value="${Number(c.cooldown_seconds || 60)}"></label>`;
|
else if (node.kind === 'zone_thermostat') fields = `<label><span>${esc(tr('common.zone'))}</span><select data-flow-config="zone_id">${flowSelectOptions(app.zones, c.zone_id)}</select></label><div class="two"><label><span>${esc(tr('schedules.profile'))}</span><select data-flow-config="preset">${['auto', 'comfort', 'sleep', 'away', 'custom'].map(v => `<option value="${v}" ${c.preset === v ? 'selected' : ''}>${esc(zonePresetLabel(v))}</option>`).join('')}</select></label><label><span>${esc(tr('common.targetC'))}</span><input type="number" min="8" max="30" step="0.1" data-flow-config="setpoint" value="${Number(c.setpoint ?? 21)}"></label></div><div class="two"><label><span>${esc(tr('common.mode'))}</span><select data-flow-config="mode"><option value="auto" ${c.mode === 'auto' ? 'selected' : ''}>Auto</option><option value="heat" ${c.mode === 'heat' ? 'selected' : ''}>${esc(tr('mode.heat'))}</option><option value="cool" ${c.mode === 'cool' ? 'selected' : ''}>${esc(tr('mode.cool'))}</option></select></label><label><span>${esc(tr('common.power'))}</span><select data-flow-config="power"><option value="" ${c.power == null ? 'selected' : ''}>${esc(tr('actions.noChange'))}</option><option value="true" ${c.power === true ? 'selected' : ''}>${esc(tr('common.on'))}</option><option value="false" ${c.power === false ? 'selected' : ''}>${esc(tr('common.off'))}</option></select></label></div><details class="flow-device-options"><summary>${esc(tr('flow.thermostatDeviceOptions'))}</summary><div class="two">${flowOptionalBoolField(c, 'swing_vertical')}${flowOptionalBoolField(c, 'swing_horizontal')}</div><p class="field-note">${esc(tr('flow.thermostatDeviceOptionsHint'))}</p></details><label><span>${esc(tr('common.cooldownSeconds'))}</span><input type="number" min="30" data-flow-config="cooldown_seconds" value="${Number(c.cooldown_seconds || 60)}"></label>`;
|
||||||
else if (node.kind === 'device_action') fields = `<label><span>${esc(tr('common.device'))}</span><select data-flow-config="device_id">${flowSelectOptions(app.devices, c.device_id)}</select></label>${flowActionFields(c, false)}`;
|
else if (node.kind === 'device_action') fields = `<label><span>${esc(tr('common.device'))}</span><select data-flow-config="device_id">${flowSelectOptions(app.devices, c.device_id)}</select></label>${flowActionFields(c, false)}`;
|
||||||
else if (node.kind === 'group_action') fields = `<label><span>${esc(tr('groups.group'))}</span><select data-flow-config="group_id">${flowSelectOptions(app.groups, c.group_id)}</select></label>${flowActionFields(c, true)}`;
|
else if (node.kind === 'group_action') fields = `<label><span>${esc(tr('groups.group'))}</span><select data-flow-config="group_id">${flowSelectOptions(app.groups, c.group_id)}</select></label>${flowActionFields(c, true)}`;
|
||||||
else if (node.kind === 'ha_service_action') fields = `<div class="two"><label><span>domain</span><input data-flow-config="domain" value="${esc(c.domain || 'switch')}" placeholder="switch"></label><label><span>service</span><input data-flow-config="service" value="${esc(c.service || 'turn_on')}" placeholder="turn_on"></label></div><p class="field-note">${esc(tr('flow.serviceExample'))}</p><label><span>entity_id</span><input data-flow-config="entity_id" list="haEntitySuggestions" value="${esc(c.entity_id || '')}" placeholder="switch.gas"></label><label><span>${esc(tr('flow.serviceData'))}</span><textarea rows="5" data-flow-config="data_json">${esc(JSON.stringify(c.data || {}, null, 2))}</textarea></label><label><span>${esc(tr('common.cooldownSeconds'))}</span><input type="number" min="30" data-flow-config="cooldown_seconds" value="${Number(c.cooldown_seconds || 60)}"></label>`;
|
else if (node.kind === 'ha_service_action') fields = `<div class="two"><label><span>domain</span><input data-flow-config="domain" value="${esc(c.domain || 'switch')}" placeholder="switch"></label><label><span>service</span><input data-flow-config="service" value="${esc(c.service || 'turn_on')}" placeholder="turn_on"></label></div><p class="field-note">${esc(tr('flow.serviceExample'))}</p><label><span>entity_id</span><input data-flow-config="entity_id" list="haEntitySuggestions" value="${esc(c.entity_id || '')}" placeholder="switch.gas"></label><label><span>${esc(tr('flow.serviceData'))}</span><textarea rows="5" data-flow-config="data_json">${esc(JSON.stringify(c.data || {}, null, 2))}</textarea></label><label><span>${esc(tr('common.cooldownSeconds'))}</span><input type="number" min="30" data-flow-config="cooldown_seconds" value="${Number(c.cooldown_seconds || 60)}"></label>`;
|
||||||
@@ -556,18 +538,10 @@ function renderFlowInspector() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function flowTextComparisonFields(c, numeric = false) { const options = numeric ? flowOperatorOptions(c.operator || 'eq') : [['eq', '='], ['neq', '≠']].map(([v, l]) => `<option value="${v}" ${v === (c.operator || 'eq') ? 'selected' : ''}>${l}</option>`).join(''); return `<div class="two"><label><span>${esc(tr('flow.operator'))}</span><select data-flow-config="operator">${options}</select></label><label><span>${esc(tr('flow.value'))}</span><input data-flow-config="value" value="${esc(c.value ?? '')}"></label></div>`; }
|
function flowTextComparisonFields(c, numeric = false) { const options = numeric ? flowOperatorOptions(c.operator || 'eq') : [['eq', '='], ['neq', '≠']].map(([v, l]) => `<option value="${v}" ${v === (c.operator || 'eq') ? 'selected' : ''}>${l}</option>`).join(''); return `<div class="two"><label><span>${esc(tr('flow.operator'))}</span><select data-flow-config="operator">${options}</select></label><label><span>${esc(tr('flow.value'))}</span><input data-flow-config="value" value="${esc(c.value ?? '')}"></label></div>`; }
|
||||||
function flowLouverComparisonFields(c, axis) {
|
|
||||||
const options = [['eq', '='], ['neq', '≠']].map(([v, l]) => `<option value="${v}" ${v === (c.operator || 'eq') ? 'selected' : ''}>${l}</option>`).join('');
|
|
||||||
const value = Number.isInteger(Number(c.value)) ? Number(c.value) : 0;
|
|
||||||
return `<div class="two"><label><span>${esc(tr('flow.operator'))}</span><select data-flow-config="operator">${options}</select></label><label><span>${esc(tr('flow.value'))}</span><select data-flow-config="value">${louverOptions(axis, value)}</select></label></div>`;
|
|
||||||
}
|
|
||||||
function flowComparisonFields(c, temperature = true) { return `<div class="two"><label><span>${esc(tr('flow.operator'))}</span><select data-flow-config="operator">${flowOperatorOptions(c.operator || 'lt')}</select></label><label><span>${esc(temperature ? tr('automations.thresholdC') : tr('flow.value'))}</span><input type="number" step="0.1" data-flow-config="value" value="${Number(c.value ?? 0)}"></label></div>`; }
|
function flowComparisonFields(c, temperature = true) { return `<div class="two"><label><span>${esc(tr('flow.operator'))}</span><select data-flow-config="operator">${flowOperatorOptions(c.operator || 'lt')}</select></label><label><span>${esc(temperature ? tr('automations.thresholdC') : tr('flow.value'))}</span><input type="number" step="0.1" data-flow-config="value" value="${Number(c.value ?? 0)}"></label></div>`; }
|
||||||
function flowOptionalBoolField(c, key) {
|
function flowOptionalBoolField(c, key) {
|
||||||
return `<label><span>${esc(deviceCommandFieldLabel(key))}</span><select data-flow-config="${esc(key)}"><option value="" ${c[key] == null ? 'selected' : ''}>${esc(tr('actions.noChange'))}</option><option value="true" ${c[key] === true ? 'selected' : ''}>${esc(tr('common.on'))}</option><option value="false" ${c[key] === false ? 'selected' : ''}>${esc(tr('common.off'))}</option></select></label>`;
|
return `<label><span>${esc(deviceCommandFieldLabel(key))}</span><select data-flow-config="${esc(key)}"><option value="" ${c[key] == null ? 'selected' : ''}>${esc(tr('actions.noChange'))}</option><option value="true" ${c[key] === true ? 'selected' : ''}>${esc(tr('common.on'))}</option><option value="false" ${c[key] === false ? 'selected' : ''}>${esc(tr('common.off'))}</option></select></label>`;
|
||||||
}
|
}
|
||||||
function flowOptionalLouverField(c, key, axis) {
|
|
||||||
return `<label><span>${esc(deviceCommandFieldLabel(key))}</span><select data-flow-config="${esc(key)}">${louverOptions(axis, c[key], { includeNoChange: true })}</select></label>`;
|
|
||||||
}
|
|
||||||
function flowActionFields(c, group) {
|
function flowActionFields(c, group) {
|
||||||
const modes = group ? ['auto', 'house', 'heat', 'cool'] : ['auto', 'cool', 'dry', 'fan', 'heat'];
|
const modes = group ? ['auto', 'house', 'heat', 'cool'] : ['auto', 'cool', 'dry', 'fan', 'heat'];
|
||||||
const modeOptions = modes.map(v => `<option value="${v}" ${c.mode === v ? 'selected' : ''}>${esc(v === 'auto' ? 'Auto' : v === 'house' ? tr('flow.houseMode') : (tr(`mode.${v}`) || v))}</option>`).join('');
|
const modeOptions = modes.map(v => `<option value="${v}" ${c.mode === v ? 'selected' : ''}>${esc(v === 'auto' ? 'Auto' : v === 'house' ? tr('flow.houseMode') : (tr(`mode.${v}`) || v))}</option>`).join('');
|
||||||
@@ -575,7 +549,7 @@ function flowActionFields(c, group) {
|
|||||||
const target = group
|
const target = group
|
||||||
? `<label><span>${esc(tr('groups.profile'))}</span><select data-flow-config="preset"><option value="">${esc(tr('actions.noChange'))}</option>${['auto', 'comfort', 'sleep', 'away', 'custom'].map(v => `<option value="${v}" ${c.preset === v ? 'selected' : ''}>${esc(zonePresetLabel(v))}</option>`).join('')}</select></label><label><span>${esc(tr('common.targetC'))}</span><input type="number" min="8" max="30" step="0.1" data-flow-config="setpoint" value="${c.setpoint ?? 21}"></label>`
|
? `<label><span>${esc(tr('groups.profile'))}</span><select data-flow-config="preset"><option value="">${esc(tr('actions.noChange'))}</option>${['auto', 'comfort', 'sleep', 'away', 'custom'].map(v => `<option value="${v}" ${c.preset === v ? 'selected' : ''}>${esc(zonePresetLabel(v))}</option>`).join('')}</select></label><label><span>${esc(tr('common.targetC'))}</span><input type="number" min="8" max="30" step="0.1" data-flow-config="setpoint" value="${c.setpoint ?? 21}"></label>`
|
||||||
: `<label><span>${esc(tr('common.targetC'))}</span><input type="number" min="8" max="30" step="0.5" data-flow-config="target_temperature" value="${c.target_temperature ?? ''}"></label>`;
|
: `<label><span>${esc(tr('common.targetC'))}</span><input type="number" min="8" max="30" step="0.5" data-flow-config="target_temperature" value="${c.target_temperature ?? ''}"></label>`;
|
||||||
const deviceOptions = group ? '' : `<details class="flow-device-options"><summary>${esc(tr('flow.deviceOptions'))}</summary><div class="two"><label><span>${esc(deviceCommandFieldLabel('fan_speed'))}</span><select data-flow-config="fan_speed"><option value="" ${c.fan_speed == null ? 'selected' : ''}>${esc(tr('actions.noChange'))}</option>${[0, 1, 2, 3, 4, 5].map(v => `<option value="${v}" ${Number(c.fan_speed) === v ? 'selected' : ''}>${v}</option>`).join('')}</select></label>${flowOptionalLouverField(c, 'swing_vertical', 'vertical')}${flowOptionalLouverField(c, 'swing_horizontal', 'horizontal')}${flowOptionalBoolField(c, 'quiet')}${flowOptionalBoolField(c, 'turbo')}${flowOptionalBoolField(c, 'light')}${flowOptionalBoolField(c, 'air')}${flowOptionalBoolField(c, 'xfan')}${flowOptionalBoolField(c, 'health')}${flowOptionalBoolField(c, 'sleep')}</div><p class="field-note">${esc(tr('flow.deviceOptionsHint'))}</p></details>`;
|
const deviceOptions = group ? '' : `<details class="flow-device-options"><summary>${esc(tr('flow.deviceOptions'))}</summary><div class="two"><label><span>${esc(deviceCommandFieldLabel('fan_speed'))}</span><select data-flow-config="fan_speed"><option value="" ${c.fan_speed == null ? 'selected' : ''}>${esc(tr('actions.noChange'))}</option>${[0, 1, 2, 3, 4, 5].map(v => `<option value="${v}" ${Number(c.fan_speed) === v ? 'selected' : ''}>${v}</option>`).join('')}</select></label>${flowOptionalBoolField(c, 'swing_vertical')}${flowOptionalBoolField(c, 'swing_horizontal')}${flowOptionalBoolField(c, 'quiet')}${flowOptionalBoolField(c, 'turbo')}${flowOptionalBoolField(c, 'light')}${flowOptionalBoolField(c, 'air')}${flowOptionalBoolField(c, 'xfan')}${flowOptionalBoolField(c, 'health')}${flowOptionalBoolField(c, 'sleep')}</div><p class="field-note">${esc(tr('flow.deviceOptionsHint'))}</p></details>`;
|
||||||
return `${base}${target}${deviceOptions}<label><span>${esc(tr('common.cooldownSeconds'))}</span><input type="number" min="30" data-flow-config="cooldown_seconds" value="${Number(c.cooldown_seconds || 60)}"></label>`;
|
return `${base}${target}${deviceOptions}<label><span>${esc(tr('common.cooldownSeconds'))}</span><input type="number" min="30" data-flow-config="cooldown_seconds" value="${Number(c.cooldown_seconds || 60)}"></label>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -980,28 +954,13 @@ function localDateTimeInputValue(date = new Date()) {
|
|||||||
function flowSimulationOverrideNodes() {
|
function flowSimulationOverrideNodes() {
|
||||||
return (app.flowDraft?.nodes || []).filter(node => ['outdoor_temperature', 'device_temperature', 'zone_temperature', 'ha_state', 'ha_numeric', 'ha_attribute', 'ha_available', 'house_mode', 'device_state', 'zone_state', 'group_state', 'night_mode', 'shared_input'].includes(node.kind));
|
return (app.flowDraft?.nodes || []).filter(node => ['outdoor_temperature', 'device_temperature', 'zone_temperature', 'ha_state', 'ha_numeric', 'ha_attribute', 'ha_available', 'house_mode', 'device_state', 'zone_state', 'group_state', 'night_mode', 'shared_input'].includes(node.kind));
|
||||||
}
|
}
|
||||||
function flowSimulationLouverAxis(node) {
|
|
||||||
if (node?.kind === 'device_state') {
|
|
||||||
if (node.config?.field === 'swing_vertical') return 'vertical';
|
|
||||||
if (node.config?.field === 'swing_horizontal') return 'horizontal';
|
|
||||||
}
|
|
||||||
if (node?.kind === 'shared_input') {
|
|
||||||
const item = (app.flowSharedInputs || []).find(value => value.id === node.config?.input_id);
|
|
||||||
return sharedFlowInputLouverAxis(item);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
function renderFlowSimulationOverrides() {
|
function renderFlowSimulationOverrides() {
|
||||||
const host = $('#flowSimulationOverrides'); if (!host) return;
|
const host = $('#flowSimulationOverrides'); if (!host) return;
|
||||||
const nodes = flowSimulationOverrideNodes();
|
const nodes = flowSimulationOverrideNodes();
|
||||||
host.innerHTML = nodes.length ? nodes.map(node => {
|
host.innerHTML = nodes.length ? nodes.map(node => {
|
||||||
const effectiveKind = node.kind === 'shared_input' ? (app.flowSharedInputs || []).find(item => item.id === node.config?.input_id)?.kind : node.kind;
|
const effectiveKind = node.kind === 'shared_input' ? (app.flowSharedInputs || []).find(item => item.id === node.config?.input_id)?.kind : node.kind;
|
||||||
const louverAxis = flowSimulationLouverAxis(node);
|
|
||||||
const numeric = ['outdoor_temperature', 'device_temperature', 'zone_temperature', 'ha_numeric'].includes(effectiveKind);
|
const numeric = ['outdoor_temperature', 'device_temperature', 'zone_temperature', 'ha_numeric'].includes(effectiveKind);
|
||||||
const control = louverAxis
|
return `<label><span>${esc(flowNodeTitle(FLOW_NODE_META[node.kind]))} · ${esc(flowNodeSummary(node))}</span><input ${numeric ? 'type="number" step="0.1"' : 'type="text"'} data-flow-sim-node="${esc(node.id)}" placeholder="${esc(tr('flow.useLiveValue'))}"></label>`;
|
||||||
? `<select data-flow-sim-node="${esc(node.id)}"><option value="">${esc(tr('flow.useLiveValue'))}</option>${louverOptions(louverAxis, null)}</select>`
|
|
||||||
: `<input ${numeric ? 'type="number" step="0.1"' : 'type="text"'} data-flow-sim-node="${esc(node.id)}" placeholder="${esc(tr('flow.useLiveValue'))}">`;
|
|
||||||
return `<label><span>${esc(flowNodeTitle(FLOW_NODE_META[node.kind]))} · ${esc(flowNodeSummary(node))}</span>${control}</label>`;
|
|
||||||
}).join('') : `<p class="field-note">${esc(tr('flow.noSimulationOverrides'))}</p>`;
|
}).join('') : `<p class="field-note">${esc(tr('flow.noSimulationOverrides'))}</p>`;
|
||||||
}
|
}
|
||||||
function collectFlowSimulationOverrides() {
|
function collectFlowSimulationOverrides() {
|
||||||
@@ -1010,7 +969,7 @@ function collectFlowSimulationOverrides() {
|
|||||||
if (input.value.trim() === '') return;
|
if (input.value.trim() === '') return;
|
||||||
const node = flowNodeById(input.dataset.flowSimNode); let value = input.value.trim();
|
const node = flowNodeById(input.dataset.flowSimNode); let value = input.value.trim();
|
||||||
const effectiveKind = node?.kind === 'shared_input' ? (app.flowSharedInputs || []).find(item => item.id === node.config?.input_id)?.kind : node?.kind;
|
const effectiveKind = node?.kind === 'shared_input' ? (app.flowSharedInputs || []).find(item => item.id === node.config?.input_id)?.kind : node?.kind;
|
||||||
if (node && (flowSimulationLouverAxis(node) || ['outdoor_temperature', 'device_temperature', 'zone_temperature', 'ha_numeric'].includes(effectiveKind))) value = Number(value);
|
if (node && ['outdoor_temperature', 'device_temperature', 'zone_temperature', 'ha_numeric'].includes(effectiveKind)) value = Number(value);
|
||||||
else if (/^(true|false)$/i.test(value)) value = value.toLowerCase() === 'true';
|
else if (/^(true|false)$/i.test(value)) value = value.toLowerCase() === 'true';
|
||||||
result[input.dataset.flowSimNode] = value;
|
result[input.dataset.flowSimNode] = value;
|
||||||
});
|
});
|
||||||
@@ -1166,18 +1125,10 @@ function updateFlowConfig(input) {
|
|||||||
}
|
}
|
||||||
else if (node.kind === 'shared_input' && key === 'value') {
|
else if (node.kind === 'shared_input' && key === 'value') {
|
||||||
const item = (app.flowSharedInputs || []).find(value => value.id === node.config.input_id);
|
const item = (app.flowSharedInputs || []).find(value => value.id === node.config.input_id);
|
||||||
const numeric = Boolean(sharedFlowInputLouverAxis(item)) || ['outdoor_temperature', 'device_temperature', 'zone_temperature', 'ha_numeric'].includes(item?.kind);
|
const numeric = ['outdoor_temperature', 'device_temperature', 'zone_temperature', 'ha_numeric'].includes(item?.kind);
|
||||||
node.config.value = numeric ? (input.value === '' ? null : Number(input.value)) : input.value;
|
node.config.value = numeric ? (input.value === '' ? null : Number(input.value)) : input.value;
|
||||||
}
|
}
|
||||||
else if (node.kind === 'device_state' && key === 'field') {
|
else if (['power', 'swing_vertical', 'swing_horizontal', 'quiet', 'turbo', 'light', 'air', 'xfan', 'health', 'sleep'].includes(key)) node.config[key] = input.value === '' ? null : input.value === 'true';
|
||||||
const wasLouver = ['swing_vertical', 'swing_horizontal'].includes(node.config.field);
|
|
||||||
node.config.field = input.value;
|
|
||||||
if (['swing_vertical', 'swing_horizontal'].includes(input.value)) node.config.value = 0;
|
|
||||||
else if (wasLouver) node.config.value = '';
|
|
||||||
}
|
|
||||||
else if (node.kind === 'device_state' && key === 'value' && ['swing_vertical', 'swing_horizontal'].includes(node.config.field)) node.config.value = Number(input.value);
|
|
||||||
else if (['swing_vertical', 'swing_horizontal'].includes(key)) node.config[key] = input.value === '' ? null : Number(input.value);
|
|
||||||
else if (['power', 'quiet', 'turbo', 'light', 'air', 'xfan', 'health', 'sleep'].includes(key)) node.config[key] = input.value === '' ? null : input.value === 'true';
|
|
||||||
else if (key === 'value' && node.kind === 'constant') node.config[key] = input.value === 'true';
|
else if (key === 'value' && node.kind === 'constant') node.config[key] = input.value === 'true';
|
||||||
else if (key === 'data_json' && node.kind === 'ha_service_action') { try { node.config.data = JSON.parse(input.value || '{}'); } catch { toast(tr('flow.invalidJson'), true); return; } }
|
else if (key === 'data_json' && node.kind === 'ha_service_action') { try { node.config.data = JSON.parse(input.value || '{}'); } catch { toast(tr('flow.invalidJson'), true); return; } }
|
||||||
else if (key === 'seconds' && ['stable_for', 'delay'].includes(node.kind)) node.config.seconds = Math.max(1, Number(input.value || 1));
|
else if (key === 'seconds' && ['stable_for', 'delay'].includes(node.kind)) node.config.seconds = Math.max(1, Number(input.value || 1));
|
||||||
|
|||||||
@@ -131,17 +131,58 @@ async function handleWebSocketMessage(event) {
|
|||||||
} catch (_) { }
|
} catch (_) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const WEBSOCKET_RECONNECT_DELAY_MS = 3000;
|
||||||
|
const WEBSOCKET_ERROR_DELAY_MS = 15000;
|
||||||
|
|
||||||
|
function clearWebSocketOutage() {
|
||||||
|
app.wsOutageStartedAt = 0;
|
||||||
|
clearTimeout(app.wsErrorTimer);
|
||||||
|
app.wsErrorTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function markWebSocketOutage() {
|
||||||
|
if (!app.wsOutageStartedAt) app.wsOutageStartedAt = Date.now();
|
||||||
|
const elapsed = Date.now() - app.wsOutageStartedAt;
|
||||||
|
if (elapsed >= WEBSOCKET_ERROR_DELAY_MS) {
|
||||||
|
updateConnectionIndicator('connectionError');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updateConnectionIndicator('reconnecting');
|
||||||
|
if (app.wsErrorTimer) return;
|
||||||
|
app.wsErrorTimer = setTimeout(() => {
|
||||||
|
app.wsErrorTimer = null;
|
||||||
|
if (app.ws?.readyState === WebSocket.OPEN || !app.wsOutageStartedAt) return;
|
||||||
|
updateConnectionIndicator('connectionError');
|
||||||
|
}, WEBSOCKET_ERROR_DELAY_MS - elapsed);
|
||||||
|
}
|
||||||
|
|
||||||
function connectWebSocket() {
|
function connectWebSocket() {
|
||||||
if (app.ws && [WebSocket.OPEN, WebSocket.CONNECTING].includes(app.ws.readyState)) return;
|
if (app.ws && [WebSocket.OPEN, WebSocket.CONNECTING].includes(app.ws.readyState)) return;
|
||||||
clearTimeout(app.wsTimer);
|
clearTimeout(app.wsTimer);
|
||||||
|
app.wsTimer = null;
|
||||||
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
const query = app.token ? `?token=${encodeURIComponent(app.token)}` : '';
|
const query = app.token ? `?token=${encodeURIComponent(app.token)}` : '';
|
||||||
const ws = new WebSocket(`${protocol}//${location.host}${withBase('/ws')}${query}`); app.ws = ws;
|
const ws = new WebSocket(`${protocol}//${location.host}${withBase('/ws')}${query}`); app.ws = ws;
|
||||||
ws.onopen = () => updateConnectionIndicator('connected');
|
ws.onopen = () => {
|
||||||
ws.onclose = () => { app.controlPlanPushReady = false; updateConnectionIndicator('disconnected'); scheduleControlPlanLoad(0); app.wsTimer = setTimeout(connectWebSocket, 3000); };
|
if (app.ws !== ws) return;
|
||||||
ws.onerror = () => updateConnectionIndicator('connectionError');
|
clearWebSocketOutage();
|
||||||
|
updateConnectionIndicator('connected');
|
||||||
|
};
|
||||||
|
ws.onclose = () => {
|
||||||
|
if (app.ws !== ws) return;
|
||||||
|
app.controlPlanPushReady = false;
|
||||||
|
markWebSocketOutage();
|
||||||
|
scheduleControlPlanLoad(0);
|
||||||
|
app.wsTimer = setTimeout(connectWebSocket, WEBSOCKET_RECONNECT_DELAY_MS);
|
||||||
|
};
|
||||||
|
// Browser WebSocket errors are transport-level signals and are normally followed by `close`.
|
||||||
|
// Let `close` drive reconnect state so a transient error does not look like an application failure.
|
||||||
|
ws.onerror = () => {
|
||||||
|
if (app.ws === ws) console.warn('WebSocket transport error; waiting for close/reconnect');
|
||||||
|
};
|
||||||
let messageQueue = Promise.resolve();
|
let messageQueue = Promise.resolve();
|
||||||
ws.onmessage = event => {
|
ws.onmessage = event => {
|
||||||
|
if (app.ws !== ws) return;
|
||||||
messageQueue = messageQueue.then(() => handleWebSocketMessage(event)).catch(() => { });
|
messageQueue = messageQueue.then(() => handleWebSocketMessage(event)).catch(() => { });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ function sharedFlowInputSourceSummary(item) {
|
|||||||
if (item.kind === 'ha_state' || item.kind === 'ha_numeric' || item.kind === 'ha_available') return c.entity_id ? haSensorLabel(c.entity_id) : 'entity_id';
|
if (item.kind === 'ha_state' || item.kind === 'ha_numeric' || item.kind === 'ha_available') return c.entity_id ? haSensorLabel(c.entity_id) : 'entity_id';
|
||||||
if (item.kind === 'ha_attribute') return `${c.entity_id ? haSensorLabel(c.entity_id) : 'entity_id'}.${c.attribute || 'attribute'}`;
|
if (item.kind === 'ha_attribute') return `${c.entity_id ? haSensorLabel(c.entity_id) : 'entity_id'}.${c.attribute || 'attribute'}`;
|
||||||
if (item.kind === 'house_mode') return tr('flow.houseMode');
|
if (item.kind === 'house_mode') return tr('flow.houseMode');
|
||||||
if (item.kind === 'device_state') return `${app.devices.find(value => value.id === c.device_id)?.name || tr('common.noDevice')} · ${deviceCommandFieldLabel(c.field || 'state')}`;
|
if (item.kind === 'device_state') return `${app.devices.find(value => value.id === c.device_id)?.name || tr('common.noDevice')} · ${c.field || 'state'}`;
|
||||||
if (item.kind === 'zone_state') return `${app.zones.find(value => value.id === c.zone_id)?.name || tr('common.noZone')} · ${c.field || 'state'}`;
|
if (item.kind === 'zone_state') return `${app.zones.find(value => value.id === c.zone_id)?.name || tr('common.noZone')} · ${c.field || 'state'}`;
|
||||||
if (item.kind === 'group_state') return `${app.groups.find(value => value.id === c.group_id)?.name || tr('groups.group')} · ${c.field || 'power_enabled'}`;
|
if (item.kind === 'group_state') return `${app.groups.find(value => value.id === c.group_id)?.name || tr('groups.group')} · ${c.field || 'power_enabled'}`;
|
||||||
return flowNodeSummary({ kind: item.kind, config: c });
|
return flowNodeSummary({ kind: item.kind, config: c });
|
||||||
|
|||||||
Reference in New Issue
Block a user