Skip to content

Commit c1b89a5

Browse files
authored
Bugfix: automatic sensor update not working - Fixes #43 (#44)
* Fixed Coordinator Vererbung * bump version * Tests added * Added test requirements
1 parent 0fd834c commit c1b89a5

4 files changed

Lines changed: 85 additions & 26 deletions

File tree

custom_components/enpal_webparser/entity_factory.py

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,31 +18,39 @@
1818
#
1919

2020
from functools import cached_property
21+
2122
from homeassistant.components.sensor import SensorEntity, SensorDeviceClass
22-
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
23+
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, CoordinatorEntity
2324
from homeassistant.helpers.entity import DeviceInfo
2425

2526
from .utils import make_id
2627

27-
class EnpalBaseSensor(SensorEntity):
28-
"""Generische Enpal Sensor-Entity, geeignet für die meisten Sensoren."""
28+
class EnpalBaseSensor(CoordinatorEntity, SensorEntity):
29+
"""Generic Enpal sensor entity using the update coordinator."""
2930

3031
def __init__(self, sensor: dict, coordinator: DataUpdateCoordinator):
32+
super().__init__(coordinator)
33+
self._sensor = sensor
3134
self._attr_name = sensor.get("name")
3235
self._attr_unique_id = make_id(sensor.get("name", "unknown"))
33-
self._attr_native_value = sensor.get("value")
3436
self._attr_native_unit_of_measurement = sensor.get("unit")
35-
# device_class als Enum, falls möglich:
37+
self._attr_enabled_default = sensor.get("enabled", True)
38+
3639
device_class = sensor.get("device_class")
3740
if device_class and hasattr(SensorDeviceClass, device_class.upper()):
3841
self._attr_device_class = getattr(SensorDeviceClass, device_class.upper())
3942
else:
4043
self._attr_device_class = device_class
41-
self._attr_enabled_default = sensor.get("enabled", True)
42-
self._attr_extra_state_attributes = {
43-
"enpal_last_update": sensor.get("enpal_last_update")
44+
45+
@property
46+
def native_value(self):
47+
return self._sensor.get("value")
48+
49+
@property
50+
def extra_state_attributes(self):
51+
return {
52+
"enpal_last_update": self._sensor.get("enpal_last_update")
4453
}
45-
self._coordinator = coordinator
4654

4755
@cached_property
4856
def device_info(self) -> DeviceInfo:
@@ -53,32 +61,29 @@ def device_info(self) -> DeviceInfo:
5361
"model": "Webparser",
5462
}
5563

56-
57-
async def async_update(self):
58-
await self._coordinator.async_request_refresh()
64+
def _handle_coordinator_update(self):
65+
for s in self.coordinator.data:
66+
if make_id(s.get("name", "")) == self._attr_unique_id:
67+
self._sensor = s
68+
break
69+
self.async_write_ha_state()
5970

6071
async def async_added_to_hass(self):
61-
self._coordinator.async_add_listener(self._handle_coordinator_update)
72+
await super().async_added_to_hass()
73+
self._handle_coordinator_update()
6274

63-
def _handle_coordinator_update(self):
64-
# Hier kannst du das Update-Handling noch anpassen, falls nötig!
65-
self.async_write_ha_state()
6675

6776
def build_sensor_entity(sensor: dict, coordinator: DataUpdateCoordinator) -> SensorEntity:
6877
"""
69-
Factory-Funktion: Baut die passende SensorEntity.
70-
Hier kannst du auch Spezialfälle oder Subklassen einbauen.
78+
Factory function: Builds the appropriate sensor entity.
79+
Extendable for special cases or subclasses.
7180
"""
72-
# Beispiel für Spezialfall: Energiesensor mit zusätzlichem Attribut
7381
if sensor.get("device_class") == "energy":
7482
return EnpalEnergySensor(sensor, coordinator)
75-
# Weitere Spezialfälle ...
7683
return EnpalBaseSensor(sensor, coordinator)
7784

78-
# Beispiel für einen spezialisierten Sensor
85+
7986
class EnpalEnergySensor(EnpalBaseSensor):
8087
def __init__(self, sensor: dict, coordinator: DataUpdateCoordinator):
8188
super().__init__(sensor, coordinator)
82-
self._attr_state_class = "total_increasing" # Nur für Energie!
83-
84-
# Hier könntest du noch eigene Properties oder Methoden ergänzen
89+
self._attr_state_class = "total_increasing"

custom_components/enpal_webparser/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@
88
"iot_class": "local_polling",
99
"issue_tracker": "https://github.com/derolli1976/enpal/issues",
1010
"requirements": [],
11-
"version": "2.1.3"
11+
"version": "2.1.4"
1212
}

custom_components/enpal_webparser/tests/test_entity_factory.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,14 @@
99
# To run: pytest custom_components/enpal_webparser/tests/test_entity_factory.py
1010
#
1111

12+
import pytest
13+
from datetime import timedelta
14+
15+
from unittest.mock import AsyncMock
16+
1217
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
18+
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
19+
from homeassistant.core import HomeAssistant
1320
from custom_components.enpal_webparser.entity_factory import (
1421
build_sensor_entity,
1522
EnpalBaseSensor,
@@ -78,3 +85,50 @@ def test_sensor_device_info():
7885
assert device_info.get("manufacturer") == "Enpal"
7986
assert device_info.get("model") == "Webparser"
8087

88+
@pytest.fixture
89+
def hass():
90+
"""Return a mocked hass instance."""
91+
hass = AsyncMock(spec=HomeAssistant)
92+
return hass
93+
94+
@pytest.fixture
95+
def mock_sensor_dict():
96+
return {
97+
"name": "Test Sensor",
98+
"value": 123.45,
99+
"unit": "kWh",
100+
"device_class": "energy",
101+
"enpal_last_update": "2024-06-01T12:00:00",
102+
}
103+
104+
@pytest.fixture
105+
def hass_coordinator(hass: HomeAssistant):
106+
async def _update_method():
107+
return [{
108+
"name": "Test Sensor",
109+
"value": 987.65,
110+
"unit": "kWh",
111+
"device_class": "energy",
112+
"enpal_last_update": "2024-06-01T12:30:00",
113+
}]
114+
coordinator = DataUpdateCoordinator(
115+
hass,
116+
logger=None,
117+
name="test",
118+
update_method=_update_method,
119+
update_interval=timedelta(seconds=30),
120+
)
121+
return coordinator
122+
123+
@pytest.mark.asyncio
124+
async def test_build_energy_sensor_full(hass: HomeAssistant, mock_sensor_dict, hass_coordinator):
125+
sensor = build_sensor_entity(mock_sensor_dict, hass_coordinator)
126+
assert isinstance(sensor, EnpalEnergySensor)
127+
assert sensor.name == "Test Sensor"
128+
assert sensor.native_value == 123.45
129+
assert sensor.native_unit_of_measurement == "kWh"
130+
assert sensor.device_class == "energy"
131+
assert sensor.state_class == "total_increasing"
132+
assert "enpal_last_update" in sensor.extra_state_attributes
133+
134+

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ bs4
22
homeassistant
33
voluptuous
44
requests
5-
5+
pytest-asyncio>=0.21

0 commit comments

Comments
 (0)