diff --git a/Cargo.lock b/Cargo.lock index 4dbf800..b5dc356 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -700,7 +700,7 @@ dependencies = [ [[package]] name = "gree-controller" -version = "0.14.2" +version = "0.14.4" dependencies = [ "aes", "aes-gcm", diff --git a/Cargo.toml b/Cargo.toml index 6b6a18e..c3956b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gree-controller" -version = "0.14.2" +version = "0.14.4" 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 221af11..2c4be02 100644 --- a/FILE_MANIFEST.sha256 +++ b/FILE_MANIFEST.sha256 @@ -1,19 +1,19 @@ -80472072ffaa0e14ef6fd28b02f757d1947fc656687fef19fa96b58b5f13baa5 ./.env.example +b99b779e89161c581acf74b67a32aface8f410a9fdf1d6d6fd7424226c0c1075 ./.env.example a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore -59fe6d7de7f780481d0a7f614d4325f905a8aee8d971717566fc8889340223b3 ./Cargo.lock -97b67fd716185b18bb12d9170b502f81c6ffaf099816c893305c9f837048cb1f ./Cargo.toml +6428e098916d33b88865b31840f3a0ec7d1191ed86eb30a634c5c74a188f0fb9 ./Cargo.lock +708d68ca64a4623fb2e21be1e6e770b33facd2b901b3057e7f289b0ec0611e03 ./Cargo.toml 19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE -ee65b2f48b4cc5553e01689ace0f33151bbb2a2738f07ff7918cbf033b6a0f4d ./README.md -31d804b6af6addcdda8964a08a8a8c4b90fde7cc92eba7527d895d3c3deecca9 ./SOURCE_MANIFEST.sha256 +8601961aee2029e5bc8281b7f950d7de25fb1d85c6b89827cab471625a745d6c ./README.md +2345439df2b16d0478cff70d305307580052b1d2495e699c99fd743f3d3aeb2e ./SOURCE_MANIFEST.sha256 bb549919b96b0413152dd905aec8640bb53301f07ec7d0b1be48ccbd37936fef ./build.rs -40199fea4917f9278344780440b0f47ca3ed5c6e7873e56040e16519edac7834 ./docs/API.md -ff213ad348e7f8b5cae7fe4adcad84d72db693765d424f270faa1acca9c23ce7 ./docs/FLOW.md -be8879ede6b807450dd79af22ad739f4089d0ce0b04f66a8bc00ada685789506 ./docs/openapi.json +1aa5bb37b5d0c315fcc085d40678a63964be1fd537bb3b8f35f5497fea1af6a3 ./docs/API.md +f21d732eb1147f8f3167d2172cdf0b57e61edabdfecbaf97811d46c5bff7e47a ./docs/FLOW.md +0022f50fa10077531c3d1aa6afea5b4ea5ecff5e9d015269327df701e4a23c04 ./docs/openapi.json c65aa543d2e99f8f7d52736307b07ebd182597d80124661d53a16bb23f6f1118 ./ha-addon/.env.example a18d2460340264d34c267fa704ee51a42e59bcc1783ee4c0dbb2a8ef42fa0f16 ./ha-addon/Dockerfile 0417c88a990c72aecf071fba64d5d9da65dcda4d396cd7565477f7a4532c8c5e ./ha-addon/README.md cd7fd4b937a1d6138699f7effd184e3c077912b881e9c73c37b90bbafba68477 ./ha-addon/build.sh -fd041032f353ceb0a2b02005f5a78d74a460b12e4e78de13dcb60129859f8c4e ./ha-addon/home-assistant/README.md +082dfeb6326f3e1b9841ccf7f2954117ecdf0aa1a90b0aef1e6a1960dacdecbf ./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 0c008329aad15f5af6328380c1afe1f49db186b37b3d1cafec4b6f4088386e8e ./ha-addon/home-assistant/custom_components/gree_controller/climate.py @@ -22,7 +22,7 @@ b7f0873109c52be9d7f09bea3dffc416103c50085e1f0680d11661a969479898 ./ha-addon/hom 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 -25c5ffb84460bf81f0c54abfc912e5bfd85535d3558df7a10e1de9541dd2afa3 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json +b730225f7303eb2b4a19930a2cb80c724bea7afb3427770e72a987152b5402de ./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 -56d3c922fdabb05213715d239e2b6279431d75e81a9a861d8172bf35b9bb4e15 ./ha-addon/repository/gree-controller/CHANGELOG.md +9372c3d9cfcbffd4aea4d195a646570ab52be5019c756827645bbd56725eb402 ./ha-addon/repository/gree-controller/CHANGELOG.md 3c06bd11671452ea6472afd5543fb91a39aba73a1d2d95c7762c04f94ab64030 ./ha-addon/repository/gree-controller/DOCS.md c3db302f38d26dc9f8caf37a1a8a07a1334cc07ae301d452f2940a5ec5915719 ./ha-addon/repository/gree-controller/README.md -a3a74b2aaa20fd39a050f8f360eec543d70b3fd43271617a60a0b95fd0cdf2ca ./ha-addon/repository/gree-controller/config.yaml +9a902d607e10f8a0dc1fffb5b41da92a2d5326ccec2b2df13aa82cd58a9ae3b4 ./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 -2db3d2a8611d6bc328452a6a8bd3da6fc6d52cddf65846b778e8e01ca841c070 ./lang/en.json -da9bb73b06e84965b7f4f85c18c94b87f0d1dc4460459fdb6b2a45b271b884c0 ./lang/pl.json +8e64e916d21bca02f1a29dac76e6458c5b6b2ebd90eabf9037ae6463ee057d7e ./lang/en.json +ee29bebe2cda2cdf818c9a6829feecb9cb069974f9d3602bb1e3fbb6faba2744 ./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 -4094c8b169b7c3babc15b7af3d4d6f7fa12239a279dc8c73c546ea671ad1aef8 ./scripts/api_dev_test.py +a23ca9caaf80da017b1186893b0c56fc89bdd87c05ebf401ee769354a1842b7e ./scripts/api_dev_test.py 5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh 6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh 1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh @@ -102,7 +102,7 @@ b50782b3742dfbf8a319c60571c968e93fdf8547db747c759edcffae68cb98bf ./scripts/upda 3dfd1e053cbfa0d684dd88cf99b244a96a36e2ff23d2a0715b8327ab62258254 ./src/api/assets.rs 05c255919f7b3d81188fc5f1410be6a0037471dfc44e93bffb009f29a1e6a3fc ./src/api/auth.rs 9e4fc675306e111ed3db7af9822e2925d88109c31b9e309851665a975c0bd83c ./src/api/automations.rs -cc439eeeeb3cb87202ab9f78a9e2e746ce79c15e18647c7f32442b4e9053f160 ./src/api/configuration.rs +1c913c3119deaa0f473b1e4b40e2f8ba8f508a656640539141cd22fa69fe70a8 ./src/api/configuration.rs 19bc141286a336ad67e99079496eb0ae5126f986ab28be62b5f97758e5f6969c ./src/api/debug_tokens.rs 32ade46ec45d6faf7852242b9f5ba1be6eacbacbeb33d2deabfab59bf1610ce0 ./src/api/device_groups.rs fcbb3da0538aab451f71810f829cc248d6bb67e586dc418d71a572980583823d ./src/api/devices.rs @@ -110,18 +110,18 @@ fcbb3da0538aab451f71810f829cc248d6bb67e586dc418d71a572980583823d ./src/api/devi d59b080b392b574ec94276c7b485b455d0d1abacc7de2ef29dcbfcf7d86dbdb0 ./src/api/flows.rs 2cfee7029f065e81628a8942e3dad00a03c3a5cf4f8492d07f12b75d05c07359 ./src/api/gree_cloud.rs b61b0b4f196c5e72e47cf06ca0c14df3cf8eedbdef0242a9d2af33623663489f ./src/api/groups.rs -cecf1cc7ebf97b6b2ea4dcad0845b2dd434718c86b2e8300a1a52c2ceaf01611 ./src/api/history.rs +376ac63794378c427d798e853a060612e76a95c9aa0357c2f216ba9e4f769f2f ./src/api/history.rs e8edfbb725d6de650442444b0a929ad2be0bd2c2d7d227d2b3fdd99d05c94f94 ./src/api/house.rs -27e85400ccbdb0adcb8f5c1624e7187e9b649237cfb97349fcf1c0d78300540d ./src/api/integrations.rs +8bcaf384ab3a2c297f232800fbffa72edcaa09ea340de0d665f2dc63ccba139c ./src/api/integrations.rs 6bfbe5135d4f93bd8d19a707877b53ed2029fd8490b4b1954a7520888fb1d664 ./src/api/middleware.rs 45e34a447ff384d99a3c42c489ca9fa75849c74d4d246caa91c63d927de17396 ./src/api/openapi.rs -744ce18aa9e0227c6cdfb6980063d0d738d74c5865d45c20fd20026d4742bb3e ./src/api/public_settings.rs +4feca416de6ce8acc34082f432721016b109c7e6e70116e5e0b710ea403c7006 ./src/api/public_settings.rs 5b29f2e5aa2c422c5829ef89c7b59cf753883aa7f60661c95ab0f95cf676fa3f ./src/api/schedules.rs -1602f70f93b8fc5dbbc2d6b370d5a43faae90b13bb38beb043b285a8a121b4e1 ./src/api/settings.rs +513743ac7752d92f16213099d1fca6431759f2cd05a8a12f4ee1226cfd1637b3 ./src/api/settings.rs 1c1fe39aec55d10608ca3db83827855b07249cd1a81ae054eb1ee6464514092b ./src/api/system.rs 9118a5a3a381b010e22d646498ff56d2e05f51656789682cc4371b09c384aa98 ./src/api/websocket.rs -455c7238c9788d378e909a4fbc94d5390b67edd92e127f48b4b913ddcbe82fb0 ./src/api/zones.rs -788d80732d1266798b0d67ed016ecfcc55161ef90bf240a034d186a0ff186e5b ./src/config.rs +ddd1bfc8625b66f6e5f3d24b3122ac86c25f89b0af527e3dfce9102551a8f142 ./src/api/zones.rs +ca2b42d341a1ca0c7772a1256aed12c06667778d6588687f3edc9e88ed1db4fc ./src/config.rs f8394e0daca284070ac9ae0844d6b04fed7c30386f2d1ec409ecf07880bb0e6a ./src/db.rs fdc9d019097becc5c36a0560caa1619aa35d802c0d208902b3bfecbe1643e3e8 ./src/db/climate.rs e5d2027d40a99e61c2c572499af617a92de9e554f8916f3ea6f6731d1d6b09b4 ./src/db/configuration.rs @@ -152,11 +152,11 @@ c1e8de5392681ba1255e3e2d871a6ee860fb8ca987a0a8f973d9ded3977a68fb ./src/engine/s b0189f28d0f337dfd3ce0934da7a565e4a5535afc1c242214c26ed584b3b7840 ./src/engine/targets.rs 682c8b3956c657b655746cbd19ee97ee2f4cf953bc9de67d6d70a1b14f3dd746 ./src/engine/temperature.rs 833b72907375e75c3f42b3921ad70a8e298f5a93743bf0d0c19059aa272462d1 ./src/engine/temporary_thermostat.rs -47330f0d8ff170ea91ba37d019c0d5577151c87f0474bcc2a287850415f6b953 ./src/engine/tests.rs +82fde10161ac760e728532ac214018018a1cf7519b86ef6539705396206f5bf8 ./src/engine/tests.rs c89ff36c6a67bf4fbf68c43e18aa0a556de2dc69263fd79db376363afc741a4f ./src/engine/zone_actions.rs -28571e25e539fb1fe5e77e8bd003caf2da9e84fc34d156fd5f3e65e805798ca0 ./src/engine/zone_control.rs +81eab9a7ed4be387572f2377a14bf7f7d84f7c3f4ed8b33059a3f7e685514a7a ./src/engine/zone_control.rs bfac95cec87f0df6ce562153cd3cfe6731359d703e6c592d2c2f7ba01ea62d99 ./src/error.rs -ea553a1d8b44c74b300d7c4a76dcd3b370524eb57766730a1376d1ab3079913b ./src/home_assistant.rs +10271a141ec441964c66d5732155abda8993d9fcba8dc0cc77e7c35d5a40e466 ./src/home_assistant.rs e78ca9aff23362971eb6450534ea21bdc15150456c378ef9b226cb93a8dbc612 ./src/influxdb.rs da4068295f37c23222bb13bebac022d88ba7729604a49ad25438c55e45458a49 ./src/influxdb/codec.rs b96624fa34b218a6d5f028ba09579633f6f8a04c109f0e94801e8d89ceeeb4b5 ./src/influxdb/query.rs @@ -169,11 +169,11 @@ fc5f2802a073a175a8985e56bcb87eb1ff481226d5b4eeabad73dbba1839fc45 ./src/models/c 5e212a072bf74ef32a186b172404844b02a92363d216167f814056e1f2a9a7a8 ./src/models/device.rs aeab5b5acf35d38a54fd1d82e964fe56635ade69c0149ad423df317095d01555 ./src/models/flow.rs ba5d359e4d6c12d9f62c1b28bdfb40d312737930df5421814f5310f7183e96c1 ./src/models/history.rs -14e053b0dbea9ed7e88e8affaa595fa126ff833c18696fa695e923488ec40d70 ./src/models/integrations.rs +110b73c179ea1f85b7afa14f4cdcfd9b8232dd005a6417cf357b8efc73e702c0 ./src/models/integrations.rs f1359b68f1d041cd441f80429e77893eeb6f6194613b58fd3e7a212fb511db3e ./src/models/runtime.rs -4caae5c95404d2ffefb7db9507dcf4ed40a90d54bc6aeb51098e77e544d85c42 ./src/models/settings_api.rs +f8bd9b4106145ebcf20d003471bb54a329e7373cf14a3a712ecabf87ac59d6d3 ./src/models/settings_api.rs c4de6101de85d9d7e6814b2504b87d9216561e3d9e274054f081f505fb152140 ./src/models/temporary_thermostat.rs -7d798228a8d4d4e60a112167c001535f4b8a5fa32ec7a33554c73125c42256c2 ./src/models/zone.rs +bc2818ce8d0dae1edfb1b98e5d1c00b193f03eeaa28e343d3532f6023288738c ./src/models/zone.rs 62e696df8b86835df7ad127f03c257a78580769dc82bc66daa5041f71e8440a5 ./src/notifications.rs cca742bb2b500eb9269f69385fcf88053cd08aabf6a98b7669b8a7a2fd67df13 ./src/protocol/crypto.rs cd0612021f8ed6c9e0823d63883c0790db09aa485583f3f3fc9f2579f2c28ea3 ./src/protocol/gree.rs @@ -201,23 +201,23 @@ f13f1be3d5789539eba3fc1b59c14835f8c681eca7634e118fa3763a53aeee2b ./src/queries/ b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service 544a31cb2e5374227026afd6abd0d910381f3fca1638154ebd74e3c3d96a35d1 ./web/404.html e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg -db1d0583efbc48c3ab8226aeba83275a14cd5c78be011555548a7491ec28b4d5 ./web/index.html +4c243c29743b402966f48d904a17bf48d0ce9022a0b25100f0b7cd8b81926f5c ./web/index.html 9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js/README.md 27eff2821c40395369be15e197fa830535d2327fcb27c93a0e8e65a1c62de3ad ./web/js/bootstrap.js -713bf6526c83b85780ed9c0f0807c5a1aa009f44ddbc9a67c47141f925db9402 ./web/js/charts.js -ead5b315171a4bf4d443f3bba00a573ffd016a7667475d0e2cde480047446c70 ./web/js/core.js -d880ffdac6aa07cd10d79fbacf12f87eb4c69b48a80564bbfdd0248abe8884d5 ./web/js/dashboard.js +def704d31b00706c26bae73190be15b289e72e6ecc4d8c9e9a5bb1804c747206 ./web/js/charts.js +f33a59e6863346d6751a74e1f912b6f0cd7a187defc3b01267a8121c9cd7ec8f ./web/js/core.js +021736e8b8d02248a41788765af2dd46914a700e8cadb0c43a75e83ed3f2dd14 ./web/js/dashboard.js ed31601df41cc82032709f960a2b4061966abd58cde456ef7dda05e42eb91432 ./web/js/entities.js -08e8e130dd33be91d2155e40aa0bf72a807355b234223b10eb01178ddbbefd13 ./web/js/events.js -8c156909d2d2e59b7d7dc969b9520c978141a448a6cc5aae8fe74a8dc124f2bc ./web/js/flows.js -9e2209d41df7624c29741f03f12ed8e83b47cee2891c5a80cea8ca1bdacf1d5a ./web/js/forms.js +ef778fb570958704c2e079fa5cd645f67f884881e16984daa6f3ed52627b26a5 ./web/js/events.js +89d5f4098e74162ecdee2eef9441f88484a09e9197d21e30c40c220cedbf7e53 ./web/js/flows.js +52b4807f05a1325565d5e866007f49ea83c568574155cded131ce42a49df44f5 ./web/js/forms.js ae6195905f33a4a83d5ba7c22d698419e2de05d80ed37df6066e0380fa715b6e ./web/js/history.js 0dc879c0056251618633c9efd188fc1593eeae6965ad1bc993132dea6e171b44 ./web/js/main.js 53ebdaa64a1dc8327db5cd4e08fb3f551dc2df33c214f218c6bb1ae3f27ad4dd ./web/js/navigation.js e3d146a683b8beb332c752e533826160c2cf2236bc269ced7a4dd1249871027b ./web/js/realtime.js 9546ca5c5566994aff3c33d92295be46b65d51d874681764abe77470dc9c9c73 ./web/js/router.js -e62ca4a0cad10ca8b60c1fe4b5ecd0ccf9bd890cd58206a937dd1655fe216353 ./web/js/settings-ui.js -0dde5e1a19ab2384156229fe2a5d708e06bf58a2227f834d215af97d4375a5d3 ./web/js/settings.js +cdc55061a41b7325aad552357d8359b42d881589c39b438cdc2a48bf0f690db9 ./web/js/settings-ui.js +f00c3f603eb314f755a0c46424681b579e0b890453e2550007c719273815147c ./web/js/settings.js 3815fea7ae1694568c2f249bc8460e0563ced6bde89214f74f21b41fe8cad9a6 ./web/lang-init.js 6b653e4fe2d4db4ffae6ff8f39b8ce57a10a990dc0000ee4aae51d949afadd52 ./web/manifest.webmanifest 4b3c94a1988e8e18bd0c39e5ebf73ce6ef31ddea5bb352ec40b1ae7b3609f5e9 ./web/styles.css diff --git a/README.md b/README.md index 0b06501..4a3988d 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.2** +**Version: 0.14.4** -Version 0.14.2 adds separate vertical and horizontal swing control plus connectivity history. Local/LAN units can be sampled periodically for response time, jitter and packet loss, while optional GREE Cloud diagnostics measure REST and MQTT responsiveness. It keeps the split/multisplit energy and outdoor-temperature improvements from 0.14.1, while preserving independent Local and Cloud device entries. +Version 0.14.4 removes the obsolete Home Assistant test/default entity. The HA connection test validates only the server/API token. `HA_OUTDOOR_ENTITY_ID` is the global outdoor-temperature sensor, and each zone can optionally override it with `ha_outdoor_entity_id`; room-temperature HA sensors remain configured per zone. Outdoor overrides participate in aliases, metrics/history and Home Assistant entity suggestions in Visual Flow. > [Full API reference](docs/API.md) — authentication, every endpoint, request bodies, response models, WebSocket events and examples. @@ -286,19 +286,20 @@ Configure **Home Assistant / Sensors** in the Web UI or use: ```text HA_URL= HA_TOKEN= -HA_ENTITY_ID= HA_OUTDOOR_ENTITY_ID= HA_SENSOR_STALE_AFTER_SECONDS=300 HA_ALLOW_INVALID_TLS=false ``` -Per zone, `sensor_source` can be: +The Home Assistant connection test checks only `HA_URL` and `HA_TOKEN`; no entity is required. `HA_OUTDOOR_ENTITY_ID` is the optional global outdoor-temperature sensor. Per zone, `sensor_source` can be: - `device` — GREE indoor sensor, -- `home_assistant` — configured HA room sensor, -- `combined` — weighted GREE + HA value. +- `home_assistant` — Home Assistant room sensor, +- `combined` — weighted GREE + Home Assistant value. -Stale/unavailable external data falls back to the GREE sensor when possible. The optional outdoor sensor is only an assist signal; it never replaces room temperature. +For `home_assistant` and `combined`, each zone must set its own `ha_entity_id` room-temperature sensor. Room sensors are independent from the outdoor-temperature source and stale/unavailable room data falls back to the GREE sensor when possible. + +`HA_OUTDOOR_ENTITY_ID` is the global outdoor-temperature source used by outdoor-temperature assist. A zone may leave `ha_outdoor_entity_id` empty to use that global sensor, or set `ha_outdoor_entity_id` to use a different outdoor sensor only for that zone. If a zone override is unavailable, the controller falls back to the global outdoor source. Global and per-zone outdoor sensors are exposed through the normal alias/history/metric paths and appear in Home Assistant entity suggestions used by Visual Flows. ### 2. GREE Controller entities inside Home Assistant diff --git a/SOURCE_MANIFEST.sha256 b/SOURCE_MANIFEST.sha256 index f9d67f4..5c2533d 100644 --- a/SOURCE_MANIFEST.sha256 +++ b/SOURCE_MANIFEST.sha256 @@ -1,18 +1,18 @@ -80472072ffaa0e14ef6fd28b02f757d1947fc656687fef19fa96b58b5f13baa5 ./.env.example +b99b779e89161c581acf74b67a32aface8f410a9fdf1d6d6fd7424226c0c1075 ./.env.example a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore -59fe6d7de7f780481d0a7f614d4325f905a8aee8d971717566fc8889340223b3 ./Cargo.lock -97b67fd716185b18bb12d9170b502f81c6ffaf099816c893305c9f837048cb1f ./Cargo.toml +6428e098916d33b88865b31840f3a0ec7d1191ed86eb30a634c5c74a188f0fb9 ./Cargo.lock +708d68ca64a4623fb2e21be1e6e770b33facd2b901b3057e7f289b0ec0611e03 ./Cargo.toml 19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE -ee65b2f48b4cc5553e01689ace0f33151bbb2a2738f07ff7918cbf033b6a0f4d ./README.md +8601961aee2029e5bc8281b7f950d7de25fb1d85c6b89827cab471625a745d6c ./README.md bb549919b96b0413152dd905aec8640bb53301f07ec7d0b1be48ccbd37936fef ./build.rs -40199fea4917f9278344780440b0f47ca3ed5c6e7873e56040e16519edac7834 ./docs/API.md -ff213ad348e7f8b5cae7fe4adcad84d72db693765d424f270faa1acca9c23ce7 ./docs/FLOW.md -be8879ede6b807450dd79af22ad739f4089d0ce0b04f66a8bc00ada685789506 ./docs/openapi.json +1aa5bb37b5d0c315fcc085d40678a63964be1fd537bb3b8f35f5497fea1af6a3 ./docs/API.md +f21d732eb1147f8f3167d2172cdf0b57e61edabdfecbaf97811d46c5bff7e47a ./docs/FLOW.md +0022f50fa10077531c3d1aa6afea5b4ea5ecff5e9d015269327df701e4a23c04 ./docs/openapi.json c65aa543d2e99f8f7d52736307b07ebd182597d80124661d53a16bb23f6f1118 ./ha-addon/.env.example a18d2460340264d34c267fa704ee51a42e59bcc1783ee4c0dbb2a8ef42fa0f16 ./ha-addon/Dockerfile 0417c88a990c72aecf071fba64d5d9da65dcda4d396cd7565477f7a4532c8c5e ./ha-addon/README.md cd7fd4b937a1d6138699f7effd184e3c077912b881e9c73c37b90bbafba68477 ./ha-addon/build.sh -fd041032f353ceb0a2b02005f5a78d74a460b12e4e78de13dcb60129859f8c4e ./ha-addon/home-assistant/README.md +082dfeb6326f3e1b9841ccf7f2954117ecdf0aa1a90b0aef1e6a1960dacdecbf ./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 0c008329aad15f5af6328380c1afe1f49db186b37b3d1cafec4b6f4088386e8e ./ha-addon/home-assistant/custom_components/gree_controller/climate.py @@ -21,7 +21,7 @@ b7f0873109c52be9d7f09bea3dffc416103c50085e1f0680d11661a969479898 ./ha-addon/hom 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 -25c5ffb84460bf81f0c54abfc912e5bfd85535d3558df7a10e1de9541dd2afa3 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json +b730225f7303eb2b4a19930a2cb80c724bea7afb3427770e72a987152b5402de ./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 -56d3c922fdabb05213715d239e2b6279431d75e81a9a861d8172bf35b9bb4e15 ./ha-addon/repository/gree-controller/CHANGELOG.md +9372c3d9cfcbffd4aea4d195a646570ab52be5019c756827645bbd56725eb402 ./ha-addon/repository/gree-controller/CHANGELOG.md 3c06bd11671452ea6472afd5543fb91a39aba73a1d2d95c7762c04f94ab64030 ./ha-addon/repository/gree-controller/DOCS.md c3db302f38d26dc9f8caf37a1a8a07a1334cc07ae301d452f2940a5ec5915719 ./ha-addon/repository/gree-controller/README.md -a3a74b2aaa20fd39a050f8f360eec543d70b3fd43271617a60a0b95fd0cdf2ca ./ha-addon/repository/gree-controller/config.yaml +9a902d607e10f8a0dc1fffb5b41da92a2d5326ccec2b2df13aa82cd58a9ae3b4 ./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 -2db3d2a8611d6bc328452a6a8bd3da6fc6d52cddf65846b778e8e01ca841c070 ./lang/en.json -da9bb73b06e84965b7f4f85c18c94b87f0d1dc4460459fdb6b2a45b271b884c0 ./lang/pl.json +8e64e916d21bca02f1a29dac76e6458c5b6b2ebd90eabf9037ae6463ee057d7e ./lang/en.json +ee29bebe2cda2cdf818c9a6829feecb9cb069974f9d3602bb1e3fbb6faba2744 ./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 -4094c8b169b7c3babc15b7af3d4d6f7fa12239a279dc8c73c546ea671ad1aef8 ./scripts/api_dev_test.py +a23ca9caaf80da017b1186893b0c56fc89bdd87c05ebf401ee769354a1842b7e ./scripts/api_dev_test.py 5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh 6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh 1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh @@ -101,7 +101,7 @@ b50782b3742dfbf8a319c60571c968e93fdf8547db747c759edcffae68cb98bf ./scripts/upda 3dfd1e053cbfa0d684dd88cf99b244a96a36e2ff23d2a0715b8327ab62258254 ./src/api/assets.rs 05c255919f7b3d81188fc5f1410be6a0037471dfc44e93bffb009f29a1e6a3fc ./src/api/auth.rs 9e4fc675306e111ed3db7af9822e2925d88109c31b9e309851665a975c0bd83c ./src/api/automations.rs -cc439eeeeb3cb87202ab9f78a9e2e746ce79c15e18647c7f32442b4e9053f160 ./src/api/configuration.rs +1c913c3119deaa0f473b1e4b40e2f8ba8f508a656640539141cd22fa69fe70a8 ./src/api/configuration.rs 19bc141286a336ad67e99079496eb0ae5126f986ab28be62b5f97758e5f6969c ./src/api/debug_tokens.rs 32ade46ec45d6faf7852242b9f5ba1be6eacbacbeb33d2deabfab59bf1610ce0 ./src/api/device_groups.rs fcbb3da0538aab451f71810f829cc248d6bb67e586dc418d71a572980583823d ./src/api/devices.rs @@ -109,18 +109,18 @@ fcbb3da0538aab451f71810f829cc248d6bb67e586dc418d71a572980583823d ./src/api/devi d59b080b392b574ec94276c7b485b455d0d1abacc7de2ef29dcbfcf7d86dbdb0 ./src/api/flows.rs 2cfee7029f065e81628a8942e3dad00a03c3a5cf4f8492d07f12b75d05c07359 ./src/api/gree_cloud.rs b61b0b4f196c5e72e47cf06ca0c14df3cf8eedbdef0242a9d2af33623663489f ./src/api/groups.rs -cecf1cc7ebf97b6b2ea4dcad0845b2dd434718c86b2e8300a1a52c2ceaf01611 ./src/api/history.rs +376ac63794378c427d798e853a060612e76a95c9aa0357c2f216ba9e4f769f2f ./src/api/history.rs e8edfbb725d6de650442444b0a929ad2be0bd2c2d7d227d2b3fdd99d05c94f94 ./src/api/house.rs -27e85400ccbdb0adcb8f5c1624e7187e9b649237cfb97349fcf1c0d78300540d ./src/api/integrations.rs +8bcaf384ab3a2c297f232800fbffa72edcaa09ea340de0d665f2dc63ccba139c ./src/api/integrations.rs 6bfbe5135d4f93bd8d19a707877b53ed2029fd8490b4b1954a7520888fb1d664 ./src/api/middleware.rs 45e34a447ff384d99a3c42c489ca9fa75849c74d4d246caa91c63d927de17396 ./src/api/openapi.rs -744ce18aa9e0227c6cdfb6980063d0d738d74c5865d45c20fd20026d4742bb3e ./src/api/public_settings.rs +4feca416de6ce8acc34082f432721016b109c7e6e70116e5e0b710ea403c7006 ./src/api/public_settings.rs 5b29f2e5aa2c422c5829ef89c7b59cf753883aa7f60661c95ab0f95cf676fa3f ./src/api/schedules.rs -1602f70f93b8fc5dbbc2d6b370d5a43faae90b13bb38beb043b285a8a121b4e1 ./src/api/settings.rs +513743ac7752d92f16213099d1fca6431759f2cd05a8a12f4ee1226cfd1637b3 ./src/api/settings.rs 1c1fe39aec55d10608ca3db83827855b07249cd1a81ae054eb1ee6464514092b ./src/api/system.rs 9118a5a3a381b010e22d646498ff56d2e05f51656789682cc4371b09c384aa98 ./src/api/websocket.rs -455c7238c9788d378e909a4fbc94d5390b67edd92e127f48b4b913ddcbe82fb0 ./src/api/zones.rs -788d80732d1266798b0d67ed016ecfcc55161ef90bf240a034d186a0ff186e5b ./src/config.rs +ddd1bfc8625b66f6e5f3d24b3122ac86c25f89b0af527e3dfce9102551a8f142 ./src/api/zones.rs +ca2b42d341a1ca0c7772a1256aed12c06667778d6588687f3edc9e88ed1db4fc ./src/config.rs f8394e0daca284070ac9ae0844d6b04fed7c30386f2d1ec409ecf07880bb0e6a ./src/db.rs fdc9d019097becc5c36a0560caa1619aa35d802c0d208902b3bfecbe1643e3e8 ./src/db/climate.rs e5d2027d40a99e61c2c572499af617a92de9e554f8916f3ea6f6731d1d6b09b4 ./src/db/configuration.rs @@ -151,11 +151,11 @@ c1e8de5392681ba1255e3e2d871a6ee860fb8ca987a0a8f973d9ded3977a68fb ./src/engine/s b0189f28d0f337dfd3ce0934da7a565e4a5535afc1c242214c26ed584b3b7840 ./src/engine/targets.rs 682c8b3956c657b655746cbd19ee97ee2f4cf953bc9de67d6d70a1b14f3dd746 ./src/engine/temperature.rs 833b72907375e75c3f42b3921ad70a8e298f5a93743bf0d0c19059aa272462d1 ./src/engine/temporary_thermostat.rs -47330f0d8ff170ea91ba37d019c0d5577151c87f0474bcc2a287850415f6b953 ./src/engine/tests.rs +82fde10161ac760e728532ac214018018a1cf7519b86ef6539705396206f5bf8 ./src/engine/tests.rs c89ff36c6a67bf4fbf68c43e18aa0a556de2dc69263fd79db376363afc741a4f ./src/engine/zone_actions.rs -28571e25e539fb1fe5e77e8bd003caf2da9e84fc34d156fd5f3e65e805798ca0 ./src/engine/zone_control.rs +81eab9a7ed4be387572f2377a14bf7f7d84f7c3f4ed8b33059a3f7e685514a7a ./src/engine/zone_control.rs bfac95cec87f0df6ce562153cd3cfe6731359d703e6c592d2c2f7ba01ea62d99 ./src/error.rs -ea553a1d8b44c74b300d7c4a76dcd3b370524eb57766730a1376d1ab3079913b ./src/home_assistant.rs +10271a141ec441964c66d5732155abda8993d9fcba8dc0cc77e7c35d5a40e466 ./src/home_assistant.rs e78ca9aff23362971eb6450534ea21bdc15150456c378ef9b226cb93a8dbc612 ./src/influxdb.rs da4068295f37c23222bb13bebac022d88ba7729604a49ad25438c55e45458a49 ./src/influxdb/codec.rs b96624fa34b218a6d5f028ba09579633f6f8a04c109f0e94801e8d89ceeeb4b5 ./src/influxdb/query.rs @@ -168,11 +168,11 @@ fc5f2802a073a175a8985e56bcb87eb1ff481226d5b4eeabad73dbba1839fc45 ./src/models/c 5e212a072bf74ef32a186b172404844b02a92363d216167f814056e1f2a9a7a8 ./src/models/device.rs aeab5b5acf35d38a54fd1d82e964fe56635ade69c0149ad423df317095d01555 ./src/models/flow.rs ba5d359e4d6c12d9f62c1b28bdfb40d312737930df5421814f5310f7183e96c1 ./src/models/history.rs -14e053b0dbea9ed7e88e8affaa595fa126ff833c18696fa695e923488ec40d70 ./src/models/integrations.rs +110b73c179ea1f85b7afa14f4cdcfd9b8232dd005a6417cf357b8efc73e702c0 ./src/models/integrations.rs f1359b68f1d041cd441f80429e77893eeb6f6194613b58fd3e7a212fb511db3e ./src/models/runtime.rs -4caae5c95404d2ffefb7db9507dcf4ed40a90d54bc6aeb51098e77e544d85c42 ./src/models/settings_api.rs +f8bd9b4106145ebcf20d003471bb54a329e7373cf14a3a712ecabf87ac59d6d3 ./src/models/settings_api.rs c4de6101de85d9d7e6814b2504b87d9216561e3d9e274054f081f505fb152140 ./src/models/temporary_thermostat.rs -7d798228a8d4d4e60a112167c001535f4b8a5fa32ec7a33554c73125c42256c2 ./src/models/zone.rs +bc2818ce8d0dae1edfb1b98e5d1c00b193f03eeaa28e343d3532f6023288738c ./src/models/zone.rs 62e696df8b86835df7ad127f03c257a78580769dc82bc66daa5041f71e8440a5 ./src/notifications.rs cca742bb2b500eb9269f69385fcf88053cd08aabf6a98b7669b8a7a2fd67df13 ./src/protocol/crypto.rs cd0612021f8ed6c9e0823d63883c0790db09aa485583f3f3fc9f2579f2c28ea3 ./src/protocol/gree.rs @@ -200,23 +200,23 @@ f13f1be3d5789539eba3fc1b59c14835f8c681eca7634e118fa3763a53aeee2b ./src/queries/ b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service 544a31cb2e5374227026afd6abd0d910381f3fca1638154ebd74e3c3d96a35d1 ./web/404.html e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg -db1d0583efbc48c3ab8226aeba83275a14cd5c78be011555548a7491ec28b4d5 ./web/index.html +4c243c29743b402966f48d904a17bf48d0ce9022a0b25100f0b7cd8b81926f5c ./web/index.html 9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js/README.md 27eff2821c40395369be15e197fa830535d2327fcb27c93a0e8e65a1c62de3ad ./web/js/bootstrap.js -713bf6526c83b85780ed9c0f0807c5a1aa009f44ddbc9a67c47141f925db9402 ./web/js/charts.js -ead5b315171a4bf4d443f3bba00a573ffd016a7667475d0e2cde480047446c70 ./web/js/core.js -d880ffdac6aa07cd10d79fbacf12f87eb4c69b48a80564bbfdd0248abe8884d5 ./web/js/dashboard.js +def704d31b00706c26bae73190be15b289e72e6ecc4d8c9e9a5bb1804c747206 ./web/js/charts.js +f33a59e6863346d6751a74e1f912b6f0cd7a187defc3b01267a8121c9cd7ec8f ./web/js/core.js +021736e8b8d02248a41788765af2dd46914a700e8cadb0c43a75e83ed3f2dd14 ./web/js/dashboard.js ed31601df41cc82032709f960a2b4061966abd58cde456ef7dda05e42eb91432 ./web/js/entities.js -08e8e130dd33be91d2155e40aa0bf72a807355b234223b10eb01178ddbbefd13 ./web/js/events.js -8c156909d2d2e59b7d7dc969b9520c978141a448a6cc5aae8fe74a8dc124f2bc ./web/js/flows.js -9e2209d41df7624c29741f03f12ed8e83b47cee2891c5a80cea8ca1bdacf1d5a ./web/js/forms.js +ef778fb570958704c2e079fa5cd645f67f884881e16984daa6f3ed52627b26a5 ./web/js/events.js +89d5f4098e74162ecdee2eef9441f88484a09e9197d21e30c40c220cedbf7e53 ./web/js/flows.js +52b4807f05a1325565d5e866007f49ea83c568574155cded131ce42a49df44f5 ./web/js/forms.js ae6195905f33a4a83d5ba7c22d698419e2de05d80ed37df6066e0380fa715b6e ./web/js/history.js 0dc879c0056251618633c9efd188fc1593eeae6965ad1bc993132dea6e171b44 ./web/js/main.js 53ebdaa64a1dc8327db5cd4e08fb3f551dc2df33c214f218c6bb1ae3f27ad4dd ./web/js/navigation.js e3d146a683b8beb332c752e533826160c2cf2236bc269ced7a4dd1249871027b ./web/js/realtime.js 9546ca5c5566994aff3c33d92295be46b65d51d874681764abe77470dc9c9c73 ./web/js/router.js -e62ca4a0cad10ca8b60c1fe4b5ecd0ccf9bd890cd58206a937dd1655fe216353 ./web/js/settings-ui.js -0dde5e1a19ab2384156229fe2a5d708e06bf58a2227f834d215af97d4375a5d3 ./web/js/settings.js +cdc55061a41b7325aad552357d8359b42d881589c39b438cdc2a48bf0f690db9 ./web/js/settings-ui.js +f00c3f603eb314f755a0c46424681b579e0b890453e2550007c719273815147c ./web/js/settings.js 3815fea7ae1694568c2f249bc8460e0563ced6bde89214f74f21b41fe8cad9a6 ./web/lang-init.js 6b653e4fe2d4db4ffae6ff8f39b8ce57a10a990dc0000ee4aae51d949afadd52 ./web/manifest.webmanifest 4b3c94a1988e8e18bd0c39e5ebf73ce6ef31ddea5bb352ec40b1ae7b3609f5e9 ./web/styles.css diff --git a/docs/API.md b/docs/API.md index 4991c53..ec9fb23 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.2**. +HTTP and WebSocket API for GREE Controller **0.14.4**. [← Main documentation](../README.md) @@ -196,7 +196,7 @@ Common statuses: | GET/PUT | `/api/settings/debug` | Debug overlay/GREE frame settings. | | GET | `/api/configuration/export` | Export full application configuration. | | POST | `/api/configuration/import` | Import/replace application configuration. | -| POST | `/api/integrations/home-assistant/test` | Test HA temperature read. | +| POST | `/api/integrations/home-assistant/test` | Test HA server/API authentication without requiring an entity. | | POST | `/api/integrations/home-assistant/entity` | Read raw HA entity state/attributes for shared Flow input diagnostics. | | POST | `/api/integrations/notifications/test` | Send a test notification. | @@ -232,7 +232,7 @@ Response: { "status": "ok", "name": "gree-controller", - "version": "0.14.2", + "version": "0.14.4", "uptime_seconds": 1234, "control_ready": true, "time": "2026-08-30T06:54:00Z" @@ -283,7 +283,7 @@ Returns the initial Web UI snapshot: "max_fan_speed": 1, "force_quiet": true, "use_native_sleep": true }, "home_assistant": { - "url": "", "token_configured": false, "default_entity_id": "", + "url": "", "token_configured": false, "outdoor_entity_id": "", "sensor_stale_after_seconds": 300, "allow_invalid_tls": false, "sensor_aliases": {}, "flow_inputs": [], "outdoor_assist_enabled": true @@ -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.2", + "version": "0.14.4", "uptime_seconds": 1234, "auth_required": false, "control_ready": true, @@ -523,6 +523,7 @@ When the physical device belongs to a disabled thermostat zone, direct web/API c "smart_fan": true, "sensor_source": "combined", "ha_entity_id": "sensor.living_room_temperature", + "ha_outdoor_entity_id": "sensor.garden_temperature", "external_sensor_weight": 0.4, "max_sensor_difference": 3.0, "sensor_stale_after_seconds": 300, @@ -540,6 +541,8 @@ Important rules: - `separate_hysteresis`: when `true`, cooling uses `cool_hysteresis` and heating uses `heat_hysteresis`; both use the same `0.1..5.0` °C range. - `standby_offset_c`: bounded thermostat offset. - `sensor_source`: `device`, `home_assistant` or `combined`. +- for `home_assistant`/`combined`, `ha_entity_id` is the required Home Assistant room-temperature sensor for that zone. +- `ha_outdoor_entity_id` is optional; when empty, the zone uses global Home Assistant `outdoor_entity_id`, and when set it overrides the outdoor-temperature source only for that zone. If the override is unavailable, the global outdoor source is used. - `external_sensor_weight`: `0..1`. - `revision` is used for optimistic concurrency where supplied; stale updates can return `409`. @@ -1182,7 +1185,6 @@ Times must use `HH:MM`; maximum fan speed is clamped to `1..5`. { "url": "http://homeassistant.local:8123", "token_configured": true, - "default_entity_id": "sensor.room_temperature", "outdoor_entity_id": "sensor.outdoor_temperature", "sensor_stale_after_seconds": 300, "allow_invalid_tls": false, @@ -1194,7 +1196,7 @@ Times must use `HH:MM`; maximum fan speed is clamped to `1..5`. } ``` -`PUT` replaces `token_configured` with optional `token`. Omitted/`null` token preserves the saved token; an explicit empty string clears it. Sensor age is clamped to `30..86400` seconds. URLs, aliases, entity IDs and shared Flow inputs are normalized/validated before persistence. Shared inputs are value sources only; comparison operators and thresholds belong to Flow nodes. +`PUT` replaces `token_configured` with optional `token`. Omitted/`null` token preserves the saved token; an explicit empty string clears it. `outdoor_entity_id` is the optional global Home Assistant outdoor-temperature sensor. Each zone can set `ha_outdoor_entity_id` to override that source only for the zone; a blank override uses the global sensor. Home Assistant room-temperature sensors remain configured per zone with `ha_entity_id` when `sensor_source` is `home_assistant` or `combined`. Sensor age is clamped to `30..86400` seconds. URLs, aliases, entity IDs and shared Flow inputs are normalized/validated before persistence. Global/per-zone outdoor sensors and per-zone room sensors are exposed through the normal alias and sensor-history/metric surfaces, and the Web UI offers them as suggestions in Flow Home Assistant entity fields. Shared inputs are value sources only; comparison operators and thresholds belong to Flow nodes. The Home Assistant connection test validates only the server/API token and requires no entity. ### `/api/settings/debug` @@ -1266,19 +1268,11 @@ Response: ### `POST /api/integrations/home-assistant/test` -```json -{ - "entity_id": "sensor.room_temperature" -} -``` - -`entity_id` is optional; controller defaults/aliases are resolved. Response: +No request body is required. The endpoint validates the configured Home Assistant URL and access token against the Home Assistant API root; it does not read or require any entity. Response: ```json { - "ok": true, - "temperature_c": 23.4, - "entity_id": "sensor.room_temperature" + "ok": true } ``` diff --git a/docs/FLOW.md b/docs/FLOW.md index ae847fd..be95f31 100644 --- a/docs/FLOW.md +++ b/docs/FLOW.md @@ -67,7 +67,7 @@ Actions include thermostat zone, GREE device, climate group, and generic Home As The editor ships 37 editable presets grouped into seven categories: Comfort, Energy & cost, Protection & safety, Night, Reliability, Home Assistant / heat sources and Advanced. The library UI also provides search, Favorites, Recent, a mini graph preview and requirement checks before applying a preset. The library includes workday/weekend comfort, weather-aware demand, morning boost, presence and energy-price eco modes, peak-power limiting, open-window/humidity/overheat/frost guards, night profiles, sensor/device resilience, dual heating/cooling thresholds, multi-room group control, nested AND/OR/NOT Home Assistant scenarios, and gas-boiler/external-heat-source coordination including off, reduced-target, boost and fallback heating patterns. -Shared Flow inputs show the Flows that reference them and link directly to those editors. Home Assistant-backed shared inputs can be tested live in the input modal. Shared inputs report the current raw value/attribute. Each Flow reference defines its own comparison without changing the shared source. +Shared Flow inputs show the Flows that reference them and link directly to those editors. Home Assistant-backed shared inputs can be tested live in the input modal. Shared inputs report the current raw value/attribute. Each Flow reference defines its own comparison without changing the shared source. Home Assistant temperature entities remain normal HA entities: room-temperature sensors are configured per zone, while outdoor temperature has one global `outdoor_entity_id` plus an optional per-zone `ha_outdoor_entity_id` override. Their aliases are shared with the rest of the UI, outdoor overrides are recorded in sensor metrics/history, and all configured entities are offered as suggestions in Home Assistant Flow blocks; selecting a zone sensor does not create a separate shared input. Presets are data files, not JavaScript definitions. Every preset lives in `presets/.json` and contains `id`, `category`, localized `name`, localized `description`, and the complete `flow` graph (`nodes` + `edges`). `build.rs` validates and embeds the directory, exposes `/presets/index.json` plus `/presets/.json`, and the Flow editor loads that library dynamically. Device/zone/group placeholders such as `$zone1`, `$zone2`, `$device1` and `$group1` are resolved when a preset is applied. Adding a preset therefore does not require adding graph-building code to `web/js/flows.js`. diff --git a/docs/openapi.json b/docs/openapi.json index a76cd06..ad24064 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.2", + "version": "0.14.4", "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.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.", + "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.4\nHome Assistant connection tests validate only the configured server/API token and require no entity. `outdoor_entity_id` is the global Home Assistant outdoor-temperature sensor; zones can override it with `ha_outdoor_entity_id`. Room-temperature Home Assistant sensors remain configured per zone with `ha_entity_id`. Global/per-zone outdoor sensors are integrated with aliases, metrics/history and Visual Flow entity suggestions.\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" } @@ -3076,19 +3076,9 @@ "tags": [ "Diagnostics" ], - "summary": "Test Home Assistant temperature", - "description": "Tests the configured Home Assistant connection and resolves a temperature from the supplied entity or controller default/alias.", + "summary": "Test Home Assistant connection", + "description": "Validates the configured Home Assistant URL and access token against the Home Assistant API root. No entity is required or read.", "operationId": "testHomeAssistant", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HaTestRequest" - } - } - } - }, "security": [ { "BearerToken": [] @@ -3099,7 +3089,7 @@ ], "responses": { "200": { - "description": "Test result", + "description": "Connection test result", "content": { "application/json": { "schema": { @@ -3107,27 +3097,15 @@ "properties": { "ok": { "type": "boolean" - }, - "temperature_c": { - "type": [ - "number", - "null" - ] - }, - "entity_id": { - "type": "string" } }, "required": [ - "ok", - "entity_id" + "ok" ], "additionalProperties": false }, "example": { - "ok": true, - "temperature_c": 23.4, - "entity_id": "sensor.room_temperature" + "ok": true } } } @@ -5497,7 +5475,7 @@ }, "version": { "type": "string", - "example": "0.14.2" + "example": "0.14.4" }, "uptime_seconds": { "type": "integer", @@ -6536,7 +6514,16 @@ "string", "null" ], - "example": "sensor.living_room_temperature" + "example": "sensor.living_room_temperature", + "description": "Per-zone Home Assistant room-temperature entity. Required when sensor_source is home_assistant or combined." + }, + "ha_outdoor_entity_id": { + "type": [ + "string", + "null" + ], + "example": "sensor.garden_temperature", + "description": "Optional per-zone Home Assistant outdoor-temperature override. When empty, the zone uses global outdoor_entity_id; when unavailable, control falls back to the global outdoor source." }, "external_sensor_weight": { "type": "number", @@ -6586,6 +6573,20 @@ "type": "string", "example": "gree-aabbccddeeff" }, + "ha_entity_id": { + "type": [ + "string", + "null" + ], + "description": "Per-zone Home Assistant room-temperature entity used by external/combined control." + }, + "ha_outdoor_entity_id": { + "type": [ + "string", + "null" + ], + "description": "Optional per-zone Home Assistant outdoor-temperature override; empty uses global outdoor_entity_id." + }, "enabled": { "type": "boolean" }, @@ -7973,19 +7974,6 @@ ], "additionalProperties": false }, - "HaTestRequest": { - "type": "object", - "properties": { - "entity_id": { - "type": [ - "string", - "null" - ], - "description": "Optional. Defaults/aliases are resolved when omitted.", - "example": "sensor.room_temperature" - } - } - }, "HaEntityRequest": { "type": "object", "properties": { @@ -8487,11 +8475,9 @@ "url": { "type": "string" }, - "default_entity_id": { - "type": "string" - }, "outdoor_entity_id": { - "type": "string" + "type": "string", + "description": "Optional global Home Assistant outdoor-temperature entity. Zones use it by default and may override it with ha_outdoor_entity_id." }, "sensor_stale_after_seconds": { "type": "integer" @@ -8521,7 +8507,6 @@ }, "required": [ "url", - "default_entity_id", "outdoor_entity_id", "sensor_stale_after_seconds", "allow_invalid_tls", @@ -8538,11 +8523,9 @@ "url": { "type": "string" }, - "default_entity_id": { - "type": "string" - }, "outdoor_entity_id": { - "type": "string" + "type": "string", + "description": "Optional global Home Assistant outdoor-temperature entity. Zones use it by default and may override it with ha_outdoor_entity_id." }, "sensor_stale_after_seconds": { "type": "integer" @@ -8576,7 +8559,6 @@ }, "required": [ "url", - "default_entity_id", "outdoor_entity_id", "sensor_stale_after_seconds", "allow_invalid_tls", diff --git a/ha-addon/home-assistant/README.md b/ha-addon/home-assistant/README.md index f07493e..8d572b4 100644 --- a/ha-addon/home-assistant/README.md +++ b/ha-addon/home-assistant/README.md @@ -112,14 +112,9 @@ Tokens are used only during validation and are not written to the mapping file. ## HA as an external temperature source -This is independent from the custom climate integration. Each Rust controller zone can assign its own HA room-temperature entity, for example: +This is independent from the custom climate integration. Home Assistant room-temperature sensors are configured per zone with `ha_entity_id`. The recommended `combined` strategy keeps the GREE sensor as the primary input and uses the selected Home Assistant room sensor as a configurable supporting measurement (40% weight by default). If HA or the selected room entity becomes unavailable, the controller falls back to the corresponding GREE unit, so local control and schedules continue to run. -```text -Living room -> GREE Living Room + sensor.living_room_temperature -Bedroom -> GREE Bedroom + sensor.bedroom_temperature -``` - -The recommended `combined` strategy keeps the GREE sensor as the primary input and uses the room sensor as a configurable supporting measurement (40% weight by default). A zone may also select the room sensor as its preferred source. If HA or that entity becomes unavailable, the controller falls back to the corresponding GREE unit, so local control and schedules continue to run. +Outdoor temperature is separate: `outdoor_entity_id` / `HA_OUTDOOR_ENTITY_ID` is the global Home Assistant outdoor-temperature sensor. A zone may optionally set `ha_outdoor_entity_id` to use a different outdoor sensor for that zone's outdoor-temperature assist. If the override is unavailable, the global outdoor source is used. Global and per-zone outdoor entities participate in aliases, metrics/history and Flow entity suggestions. The Home Assistant connection test validates only the server URL/API token and does not require any entity. ## Zone climate and optional unit features Each controller zone is exposed as a full Home Assistant `climate` entity with current/target temperature and HVAC modes: Off, Auto (follow the controller house mode), Cool and Heat. For a **zone thermostat**, Auto does not mean the GREE unit's native automatic heat/cool algorithm: it means **inherit the whole-house Heating/Cooling selection from GREE Controller**. Direct physical-device climate entities still use the native GREE Auto mode. The target temperature remains published while a zone is Off, so Home Assistant can display the configured setpoint instead of `unknown`. The existing zone target `number` and enabled `switch` remain available for compatibility. 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 d87e256..285bb11 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.2", + "version": "0.14.4", "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 e365b29..052f6ec 100644 --- a/ha-addon/repository/gree-controller/CHANGELOG.md +++ b/ha-addon/repository/gree-controller/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## 0.14.4 + +- Removes the obsolete Home Assistant connection-test/default entity; HA connection testing uses only the configured URL and token. +- `outdoor_entity_id` / `HA_OUTDOOR_ENTITY_ID` is the global Home Assistant outdoor-temperature sensor. +- Adds optional per-zone `ha_outdoor_entity_id`; when empty, the zone uses the global outdoor sensor, and when set it overrides the outdoor source only for that zone. +- Keeps Home Assistant room temperature sensors per zone via `ha_entity_id`. +- Global and per-zone outdoor sensors are available in aliases, metrics/history and Home Assistant entity suggestions used by Visual Flow. + +## 0.14.3 + +- Home Assistant connection test now validates the configured server and token without requiring any entity. + ## 0.14.2 - Improves connectivity controls: Local/LAN ping measurement can be explicitly enabled/disabled in Settings, disabled workers gray out their interval/sample fields, Cloud REST/MQTT measurement settings live in a dedicated block, and History → Pings can hide/show jitter with one button while rendering jitter as a dashed line. diff --git a/ha-addon/repository/gree-controller/config.yaml b/ha-addon/repository/gree-controller/config.yaml index 325367b..9bf6197 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.2" +version: "0.14.4" 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 53e974c..95c26ed 100644 --- a/lang/en.json +++ b/lang/en.json @@ -131,7 +131,10 @@ "zones.roomSensorEntity": "Room sensor entity_id", "zones.roomSensorWeight": "Room sensor weight %", "zones.maxDifference": "Max. sensor difference °C", - "zones.sensorHelp": "This sensor belongs only to this zone. If it becomes unavailable, the controller automatically falls back to the GREE sensor.", + "zones.sensorHelp": "Required when the temperature strategy uses Home Assistant. If it becomes unavailable, the controller falls back to the GREE sensor.", + "zones.sensorRequired": "Enter the Home Assistant room temperature entity_id for this zone.", + "zones.outdoorSensorEntity": "Outdoor temperature entity_id override", + "zones.outdoorSensorHelp": "Leave empty to use the global Outdoor temperature entity_id. Set another entity_id to use a different outdoor sensor only for this zone. It is used by outdoor-temperature assist and recorded in history/metrics.", "zones.greeTemp": "GREE", "zones.externalTemp": "Room", "zones.usedSource": "used", @@ -180,8 +183,6 @@ "settings.haTokenKeep": "Leave empty to keep the saved token", "settings.haTokenSaved": "Token saved — leave empty to keep it", "settings.haLongLivedToken": "Long-Lived Access Token", - "settings.defaultEntity": "Connection test entity_id", - "settings.defaultEntityHint": "This entity is used by the Home Assistant connection test when no other entity is specified. It is not automatically used as a zone temperature source.", "settings.testHa": "Test HA", "settings.systemState": "System status", "settings.version": "Version", @@ -247,7 +248,7 @@ "label.schedule": "schedule", "label.automation": "automation", "toast.found": "Found: {count}", - "toast.haTemperature": "Home Assistant: {temperature}°C", + "toast.haConnected": "Home Assistant connection OK", "day.1": "Mon", "day.2": "Tue", "day.3": "Wed", @@ -328,7 +329,8 @@ "schedules.chooseZone": "Choose a zone first", "schedules.templateApplied": "Schedule preset applied", "schedules.profile": "Profile", - "settings.outdoorEntity": "Outdoor temperature entity_id", + "settings.outdoorEntity": "Global outdoor temperature entity_id", + "settings.outdoorEntityHint": "Global Home Assistant outdoor temperature sensor. Zones use it by default and may override it with another outdoor sensor in zone settings.", "settings.outdoorAssist": "Use outdoor temperature as smart-control assist", "settings.allowInvalidTls": "Allow invalid/self-signed HTTPS certificate", "settings.allowInvalidTlsHint": "Use only for a trusted local Home Assistant server, for example https://192.168.50.25.", diff --git a/lang/pl.json b/lang/pl.json index 05adbc6..a677514 100644 --- a/lang/pl.json +++ b/lang/pl.json @@ -128,10 +128,13 @@ "zones.demand": "Żądanie", "zones.emptyTitle": "Brak stref", "zones.emptyText": "Dodaj strefę, aby sterować temperaturą automatycznie.", - "zones.roomSensorEntity": "entity_id czujnika pokojowego", + "zones.roomSensorEntity": "entity_id czujnika temperatury pokojowej", "zones.roomSensorWeight": "Waga czujnika pokojowego %", "zones.maxDifference": "Maks. różnica czujników °C", - "zones.sensorHelp": "Ten czujnik jest przypisany tylko do tej strefy. Gdy przestanie być dostępny, kontroler automatycznie wróci do sensora GREE.", + "zones.sensorHelp": "Wymagany, gdy strategia temperatury używa Home Assistant. Gdy czujnik będzie niedostępny, kontroler wróci do sensora GREE.", + "zones.sensorRequired": "Podaj entity_id czujnika temperatury pokojowej z Home Assistant dla tej strefy.", + "zones.outdoorSensorEntity": "Nadpisanie entity_id temperatury zewnętrznej", + "zones.outdoorSensorHelp": "Pozostaw puste, aby użyć globalnego Outdoor temperature entity_id. Wybierz inne entity_id, aby tylko ta strefa używała innego czujnika zewnętrznego. Czujnik jest używany przez wspomaganie temperaturą zewnętrzną oraz zapisywany w historii i metrykach.", "zones.greeTemp": "GREE", "zones.externalTemp": "Pokój", "zones.usedSource": "użyte", @@ -180,8 +183,6 @@ "settings.haTokenKeep": "Pozostaw puste, aby zachować zapisany token", "settings.haTokenSaved": "Token zapisany — pozostaw puste", "settings.haLongLivedToken": "Długotrwały token dostępu", - "settings.defaultEntity": "Entity_id do testu połączenia", - "settings.defaultEntityHint": "Ta encja jest używana przez test połączenia z Home Assistant, gdy nie wskazano innej encji. Nie jest automatycznie źródłem temperatury strefy.", "settings.testHa": "Testuj HA", "settings.systemState": "Stan systemu", "settings.version": "Wersja", @@ -247,7 +248,7 @@ "label.schedule": "harmonogram", "label.automation": "automatyzację", "toast.found": "Znaleziono: {count}", - "toast.haTemperature": "Home Assistant: {temperature}°C", + "toast.haConnected": "Połączenie z Home Assistant działa", "day.1": "Pn", "day.2": "Wt", "day.3": "Śr", @@ -328,7 +329,8 @@ "schedules.chooseZone": "Najpierw wybierz strefę", "schedules.templateApplied": "Zastosowano preset harmonogramu", "schedules.profile": "Profil", - "settings.outdoorEntity": "entity_id temperatury zewnętrznej", + "settings.outdoorEntity": "Globalny entity_id temperatury zewnętrznej", + "settings.outdoorEntityHint": "Globalny czujnik temperatury zewnętrznej z Home Assistant. Strefy używają go domyślnie i mogą wskazać własny czujnik zewnętrzny w konfiguracji strefy.", "settings.outdoorAssist": "Używaj temperatury zewnętrznej jako wsparcia inteligentnego sterowania", "settings.allowInvalidTls": "Zezwól na nieważny/samopodpisany certyfikat HTTPS", "settings.allowInvalidTlsHint": "Używaj tylko dla zaufanego lokalnego serwera Home Assistant, np. https://192.168.50.25.", diff --git a/scripts/api_dev_test.py b/scripts/api_dev_test.py index 83b482f..115214c 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.2. +"""Developer smoke/integration tests for GREE Controller API 0.14.4. 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.2" +DEFAULT_EXPECTED_VERSION = "0.14.4" SETTINGS_PATHS: dict[str, set[str]] = { "/api/settings/application": {"simulator_enabled"}, @@ -88,7 +88,6 @@ SETTINGS_PATHS: dict[str, set[str]] = { "/api/settings/home-assistant": { "url", "token_configured", - "default_entity_id", "outdoor_entity_id", "sensor_stale_after_seconds", "allow_invalid_tls", @@ -580,7 +579,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.2 contract", lambda: test_openapi(client, args.expected_version)) + runner.run("OpenAPI 0.14.4 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)) @@ -649,7 +648,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.2.", + description="Developer smoke/integration tests for GREE Controller API 0.14.4.", 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 90494af..a2050e8 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.2".into())); + return Err(AppError::BadRequest("unsupported configuration export version; version 3 is required by GREE Controller 0.14.4".into())); } if export.settings.control_strategy != "setpoint" { return Err(AppError::BadRequest( @@ -157,6 +157,18 @@ fn validate_configuration_devices_and_zones( "import contains an invalid zone sensor source".into(), )); } + if matches!(zone.sensor_source.as_str(), "home_assistant" | "combined") + && zone + .ha_entity_id + .as_deref() + .map(str::trim) + .unwrap_or("") + .is_empty() + { + return Err(AppError::BadRequest( + "import contains a zone that needs a per-zone Home Assistant room temperature entity_id".into(), + )); + } } let mut installation_devices = std::collections::HashSet::new(); for group in &export.device_groups { @@ -687,7 +699,7 @@ fn normalize_imported_runtime_settings(settings: &mut RuntimeSettings) -> Result fn prepare_configuration_import(export: &mut ConfigurationExport) -> Result<(), AppError> { normalize_imported_runtime_settings(&mut export.settings)?; for zone in &mut export.zones { - canonicalize_zone_ha_entity(zone, &export.settings.home_assistant); + canonicalize_zone_ha_entities(zone, &export.settings.home_assistant); } Ok(()) } diff --git a/src/api/history.rs b/src/api/history.rs index 066a270..c814f55 100644 --- a/src/api/history.rs +++ b/src/api/history.rs @@ -130,13 +130,17 @@ fn sensor_history_with_fallback( let mut existing: std::collections::HashSet = values.iter().map(|row| row.entity_id.clone()).collect(); for zone in state.db.list_zones()? { - let Some(entity_id) = zone + if !matches!(zone.sensor_source.as_str(), "home_assistant" | "combined") { + continue; + } + let entity_id = zone .ha_entity_id .as_deref() - .filter(|value| !value.trim().is_empty()) - else { + .map(str::trim) + .unwrap_or(""); + if entity_id.is_empty() { continue; - }; + } if existing.contains(entity_id) { continue; } @@ -162,31 +166,37 @@ fn sensor_history_with_fallback( existing.insert(entity_id.to_string()); } } - let outdoor_entity = outdoor_entity.trim(); - if !outdoor_entity.is_empty() && !existing.contains(outdoor_entity) { - for zone in state.db.list_zones()? { - let rows = - state - .db - .list_zone_history(Some(&zone.id), since.clone(), bucket_seconds, limit)?; - let mut added = false; - for row in rows { - if let Some(temperature) = row.outdoor_temperature { - values.push(HaReading { - id: row.id, - entity_id: outdoor_entity.to_string(), - zone_id: None, - kind: "outdoor".into(), - timestamp: row.timestamp, - temperature, - }); - added = true; - } - } - if added { - break; + let global_outdoor_entity = outdoor_entity.trim(); + for zone in state.db.list_zones()? { + let zone_override = zone + .ha_outdoor_entity_id + .as_deref() + .map(str::trim) + .filter(|value| !value.is_empty()); + let entity_id = zone_override.unwrap_or(global_outdoor_entity); + if entity_id.is_empty() || existing.contains(entity_id) { + continue; + } + let rows = state + .db + .list_zone_history(Some(&zone.id), since.clone(), bucket_seconds, limit)?; + let mut added = false; + for row in rows { + if let Some(temperature) = row.outdoor_temperature { + values.push(HaReading { + id: row.id, + entity_id: entity_id.to_string(), + zone_id: zone_override.map(|_| zone.id.clone()), + kind: "outdoor".into(), + timestamp: row.timestamp, + temperature, + }); + added = true; } } + if added { + existing.insert(entity_id.to_string()); + } } values.sort_by(|left, right| left.timestamp.cmp(&right.timestamp)); if values.len() > limit as usize { @@ -332,7 +342,13 @@ async fn combined_sensor_history( .db .list_ha_history(entity_id, start, bucket_seconds, limit)?) } else { - sensor_history_with_fallback(state, start, bucket_seconds, limit, outdoor_entity) + sensor_history_with_fallback( + state, + start, + bucket_seconds, + limit, + outdoor_entity, + ) } }; if !influx.enabled || since >= cutoff { @@ -391,13 +407,8 @@ async fn history( let bucket_seconds = history_bucket_seconds(hours); let limit = query.limit.unwrap_or(12_000).clamp(1, 20_000); let scope = query.scope.as_deref().unwrap_or("zones"); - let outdoor_entity = state - .settings - .read() - .await - .home_assistant - .outdoor_entity_id - .clone(); + let ha_settings = state.settings.read().await.home_assistant.clone(); + let outdoor_entity = ha_settings.outdoor_entity_id; let (device_count, zone_count, ha_count) = state.db.history_counts()?; match scope { diff --git a/src/api/integrations.rs b/src/api/integrations.rs index dd93fc6..f9c2b6e 100644 --- a/src/api/integrations.rs +++ b/src/api/integrations.rs @@ -11,28 +11,14 @@ async fn home_assistant_snapshot( }))) } -#[derive(Debug, Deserialize)] -struct HaTestRequest { - entity_id: Option, -} async fn test_home_assistant( State(state): State, - Json(input): Json, ) -> Result, AppError> { let settings = state.settings.read().await.clone(); - let resolved_entity_id = - home_assistant::resolve_entity_id(&settings.home_assistant, input.entity_id.as_deref()); - let temperature = home_assistant::read_temperature( - &state.http, - &settings.home_assistant, - resolved_entity_id.as_deref(), - Some(settings.home_assistant.sensor_stale_after_seconds), - ) - .await - .map_err(|e| AppError::Device(e.to_string()))?; - Ok(Json( - json!({"ok": true, "temperature_c": temperature, "entity_id": resolved_entity_id}), - )) + home_assistant::test_connection(&state.http, &settings.home_assistant) + .await + .map_err(|e| AppError::Device(e.to_string()))?; + Ok(Json(json!({"ok": true}))) } #[derive(Debug, Deserialize)] diff --git a/src/api/public_settings.rs b/src/api/public_settings.rs index 6930784..434783c 100644 --- a/src/api/public_settings.rs +++ b/src/api/public_settings.rs @@ -52,7 +52,6 @@ fn public_settings(settings: &RuntimeSettings) -> Value { "url": settings.home_assistant.url, "token": "", "token_configured": !settings.home_assistant.token.trim().is_empty(), - "default_entity_id": settings.home_assistant.default_entity_id, "outdoor_entity_id": settings.home_assistant.outdoor_entity_id, "sensor_stale_after_seconds": settings.home_assistant.sensor_stale_after_seconds, "allow_invalid_tls": settings.home_assistant.allow_invalid_tls, diff --git a/src/api/settings.rs b/src/api/settings.rs index 4587498..3543a89 100644 --- a/src/api/settings.rs +++ b/src/api/settings.rs @@ -80,7 +80,6 @@ fn home_assistant_settings(settings: &RuntimeSettings) -> HomeAssistantSettingsV HomeAssistantSettingsView { url: settings.home_assistant.url.clone(), token_configured: !settings.home_assistant.token.trim().is_empty(), - default_entity_id: settings.home_assistant.default_entity_id.clone(), outdoor_entity_id: settings.home_assistant.outdoor_entity_id.clone(), sensor_stale_after_seconds: settings.home_assistant.sensor_stale_after_seconds, allow_invalid_tls: settings.home_assistant.allow_invalid_tls, @@ -618,10 +617,6 @@ fn validate_flow_shared_inputs( } fn canonicalize_home_assistant_entities(settings: &mut HomeAssistantSettings) { - let default_entity = settings.default_entity_id.clone(); - if let Some(entity_id) = home_assistant::resolve_entity_id(settings, Some(&default_entity)) { - settings.default_entity_id = entity_id; - } let outdoor_entity = settings.outdoor_entity_id.clone(); if !outdoor_entity.trim().is_empty() { if let Some(entity_id) = home_assistant::resolve_entity_id(settings, Some(&outdoor_entity)) @@ -645,11 +640,20 @@ fn validate_home_assistant_url(settings: &HomeAssistantSettings) -> Result<(), A Ok(()) } -fn canonicalize_zone_ha_entity(zone: &mut Zone, settings: &HomeAssistantSettings) { - let Some(configured) = zone.ha_entity_id.clone() else { - return; +fn canonicalize_zone_ha_entities(zone: &mut Zone, settings: &HomeAssistantSettings) { + let room = zone.ha_entity_id.clone().unwrap_or_default(); + zone.ha_entity_id = if room.trim().is_empty() { + None + } else { + home_assistant::resolve_entity_id(settings, Some(room.trim())) + }; + + let outdoor = zone.ha_outdoor_entity_id.clone().unwrap_or_default(); + zone.ha_outdoor_entity_id = if outdoor.trim().is_empty() { + None + } else { + home_assistant::resolve_entity_id(settings, Some(outdoor.trim())) }; - zone.ha_entity_id = home_assistant::resolve_entity_id(settings, Some(&configured)); } async fn canonicalize_saved_zone_entities( @@ -673,9 +677,10 @@ async fn canonicalize_saved_zone_entities( let Some(mut zone) = state.db.get_zone(&zone_id)? else { continue; }; - let previous = zone.ha_entity_id.clone(); - canonicalize_zone_ha_entity(&mut zone, settings); - if zone.ha_entity_id != previous { + let previous_room = zone.ha_entity_id.clone(); + let previous_outdoor = zone.ha_outdoor_entity_id.clone(); + canonicalize_zone_ha_entities(&mut zone, settings); + if zone.ha_entity_id != previous_room || zone.ha_outdoor_entity_id != previous_outdoor { zone.updated_at = Utc::now(); state.db.save_zone(&zone)?; state.broadcast("zone.updated", serde_json::to_value(&zone)?); @@ -691,7 +696,6 @@ fn apply_home_assistant_update( let mut next = HomeAssistantSettings { url: input.url, token: current.token.clone(), - default_entity_id: input.default_entity_id, outdoor_entity_id: input.outdoor_entity_id, sensor_stale_after_seconds: input.sensor_stale_after_seconds.clamp(30, 86_400), allow_invalid_tls: input.allow_invalid_tls, diff --git a/src/api/zones.rs b/src/api/zones.rs index 42e0378..b9260b5 100644 --- a/src/api/zones.rs +++ b/src/api/zones.rs @@ -44,6 +44,8 @@ struct ZoneInput { sensor_source: String, #[serde(default)] ha_entity_id: Option, + #[serde(default)] + ha_outdoor_entity_id: Option, #[serde(default = "external_sensor_weight")] external_sensor_weight: f64, #[serde(default = "max_sensor_difference")] @@ -170,11 +172,13 @@ impl ZoneInput { && self .ha_entity_id .as_deref() - .map(|value| value.trim()) + .map(str::trim) .unwrap_or("") .is_empty() { - return Err(AppError::BadRequest("a per-zone Home Assistant entity_id is required for external or combined temperature control".into())); + return Err(AppError::BadRequest( + "a per-zone Home Assistant room temperature entity_id is required for external or combined temperature control".into(), + )); } Ok(()) } @@ -205,6 +209,7 @@ impl ZoneInput { smart_fan: self.smart_fan, sensor_source: self.sensor_source, ha_entity_id: self.ha_entity_id.filter(|v| !v.trim().is_empty()), + ha_outdoor_entity_id: self.ha_outdoor_entity_id.filter(|v| !v.trim().is_empty()), external_sensor_weight: self.external_sensor_weight, max_sensor_difference: self.max_sensor_difference, sensor_stale_after_seconds: self.sensor_stale_after_seconds, @@ -289,6 +294,7 @@ async fn create_zone( ) -> Result<(StatusCode, Json), AppError> { let _configuration_guard = state.lock_configuration_operation().await; let _reference_guard = state.lock_automation_operation().await; + let settings = state.settings.read().await.clone(); input.validate()?; if state.db.get_device(&input.device_id)?.is_none() { return Err(AppError::BadRequest("zone device does not exist".into())); @@ -299,8 +305,7 @@ async fn create_zone( // the newly created zone without participating in the zone operation lock. let _device_guard = state.lock_device_operation(&input.device_id).await; let mut zone = input.into_zone(Uuid::new_v4().to_string(), Utc::now()); - let settings = state.settings.read().await.clone(); - canonicalize_zone_ha_entity(&mut zone, &settings.home_assistant); + canonicalize_zone_ha_entities(&mut zone, &settings.home_assistant); state.db.save_zone(&zone)?; state.broadcast("zone.created", serde_json::to_value(&zone)?); state.wake_zone_control(); @@ -313,6 +318,7 @@ async fn update_zone( ) -> Result, AppError> { let _configuration_guard = state.lock_configuration_operation().await; let _reference_guard = state.lock_automation_operation().await; + let settings = state.settings.read().await.clone(); input.validate()?; let _zone_guard = state.lock_zone_operation(&id).await; let mut existing = state @@ -409,8 +415,7 @@ async fn update_zone( .await?; zone.revision = existing.revision.saturating_add(1); } - let settings = state.settings.read().await.clone(); - canonicalize_zone_ha_entity(&mut zone, &settings.home_assistant); + canonicalize_zone_ha_entities(&mut zone, &settings.home_assistant); let power_off_device = !device_changed && existing.enabled && !zone.enabled; if power_off_device { // Full configuration PUT and quick-control disable use the same ownership cleanup. diff --git a/src/config.rs b/src/config.rs index addc647..71c737e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -124,7 +124,6 @@ impl Config { home_assistant: HomeAssistantSettings { url: env::var("HA_URL").unwrap_or_default(), token: env::var("HA_TOKEN").unwrap_or_default(), - default_entity_id: env::var("HA_ENTITY_ID").unwrap_or_default(), outdoor_entity_id: env::var("HA_OUTDOOR_ENTITY_ID").unwrap_or_default(), sensor_stale_after_seconds: env_u64("HA_SENSOR_STALE_AFTER_SECONDS") .unwrap_or(300) diff --git a/src/engine/tests.rs b/src/engine/tests.rs index de8c1b9..74c2625 100644 --- a/src/engine/tests.rs +++ b/src/engine/tests.rs @@ -230,7 +230,7 @@ mod tests { heat_comfort_setpoint: 21.0, heat_sleep_setpoint: 19.0, heat_away_setpoint: 17.0, hysteresis: 0.6, separate_hysteresis: false, cool_hysteresis: 0.6, heat_hysteresis: 0.6, min_on_seconds: 180, min_off_seconds: 180, min_adjust_seconds: 120, standby_offset_c: 2.0, smart_fan: true, - sensor_source: source.into(), ha_entity_id: Some("sensor.room_temperature".into()), + sensor_source: source.into(), ha_entity_id: Some("sensor.room_temperature".into()), ha_outdoor_entity_id: None, external_sensor_weight: 0.4, max_sensor_difference: 3.0, sensor_stale_after_seconds: 300, device_temperature: None, external_temperature: None, current_temperature: None, control_temperature_source: "device".into(), active_preset: "comfort".into(), manual_preset: None, manual_setpoint: None, manual_override_until: None, local_thermostat_power: None, local_thermostat_resume_at: None, local_thermostat_restore_zone_enabled: None, temporary_quick_thermostat: None, diff --git a/src/engine/zone_control.rs b/src/engine/zone_control.rs index 1e11475..53401c1 100644 --- a/src/engine/zone_control.rs +++ b/src/engine/zone_control.rs @@ -86,6 +86,58 @@ async fn resolve_cycle_outdoor_temperature( temperature } +async fn read_cycle_zone_outdoor_sensors( + state: &AppState, + settings: &RuntimeSettings, + zones: &[Zone], +) -> HashMap> { + futures_util::future::join_all(zones.iter().filter_map(|zone| { + let configured = zone + .ha_outdoor_entity_id + .as_deref() + .map(str::trim) + .filter(|value| !value.is_empty())?; + let zone_id = zone.id.clone(); + let resolved_entity = + home_assistant::resolve_entity_id(&settings.home_assistant, Some(configured)); + let http = &state.http; + let ha_settings = &settings.home_assistant; + let poll_interval_seconds = settings.poll_interval_seconds; + Some(async move { + let Some(entity_id) = resolved_entity else { + return (zone_id, None); + }; + match home_assistant::read_temperature( + http, + ha_settings, + Some(&entity_id), + Some(ha_settings.sensor_stale_after_seconds), + ) + .await + { + Ok(value) => { + record_ha_history( + state, + &entity_id, + Some(&zone_id), + "outdoor", + value, + poll_interval_seconds, + ); + (zone_id, Some(value)) + } + Err(err) => { + tracing::debug!(zone_id=%zone_id, resolved_entity=%entity_id, error=?err, "zone outdoor Home Assistant sensor unavailable; using global outdoor fallback"); + (zone_id, None) + } + } + }) + })) + .await + .into_iter() + .collect() +} + async fn read_cycle_room_sensors( state: &AppState, settings: &RuntimeSettings, @@ -479,13 +531,10 @@ async fn control_zones(state: &AppState) -> Result<()> { .await?; let device_snapshot = state.db.list_devices()?; - let outdoor_temperature = + let global_outdoor_temperature = resolve_cycle_outdoor_temperature(state, &settings, &device_snapshot).await; - let outdoor_assist_temperature = if settings.outdoor_assist_enabled { - outdoor_temperature - } else { - None - }; + let mut zone_outdoor_sensor_results = + read_cycle_zone_outdoor_sensors(state, &settings, &zone_snapshot).await; let night_active = night_mode_active(&settings.night_mode, Local::now().time()); let mut room_sensor_results = read_cycle_room_sensors(state, &settings, &zone_snapshot).await; @@ -498,6 +547,15 @@ async fn control_zones(state: &AppState) -> Result<()> { continue; }; let cycle_started_at = zone.updated_at.clone(); + let outdoor_temperature = zone_outdoor_sensor_results + .remove(&zone.id) + .flatten() + .or(global_outdoor_temperature); + let outdoor_assist_temperature = if settings.outdoor_assist_enabled { + outdoor_temperature + } else { + None + }; if zone .manual_override_until .map(|until| until <= Utc::now()) diff --git a/src/home_assistant.rs b/src/home_assistant.rs index df9a59d..60761c6 100644 --- a/src/home_assistant.rs +++ b/src/home_assistant.rs @@ -26,11 +26,7 @@ pub fn resolve_entity_id( ) -> Option { let requested = entity_override .filter(|value| !value.trim().is_empty()) - .map(str::trim) - .unwrap_or_else(|| settings.default_entity_id.trim()); - if requested.is_empty() { - return None; - } + .map(str::trim)?; // Aliases are presentation-only. Accepting an alias here is a defensive // compatibility path for settings saved by older UI revisions or manual edits; @@ -48,6 +44,39 @@ pub fn resolve_entity_id( Some(requested.to_string()) } +pub async fn test_connection( + default_client: &reqwest::Client, + settings: &HomeAssistantSettings, +) -> Result<()> { + if settings.url.trim().is_empty() { + bail!("Home Assistant URL is not configured") + } + if settings.token.trim().is_empty() { + bail!("Home Assistant token is not configured") + } + let mut base = Url::parse(settings.url.trim()).context("invalid Home Assistant URL")?; + if !matches!(base.scheme(), "http" | "https") { + bail!("Home Assistant URL must use http or https") + } + base = base.join("api/").context("cannot build Home Assistant API URL")?; + let response = request_client(default_client, settings)? + .get(base) + .bearer_auth(settings.token.trim()) + .header("Accept", "application/json") + .send() + .await + .context("Home Assistant request failed")?; + if !response.status().is_success() { + let status = response.status(); + let body = response.text().await.unwrap_or_default(); + bail!( + "Home Assistant returned {status}: {}", + body.chars().take(200).collect::() + ) + } + Ok(()) +} + pub async fn read_temperature( default_client: &reqwest::Client, settings: &HomeAssistantSettings, @@ -136,7 +165,6 @@ mod tests { HomeAssistantSettings { url: "http://homeassistant.local:8123".into(), token: "token".into(), - default_entity_id: "sensor.salon_temperature".into(), outdoor_entity_id: "sensor.zewnatrz_temperature".into(), sensor_stale_after_seconds: 300, allow_invalid_tls: false, @@ -156,10 +184,7 @@ mod tests { resolve_entity_id(&settings, Some("Gabinet")).as_deref(), Some("sensor.gabinet_temperature") ); - assert_eq!( - resolve_entity_id(&settings, None).as_deref(), - Some("sensor.salon_temperature") - ); + assert_eq!(resolve_entity_id(&settings, None), None); } } diff --git a/src/models/integrations.rs b/src/models/integrations.rs index 62055b6..b15b798 100644 --- a/src/models/integrations.rs +++ b/src/models/integrations.rs @@ -13,9 +13,7 @@ pub struct HomeAssistantSettings { pub url: String, #[serde(default)] pub token: String, - #[serde(default)] - pub default_entity_id: String, - /// Optional outdoor temperature sensor used only as an assist signal. + /// Global Home Assistant outdoor temperature sensor. Zones may override it individually. #[serde(default)] pub outdoor_entity_id: String, /// Maximum accepted age of Home Assistant sensor samples. diff --git a/src/models/settings_api.rs b/src/models/settings_api.rs index 3b3d8e9..aaaf338 100644 --- a/src/models/settings_api.rs +++ b/src/models/settings_api.rs @@ -128,7 +128,6 @@ pub struct HomeAssistantSettingsUpdate { pub url: String, #[serde(default)] pub token: Option, - pub default_entity_id: String, pub outdoor_entity_id: String, pub sensor_stale_after_seconds: u64, pub allow_invalid_tls: bool, @@ -141,7 +140,6 @@ pub struct HomeAssistantSettingsUpdate { pub struct HomeAssistantSettingsView { pub url: String, pub token_configured: bool, - pub default_entity_id: String, pub outdoor_entity_id: String, pub sensor_stale_after_seconds: u64, pub allow_invalid_tls: bool, diff --git a/src/models/zone.rs b/src/models/zone.rs index f51717c..c012fbc 100644 --- a/src/models/zone.rs +++ b/src/models/zone.rs @@ -52,8 +52,13 @@ pub struct Zone { pub smart_fan: bool, #[serde(default = "default_sensor_source")] pub sensor_source: String, + /// Per-zone Home Assistant room temperature sensor used by external/combined control. #[serde(default)] pub ha_entity_id: Option, + /// Optional per-zone Home Assistant outdoor temperature sensor. When absent, the zone + /// uses `HomeAssistantSettings::outdoor_entity_id` (and then the existing GREE fallback). + #[serde(default)] + pub ha_outdoor_entity_id: Option, /// Weight of the optional room sensor when sensor_source is `combined`. #[serde(default = "default_external_sensor_weight")] pub external_sensor_weight: f64, diff --git a/web/index.html b/web/index.html index 26a8598..c28dcdc 100644 --- a/web/index.html +++ b/web/index.html @@ -437,13 +437,9 @@
- -

This entity is used by the Home Assistant - connection test when no other entity is specified. It is not automatically used as a zone temperature - source.

- + +

Global Home Assistant outdoor temperature sensor. Zones use it by default and may override it with another outdoor sensor in zone settings.

@@ -477,7 +473,7 @@
-
@@ -1303,6 +1299,8 @@ + +
@@ -1358,15 +1356,17 @@ °C
-
-

The room sensor is assigned only to this zone. If it becomes - unavailable, the controller falls back to the GREE sensor.

+

This room sensor is required when the temperature strategy uses Home Assistant. If it becomes unavailable, the controller falls back to the GREE sensor.

+ +

Leave empty to use the global Outdoor temperature entity_id. Set another entity_id to use a different outdoor sensor only for this zone. It is used by outdoor-temperature assist and recorded in history/metrics.