Files
core/homeassistant/components/iotawatt/sensor.py
T

233 lines
7.3 KiB
Python

"""Support for IoTaWatt Energy monitor."""
from collections.abc import Callable
from dataclasses import dataclass
import logging
from typing import override
from iotawattpy.sensor import Sensor
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import (
PERCENTAGE,
UnitOfApparentPower,
UnitOfElectricCurrent,
UnitOfElectricPotential,
UnitOfEnergy,
UnitOfFrequency,
UnitOfPower,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util import dt as dt_util
from .const import VOLT_AMPERE_REACTIVE, VOLT_AMPERE_REACTIVE_HOURS
from .coordinator import IotawattConfigEntry, IotawattUpdater
_LOGGER = logging.getLogger(__name__)
@dataclass(frozen=True)
class IotaWattSensorEntityDescription(SensorEntityDescription):
"""Class describing IotaWatt sensor entities."""
value: Callable | None = None
ENTITY_DESCRIPTION_KEY_MAP: dict[str, IotaWattSensorEntityDescription] = {
"Amps": IotaWattSensorEntityDescription(
key="Amps",
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.CURRENT,
entity_registry_enabled_default=False,
),
"Hz": IotaWattSensorEntityDescription(
key="Hz",
native_unit_of_measurement=UnitOfFrequency.HERTZ,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.FREQUENCY,
icon="mdi:flash",
entity_registry_enabled_default=False,
),
"PF": IotaWattSensorEntityDescription(
key="PF",
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.POWER_FACTOR,
value=lambda value: value * 100,
entity_registry_enabled_default=False,
),
"Watts": IotaWattSensorEntityDescription(
key="Watts",
native_unit_of_measurement=UnitOfPower.WATT,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.POWER,
),
"WattHours": IotaWattSensorEntityDescription(
key="WattHours",
native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
state_class=SensorStateClass.TOTAL,
device_class=SensorDeviceClass.ENERGY,
),
"VA": IotaWattSensorEntityDescription(
key="VA",
native_unit_of_measurement=UnitOfApparentPower.VOLT_AMPERE,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.APPARENT_POWER,
entity_registry_enabled_default=False,
),
"VAR": IotaWattSensorEntityDescription(
key="VAR",
native_unit_of_measurement=VOLT_AMPERE_REACTIVE,
state_class=SensorStateClass.MEASUREMENT,
icon="mdi:flash",
entity_registry_enabled_default=False,
),
"VARh": IotaWattSensorEntityDescription(
key="VARh",
native_unit_of_measurement=VOLT_AMPERE_REACTIVE_HOURS,
state_class=SensorStateClass.MEASUREMENT,
icon="mdi:flash",
entity_registry_enabled_default=False,
),
"Volts": IotaWattSensorEntityDescription(
key="Volts",
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.VOLTAGE,
entity_registry_enabled_default=False,
),
}
async def async_setup_entry(
hass: HomeAssistant,
config_entry: IotawattConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Add sensors for passed config_entry in HA."""
coordinator = config_entry.runtime_data
created = set()
@callback
def _create_entity(key: str) -> IotaWattSensor:
"""Create a sensor entity."""
created.add(key)
data = coordinator.data["sensors"][key]
description = ENTITY_DESCRIPTION_KEY_MAP.get(
data.getUnit(), IotaWattSensorEntityDescription(key="base_sensor")
)
return IotaWattSensor(
coordinator=coordinator,
key=key,
entity_description=description,
)
async_add_entities(_create_entity(key) for key in coordinator.data["sensors"])
@callback
def new_data_received():
"""Check for new sensors."""
entities = [
_create_entity(key)
for key in coordinator.data["sensors"]
if key not in created
]
async_add_entities(entities)
coordinator.async_add_listener(new_data_received)
class IotaWattSensor(CoordinatorEntity[IotawattUpdater], SensorEntity):
"""Defines a IoTaWatt Energy Sensor."""
entity_description: IotaWattSensorEntityDescription
def __init__(
self,
coordinator: IotawattUpdater,
key: str,
entity_description: IotaWattSensorEntityDescription,
) -> None:
"""Initialize the sensor."""
super().__init__(coordinator=coordinator)
self._key = key
data = self._sensor_data
if data.getType() == "Input":
self._attr_unique_id = (
f"{data.hub_mac_address}-input-{data.getChannel()}-{data.getUnit()}"
)
self.entity_description = entity_description
@property
def _sensor_data(self) -> Sensor:
"""Return sensor data."""
return self.coordinator.data["sensors"][self._key]
@property
@override
def name(self) -> str | None:
"""Return name of the entity."""
return self._sensor_data.getName()
@property
@override
def device_info(self) -> dr.DeviceInfo:
"""Return device info."""
return dr.DeviceInfo(
connections={
(dr.CONNECTION_NETWORK_MAC, self._sensor_data.hub_mac_address)
},
manufacturer="IoTaWatt",
model="IoTaWatt",
)
@callback
@override
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
if self._key not in self.coordinator.data["sensors"]:
if self._attr_unique_id:
er.async_get(self.hass).async_remove(self.entity_id)
else:
self.hass.async_create_task(self.async_remove())
return
if (begin := self._sensor_data.getBegin()) and (
last_reset := dt_util.parse_datetime(begin)
):
self._attr_last_reset = last_reset
super()._handle_coordinator_update()
@property
@override
def extra_state_attributes(self) -> dict[str, str]:
"""Return the extra state attributes of the entity."""
data = self._sensor_data
attrs = {"type": data.getType()}
if attrs["type"] == "Input":
attrs["channel"] = data.getChannel()
return attrs
@property
@override
def native_value(self) -> StateType:
"""Return the state of the sensor."""
if func := self.entity_description.value:
return func(self._sensor_data.getValue())
return self._sensor_data.getValue()