Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for monitoring a Smappee energy sensor."""
2 
3 from __future__ import annotations
4 
5 from dataclasses import dataclass, field
6 
8  SensorDeviceClass,
9  SensorEntity,
10  SensorEntityDescription,
11  SensorStateClass,
12 )
13 from homeassistant.const import UnitOfElectricPotential, UnitOfEnergy, UnitOfPower
14 from homeassistant.core import HomeAssistant
15 from homeassistant.helpers.device_registry import DeviceInfo
16 from homeassistant.helpers.entity_platform import AddEntitiesCallback
17 
18 from . import SmappeeConfigEntry
19 from .const import DOMAIN
20 
21 
22 @dataclass(frozen=True, kw_only=True)
24  """Describes Smappee sensor entity."""
25 
26  sensor_id: str
27 
28 
29 @dataclass(frozen=True, kw_only=True)
31  """Describes Smappee sensor entity."""
32 
33  local_polling: bool = False
34 
35 
36 @dataclass(frozen=True, kw_only=True)
38  """Describes Smappee sensor entity."""
39 
40  phase_types: set[str] = field(default_factory=set)
41 
42 
43 TREND_SENSORS: tuple[SmappeePollingSensorEntityDescription, ...] = (
45  key="total_power",
46  name="Total consumption - Active power",
47  native_unit_of_measurement=UnitOfPower.WATT,
48  sensor_id="total_power",
49  device_class=SensorDeviceClass.POWER,
50  state_class=SensorStateClass.MEASUREMENT,
51  local_polling=True, # both cloud and local
52  ),
54  key="alwayson",
55  name="Always on - Active power",
56  native_unit_of_measurement=UnitOfPower.WATT,
57  sensor_id="alwayson",
58  device_class=SensorDeviceClass.POWER,
59  state_class=SensorStateClass.MEASUREMENT,
60  ),
62  key="power_today",
63  name="Total consumption - Today",
64  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
65  sensor_id="power_today",
66  device_class=SensorDeviceClass.ENERGY,
67  state_class=SensorStateClass.TOTAL_INCREASING,
68  ),
70  key="power_current_hour",
71  name="Total consumption - Current hour",
72  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
73  sensor_id="power_current_hour",
74  device_class=SensorDeviceClass.ENERGY,
75  state_class=SensorStateClass.TOTAL_INCREASING,
76  ),
78  key="power_last_5_minutes",
79  name="Total consumption - Last 5 minutes",
80  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
81  sensor_id="power_last_5_minutes",
82  device_class=SensorDeviceClass.ENERGY,
83  state_class=SensorStateClass.TOTAL_INCREASING,
84  ),
86  key="alwayson_today",
87  name="Always on - Today",
88  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
89  sensor_id="alwayson_today",
90  device_class=SensorDeviceClass.ENERGY,
91  state_class=SensorStateClass.TOTAL_INCREASING,
92  ),
93 )
94 REACTIVE_SENSORS: tuple[SmappeeSensorEntityDescription, ...] = (
96  key="total_reactive_power",
97  name="Total consumption - Reactive power",
98  native_unit_of_measurement=UnitOfPower.WATT,
99  sensor_id="total_reactive_power",
100  device_class=SensorDeviceClass.POWER,
101  state_class=SensorStateClass.MEASUREMENT,
102  ),
103 )
104 SOLAR_SENSORS: tuple[SmappeePollingSensorEntityDescription, ...] = (
106  key="solar_power",
107  name="Total production - Active power",
108  native_unit_of_measurement=UnitOfPower.WATT,
109  sensor_id="solar_power",
110  device_class=SensorDeviceClass.POWER,
111  state_class=SensorStateClass.MEASUREMENT,
112  local_polling=True, # both cloud and local
113  ),
115  key="solar_today",
116  name="Total production - Today",
117  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
118  sensor_id="solar_today",
119  device_class=SensorDeviceClass.ENERGY,
120  state_class=SensorStateClass.TOTAL_INCREASING,
121  ),
123  key="solar_current_hour",
124  name="Total production - Current hour",
125  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
126  sensor_id="solar_current_hour",
127  device_class=SensorDeviceClass.ENERGY,
128  state_class=SensorStateClass.TOTAL_INCREASING,
129  ),
130 )
131 VOLTAGE_SENSORS: tuple[SmappeeVoltageSensorEntityDescription, ...] = (
133  key="phase_voltages_a",
134  name="Phase voltages - A",
135  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
136  sensor_id="phase_voltage_a",
137  device_class=SensorDeviceClass.VOLTAGE,
138  state_class=SensorStateClass.MEASUREMENT,
139  phase_types={"ONE", "TWO", "THREE_STAR", "THREE_DELTA"},
140  ),
142  key="phase_voltages_b",
143  name="Phase voltages - B",
144  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
145  sensor_id="phase_voltage_b",
146  device_class=SensorDeviceClass.VOLTAGE,
147  state_class=SensorStateClass.MEASUREMENT,
148  phase_types={"TWO", "THREE_STAR", "THREE_DELTA"},
149  ),
151  key="phase_voltages_c",
152  name="Phase voltages - C",
153  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
154  sensor_id="phase_voltage_c",
155  device_class=SensorDeviceClass.VOLTAGE,
156  state_class=SensorStateClass.MEASUREMENT,
157  phase_types={"THREE_STAR"},
158  ),
160  key="line_voltages_a",
161  name="Line voltages - A",
162  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
163  sensor_id="line_voltage_a",
164  device_class=SensorDeviceClass.VOLTAGE,
165  state_class=SensorStateClass.MEASUREMENT,
166  phase_types={"ONE", "TWO", "THREE_STAR", "THREE_DELTA"},
167  ),
169  key="line_voltages_b",
170  name="Line voltages - B",
171  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
172  sensor_id="line_voltage_b",
173  device_class=SensorDeviceClass.VOLTAGE,
174  state_class=SensorStateClass.MEASUREMENT,
175  phase_types={"TWO", "THREE_STAR", "THREE_DELTA"},
176  ),
178  key="line_voltages_c",
179  name="Line voltages - C",
180  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
181  sensor_id="line_voltage_c",
182  device_class=SensorDeviceClass.VOLTAGE,
183  state_class=SensorStateClass.MEASUREMENT,
184  phase_types={"THREE_STAR", "THREE_DELTA"},
185  ),
186 )
187 
188 
190  hass: HomeAssistant,
191  config_entry: SmappeeConfigEntry,
192  async_add_entities: AddEntitiesCallback,
193 ) -> None:
194  """Set up the Smappee sensor."""
195  smappee_base = config_entry.runtime_data
196 
197  entities = []
198  for service_location in smappee_base.smappee.service_locations.values():
199  # Add all basic sensors (realtime values and aggregators)
200  # Some are available in local only env
201  entities.extend(
202  [
204  smappee_base=smappee_base,
205  service_location=service_location,
206  description=description,
207  )
208  for description in TREND_SENSORS
209  if not service_location.local_polling or description.local_polling
210  ]
211  )
212 
213  if service_location.has_reactive_value:
214  entities.extend(
215  [
217  smappee_base=smappee_base,
218  service_location=service_location,
219  description=description,
220  )
221  for description in REACTIVE_SENSORS
222  ]
223  )
224 
225  # Add solar sensors (some are available in local only env)
226  if service_location.has_solar_production:
227  entities.extend(
228  [
230  smappee_base=smappee_base,
231  service_location=service_location,
232  description=description,
233  )
234  for description in SOLAR_SENSORS
235  if not service_location.local_polling or description.local_polling
236  ]
237  )
238 
239  # Add all CT measurements
240  entities.extend(
241  [
243  smappee_base=smappee_base,
244  service_location=service_location,
245  description=SmappeeSensorEntityDescription(
246  key="load",
247  name=measurement.name,
248  native_unit_of_measurement=UnitOfPower.WATT,
249  sensor_id=measurement_id,
250  device_class=SensorDeviceClass.POWER,
251  state_class=SensorStateClass.MEASUREMENT,
252  ),
253  )
254  for measurement_id, measurement in service_location.measurements.items()
255  ]
256  )
257 
258  # Add phase- and line voltages if available
259  if service_location.has_voltage_values:
260  entities.extend(
261  [
263  smappee_base=smappee_base,
264  service_location=service_location,
265  description=description,
266  )
267  for description in VOLTAGE_SENSORS
268  if (
269  service_location.phase_type in description.phase_types
270  and not (
271  description.key.startswith("line_")
272  and service_location.local_polling
273  )
274  )
275  ]
276  )
277 
278  # Add Gas and Water sensors
279  entities.extend(
280  [
282  smappee_base=smappee_base,
283  service_location=service_location,
284  description=SmappeeSensorEntityDescription(
285  key="sensor",
286  name=channel.get("name"),
287  icon=(
288  "mdi:water"
289  if channel.get("type") == "water"
290  else "mdi:gas-cylinder"
291  ),
292  native_unit_of_measurement=channel.get("uom"),
293  sensor_id=f"{sensor_id}-{channel.get('channel')}",
294  state_class=SensorStateClass.MEASUREMENT,
295  ),
296  )
297  for sensor_id, sensor in service_location.sensors.items()
298  for channel in sensor.channels
299  ]
300  )
301 
302  # Add today_energy_kwh sensors for switches
303  entities.extend(
304  [
306  smappee_base=smappee_base,
307  service_location=service_location,
308  description=SmappeeSensorEntityDescription(
309  key="switch",
310  name=f"{actuator.name} - energy today",
311  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
312  sensor_id=actuator_id,
313  device_class=SensorDeviceClass.ENERGY,
314  state_class=SensorStateClass.TOTAL_INCREASING,
315  ),
316  )
317  for actuator_id, actuator in service_location.actuators.items()
318  if actuator.type == "SWITCH" and not service_location.local_polling
319  ]
320  )
321 
322  async_add_entities(entities, True)
323 
324 
326  """Implementation of a Smappee sensor."""
327 
328  entity_description: SmappeeSensorEntityDescription
329 
330  def __init__(
331  self,
332  smappee_base,
333  service_location,
334  description: SmappeeSensorEntityDescription,
335  ) -> None:
336  """Initialize the Smappee sensor."""
337  self.entity_descriptionentity_description = description
338  self._smappee_base_smappee_base = smappee_base
339  self._service_location_service_location = service_location
340  self._attr_device_info_attr_device_info = DeviceInfo(
341  identifiers={(DOMAIN, service_location.device_serial_number)},
342  manufacturer="Smappee",
343  model=service_location.device_model,
344  name=service_location.service_location_name,
345  sw_version=service_location.firmware_version,
346  )
347 
348  @property
349  def name(self):
350  """Return the name for this sensor."""
351  sensor_key = self.entity_descriptionentity_description.key
352  sensor_name = self.entity_descriptionentity_description.name
353  if sensor_key in ("sensor", "load", "switch"):
354  return (
355  f"{self._service_location.service_location_name} - "
356  f"{sensor_key.title()} - {sensor_name}"
357  )
358 
359  return f"{self._service_location.service_location_name} - {sensor_name}"
360 
361  @property
362  def unique_id(self):
363  """Return the unique ID for this sensor."""
364  sensor_key = self.entity_descriptionentity_description.key
365  if sensor_key in ("load", "sensor", "switch"):
366  return (
367  f"{self._service_location.device_serial_number}-"
368  f"{self._service_location.service_location_id}-"
369  f"{sensor_key}-{self.entity_description.sensor_id}"
370  )
371 
372  return (
373  f"{self._service_location.device_serial_number}-"
374  f"{self._service_location.service_location_id}-"
375  f"{sensor_key}"
376  )
377 
378  async def async_update(self) -> None:
379  """Get the latest data from Smappee and update the state."""
380  await self._smappee_base_smappee_base.async_update()
381 
382  sensor_key = self.entity_descriptionentity_description.key
383  if sensor_key == "total_power":
384  self._attr_native_value_attr_native_value = self._service_location_service_location.total_power
385  elif sensor_key == "total_reactive_power":
386  self._attr_native_value_attr_native_value = self._service_location_service_location.total_reactive_power
387  elif sensor_key == "solar_power":
388  self._attr_native_value_attr_native_value = self._service_location_service_location.solar_power
389  elif sensor_key == "alwayson":
390  self._attr_native_value_attr_native_value = self._service_location_service_location.alwayson
391  elif sensor_key in (
392  "phase_voltages_a",
393  "phase_voltages_b",
394  "phase_voltages_c",
395  ):
396  phase_voltages = self._service_location_service_location.phase_voltages
397  if phase_voltages is not None:
398  if sensor_key == "phase_voltages_a":
399  self._attr_native_value_attr_native_value = phase_voltages[0]
400  elif sensor_key == "phase_voltages_b":
401  self._attr_native_value_attr_native_value = phase_voltages[1]
402  elif sensor_key == "phase_voltages_c":
403  self._attr_native_value_attr_native_value = phase_voltages[2]
404  elif sensor_key in ("line_voltages_a", "line_voltages_b", "line_voltages_c"):
405  line_voltages = self._service_location_service_location.line_voltages
406  if line_voltages is not None:
407  if sensor_key == "line_voltages_a":
408  self._attr_native_value_attr_native_value = line_voltages[0]
409  elif sensor_key == "line_voltages_b":
410  self._attr_native_value_attr_native_value = line_voltages[1]
411  elif sensor_key == "line_voltages_c":
412  self._attr_native_value_attr_native_value = line_voltages[2]
413  elif sensor_key in (
414  "power_today",
415  "power_current_hour",
416  "power_last_5_minutes",
417  "solar_today",
418  "solar_current_hour",
419  "alwayson_today",
420  ):
421  trend_value = self._service_location_service_location.aggregated_values.get(sensor_key)
422  self._attr_native_value_attr_native_value = (
423  round(trend_value) if trend_value is not None else None
424  )
425  elif sensor_key == "load":
426  self._attr_native_value_attr_native_value = self._service_location_service_location.measurements.get(
427  self.entity_descriptionentity_description.sensor_id
428  ).active_total
429  elif sensor_key == "sensor":
430  sensor_id, channel_id = self.entity_descriptionentity_description.sensor_id.split("-")
431  sensor = self._service_location_service_location.sensors.get(int(sensor_id))
432  for channel in sensor.channels:
433  if channel.get("channel") == int(channel_id):
434  self._attr_native_value_attr_native_value = channel.get("value_today")
435  elif sensor_key == "switch":
436  cons = self._service_location_service_location.actuators.get(
437  self.entity_descriptionentity_description.sensor_id
438  ).consumption_today
439  if cons is not None:
440  self._attr_native_value_attr_native_value = round(cons / 1000.0, 2)
None __init__(self, smappee_base, service_location, SmappeeSensorEntityDescription description)
Definition: sensor.py:335
None async_setup_entry(HomeAssistant hass, SmappeeConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:193