This commit is contained in:
Mateusz Gruszczyński committed 2026-10-17 20:00:28 +02:00
1 parent ccf85a2fb8
commit f376f0a7b6
32 files changed
+312 -114

No files matched your search

+27 -9
View File
@@ -683,6 +683,7 @@ async fn control_zones(state: &AppState) -> Result<()> {
// publishing the zone, but never overwrite manual device state while it follows
// the house mode. Explicit per-zone heat/cool bypasses this branch above.
if effective_mode == "off" {
zone.heat_coasting = false;
zone.effective_setpoint = None;
zone.device_setpoint = None;
zone.demand = false;
@@ -709,6 +710,7 @@ async fn control_zones(state: &AppState) -> Result<()> {
// controller stays physically OFF. This also makes gaps between schedule windows true
// OFF periods instead of silently falling back to Comfort and recreating demand.
if !zone_has_active_thermostat_intent(&zone, active_schedule, Utc::now()) {
zone.heat_coasting = false;
clear_compressor_pending(&mut zone, true);
zone.effective_mode = "off".into();
zone.effective_setpoint = None;
@@ -802,6 +804,14 @@ async fn control_zones(state: &AppState) -> Result<()> {
let half = zone.hysteresis_for_mode(effective_mode) / 2.0;
let previous_demand = zone.demand;
let previous_heat_coasting = zone.heat_coasting;
zone.heat_coasting = heat_coasting_next_state(
zone.heat_coasting,
effective_mode,
temp,
target,
zone.heat_anticipation_c,
);
zone.demand = match effective_mode {
"heat" => {
if temp <= target - half {
@@ -857,14 +867,17 @@ async fn control_zones(state: &AppState) -> Result<()> {
"heat" => target + outdoor_assist,
_ => target - demand_assist,
};
// Early heat coasting changes only GREE's requested temperature. It does NOT
// toggle unit power or touch compressor protection/whole-multisplit OFF policy.
let delivering_heat = zone.demand && !(effective_mode == "heat" && zone.heat_coasting);
let standby_target = match effective_mode {
"heat" => target - zone.standby_offset_c.max(0.5),
_ => target + zone.standby_offset_c.max(0.5),
};
let desired_device_target = round_device_setpoint(
effective_mode,
zone.demand,
if zone.demand {
delivering_heat,
if delivering_heat {
active_target
} else {
standby_target
@@ -878,7 +891,10 @@ async fn control_zones(state: &AppState) -> Result<()> {
None
};
let demand_changed = previous_demand != zone.demand;
let demand_changed = previous_demand != zone.demand
|| (effective_mode == "heat" && previous_heat_coasting != zone.heat_coasting);
let previous_delivering_heat = previous_demand
&& !(effective_mode == "heat" && previous_heat_coasting);
let desired_fan = if night_active {
let max_fan = settings.night_mode.max_fan_speed.clamp(1, 5);
if zone.smart_fan {
@@ -888,7 +904,7 @@ async fn control_zones(state: &AppState) -> Result<()> {
temp,
target,
outdoor_assist_temperature,
zone.demand,
delivering_heat,
),
max_fan,
))
@@ -903,7 +919,7 @@ async fn control_zones(state: &AppState) -> Result<()> {
temp,
target,
outdoor_assist_temperature,
zone.demand,
delivering_heat,
))
} else {
None
@@ -934,8 +950,8 @@ async fn control_zones(state: &AppState) -> Result<()> {
let desired_quiet = smart_quiet_command(
zone.smart_fan,
quiet_supported,
previous_demand,
zone.demand,
previous_delivering_heat,
delivering_heat,
device.quiet,
settings.night_mode.enabled,
night_active,
@@ -1113,7 +1129,7 @@ async fn control_zones(state: &AppState) -> Result<()> {
let fan_needs_command = desired_fan
.map(|fan| fan != device.fan_speed)
.unwrap_or(false)
&& (zone.demand || demand_changed || core_needs_command || night_active);
&& (delivering_heat || demand_changed || core_needs_command || (night_active && !zone.smart_fan));
let needs_command = core_needs_command
|| fan_needs_command
|| desired_quiet
@@ -1150,10 +1166,12 @@ async fn control_zones(state: &AppState) -> Result<()> {
};
zone.last_action_at = Some(transition_at);
clear_compressor_pending(&mut zone, true);
state.log("info", "zone.setpoint_modulation", &format!("Zone {} -> {:.1} C ({})", zone.name, desired_device_target, if zone.demand { "demand" } else { "standby" }), json!({
state.log("info", "zone.setpoint_modulation", &format!("Zone {} -> {:.1} C ({})", zone.name, desired_device_target, if zone.heat_coasting { "coasting" } else if zone.demand { "demand" } else { "standby" }), json!({
"zone_id": zone.id,
"room_temperature": temp,
"comfort_target": target,
"heat_coasting": zone.heat_coasting,
"heat_anticipation_c": zone.heat_anticipation_c,
"device_target": desired_device_target,
"mode": effective_mode,
"preset": zone.active_preset,