Compare commits
4 Commits
jmcollin78
...
6.6.1.beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a97a2faf2 | ||
|
|
75dd2be2b1 | ||
|
|
c2f53a8232 | ||
|
|
ae9b065387 |
@@ -1353,7 +1353,7 @@ Example of graph obtained with Plotly :
|
||||
|
||||
|
||||
## And always better and better with the NOTIFIER daemon app to notify events
|
||||
This automation uses the excellent App Daemon named NOTIFIER developed by Horizon Domotique that you will find in demonstration [here](https://www.youtube.com/watch?v=chJylIK0ASo&ab_channel=HorizonDomotique) and the code is [here](https ://github.com/jlpouffier/home-assistant-config/blob/master/appdaemon/apps/notifier.py). It allows you to notify the users of the accommodation when one of the events affecting safety occurs on one of the Versatile Thermostats.
|
||||
This automation uses the excellent App Daemon named NOTIFIER developed by Horizon Domotique that you will find in demonstration [here](https://www.youtube.com/watch?v=chJylIK0ASo&ab_channel=HorizonDomotique) and the code is [here](https://github.com/jlpouffier/home-assistant-config/blob/master/appdaemon/apps/notifier.py). It allows you to notify the users of the accommodation when one of the events affecting safety occurs on one of the Versatile Thermostats.
|
||||
|
||||
This is a great example of using the notifications described here [notification](#notifications).
|
||||
|
||||
|
||||
@@ -178,13 +178,20 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
if hass.state == CoreState.running:
|
||||
await api.reload_central_boiler_entities_list()
|
||||
await api.init_vtherm_links()
|
||||
await api.init_vtherm_links(entry.entry_id)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
"""Update listener."""
|
||||
|
||||
_LOGGER.debug(
|
||||
"Calling update_listener entry: entry_id='%s', value='%s'",
|
||||
entry.entry_id,
|
||||
entry.data,
|
||||
)
|
||||
|
||||
if entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CENTRAL_CONFIG:
|
||||
await reload_all_vtherm(hass)
|
||||
else:
|
||||
@@ -193,7 +200,7 @@ async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(hass)
|
||||
if api is not None:
|
||||
await api.reload_central_boiler_entities_list()
|
||||
await api.init_vtherm_links()
|
||||
await api.init_vtherm_links(entry.entry_id)
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
@@ -19,7 +19,10 @@ from homeassistant.core import (
|
||||
)
|
||||
|
||||
from homeassistant.components.climate import ClimateEntity
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.helpers.restore_state import (
|
||||
RestoreEntity,
|
||||
async_get as restore_async_get,
|
||||
)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
|
||||
@@ -84,6 +87,10 @@ def get_tz(hass: HomeAssistant):
|
||||
return dt_util.get_time_zone(hass.config.time_zone)
|
||||
|
||||
|
||||
_LOGGER_ENERGY = logging.getLogger(
|
||||
"custom_components.versatile_thermostat.energy_debug"
|
||||
)
|
||||
|
||||
class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
"""Representation of a base class for all Versatile Thermostat device."""
|
||||
|
||||
@@ -198,6 +205,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
self._attr_translation_key = "versatile_thermostat"
|
||||
|
||||
self._total_energy = None
|
||||
_LOGGER_ENERGY.debug("%s - _init_ resetting energy to None", self)
|
||||
|
||||
# because energy of climate is calculated in the thermostat we have to keep that here and not in underlying entity
|
||||
self._underlying_climate_start_hvac_action_date = None
|
||||
@@ -470,6 +478,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
self._presence_state = None
|
||||
|
||||
self._total_energy = None
|
||||
_LOGGER_ENERGY.debug("%s - post_init_ resetting energy to None", self)
|
||||
|
||||
# Read the parameter from configuration.yaml if it exists
|
||||
short_ema_params = DEFAULT_SHORT_EMA_PARAMS
|
||||
@@ -585,14 +594,24 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
# issue 428. Link to others entities will start at link
|
||||
# await self.async_startup()
|
||||
|
||||
async def async_will_remove_from_hass(self):
|
||||
"""Try to force backup of entity"""
|
||||
_LOGGER_ENERGY.debug(
|
||||
"%s - force write before remove. Energy is %s", self, self.total_energy
|
||||
)
|
||||
# Force dump in background
|
||||
await restore_async_get(self.hass).async_dump_states()
|
||||
|
||||
def remove_thermostat(self):
|
||||
"""Called when the thermostat will be removed"""
|
||||
_LOGGER.info("%s - Removing thermostat", self)
|
||||
|
||||
for under in self._underlyings:
|
||||
under.remove_entity()
|
||||
|
||||
async def async_startup(self, central_configuration):
|
||||
"""Triggered on startup, used to get old state and set internal states accordingly"""
|
||||
"""Triggered on startup, used to get old state and set internal states accordingly. This is triggered by
|
||||
VTherm API"""
|
||||
_LOGGER.debug("%s - Calling async_startup", self)
|
||||
|
||||
_LOGGER.debug("%s - Calling async_startup_internal", self)
|
||||
@@ -804,6 +823,11 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
|
||||
old_total_energy = old_state.attributes.get(ATTR_TOTAL_ENERGY)
|
||||
self._total_energy = old_total_energy if old_total_energy is not None else 0
|
||||
_LOGGER_ENERGY.debug(
|
||||
"%s - get_my_previous_state restored energy is %s",
|
||||
self,
|
||||
self._total_energy,
|
||||
)
|
||||
|
||||
self.restore_specific_previous_state(old_state)
|
||||
else:
|
||||
@@ -817,6 +841,11 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
"No previously saved temperature, setting to %s", self._target_temp
|
||||
)
|
||||
self._total_energy = 0
|
||||
_LOGGER_ENERGY.debug(
|
||||
"%s - get_my_previous_state no previous state energy is %s",
|
||||
self,
|
||||
self._total_energy,
|
||||
)
|
||||
|
||||
if not self._hvac_mode:
|
||||
self._hvac_mode = HVACMode.OFF
|
||||
@@ -2622,6 +2651,22 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
"hvac_off_reason": self.hvac_off_reason,
|
||||
}
|
||||
|
||||
_LOGGER_ENERGY.debug(
|
||||
"%s - update_custom_attributes saved energy is %s",
|
||||
self,
|
||||
self.total_energy,
|
||||
)
|
||||
|
||||
@overrides
|
||||
def async_write_ha_state(self):
|
||||
"""overrides to have log"""
|
||||
_LOGGER_ENERGY.debug(
|
||||
"%s - async_write_ha_state written state energy is %s",
|
||||
self,
|
||||
self._total_energy,
|
||||
)
|
||||
return super().async_write_ha_state()
|
||||
|
||||
@callback
|
||||
def async_registry_entry_updated(self):
|
||||
"""update the entity if the config entry have been updated
|
||||
|
||||
@@ -17,7 +17,6 @@ from .const import DOMAIN, DEVICE_MANUFACTURER, ServiceConfigurationError
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_tz(hass: HomeAssistant):
|
||||
"""Get the current timezone"""
|
||||
|
||||
|
||||
@@ -109,17 +109,17 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
||||
or self._infos.get(CONF_WINDOW_AUTO_OPEN_THRESHOLD) is not None
|
||||
)
|
||||
self._infos[CONF_USE_MOTION_FEATURE] = self._infos.get(
|
||||
CONF_USE_MOTION_FEATURE
|
||||
CONF_USE_MOTION_FEATURE, False
|
||||
) and (self._infos.get(CONF_MOTION_SENSOR) is not None or is_central_config)
|
||||
|
||||
self._infos[CONF_USE_POWER_FEATURE] = self._infos.get(
|
||||
CONF_USE_POWER_CENTRAL_CONFIG
|
||||
CONF_USE_POWER_CENTRAL_CONFIG, False
|
||||
) or (
|
||||
self._infos.get(CONF_POWER_SENSOR) is not None
|
||||
and self._infos.get(CONF_MAX_POWER_SENSOR) is not None
|
||||
)
|
||||
self._infos[CONF_USE_PRESENCE_FEATURE] = (
|
||||
self._infos.get(CONF_USE_PRESENCE_CENTRAL_CONFIG)
|
||||
self._infos.get(CONF_USE_PRESENCE_CENTRAL_CONFIG, False)
|
||||
or self._infos.get(CONF_PRESENCE_SENSOR) is not None
|
||||
)
|
||||
|
||||
@@ -129,7 +129,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
||||
)
|
||||
|
||||
self._infos[CONF_USE_AUTO_START_STOP_FEATURE] = (
|
||||
self._infos.get(CONF_USE_AUTO_START_STOP_FEATURE) is True
|
||||
self._infos.get(CONF_USE_AUTO_START_STOP_FEATURE, False) is True
|
||||
and self._infos.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CLIMATE
|
||||
)
|
||||
|
||||
@@ -145,12 +145,17 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
||||
CONF_USE_PRESETS_CENTRAL_CONFIG,
|
||||
CONF_USE_PRESENCE_CENTRAL_CONFIG,
|
||||
CONF_USE_ADVANCED_CENTRAL_CONFIG,
|
||||
CONF_USE_CENTRAL_MODE,
|
||||
):
|
||||
if not is_empty:
|
||||
current_config = self._infos.get(config, None)
|
||||
self._infos[config] = current_config is True or (
|
||||
current_config is None and self._central_config is not None
|
||||
|
||||
self._infos[config] = self._central_config is not None and (
|
||||
current_config is True or current_config is None
|
||||
)
|
||||
# self._infos[config] = current_config is True or (
|
||||
# current_config is None and self._central_config is not None
|
||||
# )
|
||||
else:
|
||||
self._infos[config] = self._central_config is not None
|
||||
|
||||
@@ -209,6 +214,9 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
||||
CONF_USE_PRESENCE_CENTRAL_CONFIG,
|
||||
CONF_USE_PRESETS_CENTRAL_CONFIG,
|
||||
CONF_USE_ADVANCED_CENTRAL_CONFIG,
|
||||
CONF_USE_CENTRAL_MODE,
|
||||
# CONF_USE_CENTRAL_BOILER_FEATURE, this is for Central Config
|
||||
CONF_USED_BY_CENTRAL_BOILER,
|
||||
]:
|
||||
if data.get(conf) is True:
|
||||
_LOGGER.error(
|
||||
@@ -306,6 +314,22 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
||||
):
|
||||
return False
|
||||
|
||||
if (
|
||||
infos.get(CONF_PROP_FUNCTION, None) == PROPORTIONAL_FUNCTION_TPI
|
||||
and infos.get(CONF_USE_TPI_CENTRAL_CONFIG, False) is False
|
||||
and (
|
||||
infos.get(CONF_TPI_COEF_INT, None) is None
|
||||
or infos.get(CONF_TPI_COEF_EXT) is None
|
||||
)
|
||||
):
|
||||
return False
|
||||
|
||||
if (
|
||||
infos.get(CONF_USE_PRESETS_CENTRAL_CONFIG, False) is True
|
||||
and self._central_config is None
|
||||
):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def merge_user_input(self, data_schema: vol.Schema, user_input: dict):
|
||||
|
||||
@@ -354,7 +354,11 @@ CONF_WINDOW_ACTIONS = [
|
||||
CONF_WINDOW_ECO_TEMP,
|
||||
]
|
||||
|
||||
SUPPORT_FLAGS = ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
|
||||
SUPPORT_FLAGS = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
|
||||
SERVICE_SET_PRESENCE = "set_presence"
|
||||
SERVICE_SET_PRESET_TEMPERATURE = "set_preset_temperature"
|
||||
|
||||
@@ -31,6 +31,10 @@ from .auto_start_stop_algorithm import (
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
_LOGGER_ENERGY = logging.getLogger(
|
||||
"custom_components.versatile_thermostat.energy_debug"
|
||||
)
|
||||
|
||||
|
||||
HVAC_ACTION_ON = [ # pylint: disable=invalid-name
|
||||
HVACAction.COOLING,
|
||||
@@ -97,7 +101,7 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
||||
"""Initialize the Thermostat"""
|
||||
|
||||
super().post_init(config_entry)
|
||||
|
||||
|
||||
for climate in config_entry.get(CONF_UNDERLYING_LIST):
|
||||
self._underlyings.append(
|
||||
UnderlyingClimate(
|
||||
@@ -549,6 +553,7 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
||||
] = self._auto_start_stop_algo.accumulated_error_threshold
|
||||
|
||||
self.async_write_ha_state()
|
||||
|
||||
_LOGGER.debug(
|
||||
"%s - Calling update_custom_attributes: %s",
|
||||
self,
|
||||
@@ -595,8 +600,18 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
||||
|
||||
if self._total_energy is None:
|
||||
self._total_energy = added_energy
|
||||
_LOGGER_ENERGY.debug(
|
||||
"%s - incremente_energy set energy is %s",
|
||||
self,
|
||||
self._total_energy,
|
||||
)
|
||||
else:
|
||||
self._total_energy += added_energy
|
||||
_LOGGER_ENERGY.debug(
|
||||
"%s - incremente_energy incremented energy is %s",
|
||||
self,
|
||||
self._total_energy,
|
||||
)
|
||||
|
||||
_LOGGER.debug(
|
||||
"%s - added energy is %.3f . Total energy is now: %.3f",
|
||||
|
||||
@@ -21,7 +21,9 @@ from .underlyings import UnderlyingSwitch
|
||||
from .prop_algorithm import PropAlgorithm
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
_LOGGER_ENERGY = logging.getLogger(
|
||||
"custom_components.versatile_thermostat.energy_debug"
|
||||
)
|
||||
|
||||
class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
|
||||
"""Representation of a base class for a Versatile Thermostat over a switch."""
|
||||
@@ -183,8 +185,18 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
|
||||
|
||||
if self._total_energy is None:
|
||||
self._total_energy = added_energy
|
||||
_LOGGER_ENERGY.debug(
|
||||
"%s - incremente_energy set energy is %s",
|
||||
self,
|
||||
self._total_energy,
|
||||
)
|
||||
else:
|
||||
self._total_energy += added_energy
|
||||
_LOGGER_ENERGY.debug(
|
||||
"%s - incremente_energy increment energy is %s",
|
||||
self,
|
||||
self._total_energy,
|
||||
)
|
||||
|
||||
self.update_custom_attributes()
|
||||
|
||||
|
||||
@@ -25,7 +25,9 @@ from .const import (
|
||||
from .underlyings import UnderlyingValve
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
_LOGGER_ENERGY = logging.getLogger(
|
||||
"custom_components.versatile_thermostat.energy_debug"
|
||||
)
|
||||
|
||||
class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=abstract-method
|
||||
"""Representation of a class for a Versatile Thermostat over a Valve"""
|
||||
@@ -265,8 +267,18 @@ class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=a
|
||||
|
||||
if self._total_energy is None:
|
||||
self._total_energy = added_energy
|
||||
_LOGGER_ENERGY.debug(
|
||||
"%s - incremente_energy set energy is %s",
|
||||
self,
|
||||
self._total_energy,
|
||||
)
|
||||
else:
|
||||
self._total_energy += added_energy
|
||||
_LOGGER_ENERGY.debug(
|
||||
"%s - get_my_previous_state increment energy is %s",
|
||||
self,
|
||||
self._total_energy,
|
||||
)
|
||||
|
||||
self.update_custom_attributes()
|
||||
|
||||
|
||||
@@ -150,10 +150,11 @@ class VersatileThermostatAPI(dict):
|
||||
return entity.state
|
||||
return None
|
||||
|
||||
async def init_vtherm_links(self):
|
||||
async def init_vtherm_links(self, entry_id=None):
|
||||
"""Initialize all VTherms entities links
|
||||
This method is called when HA is fully started (and all entities should be initialized)
|
||||
Or when we need to reload all VTherm links (with Number temp entities, central boiler, ...)
|
||||
If entry_id is set, only the VTherm of this entry will be reloaded
|
||||
"""
|
||||
await self.reload_central_boiler_binary_listener()
|
||||
await self.reload_central_boiler_entities_list()
|
||||
@@ -175,7 +176,8 @@ class VersatileThermostatAPI(dict):
|
||||
entity.device_info
|
||||
and entity.device_info.get("model", None) == DOMAIN
|
||||
):
|
||||
await entity.async_startup(self.find_central_configuration())
|
||||
if entry_id is None or entry_id == entity.unique_id:
|
||||
await entity.async_startup(self.find_central_configuration())
|
||||
|
||||
async def init_vtherm_preset_with_central(self):
|
||||
"""Init all VTherm presets when the VTherm uses central temperature"""
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user