diff --git a/Cargo.lock b/Cargo.lock index b8c2b52..4dbf800 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -700,7 +700,7 @@ dependencies = [ [[package]] name = "gree-controller" -version = "0.14.1" +version = "0.14.2" dependencies = [ "aes", "aes-gcm", diff --git a/Cargo.toml b/Cargo.toml index e72d143..6b6a18e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gree-controller" -version = "0.14.1" +version = "0.14.2" edition = "2021" authors = ["GREE Controller contributors"] description = "Standalone local GREE HVAC controller with Web UI, SQLite and Home Assistant sensor support" diff --git a/FILE_MANIFEST.sha256 b/FILE_MANIFEST.sha256 index 60b2961..ae5a0ae 100644 --- a/FILE_MANIFEST.sha256 +++ b/FILE_MANIFEST.sha256 @@ -1,14 +1,14 @@ d67af429e4da9ce08e9d2f2a8472849ffbd70d135b1c5da535a076026794d04c ./.env.example a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore -1baa24e6934a05f48ea7f289ce5f8804fc0981236a2b6c468539a052c0d4e583 ./Cargo.lock -f3204447421f334110e1a626ec29fdd8ee88bb2b9d1e9e1a391c2720c9af7de9 ./Cargo.toml +59fe6d7de7f780481d0a7f614d4325f905a8aee8d971717566fc8889340223b3 ./Cargo.lock +97b67fd716185b18bb12d9170b502f81c6ffaf099816c893305c9f837048cb1f ./Cargo.toml 19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE -a4f7dc180e422d6de9d78b1235206ebc3b0436d6041730df8ca065532c36a939 ./README.md -b27097a57e62a3096b133899dea455a68aa04c0fa428f6228d08615e1b11ca29 ./SOURCE_MANIFEST.sha256 +de56b105caea31afd80b4051912329a0c1c03233979fd710732cb606acae1dbd ./README.md +c45e67ac73f7606e0b4f90fa05bd9c3a20dc07441ac04e8bc68235ef558143cc ./SOURCE_MANIFEST.sha256 bb549919b96b0413152dd905aec8640bb53301f07ec7d0b1be48ccbd37936fef ./build.rs -50a46eac5dfcdc80c5db068f0eb1ed7d696a2b6070e2ddff808f2bb2a33a913b ./docs/API.md -2e1e18fd8167dabfe2469c26e85cce62486c7cb6a502c63c6f6b0cd74d5885e0 ./docs/FLOW.md -919d54a6825385fdb4e9b8f07db756640d512b2c21d2d33f407f850680285ed0 ./docs/openapi.json +0eb39057762c51718e7e7ba220ae1e3a729409c3d91736afa3baa88c64d3a8d6 ./docs/API.md +ff213ad348e7f8b5cae7fe4adcad84d72db693765d424f270faa1acca9c23ce7 ./docs/FLOW.md +52181be4d4a161710bbb5e7cde1b3061976e41691d9e10bf99f0c224c65732d1 ./docs/openapi.json c65aa543d2e99f8f7d52736307b07ebd182597d80124661d53a16bb23f6f1118 ./ha-addon/.env.example a18d2460340264d34c267fa704ee51a42e59bcc1783ee4c0dbb2a8ef42fa0f16 ./ha-addon/Dockerfile 0417c88a990c72aecf071fba64d5d9da65dcda4d396cd7565477f7a4532c8c5e ./ha-addon/README.md @@ -16,13 +16,13 @@ cd7fd4b937a1d6138699f7effd184e3c077912b881e9c73c37b90bbafba68477 ./ha-addon/bui fd041032f353ceb0a2b02005f5a78d74a460b12e4e78de13dcb60129859f8c4e ./ha-addon/home-assistant/README.md f8e8559fe10fe523ac5bc9aac25c6e26e862f679d502e8f3c39f38a0a8e40911 ./ha-addon/home-assistant/custom_components/gree_controller/__init__.py 3f6ef15ef58456376ac53fde7cace1ef359d6a6f7a64c5b575ad77ea6e55ccc4 ./ha-addon/home-assistant/custom_components/gree_controller/api.py -021a235617852c3adc4990936c3daf497309ea9a18574223805ae7496b1366c3 ./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 b7f0873109c52be9d7f09bea3dffc416103c50085e1f0680d11661a969479898 ./ha-addon/home-assistant/custom_components/gree_controller/const.py 41a8958a6fe6d10e5679c95ac34832583a2a15ecfd67d09b4e094a83d06f5c47 ./ha-addon/home-assistant/custom_components/gree_controller/coordinator.py 5a96fe8f5c035c34f1339370270cd078056202d09e236dec75735be11de92a7d ./ha-addon/home-assistant/custom_components/gree_controller/entity_map.py c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./ha-addon/home-assistant/custom_components/gree_controller/icon.png -5a7a8cf9545c9346d9bcc92e16265fa2ef63567fa28abe153a2e361cc279c082 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json +25c5ffb84460bf81f0c54abfc912e5bfd85535d3558df7a10e1de9541dd2afa3 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json c52a484b671ce738ecc00228a1db19a5d703f69ab27530c2896a5c2ae3b4a96f ./ha-addon/home-assistant/custom_components/gree_controller/number.py 39c4309001b75abb56234f05662bc06e077054986876f1927937edbce528ec95 ./ha-addon/home-assistant/custom_components/gree_controller/select.py cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./ha-addon/home-assistant/custom_components/gree_controller/sensor.py @@ -30,10 +30,10 @@ cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./ha-addon/hom 6bddb7b4620021ecd2099a86a77ef5c7f2c2dcd3d07d5db4e7b4c4ce6d3e8c03 ./ha-addon/home-assistant/custom_components/gree_controller/translations/en.json 13f30e2dcdcedbd1b6c3f99c2335e0487108fd72c8e86922368b84f2fa2038ae ./ha-addon/home-assistant/custom_components/gree_controller/translations/pl.json c5fc5c87273d82d2834a0a5a365c821a416413ed509613441e24e4dc3507c4d2 ./ha-addon/home-assistant/generated/gree_controller_entities.example.json -131509e7dfefcca4ad61a3a060bc0a33ed2f731141496ba99a3cb81a563399b1 ./ha-addon/repository/gree-controller/CHANGELOG.md +e561712958ce5ef1068989af0515687358adc69b1d92af29d6b576d46a182859 ./ha-addon/repository/gree-controller/CHANGELOG.md 3c06bd11671452ea6472afd5543fb91a39aba73a1d2d95c7762c04f94ab64030 ./ha-addon/repository/gree-controller/DOCS.md c3db302f38d26dc9f8caf37a1a8a07a1334cc07ae301d452f2940a5ec5915719 ./ha-addon/repository/gree-controller/README.md -09e38d1d821a6c318152e5a023954310e9f361985cfddc456abfcc0b88d79c62 ./ha-addon/repository/gree-controller/config.yaml +a3a74b2aaa20fd39a050f8f360eec543d70b3fd43271617a60a0b95fd0cdf2ca ./ha-addon/repository/gree-controller/config.yaml 3aae6cdae4c3aaab7786a9575e7093b7e2c5b69288167075f4b7475611691a6b ./ha-addon/repository/gree-controller/topologia-ha-vlan-gree-pl.png 0d0b42a7639b128946eec379175436fa1f150294f39d2dfd169077bfa87fd14d ./ha-addon/repository/gree-controller/topology-ha-vlan-gree-en.png d13e4c2b993d209078342c0c86d852b300692089b377aa044f811308dd188a1e ./ha-addon/repository/gree-controller/translations/en.yaml @@ -43,8 +43,8 @@ c123b190219556382f0e7f3058056fb233f6261f257a01eceb96a2c0bbfa12f3 ./ha-addon/run 081e27b6e43070d44f865335c30a42a5a29668eaa6a5ea5ab6967ac4447b8e06 ./ha-addon/sync-repository.sh f9e31de3d109ead32bf531c074d46a087df398d62488caee0b46b5e0a1c027f7 ./ha_addon.md 253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md -301df7a84ce546db1c2e25da841aa0fc9696435d7bb4db358e3fb082a723afd9 ./lang/en.json -c01e081782251122fec4fb50500a5f3ee4d865a2213af89cc77f3f3661606dbd ./lang/pl.json +4bbfa7cfb7644b6949aa8bc212154fce4dbf1f8bc5c7c56863a30fa63a0efd80 ./lang/en.json +bd35f928fbca252a90057a04dc1d363c85bd844b29076238e00751d8ecf196cc ./lang/pl.json d8459024f04ca514bd8e9d6bd3af872fb942fd85a7cdd5583f4a9d28aab6faba ./make_zip.py b14233a8987e53bbbdd6770386ba10fa166ec25c1e098275c173c544b37846fc ./presets/bedroom_window_night.json 1960841119c0b673fb2f03621b32237552fd828045304cd221401214b997c910 ./presets/device_resilience.json @@ -85,7 +85,7 @@ fdcd9a5055d08037278b842e7ab69265345c5811f0a06867136c511d140bb191 ./presets/wind 804f22123cd3e8db0fac791826c8dd9f758fb866c8e3b5655fb6d25d259dccf1 ./presets/workday_comfort.json 01952aa92b217f8eae2493b88870e2dec595100cd15c4d561ff11ae2b936c46f ./regenerate-sha.sh c64b1c6deeb24f20af662bfce7fa3683703914987241107ce664fa2f6d1a9dcb ./scripts/README.md -3461b89eff40737f5f250b50bcccadb882bb0b5afbd96542d9d67184142191f8 ./scripts/api_dev_test.py +506942d9c96b586b7f265742421fcea45c8d7e86824ad8934147a4388873c3cc ./scripts/api_dev_test.py 5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh 6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh 1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh @@ -102,12 +102,12 @@ e89b3503b838670f3213e696395024e74d91c718bc67a4cb295077e944ea4c6c ./src/api.rs 3dfd1e053cbfa0d684dd88cf99b244a96a36e2ff23d2a0715b8327ab62258254 ./src/api/assets.rs 05c255919f7b3d81188fc5f1410be6a0037471dfc44e93bffb009f29a1e6a3fc ./src/api/auth.rs 9e4fc675306e111ed3db7af9822e2925d88109c31b9e309851665a975c0bd83c ./src/api/automations.rs -be865a0be2167131a3778d7ceb825d9a65991a7c063fffadbe7188eac459d078 ./src/api/configuration.rs +a9e8b74c9ba3ae723d36d2d87c0b1a72138f9ad2fed8e62dc5ad442278a1986d ./src/api/configuration.rs 19bc141286a336ad67e99079496eb0ae5126f986ab28be62b5f97758e5f6969c ./src/api/debug_tokens.rs 32ade46ec45d6faf7852242b9f5ba1be6eacbacbeb33d2deabfab59bf1610ce0 ./src/api/device_groups.rs fcbb3da0538aab451f71810f829cc248d6bb67e586dc418d71a572980583823d ./src/api/devices.rs 4b5b784670ae761a6848b9a33fe3fb391bebd39d65e87936907428711e96ca44 ./src/api/events.rs -44b74965f3256c2972d6b3cd384a63c68e3b422f414a0902c085bc92e17369bf ./src/api/flows.rs +d59b080b392b574ec94276c7b485b455d0d1abacc7de2ef29dcbfcf7d86dbdb0 ./src/api/flows.rs 2cfee7029f065e81628a8942e3dad00a03c3a5cf4f8492d07f12b75d05c07359 ./src/api/gree_cloud.rs b61b0b4f196c5e72e47cf06ca0c14df3cf8eedbdef0242a9d2af33623663489f ./src/api/groups.rs 63733cf1fd70b9c2cb1bde3c9304365e8940af4a7524bfc82b47444458a56f4a ./src/api/history.rs @@ -118,7 +118,7 @@ e8edfbb725d6de650442444b0a929ad2be0bd2c2d7d227d2b3fdd99d05c94f94 ./src/api/hous 744ce18aa9e0227c6cdfb6980063d0d738d74c5865d45c20fd20026d4742bb3e ./src/api/public_settings.rs 5b29f2e5aa2c422c5829ef89c7b59cf753883aa7f60661c95ab0f95cf676fa3f ./src/api/schedules.rs e69df8a5f612cd37d25fbf9f8e824c5a7d655ac5a6376ec3b31addf1fe8597e2 ./src/api/settings.rs -264f884e2fdef24303d441ce9cfe11190ed8f2bba8ca5782c1d13c2ee30e6a7f ./src/api/system.rs +1c1fe39aec55d10608ca3db83827855b07249cd1a81ae054eb1ee6464514092b ./src/api/system.rs 9118a5a3a381b010e22d646498ff56d2e05f51656789682cc4371b09c384aa98 ./src/api/websocket.rs 455c7238c9788d378e909a4fbc94d5390b67edd92e127f48b4b913ddcbe82fb0 ./src/api/zones.rs f4feaff281163eda543bf094d0fe9158dc6662be5f76f39c81570da7ed0355e7 ./src/config.rs @@ -134,14 +134,14 @@ bb5048ee60053f39ce3c1203dc1f24c7047310538d2357f105a052c016d21ec4 ./src/db/flows d6b9b70a04c774173c97f7426487d5e2932d7713e12ae3fb7f64cf2b4e0b3c10 ./src/db/schedules_automations.rs a76938b2e49719b97f8abf40a5cce03df6740ce2e8ac138b179c761619b32ce6 ./src/db/tests.rs f90fbb14f29a20b799b3856bdaa22812a9f0d9dfa46693f1341c160dccda8a97 ./src/db/zone_history.rs -3c97e04e93d14f37d39ee9f8f53257101517a3d90a0dfeba81ca3dcd5e39ff31 ./src/engine.rs -6046bdbc9c02ac74f47068229c59af69abc8bfa46cfeff6fe7ba573b493e128c ./src/engine/automations.rs +a66b4c2f4f62bd4a981f57ad574c797b73d8400f3492f8ff69126f74b5010899 ./src/engine.rs +ed5c9a0c61c6e597acb33f58593ebe02b562ddc68fa61271e1c1cd436c1003d3 ./src/engine/automations.rs 0bf7fe7d28b4749e53ec25d71effbb78bc8f70c716714f838a4a2d8f20627d63 ./src/engine/commands.rs 925135985f640ba9ae0a6109d908187b659341a91451e91d27e533adfd610b76 ./src/engine/control_plan.rs 7b48a33343d519a2ff2d81e9d757eb56a0c1beacfc0b7b8ef61f49c2ad363f9a ./src/engine/deadlines.rs -9a51cc46b8befb992d7f7dd3dbe5025cd739755a684ed36013be78eaaf1a1743 ./src/engine/energy.rs +67ddb482ad08608e799a88faa6183d9bb65f62ca8ac138c6314cab18bc033033 ./src/engine/energy.rs e350aa63f3bb14ca887eaaa7555edbeae00fb584cbcaf552cc972f0c8918f5d8 ./src/engine/groups.rs -0e78e8d1c31503b313926af025281714a6e22a23f46af081b3da8e94a2d7315a ./src/engine/history.rs +9f650203959acebc72f3ee87faaeebe4c5c8beee7d77d2039d56fa2c0277aab4 ./src/engine/history.rs 41b7045877771b7c352328998cb45ce2433e7f9631fad3eb1a882ab4897dc31f ./src/engine/local_thermostat.rs 99527adab9fea1caede9ebb24faa6a63d5cef4810c64c06306b571cf5cbbb0da ./src/engine/ownership.rs 1a29b7fd8de77f0a5e5964ca05e41cc5ea78855c1ab961750d40461faec305e5 ./src/engine/polling.rs @@ -150,9 +150,9 @@ c1e8de5392681ba1255e3e2d871a6ee860fb8ca987a0a8f973d9ded3977a68fb ./src/engine/s b0189f28d0f337dfd3ce0934da7a565e4a5535afc1c242214c26ed584b3b7840 ./src/engine/targets.rs 682c8b3956c657b655746cbd19ee97ee2f4cf953bc9de67d6d70a1b14f3dd746 ./src/engine/temperature.rs 833b72907375e75c3f42b3921ad70a8e298f5a93743bf0d0c19059aa272462d1 ./src/engine/temporary_thermostat.rs -338161bd94c868be6155efabdc63e81e9a572d559cc00493d6c69f15cc1612a3 ./src/engine/tests.rs +47330f0d8ff170ea91ba37d019c0d5577151c87f0474bcc2a287850415f6b953 ./src/engine/tests.rs c89ff36c6a67bf4fbf68c43e18aa0a556de2dc69263fd79db376363afc741a4f ./src/engine/zone_actions.rs -6db07dc9c3003c97b80d3955761329ffea9876279e7afc0644bfba8878e41209 ./src/engine/zone_control.rs +28571e25e539fb1fe5e77e8bd003caf2da9e84fc34d156fd5f3e65e805798ca0 ./src/engine/zone_control.rs bfac95cec87f0df6ce562153cd3cfe6731359d703e6c592d2c2f7ba01ea62d99 ./src/error.rs ea553a1d8b44c74b300d7c4a76dcd3b370524eb57766730a1376d1ab3079913b ./src/home_assistant.rs b43d95ac42f5edc1a38323e47626b6c0ec6cbbda3c30ad6f245d23865cd1993e ./src/influxdb.rs @@ -199,25 +199,25 @@ f13f1be3d5789539eba3fc1b59c14835f8c681eca7634e118fa3763a53aeee2b ./src/queries/ b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service 544a31cb2e5374227026afd6abd0d910381f3fca1638154ebd74e3c3d96a35d1 ./web/404.html e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg -6cd0fa21dcc1b5170c4375d7fb70b72fc753f6ac80fbd1f6a91ba0e52ccca7e4 ./web/index.html +54350093b25e335f7e2bcf99fd0e17ba6df9035a0711c02bcbe0c4dd612c62e5 ./web/index.html 9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js/README.md -dfea965a2093f9f85744b50993c867118afc7a65d2c5d3abd975d4b6f4acc6f2 ./web/js/bootstrap.js -761ad87ed20a819d8d87bc260bcd7f5b39f19582c346a69c8b717c94e7e94302 ./web/js/charts.js -e6c459f92f18d72f11ffaead5caede0b9c8e6b6a1c9cf1a6e97e6b07d5425ab5 ./web/js/core.js +a183492c9cf91d1318675f242c57ed30c11661ffc8a5a52f03e5aaeea060ef24 ./web/js/bootstrap.js +e987328623938637135c8a8d3952043fe7ae5d8568eff12bf4d95a601409f1df ./web/js/charts.js +3c61d86420d1f3765c9ccc184553425132fc432fd60d93763da4669d113b449b ./web/js/core.js d880ffdac6aa07cd10d79fbacf12f87eb4c69b48a80564bbfdd0248abe8884d5 ./web/js/dashboard.js -83189a47d114e7b911c9cbf368bf80232a7a481d416297db5fc451c12bb710c7 ./web/js/entities.js -1ff265beab8eb15ced854b5da260fd3952add3acc6b290ea73c76b1a54853165 ./web/js/events.js -2e822a00b5a6941b6a251eaf2c54ca95a53399b5d1056f3d8240e11f82d6065f ./web/js/flows.js +ed31601df41cc82032709f960a2b4061966abd58cde456ef7dda05e42eb91432 ./web/js/entities.js +424b52fa05cc6f45ab3bcac0f5f1fd0ac2102d840334fc3d762fd208816565a8 ./web/js/events.js +8c156909d2d2e59b7d7dc969b9520c978141a448a6cc5aae8fe74a8dc124f2bc ./web/js/flows.js 9e2209d41df7624c29741f03f12ed8e83b47cee2891c5a80cea8ca1bdacf1d5a ./web/js/forms.js -6d2c2fc497f0ea4e3ef4a35e6518c9e4be680ace9100ed09a327f9dbe4261d0b ./web/js/history.js +688390149f023b4f7e8701ca30ea5cb56c27692dc5083344739411405e320bb1 ./web/js/history.js 0dc879c0056251618633c9efd188fc1593eeae6965ad1bc993132dea6e171b44 ./web/js/main.js -341c4bc441782bf60bd92c899154ca565ac1ba884a6f3a1ea89aae58f5d0322f ./web/js/navigation.js -d105fb2a3b4000a9f22ed23c7b030c1018fee4d2a78dba3ad96b3d9d95689f2a ./web/js/realtime.js +419e0859d1553972f670bb1719be6c0aca14a4ab450041d0017abd41a094b1da ./web/js/navigation.js +e3d146a683b8beb332c752e533826160c2cf2236bc269ced7a4dd1249871027b ./web/js/realtime.js abdedaca20ecad50ae2b5c7f6081c80a142112bd867cda19949d5253e609f1b4 ./web/js/router.js -3426ec31011173172199bff7deacbef1d5eb0121012b5ea0e53b7a72a674a853 ./web/js/settings-ui.js -13806217b2312dff3bd6cc2b16f75d9bdffc7a587215ae9f862706cd7699673f ./web/js/settings.js +55c163fce9520979d380739546ef3036186297466d5fc3ac8cb0680408fb444e ./web/js/settings-ui.js +29e6ca03eb62c6158de9ab0b9e74100129478794ae5c697321e217fb1b69ff23 ./web/js/settings.js 3815fea7ae1694568c2f249bc8460e0563ced6bde89214f74f21b41fe8cad9a6 ./web/lang-init.js 6b653e4fe2d4db4ffae6ff8f39b8ce57a10a990dc0000ee4aae51d949afadd52 ./web/manifest.webmanifest -10a47e3645316103b8ad85a8635ec6305da9717226568e76db58e2d77e894e8f ./web/styles.css +3274600badfc59219db1d76e7767d7448e4cf1a9aedee94e90435c906c8269b3 ./web/styles.css fefdf12cbbb0a5d8a2cb89275cae28455aa42aaf179542305cc8f5e0f4825898 ./web/sw.js d505d793ce7cc9485b45b78bba1c0d51887adc7451ab59a42702946e5b991382 ./web/theme-init.js diff --git a/README.md b/README.md index 45ba4aa..4752f63 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ Self-hosted controller for GREE-compatible air conditioners with a local Web UI, thermostat zones, schedules, Home Assistant integration, history, notifications and a documented HTTP/WebSocket API. -**Version: 0.14.1** +**Version: 0.14.2** -Version 0.14.1 builds on the GREE Cloud and energy support from 0.14.0 with split/multisplit installation groups, shared energy and outdoor-temperature sources, multi-series energy comparison, cleaner Cloud status/alerts and safer MQTT cleanup after device removal. Local and Cloud entries remain deliberately independent, even when they represent the same physical MAC. +Version 0.14.2 adds separate vertical and horizontal swing control across manual unit control, thermostat views, legacy automations, Visual Flow and Home Assistant zone thermostats. It keeps the split/multisplit energy and outdoor-temperature improvements from 0.14.1, while preserving independent Local and Cloud device entries. > [Full API reference](docs/API.md) — authentication, every endpoint, request bodies, response models, WebSocket events and examples. diff --git a/SOURCE_MANIFEST.sha256 b/SOURCE_MANIFEST.sha256 index 7cec844..fbb1117 100644 --- a/SOURCE_MANIFEST.sha256 +++ b/SOURCE_MANIFEST.sha256 @@ -1,13 +1,13 @@ d67af429e4da9ce08e9d2f2a8472849ffbd70d135b1c5da535a076026794d04c ./.env.example a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore -1baa24e6934a05f48ea7f289ce5f8804fc0981236a2b6c468539a052c0d4e583 ./Cargo.lock -f3204447421f334110e1a626ec29fdd8ee88bb2b9d1e9e1a391c2720c9af7de9 ./Cargo.toml +59fe6d7de7f780481d0a7f614d4325f905a8aee8d971717566fc8889340223b3 ./Cargo.lock +97b67fd716185b18bb12d9170b502f81c6ffaf099816c893305c9f837048cb1f ./Cargo.toml 19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE -a4f7dc180e422d6de9d78b1235206ebc3b0436d6041730df8ca065532c36a939 ./README.md +de56b105caea31afd80b4051912329a0c1c03233979fd710732cb606acae1dbd ./README.md bb549919b96b0413152dd905aec8640bb53301f07ec7d0b1be48ccbd37936fef ./build.rs -50a46eac5dfcdc80c5db068f0eb1ed7d696a2b6070e2ddff808f2bb2a33a913b ./docs/API.md -2e1e18fd8167dabfe2469c26e85cce62486c7cb6a502c63c6f6b0cd74d5885e0 ./docs/FLOW.md -919d54a6825385fdb4e9b8f07db756640d512b2c21d2d33f407f850680285ed0 ./docs/openapi.json +0eb39057762c51718e7e7ba220ae1e3a729409c3d91736afa3baa88c64d3a8d6 ./docs/API.md +ff213ad348e7f8b5cae7fe4adcad84d72db693765d424f270faa1acca9c23ce7 ./docs/FLOW.md +52181be4d4a161710bbb5e7cde1b3061976e41691d9e10bf99f0c224c65732d1 ./docs/openapi.json c65aa543d2e99f8f7d52736307b07ebd182597d80124661d53a16bb23f6f1118 ./ha-addon/.env.example a18d2460340264d34c267fa704ee51a42e59bcc1783ee4c0dbb2a8ef42fa0f16 ./ha-addon/Dockerfile 0417c88a990c72aecf071fba64d5d9da65dcda4d396cd7565477f7a4532c8c5e ./ha-addon/README.md @@ -15,13 +15,13 @@ cd7fd4b937a1d6138699f7effd184e3c077912b881e9c73c37b90bbafba68477 ./ha-addon/bui fd041032f353ceb0a2b02005f5a78d74a460b12e4e78de13dcb60129859f8c4e ./ha-addon/home-assistant/README.md f8e8559fe10fe523ac5bc9aac25c6e26e862f679d502e8f3c39f38a0a8e40911 ./ha-addon/home-assistant/custom_components/gree_controller/__init__.py 3f6ef15ef58456376ac53fde7cace1ef359d6a6f7a64c5b575ad77ea6e55ccc4 ./ha-addon/home-assistant/custom_components/gree_controller/api.py -021a235617852c3adc4990936c3daf497309ea9a18574223805ae7496b1366c3 ./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 b7f0873109c52be9d7f09bea3dffc416103c50085e1f0680d11661a969479898 ./ha-addon/home-assistant/custom_components/gree_controller/const.py 41a8958a6fe6d10e5679c95ac34832583a2a15ecfd67d09b4e094a83d06f5c47 ./ha-addon/home-assistant/custom_components/gree_controller/coordinator.py 5a96fe8f5c035c34f1339370270cd078056202d09e236dec75735be11de92a7d ./ha-addon/home-assistant/custom_components/gree_controller/entity_map.py c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./ha-addon/home-assistant/custom_components/gree_controller/icon.png -5a7a8cf9545c9346d9bcc92e16265fa2ef63567fa28abe153a2e361cc279c082 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json +25c5ffb84460bf81f0c54abfc912e5bfd85535d3558df7a10e1de9541dd2afa3 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json c52a484b671ce738ecc00228a1db19a5d703f69ab27530c2896a5c2ae3b4a96f ./ha-addon/home-assistant/custom_components/gree_controller/number.py 39c4309001b75abb56234f05662bc06e077054986876f1927937edbce528ec95 ./ha-addon/home-assistant/custom_components/gree_controller/select.py cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./ha-addon/home-assistant/custom_components/gree_controller/sensor.py @@ -29,10 +29,10 @@ cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./ha-addon/hom 6bddb7b4620021ecd2099a86a77ef5c7f2c2dcd3d07d5db4e7b4c4ce6d3e8c03 ./ha-addon/home-assistant/custom_components/gree_controller/translations/en.json 13f30e2dcdcedbd1b6c3f99c2335e0487108fd72c8e86922368b84f2fa2038ae ./ha-addon/home-assistant/custom_components/gree_controller/translations/pl.json c5fc5c87273d82d2834a0a5a365c821a416413ed509613441e24e4dc3507c4d2 ./ha-addon/home-assistant/generated/gree_controller_entities.example.json -131509e7dfefcca4ad61a3a060bc0a33ed2f731141496ba99a3cb81a563399b1 ./ha-addon/repository/gree-controller/CHANGELOG.md +e561712958ce5ef1068989af0515687358adc69b1d92af29d6b576d46a182859 ./ha-addon/repository/gree-controller/CHANGELOG.md 3c06bd11671452ea6472afd5543fb91a39aba73a1d2d95c7762c04f94ab64030 ./ha-addon/repository/gree-controller/DOCS.md c3db302f38d26dc9f8caf37a1a8a07a1334cc07ae301d452f2940a5ec5915719 ./ha-addon/repository/gree-controller/README.md -09e38d1d821a6c318152e5a023954310e9f361985cfddc456abfcc0b88d79c62 ./ha-addon/repository/gree-controller/config.yaml +a3a74b2aaa20fd39a050f8f360eec543d70b3fd43271617a60a0b95fd0cdf2ca ./ha-addon/repository/gree-controller/config.yaml 3aae6cdae4c3aaab7786a9575e7093b7e2c5b69288167075f4b7475611691a6b ./ha-addon/repository/gree-controller/topologia-ha-vlan-gree-pl.png 0d0b42a7639b128946eec379175436fa1f150294f39d2dfd169077bfa87fd14d ./ha-addon/repository/gree-controller/topology-ha-vlan-gree-en.png d13e4c2b993d209078342c0c86d852b300692089b377aa044f811308dd188a1e ./ha-addon/repository/gree-controller/translations/en.yaml @@ -42,8 +42,8 @@ c123b190219556382f0e7f3058056fb233f6261f257a01eceb96a2c0bbfa12f3 ./ha-addon/run 081e27b6e43070d44f865335c30a42a5a29668eaa6a5ea5ab6967ac4447b8e06 ./ha-addon/sync-repository.sh f9e31de3d109ead32bf531c074d46a087df398d62488caee0b46b5e0a1c027f7 ./ha_addon.md 253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md -301df7a84ce546db1c2e25da841aa0fc9696435d7bb4db358e3fb082a723afd9 ./lang/en.json -c01e081782251122fec4fb50500a5f3ee4d865a2213af89cc77f3f3661606dbd ./lang/pl.json +4bbfa7cfb7644b6949aa8bc212154fce4dbf1f8bc5c7c56863a30fa63a0efd80 ./lang/en.json +bd35f928fbca252a90057a04dc1d363c85bd844b29076238e00751d8ecf196cc ./lang/pl.json d8459024f04ca514bd8e9d6bd3af872fb942fd85a7cdd5583f4a9d28aab6faba ./make_zip.py b14233a8987e53bbbdd6770386ba10fa166ec25c1e098275c173c544b37846fc ./presets/bedroom_window_night.json 1960841119c0b673fb2f03621b32237552fd828045304cd221401214b997c910 ./presets/device_resilience.json @@ -84,7 +84,7 @@ fdcd9a5055d08037278b842e7ab69265345c5811f0a06867136c511d140bb191 ./presets/wind 804f22123cd3e8db0fac791826c8dd9f758fb866c8e3b5655fb6d25d259dccf1 ./presets/workday_comfort.json 01952aa92b217f8eae2493b88870e2dec595100cd15c4d561ff11ae2b936c46f ./regenerate-sha.sh c64b1c6deeb24f20af662bfce7fa3683703914987241107ce664fa2f6d1a9dcb ./scripts/README.md -3461b89eff40737f5f250b50bcccadb882bb0b5afbd96542d9d67184142191f8 ./scripts/api_dev_test.py +506942d9c96b586b7f265742421fcea45c8d7e86824ad8934147a4388873c3cc ./scripts/api_dev_test.py 5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh 6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh 1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh @@ -101,12 +101,12 @@ e89b3503b838670f3213e696395024e74d91c718bc67a4cb295077e944ea4c6c ./src/api.rs 3dfd1e053cbfa0d684dd88cf99b244a96a36e2ff23d2a0715b8327ab62258254 ./src/api/assets.rs 05c255919f7b3d81188fc5f1410be6a0037471dfc44e93bffb009f29a1e6a3fc ./src/api/auth.rs 9e4fc675306e111ed3db7af9822e2925d88109c31b9e309851665a975c0bd83c ./src/api/automations.rs -be865a0be2167131a3778d7ceb825d9a65991a7c063fffadbe7188eac459d078 ./src/api/configuration.rs +a9e8b74c9ba3ae723d36d2d87c0b1a72138f9ad2fed8e62dc5ad442278a1986d ./src/api/configuration.rs 19bc141286a336ad67e99079496eb0ae5126f986ab28be62b5f97758e5f6969c ./src/api/debug_tokens.rs 32ade46ec45d6faf7852242b9f5ba1be6eacbacbeb33d2deabfab59bf1610ce0 ./src/api/device_groups.rs fcbb3da0538aab451f71810f829cc248d6bb67e586dc418d71a572980583823d ./src/api/devices.rs 4b5b784670ae761a6848b9a33fe3fb391bebd39d65e87936907428711e96ca44 ./src/api/events.rs -44b74965f3256c2972d6b3cd384a63c68e3b422f414a0902c085bc92e17369bf ./src/api/flows.rs +d59b080b392b574ec94276c7b485b455d0d1abacc7de2ef29dcbfcf7d86dbdb0 ./src/api/flows.rs 2cfee7029f065e81628a8942e3dad00a03c3a5cf4f8492d07f12b75d05c07359 ./src/api/gree_cloud.rs b61b0b4f196c5e72e47cf06ca0c14df3cf8eedbdef0242a9d2af33623663489f ./src/api/groups.rs 63733cf1fd70b9c2cb1bde3c9304365e8940af4a7524bfc82b47444458a56f4a ./src/api/history.rs @@ -117,7 +117,7 @@ e8edfbb725d6de650442444b0a929ad2be0bd2c2d7d227d2b3fdd99d05c94f94 ./src/api/hous 744ce18aa9e0227c6cdfb6980063d0d738d74c5865d45c20fd20026d4742bb3e ./src/api/public_settings.rs 5b29f2e5aa2c422c5829ef89c7b59cf753883aa7f60661c95ab0f95cf676fa3f ./src/api/schedules.rs e69df8a5f612cd37d25fbf9f8e824c5a7d655ac5a6376ec3b31addf1fe8597e2 ./src/api/settings.rs -264f884e2fdef24303d441ce9cfe11190ed8f2bba8ca5782c1d13c2ee30e6a7f ./src/api/system.rs +1c1fe39aec55d10608ca3db83827855b07249cd1a81ae054eb1ee6464514092b ./src/api/system.rs 9118a5a3a381b010e22d646498ff56d2e05f51656789682cc4371b09c384aa98 ./src/api/websocket.rs 455c7238c9788d378e909a4fbc94d5390b67edd92e127f48b4b913ddcbe82fb0 ./src/api/zones.rs f4feaff281163eda543bf094d0fe9158dc6662be5f76f39c81570da7ed0355e7 ./src/config.rs @@ -133,14 +133,14 @@ bb5048ee60053f39ce3c1203dc1f24c7047310538d2357f105a052c016d21ec4 ./src/db/flows d6b9b70a04c774173c97f7426487d5e2932d7713e12ae3fb7f64cf2b4e0b3c10 ./src/db/schedules_automations.rs a76938b2e49719b97f8abf40a5cce03df6740ce2e8ac138b179c761619b32ce6 ./src/db/tests.rs f90fbb14f29a20b799b3856bdaa22812a9f0d9dfa46693f1341c160dccda8a97 ./src/db/zone_history.rs -3c97e04e93d14f37d39ee9f8f53257101517a3d90a0dfeba81ca3dcd5e39ff31 ./src/engine.rs -6046bdbc9c02ac74f47068229c59af69abc8bfa46cfeff6fe7ba573b493e128c ./src/engine/automations.rs +a66b4c2f4f62bd4a981f57ad574c797b73d8400f3492f8ff69126f74b5010899 ./src/engine.rs +ed5c9a0c61c6e597acb33f58593ebe02b562ddc68fa61271e1c1cd436c1003d3 ./src/engine/automations.rs 0bf7fe7d28b4749e53ec25d71effbb78bc8f70c716714f838a4a2d8f20627d63 ./src/engine/commands.rs 925135985f640ba9ae0a6109d908187b659341a91451e91d27e533adfd610b76 ./src/engine/control_plan.rs 7b48a33343d519a2ff2d81e9d757eb56a0c1beacfc0b7b8ef61f49c2ad363f9a ./src/engine/deadlines.rs -9a51cc46b8befb992d7f7dd3dbe5025cd739755a684ed36013be78eaaf1a1743 ./src/engine/energy.rs +67ddb482ad08608e799a88faa6183d9bb65f62ca8ac138c6314cab18bc033033 ./src/engine/energy.rs e350aa63f3bb14ca887eaaa7555edbeae00fb584cbcaf552cc972f0c8918f5d8 ./src/engine/groups.rs -0e78e8d1c31503b313926af025281714a6e22a23f46af081b3da8e94a2d7315a ./src/engine/history.rs +9f650203959acebc72f3ee87faaeebe4c5c8beee7d77d2039d56fa2c0277aab4 ./src/engine/history.rs 41b7045877771b7c352328998cb45ce2433e7f9631fad3eb1a882ab4897dc31f ./src/engine/local_thermostat.rs 99527adab9fea1caede9ebb24faa6a63d5cef4810c64c06306b571cf5cbbb0da ./src/engine/ownership.rs 1a29b7fd8de77f0a5e5964ca05e41cc5ea78855c1ab961750d40461faec305e5 ./src/engine/polling.rs @@ -149,9 +149,9 @@ c1e8de5392681ba1255e3e2d871a6ee860fb8ca987a0a8f973d9ded3977a68fb ./src/engine/s b0189f28d0f337dfd3ce0934da7a565e4a5535afc1c242214c26ed584b3b7840 ./src/engine/targets.rs 682c8b3956c657b655746cbd19ee97ee2f4cf953bc9de67d6d70a1b14f3dd746 ./src/engine/temperature.rs 833b72907375e75c3f42b3921ad70a8e298f5a93743bf0d0c19059aa272462d1 ./src/engine/temporary_thermostat.rs -338161bd94c868be6155efabdc63e81e9a572d559cc00493d6c69f15cc1612a3 ./src/engine/tests.rs +47330f0d8ff170ea91ba37d019c0d5577151c87f0474bcc2a287850415f6b953 ./src/engine/tests.rs c89ff36c6a67bf4fbf68c43e18aa0a556de2dc69263fd79db376363afc741a4f ./src/engine/zone_actions.rs -6db07dc9c3003c97b80d3955761329ffea9876279e7afc0644bfba8878e41209 ./src/engine/zone_control.rs +28571e25e539fb1fe5e77e8bd003caf2da9e84fc34d156fd5f3e65e805798ca0 ./src/engine/zone_control.rs bfac95cec87f0df6ce562153cd3cfe6731359d703e6c592d2c2f7ba01ea62d99 ./src/error.rs ea553a1d8b44c74b300d7c4a76dcd3b370524eb57766730a1376d1ab3079913b ./src/home_assistant.rs b43d95ac42f5edc1a38323e47626b6c0ec6cbbda3c30ad6f245d23865cd1993e ./src/influxdb.rs @@ -198,25 +198,25 @@ f13f1be3d5789539eba3fc1b59c14835f8c681eca7634e118fa3763a53aeee2b ./src/queries/ b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service 544a31cb2e5374227026afd6abd0d910381f3fca1638154ebd74e3c3d96a35d1 ./web/404.html e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg -6cd0fa21dcc1b5170c4375d7fb70b72fc753f6ac80fbd1f6a91ba0e52ccca7e4 ./web/index.html +54350093b25e335f7e2bcf99fd0e17ba6df9035a0711c02bcbe0c4dd612c62e5 ./web/index.html 9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js/README.md -dfea965a2093f9f85744b50993c867118afc7a65d2c5d3abd975d4b6f4acc6f2 ./web/js/bootstrap.js -761ad87ed20a819d8d87bc260bcd7f5b39f19582c346a69c8b717c94e7e94302 ./web/js/charts.js -e6c459f92f18d72f11ffaead5caede0b9c8e6b6a1c9cf1a6e97e6b07d5425ab5 ./web/js/core.js +a183492c9cf91d1318675f242c57ed30c11661ffc8a5a52f03e5aaeea060ef24 ./web/js/bootstrap.js +e987328623938637135c8a8d3952043fe7ae5d8568eff12bf4d95a601409f1df ./web/js/charts.js +3c61d86420d1f3765c9ccc184553425132fc432fd60d93763da4669d113b449b ./web/js/core.js d880ffdac6aa07cd10d79fbacf12f87eb4c69b48a80564bbfdd0248abe8884d5 ./web/js/dashboard.js -83189a47d114e7b911c9cbf368bf80232a7a481d416297db5fc451c12bb710c7 ./web/js/entities.js -1ff265beab8eb15ced854b5da260fd3952add3acc6b290ea73c76b1a54853165 ./web/js/events.js -2e822a00b5a6941b6a251eaf2c54ca95a53399b5d1056f3d8240e11f82d6065f ./web/js/flows.js +ed31601df41cc82032709f960a2b4061966abd58cde456ef7dda05e42eb91432 ./web/js/entities.js +424b52fa05cc6f45ab3bcac0f5f1fd0ac2102d840334fc3d762fd208816565a8 ./web/js/events.js +8c156909d2d2e59b7d7dc969b9520c978141a448a6cc5aae8fe74a8dc124f2bc ./web/js/flows.js 9e2209d41df7624c29741f03f12ed8e83b47cee2891c5a80cea8ca1bdacf1d5a ./web/js/forms.js -6d2c2fc497f0ea4e3ef4a35e6518c9e4be680ace9100ed09a327f9dbe4261d0b ./web/js/history.js +688390149f023b4f7e8701ca30ea5cb56c27692dc5083344739411405e320bb1 ./web/js/history.js 0dc879c0056251618633c9efd188fc1593eeae6965ad1bc993132dea6e171b44 ./web/js/main.js -341c4bc441782bf60bd92c899154ca565ac1ba884a6f3a1ea89aae58f5d0322f ./web/js/navigation.js -d105fb2a3b4000a9f22ed23c7b030c1018fee4d2a78dba3ad96b3d9d95689f2a ./web/js/realtime.js +419e0859d1553972f670bb1719be6c0aca14a4ab450041d0017abd41a094b1da ./web/js/navigation.js +e3d146a683b8beb332c752e533826160c2cf2236bc269ced7a4dd1249871027b ./web/js/realtime.js abdedaca20ecad50ae2b5c7f6081c80a142112bd867cda19949d5253e609f1b4 ./web/js/router.js -3426ec31011173172199bff7deacbef1d5eb0121012b5ea0e53b7a72a674a853 ./web/js/settings-ui.js -13806217b2312dff3bd6cc2b16f75d9bdffc7a587215ae9f862706cd7699673f ./web/js/settings.js +55c163fce9520979d380739546ef3036186297466d5fc3ac8cb0680408fb444e ./web/js/settings-ui.js +29e6ca03eb62c6158de9ab0b9e74100129478794ae5c697321e217fb1b69ff23 ./web/js/settings.js 3815fea7ae1694568c2f249bc8460e0563ced6bde89214f74f21b41fe8cad9a6 ./web/lang-init.js 6b653e4fe2d4db4ffae6ff8f39b8ce57a10a990dc0000ee4aae51d949afadd52 ./web/manifest.webmanifest -10a47e3645316103b8ad85a8635ec6305da9717226568e76db58e2d77e894e8f ./web/styles.css +3274600badfc59219db1d76e7767d7448e4cf1a9aedee94e90435c906c8269b3 ./web/styles.css fefdf12cbbb0a5d8a2cb89275cae28455aa42aaf179542305cc8f5e0f4825898 ./web/sw.js d505d793ce7cc9485b45b78bba1c0d51887adc7451ab59a42702946e5b991382 ./web/theme-init.js diff --git a/docs/API.md b/docs/API.md index 1f7c586..aa890f6 100644 --- a/docs/API.md +++ b/docs/API.md @@ -1,6 +1,6 @@ # GREE Controller API reference -HTTP and WebSocket API for GREE Controller **0.14.1**. +HTTP and WebSocket API for GREE Controller **0.14.2**. [← Main documentation](../README.md) @@ -232,7 +232,7 @@ Response: { "status": "ok", "name": "gree-controller", - "version": "0.14.1", + "version": "0.14.2", "uptime_seconds": 1234, "control_ready": true, "time": "2026-08-30T06:54:00Z" @@ -295,7 +295,7 @@ Returns the initial Web UI snapshot: "control_plan": {"generated_at": "2026-09-04T08:00:00Z", "zones": [], "rules": []}, "control_plan_revision": 42, "system": { - "version": "0.14.1", + "version": "0.14.2", "uptime_seconds": 1234, "auth_required": false, "control_ready": true, @@ -1654,6 +1654,12 @@ Additional Flow condition blocks are `house_mode`, `device_state`, `zone_state`, +## Swing control across manual, thermostat and automation surfaces — 0.14.2 + +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 Installation configuration endpoints: diff --git a/docs/FLOW.md b/docs/FLOW.md index 1365f04..ae847fd 100644 --- a/docs/FLOW.md +++ b/docs/FLOW.md @@ -4,7 +4,7 @@ Flow is an authoring/orchestration layer. It does not replace the thermostat, sc ## Compilation -- `weekday + time_range -> zone_thermostat` with schedule-compatible settings compiles to a native `Schedule`. +- `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"`. - Generated rows keep `flow_id` and `flow_node_id`, use stable `flow-` 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. @@ -14,7 +14,7 @@ Flow is an authoring/orchestration layer. It does not replace the thermostat, sc ## Existing control domains remain authoritative -- `zone_thermostat` writes the existing persistent Zone intent and wakes the normal thermostat cycle. +- `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. - `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. diff --git a/docs/openapi.json b/docs/openapi.json index 9943e22..24a9254 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -2,9 +2,9 @@ "openapi": "3.1.0", "info": { "title": "GREE Controller API", - "version": "0.14.1", + "version": "0.14.2", "summary": "Local HTTP/WebSocket API for GREE HVAC control", - "description": "Self-hosted API used by the GREE Controller Web UI and Home Assistant integration.\n\n### Authentication\nAdministrator endpoints accept `Authorization: Bearer ` or `x-api-token: ` when configured. Restricted Home Assistant control endpoints also accept generated integration tokens.\n\n### Local and GREE Cloud\nDevices use one API model and one command surface. `connection_type=local` is dispatched only to the existing LAN/UDP provider; `connection_type=gree_cloud` is dispatched only to the GREE Cloud provider. No automatic transport fallback is performed.\n\n### GREE Cloud security\nCloud passwords, REST/MQTT tokens, Authorization values and device cipher keys are never returned in normal API responses or Cloud diagnostics. MQTT uses TLS with certificate and hostname validation.\n\n### Energy\nGREE Cloud `ElcAll` and selected Home Assistant cumulative energy sensors are normalized to kWh and converted to consumption deltas before aggregation. Raw cumulative values are retained only as meter samples/baselines.\n\n### 0.14.1\nAdds split/multisplit installation groups with shared energy and outdoor-temperature sources, multi-target energy charts with period comparison, cleaner Cloud status/alerts and safe MQTT cleanup after the last Cloud device is removed.\n\n### 0.14.0\nAdds first-class GREE Cloud MQTT status/control, reconnect/resubscribe lifecycle, Cloud diagnostics, per-device capabilities, Cloud/HA energy sources and energy-history aggregation while preserving the LAN transport contract.", + "description": "Self-hosted API used by the GREE Controller Web UI and Home Assistant integration.\n\n### Authentication\nAdministrator endpoints accept `Authorization: Bearer ` or `x-api-token: ` when configured. Restricted Home Assistant control endpoints also accept generated integration tokens.\n\n### Local and GREE Cloud\nDevices use one API model and one command surface. `connection_type=local` is dispatched only to the existing LAN/UDP provider; `connection_type=gree_cloud` is dispatched only to the GREE Cloud provider. No automatic transport fallback is performed.\n\n### GREE Cloud security\nCloud passwords, REST/MQTT tokens, Authorization values and device cipher keys are never returned in normal API responses or Cloud diagnostics. MQTT uses TLS with certificate and hostname validation.\n\n### Energy\nGREE Cloud `ElcAll` and selected Home Assistant cumulative energy sensors are normalized to kWh and converted to consumption deltas before aggregation. Raw cumulative values are retained only as meter samples/baselines.\n\n### 0.14.2\nAdds independent vertical/horizontal swing controls across manual device control, thermostat zones, legacy automations, Visual Flow and Home Assistant zone climates. Swing is treated as an auxiliary one-shot unit setting and does not replace thermostat ownership.\n\n### 0.14.1\nAdds split/multisplit installation groups with shared energy and outdoor-temperature sources, multi-target energy charts with period comparison, cleaner Cloud status/alerts and safe MQTT cleanup after the last Cloud device is removed.\n\n### 0.14.0\nAdds first-class GREE Cloud MQTT status/control, reconnect/resubscribe lifecycle, Cloud diagnostics, per-device capabilities, Cloud/HA energy sources and energy-history aggregation while preserving the LAN transport contract.", "license": { "name": "MIT" } @@ -5391,7 +5391,7 @@ }, "version": { "type": "string", - "example": "0.14.1" + "example": "0.14.2" }, "uptime_seconds": { "type": "integer", diff --git a/ha-addon/home-assistant/custom_components/gree_controller/climate.py b/ha-addon/home-assistant/custom_components/gree_controller/climate.py index 79f58a0..6c06b2a 100644 --- a/ha-addon/home-assistant/custom_components/gree_controller/climate.py +++ b/ha-addon/home-assistant/custom_components/gree_controller/climate.py @@ -266,12 +266,8 @@ class GreeControllerZoneClimate(CoordinatorEntity[GreeControllerCoordinator], Cl _attr_target_temperature_step = 0.5 _attr_hvac_modes = [HVACMode.OFF, HVACMode.AUTO, HVACMode.COOL, HVACMode.HEAT] _attr_preset_modes = ["auto", "comfort", "sleep", "away"] - _attr_supported_features = ( - ClimateEntityFeature.TARGET_TEMPERATURE - | ClimateEntityFeature.PRESET_MODE - | ClimateEntityFeature.TURN_ON - | ClimateEntityFeature.TURN_OFF - ) + _attr_swing_modes = [SWING_OFF, SWING_ON] + _attr_swing_horizontal_modes = [SWING_OFF, SWING_ON] def __init__(self, coordinator: GreeControllerCoordinator, zone_id: str) -> None: super().__init__(coordinator) @@ -285,10 +281,30 @@ class GreeControllerZoneClimate(CoordinatorEntity[GreeControllerCoordinator], Cl return zone return {} + @property + def _device(self) -> dict[str, Any]: + device_id = str(self._zone.get("device_id") or "").strip() + return self.coordinator.data.get(device_id, {}) if device_id else {} + @property def available(self) -> bool: return super().available and bool(self._zone) + @property + def supported_features(self) -> ClimateEntityFeature: + features = ( + ClimateEntityFeature.TARGET_TEMPERATURE + | ClimateEntityFeature.PRESET_MODE + | ClimateEntityFeature.TURN_ON + | ClimateEntityFeature.TURN_OFF + ) + capabilities = self._device.get("capabilities") or {} + if capabilities.get("vertical_swing", True) is not False: + features |= ClimateEntityFeature.SWING_MODE + if capabilities.get("horizontal_swing", True) is not False: + features |= ClimateEntityFeature.SWING_HORIZONTAL_MODE + return features + @property def device_info(self) -> DeviceInfo: zone = self._zone @@ -325,6 +341,14 @@ class GreeControllerZoneClimate(CoordinatorEntity[GreeControllerCoordinator], Cl def preset_mode(self) -> str: return str(self._zone.get("preset_override") or "auto") + @property + def swing_mode(self) -> str: + return SWING_ON if self._device.get("swing_vertical", False) else SWING_OFF + + @property + def swing_horizontal_mode(self) -> str: + return SWING_ON if self._device.get("swing_horizontal", False) else SWING_OFF + @property def extra_state_attributes(self) -> dict[str, Any]: zone = self._zone @@ -380,6 +404,20 @@ class GreeControllerZoneClimate(CoordinatorEntity[GreeControllerCoordinator], Cl return await self.coordinator.async_zone_control(self._zone_id, {"preset": preset_mode}) + async def async_set_swing_mode(self, swing_mode: str) -> None: + device_id = str(self._zone.get("device_id") or "").strip() + if device_id: + await self.coordinator.async_device_command( + device_id, {"swing_vertical": swing_mode == SWING_ON} + ) + + async def async_set_swing_horizontal_mode(self, swing_horizontal_mode: str) -> None: + device_id = str(self._zone.get("device_id") or "").strip() + if device_id: + await self.coordinator.async_device_command( + device_id, {"swing_horizontal": swing_horizontal_mode == SWING_ON} + ) + async def async_turn_on(self) -> None: await self.coordinator.async_zone_control(self._zone_id, {"power": True}) diff --git a/ha-addon/home-assistant/custom_components/gree_controller/manifest.json b/ha-addon/home-assistant/custom_components/gree_controller/manifest.json index 92b6c62..d87e256 100644 --- a/ha-addon/home-assistant/custom_components/gree_controller/manifest.json +++ b/ha-addon/home-assistant/custom_components/gree_controller/manifest.json @@ -1,7 +1,7 @@ { "domain": "gree_controller", "name": "GREE Controller", - "version": "0.14.1", + "version": "0.14.2", "config_flow": true, "integration_type": "hub", "iot_class": "local_polling", diff --git a/ha-addon/repository/gree-controller/CHANGELOG.md b/ha-addon/repository/gree-controller/CHANGELOG.md index 1af6db1..52396bd 100644 --- a/ha-addon/repository/gree-controller/CHANGELOG.md +++ b/ha-addon/repository/gree-controller/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 0.14.2 + +- Adds separate vertical and horizontal swing controls for local units in Manual Control, respecting per-device capabilities. +- Adds vertical and horizontal swing controls to thermostat zone cards without switching the zone into manual device ownership. +- Adds optional `swing_vertical` and `swing_horizontal` actions to legacy direct-device automations. +- Extends Visual Flow so both `device_action` and `zone_thermostat` can control vertical/horizontal swing, and device-state selectors use readable swing labels. +- Prevents swing-enabled thermostat Flows from being reduced to native schedules, preserving the auxiliary swing action at runtime. +- Exposes vertical and horizontal swing on Home Assistant zone thermostat climate entities when supported by the assigned unit. + ## 0.14.1 - Adds split and multisplit installation groups in the Devices section, including assignment of indoor units to a shared outdoor installation. diff --git a/ha-addon/repository/gree-controller/config.yaml b/ha-addon/repository/gree-controller/config.yaml index b527e7e..325367b 100644 --- a/ha-addon/repository/gree-controller/config.yaml +++ b/ha-addon/repository/gree-controller/config.yaml @@ -1,5 +1,5 @@ name: "GREE Controller" -version: "0.14.1" +version: "0.14.2" slug: "gree_controller" description: "Local GREE HVAC controller with Web UI and Home Assistant integration" url: "https://git.linuxiarz.pl/gru/gree-controller-ha-addon/" diff --git a/lang/en.json b/lang/en.json index 0596c64..0fb438f 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1286,6 +1286,19 @@ "energy.comparisonPeriod": "Comparison period", "energy.groupSource": "Group source", "energy.selectAtLeastOne": "Select at least one device or installation.", - "energy.multiselectHint": "You can select several items (Ctrl/Cmd or Shift)." + "energy.multiselectHint": "Select up to 8 items.", + "energy.totalMeter": "Total consumption", + "energy.lastReading": "Last reading", + "devices.sharedOutdoorMetric": "Shared outdoor temperature", + "history.sharedOutdoor": "shared outdoor temperature", + "energy.chooseTargets": "Choose devices / installations", + "energy.selectedCount": "Selected: {count}", + "energy.maxTargets": "You can select up to 8 devices or installations.", + "energy.period": "Data period", + "energy.periodHint": "Time range used for energy bars and totals.", + "devices.swingVertical": "Vertical swing", + "devices.swingHorizontal": "Horizontal swing", + "flow.thermostatDeviceOptions": "Unit swing control", + "flow.thermostatDeviceOptionsHint": "Vertical and horizontal swing are sent to the unit as independent settings and do not change thermostat logic." } -} \ No newline at end of file +} diff --git a/lang/pl.json b/lang/pl.json index 05fa6aa..92ff316 100644 --- a/lang/pl.json +++ b/lang/pl.json @@ -1286,6 +1286,19 @@ "energy.comparisonPeriod": "Okres porównawczy", "energy.groupSource": "Źródło grupy", "energy.selectAtLeastOne": "Wybierz co najmniej jedno urządzenie lub instalację.", - "energy.multiselectHint": "Możesz zaznaczyć kilka pozycji (Ctrl/Cmd lub Shift)." + "energy.multiselectHint": "Wybierz do 8 pozycji.", + "energy.totalMeter": "Całkowite zużycie", + "energy.lastReading": "Ostatni odczyt", + "devices.sharedOutdoorMetric": "Wspólna temperatura zewnętrzna", + "history.sharedOutdoor": "wspólna temperatura zewnętrzna", + "energy.chooseTargets": "Wybierz urządzenia / instalacje", + "energy.selectedCount": "Wybrano: {count}", + "energy.maxTargets": "Możesz wybrać maksymalnie 8 urządzeń lub instalacji.", + "energy.period": "Okres danych", + "energy.periodHint": "Zakres czasu używany do słupków i sum energii.", + "devices.swingVertical": "Swing pionowy", + "devices.swingHorizontal": "Swing poziomy", + "flow.thermostatDeviceOptions": "Sterowanie żaluzjami jednostki", + "flow.thermostatDeviceOptionsHint": "Swing pionowy i poziomy są wysyłane do jednostki jako niezależne ustawienia i nie zmieniają logiki termostatu." } -} \ No newline at end of file +} diff --git a/scripts/api_dev_test.py b/scripts/api_dev_test.py index c37189e..6fcbd84 100644 --- a/scripts/api_dev_test.py +++ b/scripts/api_dev_test.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Developer smoke/integration tests for GREE Controller API 0.14.1. +"""Developer smoke/integration tests for GREE Controller API 0.14.2. Default mode is read-only and safe to run against a real controller. Use --settings-write to additionally round-trip all split settings resources and @@ -26,7 +26,7 @@ from typing import Any, Callable, Iterable, Optional DEFAULT_BASE_URL = os.environ.get("GREE_API_URL", "http://127.0.0.1:8787") DEFAULT_TOKEN = os.environ.get("GREE_API_TOKEN", "") -DEFAULT_EXPECTED_VERSION = "0.14.1" +DEFAULT_EXPECTED_VERSION = "0.14.2" SETTINGS_PATHS: dict[str, set[str]] = { "/api/settings/application": {"simulator_enabled"}, @@ -571,7 +571,7 @@ def run_suite(args: argparse.Namespace) -> int: print(f"Settings write tests: {'ENABLED' if args.settings_write else 'disabled'}\n") runner.run("public health", lambda: test_health(client, args.expected_version)) - runner.run("OpenAPI 0.14.1 contract", lambda: test_openapi(client, args.expected_version)) + runner.run("OpenAPI 0.14.2 contract", lambda: test_openapi(client, args.expected_version)) runner.run("bootstrap snapshot contract", lambda: test_bootstrap_contract(client)) runner.run("protected API requires auth", lambda: test_protected_auth(client)) @@ -640,7 +640,7 @@ def run_suite(args: argparse.Namespace) -> int: def parse_args(argv: Optional[Iterable[str]] = None) -> argparse.Namespace: parser = argparse.ArgumentParser( - description="Developer smoke/integration tests for GREE Controller API 0.14.1.", + description="Developer smoke/integration tests for GREE Controller API 0.14.2.", formatter_class=argparse.RawDescriptionHelpFormatter, epilog="""Examples: python3 scripts/api_dev_test.py diff --git a/src/api/configuration.rs b/src/api/configuration.rs index dc2ea33..6eed3ac 100644 --- a/src/api/configuration.rs +++ b/src/api/configuration.rs @@ -27,7 +27,7 @@ async fn export_configuration( fn validate_configuration_header(export: &ConfigurationExport) -> Result<(), AppError> { if export.format_version != 3 { - return Err(AppError::BadRequest("unsupported configuration export version; version 3 is required by GREE Controller 0.14.1".into())); + return Err(AppError::BadRequest("unsupported configuration export version; version 3 is required by GREE Controller 0.14.2".into())); } if export.settings.control_strategy != "setpoint" { return Err(AppError::BadRequest( diff --git a/src/api/flows.rs b/src/api/flows.rs index cb2d7b4..161ac8b 100644 --- a/src/api/flows.rs +++ b/src/api/flows.rs @@ -1107,7 +1107,9 @@ fn compile_flow( && native_time_range && preset_for_schedule != "auto" && mode_for_schedule == "auto" - && flow_bool(&action_node.config, "power").is_none(); + && flow_bool(&action_node.config, "power").is_none() + && flow_bool(&action_node.config, "swing_vertical").is_none() + && flow_bool(&action_node.config, "swing_horizontal").is_none(); if schedule_only { let zone_id = flow_string(&action_node.config, "zone_id") .ok_or_else(|| AppError::BadRequest("thermostat block needs a zone".into()))?; @@ -1285,6 +1287,8 @@ fn compile_flow( } item.action.mode = Some(mode); } + item.action.swing_vertical = flow_bool(&action_node.config, "swing_vertical"); + item.action.swing_horizontal = flow_bool(&action_node.config, "swing_horizontal"); } "device_action" => { let id = flow_string(&action_node.config, "device_id") diff --git a/src/api/system.rs b/src/api/system.rs index f482ce6..652da8d 100644 --- a/src/api/system.rs +++ b/src/api/system.rs @@ -20,12 +20,81 @@ struct SystemInfoResponse { gree_received_frames_by_device: std::collections::HashMap, } +#[derive(Debug, Clone, serde::Serialize)] +struct DeviceGroupEnergySnapshot { + total_kwh: f64, + timestamp: Option>, + source: String, + origin: String, +} + +fn device_group_energy_snapshot( + state: &AppState, + group: &DeviceGroup, + devices: &[Device], +) -> Result, AppError> { + let selected_source = match group.energy_source { + EnergySourcePreference::GreeCloud => Some("gree_cloud"), + EnergySourcePreference::HomeAssistant => Some("home_assistant"), + EnergySourcePreference::Auto if group.energy_device_id.is_some() => Some("gree_cloud"), + EnergySourcePreference::Auto if group.ha_energy_entity_id.is_some() => Some("home_assistant"), + EnergySourcePreference::Auto => None, + }; + + match selected_source { + Some("gree_cloud") => { + let Some(device_id) = group.energy_device_id.as_deref() else { + return Ok(None); + }; + if let Some(device) = devices.iter().find(|device| device.id == device_id) { + if let Some(total_kwh) = device + .total_energy_kwh + .filter(|value| value.is_finite() && *value >= 0.0) + { + return Ok(Some(DeviceGroupEnergySnapshot { + total_kwh, + timestamp: device + .last_cloud_sync + .clone() + .or_else(|| device.last_seen.clone()), + source: "gree_cloud".into(), + origin: "cloud".into(), + })); + } + } + Ok(state + .db + .last_energy_reading(device_id, "gree_cloud")? + .map(|reading| DeviceGroupEnergySnapshot { + total_kwh: reading.normalized_meter_kwh, + timestamp: Some(reading.timestamp), + source: "gree_cloud".into(), + origin: "database".into(), + })) + } + Some("home_assistant") => { + let storage_id = format!("group:{}", group.id); + Ok(state + .db + .last_energy_reading(&storage_id, "home_assistant")? + .map(|reading| DeviceGroupEnergySnapshot { + total_kwh: reading.normalized_meter_kwh, + timestamp: Some(reading.timestamp), + source: "home_assistant".into(), + origin: "database".into(), + })) + } + _ => Ok(None), + } +} + #[derive(Debug, serde::Serialize)] struct BootstrapResponse { devices: Vec, zones: Vec, groups: Vec, device_groups: Vec, + device_group_energy: std::collections::HashMap, schedules: Vec, automations: Vec, flows: Vec, @@ -59,6 +128,13 @@ async fn build_bootstrap(state: &AppState) -> Result Result device lock order so the durable Flow intent // cannot leave a unit running and polling/manual control cannot interleave with the frame. + // Swing can safely share this explicit frame because it is outside thermostat regulation. let _device_guard = state.lock_device_operation(&device_id).await; - send_command_locked_forced(state, &device_id, DeviceCommand { power: Some(false), ..Default::default() }).await?; + send_command_locked_forced( + state, + &device_id, + DeviceCommand { + power: Some(false), + swing_vertical: swing_command.swing_vertical, + swing_horizontal: swing_command.swing_horizontal, + ..Default::default() + }, + ) + .await?; + } else if !swing_command.is_empty() { + // Swing is intentionally a one-shot auxiliary unit setting. It does not participate in + // temperature/fan regulation, so the thermostat keeps ownership of the zone. + let _ = send_automatic_device_command_if_owned(state, &device_id, swing_command).await?; } + state.wake_zone_control(); Ok(true) } diff --git a/src/engine/energy.rs b/src/engine/energy.rs index fccbba7..5f3d157 100644 --- a/src/engine/energy.rs +++ b/src/engine/energy.rs @@ -56,6 +56,15 @@ pub(crate) fn record_cumulative_energy_sample( reset_detected, }; state.db.add_energy_reading(&reading)?; + state.broadcast( + "energy.updated", + json!({ + "target_id": reading.device_id.clone(), + "source": reading.source.clone(), + "total_kwh": reading.normalized_meter_kwh, + "timestamp": reading.timestamp, + }), + ); queue_influx_energy(state, reading.clone()); Ok(reading) } diff --git a/src/engine/history.rs b/src/engine/history.rs index 0a323e3..33fe72b 100644 --- a/src/engine/history.rs +++ b/src/engine/history.rs @@ -121,13 +121,7 @@ fn queue_influx_ha(state: &AppState, reading: HaReading) { }); } -fn gree_outdoor_temperature(devices: &[Device]) -> Option { - let mut values: Vec = devices - .iter() - .filter(|device| device.enabled && device.online && device.communication_failures == 0) - .filter_map(|device| device.outdoor_temperature) - .filter(|value| value.is_finite() && (-60.0..=70.0).contains(value)) - .collect(); +fn median_outdoor_temperature(mut values: Vec) -> Option { if values.is_empty() { return None; } @@ -140,3 +134,53 @@ fn gree_outdoor_temperature(devices: &[Device]) -> Option { }; Some((value * 10.0).round() / 10.0) } + +fn valid_outdoor_temperature(device: &Device) -> Option { + if !device.enabled || !device.online || device.communication_failures != 0 { + return None; + } + device + .outdoor_temperature + .filter(|value| value.is_finite() && (-60.0..=70.0).contains(value)) +} + +fn gree_outdoor_temperature(devices: &[Device], device_groups: &[DeviceGroup]) -> Option { + let by_id: HashMap<&str, &Device> = devices + .iter() + .map(|device| (device.id.as_str(), device)) + .collect(); + let mut grouped_ids = HashSet::new(); + let mut values = Vec::new(); + + for group in device_groups { + for device_id in &group.device_ids { + grouped_ids.insert(device_id.as_str()); + } + let group_value = group + .outdoor_temperature_device_id + .as_deref() + .and_then(|source_id| by_id.get(source_id).copied()) + .and_then(valid_outdoor_temperature) + .or_else(|| { + median_outdoor_temperature( + group + .device_ids + .iter() + .filter_map(|id| by_id.get(id.as_str()).copied()) + .filter_map(valid_outdoor_temperature) + .collect(), + ) + }); + if let Some(value) = group_value { + values.push(value); + } + } + + values.extend( + devices + .iter() + .filter(|device| !grouped_ids.contains(device.id.as_str())) + .filter_map(valid_outdoor_temperature), + ); + median_outdoor_temperature(values) +} diff --git a/src/engine/tests.rs b/src/engine/tests.rs index 333f74f..de8c1b9 100644 --- a/src/engine/tests.rs +++ b/src/engine/tests.rs @@ -352,6 +352,7 @@ mod tests { assert!(automation_action_conflicts_with_thermostat(&DeviceCommand { mode: Some("dry".into()), ..Default::default() })); assert!(!automation_action_conflicts_with_thermostat(&DeviceCommand { mode: Some("heat".into()), ..Default::default() })); assert!(!automation_action_conflicts_with_thermostat(&DeviceCommand { light: Some(false), turbo: Some(true), ..Default::default() })); + assert!(!automation_action_conflicts_with_thermostat(&DeviceCommand { swing_vertical: Some(true), swing_horizontal: Some(false), ..Default::default() })); } #[test] @@ -988,9 +989,38 @@ mod tests { let mut c = Device::simulated_default(); c.id = "sim-c".into(); c.outdoor_temperature = Some(40.0); - assert_eq!(gree_outdoor_temperature(&[a, b, c]), Some(12.0)); + assert_eq!(gree_outdoor_temperature(&[a, b, c], &[]), Some(12.0)); } + #[test] + fn gree_outdoor_fallback_counts_multisplit_once() { + let mut a = Device::simulated_default(); + a.id = "a".into(); + a.outdoor_temperature = Some(10.0); + let mut b = Device::simulated_default(); + b.id = "b".into(); + b.outdoor_temperature = Some(10.0); + let mut c = Device::simulated_default(); + c.id = "c".into(); + c.outdoor_temperature = Some(30.0); + let now = Utc::now(); + let group = DeviceGroup { + id: "multi".into(), + name: "Multi".into(), + kind: crate::models::DeviceGroupKind::Multisplit, + device_ids: vec!["a".into(), "b".into()], + energy_source: EnergySourcePreference::Auto, + energy_device_id: None, + ha_energy_entity_id: None, + ha_energy_unit: None, + ha_energy_device_class: None, + ha_energy_state_class: None, + outdoor_temperature_device_id: Some("a".into()), + created_at: now, + updated_at: now, + }; + assert_eq!(gree_outdoor_temperature(&[a, b, c], &[group]), Some(20.0)); + } #[test] fn global_one_shot_queue_does_not_change_zone_ownership() { diff --git a/src/engine/zone_control.rs b/src/engine/zone_control.rs index 2a87017..1e11475 100644 --- a/src/engine/zone_control.rs +++ b/src/engine/zone_control.rs @@ -76,7 +76,8 @@ async fn resolve_cycle_outdoor_temperature( } else { None }; - let temperature = from_home_assistant.or_else(|| gree_outdoor_temperature(devices)); + let device_groups = state.db.list_device_groups().unwrap_or_default(); + let temperature = from_home_assistant.or_else(|| gree_outdoor_temperature(devices, &device_groups)); let mut current = state.outdoor_temperature.write().await; if *current != temperature { *current = temperature; diff --git a/web/index.html b/web/index.html index 76c1a2f..1407291 100644 --- a/web/index.html +++ b/web/index.html @@ -333,7 +333,7 @@
- +
@@ -1584,6 +1584,18 @@ type="text" inputmode="decimal" step="0.1" min="8" max="30" name="action_target_temperature">
+
+ + +