<gh_stars>0
"""Sensor platform for Zeversolar inverter."""
import async_timeout
from homeassistant.components.sensor import (  # STATE_CLASS_TOTAL_INCREASING,
    STATE_CLASS_MEASUREMENT,
    SensorDeviceClass,
    SensorEntity,
    SensorEntityDescription,
    SensorStateClass,
)
from homeassistant.const import CONF_HOST, ENERGY_KILO_WATT_HOUR, POWER_WATT
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.entity import DeviceInfo, EntityCategory
from homeassistant.helpers.update_coordinator import (
    CoordinatorEntity,
    DataUpdateCoordinator,
    UpdateFailed,
)
from zever_local.inverter import ArrayPosition, ZeversolarError, ZeversolarTimeout

from .const import CONF_SERIAL_NO, DOMAIN, ENTRY_COORDINATOR, ENTRY_DEVICE_INFO
from .coordinator import ZeversolarApiCoordinator

# not needed
# SCAN_INTERVAL = timedelta(seconds=30)

PARALLEL_UPDATES = 1

_SENSOR_DESCRIPTIONS = {
    ArrayPosition.pac_watt.name: SensorEntityDescription(
        key="W",
        device_class=SensorDeviceClass.POWER,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=POWER_WATT,
        name="Current Power",
        icon="mdi:solar-power",
        entity_category=None,
    ),
    ArrayPosition.energy_today_KWh.name: SensorEntityDescription(
        key="kWh",
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.TOTAL_INCREASING,
        native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
        name="Total Energy Today",
        icon="mdi:solar-power",
        entity_category=None,
    ),
}

_DEFAULT_SENSOR = SensorEntityDescription(
    key="_",
    state_class=STATE_CLASS_MEASUREMENT,
    entity_category=EntityCategory.DIAGNOSTIC,
)


# see: https://developers.home-assistant.io/docs/integration_fetching_data/
async def async_setup_entry(hass, entry, async_add_entities):
    """Setup sensor platform."""
    zever_coordinator: ZeversolarApiCoordinator = hass.data[DOMAIN][entry.entry_id][
        ENTRY_COORDINATOR
    ]

    async def async_update_data():
        """Fetch data from API endpoint.

        This is the place to pre-process the data to lookup tables
        so entities can quickly look up their data.
        """
        try:
            # Note: asyncio.TimeoutError and aiohttp.ClientError are already
            # handled by the data update coordinator.
            async with async_timeout.timeout(10):
                return await zever_coordinator.client.async_get_data()
        except ZeversolarTimeout as err:
            raise UpdateFailed(f"Timeout communicating with API: {err}") from err
        except ZeversolarError as err:
            raise UpdateFailed(f"Error communicating with API: {err}") from err

    zever_coordinator.update_method = async_update_data

    await zever_coordinator.async_config_entry_first_refresh()

    serial_number = entry.data[CONF_SERIAL_NO]
    url = entry.data[CONF_HOST]

    inverter_data = await zever_coordinator.client.async_get_data()
    hardware_version = inverter_data.hardware_version
    software_version = inverter_data.software_version

    inverter = Inverter(serial_number, url, hardware_version, software_version)

    daily_energy_sensor = Sensor(ArrayPosition.energy_today_KWh.name)
    current_power_sensor = Sensor(ArrayPosition.pac_watt.name)

    all_sensors = (daily_energy_sensor, current_power_sensor)

    device_info: DeviceInfo = hass.data[DOMAIN][entry.entry_id][ENTRY_DEVICE_INFO]

    entities = []

    # Individual inverter sensors entities
    entities.extend(
        ZeverSolarSensor(zever_coordinator, device_info, inverter.serial_number, sensor)
        for sensor in all_sensors
    )

    async_add_entities(entities)


class Sensor:
    """Defines a sensor of the Zeversolar inverter"""

    def __init__(self, sensor_id: str) -> None:
        self._sensor_id = sensor_id

    @property
    def sensor_id(self) -> str:
        """The Zeversolar sensor ID."""
        return self._sensor_id


class Inverter:
    """Defines a Zeversolar inverter."""

    def __init__(
        self,
        serial_number: str,
        address: str,
        hardware_version: str,
        software_version: str,
    ) -> None:
        self._serial_number = serial_number
        self._address = address
        self._hardware_version = hardware_version
        self._software_version = software_version

    @property
    def serial_number(self) -> str:
        """Gets the serial number."""
        return self._serial_number

    @property
    def address(self) -> str:
        """Gets the inverter address."""
        return self._address

    @property
    def hardware_version(self) -> str:
        """Gets the hardware version."""
        return self._hardware_version

    @property
    def software_version(self) -> str:
        """Gets the software version."""
        return self._software_version


class ZeverSolarSensor(CoordinatorEntity, SensorEntity):
    """Entity representing individual inverter sensor."""

    def __init__(
        self,
        coordinator: DataUpdateCoordinator,
        device_info: DeviceInfo,
        serial_number: str,
        sensor: Sensor,
    ) -> None:
        """Initialize an inverter sensor."""
        super().__init__(coordinator)

        self._attr_unique_id = f"{DOMAIN}_{serial_number}_{sensor.sensor_id}"
        self._attr_device_info = device_info
        self.entity_description = _SENSOR_DESCRIPTIONS.get(
            sensor.sensor_id, _DEFAULT_SENSOR
        )
        self._sensor = sensor
        self._previous_value = None

    @property
    def native_value(self):
        """Return the value reported by the sensor."""
        my_data = self.coordinator.data
        if my_data is None:
            raise ConfigEntryNotReady

        value = getattr(my_data, self._sensor.sensor_id)
        self._previous_value = value
        return value

"""Sensor platform for Zeversolar inverter."""
import async_timeout
from homeassistant.components.sensor import (  # STATE_CLASS_TOTAL_INCREASING,
    STATE_CLASS_MEASUREMENT,
    SensorDeviceClass,
    SensorEntity,
    SensorEntityDescription,
    SensorStateClass,
)
from homeassistant.const import CONF_HOST, ENERGY_KILO_WATT_HOUR, POWER_WATT
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.entity import DeviceInfo, EntityCategory
from homeassistant.helpers.update_coordinator import (
    CoordinatorEntity,
    DataUpdateCoordinator,
    UpdateFailed,
)
from zever_local.inverter import ArrayPosition, ZeversolarError, ZeversolarTimeout

from .const import CONF_SERIAL_NO, DOMAIN, ENTRY_COORDINATOR, ENTRY_DEVICE_INFO
from .coordinator import ZeversolarApiCoordinator

# not needed
# SCAN_INTERVAL = timedelta(seconds=30)

PARALLEL_UPDATES = 1

_SENSOR_DESCRIPTIONS = {
    ArrayPosition.pac_watt.name: SensorEntityDescription(
        key="W",
        device_class=SensorDeviceClass.POWER,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=POWER_WATT,
        name="Current Power",
        icon="mdi:solar-power",
        entity_category=None,
    ),
    ArrayPosition.energy_today_KWh.name: SensorEntityDescription(
        key="kWh",
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.TOTAL_INCREASING,
        native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
        name="Total Energy Today",
        icon="mdi:solar-power",
        entity_category=None,
    ),
}

_DEFAULT_SENSOR = SensorEntityDescription(
    key="_",
    state_class=STATE_CLASS_MEASUREMENT,
    entity_category=EntityCategory.DIAGNOSTIC,
)


# see: https://developers.home-assistant.io/docs/integration_fetching_data/
async def async_setup_entry(hass, entry, async_add_entities):
    """Setup sensor platform."""
    zever_coordinator: ZeversolarApiCoordinator = hass.data[DOMAIN][entry.entry_id][
        ENTRY_COORDINATOR
    ]

    async def async_update_data():
        """Fetch data from API endpoint.

        This is the place to pre-process the data to lookup tables
        so entities can quickly look up their data.
        """
        try:
            # Note: asyncio.TimeoutError and aiohttp.ClientError are already
            # handled by the data update coordinator.
            async with async_timeout.timeout(10):
                return await zever_coordinator.client.async_get_data()
        except ZeversolarTimeout as err:
            raise UpdateFailed(f"Timeout communicating with API: {err}") from err
        except ZeversolarError as err:
            raise UpdateFailed(f"Error communicating with API: {err}") from err

    zever_coordinator.update_method = async_update_data

    await zever_coordinator.async_config_entry_first_refresh()

    serial_number = entry.data[CONF_SERIAL_NO]
    url = entry.data[CONF_HOST]

    inverter_data = await zever_coordinator.client.async_get_data()
    hardware_version = inverter_data.hardware_version
    software_version = inverter_data.software_version

    inverter = Inverter(serial_number, url, hardware_version, software_version)

    daily_energy_sensor = Sensor(ArrayPosition.energy_today_KWh.name)
    current_power_sensor = Sensor(ArrayPosition.pac_watt.name)

    all_sensors = (daily_energy_sensor, current_power_sensor)

    device_info: DeviceInfo = hass.data[DOMAIN][entry.entry_id][ENTRY_DEVICE_INFO]

    entities = []

    # Individual inverter sensors entities
    entities.extend(
        ZeverSolarSensor(zever_coordinator, device_info, inverter.serial_number, sensor)
        for sensor in all_sensors
    )

    async_add_entities(entities)


class Sensor:
    """Defines a sensor of the Zeversolar inverter"""

    def __init__(self, sensor_id: str) -> None:
        self._sensor_id = sensor_id

    @property
    def sensor_id(self) -> str:
        """The Zeversolar sensor ID."""
        return self._sensor_id


class Inverter:
    """Defines a Zeversolar inverter."""

    def __init__(
        self,
        serial_number: str,
        address: str,
        hardware_version: str,
        software_version: str,
    ) -> None:
        self._serial_number = serial_number
        self._address = address
        self._hardware_version = hardware_version
        self._software_version = software_version

    @property
    def serial_number(self) -> str:
        """Gets the serial number."""
        return self._serial_number

    @property
    def address(self) -> str:
        """Gets the inverter address."""
        return self._address

    @property
    def hardware_version(self) -> str:
        """Gets the hardware version."""
        return self._hardware_version

    @property
    def software_version(self) -> str:
        """Gets the software version."""
        return self._software_version


class ZeverSolarSensor(CoordinatorEntity, SensorEntity):
    """Entity representing individual inverter sensor."""

    def __init__(
        self,
        coordinator: DataUpdateCoordinator,
        device_info: DeviceInfo,
        serial_number: str,
        sensor: Sensor,
    ) -> None:
        """Initialize an inverter sensor."""
        super().__init__(coordinator)

        self._attr_unique_id = f"{DOMAIN}_{serial_number}_{sensor.sensor_id}"
        self._attr_device_info = device_info
        self.entity_description = _SENSOR_DESCRIPTIONS.get(
            sensor.sensor_id, _DEFAULT_SENSOR
        )
        self._sensor = sensor
        self._previous_value = None

    @property
    def native_value(self):
        """Return the value reported by the sensor."""
        my_data = self.coordinator.data
        if my_data is None:
            raise ConfigEntryNotReady

        value = getattr(my_data, self._sensor.sensor_id)
        self._previous_value = value
        return value
Sensor platform for Zeversolar inverter. # STATE_CLASS_TOTAL_INCREASING, # not needed # SCAN_INTERVAL = timedelta(seconds=30) # see: https://developers.home-assistant.io/docs/integration_fetching_data/ Setup sensor platform. Fetch data from API endpoint.          This is the place to pre-process the data to lookup tables         so entities can quickly look up their data. # Note: asyncio.TimeoutError and aiohttp.ClientError are already # handled by the data update coordinator. # Individual inverter sensors entities Defines a sensor of the Zeversolar inverter The Zeversolar sensor ID. Defines a Zeversolar inverter. Gets the serial number. Gets the inverter address. Gets the hardware version. Gets the software version. Entity representing individual inverter sensor. Initialize an inverter sensor. Return the value reported by the sensor.