This commit is contained in:
Mateusz Gruszczyński
2026-08-26 22:47:56 +02:00
parent f8d6bc2304
commit 584bdca7c6
11 changed files with 62 additions and 36 deletions
+8 -1
View File
@@ -1,4 +1,4 @@
# GREE Controller v0.7.7 - build and validation report
# GREE Controller v0.7.8 - build and validation report
## Scope
@@ -374,3 +374,10 @@ Rust compiler note: this packaging environment still does not provide `cargo`, `
- Rebalanced only the five quick-device mode columns and reduced their horizontal padding from 5 px to 3 px. This keeps all five labels on one line without reducing the shared 9 px control font.
- Verified the Home Assistant integration already exposes every controller zone as an independent climate thermostat with `TURN_ON`/`TURN_OFF`, plus a separate per-zone Enabled switch. Turning on a zone thermostat sends only `enabled: true` for that zone; it does not power the whole group. Physical indoor units also remain independently controllable through their own climate entities.
- Bumped application/Home Assistant package metadata to `0.7.7` and rotated the PWA cache key.
## 2026-08-26 v0.7.8 zone status wording and quick unit power
- Replaced the misleading thermostat runtime labels **Potrzeba komfortu** / **Komfort / standby inwertera** with **Zapotrzebowanie aktywne** / **Cel osiągnięty**. The Comfort wording remains reserved for the actual Comfort preset/profile. Runtime status also reports **Oczekiwanie** when thermostat control is paused by mode and **Brak pomiaru** when no valid control temperature is available, so **Cel osiągnięty** is not used as a generic idle state.
- Simplified per-unit manual power in Dashboard -> Quick thermostats: removed the redundant **Jednostka · pilot** caption and the separate ON/OFF pair. A single contextual **Włącz jednostkę** / **Wyłącz jednostkę** button now directly controls only that physical unit with the existing manual-takeover semantics.
- Bumped package/Home Assistant metadata to `0.7.8` and rotated the PWA cache key.
Generated
+1 -1
View File
@@ -633,7 +633,7 @@ dependencies = [
[[package]]
name = "gree-controller"
version = "0.7.7"
version = "0.7.8"
dependencies = [
"aes",
"aes-gcm",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "gree-controller"
version = "0.7.7"
version = "0.7.8"
edition = "2021"
authors = ["GREE Controller contributors"]
description = "Standalone local GREE HVAC controller with Web UI, SQLite and Home Assistant sensor support"
+10 -10
View File
@@ -1,10 +1,10 @@
d67af429e4da9ce08e9d2f2a8472849ffbd70d135b1c5da535a076026794d04c ./.env.example
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
702331da239c3b8ef2166913dfa40340cb8c44a996b03983ccc7490a1089e898 ./BUILD_REPORT.md
401e399e01e9999501f14771d9b5a693b34bb1e51ba33a37b441248b35c60479 ./Cargo.lock
e9336e73b6279a5b58d6577c8784c23f19bc1ae0c0e2c7074e6781dfec5a61e3 ./Cargo.toml
ff0beb45aafde9e919deaa38a8900c8bc09ae5a853e1a17ef6a2b596eaa38a4e ./BUILD_REPORT.md
8d644882219821d356fdc1e65f2dfd6e15e0ac6ad4d83b6cc9e99b914d28afe1 ./Cargo.lock
5f372aa21533c57499cb6ce87db5708e5127bd8021b1113a0cc5393a8c74dd8e ./Cargo.toml
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
9abaeb7f6c29ec8775ef6edeb683916323aa17ad83027bbe2b449456d682dd9c ./README.md
8d296b79780b105cce00d1ca447727316bb3363984129740ac1c6d3868b3b0b6 ./README.md
f2c8eb28fedb26bb8f5636e1805e7a782cc472b87d882f6752d520f16216a248 ./build.rs
8e72ed628055338d8e9ecc3d9f6929f225fc1ce00fa2e0f19765594a1bcb824f ./docs/API.md
234dd200e380a13ecd3e61b4ea455f6f08d64ce89382077dee80684acadb9703 ./docs/HOME_ASSISTANT_MIGRATION.md
@@ -23,7 +23,7 @@ f31f3909f03fe4be105f89bcb4fe86331b01757288ac342ed0d26b35422d6387 ./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
b89c0838188944f1054f3885a75cd5a35f4084b6a740f0fa3cb373967b5af61f ./home-assistant/custom_components/gree_controller/manifest.json
f37d31b0b823a6ee516ebc34599c52e19dafcf1e7074253d08913587f3c5294c ./home-assistant/custom_components/gree_controller/manifest.json
d0206766d76f70ea1c75a618e41bf1e8e8c0429f42ee97f40daac1ea12f16658 ./home-assistant/custom_components/gree_controller/number.py
a697d18173087621a666781137f07617d23c1fb2720d8b246637a804634d5299 ./home-assistant/custom_components/gree_controller/select.py
047f3db03f0db54a40d63092370e5861510d611edfb0a0659e4c25b949d47854 ./home-assistant/custom_components/gree_controller/sensor.py
@@ -32,8 +32,8 @@ a697d18173087621a666781137f07617d23c1fb2720d8b246637a804634d5299 ./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
a30a0009e07f79531e1989a08c4585020368f558036f5b50630fa54ea5fe3e36 ./lang/en.json
439e58a61087c67baaae1f7d5d58620cab69fd630e7777c8ab827ad20f109522 ./lang/pl.json
62dcacc6f168edfaa861d8c5ff32883f35bcf0a045f6ee3899588a748427ce63 ./lang/en.json
79cda4f5b28ebf760e82b05aeef11a79c0129e895c4c1588344561d1a948bce3 ./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
48a001996a8e0f7ab847fad062f6973173fce2766bdfa50cbd49c07bcf628fa5 ./src/state.rs
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
7bdca69f08f871b9fcf965751ccc6354704c1b1bf38fd94a04637ebc0bd2e881 ./web/app.js
b04c9a8d013a1d512cb5246b76ae84ca20770a4561cfa23f51109db01e6a468f ./web/app.js
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
5e39be3a379e3036c03773d5f4cc22baed79e9688b574b438fe87b6f5828089a ./web/index.html
fd26156e9f1d6713d3def564ad000553d9a16a24376059701db2ee762c99ee6c ./web/manifest.webmanifest
ba3b35a38d76993140f37a3c1f05aa4271e83d57b365346db1e14d9ca4ee7d02 ./web/styles.css
68578c989fe37be717905eda435fd6d725345c28260aad06fe44bfb1116326ca ./web/sw.js
d4369e34b5392be142c038f5fc68343bd3514a8b34aa8503f1ed2ed700737299 ./web/styles.css
47189dc2da72f976e5e868fcc82284f7ee45b47747812ac08f54872e6d5b758d ./web/sw.js
d505d793ce7cc9485b45b78bba1c0d51887adc7451ab59a42702946e5b991382 ./web/theme-init.js
+9 -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.7**.
Current version: **0.7.8**.
## 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.7.zip
unzip gree-controller-v0.7.8.zip
cd gree-controller
chmod +x scripts/*.sh
./scripts/dev.sh
@@ -475,3 +475,10 @@ Zones now use setpoint modulation, global seasonal house mode, cooling/heating C
- Adjusted the five-column quick-device mode row so longer Polish labels such as **Chłodzenie** and **Osuszanie** receive more width without reducing the whole row font size.
- The change is limited to the dashboard quick-device mode row; thermostat/group layouts are unchanged.
- Rotated the PWA cache key so browsers fetch the corrected stylesheet after upgrade.
## 0.7.8 Zone status wording and unit power
- Zone runtime status now says **Zapotrzebowanie aktywne** when the thermostat is requesting work and **Cel osiągnięty** when the target/hysteresis condition is satisfied; **Komfort** remains only the preset name. Paused control is shown as **Oczekiwanie**, and missing control temperature as **Brak pomiaru**.
- Quick thermostats use one contextual **Włącz jednostkę / Wyłącz jednostkę** control instead of the redundant **Jednostka · pilot** caption and separate ON/OFF buttons.
- Direct unit power still behaves as manual/remote takeover and does not enable the whole group.
@@ -1,7 +1,7 @@
{
"domain": "gree_controller",
"name": "GREE Controller",
"version": "0.7.7",
"version": "0.7.8",
"config_flow": true,
"integration_type": "hub",
"iot_class": "local_polling",
+8 -5
View File
@@ -260,8 +260,8 @@
"zones.overrideUntil": "Manual until",
"zones.scheduleControl": "Schedule control",
"zones.manualNoBoundary": "Manual · no next transition",
"zones.requesting": "Comfort demand",
"zones.satisfied": "Satisfied / inverter standby",
"zones.requesting": "Demand active",
"zones.satisfied": "Target reached",
"zones.quickHintSmart": "Quick changes last until the next schedule transition; without a schedule they stay active.",
"preset.auto": "Auto",
"preset.comfort": "Comfort",
@@ -593,8 +593,11 @@
"zones.manualDeviceUntil": "Manual control / remote until {time}",
"zones.manualDeviceNoBoundary": "Manual control / remote · until resumed",
"zones.resumeAutomation": "Resume automation",
"zones.unitPowerManual": "Unit · remote",
"zones.unitPowerManualHint": "Turns only this unit on or off like a remote; it does not enable the whole group.",
"zones.unitPowerUpdated": "Single-unit power updated"
"zones.unitPowerManualHint": "Controls only this unit directly like a remote; it does not enable the whole group.",
"zones.unitPowerUpdated": "Single-unit power updated",
"zones.turnUnitOn": "Turn unit on",
"zones.turnUnitOff": "Turn unit off",
"zones.waiting": "Waiting",
"zones.noMeasurement": "No measurement"
}
}
+8 -5
View File
@@ -260,8 +260,8 @@
"zones.overrideUntil": "Ręcznie do",
"zones.scheduleControl": "Sterowanie harmonogramem",
"zones.manualNoBoundary": "Ręcznie · brak kolejnego przejścia",
"zones.requesting": "Potrzeba komfortu",
"zones.satisfied": "Komfort / standby inwertera",
"zones.requesting": "Zapotrzebowanie aktywne",
"zones.satisfied": "Cel osiągnięty",
"zones.quickHintSmart": "Szybka zmiana działa do kolejnego przejścia harmonogramu; bez harmonogramu pozostaje aktywna.",
"preset.auto": "Auto",
"preset.comfort": "Komfort",
@@ -593,8 +593,11 @@
"zones.manualDeviceUntil": "Sterowanie ręczne / pilot do {time}",
"zones.manualDeviceNoBoundary": "Sterowanie ręczne / pilot · do wznowienia",
"zones.resumeAutomation": "Wznów automatykę",
"zones.unitPowerManual": "Jednostka · pilot",
"zones.unitPowerManualHint": "Włącza lub wyłącza tylko tę jednostkę jak pilot; nie włącza całej grupy.",
"zones.unitPowerUpdated": "Zmieniono zasilanie pojedynczej jednostki"
"zones.unitPowerManualHint": "Steruje bezpośrednio tylko tą jednostką jak pilot; nie włącza całej grupy.",
"zones.unitPowerUpdated": "Zmieniono zasilanie pojedynczej jednostki",
"zones.turnUnitOn": "Włącz jednostkę",
"zones.turnUnitOff": "Wyłącz jednostkę",
"zones.waiting": "Oczekiwanie",
"zones.noMeasurement": "Brak pomiaru"
}
}
+12 -6
View File
@@ -314,9 +314,7 @@ function renderControlPlan() {
const target = zone.target_temperature == null ? '--' : Number(zone.target_temperature).toFixed(1);
const groupNames = zoneGroups.get(zone.zone_id) || [];
const scope = groupNames.length ? `${tr('groups.group')}: ${groupNames.join(' · ')}` : (zone.device_name || tr('common.noDevice'));
const planState = zone.device_manual_override
? tr('zones.manualDeviceControl')
: (zone.enabled ? (zone.demand ? tr('zones.requesting') : tr('zones.satisfied')) : tr('common.disabled'));
const planState = zoneRuntimeStatusLabel(zone, zone.mode || 'off');
return `<article class="panel plan-card plan-zone ${zone.enabled ? '' : 'disabled'}"><div class="plan-card-head"><div><span class="eyebrow">${esc(scope)}</span><h3>${esc(zone.zone_name)}</h3></div><span class="badge ${zone.device_manual_override ? 'manual-override' : (zone.enabled && zone.demand ? 'active' : '')}">${esc(planState)}</span></div><div class="plan-temp"><span>${fmtTemp(zone.current_temperature)}</span><b>→</b><strong>${esc(target)}<small>°C</small></strong></div><p>${esc(houseModeLabel(zone.mode || 'off'))} · ${esc(zonePresetLabel(zone.preset))}${zone.current_schedule_name ? ` · ${esc(zone.current_schedule_name)}` : ''}</p><ul class="plan-events">${events.length ? events.map(planEventMarkup).join('') : `<li class="muted">${esc(tr('plan.noEvents'))}</li>`}</ul></article>`;
}).join('');
@@ -686,6 +684,14 @@ function zonePresetLabel(preset) {
return tr(key) === key ? (preset || 'comfort') : tr(key);
}
function zoneRuntimeStatusLabel(zone, effectiveMode) {
if (zone.device_manual_override === true) return tr('zones.manualDeviceControl');
if (!zone.enabled) return tr('common.disabled');
if ((effectiveMode || 'off') === 'off') return tr('zones.waiting');
if (zone.current_temperature == null) return tr('zones.noMeasurement');
return zone.demand ? tr('zones.requesting') : tr('zones.satisfied');
}
function zoneCard(zone, detailed = true) {
const device = app.devices.find(d => d.id === zone.device_id);
const state = zone.enabled ? tr('common.active') : tr('common.disabled');
@@ -710,7 +716,7 @@ function zoneCard(zone, detailed = true) {
? `<div class="manual-override-panel"><div><strong>${esc(tr('zones.manualDeviceControl'))}</strong><p>${esc(tr('zones.manualDeviceDescription'))}</p></div><button type="button" data-action="zone-resume-automation" data-id="${esc(zone.id)}">${esc(tr('zones.resumeAutomation'))}</button></div>`
: '';
const manualPowerDisabled = !device || device.enabled === false ? ' disabled' : '';
const manualUnitPower = `<div class="zone-manual-power"><small title="${esc(tr('zones.unitPowerManualHint'))}">${esc(tr('zones.unitPowerManual'))}</small><div class="quick-control-row quick-control-row-2"><button class="${device?.power ? 'active' : ''}" data-action="zone-device-power" data-id="${esc(zone.id)}" data-value="true"${manualPowerDisabled}>${esc(tr('common.on'))}</button><button class="${device && !device.power ? 'active' : ''}" data-action="zone-device-power" data-id="${esc(zone.id)}" data-value="false"${manualPowerDisabled}>${esc(tr('common.off'))}</button></div></div>`;
const manualUnitPower = `<div class="zone-manual-power"><button type="button" class="zone-unit-power-toggle ${device?.power ? 'active' : ''}" title="${esc(tr('zones.unitPowerManualHint'))}" data-action="zone-device-power" data-id="${esc(zone.id)}" data-value="${device?.power ? 'false' : 'true'}" aria-pressed="${device?.power ? 'true' : 'false'}"${manualPowerDisabled}>${esc(tr(device?.power ? 'zones.turnUnitOff' : 'zones.turnUnitOn'))}</button></div>`;
if (detailed) {
const groupNames = (app.groups || []).filter(group => (group.zone_ids || []).includes(zone.id)).map(group => group.name);
@@ -720,7 +726,7 @@ function zoneCard(zone, detailed = true) {
<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 class="zone-config-runtime"><span class="badge ${zone.enabled && zone.demand ? 'active' : ''}">${esc(zoneRuntimeStatusLabel(zone, effectiveMode))}</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>
@@ -742,7 +748,7 @@ function zoneCard(zone, detailed = true) {
${['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 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>${deviceManualOverride ? esc(tr('zones.manualDeviceControl')) : (zone.demand ? esc(tr('zones.requesting')) : esc(tr('zones.satisfied')))}</span><span>${esc(override)}</span></div>
<div class="zone-state-line"><span>${esc(zoneRuntimeStatusLabel(zone, effectiveMode))}</span><span>${esc(override)}</span></div>
${manualTakeover}
</article>`;
}
+3 -3
View File
@@ -780,9 +780,9 @@ body.simulation-standalone [data-view="simulation"] { min-height:calc(100vh - 36
.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 .zone-manual-power { margin-top:6px; }
.quick-thermostat-control .zone-manual-power > small { display:block; margin-bottom:2px; color:var(--muted); font-size:9px; }
.quick-thermostat-control .zone-manual-power button.active { color:var(--accent-text); background:var(--accent); font-weight:800; }
.quick-thermostat-control .zone-manual-power { display:flex; margin-top:6px; }
.quick-thermostat-control .zone-manual-power .zone-unit-power-toggle { width:100%; min-height:30px; font-size:10px; }
.quick-thermostat-control .zone-manual-power .zone-unit-power-toggle.active { color:var(--accent-text); background:var(--accent); font-weight:800; }
.quick-thermostat-control .thermostat-main { margin:8px 0 7px; }
.quick-thermostat-control .zone-enable-toggle { min-height:28px; padding:4px 8px; font-size:10px; }
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'gree-controller-v077-mode-label-fit';
const CACHE = 'gree-controller-v078-zone-status-unit-power';
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')];