v0.8.20
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
async fn control_zones(state: &AppState) -> Result<()> {
|
||||
let _cycle_guard = state.lock_zone_control_cycle().await;
|
||||
let schedules = state.db.list_schedules()?;
|
||||
let groups = state.db.list_groups()?;
|
||||
let settings = state.settings.read().await.clone();
|
||||
@@ -78,16 +79,29 @@ async fn control_zones(state: &AppState) -> Result<()> {
|
||||
.map(|(zone_id, entity_id, result)| (zone_id, (entity_id, result)))
|
||||
.collect();
|
||||
|
||||
for mut zone in zone_snapshot {
|
||||
for zone_snapshot_item in zone_snapshot {
|
||||
// Every thermostat decision participates in the same zone -> device ordering as
|
||||
// Web/HA/manual control and polling. Re-read after taking the zone lock so an
|
||||
// interactive change cannot be evaluated from a stale snapshot.
|
||||
let _zone_guard = state.lock_zone_operation(&zone_snapshot_item.id).await;
|
||||
let Some(mut zone) = state.db.get_zone(&zone_snapshot_item.id)? else { continue; };
|
||||
let cycle_started_at = zone.updated_at;
|
||||
if zone.manual_override_until.map(|until| until <= Utc::now()).unwrap_or(false) {
|
||||
zone.manual_preset = None;
|
||||
zone.manual_setpoint = None;
|
||||
zone.manual_override_until = None;
|
||||
if zone.control_source.starts_with("group:") {
|
||||
zone.control_source = "automation".into();
|
||||
zone.control_since = Some(Utc::now());
|
||||
zone.control_reason = "Group override expired at schedule boundary".into();
|
||||
}
|
||||
}
|
||||
if zone.device_manual_override_until.map(|until| until <= Utc::now()).unwrap_or(false) {
|
||||
reset_device_manual_override(&mut zone);
|
||||
state.log("info", "zone.device_manual_override_expired", &format!("Manual device control expired for {} at schedule transition", zone.name), json!({
|
||||
// v0.8.20 makes direct/manual takeover persistent. Normalize any legacy persisted
|
||||
// boundary from older releases instead of silently returning ownership to schedules.
|
||||
if zone.device_manual_override && zone.device_manual_override_until.is_some() {
|
||||
zone.device_manual_override_until = None;
|
||||
zone.control_resume_at = None;
|
||||
state.log("info", "zone.device_manual_override_migrated", &format!("Manual device control remains active for {} until explicit resume", zone.name), json!({
|
||||
"zone_id": zone.id, "device_id": zone.device_id
|
||||
}));
|
||||
}
|
||||
@@ -541,3 +555,9 @@ async fn control_zones(state: &AppState) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
/// Run one thermostat arbitration cycle immediately and wait for all currently eligible zones.
|
||||
/// The cycle lock prevents overlap with the background regulator.
|
||||
pub async fn run_zone_control_now(state: &AppState) -> Result<(), AppError> {
|
||||
control_zones(state).await.map_err(AppError::from)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user