Missing translations for preset mode Power and Security #46
Set preset internal #45 Notify (send event messages) when something important happens #43 Enhancing Security mode #42 Rename None preset to Manual #3
This commit is contained in:
@@ -20,7 +20,6 @@ from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.util.unit_system import UnitOfTemperature
|
||||
|
||||
from homeassistant.helpers.event import (
|
||||
async_track_state_change_event,
|
||||
@@ -118,6 +117,9 @@ from .const import (
|
||||
PRESET_AWAY_SUFFIX,
|
||||
CONF_SECURITY_DELAY_MIN,
|
||||
CONF_SECURITY_MIN_ON_PERCENT,
|
||||
CONF_SECURITY_DEFAULT_ON_PERCENT,
|
||||
DEFAULT_SECURITY_MIN_ON_PERCENT,
|
||||
DEFAULT_SECURITY_DEFAULT_ON_PERCENT,
|
||||
CONF_MINIMAL_ACTIVATION_DELAY,
|
||||
CONF_TEMP_MAX,
|
||||
CONF_TEMP_MIN,
|
||||
@@ -127,6 +129,7 @@ from .const import (
|
||||
CONF_THERMOSTAT_CLIMATE,
|
||||
CONF_CLIMATE,
|
||||
UnknownEntity,
|
||||
EventType,
|
||||
)
|
||||
|
||||
from .prop_algorithm import PropAlgorithm
|
||||
@@ -183,7 +186,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
# No more needed
|
||||
# _registry: dict[str, object] = {}
|
||||
|
||||
def __init__(self, hass, unique_id, name, entry_infos) -> None:
|
||||
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
|
||||
"""Initialize the thermostat."""
|
||||
|
||||
super().__init__()
|
||||
@@ -218,9 +221,10 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
self._presence_state = None
|
||||
self._overpowering_state = None
|
||||
self._should_relaunch_control_heating = None
|
||||
self._security_delay_min = None
|
||||
self._security_min_on_percent= None
|
||||
|
||||
self._security_delay_min = None
|
||||
self._security_min_on_percent = None
|
||||
self._security_default_on_percent = None
|
||||
self._security_state = None
|
||||
|
||||
self._thermostat_type = None
|
||||
@@ -243,7 +247,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
)
|
||||
# convert entry_infos into usable attributes
|
||||
presets = {}
|
||||
for (key, value) in CONF_PRESETS.items():
|
||||
for key, value in CONF_PRESETS.items():
|
||||
_LOGGER.debug("looking for key=%s, value=%s", key, value)
|
||||
if value in entry_infos:
|
||||
presets[key] = entry_infos.get(value)
|
||||
@@ -251,7 +255,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
_LOGGER.debug("value %s not found in Entry", value)
|
||||
|
||||
presets_away = {}
|
||||
for (key, value) in CONF_PRESETS_AWAY.items():
|
||||
for key, value in CONF_PRESETS_AWAY.items():
|
||||
_LOGGER.debug("looking for key=%s, value=%s", key, value)
|
||||
if value in entry_infos:
|
||||
presets_away[key] = entry_infos.get(value)
|
||||
@@ -367,7 +371,14 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
self._tpi_coef_ext = 0
|
||||
|
||||
self._security_delay_min = entry_infos.get(CONF_SECURITY_DELAY_MIN)
|
||||
self._security_min_on_percent = entry_infos.get(CONF_SECURITY_MIN_ON_PERCENT)
|
||||
self._security_min_on_percent = (
|
||||
entry_infos.get(CONF_SECURITY_MIN_ON_PERCENT)
|
||||
or DEFAULT_SECURITY_MIN_ON_PERCENT
|
||||
)
|
||||
self._security_default_on_percent = (
|
||||
entry_infos.get(CONF_SECURITY_DEFAULT_ON_PERCENT)
|
||||
or DEFAULT_SECURITY_DEFAULT_ON_PERCENT
|
||||
)
|
||||
self._minimal_activation_delay = entry_infos.get(CONF_MINIMAL_ACTIVATION_DELAY)
|
||||
self._last_temperature_mesure = datetime.now()
|
||||
self._last_ext_temperature_mesure = datetime.now()
|
||||
@@ -732,7 +743,12 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
float(old_state.attributes[ATTR_TEMPERATURE])
|
||||
)
|
||||
|
||||
if old_state.attributes.get(ATTR_PRESET_MODE) in self._attr_preset_modes:
|
||||
old_preset_mode = old_state.attributes.get(ATTR_PRESET_MODE)
|
||||
# Never restore a Power or Security preset
|
||||
if (
|
||||
old_preset_mode in self._attr_preset_modes
|
||||
and old_preset_mode not in HIDDEN_PRESETS
|
||||
):
|
||||
self._attr_preset_mode = old_state.attributes.get(ATTR_PRESET_MODE)
|
||||
self.save_preset_mode()
|
||||
|
||||
@@ -756,6 +772,9 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
if not self._hvac_mode:
|
||||
self._hvac_mode = HVACMode.OFF
|
||||
|
||||
self.send_event(EventType.PRESET_EVENT, {"preset": self._attr_preset_mode})
|
||||
self.send_event(EventType.HVAC_MODE_EVENT, {"hvac_mode": self._hvac_mode})
|
||||
|
||||
_LOGGER.info(
|
||||
"%s - restored state is target_temp=%.1f, preset_mode=%s, hvac_mode=%s",
|
||||
self,
|
||||
@@ -1011,12 +1030,11 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
_LOGGER.error("Unrecognized hvac mode: %s", hvac_mode)
|
||||
return
|
||||
# Ensure we update the current operation after changing the mode
|
||||
self._last_temperature_mesure = (
|
||||
self._last_ext_temperature_mesure
|
||||
) = datetime.now()
|
||||
self.reset_last_temperature_time()
|
||||
|
||||
self.update_custom_attributes()
|
||||
self.async_write_ha_state()
|
||||
self.send_event(EventType.HVAC_MODE_EVENT, {"hvac_mode": self._hvac_mode})
|
||||
|
||||
async def async_set_preset_mode(self, preset_mode):
|
||||
"""Set new preset mode."""
|
||||
@@ -1052,11 +1070,18 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
self.find_preset_temp(preset_mode)
|
||||
)
|
||||
|
||||
self._last_temperature_mesure = (
|
||||
self._last_ext_temperature_mesure
|
||||
) = datetime.now()
|
||||
self.reset_last_temperature_time()
|
||||
|
||||
self.save_preset_mode()
|
||||
self.recalculate()
|
||||
self.send_event(EventType.PRESET_EVENT, {"preset": self._attr_preset_mode})
|
||||
|
||||
def reset_last_temperature_time(self):
|
||||
"""Reset to now the last temperature time if conditions are satisfied"""
|
||||
if self._attr_preset_mode not in HIDDEN_PRESETS:
|
||||
self._last_temperature_mesure = (
|
||||
self._last_ext_temperature_mesure
|
||||
) = datetime.now()
|
||||
|
||||
def find_preset_temp(self, preset_mode):
|
||||
"""Find the right temperature of a preset considering the presence if configured"""
|
||||
@@ -1623,6 +1648,15 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
self.save_preset_mode()
|
||||
await self._async_underlying_entity_turn_off()
|
||||
await self._async_set_preset_mode_internal(PRESET_POWER)
|
||||
self.send_event(
|
||||
EventType.POWER_EVENT,
|
||||
{
|
||||
"type": "start",
|
||||
"current_power": self._current_power,
|
||||
"device_power": self._device_power,
|
||||
"current_power_max": self._current_power_max,
|
||||
},
|
||||
)
|
||||
|
||||
# Check if we need to remove the POWER preset
|
||||
if (
|
||||
@@ -1638,6 +1672,15 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
if self._is_over_climate:
|
||||
await self.restore_hvac_mode()
|
||||
await self.restore_preset_mode()
|
||||
self.send_event(
|
||||
EventType.POWER_EVENT,
|
||||
{
|
||||
"type": "end",
|
||||
"current_power": self._current_power,
|
||||
"device_power": self._device_power,
|
||||
"current_power_max": self._current_power_max,
|
||||
},
|
||||
)
|
||||
|
||||
self._overpowering_state = ret
|
||||
return self._overpowering_state
|
||||
@@ -1649,12 +1692,6 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
delta_ext_temp = (
|
||||
now - self._last_ext_temperature_mesure
|
||||
).total_seconds() / 60.0
|
||||
_LOGGER.debug(
|
||||
"%s - checking security delta_temp=%.1f delta_ext_temp=%.1f",
|
||||
self,
|
||||
delta_temp,
|
||||
delta_ext_temp,
|
||||
)
|
||||
|
||||
temp_cond: bool = (
|
||||
delta_temp > self._security_delay_min
|
||||
@@ -1667,7 +1704,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
switch_cond: bool = (
|
||||
not self._is_over_climate
|
||||
and self._prop_algorithm is not None
|
||||
and self._prop_algorithm.on_percent > self._security_min_on_percent
|
||||
and self._prop_algorithm.calculated_on_percent
|
||||
> self._security_min_on_percent
|
||||
)
|
||||
|
||||
ret = False
|
||||
@@ -1683,6 +1721,16 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
)
|
||||
ret = True
|
||||
|
||||
_LOGGER.debug(
|
||||
"%s - checking security delta_temp=%.1f delta_ext_temp=%.1f temp_cond=%s climate_cond=%s switch_cond=%s",
|
||||
self,
|
||||
delta_temp,
|
||||
delta_ext_temp,
|
||||
temp_cond,
|
||||
climate_cond,
|
||||
switch_cond,
|
||||
)
|
||||
|
||||
if temp_cond and switch_cond:
|
||||
if not self._security_state:
|
||||
_LOGGER.warning(
|
||||
@@ -1696,12 +1744,40 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
)
|
||||
ret = True
|
||||
|
||||
if not self._security_state and temp_cond:
|
||||
self.send_event(
|
||||
EventType.TEMPERATURE_EVENT,
|
||||
{
|
||||
"last_temperature_mesure": self._last_temperature_mesure.isoformat(),
|
||||
"last_ext_temperature_mesure": self._last_ext_temperature_mesure.isoformat(),
|
||||
"current_temp": self._cur_temp,
|
||||
"current_ext_temp": self._cur_ext_temp,
|
||||
"target_temp": self.target_temperature,
|
||||
},
|
||||
)
|
||||
|
||||
if not self._security_state and ret:
|
||||
self._security_state = ret
|
||||
self.save_hvac_mode()
|
||||
self.save_preset_mode()
|
||||
await self._async_set_preset_mode_internal(PRESET_SECURITY)
|
||||
await self.async_set_hvac_mode(HVACMode.OFF)
|
||||
# Turn off the underlying climate or heater if security default on_percent is 0
|
||||
if self._is_over_climate or self._security_default_on_percent <= 0.0:
|
||||
await self.async_set_hvac_mode(HVACMode.OFF)
|
||||
if self._prop_algorithm:
|
||||
self._prop_algorithm.set_security(self._security_default_on_percent)
|
||||
|
||||
self.send_event(
|
||||
EventType.SECURITY_EVENT,
|
||||
{
|
||||
"type": "start",
|
||||
"last_temperature_mesure": self._last_temperature_mesure.isoformat(),
|
||||
"last_ext_temperature_mesure": self._last_ext_temperature_mesure.isoformat(),
|
||||
"current_temp": self._cur_temp,
|
||||
"current_ext_temp": self._cur_ext_temp,
|
||||
"target_temp": self.target_temperature,
|
||||
},
|
||||
)
|
||||
|
||||
if (
|
||||
self._security_state
|
||||
@@ -1715,15 +1791,30 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
self._saved_preset_mode,
|
||||
)
|
||||
self._security_state = ret
|
||||
await self.restore_hvac_mode()
|
||||
# Restore hvac_mode if previously saved
|
||||
if self._is_over_climate or self._security_default_on_percent <= 0.0:
|
||||
await self.restore_hvac_mode()
|
||||
await self.restore_preset_mode()
|
||||
if self._prop_algorithm:
|
||||
self._prop_algorithm.unset_security()
|
||||
self.send_event(
|
||||
EventType.SECURITY_EVENT,
|
||||
{
|
||||
"type": "end",
|
||||
"last_temperature_mesure": self._last_temperature_mesure.isoformat(),
|
||||
"last_ext_temperature_mesure": self._last_ext_temperature_mesure.isoformat(),
|
||||
"current_temp": self._cur_temp,
|
||||
"current_ext_temp": self._cur_ext_temp,
|
||||
"target_temp": self.target_temperature,
|
||||
},
|
||||
)
|
||||
|
||||
return ret
|
||||
|
||||
async def _async_control_heating(self, force=False, _=None):
|
||||
"""The main function used to run the calculation at each cycle"""
|
||||
|
||||
_LOGGER.info(
|
||||
_LOGGER.debug(
|
||||
"%s - Checking new cycle. hvac_mode=%s, security_state=%s, preset_mode=%s",
|
||||
self,
|
||||
self._hvac_mode,
|
||||
@@ -1738,8 +1829,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
return
|
||||
|
||||
security: bool = await self.check_security()
|
||||
if security:
|
||||
_LOGGER.debug("%s - End of cycle (security)", self)
|
||||
if security and self._is_over_climate:
|
||||
_LOGGER.debug("%s - End of cycle (security and over climate)", self)
|
||||
return
|
||||
|
||||
# Stop here if we are off
|
||||
@@ -1752,7 +1843,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
if not self._is_over_climate:
|
||||
on_time_sec: int = self._prop_algorithm.on_time_sec
|
||||
off_time_sec: int = self._prop_algorithm.off_time_sec
|
||||
_LOGGER.info(
|
||||
|
||||
_LOGGER.debug(
|
||||
"%s - Checking new cycle. on_time_sec=%.0f, off_time_sec=%.0f, security_state=%s, preset_mode=%s",
|
||||
self,
|
||||
on_time_sec,
|
||||
@@ -1793,13 +1885,12 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
return
|
||||
|
||||
if on:
|
||||
security = (
|
||||
await self.check_security()
|
||||
or await self.check_overpowering()
|
||||
)
|
||||
if security:
|
||||
if await self.check_overpowering():
|
||||
_LOGGER.debug("%s - End of cycle (3)", self)
|
||||
return
|
||||
# Security mode could have change the on_time percent
|
||||
await self.check_security()
|
||||
time = self._prop_algorithm.on_time_sec
|
||||
|
||||
action_label = "start" if on else "stop"
|
||||
if self._should_relaunch_control_heating:
|
||||
@@ -1814,7 +1905,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
|
||||
if time > 0:
|
||||
_LOGGER.info(
|
||||
"%s - !!! %s heating for %d min %d sec",
|
||||
"%s - %s heating for %d min %d sec",
|
||||
self,
|
||||
action_label,
|
||||
time // 60,
|
||||
@@ -1910,6 +2001,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
"presence_state": self._presence_state,
|
||||
"security_delay_min": self._security_delay_min,
|
||||
"security_min_on_percent": self._security_min_on_percent,
|
||||
"security_default_on_percent": self._security_default_on_percent,
|
||||
"last_temperature_datetime": self._last_temperature_mesure.isoformat(),
|
||||
"last_ext_temperature_datetime": self._last_ext_temperature_mesure.isoformat(),
|
||||
"security_state": self._security_state,
|
||||
@@ -1999,3 +2091,11 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||
if self._attr_preset_mode == preset:
|
||||
await self._async_set_preset_mode_internal(preset, force=True)
|
||||
await self._async_control_heating(force=True)
|
||||
|
||||
def send_event(self, event_type: EventType, data: dict):
|
||||
"""Send an event"""
|
||||
_LOGGER.info("%s - Sending event %s with data: %s", self, event_type, data)
|
||||
data["entity_id"] = self.entity_id
|
||||
data["name"] = self.name
|
||||
data["state_attributes"] = self.state_attributes
|
||||
self._hass.bus.fire(event_type.value, data)
|
||||
|
||||
@@ -64,6 +64,9 @@ from .const import (
|
||||
PROPORTIONAL_FUNCTION_TPI,
|
||||
CONF_SECURITY_DELAY_MIN,
|
||||
CONF_SECURITY_MIN_ON_PERCENT,
|
||||
CONF_SECURITY_DEFAULT_ON_PERCENT,
|
||||
DEFAULT_SECURITY_MIN_ON_PERCENT,
|
||||
DEFAULT_SECURITY_DEFAULT_ON_PERCENT,
|
||||
CONF_MINIMAL_ACTIVATION_DELAY,
|
||||
CONF_TEMP_MAX,
|
||||
CONF_TEMP_MIN,
|
||||
@@ -177,53 +180,16 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
||||
is_empty or self._infos.get(CONF_PRESENCE_SENSOR) is not None
|
||||
)
|
||||
|
||||
# self.hass = async_get_hass()
|
||||
# ent_reg = async_get(hass=self.hass)
|
||||
|
||||
# climates = []
|
||||
# switches = []
|
||||
# temp_sensors = []
|
||||
# power_sensors = []
|
||||
# window_sensors = []
|
||||
# presence_sensors = []
|
||||
#
|
||||
# k: str
|
||||
# for k in ent_reg.entities:
|
||||
# v: RegistryEntry = ent_reg.entities[k]
|
||||
# _LOGGER.debug("Looking entity: %s", k)
|
||||
# # if k.startswith(CLIMATE_DOMAIN) and (
|
||||
# # infos is None or k != infos.get("entity_id")
|
||||
# # ):
|
||||
# # _LOGGER.debug("Climate !")
|
||||
# # climates.append(k)
|
||||
# if k.startswith(SWITCH_DOMAIN) or k.startswith(INPUT_BOOLEAN_DOMAIN):
|
||||
# _LOGGER.debug("Switch !")
|
||||
# switches.append(k)
|
||||
# elif is_temperature_sensor(v):
|
||||
# _LOGGER.debug("Temperature sensor !")
|
||||
# temp_sensors.append(k)
|
||||
# elif is_power_sensor(v):
|
||||
# _LOGGER.debug("Power sensor !")
|
||||
# power_sensors.append(k)
|
||||
# elif k.startswith(PERSON_DOMAIN):
|
||||
# _LOGGER.debug("Presence sensor !")
|
||||
# presence_sensors.append(k)
|
||||
#
|
||||
# # window sensor and presence
|
||||
# if k.startswith(INPUT_BOOLEAN_DOMAIN) or k.startswith(BINARY_SENSOR_DOMAIN):
|
||||
# _LOGGER.debug("Window or presence sensor !")
|
||||
# window_sensors.append(k)
|
||||
# presence_sensors.append(k)
|
||||
#
|
||||
# # Special case for climates which are not in EntityRegistry
|
||||
# climates = self.find_all_climates()
|
||||
|
||||
self.STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
vol.Required(
|
||||
CONF_THERMOSTAT_TYPE, default=CONF_THERMOSTAT_SWITCH
|
||||
): vol.In(CONF_THERMOSTAT_TYPES),
|
||||
): selector.SelectSelector(
|
||||
selector.SelectSelectorConfig(
|
||||
options=CONF_THERMOSTAT_TYPES, translation_key="thermostat_type"
|
||||
)
|
||||
),
|
||||
vol.Required(CONF_TEMP_SENSOR): selector.EntitySelector(
|
||||
selector.EntitySelectorConfig(
|
||||
domain=[SENSOR_DOMAIN, INPUT_NUMBER_DOMAIN]
|
||||
@@ -350,9 +316,18 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
||||
|
||||
self.STEP_ADVANCED_DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_MINIMAL_ACTIVATION_DELAY, default=10): cv.positive_int,
|
||||
vol.Required(
|
||||
CONF_MINIMAL_ACTIVATION_DELAY, default=10
|
||||
): cv.positive_int,
|
||||
vol.Required(CONF_SECURITY_DELAY_MIN, default=60): cv.positive_int,
|
||||
vol.Required(CONF_SECURITY_MIN_ON_PERCENT, default=0.75): vol.Coerce(float),
|
||||
vol.Required(
|
||||
CONF_SECURITY_MIN_ON_PERCENT,
|
||||
default=DEFAULT_SECURITY_MIN_ON_PERCENT,
|
||||
): vol.Coerce(float),
|
||||
vol.Required(
|
||||
CONF_SECURITY_DEFAULT_ON_PERCENT,
|
||||
default=DEFAULT_SECURITY_DEFAULT_ON_PERCENT,
|
||||
): vol.Coerce(float),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Constants for the Versatile Thermostat integration."""
|
||||
|
||||
from enum import Enum
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.components.climate.const import (
|
||||
# PRESET_ACTIVITY,
|
||||
@@ -45,6 +46,7 @@ CONF_TEMP_MIN = "temp_min"
|
||||
CONF_TEMP_MAX = "temp_max"
|
||||
CONF_SECURITY_DELAY_MIN = "security_delay_min"
|
||||
CONF_SECURITY_MIN_ON_PERCENT = "security_min_on_percent"
|
||||
CONF_SECURITY_DEFAULT_ON_PERCENT = "security_default_on_percent"
|
||||
CONF_THERMOSTAT_TYPE = "thermostat_type"
|
||||
CONF_THERMOSTAT_SWITCH = "thermostat_over_switch"
|
||||
CONF_THERMOSTAT_CLIMATE = "thermostat_over_climate"
|
||||
@@ -104,6 +106,7 @@ ALL_CONF = (
|
||||
CONF_TEMP_MAX,
|
||||
CONF_SECURITY_DELAY_MIN,
|
||||
CONF_SECURITY_MIN_ON_PERCENT,
|
||||
CONF_SECURITY_DEFAULT_ON_PERCENT,
|
||||
CONF_THERMOSTAT_TYPE,
|
||||
CONF_THERMOSTAT_SWITCH,
|
||||
CONF_THERMOSTAT_CLIMATE,
|
||||
@@ -128,6 +131,17 @@ SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
|
||||
SERVICE_SET_PRESENCE = "set_presence"
|
||||
SERVICE_SET_PRESET_TEMPERATURE = "set_preset_temperature"
|
||||
|
||||
DEFAULT_SECURITY_MIN_ON_PERCENT = 0.5
|
||||
DEFAULT_SECURITY_DEFAULT_ON_PERCENT = 0.1
|
||||
|
||||
|
||||
class EventType(Enum):
|
||||
SECURITY_EVENT: str = "versatile_thermostat_security_event"
|
||||
POWER_EVENT: str = "versatile_thermostat_power_event"
|
||||
TEMPERATURE_EVENT: str = "versatile_thermostat_temperature_event"
|
||||
HVAC_MODE_EVENT: str = "versatile_thermostat_hvac_mode_event"
|
||||
PRESET_EVENT: str = "versatile_thermostat_preset_event"
|
||||
|
||||
|
||||
class UnknownEntity(HomeAssistantError):
|
||||
"""Error to indicate there is an unknown entity_id given."""
|
||||
|
||||
@@ -37,8 +37,11 @@ class PropAlgorithm:
|
||||
self._cycle_min = cycle_min
|
||||
self._minimal_activation_delay = minimal_activation_delay
|
||||
self._on_percent = 0
|
||||
self._calculated_on_percent = 0
|
||||
self._on_time_sec = 0
|
||||
self._off_time_sec = self._cycle_min * 60
|
||||
self._security = False
|
||||
self._default_on_percent = 0
|
||||
|
||||
def calculate(
|
||||
self, target_temp: float, current_temp: float, ext_current_temp: float
|
||||
@@ -48,7 +51,7 @@ class PropAlgorithm:
|
||||
_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
|
||||
)
|
||||
self._on_percent = 0
|
||||
self._calculated_on_percent = 0
|
||||
else:
|
||||
delta_temp = target_temp - current_temp
|
||||
delta_ext_temp = (
|
||||
@@ -56,7 +59,7 @@ class PropAlgorithm:
|
||||
)
|
||||
|
||||
if self._function == PROPORTIONAL_FUNCTION_TPI:
|
||||
self._on_percent = (
|
||||
self._calculated_on_percent = (
|
||||
self._tpi_coef_int * delta_temp
|
||||
+ self._tpi_coef_ext * delta_ext_temp
|
||||
)
|
||||
@@ -65,7 +68,35 @@ class PropAlgorithm:
|
||||
"Proportional algorithm: unknown %s function. Heating will be disabled",
|
||||
self._function,
|
||||
)
|
||||
self._on_percent = 0
|
||||
self._calculated_on_percent = 0
|
||||
|
||||
self._calculate_internal()
|
||||
|
||||
_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,
|
||||
self._calculated_on_percent,
|
||||
self.on_time_sec,
|
||||
self.off_time_sec,
|
||||
)
|
||||
|
||||
def _calculate_internal(self):
|
||||
"""Finish the calculation to get the on_percent in seconds"""
|
||||
|
||||
if self._security:
|
||||
_LOGGER.debug(
|
||||
"Security is On using the default_on_percent %f",
|
||||
self._default_on_percent,
|
||||
)
|
||||
self._on_percent = self._default_on_percent
|
||||
else:
|
||||
_LOGGER.debug(
|
||||
"Security is Off using the calculated_on_percent %f",
|
||||
self._calculated_on_percent,
|
||||
)
|
||||
self._on_percent = self._calculated_on_percent
|
||||
|
||||
# calculated on_time duration in seconds
|
||||
if self._on_percent > 1:
|
||||
@@ -92,21 +123,31 @@ class PropAlgorithm:
|
||||
|
||||
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,
|
||||
self._on_percent,
|
||||
self.on_time_sec,
|
||||
self.off_time_sec,
|
||||
)
|
||||
def set_security(self, default_on_percent: float):
|
||||
"""Set a default value for on_percent (used for security mode)"""
|
||||
self._security = True
|
||||
self._default_on_percent = default_on_percent
|
||||
self._calculate_internal()
|
||||
|
||||
def unset_security(self):
|
||||
"""Unset the security mode"""
|
||||
self._security = False
|
||||
self._calculate_internal()
|
||||
|
||||
@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
|
||||
"""Returns the percentage the heater must be ON
|
||||
In security mode this value is overriden with the _default_on_percent
|
||||
(1 means the heater will be always on, 0 never on)""" # pylint: disable=line-too-long
|
||||
return round(self._on_percent, 2)
|
||||
|
||||
@property
|
||||
def calculated_on_percent(self) -> float:
|
||||
"""Returns the calculated percentage the heater must be ON
|
||||
Calculated means NOT overriden even in security mode
|
||||
(1 means the heater will be always on, 0 never on)""" # pylint: disable=line-too-long
|
||||
return round(self._calculated_on_percent, 2)
|
||||
|
||||
@property
|
||||
def on_time_sec(self) -> int:
|
||||
"""Returns the calculated time in sec the heater must be ON"""
|
||||
|
||||
@@ -90,7 +90,8 @@
|
||||
"data": {
|
||||
"minimal_activation_delay": "Delay in secondes under which the equipment will not be activated",
|
||||
"security_delay_min": "Maximum allowed delay in minutes between two temperature mesures. Above this delay, the thermostat will turn to a sceurity off state",
|
||||
"security_min_on_percent": "Minimal On-Percent value for security preset activation. Below this amount of on_percent the thermostat won't go into security preset"
|
||||
"security_min_on_percent": "Minimal heating percent value for security preset activation. Below this amount of on_percent the thermostat won't go into security preset",
|
||||
"security_default_on_percent": "The default heating percent value in security preset. Set to 0 to switch off heater in security present"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -192,7 +193,8 @@
|
||||
"data": {
|
||||
"minimal_activation_delay": "Delay in secondes under which the equipment will not be activated",
|
||||
"security_delay_min": "Maximum allowed delay in minutes between two temperature mesures. Above this delay, the thermostat will turn to a security off state",
|
||||
"security_min_on_percent": "Minimal On-Percent value for security preset activation. Below this amount of on_percent the thermostat won't go into security preset"
|
||||
"security_min_on_percent": "Minimal heating percent value for security preset activation. Below this amount of on_percent the thermostat won't go into security preset",
|
||||
"security_default_on_percent": "The default heating percent value in security preset. Set to 0 to switch off heater in security present"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -215,10 +217,13 @@
|
||||
"entity": {
|
||||
"climate": {
|
||||
"versatile_thermostat": {
|
||||
"states_attributes": {
|
||||
"state_attributes": {
|
||||
"preset_mode": {
|
||||
"power": "Shedding",
|
||||
"security": "Security"
|
||||
"state": {
|
||||
"power": "Shedding",
|
||||
"security": "Security",
|
||||
"none": "Manual"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,8 @@
|
||||
"data": {
|
||||
"minimal_activation_delay": "Delay in secondes under which the equipment will not be activated",
|
||||
"security_delay_min": "Maximum allowed delay in minutes between two temperature mesures. Above this delay, the thermostat will turn to a sceurity off state",
|
||||
"security_min_on_percent": "Minimal On-Percent value for security preset activation. Below this amount of on_percent the thermostat won't go into security preset"
|
||||
"security_min_on_percent": "Minimal heating percent value for security preset activation. Below this amount of on_percent the thermostat won't go into security preset",
|
||||
"security_default_on_percent": "The default heating percent value in security preset. Set to 0 to switch off heater in security present"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -192,7 +193,8 @@
|
||||
"data": {
|
||||
"minimal_activation_delay": "Delay in secondes under which the equipment will not be activated",
|
||||
"security_delay_min": "Maximum allowed delay in minutes between two temperature mesures. Above this delay, the thermostat will turn to a sceurity off state",
|
||||
"security_min_on_percent": "Minimal On-Percent value for security preset activation. Below this amount of on_percent the thermostat won't go into security preset"
|
||||
"security_min_on_percent": "Minimal heating percent value for security preset activation. Below this amount of on_percent the thermostat won't go into security preset",
|
||||
"security_default_on_percent": "The default heating percent value in security preset. Set to 0 to switch off heater in security present"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -215,10 +217,13 @@
|
||||
"entity": {
|
||||
"climate": {
|
||||
"versatile_thermostat": {
|
||||
"states_attributes": {
|
||||
"state_attributes": {
|
||||
"preset_mode": {
|
||||
"power": "Shedding",
|
||||
"security": "Security"
|
||||
"state": {
|
||||
"power": "Shedding",
|
||||
"security": "Security",
|
||||
"none": "Manual"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,8 @@
|
||||
"data": {
|
||||
"minimal_activation_delay": "Délai en secondes en-dessous duquel l'équipement ne sera pas activé",
|
||||
"security_delay_min": "Délai maximal autorisé en minutes entre 2 mesures de températures. Au-dessus de ce délai, le thermostat se mettra en position éteinte de sécurité",
|
||||
"security_min_on_percent": "Seuil de chauffage en-dessous duquel le préréglage sécurité ne sera jamais activé."
|
||||
"security_min_on_percent": "Seuil minimal de pourcentage de chauffage en-dessous duquel le préréglage sécurité ne sera jamais activé",
|
||||
"security_default_on_percent": "Valeur par défaut pour le pourcentage de chauffage en mode sécurité. Mettre 0 pour éteindre le radiateur en mode sécurité"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -192,7 +193,8 @@
|
||||
"data": {
|
||||
"minimal_activation_delay": "Délai en seondes en-dessous duquel l'équipement ne sera pas activé",
|
||||
"security_delay_min": "Délai maximal autorisé en minutes entre 2 mesures de températures. Au-dessus de ce délai, le thermostat se mettra en position éteinte de sécurité",
|
||||
"security_min_on_percent": "Seuil de chauffage en-dessous duquel le préréglage sécurité ne sera jamais activé."
|
||||
"security_min_on_percent": "Seuil minimal de pourcentage de chauffage en-dessous duquel le préréglage sécurité ne sera jamais activé",
|
||||
"security_default_on_percent": "Valeur par défaut pour le pourcentage de chauffage en mode sécurité. Mettre 0 pour éteindre le radiateur en mode sécurité"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -215,10 +217,13 @@
|
||||
"entity": {
|
||||
"climate": {
|
||||
"versatile_thermostat": {
|
||||
"states_attributes": {
|
||||
"state_attributes": {
|
||||
"preset_mode": {
|
||||
"power": "Délestage",
|
||||
"security": "Sécurité"
|
||||
"state": {
|
||||
"power": "Délestage",
|
||||
"security": "Sécurité",
|
||||
"none": "Manuel"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user