From 556a031358ff7f962629e7b02f9fbd7602a4bc2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Fri, 28 Aug 2026 10:12:48 +0200 Subject: [PATCH] v0.8.5 --- BUILD_REPORT.md | 60 ++++++------------- Cargo.lock | 2 +- Cargo.toml | 2 +- FILE_MANIFEST.sha256 | 14 ++--- README.md | 11 +++- .../gree_controller/manifest.json | 2 +- src/engine.rs | 28 ++++++++- web/sw.js | 2 +- 8 files changed, 65 insertions(+), 56 deletions(-) diff --git a/BUILD_REPORT.md b/BUILD_REPORT.md index 487c283..ca9bc54 100644 --- a/BUILD_REPORT.md +++ b/BUILD_REPORT.md @@ -1,53 +1,29 @@ -# GREE Controller v0.8.4 - build and validation report +# GREE Controller v0.8.5 - build and validation report -## 0.8.4 Scheduled Temporary Quick Thermostat start and ownership hardening +## 0.8.5 Temporary temperature-condition activation fix -Source baseline: v0.8.3. +Source baseline: v0.8.4. -### Changes +### Fix -- Added independent Temporary Quick Thermostat start rules: immediately, after a delay, or at an absolute date/time. -- Delayed sessions are persisted without taking local thermostat ownership before `started_at`; the controller wakes at the scheduled start and activates the saved target then. -- Duration, absolute finish validation, safety timeout and next-schedule-boundary calculations are anchored to the effective start time. -- Added pending-session countdown/status to the Quick Thermostat button and modal; after activation the same control switches to the existing finish countdown/status. -- Active Temporary Quick Thermostat targets keep priority over schedule transitions and schedule edits. -- Direct device automations remain blocked by local thermostat ownership while the temporary session is active; group climate mode/preset actions are deferred for an actively owned zone, while group power state may change underneath and becomes effective after hand-back. -- Before a delayed start, normal schedule/automation ownership is unchanged unless another pre-existing local/manual override already owns the zone. -- Local Quick Thermostat ON can use the zone's last Heat/Cool mode when inherited house climate mode is `off`; whole-house master power remains authoritative. -- Cancelling a pending session does not clear unrelated manual quick-control state. -- Added Rust regression tests for delayed-session ownership, pre-start temperature conditions, schedule target priority and local Quick Thermostat operation with inherited house mode `off`. -- Bumped controller and Home Assistant integration version to 0.8.4 and rotated the PWA service-worker cache key. - -### Ownership/priority scenarios reviewed - -| Scenario | Expected result | -| --- | --- | -| Delayed session before start | Normal schedule and automations continue; pending session owns nothing yet. | -| Start time reached | Session acquires local thermostat ownership, enables the zone if needed and applies its saved target. | -| Schedule transitions while active | Schedule may advance internally, but `manual_setpoint` from Temporary Quick Thermostat remains effective. | -| Schedule edited while active | Generic schedule-boundary override timer is not re-armed, so the temporary target is not cleared. | -| Direct-device automation while active | Suppressed by local thermostat ownership. | -| Group mode/preset automation while active | Climate change is deferred for the owned zone; it cannot clear/change the temporary target. | -| Group power automation while active | Group gate can change underneath, but local temporary ownership keeps the zone running; the gate applies after hand-back. | -| Temporary finish | Local ownership is cleared and the currently applicable schedule/group/house state is recalculated. | -| Session started from disabled zone | Zone is temporarily enabled, then restored to disabled and its unit is stopped after hand-back. | +- Corrected Temporary Quick Thermostat temperature-condition activation detection. +- `activated_at` now correctly marks a session as active for `temperature_reached` and `temperature_stable` evaluation. +- Existing `local_thermostat_power == true` remains a compatibility/recovery marker for sessions persisted by earlier releases. +- Future delayed sessions still cannot evaluate or complete their temperature condition before `started_at`. +- The change directly addresses the v0.8.4 updater failures: + - `temporary_stable_condition_requires_continuous_hold_time` + - `temporary_stable_condition_resets_when_temperature_leaves_range` ### Validation executed in this environment -- `node --check web/app.js` -- `node --check web/theme-init.js` -- `node --check web/sw.js` -- parsed all JSON files, including PL/EN language packs and Home Assistant manifest -- checked duplicate HTML IDs -- checked that newly referenced PL/EN translation keys exist -- `python3 -m compileall -q home-assistant scripts make_zip.py` -- `bash -n scripts/*.sh` -- checked version consistency across Cargo.toml, Cargo.lock, README and Home Assistant manifest +- JavaScript syntax checks for `web/app.js`, `web/theme-init.js`, `web/sw.js` +- JSON parsing for project JSON files +- Python syntax compilation for Python sources +- shell syntax checks for `scripts/*.sh` +- version consistency checks - regenerated and verified `FILE_MANIFEST.sha256` -- tested the final ZIP with `unzip -t` +- final ZIP integrity check ### Rust toolchain note -This execution environment does not contain `cargo`, `rustc` or `rustfmt`, so the Rust regression tests and full compilation cannot be executed here. The tests are included in `src/engine.rs`, and the installer/updater still runs `cargo test --all-targets` before replacing the running service. - ---- +This environment does not contain `cargo`, `rustc` or `rustfmt`, so Rust tests cannot be executed here. The supplied deployment log confirms v0.8.4 compiled and 46/48 tests passed; this hotfix changes the activation predicate responsible for the two failures. `scripts/update.sh` will run the full Rust test suite before replacing the service. diff --git a/Cargo.lock b/Cargo.lock index 4e17093..7dc7a2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -633,7 +633,7 @@ dependencies = [ [[package]] name = "gree-controller" -version = "0.8.4" +version = "0.8.5" dependencies = [ "aes", "aes-gcm", diff --git a/Cargo.toml b/Cargo.toml index d5cb483..8450463 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gree-controller" -version = "0.8.4" +version = "0.8.5" 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 692d767..949b1e3 100644 --- a/FILE_MANIFEST.sha256 +++ b/FILE_MANIFEST.sha256 @@ -1,10 +1,10 @@ d67af429e4da9ce08e9d2f2a8472849ffbd70d135b1c5da535a076026794d04c ./.env.example a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore -23209d6e3c3ba681d5e414aa85840b019c5e4c4ac3fe86afab8590eccbfb8315 ./BUILD_REPORT.md -450f637f06e0ca0e516bd144c3000ad707db51a343a095994bfc657ffcdf130a ./Cargo.lock -a9d33234e320683b0d6413dfa9098435d17213b8058639a849cc64a5f0ca25fa ./Cargo.toml +04ab15fbace991a367ab08543d17cd7c8f3aed06465e905f179ac6ca47b58359 ./BUILD_REPORT.md +bd7d9b714ad9096d7ecde66df3b97ae9a73c4caddcbbbd338e4fd3af5c09dd03 ./Cargo.lock +310dd2cd0f8be29268d31c4b969ef01e956265614df1c899f3291ed639452bc7 ./Cargo.toml 19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE -87c67cc28db5426d89cdbd4369a05016f0b058331273688bc52e286088b1b5dc ./README.md +ff399e8c70e10e8622ad08beba55c253fa5eb9627719aa2023147a91139b74b4 ./README.md 91c826e2f8c974bdb7b15e01e9b20487f06ff887247ecd9600f1263c11a61b96 ./build.rs d458a3b1ed381c885d7c4b7e38f306f3e66838d27480551436f1e340995ea6e2 ./docs/API.md 234dd200e380a13ecd3e61b4ea455f6f08d64ce89382077dee80684acadb9703 ./docs/HOME_ASSISTANT_MIGRATION.md @@ -24,7 +24,7 @@ ab08fbe40e9bb48ebcbfff98760aaa0c9434b61b705aaaadea0c09c255d99b0e ./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 -59c5239f76835e0a1c8326b2525f4cc1528a1a3021b7e197a216308e2b1421e5 ./home-assistant/custom_components/gree_controller/manifest.json +f3b837e006c0696928fa0140b42e0d69d44bbe4083460db71a62b4629f42ba84 ./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 @@ -50,7 +50,7 @@ b50782b3742dfbf8a319c60571c968e93fdf8547db747c759edcffae68cb98bf ./scripts/upda e1153953e3217380ed4f4559e85236d3e26d6dd6168683a721f053252426c454 ./src/api.rs 9040e8cb6647c76a875148b7591abcccfe4e2d4708ef462a1e5d25dd4ccac911 ./src/config.rs 5dfda2f4dc540c502885b0cd7017dc77768684588acf528f01d1fd88f1af4aec ./src/db.rs -ffb8abf8e7d092c01e75940744a747debcb2ecc35d417f73bb4c094842ed1607 ./src/engine.rs +da12b76d1de2f6394829720805c0a82f88cbda05d7e32e55915d18a648b6685f ./src/engine.rs 4b271b6fc365b1078c01d6178eb563841b2ecaed5d8639196f58e1312d2236fe ./src/error.rs c6ff66da9ad08506f839ec56a869ead3abc824b5311e0ea9244517d11f7f4207 ./src/home_assistant.rs 190b0a33431539676e5dd7796698077f16c179d42eae4501ca96a91bf797cbf8 ./src/influxdb.rs @@ -68,5 +68,5 @@ e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon. ee64c273b334c2d0f9747408b6142a8d506021cbf2107178c2213ca4e4a17c8c ./web/index.html fd26156e9f1d6713d3def564ad000553d9a16a24376059701db2ee762c99ee6c ./web/manifest.webmanifest 6aa06f52e4c6391a43016a6a585b7422e3de319744db40a9b233beb27e57412d ./web/styles.css -c93bacaf417610558881bdfd0a3eae324c5d783a0a34c1b7527a145abb469ce1 ./web/sw.js +bd2fe227d905b0cd5fb00ca9e92fb94c0d6c4402a9ae3ee51c68f6c1848fb67b ./web/sw.js d505d793ce7cc9485b45b78bba1c0d51887adc7451ab59a42702946e5b991382 ./web/theme-init.js diff --git a/README.md b/README.md index b2aad56..9c745e8 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.8.4**. +Current version: **0.8.5**. ## 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.8.4.zip +unzip gree-controller-v0.8.5.zip cd gree-controller chmod +x scripts/*.sh ./scripts/dev.sh @@ -480,6 +480,13 @@ Zones now use setpoint modulation, global seasonal house mode, cooling/heating C +## 0.8.5 Temporary temperature-condition activation fix + +- Fixed `temperature_stable` / `temperature_reached` evaluation for sessions already marked active through `activated_at`. +- Kept delayed sessions protected from evaluating or completing before their scheduled start. +- Preserved migration compatibility with active v0.8.3 sessions by also accepting existing local Quick Thermostat ownership as an activation marker. +- Fixes the two v0.8.4 regression tests that stopped `scripts/update.sh` before service replacement. + ## 0.8.4 Scheduled Temporary Quick Thermostat start and ownership hardening - Temporary Quick Thermostat can start immediately, after a configurable delay, or at a specific local date/time. diff --git a/home-assistant/custom_components/gree_controller/manifest.json b/home-assistant/custom_components/gree_controller/manifest.json index 2b65879..cf96c0a 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.8.4", + "version": "0.8.5", "config_flow": true, "integration_type": "hub", "iot_class": "local_polling", diff --git a/src/engine.rs b/src/engine.rs index 709701f..19f24fc 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -762,7 +762,16 @@ fn temporary_temperature_condition_met(zone: &Zone, session: &TemporaryQuickTher /// Update a temperature-based temporary session from the freshly selected room sensor. /// Returns a completion reason when ownership should be handed back immediately. fn evaluate_temporary_quick_thermostat_condition(zone: &mut Zone, now: DateTime) -> Option { - if zone.local_thermostat_power != Some(true) { return None; } + // `activated_at` is the primary marker that a scheduled Temporary Quick Thermostat + // actually started. `local_thermostat_power == Some(true)` is retained as a legacy/ + // recovery marker for sessions persisted before `activated_at` existed. A future + // scheduled session must never start or finish a temperature condition early. + let session_active = zone.temporary_quick_thermostat.as_ref().map(|session| { + session.activated_at.as_ref().map(|at| at <= &now).unwrap_or(false) + || (zone.local_thermostat_power == Some(true) && session.started_at <= now) + }).unwrap_or(false); + if !session_active { return None; } + let met = zone.temporary_quick_thermostat.as_ref() .filter(|session| matches!(session.finish_kind.as_str(), "temperature_reached" | "temperature_stable")) .map(|session| temporary_temperature_condition_met(zone, session))?; @@ -2901,6 +2910,23 @@ mod tests { assert!(zone.temporary_quick_thermostat.as_ref().unwrap().condition_started_at.is_none()); } + #[test] + fn legacy_active_temporary_session_without_activated_at_still_evaluates_condition() { + let now = Utc::now(); + let mut zone = test_zone("device"); + zone.current_temperature = Some(23.0); + zone.local_thermostat_power = Some(true); + let mut session = temporary_session(now.clone()); + session.activated_at = None; + session.condition_started_at = Some(now.clone() - chrono::Duration::hours(1)); + zone.temporary_quick_thermostat = Some(session); + + assert_eq!( + evaluate_temporary_quick_thermostat_condition(&mut zone, now), + Some("temperature_stable".into()) + ); + } + #[test] fn temporary_setpoint_keeps_priority_over_active_schedule() { let mut zone = test_zone("device"); diff --git a/web/sw.js b/web/sw.js index 88d8b2f..583d25c 100644 --- a/web/sw.js +++ b/web/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'gree-controller-v084-scheduled-temporary-quick-thermostat'; +const CACHE = 'gree-controller-v085-temporary-condition-activation-fix'; 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')];