v0.9.0
This commit is contained in:
@@ -109,6 +109,7 @@ impl ZoneInput {
|
||||
device_manual_override: false, device_manual_override_since: None, device_manual_override_until: None, device_manual_override_fields: Vec::new(), device_manual_override_baseline: None,
|
||||
revision: 1, control_owner: "automation".into(), control_source: "automation".into(), control_since: Some(Utc::now()), control_resume_at: None, control_reason: "zone created".into(),
|
||||
last_power_change_at: None, last_mode_change_at: None, lockout_until: None, lockout_reason: None,
|
||||
compressor_pending_action: None, compressor_pending_since: None, compressor_pending_until: None, compressor_cancelled_action: None,
|
||||
effective_mode: String::new(), effective_setpoint: None, device_setpoint: None,
|
||||
demand: false, demand_since: None, target_alerted_at: None, last_action_at: None,
|
||||
created_at, updated_at: Utc::now(),
|
||||
@@ -208,6 +209,10 @@ async fn update_zone(State(state): State<AppState>, Path(id): Path<String>, Json
|
||||
zone.last_mode_change_at = existing.last_mode_change_at;
|
||||
zone.lockout_until = existing.lockout_until;
|
||||
zone.lockout_reason = existing.lockout_reason;
|
||||
zone.compressor_pending_action = existing.compressor_pending_action;
|
||||
zone.compressor_pending_since = existing.compressor_pending_since;
|
||||
zone.compressor_pending_until = existing.compressor_pending_until;
|
||||
zone.compressor_cancelled_action = existing.compressor_cancelled_action;
|
||||
zone.effective_mode = existing.effective_mode;
|
||||
zone.effective_setpoint = existing.effective_setpoint;
|
||||
zone.device_setpoint = existing.device_setpoint;
|
||||
@@ -234,6 +239,7 @@ async fn update_zone(State(state): State<AppState>, Path(id): Path<String>, Json
|
||||
}
|
||||
engine::reset_local_thermostat_override(&mut zone);
|
||||
engine::reset_device_manual_override(&mut zone);
|
||||
engine::rearm_compressor_queue(&mut zone);
|
||||
zone.enabled = false;
|
||||
}
|
||||
state.db.save_zone(&zone)?;
|
||||
@@ -263,6 +269,13 @@ async fn apply_zone_control_patch(state: &AppState, id: &str, patch: ZoneControl
|
||||
let mut zone = state.db.get_zone(id)?.ok_or_else(|| AppError::NotFound(format!("zone {id}")))?;
|
||||
let was_enabled = zone.enabled;
|
||||
let schedules = state.db.list_schedules()?;
|
||||
let rearm_compressor = patch.power.is_some() || patch.setpoint.is_some() || patch.mode.is_some()
|
||||
|| patch.enabled.is_some() || patch.preset.is_some() || patch.clear_override.unwrap_or(false)
|
||||
|| patch.clear_device_manual_override.unwrap_or(false) || patch.clear_local_thermostat_override.unwrap_or(false)
|
||||
|| patch.temporary_quick_thermostat.is_some() || patch.clear_temporary_quick_thermostat.unwrap_or(false);
|
||||
if rearm_compressor {
|
||||
engine::rearm_compressor_queue(&mut zone);
|
||||
}
|
||||
let resume_device_takeover = patch.clear_device_manual_override.unwrap_or(false);
|
||||
let resume_local_thermostat = patch.clear_local_thermostat_override.unwrap_or(false);
|
||||
let stop_temporary_quick_thermostat = patch.clear_temporary_quick_thermostat.unwrap_or(false);
|
||||
@@ -692,3 +705,65 @@ async fn power_off_zone_device(state: &AppState, zone: &Zone, source: &str) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
async fn cancel_zone_compressor_queue(State(state): State<AppState>, Path(id): Path<String>) -> Result<Json<Value>, AppError> {
|
||||
// Serialize cancellation with the thermostat cycle. If cancellation wins this lock, the
|
||||
// pending action cannot expire and execute between the UI click and the persisted marker.
|
||||
let _cycle_guard = state.lock_zone_control_cycle().await;
|
||||
let _zone_guard = state.lock_zone_operation(&id).await;
|
||||
let mut zone = state.db.get_zone(&id)?.ok_or_else(|| AppError::NotFound(format!("zone {id}")))?;
|
||||
let cancelled = zone.compressor_pending_action.clone();
|
||||
if let Some(action) = cancelled.clone() {
|
||||
zone.compressor_cancelled_action = Some(action);
|
||||
zone.compressor_pending_action = None;
|
||||
zone.compressor_pending_since = None;
|
||||
zone.compressor_pending_until = None;
|
||||
zone.lockout_until = None;
|
||||
zone.lockout_reason = None;
|
||||
zone.revision = zone.revision.saturating_add(1);
|
||||
zone.updated_at = Utc::now();
|
||||
state.db.save_zone(&zone)?;
|
||||
state.broadcast("zone.updated", serde_json::to_value(&zone)?);
|
||||
state.log("info", "zone.compressor_queue_cancelled", &format!("Cancelled compressor-protection task for {}", zone.name), json!({
|
||||
"zone_id": zone.id, "device_id": zone.device_id, "action": cancelled.clone()
|
||||
}));
|
||||
}
|
||||
if cancelled.is_some() { state.wake_zone_control(); }
|
||||
Ok(Json(json!({"cancelled": cancelled.is_some(), "action": cancelled, "zone": zone})))
|
||||
}
|
||||
|
||||
async fn cancel_all_compressor_queues(State(state): State<AppState>) -> Result<Json<Value>, AppError> {
|
||||
// Stop the thermostat arbiter while taking all zone locks, so a task cannot expire and
|
||||
// execute between discovering it and persisting the cancellation marker.
|
||||
let _cycle_guard = state.lock_zone_control_cycle().await;
|
||||
let mut zone_ids: Vec<String> = state.db.list_zones()?.into_iter().map(|zone| zone.id).collect();
|
||||
zone_ids.sort();
|
||||
zone_ids.dedup();
|
||||
let mut guards = Vec::with_capacity(zone_ids.len());
|
||||
for zone_id in &zone_ids { guards.push(state.lock_zone_operation(zone_id).await); }
|
||||
|
||||
let mut cancelled = 0usize;
|
||||
let mut zones = Vec::new();
|
||||
for zone_id in &zone_ids {
|
||||
let Some(mut zone) = state.db.get_zone(zone_id)? else { continue; };
|
||||
let Some(action) = zone.compressor_pending_action.clone() else { continue; };
|
||||
zone.compressor_cancelled_action = Some(action.clone());
|
||||
zone.compressor_pending_action = None;
|
||||
zone.compressor_pending_since = None;
|
||||
zone.compressor_pending_until = None;
|
||||
zone.lockout_until = None;
|
||||
zone.lockout_reason = None;
|
||||
zone.revision = zone.revision.saturating_add(1);
|
||||
zone.updated_at = Utc::now();
|
||||
state.db.save_zone(&zone)?;
|
||||
state.broadcast("zone.updated", serde_json::to_value(&zone)?);
|
||||
state.log("info", "zone.compressor_queue_cancelled", &format!("Cancelled compressor-protection task for {}", zone.name), json!({
|
||||
"zone_id": zone.id, "device_id": zone.device_id, "action": action, "source": "cancel_all"
|
||||
}));
|
||||
cancelled += 1;
|
||||
zones.push(zone);
|
||||
}
|
||||
if cancelled > 0 { state.wake_zone_control(); }
|
||||
Ok(Json(json!({"cancelled": cancelled, "zones": zones})))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user