Compare commits
7 Commits
issue_662-
...
6.8.0.beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64968687f5 | ||
|
|
132c98a612 | ||
|
|
3113f3b57f | ||
|
|
f0526a0c93 | ||
|
|
c2677bf7c5 | ||
|
|
8e01e69585 | ||
|
|
643f061fe2 |
@@ -91,6 +91,48 @@ input_number:
|
||||
icon: mdi:thermostat
|
||||
unit_of_measurement: °C
|
||||
mode: box
|
||||
fake_offset_calibration1:
|
||||
name: Sonoff offset calibration 1
|
||||
min: -12
|
||||
max: 12
|
||||
icon: mdi:tune
|
||||
unit_of_measurement: °C
|
||||
mode: box
|
||||
fake_opening_degree1:
|
||||
name: Sonoff Opening degree 1
|
||||
min: 0
|
||||
max: 100
|
||||
icon: mdi:valve-open
|
||||
unit_of_measurement: "%"
|
||||
mode: box
|
||||
fake_closing_degree1:
|
||||
name: Sonoff Closing degree 1
|
||||
min: 0
|
||||
max: 100
|
||||
icon: mdi:valve-closed
|
||||
unit_of_measurement: "%"
|
||||
mode: box
|
||||
fake_offset_calibration2:
|
||||
name: Sonoff offset calibration 2
|
||||
min: -12
|
||||
max: 12
|
||||
icon: mdi:tune
|
||||
unit_of_measurement: °C
|
||||
mode: box
|
||||
fake_opening_degree2:
|
||||
name: Sonoff Opening degree 2
|
||||
min: 0
|
||||
max: 100
|
||||
icon: mdi:valve-open
|
||||
unit_of_measurement: "%"
|
||||
mode: box
|
||||
fake_closing_degree2:
|
||||
name: Sonoff Closing degree 2
|
||||
min: 0
|
||||
max: 100
|
||||
icon: mdi:valve-closed
|
||||
unit_of_measurement: "%"
|
||||
mode: box
|
||||
|
||||
input_boolean:
|
||||
# input_boolean to simulate the windows entity. Only for development environment.
|
||||
@@ -142,6 +184,12 @@ input_boolean:
|
||||
fake_presence_sensor1:
|
||||
name: Presence Sensor 1
|
||||
icon: mdi:home
|
||||
fake_valve_sonoff_trvzb1:
|
||||
name: Valve Sonoff TRVZB1
|
||||
icon: mdi:valve
|
||||
fake_valve_sonoff_trvzb2:
|
||||
name: Valve Sonoff TRVZB2
|
||||
icon: mdi:valve
|
||||
|
||||
climate:
|
||||
- platform: generic_thermostat
|
||||
@@ -185,6 +233,16 @@ climate:
|
||||
name: Underlying thermostat9
|
||||
heater: input_boolean.fake_heater_switch3
|
||||
target_sensor: input_number.fake_temperature_sensor1
|
||||
- platform: generic_thermostat
|
||||
name: Underlying Sonoff TRVZB1
|
||||
heater: input_boolean.fake_valve_sonoff_trvzb1
|
||||
target_sensor: input_number.fake_temperature_sensor1
|
||||
ac_mode: false
|
||||
- platform: generic_thermostat
|
||||
name: Underlying Sonoff TRVZB2
|
||||
heater: input_boolean.fake_valve_sonoff_trvzb2
|
||||
target_sensor: input_number.fake_temperature_sensor1
|
||||
ac_mode: false
|
||||
|
||||
input_datetime:
|
||||
fake_last_seen:
|
||||
@@ -238,14 +296,14 @@ switch:
|
||||
friendly_name: "Pilote chauffage SDB RDC"
|
||||
value_template: "{{ is_state_attr('switch_seche_serviettes_sdb_rdc', 'sensor_state', 'on') }}"
|
||||
turn_on:
|
||||
service: select.select_option
|
||||
action: select.select_option
|
||||
data:
|
||||
option: comfort
|
||||
target:
|
||||
entity_id: select.seche_serviettes_sdb_rdc_cable_outlet_mode
|
||||
|
||||
turn_off:
|
||||
service: select.select_option
|
||||
action: select.select_option
|
||||
data:
|
||||
option: comfort-2
|
||||
target:
|
||||
|
||||
@@ -87,10 +87,6 @@ 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."""
|
||||
|
||||
@@ -205,7 +201,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)
|
||||
_LOGGER.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
|
||||
@@ -478,7 +474,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)
|
||||
_LOGGER.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
|
||||
@@ -596,7 +592,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
|
||||
async def async_will_remove_from_hass(self):
|
||||
"""Try to force backup of entity"""
|
||||
_LOGGER_ENERGY.debug(
|
||||
_LOGGER.debug(
|
||||
"%s - force write before remove. Energy is %s", self, self.total_energy
|
||||
)
|
||||
# Force dump in background
|
||||
@@ -823,7 +819,7 @@ 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(
|
||||
_LOGGER.debug(
|
||||
"%s - get_my_previous_state restored energy is %s",
|
||||
self,
|
||||
self._total_energy,
|
||||
@@ -841,7 +837,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
"No previously saved temperature, setting to %s", self._target_temp
|
||||
)
|
||||
self._total_energy = 0
|
||||
_LOGGER_ENERGY.debug(
|
||||
_LOGGER.debug(
|
||||
"%s - get_my_previous_state no previous state energy is %s",
|
||||
self,
|
||||
self._total_energy,
|
||||
@@ -2668,7 +2664,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
"hvac_off_reason": self.hvac_off_reason,
|
||||
}
|
||||
|
||||
_LOGGER_ENERGY.debug(
|
||||
_LOGGER.debug(
|
||||
"%s - update_custom_attributes saved energy is %s",
|
||||
self,
|
||||
self.total_energy,
|
||||
@@ -2677,7 +2673,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
@overrides
|
||||
def async_write_ha_state(self):
|
||||
"""overrides to have log"""
|
||||
_LOGGER_ENERGY.debug(
|
||||
_LOGGER.debug(
|
||||
"%s - async_write_ha_state written state energy is %s",
|
||||
self,
|
||||
self._total_energy,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -162,7 +162,26 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
||||
if COMES_FROM in self._infos:
|
||||
del self._infos[COMES_FROM]
|
||||
|
||||
async def validate_input(self, data: dict) -> None:
|
||||
def check_sonoff_trvzb_nb_entities(self, data: dict) -> bool:
|
||||
"""Check the number of entities for Sonoff TRVZB"""
|
||||
ret = True
|
||||
if (
|
||||
self._infos.get(CONF_SONOFF_TRZB_MODE)
|
||||
and data.get(CONF_OFFSET_CALIBRATION_LIST) is not None
|
||||
):
|
||||
nb_unders = len(self._infos.get(CONF_UNDERLYING_LIST))
|
||||
nb_offset = len(data.get(CONF_OFFSET_CALIBRATION_LIST))
|
||||
nb_opening = len(data.get(CONF_OPENING_DEGREE_LIST))
|
||||
nb_closing = len(data.get(CONF_CLOSING_DEGREE_LIST))
|
||||
if (
|
||||
nb_unders != nb_offset
|
||||
or nb_unders != nb_opening
|
||||
or nb_unders != nb_closing
|
||||
):
|
||||
ret = False
|
||||
return ret
|
||||
|
||||
async def validate_input(self, data: dict, step_id) -> None:
|
||||
"""Validate the user input allows us to connect.
|
||||
|
||||
Data has the keys from STEP_*_DATA_SCHEMA with values provided by the user.
|
||||
@@ -178,6 +197,9 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
||||
CONF_POWER_SENSOR,
|
||||
CONF_MAX_POWER_SENSOR,
|
||||
CONF_PRESENCE_SENSOR,
|
||||
CONF_OFFSET_CALIBRATION_LIST,
|
||||
CONF_OPENING_DEGREE_LIST,
|
||||
CONF_CLOSING_DEGREE_LIST,
|
||||
]:
|
||||
d = data.get(conf, None) # pylint: disable=invalid-name
|
||||
if not isinstance(d, list):
|
||||
@@ -235,6 +257,11 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
||||
except ServiceConfigurationError as err:
|
||||
raise ServiceConfigurationError(conf) from err
|
||||
|
||||
# Check that the number of offet_calibration and opening_degree and closing_degree are equals
|
||||
# to the number of underlying entities
|
||||
if not self.check_sonoff_trvzb_nb_entities(data):
|
||||
raise SonoffTRVZBNbEntitiesIncorrect()
|
||||
|
||||
def check_config_complete(self, infos) -> bool:
|
||||
"""True if the config is now complete (ie all mandatory attributes are set)"""
|
||||
is_central_config = (
|
||||
@@ -330,6 +357,9 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
||||
):
|
||||
return False
|
||||
|
||||
if not self.check_sonoff_trvzb_nb_entities(infos):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def merge_user_input(self, data_schema: vol.Schema, user_input: dict):
|
||||
@@ -359,7 +389,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
||||
if user_input is not None:
|
||||
defaults.update(user_input or {})
|
||||
try:
|
||||
await self.validate_input(user_input)
|
||||
await self.validate_input(user_input, step_id)
|
||||
except UnknownEntity as err:
|
||||
errors[str(err)] = "unknown_entity"
|
||||
except WindowOpenDetectionMethod as err:
|
||||
@@ -370,6 +400,8 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
||||
errors[str(err)] = "service_configuration_format"
|
||||
except ConfigurationNotCompleteError as err:
|
||||
errors["base"] = "configuration_not_complete"
|
||||
except SonoffTRVZBNbEntitiesIncorrect as err:
|
||||
errors["base"] = "sonoff_trvzb_nb_entities_incorrect"
|
||||
except Exception: # pylint: disable=broad-except
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
errors["base"] = "unknown"
|
||||
@@ -456,6 +488,9 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
||||
]:
|
||||
menu_options.append("auto_start_stop")
|
||||
|
||||
if self._infos.get(CONF_SONOFF_TRZB_MODE) is True:
|
||||
menu_options.append("sonoff_trvzb")
|
||||
|
||||
menu_options.append("advanced")
|
||||
|
||||
if self.check_config_complete(self._infos):
|
||||
@@ -525,6 +560,24 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
||||
"""Handle the Type flow steps"""
|
||||
_LOGGER.debug("Into ConfigFlow.async_step_type user_input=%s", user_input)
|
||||
|
||||
if (
|
||||
self._infos[CONF_THERMOSTAT_TYPE] == CONF_THERMOSTAT_CLIMATE
|
||||
and user_input is not None
|
||||
and not user_input.get(CONF_SONOFF_TRZB_MODE)
|
||||
):
|
||||
# Remove TPI info
|
||||
for key in [
|
||||
PROPORTIONAL_FUNCTION_TPI,
|
||||
CONF_PROP_FUNCTION,
|
||||
CONF_TPI_COEF_INT,
|
||||
CONF_TPI_COEF_EXT,
|
||||
CONF_OFFSET_CALIBRATION_LIST,
|
||||
CONF_OPENING_DEGREE_LIST,
|
||||
CONF_CLOSING_DEGREE_LIST,
|
||||
]:
|
||||
if self._infos.get(key):
|
||||
del self._infos[key]
|
||||
|
||||
if self._infos[CONF_THERMOSTAT_TYPE] == CONF_THERMOSTAT_SWITCH:
|
||||
return await self.generic_step(
|
||||
"type", STEP_THERMOSTAT_SWITCH, user_input, self.async_step_menu
|
||||
@@ -568,6 +621,20 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
||||
|
||||
return await self.generic_step("auto_start_stop", schema, user_input, next_step)
|
||||
|
||||
async def async_step_sonoff_trvzb(
|
||||
self, user_input: dict | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle the Sonoff TRVZB configuration step"""
|
||||
_LOGGER.debug(
|
||||
"Into ConfigFlow.async_step_sonoff_trvzb user_input=%s", user_input
|
||||
)
|
||||
|
||||
schema = STEP_SONOFF_TRVZB
|
||||
self._infos[COMES_FROM] = None
|
||||
next_step = self.async_step_menu
|
||||
|
||||
return await self.generic_step("sonoff_trvzb", schema, user_input, next_step)
|
||||
|
||||
async def async_step_tpi(self, user_input: dict | None = None) -> FlowResult:
|
||||
"""Handle the TPI flow steps"""
|
||||
_LOGGER.debug("Into ConfigFlow.async_step_tpi user_input=%s", user_input)
|
||||
|
||||
@@ -141,6 +141,7 @@ STEP_THERMOSTAT_CLIMATE = vol.Schema( # pylint: disable=invalid-name
|
||||
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN, multiple=True),
|
||||
),
|
||||
vol.Optional(CONF_AC_MODE, default=False): cv.boolean,
|
||||
vol.Optional(CONF_SONOFF_TRZB_MODE, default=False): cv.boolean,
|
||||
vol.Optional(
|
||||
CONF_AUTO_REGULATION_MODE, default=CONF_AUTO_REGULATION_NONE
|
||||
): selector.SelectSelector(
|
||||
@@ -197,6 +198,31 @@ STEP_AUTO_START_STOP = vol.Schema( # pylint: disable=invalid-name
|
||||
}
|
||||
)
|
||||
|
||||
STEP_SONOFF_TRVZB = vol.Schema( # pylint: disable=invalid-name
|
||||
{
|
||||
vol.Required(CONF_OFFSET_CALIBRATION_LIST): selector.EntitySelector(
|
||||
selector.EntitySelectorConfig(
|
||||
domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN], multiple=True
|
||||
),
|
||||
),
|
||||
vol.Required(CONF_OPENING_DEGREE_LIST): selector.EntitySelector(
|
||||
selector.EntitySelectorConfig(
|
||||
domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN], multiple=True
|
||||
),
|
||||
),
|
||||
vol.Required(CONF_CLOSING_DEGREE_LIST): selector.EntitySelector(
|
||||
selector.EntitySelectorConfig(
|
||||
domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN], multiple=True
|
||||
),
|
||||
),
|
||||
vol.Required(CONF_PROP_FUNCTION, default=PROPORTIONAL_FUNCTION_TPI): vol.In(
|
||||
[
|
||||
PROPORTIONAL_FUNCTION_TPI,
|
||||
]
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
STEP_TPI_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
|
||||
{
|
||||
vol.Required(CONF_USE_TPI_CENTRAL_CONFIG, default=True): cv.boolean,
|
||||
|
||||
@@ -94,6 +94,7 @@ CONF_USE_POWER_FEATURE = "use_power_feature"
|
||||
CONF_USE_CENTRAL_BOILER_FEATURE = "use_central_boiler_feature"
|
||||
CONF_USE_AUTO_START_STOP_FEATURE = "use_auto_start_stop_feature"
|
||||
CONF_AC_MODE = "ac_mode"
|
||||
CONF_SONOFF_TRZB_MODE = "sonoff_trvzb_mode"
|
||||
CONF_WINDOW_AUTO_OPEN_THRESHOLD = "window_auto_open_threshold"
|
||||
CONF_WINDOW_AUTO_CLOSE_THRESHOLD = "window_auto_close_threshold"
|
||||
CONF_WINDOW_AUTO_MAX_DURATION = "window_auto_max_duration"
|
||||
@@ -115,6 +116,9 @@ CONF_AUTO_FAN_MEDIUM = "auto_fan_medium"
|
||||
CONF_AUTO_FAN_HIGH = "auto_fan_high"
|
||||
CONF_AUTO_FAN_TURBO = "auto_fan_turbo"
|
||||
CONF_STEP_TEMPERATURE = "step_temperature"
|
||||
CONF_OFFSET_CALIBRATION_LIST = "offset_calibration_entity_ids"
|
||||
CONF_OPENING_DEGREE_LIST = "opening_degree_entity_ids"
|
||||
CONF_CLOSING_DEGREE_LIST = "closing_degree_entity_ids"
|
||||
|
||||
# Deprecated
|
||||
CONF_HEATER = "heater_entity_id"
|
||||
@@ -287,6 +291,7 @@ ALL_CONF = (
|
||||
CONF_USE_POWER_FEATURE,
|
||||
CONF_USE_CENTRAL_BOILER_FEATURE,
|
||||
CONF_AC_MODE,
|
||||
CONF_SONOFF_TRZB_MODE,
|
||||
CONF_AUTO_REGULATION_MODE,
|
||||
CONF_AUTO_REGULATION_DTEMP,
|
||||
CONF_AUTO_REGULATION_PERIOD_MIN,
|
||||
@@ -505,6 +510,11 @@ class ConfigurationNotCompleteError(HomeAssistantError):
|
||||
"""Error the configuration is not complete"""
|
||||
|
||||
|
||||
class SonoffTRVZBNbEntitiesIncorrect(HomeAssistantError):
|
||||
"""Error to indicate there is an error in the configuration of the Sonoff TRVZB.
|
||||
The number of specific entities is incorrect."""
|
||||
|
||||
|
||||
class overrides: # pylint: disable=invalid-name
|
||||
"""An annotation to inform overrides"""
|
||||
|
||||
|
||||
@@ -14,6 +14,6 @@
|
||||
"quality_scale": "silver",
|
||||
"requirements": [],
|
||||
"ssdp": [],
|
||||
"version": "6.7.0",
|
||||
"version": "6.8.0.alpha",
|
||||
"zeroconf": []
|
||||
}
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"presence": "Presence detection",
|
||||
"advanced": "Advanced parameters",
|
||||
"auto_start_stop": "Auto start and stop",
|
||||
"sonoff_trvzb": "Sonoff TRVZB configuration",
|
||||
"finalize": "All done",
|
||||
"configuration_not_complete": "Configuration not complete"
|
||||
}
|
||||
@@ -76,6 +77,7 @@
|
||||
"heater_keep_alive": "Switch keep-alive interval in seconds",
|
||||
"proportional_function": "Algorithm",
|
||||
"ac_mode": "AC mode",
|
||||
"sonoff_trvzb_mode": "SONOFF TRVZB mode",
|
||||
"auto_regulation_mode": "Self-regulation",
|
||||
"auto_regulation_dtemp": "Regulation threshold",
|
||||
"auto_regulation_periode_min": "Regulation minimum period",
|
||||
@@ -88,6 +90,7 @@
|
||||
"heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.",
|
||||
"proportional_function": "Algorithm to use (TPI is the only one for now)",
|
||||
"ac_mode": "Use the Air Conditioning (AC) mode",
|
||||
"sonoff_trvzb_mode": "The underlyings are SONOFF TRVZB. You have to configure some extra entities in the specific menu option 'Sonoff trvzb configuration'",
|
||||
"auto_regulation_mode": "Auto adjustment of the target temperature",
|
||||
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
|
||||
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
|
||||
@@ -203,6 +206,34 @@
|
||||
"security_default_on_percent": "The default heating power percent value in safety preset. Set to 0 to switch off heater in safety preset",
|
||||
"use_advanced_central_config": "Check to use the central advanced configuration. Uncheck to use a specific advanced configuration for this VTherm"
|
||||
}
|
||||
},
|
||||
"central_boiler": {
|
||||
"title": "Control of the central boiler",
|
||||
"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",
|
||||
"central_boiler_deactivation_service": "Command to turn-off"
|
||||
},
|
||||
"data_description": {
|
||||
"central_boiler_activation_service": "Command to turn-on the central boiler formatted like entity_id/service_name[/attribut:valeur]",
|
||||
"central_boiler_deactivation_service": "Command to turn-off the central boiler formatted like entity_id/service_name[/attribut:valeur]"
|
||||
}
|
||||
},
|
||||
"sonoff_trvzb": {
|
||||
"title": "Sonoff TRVZB configuration",
|
||||
"description": "Specific Sonoff TRVZB configuration",
|
||||
"data": {
|
||||
"offset_calibration_entity_ids": "Offset calibration entities",
|
||||
"opening_degree_entity_ids": "Opening degree entities",
|
||||
"closing_degree_entity_ids": "Closing degree entities",
|
||||
"proportional_function": "Algorithm"
|
||||
},
|
||||
"data_description": {
|
||||
"offset_calibration_entity_ids": "The list of the 'offset calibration' entities. There should be one per underlying climate entities",
|
||||
"opening_degree_entity_ids": "The list of the 'opening degree' entities. There should be one per underlying climate entities",
|
||||
"closing_degree_entity_ids": "The list of the 'closing degree' entities. There should be one per underlying climate entities",
|
||||
"proportional_function": "Algorithm to use (TPI is the only one for now)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -243,6 +274,7 @@
|
||||
"presence": "Presence detection",
|
||||
"advanced": "Advanced parameters",
|
||||
"auto_start_stop": "Auto start and stop",
|
||||
"sonoff_trvzb": "Sonoff TRVZB configuration",
|
||||
"finalize": "All done",
|
||||
"configuration_not_complete": "Configuration not complete"
|
||||
}
|
||||
@@ -291,6 +323,7 @@
|
||||
"heater_keep_alive": "Switch keep-alive interval in seconds",
|
||||
"proportional_function": "Algorithm",
|
||||
"ac_mode": "AC mode",
|
||||
"sonoff_trvzb_mode": "SONOFF TRVZB mode",
|
||||
"auto_regulation_mode": "Self-regulation",
|
||||
"auto_regulation_dtemp": "Regulation threshold",
|
||||
"auto_regulation_periode_min": "Regulation minimum period",
|
||||
@@ -303,6 +336,7 @@
|
||||
"heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.",
|
||||
"proportional_function": "Algorithm to use (TPI is the only one for now)",
|
||||
"ac_mode": "Use the Air Conditioning (AC) mode",
|
||||
"sonoff_trvzb_mode": "The underlyings are SONOFF TRVZB. You have to configure some extra entities in the specific menu option 'Sonoff trvzb configuration'",
|
||||
"auto_regulation_mode": "Auto adjustment of the target temperature",
|
||||
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
|
||||
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
|
||||
@@ -418,6 +452,34 @@
|
||||
"security_default_on_percent": "The default heating power percent value in safety preset. Set to 0 to switch off heater in safety preset",
|
||||
"use_advanced_central_config": "Check to use the central advanced configuration. Uncheck to use a specific advanced configuration for this VTherm"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"central_boiler_deactivation_service": "Command to turn-off"
|
||||
},
|
||||
"data_description": {
|
||||
"central_boiler_activation_service": "Command to turn-on the central boiler formatted like entity_id/service_name[/attribut:valeur]",
|
||||
"central_boiler_deactivation_service": "Command to turn-off the central boiler formatted like entity_id/service_name[/attribut:valeur]"
|
||||
}
|
||||
},
|
||||
"sonoff_trvzb": {
|
||||
"title": "Sonoff TRVZB configuration - {name}",
|
||||
"description": "Specific Sonoff TRVZB configuration",
|
||||
"data": {
|
||||
"offset_calibration_entity_ids": "Offset calibration entities",
|
||||
"opening_degree_entity_ids": "Opening degree entities",
|
||||
"closing_degree_entity_ids": "Closing degree entities",
|
||||
"proportional_function": "Algorithm"
|
||||
},
|
||||
"data_description": {
|
||||
"offset_calibration_entity_ids": "The list of the 'offset calibration' entities. There should be one per underlying climate entities",
|
||||
"opening_degree_entity_ids": "The list of the 'opening degree' entities. There should be one per underlying climate entities",
|
||||
"closing_degree_entity_ids": "The list of the 'closing degree' entities. There should be one per underlying climate entities",
|
||||
"proportional_function": "Algorithm to use (TPI is the only one for now)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -425,7 +487,8 @@
|
||||
"unknown_entity": "Unknown entity id",
|
||||
"window_open_detection_method": "Only one window open detection method should be used. Use either window sensor or automatic detection through temperature threshold but not both",
|
||||
"no_central_config": "You cannot check 'use central configuration' because no central configuration was found. You need to create a Versatile Thermostat of type 'Central Configuration' to use it.",
|
||||
"service_configuration_format": "The format of the service configuration is wrong"
|
||||
"service_configuration_format": "The format of the service configuration is wrong",
|
||||
"sonoff_trvzb_nb_entities_incorrect": "The number of specific entities for Sonoff TRVZB should be equal to the number of underlyings"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "Device is already configured"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# pylint: disable=line-too-long, too-many-lines
|
||||
""" A climate over switch classe """
|
||||
# pylint: disable=line-too-long, too-many-lines, abstract-method
|
||||
""" A climate over climate classe """
|
||||
import logging
|
||||
from datetime import timedelta, datetime
|
||||
|
||||
@@ -31,10 +31,6 @@ 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,
|
||||
@@ -64,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",
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
@@ -105,13 +99,12 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
||||
super().post_init(config_entry)
|
||||
|
||||
for climate in config_entry.get(CONF_UNDERLYING_LIST):
|
||||
self._underlyings.append(
|
||||
UnderlyingClimate(
|
||||
hass=self._hass,
|
||||
thermostat=self,
|
||||
climate_entity_id=climate,
|
||||
)
|
||||
under = UnderlyingClimate(
|
||||
hass=self._hass,
|
||||
thermostat=self,
|
||||
climate_entity_id=climate,
|
||||
)
|
||||
self._underlyings.append(under)
|
||||
|
||||
self.choose_auto_regulation_mode(
|
||||
config_entry.get(CONF_AUTO_REGULATION_MODE)
|
||||
@@ -606,14 +599,14 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
||||
|
||||
if self._total_energy is None:
|
||||
self._total_energy = added_energy
|
||||
_LOGGER_ENERGY.debug(
|
||||
_LOGGER.debug(
|
||||
"%s - incremente_energy set energy is %s",
|
||||
self,
|
||||
self._total_energy,
|
||||
)
|
||||
else:
|
||||
self._total_energy += added_energy
|
||||
_LOGGER_ENERGY.debug(
|
||||
_LOGGER.debug(
|
||||
"%s - incremente_energy incremented energy is %s",
|
||||
self,
|
||||
self._total_energy,
|
||||
@@ -966,7 +959,7 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
||||
if not continu:
|
||||
return ret
|
||||
else:
|
||||
_LOGGER.debug("%s - auto start/stop is disabled")
|
||||
_LOGGER.debug("%s - auto start/stop is disabled", self)
|
||||
|
||||
# Continue the normal async_control_heating
|
||||
|
||||
@@ -1115,6 +1108,14 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
def current_humidity(self) -> float | None:
|
||||
"""Return the humidity."""
|
||||
if self.underlying_entity(0):
|
||||
return self.underlying_entity(0).current_humidity
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
def is_aux_heat(self) -> bool | None:
|
||||
"""Return true if aux heater.
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
# 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
|
||||
|
||||
from .underlyings import UnderlyingSonoffTRVZB
|
||||
|
||||
# from .commons import NowClass, round_to_nearest
|
||||
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
|
||||
|
||||
# from .vtherm_api import VersatileThermostatAPI
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ThermostatOverSonoffTRVZB(ThermostatOverClimate):
|
||||
"""This class represent a VTherm over a Sonoff TRVZB climate"""
|
||||
|
||||
_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
|
||||
):
|
||||
"""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 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]
|
||||
closing = config_entry.get(CONF_CLOSING_DEGREE_LIST)[idx]
|
||||
under = UnderlyingSonoffTRVZB(
|
||||
hass=self._hass,
|
||||
thermostat=self,
|
||||
offset_calibration_entity_id=offset,
|
||||
opening_degree_entity_id=opening,
|
||||
closing_degree_entity_id=closing,
|
||||
)
|
||||
self._underlyings_sonoff_trvzb.append(under)
|
||||
|
||||
@overrides
|
||||
def update_custom_attributes(self):
|
||||
"""Custom attributes"""
|
||||
super().update_custom_attributes()
|
||||
|
||||
self._attr_extra_state_attributes["is_over_sonoff_trvzb"] = (
|
||||
self.is_over_sonoff_trvzb
|
||||
)
|
||||
|
||||
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"] = (
|
||||
self._prop_algorithm.on_percent
|
||||
)
|
||||
self._attr_extra_state_attributes["power_percent"] = self.power_percent
|
||||
self._attr_extra_state_attributes["on_time_sec"] = (
|
||||
self._prop_algorithm.on_time_sec
|
||||
)
|
||||
self._attr_extra_state_attributes["off_time_sec"] = (
|
||||
self._prop_algorithm.off_time_sec
|
||||
)
|
||||
self._attr_extra_state_attributes["cycle_min"] = self._cycle_min
|
||||
self._attr_extra_state_attributes["function"] = self._proportional_function
|
||||
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",
|
||||
self,
|
||||
self._attr_extra_state_attributes,
|
||||
)
|
||||
|
||||
@overrides
|
||||
def recalculate(self):
|
||||
"""A utility function to force the calculation of a the algo and
|
||||
update the custom attributes and write the state
|
||||
"""
|
||||
_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
|
||||
def is_over_sonoff_trvzb(self) -> bool:
|
||||
"""True if the Thermostat is over_sonoff_trvzb"""
|
||||
return True
|
||||
|
||||
@property
|
||||
def power_percent(self) -> float | None:
|
||||
"""Get the current on_percent value"""
|
||||
if self._prop_algorithm:
|
||||
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
|
||||
@@ -21,29 +21,24 @@ 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."""
|
||||
|
||||
_entity_component_unrecorded_attributes = (
|
||||
BaseThermostat._entity_component_unrecorded_attributes.union(
|
||||
frozenset(
|
||||
{
|
||||
"is_over_switch",
|
||||
"is_inversed",
|
||||
"underlying_entities",
|
||||
"on_time_sec",
|
||||
"off_time_sec",
|
||||
"cycle_min",
|
||||
"function",
|
||||
"tpi_coef_int",
|
||||
"tpi_coef_ext",
|
||||
"power_percent",
|
||||
}
|
||||
)
|
||||
_entity_component_unrecorded_attributes = BaseThermostat._entity_component_unrecorded_attributes.union( # pylint: disable=protected-access
|
||||
frozenset(
|
||||
{
|
||||
"is_over_switch",
|
||||
"is_inversed",
|
||||
"underlying_entities",
|
||||
"on_time_sec",
|
||||
"off_time_sec",
|
||||
"cycle_min",
|
||||
"function",
|
||||
"tpi_coef_int",
|
||||
"tpi_coef_ext",
|
||||
"power_percent",
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
@@ -185,14 +180,14 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
|
||||
|
||||
if self._total_energy is None:
|
||||
self._total_energy = added_energy
|
||||
_LOGGER_ENERGY.debug(
|
||||
_LOGGER.debug(
|
||||
"%s - incremente_energy set energy is %s",
|
||||
self,
|
||||
self._total_energy,
|
||||
)
|
||||
else:
|
||||
self._total_energy += added_energy
|
||||
_LOGGER_ENERGY.debug(
|
||||
_LOGGER.debug(
|
||||
"%s - incremente_energy increment energy is %s",
|
||||
self,
|
||||
self._total_energy,
|
||||
|
||||
@@ -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
|
||||
@@ -25,9 +25,6 @@ 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"""
|
||||
@@ -267,14 +264,14 @@ class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=a
|
||||
|
||||
if self._total_energy is None:
|
||||
self._total_energy = added_energy
|
||||
_LOGGER_ENERGY.debug(
|
||||
_LOGGER.debug(
|
||||
"%s - incremente_energy set energy is %s",
|
||||
self,
|
||||
self._total_energy,
|
||||
)
|
||||
else:
|
||||
self._total_energy += added_energy
|
||||
_LOGGER_ENERGY.debug(
|
||||
_LOGGER.debug(
|
||||
"%s - get_my_previous_state increment energy is %s",
|
||||
self,
|
||||
self._total_energy,
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"presence": "Presence detection",
|
||||
"advanced": "Advanced parameters",
|
||||
"auto_start_stop": "Auto start and stop",
|
||||
"sonoff_trvzb": "Sonoff TRVZB configuration",
|
||||
"finalize": "All done",
|
||||
"configuration_not_complete": "Configuration not complete"
|
||||
}
|
||||
@@ -64,7 +65,7 @@
|
||||
"use_motion_feature": "Use motion detection",
|
||||
"use_power_feature": "Use power management",
|
||||
"use_presence_feature": "Use presence detection",
|
||||
"use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after selecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page",
|
||||
"use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after seecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page",
|
||||
"use_auto_start_stop_feature": "Use the auto start and stop feature"
|
||||
}
|
||||
},
|
||||
@@ -76,6 +77,7 @@
|
||||
"heater_keep_alive": "Switch keep-alive interval in seconds",
|
||||
"proportional_function": "Algorithm",
|
||||
"ac_mode": "AC mode",
|
||||
"sonoff_trvzb_mode": "SONOFF TRVZB mode",
|
||||
"auto_regulation_mode": "Self-regulation",
|
||||
"auto_regulation_dtemp": "Regulation threshold",
|
||||
"auto_regulation_periode_min": "Regulation minimum period",
|
||||
@@ -88,6 +90,7 @@
|
||||
"heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.",
|
||||
"proportional_function": "Algorithm to use (TPI is the only one for now)",
|
||||
"ac_mode": "Use the Air Conditioning (AC) mode",
|
||||
"sonoff_trvzb_mode": "The underlyings are SONOFF TRVZB. You have to configure some extra entities in the specific menu option 'Sonoff trvzb configuration'",
|
||||
"auto_regulation_mode": "Auto adjustment of the target temperature",
|
||||
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
|
||||
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
|
||||
@@ -203,6 +206,34 @@
|
||||
"security_default_on_percent": "The default heating power percent value in safety preset. Set to 0 to switch off heater in safety preset",
|
||||
"use_advanced_central_config": "Check to use the central advanced configuration. Uncheck to use a specific advanced configuration for this VTherm"
|
||||
}
|
||||
},
|
||||
"central_boiler": {
|
||||
"title": "Control of the central boiler",
|
||||
"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",
|
||||
"central_boiler_deactivation_service": "Command to turn-off"
|
||||
},
|
||||
"data_description": {
|
||||
"central_boiler_activation_service": "Command to turn-on the central boiler formatted like entity_id/service_name[/attribut:valeur]",
|
||||
"central_boiler_deactivation_service": "Command to turn-off the central boiler formatted like entity_id/service_name[/attribut:valeur]"
|
||||
}
|
||||
},
|
||||
"sonoff_trvzb": {
|
||||
"title": "Sonoff TRVZB configuration",
|
||||
"description": "Specific Sonoff TRVZB configuration",
|
||||
"data": {
|
||||
"offset_calibration_entity_ids": "Offset calibration entities",
|
||||
"opening_degree_entity_ids": "Opening degree entities",
|
||||
"closing_degree_entity_ids": "Closing degree entities",
|
||||
"proportional_function": "Algorithm"
|
||||
},
|
||||
"data_description": {
|
||||
"offset_calibration_entity_ids": "The list of the 'offset calibration' entities. There should be one per underlying climate entities",
|
||||
"opening_degree_entity_ids": "The list of the 'opening degree' entities. There should be one per underlying climate entities",
|
||||
"closing_degree_entity_ids": "The list of the 'closing degree' entities. There should be one per underlying climate entities",
|
||||
"proportional_function": "Algorithm to use (TPI is the only one for now)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -243,6 +274,7 @@
|
||||
"presence": "Presence detection",
|
||||
"advanced": "Advanced parameters",
|
||||
"auto_start_stop": "Auto start and stop",
|
||||
"sonoff_trvzb": "Sonoff TRVZB configuration",
|
||||
"finalize": "All done",
|
||||
"configuration_not_complete": "Configuration not complete"
|
||||
}
|
||||
@@ -291,6 +323,7 @@
|
||||
"heater_keep_alive": "Switch keep-alive interval in seconds",
|
||||
"proportional_function": "Algorithm",
|
||||
"ac_mode": "AC mode",
|
||||
"sonoff_trvzb_mode": "SONOFF TRVZB mode",
|
||||
"auto_regulation_mode": "Self-regulation",
|
||||
"auto_regulation_dtemp": "Regulation threshold",
|
||||
"auto_regulation_periode_min": "Regulation minimum period",
|
||||
@@ -303,6 +336,7 @@
|
||||
"heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.",
|
||||
"proportional_function": "Algorithm to use (TPI is the only one for now)",
|
||||
"ac_mode": "Use the Air Conditioning (AC) mode",
|
||||
"sonoff_trvzb_mode": "The underlyings are SONOFF TRVZB. You have to configure some extra entities in the specific menu option 'Sonoff trvzb configuration'",
|
||||
"auto_regulation_mode": "Auto adjustment of the target temperature",
|
||||
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
|
||||
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
|
||||
@@ -418,6 +452,34 @@
|
||||
"security_default_on_percent": "The default heating power percent value in safety preset. Set to 0 to switch off heater in safety preset",
|
||||
"use_advanced_central_config": "Check to use the central advanced configuration. Uncheck to use a specific advanced configuration for this VTherm"
|
||||
}
|
||||
},
|
||||
"central_boiler": {
|
||||
"title": "Control of the central boiler",
|
||||
"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",
|
||||
"central_boiler_deactivation_service": "Command to turn-off"
|
||||
},
|
||||
"data_description": {
|
||||
"central_boiler_activation_service": "Command to turn-on the central boiler formatted like entity_id/service_name[/attribut:valeur]",
|
||||
"central_boiler_deactivation_service": "Command to turn-off the central boiler formatted like entity_id/service_name[/attribut:valeur]"
|
||||
}
|
||||
},
|
||||
"sonoff_trvzb": {
|
||||
"title": "Sonoff TRVZB configuration",
|
||||
"description": "Specific Sonoff TRVZB configuration",
|
||||
"data": {
|
||||
"offset_calibration_entity_ids": "Offset calibration entities",
|
||||
"opening_degree_entity_ids": "Opening degree entities",
|
||||
"closing_degree_entity_ids": "Closing degree entities",
|
||||
"proportional_function": "Algorithm"
|
||||
},
|
||||
"data_description": {
|
||||
"offset_calibration_entity_ids": "The list of the 'offset calibration' entities. There should be one per underlying climate entities",
|
||||
"opening_degree_entity_ids": "The list of the 'opening degree' entities. There should be one per underlying climate entities",
|
||||
"closing_degree_entity_ids": "The list of the 'closing degree' entities. There should be one per underlying climate entities",
|
||||
"proportional_function": "Algorithm to use (TPI is the only one for now)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -425,7 +487,8 @@
|
||||
"unknown_entity": "Unknown entity id",
|
||||
"window_open_detection_method": "Only one window open detection method should be used. Use either window sensor or automatic detection through temperature threshold but not both",
|
||||
"no_central_config": "You cannot check 'use central configuration' because no central configuration was found. You need to create a Versatile Thermostat of type 'Central Configuration' to use it.",
|
||||
"service_configuration_format": "The format of the service configuration is wrong"
|
||||
"service_configuration_format": "The format of the service configuration is wrong",
|
||||
"sonoff_trvzb_nb_entities_incorrect": "The number of specific entities for Sonoff TRVZB should be equal to the number of underlyings"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "Device is already configured"
|
||||
@@ -547,4 +610,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@
|
||||
"presence": "Détection de présence",
|
||||
"advanced": "Paramètres avancés",
|
||||
"auto_start_stop": "Allumage/extinction automatique",
|
||||
"sonoff_trvzb": "Configuration spécifique à Sonoff TRVZB",
|
||||
"finalize": "Finaliser la création",
|
||||
"configuration_not_complete": "Configuration incomplète"
|
||||
}
|
||||
@@ -72,48 +73,28 @@
|
||||
"title": "Entité(s) liée(s)",
|
||||
"description": "Attributs de(s) l'entité(s) liée(s)",
|
||||
"data": {
|
||||
"heater_entity_id": "1er radiateur",
|
||||
"heater_entity2_id": "2ème radiateur",
|
||||
"heater_entity3_id": "3ème radiateur",
|
||||
"heater_entity4_id": "4ème radiateur",
|
||||
"underlying_entity_ids": "Les équipements à controller",
|
||||
"heater_keep_alive": "keep-alive (sec)",
|
||||
"proportional_function": "Algorithme",
|
||||
"climate_entity_id": "Thermostat sous-jacent",
|
||||
"climate_entity2_id": "2ème thermostat sous-jacent",
|
||||
"climate_entity3_id": "3ème thermostat sous-jacent",
|
||||
"climate_entity4_id": "4ème thermostat sous-jacent",
|
||||
"ac_mode": "AC mode ?",
|
||||
"valve_entity_id": "1ère valve number",
|
||||
"valve_entity2_id": "2ème valve number",
|
||||
"valve_entity3_id": "3ème valve number",
|
||||
"valve_entity4_id": "4ème valve number",
|
||||
"sonoff_trvzb_mode": "Mode Sonoff TRVZB",
|
||||
"auto_regulation_mode": "Auto-régulation",
|
||||
"auto_regulation_dtemp": "Seuil de régulation",
|
||||
"auto_regulation_periode_min": "Période minimale de régulation",
|
||||
"auto_regulation_use_device_temp": "Utiliser la température interne du sous-jacent",
|
||||
"auto_regulation_use_device_temp": "Compenser la température interne du sous-jacent",
|
||||
"inverse_switch_command": "Inverser la commande",
|
||||
"auto_fan_mode": " Auto ventilation mode"
|
||||
},
|
||||
"data_description": {
|
||||
"heater_entity_id": "Entity id du 1er radiateur obligatoire",
|
||||
"heater_entity2_id": "Optionnel entity id du 2ème radiateur",
|
||||
"heater_entity3_id": "Optionnel entity id du 3ème radiateur",
|
||||
"heater_entity4_id": "Optionnel entity id du 4ème radiateur",
|
||||
"underlying_entity_ids": "La liste des équipements qui seront controlés par ce VTherm",
|
||||
"heater_keep_alive": "Intervalle de rafraichissement du switch en secondes. Laisser vide pour désactiver. À n'utiliser que pour les switchs qui le nécessite.",
|
||||
"proportional_function": "Algorithme à utiliser (Seul TPI est disponible pour l'instant)",
|
||||
"climate_entity_id": "Entity id du thermostat sous-jacent",
|
||||
"climate_entity2_id": "Entity id du 2ème thermostat sous-jacent",
|
||||
"climate_entity3_id": "Entity id du 3ème thermostat sous-jacent",
|
||||
"climate_entity4_id": "Entity id du 4ème thermostat sous-jacent",
|
||||
"ac_mode": "Utilisation du mode Air Conditionné (AC)",
|
||||
"valve_entity_id": "Entity id de la 1ère valve",
|
||||
"valve_entity2_id": "Entity id de la 2ème valve",
|
||||
"valve_entity3_id": "Entity id de la 3ème valve",
|
||||
"valve_entity4_id": "Entity id de la 4ème valve",
|
||||
"sonoff_trvzb_mode": "Les équipements sont des Sonoff TRVZB. Vous devez configurer les entités dédiées dans le menu 'Configuration Sonoff TRVZB'",
|
||||
"auto_regulation_mode": "Ajustement automatique de la température cible",
|
||||
"auto_regulation_dtemp": "Le seuil en ° (ou % pour les valves) en-dessous duquel la régulation ne sera pas envoyée",
|
||||
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation",
|
||||
"auto_regulation_use_device_temp": "Utiliser la temperature interne du sous-jacent pour accélérer l'auto-régulation",
|
||||
"auto_regulation_use_device_temp": "Compenser la temperature interne du sous-jacent pour accélérer l'auto-régulation",
|
||||
"inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode",
|
||||
"auto_fan_mode": "Active la ventilation automatiquement en cas d'écart important"
|
||||
}
|
||||
@@ -237,6 +218,22 @@
|
||||
"central_boiler_activation_service": "Commande à éxecuter pour allumer la chaudière centrale au format entity_id/service_name[/attribut:valeur]",
|
||||
"central_boiler_deactivation_service": "Commande à éxecuter pour étiendre la chaudière centrale au format entity_id/service_name[/attribut:valeur]"
|
||||
}
|
||||
},
|
||||
"sonoff_trvzb": {
|
||||
"title": "Configuration Sonoff TRVZB",
|
||||
"description": "Configuration spécifique des Sonoff TRVZB",
|
||||
"data": {
|
||||
"offset_calibration_entity_ids": "Entités de 'Offset calibration'",
|
||||
"opening_degree_entity_ids": "Entités de 'Opening degree'",
|
||||
"closing_degree_entity_ids": "Entités de 'Closing degree'",
|
||||
"proportional_function": "Algorithme"
|
||||
},
|
||||
"data_description": {
|
||||
"offset_calibration_entity_ids": "La liste des entités 'offset calibration' entities. Il doit y en avoir une par entité climate sous-jacente",
|
||||
"opening_degree_entity_ids": "La liste des entités 'opening degree' entities. Il doit y en avoir une par entité climate sous-jacente",
|
||||
"closing_degree_entity_ids": "La liste des entités 'closing degree' entities. Il doit y en avoir une par entité climate sous-jacente",
|
||||
"proportional_function": "Algorithme à utiliser (seulement TPI est disponible)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -262,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",
|
||||
@@ -277,6 +274,7 @@
|
||||
"presence": "Détection de présence",
|
||||
"advanced": "Paramètres avancés",
|
||||
"auto_start_stop": "Allumage/extinction automatique",
|
||||
"sonoff_trvzb": "Configuration spécifique à Sonoff TRVZB",
|
||||
"finalize": "Finaliser les modifications",
|
||||
"configuration_not_complete": "Configuration incomplète"
|
||||
}
|
||||
@@ -318,51 +316,31 @@
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"title": "Entités - {name}",
|
||||
"title": "Entité(s) liée(s) - {name}",
|
||||
"description": "Attributs de(s) l'entité(s) liée(s)",
|
||||
"data": {
|
||||
"heater_entity_id": "1er radiateur",
|
||||
"heater_entity2_id": "2ème radiateur",
|
||||
"heater_entity3_id": "3ème radiateur",
|
||||
"heater_entity4_id": "4ème radiateur",
|
||||
"heater_keep_alive": "Keep-alive (sec)",
|
||||
"underlying_entity_ids": "Les équipements à controller",
|
||||
"heater_keep_alive": "keep-alive (sec)",
|
||||
"proportional_function": "Algorithme",
|
||||
"climate_entity_id": "Thermostat sous-jacent",
|
||||
"climate_entity2_id": "2ème thermostat sous-jacent",
|
||||
"climate_entity3_id": "3ème thermostat sous-jacent",
|
||||
"climate_entity4_id": "4ème thermostat sous-jacent",
|
||||
"ac_mode": "AC mode ?",
|
||||
"valve_entity_id": "1ère valve",
|
||||
"valve_entity2_id": "2ème valve",
|
||||
"valve_entity3_id": "3ème valve",
|
||||
"valve_entity4_id": "4ème valve",
|
||||
"auto_regulation_mode": "Auto-regulation",
|
||||
"sonoff_trvzb_mode": "Mode Sonoff TRVZB",
|
||||
"auto_regulation_mode": "Auto-régulation",
|
||||
"auto_regulation_dtemp": "Seuil de régulation",
|
||||
"auto_regulation_periode_min": "Période minimale de régulation",
|
||||
"auto_regulation_use_device_temp": "Utiliser la température interne du sous-jacent",
|
||||
"auto_regulation_use_device_temp": "Compenser la température interne du sous-jacent",
|
||||
"inverse_switch_command": "Inverser la commande",
|
||||
"auto_fan_mode": "Auto fan mode"
|
||||
"auto_fan_mode": " Auto ventilation mode"
|
||||
},
|
||||
"data_description": {
|
||||
"heater_entity_id": "Entity id du 1er radiateur obligatoire",
|
||||
"heater_entity2_id": "Optionnel entity id du 2ème radiateur",
|
||||
"heater_entity3_id": "Optionnel entity id du 3ème radiateur",
|
||||
"heater_entity4_id": "Optionnel entity id du 4ème radiateur",
|
||||
"underlying_entity_ids": "La liste des équipements qui seront controlés par ce VTherm",
|
||||
"heater_keep_alive": "Intervalle de rafraichissement du switch en secondes. Laisser vide pour désactiver. À n'utiliser que pour les switchs qui le nécessite.",
|
||||
"proportional_function": "Algorithme à utiliser (Seul TPI est disponible pour l'instant)",
|
||||
"climate_entity_id": "Entity id du thermostat sous-jacent",
|
||||
"climate_entity2_id": "Entity id du 2ème thermostat sous-jacent",
|
||||
"climate_entity3_id": "Entity id du 3ème thermostat sous-jacent",
|
||||
"climate_entity4_id": "Entity id du 4ème thermostat sous-jacent",
|
||||
"ac_mode": "Utilisation du mode Air Conditionné (AC)",
|
||||
"valve_entity_id": "Entity id de la 1ère valve",
|
||||
"valve_entity2_id": "Entity id de la 2ème valve",
|
||||
"valve_entity3_id": "Entity id de la 3ème valve",
|
||||
"valve_entity4_id": "Entity id de la 4ème valve",
|
||||
"auto_regulation_mode": "Ajustement automatique de la consigne",
|
||||
"sonoff_trvzb_mode": "Les équipements sont des Sonoff TRVZB. Vous devez configurer les entités dédiées dans le menu 'Configuration Sonoff TRVZB'",
|
||||
"auto_regulation_mode": "Ajustement automatique de la température cible",
|
||||
"auto_regulation_dtemp": "Le seuil en ° (ou % pour les valves) en-dessous duquel la régulation ne sera pas envoyée",
|
||||
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation",
|
||||
"auto_regulation_use_device_temp": "Utiliser la temperature interne du sous-jacent pour accélérer l'auto-régulation",
|
||||
"auto_regulation_use_device_temp": "Compenser la temperature interne du sous-jacent pour accélérer l'auto-régulation",
|
||||
"inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode",
|
||||
"auto_fan_mode": "Active la ventilation automatiquement en cas d'écart important"
|
||||
}
|
||||
@@ -480,6 +458,22 @@
|
||||
"central_boiler_activation_service": "Commande à éxecuter pour allumer la chaudière centrale au format entity_id/service_name[/attribut:valeur]",
|
||||
"central_boiler_deactivation_service": "Commande à éxecuter pour étiendre la chaudière centrale au format entity_id/service_name[/attribut:valeur]"
|
||||
}
|
||||
},
|
||||
"sonoff_trvzb": {
|
||||
"title": "Configuration Sonoff TRVZB - {name}",
|
||||
"description": "Configuration spécifique des Sonoff TRVZB",
|
||||
"data": {
|
||||
"offset_calibration_entity_ids": "Entités de 'Offset calibration'",
|
||||
"opening_degree_entity_ids": "Entités de 'Opening degree'",
|
||||
"closing_degree_entity_ids": "Entités de 'Closing degree'",
|
||||
"proportional_function": "Algorithme"
|
||||
},
|
||||
"data_description": {
|
||||
"offset_calibration_entity_ids": "La liste des entités 'offset calibration' entities. Il doit y en avoir une par entité climate sous-jacente",
|
||||
"opening_degree_entity_ids": "La liste des entités 'opening degree' entities. Il doit y en avoir une par entité climate sous-jacente",
|
||||
"closing_degree_entity_ids": "La liste des entités 'closing degree' entities. Il doit y en avoir une par entité climate sous-jacente",
|
||||
"proportional_function": "Algorithme à utiliser (seulement TPI est disponible)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -487,7 +481,8 @@
|
||||
"unknown_entity": "entity id inconnu",
|
||||
"window_open_detection_method": "Une seule méthode de détection des ouvertures ouvertes doit être utilisée. Utilisez le détecteur d'ouverture ou les seuils de température mais pas les deux.",
|
||||
"no_central_config": "Vous ne pouvez pas cocher 'Utiliser la configuration centrale' car aucune configuration centrale n'a été trouvée. Vous devez créer un Versatile Thermostat de type 'Central Configuration' pour pouvoir l'utiliser.",
|
||||
"service_configuration_format": "Mauvais format de la configuration du service"
|
||||
"service_configuration_format": "Mauvais format de la configuration du service",
|
||||
"sonoff_trvzb_nb_entities_incorrect": "Le nombre d'entités spécifiques au Sonoff TRVZB doit être égal au nombre d'entité sous-jacentes"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "Le device est déjà configuré"
|
||||
|
||||
@@ -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
|
||||
@@ -53,6 +53,9 @@ class UnderlyingEntityType(StrEnum):
|
||||
# a valve
|
||||
VALVE = "valve"
|
||||
|
||||
# a Sonoff TRVZB
|
||||
SONOFF_TRVZB = "sonoff_trvzb"
|
||||
|
||||
|
||||
class UnderlyingEntity:
|
||||
"""Represent a underlying device which could be a switch or a climate"""
|
||||
@@ -62,6 +65,7 @@ class UnderlyingEntity:
|
||||
_thermostat: Any
|
||||
_entity_id: str
|
||||
_type: UnderlyingEntityType
|
||||
_hvac_mode: HVACMode | None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -75,6 +79,7 @@ class UnderlyingEntity:
|
||||
self._thermostat = thermostat
|
||||
self._type = entity_type
|
||||
self._entity_id = entity_id
|
||||
self._hvac_mode = None
|
||||
|
||||
def __str__(self):
|
||||
return str(self._thermostat) + "-" + self._entity_id
|
||||
@@ -100,13 +105,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
|
||||
@@ -181,7 +197,6 @@ class UnderlyingSwitch(UnderlyingEntity):
|
||||
_initialDelaySec: int
|
||||
_on_time_sec: int
|
||||
_off_time_sec: int
|
||||
_hvac_mode: HVACMode
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -204,7 +219,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
|
||||
@@ -237,8 +251,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
|
||||
@@ -713,6 +727,13 @@ class UnderlyingClimate(UnderlyingEntity):
|
||||
return []
|
||||
return self._underlying_climate.hvac_modes
|
||||
|
||||
@property
|
||||
def current_humidity(self) -> float | None:
|
||||
"""Get the humidity"""
|
||||
if not self.is_initialized:
|
||||
return None
|
||||
return self._underlying_climate.current_humidity
|
||||
|
||||
@property
|
||||
def fan_modes(self) -> list[str]:
|
||||
"""Get the fan_modes"""
|
||||
@@ -847,11 +868,12 @@ 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
|
||||
) -> None:
|
||||
"""Initialize the underlying switch"""
|
||||
"""Initialize the underlying valve"""
|
||||
|
||||
super().__init__(
|
||||
hass=hass,
|
||||
@@ -865,13 +887,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,
|
||||
@@ -883,6 +904,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)
|
||||
@@ -988,3 +1014,88 @@ class UnderlyingValve(UnderlyingEntity):
|
||||
def remove_entity(self):
|
||||
"""Remove the entity after stopping its cycle"""
|
||||
self._cancel_cycle()
|
||||
|
||||
|
||||
class UnderlyingSonoffTRVZB(UnderlyingValve):
|
||||
"""A specific underlying class for Sonoff TRVZB TRV"""
|
||||
|
||||
_offset_calibration_entity_id: str
|
||||
_opening_degree_entity_id: str
|
||||
_closing_degree_entity_id: str
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
thermostat: Any,
|
||||
offset_calibration_entity_id: str,
|
||||
opening_degree_entity_id: str,
|
||||
closing_degree_entity_id: str,
|
||||
) -> None:
|
||||
"""Initialize the underlying Sonoff TRV"""
|
||||
super().__init__(hass, thermostat, opening_degree_entity_id)
|
||||
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:
|
||||
"""The offset_calibration_entity_id"""
|
||||
return self._offset_calibration_entity_id
|
||||
|
||||
@property
|
||||
def opening_degree_entity_id(self) -> str:
|
||||
"""The offset_calibration_entity_id"""
|
||||
return self._opening_degree_entity_id
|
||||
|
||||
@property
|
||||
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]
|
||||
|
||||
@@ -517,6 +517,7 @@ async def test_user_config_flow_over_climate(
|
||||
CONF_USE_ADVANCED_CENTRAL_CONFIG: False,
|
||||
CONF_USED_BY_CENTRAL_BOILER: False,
|
||||
CONF_USE_CENTRAL_MODE: False,
|
||||
CONF_SONOFF_TRZB_MODE: False,
|
||||
}
|
||||
assert result["result"]
|
||||
assert result["result"].domain == DOMAIN
|
||||
@@ -1126,6 +1127,7 @@ async def test_user_config_flow_over_climate_auto_start_stop(
|
||||
CONF_USED_BY_CENTRAL_BOILER: False,
|
||||
CONF_USE_AUTO_START_STOP_FEATURE: True,
|
||||
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_MEDIUM,
|
||||
CONF_SONOFF_TRZB_MODE: False,
|
||||
}
|
||||
assert result["result"]
|
||||
assert result["result"].domain == DOMAIN
|
||||
|
||||
Reference in New Issue
Block a user