Skip to content

Commit

Permalink
Add support to determine how much time it takes to heat up the house
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexwijn committed May 13, 2023
1 parent 5794308 commit ec6553e
Showing 1 changed file with 47 additions and 5 deletions.
52 changes: 47 additions & 5 deletions custom_components/sat/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
from .pwm import PWM, PWMState

ATTR_ROOMS = "rooms"
ATTR_WARMING_UP = "warming_up"
ATTR_WARMING_UP_DERIVATIVE = "warming_up_derivative"
SENSOR_TEMPERATURE_ID = "sensor_temperature_id"

_LOGGER = logging.getLogger(__name__)
Expand All @@ -63,6 +65,24 @@ def convert_time_str_to_seconds(time_str: str) -> float:
return (date_time.hour * 3600) + (date_time.minute * 60) + date_time.second


def calculate_derivative_per_hour(temperature_error: float, time_taken_seconds: float):
"""
Calculates the derivative per hour based on the temperature error and time taken.
Args:
temperature_error (float): The temperature error or difference.
time_taken_seconds (float): The time taken in seconds.
Returns:
float: The derivative per hour.
"""
# Convert time taken from seconds to hours
time_taken_hours = time_taken_seconds / 3600
# Calculate the derivative per hour by dividing temperature error by time taken
return round(temperature_error / time_taken_hours, 2)


def create_pid_controller(options) -> PID:
"""Create and return a PID controller instance with the given configuration options."""
# Extract the configuration options
Expand Down Expand Up @@ -107,6 +127,12 @@ async def async_setup_entry(_hass: HomeAssistant, _config_entry: ConfigEntry, _a
_hass.data[DOMAIN][_config_entry.entry_id][CLIMATE] = climate


class SatWarmingUp:
def __init__(self, error: float, started: int = None):
self.error = error
self.started = started if started is not None else monotonic()


class SatClimate(SatEntity, ClimateEntity, RestoreEntity):
def __init__(self, coordinator: SatDataUpdateCoordinator, config_entry: ConfigEntry, unit: str):
super().__init__(coordinator, config_entry)
Expand Down Expand Up @@ -150,9 +176,11 @@ def __init__(self, coordinator: SatDataUpdateCoordinator, config_entry: ConfigEn
self._sensors = []
self._rooms = None
self._setpoint = None
self._warming_up = False
self._outputs = deque(maxlen=50)

self._warming_up = None
self._warming_up_derivative = None

self._hvac_mode = None
self._target_temperature = None
self._window_sensor_handle = None
Expand Down Expand Up @@ -276,6 +304,12 @@ async def _restore_previous_state_or_set_defaults(self):
if old_state.attributes.get(ATTR_PRESET_MODE):
self._attr_preset_mode = old_state.attributes.get(ATTR_PRESET_MODE)

if warming_up := old_state.attributes.get(ATTR_WARMING_UP):
self._warming_up = SatWarmingUp(warming_up["error"], warming_up["started"])

if old_state.attributes.get(ATTR_WARMING_UP_DERIVATIVE):
self._warming_up_derivative = old_state.attributes.get(ATTR_WARMING_UP_DERIVATIVE)

if old_state.attributes.get(ATTR_ROOMS):
self._rooms = old_state.attributes.get(ATTR_ROOMS)
else:
Expand All @@ -292,6 +326,8 @@ async def _restore_previous_state_or_set_defaults(self):
if not self._hvac_mode:
self._hvac_mode = HVACMode.OFF

self.async_write_ha_state()

async def _register_services(self):
async def reset_integral(_call: ServiceCall):
"""Service to reset the integral part of the PID controller."""
Expand Down Expand Up @@ -346,7 +382,8 @@ def extra_state_attributes(self):

"rooms": self._rooms,
"setpoint": self._setpoint,
"warming_up": self._warming_up,
"warming_up_derivative": self._warming_up_derivative,
"warming_up": vars(self._warming_up) if self._warming_up is not None else None,
"valves_open": self.valves_open,
"heating_curve": self._heating_curve.value,
"minimum_setpoint": self._coordinator.minimum_setpoint,
Expand Down Expand Up @@ -513,7 +550,7 @@ def pulse_width_modulation_enabled(self) -> bool:
if self.max_error <= 0.1:
return True

if self._warming_up and self._setpoint < (overshoot_protection_value - 2):
if self._warming_up is not None and self._setpoint < (overshoot_protection_value - 2):
return True

return False
Expand Down Expand Up @@ -734,8 +771,13 @@ async def _async_control_pid(self, reset: bool = False):

# Since we are in the deadband, we can safely assume we are not warming up anymore
if self._warming_up and max_error <= 0.1:
self._warming_up = False
# Calculate the derivative per hour
time_taken_seconds = monotonic() - self._warming_up.started
self._warming_up_derivative = calculate_derivative_per_hour(self._warming_up.error, time_taken_seconds)

# Notify that we are not warming anymore
_LOGGER.info("Reached deadband, turning off warming up.")
self._warming_up = None

# Update the pid controller
self._pid.update(error=max_error, heating_curve_value=self._heating_curve.value)
Expand All @@ -748,7 +790,7 @@ async def _async_control_pid(self, reset: bool = False):

# Determine if we are warming up
if self.max_error > 0.1:
self._warming_up = True
self._warming_up = SatWarmingUp(self.max_error)
_LOGGER.info("Outside of deadband, we are warming up")

self.async_write_ha_state()
Expand Down

0 comments on commit ec6553e

Please sign in to comment.