This commit is contained in:
Mateusz Gruszczyński
2026-08-25 19:05:18 +02:00
parent 3ee93b7225
commit dce9ef7270
14 changed files with 132 additions and 93 deletions
+21 -1
View File
@@ -1,4 +1,4 @@
# GREE Controller v0.7.0 - build and validation report
# GREE Controller v0.7.1 - build and validation report
## Scope
@@ -225,3 +225,23 @@ Validation for the 0.7.0 package:
- All built-in schedule templates were exercised against the weekly minute-mask model: no overlaps; Family/Child/Bedroom/Always cover the full week, and Workday's intentional uncovered period falls back to the normal Comfort profile.
- SQLite `INIT_SCHEMA` and device-delete orphan-schedule cleanup SQL were executed successfully against temporary in-memory databases.
- Rust source delimiter/static checks passed. Rust compile/test execution is unavailable here because no Rust toolchain is installed.
## 0.7.1 quick-control visual consistency
- Refactored dashboard-only thermostat/device controls to semantic `quick-control-card` and `quick-control-row-*` classes.
- Quick thermostat preset/mode controls and quick manual HVAC/fan/function controls now share the same compact height, spacing, typography and fixed responsive grids.
- Removed the superseded `#dashboardDevices` desktop-only size override; full Devices/Zones views remain unchanged.
- Changed the compact inherited-mode label from `Dom/House` to `Globalny/Global`, while retaining the internal `house` value for API/backward compatibility.
- Changed `Sen teraz/Sleep now` to `Sen/Sleep`.
Validation for the 0.7.1 package:
- `node --check` passed for `web/app.js` and `web/sw.js`.
- PL/EN JSON parsed successfully and translation key sets are identical (574 keys each).
- Home Assistant manifest parsed successfully and Home Assistant Python modules passed `python3 -m py_compile`.
- `web/index.html` parsed with Python's HTML parser and CSS brace balance passed.
- Static assertions confirm the compact classes are attached only to dashboard quick cards (`detailed == false`), while full Zone/Device cards keep their existing classes and sizes.
- Quick controls use explicit 3/4/5-column grids with no horizontal overflow behavior; all compact option buttons share the same 28 px minimum height, 9 px typography and 4 px gap.
- User-facing inherited-mode labels are `Globalny/Global`; the internal/API value remains `house`, preserving backward compatibility.
- No thermostat, schedule, automation, group, database or device-control logic was changed in 0.7.1.
Generated
+1 -1
View File
@@ -633,7 +633,7 @@ dependencies = [
[[package]]
name = "gree-controller"
version = "0.7.0"
version = "0.7.1"
dependencies = [
"aes",
"aes-gcm",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "gree-controller"
version = "0.7.0"
version = "0.7.1"
edition = "2021"
authors = ["GREE Controller contributors"]
description = "Standalone local GREE HVAC controller with Web UI, SQLite and Home Assistant sensor support"
+13 -13
View File
@@ -1,16 +1,16 @@
d67af429e4da9ce08e9d2f2a8472849ffbd70d135b1c5da535a076026794d04c ./.env.example
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
88a25226a5831a9ca8aa8cad30c6dea3d0df8138d0f2a5b1542218b5ac1f9864 ./BUILD_REPORT.md
180a882f534228ac685d1a97696f8d2874aa500b905d4dbbe42ee38eddbe57b8 ./Cargo.lock
72d6873d30d9d76f7e402c3370f0f0bf8e7515a1ab50a67fcda4ddd396bc8036 ./Cargo.toml
6145cb2513f1813a252721b295d7b98b48c916c4e6f29e1ea83b1401ba24a817 ./BUILD_REPORT.md
9206ce86a9de52a5bfae90c1b9c0d820d4b60c3f871596f6850d8ccc8ffa0beb ./Cargo.lock
0dffb9821d84c54f9f6115cdfe38c1549f70bf5f470c660fef1f819eacb9247f ./Cargo.toml
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
f53e008970ef05699efca9f15e6de487af5c7f7bef6e226eb409e9f8c17e54b8 ./README.md
34fb4263e9248c6887020502c61c43825a0d396e5fe4c071de8875ad42af7d5e ./README.md
f2c8eb28fedb26bb8f5636e1805e7a782cc472b87d882f6752d520f16216a248 ./build.rs
53b04025be8be67e6970e825f5c80a067715eea5ecd2d592aa26a9d37a0dcb51 ./docs/API.md
cd45c288c3e002925656f7e06656034fd07013774f4ba0a00ba2bfb1fe60195a ./docs/API.md
234dd200e380a13ecd3e61b4ea455f6f08d64ce89382077dee80684acadb9703 ./docs/HOME_ASSISTANT_MIGRATION.md
7a88d6e76fda21e5d34ab351e26bc10dc1f8f7b3055505aefad1df7c56d65ae4 ./docs/LOCALIZATION.md
10a0722e1100fb4a05e3067daeb67dc47b0c0a096b43b1cbf2bf002967ce7d98 ./docs/LXC.md
f135c029c528f55918e394d98dae7f92794ff28e00d49cc7e9f519e409c0d3ee ./docs/PROJECT_SPEC.md
2ef5cb04cc74be3aab77a9d117b9040f186c73a7dcf24375f1cca2d73e428449 ./docs/PROJECT_SPEC.md
7748b1230029439a405139750b2e0607b8e463bfd56ad86982394105069697c5 ./docs/REVERSE_PROXY.md
28f1403d84b01b37bebb72548aa39a75bb14bdbb611a43bfa761319957cd6f39 ./docs/SECURITY.md
1192c5442cb0439723294bbed8b4610728c51bca23e1d8c0ef030270d1c697ba ./home-assistant/README.md
@@ -23,7 +23,7 @@ b7f0873109c52be9d7f09bea3dffc416103c50085e1f0680d11661a969479898 ./home-assista
5a96fe8f5c035c34f1339370270cd078056202d09e236dec75735be11de92a7d ./home-assistant/custom_components/gree_controller/entity_map.py
c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./home-assistant/custom_components/gree_controller/icon.png
c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./home-assistant/custom_components/gree_controller/logo.png
9032738bebf9c0e030f711f22853f6541ed6a4ab80a659b930756ba722052848 ./home-assistant/custom_components/gree_controller/manifest.json
fc73252d4c9d6b747fb6290adcd6fd1063d87e2a75e12437c7359dc80245958d ./home-assistant/custom_components/gree_controller/manifest.json
d0206766d76f70ea1c75a618e41bf1e8e8c0429f42ee97f40daac1ea12f16658 ./home-assistant/custom_components/gree_controller/number.py
459233ccefa7827fc1c6887146d8a69e01149a497635fd5e1802c220f418eb28 ./home-assistant/custom_components/gree_controller/select.py
ec35191e27a018a72cb341914e2abd7fecd25f6b641ea25bebb9e809dd8ecaff ./home-assistant/custom_components/gree_controller/sensor.py
@@ -32,8 +32,8 @@ ec35191e27a018a72cb341914e2abd7fecd25f6b641ea25bebb9e809dd8ecaff ./home-assista
13f30e2dcdcedbd1b6c3f99c2335e0487108fd72c8e86922368b84f2fa2038ae ./home-assistant/custom_components/gree_controller/translations/pl.json
4513070521d3dda0efb0d974a86ba674494cfb2b66fe9e5cac5b1b0430dede97 ./home-assistant/generated/gree_controller_entities.example.json
253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md
54c6f8405d090d9be4fe8247636679603132662d1c3359fdcb272bf86a144609 ./lang/en.json
caa87ffcace5e00a1cc116f1183481f2a191d5a377d04f870d1ac9f9751213ad ./lang/pl.json
00a4d668e9665c7e98211e19974ded8aaf7bc5e89f2d29688d9080e4cd34ef3b ./lang/en.json
44923d70326c62f1e766928360ea4b4909f82b240e5f3773ef44a37f02b75ac4 ./lang/pl.json
028e1f16e9fbaed57cadb88eff04e65b4bd67722c50b4d6b1fb525f5a2f39abf ./make_zip.py
bb89bac237e750e9b1bf73761d7df97a6b81853091615878c03f13d7b6399aa7 ./scripts/README.md
5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh
@@ -62,10 +62,10 @@ a910bd9432a393740c0f6fab52bfcb551f0ea756718d66d290fd2610767cf07c ./src/protocol
6a1c0cab3eab80ecd254c5e486b1fba068523edb8d699c64054c17f67f4a31d0 ./src/queries.rs
f74ac398c6f2e64ebb965900330324f3ec7403e70339638a408229f29fbd07ae ./src/state.rs
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
a51b4bc14f9fbc4d5b936c1f56b4c53a5aaded69f5334991da3328cb7b3f3cb6 ./web/app.js
e8776757097634bf09d75705a93b4225f619345f3b9bf242e37f1948a7edf394 ./web/app.js
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
954b5fdabef37d7788790fb15a5f95a96aeb810cba7ccf24763aa28615d4749c ./web/index.html
ff8c37afb1091b46a5a37683d352226d8cb5bc7ca3e2c8a11ee22d3144bd0494 ./web/index.html
fd26156e9f1d6713d3def564ad000553d9a16a24376059701db2ee762c99ee6c ./web/manifest.webmanifest
9bc2a34daeb2b7083af44215e904aaadf1d4da9d003e6fcef2d3fc9f994de363 ./web/styles.css
bf2db03bf27c567121a3ebe3bd11d84d13dce91f3c1f068aadff6e11c5120a26 ./web/sw.js
e624f7673e0f0fa44f49771e535db5f360ad766e411fb0a810b572459e4617d4 ./web/styles.css
a80e68a9afc2cec5d12249cdf4c5cdb83abd96690fb58b306ec7f36d49ca70fa ./web/sw.js
d505d793ce7cc9485b45b78bba1c0d51887adc7451ab59a42702946e5b991382 ./web/theme-init.js
+16 -6
View File
@@ -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.
Current version: **0.7.0**.
Current version: **0.7.1**.
## Highlights
@@ -17,7 +17,7 @@ Current version: **0.7.0**.
- automatic SQLite history compaction plus optional InfluxDB 1.x/2.x long-term archive,
- smart thermostat zones with global house Heat/Cool/Off mode and per-zone overrides,
- setpoint modulation that keeps indoor units powered during normal operation instead of repeatedly cycling power,
- Comfort/Sleep/Away profiles, temporary overrides and one-tap **Sleep now**,
- Comfort/Sleep/Away profiles, temporary overrides and one-tap **Sleep**,
- named climate groups (for example Upstairs/Downstairs) with independent power, Heat/Cool/house-follow mode and profile control, including automation targets,
- editable weekly schedules plus Family/Child room/Bedroom/Workday/Always-comfort templates,
- temperature/time automations for advanced exceptions,
@@ -47,7 +47,7 @@ See [`BUILD_REPORT.md`](BUILD_REPORT.md) for package validation details and [`do
On Debian, Ubuntu or an LXC container:
```bash
unzip gree-controller-v0.7.0.zip
unzip gree-controller-v0.7.1.zip
cd gree-controller
chmod +x scripts/*.sh
./scripts/dev.sh
@@ -218,15 +218,15 @@ For combined control, `max_sensor_difference` protects against an obviously inco
The zone API exposes `device_temperature`, `external_temperature`, `current_temperature` (the actual control temperature) and `control_temperature_source` for diagnostics. Existing SQLite zone records remain compatible because the new fields have defaults and are stored in the existing JSON payload.
Zone cards are fast thermostats: `- / +`, **Auto schedule / Comfort / Sleep now / Away**, and **House / Heat / Cool** are available without opening the edit dialog. Temporary overrides automatically end at the next schedule boundary; if a zone has no future schedule transition, the override stays active until it is cleared. The zone engine, not the browser, owns device commands so schedules, sensors and manual overrides cannot fight each other.
Zone cards are fast thermostats: `- / +`, **Auto schedule / Comfort / Sleep / Away**, and **Global / Heat / Cool** are available without opening the edit dialog. Temporary overrides automatically end at the next schedule boundary; if a zone has no future schedule transition, the override stays active until it is cleared. The zone engine, not the browser, owns device commands so schedules, sensors and manual overrides cannot fight each other.
## Smart thermostat and schedules
Version 0.4.0 is designed around a simple rule: during normal heating/cooling, keep the indoor unit powered and modulate its target instead of repeatedly switching the unit OFF and ON. When a room is satisfied, cooling moves the device target above the comfort target and heating moves it below the comfort target. When hysteresis requests conditioning again, the working target is restored. **House Off** is now a no-control state for inherited zones, so manual device operation is not overwritten; whole-house master power on/off is available separately and can authoritatively keep every unit off without changing the selected thermostat mode.
Choose one seasonal **House mode** — Cooling in summer, Heating in winter, or Off. Zones follow the house mode by default, while a zone may explicitly override Heat/Cool when required. In **Mode policy**, leave **Follow house mode** unchanged when the zone should inherit the mode selected globally in GREE Controller; **Cooling only** / **Heating only** are deliberate per-zone overrides. Each zone has separate Comfort/Sleep/Away temperatures for cooling and heating.
Choose one seasonal **global mode** — Cooling in summer, Heating in winter, or Off. Zones inherit this global mode by default, while a zone may explicitly override Heat/Cool when required. In **Mode policy**, leave **Follow global mode** unchanged when the zone should inherit the mode selected globally in GREE Controller; **Cooling only** / **Heating only** are deliberate per-zone overrides. Each zone has separate Comfort/Sleep/Away temperatures for cooling and heating.
Schedules store profiles rather than duplicating temperatures. Ready-made **Family**, **Child room**, **Bedroom**, **Workday** and **Always comfort** templates generate ordinary editable schedule entries. For example, a child room template can enter Sleep earlier; if the child goes to bed even earlier, press **Sleep now** and that zone remains in Sleep until its next schedule boundary (or until manually cleared when no future boundary exists). Whole-house **Comfort all / Sleep all / Away all / Auto schedule** actions are also available.
Schedules store profiles rather than duplicating temperatures. Ready-made **Family**, **Child room**, **Bedroom**, **Workday** and **Always comfort** templates generate ordinary editable schedule entries. For example, a child room template can enter Sleep earlier; if the child goes to bed even earlier, press **Sleep** and that zone remains in Sleep until its next schedule boundary (or until manually cleared when no future boundary exists). Whole-house **Comfort all / Sleep all / Away all / Auto schedule** actions are also available.
The optional HA outdoor sensor is an assist signal only. It never replaces the room-control temperature. In extreme weather it can make the active device setpoint slightly more assertive and let Smart Fan increase airflow.
@@ -415,3 +415,13 @@ GREE protocol identifiers are now emitted as canonical lowercase hexadecimal in
### v0.4.0 smart thermostat
Zones now use setpoint modulation, global seasonal house mode, cooling/heating Comfort/Sleep/Away profiles, temporary per-zone overrides, ready-made editable schedule templates, whole-house preset actions and optional outdoor-temperature assist. The UI also uses theme-aware thin scrollbars and can opt in to invalid/self-signed certificates for the outbound Home Assistant sensor connection. Existing zones remain readable; a legacy zone keeps its previous setpoint as its comfort target until it is edited and saved with the new profile fields.
## 0.7.1 quick-control visual consistency
- Unified dashboard Quick Thermostats and Quick Manual Control under shared `quick-control-*` CSS classes instead of separate dashboard-ID overrides.
- Standardized compact option buttons, gaps and row grids across presets, HVAC modes, fan speeds and quick unit toggles.
- Kept full Zones and Devices pages at their existing larger control sizes; the compact style applies only to dashboard quick controls.
- Renamed the compact zone mode from House to Global to make its meaning explicit: the zone inherits the global Heating/Cooling mode selected in GREE Controller.
- Renamed Sleep now to Sleep in quick thermostat presets.
- Advanced the PWA cache key to `gree-controller-v071-quick-controls`.
+2 -2
View File
@@ -111,7 +111,7 @@ A zone stores separate profile temperatures for both seasons:
Quick control does not directly fight the schedule engine:
```bash
# Sleep now until the next schedule boundary (or until cleared if there is no future transition)
# Sleep until the next schedule boundary (or until cleared if there is no future transition)
curl -X POST "$BASE/api/zones/ZONE_ID/control" -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"preset":"sleep"}'
@@ -124,7 +124,7 @@ curl -X POST "$BASE/api/zones/ZONE_ID/control" -H "$AUTH" -H 'Content-Type: appl
-d '{"setpoint":22.5}'
```
`mode` on the quick zone endpoint accepts `house`, `cool`, or `heat`. `house` means **Follow house mode** and should be left unchanged when the zone is meant to inherit the global GREE Controller mode. A fixed `cool`/`heat` is an explicit per-zone override; therefore house mode `off` stops inherited zones but does not disable a zone deliberately fixed to Cooling or Heating. Whole-house master power remains authoritative over all zones.
`mode` on the quick zone endpoint accepts `house`, `cool`, or `heat`. `house` means **Follow global mode** in the UI and should be left unchanged when the zone is meant to inherit the global GREE Controller mode. A fixed `cool`/`heat` is an explicit per-zone override; therefore house mode `off` stops inherited zones but does not disable a zone deliberately fixed to Cooling or Heating. Whole-house master power remains authoritative over all zones.
### Climate groups
+2 -2
View File
@@ -68,7 +68,7 @@ Each device stores:
## Smart thermostat, zones and schedules
The primary user model is a fast thermostat, not raw device automation. A global **House mode** selects the season (`cool`, `heat`, `off`). Zones follow it by default and can optionally override Heat/Cool. The UI labels this as **Mode policy**: keep **Follow house mode** when the zone should inherit the mode selected globally in GREE Controller; the fixed Cooling/Heating policies are explicit overrides. House `off` is a **do not control** state for inherited zones: manual device operation is left untouched, while a zone explicitly switched to Heat/Cool can operate independently. Whole-house power on/off is a separate master state. Master Off is authoritative for zones and controller automations but does not modify the selected house thermostat mode.
The primary user model is a fast thermostat, not raw device automation. A global climate mode selects the season (`cool`, `heat`, `off`). Zones follow it by default and can optionally override Heat/Cool. The UI labels this as **Mode policy**: keep **Follow global mode** when the zone should inherit the mode selected globally in GREE Controller; the fixed Cooling/Heating policies are explicit overrides. House `off` is a **do not control** state for inherited zones: manual device operation is left untouched, while a zone explicitly switched to Heat/Cool can operate independently. Whole-house power on/off is a separate master state. Master Off is authoritative for zones and controller automations but does not modify the selected house thermostat mode.
During normal heating/cooling the controller minimizes unit power cycling. It keeps the indoor unit powered and uses **setpoint modulation**:
@@ -84,7 +84,7 @@ Each zone has separate seasonal profile temperatures:
The zone can use the GREE internal sensor, its own Home Assistant room sensor, or a weighted combination. External room sensors are configured per zone; loss/discrepancy falls back safely to the GREE sensor.
Weekly schedules reference profiles instead of duplicating temperatures. Ready-made Family, Child room, Bedroom, Workday and Always-comfort templates create ordinary schedule rows that remain fully editable. A manual **Sleep now**, Comfort, Away or custom-temperature override ends automatically at the next schedule boundary; if no future transition exists, it remains active until explicitly cleared. Whole-house preset actions apply the same temporary policy to every zone.
Weekly schedules reference profiles instead of duplicating temperatures. Ready-made Family, Child room, Bedroom, Workday and Always-comfort templates create ordinary schedule rows that remain fully editable. A manual **Sleep**, Comfort, Away or custom-temperature override ends automatically at the next schedule boundary; if no future transition exists, it remains active until explicitly cleared. Whole-house preset actions apply the same temporary policy to every zone.
Automations remain available for advanced exceptions. Daily comfort should be implemented with zones/profiles/schedules so direct-device automation commands do not compete with the thermostat engine.
@@ -1,7 +1,7 @@
{
"domain": "gree_controller",
"name": "GREE Controller",
"version": "0.7.0",
"version": "0.7.1",
"config_flow": true,
"integration_type": "hub",
"iot_class": "local_polling",
+5 -5
View File
@@ -248,9 +248,9 @@
"house.outdoor": "Outdoor",
"house.modeUpdated": "Home climate mode updated",
"zones.modePolicy": "Mode policy",
"zones.modePolicyHint": "If this zone should inherit the mode selected globally in the app, leave “Follow house mode” unchanged. “Cooling only” or “Heating only” forces a fixed mode only for this zone.",
"zones.followHouse": "Follow house mode",
"zones.followHouseShort": "House",
"zones.modePolicyHint": "If this zone should use the global Heating/Cooling mode selected in GREE Controller, leave “Follow global mode” unchanged. “Cooling only” or “Heating only” forces a fixed mode only for this zone.",
"zones.followHouse": "Follow global mode",
"zones.followHouseShort": "Global",
"zones.profileTemperatures": "Comfort / sleep / away temperatures",
"zones.minAdjust": "Minimum adjustment interval (s)",
"zones.standbyOffset": "Standby setpoint offset °C",
@@ -281,7 +281,7 @@
"settings.outdoorAssist": "Use outdoor temperature as smart-control assist",
"settings.allowInvalidTls": "Allow invalid/self-signed HTTPS certificate",
"settings.allowInvalidTlsHint": "Use only for a trusted local Home Assistant server, for example https://192.168.50.25.",
"zones.sleepNow": "Sleep now",
"zones.sleepNow": "Sleep",
"house.autoAll": "Auto schedule",
"house.comfortAll": "Comfort all",
"house.sleepAll": "Sleep all",
@@ -558,7 +558,7 @@
"groups.membersHint": "Groups are built from thermostat zones so their temperature profiles and schedules keep working.",
"groups.noMembers": "No members",
"groups.mixed": "Mixed",
"groups.followHouse": "House / Auto",
"groups.followHouse": "Global mode",
"groups.mode": "Mode",
"groups.profile": "Profile",
"groups.masterOff": "Global power is off. Group settings are saved, but units will stay off until global power is enabled.",
+5 -5
View File
@@ -248,9 +248,9 @@
"house.outdoor": "Na zewnątrz",
"house.modeUpdated": "Zmieniono tryb klimatu domu",
"zones.modePolicy": "Polityka trybu",
"zones.modePolicyHint": "Jeśli strefa ma dziedziczyć tryb ustawiony globalnie w aplikacji, zostaw „Dziedzicz tryb domu” i nie zmieniaj tej opcji. „Tylko chłodzenie” lub „Tylko grzanie” wymusza stały tryb tylko dla tej strefy.",
"zones.followHouse": "Dziedzicz tryb domu",
"zones.followHouseShort": "Dom",
"zones.modePolicyHint": "Jeśli strefa ma korzystać z globalnego trybu Grzanie/Chłodzenie ustawionego w GREE Controller, zostaw „Dziedzicz tryb globalny” i nie zmieniaj tej opcji. „Tylko chłodzenie” lub „Tylko grzanie” wymusza stały tryb tylko dla tej strefy.",
"zones.followHouse": "Dziedzicz tryb globalny",
"zones.followHouseShort": "Globalny",
"zones.profileTemperatures": "Temperatury komfort / sen / poza domem",
"zones.minAdjust": "Minimalny odstęp regulacji (s)",
"zones.standbyOffset": "Przesunięcie temperatury standby °C",
@@ -281,7 +281,7 @@
"settings.outdoorAssist": "Używaj temperatury zewnętrznej jako wsparcia inteligentnego sterowania",
"settings.allowInvalidTls": "Zezwól na nieważny/samopodpisany certyfikat HTTPS",
"settings.allowInvalidTlsHint": "Używaj tylko dla zaufanego lokalnego serwera Home Assistant, np. https://192.168.50.25.",
"zones.sleepNow": "Sen teraz",
"zones.sleepNow": "Sen",
"house.autoAll": "Auto harmonogram",
"house.comfortAll": "Komfort wszędzie",
"house.sleepAll": "Sen wszędzie",
@@ -558,7 +558,7 @@
"groups.membersHint": "Grupy bazują na strefach termostatów, więc nadal działają ich profile temperatur i harmonogramy.",
"groups.noMembers": "Brak członków",
"groups.mixed": "Różne",
"groups.followHouse": "Dom / Auto",
"groups.followHouse": "Tryb globalny",
"groups.mode": "Tryb",
"groups.profile": "Profil",
"groups.masterOff": "Zasilanie globalne jest wyłączone. Ustawienia grupy zostaną zapisane, ale jednostki pozostaną wyłączone do włączenia zasilania globalnego.",
+11 -7
View File
@@ -621,7 +621,9 @@ function deviceCard(device, detailed = false) {
const responseTime = hasResponseTime ? `${Math.max(0, Math.round(Number(device.response_time_ms)))} ms` : '— ms';
const protocol = device.simulated ? tr('devices.simulator') : device.protocol_version === 2 ? 'V2 GCM' : device.protocol_version === 1 ? 'V1 ECB' : tr('devices.protocolAuto');
const networkInfo = `<div class="device-network-info"><small>${esc(device.ip)} · ${esc(responseTime)} · ${esc(protocol)}</small>${device.last_error ? `<small class="device-network-error" title="${esc(device.last_error)}">${esc(device.last_error)}</small>` : ''}</div>`;
return `<article class="device-card ${device.power ? '' : 'off'}" data-device-card="${esc(device.id)}">
const quickClass = detailed ? '' : ' quick-control-card quick-device-control';
const quickRow = detailed ? '' : ' quick-control-row';
return `<article class="device-card${quickClass} ${device.power ? '' : 'off'}" data-device-card="${esc(device.id)}">
<div class="device-head">
<div class="device-title"><h3>${esc(device.name)}</h3><p><span class="status ${device.online ? 'online' : ''}">${esc(tr(device.online ? 'status.online' : 'status.offline'))}</span> · ${esc(device.model || device.mac)}</p></div>
<button class="power-button ${device.power ? 'on' : ''}" data-action="power" data-device="${esc(device.id)}" aria-label="${esc(tr('devices.power'))}">⏻</button>
@@ -632,9 +634,9 @@ function deviceCard(device, detailed = false) {
<button data-action="temperature" data-delta="1" data-device="${esc(device.id)}">+</button>
</div>
<div class="current-line">${esc(tr('devices.currentTemperature'))}: <strong>${fmtTemp(device.current_temperature)}</strong>${device.outdoor_temperature == null ? '' : ` · ${esc(tr('devices.outdoor'))} ${fmtTemp(device.outdoor_temperature)}`}</div>
<div class="mode-row">${modes.map(mode => `<button class="${device.mode === mode ? 'active' : ''}" data-action="mode" data-value="${mode}" data-device="${esc(device.id)}">${esc(modeLabel(mode))}</button>`).join('')}</div>
<div class="fan-row">${fans.map(fan => `<button class="${Number(device.fan_speed) === fan ? 'active' : ''}" data-action="fan" data-value="${fan}" data-device="${esc(device.id)}">${esc(fanLabel(fan))}</button>`).join('')}</div>
<div class="device-toggles">
<div class="mode-row${quickRow}${detailed ? '' : ' quick-control-row-5'}">${modes.map(mode => `<button class="${device.mode === mode ? 'active' : ''}" data-action="mode" data-value="${mode}" data-device="${esc(device.id)}">${esc(modeLabel(mode))}</button>`).join('')}</div>
<div class="fan-row${quickRow}${detailed ? '' : ' quick-control-row-4'}">${fans.map(fan => `<button class="${Number(device.fan_speed) === fan ? 'active' : ''}" data-action="fan" data-value="${fan}" data-device="${esc(device.id)}">${esc(fanLabel(fan))}</button>`).join('')}</div>
<div class="device-toggles${quickRow}${detailed ? '' : ' quick-control-row-3'}">
<button class="${device.swing_vertical ? 'active' : ''}" data-action="toggle" data-field="swing_vertical" data-device="${esc(device.id)}">${esc(tr('devices.swing'))}</button>
${device.supports_quiet === false ? '' : `<button class="${device.quiet ? 'active' : ''}" data-action="toggle" data-field="quiet" data-device="${esc(device.id)}">${esc(tr('devices.quiet'))}</button>`}
${device.supports_turbo === false ? '' : `<button class="${device.turbo ? 'active' : ''}" data-action="toggle" data-field="turbo" data-device="${esc(device.id)}">${esc(tr('devices.turbo'))}</button>`}
@@ -691,13 +693,15 @@ function zoneCard(zone, detailed = true) {
const enabledControl = detailed
? `<span class="badge ${zone.enabled ? 'active' : ''}">${esc(state)}</span>`
: `<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'}">
const quickClass = detailed ? '' : ' quick-control-card quick-thermostat-control';
const quickRow = detailed ? '' : ' quick-control-row';
return `<article class="list-card zone-thermostat${quickClass} ${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="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${quickRow}${detailed ? '' : ' quick-control-row-4'}">
${['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 class="mode-row zone-mode-row"><button class="${mode==='house'?'active':''}" data-action="zone-mode" data-id="${esc(zone.id)}" data-value="house">${esc(tr('zones.followHouseShort'))}</button><button class="${mode==='heat'?'active':''}" data-action="zone-mode" data-id="${esc(zone.id)}" data-value="heat">${esc(modeLabel('heat'))}</button><button class="${mode==='cool'?'active':''}" data-action="zone-mode" data-id="${esc(zone.id)}" data-value="cool">${esc(modeLabel('cool'))}</button></div>
<div class="mode-row zone-mode-row${quickRow}${detailed ? '' : ' quick-control-row-3'}"><button class="${mode==='house'?'active':''}" data-action="zone-mode" data-id="${esc(zone.id)}" data-value="house">${esc(tr('zones.followHouseShort'))}</button><button class="${mode==='heat'?'active':''}" data-action="zone-mode" data-id="${esc(zone.id)}" data-value="heat">${esc(modeLabel('heat'))}</button><button class="${mode==='cool'?'active':''}" data-action="zone-mode" data-id="${esc(zone.id)}" data-value="cool">${esc(modeLabel('cool'))}</button></div>
<div class="zone-state-line"><span>${zone.demand ? esc(tr('zones.requesting')) : esc(tr('zones.satisfied'))}</span><span>${esc(override)}</span></div>
${detailed ? `<div class="sensor-detail">${esc(sensorDetails)}</div><div class="card-footer"><small>${esc(tr('zones.quickHintSmart'))}</small><div class="card-menu"><button data-action="edit-zone" data-id="${esc(zone.id)}">${esc(tr('actions.edit'))}</button><button class="danger" data-action="delete-zone" data-id="${esc(zone.id)}">${esc(tr('actions.delete'))}</button></div></div>` : ''}
</article>`;
+2 -2
View File
@@ -375,8 +375,8 @@
<label><span data-i18n="common.name">Name</span><input name="name" required data-i18n-placeholder="placeholder.livingRoom" placeholder="Living room"></label>
<label><span data-i18n="common.device">Device</span><select name="device_id" required></select></label>
<div class="copy-settings-row"><select id="copyZoneSource"><option value="" data-i18n="zones.copyFrom">Copy thermostat settings from…</option></select><button type="button" class="secondary" id="copyZoneSettings" data-i18n="zones.copySettings">Copy settings</button></div>
<div class="two"><label><span data-i18n="zones.modePolicy">Mode policy</span><select name="mode_policy"><option value="house" data-i18n="zones.followHouse">Follow house mode</option><option value="cool" data-i18n="mode.cool">Cooling only</option><option value="heat" data-i18n="mode.heat">Heating only</option></select></label><label><span data-i18n="common.targetC">Manual target °C</span><input type="text" inputmode="decimal" step="0.5" min="8" max="30" name="setpoint" value="23"></label></div>
<p class="field-note" data-i18n="zones.modePolicyHint">If this zone should inherit the mode selected globally in the app, leave “Follow house mode” unchanged. Cooling only / Heating only forces a fixed mode for this zone.</p>
<div class="two"><label><span data-i18n="zones.modePolicy">Mode policy</span><select name="mode_policy"><option value="house" data-i18n="zones.followHouse">Follow global mode</option><option value="cool" data-i18n="mode.cool">Cooling only</option><option value="heat" data-i18n="mode.heat">Heating only</option></select></label><label><span data-i18n="common.targetC">Manual target °C</span><input type="text" inputmode="decimal" step="0.5" min="8" max="30" name="setpoint" value="23"></label></div>
<p class="field-note" data-i18n="zones.modePolicyHint">If this zone should use the global Heating/Cooling mode selected in GREE Controller, leave “Follow global mode” unchanged. Cooling only / Heating only forces a fixed mode for this zone.</p>
<details class="profile-editor"><summary data-i18n="zones.profileTemperatures">Comfort / sleep / away temperatures</summary>
<div class="profile-grid"><strong data-i18n="mode.cool">Cooling</strong><label><span data-i18n="preset.comfort">Comfort</span><input type="text" inputmode="decimal" step="0.5" name="cool_comfort_setpoint" value="23"></label><label><span data-i18n="preset.sleep">Sleep</span><input type="text" inputmode="decimal" step="0.5" name="cool_sleep_setpoint" value="24.5"></label><label><span data-i18n="preset.away">Away</span><input type="text" inputmode="decimal" step="0.5" name="cool_away_setpoint" value="27"></label></div>
<div class="profile-grid"><strong data-i18n="mode.heat">Heating</strong><label><span data-i18n="preset.comfort">Comfort</span><input type="text" inputmode="decimal" step="0.5" name="heat_comfort_setpoint" value="21"></label><label><span data-i18n="preset.sleep">Sleep</span><input type="text" inputmode="decimal" step="0.5" name="heat_sleep_setpoint" value="19"></label><label><span data-i18n="preset.away">Away</span><input type="text" inputmode="decimal" step="0.5" name="heat_away_setpoint" value="17"></label></div>
+51 -46
View File
@@ -732,19 +732,41 @@ body.simulation-standalone [data-view="simulation"] { min-height:calc(100vh - 36
.dashboard-disclosure-body .device-grid,
.dashboard-disclosure-body .list-grid,
.dashboard-disclosure-body .automation-plan-grid { gap:10px; grid-template-columns:repeat(auto-fit,minmax(min(100%,300px),1fr)); }
#dashboardZones .list-card, #dashboardDevices .device-card { padding:15px; }
#dashboardZones .preset-row,
#dashboardZones .mode-row,
#dashboardDevices .mode-row,
#dashboardDevices .fan-row { display:grid; grid-template-columns:repeat(auto-fit,minmax(70px,1fr)); gap:5px; overflow:visible; padding:2px 0 6px; }
#dashboardZones .preset-row button,
#dashboardZones .mode-row button,
#dashboardDevices .mode-row button,
#dashboardDevices .fan-row button { min-width:0; min-height:32px; padding:6px 7px; font-size:10px; }
#dashboardDevices .temperature-control { gap:16px; padding:18px 0 14px; }
#dashboardDevices .target-temp { min-width:92px; font-size:40px; }
#dashboardDevices .current-line { margin:-6px 0 13px; }
#dashboardZones .thermostat-main { margin:12px 0 8px; }
/* Dashboard quick controls share one compact visual language. */
.quick-control-card {
--quick-control-height:28px;
--quick-control-gap:4px;
padding:14px;
}
.quick-control-card .quick-control-row {
display:grid;
gap:var(--quick-control-gap);
overflow:visible;
padding:2px 0 5px;
}
.quick-control-card .quick-control-row-3 { grid-template-columns:repeat(3,minmax(0,1fr)); }
.quick-control-card .quick-control-row-4 { grid-template-columns:repeat(4,minmax(0,1fr)); }
.quick-control-card .quick-control-row-5 { grid-template-columns:repeat(5,minmax(0,1fr)); }
.quick-control-card .quick-control-row button {
min-width:0;
min-height:var(--quick-control-height);
padding:4px 5px;
font-size:9px;
line-height:1.05;
white-space:nowrap;
}
.quick-control-card .power-button { width:38px; height:38px; font-size:18px; }
.quick-control-card .temperature-control button { width:34px; height:34px; font-size:18px; }
.quick-control-card .target-temp { min-width:80px; font-size:32px; }
.quick-control-card .device-title h3,
.quick-control-card .list-card-head h3 { font-size:16px; }
.quick-control-card .device-title p,
.quick-control-card .list-card-head p { font-size:11px; }
.quick-device-control .temperature-control { gap:12px; padding:12px 0 10px; }
.quick-device-control .current-line { margin:-3px 0 10px; font-size:11px; }
.quick-device-control .device-toggles.quick-control-row { margin-top:1px; }
.quick-thermostat-control .thermostat-main { margin:8px 0 7px; }
.quick-thermostat-control .zone-enable-toggle { min-height:28px; padding:4px 8px; font-size:10px; }
@media (max-width: 700px) {
[data-view="dashboard"] .hero { min-height:145px; padding:20px; }
@@ -785,26 +807,26 @@ body.simulation-standalone [data-view="simulation"] { min-height:calc(100vh - 36
/* Quick thermostat: target control gets its own row, readings no longer collide with it. */
#dashboardZones { grid-template-columns:repeat(auto-fit,minmax(min(100%,280px),1fr)); }
#dashboardZones .zone-thermostat { min-width:0; }
#dashboardZones .thermostat-main {
.quick-thermostat-control { min-width:0; }
.quick-thermostat-control .thermostat-main {
grid-template-columns:minmax(0,1fr) minmax(0,1fr);
grid-template-areas:"control control" "room device";
gap:5px 10px;
margin:8px 0 7px;
}
#dashboardZones .thermostat-main > div:first-child { grid-area:room; min-width:0; text-align:left; }
#dashboardZones .thermostat-main > .temperature-control { grid-area:control; }
#dashboardZones .thermostat-main > div:last-child { grid-area:device; min-width:0; text-align:right; }
#dashboardZones .thermostat-main small { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
#dashboardZones .thermostat-main strong { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:14px; }
#dashboardZones .thermostat-main .temperature-control.compact { gap:12px; padding:0 0 2px; }
#dashboardZones .thermostat-main .temperature-control.compact button { width:36px; height:36px; font-size:18px; }
#dashboardZones .thermostat-main .target-temp.compact { min-width:80px; font-size:30px; }
#dashboardZones .list-card-head h3 { font-size:16px; }
#dashboardZones .list-card-head p { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:11px; }
#dashboardZones .zone-enable-toggle { flex:0 0 auto; }
#dashboardZones .zone-state-line { gap:8px; font-size:10px; }
#dashboardZones .zone-state-line span { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.quick-thermostat-control .thermostat-main > div:first-child { grid-area:room; min-width:0; text-align:left; }
.quick-thermostat-control .thermostat-main > .temperature-control { grid-area:control; }
.quick-thermostat-control .thermostat-main > div:last-child { grid-area:device; min-width:0; text-align:right; }
.quick-thermostat-control .thermostat-main small { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.quick-thermostat-control .thermostat-main strong { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:14px; }
.quick-thermostat-control .thermostat-main .temperature-control.compact { gap:12px; padding:0 0 2px; }
.quick-thermostat-control .thermostat-main .temperature-control.compact button { width:34px; height:34px; font-size:18px; }
.quick-thermostat-control .thermostat-main .target-temp.compact { min-width:80px; font-size:32px; }
.quick-thermostat-control .list-card-head h3 { font-size:16px; }
.quick-thermostat-control .list-card-head p { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:11px; }
.quick-thermostat-control .zone-enable-toggle { flex:0 0 auto; }
.quick-thermostat-control .zone-state-line { gap:8px; font-size:10px; }
.quick-thermostat-control .zone-state-line span { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* Smaller Node-RED/simulator-like plan blocks with explicit group context. */
#controlPlan.automation-plan-grid { grid-template-columns:repeat(auto-fit,minmax(min(100%,210px),1fr)); gap:8px; }
@@ -854,21 +876,4 @@ body.simulation-standalone [data-view="simulation"] { min-height:calc(100vh - 36
}
#zoneList .zone-thermostat .card-menu { flex:0 0 auto; }
@media (min-width: 701px) {
#dashboardDevices .mode-row {
grid-template-columns:repeat(5,minmax(0,1fr));
gap:4px;
}
#dashboardDevices .fan-row {
grid-template-columns:repeat(4,minmax(0,1fr));
gap:4px;
}
#dashboardDevices .mode-row button,
#dashboardDevices .fan-row button {
min-height:26px;
padding:4px 3px;
font-size:9px;
line-height:1.05;
white-space:nowrap;
}
}
/* 0.7.1: quick thermostats and quick manual control now use the shared quick-control classes above. */
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'gree-controller-v070-schedule-audit-ui';
const CACHE = 'gree-controller-v071-quick-controls';
const SCOPE = new URL(self.registration.scope).pathname.replace(/\/$/, '');
const path = value => `${SCOPE}${value.startsWith('/') ? value : `/${value}`}` || '/';
const ASSETS = [path('/'), path('/styles.css'), path('/app.js'), path('/theme-init.js'), path('/favicon.svg'), path('/manifest.webmanifest'), path('/lang/index.json'), path('/lang/en.json')];