Work in simuated environment
This commit is contained in:
@@ -37,11 +37,13 @@ from .const import (
|
||||
CONF_THERMOSTAT_CLIMATE,
|
||||
CONF_THERMOSTAT_VALVE,
|
||||
CONF_THERMOSTAT_CENTRAL_CONFIG,
|
||||
CONF_SONOFF_TRZB_MODE,
|
||||
)
|
||||
|
||||
from .thermostat_switch import ThermostatOverSwitch
|
||||
from .thermostat_climate import ThermostatOverClimate
|
||||
from .thermostat_valve import ThermostatOverValve
|
||||
from .thermostat_sonoff_trvzb import ThermostatOverSonoffTRVZB
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -60,6 +62,7 @@ async def async_setup_entry(
|
||||
unique_id = entry.entry_id
|
||||
name = entry.data.get(CONF_NAME)
|
||||
vt_type = entry.data.get(CONF_THERMOSTAT_TYPE)
|
||||
is_sonoff_trvzb = entry.data.get(CONF_SONOFF_TRZB_MODE)
|
||||
|
||||
if vt_type == CONF_THERMOSTAT_CENTRAL_CONFIG:
|
||||
return
|
||||
@@ -69,7 +72,10 @@ async def async_setup_entry(
|
||||
if vt_type == CONF_THERMOSTAT_SWITCH:
|
||||
entity = ThermostatOverSwitch(hass, unique_id, name, entry.data)
|
||||
elif vt_type == CONF_THERMOSTAT_CLIMATE:
|
||||
entity = ThermostatOverClimate(hass, unique_id, name, entry.data)
|
||||
if is_sonoff_trvzb is True:
|
||||
entity = ThermostatOverSonoffTRVZB(hass, unique_id, name, entry.data)
|
||||
else:
|
||||
entity = ThermostatOverClimate(hass, unique_id, name, entry.data)
|
||||
elif vt_type == CONF_THERMOSTAT_VALVE:
|
||||
entity = ThermostatOverValve(hass, unique_id, name, entry.data)
|
||||
else:
|
||||
|
||||
@@ -17,7 +17,6 @@ from homeassistant.components.sensor import (
|
||||
SensorEntity,
|
||||
SensorDeviceClass,
|
||||
SensorStateClass,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
|
||||
@@ -50,6 +49,7 @@ from .const import (
|
||||
CONF_THERMOSTAT_TYPE,
|
||||
CONF_THERMOSTAT_CENTRAL_CONFIG,
|
||||
CONF_USE_CENTRAL_BOILER_FEATURE,
|
||||
CONF_SONOFF_TRZB_MODE,
|
||||
overrides,
|
||||
)
|
||||
|
||||
@@ -71,6 +71,7 @@ async def async_setup_entry(
|
||||
unique_id = entry.entry_id
|
||||
name = entry.data.get(CONF_NAME)
|
||||
vt_type = entry.data.get(CONF_THERMOSTAT_TYPE)
|
||||
is_sonoff_trvzb = entry.data.get(CONF_SONOFF_TRZB_MODE)
|
||||
|
||||
entities = None
|
||||
|
||||
@@ -99,10 +100,16 @@ async def async_setup_entry(
|
||||
entities.append(OnTimeSensor(hass, unique_id, name, entry.data))
|
||||
entities.append(OffTimeSensor(hass, unique_id, name, entry.data))
|
||||
|
||||
if entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_VALVE:
|
||||
if (
|
||||
entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_VALVE
|
||||
or is_sonoff_trvzb
|
||||
):
|
||||
entities.append(ValveOpenPercentSensor(hass, unique_id, name, entry.data))
|
||||
|
||||
if entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CLIMATE:
|
||||
if (
|
||||
entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CLIMATE
|
||||
and not is_sonoff_trvzb
|
||||
):
|
||||
entities.append(
|
||||
RegulatedTemperatureSensor(hass, unique_id, name, entry.data)
|
||||
)
|
||||
|
||||
@@ -454,7 +454,7 @@
|
||||
}
|
||||
},
|
||||
"central_boiler": {
|
||||
"title": "Control of the central boiler",
|
||||
"title": "Control of the central boiler - {name}",
|
||||
"description": "Enter the services to call to turn on/off the central boiler. Leave blank if no service call is to be made (in this case, you will have to manage the turning on/off of your central boiler yourself). The service called must be formatted as follows: `entity_id/service_name[/attribute:value]` (/attribute:value is optional)\nFor example:\n- to turn on a switch: `switch.controle_chaudiere/switch.turn_on`\n- to turn off a switch: `switch.controle_chaudiere/switch.turn_off`\n- to program the boiler to 25° and thus force its ignition: `climate.thermostat_chaudiere/climate.set_temperature/temperature:25`\n- to send 10° to the boiler and thus force its extinction: `climate.thermostat_chaudiere/climate.set_temperature/temperature:10`",
|
||||
"data": {
|
||||
"central_boiler_activation_service": "Command to turn-on",
|
||||
@@ -466,7 +466,7 @@
|
||||
}
|
||||
},
|
||||
"sonoff_trvzb": {
|
||||
"title": "Sonoff TRVZB configuration",
|
||||
"title": "Sonoff TRVZB configuration - {name}",
|
||||
"description": "Specific Sonoff TRVZB configuration",
|
||||
"data": {
|
||||
"offset_calibration_entity_ids": "Offset calibration entities",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# pylint: disable=line-too-long, too-many-lines
|
||||
# pylint: disable=line-too-long, too-many-lines, abstract-method
|
||||
""" A climate over climate classe """
|
||||
import logging
|
||||
from datetime import timedelta, datetime
|
||||
@@ -60,28 +60,26 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
||||
_is_auto_start_stop_enabled: bool = False
|
||||
_follow_underlying_temp_change: bool = False
|
||||
|
||||
_entity_component_unrecorded_attributes = (
|
||||
BaseThermostat._entity_component_unrecorded_attributes.union(
|
||||
frozenset(
|
||||
{
|
||||
"is_over_climate",
|
||||
"start_hvac_action_date",
|
||||
"underlying_entities",
|
||||
"regulation_accumulated_error",
|
||||
"auto_regulation_mode",
|
||||
"auto_fan_mode",
|
||||
"current_auto_fan_mode",
|
||||
"auto_activated_fan_mode",
|
||||
"auto_deactivated_fan_mode",
|
||||
"auto_regulation_use_device_temp",
|
||||
"auto_start_stop_level",
|
||||
"auto_start_stop_dtmin",
|
||||
"auto_start_stop_enable",
|
||||
"auto_start_stop_accumulated_error",
|
||||
"auto_start_stop_accumulated_error_threshold",
|
||||
"follow_underlying_temp_change",
|
||||
}
|
||||
)
|
||||
_entity_component_unrecorded_attributes = BaseThermostat._entity_component_unrecorded_attributes.union( # pylint: disable=protected-access
|
||||
frozenset(
|
||||
{
|
||||
"is_over_climate",
|
||||
"start_hvac_action_date",
|
||||
"underlying_entities",
|
||||
"regulation_accumulated_error",
|
||||
"auto_regulation_mode",
|
||||
"auto_fan_mode",
|
||||
"current_auto_fan_mode",
|
||||
"auto_activated_fan_mode",
|
||||
"auto_deactivated_fan_mode",
|
||||
"auto_regulation_use_device_temp",
|
||||
"auto_start_stop_level",
|
||||
"auto_start_stop_dtmin",
|
||||
"auto_start_stop_enable",
|
||||
"auto_start_stop_accumulated_error",
|
||||
"auto_start_stop_accumulated_error_threshold",
|
||||
"follow_underlying_temp_change",
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# pylint: disable=line-too-long, too-many-lines
|
||||
# pylint: disable=line-too-long, too-many-lines, abstract-method
|
||||
""" A climate over Sonoff TRVZB classe """
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.components.climate import HVACMode
|
||||
@@ -9,7 +10,8 @@ from homeassistant.components.climate import HVACMode
|
||||
from .underlyings import UnderlyingSonoffTRVZB
|
||||
|
||||
# from .commons import NowClass, round_to_nearest
|
||||
from .base_thermostat import BaseThermostat, ConfigData
|
||||
from .base_thermostat import ConfigData
|
||||
from .thermostat_climate import ThermostatOverClimate
|
||||
from .prop_algorithm import PropAlgorithm
|
||||
|
||||
from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||
@@ -19,27 +21,30 @@ from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ThermostatOverSonoffTRVZB(BaseThermostat[UnderlyingSonoffTRVZB]):
|
||||
class ThermostatOverSonoffTRVZB(ThermostatOverClimate):
|
||||
"""This class represent a VTherm over a Sonoff TRVZB climate"""
|
||||
|
||||
_entity_component_unrecorded_attributes = (
|
||||
BaseThermostat._entity_component_unrecorded_attributes.union(
|
||||
frozenset(
|
||||
{
|
||||
"is_over_climate",
|
||||
"is_over_sonoff_trvzb",
|
||||
"underlying_entities",
|
||||
"on_time_sec",
|
||||
"off_time_sec",
|
||||
"cycle_min",
|
||||
"function",
|
||||
"tpi_coef_int",
|
||||
"tpi_coef_ext",
|
||||
"power_percent",
|
||||
}
|
||||
)
|
||||
_entity_component_unrecorded_attributes = ThermostatOverClimate._entity_component_unrecorded_attributes.union( # pylint: disable=protected-access
|
||||
frozenset(
|
||||
{
|
||||
"is_over_climate",
|
||||
"is_over_sonoff_trvzb",
|
||||
"underlying_entities",
|
||||
"on_time_sec",
|
||||
"off_time_sec",
|
||||
"cycle_min",
|
||||
"function",
|
||||
"tpi_coef_int",
|
||||
"tpi_coef_ext",
|
||||
"power_percent",
|
||||
}
|
||||
)
|
||||
)
|
||||
_underlyings_sonoff_trvzb: list[UnderlyingSonoffTRVZB] = []
|
||||
_valve_open_percent: int = 0
|
||||
_last_calculation_timestamp: datetime | None = None
|
||||
_auto_regulation_dpercent: float | None = None
|
||||
_auto_regulation_period_min: int | None = None
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, unique_id: str, name: str, entry_infos: ConfigData
|
||||
@@ -47,13 +52,40 @@ class ThermostatOverSonoffTRVZB(BaseThermostat[UnderlyingSonoffTRVZB]):
|
||||
"""Initialize the ThermostatOverSonoffTRVZB class"""
|
||||
_LOGGER.debug("%s - creating a ThermostatOverSonoffTRVZB VTherm", name)
|
||||
super().__init__(hass, unique_id, name, entry_infos)
|
||||
# self._valve_open_percent: int = 0
|
||||
# self._last_calculation_timestamp: datetime | None = None
|
||||
# self._auto_regulation_dpercent: float | None = None
|
||||
# self._auto_regulation_period_min: int | None = None
|
||||
|
||||
@overrides
|
||||
def post_init(self, config_entry: ConfigData):
|
||||
"""Initialize the Thermostat"""
|
||||
"""Initialize the Thermostat and underlyings
|
||||
Beware that the underlyings list contains the climate which represent the Sonoff TRVZB
|
||||
but also the UnderlyingSonoff which reprensent the valve"""
|
||||
|
||||
super().post_init(config_entry)
|
||||
|
||||
self._auto_regulation_dpercent = (
|
||||
config_entry.get(CONF_AUTO_REGULATION_DTEMP)
|
||||
if config_entry.get(CONF_AUTO_REGULATION_DTEMP) is not None
|
||||
else 0.0
|
||||
)
|
||||
self._auto_regulation_period_min = (
|
||||
config_entry.get(CONF_AUTO_REGULATION_PERIOD_MIN)
|
||||
if config_entry.get(CONF_AUTO_REGULATION_PERIOD_MIN) is not None
|
||||
else 0
|
||||
)
|
||||
|
||||
# Initialization of the TPI algo
|
||||
self._prop_algorithm = PropAlgorithm(
|
||||
self._proportional_function,
|
||||
self._tpi_coef_int,
|
||||
self._tpi_coef_ext,
|
||||
self._cycle_min,
|
||||
self._minimal_activation_delay,
|
||||
self.name,
|
||||
)
|
||||
|
||||
for idx, _ in enumerate(config_entry.get(CONF_UNDERLYING_LIST)):
|
||||
offset = config_entry.get(CONF_OFFSET_CALIBRATION_LIST)[idx]
|
||||
opening = config_entry.get(CONF_OPENING_DEGREE_LIST)[idx]
|
||||
@@ -65,31 +97,19 @@ class ThermostatOverSonoffTRVZB(BaseThermostat[UnderlyingSonoffTRVZB]):
|
||||
opening_degree_entity_id=opening,
|
||||
closing_degree_entity_id=closing,
|
||||
)
|
||||
self._underlyings.append(under)
|
||||
|
||||
# Initialization of the TPI algo
|
||||
self._prop_algorithm = PropAlgorithm(
|
||||
self._proportional_function,
|
||||
self._tpi_coef_int,
|
||||
self._tpi_coef_ext,
|
||||
self._cycle_min,
|
||||
self._minimal_activation_delay,
|
||||
self.name,
|
||||
)
|
||||
self._underlyings_sonoff_trvzb.append(under)
|
||||
|
||||
@overrides
|
||||
def update_custom_attributes(self):
|
||||
"""Custom attributes"""
|
||||
super().update_custom_attributes()
|
||||
|
||||
under0: UnderlyingSonoffTRVZB = self._underlyings[0]
|
||||
self._attr_extra_state_attributes["is_over_sonoff_trvzb"] = (
|
||||
self.is_over_sonoff_trvzb
|
||||
)
|
||||
self._attr_extra_state_attributes["keep_alive_sec"] = under0.keep_alive_sec
|
||||
|
||||
self._attr_extra_state_attributes["underlying_entities"] = [
|
||||
underlying.entity_id for underlying in self._underlyings
|
||||
self._attr_extra_state_attributes["underlying_sonoff_trvzb_entities"] = [
|
||||
underlying.entity_id for underlying in self._underlyings_sonoff_trvzb
|
||||
]
|
||||
|
||||
self._attr_extra_state_attributes["on_percent"] = (
|
||||
@@ -107,6 +127,22 @@ class ThermostatOverSonoffTRVZB(BaseThermostat[UnderlyingSonoffTRVZB]):
|
||||
self._attr_extra_state_attributes["tpi_coef_int"] = self._tpi_coef_int
|
||||
self._attr_extra_state_attributes["tpi_coef_ext"] = self._tpi_coef_ext
|
||||
|
||||
self._attr_extra_state_attributes["valve_open_percent"] = (
|
||||
self.valve_open_percent
|
||||
)
|
||||
|
||||
self._attr_extra_state_attributes["auto_regulation_dpercent"] = (
|
||||
self._auto_regulation_dpercent
|
||||
)
|
||||
self._attr_extra_state_attributes["auto_regulation_period_min"] = (
|
||||
self._auto_regulation_period_min
|
||||
)
|
||||
self._attr_extra_state_attributes["last_calculation_timestamp"] = (
|
||||
self._last_calculation_timestamp.astimezone(self._current_tz).isoformat()
|
||||
if self._last_calculation_timestamp
|
||||
else None
|
||||
)
|
||||
|
||||
self.async_write_ha_state()
|
||||
_LOGGER.debug(
|
||||
"%s - Calling update_custom_attributes: %s",
|
||||
@@ -119,13 +155,64 @@ class ThermostatOverSonoffTRVZB(BaseThermostat[UnderlyingSonoffTRVZB]):
|
||||
"""A utility function to force the calculation of a the algo and
|
||||
update the custom attributes and write the state
|
||||
"""
|
||||
_LOGGER.debug("%s - recalculate all", self)
|
||||
_LOGGER.debug("%s - recalculate the open percent", self)
|
||||
|
||||
# TODO this is exactly the same method as the thermostat_valve recalculate. Put that in common
|
||||
|
||||
# For testing purpose. Should call _set_now() before
|
||||
now = self.now
|
||||
|
||||
if self._last_calculation_timestamp is not None:
|
||||
period = (now - self._last_calculation_timestamp).total_seconds() / 60
|
||||
if period < self._auto_regulation_period_min:
|
||||
_LOGGER.info(
|
||||
"%s - do not calculate TPI because regulation_period (%d) is not exceeded",
|
||||
self,
|
||||
period,
|
||||
)
|
||||
return
|
||||
|
||||
self._prop_algorithm.calculate(
|
||||
self._target_temp,
|
||||
self._cur_temp,
|
||||
self._cur_ext_temp,
|
||||
self._hvac_mode or HVACMode.OFF,
|
||||
)
|
||||
|
||||
new_valve_percent = round(
|
||||
max(0, min(self.proportional_algorithm.on_percent, 1)) * 100
|
||||
)
|
||||
|
||||
# Issue 533 - don't filter with dtemp if valve should be close. Else it will never close
|
||||
if new_valve_percent < self._auto_regulation_dpercent:
|
||||
new_valve_percent = 0
|
||||
|
||||
dpercent = new_valve_percent - self.valve_open_percent
|
||||
if (
|
||||
new_valve_percent > 0
|
||||
and -1 * self._auto_regulation_dpercent
|
||||
<= dpercent
|
||||
< self._auto_regulation_dpercent
|
||||
):
|
||||
_LOGGER.debug(
|
||||
"%s - do not calculate TPI because regulation_dpercent (%.1f) is not exceeded",
|
||||
self,
|
||||
dpercent,
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
if self._valve_open_percent == new_valve_percent:
|
||||
_LOGGER.debug("%s - no change in valve_open_percent.", self)
|
||||
return
|
||||
|
||||
self._valve_open_percent = new_valve_percent
|
||||
|
||||
for under in self._underlyings_sonoff_trvzb:
|
||||
under.set_valve_open_percent()
|
||||
|
||||
self._last_calculation_timestamp = now
|
||||
|
||||
self.update_custom_attributes()
|
||||
|
||||
@property
|
||||
@@ -140,3 +227,16 @@ class ThermostatOverSonoffTRVZB(BaseThermostat[UnderlyingSonoffTRVZB]):
|
||||
return round(self._prop_algorithm.on_percent * 100, 0)
|
||||
else:
|
||||
return None
|
||||
|
||||
# @property
|
||||
# def hvac_modes(self) -> list[HVACMode]:
|
||||
# """Get the hvac_modes"""
|
||||
# return self._hvac_list
|
||||
|
||||
@property
|
||||
def valve_open_percent(self) -> int:
|
||||
"""Gives the percentage of valve needed"""
|
||||
if self._hvac_mode == HVACMode.OFF:
|
||||
return 0
|
||||
else:
|
||||
return self._valve_open_percent
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# pylint: disable=line-too-long
|
||||
# pylint: disable=line-too-long, abstract-method
|
||||
|
||||
""" A climate over switch classe """
|
||||
import logging
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# pylint: disable=line-too-long
|
||||
# pylint: disable=line-too-long, abstract-method
|
||||
""" A climate over switch classe """
|
||||
import logging
|
||||
from datetime import timedelta, datetime
|
||||
|
||||
@@ -259,7 +259,7 @@
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"title": "Menu",
|
||||
"title": "Menu - {name}",
|
||||
"description": "Paramétrez votre thermostat. Vous pourrez finaliser la configuration quand tous les paramètres auront été saisis.",
|
||||
"menu_options": {
|
||||
"main": "Principaux Attributs",
|
||||
@@ -316,7 +316,7 @@
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"title": "Entité(s) liée(s)",
|
||||
"title": "Entité(s) liée(s) - {name}",
|
||||
"description": "Attributs de(s) l'entité(s) liée(s)",
|
||||
"data": {
|
||||
"underlying_entity_ids": "Les équipements à controller",
|
||||
@@ -460,7 +460,7 @@
|
||||
}
|
||||
},
|
||||
"sonoff_trvzb": {
|
||||
"title": "Configuration Sonoff TRVZB",
|
||||
"title": "Configuration Sonoff TRVZB - {name}",
|
||||
"description": "Configuration spécifique des Sonoff TRVZB",
|
||||
"data": {
|
||||
"offset_calibration_entity_ids": "Entités de 'Offset calibration'",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# pylint: disable=unused-argument, line-too-long
|
||||
# pylint: disable=unused-argument, line-too-long, too-many-lines
|
||||
|
||||
""" Underlying entities classes """
|
||||
import logging
|
||||
@@ -65,6 +65,7 @@ class UnderlyingEntity:
|
||||
_thermostat: Any
|
||||
_entity_id: str
|
||||
_type: UnderlyingEntityType
|
||||
_hvac_mode: HVACMode | None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -103,13 +104,24 @@ class UnderlyingEntity:
|
||||
|
||||
async def set_hvac_mode(self, hvac_mode: HVACMode):
|
||||
"""Set the HVACmode"""
|
||||
self._hvac_mode = hvac_mode
|
||||
return
|
||||
|
||||
@property
|
||||
def hvac_mode(self) -> HVACMode | None:
|
||||
"""Return the current hvac_mode"""
|
||||
return self._hvac_mode
|
||||
|
||||
@property
|
||||
def is_device_active(self) -> bool | None:
|
||||
"""If the toggleable device is currently active."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def hvac_action(self) -> HVACAction:
|
||||
"""Calculate a hvac_action"""
|
||||
return HVACAction.HEATING if self.is_device_active is True else HVACAction.OFF
|
||||
|
||||
async def set_temperature(self, temperature, max_temp, min_temp):
|
||||
"""Set the target temperature"""
|
||||
return
|
||||
@@ -184,7 +196,6 @@ class UnderlyingSwitch(UnderlyingEntity):
|
||||
_initialDelaySec: int
|
||||
_on_time_sec: int
|
||||
_off_time_sec: int
|
||||
_hvac_mode: HVACMode
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -207,7 +218,6 @@ class UnderlyingSwitch(UnderlyingEntity):
|
||||
self._should_relaunch_control_heating = False
|
||||
self._on_time_sec = 0
|
||||
self._off_time_sec = 0
|
||||
self._hvac_mode = None
|
||||
self._keep_alive = IntervalCaller(hass, keep_alive_sec)
|
||||
|
||||
@property
|
||||
@@ -240,8 +250,8 @@ class UnderlyingSwitch(UnderlyingEntity):
|
||||
await self.turn_off()
|
||||
self._cancel_cycle()
|
||||
|
||||
if self._hvac_mode != hvac_mode:
|
||||
self._hvac_mode = hvac_mode
|
||||
if self.hvac_mode != hvac_mode:
|
||||
super().set_hvac_mode(hvac_mode)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
@@ -857,6 +867,7 @@ class UnderlyingValve(UnderlyingEntity):
|
||||
_hvac_mode: HVACMode
|
||||
# This is the percentage of opening int integer (from 0 to 100)
|
||||
_percent_open: int
|
||||
_last_sent_temperature = None
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, thermostat: Any, valve_entity_id: str
|
||||
@@ -875,13 +886,12 @@ class UnderlyingValve(UnderlyingEntity):
|
||||
self._percent_open = self._thermostat.valve_open_percent
|
||||
self._valve_entity_id = valve_entity_id
|
||||
|
||||
async def send_percent_open(self):
|
||||
"""Send the percent open to the underlying valve"""
|
||||
# This may fails if called after shutdown
|
||||
async def _send_value_to_number(self, number_entity_id: str, value: int):
|
||||
"""Send a value to a number entity"""
|
||||
try:
|
||||
data = {"value": self._percent_open}
|
||||
target = {ATTR_ENTITY_ID: self._entity_id}
|
||||
domain = self._entity_id.split(".")[0]
|
||||
data = {"value": value}
|
||||
target = {ATTR_ENTITY_ID: number_entity_id}
|
||||
domain = number_entity_id.split(".")[0]
|
||||
await self._hass.services.async_call(
|
||||
domain=domain,
|
||||
service=SERVICE_SET_VALUE,
|
||||
@@ -893,6 +903,11 @@ class UnderlyingValve(UnderlyingEntity):
|
||||
# This could happens in unit test if input_number domain is not yet loaded
|
||||
# raise err
|
||||
|
||||
async def send_percent_open(self):
|
||||
"""Send the percent open to the underlying valve"""
|
||||
# This may fails if called after shutdown
|
||||
return await self._send_value_to_number(self._entity_id, self._percent_open)
|
||||
|
||||
async def turn_off(self):
|
||||
"""Turn heater toggleable device off."""
|
||||
_LOGGER.debug("%s - Stopping underlying valve entity %s", self, self._entity_id)
|
||||
@@ -1020,6 +1035,47 @@ class UnderlyingSonoffTRVZB(UnderlyingValve):
|
||||
self._offset_calibration_entity_id = offset_calibration_entity_id
|
||||
self._opening_degree_entity_id = opening_degree_entity_id
|
||||
self._closing_degree_entity_id = closing_degree_entity_id
|
||||
self._is_min_max_initialized = False
|
||||
self._max_opening_degree = None
|
||||
self._min_offset_calibration = None
|
||||
|
||||
async def send_percent_open(self):
|
||||
"""Send the percent open to the underlying valve"""
|
||||
if not self._is_min_max_initialized:
|
||||
_LOGGER.debug(
|
||||
"%s - initialize min offset_calibration and max open_degree", self
|
||||
)
|
||||
self._max_opening_degree = self._hass.states.get(
|
||||
self._opening_degree_entity_id
|
||||
).attributes.get("max")
|
||||
self._min_offset_calibration = self._hass.states.get(
|
||||
self._offset_calibration_entity_id
|
||||
).attributes.get("min")
|
||||
|
||||
self._is_min_max_initialized = (
|
||||
self._max_opening_degree is not None
|
||||
and self._min_offset_calibration is not None
|
||||
)
|
||||
|
||||
if not self._is_min_max_initialized:
|
||||
_LOGGER.warning(
|
||||
"%s - impossible to initialize max_opening_degree or min_offset_calibration. Abort sending percent open to the valve. This could be a temporary message at startup."
|
||||
)
|
||||
return
|
||||
|
||||
# Send opening_degree
|
||||
await super().send_percent_open()
|
||||
|
||||
# Send closing_degree. TODO 100 hard-coded or take the max of the _closing_degree_entity_id ?
|
||||
await self._send_value_to_number(
|
||||
self._closing_degree_entity_id,
|
||||
self._max_opening_degree - self._percent_open,
|
||||
)
|
||||
|
||||
# send offset_calibration to the min value
|
||||
await self._send_value_to_number(
|
||||
self._offset_calibration_entity_id, self._min_offset_calibration
|
||||
)
|
||||
|
||||
@property
|
||||
def offset_calibration_entity_id(self) -> str:
|
||||
@@ -1035,3 +1091,10 @@ class UnderlyingSonoffTRVZB(UnderlyingValve):
|
||||
def closing_degree_entity_id(self) -> str:
|
||||
"""The offset_calibration_entity_id"""
|
||||
return self._closing_degree_entity_id
|
||||
|
||||
@property
|
||||
def hvac_modes(self) -> list[HVACMode]:
|
||||
"""Get the hvac_modes"""
|
||||
if not self.is_initialized:
|
||||
return []
|
||||
return [HVACMode.OFF, HVACMode.HEAT]
|
||||
|
||||
Reference in New Issue
Block a user