Aurora abb energy metering (#58454)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Dave T 2021-10-28 22:47:49 +01:00 committed by GitHub
parent 6cdc372dcb
commit 37930aeeb6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 58 additions and 46 deletions

View File

@ -32,7 +32,7 @@ class AuroraDevice(Entity):
def unique_id(self) -> str: def unique_id(self) -> str:
"""Return the unique id for this device.""" """Return the unique id for this device."""
serial = self._data[ATTR_SERIAL_NUMBER] serial = self._data[ATTR_SERIAL_NUMBER]
return f"{serial}_{self.type}" return f"{serial}_{self.entity_description.key}"
@property @property
def available(self) -> bool: def available(self) -> bool:

View File

@ -1,6 +1,9 @@
"""Support for Aurora ABB PowerOne Solar Photvoltaic (PV) inverter.""" """Support for Aurora ABB PowerOne Solar Photvoltaic (PV) inverter."""
from __future__ import annotations
from collections.abc import Mapping
import logging import logging
from typing import Any
from aurorapy.client import AuroraError, AuroraSerialClient from aurorapy.client import AuroraError, AuroraSerialClient
import voluptuous as vol import voluptuous as vol
@ -8,19 +11,22 @@ import voluptuous as vol
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
PLATFORM_SCHEMA, PLATFORM_SCHEMA,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
SensorEntity, SensorEntity,
SensorEntityDescription,
) )
from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import ( from homeassistant.const import (
CONF_ADDRESS, CONF_ADDRESS,
CONF_DEVICE, CONF_DEVICE,
CONF_NAME, CONF_NAME,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER, DEVICE_CLASS_POWER,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
ENERGY_KILO_WATT_HOUR,
POWER_WATT, POWER_WATT,
TEMP_CELSIUS, TEMP_CELSIUS,
) )
from homeassistant.exceptions import InvalidStateError
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from .aurora_device import AuroraDevice from .aurora_device import AuroraDevice
@ -28,6 +34,29 @@ from .const import DEFAULT_ADDRESS, DOMAIN
_LOGGER = logging.getLogger(__name__) _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( 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.""" """Set up aurora_abb_powerone sensor based on a config entry."""
entities = [] entities = []
sensortypes = [
{"parameter": "instantaneouspower", "name": "Power Output"},
{"parameter": "temperature", "name": "Temperature"},
]
client = hass.data[DOMAIN][config_entry.unique_id] client = hass.data[DOMAIN][config_entry.unique_id]
data = config_entry.data data = config_entry.data
for sens in sensortypes: for sens in SENSOR_TYPES:
entities.append(AuroraSensor(client, data, sens["name"], sens["parameter"])) entities.append(AuroraSensor(client, data, sens))
_LOGGER.debug("async_setup_entry adding %d entities", len(entities)) _LOGGER.debug("async_setup_entry adding %d entities", len(entities))
async_add_entities(entities, True) 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): class AuroraSensor(AuroraDevice, SensorEntity):
"""Representation of a Sensor on a Aurora ABB PowerOne Solar inverter.""" """Representation of a Sensor on a Aurora ABB PowerOne Solar inverter."""
_attr_state_class = STATE_CLASS_MEASUREMENT def __init__(
self,
def __init__(self, client: AuroraSerialClient, data, name, typename): client: AuroraSerialClient,
data: Mapping[str, Any],
entity_description: SensorEntityDescription,
) -> None:
"""Initialize the sensor.""" """Initialize the sensor."""
super().__init__(client, data) super().__init__(client, data)
if typename == "instantaneouspower": self.entity_description = entity_description
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.availableprev = True self.availableprev = True
def update(self): def update(self):
@ -98,13 +116,16 @@ class AuroraSensor(AuroraDevice, SensorEntity):
try: try:
self.availableprev = self._attr_available self.availableprev = self._attr_available
self.client.connect() self.client.connect()
if self.type == "instantaneouspower": if self.entity_description.key == "instantaneouspower":
# read ADC channel 3 (grid power output) # read ADC channel 3 (grid power output)
power_watts = self.client.measure(3, True) power_watts = self.client.measure(3, True)
self._attr_native_value = round(power_watts, 1) self._attr_native_value = round(power_watts, 1)
elif self.type == "temperature": elif self.entity_description.key == "temp":
temperature_c = self.client.measure(21) temperature_c = self.client.measure(21)
self._attr_native_value = round(temperature_c, 1) 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 self._attr_available = True
except AuroraError as error: except AuroraError as error:

View File

@ -3,7 +3,6 @@ from datetime import timedelta
from unittest.mock import patch from unittest.mock import patch
from aurorapy.client import AuroraError from aurorapy.client import AuroraError
import pytest
from homeassistant.components.aurora_abb_powerone.const import ( from homeassistant.components.aurora_abb_powerone.const import (
ATTR_DEVICE_NAME, ATTR_DEVICE_NAME,
@ -13,10 +12,8 @@ from homeassistant.components.aurora_abb_powerone.const import (
DEFAULT_INTEGRATION_TITLE, DEFAULT_INTEGRATION_TITLE,
DOMAIN, DOMAIN,
) )
from homeassistant.components.aurora_abb_powerone.sensor import AuroraSensor
from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import CONF_ADDRESS, CONF_PORT from homeassistant.const import CONF_ADDRESS, CONF_PORT
from homeassistant.exceptions import InvalidStateError
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
@ -39,6 +36,7 @@ def _simulated_returns(index, global_measure=None):
returns = { returns = {
3: 45.678, # power 3: 45.678, # power
21: 9.876, # temperature 21: 9.876, # temperature
5: 12345, # energy
} }
return returns[index] 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( with patch("aurorapy.client.AuroraSerialClient.connect", return_value=None), patch(
"aurorapy.client.AuroraSerialClient.measure", "aurorapy.client.AuroraSerialClient.measure",
side_effect=_simulated_returns, 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) assert await async_setup_component(hass, "sensor", TEST_CONFIG)
await hass.async_block_till_done() await hass.async_block_till_done()
power = hass.states.get("sensor.power_output") 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( with patch("aurorapy.client.AuroraSerialClient.connect", return_value=None), patch(
"aurorapy.client.AuroraSerialClient.measure", "aurorapy.client.AuroraSerialClient.measure",
side_effect=_simulated_returns, side_effect=_simulated_returns,
), patch(
"aurorapy.client.AuroraSerialClient.cumulated_energy",
side_effect=_simulated_returns,
): ):
mock_entry.add_to_hass(hass) mock_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_entry.entry_id) await hass.config_entries.async_setup(mock_entry.entry_id)
@ -104,24 +110,9 @@ async def test_sensors(hass):
assert temperature assert temperature
assert temperature.state == "9.9" assert temperature.state == "9.9"
energy = hass.states.get("sensor.total_energy")
async def test_sensor_invalid_type(hass): assert energy
"""Test invalid sensor type during setup.""" assert energy.state == "12.35"
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"))
async def test_sensor_dark(hass): async def test_sensor_dark(hass):