From 37930aeeb63d0d12652e094ae15b283d0a8bb5b2 Mon Sep 17 00:00:00 2001
From: Dave T <17680170+davet2001@users.noreply.github.com>
Date: Thu, 28 Oct 2021 22:47:49 +0100
Subject: [PATCH] Aurora abb energy metering (#58454)

Co-authored-by: J. Nick Koston <nick@koston.org>
---
 .../aurora_abb_powerone/aurora_device.py      |  2 +-
 .../components/aurora_abb_powerone/sensor.py  | 67 ++++++++++++-------
 .../aurora_abb_powerone/test_sensor.py        | 35 ++++------
 3 files changed, 58 insertions(+), 46 deletions(-)

diff --git a/homeassistant/components/aurora_abb_powerone/aurora_device.py b/homeassistant/components/aurora_abb_powerone/aurora_device.py
index 0a7aab4a921..3913515a9b9 100644
--- a/homeassistant/components/aurora_abb_powerone/aurora_device.py
+++ b/homeassistant/components/aurora_abb_powerone/aurora_device.py
@@ -32,7 +32,7 @@ class AuroraDevice(Entity):
     def unique_id(self) -> str:
         """Return the unique id for this device."""
         serial = self._data[ATTR_SERIAL_NUMBER]
-        return f"{serial}_{self.type}"
+        return f"{serial}_{self.entity_description.key}"
 
     @property
     def available(self) -> bool:
diff --git a/homeassistant/components/aurora_abb_powerone/sensor.py b/homeassistant/components/aurora_abb_powerone/sensor.py
index 946f5645bdc..4f196c39630 100644
--- a/homeassistant/components/aurora_abb_powerone/sensor.py
+++ b/homeassistant/components/aurora_abb_powerone/sensor.py
@@ -1,6 +1,9 @@
 """Support for Aurora ABB PowerOne Solar Photvoltaic (PV) inverter."""
+from __future__ import annotations
 
+from collections.abc import Mapping
 import logging
+from typing import Any
 
 from aurorapy.client import AuroraError, AuroraSerialClient
 import voluptuous as vol
@@ -8,19 +11,22 @@ import voluptuous as vol
 from homeassistant.components.sensor import (
     PLATFORM_SCHEMA,
     STATE_CLASS_MEASUREMENT,
+    STATE_CLASS_TOTAL_INCREASING,
     SensorEntity,
+    SensorEntityDescription,
 )
 from homeassistant.config_entries import SOURCE_IMPORT
 from homeassistant.const import (
     CONF_ADDRESS,
     CONF_DEVICE,
     CONF_NAME,
+    DEVICE_CLASS_ENERGY,
     DEVICE_CLASS_POWER,
     DEVICE_CLASS_TEMPERATURE,
+    ENERGY_KILO_WATT_HOUR,
     POWER_WATT,
     TEMP_CELSIUS,
 )
-from homeassistant.exceptions import InvalidStateError
 import homeassistant.helpers.config_validation as cv
 
 from .aurora_device import AuroraDevice
@@ -28,6 +34,29 @@ from .const import DEFAULT_ADDRESS, DOMAIN
 
 _LOGGER = logging.getLogger(__name__)
 
+SENSOR_TYPES = [
+    SensorEntityDescription(
+        key="instantaneouspower",
+        device_class=DEVICE_CLASS_POWER,
+        native_unit_of_measurement=POWER_WATT,
+        state_class=STATE_CLASS_MEASUREMENT,
+        name="Power Output",
+    ),
+    SensorEntityDescription(
+        key="temp",
+        device_class=DEVICE_CLASS_TEMPERATURE,
+        native_unit_of_measurement=TEMP_CELSIUS,
+        state_class=STATE_CLASS_MEASUREMENT,
+        name="Temperature",
+    ),
+    SensorEntityDescription(
+        key="totalenergy",
+        device_class=DEVICE_CLASS_ENERGY,
+        native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
+        state_class=STATE_CLASS_TOTAL_INCREASING,
+        name="Total Energy",
+    ),
+]
 
 PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
     {
@@ -55,15 +84,11 @@ async def async_setup_entry(hass, config_entry, async_add_entities) -> None:
     """Set up aurora_abb_powerone sensor based on a config entry."""
     entities = []
 
-    sensortypes = [
-        {"parameter": "instantaneouspower", "name": "Power Output"},
-        {"parameter": "temperature", "name": "Temperature"},
-    ]
     client = hass.data[DOMAIN][config_entry.unique_id]
     data = config_entry.data
 
-    for sens in sensortypes:
-        entities.append(AuroraSensor(client, data, sens["name"], sens["parameter"]))
+    for sens in SENSOR_TYPES:
+        entities.append(AuroraSensor(client, data, sens))
 
     _LOGGER.debug("async_setup_entry adding %d entities", len(entities))
     async_add_entities(entities, True)
@@ -72,22 +97,15 @@ async def async_setup_entry(hass, config_entry, async_add_entities) -> None:
 class AuroraSensor(AuroraDevice, SensorEntity):
     """Representation of a Sensor on a Aurora ABB PowerOne Solar inverter."""
 
-    _attr_state_class = STATE_CLASS_MEASUREMENT
-
-    def __init__(self, client: AuroraSerialClient, data, name, typename):
+    def __init__(
+        self,
+        client: AuroraSerialClient,
+        data: Mapping[str, Any],
+        entity_description: SensorEntityDescription,
+    ) -> None:
         """Initialize the sensor."""
         super().__init__(client, data)
-        if typename == "instantaneouspower":
-            self.type = typename
-            self._attr_native_unit_of_measurement = POWER_WATT
-            self._attr_device_class = DEVICE_CLASS_POWER
-        elif typename == "temperature":
-            self.type = typename
-            self._attr_native_unit_of_measurement = TEMP_CELSIUS
-            self._attr_device_class = DEVICE_CLASS_TEMPERATURE
-        else:
-            raise InvalidStateError(f"Unrecognised typename '{typename}'")
-        self._attr_name = f"{name}"
+        self.entity_description = entity_description
         self.availableprev = True
 
     def update(self):
@@ -98,13 +116,16 @@ class AuroraSensor(AuroraDevice, SensorEntity):
         try:
             self.availableprev = self._attr_available
             self.client.connect()
-            if self.type == "instantaneouspower":
+            if self.entity_description.key == "instantaneouspower":
                 # read ADC channel 3 (grid power output)
                 power_watts = self.client.measure(3, True)
                 self._attr_native_value = round(power_watts, 1)
-            elif self.type == "temperature":
+            elif self.entity_description.key == "temp":
                 temperature_c = self.client.measure(21)
                 self._attr_native_value = round(temperature_c, 1)
+            elif self.entity_description.key == "totalenergy":
+                energy_wh = self.client.cumulated_energy(5)
+                self._attr_native_value = round(energy_wh / 1000, 2)
             self._attr_available = True
 
         except AuroraError as error:
diff --git a/tests/components/aurora_abb_powerone/test_sensor.py b/tests/components/aurora_abb_powerone/test_sensor.py
index 26486c6a116..ae9360498c7 100644
--- a/tests/components/aurora_abb_powerone/test_sensor.py
+++ b/tests/components/aurora_abb_powerone/test_sensor.py
@@ -3,7 +3,6 @@ from datetime import timedelta
 from unittest.mock import patch
 
 from aurorapy.client import AuroraError
-import pytest
 
 from homeassistant.components.aurora_abb_powerone.const import (
     ATTR_DEVICE_NAME,
@@ -13,10 +12,8 @@ from homeassistant.components.aurora_abb_powerone.const import (
     DEFAULT_INTEGRATION_TITLE,
     DOMAIN,
 )
-from homeassistant.components.aurora_abb_powerone.sensor import AuroraSensor
 from homeassistant.config_entries import SOURCE_IMPORT
 from homeassistant.const import CONF_ADDRESS, CONF_PORT
-from homeassistant.exceptions import InvalidStateError
 from homeassistant.setup import async_setup_component
 import homeassistant.util.dt as dt_util
 
@@ -39,6 +36,7 @@ def _simulated_returns(index, global_measure=None):
     returns = {
         3: 45.678,  # power
         21: 9.876,  # temperature
+        5: 12345,  # energy
     }
     return returns[index]
 
@@ -66,7 +64,12 @@ async def test_setup_platform_valid_config(hass):
     with patch("aurorapy.client.AuroraSerialClient.connect", return_value=None), patch(
         "aurorapy.client.AuroraSerialClient.measure",
         side_effect=_simulated_returns,
-    ), assert_setup_component(1, "sensor"):
+    ), patch(
+        "aurorapy.client.AuroraSerialClient.cumulated_energy",
+        side_effect=_simulated_returns,
+    ), assert_setup_component(
+        1, "sensor"
+    ):
         assert await async_setup_component(hass, "sensor", TEST_CONFIG)
         await hass.async_block_till_done()
     power = hass.states.get("sensor.power_output")
@@ -91,6 +94,9 @@ async def test_sensors(hass):
     with patch("aurorapy.client.AuroraSerialClient.connect", return_value=None), patch(
         "aurorapy.client.AuroraSerialClient.measure",
         side_effect=_simulated_returns,
+    ), patch(
+        "aurorapy.client.AuroraSerialClient.cumulated_energy",
+        side_effect=_simulated_returns,
     ):
         mock_entry.add_to_hass(hass)
         await hass.config_entries.async_setup(mock_entry.entry_id)
@@ -104,24 +110,9 @@ async def test_sensors(hass):
         assert temperature
         assert temperature.state == "9.9"
 
-
-async def test_sensor_invalid_type(hass):
-    """Test invalid sensor type during setup."""
-    entities = []
-    mock_entry = _mock_config_entry()
-
-    with patch("aurorapy.client.AuroraSerialClient.connect", return_value=None), patch(
-        "aurorapy.client.AuroraSerialClient.measure",
-        side_effect=_simulated_returns,
-    ):
-        mock_entry.add_to_hass(hass)
-        await hass.config_entries.async_setup(mock_entry.entry_id)
-        await hass.async_block_till_done()
-
-        client = hass.data[DOMAIN][mock_entry.unique_id]
-        data = mock_entry.data
-    with pytest.raises(InvalidStateError):
-        entities.append(AuroraSensor(client, data, "WrongSensor", "wrongparameter"))
+        energy = hass.states.get("sensor.total_energy")
+        assert energy
+        assert energy.state == "12.35"
 
 
 async def test_sensor_dark(hass):
-- 
GitLab