mirror of
https://github.com/home-assistant/core.git
synced 2026-08-06 13:26:29 +01:00
218 lines
6.6 KiB
Python
218 lines
6.6 KiB
Python
"""SAJ solar inverter interface."""
|
|
|
|
from datetime import date
|
|
from typing import override
|
|
|
|
import pysaj
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.components.sensor import (
|
|
PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
|
|
SensorDeviceClass,
|
|
SensorEntity,
|
|
SensorStateClass,
|
|
)
|
|
from homeassistant.config_entries import SOURCE_IMPORT
|
|
from homeassistant.const import (
|
|
CONF_HOST,
|
|
CONF_NAME,
|
|
CONF_PASSWORD,
|
|
CONF_TYPE,
|
|
CONF_USERNAME,
|
|
UnitOfEnergy,
|
|
UnitOfMass,
|
|
UnitOfPower,
|
|
UnitOfTemperature,
|
|
UnitOfTime,
|
|
)
|
|
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant, callback
|
|
from homeassistant.data_entry_flow import FlowResultType
|
|
from homeassistant.helpers import config_validation as cv, issue_registry as ir
|
|
from homeassistant.helpers.entity_platform import (
|
|
AddConfigEntryEntitiesCallback,
|
|
AddEntitiesCallback,
|
|
)
|
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType
|
|
from homeassistant.util import dt as dt_util
|
|
|
|
from . import SAJConfigEntry, SAJRuntimeData
|
|
from .const import CONNECTION_TYPES, DOMAIN, INTEGRATION_TITLE
|
|
|
|
SAJ_UNIT_MAPPINGS = {
|
|
"": None,
|
|
"h": UnitOfTime.HOURS,
|
|
"kg": UnitOfMass.KILOGRAMS,
|
|
"kWh": UnitOfEnergy.KILO_WATT_HOUR,
|
|
"W": UnitOfPower.WATT,
|
|
"°C": UnitOfTemperature.CELSIUS,
|
|
}
|
|
|
|
PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
|
|
{
|
|
vol.Required(CONF_HOST): cv.string,
|
|
vol.Optional(CONF_NAME): cv.string,
|
|
vol.Optional(CONF_TYPE, default=CONNECTION_TYPES[0]): vol.In(CONNECTION_TYPES),
|
|
vol.Inclusive(CONF_USERNAME, "credentials"): cv.string,
|
|
vol.Inclusive(CONF_PASSWORD, "credentials"): cv.string,
|
|
}
|
|
)
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
entry: SAJConfigEntry,
|
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
|
) -> None:
|
|
"""Set up the SAJ sensors from a config entry."""
|
|
runtime = entry.runtime_data
|
|
sensor_def = runtime.sensor_def
|
|
|
|
hass_sensors = [
|
|
SAJsensor(runtime, entry.unique_id, sensor, inverter_name=None)
|
|
for sensor in sensor_def
|
|
if sensor.enabled
|
|
]
|
|
|
|
async_add_entities(hass_sensors)
|
|
|
|
|
|
async def async_setup_platform(
|
|
hass: HomeAssistant,
|
|
config: ConfigType,
|
|
async_add_entities: AddEntitiesCallback,
|
|
discovery_info: DiscoveryInfoType | None = None,
|
|
) -> None:
|
|
"""Migrate YAML sensor platform configuration to a config entry."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": SOURCE_IMPORT},
|
|
data=dict(config),
|
|
)
|
|
if (
|
|
result.get("type") is FlowResultType.ABORT
|
|
and result.get("reason") != "already_configured"
|
|
):
|
|
reason = result.get("reason", "unknown")
|
|
ir.async_create_issue(
|
|
hass,
|
|
DOMAIN,
|
|
f"deprecated_yaml_import_issue_{reason}",
|
|
is_fixable=False,
|
|
issue_domain=DOMAIN,
|
|
severity=ir.IssueSeverity.WARNING,
|
|
translation_key=f"deprecated_yaml_import_issue_{reason}",
|
|
translation_placeholders={
|
|
"domain": DOMAIN,
|
|
"integration_title": INTEGRATION_TITLE,
|
|
},
|
|
)
|
|
return
|
|
|
|
ir.async_create_issue(
|
|
hass,
|
|
HOMEASSISTANT_DOMAIN,
|
|
f"deprecated_yaml_{DOMAIN}",
|
|
is_fixable=False,
|
|
issue_domain=DOMAIN,
|
|
severity=ir.IssueSeverity.WARNING,
|
|
translation_key="deprecated_yaml",
|
|
translation_placeholders={
|
|
"domain": DOMAIN,
|
|
"integration_title": INTEGRATION_TITLE,
|
|
},
|
|
)
|
|
|
|
|
|
class SAJsensor(SensorEntity):
|
|
"""Representation of a SAJ sensor."""
|
|
|
|
_attr_should_poll = False
|
|
_state: StateType
|
|
|
|
def __init__(
|
|
self,
|
|
runtime: SAJRuntimeData,
|
|
serialnumber: str | None,
|
|
pysaj_sensor: pysaj.Sensor,
|
|
inverter_name: str | None = None,
|
|
) -> None:
|
|
"""Initialize the SAJ sensor."""
|
|
self._runtime = runtime
|
|
self._sensor = pysaj_sensor
|
|
self._inverter_name = inverter_name
|
|
self._serialnumber = serialnumber
|
|
self._state = self._sensor.value
|
|
|
|
if pysaj_sensor.name in ("current_power", "temperature"):
|
|
self._attr_state_class = SensorStateClass.MEASUREMENT
|
|
if pysaj_sensor.name == "total_yield":
|
|
self._attr_state_class = SensorStateClass.TOTAL_INCREASING
|
|
|
|
self._attr_unique_id = f"{serialnumber}_{pysaj_sensor.name}"
|
|
native_uom = SAJ_UNIT_MAPPINGS[pysaj_sensor.unit]
|
|
self._attr_native_unit_of_measurement = native_uom
|
|
if self._inverter_name:
|
|
self._attr_name = f"saj_{self._inverter_name}_{pysaj_sensor.name}"
|
|
else:
|
|
self._attr_name = f"saj_{pysaj_sensor.name}"
|
|
if native_uom == UnitOfPower.WATT:
|
|
self._attr_device_class = SensorDeviceClass.POWER
|
|
if native_uom == UnitOfEnergy.KILO_WATT_HOUR:
|
|
self._attr_device_class = SensorDeviceClass.ENERGY
|
|
if native_uom in (
|
|
UnitOfTemperature.CELSIUS,
|
|
UnitOfTemperature.FAHRENHEIT,
|
|
):
|
|
self._attr_device_class = SensorDeviceClass.TEMPERATURE
|
|
|
|
@override
|
|
async def async_added_to_hass(self) -> None:
|
|
"""Register for inverter poll updates."""
|
|
await super().async_added_to_hass()
|
|
self.async_on_remove(
|
|
self._runtime.polling.async_add_poll_listener(self._on_poll_success)
|
|
)
|
|
|
|
@property
|
|
@override
|
|
def native_value(self) -> StateType:
|
|
"""Return the state of the sensor."""
|
|
return self._state
|
|
|
|
@property
|
|
def per_day_basis(self) -> bool:
|
|
"""Return if the sensors value is on daily basis or not."""
|
|
return self._sensor.per_day_basis
|
|
|
|
@property
|
|
def per_total_basis(self) -> bool:
|
|
"""Return if the sensors value is cumulative or not."""
|
|
return self._sensor.per_total_basis
|
|
|
|
@property
|
|
def date_updated(self) -> date:
|
|
"""Return the date when the sensor was last updated."""
|
|
return self._sensor.date
|
|
|
|
@callback
|
|
def _on_poll_success(self, success: bool) -> None:
|
|
"""Update state from the inverter after a poll."""
|
|
state_unknown = False
|
|
if not success and (
|
|
(self.per_day_basis and dt_util.now().date() > self.date_updated)
|
|
or (not self.per_day_basis and not self.per_total_basis)
|
|
):
|
|
state_unknown = True
|
|
|
|
update = False
|
|
if self._sensor.value != self._state:
|
|
update = True
|
|
self._state = self._sensor.value
|
|
|
|
if state_unknown and self._state is not None:
|
|
update = True
|
|
self._state = None
|
|
|
|
if update:
|
|
self.async_write_ha_state()
|