This commit is contained in:
Mateusz Gruszczyński
2026-08-29 22:07:54 +02:00
parent 930cb7b1e1
commit 2383526a83
16 changed files with 296 additions and 79 deletions
+1
View File
@@ -2705,6 +2705,7 @@ fn public_settings(settings: &RuntimeSettings) -> Value {
"cooldown_seconds": settings.notifications.cooldown_seconds,
"communication_failure_threshold": settings.notifications.communication_failure_threshold,
"target_timeout_minutes": settings.notifications.target_timeout_minutes,
"alert_types": &settings.notifications.alert_types,
},
"influxdb": {
"enabled": settings.influxdb.enabled,
+6 -1
View File
@@ -1915,7 +1915,12 @@ async fn control_zones(state: &AppState) -> Result<()> {
}
Some((resolved_entity, Err(err))) => {
if !matches!(previous_source.as_str(), "device_fallback" | "device_discrepancy_fallback") {
state.log("warn", "ha.sensor_error", &err, json!({
let notification_kind = if err.contains("Home Assistant sensor is stale:") {
"ha.sensor_stale"
} else {
"ha.sensor_error"
};
state.log("warn", notification_kind, &err, json!({
"zone_id": zone.id,
"configured_entity_id": zone.ha_entity_id.as_deref(),
"resolved_entity_id": resolved_entity,
+41
View File
@@ -764,6 +764,43 @@ impl Default for InfluxDbSettings {
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NotificationAlertTypes {
/// Home Assistant sensor exceeded the configured freshness window.
#[serde(default = "default_true")]
pub stale_sensor: bool,
/// Other Home Assistant sensor errors (missing/unavailable/invalid value).
#[serde(default = "default_true")]
pub sensor_errors: bool,
/// Device connectivity, polling and communication problems.
#[serde(default = "default_true")]
pub communication: bool,
/// Zone failed to reach its target within the configured timeout.
#[serde(default = "default_true")]
pub target_timeout: bool,
/// Automation execution errors and conflicts.
#[serde(default = "default_true")]
pub automation: bool,
/// Thermostat/group/device control failures and sensor discrepancies.
#[serde(default = "default_true")]
pub control_errors: bool,
/// Informational state changes sent when notification mode is "important".
#[serde(default = "default_true")]
pub important_events: bool,
/// Any warning/error not matched by one of the categories above.
#[serde(default = "default_true")]
pub other: bool,
}
impl Default for NotificationAlertTypes {
fn default() -> Self {
Self {
stale_sensor: true, sensor_errors: true, communication: true, target_timeout: true,
automation: true, control_errors: true, important_events: true, other: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NotificationSettings {
#[serde(default)]
@@ -788,6 +825,9 @@ pub struct NotificationSettings {
pub communication_failure_threshold: u32,
#[serde(default = "default_notification_target_timeout")]
pub target_timeout_minutes: u32,
/// Fine-grained selection of which alert categories may be delivered.
#[serde(default)]
pub alert_types: NotificationAlertTypes,
}
fn default_notification_mode() -> String { "problems".into() }
@@ -805,6 +845,7 @@ impl Default for NotificationSettings {
cooldown_seconds: default_notification_cooldown(),
communication_failure_threshold: default_notification_failure_threshold(),
target_timeout_minutes: default_notification_target_timeout(),
alert_types: NotificationAlertTypes::default(),
}
}
}
+18 -1
View File
@@ -12,8 +12,25 @@ fn important_kind(kind: &str) -> bool {
"device.communication_error")
}
fn alert_type_enabled(cfg: &NotificationSettings, kind: &str) -> bool {
let types = &cfg.alert_types;
if kind == "ha.sensor_stale" { return types.stale_sensor; }
if kind.starts_with("ha.sensor_") { return types.sensor_errors; }
if matches!(kind, "device.offline" | "device.communication_error" | "device.command_unconfirmed") { return types.communication; }
if kind == "zone.target_timeout" { return types.target_timeout; }
if kind.starts_with("automation.") { return types.automation; }
if matches!(kind,
"zone.action_error" | "zone.mode_change_off_error" | "zone.local_power_error" |
"zone.device_missing" | "zone.sensor_discrepancy" |
"zone.temporary_quick_thermostat_cancelled" | "zone.temporary_quick_thermostat_poweroff_error" |
"group.power_error"
) { return types.control_errors; }
if important_kind(kind) { return types.important_events; }
types.other
}
fn should_send(cfg: &NotificationSettings, level: &str, kind: &str) -> bool {
if !cfg.enabled { return false; }
if !cfg.enabled || !alert_type_enabled(cfg, kind) { return false; }
if level == "error" || level == "warn" { return true; }
cfg.mode == "important" && important_kind(kind)
}