v0.5.5
This commit is contained in:
+7
-2
@@ -1,11 +1,16 @@
|
|||||||
# GREE Controller v0.5.4 - build and validation report
|
# GREE Controller v0.5.5 - build and validation report
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|
||||||
Version 0.5.4 builds on 0.5.3 with Home Assistant sensor-read fixes, full zone climate mode control, GREE capability discovery and native Sleep support during night mode.
|
Version 0.5.5 builds on 0.5.4 with dedicated Night mode and Home Assistant/Sensors pages, compact unit-feature controls, and room-temperature refresh for disabled zones.
|
||||||
|
|
||||||
## Implemented changes
|
## Implemented changes
|
||||||
|
|
||||||
|
- Night-mode settings moved out of application Settings into a dedicated **Night mode** page.
|
||||||
|
- Home Assistant connection, sensor aliases and integration access tokens moved to a dedicated **Home Assistant / Sensors** page.
|
||||||
|
- Settings now contains application/technical settings only.
|
||||||
|
- Unit-feature controls on device cards are smaller, centered and visually aligned with the other compact device controls.
|
||||||
|
- Disabled thermostat zones continue refreshing and showing room temperature while control commands remain disabled.
|
||||||
- Home Assistant sensor aliases are now presentation-only end-to-end: backend reads resolve aliases back to the original `entity_id`, settings are canonicalized, and zone/outdoor polling records the real entity ID.
|
- Home Assistant sensor aliases are now presentation-only end-to-end: backend reads resolve aliases back to the original `entity_id`, settings are canonicalized, and zone/outdoor polling records the real entity ID.
|
||||||
- Outdoor temperature now falls back to the median outdoor reading from online GREE units when the configured Home Assistant outdoor sensor is unavailable.
|
- Outdoor temperature now falls back to the median outdoor reading from online GREE units when the configured Home Assistant outdoor sensor is unavailable.
|
||||||
- GREE optional capabilities are discovered from status properties (`Lig`, `Quiet`, `Tur`, `Air`, `Blo`, `Health`, `SwhSlp`) and persisted with each device.
|
- GREE optional capabilities are discovered from status properties (`Lig`, `Quiet`, `Tur`, `Air`, `Blo`, `Health`, `SwhSlp`) and persisted with each device.
|
||||||
|
|||||||
Generated
+1
-1
@@ -633,7 +633,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gree-controller"
|
name = "gree-controller"
|
||||||
version = "0.5.4"
|
version = "0.5.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"aes-gcm",
|
"aes-gcm",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "gree-controller"
|
name = "gree-controller"
|
||||||
version = "0.5.4"
|
version = "0.5.5"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["GREE Controller contributors"]
|
authors = ["GREE Controller contributors"]
|
||||||
description = "Standalone local GREE HVAC controller with Web UI, SQLite and Home Assistant sensor support"
|
description = "Standalone local GREE HVAC controller with Web UI, SQLite and Home Assistant sensor support"
|
||||||
|
|||||||
+14
-14
@@ -1,17 +1,17 @@
|
|||||||
e027a3bf37a7917e249ebffb980d43b5e18c70e5931113271975cbeda81a88bd ./.env.example
|
e027a3bf37a7917e249ebffb980d43b5e18c70e5931113271975cbeda81a88bd ./.env.example
|
||||||
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
|
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
|
||||||
7e0bbddcccea0726bdbf7c93a0fa3a892c1d60279b6da8d992a55a7183522b01 ./BUILD_REPORT.md
|
52c2c492893ce42e68dac763533a5070170c21b18d67b302a4e1380a4d4cc4ab ./BUILD_REPORT.md
|
||||||
ce1d4a2d05be90ffecc39af85cdf5383b08484c3a27482e02f387030075db17e ./Cargo.lock
|
4202057467ffdc3f53ef250c3e7852a929be788bb0af03fbd2366a57ce42b79e ./Cargo.lock
|
||||||
d9e0c071ee11f26bd8e804e6cff976339982ebca45dad2f03614e147c35df327 ./Cargo.toml
|
9edc867d716d9d8ce2c54cdf17e16628f9c2b338ee36f88e0bb0fcb4f7884b57 ./Cargo.toml
|
||||||
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
||||||
e7b879527c9d1eb7d6bbd054813e709026777e4a79e62b4c8912d2d5aa5e7ff3 ./README.md
|
44675b0d5d9e6e89dd0bae8eff3ab76a190a3b1bdd6507e7402957bbd92cac36 ./README.md
|
||||||
a4fa9bfee9735ed8ed95ea31456e0cce503d82502ae3f550108ffca51b0f0c3d ./build.rs
|
a4fa9bfee9735ed8ed95ea31456e0cce503d82502ae3f550108ffca51b0f0c3d ./build.rs
|
||||||
f00f7475d690694c837e29a618c7737199515bd7a64a5a3be5f44291fe73df0c ./docs/API.md
|
674f8cf1ab9f1a49188e20b8e3977f1e2b7e9b8ef975315266730c44a55bbb48 ./docs/API.md
|
||||||
234dd200e380a13ecd3e61b4ea455f6f08d64ce89382077dee80684acadb9703 ./docs/HOME_ASSISTANT_MIGRATION.md
|
234dd200e380a13ecd3e61b4ea455f6f08d64ce89382077dee80684acadb9703 ./docs/HOME_ASSISTANT_MIGRATION.md
|
||||||
7a88d6e76fda21e5d34ab351e26bc10dc1f8f7b3055505aefad1df7c56d65ae4 ./docs/LOCALIZATION.md
|
7a88d6e76fda21e5d34ab351e26bc10dc1f8f7b3055505aefad1df7c56d65ae4 ./docs/LOCALIZATION.md
|
||||||
10a0722e1100fb4a05e3067daeb67dc47b0c0a096b43b1cbf2bf002967ce7d98 ./docs/LXC.md
|
10a0722e1100fb4a05e3067daeb67dc47b0c0a096b43b1cbf2bf002967ce7d98 ./docs/LXC.md
|
||||||
47e18e6b1723f1f6eb2516be6d83bbebd31b8155d9d3cdfccc0973177f3d8441 ./docs/PROJECT_SPEC.md
|
47e18e6b1723f1f6eb2516be6d83bbebd31b8155d9d3cdfccc0973177f3d8441 ./docs/PROJECT_SPEC.md
|
||||||
cf889651576a0860c3dc383c255aa011306f82d6287533617c523bcc41caca21 ./home-assistant/README.md
|
925ffa324b1b2cfad7ca40354e8255548f70627f95c4bb345b957d3f7e53694c ./home-assistant/README.md
|
||||||
f8e8559fe10fe523ac5bc9aac25c6e26e862f679d502e8f3c39f38a0a8e40911 ./home-assistant/custom_components/gree_controller/__init__.py
|
f8e8559fe10fe523ac5bc9aac25c6e26e862f679d502e8f3c39f38a0a8e40911 ./home-assistant/custom_components/gree_controller/__init__.py
|
||||||
b8e7c0dd722bfac1d1d8d5c2a4b5ff6b2d6fa89525b9ff5c121ee1d09623a7fd ./home-assistant/custom_components/gree_controller/api.py
|
b8e7c0dd722bfac1d1d8d5c2a4b5ff6b2d6fa89525b9ff5c121ee1d09623a7fd ./home-assistant/custom_components/gree_controller/api.py
|
||||||
b0314469ddb7c69ee4a96c677815af30cb97713e93911a05597f2fdc3d10f489 ./home-assistant/custom_components/gree_controller/climate.py
|
b0314469ddb7c69ee4a96c677815af30cb97713e93911a05597f2fdc3d10f489 ./home-assistant/custom_components/gree_controller/climate.py
|
||||||
@@ -19,7 +19,7 @@ b0314469ddb7c69ee4a96c677815af30cb97713e93911a05597f2fdc3d10f489 ./home-assista
|
|||||||
071f721b75af2ed68812c9be3a62b7ae63771e56e567a569f59ca976f8ff42f1 ./home-assistant/custom_components/gree_controller/const.py
|
071f721b75af2ed68812c9be3a62b7ae63771e56e567a569f59ca976f8ff42f1 ./home-assistant/custom_components/gree_controller/const.py
|
||||||
8dd51322798cad28d4d19938fe459aa2a4d3b64b075532149f3c8b62049a6c8b ./home-assistant/custom_components/gree_controller/coordinator.py
|
8dd51322798cad28d4d19938fe459aa2a4d3b64b075532149f3c8b62049a6c8b ./home-assistant/custom_components/gree_controller/coordinator.py
|
||||||
5a96fe8f5c035c34f1339370270cd078056202d09e236dec75735be11de92a7d ./home-assistant/custom_components/gree_controller/entity_map.py
|
5a96fe8f5c035c34f1339370270cd078056202d09e236dec75735be11de92a7d ./home-assistant/custom_components/gree_controller/entity_map.py
|
||||||
b3ac1d74c2f8decdc0dcb9689d3a62bea803a9533164929c09d97922c08c605a ./home-assistant/custom_components/gree_controller/manifest.json
|
f79cc14c65131fa86a5bf41d55f49b60a3aa71a6d15de98027de830e3b11b2a1 ./home-assistant/custom_components/gree_controller/manifest.json
|
||||||
d0206766d76f70ea1c75a618e41bf1e8e8c0429f42ee97f40daac1ea12f16658 ./home-assistant/custom_components/gree_controller/number.py
|
d0206766d76f70ea1c75a618e41bf1e8e8c0429f42ee97f40daac1ea12f16658 ./home-assistant/custom_components/gree_controller/number.py
|
||||||
5c914c2faa18a9cb9eb442dc111c9416f89a630578f820beb021d5381aa1b2d0 ./home-assistant/custom_components/gree_controller/sensor.py
|
5c914c2faa18a9cb9eb442dc111c9416f89a630578f820beb021d5381aa1b2d0 ./home-assistant/custom_components/gree_controller/sensor.py
|
||||||
7c23374d1c82e4b361089386ff89265322b80def0f4db9d3fd2c039867033105 ./home-assistant/custom_components/gree_controller/switch.py
|
7c23374d1c82e4b361089386ff89265322b80def0f4db9d3fd2c039867033105 ./home-assistant/custom_components/gree_controller/switch.py
|
||||||
@@ -27,8 +27,8 @@ d0206766d76f70ea1c75a618e41bf1e8e8c0429f42ee97f40daac1ea12f16658 ./home-assista
|
|||||||
13f30e2dcdcedbd1b6c3f99c2335e0487108fd72c8e86922368b84f2fa2038ae ./home-assistant/custom_components/gree_controller/translations/pl.json
|
13f30e2dcdcedbd1b6c3f99c2335e0487108fd72c8e86922368b84f2fa2038ae ./home-assistant/custom_components/gree_controller/translations/pl.json
|
||||||
4513070521d3dda0efb0d974a86ba674494cfb2b66fe9e5cac5b1b0430dede97 ./home-assistant/generated/gree_controller_entities.example.json
|
4513070521d3dda0efb0d974a86ba674494cfb2b66fe9e5cac5b1b0430dede97 ./home-assistant/generated/gree_controller_entities.example.json
|
||||||
253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md
|
253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md
|
||||||
e824a016cff1fa0411ab456b74f3cfe597546072c9e334ef92de69f86a868f48 ./lang/en.json
|
eb5370fbee1a8edc11a536ff0e68dba2152767734feb2976fef3cb7cc8c885f4 ./lang/en.json
|
||||||
93d26be77deb820dbce29484db8f2ef6c56b5b05a451c4e9d57013e43aa88395 ./lang/pl.json
|
9e4f43dd809f2d92aca8ce6813e843dbf7e2f38adcfd9cdae584a94e22197fa3 ./lang/pl.json
|
||||||
028e1f16e9fbaed57cadb88eff04e65b4bd67722c50b4d6b1fb525f5a2f39abf ./make_zip.py
|
028e1f16e9fbaed57cadb88eff04e65b4bd67722c50b4d6b1fb525f5a2f39abf ./make_zip.py
|
||||||
bb89bac237e750e9b1bf73761d7df97a6b81853091615878c03f13d7b6399aa7 ./scripts/README.md
|
bb89bac237e750e9b1bf73761d7df97a6b81853091615878c03f13d7b6399aa7 ./scripts/README.md
|
||||||
3640e34a1ed5a93be3e96610848b16b8e748d5ca95975ac7e95d38cbf32695a8 ./scripts/common.sh
|
3640e34a1ed5a93be3e96610848b16b8e748d5ca95975ac7e95d38cbf32695a8 ./scripts/common.sh
|
||||||
@@ -44,7 +44,7 @@ b50782b3742dfbf8a319c60571c968e93fdf8547db747c759edcffae68cb98bf ./scripts/upda
|
|||||||
ca63498714f3e154fd98c0c2157ab355bf36fc2208d7b3f82c201e153749fb04 ./src/api.rs
|
ca63498714f3e154fd98c0c2157ab355bf36fc2208d7b3f82c201e153749fb04 ./src/api.rs
|
||||||
31ef3ddbc096e580e9212486683e4db86998322ee87190c61b0cab5ae6bc8fc9 ./src/config.rs
|
31ef3ddbc096e580e9212486683e4db86998322ee87190c61b0cab5ae6bc8fc9 ./src/config.rs
|
||||||
a89bc49e359715109149130a10ac60abd26bfd2bc18c5ee5142e01696d9f9cdd ./src/db.rs
|
a89bc49e359715109149130a10ac60abd26bfd2bc18c5ee5142e01696d9f9cdd ./src/db.rs
|
||||||
c2ac8e8b4acc2bd15e9c41af5a003b7ced4424346617946e387ec57ce9cb2583 ./src/engine.rs
|
9dfea316b39da6ded85cb9c7ec6f6a61e0cd9a51f40469145782d5766c9cf7a4 ./src/engine.rs
|
||||||
ae3b496749a3fd723b243d9bea92e5d76249f52814c80359ad9bac53abacb074 ./src/error.rs
|
ae3b496749a3fd723b243d9bea92e5d76249f52814c80359ad9bac53abacb074 ./src/error.rs
|
||||||
081f6909e95839c7da8c40b866af9de237e0540f3e65e1ce739ce70a6fa94846 ./src/home_assistant.rs
|
081f6909e95839c7da8c40b866af9de237e0540f3e65e1ce739ce70a6fa94846 ./src/home_assistant.rs
|
||||||
190b0a33431539676e5dd7796698077f16c179d42eae4501ca96a91bf797cbf8 ./src/influxdb.rs
|
190b0a33431539676e5dd7796698077f16c179d42eae4501ca96a91bf797cbf8 ./src/influxdb.rs
|
||||||
@@ -56,9 +56,9 @@ a910bd9432a393740c0f6fab52bfcb551f0ea756718d66d290fd2610767cf07c ./src/protocol
|
|||||||
d1cba5e55c77d44c75a9c9897ae76c327fd96bdf9c01ad27e34322b689348487 ./src/queries.rs
|
d1cba5e55c77d44c75a9c9897ae76c327fd96bdf9c01ad27e34322b689348487 ./src/queries.rs
|
||||||
e3c02b232bd9d632ca1d3b6ad69a7cc61dc2e51954765d9e963d713d135f7dca ./src/state.rs
|
e3c02b232bd9d632ca1d3b6ad69a7cc61dc2e51954765d9e963d713d135f7dca ./src/state.rs
|
||||||
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
|
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
|
||||||
e82ff244991b968a3dd69f117ce9feeeee6e21c00a257d1d42273283b2d7ff99 ./web/app.js
|
17342e3aa40a55285f5d056502b232e6aeed41a64ff3e1fdf94c0d8278385ffe ./web/app.js
|
||||||
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
|
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
|
||||||
3a7b734e25955f148c5ef0518f6b4abbb04c0a1e660cd58270292fba3bee149f ./web/index.html
|
1474d31bf8266f6b6dc2dc47225644394259dc6e0afc8e09f0eaddfa93db1d82 ./web/index.html
|
||||||
c0e628423a82c5037b6d650aad5e8befe30945492c8cb0a5d94433db7b58c77b ./web/manifest.webmanifest
|
c0e628423a82c5037b6d650aad5e8befe30945492c8cb0a5d94433db7b58c77b ./web/manifest.webmanifest
|
||||||
2d20ba68af2b67b1c57104f0fbb67a0144e2f6aeb12ed8fc9b616a32baa89a3e ./web/styles.css
|
b8a3cecbde31f9aa4ed3afb5755c62cf98ad90a12faf0a616835170ee889e252 ./web/styles.css
|
||||||
f75381d719eb299730a85406eb86a58f56fd2ec9bd566bcb3cb84ae6a2d38f87 ./web/sw.js
|
f8bda0083c138828a037469d3db189559190d4b893bcff764a6f9bfd9030b265 ./web/sw.js
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
Standalone local GREE air-conditioner controller written in Rust. It runs on a regular Linux host or an LXC container and provides a mobile-first web interface without depending on the vendor cloud.
|
Standalone local GREE air-conditioner controller written in Rust. It runs on a regular Linux host or an LXC container and provides a mobile-first web interface without depending on the vendor cloud.
|
||||||
|
|
||||||
Current version: **0.5.4**.
|
Current version: **0.5.5**.
|
||||||
|
|
||||||
## Highlights
|
## Highlights
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ See [`BUILD_REPORT.md`](BUILD_REPORT.md) for package validation details and [`do
|
|||||||
On Debian, Ubuntu or an LXC container:
|
On Debian, Ubuntu or an LXC container:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
unzip gree-controller-v0.5.4.zip
|
unzip gree-controller-v0.5.5.zip
|
||||||
cd gree-controller
|
cd gree-controller
|
||||||
chmod +x scripts/*.sh
|
chmod +x scripts/*.sh
|
||||||
./scripts/dev.sh
|
./scripts/dev.sh
|
||||||
@@ -177,6 +177,8 @@ Use `--skip-tests` with `install.sh` or `update.sh` only when you explicitly wan
|
|||||||
|
|
||||||
Settings changed from the web panel are stored in SQLite. Explicit history/debug/Influx environment variables override their persisted values at process startup. Supplying an Influx URL enables the archive unless `GREE_CONTROLLER_INFLUX_ENABLED=false` is explicitly set. `GREE_CONTROLLER_APP_TOKEN` is loaded at process startup.
|
Settings changed from the web panel are stored in SQLite. Explicit history/debug/Influx environment variables override their persisted values at process startup. Supplying an Influx URL enables the archive unless `GREE_CONTROLLER_INFLUX_ENABLED=false` is explicitly set. `GREE_CONTROLLER_APP_TOKEN` is loaded at process startup.
|
||||||
|
|
||||||
|
Web UI organization: **Settings** contains controller/application and technical storage/debug options, while **Night mode** and **Home Assistant / Sensors** are separate pages under More. Disabled zones continue reporting room temperature even though thermostat control is stopped.
|
||||||
|
|
||||||
## Climate history
|
## Climate history
|
||||||
|
|
||||||
History is collected independently for physical GREE devices, control zones, and configured Home Assistant temperature sensors. Device samples continue to use the long-standing `readings` table, so upgrading does not require waiting for a new zone-history table to fill. When rich `zone_readings` are not available yet, the API automatically reconstructs a compatible zone timeline from existing device readings.
|
History is collected independently for physical GREE devices, control zones, and configured Home Assistant temperature sensors. Device samples continue to use the long-standing `readings` table, so upgrading does not require waiting for a new zone-history table to fill. When rich `zone_readings` are not available yet, the API automatically reconstructs a compatible zone timeline from existing device readings.
|
||||||
@@ -234,9 +236,9 @@ There are two independent HA directions:
|
|||||||
1. **HA as an optional sensor source**: the Rust controller can read a selected HA sensor and use it for a zone.
|
1. **HA as an optional sensor source**: the Rust controller can read a selected HA sensor and use it for a zone.
|
||||||
2. **HA as a client of GREE Controller**: the included custom integration creates `climate` entities whose commands are sent to this Rust application.
|
2. **HA as a client of GREE Controller**: the included custom integration creates `climate` entities whose commands are sent to this Rust application.
|
||||||
|
|
||||||
For the HA client connection, open **Settings -> Home Assistant integration access** in GREE Controller and press **Create new token**. The secret is shown once. Paste that token into the Home Assistant `GREE Controller` integration together with the controller URL. Managed HA tokens are stored as SHA-256 hashes and are scoped to the dedicated HA device/control-plan/zone-control API; they cannot change controller settings or manage other tokens.
|
For the HA client connection, open **More -> Home Assistant / Sensors** in GREE Controller and press **Create new token**. The secret is shown once. Paste that token into the Home Assistant `GREE Controller` integration together with the controller URL. Managed HA tokens are stored as SHA-256 hashes and are scoped to the dedicated HA device/control-plan/zone-control API; they cannot change controller settings or manage other tokens.
|
||||||
|
|
||||||
The outbound HA sensor client also supports local HTTPS endpoints such as `https://10.87.65.2`. If the server uses a self-signed, expired or hostname-mismatched certificate, enable **Settings -> Allow invalid/self-signed HTTPS certificate**. This disables certificate/hostname validation only for the controller's outbound Home Assistant sensor client and should only be used on a trusted LAN.
|
The outbound HA sensor client also supports local HTTPS endpoints such as `https://192.168.50.25`. If the server uses a self-signed, expired or hostname-mismatched certificate, enable **Home Assistant / Sensors -> Allow invalid/self-signed HTTPS certificate**. This disables certificate/hostname validation only for the controller's outbound Home Assistant sensor client and should only be used on a trusted LAN.
|
||||||
|
|
||||||
The second option is designed to replace the built-in/default GREE integration without changing automation/dashboard references.
|
The second option is designed to replace the built-in/default GREE integration without changing automation/dashboard references.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ BASE='http://127.0.0.1:8787'
|
|||||||
|
|
||||||
## Home Assistant access tokens
|
## Home Assistant access tokens
|
||||||
|
|
||||||
Create and revoke integration tokens from the controller Web UI under **Settings -> Home Assistant integration access**. The clear-text secret is returned only once and the SQLite database stores only its SHA-256 hash.
|
Create and revoke integration tokens from the controller Web UI under **More -> Home Assistant / Sensors**. The clear-text secret is returned only once and the SQLite database stores only its SHA-256 hash.
|
||||||
|
|
||||||
Administrator endpoints:
|
Administrator endpoints:
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ Copy the directory into your HA configuration:
|
|||||||
/config/custom_components/gree_controller/
|
/config/custom_components/gree_controller/
|
||||||
```
|
```
|
||||||
|
|
||||||
Before adding the integration, open the standalone controller Web UI and go to **Settings -> Home Assistant integration access -> Create new token**. Copy the generated secret; it is shown only once.
|
Before adding the integration, open the standalone controller Web UI and go to **More -> Home Assistant / Sensors -> Create new token**. Copy the generated secret; it is shown only once.
|
||||||
|
|
||||||
Restart Home Assistant, then open **Settings -> Devices & services -> Add integration -> GREE Controller** and enter only:
|
Restart Home Assistant, then open **Settings -> Devices & services -> Add integration -> GREE Controller** and enter only:
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"domain": "gree_controller",
|
"domain": "gree_controller",
|
||||||
"name": "GREE Controller",
|
"name": "GREE Controller",
|
||||||
"version": "0.5.4",
|
"version": "0.5.5",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
|
|||||||
+9
-1
@@ -490,6 +490,14 @@
|
|||||||
"devices.xfan": "X-FAN",
|
"devices.xfan": "X-FAN",
|
||||||
"devices.health": "Health",
|
"devices.health": "Health",
|
||||||
"devices.air": "Air",
|
"devices.air": "Air",
|
||||||
"devices.sleep": "Sleep"
|
"devices.sleep": "Sleep",
|
||||||
|
"nav.nightMode": "Night mode",
|
||||||
|
"nav.homeAssistant": "Home Assistant / Sensors",
|
||||||
|
"night.eyebrow": "Comfort",
|
||||||
|
"night.description": "Configure quieter thermostat behavior for sleeping hours without mixing it with application settings.",
|
||||||
|
"night.saveHint": "Save night-mode changes.",
|
||||||
|
"homeAssistant.eyebrow": "Integration",
|
||||||
|
"homeAssistant.description": "Connection, temperature sensors, friendly aliases and access tokens for the Home Assistant integration.",
|
||||||
|
"homeAssistant.saveHint": "Save Home Assistant and sensor changes."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-1
@@ -490,6 +490,14 @@
|
|||||||
"devices.xfan": "X-FAN",
|
"devices.xfan": "X-FAN",
|
||||||
"devices.health": "Health",
|
"devices.health": "Health",
|
||||||
"devices.air": "Air",
|
"devices.air": "Air",
|
||||||
"devices.sleep": "Sleep"
|
"devices.sleep": "Sleep",
|
||||||
|
"nav.nightMode": "Tryb nocny",
|
||||||
|
"nav.homeAssistant": "Home Assistant / Czujniki",
|
||||||
|
"night.eyebrow": "Komfort",
|
||||||
|
"night.description": "Ustaw cichszą pracę termostatu w godzinach nocnych bez mieszania tych opcji z ustawieniami aplikacji.",
|
||||||
|
"night.saveHint": "Zapisz zmiany trybu nocnego.",
|
||||||
|
"homeAssistant.eyebrow": "Integracja",
|
||||||
|
"homeAssistant.description": "Połączenie, czujniki temperatury, przyjazne aliasy i tokeny dostępu dla integracji Home Assistant.",
|
||||||
|
"homeAssistant.saveHint": "Zapisz ustawienia Home Assistant i czujników."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-2
@@ -354,8 +354,6 @@ async fn control_zones(state: &AppState) -> Result<()> {
|
|||||||
let night_active = night_mode_active(&settings.night_mode, Local::now().time());
|
let night_active = night_mode_active(&settings.night_mode, Local::now().time());
|
||||||
|
|
||||||
for mut zone in state.db.list_zones()? {
|
for mut zone in state.db.list_zones()? {
|
||||||
if !zone.enabled { continue; }
|
|
||||||
|
|
||||||
if zone.manual_override_until.map(|until| until <= Utc::now()).unwrap_or(false) {
|
if zone.manual_override_until.map(|until| until <= Utc::now()).unwrap_or(false) {
|
||||||
zone.manual_preset = None;
|
zone.manual_preset = None;
|
||||||
zone.manual_setpoint = None;
|
zone.manual_setpoint = None;
|
||||||
@@ -419,6 +417,17 @@ async fn control_zones(state: &AppState) -> Result<()> {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Disabled zones still refresh and publish their room temperature. Disabling a
|
||||||
|
// thermostat stops control actions, but it must not make the room sensor disappear
|
||||||
|
// from the dashboard or zone view.
|
||||||
|
if !zone.enabled {
|
||||||
|
zone.demand = false;
|
||||||
|
record_zone_history(state, &zone, outdoor_temperature, settings.poll_interval_seconds);
|
||||||
|
state.db.save_zone(&zone)?;
|
||||||
|
state.broadcast("zone.updated", serde_json::to_value(&zone)?);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Global Off is the only normal path that intentionally powers units down.
|
// Global Off is the only normal path that intentionally powers units down.
|
||||||
if effective_mode == "off" {
|
if effective_mode == "off" {
|
||||||
zone.active_preset = "off".into();
|
zone.active_preset = "off".into();
|
||||||
|
|||||||
+158
-38
@@ -213,6 +213,8 @@ function renderAll() {
|
|||||||
renderAccessTokens();
|
renderAccessTokens();
|
||||||
fillSelects();
|
fillSelects();
|
||||||
renderSettings();
|
renderSettings();
|
||||||
|
renderNightSettings();
|
||||||
|
renderHomeAssistantSettings();
|
||||||
renderDebugOverlay();
|
renderDebugOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -565,6 +567,7 @@ function zoneCard(zone, detailed = true) {
|
|||||||
const sensorDetails = zone.sensor_source === 'device'
|
const sensorDetails = zone.sensor_source === 'device'
|
||||||
? `${tr('zones.greeTemp')}: ${fmtTemp(zone.device_temperature ?? device?.current_temperature)}`
|
? `${tr('zones.greeTemp')}: ${fmtTemp(zone.device_temperature ?? device?.current_temperature)}`
|
||||||
: `${tr('zones.greeTemp')}: ${fmtTemp(zone.device_temperature)} · ${tr('zones.externalTemp')}: ${fmtTemp(zone.external_temperature)} · ${tr('zones.usedSource')}: ${zoneControlSourceLabel(zone.control_temperature_source)}`;
|
: `${tr('zones.greeTemp')}: ${fmtTemp(zone.device_temperature)} · ${tr('zones.externalTemp')}: ${fmtTemp(zone.external_temperature)} · ${tr('zones.usedSource')}: ${zoneControlSourceLabel(zone.control_temperature_source)}`;
|
||||||
|
const roomTemperature = zone.current_temperature ?? zone.device_temperature ?? device?.current_temperature;
|
||||||
const target = Number(zone.effective_setpoint ?? zone.setpoint);
|
const target = Number(zone.effective_setpoint ?? zone.setpoint);
|
||||||
const manual = zone.manual_preset || 'auto';
|
const manual = zone.manual_preset || 'auto';
|
||||||
const mode = zone.inherit_house_mode ? 'house' : zone.mode;
|
const mode = zone.inherit_house_mode ? 'house' : zone.mode;
|
||||||
@@ -574,7 +577,7 @@ function zoneCard(zone, detailed = true) {
|
|||||||
: `<button type="button" class="zone-enable-toggle ${zone.enabled ? 'active' : ''}" data-action="zone-enabled" data-id="${esc(zone.id)}" data-value="${zone.enabled ? 'false' : 'true'}" aria-label="${esc(tr(zone.enabled ? 'zones.disable' : 'zones.enable'))}"><span>${zone.enabled ? '✓' : '○'}</span>${esc(state)}</button>`;
|
: `<button type="button" class="zone-enable-toggle ${zone.enabled ? 'active' : ''}" data-action="zone-enabled" data-id="${esc(zone.id)}" data-value="${zone.enabled ? 'false' : 'true'}" aria-label="${esc(tr(zone.enabled ? 'zones.disable' : 'zones.enable'))}"><span>${zone.enabled ? '✓' : '○'}</span>${esc(state)}</button>`;
|
||||||
return `<article class="list-card zone-thermostat ${zone.demand ? 'demanding' : ''} ${zone.enabled ? '' : 'zone-disabled'}">
|
return `<article class="list-card zone-thermostat ${zone.demand ? 'demanding' : ''} ${zone.enabled ? '' : 'zone-disabled'}">
|
||||||
<div class="list-card-head"><div><h3>${esc(zone.name)}</h3><p>${esc(device?.name || tr('common.noDevice'))} · ${esc(zonePresetLabel(zone.active_preset))}</p></div>${enabledControl}</div>
|
<div class="list-card-head"><div><h3>${esc(zone.name)}</h3><p>${esc(device?.name || tr('common.noDevice'))} · ${esc(zonePresetLabel(zone.active_preset))}</p></div>${enabledControl}</div>
|
||||||
<div class="thermostat-main"><div><small>${esc(tr('zones.measurement'))}</small><strong>${fmtTemp(zone.current_temperature)}</strong></div><div class="temperature-control compact"><button data-action="zone-temperature" data-id="${esc(zone.id)}" data-delta="-0.5">−</button><div class="target-temp compact">${Number.isFinite(target)?target.toFixed(1):'--'}<small>°C</small></div><button data-action="zone-temperature" data-id="${esc(zone.id)}" data-delta="0.5">+</button></div><div><small>${esc(tr('zones.deviceTarget'))}</small><strong>${fmtTemp(zone.device_setpoint)}</strong></div></div>
|
<div class="thermostat-main"><div><small>${esc(tr('zones.measurement'))}</small><strong>${fmtTemp(roomTemperature)}</strong></div><div class="temperature-control compact"><button data-action="zone-temperature" data-id="${esc(zone.id)}" data-delta="-0.5">−</button><div class="target-temp compact">${Number.isFinite(target)?target.toFixed(1):'--'}<small>°C</small></div><button data-action="zone-temperature" data-id="${esc(zone.id)}" data-delta="0.5">+</button></div><div><small>${esc(tr('zones.deviceTarget'))}</small><strong>${fmtTemp(zone.device_setpoint)}</strong></div></div>
|
||||||
<div class="preset-row">
|
<div class="preset-row">
|
||||||
${['auto','comfort','sleep','away'].map(preset=>`<button class="${manual===preset?'active':''}" data-action="zone-preset" data-id="${esc(zone.id)}" data-value="${preset}">${esc(preset==='sleep'?tr('zones.sleepNow'):zonePresetLabel(preset))}</button>`).join('')}
|
${['auto','comfort','sleep','away'].map(preset=>`<button class="${manual===preset?'active':''}" data-action="zone-preset" data-id="${esc(zone.id)}" data-value="${preset}">${esc(preset==='sleep'?tr('zones.sleepNow'):zonePresetLabel(preset))}</button>`).join('')}
|
||||||
</div>
|
</div>
|
||||||
@@ -663,6 +666,7 @@ function renderLogRetention() {
|
|||||||
function renderSettings() {
|
function renderSettings() {
|
||||||
if (!app.settings) return;
|
if (!app.settings) return;
|
||||||
const form = $('#settingsForm');
|
const form = $('#settingsForm');
|
||||||
|
if (!form) return;
|
||||||
form.controller_id.value = app.settings.controller_id || '';
|
form.controller_id.value = app.settings.controller_id || '';
|
||||||
form.poll_interval_seconds.value = app.settings.poll_interval_seconds || 15;
|
form.poll_interval_seconds.value = app.settings.poll_interval_seconds || 15;
|
||||||
form.zone_interval_seconds.value = app.settings.zone_interval_seconds || 5;
|
form.zone_interval_seconds.value = app.settings.zone_interval_seconds || 5;
|
||||||
@@ -673,12 +677,6 @@ function renderSettings() {
|
|||||||
form.event_log_retention_days.value = app.settings.event_log_retention_days || 30;
|
form.event_log_retention_days.value = app.settings.event_log_retention_days || 30;
|
||||||
form.history_compaction_enabled.checked = app.settings.history_compaction_enabled !== false;
|
form.history_compaction_enabled.checked = app.settings.history_compaction_enabled !== false;
|
||||||
form.suppress_device_beep.checked = !!app.settings.suppress_device_beep;
|
form.suppress_device_beep.checked = !!app.settings.suppress_device_beep;
|
||||||
form.night_mode_enabled.checked = !!app.settings.night_mode?.enabled;
|
|
||||||
form.night_mode_start.value = app.settings.night_mode?.start_time || '22:00';
|
|
||||||
form.night_mode_end.value = app.settings.night_mode?.end_time || '06:00';
|
|
||||||
form.night_mode_max_fan_speed.value = String(app.settings.night_mode?.max_fan_speed || 1);
|
|
||||||
form.night_mode_force_quiet.checked = app.settings.night_mode?.force_quiet !== false;
|
|
||||||
form.night_mode_native_sleep.checked = app.settings.night_mode?.use_native_sleep !== false;
|
|
||||||
form.influx_enabled.checked = !!app.settings.influxdb?.enabled;
|
form.influx_enabled.checked = !!app.settings.influxdb?.enabled;
|
||||||
form.influx_version.value = String(app.settings.influxdb?.version || '2');
|
form.influx_version.value = String(app.settings.influxdb?.version || '2');
|
||||||
form.influx_threshold_days.value = app.settings.influxdb?.history_threshold_days || 30;
|
form.influx_threshold_days.value = app.settings.influxdb?.history_threshold_days || 30;
|
||||||
@@ -693,6 +691,27 @@ function renderSettings() {
|
|||||||
form.influx_token.placeholder = app.settings.influxdb?.token_configured ? tr('settings.secretSaved') : tr('settings.secretKeep');
|
form.influx_token.placeholder = app.settings.influxdb?.token_configured ? tr('settings.secretSaved') : tr('settings.secretKeep');
|
||||||
form.debug_overlay_enabled.checked = !!app.settings.debug?.overlay_enabled;
|
form.debug_overlay_enabled.checked = !!app.settings.debug?.overlay_enabled;
|
||||||
form.debug_gree_frames.checked = !!app.settings.debug?.gree_frames;
|
form.debug_gree_frames.checked = !!app.settings.debug?.gree_frames;
|
||||||
|
updateInfluxFields();
|
||||||
|
renderLogRetention();
|
||||||
|
$('#systemInfo').innerHTML = `<h3>${esc(tr('settings.systemState'))}</h3><div>${esc(tr('settings.version'))}: <strong>${esc(app.system.version || '—')}</strong></div><div>${esc(tr('settings.uptime'))}: <strong>${esc(formatDuration(app.system.uptime_seconds || 0))}</strong></div><div>${esc(tr('settings.apiAuth'))}: <strong>${esc(app.system.auth_required ? tr('settings.enabled') : tr('settings.disabled'))}</strong></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderNightSettings() {
|
||||||
|
if (!app.settings) return;
|
||||||
|
const form = $('#nightModeForm');
|
||||||
|
if (!form) return;
|
||||||
|
form.night_mode_enabled.checked = !!app.settings.night_mode?.enabled;
|
||||||
|
form.night_mode_start.value = app.settings.night_mode?.start_time || '22:00';
|
||||||
|
form.night_mode_end.value = app.settings.night_mode?.end_time || '06:00';
|
||||||
|
form.night_mode_max_fan_speed.value = String(app.settings.night_mode?.max_fan_speed || 1);
|
||||||
|
form.night_mode_force_quiet.checked = app.settings.night_mode?.force_quiet !== false;
|
||||||
|
form.night_mode_native_sleep.checked = app.settings.night_mode?.use_native_sleep !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderHomeAssistantSettings() {
|
||||||
|
if (!app.settings) return;
|
||||||
|
const form = $('#homeAssistantForm');
|
||||||
|
if (!form) return;
|
||||||
form.ha_url.value = app.settings.home_assistant?.url || '';
|
form.ha_url.value = app.settings.home_assistant?.url || '';
|
||||||
form.ha_token.value = '';
|
form.ha_token.value = '';
|
||||||
form.ha_token.placeholder = app.settings.home_assistant?.token_configured ? tr('settings.haTokenSaved') : tr('settings.haLongLivedToken');
|
form.ha_token.placeholder = app.settings.home_assistant?.token_configured ? tr('settings.haTokenSaved') : tr('settings.haLongLivedToken');
|
||||||
@@ -700,10 +719,8 @@ function renderSettings() {
|
|||||||
form.ha_outdoor_entity_id.value = app.settings.home_assistant?.outdoor_entity_id || '';
|
form.ha_outdoor_entity_id.value = app.settings.home_assistant?.outdoor_entity_id || '';
|
||||||
form.ha_allow_invalid_tls.checked = !!app.settings.home_assistant?.allow_invalid_tls;
|
form.ha_allow_invalid_tls.checked = !!app.settings.home_assistant?.allow_invalid_tls;
|
||||||
form.outdoor_assist_enabled.checked = !!app.settings.outdoor_assist_enabled;
|
form.outdoor_assist_enabled.checked = !!app.settings.outdoor_assist_enabled;
|
||||||
updateInfluxFields();
|
|
||||||
renderSensorAliases();
|
renderSensorAliases();
|
||||||
renderLogRetention();
|
renderAccessTokens();
|
||||||
$('#systemInfo').innerHTML = `<h3>${esc(tr('settings.systemState'))}</h3><div>${esc(tr('settings.version'))}: <strong>${esc(app.system.version || '—')}</strong></div><div>${esc(tr('settings.uptime'))}: <strong>${esc(formatDuration(app.system.uptime_seconds || 0))}</strong></div><div>${esc(tr('settings.apiAuth'))}: <strong>${esc(app.system.auth_required ? tr('settings.enabled') : tr('settings.disabled'))}</strong></div>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateInfluxFields() {
|
function updateInfluxFields() {
|
||||||
@@ -747,7 +764,7 @@ function formatDuration(seconds) {
|
|||||||
return `${days ? `${days}d ` : ''}${hours}h ${minutes}m`;
|
return `${days ? `${days}d ` : ''}${hours}h ${minutes}m`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const VIEW_ROUTES = {dashboard:'/dashboard', devices:'/devices', zones:'/zones', schedules:'/schedules', automations:'/automations', simulation:'/simulation', settings:'/settings', logs:'/events'};
|
const VIEW_ROUTES = {dashboard:'/dashboard', devices:'/devices', zones:'/zones', schedules:'/schedules', automations:'/automations', simulation:'/simulation', night:'/night-mode', homeassistant:'/home-assistant', settings:'/settings', logs:'/events'};
|
||||||
const HISTORY_TABS = ['overview','zones','devices','sensors','custom'];
|
const HISTORY_TABS = ['overview','zones','devices','sensors','custom'];
|
||||||
|
|
||||||
function currentHistoryPath() {
|
function currentHistoryPath() {
|
||||||
@@ -772,7 +789,7 @@ function updateBrowserUrl(path, replace=false) {
|
|||||||
function showView(name, {push=true, scroll=true}={}) {
|
function showView(name, {push=true, scroll=true}={}) {
|
||||||
app.currentView = name;
|
app.currentView = name;
|
||||||
$$('.view').forEach(view => view.classList.toggle('active', view.dataset.view === name));
|
$$('.view').forEach(view => view.classList.toggle('active', view.dataset.view === name));
|
||||||
$$('.bottom-nav button').forEach(button => button.classList.toggle('active', button.dataset.nav === name || (button.dataset.nav === 'more' && ['schedules','automations','simulation','settings','logs'].includes(name))));
|
$$('.bottom-nav button').forEach(button => button.classList.toggle('active', button.dataset.nav === name || (button.dataset.nav === 'more' && ['schedules','automations','simulation','night','homeassistant','settings','logs'].includes(name))));
|
||||||
if (push) updateBrowserUrl(name === 'history' ? currentHistoryPath() : (VIEW_ROUTES[name] || '/dashboard'));
|
if (push) updateBrowserUrl(name === 'history' ? currentHistoryPath() : (VIEW_ROUTES[name] || '/dashboard'));
|
||||||
if (scroll) window.scrollTo({top: 0, behavior: 'smooth'});
|
if (scroll) window.scrollTo({top: 0, behavior: 'smooth'});
|
||||||
if (name === 'history') { renderHistoryNavigation(); loadHistory(); }
|
if (name === 'history') { renderHistoryNavigation(); loadHistory(); }
|
||||||
@@ -1302,7 +1319,7 @@ function connectWebSocket() {
|
|||||||
else if (message.event === 'device.deleted') { app.devices=app.devices.filter(v=>v.id!==data.id); renderAll(); scheduleControlPlanLoad(); }
|
else if (message.event === 'device.deleted') { app.devices=app.devices.filter(v=>v.id!==data.id); renderAll(); scheduleControlPlanLoad(); }
|
||||||
else if (message.event === 'devices.discovered') { (data.devices||[]).forEach(updateDevice); renderAll(); scheduleControlPlanLoad(); }
|
else if (message.event === 'devices.discovered') { (data.devices||[]).forEach(updateDevice); renderAll(); scheduleControlPlanLoad(); }
|
||||||
else if (message.event === 'zone.updated') { const i=app.zones.findIndex(v=>v.id===data.id); if(i>=0) app.zones[i]=data; else app.zones.push(data); renderSummary(); renderHouseClimate(); renderZones(); scheduleControlPlanLoad(); }
|
else if (message.event === 'zone.updated') { const i=app.zones.findIndex(v=>v.id===data.id); if(i>=0) app.zones[i]=data; else app.zones.push(data); renderSummary(); renderHouseClimate(); renderZones(); scheduleControlPlanLoad(); }
|
||||||
else if (message.event === 'settings.updated') { app.settings=data; app.sensorAliases={...(app.settings?.home_assistant?.sensor_aliases||{})}; renderSettings(); renderHouseClimate(); renderDebugOverlay(); if(app.settings?.debug?.overlay_enabled) loadDebugBacklog(); scheduleControlPlanLoad(); }
|
else if (message.event === 'settings.updated') { app.settings=data; app.sensorAliases={...(app.settings?.home_assistant?.sensor_aliases||{})}; renderSettings(); renderNightSettings(); renderHomeAssistantSettings(); renderHouseClimate(); renderDebugOverlay(); if(app.settings?.debug?.overlay_enabled) loadDebugBacklog(); scheduleControlPlanLoad(); }
|
||||||
else if (message.event === 'debug.settings') { app.settings = app.settings || {}; app.settings.debug = data; renderSettings(); renderDebugOverlay(); if(data.overlay_enabled) loadDebugBacklog(); }
|
else if (message.event === 'debug.settings') { app.settings = app.settings || {}; app.settings.debug = data; renderSettings(); renderDebugOverlay(); if(data.overlay_enabled) loadDebugBacklog(); }
|
||||||
else if (message.event === 'outdoor.updated') { app.outdoorTemperature=Number.isFinite(Number(data.temperature))?Number(data.temperature):null; renderHouseClimate(); scheduleControlPlanLoad(); }
|
else if (message.event === 'outdoor.updated') { app.outdoorTemperature=Number.isFinite(Number(data.temperature))?Number(data.temperature):null; renderHouseClimate(); scheduleControlPlanLoad(); }
|
||||||
else if (message.event === 'gree.frame') { if(app.settings?.debug?.overlay_enabled) debugLine('GREE', `${data.direction || '?'} ${data.protocol_version || ''}`, data.device_name || data.device_id || data.target || '', message.timestamp, data.payload); }
|
else if (message.event === 'gree.frame') { if(app.settings?.debug?.overlay_enabled) debugLine('GREE', `${data.direction || '?'} ${data.protocol_version || ''}`, data.device_name || data.device_id || data.target || '', message.timestamp, data.payload); }
|
||||||
@@ -1463,38 +1480,141 @@ $('#automationForm').addEventListener('submit', async event => {
|
|||||||
catch(error){toast(error.message,true);}
|
catch(error){toast(error.message,true);}
|
||||||
});
|
});
|
||||||
|
|
||||||
function settingsBodyFromForm(form) {
|
function currentSettingsBody() {
|
||||||
const raw=Object.fromEntries(new FormData(form));
|
const settings = app.settings || {};
|
||||||
|
const influx = settings.influxdb || {};
|
||||||
|
const ha = settings.home_assistant || {};
|
||||||
return {
|
return {
|
||||||
controller_id:raw.controller_id, simulator_enabled:form.simulator_enabled.checked,
|
controller_id: settings.controller_id || 'gree-controller',
|
||||||
poll_interval_seconds:Number(raw.poll_interval_seconds), zone_interval_seconds:Number(raw.zone_interval_seconds),
|
simulator_enabled: !!settings.simulator_enabled,
|
||||||
discovery_timeout_ms:Number(raw.discovery_timeout_ms), discovery_broadcast:raw.discovery_broadcast,
|
poll_interval_seconds: Number(settings.poll_interval_seconds || 15),
|
||||||
house_mode:app.settings?.house_mode||'cool', control_strategy:'setpoint', outdoor_assist_enabled:form.outdoor_assist_enabled.checked,
|
zone_interval_seconds: Number(settings.zone_interval_seconds || 5),
|
||||||
history_retention_days:Number(raw.history_retention_days), history_compaction_enabled:form.history_compaction_enabled.checked,
|
discovery_timeout_ms: Number(settings.discovery_timeout_ms || 3000),
|
||||||
event_log_retention_days:Number(raw.event_log_retention_days),
|
discovery_broadcast: settings.discovery_broadcast || '255.255.255.255:7000',
|
||||||
suppress_device_beep:form.suppress_device_beep.checked,
|
house_mode: settings.house_mode || 'cool',
|
||||||
night_mode:{
|
control_strategy: 'setpoint',
|
||||||
enabled:form.night_mode_enabled.checked,start_time:raw.night_mode_start,end_time:raw.night_mode_end,
|
outdoor_assist_enabled: !!settings.outdoor_assist_enabled,
|
||||||
max_fan_speed:Number(raw.night_mode_max_fan_speed),force_quiet:form.night_mode_force_quiet.checked,
|
history_retention_days: Number(settings.history_retention_days || 30),
|
||||||
use_native_sleep:form.night_mode_native_sleep.checked,
|
history_compaction_enabled: settings.history_compaction_enabled !== false,
|
||||||
|
event_log_retention_days: Number(settings.event_log_retention_days || 30),
|
||||||
|
suppress_device_beep: !!settings.suppress_device_beep,
|
||||||
|
night_mode: {
|
||||||
|
enabled: !!settings.night_mode?.enabled,
|
||||||
|
start_time: settings.night_mode?.start_time || '22:00',
|
||||||
|
end_time: settings.night_mode?.end_time || '06:00',
|
||||||
|
max_fan_speed: Number(settings.night_mode?.max_fan_speed || 1),
|
||||||
|
force_quiet: settings.night_mode?.force_quiet !== false,
|
||||||
|
use_native_sleep: settings.night_mode?.use_native_sleep !== false,
|
||||||
},
|
},
|
||||||
influxdb:{
|
influxdb: {
|
||||||
enabled:form.influx_enabled.checked, version:raw.influx_version, url:raw.influx_url,
|
enabled: !!influx.enabled,
|
||||||
database:raw.influx_database, username:raw.influx_username, password:raw.influx_password,
|
version: String(influx.version || '2'),
|
||||||
org:raw.influx_org, bucket:raw.influx_bucket, token:raw.influx_token,
|
url: influx.url || '',
|
||||||
history_threshold_days:Number(raw.influx_threshold_days),
|
database: influx.database || 'gree_controller',
|
||||||
|
username: influx.username || '',
|
||||||
|
password: '',
|
||||||
|
org: influx.org || '',
|
||||||
|
bucket: influx.bucket || 'gree_controller',
|
||||||
|
token: '',
|
||||||
|
history_threshold_days: Number(influx.history_threshold_days || 30),
|
||||||
|
},
|
||||||
|
debug: {
|
||||||
|
overlay_enabled: !!settings.debug?.overlay_enabled,
|
||||||
|
gree_frames: !!settings.debug?.gree_frames,
|
||||||
|
},
|
||||||
|
home_assistant: {
|
||||||
|
url: ha.url || '',
|
||||||
|
token: '',
|
||||||
|
default_entity_id: ha.default_entity_id || '',
|
||||||
|
outdoor_entity_id: ha.outdoor_entity_id || '',
|
||||||
|
allow_invalid_tls: !!ha.allow_invalid_tls,
|
||||||
|
sensor_aliases: {...(ha.sensor_aliases || {})},
|
||||||
},
|
},
|
||||||
debug:{overlay_enabled:form.debug_overlay_enabled.checked, gree_frames:form.debug_gree_frames.checked},
|
|
||||||
home_assistant:{url:raw.ha_url,token:raw.ha_token,default_entity_id:raw.ha_entity_id,outdoor_entity_id:raw.ha_outdoor_entity_id,allow_invalid_tls:form.ha_allow_invalid_tls.checked,sensor_aliases:{...(app.sensorAliases||{})}},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveSettingsForm(form, notify=true) {
|
function settingsBodyFromForm(form) {
|
||||||
app.settings=await api('/api/settings',{method:'PUT',body:settingsBodyFromForm(form)}); app.sensorAliases={...(app.settings?.home_assistant?.sensor_aliases||{})}; renderSettings(); renderHouseClimate(); renderDebugOverlay(); scheduleControlPlanLoad(); if(app.settings?.debug?.overlay_enabled) loadDebugBacklog(); if(notify) toast(tr('common.saved')); return app.settings;
|
const raw = Object.fromEntries(new FormData(form));
|
||||||
|
const body = currentSettingsBody();
|
||||||
|
body.controller_id = raw.controller_id;
|
||||||
|
body.simulator_enabled = form.simulator_enabled.checked;
|
||||||
|
body.poll_interval_seconds = Number(raw.poll_interval_seconds);
|
||||||
|
body.zone_interval_seconds = Number(raw.zone_interval_seconds);
|
||||||
|
body.discovery_timeout_ms = Number(raw.discovery_timeout_ms);
|
||||||
|
body.discovery_broadcast = raw.discovery_broadcast;
|
||||||
|
body.history_retention_days = Number(raw.history_retention_days);
|
||||||
|
body.history_compaction_enabled = form.history_compaction_enabled.checked;
|
||||||
|
body.event_log_retention_days = Number(raw.event_log_retention_days);
|
||||||
|
body.suppress_device_beep = form.suppress_device_beep.checked;
|
||||||
|
body.influxdb = {
|
||||||
|
enabled: form.influx_enabled.checked, version: raw.influx_version, url: raw.influx_url,
|
||||||
|
database: raw.influx_database, username: raw.influx_username, password: raw.influx_password,
|
||||||
|
org: raw.influx_org, bucket: raw.influx_bucket, token: raw.influx_token,
|
||||||
|
history_threshold_days: Number(raw.influx_threshold_days),
|
||||||
|
};
|
||||||
|
body.debug = {overlay_enabled: form.debug_overlay_enabled.checked, gree_frames: form.debug_gree_frames.checked};
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
function nightSettingsBodyFromForm(form) {
|
||||||
|
const raw = Object.fromEntries(new FormData(form));
|
||||||
|
const body = currentSettingsBody();
|
||||||
|
body.night_mode = {
|
||||||
|
enabled: form.night_mode_enabled.checked,
|
||||||
|
start_time: raw.night_mode_start,
|
||||||
|
end_time: raw.night_mode_end,
|
||||||
|
max_fan_speed: Number(raw.night_mode_max_fan_speed),
|
||||||
|
force_quiet: form.night_mode_force_quiet.checked,
|
||||||
|
use_native_sleep: form.night_mode_native_sleep.checked,
|
||||||
|
};
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
function homeAssistantSettingsBodyFromForm(form) {
|
||||||
|
const raw = Object.fromEntries(new FormData(form));
|
||||||
|
const body = currentSettingsBody();
|
||||||
|
body.outdoor_assist_enabled = form.outdoor_assist_enabled.checked;
|
||||||
|
body.home_assistant = {
|
||||||
|
url: raw.ha_url,
|
||||||
|
token: raw.ha_token,
|
||||||
|
default_entity_id: raw.ha_entity_id,
|
||||||
|
outdoor_entity_id: raw.ha_outdoor_entity_id,
|
||||||
|
allow_invalid_tls: form.ha_allow_invalid_tls.checked,
|
||||||
|
sensor_aliases: {...(app.sensorAliases || {})},
|
||||||
|
};
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveRuntimeSettings(body, notify = true) {
|
||||||
|
app.settings = await api('/api/settings', {method:'PUT', body});
|
||||||
|
app.sensorAliases = {...(app.settings?.home_assistant?.sensor_aliases || {})};
|
||||||
|
renderSettings();
|
||||||
|
renderNightSettings();
|
||||||
|
renderHomeAssistantSettings();
|
||||||
|
renderHouseClimate();
|
||||||
|
renderDebugOverlay();
|
||||||
|
scheduleControlPlanLoad();
|
||||||
|
if (app.settings?.debug?.overlay_enabled) loadDebugBacklog();
|
||||||
|
if (notify) toast(tr('common.saved'));
|
||||||
|
return app.settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#settingsForm').addEventListener('submit', async event => {
|
$('#settingsForm').addEventListener('submit', async event => {
|
||||||
event.preventDefault(); try { await saveSettingsForm(event.currentTarget, true); } catch(error){toast(error.message,true);}
|
event.preventDefault();
|
||||||
|
try { await saveRuntimeSettings(settingsBodyFromForm(event.currentTarget), true); }
|
||||||
|
catch(error) { toast(error.message, true); }
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#nightModeForm')?.addEventListener('submit', async event => {
|
||||||
|
event.preventDefault();
|
||||||
|
try { await saveRuntimeSettings(nightSettingsBodyFromForm(event.currentTarget), true); }
|
||||||
|
catch(error) { toast(error.message, true); }
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#homeAssistantForm')?.addEventListener('submit', async event => {
|
||||||
|
event.preventDefault();
|
||||||
|
try { await saveRuntimeSettings(homeAssistantSettingsBodyFromForm(event.currentTarget), true); }
|
||||||
|
catch(error) { toast(error.message, true); }
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#addSensorAlias')?.addEventListener('click', () => {
|
$('#addSensorAlias')?.addEventListener('click', () => {
|
||||||
@@ -1555,9 +1675,9 @@ $('#copyAccessToken').addEventListener('click', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$('#haTest').addEventListener('click', async () => {
|
$('#haTest').addEventListener('click', async () => {
|
||||||
const form=$('#settingsForm');
|
const form=$('#homeAssistantForm');
|
||||||
try {
|
try {
|
||||||
await saveSettingsForm(form, false);
|
await saveRuntimeSettings(homeAssistantSettingsBodyFromForm(form), false);
|
||||||
const entity = form.ha_entity_id.value || form.ha_outdoor_entity_id.value || null;
|
const entity = form.ha_entity_id.value || form.ha_outdoor_entity_id.value || null;
|
||||||
const result=await api('/api/integrations/home-assistant/test',{method:'POST',body:{entity_id:entity}});
|
const result=await api('/api/integrations/home-assistant/test',{method:'POST',body:{entity_id:entity}});
|
||||||
toast(tr('toast.haTemperature',{temperature:result.temperature_c.toFixed(1)}));
|
toast(tr('toast.haTemperature',{temperature:result.temperature_c.toFixed(1)}));
|
||||||
|
|||||||
+55
-33
@@ -135,6 +135,56 @@
|
|||||||
<div id="historyCharts" class="history-charts"></div>
|
<div id="historyCharts" class="history-charts"></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
<section class="view" data-view="night">
|
||||||
|
<div class="section-heading"><div><span class="eyebrow" data-i18n="night.eyebrow">Comfort</span><h1 data-i18n="nav.nightMode">Night mode</h1></div></div>
|
||||||
|
<p class="lead" data-i18n="night.description">Configure quieter thermostat behavior for sleeping hours without mixing it with application settings.</p>
|
||||||
|
<form class="settings-form standalone-settings-form" id="nightModeForm">
|
||||||
|
<section class="panel settings-block">
|
||||||
|
<div class="settings-block-head"><div><h3 data-i18n="settings.nightMode">Night mode</h3><p data-i18n="settings.nightModeHint">During selected hours the thermostat limits fan speed and can request Quiet on supported units.</p></div></div>
|
||||||
|
<div class="settings-grid">
|
||||||
|
<label class="check wide"><input type="checkbox" name="night_mode_enabled"> <span data-i18n="settings.nightModeEnabled">Enable night mode</span></label>
|
||||||
|
<label><span data-i18n="settings.nightStart">Start</span><input type="time" name="night_mode_start" value="22:00"></label>
|
||||||
|
<label><span data-i18n="settings.nightEnd">End</span><input type="time" name="night_mode_end" value="06:00"></label>
|
||||||
|
<label><span data-i18n="settings.nightMaxFan">Maximum fan speed</span><select name="night_mode_max_fan_speed"><option value="1" data-i18n="fan.low">Low</option><option value="2" data-i18n="fan.mediumLow">Medium-low</option><option value="3" data-i18n="fan.medium">Medium</option><option value="4" data-i18n="fan.mediumHigh">Medium-high</option><option value="5" data-i18n="fan.high">High</option></select></label>
|
||||||
|
<label class="check"><input type="checkbox" name="night_mode_force_quiet"> <span data-i18n="settings.nightForceQuiet">Use Quiet when supported</span></label>
|
||||||
|
<label class="check wide"><input type="checkbox" name="night_mode_native_sleep"> <span data-i18n="settings.nightNativeSleep">Use native Sleep when supported</span></label>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<div class="settings-save-bar"><span data-i18n="night.saveHint">Save night-mode changes.</span><button class="primary" type="submit" data-i18n="actions.save">Save</button></div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="view" data-view="homeassistant">
|
||||||
|
<div class="section-heading"><div><span class="eyebrow" data-i18n="homeAssistant.eyebrow">Integration</span><h1 data-i18n="nav.homeAssistant">Home Assistant / Sensors</h1></div></div>
|
||||||
|
<p class="lead" data-i18n="homeAssistant.description">Connection, temperature sensors, friendly aliases and access tokens for the Home Assistant integration.</p>
|
||||||
|
<form class="settings-form standalone-settings-form" id="homeAssistantForm">
|
||||||
|
<section class="panel settings-block">
|
||||||
|
<div class="settings-block-head"><div><h3 data-i18n="settings.haSensorInput">Home Assistant sensor input</h3><p data-i18n="settings.haSensorInputHint">Optional. Used when a room zone reads an external Home Assistant temperature sensor.</p></div></div>
|
||||||
|
<div class="settings-grid">
|
||||||
|
<label class="wide"><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 type="password" name="ha_token" autocomplete="new-password" data-i18n-placeholder="settings.haTokenKeep" placeholder="Leave empty to keep the saved token"></label>
|
||||||
|
<label class="wide"><span data-i18n="settings.defaultEntity">Default room entity_id</span><input name="ha_entity_id" placeholder="sensor.living_room_temperature"></label>
|
||||||
|
<label class="wide"><span data-i18n="settings.outdoorEntity">Outdoor temperature entity_id</span><input name="ha_outdoor_entity_id" placeholder="sensor.outdoor_temperature"></label>
|
||||||
|
<label class="check wide"><input type="checkbox" name="outdoor_assist_enabled"> <span data-i18n="settings.outdoorAssist">Use outdoor temperature as smart-control assist</span></label>
|
||||||
|
<label class="check wide"><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 local Home Assistant server, for example https://192.168.50.25.</p>
|
||||||
|
<div class="wide sensor-alias-manager">
|
||||||
|
<div class="settings-subhead"><div><strong data-i18n="settings.sensorAliases">Sensor aliases</strong><small data-i18n="settings.sensorAliasesHint">Friendly names are used in charts and selectors; the original entity_id remains unchanged.</small></div></div>
|
||||||
|
<div id="sensorAliasList" class="sensor-alias-list"></div>
|
||||||
|
<div class="sensor-alias-add"><input id="sensorAliasEntity" placeholder="sensor.gabinet_temperature"><input id="sensorAliasName" data-i18n-placeholder="settings.aliasPlaceholder" placeholder="Gabinet"><button type="button" class="secondary" id="addSensorAlias" data-i18n="actions.add">Add</button></div>
|
||||||
|
</div>
|
||||||
|
<div class="form-actions wide"><button type="button" class="secondary" id="haTest" data-i18n="settings.testHa">Test HA</button></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="panel settings-block">
|
||||||
|
<div class="settings-block-head"><div><h3 data-i18n="settings.haIntegrationAccess">Home Assistant integration access</h3><p data-i18n="settings.haIntegrationHint">Create a controller token and paste it into the GREE Controller integration in Home Assistant.</p></div></div>
|
||||||
|
<div class="token-manager"><div id="accessTokenList" class="token-list"></div><div class="form-actions"><button type="button" class="primary" id="createAccessToken" data-i18n="settings.newToken">Create new token</button></div></div>
|
||||||
|
</section>
|
||||||
|
<div class="settings-save-bar"><span data-i18n="homeAssistant.saveHint">Save Home Assistant and sensor changes.</span><button class="primary" type="submit" data-i18n="actions.save">Save</button></div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="view" data-view="settings">
|
<section class="view" data-view="settings">
|
||||||
<div class="section-heading"><div><span class="eyebrow" data-i18n="settings.system">System</span><h1 data-i18n="nav.settings">Settings</h1></div></div>
|
<div class="section-heading"><div><span class="eyebrow" data-i18n="settings.system">System</span><h1 data-i18n="nav.settings">Settings</h1></div></div>
|
||||||
<form class="settings-form" id="settingsForm">
|
<form class="settings-form" id="settingsForm">
|
||||||
@@ -150,17 +200,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="panel settings-block">
|
|
||||||
<div class="settings-block-head"><div><h3 data-i18n="settings.nightMode">Night mode</h3><p data-i18n="settings.nightModeHint">During selected hours the thermostat limits fan speed and can request Quiet on supported units.</p></div></div>
|
|
||||||
<div class="settings-grid">
|
|
||||||
<label class="check wide"><input type="checkbox" name="night_mode_enabled"> <span data-i18n="settings.nightModeEnabled">Enable night mode</span></label>
|
|
||||||
<label><span data-i18n="settings.nightStart">Start</span><input type="time" name="night_mode_start" value="22:00"></label>
|
|
||||||
<label><span data-i18n="settings.nightEnd">End</span><input type="time" name="night_mode_end" value="06:00"></label>
|
|
||||||
<label><span data-i18n="settings.nightMaxFan">Maximum fan speed</span><select name="night_mode_max_fan_speed"><option value="1" data-i18n="fan.low">Low</option><option value="2" data-i18n="fan.mediumLow">Medium-low</option><option value="3" data-i18n="fan.medium">Medium</option><option value="4" data-i18n="fan.mediumHigh">Medium-high</option><option value="5" data-i18n="fan.high">High</option></select></label>
|
|
||||||
<label class="check"><input type="checkbox" name="night_mode_force_quiet"> <span data-i18n="settings.nightForceQuiet">Use Quiet when supported</span></label>
|
|
||||||
<label class="check wide"><input type="checkbox" name="night_mode_native_sleep"> <span data-i18n="settings.nightNativeSleep">Use native Sleep when supported</span></label>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="panel settings-block">
|
<section class="panel settings-block">
|
||||||
<div class="settings-block-head"><div><h3 data-i18n="settings.metricsAndLogs">Metrics and logs</h3><p data-i18n="settings.compactionHint">SQLite keeps recent data locally and compacts older samples to the resolution used by charts.</p></div></div>
|
<div class="settings-block-head"><div><h3 data-i18n="settings.metricsAndLogs">Metrics and logs</h3><p data-i18n="settings.compactionHint">SQLite keeps recent data locally and compacts older samples to the resolution used by charts.</p></div></div>
|
||||||
@@ -204,29 +244,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="panel settings-block">
|
|
||||||
<div class="settings-block-head"><div><h3 data-i18n="settings.haSensorInput">Home Assistant sensor input</h3><p data-i18n="settings.haSensorInputHint">Optional. Used when a room zone reads an external Home Assistant temperature sensor.</p></div></div>
|
|
||||||
<div class="settings-grid">
|
|
||||||
<label class="wide"><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 type="password" name="ha_token" autocomplete="new-password" data-i18n-placeholder="settings.haTokenKeep" placeholder="Leave empty to keep the saved token"></label>
|
|
||||||
<label class="wide"><span data-i18n="settings.defaultEntity">Default room entity_id</span><input name="ha_entity_id" placeholder="sensor.living_room_temperature"></label>
|
|
||||||
<label class="wide"><span data-i18n="settings.outdoorEntity">Outdoor temperature entity_id</span><input name="ha_outdoor_entity_id" placeholder="sensor.outdoor_temperature"></label>
|
|
||||||
<label class="check wide"><input type="checkbox" name="outdoor_assist_enabled"> <span data-i18n="settings.outdoorAssist">Use outdoor temperature as smart-control assist</span></label>
|
|
||||||
<label class="check wide"><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 local Home Assistant server, for example https://192.168.50.25.</p>
|
|
||||||
<div class="wide sensor-alias-manager">
|
|
||||||
<div class="settings-subhead"><div><strong data-i18n="settings.sensorAliases">Sensor aliases</strong><small data-i18n="settings.sensorAliasesHint">Friendly names are used in charts and selectors; the original entity_id remains unchanged.</small></div></div>
|
|
||||||
<div id="sensorAliasList" class="sensor-alias-list"></div>
|
|
||||||
<div class="sensor-alias-add"><input id="sensorAliasEntity" placeholder="sensor.gabinet_temperature"><input id="sensorAliasName" data-i18n-placeholder="settings.aliasPlaceholder" placeholder="Gabinet"><button type="button" class="secondary" id="addSensorAlias" data-i18n="actions.add">Add</button></div>
|
|
||||||
</div>
|
|
||||||
<div class="form-actions wide"><button type="button" class="secondary" id="haTest" data-i18n="settings.testHa">Test HA</button></div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="panel settings-block">
|
|
||||||
<div class="settings-block-head"><div><h3 data-i18n="settings.haIntegrationAccess">Home Assistant integration access</h3><p data-i18n="settings.haIntegrationHint">Create a controller token and paste it into the GREE Controller integration in Home Assistant.</p></div></div>
|
|
||||||
<div class="token-manager"><div id="accessTokenList" class="token-list"></div><div class="form-actions"><button type="button" class="primary" id="createAccessToken" data-i18n="settings.newToken">Create new token</button></div></div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="panel settings-block">
|
<section class="panel settings-block">
|
||||||
<div class="settings-block-head"><div><h3 data-i18n="settings.backup">Configuration backup</h3><p class="warning-note" data-i18n="settings.backupHint">Export/import application configuration. Exported files can contain GREE device keys plus Home Assistant and InfluxDB secrets; metrics and API access tokens are not included.</p></div></div>
|
<div class="settings-block-head"><div><h3 data-i18n="settings.backup">Configuration backup</h3><p class="warning-note" data-i18n="settings.backupHint">Export/import application configuration. Exported files can contain GREE device keys plus Home Assistant and InfluxDB secrets; metrics and API access tokens are not included.</p></div></div>
|
||||||
@@ -259,6 +279,8 @@
|
|||||||
<button data-go="schedules"><span data-i18n="nav.schedules">Schedules</span><span>›</span></button>
|
<button data-go="schedules"><span data-i18n="nav.schedules">Schedules</span><span>›</span></button>
|
||||||
<button data-go="automations"><span data-i18n="nav.automations">Automations</span><span>›</span></button>
|
<button data-go="automations"><span data-i18n="nav.automations">Automations</span><span>›</span></button>
|
||||||
<button data-go="simulation"><span data-i18n="nav.simulation">Simulator</span><span>›</span></button>
|
<button data-go="simulation"><span data-i18n="nav.simulation">Simulator</span><span>›</span></button>
|
||||||
|
<button data-go="night"><span data-i18n="nav.nightMode">Night mode</span><span>›</span></button>
|
||||||
|
<button data-go="homeassistant"><span data-i18n="nav.homeAssistant">Home Assistant / Sensors</span><span>›</span></button>
|
||||||
<button data-go="settings"><span data-i18n="nav.settings">Settings</span><span>›</span></button>
|
<button data-go="settings"><span data-i18n="nav.settings">Settings</span><span>›</span></button>
|
||||||
<button data-go="logs"><span data-i18n="nav.logsAndEvents">Events and logs</span><span>›</span></button>
|
<button data-go="logs"><span data-i18n="nav.logsAndEvents">Events and logs</span><span>›</span></button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -612,3 +612,12 @@ html[data-theme='light'] .simulation-metrics > div, html[data-theme='light'] .si
|
|||||||
.device-capability-buttons { display:flex; flex-wrap:wrap; gap:7px; }
|
.device-capability-buttons { display:flex; flex-wrap:wrap; gap:7px; }
|
||||||
.device-capability-buttons button { min-height:34px; padding:7px 11px; border:1px solid var(--line); border-radius:11px; background:var(--surface-muted); color:var(--text); }
|
.device-capability-buttons button { min-height:34px; padding:7px 11px; border:1px solid var(--line); border-radius:11px; background:var(--surface-muted); color:var(--text); }
|
||||||
.device-capability-buttons button.active { border-color:var(--accent); color:var(--accent); }
|
.device-capability-buttons button.active { border-color:var(--accent); color:var(--accent); }
|
||||||
|
|
||||||
|
/* v0.5.5: compact, centered unit-feature controls */
|
||||||
|
.device-capability-panel { justify-items:center; text-align:center; padding:11px 0 3px; }
|
||||||
|
.device-capability-panel > small { text-align:center; }
|
||||||
|
.device-capability-buttons { justify-content:center; align-items:center; gap:6px; max-width:100%; }
|
||||||
|
.device-capability-buttons button { min-height:28px; padding:5px 9px; border-radius:10px; color:var(--muted); font-size:10px; line-height:1.1; background:transparent; }
|
||||||
|
.device-capability-buttons button:hover { color:var(--text); background:var(--surface-muted); }
|
||||||
|
.device-capability-buttons button.active { color:var(--accent); background:color-mix(in srgb, var(--accent) 12%, var(--surface)); border-color:color-mix(in srgb, var(--accent) 45%, var(--line)); }
|
||||||
|
.standalone-settings-form { max-width:1100px; }
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const CACHE = 'gree-controller-v054';
|
const CACHE = 'gree-controller-v055';
|
||||||
const ASSETS = ['/', '/styles.css', '/app.js', '/favicon.svg', '/manifest.webmanifest', '/lang/index.json', '/lang/en.json'];
|
const ASSETS = ['/', '/styles.css', '/app.js', '/favicon.svg', '/manifest.webmanifest', '/lang/index.json', '/lang/en.json'];
|
||||||
self.addEventListener('install', event => event.waitUntil(caches.open(CACHE).then(cache => cache.addAll(ASSETS)).then(() => self.skipWaiting())));
|
self.addEventListener('install', event => event.waitUntil(caches.open(CACHE).then(cache => cache.addAll(ASSETS)).then(() => self.skipWaiting())));
|
||||||
self.addEventListener('activate', event => event.waitUntil(caches.keys().then(keys => Promise.all(keys.filter(key => key !== CACHE).map(key => caches.delete(key)))).then(() => self.clients.claim())));
|
self.addEventListener('activate', event => event.waitUntil(caches.keys().then(keys => Promise.all(keys.filter(key => key !== CACHE).map(key => caches.delete(key)))).then(() => self.clients.claim())));
|
||||||
|
|||||||
Reference in New Issue
Block a user