This commit is contained in:
Mateusz Gruszczyński
2026-09-01 11:10:28 +02:00
parent 3479ed750d
commit 9f08d7ccf9
30 changed files with 559 additions and 150 deletions
+26
View File
@@ -1,3 +1,16 @@
pub fn automation_action_conflicts_with_thermostat(command: &DeviceCommand) -> bool {
// Power/mode/target are translated by apply_automatic_device_action into durable zone
// state, so they do not fight the thermostat. Fan/quiet/sleep are thermostat outputs with
// no independent zone override model; accepting them as one-shot direct automation would
// let the next thermostat cycle immediately overwrite them.
command.fan_speed.is_some() || command.quiet.is_some() || command.sleep.is_some()
}
fn device_has_enabled_thermostat_zone(device_id: &str, zones: &[Zone]) -> bool {
zones.iter().any(|zone| zone.device_id == device_id && zone.enabled)
}
async fn run_automations(state: &AppState) -> Result<()> {
if !state.settings.read().await.house_power_enabled { return Ok(()); }
let devices = state.db.list_devices()?;
@@ -62,6 +75,19 @@ async fn run_automations(state: &AppState) -> Result<()> {
continue;
}
if item.action_group_id.is_none()
&& device_has_enabled_thermostat_zone(&item.action_device_id, &zones)
&& automation_action_conflicts_with_thermostat(&item.action)
{
// Fan/quiet/sleep are outputs continuously managed by the thermostat. Unlike
// power/mode/target they cannot be translated into durable zone state, so a direct
// automation would be immediately overwritten by the next thermostat cycle.
state.log("warn", "automation.blocked_by_thermostat_owner", &format!("Automation {} suppressed because the device is owned by an enabled thermostat zone", item.name), json!({
"automation_id": item.id, "device_id": item.action_device_id
}));
continue;
}
let target_devices: Vec<String> = if let Some(group_id) = item.action_group_id.as_deref() {
groups.iter().find(|group| group.id == group_id)
.map(|group| group.zone_ids.iter()