mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
Aurora abb energy metering (#58454)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
6cdc372dcb
commit
37930aeeb6
@ -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:
|
||||||
|
@ -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:
|
||||||
|
@ -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):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user