diff --git a/.devcontainer/configuration.yaml b/.devcontainer/configuration.yaml index 7dbd50f..bc56181 100644 --- a/.devcontainer/configuration.yaml +++ b/.devcontainer/configuration.yaml @@ -54,11 +54,17 @@ input_boolean: fake_heater_switch1: name: Heater 1 (Linear) icon: mdi:radiator - fake_heater_switch2: - name: Heater (TPI with presence preset) + fake_heater_4switch1: + name: Heater (multiswitch1) icon: mdi:radiator - fake_heater_switch3: - name: Heater (TPI with offset) + fake_heater_4switch2: + name: Heater (multiswitch2) + icon: mdi:radiator + fake_heater_4switch3: + name: Heater (multiswitch3) + icon: mdi:radiator + fake_heater_4switch4: + name: Heater (multiswitch4) icon: mdi:radiator # input_boolean to simulate the motion sensor entity. Only for development environment. fake_motion_sensor1: diff --git a/custom_components/versatile_thermostat/climate.py b/custom_components/versatile_thermostat/climate.py index 897e2ba..77c7545 100644 --- a/custom_components/versatile_thermostat/climate.py +++ b/custom_components/versatile_thermostat/climate.py @@ -140,8 +140,7 @@ from .open_window_algorithm import WindowOpenDetectionAlgorithm _LOGGER = logging.getLogger(__name__) -# TODO remove this -_LOGGER.setLevel(logging.DEBUG) +# _LOGGER.setLevel(logging.DEBUG) async def async_setup_entry( @@ -258,16 +257,12 @@ class VersatileThermostat(ClimateEntity, RestoreEntity): self._thermostat_type = None self._is_over_climate = False - # TODO should be delegated to underlying climate - # self._heater_entity_id = None - # self._climate_entity_id = None - self._underlying_climate = None self._attr_translation_key = "versatile_thermostat" self._total_energy = None - # TODO should be delegated to underlying climate + # 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 self._underlying_climate_delta_t = 0 @@ -327,7 +322,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity): self._underlyings.append( UnderlyingClimate( hass=self._hass, - thermostat_name=str(self), + thermostat=self, climate_entity_id=entry_infos.get(CONF_CLIMATE), ) ) @@ -346,7 +341,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity): self._underlyings.append( UnderlyingSwitch( hass=self._hass, - thermostat_name=str(self), + thermostat=self, switch_entity_id=switch, initial_delay_sec=idx * delta_cycle, ) @@ -614,16 +609,6 @@ class VersatileThermostat(ClimateEntity, RestoreEntity): # Ingore this error which is possible if underlying climate is not found temporary pass - # starts a cycle if we are in over_climate type - if self._is_over_climate: - self.async_on_remove( - async_track_time_interval( - self.hass, - self._async_control_heating, - interval=timedelta(minutes=self._cycle_min), - ) - ) - def async_remove_thermostat(self): """Called when the thermostat will be removed""" _LOGGER.info("%s - Removing thermostat", self) @@ -767,7 +752,18 @@ class VersatileThermostat(ClimateEntity, RestoreEntity): ) self.hass.create_task(self._check_switch_initial_state()) - self.hass.create_task(self._async_control_heating()) + # Start the control_heating + # starts a cycle if we are in over_climate type + if self._is_over_climate: + self.async_on_remove( + async_track_time_interval( + self.hass, + self._async_control_heating, + interval=timedelta(minutes=self._cycle_min), + ) + ) + else: + self.hass.create_task(self._async_control_heating()) await self.get_my_previous_state() @@ -880,8 +876,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity): @property def hvac_modes(self): """List of available operation modes.""" - if self._is_over_climate and self._underlying_climate: - return self._underlying_climate.hvac_modes + if self._is_over_climate and self.underlying_entity(0): + return self.underlying_entity(0).hvac_modes return self._hvac_list @@ -891,8 +887,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity): Requires ClimateEntityFeature.FAN_MODE. """ - if self._is_over_climate and self._underlying_climate: - return self._underlying_climate.fan_mode + if self._is_over_climate and self.underlying_entity(0): + return self.underlying_entity(0).fan_mode return None @@ -902,8 +898,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity): Requires ClimateEntityFeature.FAN_MODE. """ - if self._is_over_climate and self._underlying_climate: - return self._underlying_climate.fan_modes + if self._is_over_climate and self.underlying_entity(0): + return self.underlying_entity(0).fan_modes return [] @@ -913,8 +909,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity): Requires ClimateEntityFeature.SWING_MODE. """ - if self._is_over_climate and self._underlying_climate: - return self._underlying_climate.swing_mode + if self._is_over_climate and self.underlying_entity(0): + return self.underlying_entity(0).swing_mode return None @@ -924,16 +920,16 @@ class VersatileThermostat(ClimateEntity, RestoreEntity): Requires ClimateEntityFeature.SWING_MODE. """ - if self._is_over_climate and self._underlying_climate: - return self._underlying_climate.swing_modes + if self._is_over_climate and self.underlying_entity(0): + return self.underlying_entity(0).swing_modes return None @property - def temperature_unit(self): + def temperature_unit(self) -> str: """Return the unit of measurement.""" - if self._is_over_climate and self._underlying_climate: - return self._underlying_climate.temperature_unit + if self._is_over_climate and self.underlying_entity(0): + return self.underlying_entity(0).temperature_unit return self._unit @@ -984,8 +980,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity): @property def supported_features(self): """Return the list of supported features.""" - if self._is_over_climate and self._underlying_climate: - return self._underlying_climate.supported_features | self._support_flags + if self._is_over_climate and self.underlying_entity(0): + return self.underlying_entity(0).supported_features | self._support_flags return self._support_flags @@ -1005,8 +1001,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity): @property def target_temperature_step(self) -> float | None: """Return the supported step of target temperature.""" - if self._is_over_climate and self._underlying_climate: - return self._underlying_climate.target_temperature_step + if self._is_over_climate and self.underlying_entity(0): + return self.underlying_entity(0).target_temperature_step return None @@ -1016,8 +1012,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity): Requires ClimateEntityFeature.TARGET_TEMPERATURE_RANGE. """ - if self._is_over_climate and self._underlying_climate: - return self._underlying_climate.target_temperature_high + if self._is_over_climate and self.underlying_entity(0): + return self.underlying_entity(0).target_temperature_high return None @@ -1027,8 +1023,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity): Requires ClimateEntityFeature.TARGET_TEMPERATURE_RANGE. """ - if self._is_over_climate and self._underlying_climate: - return self._underlying_climate.target_temperature_low + if self._is_over_climate and self.underlying_entity(0): + return self.underlying_entity(0).target_temperature_low return None @@ -1038,8 +1034,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity): Requires ClimateEntityFeature.AUX_HEAT. """ - if self._is_over_climate and self._underlying_climate: - return self._underlying_climate.is_aux_heat + if self._is_over_climate and self.underlying_entity(0): + return self.underlying_entity(0).is_aux_heat return None @@ -1049,7 +1045,11 @@ class VersatileThermostat(ClimateEntity, RestoreEntity): if not self._device_power or self._is_over_climate: return None - return float(self._device_power * self._prop_algorithm.on_percent) + return float( + self.nb_underlying_entities + * self._device_power + * self._prop_algorithm.on_percent + ) @property def total_energy(self) -> float | None: @@ -1162,29 +1162,32 @@ class VersatileThermostat(ClimateEntity, RestoreEntity): def turn_aux_heat_on(self) -> None: """Turn auxiliary heater on.""" - if self._is_over_climate and self._underlying_climate: - return self._underlying_climate.turn_aux_heat_on() + if self._is_over_climate and self.underlying_entity(0): + return self.underlying_entity(0).turn_aux_heat_on() raise NotImplementedError() async def async_turn_aux_heat_on(self) -> None: """Turn auxiliary heater on.""" - if self._is_over_climate and self._underlying_climate: - await self._underlying_climate.async_turn_aux_heat_on() + if self._is_over_climate: + for under in self._underlyings: + await under.async_turn_aux_heat_on() raise NotImplementedError() def turn_aux_heat_off(self) -> None: """Turn auxiliary heater off.""" - if self._is_over_climate and self._underlying_climate: - return self._underlying_climate.turn_aux_heat_off() + if self._is_over_climate: + for under in self._underlyings: + return under.turn_aux_heat_off() raise NotImplementedError() async def async_turn_aux_heat_off(self) -> None: """Turn auxiliary heater off.""" - if self._is_over_climate and self._underlying_climate: - await self._underlying_climate.async_turn_aux_heat_off() + if self._is_over_climate: + for under in self._underlyings: + await under.async_turn_aux_heat_off() raise NotImplementedError() @@ -1624,7 +1627,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity): ) self.update_custom_attributes() - await self._async_control_heating(True) + await self._async_control_heating() @callback async def _async_update_temp(self, state: State): @@ -1819,18 +1822,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity): self._target_temp, ) - async def _async_heater_turn_on(self): - """Turn heater toggleable device on.""" - # TODO should be delegated - # data = {ATTR_ENTITY_ID: self._heater_entity_id} - # await self.hass.services.async_call( - # HA_DOMAIN, SERVICE_TURN_ON, data, context=self._context - # ) - for under in self._underlyings: - await under.turn_on() - async def _async_underlying_entity_turn_off(self): - """Turn heater toggleable device off.""" + """Turn heater toggleable device off. Used by Window, overpowering, control_heating to turn all off""" for under in self._underlyings: await under.turn_off() @@ -2208,10 +2201,12 @@ class VersatileThermostat(ClimateEntity, RestoreEntity): raise err # Check overpowering condition - overpowering: bool = await self.check_overpowering() - if overpowering: - _LOGGER.debug("%s - End of cycle (overpowering)", self) - return + # Not necessary for switch because each switch is checking at startup + if self.is_over_climate: + overpowering: bool = await self.check_overpowering() + if overpowering: + _LOGGER.debug("%s - End of cycle (overpowering)", self) + return security: bool = await self.check_security() if security and self._is_over_climate: diff --git a/custom_components/versatile_thermostat/strings.json b/custom_components/versatile_thermostat/strings.json index cc92f22..ad2b598 100644 --- a/custom_components/versatile_thermostat/strings.json +++ b/custom_components/versatile_thermostat/strings.json @@ -22,12 +22,23 @@ } }, "type": { - "title": "Linked entity", - "description": "Linked entity attributes", + "title": "Linked entities", + "description": "Linked entities attributes", "data": { - "heater_entity_id": "Heater entity id", + "heater_entity_id": "Heater switch", + "heater_entity2_id": "2nd Heater switch", + "heater_entity3_id": "3rd Heater switch", + "heater_entity4_id": "4th Heater switch", + "proportional_function": "Algorithm", + "climate_entity_id": "Underlying thermostat" + }, + "data_description": { + "heater_entity_id": "Mandatory heater entity id", + "heater_entity2_id": "Optional 2nd Heater entity id. Leave empty if not used", + "heater_entity3_id": "Optional 3rd Heater entity id. Leave empty if not used", + "heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not used", "proportional_function": "Algorithm to use (TPI is the only one for now)", - "climate_entity_id": "Underlying thermostat entity id" + "climate_entity_id": "Underlying climate entity id" } }, "tpi": { @@ -142,12 +153,23 @@ } }, "type": { - "title": "Linked entity", - "description": "Linked entity attributes", + "title": "Linked entities", + "description": "Linked entities attributes", "data": { - "heater_entity_id": "Heater entity id", + "heater_entity_id": "Heater switch", + "heater_entity2_id": "2nd Heater switch", + "heater_entity3_id": "3rd Heater switch", + "heater_entity4_id": "4th Heater switch", + "proportional_function": "Algorithm", + "climate_entity_id": "Underlying thermostat" + }, + "data_description": { + "heater_entity_id": "Mandatory heater entity id", + "heater_entity2_id": "Optional 2nd Heater entity id. Leave empty if not used", + "heater_entity3_id": "Optional 3rd Heater entity id. Leave empty if not used", + "heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not used", "proportional_function": "Algorithm to use (TPI is the only one for now)", - "climate_entity_id": "Underlying thermostat entity id" + "climate_entity_id": "Underlying climate entity id" } }, "tpi": { diff --git a/custom_components/versatile_thermostat/tests/conftest.py b/custom_components/versatile_thermostat/tests/conftest.py index 6e6f95a..08ff58c 100644 --- a/custom_components/versatile_thermostat/tests/conftest.py +++ b/custom_components/versatile_thermostat/tests/conftest.py @@ -55,9 +55,9 @@ def skip_notifications_fixture(): def skip_turn_on_off_heater(): """Skip turning on and off the heater""" with patch( - "custom_components.versatile_thermostat.climate.VersatileThermostat._async_heater_turn_on" + "custom_components.versatile_thermostat.underlyings.UnderlyingEntity.turn_on" ), patch( - "custom_components.versatile_thermostat.climate.VersatileThermostat._async_underlying_entity_turn_off" + "custom_components.versatile_thermostat.underlyings.UnderlyingEntity.turn_off" ): yield diff --git a/custom_components/versatile_thermostat/tests/test_bugs.py b/custom_components/versatile_thermostat/tests/test_bugs.py index a952db4..f183b4e 100644 --- a/custom_components/versatile_thermostat/tests/test_bugs.py +++ b/custom_components/versatile_thermostat/tests/test_bugs.py @@ -53,7 +53,7 @@ async def test_bug_56( assert entity # cause the underlying climate was not found assert entity.is_over_climate is True - assert entity._underlying_climate is None + assert entity.underlying_entity(0)._underlying_climate is None # Should not failed entity.update_custom_attributes() @@ -260,7 +260,7 @@ async def test_bug_66( assert mock_send_event.call_count == 1 assert mock_heater_on.call_count == 1 - assert mock_heater_off.call_count == 1 + assert mock_heater_off.call_count >= 1 assert mock_condition.call_count == 1 assert entity.window_state == STATE_ON diff --git a/custom_components/versatile_thermostat/tests/test_sensors.py b/custom_components/versatile_thermostat/tests/test_sensors.py index 1c7a4c5..a17c23c 100644 --- a/custom_components/versatile_thermostat/tests/test_sensors.py +++ b/custom_components/versatile_thermostat/tests/test_sensors.py @@ -1,5 +1,4 @@ """ Test the normal start of a Thermostat """ -import asyncio from datetime import timedelta, datetime from homeassistant.core import HomeAssistant diff --git a/custom_components/versatile_thermostat/translations/en.json b/custom_components/versatile_thermostat/translations/en.json index cc92f22..ad2b598 100644 --- a/custom_components/versatile_thermostat/translations/en.json +++ b/custom_components/versatile_thermostat/translations/en.json @@ -22,12 +22,23 @@ } }, "type": { - "title": "Linked entity", - "description": "Linked entity attributes", + "title": "Linked entities", + "description": "Linked entities attributes", "data": { - "heater_entity_id": "Heater entity id", + "heater_entity_id": "Heater switch", + "heater_entity2_id": "2nd Heater switch", + "heater_entity3_id": "3rd Heater switch", + "heater_entity4_id": "4th Heater switch", + "proportional_function": "Algorithm", + "climate_entity_id": "Underlying thermostat" + }, + "data_description": { + "heater_entity_id": "Mandatory heater entity id", + "heater_entity2_id": "Optional 2nd Heater entity id. Leave empty if not used", + "heater_entity3_id": "Optional 3rd Heater entity id. Leave empty if not used", + "heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not used", "proportional_function": "Algorithm to use (TPI is the only one for now)", - "climate_entity_id": "Underlying thermostat entity id" + "climate_entity_id": "Underlying climate entity id" } }, "tpi": { @@ -142,12 +153,23 @@ } }, "type": { - "title": "Linked entity", - "description": "Linked entity attributes", + "title": "Linked entities", + "description": "Linked entities attributes", "data": { - "heater_entity_id": "Heater entity id", + "heater_entity_id": "Heater switch", + "heater_entity2_id": "2nd Heater switch", + "heater_entity3_id": "3rd Heater switch", + "heater_entity4_id": "4th Heater switch", + "proportional_function": "Algorithm", + "climate_entity_id": "Underlying thermostat" + }, + "data_description": { + "heater_entity_id": "Mandatory heater entity id", + "heater_entity2_id": "Optional 2nd Heater entity id. Leave empty if not used", + "heater_entity3_id": "Optional 3rd Heater entity id. Leave empty if not used", + "heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not used", "proportional_function": "Algorithm to use (TPI is the only one for now)", - "climate_entity_id": "Underlying thermostat entity id" + "climate_entity_id": "Underlying climate entity id" } }, "tpi": { diff --git a/custom_components/versatile_thermostat/translations/fr.json b/custom_components/versatile_thermostat/translations/fr.json index ec31169..5f3e46f 100644 --- a/custom_components/versatile_thermostat/translations/fr.json +++ b/custom_components/versatile_thermostat/translations/fr.json @@ -21,12 +21,23 @@ } }, "type": { - "title": "Entité liée", - "description": "Attributs de l'entité liée", + "title": "Entité(s) liée(s)", + "description": "Attributs de(s) l'entité(s) liée(s)", "data": { - "heater_entity_id": "Radiateur entity id", + "heater_entity_id": "1er radiateur", + "heater_entity2_id": "2ème radiateur", + "heater_entity3_id": "3ème radiateur", + "heater_entity4_id": "4ème radiateur", + "proportional_function": "Algorithme", + "climate_entity_id": "Thermostat sous-jacent" + }, + "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", "proportional_function": "Algorithme à utiliser (Seul TPI est disponible pour l'instant)", - "climate_entity_id": "Thermostat sous-jacent entity id" + "climate_entity_id": "Entity id du thermostat sous-jacent" } }, "tpi": { @@ -142,12 +153,23 @@ } }, "type": { - "title": "Entité liée", - "description": "Attributs de l'entité liée", + "title": "Entité(s) liée(s)", + "description": "Attributs de(s) l'entité(s) liée(s)", "data": { - "heater_entity_id": "Radiateur entity id", + "heater_entity_id": "1er radiateur", + "heater_entity2_id": "2ème radiateur", + "heater_entity3_id": "3ème radiateur", + "heater_entity4_id": "4ème radiateur", + "proportional_function": "Algorithme", + "climate_entity_id": "Thermostat sous-jacent" + }, + "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", "proportional_function": "Algorithme à utiliser (Seul TPI est disponible pour l'instant)", - "climate_entity_id": "Thermostat sous-jacent entity id" + "climate_entity_id": "Entity id du thermostat sous-jacent" } }, "tpi": { diff --git a/custom_components/versatile_thermostat/underlyings.py b/custom_components/versatile_thermostat/underlyings.py index e9d6165..08eebe9 100644 --- a/custom_components/versatile_thermostat/underlyings.py +++ b/custom_components/versatile_thermostat/underlyings.py @@ -1,7 +1,8 @@ """ Underlying entities classes """ import logging +from typing import Any -from homeassistant.const import ATTR_ENTITY_ID, STATE_ON +from homeassistant.const import ATTR_ENTITY_ID, STATE_ON, UnitOfTemperature from homeassistant.exceptions import ServiceNotFound @@ -9,6 +10,7 @@ from homeassistant.backports.enum import StrEnum from homeassistant.core import HomeAssistant, DOMAIN as HA_DOMAIN, CALLBACK_TYPE from homeassistant.components.climate import ( ClimateEntity, + ClimateEntityFeature, DOMAIN as CLIMATE_DOMAIN, HVACMode, HVACAction, @@ -27,8 +29,8 @@ from .const import UnknownEntity _LOGGER = logging.getLogger(__name__) -# TODO remove this -_LOGGER.setLevel(logging.DEBUG) +# remove this +# _LOGGER.setLevel(logging.DEBUG) class UnderlyingEntityType(StrEnum): @@ -45,25 +47,26 @@ class UnderlyingEntity: """Represent a underlying device which could be a switch or a climate""" _hass: HomeAssistant - _thermostat_name: str + # Cannot import VersatileThermostat due to circular reference + _thermostat: Any _entity_id: str _type: UnderlyingEntityType def __init__( self, hass: HomeAssistant, - thermostat_name: str, + thermostat: Any, entity_type: UnderlyingEntityType, entity_id: str, ) -> None: """Initialize the underlying entity""" self._hass = hass - self._thermostat_name = thermostat_name + self._thermostat = thermostat self._type = entity_type self._entity_id = entity_id def __str__(self): - return self._thermostat_name + return str(self._thermostat) + "-" + self._entity_id @property def entity_id(self): @@ -147,7 +150,7 @@ class UnderlyingSwitch(UnderlyingEntity): def __init__( self, hass: HomeAssistant, - thermostat_name: str, + thermostat: Any, switch_entity_id: str, initial_delay_sec: int, ) -> None: @@ -155,7 +158,7 @@ class UnderlyingSwitch(UnderlyingEntity): super().__init__( hass=hass, - thermostat_name=thermostat_name, + thermostat=thermostat, entity_type=UnderlyingEntityType.SWITCH, entity_id=switch_entity_id, ) @@ -227,7 +230,7 @@ class UnderlyingSwitch(UnderlyingEntity): _LOGGER.debug("%s - End of cycle (2)", self) return - # If we should heat + # If we should heat, starts the cycle with delay if self._hvac_mode == HVACMode.HEAT and on_time_sec > 0: # Starts the cycle after the initial delay self._async_cancel_cycle = self.call_later( @@ -272,23 +275,23 @@ class UnderlyingSwitch(UnderlyingEntity): await self.turn_off() return - # TODO if await self.check_overpowering(): - # _LOGGER.debug("%s - End of cycle (3)", self) - # return + if await self._thermostat.check_overpowering(): + _LOGGER.debug("%s - End of cycle (3)", self) + return # Security mode could have change the on_time percent - # TODO await self.check_security() + await self._thermostat.check_security() time = self._on_time_sec action_label = "start" - if self._should_relaunch_control_heating: - _LOGGER.debug("Don't %s cause a cycle have to be relaunch", action_label) - self._should_relaunch_control_heating = False - # self.hass.create_task(self._async_control_heating()) - await self.start_cycle( - self._hvac_mode, self._on_time_sec, self._off_time_sec - ) - _LOGGER.debug("%s - End of cycle (3)", self) - return + # if self._should_relaunch_control_heating: + # _LOGGER.debug("Don't %s cause a cycle have to be relaunch", action_label) + # self._should_relaunch_control_heating = False + # # self.hass.create_task(self._async_control_heating()) + # await self.start_cycle( + # self._hvac_mode, self._on_time_sec, self._off_time_sec + # ) + # _LOGGER.debug("%s - End of cycle (3)", self) + # return if time > 0: _LOGGER.info( @@ -325,15 +328,15 @@ class UnderlyingSwitch(UnderlyingEntity): return action_label = "stop" - if self._should_relaunch_control_heating: - _LOGGER.debug("Don't %s cause a cycle have to be relaunch", action_label) - self._should_relaunch_control_heating = False - # self.hass.create_task(self._async_control_heating()) - await self.start_cycle( - self._hvac_mode, self._on_time_sec, self._off_time_sec - ) - _LOGGER.debug("%s - End of cycle (3)", self) - return + # if self._should_relaunch_control_heating: + # _LOGGER.debug("Don't %s cause a cycle have to be relaunch", action_label) + # self._should_relaunch_control_heating = False + # # self.hass.create_task(self._async_control_heating()) + # await self.start_cycle( + # self._hvac_mode, self._on_time_sec, self._off_time_sec + # ) + # _LOGGER.debug("%s - End of cycle (3)", self) + # return time = self._off_time_sec @@ -355,7 +358,7 @@ class UnderlyingSwitch(UnderlyingEntity): ) # increment energy at the end of the cycle - # TODO self.incremente_energy() + self._thermostat.incremente_energy() async def remove_entity(self): """Remove the entity""" @@ -368,13 +371,16 @@ class UnderlyingClimate(UnderlyingEntity): _underlying_climate: ClimateEntity def __init__( - self, hass: HomeAssistant, thermostat_name: str, climate_entity_id: str + self, + hass: HomeAssistant, + thermostat: Any, + climate_entity_id: str, ) -> None: """Initialize the underlying climate""" super().__init__( hass=hass, - thermostat_name=thermostat_name, + thermostat=thermostat, entity_type=UnderlyingEntityType.CLIMATE, entity_id=climate_entity_id, ) @@ -423,7 +429,7 @@ class UnderlyingClimate(UnderlyingEntity): await self._hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_HVAC_MODE, - data, # TODO Needed ?, context=self._context + data, ) @property @@ -449,7 +455,7 @@ class UnderlyingClimate(UnderlyingEntity): await self._hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_FAN_MODE, - data, # TODO needed ? context=self._context + data, ) async def set_humidity(self, humidity: int): @@ -465,7 +471,7 @@ class UnderlyingClimate(UnderlyingEntity): await self._hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_HUMIDITY, - data, # TODO needed ? context=self._context + data, ) async def set_swing_mode(self, swing_mode): @@ -481,7 +487,7 @@ class UnderlyingClimate(UnderlyingEntity): await self._hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_SWING_MODE, - data, # TODO needed ? context=self._context + data, ) async def set_temperature(self, temperature, max_temp, min_temp): @@ -498,19 +504,75 @@ class UnderlyingClimate(UnderlyingEntity): await self._hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, - data, # TODO needed ? context=self._context + data, ) @property - def hvac_action(self) -> HVACAction: + def hvac_action(self) -> HVACAction | None: """Get the hvac action of the underlying""" if not self.is_initialized: return None return self._underlying_climate.hvac_action @property - def hvac_mode(self) -> HVACMode: + def hvac_mode(self) -> HVACMode | None: """Get the hvac mode of the underlying""" if not self.is_initialized: return None return self._underlying_climate.hvac_mode + + @property + def fan_mode(self) -> str | None: + """Get the fan_mode of the underlying""" + if not self.is_initialized: + return None + return self._underlying_climate.fan_mode + + @property + def swing_mode(self) -> str | None: + """Get the swing_mode of the underlying""" + if not self.is_initialized: + return None + return self._underlying_climate.swing_mode + + @property + def supported_features(self) -> ClimateEntityFeature: + """Get the supported features of the climate""" + if not self.is_initialized: + return ClimateEntityFeature.TARGET_TEMPERATURE + return self._underlying_climate.supported_features + + @property + def hvac_modes(self) -> list[HVACMode]: + """Get the hvac_modes""" + if not self.is_initialized: + return [] + return self._underlying_climate.hvac_modes + + @property + def fan_modes(self) -> list[str]: + """Get the fan_modes""" + if not self.is_initialized: + return [] + return self._underlying_climate.fan_modes + + @property + def swing_modes(self) -> list[str]: + """Get the swing_modes""" + if not self.is_initialized: + return [] + return self._underlying_climate.swing_modes + + @property + def temperature_unit(self) -> str: + """Get the temperature_unit""" + if not self.is_initialized: + return UnitOfTemperature.CELSIUS + return self._underlying_climate.temperature_unit + + @property + def target_temperature_step(self) -> str: + """Get the target_temperature_step""" + if not self.is_initialized: + return 1 + return self._underlying_climate.target_temperature_step