2019-02-14 15:01:46 +00:00
|
|
|
"""Support for Huawei LTE sensors."""
|
2019-08-26 08:32:50 +00:00
|
|
|
|
2018-09-13 08:01:28 +00:00
|
|
|
import logging
|
|
|
|
import re
|
2019-07-24 04:24:22 +00:00
|
|
|
from typing import Optional
|
2018-09-13 08:01:28 +00:00
|
|
|
|
|
|
|
import attr
|
|
|
|
|
2019-07-24 04:24:22 +00:00
|
|
|
from homeassistant.components.sensor import (
|
2019-07-31 19:25:30 +00:00
|
|
|
DEVICE_CLASS_SIGNAL_STRENGTH,
|
2019-10-24 16:31:49 +00:00
|
|
|
DOMAIN as SENSOR_DOMAIN,
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2020-02-23 20:09:24 +00:00
|
|
|
from homeassistant.const import CONF_URL, DATA_BYTES, STATE_UNKNOWN, TIME_SECONDS
|
2018-09-13 08:01:28 +00:00
|
|
|
|
2019-10-24 16:31:49 +00:00
|
|
|
from . import HuaweiLteBaseEntity
|
2019-08-26 08:32:50 +00:00
|
|
|
from .const import (
|
|
|
|
DOMAIN,
|
|
|
|
KEY_DEVICE_INFORMATION,
|
|
|
|
KEY_DEVICE_SIGNAL,
|
2020-03-04 22:56:37 +00:00
|
|
|
KEY_MONITORING_MONTH_STATISTICS,
|
|
|
|
KEY_MONITORING_STATUS,
|
2019-08-26 08:32:50 +00:00
|
|
|
KEY_MONITORING_TRAFFIC_STATISTICS,
|
2020-03-05 19:29:53 +00:00
|
|
|
KEY_NET_CURRENT_PLMN,
|
|
|
|
KEY_NET_NET_MODE,
|
2020-06-03 09:59:06 +00:00
|
|
|
KEY_SMS_SMS_COUNT,
|
2020-03-04 22:56:37 +00:00
|
|
|
SENSOR_KEYS,
|
2019-08-26 08:32:50 +00:00
|
|
|
)
|
|
|
|
|
2018-09-13 08:01:28 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
SENSOR_META = {
|
2019-10-24 16:31:49 +00:00
|
|
|
KEY_DEVICE_INFORMATION: dict(
|
|
|
|
include=re.compile(r"^WanIP.*Address$", re.IGNORECASE)
|
|
|
|
),
|
|
|
|
(KEY_DEVICE_INFORMATION, "WanIPAddress"): dict(
|
|
|
|
name="WAN IP address", icon="mdi:ip", enabled_default=True
|
2019-08-26 08:32:50 +00:00
|
|
|
),
|
2019-10-24 16:31:49 +00:00
|
|
|
(KEY_DEVICE_INFORMATION, "WanIPv6Address"): dict(
|
2019-08-26 08:32:50 +00:00
|
|
|
name="WAN IPv6 address", icon="mdi:ip"
|
|
|
|
),
|
2019-10-24 16:31:49 +00:00
|
|
|
(KEY_DEVICE_SIGNAL, "band"): dict(name="Band"),
|
|
|
|
(KEY_DEVICE_SIGNAL, "cell_id"): dict(name="Cell ID"),
|
2019-10-24 23:42:54 +00:00
|
|
|
(KEY_DEVICE_SIGNAL, "lac"): dict(name="LAC", icon="mdi:map-marker"),
|
2019-10-24 16:31:49 +00:00
|
|
|
(KEY_DEVICE_SIGNAL, "mode"): dict(
|
2018-10-17 07:00:15 +00:00
|
|
|
name="Mode",
|
2019-07-31 19:25:30 +00:00
|
|
|
formatter=lambda x: ({"0": "2G", "2": "3G", "7": "4G"}.get(x, "Unknown"), None),
|
2018-10-17 07:00:15 +00:00
|
|
|
),
|
2019-10-24 16:31:49 +00:00
|
|
|
(KEY_DEVICE_SIGNAL, "pci"): dict(name="PCI"),
|
|
|
|
(KEY_DEVICE_SIGNAL, "rsrq"): dict(
|
2018-09-13 08:01:28 +00:00
|
|
|
name="RSRQ",
|
2019-07-24 04:24:22 +00:00
|
|
|
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
2018-09-13 08:01:28 +00:00
|
|
|
# http://www.lte-anbieter.info/technik/rsrq.php
|
2019-07-31 19:25:30 +00:00
|
|
|
icon=lambda x: (x is None or x < -11)
|
|
|
|
and "mdi:signal-cellular-outline"
|
|
|
|
or x < -8
|
|
|
|
and "mdi:signal-cellular-1"
|
|
|
|
or x < -5
|
|
|
|
and "mdi:signal-cellular-2"
|
|
|
|
or "mdi:signal-cellular-3",
|
2019-10-24 16:31:49 +00:00
|
|
|
enabled_default=True,
|
2018-09-13 08:01:28 +00:00
|
|
|
),
|
2019-10-24 16:31:49 +00:00
|
|
|
(KEY_DEVICE_SIGNAL, "rsrp"): dict(
|
2018-09-13 08:01:28 +00:00
|
|
|
name="RSRP",
|
2019-07-24 04:24:22 +00:00
|
|
|
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
2018-09-13 08:01:28 +00:00
|
|
|
# http://www.lte-anbieter.info/technik/rsrp.php
|
2019-07-31 19:25:30 +00:00
|
|
|
icon=lambda x: (x is None or x < -110)
|
|
|
|
and "mdi:signal-cellular-outline"
|
|
|
|
or x < -95
|
|
|
|
and "mdi:signal-cellular-1"
|
|
|
|
or x < -80
|
|
|
|
and "mdi:signal-cellular-2"
|
|
|
|
or "mdi:signal-cellular-3",
|
2019-10-24 16:31:49 +00:00
|
|
|
enabled_default=True,
|
2018-09-13 08:01:28 +00:00
|
|
|
),
|
2019-10-24 16:31:49 +00:00
|
|
|
(KEY_DEVICE_SIGNAL, "rssi"): dict(
|
2018-09-13 08:01:28 +00:00
|
|
|
name="RSSI",
|
2019-07-24 04:24:22 +00:00
|
|
|
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
2018-09-13 08:01:28 +00:00
|
|
|
# https://eyesaas.com/wi-fi-signal-strength/
|
2019-07-31 19:25:30 +00:00
|
|
|
icon=lambda x: (x is None or x < -80)
|
|
|
|
and "mdi:signal-cellular-outline"
|
|
|
|
or x < -70
|
|
|
|
and "mdi:signal-cellular-1"
|
|
|
|
or x < -60
|
|
|
|
and "mdi:signal-cellular-2"
|
|
|
|
or "mdi:signal-cellular-3",
|
2019-10-24 16:31:49 +00:00
|
|
|
enabled_default=True,
|
2018-09-13 08:01:28 +00:00
|
|
|
),
|
2019-10-24 16:31:49 +00:00
|
|
|
(KEY_DEVICE_SIGNAL, "sinr"): dict(
|
2018-09-13 08:01:28 +00:00
|
|
|
name="SINR",
|
2019-07-24 04:24:22 +00:00
|
|
|
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
2018-09-13 08:01:28 +00:00
|
|
|
# http://www.lte-anbieter.info/technik/sinr.php
|
2019-07-31 19:25:30 +00:00
|
|
|
icon=lambda x: (x is None or x < 0)
|
|
|
|
and "mdi:signal-cellular-outline"
|
|
|
|
or x < 5
|
|
|
|
and "mdi:signal-cellular-1"
|
|
|
|
or x < 10
|
|
|
|
and "mdi:signal-cellular-2"
|
|
|
|
or "mdi:signal-cellular-3",
|
2019-10-24 16:31:49 +00:00
|
|
|
enabled_default=True,
|
|
|
|
),
|
2019-10-24 23:42:54 +00:00
|
|
|
(KEY_DEVICE_SIGNAL, "rscp"): dict(
|
|
|
|
name="RSCP",
|
|
|
|
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
|
|
|
# https://wiki.teltonika.lt/view/RSCP
|
|
|
|
icon=lambda x: (x is None or x < -95)
|
|
|
|
and "mdi:signal-cellular-outline"
|
|
|
|
or x < -85
|
|
|
|
and "mdi:signal-cellular-1"
|
|
|
|
or x < -75
|
|
|
|
and "mdi:signal-cellular-2"
|
|
|
|
or "mdi:signal-cellular-3",
|
|
|
|
),
|
|
|
|
(KEY_DEVICE_SIGNAL, "ecio"): dict(
|
|
|
|
name="EC/IO",
|
|
|
|
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
|
|
|
# https://wiki.teltonika.lt/view/EC/IO
|
|
|
|
icon=lambda x: (x is None or x < -20)
|
|
|
|
and "mdi:signal-cellular-outline"
|
|
|
|
or x < -10
|
|
|
|
and "mdi:signal-cellular-1"
|
|
|
|
or x < -6
|
|
|
|
and "mdi:signal-cellular-2"
|
|
|
|
or "mdi:signal-cellular-3",
|
|
|
|
),
|
2020-03-04 22:56:37 +00:00
|
|
|
KEY_MONITORING_MONTH_STATISTICS: dict(
|
|
|
|
exclude=re.compile(r"^month(duration|lastcleartime)$", re.IGNORECASE)
|
|
|
|
),
|
|
|
|
(KEY_MONITORING_MONTH_STATISTICS, "CurrentMonthDownload"): dict(
|
|
|
|
name="Current month download", unit=DATA_BYTES, icon="mdi:download"
|
|
|
|
),
|
|
|
|
(KEY_MONITORING_MONTH_STATISTICS, "CurrentMonthUpload"): dict(
|
|
|
|
name="Current month upload", unit=DATA_BYTES, icon="mdi:upload"
|
|
|
|
),
|
|
|
|
KEY_MONITORING_STATUS: dict(
|
|
|
|
include=re.compile(
|
|
|
|
r"^(currentwifiuser|(primary|secondary).*dns)$", re.IGNORECASE
|
|
|
|
)
|
|
|
|
),
|
|
|
|
(KEY_MONITORING_STATUS, "CurrentWifiUser"): dict(
|
|
|
|
name="WiFi clients connected", icon="mdi:wifi"
|
|
|
|
),
|
|
|
|
(KEY_MONITORING_STATUS, "PrimaryDns"): dict(
|
|
|
|
name="Primary DNS server", icon="mdi:ip"
|
|
|
|
),
|
|
|
|
(KEY_MONITORING_STATUS, "SecondaryDns"): dict(
|
|
|
|
name="Secondary DNS server", icon="mdi:ip"
|
|
|
|
),
|
|
|
|
(KEY_MONITORING_STATUS, "PrimaryIPv6Dns"): dict(
|
|
|
|
name="Primary IPv6 DNS server", icon="mdi:ip"
|
|
|
|
),
|
|
|
|
(KEY_MONITORING_STATUS, "SecondaryIPv6Dns"): dict(
|
|
|
|
name="Secondary IPv6 DNS server", icon="mdi:ip"
|
|
|
|
),
|
2019-10-24 16:31:49 +00:00
|
|
|
KEY_MONITORING_TRAFFIC_STATISTICS: dict(
|
|
|
|
exclude=re.compile(r"^showtraffic$", re.IGNORECASE)
|
2018-09-13 08:01:28 +00:00
|
|
|
),
|
2019-10-24 23:42:54 +00:00
|
|
|
(KEY_MONITORING_TRAFFIC_STATISTICS, "CurrentConnectTime"): dict(
|
2020-07-14 20:17:14 +00:00
|
|
|
name="Current connection duration", unit=TIME_SECONDS, icon="mdi:timer-outline"
|
2019-10-24 23:42:54 +00:00
|
|
|
),
|
|
|
|
(KEY_MONITORING_TRAFFIC_STATISTICS, "CurrentDownload"): dict(
|
2020-02-13 16:52:58 +00:00
|
|
|
name="Current connection download", unit=DATA_BYTES, icon="mdi:download"
|
2019-10-24 23:42:54 +00:00
|
|
|
),
|
|
|
|
(KEY_MONITORING_TRAFFIC_STATISTICS, "CurrentUpload"): dict(
|
2020-02-13 16:52:58 +00:00
|
|
|
name="Current connection upload", unit=DATA_BYTES, icon="mdi:upload"
|
2019-10-24 23:42:54 +00:00
|
|
|
),
|
|
|
|
(KEY_MONITORING_TRAFFIC_STATISTICS, "TotalConnectTime"): dict(
|
2020-07-14 20:17:14 +00:00
|
|
|
name="Total connected duration", unit=TIME_SECONDS, icon="mdi:timer-outline"
|
2019-10-24 23:42:54 +00:00
|
|
|
),
|
|
|
|
(KEY_MONITORING_TRAFFIC_STATISTICS, "TotalDownload"): dict(
|
2020-02-13 16:52:58 +00:00
|
|
|
name="Total download", unit=DATA_BYTES, icon="mdi:download"
|
2019-10-24 23:42:54 +00:00
|
|
|
),
|
|
|
|
(KEY_MONITORING_TRAFFIC_STATISTICS, "TotalUpload"): dict(
|
2020-02-13 16:52:58 +00:00
|
|
|
name="Total upload", unit=DATA_BYTES, icon="mdi:upload"
|
2019-10-24 23:42:54 +00:00
|
|
|
),
|
2020-03-05 19:29:53 +00:00
|
|
|
KEY_NET_CURRENT_PLMN: dict(exclude=re.compile(r"^(Rat|ShortName)$", re.IGNORECASE)),
|
|
|
|
(KEY_NET_CURRENT_PLMN, "State"): dict(
|
|
|
|
name="Operator search mode",
|
|
|
|
formatter=lambda x: ({"0": "Auto", "1": "Manual"}.get(x, "Unknown"), None),
|
|
|
|
),
|
|
|
|
(KEY_NET_CURRENT_PLMN, "FullName"): dict(name="Operator name",),
|
|
|
|
(KEY_NET_CURRENT_PLMN, "Numeric"): dict(name="Operator code",),
|
|
|
|
KEY_NET_NET_MODE: dict(include=re.compile(r"^NetworkMode$", re.IGNORECASE)),
|
|
|
|
(KEY_NET_NET_MODE, "NetworkMode"): dict(
|
|
|
|
name="Preferred mode",
|
|
|
|
formatter=lambda x: (
|
|
|
|
{
|
|
|
|
"00": "4G/3G/2G",
|
|
|
|
"01": "2G",
|
|
|
|
"02": "3G",
|
|
|
|
"03": "4G",
|
|
|
|
"0301": "4G/2G",
|
|
|
|
"0302": "4G/3G",
|
|
|
|
"0201": "3G/2G",
|
|
|
|
}.get(x, "Unknown"),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
),
|
2020-06-03 09:59:06 +00:00
|
|
|
(KEY_SMS_SMS_COUNT, "LocalUnread"): dict(
|
|
|
|
name="SMS unread", icon="mdi:email-receive",
|
|
|
|
),
|
2018-09-13 08:01:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-10-24 16:31:49 +00:00
|
|
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
|
|
|
"""Set up from config entry."""
|
|
|
|
router = hass.data[DOMAIN].routers[config_entry.data[CONF_URL]]
|
2018-09-13 08:01:28 +00:00
|
|
|
sensors = []
|
2020-03-04 22:56:37 +00:00
|
|
|
for key in SENSOR_KEYS:
|
2019-10-24 16:31:49 +00:00
|
|
|
items = router.data.get(key)
|
|
|
|
if not items:
|
|
|
|
continue
|
|
|
|
key_meta = SENSOR_META.get(key)
|
|
|
|
if key_meta:
|
|
|
|
include = key_meta.get("include")
|
|
|
|
if include:
|
|
|
|
items = filter(include.search, items)
|
|
|
|
exclude = key_meta.get("exclude")
|
|
|
|
if exclude:
|
|
|
|
items = [x for x in items if not exclude.search(x)]
|
|
|
|
for item in items:
|
|
|
|
sensors.append(
|
|
|
|
HuaweiLteSensor(router, key, item, SENSOR_META.get((key, item), {}))
|
|
|
|
)
|
2019-02-14 15:01:46 +00:00
|
|
|
|
2019-08-01 16:22:04 +00:00
|
|
|
async_add_entities(sensors, True)
|
2018-09-13 08:01:28 +00:00
|
|
|
|
|
|
|
|
2018-10-17 07:00:15 +00:00
|
|
|
def format_default(value):
|
|
|
|
"""Format value."""
|
|
|
|
unit = None
|
|
|
|
if value is not None:
|
|
|
|
# Clean up value and infer unit, e.g. -71dBm, 15 dB
|
2019-08-19 17:34:33 +00:00
|
|
|
match = re.match(
|
|
|
|
r"([>=<]*)(?P<value>.+?)\s*(?P<unit>[a-zA-Z]+)\s*$", str(value)
|
|
|
|
)
|
2018-10-17 07:00:15 +00:00
|
|
|
if match:
|
|
|
|
try:
|
|
|
|
value = float(match.group("value"))
|
|
|
|
unit = match.group("unit")
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
return value, unit
|
|
|
|
|
|
|
|
|
2018-09-13 08:01:28 +00:00
|
|
|
@attr.s
|
2019-10-24 16:31:49 +00:00
|
|
|
class HuaweiLteSensor(HuaweiLteBaseEntity):
|
2018-09-13 08:01:28 +00:00
|
|
|
"""Huawei LTE sensor entity."""
|
|
|
|
|
2019-10-24 16:31:49 +00:00
|
|
|
key: str = attr.ib()
|
|
|
|
item: str = attr.ib()
|
|
|
|
meta: dict = attr.ib()
|
2018-09-13 08:01:28 +00:00
|
|
|
|
|
|
|
_state = attr.ib(init=False, default=STATE_UNKNOWN)
|
2019-10-24 16:31:49 +00:00
|
|
|
_unit: str = attr.ib(init=False)
|
|
|
|
|
|
|
|
async def async_added_to_hass(self):
|
|
|
|
"""Subscribe to needed data on add."""
|
|
|
|
await super().async_added_to_hass()
|
|
|
|
self.router.subscriptions[self.key].add(f"{SENSOR_DOMAIN}/{self.item}")
|
|
|
|
|
|
|
|
async def async_will_remove_from_hass(self):
|
|
|
|
"""Unsubscribe from needed data on remove."""
|
|
|
|
await super().async_will_remove_from_hass()
|
|
|
|
self.router.subscriptions[self.key].remove(f"{SENSOR_DOMAIN}/{self.item}")
|
2018-09-13 08:01:28 +00:00
|
|
|
|
|
|
|
@property
|
2019-10-24 16:31:49 +00:00
|
|
|
def _entity_name(self) -> str:
|
|
|
|
return self.meta.get("name", self.item)
|
2018-09-13 08:01:28 +00:00
|
|
|
|
|
|
|
@property
|
2019-10-24 16:31:49 +00:00
|
|
|
def _device_unique_id(self) -> str:
|
|
|
|
return f"{self.key}.{self.item}"
|
2018-09-13 08:01:28 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def state(self):
|
|
|
|
"""Return sensor state."""
|
|
|
|
return self._state
|
|
|
|
|
2019-07-24 04:24:22 +00:00
|
|
|
@property
|
|
|
|
def device_class(self) -> Optional[str]:
|
|
|
|
"""Return sensor device class."""
|
|
|
|
return self.meta.get("device_class")
|
|
|
|
|
2018-09-13 08:01:28 +00:00
|
|
|
@property
|
|
|
|
def unit_of_measurement(self):
|
|
|
|
"""Return sensor's unit of measurement."""
|
|
|
|
return self.meta.get("unit", self._unit)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def icon(self):
|
|
|
|
"""Return icon for sensor."""
|
|
|
|
icon = self.meta.get("icon")
|
|
|
|
if callable(icon):
|
|
|
|
return icon(self.state)
|
|
|
|
return icon
|
|
|
|
|
2019-10-24 16:31:49 +00:00
|
|
|
@property
|
|
|
|
def entity_registry_enabled_default(self) -> bool:
|
|
|
|
"""Return if the entity should be enabled when first added to the entity registry."""
|
|
|
|
return bool(self.meta.get("enabled_default"))
|
2018-09-13 08:01:28 +00:00
|
|
|
|
2019-10-24 16:31:49 +00:00
|
|
|
async def async_update(self):
|
|
|
|
"""Update state."""
|
2018-09-13 08:01:28 +00:00
|
|
|
try:
|
2019-10-24 16:31:49 +00:00
|
|
|
value = self.router.data[self.key][self.item]
|
2018-09-13 08:01:28 +00:00
|
|
|
except KeyError:
|
2019-10-24 16:31:49 +00:00
|
|
|
_LOGGER.debug("%s[%s] not in data", self.key, self.item)
|
|
|
|
self._available = False
|
|
|
|
return
|
|
|
|
self._available = True
|
2018-09-13 08:01:28 +00:00
|
|
|
|
2018-10-17 07:00:15 +00:00
|
|
|
formatter = self.meta.get("formatter")
|
|
|
|
if not callable(formatter):
|
|
|
|
formatter = format_default
|
|
|
|
|
|
|
|
self._state, self._unit = formatter(value)
|
2019-10-24 16:31:49 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def async_setup_platform(*args, **kwargs):
|
|
|
|
"""Old no longer used way to set up Huawei LTE sensors."""
|
|
|
|
_LOGGER.warning(
|
|
|
|
"Loading and configuring as a platform is no longer supported or "
|
|
|
|
"required, convert to enabling/disabling available entities"
|
|
|
|
)
|