v0.6.0
This commit is contained in:
+33
-1
@@ -198,6 +198,7 @@ async fn poll_device(state: &AppState, device: &mut Device) {
|
||||
simulate_tick(device);
|
||||
return;
|
||||
}
|
||||
let previous_failures = device.communication_failures;
|
||||
if device.key.as_deref().unwrap_or_default().is_empty() {
|
||||
match state.gree.bind(device).await {
|
||||
Ok(bound) => {
|
||||
@@ -207,6 +208,7 @@ async fn poll_device(state: &AppState, device: &mut Device) {
|
||||
}
|
||||
Err(err) => {
|
||||
record_poll_failure(device, &err.to_string());
|
||||
log_poll_health_transition(state, device, previous_failures).await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -225,6 +227,18 @@ async fn poll_device(state: &AppState, device: &mut Device) {
|
||||
Err(_) => record_poll_failure(device, &first_err.to_string()),
|
||||
}
|
||||
}
|
||||
log_poll_health_transition(state, device, previous_failures).await;
|
||||
}
|
||||
|
||||
async fn log_poll_health_transition(state: &AppState, device: &Device, previous_failures: u32) {
|
||||
let threshold = state.settings.read().await.notifications.communication_failure_threshold.max(2);
|
||||
if device.communication_failures >= threshold && previous_failures < threshold {
|
||||
state.log("warn", "device.offline", &format!("{} did not respond {} times in a row", device.name, device.communication_failures), json!({
|
||||
"device_id": device.id, "consecutive_failures": device.communication_failures, "threshold": threshold
|
||||
}));
|
||||
} else if device.communication_failures == 0 && previous_failures >= threshold {
|
||||
state.log("info", "device.recovered", &format!("{} is responding again", device.name), json!({"device_id": device.id}));
|
||||
}
|
||||
}
|
||||
|
||||
fn simulate_tick(device: &mut Device) {
|
||||
@@ -472,6 +486,24 @@ async fn control_zones(state: &AppState) -> Result<()> {
|
||||
else { zone.demand }
|
||||
}
|
||||
};
|
||||
if zone.demand && !previous_demand {
|
||||
zone.demand_since = Some(Utc::now());
|
||||
zone.target_alerted_at = None;
|
||||
} else if !zone.demand {
|
||||
zone.demand_since = None;
|
||||
zone.target_alerted_at = None;
|
||||
}
|
||||
if zone.demand && zone.target_alerted_at.is_none() {
|
||||
let timeout_minutes = settings.notifications.target_timeout_minutes.max(5) as i64;
|
||||
if let Some(since) = zone.demand_since {
|
||||
if (Utc::now() - since).num_minutes() >= timeout_minutes {
|
||||
state.log("warn", "zone.target_timeout", &format!("Zone {} has not reached {:.1} C within {} minutes", zone.name, target, timeout_minutes), json!({
|
||||
"zone_id": zone.id, "room_temperature": temp, "target_temperature": target, "minutes": timeout_minutes
|
||||
}));
|
||||
zone.target_alerted_at = Some(Utc::now());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Setpoint modulation: keep the indoor unit powered and let its own inverter/compressor
|
||||
// stop naturally when we move the target to the satisfied side of room temperature.
|
||||
@@ -1117,7 +1149,7 @@ mod tests {
|
||||
external_sensor_weight: 0.4, max_sensor_difference: 3.0, device_temperature: None, external_temperature: None,
|
||||
current_temperature: None, control_temperature_source: "device".into(), active_preset: "comfort".into(),
|
||||
manual_preset: None, manual_setpoint: None, manual_override_until: None, effective_mode: "heat".into(), effective_setpoint: Some(21.0), device_setpoint: None,
|
||||
demand: false, last_action_at: None, created_at: Utc::now(), updated_at: Utc::now(),
|
||||
demand: false, demand_since: None, target_alerted_at: None, last_action_at: None, created_at: Utc::now(), updated_at: Utc::now(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user