Skip to content

Commit

Permalink
fix: support for energy counter being reset
Browse files Browse the repository at this point in the history
Energy counters like `sensor.ferroamp_external_energy_consumed` may be
reset in Energy Hub leading to lower values than previously known being
received from Ferroamp. Current code ignores all lower values since
these sensors should be `TOTAL_INCREASING`. However, the total increasing
concept in HA has support for counters being reset, so it's ok to provide
a lower value. For more info please see:

https://developers.home-assistant.io/docs/core/entity/sensor/#state-class-total_increasing

This fix will accept lower value if they are "much lower". If a value is
only 10 % lower it will still be ignored, since users have reported that
invalid values sometimes comes from Ferroamp. However, if values are lower
than this we noe will accept them. The statistics functionality in HA will
handle this. However the sensor itself will get the newer lower value.
  • Loading branch information
henricm authored and argoyle committed Mar 9, 2023
1 parent bf10d8f commit 743cb44
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 2 deletions.
6 changes: 4 additions & 2 deletions custom_components/ferroamp/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -862,7 +862,8 @@ def update_state_from_events(self, events):
if self._attr_native_value is None\
or (isinstance(self._attr_native_value, str) and not isfloat(self._attr_native_value))\
or self._attr_state_class != SensorStateClass.TOTAL_INCREASING\
or val > float(self._attr_native_value):
or val > float(self._attr_native_value) \
or val * 1.1 < float(self._attr_native_value):
self._attr_native_value = val
if self._precision == 0:
self._attr_native_value = int(self._attr_native_value)
Expand Down Expand Up @@ -986,7 +987,8 @@ def update_state_from_events(self, events):
if self._attr_native_value is None \
or (isinstance(self._attr_native_value, str) and not isfloat(self._attr_native_value)) \
or self._attr_state_class != SensorStateClass.TOTAL_INCREASING \
or val > float(self._attr_native_value):
or val > float(self._attr_native_value) \
or val * 1.1 < float(self._attr_native_value):
self._attr_native_value = val
if self._precision == 0:
self._attr_native_value = int(self._attr_native_value)
Expand Down
50 changes: 50 additions & 0 deletions tests/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1607,6 +1607,31 @@ async def test_always_increasing_unknown_value(hass, mqtt_mock):
sensor = hass.data[DOMAIN][DATA_DEVICES][config_entry.unique_id]["ferroamp_ehub"][entity.unique_id]
assert sensor.state == 1228.4

async def test_always_increasing_counter_reset(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"
# 100 kWh
msg = '{"id":{"val":"1"},"wpv":{"val": "360000000000"}}'
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_total_solar_energy")
assert entity is not None
sensor = hass.data[DOMAIN][DATA_DEVICES][config_entry.unique_id]["ferroamp_ehub"][entity.unique_id]
assert sensor.state == 100.0

async def test_3phase_always_increasing(hass, mqtt_mock):
mock_restore_cache(
Expand Down Expand Up @@ -1659,6 +1684,31 @@ async def test_3phase_always_increasing_unknown_value(hass, mqtt_mock):
sensor = hass.data[DOMAIN][DATA_DEVICES][config_entry.unique_id]["ferroamp_ehub"][entity.unique_id]
assert sensor.state == 662.4

async def test_3phase_always_increasing_counter_reset(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"
# 10 kWh on each phase
msg = '{"id":{"val":"1"},"wextprodq": {"L2": "36000000000", "L3": "36000000000", "L1": "36000000000"}}'
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 sensor.state == 30.0

async def test_average_calculation(hass, mqtt_mock):
config_entry = MockConfigEntry(
Expand Down

0 comments on commit 743cb44

Please sign in to comment.