v0.12.0-preety_code
This commit is contained in:
@@ -1,12 +1,17 @@
|
||||
pub fn temporary_quick_thermostat_is_active(zone: &Zone, now: DateTime<Utc>) -> bool {
|
||||
zone.temporary_quick_thermostat.as_ref()
|
||||
zone.temporary_quick_thermostat
|
||||
.as_ref()
|
||||
.and_then(|session| session.activated_at.as_ref())
|
||||
.map(|activated_at| activated_at <= &now)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn temporary_quick_thermostat_hard_deadline(session: &TemporaryQuickThermostat) -> Option<DateTime<Utc>> {
|
||||
if session.state == "paused_manual" { return None; }
|
||||
fn temporary_quick_thermostat_hard_deadline(
|
||||
session: &TemporaryQuickThermostat,
|
||||
) -> Option<DateTime<Utc>> {
|
||||
if session.state == "paused_manual" {
|
||||
return None;
|
||||
}
|
||||
match (session.expires_at, session.safety_expires_at) {
|
||||
(Some(a), Some(b)) => Some(a.min(b)),
|
||||
(Some(a), None) => Some(a),
|
||||
@@ -15,10 +20,14 @@ fn temporary_quick_thermostat_hard_deadline(session: &TemporaryQuickThermostat)
|
||||
}
|
||||
}
|
||||
|
||||
fn temporary_quick_thermostat_next_deadline(session: &TemporaryQuickThermostat) -> Option<DateTime<Utc>> {
|
||||
fn temporary_quick_thermostat_next_deadline(
|
||||
session: &TemporaryQuickThermostat,
|
||||
) -> Option<DateTime<Utc>> {
|
||||
let hard = temporary_quick_thermostat_hard_deadline(session);
|
||||
let hold = if session.finish_kind == "temperature_stable" && session.hold_seconds > 0 {
|
||||
session.condition_started_at.map(|started| started + chrono::Duration::seconds(session.hold_seconds as i64))
|
||||
session
|
||||
.condition_started_at
|
||||
.map(|started| started + chrono::Duration::seconds(session.hold_seconds as i64))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -42,11 +51,19 @@ fn temporary_quick_thermostat_wakeup_at(zone: &Zone, now: DateTime<Utc>) -> Opti
|
||||
|
||||
/// Finish an active temporary session and apply climate changes that were deferred while
|
||||
/// it owned the zone. Pending-session cancellation should simply remove the session instead.
|
||||
pub fn finish_temporary_quick_thermostat(zone: &mut Zone, schedules: &[Schedule], house_mode: &str) -> bool {
|
||||
pub fn finish_temporary_quick_thermostat(
|
||||
zone: &mut Zone,
|
||||
schedules: &[Schedule],
|
||||
house_mode: &str,
|
||||
) -> bool {
|
||||
let now = Utc::now();
|
||||
let was_active = temporary_quick_thermostat_is_active(zone, now);
|
||||
let Some(session) = zone.temporary_quick_thermostat.take() else { return false; };
|
||||
if !was_active { return false; }
|
||||
let Some(session) = zone.temporary_quick_thermostat.take() else {
|
||||
return false;
|
||||
};
|
||||
if !was_active {
|
||||
return false;
|
||||
}
|
||||
|
||||
zone.local_thermostat_power = session.restore_local_thermostat_power;
|
||||
zone.local_thermostat_resume_at = session.restore_local_thermostat_resume_at;
|
||||
@@ -75,8 +92,11 @@ pub fn finish_temporary_quick_thermostat(zone: &mut Zone, schedules: &[Schedule]
|
||||
zone.manual_override_until = None;
|
||||
} else if matches!(preset, "comfort" | "sleep" | "away" | "custom") {
|
||||
zone.manual_preset = Some(preset.to_string());
|
||||
if preset != "custom" { zone.manual_setpoint = None; }
|
||||
zone.manual_override_until = next_schedule_boundary_utc(&zone.id, schedules, Local::now());
|
||||
if preset != "custom" {
|
||||
zone.manual_setpoint = None;
|
||||
}
|
||||
zone.manual_override_until =
|
||||
next_schedule_boundary_utc(&zone.id, schedules, Local::now());
|
||||
}
|
||||
}
|
||||
// A deferred custom temperature is valid only while the final deferred preset is custom.
|
||||
@@ -87,7 +107,8 @@ pub fn finish_temporary_quick_thermostat(zone: &mut Zone, schedules: &[Schedule]
|
||||
zone.setpoint = setpoint;
|
||||
zone.manual_preset = Some("custom".into());
|
||||
zone.manual_setpoint = Some(setpoint);
|
||||
zone.manual_override_until = next_schedule_boundary_utc(&zone.id, schedules, Local::now());
|
||||
zone.manual_override_until =
|
||||
next_schedule_boundary_utc(&zone.id, schedules, Local::now());
|
||||
}
|
||||
}
|
||||
refresh_zone_runtime_target(zone, schedules, house_mode);
|
||||
@@ -97,27 +118,53 @@ pub fn finish_temporary_quick_thermostat(zone: &mut Zone, schedules: &[Schedule]
|
||||
true
|
||||
}
|
||||
|
||||
async fn expire_temporary_quick_thermostats(state: &AppState, zones: &mut [Zone], schedules: &[Schedule], house_mode: &str) -> Result<Vec<String>, AppError> {
|
||||
async fn expire_temporary_quick_thermostats(
|
||||
state: &AppState,
|
||||
zones: &mut [Zone],
|
||||
schedules: &[Schedule],
|
||||
house_mode: &str,
|
||||
) -> Result<Vec<String>, AppError> {
|
||||
let now = Utc::now();
|
||||
let mut restored_disabled_zones = Vec::new();
|
||||
for zone in zones.iter_mut() {
|
||||
let zone_id = zone.id.clone();
|
||||
let _zone_guard = state.lock_zone_operation(&zone_id).await;
|
||||
let Some(snapshot) = state.db.get_zone(&zone_id)? else { continue; };
|
||||
let Some(snapshot) = state.db.get_zone(&zone_id)? else {
|
||||
continue;
|
||||
};
|
||||
let _device_guard = state.lock_device_operation(&snapshot.device_id).await;
|
||||
let Some(latest) = state.db.get_zone(&zone_id)? else { continue; };
|
||||
let Some(latest) = state.db.get_zone(&zone_id)? else {
|
||||
continue;
|
||||
};
|
||||
*zone = latest;
|
||||
let active_under_manual = zone.device_manual_override
|
||||
&& zone.temporary_quick_thermostat.as_ref().and_then(|session| session.activated_at).is_some();
|
||||
&& zone
|
||||
.temporary_quick_thermostat
|
||||
.as_ref()
|
||||
.and_then(|session| session.activated_at)
|
||||
.is_some();
|
||||
if active_under_manual {
|
||||
set_temporary_wait_state(state, zone, "paused_manual", now)?;
|
||||
continue;
|
||||
}
|
||||
let Some((deadline, finish_kind, restore_zone_enabled)) = zone.temporary_quick_thermostat.as_ref()
|
||||
.and_then(|session| temporary_quick_thermostat_hard_deadline(session)
|
||||
.map(|deadline| (deadline, session.finish_kind.clone(), session.restore_zone_enabled)))
|
||||
else { continue; };
|
||||
if deadline > now { continue; }
|
||||
let Some((deadline, finish_kind, restore_zone_enabled)) = zone
|
||||
.temporary_quick_thermostat
|
||||
.as_ref()
|
||||
.and_then(|session| {
|
||||
temporary_quick_thermostat_hard_deadline(session).map(|deadline| {
|
||||
(
|
||||
deadline,
|
||||
session.finish_kind.clone(),
|
||||
session.restore_zone_enabled,
|
||||
)
|
||||
})
|
||||
})
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
if deadline > now {
|
||||
continue;
|
||||
}
|
||||
let was_activated = temporary_quick_thermostat_is_active(zone, now);
|
||||
let restores_disabled = was_activated && restore_zone_enabled == Some(false);
|
||||
if was_activated {
|
||||
@@ -130,17 +177,31 @@ async fn expire_temporary_quick_thermostats(state: &AppState, zones: &mut [Zone]
|
||||
zone.updated_at = now;
|
||||
state.db.save_zone(zone)?;
|
||||
state.broadcast("zone.updated", serde_json::to_value(&*zone)?);
|
||||
state.log("info", "zone.temporary_quick_thermostat_finished", &format!("Temporary Quick Thermostat finished for {}", zone.name), json!({
|
||||
"zone_id": zone.id, "device_id": zone.device_id, "finish_kind": finish_kind,
|
||||
"reason": if was_activated { "deadline" } else { "expired_before_activation" }
|
||||
}));
|
||||
if restores_disabled { restored_disabled_zones.push(zone.id.clone()); }
|
||||
state.log(
|
||||
"info",
|
||||
"zone.temporary_quick_thermostat_finished",
|
||||
&format!("Temporary Quick Thermostat finished for {}", zone.name),
|
||||
json!({
|
||||
"zone_id": zone.id, "device_id": zone.device_id, "finish_kind": finish_kind,
|
||||
"reason": if was_activated { "deadline" } else { "expired_before_activation" }
|
||||
}),
|
||||
);
|
||||
if restores_disabled {
|
||||
restored_disabled_zones.push(zone.id.clone());
|
||||
}
|
||||
}
|
||||
Ok(restored_disabled_zones)
|
||||
}
|
||||
|
||||
fn set_temporary_wait_state(state: &AppState, zone: &mut Zone, value: &str, now: DateTime<Utc>) -> Result<(), AppError> {
|
||||
let Some(session) = zone.temporary_quick_thermostat.as_mut() else { return Ok(()); };
|
||||
fn set_temporary_wait_state(
|
||||
state: &AppState,
|
||||
zone: &mut Zone,
|
||||
value: &str,
|
||||
now: DateTime<Utc>,
|
||||
) -> Result<(), AppError> {
|
||||
let Some(session) = zone.temporary_quick_thermostat.as_mut() else {
|
||||
return Ok(());
|
||||
};
|
||||
let mut changed = false;
|
||||
if session.state != value {
|
||||
session.state = value.to_string();
|
||||
@@ -150,7 +211,9 @@ fn set_temporary_wait_state(state: &AppState, zone: &mut Zone, value: &str, now:
|
||||
session.paused_at = Some(now);
|
||||
changed = true;
|
||||
}
|
||||
if !changed { return Ok(()); }
|
||||
if !changed {
|
||||
return Ok(());
|
||||
}
|
||||
session.condition_started_at = None;
|
||||
session.condition_last_observed_at = None;
|
||||
zone.updated_at = now;
|
||||
@@ -169,28 +232,46 @@ async fn activate_due_temporary_quick_thermostats(
|
||||
for zone in zones.iter_mut() {
|
||||
let zone_id = zone.id.clone();
|
||||
let _zone_guard = state.lock_zone_operation(&zone_id).await;
|
||||
let Some(snapshot) = state.db.get_zone(&zone_id)? else { continue; };
|
||||
let Some(snapshot) = state.db.get_zone(&zone_id)? else {
|
||||
continue;
|
||||
};
|
||||
let _device_guard = state.lock_device_operation(&snapshot.device_id).await;
|
||||
let Some(latest) = state.db.get_zone(&zone_id)? else { continue; };
|
||||
let Some(latest) = state.db.get_zone(&zone_id)? else {
|
||||
continue;
|
||||
};
|
||||
*zone = latest;
|
||||
let Some((session_state, started_at)) = zone.temporary_quick_thermostat.as_ref()
|
||||
let Some((session_state, started_at)) = zone
|
||||
.temporary_quick_thermostat
|
||||
.as_ref()
|
||||
.map(|session| (session.state.clone(), session.started_at))
|
||||
else { continue; };
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
if temporary_quick_thermostat_is_active(zone, now) {
|
||||
if session_state != "active" && !zone.device_manual_override {
|
||||
set_temporary_wait_state(state, zone, "active", now)?;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if started_at > now { continue; }
|
||||
if started_at > now {
|
||||
continue;
|
||||
}
|
||||
if zone.device_manual_override {
|
||||
set_temporary_wait_state(state, zone, "paused_manual", now)?;
|
||||
continue;
|
||||
}
|
||||
|
||||
let (temperature_target, duration_seconds, safety_duration_seconds, finish_kind) = {
|
||||
let session = zone.temporary_quick_thermostat.as_ref().expect("temporary session checked above");
|
||||
(session.temperature_target, session.duration_seconds, session.safety_duration_seconds, session.finish_kind.clone())
|
||||
let session = zone
|
||||
.temporary_quick_thermostat
|
||||
.as_ref()
|
||||
.expect("temporary session checked above");
|
||||
(
|
||||
session.temperature_target,
|
||||
session.duration_seconds,
|
||||
session.safety_duration_seconds,
|
||||
session.finish_kind.clone(),
|
||||
)
|
||||
};
|
||||
let target = temperature_target
|
||||
.or(zone.manual_setpoint)
|
||||
@@ -203,11 +284,21 @@ async fn activate_due_temporary_quick_thermostats(
|
||||
let restore_manual_preset = zone.manual_preset.clone();
|
||||
let restore_manual_setpoint = zone.manual_setpoint;
|
||||
let restore_manual_override_until = zone.manual_override_until;
|
||||
let configured_mode = if zone.inherit_house_mode { house_mode } else { zone.mode.as_str() };
|
||||
let active_mode = if configured_mode == "off" { zone.mode.clone() } else { configured_mode.to_string() };
|
||||
let configured_mode = if zone.inherit_house_mode {
|
||||
house_mode
|
||||
} else {
|
||||
zone.mode.as_str()
|
||||
};
|
||||
let active_mode = if configured_mode == "off" {
|
||||
zone.mode.clone()
|
||||
} else {
|
||||
configured_mode.to_string()
|
||||
};
|
||||
let schedule_boundary = if finish_kind == "schedule_boundary" {
|
||||
next_schedule_boundary_utc(&zone.id, schedules, Local::now())
|
||||
} else { None };
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if finish_kind == "schedule_boundary" && schedule_boundary.is_none() {
|
||||
zone.temporary_quick_thermostat = None;
|
||||
zone.updated_at = now;
|
||||
@@ -247,12 +338,17 @@ async fn activate_due_temporary_quick_thermostats(
|
||||
session.condition_last_observed_at = None;
|
||||
session.paused_at = None;
|
||||
if session.finish_kind == "duration" {
|
||||
session.expires_at = duration_seconds.map(|seconds| now + chrono::Duration::seconds(seconds as i64));
|
||||
session.expires_at =
|
||||
duration_seconds.map(|seconds| now + chrono::Duration::seconds(seconds as i64));
|
||||
} else if session.finish_kind == "schedule_boundary" {
|
||||
session.expires_at = schedule_boundary;
|
||||
}
|
||||
if matches!(session.finish_kind.as_str(), "temperature_reached" | "temperature_stable") {
|
||||
session.safety_expires_at = safety_duration_seconds.map(|seconds| now + chrono::Duration::seconds(seconds as i64));
|
||||
if matches!(
|
||||
session.finish_kind.as_str(),
|
||||
"temperature_reached" | "temperature_stable"
|
||||
) {
|
||||
session.safety_expires_at = safety_duration_seconds
|
||||
.map(|seconds| now + chrono::Duration::seconds(seconds as i64));
|
||||
}
|
||||
}
|
||||
zone.updated_at = now;
|
||||
@@ -265,25 +361,62 @@ async fn activate_due_temporary_quick_thermostats(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn ensure_device_off_after_temporary_disabled_restore(state: &AppState, zone: &Zone, device: &Device) {
|
||||
if zone.enabled || zone.device_manual_override || !device.enabled || !device.online || device.communication_failures > 0 || !device.power { return; }
|
||||
async fn ensure_device_off_after_temporary_disabled_restore(
|
||||
state: &AppState,
|
||||
zone: &Zone,
|
||||
device: &Device,
|
||||
) {
|
||||
if zone.enabled
|
||||
|| zone.device_manual_override
|
||||
|| !device.enabled
|
||||
|| !device.online
|
||||
|| device.communication_failures > 0
|
||||
|| !device.power
|
||||
{
|
||||
return;
|
||||
}
|
||||
let _device_guard = state.lock_device_operation(&zone.device_id).await;
|
||||
let should_stop = state.db.get_zone(&zone.id).ok().flatten()
|
||||
.map(|latest| !latest.enabled
|
||||
&& !latest.device_manual_override
|
||||
&& latest.temporary_quick_thermostat.is_none()
|
||||
&& latest.local_thermostat_power.is_none())
|
||||
let should_stop = state
|
||||
.db
|
||||
.get_zone(&zone.id)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|latest| {
|
||||
!latest.enabled
|
||||
&& !latest.device_manual_override
|
||||
&& latest.temporary_quick_thermostat.is_none()
|
||||
&& latest.local_thermostat_power.is_none()
|
||||
})
|
||||
.unwrap_or(false);
|
||||
if !should_stop { return; }
|
||||
if let Err(err) = send_command_locked(state, &zone.device_id, DeviceCommand { power: Some(false), ..Default::default() }).await {
|
||||
state.log("error", "zone.temporary_quick_thermostat_poweroff_error", &err.to_string(), json!({
|
||||
"zone_id": zone.id, "device_id": zone.device_id
|
||||
}));
|
||||
if !should_stop {
|
||||
return;
|
||||
}
|
||||
if let Err(err) = send_command_locked(
|
||||
state,
|
||||
&zone.device_id,
|
||||
DeviceCommand {
|
||||
power: Some(false),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
state.log(
|
||||
"error",
|
||||
"zone.temporary_quick_thermostat_poweroff_error",
|
||||
&err.to_string(),
|
||||
json!({
|
||||
"zone_id": zone.id, "device_id": zone.device_id
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn temporary_temperature_condition_met(zone: &Zone, session: &TemporaryQuickThermostat) -> bool {
|
||||
let (Some(current), Some(target)) = (zone.current_temperature, session.temperature_target) else { return false; };
|
||||
let (Some(current), Some(target)) = (zone.current_temperature, session.temperature_target)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
let tolerance = session.tolerance_c.max(0.0);
|
||||
match session.temperature_operator.as_deref().unwrap_or("within") {
|
||||
"at_or_below" => current <= target + tolerance,
|
||||
@@ -300,11 +433,22 @@ fn evaluate_temporary_quick_thermostat_condition(
|
||||
sample_at: Option<DateTime<Utc>>,
|
||||
max_gap_seconds: u64,
|
||||
) -> Option<String> {
|
||||
if !temporary_quick_thermostat_is_active(zone, now) || zone.device_manual_override { return None; }
|
||||
let is_condition = zone.temporary_quick_thermostat.as_ref()
|
||||
.map(|session| matches!(session.finish_kind.as_str(), "temperature_reached" | "temperature_stable"))
|
||||
if !temporary_quick_thermostat_is_active(zone, now) || zone.device_manual_override {
|
||||
return None;
|
||||
}
|
||||
let is_condition = zone
|
||||
.temporary_quick_thermostat
|
||||
.as_ref()
|
||||
.map(|session| {
|
||||
matches!(
|
||||
session.finish_kind.as_str(),
|
||||
"temperature_reached" | "temperature_stable"
|
||||
)
|
||||
})
|
||||
.unwrap_or(false);
|
||||
if !is_condition { return None; }
|
||||
if !is_condition {
|
||||
return None;
|
||||
}
|
||||
|
||||
let Some(sample_at) = sample_at else {
|
||||
if let Some(session) = zone.temporary_quick_thermostat.as_mut() {
|
||||
@@ -313,20 +457,33 @@ fn evaluate_temporary_quick_thermostat_condition(
|
||||
}
|
||||
return None;
|
||||
};
|
||||
let last_observed = zone.temporary_quick_thermostat.as_ref().and_then(|session| session.condition_last_observed_at);
|
||||
if last_observed.map(|last| sample_at <= last).unwrap_or(false) { return None; }
|
||||
let last_observed = zone
|
||||
.temporary_quick_thermostat
|
||||
.as_ref()
|
||||
.and_then(|session| session.condition_last_observed_at);
|
||||
if last_observed.map(|last| sample_at <= last).unwrap_or(false) {
|
||||
return None;
|
||||
}
|
||||
let gap_broken = last_observed
|
||||
.map(|last| sample_at.signed_duration_since(last).num_seconds() > max_gap_seconds.max(1) as i64)
|
||||
.map(|last| {
|
||||
sample_at.signed_duration_since(last).num_seconds() > max_gap_seconds.max(1) as i64
|
||||
})
|
||||
.unwrap_or(false);
|
||||
let met = zone.temporary_quick_thermostat.as_ref()
|
||||
let met = zone
|
||||
.temporary_quick_thermostat
|
||||
.as_ref()
|
||||
.map(|session| temporary_temperature_condition_met(zone, session))?;
|
||||
|
||||
let session = zone.temporary_quick_thermostat.as_mut()?;
|
||||
session.condition_last_observed_at = Some(sample_at);
|
||||
if gap_broken { session.condition_started_at = None; }
|
||||
if gap_broken {
|
||||
session.condition_started_at = None;
|
||||
}
|
||||
match session.finish_kind.as_str() {
|
||||
"temperature_reached" => {
|
||||
if met { return Some("temperature_reached".into()); }
|
||||
if met {
|
||||
return Some("temperature_reached".into());
|
||||
}
|
||||
session.condition_started_at = None;
|
||||
}
|
||||
"temperature_stable" => {
|
||||
@@ -335,7 +492,10 @@ fn evaluate_temporary_quick_thermostat_condition(
|
||||
return None;
|
||||
}
|
||||
let started = session.condition_started_at.get_or_insert(sample_at);
|
||||
if session.hold_seconds == 0 || sample_at.signed_duration_since(*started).num_seconds() >= session.hold_seconds as i64 {
|
||||
if session.hold_seconds == 0
|
||||
|| sample_at.signed_duration_since(*started).num_seconds()
|
||||
>= session.hold_seconds as i64
|
||||
{
|
||||
return Some("temperature_stable".into());
|
||||
}
|
||||
}
|
||||
@@ -344,14 +504,23 @@ fn evaluate_temporary_quick_thermostat_condition(
|
||||
None
|
||||
}
|
||||
|
||||
async fn expire_local_thermostat_overrides(state: &AppState, zones: &mut [Zone], schedules: &[Schedule], house_mode: &str) -> Result<(), AppError> {
|
||||
async fn expire_local_thermostat_overrides(
|
||||
state: &AppState,
|
||||
zones: &mut [Zone],
|
||||
schedules: &[Schedule],
|
||||
house_mode: &str,
|
||||
) -> Result<(), AppError> {
|
||||
let now = Utc::now();
|
||||
for zone in zones.iter_mut() {
|
||||
let zone_id = zone.id.clone();
|
||||
let _zone_guard = state.lock_zone_operation(&zone_id).await;
|
||||
let Some(snapshot) = state.db.get_zone(&zone_id)? else { continue; };
|
||||
let Some(snapshot) = state.db.get_zone(&zone_id)? else {
|
||||
continue;
|
||||
};
|
||||
let _device_guard = state.lock_device_operation(&snapshot.device_id).await;
|
||||
let Some(latest) = state.db.get_zone(&zone_id)? else { continue; };
|
||||
let Some(latest) = state.db.get_zone(&zone_id)? else {
|
||||
continue;
|
||||
};
|
||||
*zone = latest;
|
||||
// A direct device/pilot takeover has higher priority than the local-OFF hand-back.
|
||||
// Do not let the old timer expire underneath someone who is actively controlling
|
||||
@@ -361,9 +530,17 @@ async fn expire_local_thermostat_overrides(state: &AppState, zones: &mut [Zone],
|
||||
// local_thermostat_power=false with resume_at=None is a deliberate indefinite OFF
|
||||
// (notably the state produced by group OFF) and must stay off until the user/group
|
||||
// explicitly turns it back on or resumes automation.
|
||||
if !local_thermostat_handback_is_active(zone) { continue; }
|
||||
let expired = zone.local_thermostat_resume_at.as_ref().map(|at| at <= &now).unwrap_or(false);
|
||||
if !expired { continue; }
|
||||
if !local_thermostat_handback_is_active(zone) {
|
||||
continue;
|
||||
}
|
||||
let expired = zone
|
||||
.local_thermostat_resume_at
|
||||
.as_ref()
|
||||
.map(|at| at <= &now)
|
||||
.unwrap_or(false);
|
||||
if !expired {
|
||||
continue;
|
||||
}
|
||||
reset_local_thermostat_override(zone);
|
||||
refresh_zone_runtime_target(zone, schedules, house_mode);
|
||||
zone.updated_at = now.clone();
|
||||
@@ -375,4 +552,3 @@ async fn expire_local_thermostat_overrides(state: &AppState, zones: &mut [Zone],
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user