v0.14.13
This commit is contained in:
Generated
+1
-1
@@ -700,7 +700,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gree-controller"
|
||||
version = "0.14.12"
|
||||
version = "0.14.13"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"aes-gcm",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "gree-controller"
|
||||
version = "0.14.12"
|
||||
version = "0.14.13"
|
||||
edition = "2021"
|
||||
authors = ["GREE Controller contributors"]
|
||||
description = "Standalone local GREE HVAC controller with Web UI, SQLite and Home Assistant sensor support"
|
||||
|
||||
+27
-27
@@ -1,14 +1,14 @@
|
||||
d67af429e4da9ce08e9d2f2a8472849ffbd70d135b1c5da535a076026794d04c ./.env.example
|
||||
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
|
||||
9f50e09694de525ba281e9a2632794331cb5002265a47da3644e2efdd335b591 ./Cargo.lock
|
||||
b10ec633e8c0845e5b31cd193366f700b067081887594910e02617db49cb1d6e ./Cargo.toml
|
||||
47f3cc18f7d2bce04f9a7ff91d6f7b9b3ada4e26ad7ed5b98231677eb899d30b ./Cargo.lock
|
||||
a8a415ba2c06cbd13177ba3330768db37a4c93e2dc2235c08bbdec4a1267efc8 ./Cargo.toml
|
||||
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
||||
70ca0a29454ccb569a7b15f9792d98d9b8d860d223052cf28d9ef4b3475a494e ./README.md
|
||||
53e0a751210bfd29d1cb8570dad2ed914ff11877fe0270adda064d8b3434f5e8 ./SOURCE_MANIFEST.sha256
|
||||
3a2c5067a74ef5c1b9deee3217ce4724d4308818fa60f9c9a73ec1ea12574124 ./README.md
|
||||
802c26ed11653a236c1d2fde8a4ede333a923442b6358563fcf426cc93e2e9de ./SOURCE_MANIFEST.sha256
|
||||
5302570299af2c2650bada12e123f598c5db8009949c75761385f73ed57343c5 ./build.rs
|
||||
25d1afb075e70422aea5ad4c8921fdf19389f6322b88fcd8f6e6a679fe4c4946 ./docs/API.md
|
||||
10df37af947b8b94f90cab1e50bae0ab15abd893fef4bbc8c5fee76b7b978abb ./docs/API.md
|
||||
f21d732eb1147f8f3167d2172cdf0b57e61edabdfecbaf97811d46c5bff7e47a ./docs/FLOW.md
|
||||
a86b1622971abb0a713cc1311b4f74f8711a754bedddff81fe5111f477688ad0 ./docs/openapi.json
|
||||
af1609a1aaf518b1343b186cf9348ff7c45a920127407958702ef571523e2a9a ./docs/openapi.json
|
||||
c65aa543d2e99f8f7d52736307b07ebd182597d80124661d53a16bb23f6f1118 ./ha-addon/.env.example
|
||||
a18d2460340264d34c267fa704ee51a42e59bcc1783ee4c0dbb2a8ef42fa0f16 ./ha-addon/Dockerfile
|
||||
0417c88a990c72aecf071fba64d5d9da65dcda4d396cd7565477f7a4532c8c5e ./ha-addon/README.md
|
||||
@@ -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
|
||||
9e90c948024ccafced975a147da079d1f65433e1b04d5ee0a0427f243e14ead8 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json
|
||||
41f991682cc01a1e4e1212e8f865f3b92a4a059aa22d42c7e202a42f69e8a2f8 ./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
|
||||
ac8d7b7d0171dd9527654ffd29e659046f862c65bb52a4d0d2f5ff78b47500cd ./ha-addon/repository/gree-controller/CHANGELOG.md
|
||||
93f82db18021aad642d7e37c30983460a7f654b72f6c5984a82b7d3fd58e0a7d ./ha-addon/repository/gree-controller/DOCS.md
|
||||
871886c02492c51883ee6aabe1ef14ac917cdb131e284e2bfd159662f28ea271 ./ha-addon/repository/gree-controller/CHANGELOG.md
|
||||
cdf3c5d60a86651353e06aad76b8fb5f8c6e0a28473b78d56334bb27fe5e1593 ./ha-addon/repository/gree-controller/DOCS.md
|
||||
c3db302f38d26dc9f8caf37a1a8a07a1334cc07ae301d452f2940a5ec5915719 ./ha-addon/repository/gree-controller/README.md
|
||||
37515b63be1acd3d8ce68011e11cbed0e89f4d03f879640edb75d4c41f48fe3c ./ha-addon/repository/gree-controller/config.yaml
|
||||
2cd7a8c8dfb998041f8bd48c33d3fd0a9db1ab63b23dcc2db9a168f80235f012 ./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 @@ e8a77dbdb98c6ebcfbe6a08b0b5546178d965f25496c19f7aebaff287630838e ./ha-addon/run
|
||||
081e27b6e43070d44f865335c30a42a5a29668eaa6a5ea5ab6967ac4447b8e06 ./ha-addon/sync-repository.sh
|
||||
f9e31de3d109ead32bf531c074d46a087df398d62488caee0b46b5e0a1c027f7 ./ha_addon.md
|
||||
253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md
|
||||
e808dc4a0ea74744fc55cdcae160a536b5f0f466540c00c477bdc9388d7ef847 ./lang/en.json
|
||||
f13b3706e56f785e539359009febef3441546f62bb94dde8e6c5dfb717d0f741 ./lang/pl.json
|
||||
3c362ffba7cc2edf683847cc320b8038dcfde5d598cf1c87db14eb56d419a4eb ./lang/en.json
|
||||
dbe4e6a67d44f9ed24279b436abe4bedd9212dac9afdcb05c3d411edcd610c46 ./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
|
||||
ca47f462f0df056191a8d71eff74179762584a864f3d43d68140714598a88885 ./scripts/api_dev_test.py
|
||||
4e1067274f3e7cbbde8d341e585292780bd8cae5f00c1942552ddce51994376f ./scripts/api_dev_test.py
|
||||
5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh
|
||||
6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh
|
||||
1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh
|
||||
@@ -98,11 +98,11 @@ e00d211e3885e30d7fed1e43b44e6fdad40a67019060156c0641816a93e3365f ./scripts/netw
|
||||
81345b6a0b51736bdbc98fd23199b62e4c721b4e7437e02dab7ea79b97dff29a ./scripts/service.sh
|
||||
bd703ef841f9763f13367aaa5764381b9c59ee87210e806c24617f3cc0739a9b ./scripts/smoke.sh
|
||||
b50782b3742dfbf8a319c60571c968e93fdf8547db747c759edcffae68cb98bf ./scripts/update.sh
|
||||
546e2ade8f4b9f6be464cb6d61c7eee9809450c4c4950c480ce056a5d683cc0e ./src/api.rs
|
||||
109eb319c7aeb814f34c6388757f509a945512e4682e5d4976ba0de21a8f401b ./src/api.rs
|
||||
3dfd1e053cbfa0d684dd88cf99b244a96a36e2ff23d2a0715b8327ab62258254 ./src/api/assets.rs
|
||||
05c255919f7b3d81188fc5f1410be6a0037471dfc44e93bffb009f29a1e6a3fc ./src/api/auth.rs
|
||||
9e4fc675306e111ed3db7af9822e2925d88109c31b9e309851665a975c0bd83c ./src/api/automations.rs
|
||||
c031fa7e9c6fc29ed9cdbd8150621111c1fd26e652d0c8e072811dfaeaaa2a19 ./src/api/configuration.rs
|
||||
269ba6c91539962ef2730badf9a2f466d9d5ca52f7ca522322bac12c63bcce7c ./src/api/configuration.rs
|
||||
19bc141286a336ad67e99079496eb0ae5126f986ab28be62b5f97758e5f6969c ./src/api/debug_tokens.rs
|
||||
32ade46ec45d6faf7852242b9f5ba1be6eacbacbeb33d2deabfab59bf1610ce0 ./src/api/device_groups.rs
|
||||
fcbb3da0538aab451f71810f829cc248d6bb67e586dc418d71a572980583823d ./src/api/devices.rs
|
||||
@@ -112,16 +112,16 @@ d59b080b392b574ec94276c7b485b455d0d1abacc7de2ef29dcbfcf7d86dbdb0 ./src/api/flow
|
||||
b61b0b4f196c5e72e47cf06ca0c14df3cf8eedbdef0242a9d2af33623663489f ./src/api/groups.rs
|
||||
376ac63794378c427d798e853a060612e76a95c9aa0357c2f216ba9e4f769f2f ./src/api/history.rs
|
||||
e8edfbb725d6de650442444b0a929ad2be0bd2c2d7d227d2b3fdd99d05c94f94 ./src/api/house.rs
|
||||
371b719cb8883c94ebe7de3b222daad503957bbff6589b19ba27595963972f1c ./src/api/integrations.rs
|
||||
c71540b2b68e805e3a79ac787db9127b87585968a205fc307e93bf68d5b028e0 ./src/api/integrations.rs
|
||||
6bfbe5135d4f93bd8d19a707877b53ed2029fd8490b4b1954a7520888fb1d664 ./src/api/middleware.rs
|
||||
45e34a447ff384d99a3c42c489ca9fa75849c74d4d246caa91c63d927de17396 ./src/api/openapi.rs
|
||||
736a5396644b65f44cbb25dbc873b8a41673a479795c4988519cf84ef175aca8 ./src/api/public_settings.rs
|
||||
b777e7fbeccef699e11ffd16bb18633843c99afecacce2221280e54b09f6d2da ./src/api/public_settings.rs
|
||||
5b29f2e5aa2c422c5829ef89c7b59cf753883aa7f60661c95ab0f95cf676fa3f ./src/api/schedules.rs
|
||||
10164f2357a23d63f22adf66e94f52dbb3df5edcb60d7bd5195e6d396e3eb1d8 ./src/api/settings.rs
|
||||
6acf6417b077a0204a815e255c667928476014ce5f94c1559c9d4268c00342d5 ./src/api/settings.rs
|
||||
1c1fe39aec55d10608ca3db83827855b07249cd1a81ae054eb1ee6464514092b ./src/api/system.rs
|
||||
9118a5a3a381b010e22d646498ff56d2e05f51656789682cc4371b09c384aa98 ./src/api/websocket.rs
|
||||
ddd1bfc8625b66f6e5f3d24b3122ac86c25f89b0af527e3dfce9102551a8f142 ./src/api/zones.rs
|
||||
ca2b42d341a1ca0c7772a1256aed12c06667778d6588687f3edc9e88ed1db4fc ./src/config.rs
|
||||
e495bbb37e81b539bfacfce065fe791f630ccd524e2a33b9dcfd8fe6856941cf ./src/config.rs
|
||||
f8394e0daca284070ac9ae0844d6b04fed7c30386f2d1ec409ecf07880bb0e6a ./src/db.rs
|
||||
fdc9d019097becc5c36a0560caa1619aa35d802c0d208902b3bfecbe1643e3e8 ./src/db/climate.rs
|
||||
e5d2027d40a99e61c2c572499af617a92de9e554f8916f3ea6f6731d1d6b09b4 ./src/db/configuration.rs
|
||||
@@ -156,7 +156,7 @@ b0189f28d0f337dfd3ce0934da7a565e4a5535afc1c242214c26ed584b3b7840 ./src/engine/t
|
||||
c89ff36c6a67bf4fbf68c43e18aa0a556de2dc69263fd79db376363afc741a4f ./src/engine/zone_actions.rs
|
||||
3a7fada1f6dd9ebab4026b353f41b21d8ca19342b02538c711dbb2dfb599cbe7 ./src/engine/zone_control.rs
|
||||
bfac95cec87f0df6ce562153cd3cfe6731359d703e6c592d2c2f7ba01ea62d99 ./src/error.rs
|
||||
26c7617b99a5a46e9afb15d317af4c804d16806dd6fcd3c8a9301c193884b1f2 ./src/home_assistant.rs
|
||||
94809ffbb35fded50d1252c310d3b5e088c41b75a6a061c07a93b889dfaee220 ./src/home_assistant.rs
|
||||
e78ca9aff23362971eb6450534ea21bdc15150456c378ef9b226cb93a8dbc612 ./src/influxdb.rs
|
||||
da4068295f37c23222bb13bebac022d88ba7729604a49ad25438c55e45458a49 ./src/influxdb/codec.rs
|
||||
b96624fa34b218a6d5f028ba09579633f6f8a04c109f0e94801e8d89ceeeb4b5 ./src/influxdb/query.rs
|
||||
@@ -169,9 +169,9 @@ fc5f2802a073a175a8985e56bcb87eb1ff481226d5b4eeabad73dbba1839fc45 ./src/models/c
|
||||
5e212a072bf74ef32a186b172404844b02a92363d216167f814056e1f2a9a7a8 ./src/models/device.rs
|
||||
aeab5b5acf35d38a54fd1d82e964fe56635ade69c0149ad423df317095d01555 ./src/models/flow.rs
|
||||
ba5d359e4d6c12d9f62c1b28bdfb40d312737930df5421814f5310f7183e96c1 ./src/models/history.rs
|
||||
110b73c179ea1f85b7afa14f4cdcfd9b8232dd005a6417cf357b8efc73e702c0 ./src/models/integrations.rs
|
||||
1fee89f939018bf1f3686c15f25434f01ab96b3239164108db7f348a1f1f8d82 ./src/models/integrations.rs
|
||||
f1359b68f1d041cd441f80429e77893eeb6f6194613b58fd3e7a212fb511db3e ./src/models/runtime.rs
|
||||
527bea024c76d8025fa8f7c4dbf2c6067cbdff8103ebe9763033eeea4accf788 ./src/models/settings_api.rs
|
||||
e3dfdd6dba63f6febd0b64da413b2fe7ca258a54abc2fdd6f2982c27425abfd6 ./src/models/settings_api.rs
|
||||
c4de6101de85d9d7e6814b2504b87d9216561e3d9e274054f081f505fb152140 ./src/models/temporary_thermostat.rs
|
||||
bc2818ce8d0dae1edfb1b98e5d1c00b193f03eeaa28e343d3532f6023288738c ./src/models/zone.rs
|
||||
62e696df8b86835df7ad127f03c257a78580769dc82bc66daa5041f71e8440a5 ./src/notifications.rs
|
||||
@@ -201,11 +201,11 @@ f13f1be3d5789539eba3fc1b59c14835f8c681eca7634e118fa3763a53aeee2b ./src/queries/
|
||||
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
|
||||
544a31cb2e5374227026afd6abd0d910381f3fca1638154ebd74e3c3d96a35d1 ./web/404.html
|
||||
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
|
||||
22b3a1497fc17e72b60ecf0e79eb416108948678cc556018abd62bd02e24af64 ./web/index.html
|
||||
019af8f410a20704e7445d0013fc37728d56b11b2772d679fe12a6c896972d87 ./web/index.html
|
||||
9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js/README.md
|
||||
27eff2821c40395369be15e197fa830535d2327fcb27c93a0e8e65a1c62de3ad ./web/js/bootstrap.js
|
||||
5970f3d05006ab975c7c88baaed4cc73eb014fc40dbfb3f07a191db3534211ea ./web/js/charts.js
|
||||
e1b6addca9512cf397111e6b554be0dcabe84196a539341eda7d6bdda7ff37a8 ./web/js/core.js
|
||||
1f1b7d1fdc2755a0888a14a314668a80ed7095763eba9c821e5e0968ec54fd73 ./web/js/core.js
|
||||
c4ef56259052ec75d362fcffbb42cdd5062485a177108c175d89614527d421e6 ./web/js/dashboard.js
|
||||
c5476aaa91123bc9d2b0ab1cb2870cba04fd67ae0067f86b3edb415b76cd4864 ./web/js/entities.js
|
||||
a59fd5b822029d0c1e74ba8078f55f13122347e036a96dec383293ae386f6667 ./web/js/events.js
|
||||
@@ -217,10 +217,10 @@ b08b0fe301642d1daa084b322a02e351d284d2465231505d45079a52ccb4028f ./web/js/flows
|
||||
799613c6d465cb2f92ecb57cb082d0c33729a03d88ab4ca7fa12a3cbd7e38e75 ./web/js/realtime.js
|
||||
9546ca5c5566994aff3c33d92295be46b65d51d874681764abe77470dc9c9c73 ./web/js/router.js
|
||||
c2710fd9c1f2b31878c1e25ceb02d74ae30456c1bbb400e69fcf7619ecc72bc2 ./web/js/select-ui.js
|
||||
7d3be06768e30884839aec682f55c5cb8085e464aa2fc389ac6ac9ed7a7639d3 ./web/js/settings-ui.js
|
||||
f00c3f603eb314f755a0c46424681b579e0b890453e2550007c719273815147c ./web/js/settings.js
|
||||
9b6554703f0a152c58a09d9dac0238de5e0c13805454d25a26544da24f9a1b69 ./web/js/settings-ui.js
|
||||
9f50794a92059357301b4c920b066bdb20ce1f64fbccea42effcd196d0a00cc4 ./web/js/settings.js
|
||||
3815fea7ae1694568c2f249bc8460e0563ced6bde89214f74f21b41fe8cad9a6 ./web/lang-init.js
|
||||
6b653e4fe2d4db4ffae6ff8f39b8ce57a10a990dc0000ee4aae51d949afadd52 ./web/manifest.webmanifest
|
||||
efea259fc5e6b078c4a0670cd6272ae86d7b7105f9be6ce449c4609ab64c1d7a ./web/styles.css
|
||||
84cb1c81280435b4ed42516f9bae5171b4e12d11a997dcb9fd087ab83c4c508e ./web/styles.css
|
||||
fefdf12cbbb0a5d8a2cb89275cae28455aa42aaf179542305cc8f5e0f4825898 ./web/sw.js
|
||||
d505d793ce7cc9485b45b78bba1c0d51887adc7451ab59a42702946e5b991382 ./web/theme-init.js
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
Self-hosted controller for GREE-compatible air conditioners with a local Web UI, thermostat zones, schedules, Home Assistant integration, history, notifications and a documented HTTP/WebSocket API.
|
||||
|
||||
**Version: 0.14.12**
|
||||
**Version: 0.14.13**
|
||||
|
||||
Version 0.14.12 adds Home Assistant Supervisor authentication for the packaged HA add-on and fixes mouse hover/click selection in custom dropdowns opened inside modal dialogs. The add-on uses the Supervisor Core API proxy and runtime `SUPERVISOR_TOKEN` automatically, while standalone installations keep the existing manual Home Assistant URL and Long-Lived Access Token configuration.
|
||||
Version 0.14.13 improves Home Assistant add-on authorization UX: Supervisor authentication is shown as automatic and manual URL/token fields stay hidden unless the HA test fails. The HA test now reports a live sample entity reading, and Shared Flow input Home Assistant sources include a searchable live entity picker. Standalone Home Assistant configuration remains unchanged.
|
||||
|
||||
> [Full API reference](docs/API.md) — authentication, every endpoint, request bodies, response models, WebSocket events and examples.
|
||||
|
||||
|
||||
+26
-26
@@ -1,13 +1,13 @@
|
||||
d67af429e4da9ce08e9d2f2a8472849ffbd70d135b1c5da535a076026794d04c ./.env.example
|
||||
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
|
||||
9f50e09694de525ba281e9a2632794331cb5002265a47da3644e2efdd335b591 ./Cargo.lock
|
||||
b10ec633e8c0845e5b31cd193366f700b067081887594910e02617db49cb1d6e ./Cargo.toml
|
||||
47f3cc18f7d2bce04f9a7ff91d6f7b9b3ada4e26ad7ed5b98231677eb899d30b ./Cargo.lock
|
||||
a8a415ba2c06cbd13177ba3330768db37a4c93e2dc2235c08bbdec4a1267efc8 ./Cargo.toml
|
||||
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
||||
70ca0a29454ccb569a7b15f9792d98d9b8d860d223052cf28d9ef4b3475a494e ./README.md
|
||||
3a2c5067a74ef5c1b9deee3217ce4724d4308818fa60f9c9a73ec1ea12574124 ./README.md
|
||||
5302570299af2c2650bada12e123f598c5db8009949c75761385f73ed57343c5 ./build.rs
|
||||
25d1afb075e70422aea5ad4c8921fdf19389f6322b88fcd8f6e6a679fe4c4946 ./docs/API.md
|
||||
10df37af947b8b94f90cab1e50bae0ab15abd893fef4bbc8c5fee76b7b978abb ./docs/API.md
|
||||
f21d732eb1147f8f3167d2172cdf0b57e61edabdfecbaf97811d46c5bff7e47a ./docs/FLOW.md
|
||||
a86b1622971abb0a713cc1311b4f74f8711a754bedddff81fe5111f477688ad0 ./docs/openapi.json
|
||||
af1609a1aaf518b1343b186cf9348ff7c45a920127407958702ef571523e2a9a ./docs/openapi.json
|
||||
c65aa543d2e99f8f7d52736307b07ebd182597d80124661d53a16bb23f6f1118 ./ha-addon/.env.example
|
||||
a18d2460340264d34c267fa704ee51a42e59bcc1783ee4c0dbb2a8ef42fa0f16 ./ha-addon/Dockerfile
|
||||
0417c88a990c72aecf071fba64d5d9da65dcda4d396cd7565477f7a4532c8c5e ./ha-addon/README.md
|
||||
@@ -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
|
||||
9e90c948024ccafced975a147da079d1f65433e1b04d5ee0a0427f243e14ead8 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json
|
||||
41f991682cc01a1e4e1212e8f865f3b92a4a059aa22d42c7e202a42f69e8a2f8 ./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
|
||||
ac8d7b7d0171dd9527654ffd29e659046f862c65bb52a4d0d2f5ff78b47500cd ./ha-addon/repository/gree-controller/CHANGELOG.md
|
||||
93f82db18021aad642d7e37c30983460a7f654b72f6c5984a82b7d3fd58e0a7d ./ha-addon/repository/gree-controller/DOCS.md
|
||||
871886c02492c51883ee6aabe1ef14ac917cdb131e284e2bfd159662f28ea271 ./ha-addon/repository/gree-controller/CHANGELOG.md
|
||||
cdf3c5d60a86651353e06aad76b8fb5f8c6e0a28473b78d56334bb27fe5e1593 ./ha-addon/repository/gree-controller/DOCS.md
|
||||
c3db302f38d26dc9f8caf37a1a8a07a1334cc07ae301d452f2940a5ec5915719 ./ha-addon/repository/gree-controller/README.md
|
||||
37515b63be1acd3d8ce68011e11cbed0e89f4d03f879640edb75d4c41f48fe3c ./ha-addon/repository/gree-controller/config.yaml
|
||||
2cd7a8c8dfb998041f8bd48c33d3fd0a9db1ab63b23dcc2db9a168f80235f012 ./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 @@ e8a77dbdb98c6ebcfbe6a08b0b5546178d965f25496c19f7aebaff287630838e ./ha-addon/run
|
||||
081e27b6e43070d44f865335c30a42a5a29668eaa6a5ea5ab6967ac4447b8e06 ./ha-addon/sync-repository.sh
|
||||
f9e31de3d109ead32bf531c074d46a087df398d62488caee0b46b5e0a1c027f7 ./ha_addon.md
|
||||
253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md
|
||||
e808dc4a0ea74744fc55cdcae160a536b5f0f466540c00c477bdc9388d7ef847 ./lang/en.json
|
||||
f13b3706e56f785e539359009febef3441546f62bb94dde8e6c5dfb717d0f741 ./lang/pl.json
|
||||
3c362ffba7cc2edf683847cc320b8038dcfde5d598cf1c87db14eb56d419a4eb ./lang/en.json
|
||||
dbe4e6a67d44f9ed24279b436abe4bedd9212dac9afdcb05c3d411edcd610c46 ./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
|
||||
ca47f462f0df056191a8d71eff74179762584a864f3d43d68140714598a88885 ./scripts/api_dev_test.py
|
||||
4e1067274f3e7cbbde8d341e585292780bd8cae5f00c1942552ddce51994376f ./scripts/api_dev_test.py
|
||||
5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh
|
||||
6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh
|
||||
1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh
|
||||
@@ -97,11 +97,11 @@ e00d211e3885e30d7fed1e43b44e6fdad40a67019060156c0641816a93e3365f ./scripts/netw
|
||||
81345b6a0b51736bdbc98fd23199b62e4c721b4e7437e02dab7ea79b97dff29a ./scripts/service.sh
|
||||
bd703ef841f9763f13367aaa5764381b9c59ee87210e806c24617f3cc0739a9b ./scripts/smoke.sh
|
||||
b50782b3742dfbf8a319c60571c968e93fdf8547db747c759edcffae68cb98bf ./scripts/update.sh
|
||||
546e2ade8f4b9f6be464cb6d61c7eee9809450c4c4950c480ce056a5d683cc0e ./src/api.rs
|
||||
109eb319c7aeb814f34c6388757f509a945512e4682e5d4976ba0de21a8f401b ./src/api.rs
|
||||
3dfd1e053cbfa0d684dd88cf99b244a96a36e2ff23d2a0715b8327ab62258254 ./src/api/assets.rs
|
||||
05c255919f7b3d81188fc5f1410be6a0037471dfc44e93bffb009f29a1e6a3fc ./src/api/auth.rs
|
||||
9e4fc675306e111ed3db7af9822e2925d88109c31b9e309851665a975c0bd83c ./src/api/automations.rs
|
||||
c031fa7e9c6fc29ed9cdbd8150621111c1fd26e652d0c8e072811dfaeaaa2a19 ./src/api/configuration.rs
|
||||
269ba6c91539962ef2730badf9a2f466d9d5ca52f7ca522322bac12c63bcce7c ./src/api/configuration.rs
|
||||
19bc141286a336ad67e99079496eb0ae5126f986ab28be62b5f97758e5f6969c ./src/api/debug_tokens.rs
|
||||
32ade46ec45d6faf7852242b9f5ba1be6eacbacbeb33d2deabfab59bf1610ce0 ./src/api/device_groups.rs
|
||||
fcbb3da0538aab451f71810f829cc248d6bb67e586dc418d71a572980583823d ./src/api/devices.rs
|
||||
@@ -111,16 +111,16 @@ d59b080b392b574ec94276c7b485b455d0d1abacc7de2ef29dcbfcf7d86dbdb0 ./src/api/flow
|
||||
b61b0b4f196c5e72e47cf06ca0c14df3cf8eedbdef0242a9d2af33623663489f ./src/api/groups.rs
|
||||
376ac63794378c427d798e853a060612e76a95c9aa0357c2f216ba9e4f769f2f ./src/api/history.rs
|
||||
e8edfbb725d6de650442444b0a929ad2be0bd2c2d7d227d2b3fdd99d05c94f94 ./src/api/house.rs
|
||||
371b719cb8883c94ebe7de3b222daad503957bbff6589b19ba27595963972f1c ./src/api/integrations.rs
|
||||
c71540b2b68e805e3a79ac787db9127b87585968a205fc307e93bf68d5b028e0 ./src/api/integrations.rs
|
||||
6bfbe5135d4f93bd8d19a707877b53ed2029fd8490b4b1954a7520888fb1d664 ./src/api/middleware.rs
|
||||
45e34a447ff384d99a3c42c489ca9fa75849c74d4d246caa91c63d927de17396 ./src/api/openapi.rs
|
||||
736a5396644b65f44cbb25dbc873b8a41673a479795c4988519cf84ef175aca8 ./src/api/public_settings.rs
|
||||
b777e7fbeccef699e11ffd16bb18633843c99afecacce2221280e54b09f6d2da ./src/api/public_settings.rs
|
||||
5b29f2e5aa2c422c5829ef89c7b59cf753883aa7f60661c95ab0f95cf676fa3f ./src/api/schedules.rs
|
||||
10164f2357a23d63f22adf66e94f52dbb3df5edcb60d7bd5195e6d396e3eb1d8 ./src/api/settings.rs
|
||||
6acf6417b077a0204a815e255c667928476014ce5f94c1559c9d4268c00342d5 ./src/api/settings.rs
|
||||
1c1fe39aec55d10608ca3db83827855b07249cd1a81ae054eb1ee6464514092b ./src/api/system.rs
|
||||
9118a5a3a381b010e22d646498ff56d2e05f51656789682cc4371b09c384aa98 ./src/api/websocket.rs
|
||||
ddd1bfc8625b66f6e5f3d24b3122ac86c25f89b0af527e3dfce9102551a8f142 ./src/api/zones.rs
|
||||
ca2b42d341a1ca0c7772a1256aed12c06667778d6588687f3edc9e88ed1db4fc ./src/config.rs
|
||||
e495bbb37e81b539bfacfce065fe791f630ccd524e2a33b9dcfd8fe6856941cf ./src/config.rs
|
||||
f8394e0daca284070ac9ae0844d6b04fed7c30386f2d1ec409ecf07880bb0e6a ./src/db.rs
|
||||
fdc9d019097becc5c36a0560caa1619aa35d802c0d208902b3bfecbe1643e3e8 ./src/db/climate.rs
|
||||
e5d2027d40a99e61c2c572499af617a92de9e554f8916f3ea6f6731d1d6b09b4 ./src/db/configuration.rs
|
||||
@@ -155,7 +155,7 @@ b0189f28d0f337dfd3ce0934da7a565e4a5535afc1c242214c26ed584b3b7840 ./src/engine/t
|
||||
c89ff36c6a67bf4fbf68c43e18aa0a556de2dc69263fd79db376363afc741a4f ./src/engine/zone_actions.rs
|
||||
3a7fada1f6dd9ebab4026b353f41b21d8ca19342b02538c711dbb2dfb599cbe7 ./src/engine/zone_control.rs
|
||||
bfac95cec87f0df6ce562153cd3cfe6731359d703e6c592d2c2f7ba01ea62d99 ./src/error.rs
|
||||
26c7617b99a5a46e9afb15d317af4c804d16806dd6fcd3c8a9301c193884b1f2 ./src/home_assistant.rs
|
||||
94809ffbb35fded50d1252c310d3b5e088c41b75a6a061c07a93b889dfaee220 ./src/home_assistant.rs
|
||||
e78ca9aff23362971eb6450534ea21bdc15150456c378ef9b226cb93a8dbc612 ./src/influxdb.rs
|
||||
da4068295f37c23222bb13bebac022d88ba7729604a49ad25438c55e45458a49 ./src/influxdb/codec.rs
|
||||
b96624fa34b218a6d5f028ba09579633f6f8a04c109f0e94801e8d89ceeeb4b5 ./src/influxdb/query.rs
|
||||
@@ -168,9 +168,9 @@ fc5f2802a073a175a8985e56bcb87eb1ff481226d5b4eeabad73dbba1839fc45 ./src/models/c
|
||||
5e212a072bf74ef32a186b172404844b02a92363d216167f814056e1f2a9a7a8 ./src/models/device.rs
|
||||
aeab5b5acf35d38a54fd1d82e964fe56635ade69c0149ad423df317095d01555 ./src/models/flow.rs
|
||||
ba5d359e4d6c12d9f62c1b28bdfb40d312737930df5421814f5310f7183e96c1 ./src/models/history.rs
|
||||
110b73c179ea1f85b7afa14f4cdcfd9b8232dd005a6417cf357b8efc73e702c0 ./src/models/integrations.rs
|
||||
1fee89f939018bf1f3686c15f25434f01ab96b3239164108db7f348a1f1f8d82 ./src/models/integrations.rs
|
||||
f1359b68f1d041cd441f80429e77893eeb6f6194613b58fd3e7a212fb511db3e ./src/models/runtime.rs
|
||||
527bea024c76d8025fa8f7c4dbf2c6067cbdff8103ebe9763033eeea4accf788 ./src/models/settings_api.rs
|
||||
e3dfdd6dba63f6febd0b64da413b2fe7ca258a54abc2fdd6f2982c27425abfd6 ./src/models/settings_api.rs
|
||||
c4de6101de85d9d7e6814b2504b87d9216561e3d9e274054f081f505fb152140 ./src/models/temporary_thermostat.rs
|
||||
bc2818ce8d0dae1edfb1b98e5d1c00b193f03eeaa28e343d3532f6023288738c ./src/models/zone.rs
|
||||
62e696df8b86835df7ad127f03c257a78580769dc82bc66daa5041f71e8440a5 ./src/notifications.rs
|
||||
@@ -200,11 +200,11 @@ f13f1be3d5789539eba3fc1b59c14835f8c681eca7634e118fa3763a53aeee2b ./src/queries/
|
||||
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
|
||||
544a31cb2e5374227026afd6abd0d910381f3fca1638154ebd74e3c3d96a35d1 ./web/404.html
|
||||
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
|
||||
22b3a1497fc17e72b60ecf0e79eb416108948678cc556018abd62bd02e24af64 ./web/index.html
|
||||
019af8f410a20704e7445d0013fc37728d56b11b2772d679fe12a6c896972d87 ./web/index.html
|
||||
9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js/README.md
|
||||
27eff2821c40395369be15e197fa830535d2327fcb27c93a0e8e65a1c62de3ad ./web/js/bootstrap.js
|
||||
5970f3d05006ab975c7c88baaed4cc73eb014fc40dbfb3f07a191db3534211ea ./web/js/charts.js
|
||||
e1b6addca9512cf397111e6b554be0dcabe84196a539341eda7d6bdda7ff37a8 ./web/js/core.js
|
||||
1f1b7d1fdc2755a0888a14a314668a80ed7095763eba9c821e5e0968ec54fd73 ./web/js/core.js
|
||||
c4ef56259052ec75d362fcffbb42cdd5062485a177108c175d89614527d421e6 ./web/js/dashboard.js
|
||||
c5476aaa91123bc9d2b0ab1cb2870cba04fd67ae0067f86b3edb415b76cd4864 ./web/js/entities.js
|
||||
a59fd5b822029d0c1e74ba8078f55f13122347e036a96dec383293ae386f6667 ./web/js/events.js
|
||||
@@ -216,10 +216,10 @@ b08b0fe301642d1daa084b322a02e351d284d2465231505d45079a52ccb4028f ./web/js/flows
|
||||
799613c6d465cb2f92ecb57cb082d0c33729a03d88ab4ca7fa12a3cbd7e38e75 ./web/js/realtime.js
|
||||
9546ca5c5566994aff3c33d92295be46b65d51d874681764abe77470dc9c9c73 ./web/js/router.js
|
||||
c2710fd9c1f2b31878c1e25ceb02d74ae30456c1bbb400e69fcf7619ecc72bc2 ./web/js/select-ui.js
|
||||
7d3be06768e30884839aec682f55c5cb8085e464aa2fc389ac6ac9ed7a7639d3 ./web/js/settings-ui.js
|
||||
f00c3f603eb314f755a0c46424681b579e0b890453e2550007c719273815147c ./web/js/settings.js
|
||||
9b6554703f0a152c58a09d9dac0238de5e0c13805454d25a26544da24f9a1b69 ./web/js/settings-ui.js
|
||||
9f50794a92059357301b4c920b066bdb20ce1f64fbccea42effcd196d0a00cc4 ./web/js/settings.js
|
||||
3815fea7ae1694568c2f249bc8460e0563ced6bde89214f74f21b41fe8cad9a6 ./web/lang-init.js
|
||||
6b653e4fe2d4db4ffae6ff8f39b8ce57a10a990dc0000ee4aae51d949afadd52 ./web/manifest.webmanifest
|
||||
efea259fc5e6b078c4a0670cd6272ae86d7b7105f9be6ce449c4609ab64c1d7a ./web/styles.css
|
||||
84cb1c81280435b4ed42516f9bae5171b4e12d11a997dcb9fd087ab83c4c508e ./web/styles.css
|
||||
fefdf12cbbb0a5d8a2cb89275cae28455aa42aaf179542305cc8f5e0f4825898 ./web/sw.js
|
||||
d505d793ce7cc9485b45b78bba1c0d51887adc7451ab59a42702946e5b991382 ./web/theme-init.js
|
||||
|
||||
+19
-7
@@ -1,6 +1,6 @@
|
||||
# GREE Controller API reference
|
||||
|
||||
HTTP and WebSocket API for GREE Controller **0.14.12**.
|
||||
HTTP and WebSocket API for GREE Controller **0.14.13**.
|
||||
|
||||
[← Main documentation](../README.md)
|
||||
|
||||
@@ -197,7 +197,8 @@ 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 server/API authentication without requiring an entity. |
|
||||
| POST | `/api/integrations/home-assistant/test` | Test the active HA authentication path and return one live sample entity reading when available. |
|
||||
| GET | `/api/integrations/home-assistant/entities` | Compact Home Assistant entity catalog used by searchable Shared Flow input suggestions. |
|
||||
| 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. |
|
||||
|
||||
@@ -233,7 +234,7 @@ Response:
|
||||
{
|
||||
"status": "ok",
|
||||
"name": "gree-controller",
|
||||
"version": "0.14.12",
|
||||
"version": "0.14.13",
|
||||
"uptime_seconds": 1234,
|
||||
"control_ready": true,
|
||||
"time": "2026-08-30T06:54:00Z"
|
||||
@@ -296,7 +297,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.12",
|
||||
"version": "0.14.13",
|
||||
"uptime_seconds": 1234,
|
||||
"auth_required": false,
|
||||
"control_ready": true,
|
||||
@@ -1198,7 +1199,7 @@ Times must use `HH:MM`; maximum fan speed is clamped to `1..5`.
|
||||
}
|
||||
```
|
||||
|
||||
`auth_mode` is runtime-only: `manual` for standalone installations and `supervisor` for the Home Assistant add-on. In Supervisor mode the returned URL is `http://supervisor/core/`, authentication uses runtime `SUPERVISOR_TOKEN`, and `PUT` does not replace the stored standalone URL/token fields. In manual mode, `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.
|
||||
`auth_mode` is `manual` for standalone installations and normally `supervisor` for the Home Assistant add-on. The add-on detects the Supervisor environment and runtime `SUPERVISOR_TOKEN`; while Supervisor auth is active, the effective URL is `http://supervisor/core/` and the Web UI hides manual URL/token/TLS fields. `supervisor_detected`, `supervisor_token_detected`, `manual_url`, `manual_token_configured` and `manual_auth_override` describe that state without exposing secrets. If the automatic HA test fails, the UI unlocks the persisted manual URL/token fallback; saving it sets `manual_auth_override=true`. Standalone URL/token behavior is unchanged. Omitted/`null` token preserves the saved manual 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. Shared Flow input HA sources can query `/api/integrations/home-assistant/entities` for live searchable entity suggestions. Shared inputs are value sources only; comparison operators and thresholds belong to Flow nodes.
|
||||
|
||||
### `/api/settings/debug`
|
||||
|
||||
@@ -1270,14 +1271,25 @@ Response:
|
||||
|
||||
### `POST /api/integrations/home-assistant/test`
|
||||
|
||||
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:
|
||||
No request body is required. The endpoint validates the active Home Assistant authentication path and reads the state registry. In the packaged add-on it uses Supervisor auth unless manual fallback is active. A successful response includes one readable entity sample when available, which the Web UI shows in the success toast:
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true
|
||||
"ok": true,
|
||||
"auth_mode": "supervisor",
|
||||
"sample": {
|
||||
"entity_id": "sensor.living_room_temperature",
|
||||
"name": "Living room temperature",
|
||||
"state": "22.4",
|
||||
"unit": "°C"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/integrations/home-assistant/entities`
|
||||
|
||||
Returns a compact entity catalog for authenticated Web UI suggestions. Each row contains `entity_id`, friendly `name`, current `state`, `unit` and `device_class`. When HA is not configured it returns `configured: false` with an empty list.
|
||||
|
||||
### `POST /api/integrations/home-assistant/entity`
|
||||
|
||||
Reads the current raw Home Assistant entity document used by the shared-Flow-input diagnostics UI.
|
||||
|
||||
+163
-7
@@ -2,9 +2,9 @@
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "GREE Controller API",
|
||||
"version": "0.14.12",
|
||||
"version": "0.14.13",
|
||||
"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 <APP_TOKEN>` or `x-api-token: <APP_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.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 <APP_TOKEN>` or `x-api-token: <APP_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.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"
|
||||
}
|
||||
@@ -3095,7 +3095,7 @@
|
||||
"Diagnostics"
|
||||
],
|
||||
"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.",
|
||||
"description": "Validates the active Home Assistant authentication path and state registry. In the add-on this uses Supervisor auth unless manual fallback is active. A successful response includes one readable entity sample when available.",
|
||||
"operationId": "testHomeAssistant",
|
||||
"security": [
|
||||
{
|
||||
@@ -3115,15 +3115,58 @@
|
||||
"properties": {
|
||||
"ok": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"auth_mode": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"manual",
|
||||
"supervisor"
|
||||
]
|
||||
},
|
||||
"sample": {
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"properties": {
|
||||
"entity_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"type": "string"
|
||||
},
|
||||
"unit": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"ok"
|
||||
"entity_id",
|
||||
"name",
|
||||
"state",
|
||||
"unit"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"ok",
|
||||
"auth_mode",
|
||||
"sample"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"example": {
|
||||
"ok": true
|
||||
"ok": true,
|
||||
"auth_mode": "supervisor",
|
||||
"sample": {
|
||||
"entity_id": "sensor.living_room_temperature",
|
||||
"name": "Living room temperature",
|
||||
"state": "22.4",
|
||||
"unit": "°C"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5465,6 +5508,89 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/integrations/home-assistant/entities": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Diagnostics"
|
||||
],
|
||||
"summary": "List Home Assistant entities for UI suggestions",
|
||||
"description": "Returns a compact, authenticated Home Assistant state-registry view used by searchable entity pickers in Shared Flow input configuration.",
|
||||
"operationId": "listHomeAssistantEntities",
|
||||
"security": [
|
||||
{
|
||||
"BearerToken": []
|
||||
},
|
||||
{
|
||||
"ApiTokenHeader": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Entity suggestion catalog",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"configured": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"entities": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"entity_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"type": "string"
|
||||
},
|
||||
"unit": {
|
||||
"type": "string"
|
||||
},
|
||||
"device_class": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"entity_id",
|
||||
"name",
|
||||
"state",
|
||||
"unit",
|
||||
"device_class"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"configured",
|
||||
"entities"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/components/responses/BadRequest"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/Unauthorized"
|
||||
},
|
||||
"424": {
|
||||
"$ref": "#/components/responses/FailedDependency"
|
||||
},
|
||||
"500": {
|
||||
"$ref": "#/components/responses/InternalError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
@@ -5520,7 +5646,7 @@
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"example": "0.14.12"
|
||||
"example": "0.14.13"
|
||||
},
|
||||
"uptime_seconds": {
|
||||
"type": "integer",
|
||||
@@ -8520,6 +8646,10 @@
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"manual_url": {
|
||||
"type": "string",
|
||||
"description": "Persisted manual Home Assistant URL used only when manual fallback is active in the add-on; equals url in standalone mode."
|
||||
},
|
||||
"auth_mode": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@@ -8556,6 +8686,22 @@
|
||||
},
|
||||
"token_configured": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"manual_token_configured": {
|
||||
"type": "boolean",
|
||||
"description": "Whether a persisted manual Home Assistant token is available for fallback."
|
||||
},
|
||||
"supervisor_detected": {
|
||||
"type": "boolean",
|
||||
"description": "True when the packaged Home Assistant add-on environment was detected."
|
||||
},
|
||||
"supervisor_token_detected": {
|
||||
"type": "boolean",
|
||||
"description": "True when the add-on environment exposes a non-empty SUPERVISOR_TOKEN."
|
||||
},
|
||||
"manual_auth_override": {
|
||||
"type": "boolean",
|
||||
"description": "True when the add-on is explicitly using the persisted manual URL/token fallback."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -8567,7 +8713,12 @@
|
||||
"sensor_aliases",
|
||||
"flow_inputs",
|
||||
"outdoor_assist_enabled",
|
||||
"token_configured"
|
||||
"token_configured",
|
||||
"manual_url",
|
||||
"manual_token_configured",
|
||||
"supervisor_detected",
|
||||
"supervisor_token_detected",
|
||||
"manual_auth_override"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
@@ -8609,6 +8760,11 @@
|
||||
"null"
|
||||
],
|
||||
"description": "Omit/null to preserve the stored token; empty string clears it."
|
||||
},
|
||||
"manual_auth_override": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "In the Home Assistant add-on only, switch from automatic Supervisor auth to the persisted manual URL/token fallback. Ignored for standalone behavior."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"domain": "gree_controller",
|
||||
"name": "GREE Controller",
|
||||
"version": "0.14.12",
|
||||
"version": "0.14.13",
|
||||
"config_flow": true,
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## 0.14.13
|
||||
|
||||
- Shows Home Assistant Supervisor authentication as automatic in the add-on and hides manual URL/token/TLS fields by default.
|
||||
- Unlocks a persisted manual Home Assistant URL/token fallback only after an automatic HA test fails; standalone installations keep their existing manual behavior.
|
||||
- Extends `Test HA` to verify the state registry and show a live sample entity/state in the success toast.
|
||||
- Adds a searchable live Home Assistant entity picker to `Reusable data -> Shared Flow input`, with source-type-aware fields and numeric filtering for numeric HA inputs.
|
||||
- Adds an authenticated compact Home Assistant entity-catalog endpoint for UI suggestions.
|
||||
|
||||
## 0.14.12
|
||||
|
||||
- Home Assistant add-on now uses the Supervisor-managed Home Assistant Core API connection automatically.
|
||||
|
||||
@@ -60,7 +60,7 @@ Broadcast zwykle nie przechodzi przez router. Sterowanie unicast może działać
|
||||
|
||||
### Home Assistant API
|
||||
|
||||
Dodatek korzysta automatycznie z wewnętrznego proxy Home Assistant Core (`http://supervisor/core/api/`) i tokenu `SUPERVISOR_TOKEN` przekazywanego przez Supervisor. Nie konfiguruj ręcznie URL ani Long-Lived Access Token dla połączenia kontrolera z Home Assistant. Token Supervisor jest używany tylko w pamięci procesu i nie jest zapisywany w SQLite.
|
||||
Dodatek korzysta automatycznie z wewnętrznego proxy Home Assistant Core (`http://supervisor/core/api/`) i tokenu `SUPERVISOR_TOKEN` przekazywanego przez Supervisor. Gdy token jest wykryty, UI pokazuje automatyczną autoryzację i ukrywa ręczne pola URL/token. Dopiero nieudany `Test HA` odblokowuje awaryjny ręczny fallback. Token Supervisor jest używany tylko w pamięci procesu i nie jest zapisywany w SQLite.
|
||||
|
||||
### Dostęp, dane i bezpieczeństwo
|
||||
|
||||
@@ -125,7 +125,7 @@ Broadcast normally does not cross routers. Unicast control may work through rout
|
||||
|
||||
### Home Assistant API
|
||||
|
||||
The add-on automatically uses the internal Home Assistant Core proxy (`http://supervisor/core/api/`) and the runtime `SUPERVISOR_TOKEN` provided by Supervisor. Do not configure a Home Assistant URL or Long-Lived Access Token for the controller-to-Home-Assistant connection. The Supervisor token is used only at runtime and is not stored in SQLite.
|
||||
The add-on automatically uses the internal Home Assistant Core proxy (`http://supervisor/core/api/`) and the runtime `SUPERVISOR_TOKEN` provided by Supervisor. When the token is detected, the UI shows automatic authorization and hides manual URL/token fields. A failed `Test HA` unlocks the optional manual fallback. The Supervisor token is used only at runtime and is not stored in SQLite.
|
||||
|
||||
### Access, data and security
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: "GREE Controller"
|
||||
version: "0.14.12"
|
||||
version: "0.14.13"
|
||||
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/"
|
||||
|
||||
@@ -184,6 +184,17 @@
|
||||
"settings.haTokenSaved": "Token saved — leave empty to keep it",
|
||||
"settings.haLongLivedToken": "Long-Lived Access Token",
|
||||
"settings.haSupervisorToken": "Token provided automatically by Home Assistant Supervisor",
|
||||
"settings.haSupervisorAutoTitle": "Automatic Supervisor authorization",
|
||||
"settings.haSupervisorAutoHint": "Supervisor token detected. Home Assistant authorization is configured automatically; no URL or Long-Lived Access Token is required.",
|
||||
"settings.haSupervisorVerifiedTitle": "Supervisor authorization verified",
|
||||
"settings.haSupervisorVerifiedHint": "The Home Assistant API test succeeded using the Supervisor token.",
|
||||
"settings.haSupervisorMissingTitle": "Supervisor token not available",
|
||||
"settings.haSupervisorMissingHint": "The add-on environment was detected, but no Supervisor token is available. Run Test HA to unlock manual fallback fields if needed.",
|
||||
"settings.haSupervisorTestFailedTitle": "Automatic HA authorization failed",
|
||||
"settings.haSupervisorTestFailedHint": "Manual Home Assistant URL and token fields are now available as a fallback.",
|
||||
"settings.haManualFallbackTitle": "Manual Home Assistant fallback active",
|
||||
"settings.haManualFallbackHint": "This add-on is using the manually saved Home Assistant URL and token instead of Supervisor authorization.",
|
||||
"settings.haUseSupervisor": "Use Supervisor automatically",
|
||||
"settings.testHa": "Test HA",
|
||||
"settings.systemState": "System status",
|
||||
"settings.version": "Version",
|
||||
@@ -250,6 +261,7 @@
|
||||
"label.automation": "automation",
|
||||
"toast.found": "Found: {count}",
|
||||
"toast.haConnected": "Home Assistant connection OK",
|
||||
"toast.haConnectedSample": "Home Assistant connected. Sample: {entity} = {value}",
|
||||
"day.1": "Mon",
|
||||
"day.2": "Tue",
|
||||
"day.3": "Wed",
|
||||
@@ -1038,6 +1050,11 @@
|
||||
"flow.sharedInputTestValueRead": "Value read successfully",
|
||||
"flow.sharedInputTestAvailable": "Entity available",
|
||||
"flow.sharedInputTestUnavailable": "Entity unavailable",
|
||||
"flow.haEntitySearchHint": "Search Home Assistant entities by name or entity_id.",
|
||||
"flow.haEntitySearchLoading": "Loading Home Assistant entities…",
|
||||
"flow.haEntitySearchConnect": "Connect and test Home Assistant to browse entity suggestions.",
|
||||
"flow.haEntitySearchCount": "{count} Home Assistant entities available",
|
||||
"flow.haEntitySearchEmpty": "No matching entities",
|
||||
"flow.sharedInputUsedBy": "Used in {count} Flows",
|
||||
"flow.sharedInputUnused": "Not used in any Flow",
|
||||
"flow.openReferencedFlow": "Open Flow “{name}”",
|
||||
|
||||
@@ -184,6 +184,17 @@
|
||||
"settings.haTokenSaved": "Token zapisany — pozostaw puste",
|
||||
"settings.haLongLivedToken": "Długotrwały token dostępu",
|
||||
"settings.haSupervisorToken": "Token dostarczany automatycznie przez Home Assistant Supervisor",
|
||||
"settings.haSupervisorAutoTitle": "Automatyczna autoryzacja przez Supervisor",
|
||||
"settings.haSupervisorAutoHint": "Wykryto token Supervisor. Autoryzacja Home Assistant jest skonfigurowana automatycznie — nie trzeba podawać URL ani Long-Lived Access Token.",
|
||||
"settings.haSupervisorVerifiedTitle": "Autoryzacja Supervisor zweryfikowana",
|
||||
"settings.haSupervisorVerifiedHint": "Test API Home Assistant zakończył się powodzeniem z użyciem tokenu Supervisor.",
|
||||
"settings.haSupervisorMissingTitle": "Brak tokenu Supervisor",
|
||||
"settings.haSupervisorMissingHint": "Wykryto środowisko dodatku, ale token Supervisor nie jest dostępny. Uruchom Test HA, aby w razie potrzeby odblokować ręczne pola awaryjne.",
|
||||
"settings.haSupervisorTestFailedTitle": "Automatyczna autoryzacja HA nie powiodła się",
|
||||
"settings.haSupervisorTestFailedHint": "Udostępniono awaryjne pola do ręcznego podania adresu Home Assistant i tokenu.",
|
||||
"settings.haManualFallbackTitle": "Aktywny ręczny fallback Home Assistant",
|
||||
"settings.haManualFallbackHint": "Dodatek używa ręcznie zapisanego adresu i tokenu Home Assistant zamiast autoryzacji Supervisor.",
|
||||
"settings.haUseSupervisor": "Użyj automatycznie Supervisor",
|
||||
"settings.testHa": "Testuj HA",
|
||||
"settings.systemState": "Stan systemu",
|
||||
"settings.version": "Wersja",
|
||||
@@ -250,6 +261,7 @@
|
||||
"label.automation": "automatyzację",
|
||||
"toast.found": "Znaleziono: {count}",
|
||||
"toast.haConnected": "Połączenie z Home Assistant działa",
|
||||
"toast.haConnectedSample": "Połączono z Home Assistant. Przykładowy odczyt: {entity} = {value}",
|
||||
"day.1": "Pn",
|
||||
"day.2": "Wt",
|
||||
"day.3": "Śr",
|
||||
@@ -1038,6 +1050,11 @@
|
||||
"flow.sharedInputTestValueRead": "Wartość odczytana poprawnie",
|
||||
"flow.sharedInputTestAvailable": "Encja dostępna",
|
||||
"flow.sharedInputTestUnavailable": "Encja niedostępna",
|
||||
"flow.haEntitySearchHint": "Wyszukaj encję Home Assistant po nazwie lub entity_id.",
|
||||
"flow.haEntitySearchLoading": "Wczytywanie encji Home Assistant…",
|
||||
"flow.haEntitySearchConnect": "Połącz i przetestuj Home Assistant, aby przeglądać propozycje encji.",
|
||||
"flow.haEntitySearchCount": "Dostępnych encji Home Assistant: {count}",
|
||||
"flow.haEntitySearchEmpty": "Brak pasujących encji",
|
||||
"flow.sharedInputUsedBy": "Używane w {count} Flow",
|
||||
"flow.sharedInputUnused": "Nieużywane w żadnym Flow",
|
||||
"flow.openReferencedFlow": "Otwórz Flow „{name}”",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Developer smoke/integration tests for GREE Controller API 0.14.12.
|
||||
"""Developer smoke/integration tests for GREE Controller API 0.14.13.
|
||||
|
||||
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.12"
|
||||
DEFAULT_EXPECTED_VERSION = "0.14.13"
|
||||
|
||||
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.12 contract", lambda: test_openapi(client, args.expected_version))
|
||||
runner.run("OpenAPI 0.14.13 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.12.",
|
||||
description="Developer smoke/integration tests for GREE Controller API 0.14.13.",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""Examples:
|
||||
python3 scripts/api_dev_test.py
|
||||
|
||||
@@ -238,6 +238,10 @@ pub fn router(state: AppState) -> Router {
|
||||
"/api/integrations/home-assistant/entity",
|
||||
post(inspect_home_assistant_entity),
|
||||
)
|
||||
.route(
|
||||
"/api/integrations/home-assistant/entities",
|
||||
get(list_home_assistant_entities),
|
||||
)
|
||||
.route(
|
||||
"/api/integrations/home-assistant/energy-sensors",
|
||||
get(list_home_assistant_energy_sensors),
|
||||
|
||||
@@ -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.12".into()));
|
||||
return Err(AppError::BadRequest("unsupported configuration export version; version 3 is required by GREE Controller 0.14.13".into()));
|
||||
}
|
||||
if export.settings.control_strategy != "setpoint" {
|
||||
return Err(AppError::BadRequest(
|
||||
|
||||
+54
-3
@@ -2,6 +2,7 @@ fn map_home_assistant_integration_error(error: anyhow::Error) -> AppError {
|
||||
let message = error.to_string();
|
||||
if message.contains("Home Assistant URL is not configured")
|
||||
|| message.contains("Home Assistant token is not configured")
|
||||
|| message.contains("Home Assistant Supervisor token is not available")
|
||||
|| message.contains("invalid Home Assistant URL")
|
||||
|| message.contains("Home Assistant URL must use http or https")
|
||||
|| message.contains("cannot build Home Assistant API URL")
|
||||
@@ -45,10 +46,58 @@ async fn test_home_assistant(
|
||||
State(state): State<AppState>,
|
||||
) -> Result<Json<Value>, AppError> {
|
||||
let settings = state.settings.read().await.clone();
|
||||
home_assistant::test_connection(&state.http, &settings.home_assistant)
|
||||
let sample = home_assistant::test_connection(&state.http, &settings.home_assistant)
|
||||
.await
|
||||
.map_err(map_home_assistant_integration_error)?;
|
||||
Ok(Json(json!({"ok": true})))
|
||||
Ok(Json(json!({
|
||||
"ok": true,
|
||||
"auth_mode": home_assistant::auth_mode(&settings.home_assistant),
|
||||
"sample": sample,
|
||||
})))
|
||||
}
|
||||
|
||||
async fn list_home_assistant_entities(
|
||||
State(state): State<AppState>,
|
||||
) -> Result<Json<Value>, AppError> {
|
||||
let settings = state.settings.read().await.home_assistant.clone();
|
||||
if !home_assistant::token_configured(&settings)
|
||||
|| home_assistant::effective_url(&settings).trim().is_empty()
|
||||
{
|
||||
return Ok(Json(json!({
|
||||
"configured": false,
|
||||
"entities": [],
|
||||
})));
|
||||
}
|
||||
|
||||
let mut entities = home_assistant::list_entities(&state.http, &settings)
|
||||
.await
|
||||
.map_err(map_home_assistant_integration_error)?
|
||||
.into_iter()
|
||||
.filter_map(|entity| {
|
||||
let entity_id = entity.get("entity_id")?.as_str()?.trim();
|
||||
if entity_id.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let attributes = entity.get("attributes").and_then(Value::as_object);
|
||||
Some(json!({
|
||||
"entity_id": entity_id,
|
||||
"name": attributes.and_then(|value| value.get("friendly_name")).and_then(Value::as_str).unwrap_or_default(),
|
||||
"state": entity.get("state").and_then(Value::as_str).unwrap_or_default(),
|
||||
"unit": attributes.and_then(|value| value.get("unit_of_measurement")).and_then(Value::as_str).unwrap_or_default(),
|
||||
"device_class": attributes.and_then(|value| value.get("device_class")).and_then(Value::as_str).unwrap_or_default(),
|
||||
}))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
entities.sort_by(|a, b| {
|
||||
a.get("entity_id")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or_default()
|
||||
.cmp(b.get("entity_id").and_then(Value::as_str).unwrap_or_default())
|
||||
});
|
||||
Ok(Json(json!({
|
||||
"configured": true,
|
||||
"entities": entities,
|
||||
})))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -116,7 +165,9 @@ async fn list_home_assistant_energy_sensors(
|
||||
State(state): State<AppState>,
|
||||
) -> Result<Json<Value>, AppError> {
|
||||
let settings = state.settings.read().await.home_assistant.clone();
|
||||
if settings.url.trim().is_empty() || settings.token.trim().is_empty() {
|
||||
if !home_assistant::token_configured(&settings)
|
||||
|| home_assistant::effective_url(&settings).trim().is_empty()
|
||||
{
|
||||
return Ok(Json(json!({
|
||||
"configured": false,
|
||||
"sensors": [],
|
||||
|
||||
@@ -50,9 +50,14 @@ fn public_settings(settings: &RuntimeSettings) -> Value {
|
||||
},
|
||||
"home_assistant": {
|
||||
"url": home_assistant::effective_url(&settings.home_assistant),
|
||||
"auth_mode": home_assistant::auth_mode(),
|
||||
"manual_url": settings.home_assistant.url,
|
||||
"auth_mode": home_assistant::auth_mode(&settings.home_assistant),
|
||||
"token": "",
|
||||
"token_configured": home_assistant::token_configured(&settings.home_assistant),
|
||||
"manual_token_configured": !settings.home_assistant.token.trim().is_empty(),
|
||||
"supervisor_detected": home_assistant::supervisor_detected(),
|
||||
"supervisor_token_detected": home_assistant::supervisor_token_detected(),
|
||||
"manual_auth_override": settings.home_assistant.manual_auth_override,
|
||||
"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,
|
||||
|
||||
+12
-5
@@ -79,8 +79,13 @@ fn notification_settings(settings: &RuntimeSettings) -> NotificationSettingsView
|
||||
fn home_assistant_settings(settings: &RuntimeSettings) -> HomeAssistantSettingsView {
|
||||
HomeAssistantSettingsView {
|
||||
url: home_assistant::effective_url(&settings.home_assistant),
|
||||
auth_mode: home_assistant::auth_mode().to_string(),
|
||||
manual_url: settings.home_assistant.url.clone(),
|
||||
auth_mode: home_assistant::auth_mode(&settings.home_assistant).to_string(),
|
||||
token_configured: home_assistant::token_configured(&settings.home_assistant),
|
||||
manual_token_configured: !settings.home_assistant.token.trim().is_empty(),
|
||||
supervisor_detected: home_assistant::supervisor_detected(),
|
||||
supervisor_token_detected: home_assistant::supervisor_token_detected(),
|
||||
manual_auth_override: settings.home_assistant.manual_auth_override,
|
||||
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,
|
||||
@@ -694,9 +699,10 @@ fn apply_home_assistant_update(
|
||||
current: &HomeAssistantSettings,
|
||||
input: HomeAssistantSettingsUpdate,
|
||||
) -> HomeAssistantSettings {
|
||||
let supervisor_managed = home_assistant::uses_supervisor_auth();
|
||||
let supervisor_detected = home_assistant::supervisor_detected();
|
||||
let manual_auth_override = supervisor_detected && input.manual_auth_override;
|
||||
let mut next = HomeAssistantSettings {
|
||||
url: if supervisor_managed {
|
||||
url: if supervisor_detected && !manual_auth_override {
|
||||
current.url.clone()
|
||||
} else {
|
||||
input.url
|
||||
@@ -705,10 +711,11 @@ fn apply_home_assistant_update(
|
||||
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,
|
||||
manual_auth_override,
|
||||
sensor_aliases: input.sensor_aliases,
|
||||
flow_inputs: input.flow_inputs,
|
||||
};
|
||||
if !supervisor_managed {
|
||||
if !supervisor_detected || manual_auth_override {
|
||||
if let Some(token) = input.token {
|
||||
next.token = token;
|
||||
}
|
||||
@@ -728,7 +735,7 @@ async fn update_home_assistant_settings(
|
||||
normalize_sensor_aliases(&mut next);
|
||||
validate_flow_shared_inputs(&mut next, &state)?;
|
||||
canonicalize_home_assistant_entities(&mut next);
|
||||
if !home_assistant::uses_supervisor_auth() {
|
||||
if !home_assistant::uses_supervisor_auth(&next) {
|
||||
validate_home_assistant_url(&next)?;
|
||||
}
|
||||
settings.home_assistant = next.clone();
|
||||
|
||||
@@ -131,6 +131,7 @@ impl Config {
|
||||
allow_invalid_tls: env::var("HA_ALLOW_INVALID_TLS")
|
||||
.map(|v| matches!(v.to_ascii_lowercase().as_str(), "1" | "true" | "yes" | "on"))
|
||||
.unwrap_or(false),
|
||||
manual_auth_override: false,
|
||||
sensor_aliases: Default::default(),
|
||||
flow_inputs: Vec::new(),
|
||||
},
|
||||
|
||||
+56
-12
@@ -9,14 +9,25 @@ const SUPERVISOR_AUTH_MODE: &str = "supervisor";
|
||||
const SUPERVISOR_TOKEN_ENV: &str = "SUPERVISOR_TOKEN";
|
||||
const SUPERVISOR_BASE_URL: &str = "http://supervisor/core/";
|
||||
|
||||
pub fn uses_supervisor_auth() -> bool {
|
||||
pub fn supervisor_detected() -> bool {
|
||||
env::var(SUPERVISOR_AUTH_ENV)
|
||||
.map(|value| value.trim().eq_ignore_ascii_case(SUPERVISOR_AUTH_MODE))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn auth_mode() -> &'static str {
|
||||
if uses_supervisor_auth() {
|
||||
pub fn supervisor_token_detected() -> bool {
|
||||
supervisor_detected()
|
||||
&& env::var(SUPERVISOR_TOKEN_ENV)
|
||||
.map(|token| !token.trim().is_empty())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn uses_supervisor_auth(settings: &HomeAssistantSettings) -> bool {
|
||||
supervisor_detected() && !settings.manual_auth_override
|
||||
}
|
||||
|
||||
pub fn auth_mode(settings: &HomeAssistantSettings) -> &'static str {
|
||||
if uses_supervisor_auth(settings) {
|
||||
SUPERVISOR_AUTH_MODE
|
||||
} else {
|
||||
"manual"
|
||||
@@ -24,7 +35,7 @@ pub fn auth_mode() -> &'static str {
|
||||
}
|
||||
|
||||
pub fn effective_url(settings: &HomeAssistantSettings) -> String {
|
||||
if uses_supervisor_auth() {
|
||||
if uses_supervisor_auth(settings) {
|
||||
SUPERVISOR_BASE_URL.to_string()
|
||||
} else {
|
||||
settings.url.clone()
|
||||
@@ -32,17 +43,15 @@ pub fn effective_url(settings: &HomeAssistantSettings) -> String {
|
||||
}
|
||||
|
||||
pub fn token_configured(settings: &HomeAssistantSettings) -> bool {
|
||||
if uses_supervisor_auth() {
|
||||
env::var(SUPERVISOR_TOKEN_ENV)
|
||||
.map(|token| !token.trim().is_empty())
|
||||
.unwrap_or(false)
|
||||
if uses_supervisor_auth(settings) {
|
||||
supervisor_token_detected()
|
||||
} else {
|
||||
!settings.token.trim().is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
fn connection(settings: &HomeAssistantSettings) -> Result<(Url, String)> {
|
||||
let (raw_url, token) = if uses_supervisor_auth() {
|
||||
let (raw_url, token) = if uses_supervisor_auth(settings) {
|
||||
let token = env::var(SUPERVISOR_TOKEN_ENV)
|
||||
.context("Home Assistant Supervisor token is not available")?;
|
||||
(SUPERVISOR_BASE_URL.to_string(), token)
|
||||
@@ -54,7 +63,7 @@ fn connection(settings: &HomeAssistantSettings) -> Result<(Url, String)> {
|
||||
bail!("Home Assistant URL is not configured")
|
||||
}
|
||||
if token.trim().is_empty() {
|
||||
if uses_supervisor_auth() {
|
||||
if uses_supervisor_auth(settings) {
|
||||
bail!("Home Assistant Supervisor token is not available")
|
||||
}
|
||||
bail!("Home Assistant token is not configured")
|
||||
@@ -110,7 +119,7 @@ pub fn resolve_entity_id(
|
||||
pub async fn test_connection(
|
||||
default_client: &reqwest::Client,
|
||||
settings: &HomeAssistantSettings,
|
||||
) -> Result<()> {
|
||||
) -> Result<Option<Value>> {
|
||||
let (mut base, token) = connection(settings)?;
|
||||
base = base.join("api/").context("cannot build Home Assistant API URL")?;
|
||||
let response = request_client(default_client, settings)?
|
||||
@@ -128,7 +137,41 @@ pub async fn test_connection(
|
||||
body.chars().take(200).collect::<String>()
|
||||
)
|
||||
}
|
||||
Ok(())
|
||||
|
||||
let entities = list_entities(default_client, settings).await?;
|
||||
let sample = entities
|
||||
.iter()
|
||||
.find(|entity| {
|
||||
entity
|
||||
.get("entity_id")
|
||||
.and_then(Value::as_str)
|
||||
.map(|id| id.starts_with("sensor."))
|
||||
.unwrap_or(false)
|
||||
&& entity
|
||||
.get("state")
|
||||
.and_then(Value::as_str)
|
||||
.map(|state| !matches!(state, "" | "unknown" | "unavailable"))
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.or_else(|| {
|
||||
entities.iter().find(|entity| {
|
||||
entity
|
||||
.get("state")
|
||||
.and_then(Value::as_str)
|
||||
.map(|state| !matches!(state, "" | "unknown" | "unavailable"))
|
||||
.unwrap_or(false)
|
||||
})
|
||||
})
|
||||
.map(|entity| {
|
||||
let attributes = entity.get("attributes").and_then(Value::as_object);
|
||||
serde_json::json!({
|
||||
"entity_id": entity.get("entity_id").and_then(Value::as_str).unwrap_or_default(),
|
||||
"name": attributes.and_then(|value| value.get("friendly_name")).and_then(Value::as_str).unwrap_or_default(),
|
||||
"state": entity.get("state").and_then(Value::as_str).unwrap_or_default(),
|
||||
"unit": attributes.and_then(|value| value.get("unit_of_measurement")).and_then(Value::as_str).unwrap_or_default(),
|
||||
})
|
||||
});
|
||||
Ok(sample)
|
||||
}
|
||||
|
||||
pub async fn read_temperature(
|
||||
@@ -213,6 +256,7 @@ mod tests {
|
||||
outdoor_entity_id: "sensor.zewnatrz_temperature".into(),
|
||||
sensor_stale_after_seconds: 300,
|
||||
allow_invalid_tls: false,
|
||||
manual_auth_override: false,
|
||||
sensor_aliases,
|
||||
flow_inputs: Vec::new(),
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ pub struct HomeAssistantSettings {
|
||||
/// Accept self-signed/expired certificates for local Home Assistant HTTPS.
|
||||
#[serde(default)]
|
||||
pub allow_invalid_tls: bool,
|
||||
/// In the Home Assistant add-on, use the persisted URL/token instead of Supervisor auth.
|
||||
/// Ignored by standalone installations.
|
||||
#[serde(default)]
|
||||
pub manual_auth_override: bool,
|
||||
/// Friendly labels used only by the controller UI/charts; entity_id remains the storage key.
|
||||
#[serde(default)]
|
||||
pub sensor_aliases: BTreeMap<String, String>,
|
||||
|
||||
@@ -131,6 +131,8 @@ pub struct HomeAssistantSettingsUpdate {
|
||||
pub outdoor_entity_id: String,
|
||||
pub sensor_stale_after_seconds: u64,
|
||||
pub allow_invalid_tls: bool,
|
||||
#[serde(default)]
|
||||
pub manual_auth_override: bool,
|
||||
pub sensor_aliases: BTreeMap<String, String>,
|
||||
pub flow_inputs: Vec<FlowSharedInput>,
|
||||
pub outdoor_assist_enabled: bool,
|
||||
@@ -139,8 +141,13 @@ pub struct HomeAssistantSettingsUpdate {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct HomeAssistantSettingsView {
|
||||
pub url: String,
|
||||
pub manual_url: String,
|
||||
pub auth_mode: String,
|
||||
pub token_configured: bool,
|
||||
pub manual_token_configured: bool,
|
||||
pub supervisor_detected: bool,
|
||||
pub supervisor_token_detected: bool,
|
||||
pub manual_auth_override: bool,
|
||||
pub outdoor_entity_id: String,
|
||||
pub sensor_stale_after_seconds: u64,
|
||||
pub allow_invalid_tls: bool,
|
||||
|
||||
+7
-5
@@ -414,16 +414,18 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-grid">
|
||||
<label class="wide"><span>URL</span><input type="url" name="ha_url"
|
||||
<div id="haSupervisorStatus" class="ha-supervisor-status wide" role="status" aria-live="polite" hidden></div>
|
||||
<label class="wide" data-ha-manual-field><span>URL</span><input type="url" name="ha_url"
|
||||
placeholder="http://homeassistant.local:8123"></label>
|
||||
<label class="wide"><span data-i18n="settings.haLongLivedToken">Long-Lived Access Token</span><input
|
||||
<label class="wide" data-ha-manual-field><span data-i18n="settings.haLongLivedToken">Long-Lived Access Token</span><input
|
||||
type="password" name="ha_token" autocomplete="new-password" data-i18n-placeholder="settings.haTokenKeep"
|
||||
placeholder="Leave empty to keep the saved token"></label>
|
||||
<label class="check wide"><input type="checkbox" name="ha_allow_invalid_tls"> <span
|
||||
<label class="check wide" data-ha-manual-field><input type="checkbox" name="ha_allow_invalid_tls"> <span
|
||||
data-i18n="settings.allowInvalidTls">Allow invalid/self-signed HTTPS certificate</span></label>
|
||||
<p class="field-note wide warning-note" data-i18n="settings.allowInvalidTlsHint">Use only for a trusted
|
||||
<p class="field-note wide warning-note" data-ha-manual-field data-i18n="settings.allowInvalidTlsHint">Use only for a trusted
|
||||
local Home Assistant server.</p>
|
||||
<div class="form-actions wide"><button type="button" class="secondary" id="haTest"
|
||||
<div class="form-actions wide"><button type="button" class="secondary" id="haUseSupervisor"
|
||||
data-i18n="settings.haUseSupervisor" hidden>Use Supervisor automatically</button><button type="button" class="secondary" id="haTest"
|
||||
data-i18n="settings.testHa">Test HA</button></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -33,6 +33,7 @@ const app = {
|
||||
historyZone: 'all', historyDevice: 'all', historySensor: 'all', historyNetworkTarget: 'all', historyNetworkShowJitter: true, historyNetwork: [], historyNetworkTargets: [], historyEnergyDevice: '', historyEnergyTargets: [], historyEnergyInterval: 'daily', historyEnergyCompare: 'none', historyEnergy: [], historyLoading: false, historyReloadPending: false,
|
||||
customChartSeries: [], savedCharts: [], chartZooms: {}, chartHiddenSeries: {}, zoneControlSeq: {}, groupControlSeq: {}, zoneControlQueue: {}, groupControlQueue: {}, deviceControlQueue: {}, houseControlQueue: null, climateControlQueue: null, groupCustomDrafts: {}, zoneTemperatureTimers: {}, deviceTemperatureDrafts: {},
|
||||
controlPlan: null, controlPlanRevision: null, controlPlanPushReady: false, controlPlanTimer: null, debugLines: [], debugBacklogLoaded: false, debugFilter: 'all', sensorAliases: {}, flowSharedInputs: [], flowSharedInputValueCache: {}, flowSharedInputValueRequests: {}, flowSharedInputValueTimer: null,
|
||||
haManualFallbackVisible: false, haSupervisorTestState: null, haEntityCatalog: [], haEntityCatalogConfigured: false, haEntityCatalogLoadedAt: 0, haEntityCatalogLoading: false,
|
||||
simulationScope: 'units', simulationTarget: 'all', standaloneSimulation: false, dashboardTab: 'main', settingsTab: 'app', systemSnapshotAt: Date.now(),
|
||||
pingMonitor: { targetId: '', all: false, running: false, timer: null, inFlight: false, samples: {} },
|
||||
flowDraft: null, flowSelectedNodeId: null, flowSelectedNodeIds: [], flowConnectFrom: null, flowDirty: false, flowZoom: 1,
|
||||
|
||||
+150
-15
@@ -34,7 +34,11 @@ function knownHaEntities() {
|
||||
|
||||
function renderHaEntitySuggestions() {
|
||||
const host = $('#haEntitySuggestions'); if (!host) return;
|
||||
host.innerHTML = knownHaEntities().map(entity => {
|
||||
const entities = [...new Set([
|
||||
...knownHaEntities(),
|
||||
...(app.haEntityCatalog || []).map(item => item?.entity_id).filter(Boolean),
|
||||
])].sort();
|
||||
host.innerHTML = entities.map(entity => {
|
||||
const alias = haSensorLabel(entity);
|
||||
return `<option value="${esc(entity)}" label="${esc(alias === entity ? '' : alias)}"></option>`;
|
||||
}).join('');
|
||||
@@ -116,15 +120,107 @@ function sharedFlowOptions(items, selected, label = item => item.name) {
|
||||
return items.map(item => `<option value="${esc(item.id)}" ${item.id === selected ? 'selected' : ''}>${esc(label(item))}</option>`).join('');
|
||||
}
|
||||
|
||||
|
||||
function haEntityMatchesSharedKind(entity, kind) {
|
||||
if (!entity?.entity_id) return false;
|
||||
if (kind === 'ha_numeric') { const state = String(entity.state ?? '').trim(); return state !== '' && Number.isFinite(Number(state)); }
|
||||
return true;
|
||||
}
|
||||
|
||||
function haEntityPickerResults(kind, query = '') {
|
||||
const needle = String(query || '').trim().toLocaleLowerCase();
|
||||
return (app.haEntityCatalog || [])
|
||||
.filter(entity => haEntityMatchesSharedKind(entity, kind))
|
||||
.filter(entity => {
|
||||
if (!needle) return true;
|
||||
return [entity.entity_id, entity.name, entity.state, entity.unit, entity.device_class]
|
||||
.some(value => String(value || '').toLocaleLowerCase().includes(needle));
|
||||
})
|
||||
.slice(0, 12);
|
||||
}
|
||||
|
||||
function renderHaEntityPickerResults(input, kind) {
|
||||
const picker = input?.closest?.('[data-ha-entity-picker]');
|
||||
const host = picker?.querySelector?.('[data-ha-entity-results]');
|
||||
const note = picker?.querySelector?.('[data-ha-entity-note]');
|
||||
if (!host || !note) return;
|
||||
|
||||
if (app.haEntityCatalogLoading) {
|
||||
note.textContent = tr('flow.haEntitySearchLoading');
|
||||
host.hidden = true;
|
||||
return;
|
||||
}
|
||||
if (!app.haEntityCatalogConfigured) {
|
||||
note.textContent = tr('flow.haEntitySearchConnect');
|
||||
host.hidden = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const items = haEntityPickerResults(kind, input.value);
|
||||
note.textContent = tr('flow.haEntitySearchCount', { count: app.haEntityCatalog.length });
|
||||
host.innerHTML = items.length ? items.map(entity => {
|
||||
const title = entity.name || entity.entity_id;
|
||||
const state = `${entity.state || '—'}${entity.unit ? ` ${entity.unit}` : ''}`;
|
||||
return `<button type="button" class="ha-entity-option" data-ha-entity-value="${esc(entity.entity_id)}"><span><strong>${esc(title)}</strong><code>${esc(entity.entity_id)}</code></span><small>${esc(state)}</small></button>`;
|
||||
}).join('') : `<div class="ha-entity-empty">${esc(tr('flow.haEntitySearchEmpty'))}</div>`;
|
||||
host.hidden = false;
|
||||
}
|
||||
|
||||
async function loadHaEntityCatalog(force = false) {
|
||||
const fresh = app.haEntityCatalogLoadedAt && Date.now() - app.haEntityCatalogLoadedAt < 60000;
|
||||
if (!force && fresh) return app.haEntityCatalog;
|
||||
if (app.haEntityCatalogLoading) return app.haEntityCatalog;
|
||||
app.haEntityCatalogLoading = true;
|
||||
const activeInput = $('#flowSharedInputFields [data-ha-entity-search]');
|
||||
if (activeInput) renderHaEntityPickerResults(activeInput, $('#flowSharedInputKind')?.value || 'ha_state');
|
||||
try {
|
||||
const response = await api('/api/integrations/home-assistant/entities');
|
||||
app.haEntityCatalog = Array.isArray(response.entities) ? response.entities : [];
|
||||
app.haEntityCatalogConfigured = response.configured === true;
|
||||
app.haEntityCatalogLoadedAt = Date.now();
|
||||
renderHaEntitySuggestions();
|
||||
} catch (_) {
|
||||
app.haEntityCatalog = [];
|
||||
app.haEntityCatalogConfigured = false;
|
||||
app.haEntityCatalogLoadedAt = Date.now();
|
||||
} finally {
|
||||
app.haEntityCatalogLoading = false;
|
||||
const input = $('#flowSharedInputFields [data-ha-entity-search]');
|
||||
if (input) renderHaEntityPickerResults(input, $('#flowSharedInputKind')?.value || 'ha_state');
|
||||
}
|
||||
return app.haEntityCatalog;
|
||||
}
|
||||
|
||||
function haEntityPickerMarkup(kind, value, placeholder) {
|
||||
return `<div class="ha-entity-picker" data-ha-entity-picker><label><span>entity_id</span><input type="search" autocomplete="off" data-shared-config="entity_id" data-ha-entity-search value="${esc(value || '')}" placeholder="${esc(placeholder)}"></label><small class="field-note" data-ha-entity-note>${esc(tr('flow.haEntitySearchHint'))}</small><div class="ha-entity-results" data-ha-entity-results hidden></div></div>`;
|
||||
}
|
||||
|
||||
function bindHaEntityPicker(kind) {
|
||||
const input = $('#flowSharedInputFields [data-ha-entity-search]');
|
||||
const results = $('#flowSharedInputFields [data-ha-entity-results]');
|
||||
if (!input || !results) return;
|
||||
const refresh = () => renderHaEntityPickerResults(input, kind);
|
||||
input.addEventListener('input', refresh);
|
||||
input.addEventListener('focus', refresh);
|
||||
results.addEventListener('click', event => {
|
||||
const option = event.target.closest?.('[data-ha-entity-value]');
|
||||
if (!option) return;
|
||||
input.value = option.dataset.haEntityValue || '';
|
||||
input.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
results.hidden = true;
|
||||
});
|
||||
loadHaEntityCatalog().then(refresh);
|
||||
}
|
||||
|
||||
function renderFlowSharedInputFields(kind, config = {}) {
|
||||
const host = $('#flowSharedInputFields'); if (!host) return;
|
||||
const c = config || {};
|
||||
let fields = '';
|
||||
if (kind === 'constant') fields = `<label><span>${esc(tr('flow.value'))}</span><select data-shared-config="value"><option value="true" ${c.value !== false ? 'selected' : ''}>${esc(tr('common.yes'))}</option><option value="false" ${c.value === false ? 'selected' : ''}>${esc(tr('common.no'))}</option></select></label>`;
|
||||
else if (kind === 'ha_state') fields = `<label><span>entity_id</span><input data-shared-config="entity_id" list="haEntitySuggestions" value="${esc(c.entity_id || '')}" placeholder="binary_sensor.window"></label>`;
|
||||
else if (kind === 'ha_numeric') fields = `<label><span>entity_id</span><input data-shared-config="entity_id" list="haEntitySuggestions" value="${esc(c.entity_id || '')}" placeholder="sensor.energy_price"></label>`;
|
||||
else if (kind === 'ha_attribute') fields = `<label><span>entity_id</span><input data-shared-config="entity_id" list="haEntitySuggestions" value="${esc(c.entity_id || '')}" placeholder="climate.living_room"></label><label><span>${esc(tr('flow.attribute'))}</span><input data-shared-config="attribute" value="${esc(c.attribute || '')}" placeholder="hvac_action"></label>`;
|
||||
else if (kind === 'ha_available') fields = `<label><span>entity_id</span><input data-shared-config="entity_id" list="haEntitySuggestions" value="${esc(c.entity_id || '')}" placeholder="binary_sensor.window"></label><p class="field-note">${esc(tr('flow.haAvailableHint'))}</p>`;
|
||||
else if (kind === 'ha_state') fields = haEntityPickerMarkup(kind, c.entity_id, 'binary_sensor.window');
|
||||
else if (kind === 'ha_numeric') fields = haEntityPickerMarkup(kind, c.entity_id, 'sensor.energy_price');
|
||||
else if (kind === 'ha_attribute') fields = `${haEntityPickerMarkup(kind, c.entity_id, 'climate.living_room')}<label><span>${esc(tr('flow.attribute'))}</span><input data-shared-config="attribute" value="${esc(c.attribute || '')}" placeholder="hvac_action"></label>`;
|
||||
else if (kind === 'ha_available') fields = `${haEntityPickerMarkup(kind, c.entity_id, 'binary_sensor.window')}<p class="field-note">${esc(tr('flow.haAvailableHint'))}</p>`;
|
||||
else if (kind === 'outdoor_temperature') fields = `<p class="field-note">${esc(tr('flow.sharedInputSourceOnlyHint'))}</p>`;
|
||||
else if (kind === 'device_temperature') fields = `<label><span>${esc(tr('common.device'))}</span><select data-shared-config="device_id">${sharedFlowOptions(app.devices, c.device_id)}</select></label>`;
|
||||
else if (kind === 'zone_temperature') fields = `<label><span>${esc(tr('common.zone'))}</span><select data-shared-config="zone_id">${sharedFlowOptions(app.zones, c.zone_id)}</select></label>`;
|
||||
@@ -134,6 +230,7 @@ function renderFlowSharedInputFields(kind, config = {}) {
|
||||
else if (kind === 'group_state') fields = `<label><span>${esc(tr('groups.group'))}</span><select data-shared-config="group_id">${sharedFlowOptions(app.groups, c.group_id)}</select></label><input type="hidden" data-shared-config="field" value="power_enabled">`;
|
||||
else if (kind === 'night_mode') fields = `<p class="field-note">${esc(tr('flow.nightModeHint'))}</p>`;
|
||||
host.innerHTML = fields;
|
||||
if (isHaSharedInputKind(kind)) bindHaEntityPicker(kind);
|
||||
const testPanel = $('#flowSharedInputTestPanel'), testResult = $('#flowSharedInputTestResult');
|
||||
if (testPanel) testPanel.hidden = !isHaSharedInputKind(kind);
|
||||
if (testResult) { testResult.hidden = true; testResult.innerHTML = ''; }
|
||||
@@ -337,22 +434,60 @@ function renderNightSettings() {
|
||||
markFormClean(form);
|
||||
}
|
||||
|
||||
function renderHomeAssistantAuthState() {
|
||||
const form = $('#homeAssistantForm');
|
||||
if (!form || !app.settings?.home_assistant) return;
|
||||
const ha = app.settings.home_assistant;
|
||||
const supervisorDetected = ha.supervisor_detected === true;
|
||||
const manualFallback = supervisorDetected && (ha.manual_auth_override === true || app.haManualFallbackVisible === true);
|
||||
form.dataset.haManualOverride = manualFallback ? 'true' : 'false';
|
||||
|
||||
$$('[data-ha-manual-field]', form).forEach(node => { node.hidden = supervisorDetected && !manualFallback; });
|
||||
form.ha_url.required = manualFallback;
|
||||
form.ha_token.required = manualFallback && !ha.manual_token_configured;
|
||||
const useSupervisor = $('#haUseSupervisor');
|
||||
if (useSupervisor) useSupervisor.hidden = !supervisorDetected || !manualFallback;
|
||||
|
||||
const status = $('#haSupervisorStatus');
|
||||
if (!status) return;
|
||||
status.hidden = !supervisorDetected;
|
||||
status.classList.remove('success', 'warning', 'error');
|
||||
if (!supervisorDetected) return;
|
||||
|
||||
const testState = app.haSupervisorTestState;
|
||||
if (manualFallback) {
|
||||
status.classList.add(testState?.ok === false ? 'error' : 'warning');
|
||||
status.innerHTML = `<strong>${esc(testState?.ok === false ? tr('settings.haSupervisorTestFailedTitle') : tr('settings.haManualFallbackTitle'))}</strong><span>${esc(testState?.ok === false ? tr('settings.haSupervisorTestFailedHint') : tr('settings.haManualFallbackHint'))}</span>`;
|
||||
} else if (testState?.ok === true) {
|
||||
status.classList.add('success');
|
||||
status.innerHTML = `<strong>${esc(tr('settings.haSupervisorVerifiedTitle'))}</strong><span>${esc(tr('settings.haSupervisorVerifiedHint'))}</span>`;
|
||||
} else if (ha.supervisor_token_detected) {
|
||||
status.classList.add('success');
|
||||
status.innerHTML = `<strong>${esc(tr('settings.haSupervisorAutoTitle'))}</strong><span>${esc(tr('settings.haSupervisorAutoHint'))}</span>`;
|
||||
} else {
|
||||
status.classList.add('warning');
|
||||
status.innerHTML = `<strong>${esc(tr('settings.haSupervisorMissingTitle'))}</strong><span>${esc(tr('settings.haSupervisorMissingHint'))}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderHomeAssistantSettings() {
|
||||
if (!app.settings) return;
|
||||
const form = $('#homeAssistantForm');
|
||||
if (!form) return;
|
||||
const supervisorManaged = app.settings.home_assistant?.auth_mode === 'supervisor';
|
||||
form.ha_url.value = app.settings.home_assistant?.url || '';
|
||||
form.ha_url.readOnly = supervisorManaged;
|
||||
const ha = app.settings.home_assistant || {};
|
||||
const supervisorDetected = ha.supervisor_detected === true;
|
||||
form.ha_url.value = supervisorDetected ? (ha.manual_url || '') : (ha.url || '');
|
||||
form.ha_url.readOnly = false;
|
||||
form.ha_token.value = '';
|
||||
form.ha_token.readOnly = supervisorManaged;
|
||||
form.ha_token.placeholder = supervisorManaged
|
||||
? tr('settings.haSupervisorToken')
|
||||
: (app.settings.home_assistant?.token_configured ? tr('settings.haTokenSaved') : tr('settings.haLongLivedToken'));
|
||||
form.ha_outdoor_entity_id.value = app.settings.home_assistant?.outdoor_entity_id || '';
|
||||
form.ha_sensor_stale_after_minutes.value = String(Math.max(1, Math.round(Number(app.settings.home_assistant?.sensor_stale_after_seconds || 300) / 60)));
|
||||
form.ha_allow_invalid_tls.checked = !!app.settings.home_assistant?.allow_invalid_tls;
|
||||
form.ha_token.readOnly = false;
|
||||
form.ha_token.placeholder = (supervisorDetected ? ha.manual_token_configured : ha.token_configured)
|
||||
? tr('settings.haTokenSaved')
|
||||
: tr('settings.haLongLivedToken');
|
||||
form.ha_outdoor_entity_id.value = ha.outdoor_entity_id || '';
|
||||
form.ha_sensor_stale_after_minutes.value = String(Math.max(1, Math.round(Number(ha.sensor_stale_after_seconds || 300) / 60)));
|
||||
form.ha_allow_invalid_tls.checked = !!ha.allow_invalid_tls;
|
||||
form.outdoor_assist_enabled.checked = !!app.settings.outdoor_assist_enabled;
|
||||
renderHomeAssistantAuthState();
|
||||
renderSensorAliases();
|
||||
renderFlowSharedInputs();
|
||||
renderAccessTokens();
|
||||
|
||||
+55
-6
@@ -146,6 +146,7 @@ function homeAssistantSettingsBodyFromForm(form) {
|
||||
outdoor_entity_id: raw.ha_outdoor_entity_id,
|
||||
sensor_stale_after_seconds: Math.max(60, Math.min(86400, Math.round(Number(raw.ha_sensor_stale_after_minutes || 5) * 60))),
|
||||
allow_invalid_tls: form.ha_allow_invalid_tls.checked,
|
||||
manual_auth_override: form.dataset.haManualOverride === 'true',
|
||||
sensor_aliases: { ...(app.sensorAliases || {}) },
|
||||
flow_inputs: JSON.parse(JSON.stringify(app.flowSharedInputs || [])),
|
||||
outdoor_assist_enabled: form.outdoor_assist_enabled.checked,
|
||||
@@ -200,6 +201,7 @@ async function saveHomeAssistantSettings(form, notify = true) {
|
||||
applySettingsSection('homeAssistant', data);
|
||||
app.sensorAliases = { ...(data.sensor_aliases || {}) };
|
||||
app.flowSharedInputs = JSON.parse(JSON.stringify(data.flow_inputs || []));
|
||||
app.haEntityCatalogLoadedAt = 0;
|
||||
refreshSettingsUi();
|
||||
if (notify) toast(tr('common.saved'));
|
||||
}
|
||||
@@ -280,19 +282,66 @@ $('#copyAccessToken').addEventListener('click', async () => {
|
||||
}
|
||||
});
|
||||
|
||||
function homeAssistantTestToast(result) {
|
||||
const sample = result?.sample;
|
||||
if (!sample?.entity_id) return tr('toast.haConnected');
|
||||
const value = `${sample.state ?? '—'}${sample.unit ? ` ${sample.unit}` : ''}`;
|
||||
const label = sample.name ? `${sample.name} (${sample.entity_id})` : sample.entity_id;
|
||||
return tr('toast.haConnectedSample', { entity: label, value });
|
||||
}
|
||||
|
||||
$('#haTest').addEventListener('click', async event => {
|
||||
const form = $('#homeAssistantForm'), button = event.currentTarget;
|
||||
if (!validateForm(form)) return;
|
||||
clearFormErrors(form);
|
||||
const ha = app.settings?.home_assistant || {};
|
||||
const supervisorAuto = ha.supervisor_detected === true && form.dataset.haManualOverride !== 'true';
|
||||
if (!supervisorAuto && !validateForm(form)) return;
|
||||
button.disabled = true;
|
||||
const idle = button.textContent;
|
||||
button.textContent = tr('settings.testingHa');
|
||||
try {
|
||||
await saveHomeAssistantSettings(form, false);
|
||||
await api('/api/integrations/home-assistant/test', { method: 'POST' });
|
||||
if (!supervisorAuto) await saveHomeAssistantSettings(form, false);
|
||||
const result = await api('/api/integrations/home-assistant/test', { method: 'POST' });
|
||||
app.haSupervisorTestState = { ok: true, sample: result.sample || null };
|
||||
if (app.settings?.home_assistant?.supervisor_detected && app.settings.home_assistant.auth_mode === 'supervisor') {
|
||||
app.haManualFallbackVisible = false;
|
||||
}
|
||||
app.haEntityCatalogLoadedAt = 0;
|
||||
renderHomeAssistantAuthState();
|
||||
markFormClean(form);
|
||||
toast(tr('toast.haConnected'));
|
||||
} catch (error) { presentFormError(form, error); }
|
||||
finally { button.disabled = false; button.textContent = idle; }
|
||||
toast(homeAssistantTestToast(result));
|
||||
} catch (error) {
|
||||
if (supervisorAuto) {
|
||||
app.haSupervisorTestState = { ok: false, message: error.message };
|
||||
app.haManualFallbackVisible = true;
|
||||
renderHomeAssistantAuthState();
|
||||
toast(`${tr('settings.haSupervisorTestFailedTitle')}: ${error.message}`, true);
|
||||
} else {
|
||||
presentFormError(form, error);
|
||||
}
|
||||
} finally { button.disabled = false; button.textContent = idle; }
|
||||
});
|
||||
|
||||
$('#haUseSupervisor')?.addEventListener('click', async event => {
|
||||
const form = $('#homeAssistantForm'), button = event.currentTarget;
|
||||
const idle = button.textContent;
|
||||
button.disabled = true;
|
||||
clearFormErrors(form);
|
||||
try {
|
||||
app.haManualFallbackVisible = false;
|
||||
form.dataset.haManualOverride = 'false';
|
||||
await saveHomeAssistantSettings(form, false);
|
||||
const result = await api('/api/integrations/home-assistant/test', { method: 'POST' });
|
||||
app.haSupervisorTestState = { ok: true, sample: result.sample || null };
|
||||
app.haEntityCatalogLoadedAt = 0;
|
||||
renderHomeAssistantSettings();
|
||||
toast(homeAssistantTestToast(result));
|
||||
} catch (error) {
|
||||
app.haManualFallbackVisible = true;
|
||||
app.haSupervisorTestState = { ok: false, message: error.message };
|
||||
renderHomeAssistantAuthState();
|
||||
toast(`${tr('settings.haSupervisorTestFailedTitle')}: ${error.message}`, true);
|
||||
} finally { button.disabled = false; button.textContent = idle; }
|
||||
});
|
||||
|
||||
$('#exportSettings').addEventListener('click', async () => {
|
||||
|
||||
@@ -9894,3 +9894,82 @@ select.select-native-proxy[aria-invalid="true"] + .custom-select .custom-select-
|
||||
.flow-fit-button-icon { display: none; }
|
||||
.flow-fit-button-label { display: inline; }
|
||||
}
|
||||
|
||||
.ha-supervisor-status {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid var(--line);
|
||||
border-left-width: 4px;
|
||||
border-radius: 12px;
|
||||
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-entity-picker {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ha-entity-picker > label { margin: 0; }
|
||||
.ha-entity-picker > .field-note { margin: 0; }
|
||||
|
||||
.ha-entity-results {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
max-height: 260px;
|
||||
overflow: auto;
|
||||
padding: 5px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.ha-entity-option {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 8px 10px;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ha-entity-option:hover,
|
||||
.ha-entity-option:focus-visible {
|
||||
background: var(--surface-2);
|
||||
}
|
||||
|
||||
.ha-entity-option > span {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ha-entity-option strong,
|
||||
.ha-entity-option code {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ha-entity-option code,
|
||||
.ha-entity-option small,
|
||||
.ha-entity-empty {
|
||||
color: var(--muted);
|
||||
font-size: .76rem;
|
||||
}
|
||||
|
||||
.ha-entity-option small { flex: 0 0 auto; }
|
||||
.ha-entity-empty { padding: 9px 10px; }
|
||||
|
||||
Reference in New Issue
Block a user