1 """Support for Huawei LTE sensors."""
3 from __future__
import annotations
5 from bisect
import bisect
6 from collections.abc
import Callable, Sequence
7 from dataclasses
import dataclass
8 from datetime
import datetime, timedelta
13 DOMAIN
as SENSOR_DOMAIN,
16 SensorEntityDescription,
36 KEY_DEVICE_INFORMATION,
38 KEY_MONITORING_CHECK_NOTIFICATIONS,
39 KEY_MONITORING_MONTH_STATISTICS,
40 KEY_MONITORING_STATUS,
41 KEY_MONITORING_TRAFFIC_STATISTICS,
47 from .entity
import HuaweiLteBaseEntityWithDevice
49 _LOGGER = logging.getLogger(__name__)
58 r"((&[gl]t;|[><])=?)?(?P<value>.+?)\s*(?P<unit>[a-zA-Z]+)\s*$",
str(value)
61 value =
float(match.group(
"value"))
62 unit = match.group(
"unit")
69 """Format a frequency value for which source is in tenths of MHz."""
71 float(value) / 10
if value
is not None else None,
72 UnitOfFrequency.MEGAHERTZ,
77 """Convert elapsed seconds to last reset datetime."""
81 last_reset = datetime.now() -
timedelta(seconds=
int(value))
82 last_reset.replace(microsecond=0)
88 def signal_icon(limits: Sequence[int], value: StateType) -> str:
89 """Get signal icon."""
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)]
99 """Get bandwidth icon."""
101 "mdi:speedometer-slow",
102 "mdi:speedometer-medium",
104 )[bisect(limits, value
if value
is not None else -1000)]
109 """Class describing Huawei LTE sensor groups."""
111 descriptions: dict[str, HuaweiSensorEntityDescription]
112 include: re.Pattern[str] |
None =
None
113 exclude: re.Pattern[str] |
None =
None
116 @dataclass(frozen=True)
118 """Class describing Huawei LTE sensor entities."""
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
131 SENSOR_META: dict[str, HuaweiSensorGroup] = {
136 include=re.compile(
r"^(WanIP.*Address|uptime)$", re.IGNORECASE),
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,
148 translation_key=
"wan_ip_address",
150 entity_category=EntityCategory.DIAGNOSTIC,
151 entity_registry_enabled_default=
True,
154 key=
"WanIPv6Address",
155 translation_key=
"wan_ipv6_address",
157 entity_category=EntityCategory.DIAGNOSTIC,
168 translation_key=
"arfcn",
169 entity_category=EntityCategory.DIAGNOSTIC,
173 translation_key=
"band",
174 entity_category=EntityCategory.DIAGNOSTIC,
178 translation_key=
"base_station_identity_code",
179 entity_category=EntityCategory.DIAGNOSTIC,
183 translation_key=
"cell_id",
184 icon=
"mdi:transmission-tower",
185 entity_category=EntityCategory.DIAGNOSTIC,
189 translation_key=
"cqi0",
190 icon=
"mdi:speedometer",
191 entity_category=EntityCategory.DIAGNOSTIC,
195 translation_key=
"cqi1",
196 icon=
"mdi:speedometer",
197 entity_category=EntityCategory.DIAGNOSTIC,
201 translation_key=
"downlink_mcs",
202 entity_category=EntityCategory.DIAGNOSTIC,
206 translation_key=
"downlink_bandwidth",
208 entity_category=EntityCategory.DIAGNOSTIC,
212 translation_key=
"downlink_frequency",
213 device_class=SensorDeviceClass.FREQUENCY,
214 entity_category=EntityCategory.DIAGNOSTIC,
218 translation_key=
"earfcn",
219 entity_category=EntityCategory.DIAGNOSTIC,
223 translation_key=
"ecio",
224 device_class=SensorDeviceClass.SIGNAL_STRENGTH,
227 state_class=SensorStateClass.MEASUREMENT,
228 entity_category=EntityCategory.DIAGNOSTIC,
232 translation_key=
"enodeb_id",
233 entity_category=EntityCategory.DIAGNOSTIC,
237 translation_key=
"lac",
238 icon=
"mdi:map-marker",
239 entity_category=EntityCategory.DIAGNOSTIC,
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,
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,
259 translation_key=
"mode",
260 format_fn=
lambda x: (
261 {
"0":
"2G",
"2":
"3G",
"7":
"4G"}.
get(x),
266 "2G":
"mdi:signal-2g",
267 "3G":
"mdi:signal-3g",
268 "4G":
"mdi:signal-4g",
269 }.
get(
str(x),
"mdi:signal")
271 entity_category=EntityCategory.DIAGNOSTIC,
275 translation_key=
"nrbler",
276 entity_category=EntityCategory.DIAGNOSTIC,
280 translation_key=
"nrcqi0",
281 icon=
"mdi:speedometer",
282 entity_category=EntityCategory.DIAGNOSTIC,
286 translation_key=
"nrcqi1",
287 icon=
"mdi:speedometer",
288 entity_category=EntityCategory.DIAGNOSTIC,
292 translation_key=
"nrdlbandwidth",
295 entity_category=EntityCategory.DIAGNOSTIC,
299 translation_key=
"nrdlmcs",
300 entity_category=EntityCategory.DIAGNOSTIC,
304 translation_key=
"nrearfcn",
305 entity_category=EntityCategory.DIAGNOSTIC,
309 translation_key=
"nrrank",
310 entity_category=EntityCategory.DIAGNOSTIC,
314 translation_key=
"nrrsrp",
315 device_class=SensorDeviceClass.SIGNAL_STRENGTH,
317 state_class=SensorStateClass.MEASUREMENT,
318 entity_category=EntityCategory.DIAGNOSTIC,
319 entity_registry_enabled_default=
True,
323 translation_key=
"nrrsrq",
324 device_class=SensorDeviceClass.SIGNAL_STRENGTH,
326 state_class=SensorStateClass.MEASUREMENT,
327 entity_category=EntityCategory.DIAGNOSTIC,
328 entity_registry_enabled_default=
True,
332 translation_key=
"nrsinr",
333 device_class=SensorDeviceClass.SIGNAL_STRENGTH,
335 state_class=SensorStateClass.MEASUREMENT,
336 entity_category=EntityCategory.DIAGNOSTIC,
337 entity_registry_enabled_default=
True,
341 translation_key=
"nrtxpower",
346 device_class_fn=
lambda x: (
347 SensorDeviceClass.SIGNAL_STRENGTH
348 if isinstance(x, (float, int))
351 entity_category=EntityCategory.DIAGNOSTIC,
355 translation_key=
"nrulbandwidth",
357 entity_category=EntityCategory.DIAGNOSTIC,
361 translation_key=
"nrulmcs",
362 entity_category=EntityCategory.DIAGNOSTIC,
366 translation_key=
"pci",
367 icon=
"mdi:transmission-tower",
368 entity_category=EntityCategory.DIAGNOSTIC,
372 translation_key=
"plmn",
373 entity_category=EntityCategory.DIAGNOSTIC,
377 translation_key=
"rac",
378 icon=
"mdi:map-marker",
379 entity_category=EntityCategory.DIAGNOSTIC,
383 translation_key=
"rrc_status",
384 entity_category=EntityCategory.DIAGNOSTIC,
388 translation_key=
"rscp",
389 device_class=SensorDeviceClass.SIGNAL_STRENGTH,
392 state_class=SensorStateClass.MEASUREMENT,
393 entity_category=EntityCategory.DIAGNOSTIC,
397 translation_key=
"rsrp",
398 device_class=SensorDeviceClass.SIGNAL_STRENGTH,
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,
407 translation_key=
"rsrq",
408 device_class=SensorDeviceClass.SIGNAL_STRENGTH,
411 state_class=SensorStateClass.MEASUREMENT,
412 entity_category=EntityCategory.DIAGNOSTIC,
413 entity_registry_enabled_default=
True,
417 translation_key=
"rssi",
418 device_class=SensorDeviceClass.SIGNAL_STRENGTH,
421 state_class=SensorStateClass.MEASUREMENT,
422 entity_category=EntityCategory.DIAGNOSTIC,
423 entity_registry_enabled_default=
True,
427 translation_key=
"sinr",
428 device_class=SensorDeviceClass.SIGNAL_STRENGTH,
431 state_class=SensorStateClass.MEASUREMENT,
432 entity_category=EntityCategory.DIAGNOSTIC,
433 entity_registry_enabled_default=
True,
437 translation_key=
"tac",
438 icon=
"mdi:map-marker",
439 entity_category=EntityCategory.DIAGNOSTIC,
443 translation_key=
"tdd",
444 entity_category=EntityCategory.DIAGNOSTIC,
448 translation_key=
"transmission_mode",
449 entity_category=EntityCategory.DIAGNOSTIC,
453 translation_key=
"transmit_power",
458 device_class_fn=
lambda x: (
459 SensorDeviceClass.SIGNAL_STRENGTH
460 if isinstance(x, (float, int))
463 entity_category=EntityCategory.DIAGNOSTIC,
467 translation_key=
"uplink_mcs",
468 entity_category=EntityCategory.DIAGNOSTIC,
472 translation_key=
"uplink_bandwidth",
474 entity_category=EntityCategory.DIAGNOSTIC,
478 translation_key=
"uplink_frequency",
479 device_class=SensorDeviceClass.FREQUENCY,
480 entity_category=EntityCategory.DIAGNOSTIC,
489 r"^(onlineupdatestatus|smsstoragefull)$",
495 translation_key=
"sms_unread",
496 icon=
"mdi:email-arrow-left",
502 r"^(currentday|month)(duration|lastcleartime)$", re.IGNORECASE
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,
516 key=
"CurrentMonthDownload",
517 translation_key=
"current_month_download",
518 native_unit_of_measurement=UnitOfInformation.BYTES,
519 device_class=SensorDeviceClass.DATA_SIZE,
521 state_class=SensorStateClass.TOTAL,
522 last_reset_item=
"MonthDuration",
523 last_reset_format_fn=format_last_reset_elapsed_seconds,
526 key=
"CurrentMonthUpload",
527 translation_key=
"current_month_upload",
528 native_unit_of_measurement=UnitOfInformation.BYTES,
529 device_class=SensorDeviceClass.DATA_SIZE,
531 state_class=SensorStateClass.TOTAL,
532 last_reset_item=
"MonthDuration",
533 last_reset_format_fn=format_last_reset_elapsed_seconds,
539 r"^(batterypercent|currentwifiuser|(primary|secondary).*dns)$",
544 key=
"BatteryPercent",
545 device_class=SensorDeviceClass.BATTERY,
546 native_unit_of_measurement=PERCENTAGE,
547 state_class=SensorStateClass.MEASUREMENT,
548 entity_category=EntityCategory.DIAGNOSTIC,
551 key=
"CurrentWifiUser",
552 translation_key=
"wifi_clients_connected",
554 state_class=SensorStateClass.MEASUREMENT,
555 entity_category=EntityCategory.DIAGNOSTIC,
559 translation_key=
"primary_dns_server",
561 entity_category=EntityCategory.DIAGNOSTIC,
564 key=
"PrimaryIPv6Dns",
565 translation_key=
"primary_ipv6_dns_server",
567 entity_category=EntityCategory.DIAGNOSTIC,
571 translation_key=
"secondary_dns_server",
573 entity_category=EntityCategory.DIAGNOSTIC,
576 key=
"SecondaryIPv6Dns",
577 translation_key=
"secondary_ipv6_dns_server",
579 entity_category=EntityCategory.DIAGNOSTIC,
584 exclude=re.compile(
r"^showtraffic$", re.IGNORECASE),
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",
594 key=
"CurrentDownload",
595 translation_key=
"current_connection_download",
596 native_unit_of_measurement=UnitOfInformation.BYTES,
597 device_class=SensorDeviceClass.DATA_SIZE,
599 state_class=SensorStateClass.TOTAL_INCREASING,
602 key=
"CurrentDownloadRate",
603 translation_key=
"current_download_rate",
604 native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
605 device_class=SensorDeviceClass.DATA_RATE,
607 state_class=SensorStateClass.MEASUREMENT,
611 translation_key=
"current_connection_upload",
612 native_unit_of_measurement=UnitOfInformation.BYTES,
613 device_class=SensorDeviceClass.DATA_SIZE,
615 state_class=SensorStateClass.TOTAL_INCREASING,
618 key=
"CurrentUploadRate",
619 translation_key=
"current_upload_rate",
620 native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
621 device_class=SensorDeviceClass.DATA_RATE,
623 state_class=SensorStateClass.MEASUREMENT,
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,
635 translation_key=
"total_download",
636 native_unit_of_measurement=UnitOfInformation.BYTES,
637 device_class=SensorDeviceClass.DATA_SIZE,
639 state_class=SensorStateClass.TOTAL_INCREASING,
643 translation_key=
"total_upload",
644 native_unit_of_measurement=UnitOfInformation.BYTES,
645 device_class=SensorDeviceClass.DATA_SIZE,
647 state_class=SensorStateClass.TOTAL_INCREASING,
655 exclude=re.compile(
r"^(Rat|ShortName|Spn)$", re.IGNORECASE),
659 translation_key=
"operator_name",
660 entity_category=EntityCategory.DIAGNOSTIC,
664 translation_key=
"operator_code",
665 entity_category=EntityCategory.DIAGNOSTIC,
669 translation_key=
"operator_search_mode",
670 entity_category=EntityCategory.DIAGNOSTIC,
675 include=re.compile(
r"^NetworkMode$", re.IGNORECASE),
679 translation_key=
"preferred_network_mode",
680 entity_category=EntityCategory.DIAGNOSTIC,
691 translation_key=
"sms_deleted_device",
692 icon=
"mdi:email-minus",
696 translation_key=
"sms_drafts_device",
697 icon=
"mdi:email-arrow-right-outline",
701 translation_key=
"sms_inbox_device",
706 translation_key=
"sms_capacity_device",
711 translation_key=
"sms_outbox_device",
712 icon=
"mdi:email-arrow-right",
716 translation_key=
"sms_unread_device",
717 icon=
"mdi:email-arrow-left",
721 translation_key=
"sms_drafts_sim",
722 icon=
"mdi:email-arrow-right-outline",
726 translation_key=
"sms_inbox_sim",
731 translation_key=
"sms_capacity_sim",
736 translation_key=
"sms_outbox_sim",
737 icon=
"mdi:email-arrow-right",
741 translation_key=
"sms_unread_sim",
742 icon=
"mdi:email-arrow-left",
746 translation_key=
"sms_messages_sim",
747 icon=
"mdi:email-arrow-left",
756 config_entry: ConfigEntry,
757 async_add_entities: AddEntitiesCallback,
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)):
765 if key_meta := SENSOR_META.get(key):
767 items = filter(key_meta.include.search, items)
769 items = [x
for x
in items
if not key_meta.exclude.search(x)]
775 SENSOR_META[key].descriptions.get(
786 """Huawei LTE sensor entity."""
788 entity_description: HuaweiSensorEntityDescription
789 _state: StateType =
None
790 _unit: str |
None =
None
791 _last_reset: datetime |
None =
None
798 entity_description: HuaweiSensorEntityDescription,
807 """Subscribe to needed data on add."""
809 self.
routerrouter.subscriptions[self.
keykey].append(f
"{SENSOR_DOMAIN}/{self.item}")
811 self.
routerrouter.subscriptions[self.
keykey].append(
812 f
"{SENSOR_DOMAIN}/{self.entity_description.last_reset_item}"
816 """Unsubscribe from needed data on remove."""
818 self.
routerrouter.subscriptions[self.
keykey].
remove(f
"{SENSOR_DOMAIN}/{self.item}")
821 f
"{SENSOR_DOMAIN}/{self.entity_description.last_reset_item}"
826 return f
"{self.key}.{self.item}"
830 """Return sensor state."""
835 """Return sensor's unit of measurement."""
840 """Return icon for sensor."""
847 """Return device class for sensor."""
851 return super().device_class
855 """Return the time when the sensor was last reset, if any."""
863 _LOGGER.debug(
"%s[%s] not in data", self.
keykey, self.
itemitem)
872 last_reset_value = self.
routerrouter.data[self.
keykey][
877 "%s[%s] not in data",
SensorDeviceClass|None device_class(self)
str|None native_unit_of_measurement(self)
None async_added_to_hass(self)
StateType native_value(self)
str _device_unique_id(self)
None async_will_remove_from_hass(self)
datetime|None last_reset(self)
None __init__(self, Router router, str key, str item, HuaweiSensorEntityDescription entity_description)
StateType|date|datetime|Decimal native_value(self)
bool remove(self, _T matcher)
web.Response get(self, web.Request request, str config_key)
str bandwidth_icon(Sequence[int] limits, StateType value)
tuple[StateType, UnitOfFrequency] format_freq_mhz(StateType value)
tuple[StateType, str|None] format_default(StateType value)
datetime|None format_last_reset_elapsed_seconds(str|None value)
str signal_icon(Sequence[int] limits, StateType value)
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)