From 65eaf50fb6136d01e7e3fcd52561e917a9c5f86c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Thu, 27 Aug 2026 16:44:41 +0200 Subject: [PATCH] v0.7.11 --- BUILD_REPORT.md | 25 ++++++++++++++++--- Cargo.lock | 2 +- Cargo.toml | 2 +- FILE_MANIFEST.sha256 | 22 ++++++++-------- README.md | 11 ++++++-- docs/API.md | 2 +- .../gree_controller/manifest.json | 2 +- src/api.rs | 20 --------------- src/engine.rs | 15 ++++++++--- web/app.js | 4 +-- web/styles.css | 6 ++--- web/sw.js | 2 +- 12 files changed, 64 insertions(+), 49 deletions(-) diff --git a/BUILD_REPORT.md b/BUILD_REPORT.md index c3e319c..ab5dac9 100644 --- a/BUILD_REPORT.md +++ b/BUILD_REPORT.md @@ -1,8 +1,8 @@ -# GREE Controller v0.7.10 - build and validation report +# GREE Controller v0.7.11 - build and validation report ## Scope -Version 0.7.10 includes the previous climate-group, whole-house and zone-profile fixes plus a strict separation between local quick-thermostat ownership and true physical/manual-device takeover. Global controls are consolidated, group control stays immediately below them, and lower-priority dashboard sections are collapsed on demand. +Version 0.7.11 includes the previous climate-group, whole-house and zone-profile fixes plus a strict separation between local quick-thermostat ownership and true physical/manual-device takeover. Global controls are consolidated, group control stays immediately below them, and lower-priority dashboard sections are collapsed on demand. ## Implemented changes @@ -398,7 +398,7 @@ Validation for this package is recorded after the final archive/manifest checks. ## 2026-08-27 v0.7.10 local quick thermostat and false takeover fix - Added persisted `local_thermostat_power` ownership with three distinct states: normal group/schedule ownership (`null`), locally forced thermostat ON (`true`) and locally forced thermostat OFF (`false`). -- Dashboard quick ON/OFF and the legacy zone manual-power route now use local thermostat semantics; they never call the direct/manual-device command path. +- Dashboard quick ON/OFF uses local thermostat semantics and never calls the direct/manual-device command path. - A locally active thermostat bypasses disabled climate-group power gates while retaining the normal sensor, hysteresis, profile/target, Smart Fan and GREE setpoint-modulation logic. - **Resume automation** for a local thermostat clears local ownership and returns the zone to group/schedule control. The existing device takeover Resume action remains separate. - Direct device commands and genuine remote/pilot changes still create `device_manual_override`; local thermostat ownership does not. @@ -417,3 +417,22 @@ Validation for this package is recorded after the final archive/manifest checks. - Rust source delimiter/static invariant checks passed, including the new regulator wake-up and control-plan fields. - `FILE_MANIFEST.sha256` is regenerated from the final tree and verified before packaging; the resulting ZIP is tested with `unzip -t`. - This environment does not provide `cargo`, `rustc` or `rustfmt`, so the normal target/CI update path must still run `cargo test --all-targets` and `cargo build --release` before replacing the running binary. + + +## 2026-08-27 v0.7.11 cleanup and compile fix + +- Removed the obsolete `/api/zones/{id}/manual-power` compatibility route, request structure and handler. The only zone quick-power API is now `POST /api/zones/{id}/control` with `power:true/false`. +- Renamed Web UI local-thermostat power internals so they no longer use misleading `manual-power` terminology. +- Fixed Rust error E0505 in `local_thermostat_ownership_blocks_direct_automation`: the test now passes a borrowed one-element slice with `std::slice::from_ref(&zone)` instead of moving `zone` while borrowing `zone.device_id`. +- Advanced package/Home Assistant/PWA metadata to `0.7.11`. + +## Final validation for v0.7.11 + +- `node --check` passed for Web UI JavaScript files. +- Home Assistant modules, helper scripts and `make_zip.py` passed `python3 -m py_compile`. +- JSON files parsed successfully; Polish and English language packs have identical 602-key sets. +- All shell scripts passed `bash -n`; `web/index.html` parsed successfully and CSS braces are balanced. +- Runtime source trees (`src`, `web`, `home-assistant`, `scripts`) contain no legacy `manual-power` route, handler, symbol or CSS naming. +- Cargo package and Home Assistant manifest report version `0.7.11`; PWA cache metadata was rotated for this release. +- The reported Rust E0505 test case was corrected with borrow-only `std::slice::from_ref(&zone)` calls. +- This packaging environment does not provide `cargo`, `rustc` or `rustfmt`; target/CI must run `cargo test --all-targets` and `cargo build --release` before deployment. diff --git a/Cargo.lock b/Cargo.lock index 45ea3b1..b21c53c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -633,7 +633,7 @@ dependencies = [ [[package]] name = "gree-controller" -version = "0.7.10" +version = "0.7.11" dependencies = [ "aes", "aes-gcm", diff --git a/Cargo.toml b/Cargo.toml index ed70ce1..e1f139f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gree-controller" -version = "0.7.10" +version = "0.7.11" edition = "2021" authors = ["GREE Controller contributors"] description = "Standalone local GREE HVAC controller with Web UI, SQLite and Home Assistant sensor support" diff --git a/FILE_MANIFEST.sha256 b/FILE_MANIFEST.sha256 index cd3f67f..f2089a7 100644 --- a/FILE_MANIFEST.sha256 +++ b/FILE_MANIFEST.sha256 @@ -1,12 +1,12 @@ d67af429e4da9ce08e9d2f2a8472849ffbd70d135b1c5da535a076026794d04c ./.env.example a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore -ef3c2cdf13fdeac192c2f94fb321af3dc60ad93d97266c00cf2b8021ed0f211c ./BUILD_REPORT.md -723ce7201821a3ff3404c93430de5dc8451f0e091f39fdbe532167d9a647250a ./Cargo.lock -1c52ed460ad66fa818d0f405ca92ef9df1beff0c220f34c548e54d93e7e99721 ./Cargo.toml +d660312751ad5ddbc730d25ddd2181b1c4c8be4a8da18e2b23eaa39ca00c1d63 ./BUILD_REPORT.md +83f9d4ee92b722c3f81bb739a55d4b2d66bbd09276c31876cbf4f610a812c280 ./Cargo.lock +d42b2cdbf06555dd327405c9f84c14b7f222faa545c4e6f5d9fde8b6e9ad55ac ./Cargo.toml 19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE -536dec61cf1e37c98b33989db53f356895fb1c241d72eccf317e800a830fa984 ./README.md +f846576df3ace004a7c4f3cd104242cbc38814b383938d6d914ad3ebc085c71e ./README.md f2c8eb28fedb26bb8f5636e1805e7a782cc472b87d882f6752d520f16216a248 ./build.rs -5254141d54e0fe55bb4942d9328221eeddae6d8f63d71d33c2e642647bd3cac6 ./docs/API.md +38bbc16b8350ca5f70646b40d1b7d0a9231425208c99f4f2d17979431c0c289a ./docs/API.md 234dd200e380a13ecd3e61b4ea455f6f08d64ce89382077dee80684acadb9703 ./docs/HOME_ASSISTANT_MIGRATION.md 7a88d6e76fda21e5d34ab351e26bc10dc1f8f7b3055505aefad1df7c56d65ae4 ./docs/LOCALIZATION.md 10a0722e1100fb4a05e3067daeb67dc47b0c0a096b43b1cbf2bf002967ce7d98 ./docs/LXC.md @@ -23,7 +23,7 @@ b065b61ef1adafe96aae3e47bff3e8b0bba57796fdfac1503669046beb1c0157 ./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 -dcfa12c94cfb939078c13658162c2f1e4bf7ad2f29d790524e72a1d3c4a4f3d7 ./home-assistant/custom_components/gree_controller/manifest.json +7a0171c08b8edd44369416b067c0a267b88c8fb28fbf34125385d0d6c9e308cb ./home-assistant/custom_components/gree_controller/manifest.json 38d659d0273c0d1428679e44574a2666c605d1b3796d323b626baa40560788cf ./home-assistant/custom_components/gree_controller/number.py 39c4309001b75abb56234f05662bc06e077054986876f1927937edbce528ec95 ./home-assistant/custom_components/gree_controller/select.py 1cae12876eb81085910907cf1fcf340a18dcba4f8c967ef222c9496325057849 ./home-assistant/custom_components/gree_controller/sensor.py @@ -46,10 +46,10 @@ e00d211e3885e30d7fed1e43b44e6fdad40a67019060156c0641816a93e3365f ./scripts/netw 81345b6a0b51736bdbc98fd23199b62e4c721b4e7437e02dab7ea79b97dff29a ./scripts/service.sh b48fc84d79aab381226363ac8473f981bcba5e4911c4cc0011261182debf4250 ./scripts/smoke.sh b50782b3742dfbf8a319c60571c968e93fdf8547db747c759edcffae68cb98bf ./scripts/update.sh -7c4bf023ad1df88abdbc98eb0ff60f984396359f6d8320b8817da5fe832e76a6 ./src/api.rs +9a9c8267a8b4dab192b1d97d2ef3a255338e2a3831f7bf33ce4b18fb85f74f19 ./src/api.rs 32b1d0a7ac2060fd18fe7f1ca1d110ee45beaf67e81514d24d6bde3d36301387 ./src/config.rs 5dfda2f4dc540c502885b0cd7017dc77768684588acf528f01d1fd88f1af4aec ./src/db.rs -5da9a1369af514300911dc59c8f53a5d254a6e3622d6a689786b3ca5bfb1fa40 ./src/engine.rs +2c7f21d18d04d63691f0856b895fd29cd625861220773dce9e74a52d5d241619 ./src/engine.rs ae3b496749a3fd723b243d9bea92e5d76249f52814c80359ad9bac53abacb074 ./src/error.rs 081f6909e95839c7da8c40b866af9de237e0540f3e65e1ce739ce70a6fa94846 ./src/home_assistant.rs 190b0a33431539676e5dd7796698077f16c179d42eae4501ca96a91bf797cbf8 ./src/influxdb.rs @@ -62,10 +62,10 @@ a910bd9432a393740c0f6fab52bfcb551f0ea756718d66d290fd2610767cf07c ./src/protocol 6a1c0cab3eab80ecd254c5e486b1fba068523edb8d699c64054c17f67f4a31d0 ./src/queries.rs 6081635986e1ce39a0e195c3976ae30a033454fe90ec80bcbdf05fc26bf7087f ./src/state.rs b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service -f7c44789b68f6f49e8409b3867f410f2ed39cb59d0bbffceec7fd9dad2bd5f71 ./web/app.js +23dcfdfc9eb2093c7289dad522dcaec76c802c7b45dbd58e074b3bdf8be2f323 ./web/app.js e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg 5e39be3a379e3036c03773d5f4cc22baed79e9688b574b438fe87b6f5828089a ./web/index.html fd26156e9f1d6713d3def564ad000553d9a16a24376059701db2ee762c99ee6c ./web/manifest.webmanifest -d4369e34b5392be142c038f5fc68343bd3514a8b34aa8503f1ed2ed700737299 ./web/styles.css -38cec1ebc782749c4838e1bd73cd371fb57a99310df793c52a69adad9f36b5c7 ./web/sw.js +59df58dc528fbbbb5b6097df7110e78d35951506cc056816c7b2be51f4d51965 ./web/styles.css +f7d2bae7f7abe896ad56996b5423547e4edd056c9bcd7fedbbf89ea91f5bacee ./web/sw.js d505d793ce7cc9485b45b78bba1c0d51887adc7451ab59a42702946e5b991382 ./web/theme-init.js diff --git a/README.md b/README.md index 43fed1a..527ede8 100644 --- a/README.md +++ b/README.md @@ -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.10**. +Current version: **0.7.11**. ## 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.10.zip +unzip gree-controller-v0.7.11.zip cd gree-controller chmod +x scripts/*.sh ./scripts/dev.sh @@ -477,6 +477,13 @@ Zones now use setpoint modulation, global seasonal house mode, cooling/heating C - Rotated the PWA cache key so browsers fetch the corrected stylesheet after upgrade. + +## 0.7.11 Cleanup and Rust test fix + +- Removed the obsolete `/api/zones/{id}/manual-power` compatibility endpoint and its handler; local thermostat power has one canonical API path: `/api/zones/{id}/control`. +- Renamed dashboard internals from `manual-power` terminology to `local-thermostat-power` to match the actual control model. +- Fixed the Rust ownership error in `local_thermostat_ownership_blocks_direct_automation` by borrowing the test zone via `std::slice::from_ref`, so the test no longer moves `zone` while borrowing `zone.device_id`. + ## 0.7.10 Local quick-thermostat ownership - Quick thermostat ON/OFF now controls a dedicated local thermostat state instead of using remote/manual-device semantics. diff --git a/docs/API.md b/docs/API.md index 0c1b7c4..e5d043e 100644 --- a/docs/API.md +++ b/docs/API.md @@ -137,7 +137,7 @@ curl -X POST "$BASE/api/zones/ZONE_ID/control" -H "$AUTH" -H 'Content-Type: appl `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. -`power:true/false` on `/api/zones/{id}/control` is a local quick-thermostat power override, not a physical/manual command. `true` lets that zone run through the full thermostat even when its climate group is off; `false` keeps only that zone locally off. `clear_local_thermostat_override:true` returns power ownership to the normal group/schedule path. The legacy `/api/zones/{id}/manual-power` route is retained for compatibility but now has the same local-thermostat semantics. Direct/pilot-style control remains `/api/devices/{id}/command`. +`power:true/false` on `/api/zones/{id}/control` is a local quick-thermostat power override, not a physical/manual command. `true` lets that zone run through the full thermostat even when its climate group is off; `false` keeps only that zone locally off. `clear_local_thermostat_override:true` returns power ownership to the normal group/schedule path. Direct/pilot-style control remains `/api/devices/{id}/command`. ### Physical/manual device takeover diff --git a/home-assistant/custom_components/gree_controller/manifest.json b/home-assistant/custom_components/gree_controller/manifest.json index b69f9f7..40c1bdd 100644 --- a/home-assistant/custom_components/gree_controller/manifest.json +++ b/home-assistant/custom_components/gree_controller/manifest.json @@ -1,7 +1,7 @@ { "domain": "gree_controller", "name": "GREE Controller", - "version": "0.7.10", + "version": "0.7.11", "config_flow": true, "integration_type": "hub", "iot_class": "local_polling", diff --git a/src/api.rs b/src/api.rs index 7a1c917..f52141c 100644 --- a/src/api.rs +++ b/src/api.rs @@ -50,7 +50,6 @@ pub fn router(state: AppState) -> Router { .route("/api/zones", get(list_zones).post(create_zone)) .route("/api/zones/:id", get(get_zone).put(update_zone).delete(delete_zone)) .route("/api/zones/:id/control", post(update_zone_control)) - .route("/api/zones/:id/manual-power", post(update_zone_manual_power)) .route("/api/zones/:id/schedule-template", post(apply_schedule_template)) .route("/api/groups", get(list_groups).post(create_group)) .route("/api/groups/:id", get(get_group).put(update_group).delete(delete_group)) @@ -720,25 +719,6 @@ async fn update_zone_control(State(state): State, Path(id): Path, - Path(id): Path, - Json(input): Json, -) -> Result, AppError> { - // Backward-compatible route: despite the historical name this now controls the local - // thermostat, not the physical unit like a pilot. Direct/pilot semantics stay on /devices. - let zone = apply_zone_control_patch( - &state, - &id, - ZoneControlPatch { power: Some(input.power), ..Default::default() }, - ).await?; - let device = state.db.get_device(&zone.device_id)?; - Ok(Json(json!({"zone": zone, "device": device}))) -} - async fn ensure_device_stopped_for_detach(state: &AppState, device_id: &str, source: &str) -> Result<(), AppError> { let Some(device) = state.db.get_device(device_id)? else { return Ok(()); }; diff --git a/src/engine.rs b/src/engine.rs index 240f6c9..20ea1d4 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -2301,11 +2301,20 @@ mod tests { #[test] fn local_thermostat_ownership_blocks_direct_automation() { let mut zone = test_zone("device"); - assert!(!device_blocked_by_local_thermostat(&zone.device_id, &[zone.clone()])); + assert!(!device_blocked_by_local_thermostat( + &zone.device_id, + std::slice::from_ref(&zone), + )); zone.local_thermostat_power = Some(true); - assert!(device_blocked_by_local_thermostat(&zone.device_id, &[zone.clone()])); + assert!(device_blocked_by_local_thermostat( + &zone.device_id, + std::slice::from_ref(&zone), + )); zone.local_thermostat_power = Some(false); - assert!(device_blocked_by_local_thermostat(&zone.device_id, &[zone])); + assert!(device_blocked_by_local_thermostat( + &zone.device_id, + std::slice::from_ref(&zone), + )); } #[test] diff --git a/web/app.js b/web/app.js index 41281c7..4bcd755 100644 --- a/web/app.js +++ b/web/app.js @@ -730,7 +730,7 @@ function zoneCard(zone, detailed = true) { : ''; const localPowerDisabled = !device || device.enabled === false ? ' disabled' : ''; const localRequestedOn = localThermostatPower === true ? true : (localThermostatPower === false ? false : !!device?.power); - const manualUnitPower = `
`; + const localThermostatPowerControl = `
`; if (detailed) { const groupNames = (app.groups || []).filter(group => (group.zone_ids || []).includes(zone.id)).map(group => group.name); @@ -756,7 +756,7 @@ function zoneCard(zone, detailed = true) { return `

${esc(zone.name)}

${esc(device?.name || tr('common.noDevice'))} · ${esc(zonePresetLabel(displayPreset))}

- ${manualUnitPower} + ${localThermostatPowerControl}
${esc(tr('zones.measurement'))}${fmtTemp(roomTemperature)}
${Number.isFinite(target)?target.toFixed(1):'--'}°C
${esc(tr('zones.deviceTarget'))}${fmtTemp(zone.device_setpoint)}
${['auto','comfort','sleep','away'].map(preset=>``).join('')} diff --git a/web/styles.css b/web/styles.css index 5f7828e..2843ecd 100644 --- a/web/styles.css +++ b/web/styles.css @@ -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 { 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 .zone-local-thermostat-power { display:flex; margin-top:6px; } +.quick-thermostat-control .zone-local-thermostat-power .zone-unit-power-toggle { width:100%; min-height:30px; font-size:10px; } +.quick-thermostat-control .zone-local-thermostat-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; } diff --git a/web/sw.js b/web/sw.js index 8f6bf4a..40c7858 100644 --- a/web/sw.js +++ b/web/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'gree-controller-v0710-local-thermostat-ownership'; +const CACHE = 'gree-controller-v0711-clean-local-thermostat'; 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')];