Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 29 additions & 24 deletions custom_components/enpal_webparser/entity_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,39 @@
#

from functools import cached_property

from homeassistant.components.sensor import SensorEntity, SensorDeviceClass
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, CoordinatorEntity
from homeassistant.helpers.entity import DeviceInfo

from .utils import make_id

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

def __init__(self, sensor: dict, coordinator: DataUpdateCoordinator):
super().__init__(coordinator)
self._sensor = sensor
self._attr_name = sensor.get("name")
self._attr_unique_id = make_id(sensor.get("name", "unknown"))
self._attr_native_value = sensor.get("value")
self._attr_native_unit_of_measurement = sensor.get("unit")
# device_class als Enum, falls möglich:
self._attr_enabled_default = sensor.get("enabled", True)

device_class = sensor.get("device_class")
if device_class and hasattr(SensorDeviceClass, device_class.upper()):
self._attr_device_class = getattr(SensorDeviceClass, device_class.upper())
else:
self._attr_device_class = device_class
self._attr_enabled_default = sensor.get("enabled", True)
self._attr_extra_state_attributes = {
"enpal_last_update": sensor.get("enpal_last_update")

@property
def native_value(self):
return self._sensor.get("value")

@property
def extra_state_attributes(self):
return {
"enpal_last_update": self._sensor.get("enpal_last_update")
}
self._coordinator = coordinator

@cached_property
def device_info(self) -> DeviceInfo:
Expand All @@ -53,32 +61,29 @@ def device_info(self) -> DeviceInfo:
"model": "Webparser",
}


async def async_update(self):
await self._coordinator.async_request_refresh()
def _handle_coordinator_update(self):
for s in self.coordinator.data:
if make_id(s.get("name", "")) == self._attr_unique_id:
self._sensor = s
break
self.async_write_ha_state()

async def async_added_to_hass(self):
self._coordinator.async_add_listener(self._handle_coordinator_update)
await super().async_added_to_hass()
self._handle_coordinator_update()

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

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

# Beispiel für einen spezialisierten Sensor

class EnpalEnergySensor(EnpalBaseSensor):
def __init__(self, sensor: dict, coordinator: DataUpdateCoordinator):
super().__init__(sensor, coordinator)
self._attr_state_class = "total_increasing" # Nur für Energie!

# Hier könntest du noch eigene Properties oder Methoden ergänzen
self._attr_state_class = "total_increasing"
2 changes: 1 addition & 1 deletion custom_components/enpal_webparser/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"iot_class": "local_polling",
"issue_tracker": "https://github.com/derolli1976/enpal/issues",
"requirements": [],
"version": "2.1.3"
"version": "2.1.4"
}
54 changes: 54 additions & 0 deletions custom_components/enpal_webparser/tests/test_entity_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@
# To run: pytest custom_components/enpal_webparser/tests/test_entity_factory.py
#

import pytest
from datetime import timedelta

from unittest.mock import AsyncMock

from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.core import HomeAssistant
from custom_components.enpal_webparser.entity_factory import (
build_sensor_entity,
EnpalBaseSensor,
Expand Down Expand Up @@ -78,3 +85,50 @@ def test_sensor_device_info():
assert device_info.get("manufacturer") == "Enpal"
assert device_info.get("model") == "Webparser"

@pytest.fixture
def hass():
"""Return a mocked hass instance."""
hass = AsyncMock(spec=HomeAssistant)
return hass

@pytest.fixture
def mock_sensor_dict():
return {
"name": "Test Sensor",
"value": 123.45,
"unit": "kWh",
"device_class": "energy",
"enpal_last_update": "2024-06-01T12:00:00",
}

@pytest.fixture
def hass_coordinator(hass: HomeAssistant):
async def _update_method():
return [{
"name": "Test Sensor",
"value": 987.65,
"unit": "kWh",
"device_class": "energy",
"enpal_last_update": "2024-06-01T12:30:00",
}]
coordinator = DataUpdateCoordinator(
hass,
logger=None,
name="test",
update_method=_update_method,
update_interval=timedelta(seconds=30),
)
return coordinator

@pytest.mark.asyncio
async def test_build_energy_sensor_full(hass: HomeAssistant, mock_sensor_dict, hass_coordinator):
sensor = build_sensor_entity(mock_sensor_dict, hass_coordinator)
assert isinstance(sensor, EnpalEnergySensor)
assert sensor.name == "Test Sensor"
assert sensor.native_value == 123.45
assert sensor.native_unit_of_measurement == "kWh"
assert sensor.device_class == "energy"
assert sensor.state_class == "total_increasing"
assert "enpal_last_update" in sensor.extra_state_attributes


2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ bs4
homeassistant
voluptuous
requests

pytest-asyncio>=0.21