Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for Modbus Register sensors."""
2 
3 from __future__ import annotations
4 
5 from datetime import datetime
6 import logging
7 from typing import Any
8 
10  CONF_STATE_CLASS,
11  RestoreSensor,
12  SensorEntity,
13 )
14 from homeassistant.const import (
15  CONF_DEVICE_CLASS,
16  CONF_NAME,
17  CONF_SENSORS,
18  CONF_UNIQUE_ID,
19  CONF_UNIT_OF_MEASUREMENT,
20 )
21 from homeassistant.core import HomeAssistant, callback
22 from homeassistant.helpers.entity_platform import AddEntitiesCallback
23 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
25  CoordinatorEntity,
26  DataUpdateCoordinator,
27 )
28 
29 from . import get_hub
30 from .const import CONF_SLAVE_COUNT, CONF_VIRTUAL_COUNT
31 from .entity import BaseStructPlatform
32 from .modbus import ModbusHub
33 
34 _LOGGER = logging.getLogger(__name__)
35 
36 PARALLEL_UPDATES = 1
37 
38 
40  hass: HomeAssistant,
41  config: ConfigType,
42  async_add_entities: AddEntitiesCallback,
43  discovery_info: DiscoveryInfoType | None = None,
44 ) -> None:
45  """Set up the Modbus sensors."""
46 
47  if discovery_info is None:
48  return
49 
50  sensors: list[ModbusRegisterSensor | SlaveSensor] = []
51  hub = get_hub(hass, discovery_info[CONF_NAME])
52  for entry in discovery_info[CONF_SENSORS]:
53  slave_count = entry.get(CONF_SLAVE_COUNT, None) or entry.get(
54  CONF_VIRTUAL_COUNT, 0
55  )
56  sensor = ModbusRegisterSensor(hass, hub, entry, slave_count)
57  if slave_count > 0:
58  sensors.extend(await sensor.async_setup_slaves(hass, slave_count, entry))
59  sensors.append(sensor)
60  async_add_entities(sensors)
61 
62 
64  """Modbus register sensor."""
65 
66  def __init__(
67  self,
68  hass: HomeAssistant,
69  hub: ModbusHub,
70  entry: dict[str, Any],
71  slave_count: int,
72  ) -> None:
73  """Initialize the modbus register sensor."""
74  super().__init__(hass, hub, entry)
75  if slave_count:
76  self._count_count_count = self._count_count_count * (slave_count + 1)
77  self._coordinator_coordinator: DataUpdateCoordinator[list[float] | None] | None = None
78  self._attr_native_unit_of_measurement_attr_native_unit_of_measurement = entry.get(CONF_UNIT_OF_MEASUREMENT)
79  self._attr_state_class_attr_state_class = entry.get(CONF_STATE_CLASS)
80  self._attr_device_class_attr_device_class_attr_device_class = entry.get(CONF_DEVICE_CLASS)
81 
82  async def async_setup_slaves(
83  self, hass: HomeAssistant, slave_count: int, entry: dict[str, Any]
84  ) -> list[SlaveSensor]:
85  """Add slaves as needed (1 read for multiple sensors)."""
86 
87  # Add a dataCoordinator for each sensor that have slaves
88  # this ensures that idx = bit position of value in result
89  # polling is done with the base class
90  name = self._attr_name_attr_name if self._attr_name_attr_name else "modbus_sensor"
91  self._coordinator_coordinator = DataUpdateCoordinator(
92  hass,
93  _LOGGER,
94  config_entry=None,
95  name=name,
96  )
97 
98  return [
99  SlaveSensor(self._coordinator_coordinator, idx, entry) for idx in range(slave_count)
100  ]
101 
102  async def async_added_to_hass(self) -> None:
103  """Handle entity which will be added."""
104  await self.async_base_added_to_hassasync_base_added_to_hass()
105  state = await self.async_get_last_sensor_dataasync_get_last_sensor_data()
106  if state:
107  self._attr_native_value_attr_native_value = state.native_value
108 
109  async def async_update(self, now: datetime | None = None) -> None:
110  """Update the state of the sensor."""
111  # remark "now" is a dummy parameter to avoid problems with
112  # async_track_time_interval
113  self._cancel_call_cancel_call_cancel_call = None
114  raw_result = await self._hub_hub.async_pb_call(
115  self._slave_slave, self._address_address, self._count_count_count, self._input_type_input_type
116  )
117  if raw_result is None:
118  self._attr_available_attr_available_attr_available = False
119  self._attr_native_value_attr_native_value = None
120  if self._coordinator_coordinator:
121  self._coordinator_coordinator.async_set_updated_data(None)
122  self.async_write_ha_stateasync_write_ha_state()
123  return
124 
125  result = self.unpack_structure_resultunpack_structure_result(raw_result.registers)
126  if self._coordinator_coordinator:
127  if result:
128  result_array = list(
129  map(
130  float if not self._value_is_int_value_is_int else int,
131  result.split(","),
132  )
133  )
134  self._attr_native_value_attr_native_value = result_array[0]
135  self._coordinator_coordinator.async_set_updated_data(result_array)
136  else:
137  self._attr_native_value_attr_native_value = None
138  self._coordinator_coordinator.async_set_updated_data(None)
139  else:
140  self._attr_native_value_attr_native_value = result
141  self._attr_available_attr_available_attr_available = self._attr_native_value_attr_native_value is not None
142  self.async_write_ha_stateasync_write_ha_state()
143 
144 
146  CoordinatorEntity[DataUpdateCoordinator[list[float] | None]],
147  RestoreSensor,
148  SensorEntity,
149 ):
150  """Modbus slave register sensor."""
151 
152  def __init__(
153  self,
154  coordinator: DataUpdateCoordinator[list[float] | None],
155  idx: int,
156  entry: dict[str, Any],
157  ) -> None:
158  """Initialize the Modbus register sensor."""
159  idx += 1
160  self._idx_idx = idx
161  self._attr_name_attr_name = f"{entry[CONF_NAME]} {idx}"
162  self._attr_unique_id_attr_unique_id = entry.get(CONF_UNIQUE_ID)
163  if self._attr_unique_id_attr_unique_id:
164  self._attr_unique_id_attr_unique_id = f"{self._attr_unique_id}_{idx}"
165  self._attr_native_unit_of_measurement_attr_native_unit_of_measurement = entry.get(CONF_UNIT_OF_MEASUREMENT)
166  self._attr_state_class_attr_state_class = entry.get(CONF_STATE_CLASS)
167  self._attr_device_class_attr_device_class = entry.get(CONF_DEVICE_CLASS)
168  self._attr_available_attr_available = False
169  super().__init__(coordinator)
170 
171  async def async_added_to_hass(self) -> None:
172  """Handle entity which will be added."""
173  if state := await self.async_get_last_state():
174  self._attr_native_value_attr_native_value = state.state
175  await super().async_added_to_hass()
176 
177  @callback
178  def _handle_coordinator_update(self) -> None:
179  """Handle updated data from the coordinator."""
180  result = self.coordinator.data
181  self._attr_native_value_attr_native_value = result[self._idx_idx] if result else None
str|None unpack_structure_result(self, list[int] registers)
Definition: entity.py:225
list[SlaveSensor] async_setup_slaves(self, HomeAssistant hass, int slave_count, dict[str, Any] entry)
Definition: sensor.py:84
None __init__(self, HomeAssistant hass, ModbusHub hub, dict[str, Any] entry, int slave_count)
Definition: sensor.py:72
None async_update(self, datetime|None now=None)
Definition: sensor.py:109
None __init__(self, DataUpdateCoordinator[list[float]|None] coordinator, int idx, dict[str, Any] entry)
Definition: sensor.py:157
SensorExtraStoredData|None async_get_last_sensor_data(self)
Definition: __init__.py:934
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: sensor.py:44
ModbusHub get_hub(HomeAssistant hass, str name)
Definition: __init__.py:445