This commit is contained in:
Mateusz Gruszczyński
2026-09-01 13:05:44 +02:00
parent e496f911da
commit 7e0160834d
32 changed files with 486 additions and 67 deletions
+31 -1
View File
@@ -37,6 +37,9 @@ async fn update_settings(State(state): State<AppState>, Json(mut input): Json<Ru
if !matches!(input.notifications.provider.as_str(), "pushover" | "slack" | "discord") { return Err(AppError::BadRequest("unsupported notification provider".into())); }
input.history_retention_days = input.history_retention_days.clamp(1, 3650);
input.event_log_retention_days = input.event_log_retention_days.clamp(1, 3650);
input.compressor_protection_seconds = input.compressor_protection_seconds.clamp(30, 1800);
let compressor_settings_changed = input.compressor_protection_enabled != old.compressor_protection_enabled
|| input.compressor_protection_seconds != old.compressor_protection_seconds;
normalize_sensor_aliases(&mut input);
canonicalize_home_assistant_entities(&mut input);
validate_night_mode(&mut input)?;
@@ -52,7 +55,29 @@ async fn update_settings(State(state): State<AppState>, Json(mut input): Json<Ru
canonicalize_saved_zone_entities(&state, &input).await?;
state.debug_gree_frames.store(input.debug.gree_frames, Ordering::Relaxed);
*state.settings.write().await = input.clone();
state.log("info", "settings.updated", "Settings updated", json!({}));
if compressor_settings_changed {
// A changed protection policy invalidates old deadlines. Clear transient tasks under
// the same control-cycle exclusion; the next thermostat pass re-evaluates intent
// against the new enabled flag/duration.
let mut zone_ids: Vec<String> = state.db.list_zones()?.into_iter().map(|zone| zone.id).collect();
zone_ids.sort();
zone_ids.dedup();
for zone_id in zone_ids {
let _zone_guard = state.lock_zone_operation(&zone_id).await;
let Some(mut zone) = state.db.get_zone(&zone_id)? else { continue; };
if zone.compressor_pending_action.is_none() && zone.compressor_cancelled_action.is_none()
&& zone.lockout_until.is_none() && zone.lockout_reason.is_none() { continue; }
engine::clear_compressor_pending(&mut zone, true);
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", "settings.updated", "Settings updated", json!({
"compressor_protection_enabled": input.compressor_protection_enabled,
"compressor_protection_seconds": input.compressor_protection_seconds
}));
state.broadcast("settings.updated", public_settings(&input));
state.wake_zone_control();
Ok(Json(public_settings(&input)))
@@ -299,6 +324,10 @@ fn sanitize_configuration_runtime(export: &mut ConfigurationExport) {
zone.last_mode_change_at = None;
zone.lockout_until = None;
zone.lockout_reason = None;
zone.compressor_pending_action = None;
zone.compressor_pending_since = None;
zone.compressor_pending_until = None;
zone.compressor_cancelled_action = None;
zone.effective_mode.clear();
zone.effective_setpoint = None;
zone.device_setpoint = None;
@@ -319,6 +348,7 @@ async fn import_settings(State(state): State<AppState>, Json(mut export): Json<C
validate_configuration_export(&export)?;
export.settings.history_retention_days = export.settings.history_retention_days.clamp(1, 3650);
export.settings.event_log_retention_days = export.settings.event_log_retention_days.clamp(1, 3650);
export.settings.compressor_protection_seconds = export.settings.compressor_protection_seconds.clamp(30, 1800);
normalize_sensor_aliases(&mut export.settings);
canonicalize_home_assistant_entities(&mut export.settings);
for zone in &mut export.zones { canonicalize_zone_ha_entity(zone, &export.settings); }