Skip to content

Commit

Permalink
Merge branch 'Alexwijn:develop' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
sergeantd83 authored Jan 14, 2025
2 parents 8664c6d + b9d259b commit 33a9117
Showing 3 changed files with 60 additions and 35 deletions.
15 changes: 11 additions & 4 deletions custom_components/sat/climate.py
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@

import asyncio
import logging
import math
from datetime import timedelta, datetime
from time import monotonic, time
from typing import Optional
@@ -41,7 +42,7 @@
from .const import *
from .coordinator import SatDataUpdateCoordinator, DeviceState, DeviceStatus
from .entity import SatEntity
from .helpers import convert_time_str_to_seconds
from .helpers import convert_time_str_to_seconds, seconds_since
from .pwm import PWMState
from .relative_modulation import RelativeModulation, RelativeModulationState
from .setpoint_adjuster import SetpointAdjuster
@@ -809,12 +810,12 @@ async def _async_control_setpoint(self, pwm_state: PWMState) -> None:
self._setpoint = self._minimum_setpoint.current

if self._minimum_setpoint_version == 2:
if self._coordinator.flame_active and self._coordinator.device_status != DeviceStatus.PUMP_STARTING:
if self._coordinator.flame_active and seconds_since(self._coordinator.flame_on_since) > 6 and self._coordinator.device_status != DeviceStatus.PUMP_STARTING:
self._setpoint = self._setpoint_adjuster.adjust(self._coordinator.boiler_temperature - 2)
elif self._setpoint_adjuster.current is not None:
self._setpoint = self._setpoint_adjuster.current
elif not self._coordinator.flame_active:
self._setpoint = self._coordinator.boiler_temperature + 10
self._setpoint = self._setpoint_adjuster.force(self._coordinator.boiler_temperature + 10)
elif self._setpoint is None:
_LOGGER.debug("Setpoint not available.")
return
@@ -914,10 +915,16 @@ async def async_control_heating_loop(self, _time: Optional[datetime] = None) ->
# Check for overshoot
if self._coordinator.device_status == DeviceStatus.OVERSHOOT_HANDLING:
self._pulse_width_modulation_enabled = True
_LOGGER.info("Overshoot Handling detected, enabling Pulse Width Modulation.")

# Check if we are above the overshoot temperature
if self._coordinator.device_status == DeviceStatus.COOLING_DOWN and self._calculated_setpoint > self.minimum_setpoint:
if (
self._setpoint_adjuster.current is not None and
self._coordinator.device_status == DeviceStatus.COOLING_DOWN and
math.floor(self._calculated_setpoint) > self._setpoint_adjuster.current + 2
):
self._pulse_width_modulation_enabled = False
_LOGGER.info("Setpoint stabilization detected, disabling Pulse Width Modulation.")

# Pulse Width Modulation
if self.pulse_width_modulation_enabled:
72 changes: 42 additions & 30 deletions custom_components/sat/coordinator.py
Original file line number Diff line number Diff line change
@@ -27,17 +27,18 @@ class DeviceState(str, Enum):


class DeviceStatus(str, Enum):
FLAME_OFF = "flame_off"
HOT_WATER = "hot_water"
PREHEATING = "preheating"
HEATING_UP = "heating_up"
AT_SETPOINT = "at_setpoint"
COOLING_DOWN = "cooling_down"
NEAR_SETPOINT = "near_setpoint"
PUMP_STARTING = "pump_starting"
WAITING_FOR_FLAME = "waiting_for_flame"
OVERSHOOT_HANDLING = "overshoot_handling"
OVERSHOOT_STABILIZED = "overshoot_stabilized"

OFF = "off"
IDLE = "idle"
UNKNOWN = "unknown"
INITIALIZING = "initializing"

@@ -79,6 +80,7 @@ def resolve(hass: HomeAssistant, mode: str, device: str, data: Mapping[str, Any]
class SatDataUpdateCoordinator(DataUpdateCoordinator):
def __init__(self, hass: HomeAssistant, data: Mapping[str, Any], options: Mapping[str, Any] | None = None) -> None:
"""Initialize."""
self._boiler_temperature_cold: float | None = None
self._boiler_temperatures: list[tuple[float, float]] = []
self._boiler_temperature_tracker = BoilerTemperatureTracker()

@@ -118,40 +120,39 @@ def device_status(self):
return DeviceStatus.HOT_WATER

if self.setpoint is None or self.setpoint <= MINIMUM_SETPOINT:
return DeviceStatus.OFF
return DeviceStatus.IDLE

if self.device_active:
if (
self.boiler_temperature_cold is not None
and self.boiler_temperature_cold > self.boiler_temperature
and self.boiler_temperature_derivative < 0
):
return DeviceStatus.PUMP_STARTING

if self.setpoint > self.boiler_temperature:
if not self.flame_active:
return DeviceStatus.WAITING_FOR_FLAME
if self.boiler_temperature_cold is not None and self.boiler_temperature_cold > self.boiler_temperature:
if self.boiler_temperature_derivative < 0:
return DeviceStatus.PUMP_STARTING

if self.flame_active:
if (
seconds_since(self._flame_on_since) <= 6
or (self.boiler_temperature_cold is not None and self.boiler_temperature_cold > self.boiler_temperature)
):
if self._boiler_temperature_tracker.active and self.setpoint > self.boiler_temperature:
return DeviceStatus.PREHEATING

if self.setpoint > self.boiler_temperature:
if self.flame_active:
if self._boiler_temperature_tracker.active:
return DeviceStatus.HEATING_UP

return DeviceStatus.OVERSHOOT_HANDLING

if self.setpoint == self.boiler_temperature:
return DeviceStatus.WAITING_FOR_FLAME

if abs(self.setpoint - self.boiler_temperature) <= DEADBAND:
return DeviceStatus.AT_SETPOINT

if self.setpoint < self.boiler_temperature:
if self.boiler_temperature > self.setpoint:
if self.flame_active:
return DeviceStatus.COOLING_DOWN
if self._boiler_temperature_tracker.active:
if self.boiler_temperature - self.setpoint > 2:
return DeviceStatus.COOLING_DOWN

return DeviceStatus.FLAME_OFF
return DeviceStatus.NEAR_SETPOINT

return DeviceStatus.OVERSHOOT_HANDLING

return DeviceStatus.WAITING_FOR_FLAME

return DeviceStatus.UNKNOWN

@@ -232,14 +233,7 @@ def boiler_temperature_derivative(self) -> float | None:

@property
def boiler_temperature_cold(self) -> float | None:
for timestamp, temperature in reversed(self._boiler_temperatures):
if self._device_on_since is None or self._device_on_since > timestamp:
return temperature

if self._flame_on_since is None or self._flame_on_since > timestamp:
return temperature

return None
return self._boiler_temperature_cold

@property
def boiler_temperature_tracking(self) -> bool:
@@ -402,6 +396,10 @@ async def async_control_heating_loop(self, climate: Optional[SatClimate] = None,
if seconds_since(timestamp) <= MAX_BOILER_TEMPERATURE_AGE
]

# Update the cold temperature of the boiler
if boiler_temperature_cold := self._get_latest_boiler_cold_temperature():
self._boiler_temperature_cold = boiler_temperature_cold

async def async_set_heater_state(self, state: DeviceState) -> None:
"""Set the state of the device heater."""
_LOGGER.info("Set central heater state %s", state)
@@ -430,6 +428,20 @@ async def async_set_control_thermostat_setpoint(self, value: float) -> None:
"""Control the setpoint temperature for the thermostat."""
pass

def _get_latest_boiler_cold_temperature(self) -> float | None:
"""Get the latest boiler cold temperature based on recent boiler temperatures."""
for timestamp, temperature in reversed(self._boiler_temperatures):
if self._device_on_since is None or self._device_on_since > timestamp:
return temperature

if self._flame_on_since is None or self._flame_on_since > timestamp:
return temperature

if self._boiler_temperature_cold is not None:
return min(self.boiler_temperature, self._boiler_temperature_cold)

return None


class SatEntityCoordinator(DataUpdateCoordinator):
def get(self, domain: str, key: str) -> Optional[Any]:
8 changes: 7 additions & 1 deletion custom_components/sat/setpoint_adjuster.py
Original file line number Diff line number Diff line change
@@ -21,10 +21,16 @@ def reset(self):
"""Reset the setpoint."""
self._current = None

def force(self, target_setpoint: float) -> float:
"""Force setpoint."""
self._current = target_setpoint

return self._current

def adjust(self, target_setpoint: float) -> float:
"""Gradually adjust the current setpoint toward the target setpoint."""
if self._current is None:
self._current = target_setpoint + INITIAL_OFFSET
self._current = target_setpoint

previous_setpoint = self._current

0 comments on commit 33a9117

Please sign in to comment.