"""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()