Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for Huawei LTE sensors."""
2 
3 from __future__ import annotations
4 
5 from bisect import bisect
6 from collections.abc import Callable, Sequence
7 from dataclasses import dataclass
8 from datetime import datetime, timedelta
9 import logging
10 import re
11 
13  DOMAIN as SENSOR_DOMAIN,
14  SensorDeviceClass,
15  SensorEntity,
16  SensorEntityDescription,
17  SensorStateClass,
18 )
19 from homeassistant.config_entries import ConfigEntry
20 from homeassistant.const import (
21  PERCENTAGE,
22  EntityCategory,
23  UnitOfDataRate,
24  UnitOfFrequency,
25  UnitOfInformation,
26  UnitOfTime,
27 )
28 from homeassistant.core import HomeAssistant
29 from homeassistant.helpers.entity import Entity
30 from homeassistant.helpers.entity_platform import AddEntitiesCallback
31 from homeassistant.helpers.typing import StateType
32 
33 from . import Router
34 from .const import (
35  DOMAIN,
36  KEY_DEVICE_INFORMATION,
37  KEY_DEVICE_SIGNAL,
38  KEY_MONITORING_CHECK_NOTIFICATIONS,
39  KEY_MONITORING_MONTH_STATISTICS,
40  KEY_MONITORING_STATUS,
41  KEY_MONITORING_TRAFFIC_STATISTICS,
42  KEY_NET_CURRENT_PLMN,
43  KEY_NET_NET_MODE,
44  KEY_SMS_SMS_COUNT,
45  SENSOR_KEYS,
46 )
47 from .entity import HuaweiLteBaseEntityWithDevice
48 
49 _LOGGER = logging.getLogger(__name__)
50 
51 
52 def format_default(value: StateType) -> tuple[StateType, str | None]:
53  """Format value."""
54  unit = None
55  if value is not None:
56  # Clean up value and infer unit, e.g. -71dBm, 15 dB
57  if match := re.match(
58  r"((&[gl]t;|[><])=?)?(?P<value>.+?)\s*(?P<unit>[a-zA-Z]+)\s*$", str(value)
59  ):
60  try:
61  value = float(match.group("value"))
62  unit = match.group("unit")
63  except ValueError:
64  pass
65  return value, unit
66 
67 
68 def format_freq_mhz(value: StateType) -> tuple[StateType, UnitOfFrequency]:
69  """Format a frequency value for which source is in tenths of MHz."""
70  return (
71  float(value) / 10 if value is not None else None,
72  UnitOfFrequency.MEGAHERTZ,
73  )
74 
75 
76 def format_last_reset_elapsed_seconds(value: str | None) -> datetime | None:
77  """Convert elapsed seconds to last reset datetime."""
78  if value is None:
79  return None
80  try:
81  last_reset = datetime.now() - timedelta(seconds=int(value))
82  last_reset.replace(microsecond=0)
83  except ValueError:
84  return None
85  return last_reset
86 
87 
88 def signal_icon(limits: Sequence[int], value: StateType) -> str:
89  """Get signal icon."""
90  return (
91  "mdi:signal-cellular-outline",
92  "mdi:signal-cellular-1",
93  "mdi:signal-cellular-2",
94  "mdi:signal-cellular-3",
95  )[bisect(limits, value if value is not None else -1000)]
96 
97 
98 def bandwidth_icon(limits: Sequence[int], value: StateType) -> str:
99  """Get bandwidth icon."""
100  return (
101  "mdi:speedometer-slow",
102  "mdi:speedometer-medium",
103  "mdi:speedometer",
104  )[bisect(limits, value if value is not None else -1000)]
105 
106 
107 @dataclass
109  """Class describing Huawei LTE sensor groups."""
110 
111  descriptions: dict[str, HuaweiSensorEntityDescription]
112  include: re.Pattern[str] | None = None
113  exclude: re.Pattern[str] | None = None
114 
115 
116 @dataclass(frozen=True)
118  """Class describing Huawei LTE sensor entities."""
119 
120  # HuaweiLteSensor does not support UNDEFINED or None,
121  # restrict the type to str.
122  name: str = ""
123 
124  format_fn: Callable[[str], tuple[StateType, str | None]] = format_default
125  icon_fn: Callable[[StateType], str] | None = None
126  device_class_fn: Callable[[StateType], SensorDeviceClass | None] | None = None
127  last_reset_item: str | None = None
128  last_reset_format_fn: Callable[[str | None], datetime | None] | None = None
129 
130 
131 SENSOR_META: dict[str, HuaweiSensorGroup] = {
132  #
133  # Device information
134  #
135  KEY_DEVICE_INFORMATION: HuaweiSensorGroup(
136  include=re.compile(r"^(WanIP.*Address|uptime)$", re.IGNORECASE),
137  descriptions={
139  key="uptime",
140  translation_key="uptime",
141  icon="mdi:timer-outline",
142  native_unit_of_measurement=UnitOfTime.SECONDS,
143  device_class=SensorDeviceClass.DURATION,
144  entity_category=EntityCategory.DIAGNOSTIC,
145  ),
146  "WanIPAddress": HuaweiSensorEntityDescription(
147  key="WanIPAddress",
148  translation_key="wan_ip_address",
149  icon="mdi:ip",
150  entity_category=EntityCategory.DIAGNOSTIC,
151  entity_registry_enabled_default=True,
152  ),
153  "WanIPv6Address": HuaweiSensorEntityDescription(
154  key="WanIPv6Address",
155  translation_key="wan_ipv6_address",
156  icon="mdi:ip",
157  entity_category=EntityCategory.DIAGNOSTIC,
158  ),
159  },
160  ),
161  #
162  # Signal
163  #
164  KEY_DEVICE_SIGNAL: HuaweiSensorGroup(
165  descriptions={
167  key="arfcn",
168  translation_key="arfcn",
169  entity_category=EntityCategory.DIAGNOSTIC,
170  ),
172  key="band",
173  translation_key="band",
174  entity_category=EntityCategory.DIAGNOSTIC,
175  ),
177  key="bsic",
178  translation_key="base_station_identity_code",
179  entity_category=EntityCategory.DIAGNOSTIC,
180  ),
182  key="cell_id",
183  translation_key="cell_id",
184  icon="mdi:transmission-tower",
185  entity_category=EntityCategory.DIAGNOSTIC,
186  ),
188  key="cqi0",
189  translation_key="cqi0",
190  icon="mdi:speedometer",
191  entity_category=EntityCategory.DIAGNOSTIC,
192  ),
194  key="cqi1",
195  translation_key="cqi1",
196  icon="mdi:speedometer",
197  entity_category=EntityCategory.DIAGNOSTIC,
198  ),
200  key="dl_mcs",
201  translation_key="downlink_mcs",
202  entity_category=EntityCategory.DIAGNOSTIC,
203  ),
204  "dlbandwidth": HuaweiSensorEntityDescription(
205  key="dlbandwidth",
206  translation_key="downlink_bandwidth",
207  icon_fn=lambda x: bandwidth_icon((8, 15), x),
208  entity_category=EntityCategory.DIAGNOSTIC,
209  ),
210  "dlfrequency": HuaweiSensorEntityDescription(
211  key="dlfrequency",
212  translation_key="downlink_frequency",
213  device_class=SensorDeviceClass.FREQUENCY,
214  entity_category=EntityCategory.DIAGNOSTIC,
215  ),
217  key="earfcn",
218  translation_key="earfcn",
219  entity_category=EntityCategory.DIAGNOSTIC,
220  ),
222  key="ecio",
223  translation_key="ecio",
224  device_class=SensorDeviceClass.SIGNAL_STRENGTH,
225  # https://wiki.teltonika.lt/view/EC/IO
226  icon_fn=lambda x: signal_icon((-20, -10, -6), x),
227  state_class=SensorStateClass.MEASUREMENT,
228  entity_category=EntityCategory.DIAGNOSTIC,
229  ),
230  "enodeb_id": HuaweiSensorEntityDescription(
231  key="enodeb_id",
232  translation_key="enodeb_id",
233  entity_category=EntityCategory.DIAGNOSTIC,
234  ),
236  key="lac",
237  translation_key="lac",
238  icon="mdi:map-marker",
239  entity_category=EntityCategory.DIAGNOSTIC,
240  ),
241  "ltedlfreq": HuaweiSensorEntityDescription(
242  key="ltedlfreq",
243  translation_key="lte_downlink_frequency",
244  format_fn=format_freq_mhz,
245  suggested_display_precision=0,
246  device_class=SensorDeviceClass.FREQUENCY,
247  entity_category=EntityCategory.DIAGNOSTIC,
248  ),
249  "lteulfreq": HuaweiSensorEntityDescription(
250  key="lteulfreq",
251  translation_key="lte_uplink_frequency",
252  format_fn=format_freq_mhz,
253  suggested_display_precision=0,
254  device_class=SensorDeviceClass.FREQUENCY,
255  entity_category=EntityCategory.DIAGNOSTIC,
256  ),
258  key="mode",
259  translation_key="mode",
260  format_fn=lambda x: (
261  {"0": "2G", "2": "3G", "7": "4G"}.get(x),
262  None,
263  ),
264  icon_fn=lambda x: (
265  {
266  "2G": "mdi:signal-2g",
267  "3G": "mdi:signal-3g",
268  "4G": "mdi:signal-4g",
269  }.get(str(x), "mdi:signal")
270  ),
271  entity_category=EntityCategory.DIAGNOSTIC,
272  ),
274  key="nrbler",
275  translation_key="nrbler",
276  entity_category=EntityCategory.DIAGNOSTIC,
277  ),
279  key="nrcqi0",
280  translation_key="nrcqi0",
281  icon="mdi:speedometer",
282  entity_category=EntityCategory.DIAGNOSTIC,
283  ),
285  key="nrcqi1",
286  translation_key="nrcqi1",
287  icon="mdi:speedometer",
288  entity_category=EntityCategory.DIAGNOSTIC,
289  ),
290  "nrdlbandwidth": HuaweiSensorEntityDescription(
291  key="nrdlbandwidth",
292  translation_key="nrdlbandwidth",
293  # Could add icon_fn like we have for dlbandwidth,
294  # if we find a good source what to use as 5G thresholds.
295  entity_category=EntityCategory.DIAGNOSTIC,
296  ),
298  key="nrdlmcs",
299  translation_key="nrdlmcs",
300  entity_category=EntityCategory.DIAGNOSTIC,
301  ),
302  "nrearfcn": HuaweiSensorEntityDescription(
303  key="nrearfcn",
304  translation_key="nrearfcn",
305  entity_category=EntityCategory.DIAGNOSTIC,
306  ),
308  key="nrrank",
309  translation_key="nrrank",
310  entity_category=EntityCategory.DIAGNOSTIC,
311  ),
313  key="nrrsrp",
314  translation_key="nrrsrp",
315  device_class=SensorDeviceClass.SIGNAL_STRENGTH,
316  # Could add icon_fn as in rsrp, source for 5G thresholds?
317  state_class=SensorStateClass.MEASUREMENT,
318  entity_category=EntityCategory.DIAGNOSTIC,
319  entity_registry_enabled_default=True,
320  ),
322  key="nrrsrq",
323  translation_key="nrrsrq",
324  device_class=SensorDeviceClass.SIGNAL_STRENGTH,
325  # Could add icon_fn as in rsrq, source for 5G thresholds?
326  state_class=SensorStateClass.MEASUREMENT,
327  entity_category=EntityCategory.DIAGNOSTIC,
328  entity_registry_enabled_default=True,
329  ),
331  key="nrsinr",
332  translation_key="nrsinr",
333  device_class=SensorDeviceClass.SIGNAL_STRENGTH,
334  # Could add icon_fn as in sinr, source for thresholds?
335  state_class=SensorStateClass.MEASUREMENT,
336  entity_category=EntityCategory.DIAGNOSTIC,
337  entity_registry_enabled_default=True,
338  ),
339  "nrtxpower": HuaweiSensorEntityDescription(
340  key="nrtxpower",
341  translation_key="nrtxpower",
342  # The value we get from the API tends to consist of several, e.g.
343  # PPusch:21dBm PPucch:2dBm PSrs:0dBm PPrach:10dBm
344  # Present as SIGNAL_STRENGTH only if it was parsed to a number.
345  # We could try to parse this to separate component sensors sometime.
346  device_class_fn=lambda x: (
347  SensorDeviceClass.SIGNAL_STRENGTH
348  if isinstance(x, (float, int))
349  else None
350  ),
351  entity_category=EntityCategory.DIAGNOSTIC,
352  ),
353  "nrulbandwidth": HuaweiSensorEntityDescription(
354  key="nrulbandwidth",
355  translation_key="nrulbandwidth",
356  # Could add icon_fn as in ulbandwidth, source for 5G thresholds?
357  entity_category=EntityCategory.DIAGNOSTIC,
358  ),
360  key="nrulmcs",
361  translation_key="nrulmcs",
362  entity_category=EntityCategory.DIAGNOSTIC,
363  ),
365  key="pci",
366  translation_key="pci",
367  icon="mdi:transmission-tower",
368  entity_category=EntityCategory.DIAGNOSTIC,
369  ),
371  key="plmn",
372  translation_key="plmn",
373  entity_category=EntityCategory.DIAGNOSTIC,
374  ),
376  key="rac",
377  translation_key="rac",
378  icon="mdi:map-marker",
379  entity_category=EntityCategory.DIAGNOSTIC,
380  ),
381  "rrc_status": HuaweiSensorEntityDescription(
382  key="rrc_status",
383  translation_key="rrc_status",
384  entity_category=EntityCategory.DIAGNOSTIC,
385  ),
387  key="rscp",
388  translation_key="rscp",
389  device_class=SensorDeviceClass.SIGNAL_STRENGTH,
390  # https://wiki.teltonika.lt/view/RSCP
391  icon_fn=lambda x: signal_icon((-95, -85, -75), x),
392  state_class=SensorStateClass.MEASUREMENT,
393  entity_category=EntityCategory.DIAGNOSTIC,
394  ),
396  key="rsrp",
397  translation_key="rsrp",
398  device_class=SensorDeviceClass.SIGNAL_STRENGTH,
399  # http://www.lte-anbieter.info/technik/rsrp.php # codespell:ignore technik
400  icon_fn=lambda x: signal_icon((-110, -95, -80), x),
401  state_class=SensorStateClass.MEASUREMENT,
402  entity_category=EntityCategory.DIAGNOSTIC,
403  entity_registry_enabled_default=True,
404  ),
406  key="rsrq",
407  translation_key="rsrq",
408  device_class=SensorDeviceClass.SIGNAL_STRENGTH,
409  # http://www.lte-anbieter.info/technik/rsrq.php # codespell:ignore technik
410  icon_fn=lambda x: signal_icon((-11, -8, -5), x),
411  state_class=SensorStateClass.MEASUREMENT,
412  entity_category=EntityCategory.DIAGNOSTIC,
413  entity_registry_enabled_default=True,
414  ),
416  key="rssi",
417  translation_key="rssi",
418  device_class=SensorDeviceClass.SIGNAL_STRENGTH,
419  # https://eyesaas.com/wi-fi-signal-strength/
420  icon_fn=lambda x: signal_icon((-80, -70, -60), x),
421  state_class=SensorStateClass.MEASUREMENT,
422  entity_category=EntityCategory.DIAGNOSTIC,
423  entity_registry_enabled_default=True,
424  ),
426  key="sinr",
427  translation_key="sinr",
428  device_class=SensorDeviceClass.SIGNAL_STRENGTH,
429  # http://www.lte-anbieter.info/technik/sinr.php # codespell:ignore technik
430  icon_fn=lambda x: signal_icon((0, 5, 10), x),
431  state_class=SensorStateClass.MEASUREMENT,
432  entity_category=EntityCategory.DIAGNOSTIC,
433  entity_registry_enabled_default=True,
434  ),
436  key="tac",
437  translation_key="tac",
438  icon="mdi:map-marker",
439  entity_category=EntityCategory.DIAGNOSTIC,
440  ),
442  key="tdd",
443  translation_key="tdd",
444  entity_category=EntityCategory.DIAGNOSTIC,
445  ),
446  "transmode": HuaweiSensorEntityDescription(
447  key="transmode",
448  translation_key="transmission_mode",
449  entity_category=EntityCategory.DIAGNOSTIC,
450  ),
452  key="txpower",
453  translation_key="transmit_power",
454  # The value we get from the API tends to consist of several, e.g.
455  # PPusch:15dBm PPucch:2dBm PSrs:42dBm PPrach:1dBm
456  # Present as SIGNAL_STRENGTH only if it was parsed to a number.
457  # We could try to parse this to separate component sensors sometime.
458  device_class_fn=lambda x: (
459  SensorDeviceClass.SIGNAL_STRENGTH
460  if isinstance(x, (float, int))
461  else None
462  ),
463  entity_category=EntityCategory.DIAGNOSTIC,
464  ),
466  key="ul_mcs",
467  translation_key="uplink_mcs",
468  entity_category=EntityCategory.DIAGNOSTIC,
469  ),
470  "ulbandwidth": HuaweiSensorEntityDescription(
471  key="ulbandwidth",
472  translation_key="uplink_bandwidth",
473  icon_fn=lambda x: bandwidth_icon((8, 15), x),
474  entity_category=EntityCategory.DIAGNOSTIC,
475  ),
476  "ulfrequency": HuaweiSensorEntityDescription(
477  key="ulfrequency",
478  translation_key="uplink_frequency",
479  device_class=SensorDeviceClass.FREQUENCY,
480  entity_category=EntityCategory.DIAGNOSTIC,
481  ),
482  }
483  ),
484  #
485  # Monitoring
486  #
487  KEY_MONITORING_CHECK_NOTIFICATIONS: HuaweiSensorGroup(
488  exclude=re.compile(
489  r"^(onlineupdatestatus|smsstoragefull)$",
490  re.IGNORECASE,
491  ),
492  descriptions={
493  "UnreadMessage": HuaweiSensorEntityDescription(
494  key="UnreadMessage",
495  translation_key="sms_unread",
496  icon="mdi:email-arrow-left",
497  ),
498  },
499  ),
500  KEY_MONITORING_MONTH_STATISTICS: HuaweiSensorGroup(
501  exclude=re.compile(
502  r"^(currentday|month)(duration|lastcleartime)$", re.IGNORECASE
503  ),
504  descriptions={
505  "CurrentDayUsed": HuaweiSensorEntityDescription(
506  key="CurrentDayUsed",
507  translation_key="current_day_transfer",
508  native_unit_of_measurement=UnitOfInformation.BYTES,
509  device_class=SensorDeviceClass.DATA_SIZE,
510  icon="mdi:arrow-up-down-bold",
511  state_class=SensorStateClass.TOTAL,
512  last_reset_item="CurrentDayDuration",
513  last_reset_format_fn=format_last_reset_elapsed_seconds,
514  ),
515  "CurrentMonthDownload": HuaweiSensorEntityDescription(
516  key="CurrentMonthDownload",
517  translation_key="current_month_download",
518  native_unit_of_measurement=UnitOfInformation.BYTES,
519  device_class=SensorDeviceClass.DATA_SIZE,
520  icon="mdi:download",
521  state_class=SensorStateClass.TOTAL,
522  last_reset_item="MonthDuration",
523  last_reset_format_fn=format_last_reset_elapsed_seconds,
524  ),
525  "CurrentMonthUpload": HuaweiSensorEntityDescription(
526  key="CurrentMonthUpload",
527  translation_key="current_month_upload",
528  native_unit_of_measurement=UnitOfInformation.BYTES,
529  device_class=SensorDeviceClass.DATA_SIZE,
530  icon="mdi:upload",
531  state_class=SensorStateClass.TOTAL,
532  last_reset_item="MonthDuration",
533  last_reset_format_fn=format_last_reset_elapsed_seconds,
534  ),
535  },
536  ),
537  KEY_MONITORING_STATUS: HuaweiSensorGroup(
538  include=re.compile(
539  r"^(batterypercent|currentwifiuser|(primary|secondary).*dns)$",
540  re.IGNORECASE,
541  ),
542  descriptions={
543  "BatteryPercent": HuaweiSensorEntityDescription(
544  key="BatteryPercent",
545  device_class=SensorDeviceClass.BATTERY,
546  native_unit_of_measurement=PERCENTAGE,
547  state_class=SensorStateClass.MEASUREMENT,
548  entity_category=EntityCategory.DIAGNOSTIC,
549  ),
550  "CurrentWifiUser": HuaweiSensorEntityDescription(
551  key="CurrentWifiUser",
552  translation_key="wifi_clients_connected",
553  icon="mdi:wifi",
554  state_class=SensorStateClass.MEASUREMENT,
555  entity_category=EntityCategory.DIAGNOSTIC,
556  ),
557  "PrimaryDns": HuaweiSensorEntityDescription(
558  key="PrimaryDns",
559  translation_key="primary_dns_server",
560  icon="mdi:ip",
561  entity_category=EntityCategory.DIAGNOSTIC,
562  ),
563  "PrimaryIPv6Dns": HuaweiSensorEntityDescription(
564  key="PrimaryIPv6Dns",
565  translation_key="primary_ipv6_dns_server",
566  icon="mdi:ip",
567  entity_category=EntityCategory.DIAGNOSTIC,
568  ),
569  "SecondaryDns": HuaweiSensorEntityDescription(
570  key="SecondaryDns",
571  translation_key="secondary_dns_server",
572  icon="mdi:ip",
573  entity_category=EntityCategory.DIAGNOSTIC,
574  ),
575  "SecondaryIPv6Dns": HuaweiSensorEntityDescription(
576  key="SecondaryIPv6Dns",
577  translation_key="secondary_ipv6_dns_server",
578  icon="mdi:ip",
579  entity_category=EntityCategory.DIAGNOSTIC,
580  ),
581  },
582  ),
583  KEY_MONITORING_TRAFFIC_STATISTICS: HuaweiSensorGroup(
584  exclude=re.compile(r"^showtraffic$", re.IGNORECASE),
585  descriptions={
586  "CurrentConnectTime": HuaweiSensorEntityDescription(
587  key="CurrentConnectTime",
588  translation_key="current_connection_duration",
589  native_unit_of_measurement=UnitOfTime.SECONDS,
590  device_class=SensorDeviceClass.DURATION,
591  icon="mdi:timer-outline",
592  ),
593  "CurrentDownload": HuaweiSensorEntityDescription(
594  key="CurrentDownload",
595  translation_key="current_connection_download",
596  native_unit_of_measurement=UnitOfInformation.BYTES,
597  device_class=SensorDeviceClass.DATA_SIZE,
598  icon="mdi:download",
599  state_class=SensorStateClass.TOTAL_INCREASING,
600  ),
601  "CurrentDownloadRate": HuaweiSensorEntityDescription(
602  key="CurrentDownloadRate",
603  translation_key="current_download_rate",
604  native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
605  device_class=SensorDeviceClass.DATA_RATE,
606  icon="mdi:download",
607  state_class=SensorStateClass.MEASUREMENT,
608  ),
609  "CurrentUpload": HuaweiSensorEntityDescription(
610  key="CurrentUpload",
611  translation_key="current_connection_upload",
612  native_unit_of_measurement=UnitOfInformation.BYTES,
613  device_class=SensorDeviceClass.DATA_SIZE,
614  icon="mdi:upload",
615  state_class=SensorStateClass.TOTAL_INCREASING,
616  ),
617  "CurrentUploadRate": HuaweiSensorEntityDescription(
618  key="CurrentUploadRate",
619  translation_key="current_upload_rate",
620  native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
621  device_class=SensorDeviceClass.DATA_RATE,
622  icon="mdi:upload",
623  state_class=SensorStateClass.MEASUREMENT,
624  ),
625  "TotalConnectTime": HuaweiSensorEntityDescription(
626  key="TotalConnectTime",
627  translation_key="total_connected_duration",
628  native_unit_of_measurement=UnitOfTime.SECONDS,
629  device_class=SensorDeviceClass.DURATION,
630  icon="mdi:timer-outline",
631  state_class=SensorStateClass.TOTAL_INCREASING,
632  ),
633  "TotalDownload": HuaweiSensorEntityDescription(
634  key="TotalDownload",
635  translation_key="total_download",
636  native_unit_of_measurement=UnitOfInformation.BYTES,
637  device_class=SensorDeviceClass.DATA_SIZE,
638  icon="mdi:download",
639  state_class=SensorStateClass.TOTAL_INCREASING,
640  ),
641  "TotalUpload": HuaweiSensorEntityDescription(
642  key="TotalUpload",
643  translation_key="total_upload",
644  native_unit_of_measurement=UnitOfInformation.BYTES,
645  device_class=SensorDeviceClass.DATA_SIZE,
646  icon="mdi:upload",
647  state_class=SensorStateClass.TOTAL_INCREASING,
648  ),
649  },
650  ),
651  #
652  # Network
653  #
654  KEY_NET_CURRENT_PLMN: HuaweiSensorGroup(
655  exclude=re.compile(r"^(Rat|ShortName|Spn)$", re.IGNORECASE),
656  descriptions={
657  "FullName": HuaweiSensorEntityDescription(
658  key="FullName",
659  translation_key="operator_name",
660  entity_category=EntityCategory.DIAGNOSTIC,
661  ),
663  key="Numeric",
664  translation_key="operator_code",
665  entity_category=EntityCategory.DIAGNOSTIC,
666  ),
668  key="State",
669  translation_key="operator_search_mode",
670  entity_category=EntityCategory.DIAGNOSTIC,
671  ),
672  },
673  ),
674  KEY_NET_NET_MODE: HuaweiSensorGroup(
675  include=re.compile(r"^NetworkMode$", re.IGNORECASE),
676  descriptions={
677  "NetworkMode": HuaweiSensorEntityDescription(
678  key="NetworkMode",
679  translation_key="preferred_network_mode",
680  entity_category=EntityCategory.DIAGNOSTIC,
681  ),
682  },
683  ),
684  #
685  # SMS
686  #
687  KEY_SMS_SMS_COUNT: HuaweiSensorGroup(
688  descriptions={
689  "LocalDeleted": HuaweiSensorEntityDescription(
690  key="LocalDeleted",
691  translation_key="sms_deleted_device",
692  icon="mdi:email-minus",
693  ),
694  "LocalDraft": HuaweiSensorEntityDescription(
695  key="LocalDraft",
696  translation_key="sms_drafts_device",
697  icon="mdi:email-arrow-right-outline",
698  ),
699  "LocalInbox": HuaweiSensorEntityDescription(
700  key="LocalInbox",
701  translation_key="sms_inbox_device",
702  icon="mdi:email",
703  ),
704  "LocalMax": HuaweiSensorEntityDescription(
705  key="LocalMax",
706  translation_key="sms_capacity_device",
707  icon="mdi:email",
708  ),
709  "LocalOutbox": HuaweiSensorEntityDescription(
710  key="LocalOutbox",
711  translation_key="sms_outbox_device",
712  icon="mdi:email-arrow-right",
713  ),
714  "LocalUnread": HuaweiSensorEntityDescription(
715  key="LocalUnread",
716  translation_key="sms_unread_device",
717  icon="mdi:email-arrow-left",
718  ),
719  "SimDraft": HuaweiSensorEntityDescription(
720  key="SimDraft",
721  translation_key="sms_drafts_sim",
722  icon="mdi:email-arrow-right-outline",
723  ),
724  "SimInbox": HuaweiSensorEntityDescription(
725  key="SimInbox",
726  translation_key="sms_inbox_sim",
727  icon="mdi:email",
728  ),
730  key="SimMax",
731  translation_key="sms_capacity_sim",
732  icon="mdi:email",
733  ),
734  "SimOutbox": HuaweiSensorEntityDescription(
735  key="SimOutbox",
736  translation_key="sms_outbox_sim",
737  icon="mdi:email-arrow-right",
738  ),
739  "SimUnread": HuaweiSensorEntityDescription(
740  key="SimUnread",
741  translation_key="sms_unread_sim",
742  icon="mdi:email-arrow-left",
743  ),
745  key="SimUsed",
746  translation_key="sms_messages_sim",
747  icon="mdi:email-arrow-left",
748  ),
749  },
750  ),
751 }
752 
753 
755  hass: HomeAssistant,
756  config_entry: ConfigEntry,
757  async_add_entities: AddEntitiesCallback,
758 ) -> None:
759  """Set up from config entry."""
760  router = hass.data[DOMAIN].routers[config_entry.entry_id]
761  sensors: list[Entity] = []
762  for key in SENSOR_KEYS:
763  if not (items := router.data.get(key)):
764  continue
765  if key_meta := SENSOR_META.get(key):
766  if key_meta.include:
767  items = filter(key_meta.include.search, items)
768  if key_meta.exclude:
769  items = [x for x in items if not key_meta.exclude.search(x)]
770  sensors.extend(
772  router,
773  key,
774  item,
775  SENSOR_META[key].descriptions.get(
776  item, HuaweiSensorEntityDescription(key=item)
777  ),
778  )
779  for item in items
780  )
781 
782  async_add_entities(sensors, True)
783 
784 
786  """Huawei LTE sensor entity."""
787 
788  entity_description: HuaweiSensorEntityDescription
789  _state: StateType = None
790  _unit: str | None = None
791  _last_reset: datetime | None = None
792 
793  def __init__(
794  self,
795  router: Router,
796  key: str,
797  item: str,
798  entity_description: HuaweiSensorEntityDescription,
799  ) -> None:
800  """Initialize."""
801  super().__init__(router)
802  self.keykey = key
803  self.itemitem = item
804  self.entity_descriptionentity_description = entity_description
805 
806  async def async_added_to_hass(self) -> None:
807  """Subscribe to needed data on add."""
808  await super().async_added_to_hass()
809  self.routerrouter.subscriptions[self.keykey].append(f"{SENSOR_DOMAIN}/{self.item}")
810  if self.entity_descriptionentity_description.last_reset_item:
811  self.routerrouter.subscriptions[self.keykey].append(
812  f"{SENSOR_DOMAIN}/{self.entity_description.last_reset_item}"
813  )
814 
815  async def async_will_remove_from_hass(self) -> None:
816  """Unsubscribe from needed data on remove."""
817  await super().async_will_remove_from_hass()
818  self.routerrouter.subscriptions[self.keykey].remove(f"{SENSOR_DOMAIN}/{self.item}")
819  if self.entity_descriptionentity_description.last_reset_item:
820  self.routerrouter.subscriptions[self.keykey].remove(
821  f"{SENSOR_DOMAIN}/{self.entity_description.last_reset_item}"
822  )
823 
824  @property
825  def _device_unique_id(self) -> str:
826  return f"{self.key}.{self.item}"
827 
828  @property
829  def native_value(self) -> StateType:
830  """Return sensor state."""
831  return self._state
832 
833  @property
834  def native_unit_of_measurement(self) -> str | None:
835  """Return sensor's unit of measurement."""
836  return self.entity_descriptionentity_description.native_unit_of_measurement or self._unit_unit
837 
838  @property
839  def icon(self) -> str | None:
840  """Return icon for sensor."""
841  if self.entity_descriptionentity_description.icon_fn:
842  return self.entity_descriptionentity_description.icon_fn(self.statestatestate)
843  return self.entity_descriptionentity_description.icon
844 
845  @property
846  def device_class(self) -> SensorDeviceClass | None:
847  """Return device class for sensor."""
848  if self.entity_descriptionentity_description.device_class_fn:
849  # Note: using self.state could infloop here.
850  return self.entity_descriptionentity_description.device_class_fn(self.native_valuenative_valuenative_value)
851  return super().device_class
852 
853  @property
854  def last_reset(self) -> datetime | None:
855  """Return the time when the sensor was last reset, if any."""
856  return self._last_reset_last_reset
857 
858  async def async_update(self) -> None:
859  """Update state."""
860  try:
861  value = self.routerrouter.data[self.keykey][self.itemitem]
862  except KeyError:
863  _LOGGER.debug("%s[%s] not in data", self.keykey, self.itemitem)
864  value = None
865 
866  last_reset = None
867  if (
868  self.entity_descriptionentity_description.last_reset_item
869  and self.entity_descriptionentity_description.last_reset_format_fn
870  ):
871  try:
872  last_reset_value = self.routerrouter.data[self.keykey][
873  self.entity_descriptionentity_description.last_reset_item
874  ]
875  except KeyError:
876  _LOGGER.debug(
877  "%s[%s] not in data",
878  self.keykey,
879  self.entity_descriptionentity_description.last_reset_item,
880  )
881  else:
882  last_reset = self.entity_descriptionentity_description.last_reset_format_fn(
883  last_reset_value
884  )
885 
886  self._state, self._unit_unit = self.entity_descriptionentity_description.format_fn(value)
887  self._last_reset_last_reset = last_reset
888  self._available_available_available = value is not None
None __init__(self, Router router, str key, str item, HuaweiSensorEntityDescription entity_description)
Definition: sensor.py:799
StateType|date|datetime|Decimal native_value(self)
Definition: __init__.py:460
bool remove(self, _T matcher)
Definition: match.py:214
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
str bandwidth_icon(Sequence[int] limits, StateType value)
Definition: sensor.py:98
tuple[StateType, UnitOfFrequency] format_freq_mhz(StateType value)
Definition: sensor.py:68
tuple[StateType, str|None] format_default(StateType value)
Definition: sensor.py:52
datetime|None format_last_reset_elapsed_seconds(str|None value)
Definition: sensor.py:76
str signal_icon(Sequence[int] limits, StateType value)
Definition: sensor.py:88
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:758