"""Provides a sensor to track various status aspects of a UPS.""" from __future__ import annotations from dataclasses import asdict import logging from typing import Final, cast from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, SensorEntityDescription, SensorStateClass, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_MANUFACTURER, ATTR_MODEL, ATTR_SW_VERSION, PERCENTAGE, STATE_UNKNOWN, UnitOfApparentPower, UnitOfElectricCurrent, UnitOfElectricPotential, UnitOfFrequency, UnitOfPower, UnitOfTemperature, UnitOfTime, ) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import DeviceInfo, EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, DataUpdateCoordinator, ) from . import PyNUTData from .const import ( COORDINATOR, DOMAIN, KEY_STATUS, KEY_STATUS_DISPLAY, PYNUT_DATA, PYNUT_UNIQUE_ID, STATE_TYPES, ) NUT_DEV_INFO_TO_DEV_INFO: dict[str, str] = { "manufacturer": ATTR_MANUFACTURER, "model": ATTR_MODEL, "firmware": ATTR_SW_VERSION, } _LOGGER = logging.getLogger(__name__) SENSOR_TYPES: Final[dict[str, SensorEntityDescription]] = { "ups.status.display": SensorEntityDescription( key="ups.status.display", name="Status", icon="mdi:information-outline", ), "ups.status": SensorEntityDescription( key="ups.status", name="Status Data", icon="mdi:information-outline", ), "ups.alarm": SensorEntityDescription( key="ups.alarm", name="Alarms", icon="mdi:alarm", ), "ups.temperature": SensorEntityDescription( key="ups.temperature", name="UPS Temperature", native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "ups.load": SensorEntityDescription( key="ups.load", name="Load", native_unit_of_measurement=PERCENTAGE, icon="mdi:gauge", state_class=SensorStateClass.MEASUREMENT, ), "ups.load.high": SensorEntityDescription( key="ups.load.high", name="Overload Setting", native_unit_of_measurement=PERCENTAGE, icon="mdi:gauge", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "ups.id": SensorEntityDescription( key="ups.id", name="System identifier", icon="mdi:information-outline", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "ups.delay.start": SensorEntityDescription( key="ups.delay.start", name="Load Restart Delay", native_unit_of_measurement=UnitOfTime.SECONDS, device_class=SensorDeviceClass.DURATION, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "ups.delay.reboot": SensorEntityDescription( key="ups.delay.reboot", name="UPS Reboot Delay", native_unit_of_measurement=UnitOfTime.SECONDS, device_class=SensorDeviceClass.DURATION, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "ups.delay.shutdown": SensorEntityDescription( key="ups.delay.shutdown", name="UPS Shutdown Delay", native_unit_of_measurement=UnitOfTime.SECONDS, device_class=SensorDeviceClass.DURATION, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "ups.timer.start": SensorEntityDescription( key="ups.timer.start", name="Load Start Timer", native_unit_of_measurement=UnitOfTime.SECONDS, device_class=SensorDeviceClass.DURATION, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "ups.timer.reboot": SensorEntityDescription( key="ups.timer.reboot", name="Load Reboot Timer", native_unit_of_measurement=UnitOfTime.SECONDS, device_class=SensorDeviceClass.DURATION, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "ups.timer.shutdown": SensorEntityDescription( key="ups.timer.shutdown", name="Load Shutdown Timer", native_unit_of_measurement=UnitOfTime.SECONDS, device_class=SensorDeviceClass.DURATION, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "ups.test.interval": SensorEntityDescription( key="ups.test.interval", name="Self-Test Interval", native_unit_of_measurement=UnitOfTime.SECONDS, device_class=SensorDeviceClass.DURATION, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "ups.test.result": SensorEntityDescription( key="ups.test.result", name="Self-Test Result", icon="mdi:information-outline", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "ups.test.date": SensorEntityDescription( key="ups.test.date", name="Self-Test Date", icon="mdi:calendar", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "ups.display.language": SensorEntityDescription( key="ups.display.language", name="Language", icon="mdi:information-outline", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "ups.contacts": SensorEntityDescription( key="ups.contacts", name="External Contacts", icon="mdi:information-outline", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "ups.efficiency": SensorEntityDescription( key="ups.efficiency", name="Efficiency", native_unit_of_measurement=PERCENTAGE, icon="mdi:gauge", state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "ups.power": SensorEntityDescription( key="ups.power", name="Current Apparent Power", native_unit_of_measurement=UnitOfApparentPower.VOLT_AMPERE, device_class=SensorDeviceClass.APPARENT_POWER, state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "ups.power.nominal": SensorEntityDescription( key="ups.power.nominal", name="Nominal Power", native_unit_of_measurement=UnitOfApparentPower.VOLT_AMPERE, device_class=SensorDeviceClass.APPARENT_POWER, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "ups.realpower": SensorEntityDescription( key="ups.realpower", name="Current Real Power", native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "ups.realpower.nominal": SensorEntityDescription( key="ups.realpower.nominal", name="Nominal Real Power", native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "ups.beeper.status": SensorEntityDescription( key="ups.beeper.status", name="Beeper Status", icon="mdi:information-outline", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "ups.type": SensorEntityDescription( key="ups.type", name="UPS Type", icon="mdi:information-outline", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "ups.watchdog.status": SensorEntityDescription( key="ups.watchdog.status", name="Watchdog Status", icon="mdi:information-outline", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "ups.start.auto": SensorEntityDescription( key="ups.start.auto", name="Start on AC", icon="mdi:information-outline", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "ups.start.battery": SensorEntityDescription( key="ups.start.battery", name="Start on Battery", icon="mdi:information-outline", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "ups.start.reboot": SensorEntityDescription( key="ups.start.reboot", name="Reboot on Battery", icon="mdi:information-outline", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "ups.shutdown": SensorEntityDescription( key="ups.shutdown", name="Shutdown Ability", icon="mdi:information-outline", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "battery.charge": SensorEntityDescription( key="battery.charge", name="Battery Charge", native_unit_of_measurement=PERCENTAGE, device_class=SensorDeviceClass.BATTERY, state_class=SensorStateClass.MEASUREMENT, ), "battery.charge.low": SensorEntityDescription( key="battery.charge.low", name="Low Battery Setpoint", native_unit_of_measurement=PERCENTAGE, icon="mdi:gauge", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "battery.charge.restart": SensorEntityDescription( key="battery.charge.restart", name="Minimum Battery to Start", native_unit_of_measurement=PERCENTAGE, icon="mdi:gauge", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "battery.charge.warning": SensorEntityDescription( key="battery.charge.warning", name="Warning Battery Setpoint", native_unit_of_measurement=PERCENTAGE, icon="mdi:gauge", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "battery.charger.status": SensorEntityDescription( key="battery.charger.status", name="Charging Status", icon="mdi:information-outline", ), "battery.voltage": SensorEntityDescription( key="battery.voltage", name="Battery Voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "battery.voltage.nominal": SensorEntityDescription( key="battery.voltage.nominal", name="Nominal Battery Voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "battery.voltage.low": SensorEntityDescription( key="battery.voltage.low", name="Low Battery Voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "battery.voltage.high": SensorEntityDescription( key="battery.voltage.high", name="High Battery Voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "battery.capacity": SensorEntityDescription( key="battery.capacity", name="Battery Capacity", native_unit_of_measurement="Ah", icon="mdi:flash", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "battery.current": SensorEntityDescription( key="battery.current", name="Battery Current", native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, device_class=SensorDeviceClass.CURRENT, state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "battery.current.total": SensorEntityDescription( key="battery.current.total", name="Total Battery Current", native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, device_class=SensorDeviceClass.CURRENT, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "battery.temperature": SensorEntityDescription( key="battery.temperature", name="Battery Temperature", native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "battery.runtime": SensorEntityDescription( key="battery.runtime", name="Battery Runtime", native_unit_of_measurement=UnitOfTime.SECONDS, device_class=SensorDeviceClass.DURATION, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "battery.runtime.low": SensorEntityDescription( key="battery.runtime.low", name="Low Battery Runtime", native_unit_of_measurement=UnitOfTime.SECONDS, device_class=SensorDeviceClass.DURATION, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "battery.runtime.restart": SensorEntityDescription( key="battery.runtime.restart", name="Minimum Battery Runtime to Start", native_unit_of_measurement=UnitOfTime.SECONDS, device_class=SensorDeviceClass.DURATION, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "battery.alarm.threshold": SensorEntityDescription( key="battery.alarm.threshold", name="Battery Alarm Threshold", icon="mdi:information-outline", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "battery.date": SensorEntityDescription( key="battery.date", name="Battery Date", icon="mdi:calendar", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "battery.mfr.date": SensorEntityDescription( key="battery.mfr.date", name="Battery Manuf. Date", icon="mdi:calendar", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "battery.packs": SensorEntityDescription( key="battery.packs", name="Number of Batteries", icon="mdi:information-outline", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "battery.packs.bad": SensorEntityDescription( key="battery.packs.bad", name="Number of Bad Batteries", icon="mdi:information-outline", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "battery.type": SensorEntityDescription( key="battery.type", name="Battery Chemistry", icon="mdi:information-outline", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "input.sensitivity": SensorEntityDescription( key="input.sensitivity", name="Input Power Sensitivity", icon="mdi:information-outline", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "input.transfer.low": SensorEntityDescription( key="input.transfer.low", name="Low Voltage Transfer", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "input.transfer.high": SensorEntityDescription( key="input.transfer.high", name="High Voltage Transfer", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "input.transfer.reason": SensorEntityDescription( key="input.transfer.reason", name="Voltage Transfer Reason", icon="mdi:information-outline", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "input.voltage": SensorEntityDescription( key="input.voltage", name="Input Voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, state_class=SensorStateClass.MEASUREMENT, ), "input.voltage.nominal": SensorEntityDescription( key="input.voltage.nominal", name="Nominal Input Voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "input.frequency": SensorEntityDescription( key="input.frequency", name="Input Line Frequency", native_unit_of_measurement=UnitOfFrequency.HERTZ, device_class=SensorDeviceClass.FREQUENCY, state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "input.frequency.nominal": SensorEntityDescription( key="input.frequency.nominal", name="Nominal Input Line Frequency", native_unit_of_measurement=UnitOfFrequency.HERTZ, device_class=SensorDeviceClass.FREQUENCY, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "input.frequency.status": SensorEntityDescription( key="input.frequency.status", name="Input Frequency Status", icon="mdi:information-outline", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "output.current": SensorEntityDescription( key="output.current", name="Output Current", native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, device_class=SensorDeviceClass.CURRENT, state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "output.current.nominal": SensorEntityDescription( key="output.current.nominal", name="Nominal Output Current", native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, device_class=SensorDeviceClass.CURRENT, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "output.voltage": SensorEntityDescription( key="output.voltage", name="Output Voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, state_class=SensorStateClass.MEASUREMENT, ), "output.voltage.nominal": SensorEntityDescription( key="output.voltage.nominal", name="Nominal Output Voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "output.frequency": SensorEntityDescription( key="output.frequency", name="Output Frequency", native_unit_of_measurement=UnitOfFrequency.HERTZ, device_class=SensorDeviceClass.FREQUENCY, state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "output.frequency.nominal": SensorEntityDescription( key="output.frequency.nominal", name="Nominal Output Frequency", native_unit_of_measurement=UnitOfFrequency.HERTZ, device_class=SensorDeviceClass.FREQUENCY, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), "ambient.humidity": SensorEntityDescription( key="ambient.humidity", name="Ambient Humidity", native_unit_of_measurement=PERCENTAGE, device_class=SensorDeviceClass.HUMIDITY, state_class=SensorStateClass.MEASUREMENT, ), "ambient.temperature": SensorEntityDescription( key="ambient.temperature", name="Ambient Temperature", native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, ), "watts": SensorEntityDescription( key="watts", name="Watts", native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, ), } def _get_nut_device_info(data: PyNUTData) -> DeviceInfo: """Return a DeviceInfo object filled with NUT device info.""" nut_dev_infos = asdict(data.device_info) nut_infos = { info_key: nut_dev_infos[nut_key] for nut_key, info_key in NUT_DEV_INFO_TO_DEV_INFO.items() if nut_dev_infos[nut_key] is not None } return cast(DeviceInfo, nut_infos) async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up the NUT sensors.""" pynut_data = hass.data[DOMAIN][config_entry.entry_id] coordinator = pynut_data[COORDINATOR] data = pynut_data[PYNUT_DATA] unique_id = pynut_data[PYNUT_UNIQUE_ID] status = coordinator.data resources = [sensor_id for sensor_id in SENSOR_TYPES if sensor_id in status] # Display status is a special case that falls back to the status value # of the UPS instead. if KEY_STATUS in resources: resources.append(KEY_STATUS_DISPLAY) entities = [ NUTSensor( coordinator, SENSOR_TYPES[sensor_type], data, unique_id, ) for sensor_type in resources ] async_add_entities(entities, True) class NUTSensor(CoordinatorEntity[DataUpdateCoordinator[dict[str, str]]], SensorEntity): """Representation of a sensor entity for NUT status values.""" def __init__( self, coordinator: DataUpdateCoordinator[dict[str, str]], sensor_description: SensorEntityDescription, data: PyNUTData, unique_id: str, ) -> None: """Initialize the sensor.""" super().__init__(coordinator) self.entity_description = sensor_description device_name = data.name.title() self._attr_name = f"{device_name} {sensor_description.name}" self._attr_unique_id = f"{unique_id}_{sensor_description.key}" self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, unique_id)}, name=device_name, ) self._attr_device_info.update(_get_nut_device_info(data)) @property def native_value(self) -> str | None: """Return entity state from ups.""" status = self.coordinator.data if self.entity_description.key == KEY_STATUS_DISPLAY: return _format_display_state(status) return status.get(self.entity_description.key) def _format_display_state(status: dict[str, str]) -> str: """Return UPS display state.""" try: return " ".join(STATE_TYPES[state] for state in status[KEY_STATUS].split()) except KeyError: return STATE_UNKNOWN