From c7d47db64ec4b0bf9f7ec5f748b71e4c0c5795e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Wed, 16 Sep 2026 17:07:17 +0200 Subject: [PATCH] v0.14.15-fix --- .env.example | 2 - Cargo.lock | 2 +- Cargo.toml | 2 +- FILE_MANIFEST.sha256 | 90 +++-- README.md | 7 +- SOURCE_MANIFEST.sha256 | 46 +-- docs/API.md | 9 +- docs/openapi.json | 6 +- ha-addon/Dockerfile | 4 +- ha-addon/README.md | 2 + ha-addon/build.sh | 27 ++ .../gree_controller/manifest.json | 2 +- .../repository/gree-controller/CHANGELOG.md | 9 + ha-addon/repository/gree-controller/DOCS.md | 6 +- .../repository/gree-controller/config.yaml | 7 +- .../gree-controller/translations/en.yaml | 3 + .../gree-controller/translations/pl.yaml | 3 + ha-addon/run.sh | 59 ++- regenerate-sha.sh | 33 +- scripts/api_dev_test.py | 8 +- src/api/configuration.rs | 2 +- src/api/middleware.rs | 3 +- src/api/system.rs | 2 + src/config.rs | 24 ++ web/js/history.js | 2 + web/styles.css | 341 ++++-------------- 26 files changed, 324 insertions(+), 377 deletions(-) diff --git a/.env.example b/.env.example index 4f3e836..53a4869 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,3 @@ -# GREE Controller 0.14.15 - standalone environment example - # HTTP / WebSocket GREE_CONTROLLER_BIND=0.0.0.0:8787 GREE_CONTROLLER_DATABASE=./data/gree-controller.db diff --git a/Cargo.lock b/Cargo.lock index 8968848..783b3d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -700,7 +700,7 @@ dependencies = [ [[package]] name = "gree-controller" -version = "0.14.14" +version = "0.14.15" dependencies = [ "aes", "aes-gcm", diff --git a/Cargo.toml b/Cargo.toml index 544a93b..b26767d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gree-controller" -version = "0.14.14" +version = "0.14.15" 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 7c43c36..d66e2f2 100644 --- a/FILE_MANIFEST.sha256 +++ b/FILE_MANIFEST.sha256 @@ -1,18 +1,20 @@ -5302570299af2c2650bada12e123f598c5db8009949c75761385f73ed57343c5 ./build.rs -6c60703633b9d6a696c22beca15e2bbc0c90424a1b98a5dd53cdc0d670dffffd ./Cargo.lock -b19e576628872d7344786c71a65de6b30f67f384b9306930b1aaa6018bf2caaa ./Cargo.toml -91e37862ce8780696128bc84ad941b27f13f2badde5748d8ba73328a8f2201af ./data/gree-controller.db -c4a46a314f328075d95ca9bf6e88f24dbc243223412c33468de0901c6d19423c ./docs/API.md -f21d732eb1147f8f3167d2172cdf0b57e61edabdfecbaf97811d46c5bff7e47a ./docs/FLOW.md -f42e2dd9d0ad82958fd554bc8a7faf08a1bbd502f897a93cb32cc86089bda2b2 ./docs/openapi.json -d67af429e4da9ce08e9d2f2a8472849ffbd70d135b1c5da535a076026794d04c ./.env f809050a6074ba8e98e08c89af1174dffef679114ffae5c2d200150a9588b8d9 ./.env.example a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore -14f342bc7d1762a04df5875d32a621f06b35b06dc86ea50f1fc554f8d7b3f94d ./gree-controller.zip -cd7fd4b937a1d6138699f7effd184e3c077912b881e9c73c37b90bbafba68477 ./ha-addon/build.sh -a18d2460340264d34c267fa704ee51a42e59bcc1783ee4c0dbb2a8ef42fa0f16 ./ha-addon/Dockerfile -fef9a676b9ce82d0539d44e456237a814169a17a158557d7b803a455985bc020 ./ha-addon/.env +597f6fb6e6b7306d2b22fa68cf4cc9c438b1d67537db29519a1de57a8775a426 ./Cargo.lock +8839d028f8c3facf329b97d8e2623b6c89b27aa25716ccd4848dff4a5e4041bf ./Cargo.toml +19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE +699e00f944554597b1684b7892cb43dc96eb3c7b6f04ed8e28fc3a8784765ff2 ./README.md +bdc52c361ff7a2e0fc3e96ab2b1599f4002cc6490064e4064a5688604cde83bc ./SOURCE_MANIFEST.sha256 +5302570299af2c2650bada12e123f598c5db8009949c75761385f73ed57343c5 ./build.rs +fa74b515483caeea518e392adfe8f235fb3f08131fb2fbd12bebe58c665bd81f ./docs/API.md +f21d732eb1147f8f3167d2172cdf0b57e61edabdfecbaf97811d46c5bff7e47a ./docs/FLOW.md +809adad678983ed8572dd558768be2f20835ebd92dc527da7207e6b5df096d47 ./docs/openapi.json c65aa543d2e99f8f7d52736307b07ebd182597d80124661d53a16bb23f6f1118 ./ha-addon/.env.example +ddec80cf2b5da73fd1bae50e46398033c95aa088afdb7c07d4889b5987a17a3f ./ha-addon/Dockerfile +3702707b5fd4f3a3d5d73e2dc827a6a43c144a22ff94861ab38720207bfcd94b ./ha-addon/README.md +dd9291b12afade6fc20d66e80a5513251270e18e4c265260b2bbbe393a5a94c4 ./ha-addon/build.sh +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 5e4aef2143e81bedb5a15dd4be5c71b64a3ab448ec6d33a20851edd098e5f529 ./ha-addon/home-assistant/custom_components/gree_controller/config_flow.py @@ -20,8 +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 -f8e8559fe10fe523ac5bc9aac25c6e26e862f679d502e8f3c39f38a0a8e40911 ./ha-addon/home-assistant/custom_components/gree_controller/__init__.py -2348f11fcbadc83258cdd63ec81a946a38163a7729dbcf531d93cbdd51faea9d ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json +c62ac791ec1abefe26ea97e001a92db2f2905ee603b50aab0e76242b45e863e7 ./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,24 +30,21 @@ 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 -082dfeb6326f3e1b9841ccf7f2954117ecdf0aa1a90b0aef1e6a1960dacdecbf ./ha-addon/home-assistant/README.md -f9e31de3d109ead32bf531c074d46a087df398d62488caee0b46b5e0a1c027f7 ./ha_addon.md -0417c88a990c72aecf071fba64d5d9da65dcda4d396cd7565477f7a4532c8c5e ./ha-addon/README.md -f3e47cf1a186608f0b70d54be54ccbd1e120c968e773f31fc85ec52a03392e61 ./ha-addon/repository/gree-controller/CHANGELOG.md -40f4f7156a882ef390011915fe38ee8299633e8b51ab554cd67a7b1e4c99f4ea ./ha-addon/repository/gree-controller/config.yaml -28936708e6cfcc7eafd0d587f6d6cb50f926f70959bdfc7e07849c81d251c71a ./ha-addon/repository/gree-controller/DOCS.md +59f420dcff7b7712ee4fb2afcda8573d2f420b593cc333989b4537179703508c ./ha-addon/repository/gree-controller/CHANGELOG.md +1b5599195329cc3e643c23aa1caadcec80f133e8e515eec7d92b9f3cbda5b80b ./ha-addon/repository/gree-controller/DOCS.md c3db302f38d26dc9f8caf37a1a8a07a1334cc07ae301d452f2940a5ec5915719 ./ha-addon/repository/gree-controller/README.md +bc356f1ea4f367084677ae05887a256056d6b403e5a0f0e57c86c11636fb20ea ./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 -ad3715c719f53dca932a208cfacbe9c65834d9552e41bfef8172bef5ce76be35 ./ha-addon/repository/gree-controller/translations/en.yaml -5564559ed847e42375ceec55e5e9f37c6c69f91b501a2d5c4e8c6656938a766d ./ha-addon/repository/gree-controller/translations/pl.yaml +2272740b185e9520e948f74b8f45cf98b8b5fd28c93bdee760da626f91fe5c2a ./ha-addon/repository/gree-controller/translations/en.yaml +e6b93d3701d83a31506617a0e6f44ff32d8c24a52be038c8540c862b13546b50 ./ha-addon/repository/gree-controller/translations/pl.yaml 2153b230357aab4766398577e23a7a9ff83bb135ddc86d1b3f2cbfc852efe0a1 ./ha-addon/repository/repository.yaml -e8a77dbdb98c6ebcfbe6a08b0b5546178d965f25496c19f7aebaff287630838e ./ha-addon/run.sh +cea12bc74c066a588a5ef0ef7bad9d4906bdbcea99f2d275429b0eefc94b4b5f ./ha-addon/run.sh 081e27b6e43070d44f865335c30a42a5a29668eaa6a5ea5ab6967ac4447b8e06 ./ha-addon/sync-repository.sh +f9e31de3d109ead32bf531c074d46a087df398d62488caee0b46b5e0a1c027f7 ./ha_addon.md +253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md ec091f3cf6745995b1a677df5b064e9ed70e67b61f2c300e146ac8d64d6af570 ./lang/en.json b14581a982441986df693849feccfcdfe4769b245a2adac0558fc86420d17f82 ./lang/pl.json -253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md -19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE d8459024f04ca514bd8e9d6bd3af872fb942fd85a7cdd5583f4a9d28aab6faba ./make_zip.py b14233a8987e53bbbdd6770386ba10fa166ec25c1e098275c173c544b37846fc ./presets/bedroom_window_night.json 1960841119c0b673fb2f03621b32237552fd828045304cd221401214b997c910 ./presets/device_resilience.json @@ -85,9 +83,9 @@ fffa18bd989a0dcc1c5458975522517d40af4af40cc1ca30640e79d3db711053 ./presets/unoc ae21459a261712bcb8d57594528b1648432e8d03a8a38502234829c0dbfef774 ./presets/weekend_comfort.json fdcd9a5055d08037278b842e7ab69265345c5811f0a06867136c511d140bb191 ./presets/window_available_guard.json 804f22123cd3e8db0fac791826c8dd9f758fb866c8e3b5655fb6d25d259dccf1 ./presets/workday_comfort.json -2b0c8072501b7c63cefb16a92694132d311c0fde79bc43a5f7681d630e035b75 ./README.md -01952aa92b217f8eae2493b88870e2dec595100cd15c4d561ff11ae2b936c46f ./regenerate-sha.sh -0d2fd90f690d00a1f21aa4e3810a3dce976b55583df752c1cb7a90bc3742e7d3 ./scripts/api_dev_test.py +fd7390d64a2378352c09382accf6efff43903d952d111b22a982f06c5b2a3b4b ./regenerate-sha.sh +c64b1c6deeb24f20af662bfce7fa3683703914987241107ce664fa2f6d1a9dcb ./scripts/README.md +8bf77e97e4789242924aa030aa63c63912f5cd7334de0778f2ea9800c6912f8e ./scripts/api_dev_test.py 5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh 6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh 1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh @@ -96,16 +94,15 @@ e4849261fd9ed1f01df96c0637c439c0c4eff8fa317b2918026167bba343af79 ./scripts/inst bb7cd2c5b27c9dceec1d1d2846fbad9600df9c08e0ad07d13fcde97af533091c ./scripts/install.sh 1a84c91a89bf7f60aa4816a7650640f315efadf39bf7b54dc20f1259b2e2d5ce ./scripts/live_realtime_test.js e00d211e3885e30d7fed1e43b44e6fdad40a67019060156c0641816a93e3365f ./scripts/network-debug.sh -c64b1c6deeb24f20af662bfce7fa3683703914987241107ce664fa2f6d1a9dcb ./scripts/README.md 01952aa92b217f8eae2493b88870e2dec595100cd15c4d561ff11ae2b936c46f ./scripts/regenerate-sha.sh 81345b6a0b51736bdbc98fd23199b62e4c721b4e7437e02dab7ea79b97dff29a ./scripts/service.sh bd703ef841f9763f13367aaa5764381b9c59ee87210e806c24617f3cc0739a9b ./scripts/smoke.sh b50782b3742dfbf8a319c60571c968e93fdf8547db747c759edcffae68cb98bf ./scripts/update.sh -28dde10947ac7a9c4b0301b67286cd01653d4d372989c1c948ec5dc39ae5dec9 ./SOURCE_MANIFEST.sha256 +a70ddd9d4c9fca073ff494db73ec13acc7cff1830c9675d0541518d814da3772 ./src/api.rs f6ed359370dacd691344402307843a0ad31bbeb46085f99e81cfa46a618ee87d ./src/api/assets.rs b24092f34a46626d64628b510eba82193f0b203fd097f6a621b91af5d91eb571 ./src/api/auth.rs 9e4fc675306e111ed3db7af9822e2925d88109c31b9e309851665a975c0bd83c ./src/api/automations.rs -5d1cbc16bcdf0e048c9ec554bfb3a8c26068f3a99fc947ae195221015b90834c ./src/api/configuration.rs +1f2e3df47e532c1a222125d3790ec97772cf535af679589fde0ef9217f2db018 ./src/api/configuration.rs 19bc141286a336ad67e99079496eb0ae5126f986ab28be62b5f97758e5f6969c ./src/api/debug_tokens.rs 32ade46ec45d6faf7852242b9f5ba1be6eacbacbeb33d2deabfab59bf1610ce0 ./src/api/device_groups.rs fcbb3da0538aab451f71810f829cc248d6bb67e586dc418d71a572980583823d ./src/api/devices.rs @@ -116,16 +113,16 @@ b61b0b4f196c5e72e47cf06ca0c14df3cf8eedbdef0242a9d2af33623663489f ./src/api/grou e41ddb47f46ccad81baab1d25ddba910c087350b4b521844a33eae010cdaaa0b ./src/api/history.rs e8edfbb725d6de650442444b0a929ad2be0bd2c2d7d227d2b3fdd99d05c94f94 ./src/api/house.rs c71540b2b68e805e3a79ac787db9127b87585968a205fc307e93bf68d5b028e0 ./src/api/integrations.rs -cafff8c070b146619a541f7b03502e85594c2d9a442ab23610b076e6bd251abe ./src/api/middleware.rs +7658111f7d125db55a6e23f88157339b110b2cd1ed13ceabd8f860b42dba5101 ./src/api/middleware.rs 45e34a447ff384d99a3c42c489ca9fa75849c74d4d246caa91c63d927de17396 ./src/api/openapi.rs b777e7fbeccef699e11ffd16bb18633843c99afecacce2221280e54b09f6d2da ./src/api/public_settings.rs -a70ddd9d4c9fca073ff494db73ec13acc7cff1830c9675d0541518d814da3772 ./src/api.rs 5b29f2e5aa2c422c5829ef89c7b59cf753883aa7f60661c95ab0f95cf676fa3f ./src/api/schedules.rs 6acf6417b077a0204a815e255c667928476014ce5f94c1559c9d4268c00342d5 ./src/api/settings.rs -dfa04f2acc0bcbfca826c4196ab960b38d6bb4193312477ca1167b190aef4b13 ./src/api/system.rs +0de92f241b5da2b7f13aaf9c24ef76eba8d26f8e27d87c9312bd644ab13d31bd ./src/api/system.rs 94d3845def693aafe15fa6e8b390881c46c3c236b62f247ddfdc055d9226ed22 ./src/api/websocket.rs ddd1bfc8625b66f6e5f3d24b3122ac86c25f89b0af527e3dfce9102551a8f142 ./src/api/zones.rs -e495bbb37e81b539bfacfce065fe791f630ccd524e2a33b9dcfd8fe6856941cf ./src/config.rs +7b15febe046c79917a22664f50d9063fd6d0968521544ef31b854424f73b4408 ./src/config.rs +f8394e0daca284070ac9ae0844d6b04fed7c30386f2d1ec409ecf07880bb0e6a ./src/db.rs fdc9d019097becc5c36a0560caa1619aa35d802c0d208902b3bfecbe1643e3e8 ./src/db/climate.rs e5d2027d40a99e61c2c572499af617a92de9e554f8916f3ea6f6731d1d6b09b4 ./src/db/configuration.rs 46213497ae26b2f799364a2e64e41df26c93a39eb4e7167b59c1e42b525f61f7 ./src/db/core_devices.rs @@ -135,10 +132,10 @@ f99de2230082f17eafaf03f5361bb67419205a23d64f22480c996f9c32c55ac5 ./src/db/energ bb5048ee60053f39ce3c1203dc1f24c7047310538d2357f105a052c016d21ec4 ./src/db/flows.rs 5f4b567c6bc866d0674b3b0c66f4aeccf561774623f575b872ff25bf015b91c2 ./src/db/ha_history.rs 7840c89515d08a23050e2634dd91bcdbf7aecc0a8a80bf5002f3eb660ca46f20 ./src/db/network_history.rs -f8394e0daca284070ac9ae0844d6b04fed7c30386f2d1ec409ecf07880bb0e6a ./src/db.rs d6b9b70a04c774173c97f7426487d5e2932d7713e12ae3fb7f64cf2b4e0b3c10 ./src/db/schedules_automations.rs 8b312e258f079d844466ad516134cdd065a017be78be41214b4239fc0f9e5b36 ./src/db/tests.rs f90fbb14f29a20b799b3856bdaa22812a9f0d9dfa46693f1341c160dccda8a97 ./src/db/zone_history.rs +4ed54df6321496a6afabf9443beb9fa8d3434c510aa01dade318bbaa44f0f0ac ./src/engine.rs ed5c9a0c61c6e597acb33f58593ebe02b562ddc68fa61271e1c1cd436c1003d3 ./src/engine/automations.rs 0bf7fe7d28b4749e53ec25d71effbb78bc8f70c716714f838a4a2d8f20627d63 ./src/engine/commands.rs f7de7b9b756bef9d6cffbc6ffdb7504b3121476f188e6ff5c8077d5fc54be811 ./src/engine/connectivity.rs @@ -150,7 +147,6 @@ e350aa63f3bb14ca887eaaa7555edbeae00fb584cbcaf552cc972f0c8918f5d8 ./src/engine/g 41b7045877771b7c352328998cb45ce2433e7f9631fad3eb1a882ab4897dc31f ./src/engine/local_thermostat.rs 99527adab9fea1caede9ebb24faa6a63d5cef4810c64c06306b571cf5cbbb0da ./src/engine/ownership.rs 1a29b7fd8de77f0a5e5964ca05e41cc5ea78855c1ab961750d40461faec305e5 ./src/engine/polling.rs -4ed54df6321496a6afabf9443beb9fa8d3434c510aa01dade318bbaa44f0f0ac ./src/engine.rs d48ae1799e045fb212e73e630a5d26ed3c5e3359a3dac3ebd5638532efdd4285 ./src/engine/runtime.rs c1e8de5392681ba1255e3e2d871a6ee860fb8ca987a0a8f973d9ded3977a68fb ./src/engine/schedules.rs b0189f28d0f337dfd3ce0934da7a565e4a5535afc1c242214c26ed584b3b7840 ./src/engine/targets.rs @@ -161,11 +157,12 @@ c89ff36c6a67bf4fbf68c43e18aa0a556de2dc69263fd79db376363afc741a4f ./src/engine/z 3a7fada1f6dd9ebab4026b353f41b21d8ca19342b02538c711dbb2dfb599cbe7 ./src/engine/zone_control.rs bfac95cec87f0df6ce562153cd3cfe6731359d703e6c592d2c2f7ba01ea62d99 ./src/error.rs a1bd54026f9c7e9f84da1a730136db81095e8ba1ceeb2596fc01479def76df83 ./src/home_assistant.rs +e78ca9aff23362971eb6450534ea21bdc15150456c378ef9b226cb93a8dbc612 ./src/influxdb.rs da4068295f37c23222bb13bebac022d88ba7729604a49ad25438c55e45458a49 ./src/influxdb/codec.rs b96624fa34b218a6d5f028ba09579633f6f8a04c109f0e94801e8d89ceeeb4b5 ./src/influxdb/query.rs -e78ca9aff23362971eb6450534ea21bdc15150456c378ef9b226cb93a8dbc612 ./src/influxdb.rs 8089b8eca47419bb6b3ee02560e5cc39c00479164bc76d11992679b2cef3258e ./src/influxdb/write.rs 6479dd1e5d7f8eb2ffb945ca958124107c5585c0abdfb34555367db0c21145da ./src/main.rs +23688a5cb68efded89471ce959328d27adff370d451bd9f378a283ab51437e76 ./src/models.rs 920e028d96a14cca9f74a964bb296e68a109cfead314d3164c180201e606d151 ./src/models/automation.rs fc5f2802a073a175a8985e56bcb87eb1ff481226d5b4eeabad73dbba1839fc45 ./src/models/control_plan.rs 06357b352ff6494d83e72796b869573a8b8f36b835985a5900ac571c922b8da9 ./src/models/defaults.rs @@ -173,32 +170,31 @@ fc5f2802a073a175a8985e56bcb87eb1ff481226d5b4eeabad73dbba1839fc45 ./src/models/c aeab5b5acf35d38a54fd1d82e964fe56635ade69c0149ad423df317095d01555 ./src/models/flow.rs ba5d359e4d6c12d9f62c1b28bdfb40d312737930df5421814f5310f7183e96c1 ./src/models/history.rs 1fee89f939018bf1f3686c15f25434f01ab96b3239164108db7f348a1f1f8d82 ./src/models/integrations.rs -23688a5cb68efded89471ce959328d27adff370d451bd9f378a283ab51437e76 ./src/models.rs f1359b68f1d041cd441f80429e77893eeb6f6194613b58fd3e7a212fb511db3e ./src/models/runtime.rs e3dfdd6dba63f6febd0b64da413b2fe7ca258a54abc2fdd6f2982c27425abfd6 ./src/models/settings_api.rs c4de6101de85d9d7e6814b2504b87d9216561e3d9e274054f081f505fb152140 ./src/models/temporary_thermostat.rs bc2818ce8d0dae1edfb1b98e5d1c00b193f03eeaa28e343d3532f6023288738c ./src/models/zone.rs 62e696df8b86835df7ad127f03c257a78580769dc82bc66daa5041f71e8440a5 ./src/notifications.rs cca742bb2b500eb9269f69385fcf88053cd08aabf6a98b7669b8a7a2fd67df13 ./src/protocol/crypto.rs +cd0612021f8ed6c9e0823d63883c0790db09aa485583f3f3fc9f2579f2c28ea3 ./src/protocol/gree.rs 7907c33e6396c7e0d61705b735b21c84d6ed784012fb61022b872c6745540f1d ./src/protocol/gree/binding.rs -d406a2c5a49fe059f3f3a00ea534a32f9bdd972c09cc051cbfc4317f7c91b38c ./src/protocol/gree_cloud_mqtt.rs -49dcca1e4a4ad858b312386e3b729f36da691916d8fe62ad682ddb86d40695b7 ./src/protocol/gree_cloud.rs d71771855d5ac90fae4994debf53ed09472b216c17bb2155d80aa2de13c57e18 ./src/protocol/gree/commands.rs 4e419ba3b86ca9d154e8c04445cf005965175c54041093b2daad3666d4d852bd ./src/protocol/gree/core.rs 05fa2e4ec133a61c23a3f4ab2ce792bbfab93feaea9f0ce0d4ccc67fc479833d ./src/protocol/gree/discovery.rs 0ab094f609edb99715402d8eeaa7e38cbfd2d271036aec787dcbb3be3e005720 ./src/protocol/gree/merge.rs c09c931fa8d512a8883fb14f3142be694af3c51290e023d25c7c972e156e4bd2 ./src/protocol/gree/network.rs 0a901ff258a74072af3812c5417196da1a9b8daa8a2eff30c96d038558601739 ./src/protocol/gree/polling.rs -cd0612021f8ed6c9e0823d63883c0790db09aa485583f3f3fc9f2579f2c28ea3 ./src/protocol/gree.rs 50152bcad4a843ae04000b991a2365abb302fc1583ea69f09320e25740f3c38c ./src/protocol/gree/tests.rs 3c93e5b254a2a40a07854532cb5fc07e493ad65d9ba2cf28093cff4a4a4e451c ./src/protocol/gree/transport.rs +49dcca1e4a4ad858b312386e3b729f36da691916d8fe62ad682ddb86d40695b7 ./src/protocol/gree_cloud.rs +d406a2c5a49fe059f3f3a00ea534a32f9bdd972c09cc051cbfc4317f7c91b38c ./src/protocol/gree_cloud_mqtt.rs 6258ee3888a5f9a4f8bb6cfbbac97f2621de07f9da6bcb2fa596d452f47f2e49 ./src/protocol/mod.rs 823da603035619894d8a0252c6a9782c44fdf8d539f09a0a0c120dc8e4b8e4f2 ./src/provider.rs +830621fa75265da7288b8135704c0ce27e0687f87d55abf3cdbe40a6e2f3d2b1 ./src/queries.rs 7fac65a9dcbcecee09816f5447c7792dc94f736b1efb66ac85629db75f6280be ./src/queries/device_history.rs 580917b91441fe9b3276cf0cfc31536ba7954b6a86f4080347b93f9367a5b1ac ./src/queries/entities.rs 2c5fba462b72158b04dbaaa53c8536263f8d72a1338a6c4ff06cb28a36f6ae27 ./src/queries/ha_history.rs 7e12e8704faa7ac15a312bcc1d714ee1c6f27c1dce0972d4ccd6e5a40c69c884 ./src/queries/maintenance.rs -830621fa75265da7288b8135704c0ce27e0687f87d55abf3cdbe40a6e2f3d2b1 ./src/queries.rs 7da8b73a1c8c30e2a12071062d36f5393938fde0b5ffebbe4af1891da40f3233 ./src/queries/schema.rs f13f1be3d5789539eba3fc1b59c14835f8c681eca7634e118fa3763a53aeee2b ./src/queries/zone_history.rs 7e7037a059b585c5709367b9431a225789e7039796e3ed19fc132776ac234099 ./src/state.rs @@ -208,6 +204,7 @@ b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree f0c3b6ed0cb99a7271dbad6d4003ff98fa1a4314a384e7341441bf988b30c0ba ./web/custom-chart.js e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg 019af8f410a20704e7445d0013fc37728d56b11b2772d679fe12a6c896972d87 ./web/index.html +9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js/README.md 27eff2821c40395369be15e197fa830535d2327fcb27c93a0e8e65a1c62de3ad ./web/js/bootstrap.js 5970f3d05006ab975c7c88baaed4cc73eb014fc40dbfb3f07a191db3534211ea ./web/js/charts.js 1f1b7d1fdc2755a0888a14a314668a80ed7095763eba9c821e5e0968ec54fd73 ./web/js/core.js @@ -216,17 +213,16 @@ c5476aaa91123bc9d2b0ab1cb2870cba04fd67ae0067f86b3edb415b76cd4864 ./web/js/entit a59fd5b822029d0c1e74ba8078f55f13122347e036a96dec383293ae386f6667 ./web/js/events.js b08b0fe301642d1daa084b322a02e351d284d2465231505d45079a52ccb4028f ./web/js/flows.js 8e6798405332eec8439c9ea28e967ee2b1d62b57012452c03089aff01dcd6452 ./web/js/forms.js -c48f1947ade3ac461a8633b56bacd8be229639c53f2057f841d6ec8fc8f4e971 ./web/js/history.js +1868a8afbfc35623b538a1c6cd73246c8cc0aada4881e56941167958c0276b18 ./web/js/history.js 511c4e7cc4c48fbbdab64558a9d1c27e6759c9b6dca255d11f301b9708e955c7 ./web/js/main.js 6bb9386707dcf601111fea15a1a366d8798313b973ca1624878618f4c3095add ./web/js/navigation.js -9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js/README.md 799613c6d465cb2f92ecb57cb082d0c33729a03d88ab4ca7fa12a3cbd7e38e75 ./web/js/realtime.js 9546ca5c5566994aff3c33d92295be46b65d51d874681764abe77470dc9c9c73 ./web/js/router.js c2710fd9c1f2b31878c1e25ceb02d74ae30456c1bbb400e69fcf7619ecc72bc2 ./web/js/select-ui.js -9f50794a92059357301b4c920b066bdb20ce1f64fbccea42effcd196d0a00cc4 ./web/js/settings.js 420cc44008d739ece4031093db772cc2bd6cdc0573b971b04ccce2aedb5762dd ./web/js/settings-ui.js +9f50794a92059357301b4c920b066bdb20ce1f64fbccea42effcd196d0a00cc4 ./web/js/settings.js 3815fea7ae1694568c2f249bc8460e0563ced6bde89214f74f21b41fe8cad9a6 ./web/lang-init.js 6b653e4fe2d4db4ffae6ff8f39b8ce57a10a990dc0000ee4aae51d949afadd52 ./web/manifest.webmanifest -8f2631e2c681940773667672b1cd0e467c82c5f93a0749300ff6852bbaf669f4 ./web/styles.css +e25c0f0419611c47a1870d0f37b5db1fe5632c12eeba0b3556179e13f222e02a ./web/styles.css fefdf12cbbb0a5d8a2cb89275cae28455aa42aaf179542305cc8f5e0f4825898 ./web/sw.js d505d793ce7cc9485b45b78bba1c0d51887adc7451ab59a42702946e5b991382 ./web/theme-init.js diff --git a/README.md b/README.md index 224046a..a8e7e96 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.14** +**Version: 0.14.15** -Version 0.14.14 makes Home Assistant entity selection in Reusable data -> Shared Flow input explicit: a separate search field filters the live HA entity catalog by name, entity_id and current state, while the selected entity_id remains a separate editable value. It keeps the automatic Supervisor authentication and manual fallback behavior introduced in 0.14.13. Standalone Home Assistant configuration remains unchanged. +Version 0.14.15 hardens Home Assistant add-on publishing and direct-access behavior. The add-on keeps a fixed internal HTTP/ingress port (`8787`), validates that contract at startup/build time, resolves the primary Home Assistant host IPv4 through the Supervisor API for chart-only share links, and supports an explicit `public_chart_base_url` override for reverse proxies or unusual network layouts. Standalone port handling remains dynamic through `GREE_CONTROLLER_BIND`. > [Full API reference](docs/API.md) — authentication, every endpoint, request bodies, response models, WebSocket events and examples. @@ -126,6 +126,7 @@ Environment values explicitly supplied for supported runtime overrides win over | `GREE_CONTROLLER_DATABASE` | `./data/gree-controller.db` | SQLite database path. | | `GREE_CONTROLLER_APP_TOKEN` | empty | Administrator API/Web UI token. Empty means trusted-LAN mode in standalone installs; with HA Supervisor auth active, empty disables direct dashboard/API access on the exposed port. | | `GREE_CONTROLLER_BASE_PATH` | empty | Optional reverse-proxy prefix such as `/gree`. | +| `GREE_CONTROLLER_PUBLIC_CHART_BASE_URL` | empty | Optional absolute HTTP(S) base URL used only when generating public Custom Chart links; useful behind a reverse proxy or alternate external port. | | `GREE_CONTROLLER_ID` | `gree-controller` | GREE client/controller identifier. | | `GREE_CONTROLLER_SIMULATE` | `false` | Initial Simulation mode. | | `GREE_CONTROLLER_AUTO_SEED` | `false` | Seed simulated sample data when applicable. | @@ -283,7 +284,7 @@ There are two independent Home Assistant directions. In the Home Assistant add-on, the connection to Home Assistant Core is automatic through the Supervisor API proxy; no Home Assistant URL or Long-Lived Access Token is required. -When the add-on detects `SUPERVISOR_TOKEN`, Home Assistant ingress remains trusted, but direct Web UI/API access on TCP `8787` requires `app_token`. If `app_token` is empty, direct dashboard/API access is disabled. History → Custom Charts → Copy link creates a random persisted share token and returns a chart-only URL. In the add-on it bypasses HA ingress and targets the same HA host/IP on the direct HTTP port (normally `8787`); in standalone mode it uses the current controller origin/base path. Only the generated chart view/data endpoint is public to the network. +When the add-on detects `SUPERVISOR_TOKEN`, Home Assistant ingress remains trusted, but direct Web UI/API access on TCP `8787` requires `app_token`. If `app_token` is empty, direct dashboard/API access is disabled. The Home Assistant add-on intentionally uses `8787` as a fixed internal ingress/application port; it is not a user-configurable Network option. The startup script verifies the Supervisor-reported `ingress_port` and refuses to start on a mismatched manually modified package. History → Custom Charts → Copy link creates a random persisted share token and returns a chart-only URL. The add-on resolves the primary HA host IPv4 through the Supervisor API and uses `http://:8787` by default; `public_chart_base_url` can override that address for a reverse proxy or non-standard routing. Standalone mode uses the current controller origin/base path and follows `GREE_CONTROLLER_BIND`. Only the generated chart view/data endpoint is public to the network. For standalone installations, configure **Home Assistant / Sensors** in the Web UI or use: diff --git a/SOURCE_MANIFEST.sha256 b/SOURCE_MANIFEST.sha256 index e532c8c..dad2565 100644 --- a/SOURCE_MANIFEST.sha256 +++ b/SOURCE_MANIFEST.sha256 @@ -1,17 +1,17 @@ -d67af429e4da9ce08e9d2f2a8472849ffbd70d135b1c5da535a076026794d04c ./.env.example +f809050a6074ba8e98e08c89af1174dffef679114ffae5c2d200150a9588b8d9 ./.env.example a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore -6c60703633b9d6a696c22beca15e2bbc0c90424a1b98a5dd53cdc0d670dffffd ./Cargo.lock -b19e576628872d7344786c71a65de6b30f67f384b9306930b1aaa6018bf2caaa ./Cargo.toml +597f6fb6e6b7306d2b22fa68cf4cc9c438b1d67537db29519a1de57a8775a426 ./Cargo.lock +8839d028f8c3facf329b97d8e2623b6c89b27aa25716ccd4848dff4a5e4041bf ./Cargo.toml 19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE -2b0c8072501b7c63cefb16a92694132d311c0fde79bc43a5f7681d630e035b75 ./README.md +699e00f944554597b1684b7892cb43dc96eb3c7b6f04ed8e28fc3a8784765ff2 ./README.md 5302570299af2c2650bada12e123f598c5db8009949c75761385f73ed57343c5 ./build.rs -c4a46a314f328075d95ca9bf6e88f24dbc243223412c33468de0901c6d19423c ./docs/API.md +fa74b515483caeea518e392adfe8f235fb3f08131fb2fbd12bebe58c665bd81f ./docs/API.md f21d732eb1147f8f3167d2172cdf0b57e61edabdfecbaf97811d46c5bff7e47a ./docs/FLOW.md -f42e2dd9d0ad82958fd554bc8a7faf08a1bbd502f897a93cb32cc86089bda2b2 ./docs/openapi.json +809adad678983ed8572dd558768be2f20835ebd92dc527da7207e6b5df096d47 ./docs/openapi.json c65aa543d2e99f8f7d52736307b07ebd182597d80124661d53a16bb23f6f1118 ./ha-addon/.env.example -a18d2460340264d34c267fa704ee51a42e59bcc1783ee4c0dbb2a8ef42fa0f16 ./ha-addon/Dockerfile -0417c88a990c72aecf071fba64d5d9da65dcda4d396cd7565477f7a4532c8c5e ./ha-addon/README.md -cd7fd4b937a1d6138699f7effd184e3c077912b881e9c73c37b90bbafba68477 ./ha-addon/build.sh +ddec80cf2b5da73fd1bae50e46398033c95aa088afdb7c07d4889b5987a17a3f ./ha-addon/Dockerfile +3702707b5fd4f3a3d5d73e2dc827a6a43c144a22ff94861ab38720207bfcd94b ./ha-addon/README.md +dd9291b12afade6fc20d66e80a5513251270e18e4c265260b2bbbe393a5a94c4 ./ha-addon/build.sh 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 @@ -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 -2348f11fcbadc83258cdd63ec81a946a38163a7729dbcf531d93cbdd51faea9d ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json +c62ac791ec1abefe26ea97e001a92db2f2905ee603b50aab0e76242b45e863e7 ./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,16 +29,16 @@ 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 -f3e47cf1a186608f0b70d54be54ccbd1e120c968e773f31fc85ec52a03392e61 ./ha-addon/repository/gree-controller/CHANGELOG.md -28936708e6cfcc7eafd0d587f6d6cb50f926f70959bdfc7e07849c81d251c71a ./ha-addon/repository/gree-controller/DOCS.md +59f420dcff7b7712ee4fb2afcda8573d2f420b593cc333989b4537179703508c ./ha-addon/repository/gree-controller/CHANGELOG.md +1b5599195329cc3e643c23aa1caadcec80f133e8e515eec7d92b9f3cbda5b80b ./ha-addon/repository/gree-controller/DOCS.md c3db302f38d26dc9f8caf37a1a8a07a1334cc07ae301d452f2940a5ec5915719 ./ha-addon/repository/gree-controller/README.md -40f4f7156a882ef390011915fe38ee8299633e8b51ab554cd67a7b1e4c99f4ea ./ha-addon/repository/gree-controller/config.yaml +bc356f1ea4f367084677ae05887a256056d6b403e5a0f0e57c86c11636fb20ea ./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 -ad3715c719f53dca932a208cfacbe9c65834d9552e41bfef8172bef5ce76be35 ./ha-addon/repository/gree-controller/translations/en.yaml -5564559ed847e42375ceec55e5e9f37c6c69f91b501a2d5c4e8c6656938a766d ./ha-addon/repository/gree-controller/translations/pl.yaml +2272740b185e9520e948f74b8f45cf98b8b5fd28c93bdee760da626f91fe5c2a ./ha-addon/repository/gree-controller/translations/en.yaml +e6b93d3701d83a31506617a0e6f44ff32d8c24a52be038c8540c862b13546b50 ./ha-addon/repository/gree-controller/translations/pl.yaml 2153b230357aab4766398577e23a7a9ff83bb135ddc86d1b3f2cbfc852efe0a1 ./ha-addon/repository/repository.yaml -e8a77dbdb98c6ebcfbe6a08b0b5546178d965f25496c19f7aebaff287630838e ./ha-addon/run.sh +cea12bc74c066a588a5ef0ef7bad9d4906bdbcea99f2d275429b0eefc94b4b5f ./ha-addon/run.sh 081e27b6e43070d44f865335c30a42a5a29668eaa6a5ea5ab6967ac4447b8e06 ./ha-addon/sync-repository.sh f9e31de3d109ead32bf531c074d46a087df398d62488caee0b46b5e0a1c027f7 ./ha_addon.md 253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md @@ -82,9 +82,9 @@ fffa18bd989a0dcc1c5458975522517d40af4af40cc1ca30640e79d3db711053 ./presets/unoc ae21459a261712bcb8d57594528b1648432e8d03a8a38502234829c0dbfef774 ./presets/weekend_comfort.json fdcd9a5055d08037278b842e7ab69265345c5811f0a06867136c511d140bb191 ./presets/window_available_guard.json 804f22123cd3e8db0fac791826c8dd9f758fb866c8e3b5655fb6d25d259dccf1 ./presets/workday_comfort.json -01952aa92b217f8eae2493b88870e2dec595100cd15c4d561ff11ae2b936c46f ./regenerate-sha.sh +fd7390d64a2378352c09382accf6efff43903d952d111b22a982f06c5b2a3b4b ./regenerate-sha.sh c64b1c6deeb24f20af662bfce7fa3683703914987241107ce664fa2f6d1a9dcb ./scripts/README.md -0d2fd90f690d00a1f21aa4e3810a3dce976b55583df752c1cb7a90bc3742e7d3 ./scripts/api_dev_test.py +8bf77e97e4789242924aa030aa63c63912f5cd7334de0778f2ea9800c6912f8e ./scripts/api_dev_test.py 5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh 6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh 1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh @@ -101,7 +101,7 @@ a70ddd9d4c9fca073ff494db73ec13acc7cff1830c9675d0541518d814da3772 ./src/api.rs f6ed359370dacd691344402307843a0ad31bbeb46085f99e81cfa46a618ee87d ./src/api/assets.rs b24092f34a46626d64628b510eba82193f0b203fd097f6a621b91af5d91eb571 ./src/api/auth.rs 9e4fc675306e111ed3db7af9822e2925d88109c31b9e309851665a975c0bd83c ./src/api/automations.rs -5d1cbc16bcdf0e048c9ec554bfb3a8c26068f3a99fc947ae195221015b90834c ./src/api/configuration.rs +1f2e3df47e532c1a222125d3790ec97772cf535af679589fde0ef9217f2db018 ./src/api/configuration.rs 19bc141286a336ad67e99079496eb0ae5126f986ab28be62b5f97758e5f6969c ./src/api/debug_tokens.rs 32ade46ec45d6faf7852242b9f5ba1be6eacbacbeb33d2deabfab59bf1610ce0 ./src/api/device_groups.rs fcbb3da0538aab451f71810f829cc248d6bb67e586dc418d71a572980583823d ./src/api/devices.rs @@ -112,15 +112,15 @@ b61b0b4f196c5e72e47cf06ca0c14df3cf8eedbdef0242a9d2af33623663489f ./src/api/grou e41ddb47f46ccad81baab1d25ddba910c087350b4b521844a33eae010cdaaa0b ./src/api/history.rs e8edfbb725d6de650442444b0a929ad2be0bd2c2d7d227d2b3fdd99d05c94f94 ./src/api/house.rs c71540b2b68e805e3a79ac787db9127b87585968a205fc307e93bf68d5b028e0 ./src/api/integrations.rs -cafff8c070b146619a541f7b03502e85594c2d9a442ab23610b076e6bd251abe ./src/api/middleware.rs +7658111f7d125db55a6e23f88157339b110b2cd1ed13ceabd8f860b42dba5101 ./src/api/middleware.rs 45e34a447ff384d99a3c42c489ca9fa75849c74d4d246caa91c63d927de17396 ./src/api/openapi.rs b777e7fbeccef699e11ffd16bb18633843c99afecacce2221280e54b09f6d2da ./src/api/public_settings.rs 5b29f2e5aa2c422c5829ef89c7b59cf753883aa7f60661c95ab0f95cf676fa3f ./src/api/schedules.rs 6acf6417b077a0204a815e255c667928476014ce5f94c1559c9d4268c00342d5 ./src/api/settings.rs -dfa04f2acc0bcbfca826c4196ab960b38d6bb4193312477ca1167b190aef4b13 ./src/api/system.rs +0de92f241b5da2b7f13aaf9c24ef76eba8d26f8e27d87c9312bd644ab13d31bd ./src/api/system.rs 94d3845def693aafe15fa6e8b390881c46c3c236b62f247ddfdc055d9226ed22 ./src/api/websocket.rs ddd1bfc8625b66f6e5f3d24b3122ac86c25f89b0af527e3dfce9102551a8f142 ./src/api/zones.rs -e495bbb37e81b539bfacfce065fe791f630ccd524e2a33b9dcfd8fe6856941cf ./src/config.rs +7b15febe046c79917a22664f50d9063fd6d0968521544ef31b854424f73b4408 ./src/config.rs f8394e0daca284070ac9ae0844d6b04fed7c30386f2d1ec409ecf07880bb0e6a ./src/db.rs fdc9d019097becc5c36a0560caa1619aa35d802c0d208902b3bfecbe1643e3e8 ./src/db/climate.rs e5d2027d40a99e61c2c572499af617a92de9e554f8916f3ea6f6731d1d6b09b4 ./src/db/configuration.rs @@ -212,7 +212,7 @@ c5476aaa91123bc9d2b0ab1cb2870cba04fd67ae0067f86b3edb415b76cd4864 ./web/js/entit a59fd5b822029d0c1e74ba8078f55f13122347e036a96dec383293ae386f6667 ./web/js/events.js b08b0fe301642d1daa084b322a02e351d284d2465231505d45079a52ccb4028f ./web/js/flows.js 8e6798405332eec8439c9ea28e967ee2b1d62b57012452c03089aff01dcd6452 ./web/js/forms.js -c48f1947ade3ac461a8633b56bacd8be229639c53f2057f841d6ec8fc8f4e971 ./web/js/history.js +1868a8afbfc35623b538a1c6cd73246c8cc0aada4881e56941167958c0276b18 ./web/js/history.js 511c4e7cc4c48fbbdab64558a9d1c27e6759c9b6dca255d11f301b9708e955c7 ./web/js/main.js 6bb9386707dcf601111fea15a1a366d8798313b973ca1624878618f4c3095add ./web/js/navigation.js 799613c6d465cb2f92ecb57cb082d0c33729a03d88ab4ca7fa12a3cbd7e38e75 ./web/js/realtime.js diff --git a/docs/API.md b/docs/API.md index 7f4569a..c1a3ea9 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.14**. +HTTP and WebSocket API for GREE Controller **0.14.15**. [← Main documentation](../README.md) @@ -239,7 +239,7 @@ Response: { "status": "ok", "name": "gree-controller", - "version": "0.14.14", + "version": "0.14.15", "uptime_seconds": 1234, "control_ready": true, "time": "2026-08-30T06:54:00Z" @@ -302,7 +302,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.14", + "version": "0.14.15", "uptime_seconds": 1234, "auth_required": false, "control_ready": true, @@ -312,6 +312,7 @@ Returns the initial Web UI snapshot: "simulator_count": 0, "bind": "0.0.0.0:8787", "base_path": "/", + "public_chart_base_url": "http://192.168.1.20:8787", "gree_interface": "auto", "gree_received_frames": 809, "gree_received_frames_by_device": {} @@ -327,7 +328,7 @@ Returns the initial Web UI snapshot: `GET /charts/custom/:token` renders only the shared chart, without the dashboard. `GET /api/public/charts/custom/:token` returns only the series configured for that share and is intentionally unauthenticated. Possession of the unguessable share URL is the authorization for this narrow read-only endpoint. -In Home Assistant add-on ingress, the Web UI builds the copied URL against the direct add-on host and bound HTTP port (normally `8787`) instead of the `/api/hassio_ingress/...` prefix. Standalone installations use their current origin and configured base path. +In the Home Assistant add-on, TCP `8787` is the fixed internal application/ingress port. The startup script validates that Supervisor reports the same `ingress_port`; a manually modified mismatched package fails fast instead of starting partially. For generated links, the add-on discovers the primary Home Assistant host IPv4 through the Supervisor API and publishes `http://:8787/charts/custom/...` by default. The optional `public_chart_base_url` add-on setting overrides that base for reverse proxies or unusual routing. Standalone installations use their current origin/configured base path and therefore follow any port configured in `GREE_CONTROLLER_BIND`. ### `GET /api/system/info` diff --git a/docs/openapi.json b/docs/openapi.json index d5c12dd..161ba0b 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.14", + "version": "0.14.15", "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.14\nShared Flow input Home Assistant sources now expose a dedicated entity search box separate from the selected entity_id, making large live entity catalogs easy to filter by name, ID and state.\n\n### 0.14.13\nHome Assistant add-on authentication is now hidden behind an automatic Supervisor status, manual URL/token fallback is unlocked only after a failed HA test, connection tests return a live sample entity reading, and Shared Flow input HA sources include searchable live entity suggestions.\n\n### 0.14.12\nThe Home Assistant add-on now uses the Supervisor Core API proxy and runtime SUPERVISOR_TOKEN automatically; standalone Home Assistant URL/token configuration is unchanged.\n\n### 0.14.11\nWeb UI icons now use a shared inline SVG set instead of font-dependent Unicode glyphs across navigation, dialogs, Flow, charts, controls and event categories for consistent cross-platform rendering.\n\n\n### 0.14.10\nNight mode and Home Assistant / Sensors standalone settings now use the full available page width, matching the other Web UI sections.\n\n### 0.14.9\nPower controls use an embedded SVG icon instead of a font-dependent Unicode glyph. Home Assistant room-sensor stale/error events identify the affected entity and zone, and historical event rows can recover that context from metadata.\n\n### 0.14.8\nRemaining Home Assistant and notification diagnostic errors no longer use device-transport 502 responses. Missing/invalid integration configuration returns 400, configured external dependency failures return 424, and 502 is reserved for Local/LAN GREE device communication.\n\n### 0.14.7\nHome Assistant energy-sensor discovery now returns an empty optional result when HA is not configured, uses 424 for configured external-dependency failures, and custom select popovers use their rendered height when positioning above a field.\n\n\n### 0.14.6\nFixes dynamic custom-select duplication in History → Energy and hides Energy history controls when no energy source is configured.\n\n### 0.14.5\nWeb UI single-choice controls now use one consistent custom dropdown, language packs are loaded on demand, and the dashboard outdoor temperature opens a 24-hour history modal.\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.", + "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.15\nHome Assistant add-on Custom Chart links now resolve the primary HA host IPv4 through Supervisor, support an explicit public_chart_base_url override, and enforce the fixed 8787 ingress/application port contract at startup and build time. Standalone bind/port behavior remains configurable.\n\n### 0.14.14\nShared Flow input Home Assistant sources now expose a dedicated entity search box separate from the selected entity_id, making large live entity catalogs easy to filter by name, ID and state.\n\n### 0.14.13\nHome Assistant add-on authentication is now hidden behind an automatic Supervisor status, manual URL/token fallback is unlocked only after a failed HA test, connection tests return a live sample entity reading, and Shared Flow input HA sources include searchable live entity suggestions.\n\n### 0.14.12\nThe Home Assistant add-on now uses the Supervisor Core API proxy and runtime SUPERVISOR_TOKEN automatically; standalone Home Assistant URL/token configuration is unchanged.\n\n### 0.14.11\nWeb UI icons now use a shared inline SVG set instead of font-dependent Unicode glyphs across navigation, dialogs, Flow, charts, controls and event categories for consistent cross-platform rendering.\n\n\n### 0.14.10\nNight mode and Home Assistant / Sensors standalone settings now use the full available page width, matching the other Web UI sections.\n\n### 0.14.9\nPower controls use an embedded SVG icon instead of a font-dependent Unicode glyph. Home Assistant room-sensor stale/error events identify the affected entity and zone, and historical event rows can recover that context from metadata.\n\n### 0.14.8\nRemaining Home Assistant and notification diagnostic errors no longer use device-transport 502 responses. Missing/invalid integration configuration returns 400, configured external dependency failures return 424, and 502 is reserved for Local/LAN GREE device communication.\n\n### 0.14.7\nHome Assistant energy-sensor discovery now returns an empty optional result when HA is not configured, uses 424 for configured external-dependency failures, and custom select popovers use their rendered height when positioning above a field.\n\n\n### 0.14.6\nFixes dynamic custom-select duplication in History → Energy and hides Energy history controls when no energy source is configured.\n\n### 0.14.5\nWeb UI single-choice controls now use one consistent custom dropdown, language packs are loaded on demand, and the dashboard outdoor temperature opens a 24-hour history modal.\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" } @@ -5819,7 +5819,7 @@ }, "version": { "type": "string", - "example": "0.14.14" + "example": "0.14.15" }, "uptime_seconds": { "type": "integer", diff --git a/ha-addon/Dockerfile b/ha-addon/Dockerfile index e79b93e..5188ce7 100644 --- a/ha-addon/Dockerfile +++ b/ha-addon/Dockerfile @@ -37,7 +37,7 @@ LABEL io.hass.version="${BUILD_VERSION}" \ org.opencontainers.image.version="${BUILD_VERSION}" \ org.opencontainers.image.architecture="${TARGETARCH}" RUN apt-get update \ - && apt-get install -y --no-install-recommends ca-certificates tzdata jq iproute2 \ + && apt-get install -y --no-install-recommends ca-certificates tzdata jq iproute2 curl \ && rm -rf /var/lib/apt/lists/* COPY --from=builder-trixie /src/target/release/gree-controller /usr/local/bin/gree-controller COPY ha-addon/run.sh /usr/local/bin/run.sh @@ -56,7 +56,7 @@ LABEL io.hass.version="${BUILD_VERSION}" \ org.opencontainers.image.title="GREE Controller" \ org.opencontainers.image.version="${BUILD_VERSION}" \ org.opencontainers.image.architecture="${TARGETARCH}" -RUN apk add --no-cache ca-certificates tzdata jq iproute2 +RUN apk add --no-cache ca-certificates tzdata jq iproute2 curl COPY --from=builder-alpine /src/target/release/gree-controller /usr/local/bin/gree-controller COPY ha-addon/run.sh /usr/local/bin/run.sh RUN chmod 0755 /usr/local/bin/run.sh /usr/local/bin/gree-controller diff --git a/ha-addon/README.md b/ha-addon/README.md index a55f316..47bd715 100644 --- a/ha-addon/README.md +++ b/ha-addon/README.md @@ -42,6 +42,8 @@ INTEGRATION_DOMAIN=gree_controller The project version comes only from root `Cargo.toml`. `render` synchronizes it to `Cargo.lock`, add-on `config.yaml` and the custom integration `manifest.json`, and sets the OCI image from `.env`. +For the Home Assistant package, TCP `8787` is an intentional fixed host-network/ingress port contract. `build.sh render` verifies `config.yaml`, `run.sh`, the watchdog URL and Docker image metadata stay consistent. The runtime also compares the Supervisor-reported ingress port before starting. Standalone installations remain free to use any `GREE_CONTROLLER_BIND` port. + ## Home Assistant repository ```bash diff --git a/ha-addon/build.sh b/ha-addon/build.sh index a2b62ad..273f53b 100755 --- a/ha-addon/build.sh +++ b/ha-addon/build.sh @@ -35,6 +35,8 @@ CONFIG="$SCRIPT_DIR/repository/$ADDON_DIR/config.yaml" LOCKFILE="$PROJECT_ROOT/Cargo.lock" INTEGRATION_MANIFEST="$SCRIPT_DIR/home-assistant/custom_components/$INTEGRATION_DOMAIN/manifest.json" REPOSITORY_CONFIG="$SCRIPT_DIR/repository/repository.yaml" +RUN_SCRIPT="$SCRIPT_DIR/run.sh" +DOCKERFILE="$SCRIPT_DIR/Dockerfile" [[ -f "$CONFIG" ]] || { echo "Missing add-on config: $CONFIG" >&2; exit 1; } [[ -f "$INTEGRATION_MANIFEST" ]] || { echo "Missing integration manifest: $INTEGRATION_MANIFEST" >&2; exit 1; } @@ -87,6 +89,30 @@ PY printf 'Synced version %s; image %s\n' "$VERSION" "$IMAGE_REPO" } +validate_addon_port_contract() { + local ingress_port + ingress_port="$(awk '/^ingress_port:/ {print $2; exit}' "$CONFIG")" + [[ "$ingress_port" =~ ^[0-9]+$ ]] || { echo "Invalid ingress_port in $CONFIG" >&2; exit 1; } + grep -Fqx "readonly HA_HTTP_PORT=${ingress_port}" "$RUN_SCRIPT" || { + echo "HA port contract mismatch: ingress_port=${ingress_port}, run.sh differs" >&2 + exit 1 + } + grep -Fq "watchdog: \"http://[HOST]:[PORT:${ingress_port}]/api/health\"" "$CONFIG" || { + echo "HA port contract mismatch: watchdog must use [PORT:${ingress_port}]" >&2 + exit 1 + } + grep -Fqx "hassio_api: true" "$CONFIG" || { + echo "HA add-on contract mismatch: hassio_api must be enabled for Supervisor runtime discovery" >&2 + exit 1 + } + local expose_count + expose_count="$(grep -Ec "^EXPOSE ${ingress_port}/tcp$" "$DOCKERFILE" || true)" + (( expose_count >= 1 )) || { + echo "HA port contract mismatch: Dockerfile must expose ${ingress_port}/tcp" >&2 + exit 1 + } +} + ensure_builder() { command -v docker >/dev/null 2>&1 || { echo "docker is required" >&2; exit 1; } docker buildx version >/dev/null 2>&1 || { echo "docker buildx is required" >&2; exit 1; } @@ -136,6 +162,7 @@ build_load() { } render_metadata +validate_addon_port_contract case "$ACTION" in render) ;; 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 c588fd9..bdd87d7 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.14", + "version": "0.14.15", "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 b16533f..1e5c41a 100644 --- a/ha-addon/repository/gree-controller/CHANGELOG.md +++ b/ha-addon/repository/gree-controller/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 0.14.15 + +- Treats TCP `8787` as the fixed internal Home Assistant add-on application/ingress port and validates the Supervisor-reported ingress port at startup. +- Makes the add-on build fail when `run.sh`, `ingress_port` and watchdog port metadata diverge. +- Uses the Home Assistant Supervisor network API to resolve the primary host IPv4 for generated chart-only share links instead of depending on the browser/ingress hostname. +- Adds optional `public_chart_base_url` for reverse proxies, alternate hostnames or non-standard external routing while keeping the public surface limited to generated Custom Chart shares. +- Uses Home Assistant's `[PORT:8787]` watchdog placeholder so Supervisor resolves the effective watchdog port from the add-on port contract. +- Keeps standalone installations fully port-configurable through `GREE_CONTROLLER_BIND`. + ## 0.14.14 - Changes History → Custom Charts links to open a standalone chart-only page instead of the full dashboard. diff --git a/ha-addon/repository/gree-controller/DOCS.md b/ha-addon/repository/gree-controller/DOCS.md index 352cfbb..0dc0bec 100644 --- a/ha-addon/repository/gree-controller/DOCS.md +++ b/ha-addon/repository/gree-controller/DOCS.md @@ -56,6 +56,7 @@ Broadcast zwykle nie przechodzi przez router. Sterowanie unicast może działać | `zone_interval_seconds` | interwał sterowania strefami/termostatem | | `discovery_timeout_ms` | timeout discovery UDP | | `app_token` | token bezpośredniego Web UI/API; w trybie Supervisor pusty = direct access zablokowany | +| `public_chart_base_url` | opcjonalny bazowy URL publicznych linków Custom Chart; puste = automatyczne główne IPv4 hosta HA + `:8787` | | `log_level` | `error`, `warn`, `info`, `debug`, `trace` | ### Home Assistant API @@ -64,7 +65,7 @@ Dodatek korzysta automatycznie z wewnętrznego proxy Home Assistant Core (`http: ### Dostęp, dane i bezpieczeństwo -Usługa słucha na TCP `8787`; ingress Home Assistant przekazuje Web UI na ten port. Gdy wykryty jest `SUPERVISOR_TOKEN`, bezpośredni dashboard/API na `:8787` wymaga `app_token`; przy pustym `app_token` bezpośredni dashboard jest zablokowany. Z sieci bez poświadczeń dostępny jest tylko publiczny widok i endpoint danych pojedynczego Custom Chart (`/charts/custom/`, `/api/public/charts/custom/`). Token udostępnienia jest losowy, jego skrót jest zapisywany w SQLite, a sam URL nie zawiera nazw urządzeń ani listy metryk. Link kopiowany z History → Custom chart omija HA ingress i wskazuje bezpośrednio host/IP dodatku na jego porcie HTTP (domyślnie `8787`). `/api/health` bez tokenu jest akceptowane tylko od peera Supervisora na potrzeby watchdoga; bezpośrednie żądanie sieciowe wymaga `app_token`. Baza jest zapisywana w `/data/gree-controller.db`; konfiguracja używa `backup: cold`, więc dane są objęte backupem dodatku. +Usługa używa stałego wewnętrznego portu TCP `8787`; ingress Home Assistant przekazuje Web UI na ten port. Port nie jest opcją użytkownika w zakładce Network. Przy starcie add-on porównuje port raportowany przez Supervisor z kontraktem `8787` i odmawia startu, jeśli ręcznie zmodyfikowana paczka jest niespójna. Gdy wykryty jest `SUPERVISOR_TOKEN`, bezpośredni dashboard/API na `:8787` wymaga `app_token`; przy pustym `app_token` bezpośredni dashboard jest zablokowany. Z sieci bez poświadczeń dostępny jest tylko publiczny widok i endpoint danych pojedynczego Custom Chart (`/charts/custom/`, `/api/public/charts/custom/`). Token udostępnienia jest losowy, jego skrót jest zapisywany w SQLite, a sam URL nie zawiera nazw urządzeń ani listy metryk. Link kopiowany z History → Custom chart omija HA ingress; domyślnie add-on pobiera główne IPv4 hosta z Supervisor API i tworzy `http://:8787/charts/custom/...`. `public_chart_base_url` pozwala jawnie wskazać reverse proxy, inną nazwę hosta lub alternatywną trasę. `/api/health` bez tokenu jest akceptowane tylko od peera Supervisora na potrzeby watchdoga; bezpośrednie żądanie sieciowe wymaga `app_token`. Baza jest zapisywana w `/data/gree-controller.db`; konfiguracja używa `backup: cold`, więc dane są objęte backupem dodatku. Watchdog sprawdza `/api/health`. Do diagnostyki sieci najpierw sprawdź `ha network info`, poprawność `gree_interface`, broadcast konkretnej podsieci i reguły UDP/firewalla. @@ -121,6 +122,7 @@ Broadcast normally does not cross routers. Unicast control may work through rout | `zone_interval_seconds` | thermostat/zone control interval | | `discovery_timeout_ms` | UDP discovery timeout | | `app_token` | token for direct Web UI/API access; in Supervisor mode empty = direct access disabled | +| `public_chart_base_url` | optional base URL for public Custom Chart links; empty = primary HA host IPv4 + `:8787` automatically | | `log_level` | `error`, `warn`, `info`, `debug`, `trace` | ### Home Assistant API @@ -129,6 +131,6 @@ The add-on automatically uses the internal Home Assistant Core proxy (`http://su ### Access, data and security -The service listens on TCP `8787`; Home Assistant ingress proxies the Web UI to that port. When `SUPERVISOR_TOKEN` is detected, direct dashboard/API access on `:8787` requires `app_token`; with an empty `app_token`, direct dashboard access is disabled. From the network, the only unauthenticated application data is the single public Custom Chart view/data endpoint (`/charts/custom/`, `/api/public/charts/custom/`). The share token is random, only its hash is stored in SQLite, and the URL does not expose device names or metric selectors. Links copied from History → Custom chart bypass HA ingress and point directly to the add-on host/IP on its HTTP port (default `8787`). `/api/health` is accepted without a token only from the Supervisor peer for the add-on watchdog; direct network requests require `app_token`. The database is stored in `/data/gree-controller.db`; `backup: cold` keeps it in the add-on backup. +The service uses fixed internal TCP port `8787`; Home Assistant ingress proxies the Web UI to that port. The port is not a user-facing Network option. On startup the add-on compares the Supervisor-reported ingress port with the `8787` contract and refuses to start if a manually modified package is inconsistent. When `SUPERVISOR_TOKEN` is detected, direct dashboard/API access on `:8787` requires `app_token`; with an empty `app_token`, direct dashboard access is disabled. From the network, the only unauthenticated application data is the single public Custom Chart view/data endpoint (`/charts/custom/`, `/api/public/charts/custom/`). The share token is random, only its hash is stored in SQLite, and the URL does not expose device names or metric selectors. Links copied from History → Custom chart bypass HA ingress; by default the add-on obtains the primary host IPv4 from the Supervisor API and builds `http://:8787/charts/custom/...`. `public_chart_base_url` can explicitly select a reverse proxy, alternate hostname or routing path. `/api/health` is accepted without a token only from the Supervisor peer for the add-on watchdog; direct network requests require `app_token`. The database is stored in `/data/gree-controller.db`; `backup: cold` keeps it in the add-on backup. The watchdog checks `/api/health`. For network troubleshooting, verify `ha network info`, `gree_interface`, the selected subnet broadcast, and UDP/firewall rules first. diff --git a/ha-addon/repository/gree-controller/config.yaml b/ha-addon/repository/gree-controller/config.yaml index cb3d855..5488a00 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.14" +version: "0.14.15" 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/" @@ -12,13 +12,14 @@ boot: auto init: false host_network: true homeassistant_api: true +hassio_api: true ingress: true ingress_port: 8787 ingress_stream: true panel_icon: mdi:air-conditioner panel_title: GREE Controller panel_admin: true -watchdog: "http://[HOST]:8787/api/health" +watchdog: "http://[HOST]:[PORT:8787]/api/health" backup: cold options: gree_interface: "" @@ -29,6 +30,7 @@ options: zone_interval_seconds: 5 discovery_timeout_ms: 3000 app_token: "" + public_chart_base_url: "" log_level: info schema: gree_interface: str @@ -39,4 +41,5 @@ schema: zone_interval_seconds: "int(2,3600)" discovery_timeout_ms: "int(500,30000)" app_token: password + public_chart_base_url: str log_level: "list(error|warn|info|debug|trace)" diff --git a/ha-addon/repository/gree-controller/translations/en.yaml b/ha-addon/repository/gree-controller/translations/en.yaml index 37c0105..29a170f 100644 --- a/ha-addon/repository/gree-controller/translations/en.yaml +++ b/ha-addon/repository/gree-controller/translations/en.yaml @@ -23,6 +23,9 @@ configuration: app_token: name: Application token description: Token required for direct dashboard/API access on port 8787. With SUPERVISOR_TOKEN active, an empty token disables direct access; only generated Custom Chart share links remain public. + public_chart_base_url: + name: Public chart base URL + description: Optional override for generated Custom Chart links, for example http://192.168.1.20:8787 or a reverse-proxy URL. Leave empty to use the primary Home Assistant host IPv4 and the fixed add-on port 8787 automatically. log_level: name: Log level description: Controller log verbosity. diff --git a/ha-addon/repository/gree-controller/translations/pl.yaml b/ha-addon/repository/gree-controller/translations/pl.yaml index 5e972cd..a067437 100644 --- a/ha-addon/repository/gree-controller/translations/pl.yaml +++ b/ha-addon/repository/gree-controller/translations/pl.yaml @@ -23,6 +23,9 @@ configuration: app_token: name: Token aplikacji description: Token wymagany do bezpośredniego dostępu do dashboardu/API na porcie 8787. Przy aktywnym SUPERVISOR_TOKEN pusty token blokuje direct access; publiczne pozostają wyłącznie wygenerowane linki Custom Chart. + public_chart_base_url: + name: Bazowy URL publicznych wykresów + description: Opcjonalne nadpisanie adresu generowanych linków Custom Chart, np. http://192.168.1.20:8787 albo adres reverse proxy. Pozostaw puste, aby automatycznie użyć głównego IPv4 hosta Home Assistant i stałego portu add-onu 8787. log_level: name: Poziom logowania description: Szczegółowość logów kontrolera. diff --git a/ha-addon/run.sh b/ha-addon/run.sh index 544cc7e..0141db3 100755 --- a/ha-addon/run.sh +++ b/ha-addon/run.sh @@ -2,6 +2,8 @@ set -Eeuo pipefail CONFIG=/data/options.json +readonly HA_HTTP_PORT=8787 +SUPERVISOR_API="${SUPERVISOR:-http://supervisor}" read_option() { local key="$1" default_value="${2-}" @@ -13,6 +15,44 @@ read_option() { fi } +supervisor_get() { + local path="$1" + [[ -n "${SUPERVISOR_TOKEN:-}" ]] || return 1 + curl --fail --silent --show-error --max-time 5 \ + -H "Authorization: Bearer ${SUPERVISOR_TOKEN}" \ + "${SUPERVISOR_API%/}${path}" +} + +validate_ingress_port_contract() { + local payload ingress_port + payload="$(supervisor_get /addons/self/info 2>/dev/null || true)" + [[ -n "$payload" ]] || return 0 + ingress_port="$(jq -r '.data.ingress_port // empty' <<<"$payload" 2>/dev/null || true)" + if [[ "$ingress_port" =~ ^[0-9]+$ ]] && (( ingress_port != HA_HTTP_PORT )); then + printf 'GREE Controller: refusing to start: HA ingress_port=%s but the add-on HTTP port contract is %s. Reinstall/update the official add-on package instead of changing ingress_port manually.\n' \ + "$ingress_port" "$HA_HTTP_PORT" >&2 + exit 78 + fi +} + +detect_primary_host_ipv4() { + local payload address + payload="$(supervisor_get /network/info 2>/dev/null || true)" + [[ -n "$payload" ]] || return 1 + address="$(jq -r ' + (.data.interfaces // []) + | (if type == "object" then [to_entries[] | (.value + {interface: (.value.interface // .key)})] else . end) + | map(select((.enabled // true) == true and (.connected // true) == true)) + | map(. + {candidate_ipv4: (.ipv4.ip_address // .ip_address // (try .ipv4.address[0] catch empty) // empty)}) + | map(select(.candidate_ipv4 != "")) + | ((map(select((.primary // false) == true)) | .[0]) // .[0] // {}) + | (.candidate_ipv4 // empty) + ' <<<"$payload" 2>/dev/null || true)" + address="${address%%/*}" + [[ "$address" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || return 1 + printf '%s\n' "$address" +} + GREE_INTERFACE="$(read_option gree_interface '')" DISCOVERY_BROADCAST="$(read_option discovery_broadcast '255.255.255.255:7000')" SIMULATE="$(read_option simulate 'false')" @@ -21,9 +61,20 @@ POLL_INTERVAL="$(read_option poll_interval_seconds '15')" ZONE_INTERVAL="$(read_option zone_interval_seconds '5')" DISCOVERY_TIMEOUT="$(read_option discovery_timeout_ms '3000')" APP_TOKEN="$(read_option app_token '')" +PUBLIC_CHART_BASE_URL="$(read_option public_chart_base_url '')" LOG_LEVEL="$(read_option log_level 'info')" -export GREE_CONTROLLER_BIND="0.0.0.0:8787" +validate_ingress_port_contract +PUBLIC_CHART_BASE_SOURCE="configured" +if [[ -z "$PUBLIC_CHART_BASE_URL" ]]; then + PUBLIC_CHART_BASE_SOURCE="browser-fallback" + if PRIMARY_HOST_IPV4="$(detect_primary_host_ipv4)"; then + PUBLIC_CHART_BASE_URL="http://${PRIMARY_HOST_IPV4}:${HA_HTTP_PORT}" + PUBLIC_CHART_BASE_SOURCE="supervisor-primary-ipv4" + fi +fi + +export GREE_CONTROLLER_BIND="0.0.0.0:${HA_HTTP_PORT}" export GREE_CONTROLLER_DATABASE="/data/gree-controller.db" export GREE_CONTROLLER_GREE_INTERFACE="$GREE_INTERFACE" export GREE_CONTROLLER_DISCOVERY_BROADCAST="$DISCOVERY_BROADCAST" @@ -33,9 +84,11 @@ export GREE_CONTROLLER_POLL_INTERVAL_SECONDS="$POLL_INTERVAL" export GREE_CONTROLLER_ZONE_INTERVAL_SECONDS="$ZONE_INTERVAL" export GREE_CONTROLLER_DISCOVERY_TIMEOUT_MS="$DISCOVERY_TIMEOUT" export GREE_CONTROLLER_APP_TOKEN="$APP_TOKEN" +export GREE_CONTROLLER_PUBLIC_CHART_BASE_URL="$PUBLIC_CHART_BASE_URL" export GREE_CONTROLLER_HA_AUTH="supervisor" export RUST_LOG="gree_controller=${LOG_LEVEL},tower_http=${LOG_LEVEL}" -printf 'GREE Controller: interface=%s discovery=%s database=%s\n' \ - "${GREE_INTERFACE:-auto}" "$DISCOVERY_BROADCAST" "$GREE_CONTROLLER_DATABASE" +printf 'GREE Controller: interface=%s discovery=%s database=%s http_port=%s public_chart_base_source=%s\n' \ + "${GREE_INTERFACE:-auto}" "$DISCOVERY_BROADCAST" "$GREE_CONTROLLER_DATABASE" "$HA_HTTP_PORT" \ + "$PUBLIC_CHART_BASE_SOURCE" exec /usr/local/bin/gree-controller diff --git a/regenerate-sha.sh b/regenerate-sha.sh index 6c8fb74..9baebd5 100755 --- a/regenerate-sha.sh +++ b/regenerate-sha.sh @@ -3,18 +3,37 @@ set -euo pipefail cd "$(dirname "$0")" -MANIFEST="FILE_MANIFEST.sha256" -TMP="${MANIFEST}.tmp" +SOURCE_MANIFEST="SOURCE_MANIFEST.sha256" +FILE_MANIFEST="FILE_MANIFEST.sha256" +SOURCE_TMP="${SOURCE_MANIFEST}.tmp" +FILE_TMP="${FILE_MANIFEST}.tmp" find . -type f \ - ! -path "./${MANIFEST}" \ - ! -path "./${TMP}" \ + ! -path "./${SOURCE_MANIFEST}" \ + ! -path "./${SOURCE_TMP}" \ + ! -path "./${FILE_MANIFEST}" \ + ! -path "./${FILE_TMP}" \ ! -path "./.git/*" \ ! -path "./target/*" \ + ! -name '*.pyc' \ + ! -path '*/__pycache__/*' \ -print0 \ | sort -z \ - | xargs -0 sha256sum > "$TMP" + | xargs -0 sha256sum > "$SOURCE_TMP" -mv "$TMP" "$MANIFEST" +mv "$SOURCE_TMP" "$SOURCE_MANIFEST" +sha256sum -c "$SOURCE_MANIFEST" -sha256sum -c "$MANIFEST" +find . -type f \ + ! -path "./${FILE_MANIFEST}" \ + ! -path "./${FILE_TMP}" \ + ! -path "./.git/*" \ + ! -path "./target/*" \ + ! -name '*.pyc' \ + ! -path '*/__pycache__/*' \ + -print0 \ + | sort -z \ + | xargs -0 sha256sum > "$FILE_TMP" + +mv "$FILE_TMP" "$FILE_MANIFEST" +sha256sum -c "$FILE_MANIFEST" diff --git a/scripts/api_dev_test.py b/scripts/api_dev_test.py index 3fbdc46..8d5c9c5 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.14. +"""Developer smoke/integration tests for GREE Controller API 0.14.15. 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.14" +DEFAULT_EXPECTED_VERSION = "0.14.15" SETTINGS_PATHS: dict[str, set[str]] = { "/api/settings/application": {"simulator_enabled"}, @@ -593,7 +593,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.14 contract", lambda: test_openapi(client, args.expected_version)) + runner.run("OpenAPI 0.14.15 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)) @@ -662,7 +662,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.14.", + description="Developer smoke/integration tests for GREE Controller API 0.14.15.", 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 bd517aa..dcfde6a 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.14".into())); + return Err(AppError::BadRequest("unsupported configuration export version; version 3 is required by GREE Controller 0.14.15".into())); } if export.settings.control_strategy != "setpoint" { return Err(AppError::BadRequest( diff --git a/src/api/middleware.rs b/src/api/middleware.rs index bc3ee1c..50431f7 100644 --- a/src/api/middleware.rs +++ b/src/api/middleware.rs @@ -2,6 +2,7 @@ async fn security_headers(request: Request, next: Next) -> Response { let path = request.uri().path(); let is_api = path == "/api" || path.starts_with("/api/"); let is_api_docs = path == "/api-docs" || path.starts_with("/api-docs/"); + let is_custom_chart = path.starts_with("/charts/custom/"); let mut response = next.run(request).await; @@ -24,7 +25,7 @@ async fn security_headers(request: Request, next: Next) -> Response { ); headers.insert( header::HeaderName::from_static("referrer-policy"), - HeaderValue::from_static(if path.starts_with("/charts/custom/") { + HeaderValue::from_static(if is_custom_chart { "no-referrer" } else { "same-origin" diff --git a/src/api/system.rs b/src/api/system.rs index 72be144..719c853 100644 --- a/src/api/system.rs +++ b/src/api/system.rs @@ -15,6 +15,7 @@ struct SystemInfoResponse { simulator_count: usize, bind: String, base_path: String, + public_chart_base_url: String, gree_interface: String, gree_received_frames: u64, gree_received_frames_by_device: std::collections::HashMap, @@ -191,6 +192,7 @@ fn build_system_info(state: &AppState, devices: &[Device]) -> SystemInfoResponse } else { state.config.base_path.clone() }, + public_chart_base_url: state.config.public_chart_base_url.clone(), gree_interface: if state.config.gree_interface.trim().is_empty() { "auto".to_string() } else { diff --git a/src/config.rs b/src/config.rs index 52f4d76..b70fbae 100644 --- a/src/config.rs +++ b/src/config.rs @@ -21,6 +21,8 @@ pub struct Config { pub app_token: String, #[arg(long, env = "GREE_CONTROLLER_BASE_PATH", default_value = "")] pub base_path: String, + #[arg(long, env = "GREE_CONTROLLER_PUBLIC_CHART_BASE_URL", default_value = "")] + pub public_chart_base_url: String, #[arg(long, env = "GREE_CONTROLLER_SIMULATE", default_value_t = false)] pub simulate: bool, #[arg(long, env = "GREE_CONTROLLER_AUTO_SEED", default_value_t = false)] @@ -60,6 +62,7 @@ impl Config { dotenvy::dotenv().ok(); let mut config = Self::parse(); config.base_path = normalize_base_path(&config.base_path)?; + config.public_chart_base_url = normalize_public_chart_base_url(&config.public_chart_base_url)?; if let Some(parent) = config.database.parent() { std::fs::create_dir_all(parent).with_context(|| { format!("cannot create database directory {}", parent.display()) @@ -282,6 +285,27 @@ fn normalize_base_path(value: &str) -> Result { Ok(format!("/{}", value.trim_matches('/'))) } +fn normalize_public_chart_base_url(value: &str) -> Result { + let value = value.trim(); + if value.is_empty() { + return Ok(String::new()); + } + + let parsed = url::Url::parse(value) + .with_context(|| "GREE_CONTROLLER_PUBLIC_CHART_BASE_URL must be an absolute HTTP(S) URL")?; + if !matches!(parsed.scheme(), "http" | "https") + || parsed.host_str().is_none() + || !parsed.username().is_empty() + || parsed.password().is_some() + || parsed.query().is_some() + || parsed.fragment().is_some() + { + anyhow::bail!("GREE_CONTROLLER_PUBLIC_CHART_BASE_URL must be an absolute HTTP(S) URL without credentials, query or fragment"); + } + + Ok(value.trim_end_matches('/').to_string()) +} + fn env_bool(name: &str) -> Option { env::var(name) .ok() diff --git a/web/js/history.js b/web/js/history.js index 03a6e04..a80c347 100644 --- a/web/js/history.js +++ b/web/js/history.js @@ -312,6 +312,8 @@ function drawCurrentChartIfVisible() { } function publicCustomChartUrl(path) { + const configuredPublicBase = String(app.system?.public_chart_base_url || '').trim().replace(/\/+$/, ''); + if (configuredPublicBase) return `${configuredPublicBase}${path}`; if (!APP_BASE.startsWith('/api/hassio_ingress/')) return `${location.origin}${APP_BASE}${path}`; const bind = String(app.system?.bind || ''); const port = bind.match(/:(\d+)$/)?.[1] || '8787'; diff --git a/web/styles.css b/web/styles.css index 2d3c23f..06ebabd 100644 --- a/web/styles.css +++ b/web/styles.css @@ -1664,7 +1664,7 @@ button.outside-pill:focus-visible { font-weight: 650; } -.outdoor-history-links>div { +.outdoor-history-links > div { display: flex; align-items: center; gap: 7px; @@ -2937,9 +2937,7 @@ button.outside-pill:focus-visible { align-items: end; } -.history-network-controls label { - min-width: 0; -} +.history-network-controls label { min-width: 0; } .history-jitter-toggle { min-height: 45px; @@ -9040,25 +9038,13 @@ body.flow-editor-open { } @keyframes cloud-command-pulse { - from { - opacity: .72; - } - - to { - opacity: 1; - } + from { opacity: .72; } + to { opacity: 1; } } @keyframes cloud-command-dot { - from { - transform: scale(.65); - opacity: .45; - } - - to { - transform: scale(1); - opacity: 1; - } + from { transform: scale(.65); opacity: .45; } + to { transform: scale(1); opacity: 1; } } @@ -9390,7 +9376,7 @@ body.flow-editor-open { content: none; } } - +/* Notification delivery audit in Events. */ .log-row .kind .log-notification-badge { margin-left: .45rem; padding: 3px 7px; @@ -9403,6 +9389,7 @@ body.flow-editor-open { vertical-align: middle; } +/* Keep the Flow footer compact before the full mobile layout kicks in. */ @media (min-width:761px) and (max-width:1200px) { .flow-natural-preview { display: block; @@ -9472,13 +9459,14 @@ body.flow-editor-open { } } +/* GREE Cloud runtime summary */ .cloud-runtime-summary { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; } -.cloud-runtime-summary>div { +.cloud-runtime-summary > div { display: grid; gap: 4px; min-width: 0; @@ -9540,6 +9528,7 @@ body.flow-editor-open { } } +/* Shared alerts and split/multisplit installation configuration */ .inline-alert { display: grid; gap: 4px; @@ -9570,30 +9559,18 @@ body.flow-editor-open { color: var(--danger); } -.inline-alert.error strong { - color: var(--danger); -} - +.inline-alert.error strong { color: var(--danger); } .inline-alert.success { border-color: color-mix(in srgb, var(--accent) 42%, var(--line)); background: color-mix(in srgb, var(--accent) 10%, var(--surface)); } - .inline-alert.info { border-color: color-mix(in srgb, var(--info) 38%, var(--line)); background: color-mix(in srgb, var(--info) 9%, var(--surface)); } +.inline-alert.wide { grid-column: 1 / -1; } -.inline-alert.wide { - grid-column: 1 / -1; -} - -.device-installation-summary { - display: grid; - gap: 8px; - margin-bottom: 12px; -} - +.device-installation-summary { display: grid; gap: 8px; margin-bottom: 12px; } .installation-summary-card, .installation-list-row { display: grid; @@ -9605,106 +9582,36 @@ body.flow-editor-open { border-radius: 12px; background: var(--surface-muted); } - -.installation-summary-card>div, -.installation-list-row>button:first-child { - display: grid; - gap: 3px; -} - +.installation-summary-card > div, +.installation-list-row > button:first-child { display: grid; gap: 3px; } .installation-summary-card strong, -.installation-list-row strong { - color: var(--text); -} - +.installation-list-row strong { color: var(--text); } .installation-summary-card small, -.installation-list-row small { - color: var(--muted); -} - -.installation-list { - display: grid; - gap: 8px; -} - -.installation-list-row>button:first-child { - border: 0; - background: transparent; - padding: 0; - text-align: left; -} - -.installation-list-row>div { - display: flex; - align-items: center; - justify-content: flex-end; - gap: 8px; - flex-wrap: wrap; -} - -.installation-form { - border-top: 1px solid var(--line); - padding-top: 14px; - margin-top: 14px; -} - -.installation-device-choices { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 6px; - margin-top: 6px; -} - -.installation-device-choices .check { - margin: 0; - padding: 8px 9px; - border: 1px solid var(--line); - border-radius: 9px; - background: var(--surface); -} - -.installation-device-choices .check.disabled { - opacity: .55; -} - +.installation-list-row small { color: var(--muted); } +.installation-list { display: grid; gap: 8px; } +.installation-list-row > button:first-child { border: 0; background: transparent; padding: 0; text-align: left; } +.installation-list-row > div { display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex-wrap: wrap; } +.installation-form { border-top: 1px solid var(--line); padding-top: 14px; margin-top: 14px; } +.installation-device-choices { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; margin-top: 6px; } +.installation-device-choices .check { margin: 0; padding: 8px 9px; border: 1px solid var(--line); border-radius: 9px; background: var(--surface); } +.installation-device-choices .check.disabled { opacity: .55; } .installation-energy-metric b { color: var(--text); font-size: 18px; font-variant-numeric: tabular-nums; letter-spacing: -.01em; } - .history-context-controls.energy-context-controls { display: grid; grid-template-columns: minmax(240px, 1.5fr) repeat(3, minmax(132px, .8fr)); gap: 10px; align-items: start; } - -.history-energy-targets { - display: grid; - gap: 7px; - min-width: 0; - color: var(--muted); - font-size: 12px; - font-weight: 650; -} - +.history-energy-targets { display: grid; gap: 7px; min-width: 0; color: var(--muted); font-size: 12px; font-weight: 650; } .history-energy-targets small, -.history-range-control small { - display: block; - color: var(--muted); - font-size: 10px; - font-weight: 550; - line-height: 1.35; -} - -.history-energy-picker { - position: relative; - min-width: 0; -} - -.history-energy-picker>summary { +.history-range-control small { display: block; color: var(--muted); font-size: 10px; font-weight: 550; line-height: 1.35; } +.history-energy-picker { position: relative; min-width: 0; } +.history-energy-picker > summary { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; @@ -9718,18 +9625,9 @@ body.flow-editor-open { cursor: pointer; list-style: none; } - -.history-energy-picker>summary::-webkit-details-marker { - display: none; -} - -.history-energy-picker>summary span { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.history-energy-picker>summary b { +.history-energy-picker > summary::-webkit-details-marker { display: none; } +.history-energy-picker > summary span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.history-energy-picker > summary b { min-width: 34px; padding: 3px 6px; border-radius: 999px; @@ -9738,13 +9636,7 @@ body.flow-editor-open { font-size: 10px; text-align: center; } - -.history-energy-picker[open]>summary { - border-color: var(--accent); - outline: 2px solid color-mix(in srgb, var(--accent) 18%, transparent); - outline-offset: 1px; -} - +.history-energy-picker[open] > summary { border-color: var(--accent); outline: 2px solid color-mix(in srgb, var(--accent) 18%, transparent); outline-offset: 1px; } .history-energy-options { position: absolute; z-index: 20; @@ -9759,7 +9651,6 @@ body.flow-editor-open { background: var(--surface); box-shadow: 0 14px 34px rgba(0, 0, 0, .22); } - .history-energy-option { display: grid; grid-template-columns: 20px minmax(0, 1fr); @@ -9773,67 +9664,28 @@ body.flow-editor-open { font-size: 11px; font-weight: 600; } - -.history-energy-option:hover { - background: var(--surface-muted); -} - -.history-energy-option input { - width: 17px; - min-height: 17px; - margin: 0; - padding: 0; - accent-color: var(--accent); -} - -.history-energy-option span { - overflow-wrap: anywhere; -} - -.history-range-control.energy-period-control { - min-width: 0; -} +.history-energy-option:hover { background: var(--surface-muted); } +.history-energy-option input { width: 17px; min-height: 17px; margin: 0; padding: 0; accent-color: var(--accent); } +.history-energy-option span { overflow-wrap: anywhere; } +.history-range-control.energy-period-control { min-width: 0; } @media (max-width: 760px) { - .installation-summary-card, - .installation-list-row { - grid-template-columns: 1fr; - } - - .installation-list-row>div { - justify-content: flex-start; - } - - .installation-device-choices { - grid-template-columns: 1fr; - } - - .history-context-controls.energy-context-controls { - grid-template-columns: 1fr; - } - - .history-toolbar-panel .chart-toolbar.energy-toolbar .history-refresh-button { - margin-top: 0; - } - - .history-energy-options { - width: min(420px, calc(100vw - 40px)); - max-height: 46vh; - } + .installation-list-row { grid-template-columns: 1fr; } + .installation-list-row > div { justify-content: flex-start; } + .installation-device-choices { grid-template-columns: 1fr; } + .history-context-controls.energy-context-controls { grid-template-columns: 1fr; } + .history-toolbar-panel .chart-toolbar.energy-toolbar .history-refresh-button { margin-top: 0; } + .history-energy-options { width: min(420px, calc(100vw - 40px)); max-height: 46vh; } } @media (max-width: 640px) { - .history-network-controls { - grid-template-columns: 1fr; - } - - .history-jitter-toggle { - width: 100%; - } + .history-network-controls { grid-template-columns: 1fr; } + .history-jitter-toggle { width: 100%; } } +/* Unified custom selects -------------------------------------------------- */ .select-native-proxy { position: absolute !important; width: 1px !important; @@ -10022,8 +9874,8 @@ body.flow-editor-open { padding: 9px 10px; } -.has-error>.custom-select .custom-select-trigger, -select.select-native-proxy[aria-invalid="true"]+.custom-select .custom-select-trigger { +.has-error > .custom-select .custom-select-trigger, +select.select-native-proxy[aria-invalid="true"] + .custom-select .custom-select-trigger { border-color: color-mix(in srgb, var(--danger) 68%, var(--line)); outline: 2px solid color-mix(in srgb, var(--danger) 13%, transparent); } @@ -10034,26 +9886,13 @@ select.select-native-proxy[aria-invalid="true"]+.custom-select .custom-select-tr } } -@media (max-width: 900px), -(orientation: landscape) and (max-height: 700px) { - .flow-fit-button-icon { - display: inline-grid; - place-items: center; - } - - .flow-fit-button-label { - display: none; - } +@media (max-width: 900px), (orientation: landscape) and (max-height: 700px) { + .flow-fit-button-icon { display: inline-grid; place-items: center; } + .flow-fit-button-label { display: none; } } - @media (orientation: landscape) and (max-height: 700px) and (min-width: 500px) { - .flow-fit-button-icon { - display: none; - } - - .flow-fit-button-label { - display: inline; - } + .flow-fit-button-icon { display: none; } + .flow-fit-button-label { display: inline; } } .ha-supervisor-status { @@ -10066,27 +9905,11 @@ select.select-native-proxy[aria-invalid="true"]+.custom-select .custom-select-tr background: var(--surface-2); } -.ha-supervisor-status strong { - font-size: .92rem; -} - -.ha-supervisor-status span { - color: var(--muted); - font-size: .82rem; - line-height: 1.45; -} - -.ha-supervisor-status.success { - border-left-color: var(--teal); -} - -.ha-supervisor-status.warning { - border-left-color: var(--accent); -} - -.ha-supervisor-status.error { - border-left-color: var(--danger); -} +.ha-supervisor-status strong { font-size: .92rem; } +.ha-supervisor-status span { color: var(--muted); font-size: .82rem; line-height: 1.45; } +.ha-supervisor-status.success { border-left-color: var(--teal); } +.ha-supervisor-status.warning { border-left-color: var(--accent); } +.ha-supervisor-status.error { border-left-color: var(--danger); } .ha-entity-picker { display: grid; @@ -10094,13 +9917,8 @@ select.select-native-proxy[aria-invalid="true"]+.custom-select .custom-select-tr min-width: 0; } -.ha-entity-picker>label { - margin: 0; -} - -.ha-entity-picker>.field-note { - margin: 0; -} +.ha-entity-picker > label { margin: 0; } +.ha-entity-picker > .field-note { margin: 0; } .ha-entity-results { display: grid; @@ -10133,7 +9951,7 @@ select.select-native-proxy[aria-invalid="true"]+.custom-select .custom-select-tr background: var(--surface-2); } -.ha-entity-option>span { +.ha-entity-option > span { display: grid; gap: 2px; min-width: 0; @@ -10153,26 +9971,15 @@ select.select-native-proxy[aria-invalid="true"]+.custom-select .custom-select-tr font-size: .76rem; } -.ha-entity-option small { - flex: 0 0 auto; -} +.ha-entity-option small { flex: 0 0 auto; } +.ha-entity-empty { padding: 9px 10px; } -.ha-entity-empty { - padding: 9px 10px; -} - -.ha-entity-search-field input[type="search"] { - width: 100%; -} - -.ha-entity-selected-field { - margin-top: 2px; -} - -.ha-entity-selected-field input { - font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace); -} +/* Shared Flow HA entity picker: keep search and selected entity visually distinct. */ +.ha-entity-search-field input[type="search"] { width: 100%; } +.ha-entity-selected-field { margin-top: 2px; } +.ha-entity-selected-field input { font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace); } +/* Standalone public custom chart */ .public-custom-chart-page { min-height: 100vh; overflow: hidden; @@ -10220,12 +10027,6 @@ select.select-native-proxy[aria-invalid="true"]+.custom-select .custom-select-tr } @media (max-width: 700px) { - .public-custom-chart-shell { - padding: 8px; - } - - .public-custom-chart-card { - min-height: calc(100vh - 16px); - padding: 14px; - } -} \ No newline at end of file + .public-custom-chart-shell { padding: 8px; } + .public-custom-chart-card { min-height: calc(100vh - 16px); padding: 14px; } +}