Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for iammeter via local API."""
2 
3 from __future__ import annotations
4 
5 from asyncio import timeout
6 from collections.abc import Callable
7 from dataclasses import dataclass
8 from datetime import datetime, timedelta
9 import logging
10 
11 from iammeter.client import IamMeter
12 import voluptuous as vol
13 
15  PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
16  SensorDeviceClass,
17  SensorEntity,
18  SensorEntityDescription,
19  SensorStateClass,
20 )
21 from homeassistant.const import (
22  CONF_HOST,
23  CONF_NAME,
24  CONF_PORT,
25  PERCENTAGE,
26  Platform,
27  UnitOfElectricCurrent,
28  UnitOfElectricPotential,
29  UnitOfEnergy,
30  UnitOfFrequency,
31  UnitOfPower,
32 )
33 from homeassistant.core import HomeAssistant
34 from homeassistant.exceptions import PlatformNotReady
35 from homeassistant.helpers import debounce, entity_registry as er, update_coordinator
37 from homeassistant.helpers.device_registry import DeviceInfo
38 from homeassistant.helpers.entity_platform import AddEntitiesCallback
39 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
40 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
41 
42 from .const import DEVICE_3080, DOMAIN
43 
44 _LOGGER = logging.getLogger(__name__)
45 
46 DEFAULT_PORT = 80
47 DEFAULT_DEVICE_NAME = "IamMeter"
48 
49 PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
50  {
51  vol.Required(CONF_HOST): cv.string,
52  vol.Optional(CONF_NAME, default=DEFAULT_DEVICE_NAME): cv.string,
53  vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
54  }
55 )
56 
57 SCAN_INTERVAL = timedelta(seconds=30)
58 PLATFORM_TIMEOUT = 8
59 
60 
62  hass: HomeAssistant, model: str, serial_number: str
63 ) -> None:
64  """Migrate old unique ids to new unique ids."""
65  ent_reg = er.async_get(hass)
66  name_list = [
67  "Voltage",
68  "Current",
69  "Power",
70  "ImportEnergy",
71  "ExportGrid",
72  "Frequency",
73  "PF",
74  ]
75  phase_list = ["A", "B", "C", "NET"]
76  id_phase_range = 1 if model == DEVICE_3080 else 4
77  id_name_range = 5 if model == DEVICE_3080 else 7
78  for row in range(id_phase_range):
79  for idx in range(id_name_range):
80  old_unique_id = f"{serial_number}-{row}-{idx}"
81  new_unique_id = (
82  f"{serial_number}_{name_list[idx]}"
83  if model == DEVICE_3080
84  else f"{serial_number}_{name_list[idx]}_{phase_list[row]}"
85  )
86  entity_id = ent_reg.async_get_entity_id(
87  Platform.SENSOR, DOMAIN, old_unique_id
88  )
89  if entity_id is not None:
90  try:
91  ent_reg.async_update_entity(entity_id, new_unique_id=new_unique_id)
92  except ValueError:
93  _LOGGER.warning(
94  "Skip migration of id [%s] to [%s] because it already exists",
95  old_unique_id,
96  new_unique_id,
97  )
98  else:
99  _LOGGER.debug(
100  "Migrating unique_id from [%s] to [%s]",
101  old_unique_id,
102  new_unique_id,
103  )
104 
105 
107  hass: HomeAssistant,
108  config: ConfigType,
109  async_add_entities: AddEntitiesCallback,
110  discovery_info: DiscoveryInfoType | None = None,
111 ) -> None:
112  """Platform setup."""
113  config_host = config[CONF_HOST]
114  config_port = config[CONF_PORT]
115  config_name = config[CONF_NAME]
116  try:
117  api = await hass.async_add_executor_job(
118  IamMeter, config_host, config_port, config_name
119  )
120  except TimeoutError as err:
121  _LOGGER.error("Device is not ready")
122  raise PlatformNotReady from err
123 
124  async def async_update_data():
125  try:
126  async with timeout(PLATFORM_TIMEOUT):
127  return await hass.async_add_executor_job(api.client.get_data)
128  except TimeoutError as err:
129  raise UpdateFailed from err
130 
131  coordinator = DataUpdateCoordinator(
132  hass,
133  _LOGGER,
134  name=config_name,
135  update_method=async_update_data,
136  update_interval=SCAN_INTERVAL,
137  request_refresh_debouncer=debounce.Debouncer(
138  hass, _LOGGER, cooldown=0.3, immediate=True
139  ),
140  )
141  await coordinator.async_refresh()
142  model = coordinator.data["Model"]
143  serial_number = coordinator.data["sn"]
144  _migrate_to_new_unique_id(hass, model, serial_number)
145  if model == DEVICE_3080:
147  IammeterSensor(coordinator, description)
148  for description in SENSOR_TYPES_3080
149  )
150  else: # DEVICE_3080T:
152  IammeterSensor(coordinator, description)
153  for description in SENSOR_TYPES_3080T
154  )
155 
156 
157 class IammeterSensor(update_coordinator.CoordinatorEntity, SensorEntity):
158  """Representation of a Sensor."""
159 
160  entity_description: IammeterSensorEntityDescription
161  _attr_has_entity_name = True
162  _attr_name = None
163 
164  def __init__(
165  self,
166  coordinator: DataUpdateCoordinator,
167  description: IammeterSensorEntityDescription,
168  ) -> None:
169  """Initialize the sensor."""
170  super().__init__(coordinator)
171  self.entity_descriptionentity_description = description
172  self._attr_unique_id_attr_unique_id = f"{coordinator.data['sn']}_{description.key}"
173  self._attr_device_info_attr_device_info = DeviceInfo(
174  identifiers={(DOMAIN, coordinator.data["sn"])},
175  manufacturer="IamMeter",
176  name=coordinator.name,
177  )
178 
179  @property
180  def native_value(self):
181  """Return the native sensor value."""
182  raw_attr = self.coordinator.data.get(self.entity_descriptionentity_description.key, None)
183  if self.entity_descriptionentity_description.value:
184  return self.entity_descriptionentity_description.value(raw_attr)
185  return raw_attr
186 
187 
188 @dataclass(frozen=True)
190  """Describes Iammeter sensor entity."""
191 
192  value: Callable[[float | int], float] | Callable[[datetime], datetime] | None = None
193 
194 
195 SENSOR_TYPES_3080: tuple[IammeterSensorEntityDescription, ...] = (
197  key="Voltage",
198  icon="mdi:solar-power",
199  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
200  device_class=SensorDeviceClass.VOLTAGE,
201  state_class=SensorStateClass.MEASUREMENT,
202  entity_registry_enabled_default=False,
203  ),
205  key="Current",
206  icon="mdi:solar-power",
207  native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
208  state_class=SensorStateClass.MEASUREMENT,
209  device_class=SensorDeviceClass.CURRENT,
210  entity_registry_enabled_default=False,
211  ),
213  key="Power",
214  icon="mdi:solar-power",
215  native_unit_of_measurement=UnitOfPower.WATT,
216  device_class=SensorDeviceClass.POWER,
217  state_class=SensorStateClass.MEASUREMENT,
218  ),
220  key="ImportEnergy",
221  icon="mdi:solar-power",
222  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
223  device_class=SensorDeviceClass.ENERGY,
224  state_class=SensorStateClass.TOTAL_INCREASING,
225  ),
227  key="ExportGrid",
228  icon="mdi:solar-power",
229  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
230  device_class=SensorDeviceClass.ENERGY,
231  state_class=SensorStateClass.TOTAL_INCREASING,
232  ),
233 )
234 SENSOR_TYPES_3080T: tuple[IammeterSensorEntityDescription, ...] = (
236  key="Voltage_A",
237  translation_key="voltage_a",
238  icon="mdi:solar-power",
239  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
240  device_class=SensorDeviceClass.VOLTAGE,
241  state_class=SensorStateClass.MEASUREMENT,
242  entity_registry_enabled_default=False,
243  ),
245  key="Current_A",
246  translation_key="current_a",
247  icon="mdi:solar-power",
248  native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
249  state_class=SensorStateClass.MEASUREMENT,
250  device_class=SensorDeviceClass.CURRENT,
251  entity_registry_enabled_default=False,
252  ),
254  key="Power_A",
255  translation_key="power_a",
256  icon="mdi:solar-power",
257  native_unit_of_measurement=UnitOfPower.WATT,
258  device_class=SensorDeviceClass.POWER,
259  state_class=SensorStateClass.MEASUREMENT,
260  ),
262  key="ImportEnergy_A",
263  translation_key="import_energy_a",
264  icon="mdi:solar-power",
265  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
266  device_class=SensorDeviceClass.ENERGY,
267  state_class=SensorStateClass.TOTAL_INCREASING,
268  ),
270  key="ExportGrid_A",
271  translation_key="export_grid_a",
272  icon="mdi:solar-power",
273  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
274  device_class=SensorDeviceClass.ENERGY,
275  state_class=SensorStateClass.TOTAL_INCREASING,
276  ),
278  key="Frequency_A",
279  translation_key="frequency_a",
280  icon="mdi:solar-power",
281  native_unit_of_measurement=UnitOfFrequency.HERTZ,
282  device_class=SensorDeviceClass.FREQUENCY,
283  state_class=SensorStateClass.MEASUREMENT,
284  entity_registry_enabled_default=False,
285  ),
287  key="PF_A",
288  translation_key="pf_a",
289  icon="mdi:solar-power",
290  native_unit_of_measurement=PERCENTAGE,
291  state_class=SensorStateClass.MEASUREMENT,
292  device_class=SensorDeviceClass.POWER_FACTOR,
293  value=lambda value: value * 100,
294  entity_registry_enabled_default=False,
295  ),
297  key="Voltage_B",
298  translation_key="voltage_b",
299  icon="mdi:solar-power",
300  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
301  device_class=SensorDeviceClass.VOLTAGE,
302  state_class=SensorStateClass.MEASUREMENT,
303  entity_registry_enabled_default=False,
304  ),
306  key="Current_B",
307  translation_key="current_b",
308  icon="mdi:solar-power",
309  native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
310  state_class=SensorStateClass.MEASUREMENT,
311  device_class=SensorDeviceClass.CURRENT,
312  entity_registry_enabled_default=False,
313  ),
315  key="Power_B",
316  translation_key="power_b",
317  icon="mdi:solar-power",
318  native_unit_of_measurement=UnitOfPower.WATT,
319  device_class=SensorDeviceClass.POWER,
320  state_class=SensorStateClass.MEASUREMENT,
321  ),
323  key="ImportEnergy_B",
324  translation_key="import_energy_b",
325  icon="mdi:solar-power",
326  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
327  device_class=SensorDeviceClass.ENERGY,
328  state_class=SensorStateClass.TOTAL_INCREASING,
329  ),
331  key="ExportGrid_B",
332  translation_key="export_grid_b",
333  icon="mdi:solar-power",
334  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
335  device_class=SensorDeviceClass.ENERGY,
336  state_class=SensorStateClass.TOTAL_INCREASING,
337  ),
339  key="Frequency_B",
340  translation_key="frequency_b",
341  icon="mdi:solar-power",
342  native_unit_of_measurement=UnitOfFrequency.HERTZ,
343  device_class=SensorDeviceClass.FREQUENCY,
344  state_class=SensorStateClass.MEASUREMENT,
345  entity_registry_enabled_default=False,
346  ),
348  key="PF_B",
349  translation_key="pf_b",
350  icon="mdi:solar-power",
351  native_unit_of_measurement=PERCENTAGE,
352  state_class=SensorStateClass.MEASUREMENT,
353  device_class=SensorDeviceClass.POWER_FACTOR,
354  value=lambda value: value * 100,
355  entity_registry_enabled_default=False,
356  ),
358  key="Voltage_C",
359  translation_key="voltage_c",
360  icon="mdi:solar-power",
361  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
362  device_class=SensorDeviceClass.VOLTAGE,
363  state_class=SensorStateClass.MEASUREMENT,
364  entity_registry_enabled_default=False,
365  ),
367  key="Current_C",
368  translation_key="current_c",
369  icon="mdi:solar-power",
370  native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
371  state_class=SensorStateClass.MEASUREMENT,
372  device_class=SensorDeviceClass.CURRENT,
373  entity_registry_enabled_default=False,
374  ),
376  key="Power_C",
377  translation_key="power_c",
378  icon="mdi:solar-power",
379  native_unit_of_measurement=UnitOfPower.WATT,
380  device_class=SensorDeviceClass.POWER,
381  state_class=SensorStateClass.MEASUREMENT,
382  ),
384  key="ImportEnergy_C",
385  translation_key="import_energy_c",
386  icon="mdi:solar-power",
387  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
388  device_class=SensorDeviceClass.ENERGY,
389  state_class=SensorStateClass.TOTAL_INCREASING,
390  ),
392  key="ExportGrid_C",
393  translation_key="export_grid_c",
394  icon="mdi:solar-power",
395  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
396  device_class=SensorDeviceClass.ENERGY,
397  state_class=SensorStateClass.TOTAL_INCREASING,
398  ),
400  key="Frequency_C",
401  translation_key="frequency_c",
402  icon="mdi:solar-power",
403  native_unit_of_measurement=UnitOfFrequency.HERTZ,
404  device_class=SensorDeviceClass.FREQUENCY,
405  state_class=SensorStateClass.MEASUREMENT,
406  entity_registry_enabled_default=False,
407  ),
409  key="PF_C",
410  translation_key="pf_c",
411  icon="mdi:solar-power",
412  native_unit_of_measurement=PERCENTAGE,
413  state_class=SensorStateClass.MEASUREMENT,
414  device_class=SensorDeviceClass.POWER_FACTOR,
415  value=lambda value: value * 100,
416  entity_registry_enabled_default=False,
417  ),
419  key="Voltage_Net",
420  translation_key="voltage_net",
421  icon="mdi:solar-power",
422  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
423  device_class=SensorDeviceClass.VOLTAGE,
424  state_class=SensorStateClass.MEASUREMENT,
425  entity_registry_enabled_default=False,
426  ),
428  key="Power_Net",
429  icon="mdi:solar-power",
430  native_unit_of_measurement=UnitOfPower.WATT,
431  device_class=SensorDeviceClass.POWER,
432  state_class=SensorStateClass.MEASUREMENT,
433  entity_registry_enabled_default=False,
434  ),
436  key="ImportEnergy_Net",
437  translation_key="import_energy_net",
438  icon="mdi:solar-power",
439  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
440  device_class=SensorDeviceClass.ENERGY,
441  state_class=SensorStateClass.TOTAL_INCREASING,
442  entity_registry_enabled_default=False,
443  ),
445  key="ExportGrid_Net",
446  translation_key="export_grid_net",
447  icon="mdi:solar-power",
448  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
449  device_class=SensorDeviceClass.ENERGY,
450  state_class=SensorStateClass.TOTAL_INCREASING,
451  entity_registry_enabled_default=False,
452  ),
454  key="Frequency_Net",
455  translation_key="frequency_net",
456  icon="mdi:solar-power",
457  native_unit_of_measurement=UnitOfFrequency.HERTZ,
458  device_class=SensorDeviceClass.FREQUENCY,
459  state_class=SensorStateClass.MEASUREMENT,
460  entity_registry_enabled_default=False,
461  ),
463  key="PF_Net",
464  translation_key="pf_net",
465  icon="mdi:solar-power",
466  native_unit_of_measurement=PERCENTAGE,
467  state_class=SensorStateClass.MEASUREMENT,
468  device_class=SensorDeviceClass.POWER_FACTOR,
469  value=lambda value: value * 100,
470  entity_registry_enabled_default=False,
471  ),
472 )
None __init__(self, DataUpdateCoordinator coordinator, IammeterSensorEntityDescription description)
Definition: sensor.py:168
None _migrate_to_new_unique_id(HomeAssistant hass, str model, str serial_number)
Definition: sensor.py:63
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: sensor.py:111