Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Representation of Z-Wave sensors."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable, Mapping
6 from dataclasses import dataclass
7 from typing import Any
8 
9 import voluptuous as vol
10 from zwave_js_server.client import Client as ZwaveClient
11 from zwave_js_server.const import CommandClass
12 from zwave_js_server.const.command_class.meter import (
13  RESET_METER_OPTION_TARGET_VALUE,
14  RESET_METER_OPTION_TYPE,
15 )
16 from zwave_js_server.exceptions import BaseZwaveJSServerError
17 from zwave_js_server.model.controller import Controller
18 from zwave_js_server.model.controller.statistics import ControllerStatistics
19 from zwave_js_server.model.driver import Driver
20 from zwave_js_server.model.node import Node as ZwaveNode
21 from zwave_js_server.model.node.statistics import NodeStatistics
22 from zwave_js_server.util.command_class.meter import get_meter_type
23 
25  DOMAIN as SENSOR_DOMAIN,
26  SensorDeviceClass,
27  SensorEntity,
28  SensorEntityDescription,
29  SensorStateClass,
30 )
31 from homeassistant.config_entries import ConfigEntry
32 from homeassistant.const import (
33  CONCENTRATION_PARTS_PER_MILLION,
34  LIGHT_LUX,
35  PERCENTAGE,
36  SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
37  UV_INDEX,
38  EntityCategory,
39  UnitOfElectricCurrent,
40  UnitOfElectricPotential,
41  UnitOfEnergy,
42  UnitOfPower,
43  UnitOfPressure,
44  UnitOfTemperature,
45  UnitOfTime,
46 )
47 from homeassistant.core import HomeAssistant, callback
48 from homeassistant.exceptions import HomeAssistantError
49 from homeassistant.helpers import entity_platform
50 from homeassistant.helpers.dispatcher import async_dispatcher_connect
51 from homeassistant.helpers.entity_platform import AddEntitiesCallback
52 from homeassistant.helpers.typing import UNDEFINED, StateType
53 
54 from .binary_sensor import is_valid_notification_binary_sensor
55 from .const import (
56  ATTR_METER_TYPE,
57  ATTR_METER_TYPE_NAME,
58  ATTR_VALUE,
59  DATA_CLIENT,
60  DOMAIN,
61  ENTITY_DESC_KEY_BATTERY,
62  ENTITY_DESC_KEY_CO,
63  ENTITY_DESC_KEY_CO2,
64  ENTITY_DESC_KEY_CURRENT,
65  ENTITY_DESC_KEY_ENERGY_MEASUREMENT,
66  ENTITY_DESC_KEY_ENERGY_PRODUCTION_POWER,
67  ENTITY_DESC_KEY_ENERGY_PRODUCTION_TIME,
68  ENTITY_DESC_KEY_ENERGY_PRODUCTION_TODAY,
69  ENTITY_DESC_KEY_ENERGY_PRODUCTION_TOTAL,
70  ENTITY_DESC_KEY_ENERGY_TOTAL_INCREASING,
71  ENTITY_DESC_KEY_HUMIDITY,
72  ENTITY_DESC_KEY_ILLUMINANCE,
73  ENTITY_DESC_KEY_MEASUREMENT,
74  ENTITY_DESC_KEY_POWER,
75  ENTITY_DESC_KEY_POWER_FACTOR,
76  ENTITY_DESC_KEY_PRESSURE,
77  ENTITY_DESC_KEY_SIGNAL_STRENGTH,
78  ENTITY_DESC_KEY_TARGET_TEMPERATURE,
79  ENTITY_DESC_KEY_TEMPERATURE,
80  ENTITY_DESC_KEY_TOTAL_INCREASING,
81  ENTITY_DESC_KEY_UV_INDEX,
82  ENTITY_DESC_KEY_VOLTAGE,
83  LOGGER,
84  SERVICE_RESET_METER,
85 )
86 from .discovery import ZwaveDiscoveryInfo
87 from .discovery_data_template import (
88  NumericSensorDataTemplate,
89  NumericSensorDataTemplateData,
90 )
91 from .entity import ZWaveBaseEntity
92 from .helpers import get_device_info, get_valueless_base_unique_id
93 from .migrate import async_migrate_statistics_sensors
94 
95 PARALLEL_UPDATES = 0
96 
97 
98 # These descriptions should include device class.
99 ENTITY_DESCRIPTION_KEY_DEVICE_CLASS_MAP: dict[
100  tuple[str, str], SensorEntityDescription
101 ] = {
102  (ENTITY_DESC_KEY_BATTERY, PERCENTAGE): SensorEntityDescription(
103  key=ENTITY_DESC_KEY_BATTERY,
104  device_class=SensorDeviceClass.BATTERY,
105  entity_category=EntityCategory.DIAGNOSTIC,
106  state_class=SensorStateClass.MEASUREMENT,
107  native_unit_of_measurement=PERCENTAGE,
108  ),
109  (ENTITY_DESC_KEY_CURRENT, UnitOfElectricCurrent.AMPERE): SensorEntityDescription(
110  key=ENTITY_DESC_KEY_CURRENT,
111  device_class=SensorDeviceClass.CURRENT,
112  state_class=SensorStateClass.MEASUREMENT,
113  native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
114  ),
115  (ENTITY_DESC_KEY_VOLTAGE, UnitOfElectricPotential.VOLT): SensorEntityDescription(
116  key=ENTITY_DESC_KEY_VOLTAGE,
117  device_class=SensorDeviceClass.VOLTAGE,
118  state_class=SensorStateClass.MEASUREMENT,
119  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
120  suggested_display_precision=0,
121  ),
122  (
123  ENTITY_DESC_KEY_VOLTAGE,
124  UnitOfElectricPotential.MILLIVOLT,
126  key=ENTITY_DESC_KEY_VOLTAGE,
127  device_class=SensorDeviceClass.VOLTAGE,
128  state_class=SensorStateClass.MEASUREMENT,
129  native_unit_of_measurement=UnitOfElectricPotential.MILLIVOLT,
130  ),
131  (
132  ENTITY_DESC_KEY_ENERGY_TOTAL_INCREASING,
133  UnitOfEnergy.KILO_WATT_HOUR,
135  key=ENTITY_DESC_KEY_ENERGY_TOTAL_INCREASING,
136  device_class=SensorDeviceClass.ENERGY,
137  state_class=SensorStateClass.TOTAL_INCREASING,
138  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
139  ),
140  (ENTITY_DESC_KEY_POWER, UnitOfPower.WATT): SensorEntityDescription(
141  key=ENTITY_DESC_KEY_POWER,
142  device_class=SensorDeviceClass.POWER,
143  state_class=SensorStateClass.MEASUREMENT,
144  native_unit_of_measurement=UnitOfPower.WATT,
145  ),
146  (ENTITY_DESC_KEY_POWER_FACTOR, PERCENTAGE): SensorEntityDescription(
147  key=ENTITY_DESC_KEY_POWER_FACTOR,
148  device_class=SensorDeviceClass.POWER_FACTOR,
149  state_class=SensorStateClass.MEASUREMENT,
150  native_unit_of_measurement=PERCENTAGE,
151  ),
152  (ENTITY_DESC_KEY_CO, CONCENTRATION_PARTS_PER_MILLION): SensorEntityDescription(
153  key=ENTITY_DESC_KEY_CO,
154  device_class=SensorDeviceClass.CO,
155  state_class=SensorStateClass.MEASUREMENT,
156  native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
157  ),
158  (ENTITY_DESC_KEY_CO2, CONCENTRATION_PARTS_PER_MILLION): SensorEntityDescription(
159  key=ENTITY_DESC_KEY_CO2,
160  device_class=SensorDeviceClass.CO2,
161  state_class=SensorStateClass.MEASUREMENT,
162  native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
163  ),
164  (ENTITY_DESC_KEY_HUMIDITY, PERCENTAGE): SensorEntityDescription(
165  key=ENTITY_DESC_KEY_HUMIDITY,
166  device_class=SensorDeviceClass.HUMIDITY,
167  state_class=SensorStateClass.MEASUREMENT,
168  native_unit_of_measurement=PERCENTAGE,
169  ),
170  (ENTITY_DESC_KEY_ILLUMINANCE, LIGHT_LUX): SensorEntityDescription(
171  key=ENTITY_DESC_KEY_ILLUMINANCE,
172  device_class=SensorDeviceClass.ILLUMINANCE,
173  state_class=SensorStateClass.MEASUREMENT,
174  native_unit_of_measurement=LIGHT_LUX,
175  ),
176  (ENTITY_DESC_KEY_PRESSURE, UnitOfPressure.KPA): SensorEntityDescription(
177  key=ENTITY_DESC_KEY_PRESSURE,
178  device_class=SensorDeviceClass.PRESSURE,
179  state_class=SensorStateClass.MEASUREMENT,
180  native_unit_of_measurement=UnitOfPressure.KPA,
181  ),
182  (ENTITY_DESC_KEY_PRESSURE, UnitOfPressure.PSI): SensorEntityDescription(
183  key=ENTITY_DESC_KEY_PRESSURE,
184  device_class=SensorDeviceClass.PRESSURE,
185  state_class=SensorStateClass.MEASUREMENT,
186  native_unit_of_measurement=UnitOfPressure.PSI,
187  ),
188  (ENTITY_DESC_KEY_PRESSURE, UnitOfPressure.INHG): SensorEntityDescription(
189  key=ENTITY_DESC_KEY_PRESSURE,
190  device_class=SensorDeviceClass.PRESSURE,
191  state_class=SensorStateClass.MEASUREMENT,
192  native_unit_of_measurement=UnitOfPressure.INHG,
193  ),
194  (ENTITY_DESC_KEY_PRESSURE, UnitOfPressure.MMHG): SensorEntityDescription(
195  key=ENTITY_DESC_KEY_PRESSURE,
196  device_class=SensorDeviceClass.PRESSURE,
197  state_class=SensorStateClass.MEASUREMENT,
198  native_unit_of_measurement=UnitOfPressure.MMHG,
199  ),
200  (
201  ENTITY_DESC_KEY_SIGNAL_STRENGTH,
202  SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
204  key=ENTITY_DESC_KEY_SIGNAL_STRENGTH,
205  device_class=SensorDeviceClass.SIGNAL_STRENGTH,
206  entity_category=EntityCategory.DIAGNOSTIC,
207  entity_registry_enabled_default=False,
208  state_class=SensorStateClass.MEASUREMENT,
209  native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
210  ),
211  (ENTITY_DESC_KEY_TEMPERATURE, UnitOfTemperature.CELSIUS): SensorEntityDescription(
212  key=ENTITY_DESC_KEY_TEMPERATURE,
213  device_class=SensorDeviceClass.TEMPERATURE,
214  state_class=SensorStateClass.MEASUREMENT,
215  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
216  ),
217  (
218  ENTITY_DESC_KEY_TEMPERATURE,
219  UnitOfTemperature.FAHRENHEIT,
221  key=ENTITY_DESC_KEY_TEMPERATURE,
222  device_class=SensorDeviceClass.TEMPERATURE,
223  state_class=SensorStateClass.MEASUREMENT,
224  native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
225  ),
226  (
227  ENTITY_DESC_KEY_TARGET_TEMPERATURE,
228  UnitOfTemperature.CELSIUS,
230  key=ENTITY_DESC_KEY_TARGET_TEMPERATURE,
231  device_class=SensorDeviceClass.TEMPERATURE,
232  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
233  ),
234  (
235  ENTITY_DESC_KEY_TARGET_TEMPERATURE,
236  UnitOfTemperature.FAHRENHEIT,
238  key=ENTITY_DESC_KEY_TARGET_TEMPERATURE,
239  device_class=SensorDeviceClass.TEMPERATURE,
240  native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
241  ),
242  (
243  ENTITY_DESC_KEY_ENERGY_PRODUCTION_TIME,
244  UnitOfTime.SECONDS,
246  key=ENTITY_DESC_KEY_ENERGY_PRODUCTION_TIME,
247  name="Energy production time",
248  device_class=SensorDeviceClass.DURATION,
249  native_unit_of_measurement=UnitOfTime.SECONDS,
250  ),
251  (ENTITY_DESC_KEY_ENERGY_PRODUCTION_TIME, UnitOfTime.HOURS): SensorEntityDescription(
252  key=ENTITY_DESC_KEY_ENERGY_PRODUCTION_TIME,
253  device_class=SensorDeviceClass.DURATION,
254  native_unit_of_measurement=UnitOfTime.HOURS,
255  ),
256  (
257  ENTITY_DESC_KEY_ENERGY_PRODUCTION_TODAY,
258  UnitOfEnergy.WATT_HOUR,
260  key=ENTITY_DESC_KEY_ENERGY_PRODUCTION_TODAY,
261  name="Energy production today",
262  device_class=SensorDeviceClass.ENERGY,
263  state_class=SensorStateClass.TOTAL_INCREASING,
264  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
265  ),
266  (
267  ENTITY_DESC_KEY_ENERGY_PRODUCTION_TOTAL,
268  UnitOfEnergy.WATT_HOUR,
270  key=ENTITY_DESC_KEY_ENERGY_PRODUCTION_TOTAL,
271  name="Energy production total",
272  device_class=SensorDeviceClass.ENERGY,
273  state_class=SensorStateClass.TOTAL_INCREASING,
274  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
275  ),
276  (
277  ENTITY_DESC_KEY_ENERGY_PRODUCTION_POWER,
278  UnitOfPower.WATT,
280  key=ENTITY_DESC_KEY_POWER,
281  name="Energy production power",
282  device_class=SensorDeviceClass.POWER,
283  state_class=SensorStateClass.MEASUREMENT,
284  native_unit_of_measurement=UnitOfPower.WATT,
285  ),
286 }
287 
288 # These descriptions are without device class.
289 ENTITY_DESCRIPTION_KEY_MAP = {
290  ENTITY_DESC_KEY_CO: SensorEntityDescription(
291  key=ENTITY_DESC_KEY_CO,
292  state_class=SensorStateClass.MEASUREMENT,
293  ),
294  ENTITY_DESC_KEY_ENERGY_MEASUREMENT: SensorEntityDescription(
295  key=ENTITY_DESC_KEY_ENERGY_MEASUREMENT,
296  state_class=SensorStateClass.MEASUREMENT,
297  ),
298  ENTITY_DESC_KEY_HUMIDITY: SensorEntityDescription(
299  key=ENTITY_DESC_KEY_HUMIDITY,
300  state_class=SensorStateClass.MEASUREMENT,
301  ),
302  ENTITY_DESC_KEY_ILLUMINANCE: SensorEntityDescription(
303  key=ENTITY_DESC_KEY_ILLUMINANCE,
304  state_class=SensorStateClass.MEASUREMENT,
305  ),
306  ENTITY_DESC_KEY_POWER_FACTOR: SensorEntityDescription(
307  key=ENTITY_DESC_KEY_POWER_FACTOR,
308  state_class=SensorStateClass.MEASUREMENT,
309  ),
310  ENTITY_DESC_KEY_SIGNAL_STRENGTH: SensorEntityDescription(
311  key=ENTITY_DESC_KEY_SIGNAL_STRENGTH,
312  entity_category=EntityCategory.DIAGNOSTIC,
313  entity_registry_enabled_default=False,
314  state_class=SensorStateClass.MEASUREMENT,
315  ),
316  ENTITY_DESC_KEY_MEASUREMENT: SensorEntityDescription(
317  key=ENTITY_DESC_KEY_MEASUREMENT,
318  state_class=SensorStateClass.MEASUREMENT,
319  ),
320  ENTITY_DESC_KEY_TOTAL_INCREASING: SensorEntityDescription(
321  key=ENTITY_DESC_KEY_TOTAL_INCREASING,
322  state_class=SensorStateClass.TOTAL_INCREASING,
323  ),
324  ENTITY_DESC_KEY_UV_INDEX: SensorEntityDescription(
325  key=ENTITY_DESC_KEY_UV_INDEX,
326  state_class=SensorStateClass.MEASUREMENT,
327  native_unit_of_measurement=UV_INDEX,
328  ),
329 }
330 
331 
333  statistics: ControllerStatistics | NodeStatistics, key: str
334 ) -> Any:
335  """Convert a string that represents a nested attr to a value."""
336  data = statistics
337  for _key in key.split("."):
338  if data is None:
339  return None # type: ignore[unreachable]
340  data = getattr(data, _key)
341  return data
342 
343 
344 @dataclass(frozen=True, kw_only=True)
346  """Class to represent a Z-Wave JS statistics sensor entity description."""
347 
348  convert: Callable[[ControllerStatistics | NodeStatistics, str], Any] = getattr
349  entity_registry_enabled_default: bool = False
350 
351 
352 # Controller statistics descriptions
353 ENTITY_DESCRIPTION_CONTROLLER_STATISTICS_LIST = [
355  key="messages_tx",
356  translation_key="successful_messages",
357  translation_placeholders={"direction": "TX"},
358  state_class=SensorStateClass.TOTAL,
359  ),
361  key="messages_rx",
362  translation_key="successful_messages",
363  translation_placeholders={"direction": "RX"},
364  state_class=SensorStateClass.TOTAL,
365  ),
367  key="messages_dropped_tx",
368  translation_key="messages_dropped",
369  translation_placeholders={"direction": "TX"},
370  state_class=SensorStateClass.TOTAL,
371  ),
373  key="messages_dropped_rx",
374  translation_key="messages_dropped",
375  translation_placeholders={"direction": "RX"},
376  state_class=SensorStateClass.TOTAL,
377  ),
379  key="nak", translation_key="nak", state_class=SensorStateClass.TOTAL
380  ),
382  key="can", translation_key="can", state_class=SensorStateClass.TOTAL
383  ),
385  key="timeout_ack",
386  translation_key="timeout_ack",
387  state_class=SensorStateClass.TOTAL,
388  ),
390  key="timeout_response",
391  translation_key="timeout_response",
392  state_class=SensorStateClass.TOTAL,
393  ),
395  key="timeout_callback",
396  translation_key="timeout_callback",
397  state_class=SensorStateClass.TOTAL,
398  ),
400  key="background_rssi.channel_0.average",
401  translation_key="average_background_rssi",
402  translation_placeholders={"channel": "0"},
403  native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
404  device_class=SensorDeviceClass.SIGNAL_STRENGTH,
405  convert=convert_nested_attr,
406  ),
408  key="background_rssi.channel_0.current",
409  translation_key="current_background_rssi",
410  translation_placeholders={"channel": "0"},
411  native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
412  device_class=SensorDeviceClass.SIGNAL_STRENGTH,
413  state_class=SensorStateClass.MEASUREMENT,
414  convert=convert_nested_attr,
415  ),
417  key="background_rssi.channel_1.average",
418  translation_key="average_background_rssi",
419  translation_placeholders={"channel": "1"},
420  native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
421  device_class=SensorDeviceClass.SIGNAL_STRENGTH,
422  convert=convert_nested_attr,
423  ),
425  key="background_rssi.channel_1.current",
426  translation_key="current_background_rssi",
427  translation_placeholders={"channel": "1"},
428  native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
429  device_class=SensorDeviceClass.SIGNAL_STRENGTH,
430  state_class=SensorStateClass.MEASUREMENT,
431  convert=convert_nested_attr,
432  ),
434  key="background_rssi.channel_2.average",
435  translation_key="average_background_rssi",
436  translation_placeholders={"channel": "2"},
437  native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
438  device_class=SensorDeviceClass.SIGNAL_STRENGTH,
439  convert=convert_nested_attr,
440  ),
442  key="background_rssi.channel_2.current",
443  translation_key="current_background_rssi",
444  translation_placeholders={"channel": "2"},
445  native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
446  device_class=SensorDeviceClass.SIGNAL_STRENGTH,
447  state_class=SensorStateClass.MEASUREMENT,
448  convert=convert_nested_attr,
449  ),
450 ]
451 
452 CONTROLLER_STATISTICS_KEY_MAP: dict[str, str] = {
453  "messages_tx": "messagesTX",
454  "messages_rx": "messagesRX",
455  "messages_dropped_tx": "messagesDroppedTX",
456  "messages_dropped_rx": "messagesDroppedRX",
457  "nak": "NAK",
458  "can": "CAN",
459  "timeout_ack": "timeoutAck",
460  "timeout_response": "timeoutResponse",
461  "timeout_callback": "timeoutCallback",
462  "background_rssi.channel_0.average": "backgroundRSSI.channel0.average",
463  "background_rssi.channel_0.current": "backgroundRSSI.channel0.current",
464  "background_rssi.channel_1.average": "backgroundRSSI.channel1.average",
465  "background_rssi.channel_1.current": "backgroundRSSI.channel1.current",
466  "background_rssi.channel_2.average": "backgroundRSSI.channel2.average",
467  "background_rssi.channel_2.current": "backgroundRSSI.channel2.current",
468 }
469 
470 # Node statistics descriptions
471 ENTITY_DESCRIPTION_NODE_STATISTICS_LIST = [
473  key="commands_rx",
474  translation_key="successful_commands",
475  translation_placeholders={"direction": "RX"},
476  state_class=SensorStateClass.TOTAL,
477  ),
479  key="commands_tx",
480  translation_key="successful_commands",
481  translation_placeholders={"direction": "TX"},
482  state_class=SensorStateClass.TOTAL,
483  ),
485  key="commands_dropped_rx",
486  translation_key="commands_dropped",
487  translation_placeholders={"direction": "RX"},
488  state_class=SensorStateClass.TOTAL,
489  ),
491  key="commands_dropped_tx",
492  translation_key="commands_dropped",
493  translation_placeholders={"direction": "TX"},
494  state_class=SensorStateClass.TOTAL,
495  ),
497  key="timeout_response",
498  translation_key="timeout_response",
499  state_class=SensorStateClass.TOTAL,
500  ),
502  key="rtt",
503  translation_key="rtt",
504  native_unit_of_measurement=UnitOfTime.MILLISECONDS,
505  device_class=SensorDeviceClass.DURATION,
506  state_class=SensorStateClass.MEASUREMENT,
507  ),
509  key="rssi",
510  translation_key="rssi",
511  native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
512  device_class=SensorDeviceClass.SIGNAL_STRENGTH,
513  state_class=SensorStateClass.MEASUREMENT,
514  ),
516  key="last_seen",
517  translation_key="last_seen",
518  device_class=SensorDeviceClass.TIMESTAMP,
519  entity_registry_enabled_default=True,
520  ),
521 ]
522 
523 NODE_STATISTICS_KEY_MAP: dict[str, str] = {
524  "commands_rx": "commandsRX",
525  "commands_tx": "commandsTX",
526  "commands_dropped_rx": "commandsDroppedRX",
527  "commands_dropped_tx": "commandsDroppedTX",
528  "timeout_response": "timeoutResponse",
529  "rtt": "rtt",
530  "rssi": "rssi",
531  "last_seen": "lastSeen",
532 }
533 
534 
536  data: NumericSensorDataTemplateData,
537 ) -> SensorEntityDescription:
538  """Return the entity description for the given data."""
539  data_description_key = data.entity_description_key or ""
540  data_unit = data.unit_of_measurement or ""
541  return ENTITY_DESCRIPTION_KEY_DEVICE_CLASS_MAP.get(
542  (data_description_key, data_unit),
543  ENTITY_DESCRIPTION_KEY_MAP.get(
544  data_description_key,
546  key="base_sensor", native_unit_of_measurement=data.unit_of_measurement
547  ),
548  ),
549  )
550 
551 
553  hass: HomeAssistant,
554  config_entry: ConfigEntry,
555  async_add_entities: AddEntitiesCallback,
556 ) -> None:
557  """Set up Z-Wave sensor from config entry."""
558  client: ZwaveClient = config_entry.runtime_data[DATA_CLIENT]
559  driver = client.driver
560  assert driver is not None # Driver is ready before platforms are loaded.
561 
562  @callback
563  def async_add_sensor(info: ZwaveDiscoveryInfo) -> None:
564  """Add Z-Wave Sensor."""
565  entities: list[ZWaveBaseEntity] = []
566 
567  if info.platform_data:
568  data: NumericSensorDataTemplateData = info.platform_data
569  else:
571 
572  entity_description = get_entity_description(data)
573 
574  if info.platform_hint == "numeric_sensor":
575  entities.append(
577  config_entry,
578  driver,
579  info,
580  entity_description,
581  data.unit_of_measurement,
582  )
583  )
584  elif info.platform_hint == "notification":
585  # prevent duplicate entities for values that are already represented as binary sensors
587  return
588  entities.append(
589  ZWaveListSensor(config_entry, driver, info, entity_description)
590  )
591  elif info.platform_hint == "config_parameter":
592  entities.append(
594  config_entry, driver, info, entity_description
595  )
596  )
597  elif info.platform_hint == "meter":
598  entities.append(
599  ZWaveMeterSensor(config_entry, driver, info, entity_description)
600  )
601  else:
602  entities.append(ZwaveSensor(config_entry, driver, info, entity_description))
603 
604  async_add_entities(entities)
605 
606  @callback
607  def async_add_controller_status_sensor() -> None:
608  """Add controller status sensor."""
609  async_add_entities([ZWaveControllerStatusSensor(config_entry, driver)])
610 
611  @callback
612  def async_add_node_status_sensor(node: ZwaveNode) -> None:
613  """Add node status sensor."""
614  async_add_entities([ZWaveNodeStatusSensor(config_entry, driver, node)])
615 
616  @callback
617  def async_add_statistics_sensors(node: ZwaveNode) -> None:
618  """Add statistics sensors."""
620  hass,
621  driver,
622  node,
623  CONTROLLER_STATISTICS_KEY_MAP
624  if driver.controller.own_node == node
625  else NODE_STATISTICS_KEY_MAP,
626  )
628  [
630  config_entry,
631  driver,
632  driver.controller if driver.controller.own_node == node else node,
633  entity_description,
634  )
635  for entity_description in (
636  ENTITY_DESCRIPTION_CONTROLLER_STATISTICS_LIST
637  if driver.controller.own_node == node
638  else ENTITY_DESCRIPTION_NODE_STATISTICS_LIST
639  )
640  ]
641  )
642 
643  config_entry.async_on_unload(
645  hass,
646  f"{DOMAIN}_{config_entry.entry_id}_add_{SENSOR_DOMAIN}",
647  async_add_sensor,
648  )
649  )
650 
651  config_entry.async_on_unload(
653  hass,
654  f"{DOMAIN}_{config_entry.entry_id}_add_controller_status_sensor",
655  async_add_controller_status_sensor,
656  )
657  )
658 
659  config_entry.async_on_unload(
661  hass,
662  f"{DOMAIN}_{config_entry.entry_id}_add_node_status_sensor",
663  async_add_node_status_sensor,
664  )
665  )
666 
667  config_entry.async_on_unload(
669  hass,
670  f"{DOMAIN}_{config_entry.entry_id}_add_statistics_sensors",
671  async_add_statistics_sensors,
672  )
673  )
674 
675  platform = entity_platform.async_get_current_platform()
676  platform.async_register_entity_service(
677  SERVICE_RESET_METER,
678  {
679  vol.Optional(ATTR_METER_TYPE): vol.Coerce(int),
680  vol.Optional(ATTR_VALUE): vol.Coerce(int),
681  },
682  "async_reset_meter",
683  )
684 
685 
687  """Basic Representation of a Z-Wave sensor."""
688 
689  def __init__(
690  self,
691  config_entry: ConfigEntry,
692  driver: Driver,
693  info: ZwaveDiscoveryInfo,
694  entity_description: SensorEntityDescription,
695  unit_of_measurement: str | None = None,
696  ) -> None:
697  """Initialize a ZWaveSensorBase entity."""
698  self.entity_descriptionentity_description = entity_description
699  super().__init__(config_entry, driver, info)
700  self._attr_native_unit_of_measurement_attr_native_unit_of_measurement = unit_of_measurement
701 
702  # Entity class attributes
703  self._attr_force_update_attr_force_update = True
704  if not entity_description.name or entity_description.name is UNDEFINED:
705  self._attr_name_attr_name_attr_name = self.generate_namegenerate_name(include_value_name=True)
706 
707  @property
708  def native_value(self) -> StateType:
709  """Return state of the sensor."""
710  key = str(self.infoinfo.primary_value.value)
711  if key not in self.infoinfo.primary_value.metadata.states:
712  return self.infoinfo.primary_value.value
713  return str(self.infoinfo.primary_value.metadata.states[key])
714 
715  @property
716  def native_unit_of_measurement(self) -> str | None:
717  """Return unit of measurement the value is expressed in."""
718  if (unit := super().native_unit_of_measurement) is not None:
719  return unit
720  if self.infoinfo.primary_value.metadata.unit is None:
721  return None
722  return str(self.infoinfo.primary_value.metadata.unit)
723 
724 
726  """Representation of a Z-Wave Numeric sensor."""
727 
728  def __init__(
729  self,
730  config_entry: ConfigEntry,
731  driver: Driver,
732  info: ZwaveDiscoveryInfo,
733  entity_description: SensorEntityDescription,
734  unit_of_measurement: str | None = None,
735  ) -> None:
736  """Initialize a ZWaveBasicSensor entity."""
737  super().__init__(
738  config_entry, driver, info, entity_description, unit_of_measurement
739  )
740  if self.infoinfo.primary_value.command_class == CommandClass.BASIC:
741  self._attr_name_attr_name_attr_name_attr_name = self.generate_namegenerate_name(
742  include_value_name=True, alternate_value_name="Basic"
743  )
744 
745  @callback
746  def on_value_update(self) -> None:
747  """Handle scale changes for this value on value updated event."""
748  data = NumericSensorDataTemplate().resolve_data(self.infoinfo.primary_value)
750  self._attr_native_unit_of_measurement_attr_native_unit_of_measurement_attr_native_unit_of_measurement = data.unit_of_measurement
751 
752  @property
753  def native_value(self) -> float:
754  """Return state of the sensor."""
755  if self.infoinfo.primary_value.value is None:
756  return 0
757  return float(self.infoinfo.primary_value.value)
758 
759 
761  """Representation of a Z-Wave Meter CC sensor."""
762 
763  @property
764  def extra_state_attributes(self) -> Mapping[str, int | str] | None:
765  """Return extra state attributes."""
766  meter_type = get_meter_type(self.infoinfo.primary_value)
767  return {
768  ATTR_METER_TYPE: meter_type.value,
769  ATTR_METER_TYPE_NAME: meter_type.name,
770  }
771 
772  async def async_reset_meter(
773  self, meter_type: int | None = None, value: int | None = None
774  ) -> None:
775  """Reset meter(s) on device."""
776  node = self.infoinfo.node
777  endpoint = self.infoinfo.primary_value.endpoint or 0
778  options = {}
779  if meter_type is not None:
780  options[RESET_METER_OPTION_TYPE] = meter_type
781  if value is not None:
782  options[RESET_METER_OPTION_TARGET_VALUE] = value
783  args = [options] if options else []
784  try:
785  await node.endpoints[endpoint].async_invoke_cc_api(
786  CommandClass.METER, "reset", *args, wait_for_result=False
787  )
788  except BaseZwaveJSServerError as err:
789  raise HomeAssistantError(
790  f"Failed to reset meters on node {node} endpoint {endpoint}: {err}"
791  ) from err
792  LOGGER.debug(
793  "Meters on node %s endpoint %s reset with the following options: %s",
794  node,
795  endpoint,
796  options,
797  )
798 
799 
801  """Representation of a Z-Wave Numeric sensor with multiple states."""
802 
803  def __init__(
804  self,
805  config_entry: ConfigEntry,
806  driver: Driver,
807  info: ZwaveDiscoveryInfo,
808  entity_description: SensorEntityDescription,
809  unit_of_measurement: str | None = None,
810  ) -> None:
811  """Initialize a ZWaveListSensor entity."""
812  super().__init__(
813  config_entry, driver, info, entity_description, unit_of_measurement
814  )
815 
816  # Entity class attributes
817  # Notification sensors have the following name mapping (variables are property
818  # keys, name is property)
819  # https://github.com/zwave-js/node-zwave-js/blob/master/packages/config/config/notifications.json
820  self._attr_name_attr_name_attr_name_attr_name = self.generate_namegenerate_name(
821  alternate_value_name=self.infoinfo.primary_value.property_name,
822  additional_info=[self.infoinfo.primary_value.property_key_name],
823  )
824  if self.infoinfo.primary_value.metadata.states:
825  self._attr_device_class_attr_device_class = SensorDeviceClass.ENUM
826  self._attr_options_attr_options = list(info.primary_value.metadata.states.values())
827 
828  @property
829  def extra_state_attributes(self) -> dict[str, str] | None:
830  """Return the device specific state attributes."""
831  if (value := self.infoinfo.primary_value.value) is None:
832  return None
833  # add the value's int value as property for multi-value (list) items
834  return {ATTR_VALUE: value}
835 
836 
838  """Representation of a Z-Wave config parameter sensor."""
839 
840  _attr_entity_category = EntityCategory.DIAGNOSTIC
841 
842  def __init__(
843  self,
844  config_entry: ConfigEntry,
845  driver: Driver,
846  info: ZwaveDiscoveryInfo,
847  entity_description: SensorEntityDescription,
848  unit_of_measurement: str | None = None,
849  ) -> None:
850  """Initialize a ZWaveConfigParameterSensor entity."""
851  super().__init__(
852  config_entry, driver, info, entity_description, unit_of_measurement
853  )
854 
855  property_key_name = self.infoinfo.primary_value.property_key_name
856  # Entity class attributes
857  self._attr_name_attr_name_attr_name_attr_name_attr_name = self.generate_namegenerate_name(
858  alternate_value_name=self.infoinfo.primary_value.property_name,
859  additional_info=[property_key_name] if property_key_name else None,
860  )
861 
862  @property
863  def extra_state_attributes(self) -> dict[str, str] | None:
864  """Return the device specific state attributes."""
865  if (value := self.infoinfo.primary_value.value) is None:
866  return None
867  # add the value's int value as property for multi-value (list) items
868  return {ATTR_VALUE: value}
869 
870 
872  """Representation of a node status sensor."""
873 
874  _attr_should_poll = False
875  _attr_entity_category = EntityCategory.DIAGNOSTIC
876  _attr_has_entity_name = True
877  _attr_translation_key = "node_status"
878 
879  def __init__(
880  self, config_entry: ConfigEntry, driver: Driver, node: ZwaveNode
881  ) -> None:
882  """Initialize a generic Z-Wave device entity."""
883  self.config_entryconfig_entry = config_entry
884  self.nodenode = node
885 
886  # Entity class attributes
887  self._base_unique_id_base_unique_id = get_valueless_base_unique_id(driver, node)
888  self._attr_unique_id_attr_unique_id = f"{self._base_unique_id}.node_status"
889  # device may not be precreated in main handler yet
890  self._attr_device_info_attr_device_info = get_device_info(driver, node)
891 
892  async def async_poll_value(self, _: bool) -> None:
893  """Poll a value."""
894  # We log an error instead of raising an exception because this service call occurs
895  # in a separate task since it is called via the dispatcher and we don't want to
896  # raise the exception in that separate task because it is confusing to the user.
897  LOGGER.error(
898  "There is no value to refresh for this entity so the zwave_js.refresh_value"
899  " service won't work for it"
900  )
901 
902  @callback
903  def _status_changed(self, _: dict) -> None:
904  """Call when status event is received."""
905  self._attr_native_value_attr_native_value = self.nodenode.status.name.lower()
906  self.async_write_ha_stateasync_write_ha_state()
907 
908  async def async_added_to_hass(self) -> None:
909  """Call when entity is added."""
910  # Add value_changed callbacks.
911  for evt in ("wake up", "sleep", "dead", "alive"):
912  self.async_on_removeasync_on_remove(self.nodenode.on(evt, self._status_changed_status_changed))
913  self.async_on_removeasync_on_remove(
915  self.hasshass,
916  f"{DOMAIN}_{self.unique_id}_poll_value",
917  self.async_poll_valueasync_poll_value,
918  )
919  )
920  # we don't listen for `remove_entity_on_ready_node` signal because this entity
921  # is created when the node is added which occurs before ready. It only needs to
922  # be removed if the node is removed from the network.
923  self.async_on_removeasync_on_remove(
925  self.hasshass,
926  f"{DOMAIN}_{self._base_unique_id}_remove_entity",
927  self.async_removeasync_remove,
928  )
929  )
930  self._attr_native_value_attr_native_value: str = self.nodenode.status.name.lower()
931  self.async_write_ha_stateasync_write_ha_state()
932 
933 
935  """Representation of a controller status sensor."""
936 
937  _attr_should_poll = False
938  _attr_entity_category = EntityCategory.DIAGNOSTIC
939  _attr_has_entity_name = True
940  _attr_translation_key = "controller_status"
941 
942  def __init__(self, config_entry: ConfigEntry, driver: Driver) -> None:
943  """Initialize a generic Z-Wave device entity."""
944  self.config_entryconfig_entry = config_entry
945  self.controllercontroller = driver.controller
946  node = self.controllercontroller.own_node
947  assert node
948 
949  # Entity class attributes
950  self._base_unique_id_base_unique_id = get_valueless_base_unique_id(driver, node)
951  self._attr_unique_id_attr_unique_id = f"{self._base_unique_id}.controller_status"
952  # device may not be precreated in main handler yet
953  self._attr_device_info_attr_device_info = get_device_info(driver, node)
954 
955  async def async_poll_value(self, _: bool) -> None:
956  """Poll a value."""
957  # We log an error instead of raising an exception because this service call occurs
958  # in a separate task since it is called via the dispatcher and we don't want to
959  # raise the exception in that separate task because it is confusing to the user.
960  LOGGER.error(
961  "There is no value to refresh for this entity so the zwave_js.refresh_value"
962  " service won't work for it"
963  )
964 
965  @callback
966  def _status_changed(self, _: dict) -> None:
967  """Call when status event is received."""
968  self._attr_native_value_attr_native_value = self.controllercontroller.status.name.lower()
969  self.async_write_ha_stateasync_write_ha_state()
970 
971  async def async_added_to_hass(self) -> None:
972  """Call when entity is added."""
973  # Add value_changed callbacks.
974  self.async_on_removeasync_on_remove(self.controllercontroller.on("status changed", self._status_changed_status_changed))
975  self.async_on_removeasync_on_remove(
977  self.hasshass,
978  f"{DOMAIN}_{self.unique_id}_poll_value",
979  self.async_poll_valueasync_poll_value,
980  )
981  )
982  # we don't listen for `remove_entity_on_ready_node` signal because this is not
983  # a regular node
984  self.async_on_removeasync_on_remove(
986  self.hasshass,
987  f"{DOMAIN}_{self._base_unique_id}_remove_entity",
988  self.async_removeasync_remove,
989  )
990  )
991  self._attr_native_value_attr_native_value: str = self.controllercontroller.status.name.lower()
992 
993 
995  """Representation of a node/controller statistics sensor."""
996 
997  entity_description: ZWaveJSStatisticsSensorEntityDescription
998  _attr_should_poll = False
999  _attr_entity_category = EntityCategory.DIAGNOSTIC
1000  _attr_has_entity_name = True
1001 
1003  self,
1004  config_entry: ConfigEntry,
1005  driver: Driver,
1006  statistics_src: ZwaveNode | Controller,
1007  description: ZWaveJSStatisticsSensorEntityDescription,
1008  ) -> None:
1009  """Initialize a Z-Wave statistics entity."""
1010  self.entity_descriptionentity_description = description
1011  self.config_entryconfig_entry = config_entry
1012  self.statistics_srcstatistics_src = statistics_src
1013  node = (
1014  statistics_src.own_node
1015  if isinstance(statistics_src, Controller)
1016  else statistics_src
1017  )
1018  assert node
1019 
1020  # Entity class attributes
1021  self._base_unique_id_base_unique_id = get_valueless_base_unique_id(driver, node)
1022  self._attr_unique_id_attr_unique_id = f"{self._base_unique_id}.statistics_{description.key}"
1023  # device may not be precreated in main handler yet
1024  self._attr_device_info_attr_device_info = get_device_info(driver, node)
1025 
1026  async def async_poll_value(self, _: bool) -> None:
1027  """Poll a value."""
1028  # We log an error instead of raising an exception because this service call occurs
1029  # in a separate task since it is called via the dispatcher and we don't want to
1030  # raise the exception in that separate task because it is confusing to the user.
1031  LOGGER.error(
1032  "There is no value to refresh for this entity so the zwave_js.refresh_value"
1033  " service won't work for it"
1034  )
1035 
1036  @callback
1037  def statistics_updated(self, event_data: dict) -> None:
1038  """Call when statistics updated event is received."""
1039  self._attr_native_value_attr_native_value = self.entity_descriptionentity_description.convert(
1040  event_data["statistics_updated"], self.entity_descriptionentity_description.key
1041  )
1042  self.async_write_ha_stateasync_write_ha_state()
1043 
1044  async def async_added_to_hass(self) -> None:
1045  """Call when entity is added."""
1046  self.async_on_removeasync_on_remove(
1048  self.hasshass,
1049  f"{DOMAIN}_{self.unique_id}_poll_value",
1050  self.async_poll_valueasync_poll_value,
1051  )
1052  )
1053  self.async_on_removeasync_on_remove(
1055  self.hasshass,
1056  f"{DOMAIN}_{self._base_unique_id}_remove_entity",
1057  self.async_removeasync_remove,
1058  )
1059  )
1060  self.async_on_removeasync_on_remove(
1061  self.statistics_srcstatistics_src.on("statistics updated", self.statistics_updatedstatistics_updated)
1062  )
1063 
1064  # Set initial state
1065  self._attr_native_value_attr_native_value = self.entity_descriptionentity_description.convert(
1066  self.statistics_srcstatistics_src.statistics, self.entity_descriptionentity_description.key
1067  )
str generate_name(self, bool include_value_name=False, str|None alternate_value_name=None, Sequence[str|None]|None additional_info=None, str|None name_prefix=None)
Definition: entity.py:163
None __init__(self, ConfigEntry config_entry, Driver driver, ZwaveDiscoveryInfo info, SensorEntityDescription entity_description, str|None unit_of_measurement=None)
Definition: sensor.py:849
None __init__(self, ConfigEntry config_entry, Driver driver)
Definition: sensor.py:942
None __init__(self, ConfigEntry config_entry, Driver driver, ZwaveDiscoveryInfo info, SensorEntityDescription entity_description, str|None unit_of_measurement=None)
Definition: sensor.py:810
Mapping[str, int|str]|None extra_state_attributes(self)
Definition: sensor.py:764
None async_reset_meter(self, int|None meter_type=None, int|None value=None)
Definition: sensor.py:774
None __init__(self, ConfigEntry config_entry, Driver driver, ZwaveNode node)
Definition: sensor.py:881
None __init__(self, ConfigEntry config_entry, Driver driver, ZwaveDiscoveryInfo info, SensorEntityDescription entity_description, str|None unit_of_measurement=None)
Definition: sensor.py:735
None __init__(self, ConfigEntry config_entry, Driver driver, ZwaveNode|Controller statistics_src, ZWaveJSStatisticsSensorEntityDescription description)
Definition: sensor.py:1008
None __init__(self, ConfigEntry config_entry, Driver driver, ZwaveDiscoveryInfo info, SensorEntityDescription entity_description, str|None unit_of_measurement=None)
Definition: sensor.py:696
None async_on_remove(self, CALLBACK_TYPE func)
Definition: entity.py:1331
None async_remove(self, *bool force_remove=False)
Definition: entity.py:1387
DeviceInfo get_device_info(str coordinates, str name)
Definition: __init__.py:156
bool|NotificationZWaveJSEntityDescription is_valid_notification_binary_sensor(ZwaveDiscoveryInfo info)
str get_valueless_base_unique_id(Driver driver, ZwaveNode node)
Definition: helpers.py:202
None async_migrate_statistics_sensors(HomeAssistant hass, Driver driver, Node node, dict[str, str] key_map)
Definition: migrate.py:218
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:556
Any convert_nested_attr(ControllerStatistics|NodeStatistics statistics, str key)
Definition: sensor.py:334
SensorEntityDescription get_entity_description(NumericSensorDataTemplateData data)
Definition: sensor.py:537
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103