1
0
mirror of https://github.com/home-assistant/core.git synced 2026-05-19 06:50:15 +01:00
Files
core/homeassistant/components/netdata/sensor.py
T
2026-04-30 21:14:48 +02:00

204 lines
6.3 KiB
Python

"""Support gathering system information of hosts which are running netdata."""
import logging
from netdata import Netdata
from netdata.exceptions import NetdataError
import voluptuous as vol
from homeassistant.components.sensor import (
PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
SensorEntity,
)
from homeassistant.const import (
CONF_HOST,
CONF_ICON,
CONF_NAME,
CONF_PORT,
CONF_RESOURCES,
PERCENTAGE,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.httpx_client import get_async_client
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
_LOGGER = logging.getLogger(__name__)
CONF_DATA_GROUP = "data_group"
CONF_ELEMENT = "element"
CONF_INVERT = "invert"
DEFAULT_HOST = "localhost"
DEFAULT_NAME = "Netdata"
DEFAULT_PORT = 19999
DEFAULT_ICON = "mdi:desktop-classic"
RESOURCE_SCHEMA = vol.Any(
{
vol.Required(CONF_DATA_GROUP): cv.string,
vol.Required(CONF_ELEMENT): cv.string,
vol.Optional(CONF_ICON, default=DEFAULT_ICON): cv.icon,
vol.Optional(CONF_INVERT, default=False): cv.boolean,
}
)
PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Required(CONF_RESOURCES): vol.Schema({cv.string: RESOURCE_SCHEMA}),
}
)
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Netdata sensor."""
name = config[CONF_NAME]
host = config[CONF_HOST]
port = config[CONF_PORT]
resources = config[CONF_RESOURCES]
netdata = NetdataData(
Netdata(host, port=port, timeout=20.0, httpx_client=get_async_client(hass))
)
await netdata.async_update()
if netdata.api.metrics is None:
raise PlatformNotReady
dev: list[SensorEntity] = []
for entry, data in resources.items():
icon = data[CONF_ICON]
sensor = data[CONF_DATA_GROUP]
element = data[CONF_ELEMENT]
invert = data[CONF_INVERT]
sensor_name = entry
try:
resource_data = netdata.api.metrics[sensor]
unit = (
PERCENTAGE
if resource_data["units"] == "percentage"
else resource_data["units"]
)
except KeyError:
_LOGGER.error("Sensor is not available: %s", sensor)
continue
dev.append(
NetdataSensor(
netdata, name, sensor, sensor_name, element, icon, unit, invert
)
)
dev.append(NetdataAlarms(netdata, name, host, port))
async_add_entities(dev, True)
class NetdataSensor(SensorEntity):
"""Implementation of a Netdata sensor."""
def __init__(self, netdata, name, sensor, sensor_name, element, icon, unit, invert):
"""Initialize the Netdata sensor."""
self.netdata = netdata
self._sensor = sensor
self._element = element
if sensor_name is None:
sensor_name = self._sensor
self._attr_name = f"{name} {sensor_name}"
self._attr_icon = icon
self._attr_native_unit_of_measurement = unit
self._invert = invert
@property
def available(self) -> bool:
"""Could the resource be accessed during the last update call."""
return self.netdata.available
async def async_update(self) -> None:
"""Get the latest data from Netdata REST API."""
await self.netdata.async_update()
resource_data = self.netdata.api.metrics.get(self._sensor)
self._attr_native_value = round(
resource_data["dimensions"][self._element]["value"], 2
) * (-1 if self._invert else 1)
class NetdataAlarms(SensorEntity):
"""Implementation of a Netdata alarm sensor."""
def __init__(self, netdata, name, host, port):
"""Initialize the Netdata alarm sensor."""
self.netdata = netdata
self._attr_name = f"{name} Alarms"
self._host = host
self._port = port
@property
def icon(self) -> str:
"""Status symbol if type is symbol."""
if self._attr_native_value == "ok":
return "mdi:check"
if self._attr_native_value == "warning":
return "mdi:alert-outline"
if self._attr_native_value == "critical":
return "mdi:alert"
return "mdi:crosshairs-question"
@property
def available(self) -> bool:
"""Could the resource be accessed during the last update call."""
return self.netdata.available
async def async_update(self) -> None:
"""Get the latest alarms from Netdata REST API."""
await self.netdata.async_update()
alarms = self.netdata.api.alarms["alarms"]
self._attr_native_value = None
number_of_alarms = len(alarms)
number_of_relevant_alarms = number_of_alarms
_LOGGER.debug("Host %s has %s alarms", self.name, number_of_alarms)
for alarm in alarms:
if alarms[alarm]["recipient"] == "silent" or alarms[alarm]["status"] in (
"CLEAR",
"UNDEFINED",
"UNINITIALIZED",
):
number_of_relevant_alarms = number_of_relevant_alarms - 1
elif alarms[alarm]["status"] == "CRITICAL":
self._attr_native_value = "critical"
return
self._attr_native_value = "ok" if number_of_relevant_alarms == 0 else "warning"
class NetdataData:
"""The class for handling the data retrieval."""
def __init__(self, api):
"""Initialize the data object."""
self.api = api
self.available = True
async def async_update(self):
"""Get the latest data from the Netdata REST API."""
try:
await self.api.get_allmetrics()
await self.api.get_alarms()
self.available = True
except NetdataError:
_LOGGER.error("Unable to retrieve data from Netdata")
self.available = False