Skip to content

Commit

Permalink
With update for UI card
Browse files Browse the repository at this point in the history
  • Loading branch information
Jean-Marc Collin committed Nov 11, 2023
1 parent 82348ad commit 2c5078c
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 46 deletions.
9 changes: 9 additions & 0 deletions custom_components/versatile_thermostat/base_thermostat.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
ClimateEntity._entity_component_unrecorded_attributes.union(
frozenset(
{
"is_on",
"type",
"eco_temp",
"boost_temp",
Expand Down Expand Up @@ -170,6 +171,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
"presence_sensor_entity_id",
"power_sensor_entity_id",
"max_power_sensor_entity_id",
"temperature_unit",
}
)
)
Expand Down Expand Up @@ -1032,6 +1034,11 @@ def nb_underlying_entities(self) -> int:
"""Returns the number of underlying entities"""
return len(self._underlyings)

@property
def is_on(self) -> bool:
"""True if the VTherm is on (! HVAC_OFF)"""
return self.hvac_mode and self.hvac_mode != HVACMode.OFF

def underlying_entity_id(self, index=0) -> str | None:
"""The climate_entity_id. Added for retrocompatibility reason"""
if index < self.nb_underlying_entities:
Expand Down Expand Up @@ -2107,6 +2114,7 @@ def update_custom_attributes(self):
"""Update the custom extra attributes for the entity"""

self._attr_extra_state_attributes: dict(str, str) = {
"is_on": self.is_on,
"hvac_action": self.hvac_action,
"hvac_mode": self.hvac_mode,
"preset_mode": self.preset_mode,
Expand Down Expand Up @@ -2166,6 +2174,7 @@ def update_custom_attributes(self):
"presence_sensor_entity_id": self._presence_sensor_entity_id,
"power_sensor_entity_id": self._power_sensor_entity_id,
"max_power_sensor_entity_id": self._max_power_sensor_entity_id,
"temperature_unit": self.temperature_unit,
}

@callback
Expand Down
153 changes: 107 additions & 46 deletions custom_components/versatile_thermostat/thermostat_climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
from datetime import timedelta, datetime

from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.event import async_track_state_change_event, async_track_time_interval
from homeassistant.helpers.event import (
async_track_state_change_event,
async_track_time_interval,
)

from homeassistant.components.climate import HVACAction, HVACMode

Expand All @@ -29,27 +32,40 @@
RegulationParamSlow,
RegulationParamLight,
RegulationParamMedium,
RegulationParamStrong
RegulationParamStrong,
)

from .underlyings import UnderlyingClimate

_LOGGER = logging.getLogger(__name__)


class ThermostatOverClimate(BaseThermostat):
"""Representation of a base class for a Versatile Thermostat over a climate"""
_auto_regulation_mode:str = None

_auto_regulation_mode: str = None
_regulation_algo = None
_regulated_target_temp: float = None
_auto_regulation_dtemp: float = None
_auto_regulation_period_min: int = None
_last_regulation_change: datetime = None

_entity_component_unrecorded_attributes = BaseThermostat._entity_component_unrecorded_attributes.union(frozenset(
{
"is_over_climate", "start_hvac_action_date", "underlying_climate_0", "underlying_climate_1",
"underlying_climate_2", "underlying_climate_3", "regulation_accumulated_error"
}))
_entity_component_unrecorded_attributes = (
BaseThermostat._entity_component_unrecorded_attributes.union(
frozenset(
{
"is_over_climate",
"start_hvac_action_date",
"underlying_climate_0",
"underlying_climate_1",
"underlying_climate_2",
"underlying_climate_3",
"regulation_accumulated_error",
"auto_regulation_mode",
}
)
)
)

def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
"""Initialize the thermostat over switch."""
Expand All @@ -60,12 +76,12 @@ def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:

@property
def is_over_climate(self) -> bool:
""" True if the Thermostat is over_climate"""
"""True if the Thermostat is over_climate"""
return True

@property
def hvac_action(self) -> HVACAction | None:
""" Returns the current hvac_action by checking all hvac_action of the underlyings """
"""Returns the current hvac_action by checking all hvac_action of the underlyings"""

# if one not IDLE or OFF -> return it
# else if one IDLE -> IDLE
Expand All @@ -92,28 +108,44 @@ async def _async_internal_set_temperature(self, temperature):
await self._send_regulated_temperature(force=True)

async def _send_regulated_temperature(self, force=False):
""" Sends the regulated temperature to all underlying """
"""Sends the regulated temperature to all underlying"""
if not self._regulated_target_temp:
self._regulated_target_temp = self.target_temperature

new_regulated_temp = round_to_nearest(
self._regulation_algo.calculate_regulated_temperature(self.current_temperature, self._cur_ext_temp),
self._auto_regulation_dtemp)
self._regulation_algo.calculate_regulated_temperature(
self.current_temperature, self._cur_ext_temp
),
self._auto_regulation_dtemp,
)
dtemp = new_regulated_temp - self._regulated_target_temp

if not force and abs(dtemp) < self._auto_regulation_dtemp:
_LOGGER.debug("%s - dtemp (%.1f) is < %.1f -> forget the regulation send", self, dtemp, self._auto_regulation_dtemp)
_LOGGER.debug(
"%s - dtemp (%.1f) is < %.1f -> forget the regulation send",
self,
dtemp,
self._auto_regulation_dtemp,
)
return

now:datetime = NowClass.get_now(self._hass)
period = float((now - self._last_regulation_change).total_seconds()) / 60.
now: datetime = NowClass.get_now(self._hass)
period = float((now - self._last_regulation_change).total_seconds()) / 60.0
if not force and period < self._auto_regulation_period_min:
_LOGGER.debug("%s - period (%.1f) is < %.0f -> forget the regulation send", self, period, self._auto_regulation_period_min)
_LOGGER.debug(
"%s - period (%.1f) is < %.0f -> forget the regulation send",
self,
period,
self._auto_regulation_period_min,
)
return


self._regulated_target_temp = new_regulated_temp
_LOGGER.info("%s - Regulated temp have changed to %.1f. Resend it to underlyings", self, new_regulated_temp)
_LOGGER.info(
"%s - Regulated temp have changed to %.1f. Resend it to underlyings",
self,
new_regulated_temp,
)
self._last_regulation_change = now

for under in self._underlyings:
Expand All @@ -123,7 +155,7 @@ async def _send_regulated_temperature(self, force=False):

@overrides
def post_init(self, entry_infos):
""" Initialize the Thermostat"""
"""Initialize the Thermostat"""

super().post_init(entry_infos)
for climate in [
Expand All @@ -142,14 +174,24 @@ def post_init(self, entry_infos):
)

self.choose_auto_regulation_mode(
entry_infos.get(CONF_AUTO_REGULATION_MODE) if entry_infos.get(CONF_AUTO_REGULATION_MODE) is not None else CONF_AUTO_REGULATION_NONE
entry_infos.get(CONF_AUTO_REGULATION_MODE)
if entry_infos.get(CONF_AUTO_REGULATION_MODE) is not None
else CONF_AUTO_REGULATION_NONE
)

self._auto_regulation_dtemp = entry_infos.get(CONF_AUTO_REGULATION_DTEMP) if entry_infos.get(CONF_AUTO_REGULATION_DTEMP) is not None else 0.5
self._auto_regulation_period_min = entry_infos.get(CONF_AUTO_REGULATION_PERIOD_MIN) if entry_infos.get(CONF_AUTO_REGULATION_PERIOD_MIN) is not None else 5
self._auto_regulation_dtemp = (
entry_infos.get(CONF_AUTO_REGULATION_DTEMP)
if entry_infos.get(CONF_AUTO_REGULATION_DTEMP) is not None
else 0.5
)
self._auto_regulation_period_min = (
entry_infos.get(CONF_AUTO_REGULATION_PERIOD_MIN)
if entry_infos.get(CONF_AUTO_REGULATION_PERIOD_MIN) is not None
else 5
)

def choose_auto_regulation_mode(self, auto_regulation_mode):
""" Choose or change the regulation mode"""
"""Choose or change the regulation mode"""
self._auto_regulation_mode = auto_regulation_mode
if self._auto_regulation_mode == CONF_AUTO_REGULATION_LIGHT:
self._regulation_algo = PITemperatureRegulator(
Expand All @@ -159,7 +201,8 @@ def choose_auto_regulation_mode(self, auto_regulation_mode):
RegulationParamLight.k_ext,
RegulationParamLight.offset_max,
RegulationParamLight.stabilization_threshold,
RegulationParamLight.accumulated_error_threshold)
RegulationParamLight.accumulated_error_threshold,
)
elif self._auto_regulation_mode == CONF_AUTO_REGULATION_MEDIUM:
self._regulation_algo = PITemperatureRegulator(
self.target_temperature,
Expand All @@ -168,7 +211,8 @@ def choose_auto_regulation_mode(self, auto_regulation_mode):
RegulationParamMedium.k_ext,
RegulationParamMedium.offset_max,
RegulationParamMedium.stabilization_threshold,
RegulationParamMedium.accumulated_error_threshold)
RegulationParamMedium.accumulated_error_threshold,
)
elif self._auto_regulation_mode == CONF_AUTO_REGULATION_STRONG:
self._regulation_algo = PITemperatureRegulator(
self.target_temperature,
Expand All @@ -177,7 +221,8 @@ def choose_auto_regulation_mode(self, auto_regulation_mode):
RegulationParamStrong.k_ext,
RegulationParamStrong.offset_max,
RegulationParamStrong.stabilization_threshold,
RegulationParamStrong.accumulated_error_threshold)
RegulationParamStrong.accumulated_error_threshold,
)
elif self._auto_regulation_mode == CONF_AUTO_REGULATION_SLOW:
self._regulation_algo = PITemperatureRegulator(
self.target_temperature,
Expand All @@ -186,11 +231,13 @@ def choose_auto_regulation_mode(self, auto_regulation_mode):
RegulationParamSlow.k_ext,
RegulationParamSlow.offset_max,
RegulationParamSlow.stabilization_threshold,
RegulationParamSlow.accumulated_error_threshold)
RegulationParamSlow.accumulated_error_threshold,
)
else:
# A default empty algo (which does nothing)
self._regulation_algo = PITemperatureRegulator(
self.target_temperature, 0, 0, 0, 0, 0.1, 0)
self.target_temperature, 0, 0, 0, 0, 0.1, 0
)

@overrides
async def async_added_to_hass(self):
Expand Down Expand Up @@ -219,27 +266,37 @@ async def async_added_to_hass(self):

@overrides
def update_custom_attributes(self):
""" Custom attributes """
"""Custom attributes"""
super().update_custom_attributes()

self._attr_extra_state_attributes["is_over_climate"] = self.is_over_climate
self._attr_extra_state_attributes["start_hvac_action_date"] = (
self._underlying_climate_start_hvac_action_date)
self._attr_extra_state_attributes["underlying_climate_0"] = (
self._underlyings[0].entity_id)
self._attr_extra_state_attributes[
"start_hvac_action_date"
] = self._underlying_climate_start_hvac_action_date
self._attr_extra_state_attributes["underlying_climate_0"] = self._underlyings[
0
].entity_id
self._attr_extra_state_attributes["underlying_climate_1"] = (
self._underlyings[1].entity_id if len(self._underlyings) > 1 else None
)
self._underlyings[1].entity_id if len(self._underlyings) > 1 else None
)
self._attr_extra_state_attributes["underlying_climate_2"] = (
self._underlyings[2].entity_id if len(self._underlyings) > 2 else None
)
self._underlyings[2].entity_id if len(self._underlyings) > 2 else None
)
self._attr_extra_state_attributes["underlying_climate_3"] = (
self._underlyings[3].entity_id if len(self._underlyings) > 3 else None
)
self._underlyings[3].entity_id if len(self._underlyings) > 3 else None
)

if self.is_regulated:
self._attr_extra_state_attributes["regulated_target_temperature"] = self._regulated_target_temp
self._attr_extra_state_attributes["regulation_accumulated_error"] = self._regulation_algo.accumulated_error
self._attr_extra_state_attributes["is_regulated"] = self.is_regulated
self._attr_extra_state_attributes[
"regulated_target_temperature"
] = self._regulated_target_temp
self._attr_extra_state_attributes[
"auto_regulation_mode"
] = self.auto_regulation_mode
self._attr_extra_state_attributes[
"regulation_accumulated_error"
] = self._regulation_algo.accumulated_error

self.async_write_ha_state()
_LOGGER.debug(
Expand Down Expand Up @@ -473,17 +530,17 @@ async def async_control_heating(self, force=False, _=None):

@property
def auto_regulation_mode(self):
""" Get the regulation mode """
"""Get the regulation mode"""
return self._auto_regulation_mode

@property
def regulated_target_temp(self):
""" Get the regulated target temperature """
"""Get the regulated target temperature"""
return self._regulated_target_temp

@property
def is_regulated(self):
""" Check if the ThermostatOverClimate is regulated """
"""Check if the ThermostatOverClimate is regulated"""
return self.auto_regulation_mode != CONF_AUTO_REGULATION_NONE

@property
Expand Down Expand Up @@ -668,7 +725,11 @@ async def service_set_auto_regulation_mode(self, auto_regulation_mode):
target:
entity_id: climate.thermostat_1
"""
_LOGGER.info("%s - Calling service_set_auto_regulation_mode, auto_regulation_mode: %s", self, auto_regulation_mode)
_LOGGER.info(
"%s - Calling service_set_auto_regulation_mode, auto_regulation_mode: %s",
self,
auto_regulation_mode,
)
if auto_regulation_mode == "None":
self.choose_auto_regulation_mode(CONF_AUTO_REGULATION_NONE)
elif auto_regulation_mode == "Light":
Expand Down
2 changes: 2 additions & 0 deletions custom_components/versatile_thermostat/thermostat_switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class ThermostatOverSwitch(BaseThermostat):
"function",
"tpi_coef_int",
"tpi_coef_ext",
"power_percent",
}
)
)
Expand Down Expand Up @@ -144,6 +145,7 @@ def update_custom_attributes(self):
self._attr_extra_state_attributes[
"on_percent"
] = self._prop_algorithm.on_percent
self._attr_extra_state_attributes["power_percent"] = self.power_percent
self._attr_extra_state_attributes[
"on_time_sec"
] = self._prop_algorithm.on_time_sec
Expand Down

0 comments on commit 2c5078c

Please sign in to comment.