diff --git a/.devcontainer/configuration.yaml b/.devcontainer/configuration.yaml index 2daa5ce3..3a224157 100644 --- a/.devcontainer/configuration.yaml +++ b/.devcontainer/configuration.yaml @@ -118,6 +118,17 @@ template: unique_id: maison_occupee state: "{{is_state('person.jmc', 'home') }}" device_class: occupancy + - sensor: + - name: "Total énergie switch1" + unique_id: total_energie_switch1 + unit_of_measurement: "kWh" + device_class: energy + state_class: total_increasing + state: > + {% set energy = state_attr('climate.thermostat_switch_1', 'total_energy') %} + {% if energy == 'unavailable' or energy is none%}unavailable{% else %} + {{ ((energy | float) / 1.0) | round(2, default=0) }} + {% endif %} switch: - platform: template diff --git a/custom_components/versatile_thermostat/climate.py b/custom_components/versatile_thermostat/climate.py index bd19cf74..0ee94ea1 100644 --- a/custom_components/versatile_thermostat/climate.py +++ b/custom_components/versatile_thermostat/climate.py @@ -134,6 +134,8 @@ CONF_CLIMATE, UnknownEntity, EventType, + ATTR_MEAN_POWER_CYCLE, + ATTR_TOTAL_ENERGY, ) from .prop_algorithm import PropAlgorithm @@ -201,6 +203,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity): # _registry: dict[str, object] = {} _last_temperature_mesure: datetime _last_ext_temperature_mesure: datetime + _total_energy: float def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None: """Initialize the thermostat.""" @@ -251,6 +254,8 @@ def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None: self._attr_translation_key = "versatile_thermostat" + self._total_energy = None + self.post_init(entry_infos) def post_init(self, entry_infos): @@ -441,6 +446,8 @@ def post_init(self, entry_infos): if self._motion_on: self._attr_preset_modes.append(PRESET_ACTIVITY) + self._total_energy = 0 + _LOGGER.debug( "%s - Creation of a new VersatileThermostat entity: unique_id=%s heater_entity_id=%s", self, @@ -780,6 +787,9 @@ async def get_my_previous_state(self): else: self._hvac_mode = HVACMode.OFF + old_total_energy = old_state.attributes.get(ATTR_TOTAL_ENERGY) + if old_total_energy: + self._total_energy = old_total_energy else: # No previous state, try and restore defaults if self._target_temp is None: @@ -981,6 +991,21 @@ def is_aux_heat(self) -> bool | None: return None + @property + def mean_cycle_power(self) -> float | None: + """Returns tne mean power consumption during the cycle""" + if self._is_over_climate: + return None + elif self._device_power: + return self._device_power * self._prop_algorithm.on_percent + else: + return None + + @property + def total_energy(self) -> float | None: + """Returns the total energy calculated for this thermostast""" + return self._total_energy + def turn_aux_heat_on(self) -> None: """Turn auxiliary heater on.""" if self._is_over_climate and self._underlying_climate: @@ -1964,7 +1989,6 @@ async def _turn_on_off_later( _LOGGER.debug( "%s - No action on heater cause duration is 0", self ) - self.update_custom_attributes() self._async_cancel_cycle = async_call_later( self.hass, time, @@ -1978,6 +2002,7 @@ async def _turn_on_later(_): heater_action=self._async_heater_turn_on, next_cycle_action=_turn_off_later, ) + self.update_custom_attributes() async def _turn_off_later(_): await _turn_on_off_later( @@ -1986,6 +2011,9 @@ async def _turn_off_later(_): heater_action=self._async_underlying_entity_turn_off, next_cycle_action=_turn_on_later, ) + # increment energy at the end of the cycle + self.incremente_energy() + self.update_custom_attributes() await _turn_on_later(None) @@ -2018,6 +2046,11 @@ def recalculate(self): self.update_custom_attributes() self.async_write_ha_state() + def incremente_energy(self): + """increment the energy counter if device is active""" + if self.hvac_mode != HVACMode.OFF: + self._total_energy += self.mean_cycle_power * float(self._cycle_min) / 60.0 + def update_custom_attributes(self): """Update the custom extra attributes for the entity""" @@ -2054,6 +2087,9 @@ def update_custom_attributes(self): "last_ext_temperature_datetime": self._last_ext_temperature_mesure.isoformat(), "security_state": self._security_state, "minimal_activation_delay_sec": self._minimal_activation_delay, + "device_power": self._device_power, + ATTR_MEAN_POWER_CYCLE: self.mean_cycle_power, + ATTR_TOTAL_ENERGY: self.total_energy, "last_update_datetime": datetime.now().isoformat(), } if self._is_over_climate: diff --git a/custom_components/versatile_thermostat/const.py b/custom_components/versatile_thermostat/const.py index 38025212..2abdffe7 100644 --- a/custom_components/versatile_thermostat/const.py +++ b/custom_components/versatile_thermostat/const.py @@ -135,6 +135,9 @@ DEFAULT_SECURITY_MIN_ON_PERCENT = 0.5 DEFAULT_SECURITY_DEFAULT_ON_PERCENT = 0.1 +ATTR_TOTAL_ENERGY = "total_energy" +ATTR_MEAN_POWER_CYCLE = "mean_cycle_power" + class EventType(Enum): """The event type that can be sent""" diff --git a/custom_components/versatile_thermostat/tests/commons.py b/custom_components/versatile_thermostat/tests/commons.py index 1e69edab..a98270e0 100644 --- a/custom_components/versatile_thermostat/tests/commons.py +++ b/custom_components/versatile_thermostat/tests/commons.py @@ -9,7 +9,7 @@ from homeassistant.helpers.entity_component import EntityComponent from ..climate import VersatileThermostat -from ..const import DOMAIN, PRESET_SECURITY, PRESET_POWER, EventType +from ..const import * from homeassistant.components.climate import ( ClimateEntity, diff --git a/custom_components/versatile_thermostat/tests/test_tpi.py b/custom_components/versatile_thermostat/tests/test_tpi.py index 93dfc026..9b4d6f65 100644 --- a/custom_components/versatile_thermostat/tests/test_tpi.py +++ b/custom_components/versatile_thermostat/tests/test_tpi.py @@ -11,24 +11,25 @@ async def test_tpi_calculation(hass: HomeAssistant, skip_hass_states_is_state): title="TheOverSwitchMockName", unique_id="uniqueId", data={ - "name": "TheOverSwitchMockName", - "thermostat_type": "thermostat_over_switch", - "temperature_sensor_entity_id": "sensor.mock_temp_sensor", - "external_temperature_sensor_entity_id": "sensor.mock_ext_temp_sensor", - "cycle_min": 5, - "temp_min": 15, - "temp_max": 30, - "use_window_feature": False, - "use_motion_feature": False, - "use_power_feature": False, - "use_presence_feature": False, - "heater_entity_id": "switch.mock_switch", - "proportional_function": "tpi", - "tpi_coef_int": 0.3, - "tpi_coef_ext": 0.01, - "minimal_activation_delay": 30, - "security_delay_min": 5, - "security_default_on_percent": 0.3, + CONF_NAME: "TheOverSwitchMockName", + CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH, + CONF_TEMP_SENSOR: "sensor.mock_temp_sensor", + CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor", + CONF_CYCLE_MIN: 5, + CONF_TEMP_MIN: 15, + CONF_TEMP_MAX: 30, + CONF_USE_WINDOW_FEATURE: False, + CONF_USE_MOTION_FEATURE: False, + CONF_USE_POWER_FEATURE: False, + CONF_USE_PRESENCE_FEATURE: False, + CONF_HEATER: "switch.mock_switch", + CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, + CONF_TPI_COEF_INT: 0.3, + CONF_TPI_COEF_EXT: 0.01, + CONF_MINIMAL_ACTIVATION_DELAY: 30, + CONF_SECURITY_DELAY_MIN: 5, + CONF_SECURITY_MIN_ON_PERCENT: 0.3, + # CONF_DEVICE_POWER: 100, }, ) @@ -45,6 +46,7 @@ async def test_tpi_calculation(hass: HomeAssistant, skip_hass_states_is_state): assert tpi_algo.calculated_on_percent == 1 assert tpi_algo.on_time_sec == 300 assert tpi_algo.off_time_sec == 0 + assert entity.mean_cycle_power is None tpi_algo.calculate(15, 14, 5) assert tpi_algo.on_percent == 0.4