Compare commits

...

5 Commits

Author SHA1 Message Date
Jean-Marc Collin acd22d1fc4 HA 2024.4.3 and release (#447)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-04-16 08:54:59 +02:00
Jean-Marc Collin d6f33d5796 [#438] - manage total_energy none until restored or calculated (#446)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-04-16 07:14:38 +02:00
Jean-Marc Collin c1ebb46ac6 [#358] - Block preset_mode change on central_mode status (#445)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-04-15 08:06:07 +02:00
Jean-Marc Collin eee4a9c4e3 Beers ! 2024-04-08 05:33:56 +00:00
Jean-Marc Collin 2a3d3ff877 [#429] - VTherm doesn't respect target temperature (#435)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-04-01 12:34:18 +02:00
12 changed files with 100 additions and 43 deletions
+10 -5
View File
@@ -3,10 +3,12 @@ default_config:
logger: logger:
default: warning default: warning
logs: logs:
custom_components.versatile_thermostat: info custom_components.versatile_thermostat: debug
custom_components.versatile_thermostat.underlyings: info # custom_components.versatile_thermostat.underlyings: info
custom_components.versatile_thermostat.climate: info # custom_components.versatile_thermostat.climate: info
custom_components.versatile_thermostat.base_thermostat: debug # custom_components.versatile_thermostat.base_thermostat: debug
custom_components.versatile_thermostat.sensor: info
custom_components.versatile_thermostat.binary_sensor: info
# If you need to debug uncommment the line below (doc: https://www.home-assistant.io/integrations/debugpy/) # If you need to debug uncommment the line below (doc: https://www.home-assistant.io/integrations/debugpy/)
debugpy: debugpy:
@@ -175,7 +177,7 @@ input_datetime:
has_time: true has_time: true
recorder: recorder:
commit_interval: 1 commit_interval: 0
include: include:
domains: domains:
- input_boolean - input_boolean
@@ -184,6 +186,9 @@ recorder:
- climate - climate
- sensor - sensor
- binary_sensor - binary_sensor
- number
- input_select
- versatile_thermostat
template: template:
- binary_sensor: - binary_sensor:
+1 -1
View File
@@ -212,7 +212,7 @@ En conséquence toute la phase de paramètrage d'un VTherm a été profondemment
</details> </details>
# Merci pour la bière [buymecoffee](https://www.buymeacoffee.com/jmcollin78) # Merci pour la bière [buymecoffee](https://www.buymeacoffee.com/jmcollin78)
Un grand merci à @salabur, @pvince83, @bergoglio, @EPicLURcher, @ecolorado66, @Kriss1670, @maia, @f.maymil, @moutte69, @Jerome, @Gunnar M, @Greg.o, @John Burgess, @abyssmal, @capinfo26, @Helge, @MattG @Mexx62, @Someone, @Lajull, @giopeco, @fredericselier, @philpagan, @studiogriffanti, @Edwin, @Sebbou, @Gerard R. pour les bières. Ca fait très plaisir et ça m'encourage à continuer ! Un grand merci à @salabur, @pvince83, @bergoglio, @EPicLURcher, @ecolorado66, @Kriss1670, @maia, @f.maymil, @moutte69, @Jerome, @Gunnar M, @Greg.o, @John Burgess, @abyssmal, @capinfo26, @Helge, @MattG @Mexx62, @Someone, @Lajull, @giopeco, @fredericselier, @philpagan, @studiogriffanti, @Edwin, @Sebbou, @Gerard R., @John Burgess, @Sylvoliv, @cdenfert pour les bières. Ca fait très plaisir et ça m'encourage à continuer !
# Quand l'utiliser et ne pas l'utiliser # Quand l'utiliser et ne pas l'utiliser
+1 -1
View File
@@ -213,7 +213,7 @@ Consequently, the entire configuration phase of a VTherm has been profoundly mod
</details> </details>
# Thanks for the beer [buymecoffee](https://www.buymeacoffee.com/jmcollin78) # Thanks for the beer [buymecoffee](https://www.buymeacoffee.com/jmcollin78)
Many thanks to @salabur, @pvince83, @bergoglio, @EPicLURcher, @ecolorado66, @Kriss1670, @maia, @f.maymil, @moutte69, @Jerome, @Gunnar M, @Greg.o, @John Burgess, @abyssmal, @capinfo26, @Helge, @MattG, @MattG, @Mexx62, @Someone, @Lajull, @giopeco, @fredericselier, @philpagan, @studiogriffanti, @Edwin, @Sebbou, @Gerard R. for the beers. It's very nice and encourages me to continue! Many thanks to @salabur, @pvince83, @bergoglio, @EPicLURcher, @ecolorado66, @Kriss1670, @maia, @f.maymil, @moutte69, @Jerome, @Gunnar M, @Greg.o, @John Burgess, @abyssmal, @capinfo26, @Helge, @MattG, @MattG, @Mexx62, @Someone, @Lajull, @giopeco, @fredericselier, @philpagan, @studiogriffanti, @Edwin, @Sebbou, @Gerard R., @John Burgess, @Sylvoliv, @cdenfert for the beers. It's very nice and encourages me to continue!
# When to use / not use # When to use / not use
This thermostat can control 3 types of equipment: This thermostat can control 3 types of equipment:
@@ -532,7 +532,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
self._overpowering_state = None self._overpowering_state = None
self._presence_state = None self._presence_state = None
self._total_energy = 0 self._total_energy = None
# Read the parameter from configuration.yaml if it exists # Read the parameter from configuration.yaml if it exists
short_ema_params = DEFAULT_SHORT_EMA_PARAMS short_ema_params = DEFAULT_SHORT_EMA_PARAMS
@@ -860,8 +860,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
self._hvac_mode = HVACMode.OFF self._hvac_mode = HVACMode.OFF
old_total_energy = old_state.attributes.get(ATTR_TOTAL_ENERGY) old_total_energy = old_state.attributes.get(ATTR_TOTAL_ENERGY)
if old_total_energy: self._total_energy = old_total_energy if old_total_energy else 0
self._total_energy = old_total_energy
self.restore_specific_previous_state(old_state) self.restore_specific_previous_state(old_state)
else: else:
@@ -874,6 +873,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
_LOGGER.warning( _LOGGER.warning(
"No previously saved temperature, setting to %s", self._target_temp "No previously saved temperature, setting to %s", self._target_temp
) )
self._total_energy = 0
self._saved_target_temp = self._target_temp self._saved_target_temp = self._target_temp
@@ -1063,7 +1063,10 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
@property @property
def total_energy(self) -> float | None: def total_energy(self) -> float | None:
"""Returns the total energy calculated for this thermostast""" """Returns the total energy calculated for this thermostast"""
return round(self._total_energy, 2) if self._total_energy is not None:
return round(self._total_energy, 2)
else:
return None
@property @property
def device_power(self) -> float | None: def device_power(self) -> float | None:
@@ -1258,6 +1261,31 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
self, preset_mode: str, overwrite_saved_preset=True self, preset_mode: str, overwrite_saved_preset=True
): ):
"""Set new preset mode.""" """Set new preset mode."""
# Wer accept a new preset when:
# 1. last_central_mode is not set,
# 2. or last_central_mode is AUTO,
# 3. or last_central_mode is CENTRAL_MODE_FROST_PROTECTION and preset_mode is PRESET_FROST_PROTECTION (to be abel to re-set the preset_mode)
accept = self._last_central_mode in [
None,
CENTRAL_MODE_AUTO,
CENTRAL_MODE_COOL_ONLY,
CENTRAL_MODE_HEAT_ONLY,
CENTRAL_MODE_STOPPED,
] or (
self._last_central_mode == CENTRAL_MODE_FROST_PROTECTION
and preset_mode == PRESET_FROST_PROTECTION
)
if not accept:
_LOGGER.info(
"%s - Impossible to change the preset to %s because central mode is %s",
self,
preset_mode,
self._last_central_mode,
)
return
await self._async_set_preset_mode_internal( await self._async_set_preset_mode_internal(
preset_mode, force=False, overwrite_saved_preset=overwrite_saved_preset preset_mode, force=False, overwrite_saved_preset=overwrite_saved_preset
) )
@@ -2302,12 +2330,12 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
self._security_state = True self._security_state = True
self.save_hvac_mode() self.save_hvac_mode()
self.save_preset_mode() self.save_preset_mode()
if self._prop_algorithm:
self._prop_algorithm.set_security(self._security_default_on_percent)
await self._async_set_preset_mode_internal(PRESET_SECURITY) await self._async_set_preset_mode_internal(PRESET_SECURITY)
# Turn off the underlying climate or heater if security default on_percent is 0 # 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: if self.is_over_climate or self._security_default_on_percent <= 0.0:
await self.async_set_hvac_mode(HVACMode.OFF, False) await self.async_set_hvac_mode(HVACMode.OFF, False)
if self._prop_algorithm:
self._prop_algorithm.set_security(self._security_default_on_percent)
self.send_event( self.send_event(
EventType.SECURITY_EVENT, EventType.SECURITY_EVENT,
@@ -2334,12 +2362,12 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
self._saved_preset_mode, self._saved_preset_mode,
) )
self._security_state = False self._security_state = False
if self._prop_algorithm:
self._prop_algorithm.unset_security()
# Restore hvac_mode if previously saved # Restore hvac_mode if previously saved
if self.is_over_climate or self._security_default_on_percent <= 0.0: if self.is_over_climate or self._security_default_on_percent <= 0.0:
await self.restore_hvac_mode(False) await self.restore_hvac_mode(False)
await self.restore_preset_mode() await self.restore_preset_mode()
if self._prop_algorithm:
self._prop_algorithm.unset_security()
self.send_event( self.send_event(
EventType.SECURITY_EVENT, EventType.SECURITY_EVENT,
{ {
@@ -2569,7 +2597,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
"""update the entity if the config entry have been updated """update the entity if the config entry have been updated
Note: this don't work either Note: this don't work either
""" """
_LOGGER.info("%s - The config entry have been updated") _LOGGER.info("%s - The config entry have been updated", self)
async def service_set_presence(self, presence: str): async def service_set_presence(self, presence: str):
"""Called by a service call: """Called by a service call:
@@ -14,6 +14,6 @@
"quality_scale": "silver", "quality_scale": "silver",
"requirements": [], "requirements": [],
"ssdp": [], "ssdp": [],
"version": "6.2.0", "version": "6.2.2",
"zeroconf": [] "zeroconf": []
} }
@@ -24,16 +24,19 @@ class PropAlgorithm:
tpi_coef_ext, tpi_coef_ext,
cycle_min: int, cycle_min: int,
minimal_activation_delay: int, minimal_activation_delay: int,
vtherm_entity_id: str = None,
) -> None: ) -> None:
"""Initialisation of the Proportional Algorithm""" """Initialisation of the Proportional Algorithm"""
_LOGGER.debug( _LOGGER.debug(
"Creation new PropAlgorithm function_type: %s, tpi_coef_int: %s, tpi_coef_ext: %s, cycle_min:%d, minimal_activation_delay:%d", # pylint: disable=line-too-long "%s - Creation new PropAlgorithm function_type: %s, tpi_coef_int: %s, tpi_coef_ext: %s, cycle_min:%d, minimal_activation_delay:%d", # pylint: disable=line-too-long
vtherm_entity_id,
function_type, function_type,
tpi_coef_int, tpi_coef_int,
tpi_coef_ext, tpi_coef_ext,
cycle_min, cycle_min,
minimal_activation_delay, minimal_activation_delay,
) )
self._vtherm_entity_id = vtherm_entity_id
self._function = function_type self._function = function_type
self._tpi_coef_int = tpi_coef_int self._tpi_coef_int = tpi_coef_int
self._tpi_coef_ext = tpi_coef_ext self._tpi_coef_ext = tpi_coef_ext
@@ -57,7 +60,10 @@ class PropAlgorithm:
if target_temp is None or current_temp is None: if target_temp is None or current_temp is None:
log = _LOGGER.debug if hvac_mode == HVACMode.OFF else _LOGGER.warning log = _LOGGER.debug if hvac_mode == HVACMode.OFF else _LOGGER.warning
log( log(
"Proportional algorithm: calculation is not possible cause target_temp or current_temp is null. Heating/cooling will be disabled" # pylint: disable=line-too-long "%s - Proportional algorithm: calculation is not possible cause target_temp (%s) or current_temp (%s) is null. Heating/cooling will be disabled. This could be normal at startup", # pylint: disable=line-too-long
self._vtherm_entity_id,
target_temp,
current_temp,
) )
self._calculated_on_percent = 0 self._calculated_on_percent = 0
else: else:
@@ -83,7 +89,8 @@ class PropAlgorithm:
) )
else: else:
_LOGGER.warning( _LOGGER.warning(
"Proportional algorithm: unknown %s function. Heating will be disabled", "%s - Proportional algorithm: unknown %s function. Heating will be disabled",
self._vtherm_entity_id,
self._function, self._function,
) )
self._calculated_on_percent = 0 self._calculated_on_percent = 0
@@ -91,7 +98,8 @@ class PropAlgorithm:
self._calculate_internal() self._calculate_internal()
_LOGGER.debug( _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 "%s - 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
self._vtherm_entity_id,
current_temp if current_temp else -9999.0, current_temp if current_temp else -9999.0,
ext_current_temp if ext_current_temp else -9999.0, ext_current_temp if ext_current_temp else -9999.0,
target_temp if target_temp else -9999.0, target_temp if target_temp else -9999.0,
@@ -110,11 +118,12 @@ class PropAlgorithm:
self._calculated_on_percent = 0 self._calculated_on_percent = 0
if self._security: 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 self._on_percent = self._default_on_percent
_LOGGER.info(
"%s - Security is On using the default_on_percent %f",
self._vtherm_entity_id,
self._on_percent,
)
else: else:
_LOGGER.debug( _LOGGER.debug(
"Security is Off using the calculated_on_percent %f", "Security is Off using the calculated_on_percent %f",
@@ -128,13 +137,8 @@ class PropAlgorithm:
if self._on_time_sec < self._minimal_activation_delay: if self._on_time_sec < self._minimal_activation_delay:
if self._on_time_sec > 0: if self._on_time_sec > 0:
_LOGGER.info( _LOGGER.info(
"No heating period due to heating period too small (%f < %f)", "%s - No heating period due to heating period too small (%f < %f)",
self._on_time_sec, self._vtherm_entity_id,
self._minimal_activation_delay,
)
else:
_LOGGER.debug(
"No heating period due to heating period too small (%f < %f)",
self._on_time_sec, self._on_time_sec,
self._minimal_activation_delay, self._minimal_activation_delay,
) )
@@ -144,12 +148,18 @@ class PropAlgorithm:
def set_security(self, default_on_percent: float): def set_security(self, default_on_percent: float):
"""Set a default value for on_percent (used for safety mode)""" """Set a default value for on_percent (used for safety mode)"""
_LOGGER.info(
"%s - Proportional Algo - set security to ON", self._vtherm_entity_id
)
self._security = True self._security = True
self._default_on_percent = default_on_percent self._default_on_percent = default_on_percent
self._calculate_internal() self._calculate_internal()
def unset_security(self): def unset_security(self):
"""Unset the safety mode""" """Unset the safety mode"""
_LOGGER.info(
"%s - Proportional Algo - set security to OFF", self._vtherm_entity_id
)
self._security = False self._security = False
self._calculate_internal() self._calculate_internal()
@@ -125,15 +125,15 @@ class EnergySensor(VersatileThermostatBaseEntity, SensorEntity):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", self._attr_unique_id) _LOGGER.debug("%s - climate state change", self._attr_unique_id)
if math.isnan(self.my_climate.total_energy) or math.isinf( energy = self.my_climate.total_energy
self.my_climate.total_energy if energy is None:
): return
if math.isnan(energy) or math.isinf(energy):
raise ValueError(f"Sensor has illegal state {self.my_climate.total_energy}") raise ValueError(f"Sensor has illegal state {self.my_climate.total_energy}")
old_state = self._attr_native_value old_state = self._attr_native_value
self._attr_native_value = round( self._attr_native_value = round(energy, self.suggested_display_precision)
self.my_climate.total_energy, self.suggested_display_precision
)
if old_state != self._attr_native_value: if old_state != self._attr_native_value:
self.async_write_ha_state() self.async_write_ha_state()
return return
@@ -581,7 +581,11 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
): ):
added_energy = self._device_power * self._underlying_climate_delta_t added_energy = self._device_power * self._underlying_climate_delta_t
self._total_energy += added_energy if self._total_energy is None:
self._total_energy = added_energy
else:
self._total_energy += added_energy
_LOGGER.debug( _LOGGER.debug(
"%s - added energy is %.3f . Total energy is now: %.3f", "%s - added energy is %.3f . Total energy is now: %.3f",
self, self,
@@ -88,6 +88,7 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
self._tpi_coef_ext, self._tpi_coef_ext,
self._cycle_min, self._cycle_min,
self._minimal_activation_delay, self._minimal_activation_delay,
self.name,
) )
lst_switches = [config_entry.get(CONF_HEATER)] lst_switches = [config_entry.get(CONF_HEATER)]
@@ -198,7 +199,11 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
if not self.is_over_climate and self.mean_cycle_power is not None: if not self.is_over_climate and self.mean_cycle_power is not None:
added_energy = self.mean_cycle_power * float(self._cycle_min) / 60.0 added_energy = self.mean_cycle_power * float(self._cycle_min) / 60.0
self._total_energy += added_energy if self._total_energy is None:
self._total_energy = added_energy
else:
self._total_energy += added_energy
_LOGGER.debug( _LOGGER.debug(
"%s - added energy is %.3f . Total energy is now: %.3f", "%s - added energy is %.3f . Total energy is now: %.3f",
self, self,
@@ -105,6 +105,7 @@ class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=a
self._tpi_coef_ext, self._tpi_coef_ext,
self._cycle_min, self._cycle_min,
self._minimal_activation_delay, self._minimal_activation_delay,
self.name,
) )
lst_valves = [config_entry.get(CONF_VALVE)] lst_valves = [config_entry.get(CONF_VALVE)]
@@ -278,7 +279,11 @@ class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=a
if not self.is_over_climate and self.mean_cycle_power is not None: if not self.is_over_climate and self.mean_cycle_power is not None:
added_energy = self.mean_cycle_power * float(self._cycle_min) / 60.0 added_energy = self.mean_cycle_power * float(self._cycle_min) / 60.0
self._total_energy += added_energy if self._total_energy is None:
self._total_energy = added_energy
else:
self._total_energy += added_energy
_LOGGER.debug( _LOGGER.debug(
"%s - added energy is %.3f . Total energy is now: %.3f", "%s - added energy is %.3f . Total energy is now: %.3f",
self, self,
+1 -1
View File
@@ -3,5 +3,5 @@
"content_in_root": false, "content_in_root": false,
"render_readme": true, "render_readme": true,
"hide_default_branch": false, "hide_default_branch": false,
"homeassistant": "2024.3.1" "homeassistant": "2024.4.3"
} }
+1 -1
View File
@@ -1 +1 @@
homeassistant==2024.3.1 homeassistant==2024.4.3