Add energy attributes to Fronius (#53741)

* Add energy attributes to Fronius

* Add solar

* Add power

* Only add last reset for total meter entities

* Only add last reset for total solar entities

* Create different entity descriptions per key

* only return the entity description for energy total

* Use correct key

* Meter devices keep it real

* keys start with energy_real

* Also device key starts with

* Lint
This commit is contained in:
Paulus Schoutsen 2021-07-29 23:34:03 -07:00 committed by GitHub
parent 87dab02ce6
commit 05a7853720
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -8,17 +8,26 @@ import logging
from pyfronius import Fronius from pyfronius import Fronius
import voluptuous as vol import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.components.sensor import (
PLATFORM_SCHEMA,
STATE_CLASS_MEASUREMENT,
SensorEntity,
SensorEntityDescription,
)
from homeassistant.const import ( from homeassistant.const import (
CONF_DEVICE, CONF_DEVICE,
CONF_MONITORED_CONDITIONS, CONF_MONITORED_CONDITIONS,
CONF_RESOURCE, CONF_RESOURCE,
CONF_SCAN_INTERVAL, CONF_SCAN_INTERVAL,
CONF_SENSOR_TYPE, CONF_SENSOR_TYPE,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
) )
from homeassistant.core import callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.event import async_track_time_interval
from homeassistant.util import dt
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -152,6 +161,12 @@ class FroniusAdapter:
"""Whether the fronius device is active.""" """Whether the fronius device is active."""
return self._available return self._available
def entity_description( # pylint: disable=no-self-use
self, key
) -> SensorEntityDescription | None:
"""Create entity description for a key."""
return None
async def async_update(self): async def async_update(self):
"""Retrieve and update latest state.""" """Retrieve and update latest state."""
try: try:
@ -198,14 +213,28 @@ class FroniusAdapter:
async def _update(self) -> dict: async def _update(self) -> dict:
"""Return values of interest.""" """Return values of interest."""
async def register(self, sensor): @callback
def register(self, sensor):
"""Register child sensor for update subscriptions.""" """Register child sensor for update subscriptions."""
self._registered_sensors.add(sensor) self._registered_sensors.add(sensor)
return lambda: self._registered_sensors.remove(sensor)
class FroniusInverterSystem(FroniusAdapter): class FroniusInverterSystem(FroniusAdapter):
"""Adapter for the fronius inverter with system scope.""" """Adapter for the fronius inverter with system scope."""
def entity_description(self, key):
"""Return the entity descriptor."""
if key != "energy_total":
return None
return SensorEntityDescription(
key=key,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_MEASUREMENT,
last_reset=dt.utc_from_timestamp(0),
)
async def _update(self): async def _update(self):
"""Get the values for the current state.""" """Get the values for the current state."""
return await self.bridge.current_system_inverter_data() return await self.bridge.current_system_inverter_data()
@ -214,6 +243,18 @@ class FroniusInverterSystem(FroniusAdapter):
class FroniusInverterDevice(FroniusAdapter): class FroniusInverterDevice(FroniusAdapter):
"""Adapter for the fronius inverter with device scope.""" """Adapter for the fronius inverter with device scope."""
def entity_description(self, key):
"""Return the entity descriptor."""
if key != "energy_total":
return None
return SensorEntityDescription(
key=key,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_MEASUREMENT,
last_reset=dt.utc_from_timestamp(0),
)
async def _update(self): async def _update(self):
"""Get the values for the current state.""" """Get the values for the current state."""
return await self.bridge.current_inverter_data(self._device) return await self.bridge.current_inverter_data(self._device)
@ -230,6 +271,18 @@ class FroniusStorage(FroniusAdapter):
class FroniusMeterSystem(FroniusAdapter): class FroniusMeterSystem(FroniusAdapter):
"""Adapter for the fronius meter with system scope.""" """Adapter for the fronius meter with system scope."""
def entity_description(self, key):
"""Return the entity descriptor."""
if not key.startswith("energy_real_"):
return None
return SensorEntityDescription(
key=key,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_MEASUREMENT,
last_reset=dt.utc_from_timestamp(0),
)
async def _update(self): async def _update(self):
"""Get the values for the current state.""" """Get the values for the current state."""
return await self.bridge.current_system_meter_data() return await self.bridge.current_system_meter_data()
@ -238,6 +291,18 @@ class FroniusMeterSystem(FroniusAdapter):
class FroniusMeterDevice(FroniusAdapter): class FroniusMeterDevice(FroniusAdapter):
"""Adapter for the fronius meter with device scope.""" """Adapter for the fronius meter with device scope."""
def entity_description(self, key):
"""Return the entity descriptor."""
if not key.startswith("energy_real_"):
return None
return SensorEntityDescription(
key=key,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_MEASUREMENT,
last_reset=dt.utc_from_timestamp(0),
)
async def _update(self): async def _update(self):
"""Get the values for the current state.""" """Get the values for the current state."""
return await self.bridge.current_meter_data(self._device) return await self.bridge.current_meter_data(self._device)
@ -246,6 +311,14 @@ class FroniusMeterDevice(FroniusAdapter):
class FroniusPowerFlow(FroniusAdapter): class FroniusPowerFlow(FroniusAdapter):
"""Adapter for the fronius power flow.""" """Adapter for the fronius power flow."""
def entity_description(self, key):
"""Return the entity descriptor."""
return SensorEntityDescription(
key=key,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
)
async def _update(self): async def _update(self):
"""Get the values for the current state.""" """Get the values for the current state."""
return await self.bridge.current_power_flow() return await self.bridge.current_power_flow()
@ -254,27 +327,13 @@ class FroniusPowerFlow(FroniusAdapter):
class FroniusTemplateSensor(SensorEntity): class FroniusTemplateSensor(SensorEntity):
"""Sensor for the single values (e.g. pv power, ac power).""" """Sensor for the single values (e.g. pv power, ac power)."""
def __init__(self, parent: FroniusAdapter, name): def __init__(self, parent: FroniusAdapter, key):
"""Initialize a singular value sensor.""" """Initialize a singular value sensor."""
self._name = name self._key = key
self.parent = parent self._attr_name = f"{key.replace('_', ' ').capitalize()} {parent.name}"
self._state = None self._parent = parent
self._unit = None if entity_description := parent.entity_description(key):
self.entity_description = entity_description
@property
def name(self):
"""Return the name of the sensor."""
return f"{self._name.replace('_', ' ').capitalize()} {self.parent.name}"
@property
def state(self):
"""Return the current state."""
return self._state
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return self._unit
@property @property
def should_poll(self): def should_poll(self):
@ -284,19 +343,19 @@ class FroniusTemplateSensor(SensorEntity):
@property @property
def available(self): def available(self):
"""Whether the fronius device is active.""" """Whether the fronius device is active."""
return self.parent.available return self._parent.available
async def async_update(self): async def async_update(self):
"""Update the internal state.""" """Update the internal state."""
state = self.parent.data.get(self._name) state = self._parent.data.get(self._key)
self._state = state.get("value") self._attr_state = state.get("value")
if isinstance(self._state, float): if isinstance(self._attr_state, float):
self._state = round(self._state, 2) self._attr_state = round(self._attr_state, 2)
self._unit = state.get("unit") self._attr_unit_of_measurement = state.get("unit")
async def async_added_to_hass(self): async def async_added_to_hass(self):
"""Register at parent component for updates.""" """Register at parent component for updates."""
await self.parent.register(self) self.async_on_remove(self._parent.register(self))
def __hash__(self): def __hash__(self):
"""Hash sensor by hashing its name.""" """Hash sensor by hashing its name."""