"""Platform for solarlog sensors.""" from __future__ import annotations from collections.abc import Callable from dataclasses import dataclass from datetime import datetime from solarlog_cli.solarlog_models import ( BatteryData, EnergyData, InverterData, SolarlogData, ) from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, SensorEntityDescription, SensorStateClass, ) from homeassistant.const import ( PERCENTAGE, UnitOfElectricPotential, UnitOfEnergy, UnitOfPower, ) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.typing import StateType from .coordinator import SolarlogConfigEntry from .entity import ( SolarLogBasicCoordinatorEntity, SolarLogInverterEntity, SolarLogLongtimeCoordinatorEntity, ) from .models import SolarlogIntegrationData @dataclass(frozen=True, kw_only=True) class SolarLogCoordinatorSensorEntityDescription(SensorEntityDescription): """Describes Solarlog coordinator sensor entity.""" value_fn: Callable[[SolarlogData], StateType | datetime | None] @dataclass(frozen=True, kw_only=True) class SolarLogLongtimeSensorEntityDescription(SensorEntityDescription): """Describes Solarlog longtime sensor entity.""" value_fn: Callable[[EnergyData], float | None] @dataclass(frozen=True, kw_only=True) class SolarLogBatterySensorEntityDescription(SensorEntityDescription): """Describes Solarlog battery sensor entity.""" value_fn: Callable[[BatteryData], float | int | None] @dataclass(frozen=True, kw_only=True) class SolarLogInverterSensorEntityDescription(SensorEntityDescription): """Describes Solarlog inverter sensor entity.""" value_fn: Callable[[InverterData], float | None] SOLARLOG_BASIC_SENSOR_TYPES: tuple[SolarLogCoordinatorSensorEntityDescription, ...] = ( SolarLogCoordinatorSensorEntityDescription( key="last_updated", translation_key="last_update", device_class=SensorDeviceClass.TIMESTAMP, value_fn=lambda data: data.last_updated, ), SolarLogCoordinatorSensorEntityDescription( key="power_ac", translation_key="power_ac", native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda data: data.power_ac, ), SolarLogCoordinatorSensorEntityDescription( key="power_dc", translation_key="power_dc", native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda data: data.power_dc, ), SolarLogCoordinatorSensorEntityDescription( key="voltage_ac", translation_key="voltage_ac", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda data: data.voltage_ac, ), SolarLogCoordinatorSensorEntityDescription( key="voltage_dc", translation_key="voltage_dc", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda data: data.voltage_dc, ), SolarLogCoordinatorSensorEntityDescription( key="yield_day", translation_key="yield_day", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, suggested_display_precision=3, value_fn=lambda data: data.yield_day, ), SolarLogCoordinatorSensorEntityDescription( key="yield_yesterday", translation_key="yield_yesterday", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, device_class=SensorDeviceClass.ENERGY, suggested_display_precision=3, value_fn=lambda data: data.yield_yesterday, ), SolarLogCoordinatorSensorEntityDescription( key="yield_month", translation_key="yield_month", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, suggested_display_precision=3, value_fn=lambda data: data.yield_month, ), SolarLogCoordinatorSensorEntityDescription( key="yield_year", translation_key="yield_year", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, value_fn=lambda data: data.yield_year, ), SolarLogCoordinatorSensorEntityDescription( key="yield_total", translation_key="yield_total", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL, suggested_display_precision=3, value_fn=lambda data: data.yield_total, ), SolarLogCoordinatorSensorEntityDescription( key="consumption_ac", translation_key="consumption_ac", native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda data: data.consumption_ac, ), SolarLogCoordinatorSensorEntityDescription( key="consumption_day", translation_key="consumption_day", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, suggested_display_precision=3, value_fn=lambda data: data.consumption_day, ), SolarLogCoordinatorSensorEntityDescription( key="consumption_yesterday", translation_key="consumption_yesterday", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, device_class=SensorDeviceClass.ENERGY, suggested_display_precision=3, value_fn=lambda data: data.consumption_yesterday, ), SolarLogCoordinatorSensorEntityDescription( key="consumption_month", translation_key="consumption_month", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, suggested_display_precision=3, value_fn=lambda data: data.consumption_month, ), SolarLogCoordinatorSensorEntityDescription( key="consumption_year", translation_key="consumption_year", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, suggested_display_precision=3, value_fn=lambda data: data.consumption_year, ), SolarLogCoordinatorSensorEntityDescription( key="consumption_total", translation_key="consumption_total", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL, suggested_display_precision=3, value_fn=lambda data: data.consumption_total, ), SolarLogCoordinatorSensorEntityDescription( key="total_power", translation_key="total_power", native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda data: data.total_power, ), SolarLogCoordinatorSensorEntityDescription( key="alternator_loss", translation_key="alternator_loss", native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda data: data.alternator_loss, ), SolarLogCoordinatorSensorEntityDescription( key="capacity", translation_key="capacity", native_unit_of_measurement=PERCENTAGE, device_class=SensorDeviceClass.POWER_FACTOR, state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=1, value_fn=lambda data: data.capacity, ), SolarLogCoordinatorSensorEntityDescription( key="efficiency", translation_key="efficiency", native_unit_of_measurement=PERCENTAGE, device_class=SensorDeviceClass.POWER_FACTOR, state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=1, value_fn=lambda data: data.efficiency, ), SolarLogCoordinatorSensorEntityDescription( key="power_available", translation_key="power_available", native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda data: data.power_available, ), SolarLogCoordinatorSensorEntityDescription( key="usage", translation_key="usage", native_unit_of_measurement=PERCENTAGE, device_class=SensorDeviceClass.POWER_FACTOR, state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=1, value_fn=lambda data: data.usage, ), ) """SOLARLOG_LONGTIME_SENSOR_TYPES represent data points that may require longer timeout and therefore are retrieved with different DataUpdateCoordinator.""" SOLARLOG_LONGTIME_SENSOR_TYPES: tuple[SolarLogLongtimeSensorEntityDescription, ...] = ( SolarLogLongtimeSensorEntityDescription( key="self_consumption_year", translation_key="self_consumption_year", native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, value_fn=lambda data: data.self_consumption, ), ) SOLARLOG_BATTERY_SENSOR_TYPES: tuple[SolarLogBatterySensorEntityDescription, ...] = ( SolarLogBatterySensorEntityDescription( key="charging_power", translation_key="charging_power", native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda battery_data: battery_data.charge_power, ), SolarLogBatterySensorEntityDescription( key="discharging_power", translation_key="discharging_power", native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda battery_data: battery_data.discharge_power, ), SolarLogBatterySensorEntityDescription( key="charge_level", translation_key="charge_level", native_unit_of_measurement=PERCENTAGE, device_class=SensorDeviceClass.BATTERY, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda battery_data: battery_data.level, ), ) SOLARLOG_INVERTER_SENSOR_TYPES: tuple[SolarLogInverterSensorEntityDescription, ...] = ( SolarLogInverterSensorEntityDescription( key="current_power", translation_key="current_power", native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, value_fn=( lambda inverter: None if inverter is None else inverter.current_power ), ), SolarLogInverterSensorEntityDescription( key="consumption_year", translation_key="consumption_year", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, suggested_display_precision=3, value_fn=( lambda inverter: None if inverter is None else inverter.consumption_year ), ), ) async def async_setup_entry( hass: HomeAssistant, entry: SolarlogConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Add solarlog entry.""" solarLogIntegrationData: SolarlogIntegrationData = entry.runtime_data entities: list[SensorEntity] = [ SolarLogBasicCoordinatorSensor( solarLogIntegrationData.basic_data_coordinator, sensor ) for sensor in SOLARLOG_BASIC_SENSOR_TYPES ] if solarLogIntegrationData.longtime_data_coordinator is not None: entities.extend( SolarLogLongtimeCoordinatorSensor( solarLogIntegrationData.longtime_data_coordinator, sensor ) for sensor in SOLARLOG_LONGTIME_SENSOR_TYPES ) # add battery sensors only if respective data is available (otherwise no battery attached to solarlog) if solarLogIntegrationData.basic_data_coordinator.data.battery_data is not None: entities.extend( SolarLogBatterySensor( solarLogIntegrationData.basic_data_coordinator, sensor ) for sensor in SOLARLOG_BATTERY_SENSOR_TYPES ) if solarLogIntegrationData.device_data_coordinator is not None: device_data = solarLogIntegrationData.device_data_coordinator.data if device_data: entities.extend( SolarLogInverterSensor( solarLogIntegrationData.device_data_coordinator, sensor, device_id, ) for device_id in device_data for sensor in SOLARLOG_INVERTER_SENSOR_TYPES ) def _async_add_new_device(device_id: int) -> None: async_add_entities( SolarLogInverterSensor( solarLogIntegrationData.device_data_coordinator, sensor, device_id, ) for sensor in SOLARLOG_INVERTER_SENSOR_TYPES if solarLogIntegrationData.device_data_coordinator is not None ) solarLogIntegrationData.device_data_coordinator.new_device_callbacks.append( _async_add_new_device ) async_add_entities(entities) class SolarLogBasicCoordinatorSensor(SolarLogBasicCoordinatorEntity, SensorEntity): """Represents a SolarLog sensor.""" entity_description: SolarLogCoordinatorSensorEntityDescription @property def native_value(self) -> StateType | datetime: """Return the state for this sensor.""" return self.entity_description.value_fn(self.coordinator.data) class SolarLogLongtimeCoordinatorSensor( SolarLogLongtimeCoordinatorEntity, SensorEntity ): """Represents a SolarLog longtime energy sensor.""" entity_description: SolarLogLongtimeSensorEntityDescription @property def native_value(self) -> float | None: """Return the state for this sensor.""" return self.entity_description.value_fn(self.coordinator.data) class SolarLogBatterySensor(SolarLogBasicCoordinatorEntity, SensorEntity): """Represents a SolarLog battery sensor.""" entity_description: SolarLogBatterySensorEntityDescription @property def native_value(self) -> StateType: """Return the state for this sensor.""" if ( battery_data := self.coordinator.config_entry.runtime_data.basic_data_coordinator.data.battery_data ) is None: return None return self.entity_description.value_fn(battery_data) class SolarLogInverterSensor(SolarLogInverterEntity, SensorEntity): """Represents a SolarLog inverter sensor.""" entity_description: SolarLogInverterSensorEntityDescription @property def native_value(self) -> StateType: """Return the state for this sensor.""" return self.entity_description.value_fn(self.coordinator.data[self.device_id])