This commit is contained in:
Mateusz Gruszczyński
2026-09-01 13:33:16 +02:00
parent dc20267787
commit 97c26c67b6
10 changed files with 67 additions and 46 deletions
+10 -5
View File
@@ -257,6 +257,11 @@ async fn apply_zone_control_patch(state: &AppState, id: &str, patch: ZoneControl
// lock before the per-zone lock so a concurrent schedule edit cannot leave an override
// pointing at an obsolete boundary (and so lock order stays schedule -> zone -> device).
let _schedule_guard = state.lock_schedule_operation().await;
// A local thermostat click is an ownership transition, not merely a field update.
// Serialize it with the complete thermostat cycle so an already-running arbitration
// pass cannot reach an automatic ON after the user has committed local OFF. Lock order
// stays schedule -> cycle -> zone -> device, matching schedule mutation paths.
let _cycle_guard = state.lock_zone_control_cycle().await;
// Serialize quick-thermostat changes with the same device lock used by GREE polling and
// manual-takeover detection. Without this, a poll that started just before a Web/HA
// thermostat action could save an older zone snapshot afterwards and resurrect a false
@@ -642,11 +647,11 @@ async fn apply_zone_control_patch(state: &AppState, id: &str, patch: ZoneControl
if was_enabled && !zone.enabled {
power_off_zone_device(state, &zone, "zone.quick_disabled").await;
} else if patch.power == Some(false) {
if let Err(err) = engine::send_command(
state,
&zone.device_id,
DeviceCommand { power: Some(false), ..Default::default() },
).await {
// Local OFF is an explicit safety/user intent. Force one OFF frame instead of
// trusting the cached device.power flag; a stale cache must never let a running
// unit survive a thermostat OFF click. The cycle lock held by this handler keeps
// automatic arbitration out until the OFF marker and physical command are complete.
if let Err(err) = engine::force_power_off_device(state, &zone.device_id).await {
state.log("error", "zone.local_power_error", &err.to_string(), json!({
"zone_id": zone.id, "device_id": zone.device_id, "power": false
}));