Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support gathering system information of hosts which are running netdata."""
2 
3 from __future__ import annotations
4 
5 import logging
6 
7 from netdata import Netdata
8 from netdata.exceptions import NetdataError
9 import voluptuous as vol
10 
12  PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
13  SensorEntity,
14 )
15 from homeassistant.const import (
16  CONF_HOST,
17  CONF_ICON,
18  CONF_NAME,
19  CONF_PORT,
20  CONF_RESOURCES,
21  PERCENTAGE,
22 )
23 from homeassistant.core import HomeAssistant
24 from homeassistant.exceptions import PlatformNotReady
26 from homeassistant.helpers.entity_platform import AddEntitiesCallback
27 from homeassistant.helpers.httpx_client import get_async_client
28 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
29 
30 _LOGGER = logging.getLogger(__name__)
31 
32 CONF_DATA_GROUP = "data_group"
33 CONF_ELEMENT = "element"
34 CONF_INVERT = "invert"
35 
36 DEFAULT_HOST = "localhost"
37 DEFAULT_NAME = "Netdata"
38 DEFAULT_PORT = 19999
39 
40 DEFAULT_ICON = "mdi:desktop-classic"
41 
42 RESOURCE_SCHEMA = vol.Any(
43  {
44  vol.Required(CONF_DATA_GROUP): cv.string,
45  vol.Required(CONF_ELEMENT): cv.string,
46  vol.Optional(CONF_ICON, default=DEFAULT_ICON): cv.icon,
47  vol.Optional(CONF_INVERT, default=False): cv.boolean,
48  }
49 )
50 
51 PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
52  {
53  vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
54  vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
55  vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
56  vol.Required(CONF_RESOURCES): vol.Schema({cv.string: RESOURCE_SCHEMA}),
57  }
58 )
59 
60 
62  hass: HomeAssistant,
63  config: ConfigType,
64  async_add_entities: AddEntitiesCallback,
65  discovery_info: DiscoveryInfoType | None = None,
66 ) -> None:
67  """Set up the Netdata sensor."""
68 
69  name = config[CONF_NAME]
70  host = config[CONF_HOST]
71  port = config[CONF_PORT]
72  resources = config[CONF_RESOURCES]
73 
74  netdata = NetdataData(
75  Netdata(host, port=port, timeout=20.0, httpx_client=get_async_client(hass))
76  )
77  await netdata.async_update()
78 
79  if netdata.api.metrics is None:
80  raise PlatformNotReady
81 
82  dev: list[SensorEntity] = []
83  for entry, data in resources.items():
84  icon = data[CONF_ICON]
85  sensor = data[CONF_DATA_GROUP]
86  element = data[CONF_ELEMENT]
87  invert = data[CONF_INVERT]
88  sensor_name = entry
89  try:
90  resource_data = netdata.api.metrics[sensor]
91  unit = (
92  PERCENTAGE
93  if resource_data["units"] == "percentage"
94  else resource_data["units"]
95  )
96  except KeyError:
97  _LOGGER.error("Sensor is not available: %s", sensor)
98  continue
99 
100  dev.append(
102  netdata, name, sensor, sensor_name, element, icon, unit, invert
103  )
104  )
105 
106  dev.append(NetdataAlarms(netdata, name, host, port))
107  async_add_entities(dev, True)
108 
109 
111  """Implementation of a Netdata sensor."""
112 
113  def __init__(self, netdata, name, sensor, sensor_name, element, icon, unit, invert):
114  """Initialize the Netdata sensor."""
115  self.netdatanetdata = netdata
116  self._state_state = None
117  self._sensor_sensor = sensor
118  self._element_element = element
119  self._sensor_name_sensor_name = self._sensor_sensor if sensor_name is None else sensor_name
120  self._name_name = name
121  self._icon_icon = icon
122  self._unit_of_measurement_unit_of_measurement = unit
123  self._invert_invert = invert
124 
125  @property
126  def name(self):
127  """Return the name of the sensor."""
128  return f"{self._name} {self._sensor_name}"
129 
130  @property
132  """Return the unit the value is expressed in."""
133  return self._unit_of_measurement_unit_of_measurement
134 
135  @property
136  def icon(self):
137  """Return the icon to use in the frontend, if any."""
138  return self._icon_icon
139 
140  @property
141  def native_value(self):
142  """Return the state of the resources."""
143  return self._state_state
144 
145  @property
146  def available(self) -> bool:
147  """Could the resource be accessed during the last update call."""
148  return self.netdatanetdata.available
149 
150  async def async_update(self) -> None:
151  """Get the latest data from Netdata REST API."""
152  await self.netdatanetdata.async_update()
153  resource_data = self.netdatanetdata.api.metrics.get(self._sensor_sensor)
154  self._state_state = round(resource_data["dimensions"][self._element_element]["value"], 2) * (
155  -1 if self._invert_invert else 1
156  )
157 
158 
160  """Implementation of a Netdata alarm sensor."""
161 
162  def __init__(self, netdata, name, host, port):
163  """Initialize the Netdata alarm sensor."""
164  self.netdatanetdata = netdata
165  self._state_state = None
166  self._name_name = name
167  self._host_host = host
168  self._port_port = port
169 
170  @property
171  def name(self):
172  """Return the name of the sensor."""
173  return f"{self._name} Alarms"
174 
175  @property
176  def native_value(self):
177  """Return the state of the resources."""
178  return self._state_state
179 
180  @property
181  def icon(self):
182  """Status symbol if type is symbol."""
183  if self._state_state == "ok":
184  return "mdi:check"
185  if self._state_state == "warning":
186  return "mdi:alert-outline"
187  if self._state_state == "critical":
188  return "mdi:alert"
189  return "mdi:crosshairs-question"
190 
191  @property
192  def available(self) -> bool:
193  """Could the resource be accessed during the last update call."""
194  return self.netdatanetdata.available
195 
196  async def async_update(self) -> None:
197  """Get the latest alarms from Netdata REST API."""
198  await self.netdatanetdata.async_update()
199  alarms = self.netdatanetdata.api.alarms["alarms"]
200  self._state_state = None
201  number_of_alarms = len(alarms)
202  number_of_relevant_alarms = number_of_alarms
203 
204  _LOGGER.debug("Host %s has %s alarms", self.namenamename, number_of_alarms)
205 
206  for alarm in alarms:
207  if alarms[alarm]["recipient"] == "silent" or alarms[alarm]["status"] in (
208  "CLEAR",
209  "UNDEFINED",
210  "UNINITIALIZED",
211  ):
212  number_of_relevant_alarms = number_of_relevant_alarms - 1
213  elif alarms[alarm]["status"] == "CRITICAL":
214  self._state_state = "critical"
215  return
216  self._state_state = "ok" if number_of_relevant_alarms == 0 else "warning"
217 
218 
220  """The class for handling the data retrieval."""
221 
222  def __init__(self, api):
223  """Initialize the data object."""
224  self.apiapi = api
225  self.availableavailable = True
226 
227  async def async_update(self):
228  """Get the latest data from the Netdata REST API."""
229 
230  try:
231  await self.apiapi.get_allmetrics()
232  await self.apiapi.get_alarms()
233  self.availableavailable = True
234  except NetdataError:
235  _LOGGER.error("Unable to retrieve data from Netdata")
236  self.availableavailable = False
def __init__(self, netdata, name, host, port)
Definition: sensor.py:162
def __init__(self, netdata, name, sensor, sensor_name, element, icon, unit, invert)
Definition: sensor.py:113
str|UndefinedType|None name(self)
Definition: entity.py:738
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: sensor.py:66
httpx.AsyncClient get_async_client(HomeAssistant hass, bool verify_ssl=True)
Definition: httpx_client.py:41