Skip to content

Commit

Permalink
Add battery data to Autarco integration (home-assistant#125924)
Browse files Browse the repository at this point in the history
* Rename site to account_site

* Add battery service with entities

* Test UpdateFailed exception in coordinator

* Add battery data to diagnostics report

* Add TOTAL state_class where needed

* Fix

---------

Co-authored-by: Joostlek <[email protected]>
  • Loading branch information
klaasnicolaas and joostlek authored Sep 20, 2024
1 parent 76967e8 commit 1845802
Show file tree
Hide file tree
Showing 8 changed files with 669 additions and 19 deletions.
33 changes: 27 additions & 6 deletions homeassistant/components/autarco/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,19 @@

from typing import NamedTuple

from autarco import AccountSite, Autarco, Inverter, Solar
from autarco import (
AccountSite,
Autarco,
AutarcoConnectionError,
Battery,
Inverter,
Site,
Solar,
)

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import DOMAIN, LOGGER, SCAN_INTERVAL

Expand All @@ -18,6 +26,8 @@ class AutarcoData(NamedTuple):

solar: Solar
inverters: dict[str, Inverter]
site: Site
battery: Battery | None


class AutarcoDataUpdateCoordinator(DataUpdateCoordinator[AutarcoData]):
Expand All @@ -29,7 +39,7 @@ def __init__(
self,
hass: HomeAssistant,
client: Autarco,
site: AccountSite,
account_site: AccountSite,
) -> None:
"""Initialize global Autarco data updater."""
super().__init__(
Expand All @@ -39,11 +49,22 @@ def __init__(
update_interval=SCAN_INTERVAL,
)
self.client = client
self.site = site
self.account_site = account_site

async def _async_update_data(self) -> AutarcoData:
"""Fetch data from Autarco API."""
battery = None
try:
site = await self.client.get_site(self.account_site.public_key)
solar = await self.client.get_solar(self.account_site.public_key)
inverters = await self.client.get_inverters(self.account_site.public_key)
if site.has_battery:
battery = await self.client.get_battery(self.account_site.public_key)
except AutarcoConnectionError as error:
raise UpdateFailed(error) from error
return AutarcoData(
solar=await self.client.get_solar(self.site.public_key),
inverters=await self.client.get_inverters(self.site.public_key),
solar=solar,
inverters=inverters,
site=site,
battery=battery,
)
23 changes: 20 additions & 3 deletions homeassistant/components/autarco/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ async def async_get_config_entry_diagnostics(
return {
"sites_data": [
{
"id": coordinator.site.site_id,
"name": coordinator.site.system_name,
"health": coordinator.site.health,
"id": coordinator.account_site.site_id,
"name": coordinator.account_site.system_name,
"health": coordinator.account_site.health,
"solar": {
"power_production": coordinator.data.solar.power_production,
"energy_production_today": coordinator.data.solar.energy_production_today,
Expand All @@ -37,6 +37,23 @@ async def async_get_config_entry_diagnostics(
}
for inverter in coordinator.data.inverters.values()
],
**(
{
"battery": {
"flow_now": coordinator.data.battery.flow_now,
"net_charged_now": coordinator.data.battery.net_charged_now,
"state_of_charge": coordinator.data.battery.state_of_charge,
"discharged_today": coordinator.data.battery.discharged_today,
"discharged_month": coordinator.data.battery.discharged_month,
"discharged_total": coordinator.data.battery.discharged_total,
"charged_today": coordinator.data.battery.charged_today,
"charged_month": coordinator.data.battery.charged_month,
"charged_total": coordinator.data.battery.charged_total,
}
}
if coordinator.data.battery is not None
else {}
),
}
for coordinator in autarco_data
],
Expand Down
130 changes: 126 additions & 4 deletions homeassistant/components/autarco/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
from collections.abc import Callable
from dataclasses import dataclass

from autarco import Inverter, Solar
from autarco import Battery, Inverter, Solar

from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import UnitOfEnergy, UnitOfPower
from homeassistant.const import PERCENTAGE, UnitOfEnergy, UnitOfPower
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
Expand All @@ -25,6 +25,81 @@
from .coordinator import AutarcoDataUpdateCoordinator


@dataclass(frozen=True, kw_only=True)
class AutarcoBatterySensorEntityDescription(SensorEntityDescription):
"""Describes an Autarco sensor entity."""

value_fn: Callable[[Battery], StateType]


SENSORS_BATTERY: tuple[AutarcoBatterySensorEntityDescription, ...] = (
AutarcoBatterySensorEntityDescription(
key="flow_now",
translation_key="flow_now",
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda battery: battery.flow_now,
),
AutarcoBatterySensorEntityDescription(
key="state_of_charge",
translation_key="state_of_charge",
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.BATTERY,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda battery: battery.state_of_charge,
),
AutarcoBatterySensorEntityDescription(
key="discharged_today",
translation_key="discharged_today",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL,
value_fn=lambda battery: battery.discharged_today,
),
AutarcoBatterySensorEntityDescription(
key="discharged_month",
translation_key="discharged_month",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL,
value_fn=lambda battery: battery.discharged_month,
),
AutarcoBatterySensorEntityDescription(
key="discharged_total",
translation_key="discharged_total",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
value_fn=lambda battery: battery.discharged_total,
),
AutarcoBatterySensorEntityDescription(
key="charged_today",
translation_key="charged_today",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL,
value_fn=lambda battery: battery.charged_today,
),
AutarcoBatterySensorEntityDescription(
key="charged_month",
translation_key="charged_month",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL,
value_fn=lambda battery: battery.charged_month,
),
AutarcoBatterySensorEntityDescription(
key="charged_total",
translation_key="charged_total",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
value_fn=lambda battery: battery.charged_total,
),
)


@dataclass(frozen=True, kw_only=True)
class AutarcoSolarSensorEntityDescription(SensorEntityDescription):
"""Describes an Autarco sensor entity."""
Expand All @@ -46,13 +121,15 @@ class AutarcoSolarSensorEntityDescription(SensorEntityDescription):
translation_key="energy_production_today",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL,
value_fn=lambda solar: solar.energy_production_today,
),
AutarcoSolarSensorEntityDescription(
key="energy_production_month",
translation_key="energy_production_month",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL,
value_fn=lambda solar: solar.energy_production_month,
),
AutarcoSolarSensorEntityDescription(
Expand Down Expand Up @@ -117,9 +194,52 @@ async def async_setup_entry(
for description in SENSORS_INVERTER
for inverter in coordinator.data.inverters
)
if coordinator.data.battery:
entities.extend(
AutarcoBatterySensorEntity(
coordinator=coordinator,
description=description,
)
for description in SENSORS_BATTERY
)
async_add_entities(entities)


class AutarcoBatterySensorEntity(
CoordinatorEntity[AutarcoDataUpdateCoordinator], SensorEntity
):
"""Defines an Autarco battery sensor."""

entity_description: AutarcoBatterySensorEntityDescription
_attr_has_entity_name = True

def __init__(
self,
*,
coordinator: AutarcoDataUpdateCoordinator,
description: AutarcoBatterySensorEntityDescription,
) -> None:
"""Initialize Autarco sensor."""
super().__init__(coordinator)

self.entity_description = description
self._attr_unique_id = (
f"{coordinator.account_site.site_id}_battery_{description.key}"
)
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, f"{coordinator.account_site.site_id}_battery")},
entry_type=DeviceEntryType.SERVICE,
manufacturer="Autarco",
name="Battery",
)

@property
def native_value(self) -> StateType:
"""Return the state of the sensor."""
assert self.coordinator.data.battery is not None
return self.entity_description.value_fn(self.coordinator.data.battery)


class AutarcoSolarSensorEntity(
CoordinatorEntity[AutarcoDataUpdateCoordinator], SensorEntity
):
Expand All @@ -138,9 +258,11 @@ def __init__(
super().__init__(coordinator)

self.entity_description = description
self._attr_unique_id = f"{coordinator.site.site_id}_solar_{description.key}"
self._attr_unique_id = (
f"{coordinator.account_site.site_id}_solar_{description.key}"
)
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, f"{coordinator.site.site_id}_solar")},
identifiers={(DOMAIN, f"{coordinator.account_site.site_id}_solar")},
entry_type=DeviceEntryType.SERVICE,
manufacturer="Autarco",
name="Solar",
Expand Down
24 changes: 24 additions & 0 deletions homeassistant/components/autarco/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,30 @@
},
"entity": {
"sensor": {
"flow_now": {
"name": "Flow now"
},
"state_of_charge": {
"name": "State of charge"
},
"discharged_today": {
"name": "Discharged today"
},
"discharged_month": {
"name": "Discharged month"
},
"discharged_total": {
"name": "Discharged total"
},
"charged_today": {
"name": "Charged today"
},
"charged_month": {
"name": "Charged month"
},
"charged_total": {
"name": "Charged total"
},
"power_production": {
"name": "Power production"
},
Expand Down
13 changes: 12 additions & 1 deletion tests/components/autarco/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from collections.abc import Generator
from unittest.mock import AsyncMock, patch

from autarco import AccountSite, Inverter, Solar
from autarco import AccountSite, Battery, Inverter, Solar
import pytest

from homeassistant.components.autarco.const import DOMAIN
Expand Down Expand Up @@ -66,6 +66,17 @@ def mock_autarco_client() -> Generator[AsyncMock]:
health="OK",
),
}
client.get_battery.return_value = Battery(
flow_now=777,
net_charged_now=777,
state_of_charge=56,
discharged_today=2,
discharged_month=25,
discharged_total=696,
charged_today=1,
charged_month=26,
charged_total=748,
)
yield client


Expand Down
11 changes: 11 additions & 0 deletions tests/components/autarco/snapshots/test_diagnostics.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@
dict({
'sites_data': list([
dict({
'battery': dict({
'charged_month': 26,
'charged_today': 1,
'charged_total': 748,
'discharged_month': 25,
'discharged_today': 2,
'discharged_total': 696,
'flow_now': 777,
'net_charged_now': 777,
'state_of_charge': 56,
}),
'health': 'OK',
'id': 1,
'inverters': list([
Expand Down
Loading

0 comments on commit 1845802

Please sign in to comment.