v0.9.4-fix_ux

This commit is contained in:
Mateusz Gruszczyński
2026-09-01 15:14:45 +02:00
parent 7d46e06a4f
commit 1392114c1e
6 changed files with 38 additions and 18 deletions
+19 -7
View File
@@ -71,6 +71,14 @@ pub async fn control_group(state: &AppState, group_id: &str, patch: GroupControl
// "blocked by a disabled group". ON clears that scoped OFF and lets group arbitration
// take ownership again. The whole-house master remains independent.
let manual_group_control = source != "automation.group";
// A manual Group OFF is a temporary bulk pause. All members receive the same
// 15-minute hand-back deadline, so their thermostat automation resumes together.
// Automation-driven group OFF remains persistent until another automation/user action.
let group_handback_at = if manual_group_control && patch.power == Some(false) {
Some(group.updated_at.clone() + chrono::Duration::minutes(LOCAL_THERMOSTAT_RESUME_DELAY_MINUTES))
} else {
None
};
let explicit_group_power = patch.power.is_some() && manual_group_control;
// Applying a manual mode/profile/custom target to an already-enabled group is itself an
// explicit scoped takeover. It must wake members that were left locally OFF by a previous
@@ -160,10 +168,10 @@ pub async fn control_group(state: &AppState, group_id: &str, patch: GroupControl
};
}
}
// Power OFF is persisted as an ordinary per-zone thermostat OFF, with no automatic
// 15-minute hand-back. This keeps the physical unit off while removing group ownership
// and, importantly, still lets the user turn that thermostat back on independently.
// Power ON clears this scoped OFF and returns the member to group arbitration.
// Group power OFF is represented on every member as a local thermostat OFF so the
// physical unit is stopped immediately and group ownership is released. A manual Group
// OFF gets one synchronized 15-minute hand-back deadline for all members; automation
// Group OFF is persistent. Power ON clears the scoped OFF immediately.
let mut force_power_off_after_save = false;
let mut applied_group_power: Option<bool> = None;
let requested_member_power = patch.power.or(manual_group_activation.then_some(true));
@@ -184,7 +192,7 @@ pub async fn control_group(state: &AppState, group_id: &str, patch: GroupControl
applied_group_power = Some(true);
} else {
zone.local_thermostat_power = Some(false);
zone.local_thermostat_resume_at = None;
zone.local_thermostat_resume_at = group_handback_at.clone();
zone.local_thermostat_restore_zone_enabled = None;
zone.demand = false;
zone.demand_since = None;
@@ -200,8 +208,12 @@ pub async fn control_group(state: &AppState, group_id: &str, patch: GroupControl
zone.control_owner = "local_thermostat".into();
zone.control_source = "local_thermostat".into();
zone.control_since = Some(Utc::now());
zone.control_resume_at = None;
zone.control_reason = format!("Group {} powered the thermostat off; group ownership released", group.name);
zone.control_resume_at = group_handback_at.clone();
zone.control_reason = if group_handback_at.is_some() {
format!("Group {} powered the thermostat off; automation resumes after {} minutes", group.name, LOCAL_THERMOSTAT_RESUME_DELAY_MINUTES)
} else {
format!("Group {} powered the thermostat off; group ownership released", group.name)
};
} else if group.power_enabled && zone.local_thermostat_power.is_none() {
zone.control_owner = "automation".into();
zone.control_source = format!("group:{}", group.id);