Skip to content

Commit

Permalink
Add custom attributes
Browse files Browse the repository at this point in the history
Fix initialization temperature bug
  • Loading branch information
Jean-Marc Collin committed Jan 7, 2023
1 parent 44174f2 commit a6a5fd3
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 18 deletions.
58 changes: 52 additions & 6 deletions custom_components/versatile_thermostat/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
import logging

from datetime import timedelta
from typing import Any, Mapping

from homeassistant.core import (
# HomeAssistant,
HomeAssistant,
callback,
CoreState,
DOMAIN as HA_DOMAIN,
Expand Down Expand Up @@ -35,10 +36,10 @@
HVAC_MODE_HEAT,
HVAC_MODE_OFF,
PRESET_ACTIVITY,
# PRESET_AWAY,
# PRESET_BOOST,
# PRESET_COMFORT,
# PRESET_ECO,
PRESET_AWAY,
PRESET_BOOST,
PRESET_COMFORT,
PRESET_ECO,
# PRESET_HOME,
PRESET_NONE,
# PRESET_SLEEP,
Expand Down Expand Up @@ -94,7 +95,7 @@


async def async_setup_entry(
_, # hass: HomeAssistant,
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
Expand Down Expand Up @@ -134,6 +135,7 @@ async def async_setup_entry(
async_add_entities(
[
VersatileThermostat(
hass,
unique_id,
name,
heater_entity_id,
Expand Down Expand Up @@ -170,6 +172,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):

def __init__(
self,
hass,
unique_id,
name,
heater_entity_id,
Expand All @@ -195,6 +198,9 @@ def __init__(

super().__init__()

self._hass = hass
self._attr_extra_state_attributes = {}

self._unique_id = unique_id
self._name = name
self._heater_entity_id = heater_entity_id
Expand Down Expand Up @@ -360,6 +366,13 @@ def current_temperature(self):
"""Return the sensor temperature."""
return self._cur_temp

# @property
# def extra_state_attributes(self) -> Mapping[str, Any] | None:
# _LOGGER.debug(
# "Calling extra_state_attributes: %s", self._hass.custom_attributes
# )
# return self._hass.custom_attributes

async def async_set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
_LOGGER.info("%s - Set hvac mode: %s", self, hvac_mode)
Expand Down Expand Up @@ -655,6 +668,10 @@ async def get_my_previous_state(self):
if not self._hvac_mode and old_state.state:
self._hvac_mode = old_state.state

self._prop_algorithm.calculate(
self._target_temp, self._cur_temp, self._cur_ext_temp
)

else:
# No previous state, try and restore defaults
if self._target_temp is None:
Expand Down Expand Up @@ -1007,6 +1024,8 @@ async def _async_control_heating(self, time=None):

await self._async_heater_turn_on()

self.update_custom_attributes()

async def _turn_off(_):
_LOGGER.info(
"%s - stop heating for %d min %d sec",
Expand All @@ -1017,6 +1036,7 @@ async def _turn_off(_):
await self._async_heater_turn_off()
self._async_cancel_cycle()
self._async_cancel_cycle = None
self.update_custom_attributes()

# Program turn off
self._async_cancel_cycle = async_call_later(
Expand All @@ -1025,6 +1045,32 @@ async def _turn_off(_):
_turn_off,
)

def update_custom_attributes(self):
"""Update the custom extra attributes for the entity"""

self._attr_extra_state_attributes = {
"away_temp": self._presets[PRESET_AWAY],
"eco_temp": self._presets[PRESET_ECO],
"boost_temp": self._presets[PRESET_BOOST],
"comfort_temp": self._presets[PRESET_BOOST],
"power_temp": self._presets[PRESET_POWER],
"on_percent": self._prop_algorithm.on_percent,
"on_time_sec": self._prop_algorithm.on_time_sec,
"off_time_sec": self._prop_algorithm.off_time_sec,
"ext_current_temperature": self._cur_ext_temp,
"current_power": self._current_power,
"current_power_max": self._current_power_max,
"cycle_min": self._cycle_min,
"bias": self._proportional_bias,
"function": self._proportional_function,
"tpi_coefc": self._tpi_coefc,
"tpi_coeft": self._tpi_coeft,
"is_device_active": self._is_device_active,
}
_LOGGER.debug(
"Calling update_custom_attributes: %s", self._attr_extra_state_attributes
)

@callback
def async_registry_entry_updated(self):
"""update the entity if the config entry have been updated
Expand Down
30 changes: 18 additions & 12 deletions custom_components/versatile_thermostat/prop_algorithm.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def __init__(
self._tpi_coefc = tpi_coefc
self._tpi_coeft = tpi_coeft
self._cycle_min = cycle_min
self._on_percent = 0
self._on_time_sec = 0
self._off_time_sec = self._cycle_min * 60

Expand All @@ -42,34 +43,34 @@ def calculate(
_LOGGER.warning(
"Proportional algorithm: calculation is not possible cause target_temp or current_temp is null. Heating will be disabled" # pylint: disable=line-too-long
)
on_percent = 0
self._on_percent = 0
else:
delta_temp = target_temp - current_temp
delta_ext_temp = (
target_temp - ext_current_temp if ext_current_temp is not None else 0
)

if self._function == PROPORTIONAL_FUNCTION_LINEAR:
on_percent = 0.25 * delta_temp + self._bias
self._on_percent = 0.25 * delta_temp + self._bias
elif self._function == PROPORTIONAL_FUNCTION_ATAN:
on_percent = math.atan(delta_temp + self._bias) / 1.4
self._on_percent = math.atan(delta_temp + self._bias) / 1.4
elif self._function == PROPORTIONAL_FUNCTION_TPI:
on_percent = (
self._on_percent = (
self._tpi_coefc * delta_temp + self._tpi_coeft * delta_ext_temp
)
else:
_LOGGER.warning(
"Proportional algorithm: unknown %s function. Heating will be disabled",
self._function,
)
on_percent = 0
self._on_percent = 0

# calculated on_time duration in seconds
if on_percent > 1:
on_percent = 1
if on_percent < 0:
on_percent = 0
self._on_time_sec = on_percent * self._cycle_min * 60
if self._on_percent > 1:
self._on_percent = 1
if self._on_percent < 0:
self._on_percent = 0
self._on_time_sec = self._on_percent * self._cycle_min * 60

# Do not heat for less than xx sec
if self._on_time_sec < PROPORTIONAL_MIN_DURATION_SEC:
Expand All @@ -80,18 +81,23 @@ def calculate(
)
self._on_time_sec = 0

self._off_time_sec = (1.0 - on_percent) * self._cycle_min * 60
self._off_time_sec = self._cycle_min * 60 - self._on_time_sec

_LOGGER.debug(
"heating percent calculated for current_temp %.1f, ext_current_temp %.1f and target_temp %.1f is %.2f, on_time is %d (sec), off_time is %d (sec)", # pylint: disable=line-too-long
current_temp if current_temp else -9999.0,
ext_current_temp if ext_current_temp else -9999.0,
target_temp if target_temp else -9999.0,
on_percent,
self._on_percent,
self.on_time_sec,
self.off_time_sec,
)

@property
def on_percent(self) -> float:
"""Returns the percentage the heater must be ON (1 means the heater will be always on, 0 never on)""" # pylint: disable=line-too-long
return round(self._on_percent, 2)

@property
def on_time_sec(self) -> int:
"""Returns the calculated time in sec the heater must be ON"""
Expand Down

0 comments on commit a6a5fd3

Please sign in to comment.