This commit is contained in:
Mateusz Gruszczyński committed 2026-10-20 22:52:13 +02:00
1 parent f376f0a7b6
commit 8059e16d12
36 files changed
+1140 -193

No files matched your search

+36 -2
View File
@@ -33,7 +33,12 @@ fn flow_zone_action_already_satisfied(
action: &DeviceCommand,
heat_anticipation_c: Option<f64>,
standby_offset_c: Option<f64>,
simple_heat: Option<&SimpleHeatSettings>,
) -> bool {
if let Some(profile) = simple_heat {
return zone.simple_heat.as_ref() == Some(profile)
&& zone.control_source == "automation.flow";
}
if heat_anticipation_c.is_some_and(|value| (zone.heat_anticipation_c - value).abs() > 0.001)
|| standby_offset_c.is_some_and(|value| (zone.standby_offset_c - value).abs() > 0.001)
{
@@ -202,6 +207,7 @@ async fn run_automations(state: &AppState) -> Result<()> {
&effective_action,
item.action_zone_heat_anticipation_c,
item.action_zone_standby_offset_c,
item.action_zone_simple_heat.as_ref(),
) {
let before_runtime = item.flow_runtime.clone();
flow_acknowledge_change_gates(&mut item);
@@ -289,6 +295,7 @@ async fn run_automations(state: &AppState) -> Result<()> {
apply_flow_zone_action(
state, zone_id, item.action_zone_preset.as_deref(), &effective_action,
item.action_zone_heat_anticipation_c, item.action_zone_standby_offset_c,
item.action_zone_simple_heat.as_ref(),
).await
} else if let Some(group_id) = item.action_group_id.as_deref() {
let group_mode = effective_action.mode.as_deref().map(|mode| if mode == "auto" { "house".to_string() } else { mode.to_string() });
@@ -1120,6 +1127,7 @@ fn flow_condition_kind_runtime(kind: &str) -> bool {
async fn apply_flow_zone_action(
state: &AppState, zone_id: &str, preset: Option<&str>, action: &DeviceCommand,
heat_anticipation_c: Option<f64>, standby_offset_c: Option<f64>,
simple_heat: Option<&SimpleHeatSettings>,
) -> Result<bool, AppError> {
let _schedule_guard = state.lock_schedule_operation().await;
// Match the canonical thermostat ordering used by Web/HA zone control. This prevents
@@ -1131,13 +1139,29 @@ async fn apply_flow_zone_action(
// A disabled Zone is an administrative gate for every Flow thermostat action.
// Flow power commands control only the assigned unit runtime; they never change zone.enabled.
if !zone.enabled { return Ok(false); }
// Explicit manual target wins until Auto is selected again. A previous Flow v4
// may have left a custom preset behind: that is *automation*, not manual,
// and must be replaceable by the simplified Flow without user intervention.
if simple_heat.is_some() && zone.control_source != "automation.flow"
&& (zone.manual_preset.is_some() || zone.manual_setpoint.is_some()) {
return Ok(false);
}
let device_id = zone.device_id.clone();
if let Some(profile) = simple_heat {
if zone.simple_heat.as_ref() != Some(profile) {
zone.simple_heat_runtime = SimpleHeatRuntime::default();
}
zone.simple_heat = Some(profile.clone());
} else {
zone.simple_heat = None;
zone.simple_heat_runtime = SimpleHeatRuntime::default();
}
if let Some(value) = heat_anticipation_c { zone.heat_anticipation_c = value; }
if let Some(value) = standby_offset_c { zone.standby_offset_c = value; }
if let Some(mode) = action.mode.as_deref() {
match mode { "auto" => zone.inherit_house_mode = true, "heat" | "cool" => { zone.mode = mode.to_string(); zone.inherit_house_mode = false; }, _ => return Err(AppError::BadRequest("unsupported Flow thermostat mode".into())) }
}
match preset.unwrap_or("auto") {
match if simple_heat.is_some() { "auto" } else { preset.unwrap_or("auto") } {
"auto" => { zone.manual_preset = None; zone.manual_setpoint = None; zone.manual_override_until = None; }
"custom" => {
let target = action.target_temperature.ok_or_else(|| AppError::BadRequest("Flow custom thermostat action has no target".into()))?;
@@ -1163,7 +1187,17 @@ async fn apply_flow_zone_action(
zone.control_source = "automation.flow".into();
zone.control_reason = "Visual Flow automation".into();
let schedules = state.db.list_schedules()?;
let house_mode = state.settings.read().await.house_mode.clone();
let runtime_settings = state.settings.read().await;
let house_mode = runtime_settings.house_mode.clone();
if simple_heat.is_some() {
// Show the correct bedroom/child-room night target immediately after
// Flow activation, not the old Comfort preset until the next zone tick.
zone.simple_heat_runtime.night = Some(night_mode_active(
&runtime_settings.night_mode,
Local::now().time(),
));
}
drop(runtime_settings);
refresh_control_ownership(&mut zone);
if zone.control_owner == "automation" {
zone.control_source = "automation.flow".into();