889 lines
34 KiB
Python
889 lines
34 KiB
Python
"""Support for Huawei LTE sensors."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from bisect import bisect
|
|
from collections.abc import Callable, Sequence
|
|
from dataclasses import dataclass
|
|
from datetime import datetime, timedelta
|
|
import logging
|
|
import re
|
|
|
|
from homeassistant.components.sensor import (
|
|
DOMAIN as SENSOR_DOMAIN,
|
|
SensorDeviceClass,
|
|
SensorEntity,
|
|
SensorEntityDescription,
|
|
SensorStateClass,
|
|
)
|
|
from homeassistant.config_entries import ConfigEntry
|
|
from homeassistant.const import (
|
|
PERCENTAGE,
|
|
EntityCategory,
|
|
UnitOfDataRate,
|
|
UnitOfFrequency,
|
|
UnitOfInformation,
|
|
UnitOfTime,
|
|
)
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers.entity import Entity
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
from homeassistant.helpers.typing import StateType
|
|
|
|
from . import Router
|
|
from .const import (
|
|
DOMAIN,
|
|
KEY_DEVICE_INFORMATION,
|
|
KEY_DEVICE_SIGNAL,
|
|
KEY_MONITORING_CHECK_NOTIFICATIONS,
|
|
KEY_MONITORING_MONTH_STATISTICS,
|
|
KEY_MONITORING_STATUS,
|
|
KEY_MONITORING_TRAFFIC_STATISTICS,
|
|
KEY_NET_CURRENT_PLMN,
|
|
KEY_NET_NET_MODE,
|
|
KEY_SMS_SMS_COUNT,
|
|
SENSOR_KEYS,
|
|
)
|
|
from .entity import HuaweiLteBaseEntityWithDevice
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
def format_default(value: StateType) -> tuple[StateType, str | None]:
|
|
"""Format value."""
|
|
unit = None
|
|
if value is not None:
|
|
# Clean up value and infer unit, e.g. -71dBm, 15 dB
|
|
if match := re.match(
|
|
r"((&[gl]t;|[><])=?)?(?P<value>.+?)\s*(?P<unit>[a-zA-Z]+)\s*$", str(value)
|
|
):
|
|
try:
|
|
value = float(match.group("value"))
|
|
unit = match.group("unit")
|
|
except ValueError:
|
|
pass
|
|
return value, unit
|
|
|
|
|
|
def format_freq_mhz(value: StateType) -> tuple[StateType, UnitOfFrequency]:
|
|
"""Format a frequency value for which source is in tenths of MHz."""
|
|
return (
|
|
float(value) / 10 if value is not None else None,
|
|
UnitOfFrequency.MEGAHERTZ,
|
|
)
|
|
|
|
|
|
def format_last_reset_elapsed_seconds(value: str | None) -> datetime | None:
|
|
"""Convert elapsed seconds to last reset datetime."""
|
|
if value is None:
|
|
return None
|
|
try:
|
|
last_reset = datetime.now() - timedelta(seconds=int(value))
|
|
last_reset.replace(microsecond=0)
|
|
except ValueError:
|
|
return None
|
|
return last_reset
|
|
|
|
|
|
def signal_icon(limits: Sequence[int], value: StateType) -> str:
|
|
"""Get signal icon."""
|
|
return (
|
|
"mdi:signal-cellular-outline",
|
|
"mdi:signal-cellular-1",
|
|
"mdi:signal-cellular-2",
|
|
"mdi:signal-cellular-3",
|
|
)[bisect(limits, value if value is not None else -1000)]
|
|
|
|
|
|
def bandwidth_icon(limits: Sequence[int], value: StateType) -> str:
|
|
"""Get bandwidth icon."""
|
|
return (
|
|
"mdi:speedometer-slow",
|
|
"mdi:speedometer-medium",
|
|
"mdi:speedometer",
|
|
)[bisect(limits, value if value is not None else -1000)]
|
|
|
|
|
|
@dataclass
|
|
class HuaweiSensorGroup:
|
|
"""Class describing Huawei LTE sensor groups."""
|
|
|
|
descriptions: dict[str, HuaweiSensorEntityDescription]
|
|
include: re.Pattern[str] | None = None
|
|
exclude: re.Pattern[str] | None = None
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class HuaweiSensorEntityDescription(SensorEntityDescription):
|
|
"""Class describing Huawei LTE sensor entities."""
|
|
|
|
# HuaweiLteSensor does not support UNDEFINED or None,
|
|
# restrict the type to str.
|
|
name: str = ""
|
|
|
|
format_fn: Callable[[str], tuple[StateType, str | None]] = format_default
|
|
icon_fn: Callable[[StateType], str] | None = None
|
|
device_class_fn: Callable[[StateType], SensorDeviceClass | None] | None = None
|
|
last_reset_item: str | None = None
|
|
last_reset_format_fn: Callable[[str | None], datetime | None] | None = None
|
|
|
|
|
|
SENSOR_META: dict[str, HuaweiSensorGroup] = {
|
|
#
|
|
# Device information
|
|
#
|
|
KEY_DEVICE_INFORMATION: HuaweiSensorGroup(
|
|
include=re.compile(r"^(WanIP.*Address|uptime)$", re.IGNORECASE),
|
|
descriptions={
|
|
"uptime": HuaweiSensorEntityDescription(
|
|
key="uptime",
|
|
translation_key="uptime",
|
|
icon="mdi:timer-outline",
|
|
native_unit_of_measurement=UnitOfTime.SECONDS,
|
|
device_class=SensorDeviceClass.DURATION,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"WanIPAddress": HuaweiSensorEntityDescription(
|
|
key="WanIPAddress",
|
|
translation_key="wan_ip_address",
|
|
icon="mdi:ip",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
entity_registry_enabled_default=True,
|
|
),
|
|
"WanIPv6Address": HuaweiSensorEntityDescription(
|
|
key="WanIPv6Address",
|
|
translation_key="wan_ipv6_address",
|
|
icon="mdi:ip",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
},
|
|
),
|
|
#
|
|
# Signal
|
|
#
|
|
KEY_DEVICE_SIGNAL: HuaweiSensorGroup(
|
|
descriptions={
|
|
"arfcn": HuaweiSensorEntityDescription(
|
|
key="arfcn",
|
|
translation_key="arfcn",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"band": HuaweiSensorEntityDescription(
|
|
key="band",
|
|
translation_key="band",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"bsic": HuaweiSensorEntityDescription(
|
|
key="bsic",
|
|
translation_key="base_station_identity_code",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"cell_id": HuaweiSensorEntityDescription(
|
|
key="cell_id",
|
|
translation_key="cell_id",
|
|
icon="mdi:transmission-tower",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"cqi0": HuaweiSensorEntityDescription(
|
|
key="cqi0",
|
|
translation_key="cqi0",
|
|
icon="mdi:speedometer",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"cqi1": HuaweiSensorEntityDescription(
|
|
key="cqi1",
|
|
translation_key="cqi1",
|
|
icon="mdi:speedometer",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"dl_mcs": HuaweiSensorEntityDescription(
|
|
key="dl_mcs",
|
|
translation_key="downlink_mcs",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"dlbandwidth": HuaweiSensorEntityDescription(
|
|
key="dlbandwidth",
|
|
translation_key="downlink_bandwidth",
|
|
icon_fn=lambda x: bandwidth_icon((8, 15), x),
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"dlfrequency": HuaweiSensorEntityDescription(
|
|
key="dlfrequency",
|
|
translation_key="downlink_frequency",
|
|
device_class=SensorDeviceClass.FREQUENCY,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"earfcn": HuaweiSensorEntityDescription(
|
|
key="earfcn",
|
|
translation_key="earfcn",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"ecio": HuaweiSensorEntityDescription(
|
|
key="ecio",
|
|
translation_key="ecio",
|
|
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
|
# https://wiki.teltonika.lt/view/EC/IO
|
|
icon_fn=lambda x: signal_icon((-20, -10, -6), x),
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"enodeb_id": HuaweiSensorEntityDescription(
|
|
key="enodeb_id",
|
|
translation_key="enodeb_id",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"lac": HuaweiSensorEntityDescription(
|
|
key="lac",
|
|
translation_key="lac",
|
|
icon="mdi:map-marker",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"ltedlfreq": HuaweiSensorEntityDescription(
|
|
key="ltedlfreq",
|
|
translation_key="lte_downlink_frequency",
|
|
format_fn=format_freq_mhz,
|
|
suggested_display_precision=0,
|
|
device_class=SensorDeviceClass.FREQUENCY,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"lteulfreq": HuaweiSensorEntityDescription(
|
|
key="lteulfreq",
|
|
translation_key="lte_uplink_frequency",
|
|
format_fn=format_freq_mhz,
|
|
suggested_display_precision=0,
|
|
device_class=SensorDeviceClass.FREQUENCY,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"mode": HuaweiSensorEntityDescription(
|
|
key="mode",
|
|
translation_key="mode",
|
|
format_fn=lambda x: (
|
|
{"0": "2G", "2": "3G", "7": "4G"}.get(x),
|
|
None,
|
|
),
|
|
icon_fn=lambda x: (
|
|
{
|
|
"2G": "mdi:signal-2g",
|
|
"3G": "mdi:signal-3g",
|
|
"4G": "mdi:signal-4g",
|
|
}.get(str(x), "mdi:signal")
|
|
),
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"nrbler": HuaweiSensorEntityDescription(
|
|
key="nrbler",
|
|
translation_key="nrbler",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"nrcqi0": HuaweiSensorEntityDescription(
|
|
key="nrcqi0",
|
|
translation_key="nrcqi0",
|
|
icon="mdi:speedometer",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"nrcqi1": HuaweiSensorEntityDescription(
|
|
key="nrcqi1",
|
|
translation_key="nrcqi1",
|
|
icon="mdi:speedometer",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"nrdlbandwidth": HuaweiSensorEntityDescription(
|
|
key="nrdlbandwidth",
|
|
translation_key="nrdlbandwidth",
|
|
# Could add icon_fn like we have for dlbandwidth,
|
|
# if we find a good source what to use as 5G thresholds.
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"nrdlmcs": HuaweiSensorEntityDescription(
|
|
key="nrdlmcs",
|
|
translation_key="nrdlmcs",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"nrearfcn": HuaweiSensorEntityDescription(
|
|
key="nrearfcn",
|
|
translation_key="nrearfcn",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"nrrank": HuaweiSensorEntityDescription(
|
|
key="nrrank",
|
|
translation_key="nrrank",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"nrrsrp": HuaweiSensorEntityDescription(
|
|
key="nrrsrp",
|
|
translation_key="nrrsrp",
|
|
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
|
# Could add icon_fn as in rsrp, source for 5G thresholds?
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
entity_registry_enabled_default=True,
|
|
),
|
|
"nrrsrq": HuaweiSensorEntityDescription(
|
|
key="nrrsrq",
|
|
translation_key="nrrsrq",
|
|
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
|
# Could add icon_fn as in rsrq, source for 5G thresholds?
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
entity_registry_enabled_default=True,
|
|
),
|
|
"nrsinr": HuaweiSensorEntityDescription(
|
|
key="nrsinr",
|
|
translation_key="nrsinr",
|
|
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
|
# Could add icon_fn as in sinr, source for thresholds?
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
entity_registry_enabled_default=True,
|
|
),
|
|
"nrtxpower": HuaweiSensorEntityDescription(
|
|
key="nrtxpower",
|
|
translation_key="nrtxpower",
|
|
# The value we get from the API tends to consist of several, e.g.
|
|
# PPusch:21dBm PPucch:2dBm PSrs:0dBm PPrach:10dBm
|
|
# Present as SIGNAL_STRENGTH only if it was parsed to a number.
|
|
# We could try to parse this to separate component sensors sometime.
|
|
device_class_fn=lambda x: (
|
|
SensorDeviceClass.SIGNAL_STRENGTH
|
|
if isinstance(x, (float, int))
|
|
else None
|
|
),
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"nrulbandwidth": HuaweiSensorEntityDescription(
|
|
key="nrulbandwidth",
|
|
translation_key="nrulbandwidth",
|
|
# Could add icon_fn as in ulbandwidth, source for 5G thresholds?
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"nrulmcs": HuaweiSensorEntityDescription(
|
|
key="nrulmcs",
|
|
translation_key="nrulmcs",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"pci": HuaweiSensorEntityDescription(
|
|
key="pci",
|
|
translation_key="pci",
|
|
icon="mdi:transmission-tower",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"plmn": HuaweiSensorEntityDescription(
|
|
key="plmn",
|
|
translation_key="plmn",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"rac": HuaweiSensorEntityDescription(
|
|
key="rac",
|
|
translation_key="rac",
|
|
icon="mdi:map-marker",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"rrc_status": HuaweiSensorEntityDescription(
|
|
key="rrc_status",
|
|
translation_key="rrc_status",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"rscp": HuaweiSensorEntityDescription(
|
|
key="rscp",
|
|
translation_key="rscp",
|
|
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
|
# https://wiki.teltonika.lt/view/RSCP
|
|
icon_fn=lambda x: signal_icon((-95, -85, -75), x),
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"rsrp": HuaweiSensorEntityDescription(
|
|
key="rsrp",
|
|
translation_key="rsrp",
|
|
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
|
# http://www.lte-anbieter.info/technik/rsrp.php # codespell:ignore technik
|
|
icon_fn=lambda x: signal_icon((-110, -95, -80), x),
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
entity_registry_enabled_default=True,
|
|
),
|
|
"rsrq": HuaweiSensorEntityDescription(
|
|
key="rsrq",
|
|
translation_key="rsrq",
|
|
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
|
# http://www.lte-anbieter.info/technik/rsrq.php # codespell:ignore technik
|
|
icon_fn=lambda x: signal_icon((-11, -8, -5), x),
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
entity_registry_enabled_default=True,
|
|
),
|
|
"rssi": HuaweiSensorEntityDescription(
|
|
key="rssi",
|
|
translation_key="rssi",
|
|
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
|
# https://eyesaas.com/wi-fi-signal-strength/
|
|
icon_fn=lambda x: signal_icon((-80, -70, -60), x),
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
entity_registry_enabled_default=True,
|
|
),
|
|
"sinr": HuaweiSensorEntityDescription(
|
|
key="sinr",
|
|
translation_key="sinr",
|
|
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
|
# http://www.lte-anbieter.info/technik/sinr.php # codespell:ignore technik
|
|
icon_fn=lambda x: signal_icon((0, 5, 10), x),
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
entity_registry_enabled_default=True,
|
|
),
|
|
"tac": HuaweiSensorEntityDescription(
|
|
key="tac",
|
|
translation_key="tac",
|
|
icon="mdi:map-marker",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"tdd": HuaweiSensorEntityDescription(
|
|
key="tdd",
|
|
translation_key="tdd",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"transmode": HuaweiSensorEntityDescription(
|
|
key="transmode",
|
|
translation_key="transmission_mode",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"txpower": HuaweiSensorEntityDescription(
|
|
key="txpower",
|
|
translation_key="transmit_power",
|
|
# The value we get from the API tends to consist of several, e.g.
|
|
# PPusch:15dBm PPucch:2dBm PSrs:42dBm PPrach:1dBm
|
|
# Present as SIGNAL_STRENGTH only if it was parsed to a number.
|
|
# We could try to parse this to separate component sensors sometime.
|
|
device_class_fn=lambda x: (
|
|
SensorDeviceClass.SIGNAL_STRENGTH
|
|
if isinstance(x, (float, int))
|
|
else None
|
|
),
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"ul_mcs": HuaweiSensorEntityDescription(
|
|
key="ul_mcs",
|
|
translation_key="uplink_mcs",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"ulbandwidth": HuaweiSensorEntityDescription(
|
|
key="ulbandwidth",
|
|
translation_key="uplink_bandwidth",
|
|
icon_fn=lambda x: bandwidth_icon((8, 15), x),
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"ulfrequency": HuaweiSensorEntityDescription(
|
|
key="ulfrequency",
|
|
translation_key="uplink_frequency",
|
|
device_class=SensorDeviceClass.FREQUENCY,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
}
|
|
),
|
|
#
|
|
# Monitoring
|
|
#
|
|
KEY_MONITORING_CHECK_NOTIFICATIONS: HuaweiSensorGroup(
|
|
exclude=re.compile(
|
|
r"^(onlineupdatestatus|smsstoragefull)$",
|
|
re.IGNORECASE,
|
|
),
|
|
descriptions={
|
|
"UnreadMessage": HuaweiSensorEntityDescription(
|
|
key="UnreadMessage",
|
|
translation_key="sms_unread",
|
|
icon="mdi:email-arrow-left",
|
|
),
|
|
},
|
|
),
|
|
KEY_MONITORING_MONTH_STATISTICS: HuaweiSensorGroup(
|
|
exclude=re.compile(
|
|
r"^(currentday|month)(duration|lastcleartime)$", re.IGNORECASE
|
|
),
|
|
descriptions={
|
|
"CurrentDayUsed": HuaweiSensorEntityDescription(
|
|
key="CurrentDayUsed",
|
|
translation_key="current_day_transfer",
|
|
native_unit_of_measurement=UnitOfInformation.BYTES,
|
|
device_class=SensorDeviceClass.DATA_SIZE,
|
|
icon="mdi:arrow-up-down-bold",
|
|
state_class=SensorStateClass.TOTAL,
|
|
last_reset_item="CurrentDayDuration",
|
|
last_reset_format_fn=format_last_reset_elapsed_seconds,
|
|
),
|
|
"CurrentMonthDownload": HuaweiSensorEntityDescription(
|
|
key="CurrentMonthDownload",
|
|
translation_key="current_month_download",
|
|
native_unit_of_measurement=UnitOfInformation.BYTES,
|
|
device_class=SensorDeviceClass.DATA_SIZE,
|
|
icon="mdi:download",
|
|
state_class=SensorStateClass.TOTAL,
|
|
last_reset_item="MonthDuration",
|
|
last_reset_format_fn=format_last_reset_elapsed_seconds,
|
|
),
|
|
"CurrentMonthUpload": HuaweiSensorEntityDescription(
|
|
key="CurrentMonthUpload",
|
|
translation_key="current_month_upload",
|
|
native_unit_of_measurement=UnitOfInformation.BYTES,
|
|
device_class=SensorDeviceClass.DATA_SIZE,
|
|
icon="mdi:upload",
|
|
state_class=SensorStateClass.TOTAL,
|
|
last_reset_item="MonthDuration",
|
|
last_reset_format_fn=format_last_reset_elapsed_seconds,
|
|
),
|
|
},
|
|
),
|
|
KEY_MONITORING_STATUS: HuaweiSensorGroup(
|
|
include=re.compile(
|
|
r"^(batterypercent|currentwifiuser|(primary|secondary).*dns)$",
|
|
re.IGNORECASE,
|
|
),
|
|
descriptions={
|
|
"BatteryPercent": HuaweiSensorEntityDescription(
|
|
key="BatteryPercent",
|
|
device_class=SensorDeviceClass.BATTERY,
|
|
native_unit_of_measurement=PERCENTAGE,
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"CurrentWifiUser": HuaweiSensorEntityDescription(
|
|
key="CurrentWifiUser",
|
|
translation_key="wifi_clients_connected",
|
|
icon="mdi:wifi",
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"PrimaryDns": HuaweiSensorEntityDescription(
|
|
key="PrimaryDns",
|
|
translation_key="primary_dns_server",
|
|
icon="mdi:ip",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"PrimaryIPv6Dns": HuaweiSensorEntityDescription(
|
|
key="PrimaryIPv6Dns",
|
|
translation_key="primary_ipv6_dns_server",
|
|
icon="mdi:ip",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"SecondaryDns": HuaweiSensorEntityDescription(
|
|
key="SecondaryDns",
|
|
translation_key="secondary_dns_server",
|
|
icon="mdi:ip",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"SecondaryIPv6Dns": HuaweiSensorEntityDescription(
|
|
key="SecondaryIPv6Dns",
|
|
translation_key="secondary_ipv6_dns_server",
|
|
icon="mdi:ip",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
},
|
|
),
|
|
KEY_MONITORING_TRAFFIC_STATISTICS: HuaweiSensorGroup(
|
|
exclude=re.compile(r"^showtraffic$", re.IGNORECASE),
|
|
descriptions={
|
|
"CurrentConnectTime": HuaweiSensorEntityDescription(
|
|
key="CurrentConnectTime",
|
|
translation_key="current_connection_duration",
|
|
native_unit_of_measurement=UnitOfTime.SECONDS,
|
|
device_class=SensorDeviceClass.DURATION,
|
|
icon="mdi:timer-outline",
|
|
),
|
|
"CurrentDownload": HuaweiSensorEntityDescription(
|
|
key="CurrentDownload",
|
|
translation_key="current_connection_download",
|
|
native_unit_of_measurement=UnitOfInformation.BYTES,
|
|
device_class=SensorDeviceClass.DATA_SIZE,
|
|
icon="mdi:download",
|
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
|
),
|
|
"CurrentDownloadRate": HuaweiSensorEntityDescription(
|
|
key="CurrentDownloadRate",
|
|
translation_key="current_download_rate",
|
|
native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
|
|
device_class=SensorDeviceClass.DATA_RATE,
|
|
icon="mdi:download",
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
),
|
|
"CurrentUpload": HuaweiSensorEntityDescription(
|
|
key="CurrentUpload",
|
|
translation_key="current_connection_upload",
|
|
native_unit_of_measurement=UnitOfInformation.BYTES,
|
|
device_class=SensorDeviceClass.DATA_SIZE,
|
|
icon="mdi:upload",
|
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
|
),
|
|
"CurrentUploadRate": HuaweiSensorEntityDescription(
|
|
key="CurrentUploadRate",
|
|
translation_key="current_upload_rate",
|
|
native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
|
|
device_class=SensorDeviceClass.DATA_RATE,
|
|
icon="mdi:upload",
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
),
|
|
"TotalConnectTime": HuaweiSensorEntityDescription(
|
|
key="TotalConnectTime",
|
|
translation_key="total_connected_duration",
|
|
native_unit_of_measurement=UnitOfTime.SECONDS,
|
|
device_class=SensorDeviceClass.DURATION,
|
|
icon="mdi:timer-outline",
|
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
|
),
|
|
"TotalDownload": HuaweiSensorEntityDescription(
|
|
key="TotalDownload",
|
|
translation_key="total_download",
|
|
native_unit_of_measurement=UnitOfInformation.BYTES,
|
|
device_class=SensorDeviceClass.DATA_SIZE,
|
|
icon="mdi:download",
|
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
|
),
|
|
"TotalUpload": HuaweiSensorEntityDescription(
|
|
key="TotalUpload",
|
|
translation_key="total_upload",
|
|
native_unit_of_measurement=UnitOfInformation.BYTES,
|
|
device_class=SensorDeviceClass.DATA_SIZE,
|
|
icon="mdi:upload",
|
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
|
),
|
|
},
|
|
),
|
|
#
|
|
# Network
|
|
#
|
|
KEY_NET_CURRENT_PLMN: HuaweiSensorGroup(
|
|
exclude=re.compile(r"^(Rat|ShortName|Spn)$", re.IGNORECASE),
|
|
descriptions={
|
|
"FullName": HuaweiSensorEntityDescription(
|
|
key="FullName",
|
|
translation_key="operator_name",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"Numeric": HuaweiSensorEntityDescription(
|
|
key="Numeric",
|
|
translation_key="operator_code",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
"State": HuaweiSensorEntityDescription(
|
|
key="State",
|
|
translation_key="operator_search_mode",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
},
|
|
),
|
|
KEY_NET_NET_MODE: HuaweiSensorGroup(
|
|
include=re.compile(r"^NetworkMode$", re.IGNORECASE),
|
|
descriptions={
|
|
"NetworkMode": HuaweiSensorEntityDescription(
|
|
key="NetworkMode",
|
|
translation_key="preferred_network_mode",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
},
|
|
),
|
|
#
|
|
# SMS
|
|
#
|
|
KEY_SMS_SMS_COUNT: HuaweiSensorGroup(
|
|
descriptions={
|
|
"LocalDeleted": HuaweiSensorEntityDescription(
|
|
key="LocalDeleted",
|
|
translation_key="sms_deleted_device",
|
|
icon="mdi:email-minus",
|
|
),
|
|
"LocalDraft": HuaweiSensorEntityDescription(
|
|
key="LocalDraft",
|
|
translation_key="sms_drafts_device",
|
|
icon="mdi:email-arrow-right-outline",
|
|
),
|
|
"LocalInbox": HuaweiSensorEntityDescription(
|
|
key="LocalInbox",
|
|
translation_key="sms_inbox_device",
|
|
icon="mdi:email",
|
|
),
|
|
"LocalMax": HuaweiSensorEntityDescription(
|
|
key="LocalMax",
|
|
translation_key="sms_capacity_device",
|
|
icon="mdi:email",
|
|
),
|
|
"LocalOutbox": HuaweiSensorEntityDescription(
|
|
key="LocalOutbox",
|
|
translation_key="sms_outbox_device",
|
|
icon="mdi:email-arrow-right",
|
|
),
|
|
"LocalUnread": HuaweiSensorEntityDescription(
|
|
key="LocalUnread",
|
|
translation_key="sms_unread_device",
|
|
icon="mdi:email-arrow-left",
|
|
),
|
|
"SimDraft": HuaweiSensorEntityDescription(
|
|
key="SimDraft",
|
|
translation_key="sms_drafts_sim",
|
|
icon="mdi:email-arrow-right-outline",
|
|
),
|
|
"SimInbox": HuaweiSensorEntityDescription(
|
|
key="SimInbox",
|
|
translation_key="sms_inbox_sim",
|
|
icon="mdi:email",
|
|
),
|
|
"SimMax": HuaweiSensorEntityDescription(
|
|
key="SimMax",
|
|
translation_key="sms_capacity_sim",
|
|
icon="mdi:email",
|
|
),
|
|
"SimOutbox": HuaweiSensorEntityDescription(
|
|
key="SimOutbox",
|
|
translation_key="sms_outbox_sim",
|
|
icon="mdi:email-arrow-right",
|
|
),
|
|
"SimUnread": HuaweiSensorEntityDescription(
|
|
key="SimUnread",
|
|
translation_key="sms_unread_sim",
|
|
icon="mdi:email-arrow-left",
|
|
),
|
|
"SimUsed": HuaweiSensorEntityDescription(
|
|
key="SimUsed",
|
|
translation_key="sms_messages_sim",
|
|
icon="mdi:email-arrow-left",
|
|
),
|
|
},
|
|
),
|
|
}
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
config_entry: ConfigEntry,
|
|
async_add_entities: AddEntitiesCallback,
|
|
) -> None:
|
|
"""Set up from config entry."""
|
|
router = hass.data[DOMAIN].routers[config_entry.entry_id]
|
|
sensors: list[Entity] = []
|
|
for key in SENSOR_KEYS:
|
|
if not (items := router.data.get(key)):
|
|
continue
|
|
if key_meta := SENSOR_META.get(key):
|
|
if key_meta.include:
|
|
items = filter(key_meta.include.search, items)
|
|
if key_meta.exclude:
|
|
items = [x for x in items if not key_meta.exclude.search(x)]
|
|
sensors.extend(
|
|
HuaweiLteSensor(
|
|
router,
|
|
key,
|
|
item,
|
|
SENSOR_META[key].descriptions.get(
|
|
item, HuaweiSensorEntityDescription(key=item)
|
|
),
|
|
)
|
|
for item in items
|
|
)
|
|
|
|
async_add_entities(sensors, True)
|
|
|
|
|
|
class HuaweiLteSensor(HuaweiLteBaseEntityWithDevice, SensorEntity):
|
|
"""Huawei LTE sensor entity."""
|
|
|
|
entity_description: HuaweiSensorEntityDescription
|
|
_state: StateType = None
|
|
_unit: str | None = None
|
|
_last_reset: datetime | None = None
|
|
|
|
def __init__(
|
|
self,
|
|
router: Router,
|
|
key: str,
|
|
item: str,
|
|
entity_description: HuaweiSensorEntityDescription,
|
|
) -> None:
|
|
"""Initialize."""
|
|
super().__init__(router)
|
|
self.key = key
|
|
self.item = item
|
|
self.entity_description = entity_description
|
|
|
|
async def async_added_to_hass(self) -> None:
|
|
"""Subscribe to needed data on add."""
|
|
await super().async_added_to_hass()
|
|
self.router.subscriptions[self.key].append(f"{SENSOR_DOMAIN}/{self.item}")
|
|
if self.entity_description.last_reset_item:
|
|
self.router.subscriptions[self.key].append(
|
|
f"{SENSOR_DOMAIN}/{self.entity_description.last_reset_item}"
|
|
)
|
|
|
|
async def async_will_remove_from_hass(self) -> None:
|
|
"""Unsubscribe from needed data on remove."""
|
|
await super().async_will_remove_from_hass()
|
|
self.router.subscriptions[self.key].remove(f"{SENSOR_DOMAIN}/{self.item}")
|
|
if self.entity_description.last_reset_item:
|
|
self.router.subscriptions[self.key].remove(
|
|
f"{SENSOR_DOMAIN}/{self.entity_description.last_reset_item}"
|
|
)
|
|
|
|
@property
|
|
def _device_unique_id(self) -> str:
|
|
return f"{self.key}.{self.item}"
|
|
|
|
@property
|
|
def native_value(self) -> StateType:
|
|
"""Return sensor state."""
|
|
return self._state
|
|
|
|
@property
|
|
def native_unit_of_measurement(self) -> str | None:
|
|
"""Return sensor's unit of measurement."""
|
|
return self.entity_description.native_unit_of_measurement or self._unit
|
|
|
|
@property
|
|
def icon(self) -> str | None:
|
|
"""Return icon for sensor."""
|
|
if self.entity_description.icon_fn:
|
|
return self.entity_description.icon_fn(self.state)
|
|
return self.entity_description.icon
|
|
|
|
@property
|
|
def device_class(self) -> SensorDeviceClass | None:
|
|
"""Return device class for sensor."""
|
|
if self.entity_description.device_class_fn:
|
|
# Note: using self.state could infloop here.
|
|
return self.entity_description.device_class_fn(self.native_value)
|
|
return super().device_class
|
|
|
|
@property
|
|
def last_reset(self) -> datetime | None:
|
|
"""Return the time when the sensor was last reset, if any."""
|
|
return self._last_reset
|
|
|
|
async def async_update(self) -> None:
|
|
"""Update state."""
|
|
try:
|
|
value = self.router.data[self.key][self.item]
|
|
except KeyError:
|
|
_LOGGER.debug("%s[%s] not in data", self.key, self.item)
|
|
value = None
|
|
|
|
last_reset = None
|
|
if (
|
|
self.entity_description.last_reset_item
|
|
and self.entity_description.last_reset_format_fn
|
|
):
|
|
try:
|
|
last_reset_value = self.router.data[self.key][
|
|
self.entity_description.last_reset_item
|
|
]
|
|
except KeyError:
|
|
_LOGGER.debug(
|
|
"%s[%s] not in data",
|
|
self.key,
|
|
self.entity_description.last_reset_item,
|
|
)
|
|
else:
|
|
last_reset = self.entity_description.last_reset_format_fn(
|
|
last_reset_value
|
|
)
|
|
|
|
self._state, self._unit = self.entity_description.format_fn(value)
|
|
self._last_reset = last_reset
|
|
self._available = value is not None
|