194 lines
6.1 KiB
Python
194 lines
6.1 KiB
Python
"""Support for Solax inverter via local API."""
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from datetime import timedelta
|
|
|
|
from solax import RealTimeAPI
|
|
from solax.discovery import InverterError
|
|
from solax.units import Units
|
|
|
|
from homeassistant.components.sensor import (
|
|
SensorDeviceClass,
|
|
SensorEntity,
|
|
SensorEntityDescription,
|
|
SensorStateClass,
|
|
)
|
|
from homeassistant.config_entries import ConfigEntry
|
|
from homeassistant.const import (
|
|
PERCENTAGE,
|
|
UnitOfElectricCurrent,
|
|
UnitOfElectricPotential,
|
|
UnitOfEnergy,
|
|
UnitOfFrequency,
|
|
UnitOfPower,
|
|
UnitOfTemperature,
|
|
)
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.exceptions import PlatformNotReady
|
|
from homeassistant.helpers.entity import DeviceInfo
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
from homeassistant.helpers.event import async_track_time_interval
|
|
|
|
from .const import DOMAIN, MANUFACTURER
|
|
|
|
DEFAULT_PORT = 80
|
|
SCAN_INTERVAL = timedelta(seconds=30)
|
|
|
|
|
|
SENSOR_DESCRIPTIONS: dict[tuple[Units, bool], SensorEntityDescription] = {
|
|
(Units.C, False): SensorEntityDescription(
|
|
key=f"{Units.C}_{False}",
|
|
device_class=SensorDeviceClass.TEMPERATURE,
|
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
),
|
|
(Units.KWH, False): SensorEntityDescription(
|
|
key=f"{Units.KWH}_{False}",
|
|
device_class=SensorDeviceClass.ENERGY,
|
|
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
),
|
|
(Units.KWH, True): SensorEntityDescription(
|
|
key=f"{Units.KWH}_{True}",
|
|
device_class=SensorDeviceClass.ENERGY,
|
|
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
|
),
|
|
(Units.V, False): SensorEntityDescription(
|
|
key=f"{Units.V}_{False}",
|
|
device_class=SensorDeviceClass.VOLTAGE,
|
|
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
),
|
|
(Units.A, False): SensorEntityDescription(
|
|
key=f"{Units.A}_{False}",
|
|
device_class=SensorDeviceClass.CURRENT,
|
|
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
),
|
|
(Units.W, False): SensorEntityDescription(
|
|
key=f"{Units.W}_{False}",
|
|
device_class=SensorDeviceClass.POWER,
|
|
native_unit_of_measurement=UnitOfPower.WATT,
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
),
|
|
(Units.PERCENT, False): SensorEntityDescription(
|
|
key=f"{Units.PERCENT}_{False}",
|
|
device_class=SensorDeviceClass.BATTERY,
|
|
native_unit_of_measurement=PERCENTAGE,
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
),
|
|
(Units.HZ, False): SensorEntityDescription(
|
|
key=f"{Units.HZ}_{False}",
|
|
device_class=SensorDeviceClass.FREQUENCY,
|
|
native_unit_of_measurement=UnitOfFrequency.HERTZ,
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
),
|
|
(Units.NONE, False): SensorEntityDescription(
|
|
key=f"{Units.NONE}_{False}",
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
),
|
|
}
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
entry: ConfigEntry,
|
|
async_add_entities: AddEntitiesCallback,
|
|
) -> None:
|
|
"""Entry setup."""
|
|
api: RealTimeAPI = hass.data[DOMAIN][entry.entry_id]
|
|
resp = await api.get_data()
|
|
serial = resp.serial_number
|
|
version = resp.version
|
|
endpoint = RealTimeDataEndpoint(hass, api)
|
|
hass.async_add_job(endpoint.async_refresh)
|
|
async_track_time_interval(hass, endpoint.async_refresh, SCAN_INTERVAL)
|
|
devices = []
|
|
for sensor, (idx, measurement) in api.inverter.sensor_map().items():
|
|
description = SENSOR_DESCRIPTIONS[(measurement.unit, measurement.is_monotonic)]
|
|
|
|
uid = f"{serial}-{idx}"
|
|
devices.append(
|
|
Inverter(
|
|
api.inverter.manufacturer,
|
|
uid,
|
|
serial,
|
|
version,
|
|
sensor,
|
|
description.native_unit_of_measurement,
|
|
description.state_class,
|
|
description.device_class,
|
|
)
|
|
)
|
|
endpoint.sensors = devices
|
|
async_add_entities(devices)
|
|
|
|
|
|
class RealTimeDataEndpoint:
|
|
"""Representation of a Sensor."""
|
|
|
|
def __init__(self, hass: HomeAssistant, api: RealTimeAPI) -> None:
|
|
"""Initialize the sensor."""
|
|
self.hass = hass
|
|
self.api = api
|
|
self.ready = asyncio.Event()
|
|
self.sensors: list[Inverter] = []
|
|
|
|
async def async_refresh(self, now=None):
|
|
"""Fetch new state data for the sensor.
|
|
|
|
This is the only method that should fetch new data for Home Assistant.
|
|
"""
|
|
try:
|
|
api_response = await self.api.get_data()
|
|
self.ready.set()
|
|
except InverterError as err:
|
|
if now is not None:
|
|
self.ready.clear()
|
|
return
|
|
raise PlatformNotReady from err
|
|
data = api_response.data
|
|
for sensor in self.sensors:
|
|
if sensor.key in data:
|
|
sensor.value = data[sensor.key]
|
|
sensor.async_schedule_update_ha_state()
|
|
|
|
|
|
class Inverter(SensorEntity):
|
|
"""Class for a sensor."""
|
|
|
|
_attr_should_poll = False
|
|
|
|
def __init__(
|
|
self,
|
|
manufacturer,
|
|
uid,
|
|
serial,
|
|
version,
|
|
key,
|
|
unit,
|
|
state_class=None,
|
|
device_class=None,
|
|
):
|
|
"""Initialize an inverter sensor."""
|
|
self._attr_unique_id = uid
|
|
self._attr_name = f"{manufacturer} {serial} {key}"
|
|
self._attr_native_unit_of_measurement = unit
|
|
self._attr_state_class = state_class
|
|
self._attr_device_class = device_class
|
|
self._attr_device_info = DeviceInfo(
|
|
identifiers={(DOMAIN, serial)},
|
|
manufacturer=MANUFACTURER,
|
|
name=f"{manufacturer} {serial}",
|
|
sw_version=version,
|
|
)
|
|
self.key = key
|
|
self.value = None
|
|
|
|
@property
|
|
def native_value(self):
|
|
"""State of this inverter attribute."""
|
|
return self.value
|