Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for monitoring emoncms feeds."""
2 
3 from __future__ import annotations
4 
5 from typing import Any
6 
7 import voluptuous as vol
8 
10  PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
11  SensorDeviceClass,
12  SensorEntity,
13  SensorEntityDescription,
14  SensorStateClass,
15 )
16 from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
17 from homeassistant.const import (
18  CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
19  CONCENTRATION_PARTS_PER_MILLION,
20  CONF_API_KEY,
21  CONF_ID,
22  CONF_UNIT_OF_MEASUREMENT,
23  CONF_URL,
24  CONF_VALUE_TEMPLATE,
25  PERCENTAGE,
26  UnitOfApparentPower,
27  UnitOfElectricCurrent,
28  UnitOfElectricPotential,
29  UnitOfEnergy,
30  UnitOfFrequency,
31  UnitOfPower,
32  UnitOfPressure,
33  UnitOfSoundPressure,
34  UnitOfSpeed,
35  UnitOfTemperature,
36  UnitOfVolume,
37  UnitOfVolumeFlowRate,
38 )
39 from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant, callback
40 from homeassistant.data_entry_flow import FlowResultType
41 from homeassistant.helpers import template
43 from homeassistant.helpers.entity_platform import AddEntitiesCallback
44 from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
45 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
46 from homeassistant.helpers.update_coordinator import CoordinatorEntity
47 
48 from .config_flow import sensor_name
49 from .const import (
50  CONF_EXCLUDE_FEEDID,
51  CONF_ONLY_INCLUDE_FEEDID,
52  DOMAIN,
53  FEED_ID,
54  FEED_NAME,
55  FEED_TAG,
56 )
57 from .coordinator import EmoncmsCoordinator
58 
59 SENSORS: dict[str | None, SensorEntityDescription] = {
61  key="energy|kWh",
62  translation_key="energy",
63  device_class=SensorDeviceClass.ENERGY,
64  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
65  state_class=SensorStateClass.TOTAL_INCREASING,
66  ),
68  key="energy|Wh",
69  translation_key="energy",
70  device_class=SensorDeviceClass.ENERGY,
71  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
72  state_class=SensorStateClass.TOTAL_INCREASING,
73  ),
75  key="power|kW",
76  translation_key="power",
77  device_class=SensorDeviceClass.POWER,
78  native_unit_of_measurement=UnitOfPower.KILO_WATT,
79  state_class=SensorStateClass.MEASUREMENT,
80  ),
82  key="power|W",
83  translation_key="power",
84  device_class=SensorDeviceClass.POWER,
85  native_unit_of_measurement=UnitOfPower.WATT,
86  state_class=SensorStateClass.MEASUREMENT,
87  ),
89  key="voltage",
90  translation_key="voltage",
91  device_class=SensorDeviceClass.VOLTAGE,
92  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
93  state_class=SensorStateClass.MEASUREMENT,
94  ),
96  key="current",
97  translation_key="current",
98  device_class=SensorDeviceClass.CURRENT,
99  native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
100  state_class=SensorStateClass.MEASUREMENT,
101  ),
103  key="apparent_power",
104  translation_key="apparent_power",
105  device_class=SensorDeviceClass.APPARENT_POWER,
106  native_unit_of_measurement=UnitOfApparentPower.VOLT_AMPERE,
107  state_class=SensorStateClass.MEASUREMENT,
108  ),
110  key="temperature|celsius",
111  translation_key="temperature",
112  device_class=SensorDeviceClass.TEMPERATURE,
113  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
114  state_class=SensorStateClass.MEASUREMENT,
115  ),
117  key="temperature|fahrenheit",
118  translation_key="temperature",
119  device_class=SensorDeviceClass.TEMPERATURE,
120  native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
121  state_class=SensorStateClass.MEASUREMENT,
122  ),
124  key="temperature|kelvin",
125  translation_key="temperature",
126  device_class=SensorDeviceClass.TEMPERATURE,
127  native_unit_of_measurement=UnitOfTemperature.KELVIN,
128  state_class=SensorStateClass.MEASUREMENT,
129  ),
131  key="frequency",
132  translation_key="frequency",
133  device_class=SensorDeviceClass.FREQUENCY,
134  native_unit_of_measurement=UnitOfFrequency.HERTZ,
135  state_class=SensorStateClass.MEASUREMENT,
136  ),
138  key="pressure",
139  translation_key="pressure",
140  device_class=SensorDeviceClass.PRESSURE,
141  native_unit_of_measurement=UnitOfPressure.HPA,
142  state_class=SensorStateClass.MEASUREMENT,
143  ),
145  key="decibel",
146  translation_key="decibel",
147  device_class=SensorDeviceClass.SIGNAL_STRENGTH,
148  native_unit_of_measurement=UnitOfSoundPressure.DECIBEL,
149  state_class=SensorStateClass.MEASUREMENT,
150  ),
152  key="volume|cubic_meter",
153  translation_key="volume",
154  device_class=SensorDeviceClass.VOLUME_STORAGE,
155  native_unit_of_measurement=UnitOfVolume.CUBIC_METERS,
156  state_class=SensorStateClass.MEASUREMENT,
157  ),
158  "m³/h": SensorEntityDescription(
159  key="flow|cubic_meters_per_hour",
160  translation_key="flow",
161  device_class=SensorDeviceClass.VOLUME_FLOW_RATE,
162  native_unit_of_measurement=UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR,
163  state_class=SensorStateClass.MEASUREMENT,
164  ),
166  key="flow|liters_per_minute",
167  translation_key="flow",
168  device_class=SensorDeviceClass.VOLUME_FLOW_RATE,
169  native_unit_of_measurement=UnitOfVolumeFlowRate.LITERS_PER_MINUTE,
170  state_class=SensorStateClass.MEASUREMENT,
171  ),
173  key="speed|meters_per_second",
174  translation_key="speed",
175  device_class=SensorDeviceClass.SPEED,
176  native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND,
177  state_class=SensorStateClass.MEASUREMENT,
178  ),
179  "µg/m³": SensorEntityDescription(
180  key="concentration|microgram_per_cubic_meter",
181  translation_key="concentration",
182  native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
183  state_class=SensorStateClass.MEASUREMENT,
184  ),
186  key="concentration|microgram_parts_per_million",
187  translation_key="concentration",
188  native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
189  state_class=SensorStateClass.MEASUREMENT,
190  ),
192  key="percent",
193  translation_key="percent",
194  native_unit_of_measurement=PERCENTAGE,
195  state_class=SensorStateClass.MEASUREMENT,
196  ),
197 }
198 
199 ATTR_FEEDID = "FeedId"
200 ATTR_FEEDNAME = "FeedName"
201 ATTR_LASTUPDATETIME = "LastUpdated"
202 ATTR_LASTUPDATETIMESTR = "LastUpdatedStr"
203 ATTR_SIZE = "Size"
204 ATTR_TAG = "Tag"
205 ATTR_USERID = "UserId"
206 CONF_SENSOR_NAMES = "sensor_names"
207 DECIMALS = 2
208 DEFAULT_UNIT = UnitOfPower.WATT
209 
210 ONLY_INCL_EXCL_NONE = "only_include_exclude_or_none"
211 
212 PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
213  {
214  vol.Required(CONF_API_KEY): cv.string,
215  vol.Required(CONF_URL): cv.string,
216  vol.Required(CONF_ID): cv.positive_int,
217  vol.Exclusive(CONF_ONLY_INCLUDE_FEEDID, ONLY_INCL_EXCL_NONE): vol.All(
218  cv.ensure_list, [cv.positive_int]
219  ),
220  vol.Exclusive(CONF_EXCLUDE_FEEDID, ONLY_INCL_EXCL_NONE): vol.All(
221  cv.ensure_list, [cv.positive_int]
222  ),
223  vol.Optional(CONF_SENSOR_NAMES): vol.All(
224  {cv.positive_int: vol.All(cv.string, vol.Length(min=1))}
225  ),
226  vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
227  vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=DEFAULT_UNIT): cv.string,
228  }
229 )
230 
231 
233  hass: HomeAssistant,
234  config: ConfigType,
235  async_add_entities: AddEntitiesCallback,
236  discovery_info: DiscoveryInfoType | None = None,
237 ) -> None:
238  """Import config from yaml."""
239  if CONF_VALUE_TEMPLATE in config:
241  hass,
242  DOMAIN,
243  f"remove_{CONF_VALUE_TEMPLATE}_{DOMAIN}",
244  is_fixable=False,
245  issue_domain=DOMAIN,
246  severity=IssueSeverity.ERROR,
247  translation_key=f"remove_{CONF_VALUE_TEMPLATE}",
248  translation_placeholders={
249  "domain": DOMAIN,
250  "parameter": CONF_VALUE_TEMPLATE,
251  },
252  )
253  return
254  if CONF_ONLY_INCLUDE_FEEDID not in config:
256  hass,
257  DOMAIN,
258  f"missing_{CONF_ONLY_INCLUDE_FEEDID}_{DOMAIN}",
259  is_fixable=False,
260  issue_domain=DOMAIN,
261  severity=IssueSeverity.WARNING,
262  translation_key=f"missing_{CONF_ONLY_INCLUDE_FEEDID}",
263  translation_placeholders={
264  "domain": DOMAIN,
265  },
266  )
267  result = await hass.config_entries.flow.async_init(
268  DOMAIN, context={"source": SOURCE_IMPORT}, data=config
269  )
270  if (
271  result.get("type") == FlowResultType.CREATE_ENTRY
272  or result.get("reason") == "already_configured"
273  ):
275  hass,
276  HOMEASSISTANT_DOMAIN,
277  f"deprecated_yaml_{DOMAIN}",
278  is_fixable=False,
279  issue_domain=DOMAIN,
280  breaks_in_ha_version="2025.3.0",
281  severity=IssueSeverity.WARNING,
282  translation_key="deprecated_yaml",
283  translation_placeholders={
284  "domain": DOMAIN,
285  "integration_title": "emoncms",
286  },
287  )
288 
289 
291  hass: HomeAssistant,
292  entry: ConfigEntry,
293  async_add_entities: AddEntitiesCallback,
294 ) -> None:
295  """Set up the emoncms sensors."""
296  name = sensor_name(entry.data[CONF_URL])
297  exclude_feeds = entry.data.get(CONF_EXCLUDE_FEEDID)
298  include_only_feeds = entry.options.get(
299  CONF_ONLY_INCLUDE_FEEDID, entry.data.get(CONF_ONLY_INCLUDE_FEEDID)
300  )
301 
302  if exclude_feeds is None and include_only_feeds is None:
303  return
304 
305  coordinator = entry.runtime_data
306  # uuid was added in emoncms database 11.5.7
307  unique_id = entry.unique_id if entry.unique_id else entry.entry_id
308  elems = coordinator.data
309  if not elems:
310  return
311  sensors: list[EmonCmsSensor] = []
312 
313  for idx, elem in enumerate(elems):
314  if include_only_feeds is not None and elem[FEED_ID] not in include_only_feeds:
315  continue
316  sensors.append(
318  coordinator,
319  unique_id,
320  elem["unit"],
321  name,
322  idx,
323  )
324  )
325  async_add_entities(sensors)
326 
327 
328 class EmonCmsSensor(CoordinatorEntity[EmoncmsCoordinator], SensorEntity):
329  """Implementation of an Emoncms sensor."""
330 
331  _attr_has_entity_name = True
332 
333  def __init__(
334  self,
335  coordinator: EmoncmsCoordinator,
336  unique_id: str,
337  unit_of_measurement: str | None,
338  name: str,
339  idx: int,
340  ) -> None:
341  """Initialize the sensor."""
342  super().__init__(coordinator)
343  self.idxidx = idx
344  elem = {}
345  if self.coordinator.data:
346  elem = self.coordinator.data[self.idxidx]
347  self._attr_translation_placeholders_attr_translation_placeholders = {
348  "emoncms_details": f"{elem[FEED_TAG]} {elem[FEED_NAME]}",
349  }
350  self._attr_unique_id_attr_unique_id = f"{unique_id}-{elem[FEED_ID]}"
351  description = SENSORS.get(unit_of_measurement)
352  if description is not None:
353  self.entity_descriptionentity_description = description
354  else:
355  self._attr_native_unit_of_measurement_attr_native_unit_of_measurement = unit_of_measurement
356  self._update_attributes_update_attributes(elem)
357 
358  def _update_attributes(self, elem: dict[str, Any]) -> None:
359  """Update entity attributes."""
360  self._attr_extra_state_attributes_attr_extra_state_attributes = {
361  ATTR_FEEDID: elem[FEED_ID],
362  ATTR_TAG: elem[FEED_TAG],
363  ATTR_FEEDNAME: elem[FEED_NAME],
364  }
365  if elem["value"] is not None:
366  self._attr_extra_state_attributes_attr_extra_state_attributes[ATTR_SIZE] = elem["size"]
367  self._attr_extra_state_attributes_attr_extra_state_attributes[ATTR_USERID] = elem["userid"]
368  self._attr_extra_state_attributes_attr_extra_state_attributes[ATTR_LASTUPDATETIME] = elem["time"]
369  self._attr_extra_state_attributes_attr_extra_state_attributes[ATTR_LASTUPDATETIMESTR] = (
370  template.timestamp_local(float(elem["time"]))
371  )
372 
373  self._attr_native_value_attr_native_value = None
374  if elem["value"] is not None:
375  self._attr_native_value_attr_native_value = round(float(elem["value"]), DECIMALS)
376 
377  @callback
378  def _handle_coordinator_update(self) -> None:
379  """Handle updated data from the coordinator."""
380  data = self.coordinator.data
381  if data:
382  self._update_attributes_update_attributes(data[self.idxidx])
None _update_attributes(self, dict[str, Any] elem)
Definition: sensor.py:358
None __init__(self, EmoncmsCoordinator coordinator, str unique_id, str|None unit_of_measurement, str name, int idx)
Definition: sensor.py:340
None async_setup_entry(HomeAssistant hass, ConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:294
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: sensor.py:237
None async_create_issue(HomeAssistant hass, str entry_id)
Definition: repairs.py:69