Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for the sensors in a GreenEye Monitor."""
2 
3 from __future__ import annotations
4 
5 from typing import Any
6 
7 import greeneye
8 
9 from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
10 from homeassistant.const import (
11  CONF_NAME,
12  CONF_SENSORS,
13  CONF_TEMPERATURE_UNIT,
14  UnitOfElectricPotential,
15  UnitOfPower,
16  UnitOfTime,
17 )
18 from homeassistant.core import HomeAssistant
19 from homeassistant.helpers.entity_platform import AddEntitiesCallback
20 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
21 
22 from .const import (
23  CONF_CHANNELS,
24  CONF_COUNTED_QUANTITY,
25  CONF_COUNTED_QUANTITY_PER_PULSE,
26  CONF_MONITORS,
27  CONF_NET_METERING,
28  CONF_NUMBER,
29  CONF_PULSE_COUNTERS,
30  CONF_SERIAL_NUMBER,
31  CONF_TEMPERATURE_SENSORS,
32  CONF_TIME_UNIT,
33  CONF_VOLTAGE_SENSORS,
34  DATA_GREENEYE_MONITOR,
35 )
36 
37 DATA_PULSES = "pulses"
38 DATA_WATT_SECONDS = "watt_seconds"
39 
40 COUNTER_ICON = "mdi:counter"
41 
42 
44  hass: HomeAssistant,
45  config: ConfigType,
46  async_add_entities: AddEntitiesCallback,
47  discovery_info: DiscoveryInfoType | None = None,
48 ) -> None:
49  """Set up a single GEM temperature sensor."""
50  if not discovery_info:
51  return
52 
53  monitor_configs = discovery_info[CONF_MONITORS]
54 
55  def on_new_monitor(monitor: greeneye.monitor.Monitor) -> None:
56  monitor_config = next(
57  filter(
58  lambda monitor_config: monitor_config[CONF_SERIAL_NUMBER]
59  == monitor.serial_number,
60  monitor_configs,
61  ),
62  None,
63  )
64  if monitor_config:
65  channel_configs = monitor_config[CONF_CHANNELS]
66  entities: list[GEMSensor] = [
68  monitor,
69  sensor[CONF_NUMBER],
70  sensor[CONF_NAME],
71  sensor[CONF_NET_METERING],
72  )
73  for sensor in channel_configs
74  ]
75 
76  pulse_counter_configs = monitor_config[CONF_PULSE_COUNTERS]
77  entities.extend(
79  monitor,
80  sensor[CONF_NUMBER],
81  sensor[CONF_NAME],
82  sensor[CONF_COUNTED_QUANTITY],
83  sensor[CONF_TIME_UNIT],
84  sensor[CONF_COUNTED_QUANTITY_PER_PULSE],
85  )
86  for sensor in pulse_counter_configs
87  )
88 
89  temperature_sensor_configs = monitor_config[CONF_TEMPERATURE_SENSORS]
90  entities.extend(
92  monitor,
93  sensor[CONF_NUMBER],
94  sensor[CONF_NAME],
95  temperature_sensor_configs[CONF_TEMPERATURE_UNIT],
96  )
97  for sensor in temperature_sensor_configs[CONF_SENSORS]
98  )
99 
100  voltage_sensor_configs = monitor_config[CONF_VOLTAGE_SENSORS]
101  entities.extend(
102  VoltageSensor(monitor, sensor[CONF_NUMBER], sensor[CONF_NAME])
103  for sensor in voltage_sensor_configs
104  )
105 
106  async_add_entities(entities)
107  monitor_configs.remove(monitor_config)
108 
109  if len(monitor_configs) == 0:
110  monitors.remove_listener(on_new_monitor)
111 
112  monitors: greeneye.Monitors = hass.data[DATA_GREENEYE_MONITOR]
113  monitors.add_listener(on_new_monitor)
114  for monitor in monitors.monitors.values():
115  on_new_monitor(monitor)
116 
117 
118 type UnderlyingSensorType = (
119  greeneye.monitor.Channel
120  | greeneye.monitor.PulseCounter
121  | greeneye.monitor.TemperatureSensor
122  | greeneye.monitor.VoltageSensor
123 )
124 
125 
127  """Base class for GreenEye Monitor sensors."""
128 
129  _attr_should_poll = False
130 
131  def __init__(
132  self,
133  monitor: greeneye.monitor.Monitor,
134  name: str,
135  sensor_type: str,
136  sensor: UnderlyingSensorType,
137  number: int,
138  ) -> None:
139  """Construct the entity."""
140  self._monitor_monitor = monitor
141  self._monitor_serial_number_monitor_serial_number = self._monitor_monitor.serial_number
142  self._attr_name_attr_name = name
143  self._sensor_type_sensor_type = sensor_type
144  self._sensor: UnderlyingSensorType = sensor
145  self._number_number = number
146  self._attr_unique_id_attr_unique_id = (
147  f"{self._monitor_serial_number}-{self._sensor_type}-{self._number}"
148  )
149 
150  async def async_added_to_hass(self) -> None:
151  """Wait for and connect to the sensor."""
152  self._sensor.add_listener(self.async_write_ha_stateasync_write_ha_state)
153 
154  async def async_will_remove_from_hass(self) -> None:
155  """Remove listener from the sensor."""
156  if self._sensor:
157  self._sensor.remove_listener(self.async_write_ha_stateasync_write_ha_state)
158 
159 
161  """Entity showing power usage on one channel of the monitor."""
162 
163  _attr_native_unit_of_measurement = UnitOfPower.WATT
164  _attr_device_class = SensorDeviceClass.POWER
165 
166  def __init__(
167  self,
168  monitor: greeneye.monitor.Monitor,
169  number: int,
170  name: str,
171  net_metering: bool,
172  ) -> None:
173  """Construct the entity."""
174  super().__init__(monitor, name, "current", monitor.channels[number - 1], number)
175  self._sensor: greeneye.monitor.Channel = self._sensor
176  self._net_metering_net_metering = net_metering
177 
178  @property
179  def native_value(self) -> float | None:
180  """Return the current number of watts being used by the channel."""
181  return self._sensor.watts
182 
183  @property
184  def extra_state_attributes(self) -> dict[str, Any] | None:
185  """Return total wattseconds in the state dictionary."""
186  if self._net_metering_net_metering:
187  watt_seconds = self._sensor.polarized_watt_seconds
188  else:
189  watt_seconds = self._sensor.absolute_watt_seconds
190 
191  return {DATA_WATT_SECONDS: watt_seconds}
192 
193 
195  """Entity showing rate of change in one pulse counter of the monitor."""
196 
197  _attr_icon = COUNTER_ICON
198 
199  def __init__(
200  self,
201  monitor: greeneye.monitor.Monitor,
202  number: int,
203  name: str,
204  counted_quantity: str,
205  time_unit: str,
206  counted_quantity_per_pulse: float,
207  ) -> None:
208  """Construct the entity."""
209  super().__init__(
210  monitor, name, "pulse", monitor.pulse_counters[number - 1], number
211  )
212  self._sensor: greeneye.monitor.PulseCounter = self._sensor
213  self._counted_quantity_per_pulse_counted_quantity_per_pulse = counted_quantity_per_pulse
214  self._time_unit_time_unit = time_unit
215  self._attr_native_unit_of_measurement_attr_native_unit_of_measurement = f"{counted_quantity}/{self._time_unit}"
216 
217  @property
218  def native_value(self) -> float | None:
219  """Return the current rate of change for the given pulse counter."""
220  if self._sensor.pulses_per_second is None:
221  return None
222 
223  return (
224  self._sensor.pulses_per_second
225  * self._counted_quantity_per_pulse_counted_quantity_per_pulse
226  * self._seconds_per_time_unit_seconds_per_time_unit
227  )
228 
229  @property
230  def _seconds_per_time_unit(self) -> int:
231  """Return the number of seconds in the given display time unit."""
232  if self._time_unit_time_unit == UnitOfTime.SECONDS:
233  return 1
234  if self._time_unit_time_unit == UnitOfTime.MINUTES:
235  return 60
236  if self._time_unit_time_unit == UnitOfTime.HOURS:
237  return 3600
238 
239  # Config schema should have ensured it is one of the above values
240  raise RuntimeError(
241  f"Invalid value for time unit: {self._time_unit}. Expected one of"
242  f" {UnitOfTime.SECONDS}, {UnitOfTime.MINUTES}, or {UnitOfTime.HOURS}"
243  )
244 
245  @property
246  def extra_state_attributes(self) -> dict[str, Any]:
247  """Return total pulses in the data dictionary."""
248  return {DATA_PULSES: self._sensor.pulses}
249 
250 
252  """Entity showing temperature from one temperature sensor."""
253 
254  _attr_device_class = SensorDeviceClass.TEMPERATURE
255 
256  def __init__(
257  self, monitor: greeneye.monitor.Monitor, number: int, name: str, unit: str
258  ) -> None:
259  """Construct the entity."""
260  super().__init__(
261  monitor, name, "temp", monitor.temperature_sensors[number - 1], number
262  )
263  self._sensor: greeneye.monitor.TemperatureSensor = self._sensor
264  self._attr_native_unit_of_measurement_attr_native_unit_of_measurement = unit
265 
266  @property
267  def native_value(self) -> float | None:
268  """Return the current temperature being reported by this sensor."""
269  return self._sensor.temperature
270 
271 
273  """Entity showing voltage."""
274 
275  _attr_native_unit_of_measurement = UnitOfElectricPotential.VOLT
276  _attr_device_class = SensorDeviceClass.VOLTAGE
277 
278  def __init__(
279  self, monitor: greeneye.monitor.Monitor, number: int, name: str
280  ) -> None:
281  """Construct the entity."""
282  super().__init__(monitor, name, "volts", monitor.voltage_sensor, number)
283  self._sensor: greeneye.monitor.VoltageSensor = self._sensor
284 
285  @property
286  def native_value(self) -> float | None:
287  """Return the current voltage being reported by this sensor."""
288  return self._sensor.voltage
None __init__(self, greeneye.monitor.Monitor monitor, int number, str name, bool net_metering)
Definition: sensor.py:172
None __init__(self, greeneye.monitor.Monitor monitor, str name, str sensor_type, UnderlyingSensorType sensor, int number)
Definition: sensor.py:138
None __init__(self, greeneye.monitor.Monitor monitor, int number, str name, str counted_quantity, str time_unit, float counted_quantity_per_pulse)
Definition: sensor.py:207
None __init__(self, greeneye.monitor.Monitor monitor, int number, str name, str unit)
Definition: sensor.py:258
None __init__(self, greeneye.monitor.Monitor monitor, int number, str name)
Definition: sensor.py:280
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: sensor.py:48