227 lines
8.0 KiB
Python
227 lines
8.0 KiB
Python
# pylint: disable=line-too-long, abstract-method
|
|
|
|
""" A climate over switch classe """
|
|
import logging
|
|
from vtherm_api.log_collector import get_vtherm_logger
|
|
from datetime import timedelta
|
|
from homeassistant.core import Event, callback
|
|
from homeassistant.helpers.event import (
|
|
async_track_state_change_event,
|
|
async_track_time_interval,
|
|
EventStateChangedData,
|
|
)
|
|
from homeassistant.core import HomeAssistant
|
|
from .vtherm_hvac_mode import VThermHvacMode_OFF
|
|
|
|
from .const import (
|
|
CONF_UNDERLYING_LIST,
|
|
CONF_HEATER_KEEP_ALIVE,
|
|
CONF_INVERSE_SWITCH,
|
|
CONF_VSWITCH_ON_CMD_LIST,
|
|
CONF_VSWITCH_OFF_CMD_LIST,
|
|
PROPORTIONAL_FUNCTION_TPI,
|
|
overrides,
|
|
)
|
|
|
|
from .commons import write_event_log
|
|
|
|
from .base_thermostat import BaseThermostat, ConfigData
|
|
from .thermostat_prop import ThermostatProp
|
|
from .underlyings import UnderlyingSwitch
|
|
from .cycle_scheduler import CycleScheduler
|
|
|
|
_LOGGER = get_vtherm_logger(__name__)
|
|
|
|
class ThermostatOverSwitch(ThermostatProp[UnderlyingSwitch]):
|
|
"""Representation of a base class for a Versatile Thermostat over a switch."""
|
|
|
|
_entity_component_unrecorded_attributes = BaseThermostat._entity_component_unrecorded_attributes.union( # pylint: disable=protected-access
|
|
frozenset(
|
|
{
|
|
"is_over_switch",
|
|
"vtherm_over_switch",
|
|
}
|
|
)
|
|
)
|
|
|
|
def __init__(self, hass: HomeAssistant, unique_id, name, config_entry) -> None:
|
|
"""Initialize the thermostat over switch."""
|
|
self._is_inversed: bool | None = None
|
|
self._lst_vswitch_on: list[str] = []
|
|
self._lst_vswitch_off: list[str] = []
|
|
super().__init__(hass, unique_id, name, config_entry)
|
|
|
|
@property
|
|
def is_over_switch(self) -> bool:
|
|
"""True if the Thermostat is over_switch"""
|
|
return True
|
|
|
|
@property
|
|
def is_inversed(self) -> bool:
|
|
"""True if the switch is inversed (for pilot wire and diode)"""
|
|
return self._is_inversed is True
|
|
|
|
@overrides
|
|
def post_init(self, config_entry: ConfigData):
|
|
"""Initialize the Thermostat"""
|
|
|
|
super().post_init(config_entry)
|
|
|
|
self._is_inversed = config_entry.get(CONF_INVERSE_SWITCH) is True
|
|
|
|
lst_switches = config_entry.get(CONF_UNDERLYING_LIST)
|
|
self._lst_vswitch_on = config_entry.get(CONF_VSWITCH_ON_CMD_LIST, [])
|
|
self._lst_vswitch_off = config_entry.get(CONF_VSWITCH_OFF_CMD_LIST, [])
|
|
|
|
for idx, switch in enumerate(lst_switches):
|
|
vswitch_on = self._lst_vswitch_on[idx] if idx < len(self._lst_vswitch_on) else None
|
|
vswitch_off = self._lst_vswitch_off[idx] if idx < len(self._lst_vswitch_off) else None
|
|
self._underlyings.append(
|
|
UnderlyingSwitch(
|
|
hass=self._hass,
|
|
thermostat=self,
|
|
switch_entity_id=switch,
|
|
keep_alive_sec=config_entry.get(CONF_HEATER_KEEP_ALIVE, 0),
|
|
vswitch_on=vswitch_on,
|
|
vswitch_off=vswitch_off,
|
|
)
|
|
)
|
|
|
|
self._bind_scheduler(CycleScheduler(
|
|
hass=self._hass,
|
|
thermostat=self,
|
|
underlyings=self._underlyings,
|
|
cycle_duration_sec=self._cycle_min * 60,
|
|
min_activation_delay=self.minimal_activation_delay,
|
|
min_deactivation_delay=self.minimal_deactivation_delay,
|
|
))
|
|
|
|
self._should_relaunch_control_heating = False
|
|
|
|
@overrides
|
|
async def async_added_to_hass(self):
|
|
"""Run when entity about to be added."""
|
|
|
|
await super().async_added_to_hass()
|
|
|
|
# Add listener to all underlying entities
|
|
for switch in self._underlyings:
|
|
self.async_on_remove(async_track_state_change_event(self.hass, [switch.entity_id], self._async_switch_changed))
|
|
|
|
# Start the control_heating
|
|
# starts a cycle
|
|
self.async_on_remove(
|
|
async_track_time_interval(
|
|
self.hass,
|
|
self.async_control_heating,
|
|
interval=timedelta(minutes=self._cycle_min),
|
|
)
|
|
)
|
|
|
|
@overrides
|
|
def update_custom_attributes(self):
|
|
"""Custom attributes"""
|
|
super().update_custom_attributes()
|
|
|
|
under0: UnderlyingSwitch = self._underlyings[0]
|
|
|
|
# Standard attributes
|
|
attributes = {
|
|
"is_over_switch": self.is_over_switch,
|
|
"on_percent": self.safe_on_percent,
|
|
"power_percent": self.power_percent,
|
|
}
|
|
|
|
# Use pre-calculated on/off times from handler's control_heating
|
|
on_time_sec = self._on_time_sec
|
|
off_time_sec = self._off_time_sec
|
|
|
|
# Underlying specific attributes
|
|
vtherm_over_switch_attr = {
|
|
"is_inversed": self.is_inversed,
|
|
"keep_alive_sec": under0.keep_alive_sec,
|
|
"underlying_entities": [underlying.entity_id for underlying in self._underlyings],
|
|
"on_percent": self.safe_on_percent,
|
|
"power_percent": self.power_percent,
|
|
"on_time_sec": on_time_sec,
|
|
"off_time_sec": off_time_sec,
|
|
"function": self._proportional_function,
|
|
"vswitch_on_commands": self._lst_vswitch_on,
|
|
"vswitch_off_commands": self._lst_vswitch_off,
|
|
}
|
|
|
|
# Add TPI attributes if active
|
|
if self._proportional_function == PROPORTIONAL_FUNCTION_TPI:
|
|
vtherm_over_switch_attr.update({
|
|
"tpi_coef_int": self._tpi_coef_int,
|
|
"tpi_coef_ext": self._tpi_coef_ext,
|
|
"tpi_threshold_low": self._tpi_threshold_low,
|
|
"tpi_threshold_high": self._tpi_threshold_high,
|
|
"minimal_activation_delay": self._minimal_activation_delay,
|
|
"minimal_deactivation_delay": self._minimal_deactivation_delay,
|
|
})
|
|
|
|
attributes["vtherm_over_switch"] = vtherm_over_switch_attr
|
|
self._attr_extra_state_attributes.update(attributes)
|
|
|
|
# _LOGGER.debug("%s - Calling update_custom_attributes: %s", self, self._attr_extra_state_attributes)
|
|
|
|
@overrides
|
|
def incremente_energy(self):
|
|
"""increment the energy counter if device is active"""
|
|
if self.vtherm_hvac_mode == VThermHvacMode_OFF:
|
|
return
|
|
|
|
added_energy = 0
|
|
if self.power_manager.mean_cycle_power is not None:
|
|
# each underlying entity calculate its own energy. So we should divide by the number of underlying entities
|
|
# see #877
|
|
added_energy = self.power_manager.mean_cycle_power * float(self._cycle_min) / 60.0 / self.nb_underlying_entities
|
|
|
|
if self._total_energy is None:
|
|
self._total_energy = added_energy
|
|
_LOGGER.debug(
|
|
"%s - incremente_energy set energy is %s",
|
|
self,
|
|
self._total_energy,
|
|
)
|
|
else:
|
|
self._total_energy += added_energy
|
|
_LOGGER.debug(
|
|
"%s - incremente_energy increment energy is %s",
|
|
self,
|
|
self._total_energy,
|
|
)
|
|
|
|
self.update_custom_attributes()
|
|
self.async_write_ha_state()
|
|
|
|
_LOGGER.debug(
|
|
"%s - added energy is %.3f . Total energy is now: %.3f",
|
|
self,
|
|
added_energy,
|
|
self._total_energy,
|
|
)
|
|
|
|
@callback
|
|
def _async_switch_changed(self, event: Event[EventStateChangedData]):
|
|
"""Handle heater switch state changes."""
|
|
new_state = event.data.get("new_state")
|
|
old_state = event.data.get("old_state")
|
|
|
|
write_event_log(_LOGGER, self, f"Underlying switch state changed from {old_state.state if old_state else None} to {new_state.state if new_state else None}")
|
|
if new_state is None:
|
|
return
|
|
# #1654 - nno more needed now
|
|
# if old_state is None:
|
|
# self.hass.create_task(self._check_initial_state())
|
|
|
|
self.calculate_hvac_action()
|
|
self.update_custom_attributes()
|
|
self.async_write_ha_state()
|
|
|
|
@property
|
|
def vtherm_type(self) -> str | None:
|
|
"""Return the type of thermostat"""
|
|
return "over_switch"
|