Fix hvac_action

Fix offset_calibration=room_temp - (local_temp - current_offset)
This commit is contained in:
Jean-Marc Collin
2024-11-19 19:32:20 +00:00
parent 9102b09691
commit 592b2ca574
3 changed files with 74 additions and 24 deletions

View File

@@ -2,6 +2,7 @@
"""Constants for the Versatile Thermostat integration.""" """Constants for the Versatile Thermostat integration."""
import logging import logging
import math
from typing import Literal from typing import Literal
from enum import Enum from enum import Enum
@@ -491,6 +492,20 @@ def send_vtherm_event(hass, event_type: EventType, entity, data: dict):
hass.bus.fire(event_type.value, data) hass.bus.fire(event_type.value, data)
def get_safe_float(hass, entity_id: str):
"""Get a safe float state value for an entity.
Return None if entity is not available"""
if (
entity_id is None
or not (state := hass.states.get(entity_id))
or state.state == "unknown"
or state.state == "unavailable"
):
return None
float_val = float(state.state)
return None if math.isinf(float_val) or not math.isfinite(float_val) else float_val
class UnknownEntity(HomeAssistantError): class UnknownEntity(HomeAssistantError):
"""Error to indicate there is an unknown entity_id given.""" """Error to indicate there is an unknown entity_id given."""

View File

@@ -41,7 +41,7 @@ class ThermostatOverSonoffTRVZB(ThermostatOverClimate):
) )
) )
_underlyings_sonoff_trvzb: list[UnderlyingSonoffTRVZB] = [] _underlyings_sonoff_trvzb: list[UnderlyingSonoffTRVZB] = []
_valve_open_percent: int = 0 _valve_open_percent: int | None = None
_last_calculation_timestamp: datetime | None = None _last_calculation_timestamp: datetime | None = None
_auto_regulation_dpercent: float | None = None _auto_regulation_dpercent: float | None = None
_auto_regulation_period_min: int | None = None _auto_regulation_period_min: int | None = None
@@ -188,9 +188,14 @@ class ThermostatOverSonoffTRVZB(ThermostatOverClimate):
if new_valve_percent < self._auto_regulation_dpercent: if new_valve_percent < self._auto_regulation_dpercent:
new_valve_percent = 0 new_valve_percent = 0
dpercent = new_valve_percent - self.valve_open_percent dpercent = (
new_valve_percent - self._valve_open_percent
if self._valve_open_percent is not None
else 0
)
if ( if (
new_valve_percent > 0 self._last_calculation_timestamp is not None
and new_valve_percent > 0
and -1 * self._auto_regulation_dpercent and -1 * self._auto_regulation_dpercent
<= dpercent <= dpercent
< self._auto_regulation_dpercent < self._auto_regulation_dpercent
@@ -203,7 +208,10 @@ class ThermostatOverSonoffTRVZB(ThermostatOverClimate):
return return
if self._valve_open_percent == new_valve_percent: if (
self._last_calculation_timestamp is not None
and self._valve_open_percent == new_valve_percent
):
_LOGGER.debug("%s - no change in valve_open_percent.", self) _LOGGER.debug("%s - no change in valve_open_percent.", self)
return return
@@ -216,6 +224,10 @@ class ThermostatOverSonoffTRVZB(ThermostatOverClimate):
self.update_custom_attributes() self.update_custom_attributes()
async def _send_regulated_temperature(self, force=False):
"""Sends the regulated temperature to all underlying"""
self.recalculate()
@property @property
def is_over_sonoff_trvzb(self) -> bool: def is_over_sonoff_trvzb(self) -> bool:
"""True if the Thermostat is over_sonoff_trvzb""" """True if the Thermostat is over_sonoff_trvzb"""
@@ -237,7 +249,7 @@ class ThermostatOverSonoffTRVZB(ThermostatOverClimate):
@property @property
def valve_open_percent(self) -> int: def valve_open_percent(self) -> int:
"""Gives the percentage of valve needed""" """Gives the percentage of valve needed"""
if self._hvac_mode == HVACMode.OFF: if self._hvac_mode == HVACMode.OFF or self._valve_open_percent is None:
return 0 return 0
else: else:
return self._valve_open_percent return self._valve_open_percent

View File

@@ -32,7 +32,7 @@ from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.event import async_call_later from homeassistant.helpers.event import async_call_later
from homeassistant.util.unit_conversion import TemperatureConverter from homeassistant.util.unit_conversion import TemperatureConverter
from .const import UnknownEntity, overrides from .const import UnknownEntity, overrides, get_safe_float
from .keep_alive import IntervalCaller from .keep_alive import IntervalCaller
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -884,7 +884,7 @@ class UnderlyingValve(UnderlyingEntity):
self._async_cancel_cycle = None self._async_cancel_cycle = None
self._should_relaunch_control_heating = False self._should_relaunch_control_heating = False
self._hvac_mode = None self._hvac_mode = None
self._percent_open = self._thermostat.valve_open_percent self._percent_open = None # self._thermostat.valve_open_percent
self._valve_entity_id = valve_entity_id self._valve_entity_id = valve_entity_id
async def _send_value_to_number(self, number_entity_id: str, value: int): async def _send_value_to_number(self, number_entity_id: str, value: int):
@@ -1062,6 +1062,7 @@ class UnderlyingSonoffTRVZB(UnderlyingValve):
self._is_min_max_initialized = ( self._is_min_max_initialized = (
self._max_opening_degree is not None self._max_opening_degree is not None
and self._min_offset_calibration is not None and self._min_offset_calibration is not None
and self._max_offset_calibration is not None
) )
if not self._is_min_max_initialized: if not self._is_min_max_initialized:
@@ -1073,25 +1074,39 @@ class UnderlyingSonoffTRVZB(UnderlyingValve):
# Send opening_degree # Send opening_degree
await super().send_percent_open() await super().send_percent_open()
# Send closing_degree. # Send closing_degree if set
await self._send_value_to_number( closing_degree = None
self._closing_degree_entity_id, if self._closing_degree_entity_id is not None:
closing_degree := self._max_opening_degree - self._percent_open, await self._send_value_to_number(
) self._closing_degree_entity_id,
closing_degree := self._max_opening_degree - self._percent_open,
# send offset_calibration to the difference between target temp and local temp
offset = 0
if (
local_temp := self._climate_underlying.underlying_current_temperature
) is not None and (
room_temp := self._thermostat.current_temperature
) is not None:
offset = min(
self._max_offset_calibration,
max(self._min_offset_calibration, room_temp - local_temp),
) )
await self._send_value_to_number(self._offset_calibration_entity_id, offset) # send offset_calibration to the difference between target temp and local temp
offset = None
if self._offset_calibration_entity_id is not None:
if (
(local_temp := self._climate_underlying.underlying_current_temperature)
is not None
and (room_temp := self._thermostat.current_temperature) is not None
and (
current_offset := get_safe_float(
self._hass, self._offset_calibration_entity_id
)
)
is not None
):
offset = min(
self._max_offset_calibration,
max(
self._min_offset_calibration,
room_temp - (local_temp - current_offset),
),
)
await self._send_value_to_number(
self._offset_calibration_entity_id, offset
)
_LOGGER.debug( _LOGGER.debug(
"%s - SonoffTRVZB - I have sent offset_calibration=%s opening_degree=%s closing_degree=%s", "%s - SonoffTRVZB - I have sent offset_calibration=%s opening_degree=%s closing_degree=%s",
@@ -1122,3 +1137,11 @@ class UnderlyingSonoffTRVZB(UnderlyingValve):
if not self.is_initialized: if not self.is_initialized:
return [] return []
return [HVACMode.OFF, HVACMode.HEAT] return [HVACMode.OFF, HVACMode.HEAT]
@property
def is_device_active(self):
"""If the opening valve is open."""
try:
return get_safe_float(self._hass, self._opening_degree_entity_id) > 0
except Exception: # pylint: disable=broad-exception-caught
return False