From 0a27b0f3534bcf1f4b54ae16e634fc332f693af4 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 --- .../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):