From e6e0865c41ef5a7db5d25b87ceef6d50203697ed Mon Sep 17 00:00:00 2001 From: henricm Date: Tue, 4 Apr 2023 21:17:15 +0200 Subject: [PATCH] fix: ignore zero values for total-increasing energy sensors Energy sensors seems to be 0 every now and then for some users. This commit ignores such values before they reach the average calculation. This commit also moves the assignment of `SensorStateClass.TOTAL_INCREASING` into energy sensor classes since they always are total increasing. This way we can assume objects of this class are always total increasing. Refs #318 --- custom_components/ferroamp/sensor.py | 39 +++++++----- tests/test_sensor.py | 88 ++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 15 deletions(-) diff --git a/custom_components/ferroamp/sensor.py b/custom_components/ferroamp/sensor.py index 37d99fc..a1b9837 100644 --- a/custom_components/ferroamp/sensor.py +++ b/custom_components/ferroamp/sensor.py @@ -207,7 +207,6 @@ def sso_event_received(msg): precision_energy, config_id, model=model, - state_class=SensorStateClass.TOTAL_INCREASING ), FaultcodeFerroampSensor( "Faultcode", @@ -300,7 +299,6 @@ def eso_event_received(msg): interval, precision_energy, config_id, - state_class=SensorStateClass.TOTAL_INCREASING, ), EnergyFerroampSensor( "Total Energy Consumed", @@ -312,7 +310,6 @@ def eso_event_received(msg): interval, precision_energy, config_id, - state_class=SensorStateClass.TOTAL_INCREASING, ), BatteryFerroampSensor( "State of Charge", @@ -627,6 +624,15 @@ def __init__(self, name, entity_prefix, key, unit: str | None, icon, device_id, def present(self, event) -> bool: return event.get(self._state_key, None) is not None + def get_value(self, event): + return event.get(self._state_key, None) + + def get_float_value(self, event) -> float: + val = event.get(self._state_key, None) + if val is None: + return 0 + return float(val["val"]) + def add_event(self, event): self.events.append(event) now = datetime.now() @@ -705,10 +711,10 @@ def update_state_from_events(self, events) -> bool: temp = None count = 0 for event in events: - v = event.get(self._state_key, None) + v = self.get_value(event) if v is not None: count += 1 - temp = (temp or 0) + float(v["val"]) + temp = (temp or 0) + self.get_float_value(event) if temp is None: return False else: @@ -727,7 +733,7 @@ def __init__(self, name, entity_prefix, key, icon, device_id, device_name, inter self._attr_state_class = SensorStateClass.MEASUREMENT def get_voltage(self, event): - voltage = event.get(self._state_key, None) + voltage = self.get_value(event) if voltage is not None: voltage = dict(neg=float(voltage["neg"]), pos=float(voltage["pos"])) return voltage @@ -853,9 +859,14 @@ def __init__(self, name, entity_prefix, key, icon, device_id, device_name, inter interval, precision, config_id, + state_class=SensorStateClass.TOTAL_INCREASING, **kwargs ) + def add_event(self, event): + if self.get_float_value(event) > 0: + super().add_event(event) + def update_state_from_events(self, events): temp = None count = 0 @@ -1036,9 +1047,16 @@ def __init__(self, name, entity_prefix, key, icon, device_id, device_name, inter interval, precision, config_id, + state_class=SensorStateClass.TOTAL_INCREASING, **kwargs ) + def add_event(self, event): + phases = self.get_phases(event) + if phases is not None and (phases["L1"] is not None or phases["L2"] is not None or phases["L3"] is not None): + if (phases["L1"] + phases["L2"] + phases["L3"]) > 0: + super().add_event(event) + def get_phases(self, event): phases = super().get_phases(event) if phases is not None and (phases["L1"] is not None or phases["L2"] is not None or phases["L3"] is not None): @@ -1303,7 +1321,6 @@ def ehub_sensors(slug, interval, precision_battery, precision_current, precision interval, precision_energy, config_id, - state_class=SensorStateClass.TOTAL_INCREASING, ), ThreePhaseEnergyFerroampSensor( "External Energy Consumed", @@ -1315,7 +1332,6 @@ def ehub_sensors(slug, interval, precision_battery, precision_current, precision interval, precision_energy, config_id, - state_class=SensorStateClass.TOTAL_INCREASING, ), ThreePhaseEnergyFerroampSensor( "Inverter Energy Produced", @@ -1327,7 +1343,6 @@ def ehub_sensors(slug, interval, precision_battery, precision_current, precision interval, precision_energy, config_id, - state_class=SensorStateClass.TOTAL_INCREASING, ), ThreePhaseEnergyFerroampSensor( "Inverter Energy Consumed", @@ -1339,7 +1354,6 @@ def ehub_sensors(slug, interval, precision_battery, precision_current, precision interval, precision_energy, config_id, - state_class=SensorStateClass.TOTAL_INCREASING, ), ThreePhaseEnergyFerroampSensor( "Load Energy Produced", @@ -1351,7 +1365,6 @@ def ehub_sensors(slug, interval, precision_battery, precision_current, precision interval, precision_energy, config_id, - state_class=SensorStateClass.TOTAL_INCREASING, ), ThreePhaseEnergyFerroampSensor( "Load Energy Consumed", @@ -1363,7 +1376,6 @@ def ehub_sensors(slug, interval, precision_battery, precision_current, precision interval, precision_energy, config_id, - state_class=SensorStateClass.TOTAL_INCREASING, ), EnergyFerroampSensor( "Total Solar Energy", @@ -1375,7 +1387,6 @@ def ehub_sensors(slug, interval, precision_battery, precision_current, precision interval, precision_energy, config_id, - state_class=SensorStateClass.TOTAL_INCREASING, ), EnergyFerroampSensor( "Battery Energy Produced", @@ -1387,7 +1398,6 @@ def ehub_sensors(slug, interval, precision_battery, precision_current, precision interval, precision_energy, config_id, - state_class=SensorStateClass.TOTAL_INCREASING, ), EnergyFerroampSensor( "Battery Energy Consumed", @@ -1399,7 +1409,6 @@ def ehub_sensors(slug, interval, precision_battery, precision_current, precision interval, precision_energy, config_id, - state_class=SensorStateClass.TOTAL_INCREASING, ), IntValFerroampSensor( "System State", diff --git a/tests/test_sensor.py b/tests/test_sensor.py index fe1c9a6..1fce812 100644 --- a/tests/test_sensor.py +++ b/tests/test_sensor.py @@ -1,5 +1,6 @@ import uuid from unittest.mock import patch +import unittest import pytest from homeassistant.const import ( @@ -1692,6 +1693,47 @@ async def test_always_increasing(hass, mqtt_mock): sensor = hass.data[DOMAIN][DATA_DEVICES][config_entry.unique_id]["ferroamp_ehub"][entity.unique_id] assert sensor.state == "1348.5" +async def test_always_increasing_zerovalues(hass, mqtt_mock): + mock_restore_cache( + hass, + [ + State("sensor.ferroamp_total_solar_energy", "1348.5") + ], + ) + + hass.state = CoreState.starting + + config_entry = create_config() + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + topic = "extapi/data/ehub" + async_fire_mqtt_message(hass, topic, '{"id":{"val":"1"},"wpv":{"val": "4422089590383"}}') + async_fire_mqtt_message(hass, topic, '{"id":{"val":"1"},"wpv":{"val": "0"}}') + await hass.async_block_till_done() + + er = entity_registry.async_get(hass) + entity = er.async_get("sensor.ferroamp_total_solar_energy") + assert entity is not None + sensor = hass.data[DOMAIN][DATA_DEVICES][config_entry.unique_id]["ferroamp_ehub"][entity.unique_id] + assert float(sensor.state) == pytest.approx(1348.5) + + async_fire_mqtt_message(hass, topic, '{"id":{"val":"1"},"wpv":{"val": "4856400000000"}}') + async_fire_mqtt_message(hass, topic, '{"id":{"val":"1"},"wpv":{"val": "0"}}') + async_fire_mqtt_message(hass, topic, '{"id":{"val":"1"},"wpv":{"val": "0"}}') + async_fire_mqtt_message(hass, topic, '{"id":{"val":"1"},"wpv":{"val": "0"}}') + async_fire_mqtt_message(hass, topic, '{"id":{"val":"1"},"wpv":{"val": "0"}}') + async_fire_mqtt_message(hass, topic, '{"id":{"val":"1"},"wpv":{"val": "0"}}') + async_fire_mqtt_message(hass, topic, '{"id":{"val":"1"},"wpv":{"val": "0"}}') + async_fire_mqtt_message(hass, topic, '{"id":{"val":"1"},"wpv":{"val": "4856400000000"}}') + await hass.async_block_till_done() + + er = entity_registry.async_get(hass) + entity = er.async_get("sensor.ferroamp_total_solar_energy") + assert entity is not None + sensor = hass.data[DOMAIN][DATA_DEVICES][config_entry.unique_id]["ferroamp_ehub"][entity.unique_id] + assert float(sensor.state) == pytest.approx(1349.0) + async def test_always_increasing_unknown_value(hass, mqtt_mock): mock_restore_cache( @@ -1771,6 +1813,52 @@ async def test_3phase_always_increasing(hass, mqtt_mock): sensor = hass.data[DOMAIN][DATA_DEVICES][config_entry.unique_id]["ferroamp_ehub"][entity.unique_id] assert sensor.state == "662.5" +async def test_3phase_always_increasing_zero_values(hass, mqtt_mock): + mock_restore_cache( + hass, + [ + State("sensor.ferroamp_external_energy_produced", "662.5") + ], + ) + + hass.state = CoreState.starting + + config_entry = create_config() + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + topic = "extapi/data/ehub" + msg = '{"id":{"val":"1"},"wextprodq": {"L2": "1118056851556", "L3": "604554554552", "L1": "662115344893"}}' + async_fire_mqtt_message(hass, topic, msg) + msg = '{"id":{"val":"1"},"wextprodq": {"L2": "0", "L3": "0", "L1": "0"}}' + async_fire_mqtt_message(hass, topic, msg) + async_fire_mqtt_message(hass, topic, msg) + async_fire_mqtt_message(hass, topic, msg) + await hass.async_block_till_done() + + er = entity_registry.async_get(hass) + entity = er.async_get("sensor.ferroamp_external_energy_produced") + assert entity is not None + sensor = hass.data[DOMAIN][DATA_DEVICES][config_entry.unique_id]["ferroamp_ehub"][entity.unique_id] + assert float(sensor.state) == pytest.approx(662.5) + + msg = '{"id":{"val":"1"},"wextprodq": {"L2": "1118056851556", "L3": "604554554552", "L1": "662115344893"}}' + async_fire_mqtt_message(hass, topic, msg) + msg = '{"id":{"val":"1"},"wextprodq": {"L2": "0", "L3": "0", "L1": "0"}}' + async_fire_mqtt_message(hass, topic, msg) + async_fire_mqtt_message(hass, topic, msg) + async_fire_mqtt_message(hass, topic, msg) + msg = '{"id":{"val":"1"},"wextprodq": {"L2": "1119056851556", "L3": "604564554552", "L1": "662116344893"}}' + async_fire_mqtt_message(hass, topic, msg) + msg = '{"id":{"val":"1"},"wextprodq": {"L2": "0", "L3": "0", "L1": "0"}}' + async_fire_mqtt_message(hass, topic, msg) + await hass.async_block_till_done() + + er = entity_registry.async_get(hass) + entity = er.async_get("sensor.ferroamp_external_energy_produced") + assert entity is not None + sensor = hass.data[DOMAIN][DATA_DEVICES][config_entry.unique_id]["ferroamp_ehub"][entity.unique_id] + assert float(sensor.state) == pytest.approx(662.7) async def test_3phase_always_increasing_unknown_value(hass, mqtt_mock): mock_restore_cache(