This commit is contained in:
Mateusz Gruszczyński
2026-08-25 19:21:27 +02:00
parent dce9ef7270
commit dcdb821c60
12 changed files with 120 additions and 47 deletions
+9 -1
View File
@@ -1,4 +1,4 @@
# GREE Controller v0.7.1 - build and validation report
# GREE Controller v0.7.2 - build and validation report
## Scope
@@ -245,3 +245,11 @@ Validation for the 0.7.1 package:
- 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.
## 0.7.2 zone configuration separation
- Reworked the Zones list into read-only status + configuration cards.
- Removed live temperature, preset, mode and enable/disable controls from the Zones list.
- Added navigation from a zone configuration card to its matching Dashboard quick thermostat.
- Kept backend zone-control endpoints and thermostat logic unchanged.
- Kept the legacy `setpoint` field in form submission as a hidden compatibility value while profile temperatures remain the explicit configuration surface.
Generated
+2 -2
View File
@@ -633,7 +633,7 @@ dependencies = [
[[package]]
name = "gree-controller"
version = "0.7.1"
version = "0.7.2"
dependencies = [
"aes",
"aes-gcm",
@@ -1807,7 +1807,7 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.7.19"
version = "0.7.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52"
dependencies = [
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "gree-controller"
version = "0.7.1"
version = "0.7.2"
edition = "2021"
authors = ["GREE Controller contributors"]
description = "Standalone local GREE HVAC controller with Web UI, SQLite and Home Assistant sensor support"
+11 -11
View File
@@ -1,10 +1,10 @@
d67af429e4da9ce08e9d2f2a8472849ffbd70d135b1c5da535a076026794d04c ./.env.example
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
6145cb2513f1813a252721b295d7b98b48c916c4e6f29e1ea83b1401ba24a817 ./BUILD_REPORT.md
9206ce86a9de52a5bfae90c1b9c0d820d4b60c3f871596f6850d8ccc8ffa0beb ./Cargo.lock
0dffb9821d84c54f9f6115cdfe38c1549f70bf5f470c660fef1f819eacb9247f ./Cargo.toml
184ddfffab6226d0966c4b4a33715adb9df8a7b251810c65ba3297b02e008b9d ./BUILD_REPORT.md
c533ad8558fe581fbc357f7098605c42ff508e6b1253a5d651e5205dd5c347c7 ./Cargo.lock
fac8e8ca6bad6375be66e3da712deb943fa3bb44c78e84cfc99aef2ee3ea21c9 ./Cargo.toml
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
34fb4263e9248c6887020502c61c43825a0d396e5fe4c071de8875ad42af7d5e ./README.md
40257d2ace2752e53c6119ef5d09e4b98433cb1b86b5a3d0a69e62cc888a8a48 ./README.md
f2c8eb28fedb26bb8f5636e1805e7a782cc472b87d882f6752d520f16216a248 ./build.rs
cd45c288c3e002925656f7e06656034fd07013774f4ba0a00ba2bfb1fe60195a ./docs/API.md
234dd200e380a13ecd3e61b4ea455f6f08d64ce89382077dee80684acadb9703 ./docs/HOME_ASSISTANT_MIGRATION.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
fc73252d4c9d6b747fb6290adcd6fd1063d87e2a75e12437c7359dc80245958d ./home-assistant/custom_components/gree_controller/manifest.json
a8cd36e29f59a4df7bc9c37098539a143db18f383c1ae5e657129e14452cd6b0 ./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
00a4d668e9665c7e98211e19974ded8aaf7bc5e89f2d29688d9080e4cd34ef3b ./lang/en.json
44923d70326c62f1e766928360ea4b4909f82b240e5f3773ef44a37f02b75ac4 ./lang/pl.json
cad2eb081cf7fbed275507763aafe288f25ae527c6775c84b89c1ac095d6843b ./lang/en.json
694c88c40860f7b14bcc6bd0575b6a53062302a4f1c29b7cb5829c00028dfa03 ./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
e8776757097634bf09d75705a93b4225f619345f3b9bf242e37f1948a7edf394 ./web/app.js
27c39b66faab98b71ea515715c3744105f26bc274587aa4e6d595e16f3674f22 ./web/app.js
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
ff8c37afb1091b46a5a37683d352226d8cb5bc7ca3e2c8a11ee22d3144bd0494 ./web/index.html
57949bed2229ceb180ea681b0126b56f6e75dc325d7a89d384f823282736bdc7 ./web/index.html
fd26156e9f1d6713d3def564ad000553d9a16a24376059701db2ee762c99ee6c ./web/manifest.webmanifest
e624f7673e0f0fa44f49771e535db5f360ad766e411fb0a810b572459e4617d4 ./web/styles.css
a80e68a9afc2cec5d12249cdf4c5cdb83abd96690fb58b306ec7f36d49ca70fa ./web/sw.js
e4ed0381250352f752e3b36133a89947db1adb08dec9cae7d2467684dbd1a807 ./web/styles.css
0e7a083b418c651fb37d121b978fd974206dfc804aaab868b1f2e4294924be3f ./web/sw.js
d505d793ce7cc9485b45b78bba1c0d51887adc7451ab59a42702946e5b991382 ./web/theme-init.js
+10 -2
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.1**.
Current version: **0.7.2**.
## Highlights
@@ -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.1.zip
unzip gree-controller-v0.7.2.zip
cd gree-controller
chmod +x scripts/*.sh
./scripts/dev.sh
@@ -425,3 +425,11 @@ Zones now use setpoint modulation, global seasonal house mode, cooling/heating C
- 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`.
## 0.7.2 zone configuration separation
- The **Zones** view is now configuration-only: no live +/- temperature, preset or mode buttons are shown there.
- Current zone status remains visible as read-only context, together with mode policy, sensor strategy, hysteresis and Smart Fan configuration.
- The **Control** button opens the Dashboard, expands **Quick thermostats** and scrolls to the matching zone.
- The zone form no longer exposes the legacy/base `setpoint` as a live-looking control; profile temperatures remain configurable.
- Live zone temperature, preset, mode and enable/disable controls remain available in **Dashboard -> Quick thermostats**.
@@ -1,7 +1,7 @@
{
"domain": "gree_controller",
"name": "GREE Controller",
"version": "0.7.1",
"version": "0.7.2",
"config_flow": true,
"integration_type": "hub",
"iot_class": "local_polling",
+9 -2
View File
@@ -75,7 +75,7 @@
"devices.added": "Device added",
"zones.automation": "Automation",
"zones.new": "New zone",
"zones.description": "Each zone is a fast thermostat. It normally follows the house heating/cooling mode and changes the AC setpoint instead of cycling power.",
"zones.description": "Zones define thermostat logic and sensor configuration. Use Quick thermostats on the Dashboard for current temperature, preset and mode changes.",
"zones.hysteresis": "Hysteresis °C",
"zones.source": "Temperature strategy",
"zones.greeSensor": "GREE only",
@@ -579,6 +579,13 @@
"simulation.show": "Show",
"simulation.allUnits": "All units",
"simulation.allGroups": "All groups",
"groups.chooseMember": "Select at least one thermostat zone."
"groups.chooseMember": "Select at least one thermostat zone.",
"zones.configuration": "Zone configuration",
"zones.currentStatus": "Current status",
"zones.controlOnDashboard": "Current control is available in Quick thermostats on the Dashboard.",
"zones.controlNow": "Control",
"zones.noGroup": "no group",
"zones.coolOnly": "Cooling only",
"zones.heatOnly": "Heating only"
}
}
+9 -2
View File
@@ -75,7 +75,7 @@
"devices.added": "Urządzenie dodane",
"zones.automation": "Automatyka",
"zones.new": "Nowa strefa",
"zones.description": "Każda strefa jest szybkim termostatem. Zwykle dziedziczy tryb grzania/chłodzenia domu i zmienia setpoint klimatyzatora zamiast cyklicznie wyłączać zasilanie.",
"zones.description": "Strefy definiują logikę termostatu i konfigurację czujników. Bieżącą temperaturę, profil i tryb zmieniaj w „Szybkich termostatach” na Pulpicie.",
"zones.hysteresis": "Histereza °C",
"zones.source": "Strategia temperatury",
"zones.greeSensor": "Tylko GREE",
@@ -579,6 +579,13 @@
"simulation.show": "Pokaż",
"simulation.allUnits": "Wszystkie jednostki",
"simulation.allGroups": "Wszystkie grupy",
"groups.chooseMember": "Wybierz co najmniej jedną strefę termostatu."
"groups.chooseMember": "Wybierz co najmniej jedną strefę termostatu.",
"zones.configuration": "Konfiguracja strefy",
"zones.currentStatus": "Stan bieżący",
"zones.controlOnDashboard": "Bieżące sterowanie znajduje się w Szybkich termostatach na Pulpicie.",
"zones.controlNow": "Steruj",
"zones.noGroup": "bez grupy",
"zones.coolOnly": "Tylko chłodzenie",
"zones.heatOnly": "Tylko grzanie"
}
}
+35 -10
View File
@@ -686,24 +686,41 @@ function zoneCard(zone, detailed = true) {
const target = Number(zone.effective_setpoint ?? zone.setpoint);
const manual = zone.manual_preset || 'auto';
const mode = zone.inherit_house_mode ? 'house' : zone.mode;
const effectiveMode = zone.effective_mode || (zone.inherit_house_mode ? (app.settings?.house_mode || 'off') : zone.mode) || 'off';
const hasManualOverride = zone.manual_preset != null || zone.manual_setpoint != null;
const override = zone.manual_override_until
? `${tr('zones.overrideUntil')} ${new Date(zone.manual_override_until).toLocaleTimeString(locale(), {hour:'2-digit',minute:'2-digit'})}`
: (hasManualOverride ? tr('zones.manualNoBoundary') : tr('zones.scheduleControl'));
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>`;
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>
if (detailed) {
const groupNames = (app.groups || []).filter(group => (group.zone_ids || []).includes(zone.id)).map(group => group.name);
const groupText = groupNames.length ? groupNames.join(' · ') : tr('zones.noGroup');
const policy = zone.inherit_house_mode ? tr('zones.followHouse') : tr(zone.mode === 'heat' ? 'zones.heatOnly' : 'zones.coolOnly');
return `<article class="list-card zone-config-card ${zone.demand ? 'demanding' : ''} ${zone.enabled ? '' : 'zone-disabled'}" data-zone-config="${esc(zone.id)}">
<div class="list-card-head"><div><span class="eyebrow">${esc(tr('zones.configuration'))}</span><h3>${esc(zone.name)}</h3><p>${esc(device?.name || tr('common.noDevice'))} · ${esc(tr('groups.group'))}: ${esc(groupText)}</p></div><span class="badge ${zone.enabled ? 'active' : ''}">${esc(state)}</span></div>
<div class="zone-config-status">
<div class="zone-config-temperature"><small>${esc(tr('zones.currentStatus'))}</small><div><span>${fmtTemp(roomTemperature)}</span><b>→</b><strong>${Number.isFinite(target) ? `${target.toFixed(1)}°C` : '—'}</strong></div></div>
<div class="zone-config-runtime"><span class="badge ${zone.enabled && zone.demand ? 'active' : ''}">${esc(zone.enabled ? (zone.demand ? tr('zones.requesting') : tr('zones.satisfied')) : tr('common.disabled'))}</span><span>${esc(houseModeLabel(effectiveMode))} · ${esc(zonePresetLabel(zone.active_preset))}</span><small>${esc(override)}</small></div>
</div>
<div class="zone-config-grid">
<div><small>${esc(tr('zones.modePolicy'))}</small><strong>${esc(policy)}</strong></div>
<div><small>${esc(tr('zones.source'))}</small><strong>${esc(zoneStrategyLabel(zone))}</strong></div>
<div><small>${esc(tr('zones.hysteresis'))}</small><strong>${Number(zone.hysteresis ?? 0.6).toFixed(1)}°C</strong></div>
<div><small>${esc(tr('zones.smartFan'))}</small><strong>${esc(tr(zone.smart_fan === false ? 'common.off' : 'common.on'))}</strong></div>
</div>
<div class="sensor-detail">${esc(sensorDetails)}</div>
<div class="card-footer"><small>${esc(tr('zones.controlOnDashboard'))}</small><div class="card-menu"><button class="primary" data-action="zone-go-control" data-id="${esc(zone.id)}">${esc(tr('zones.controlNow'))}</button><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>`;
}
return `<article class="list-card zone-thermostat quick-control-card quick-thermostat-control ${zone.demand ? 'demanding' : ''} ${zone.enabled ? '' : 'zone-disabled'}" data-zone-card="${esc(zone.id)}">
<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><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></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${quickRow}${detailed ? '' : ' quick-control-row-4'}">
<div class="preset-row quick-control-row 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${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="mode-row zone-mode-row quick-control-row 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>`;
}
@@ -1685,6 +1702,14 @@ document.addEventListener('click', async event => {
if (action === 'zone-mode') return sendZoneControl(button.dataset.id,{mode:button.dataset.value});
if (action === 'zone-preset') return sendZoneControl(button.dataset.id,{preset:button.dataset.value});
if (action === 'zone-enabled') return sendZoneControl(button.dataset.id,{enabled:button.dataset.value==='true'});
if (action === 'zone-go-control') {
showView('dashboard', {scroll:false});
const target = [...document.querySelectorAll('[data-zone-card]')].find(card => card.dataset.zoneCard === button.dataset.id);
const disclosure = target?.closest('details.dashboard-disclosure');
if (disclosure) disclosure.open = true;
requestAnimationFrame(() => target?.scrollIntoView({behavior:'smooth', block:'center'}));
return;
}
if (action === 'debug-clear') { app.debugLines = []; renderDebugOverlay(); return; }
if (action === 'edit-group') return populateGroup(button.dataset.id);
if (action === 'delete-group') return deleteEntity('groups', button.dataset.id, 'groups.group');
+2 -2
View File
@@ -79,7 +79,7 @@
<section class="view" data-view="zones">
<div class="section-heading"><div><span class="eyebrow" data-i18n="zones.automation">Automation</span><h1 data-i18n="nav.zones">Zones</h1></div><button class="secondary" data-open="zoneDialog" data-i18n="zones.new">New zone</button></div>
<p class="lead" data-i18n="zones.description">Each zone is a fast thermostat. It normally follows the house heating/cooling mode and changes the AC setpoint instead of cycling power.</p>
<p class="lead" data-i18n="zones.description">Zones define thermostat logic and sensor configuration. Use Quick thermostats on the Dashboard for current temperature changes and presets.</p>
<div class="list-grid" id="zoneList"></div>
</section>
@@ -375,7 +375,7 @@
<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 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>
<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="zones.coolOnly">Cooling only</option><option value="heat" data-i18n="zones.heatOnly">Heating only</option></select></label><input type="hidden" name="setpoint" value="23">
<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>
+30 -12
View File
@@ -864,16 +864,34 @@ body.simulation-standalone [data-view="simulation"] { min-height:calc(100vh - 36
}
[data-view="dashboard"] .dashboard-summary-card { margin-bottom:10px; }
/* 0.7.0: desktop fit and cleaner unit controls. */
#zoneList .zone-thermostat .card-footer { align-items:flex-start; }
#zoneList .zone-thermostat .card-footer > small {
flex:1 1 auto;
min-width:0;
overflow:visible;
text-overflow:clip;
white-space:normal;
line-height:1.35;
}
#zoneList .zone-thermostat .card-menu { flex:0 0 auto; }
/* 0.7.2: Zones are configuration cards; live controls stay on the Dashboard. */
.zone-config-card { display:grid; gap:12px; overflow:hidden; }
.zone-config-card.demanding { border-color:color-mix(in srgb,var(--accent) 38%,var(--line)); }
.zone-config-card.zone-disabled .zone-config-status { opacity:.68; }
.zone-config-status { display:grid; grid-template-columns:minmax(150px,.8fr) minmax(180px,1.2fr); align-items:center; gap:12px; padding:11px 12px; border:1px solid var(--line); border-radius:14px; background:var(--surface-muted); }
.zone-config-temperature > small, .zone-config-grid small { display:block; margin-bottom:4px; color:var(--muted); font-size:9px; text-transform:uppercase; letter-spacing:.06em; }
.zone-config-temperature > div { display:flex; align-items:baseline; gap:7px; }
.zone-config-temperature span { color:var(--muted); font-size:18px; }
.zone-config-temperature b { color:var(--muted); font-size:11px; }
.zone-config-temperature strong { font-size:23px; letter-spacing:-.03em; }
.zone-config-runtime { display:grid; justify-items:end; gap:4px; min-width:0; color:var(--text-soft); font-size:11px; text-align:right; }
.zone-config-runtime > span:not(.badge), .zone-config-runtime small { max-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.zone-config-runtime small { color:var(--muted); }
.zone-config-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:7px; }
.zone-config-grid > div { min-width:0; padding:9px 10px; border:1px solid var(--line); border-radius:12px; background:var(--surface-muted); }
.zone-config-grid strong { display:block; overflow:hidden; font-size:11px; text-overflow:ellipsis; white-space:nowrap; }
#zoneList .zone-config-card .sensor-detail { margin-top:0; }
#zoneList .zone-config-card .card-footer { align-items:flex-start; margin-top:0; }
#zoneList .zone-config-card .card-footer > small { flex:1 1 auto; min-width:0; overflow:visible; text-overflow:clip; white-space:normal; line-height:1.35; }
#zoneList .zone-config-card .card-menu { flex:0 0 auto; flex-wrap:wrap; justify-content:flex-end; }
#zoneList .zone-config-card .card-menu .primary { color:var(--accent-text); background:var(--accent); }
/* 0.7.1: quick thermostats and quick manual control now use the shared quick-control classes above. */
@media (max-width:620px) {
.zone-config-status { grid-template-columns:1fr; }
.zone-config-runtime { justify-items:start; text-align:left; }
.zone-config-grid { grid-template-columns:1fr; }
#zoneList .zone-config-card .card-footer { flex-direction:column; }
#zoneList .zone-config-card .card-menu { width:100%; justify-content:flex-start; }
}
/* 0.7.1+: quick thermostats and quick manual control use the shared quick-control classes above. */
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'gree-controller-v071-quick-controls';
const CACHE = 'gree-controller-v072-zone-config';
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')];