core/homeassistant/components/uptime_kuma/sensor.py

183 lines
6.2 KiB
Python

"""Sensor platform for the Uptime Kuma integration."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from enum import StrEnum
from pythonkuma import MonitorType, UptimeKumaMonitor
from pythonkuma.models import MonitorStatus
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
)
from homeassistant.const import CONF_URL, EntityCategory, UnitOfTime
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN, HAS_CERT, HAS_HOST, HAS_PORT, HAS_URL
from .coordinator import UptimeKumaConfigEntry, UptimeKumaDataUpdateCoordinator
PARALLEL_UPDATES = 0
class UptimeKumaSensor(StrEnum):
"""Uptime Kuma sensors."""
CERT_DAYS_REMAINING = "cert_days_remaining"
RESPONSE_TIME = "response_time"
STATUS = "status"
TYPE = "type"
URL = "url"
HOSTNAME = "hostname"
PORT = "port"
@dataclass(kw_only=True, frozen=True)
class UptimeKumaSensorEntityDescription(SensorEntityDescription):
"""Uptime Kuma sensor description."""
value_fn: Callable[[UptimeKumaMonitor], StateType]
create_entity: Callable[[MonitorType], bool]
SENSOR_DESCRIPTIONS: tuple[UptimeKumaSensorEntityDescription, ...] = (
UptimeKumaSensorEntityDescription(
key=UptimeKumaSensor.CERT_DAYS_REMAINING,
translation_key=UptimeKumaSensor.CERT_DAYS_REMAINING,
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.DAYS,
value_fn=lambda m: m.monitor_cert_days_remaining,
create_entity=lambda t: t in HAS_CERT,
),
UptimeKumaSensorEntityDescription(
key=UptimeKumaSensor.RESPONSE_TIME,
translation_key=UptimeKumaSensor.RESPONSE_TIME,
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.MILLISECONDS,
value_fn=(
lambda m: m.monitor_response_time if m.monitor_response_time > -1 else None
),
create_entity=lambda _: True,
),
UptimeKumaSensorEntityDescription(
key=UptimeKumaSensor.STATUS,
translation_key=UptimeKumaSensor.STATUS,
device_class=SensorDeviceClass.ENUM,
options=[m.name.lower() for m in MonitorStatus],
value_fn=lambda m: m.monitor_status.name.lower(),
create_entity=lambda _: True,
),
UptimeKumaSensorEntityDescription(
key=UptimeKumaSensor.TYPE,
translation_key=UptimeKumaSensor.TYPE,
device_class=SensorDeviceClass.ENUM,
options=[m.name.lower() for m in MonitorType],
value_fn=lambda m: m.monitor_type.name.lower(),
entity_category=EntityCategory.DIAGNOSTIC,
create_entity=lambda _: True,
),
UptimeKumaSensorEntityDescription(
key=UptimeKumaSensor.URL,
translation_key=UptimeKumaSensor.URL,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda m: m.monitor_url,
create_entity=lambda t: t in HAS_URL,
),
UptimeKumaSensorEntityDescription(
key=UptimeKumaSensor.HOSTNAME,
translation_key=UptimeKumaSensor.HOSTNAME,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda m: m.monitor_hostname,
create_entity=lambda t: t in HAS_HOST,
),
UptimeKumaSensorEntityDescription(
key=UptimeKumaSensor.PORT,
translation_key=UptimeKumaSensor.PORT,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda m: m.monitor_port,
create_entity=lambda t: t in HAS_PORT,
),
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: UptimeKumaConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the sensor platform."""
coordinator = config_entry.runtime_data
monitor_added: set[str | int] = set()
@callback
def add_entities() -> None:
"""Add sensor entities."""
nonlocal monitor_added
if new_monitor := set(coordinator.data.keys()) - monitor_added:
async_add_entities(
UptimeKumaSensorEntity(coordinator, monitor, description)
for description in SENSOR_DESCRIPTIONS
for monitor in new_monitor
if description.create_entity(coordinator.data[monitor].monitor_type)
)
monitor_added |= new_monitor
coordinator.async_add_listener(add_entities)
add_entities()
class UptimeKumaSensorEntity(
CoordinatorEntity[UptimeKumaDataUpdateCoordinator], SensorEntity
):
"""An Uptime Kuma sensor entity."""
entity_description: UptimeKumaSensorEntityDescription
_attr_has_entity_name = True
def __init__(
self,
coordinator: UptimeKumaDataUpdateCoordinator,
monitor: str | int,
entity_description: UptimeKumaSensorEntityDescription,
) -> None:
"""Initialize the entity."""
super().__init__(coordinator)
self.monitor = monitor
self.entity_description = entity_description
self._attr_unique_id = (
f"{coordinator.config_entry.entry_id}_{monitor!s}_{entity_description.key}"
)
self._attr_device_info = DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
name=coordinator.data[monitor].monitor_name,
identifiers={(DOMAIN, f"{coordinator.config_entry.entry_id}_{monitor!s}")},
manufacturer="Uptime Kuma",
configuration_url=(
None
if "127.0.0.1" in (url := coordinator.config_entry.data[CONF_URL])
else url
),
sw_version=coordinator.api.version.version,
)
@property
def native_value(self) -> StateType:
"""Return the state of the sensor."""
return self.entity_description.value_fn(self.coordinator.data[self.monitor])
@property
def available(self) -> bool:
"""Return True if entity is available."""
return super().available and self.monitor in self.coordinator.data