Modernize Huawei LTE (#26675)
* Modernization rework - config entry support, with override support from huawei_lte platform in YAML - device tracker entity registry support - refactor for easier addition of more features - internal code cleanups * Remove log level dependent subscription/data debug hack No longer needed, because pretty much all keys from supported categories are exposed as sensors. Closes https://github.com/home-assistant/home-assistant/issues/23819 * Upgrade huawei-lte-api to 1.4.1 https://github.com/Salamek/huawei-lte-api/releases * Add support for access without username and password * Use subclass init instead of config_entries.HANDLERS * Update huawei-lte-api to 1.4.3 (#27269) * Convert device state attributes to snake_case * Simplify scanner entity initialization * Remove not needed hass reference from Router * Return explicit None from unsupported old device tracker setup * Mark unknown connection errors during config as such * Drop some dead config flow code * Run config flow sync I/O in executor * Parametrize config flow login error tests * Forward entry unload to platforms * Async/sync fixups * Improve data subscription debug logging * Implement on the fly add of new and tracking of seen device tracker entities * Handle device tracker entry unload cleanup in component * Remove unnecessary _async_setup_lte, just have code in async_setup_entry * Remove time tracker on unload * Fix to not use same mutable default subscription set for all routers * Pylint fixes * Remove some redundant defensive device tracker code * Add back explicit get_scanner None return, hush pylint * Adjust approach to set system_options on entry create * Enable some sensors on first add instead of disabling everything * Fix SMS notification recipients default value * Add option to skip new device tracker entities * Fix SMS notification recipient option default * Work around https://github.com/PyCQA/pylint/issues/3202 * Remove unrelated type hint additions * Change async_add_new_entities to a regular function * Remove option to disable polling for new device tracker entries
This commit is contained in:
parent
969322e14a
commit
fc09702cc3
15 changed files with 1067 additions and 276 deletions
|
@ -5,18 +5,15 @@ import re
|
|||
from typing import Optional
|
||||
|
||||
import attr
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import CONF_URL, CONF_MONITORED_CONDITIONS, STATE_UNKNOWN
|
||||
from homeassistant.const import CONF_URL, STATE_UNKNOWN
|
||||
from homeassistant.components.sensor import (
|
||||
PLATFORM_SCHEMA,
|
||||
DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
DOMAIN as SENSOR_DOMAIN,
|
||||
)
|
||||
from homeassistant.helpers import entity_registry
|
||||
from homeassistant.helpers.entity import Entity
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from . import RouterData
|
||||
from . import HuaweiLteBaseEntity
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
KEY_DEVICE_INFORMATION,
|
||||
|
@ -27,34 +24,27 @@ from .const import (
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_NAME_TEMPLATE = "Huawei {} {}"
|
||||
DEFAULT_DEVICE_NAME = "LTE"
|
||||
|
||||
DEFAULT_SENSORS = [
|
||||
f"{KEY_DEVICE_INFORMATION}.WanIPAddress",
|
||||
f"{KEY_DEVICE_SIGNAL}.rsrq",
|
||||
f"{KEY_DEVICE_SIGNAL}.rsrp",
|
||||
f"{KEY_DEVICE_SIGNAL}.rssi",
|
||||
f"{KEY_DEVICE_SIGNAL}.sinr",
|
||||
]
|
||||
|
||||
SENSOR_META = {
|
||||
f"{KEY_DEVICE_INFORMATION}.SoftwareVersion": dict(name="Software version"),
|
||||
f"{KEY_DEVICE_INFORMATION}.WanIPAddress": dict(
|
||||
name="WAN IP address", icon="mdi:ip"
|
||||
KEY_DEVICE_INFORMATION: dict(
|
||||
include=re.compile(r"^WanIP.*Address$", re.IGNORECASE)
|
||||
),
|
||||
f"{KEY_DEVICE_INFORMATION}.WanIPv6Address": dict(
|
||||
(KEY_DEVICE_INFORMATION, "SoftwareVersion"): dict(name="Software version"),
|
||||
(KEY_DEVICE_INFORMATION, "WanIPAddress"): dict(
|
||||
name="WAN IP address", icon="mdi:ip", enabled_default=True
|
||||
),
|
||||
(KEY_DEVICE_INFORMATION, "WanIPv6Address"): dict(
|
||||
name="WAN IPv6 address", icon="mdi:ip"
|
||||
),
|
||||
f"{KEY_DEVICE_SIGNAL}.band": dict(name="Band"),
|
||||
f"{KEY_DEVICE_SIGNAL}.cell_id": dict(name="Cell ID"),
|
||||
f"{KEY_DEVICE_SIGNAL}.lac": dict(name="LAC"),
|
||||
f"{KEY_DEVICE_SIGNAL}.mode": dict(
|
||||
(KEY_DEVICE_SIGNAL, "band"): dict(name="Band"),
|
||||
(KEY_DEVICE_SIGNAL, "cell_id"): dict(name="Cell ID"),
|
||||
(KEY_DEVICE_SIGNAL, "lac"): dict(name="LAC"),
|
||||
(KEY_DEVICE_SIGNAL, "mode"): dict(
|
||||
name="Mode",
|
||||
formatter=lambda x: ({"0": "2G", "2": "3G", "7": "4G"}.get(x, "Unknown"), None),
|
||||
),
|
||||
f"{KEY_DEVICE_SIGNAL}.pci": dict(name="PCI"),
|
||||
f"{KEY_DEVICE_SIGNAL}.rsrq": dict(
|
||||
(KEY_DEVICE_SIGNAL, "pci"): dict(name="PCI"),
|
||||
(KEY_DEVICE_SIGNAL, "rsrq"): dict(
|
||||
name="RSRQ",
|
||||
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
# http://www.lte-anbieter.info/technik/rsrq.php
|
||||
|
@ -65,8 +55,9 @@ SENSOR_META = {
|
|||
or x < -5
|
||||
and "mdi:signal-cellular-2"
|
||||
or "mdi:signal-cellular-3",
|
||||
enabled_default=True,
|
||||
),
|
||||
f"{KEY_DEVICE_SIGNAL}.rsrp": dict(
|
||||
(KEY_DEVICE_SIGNAL, "rsrp"): dict(
|
||||
name="RSRP",
|
||||
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
# http://www.lte-anbieter.info/technik/rsrp.php
|
||||
|
@ -77,8 +68,9 @@ SENSOR_META = {
|
|||
or x < -80
|
||||
and "mdi:signal-cellular-2"
|
||||
or "mdi:signal-cellular-3",
|
||||
enabled_default=True,
|
||||
),
|
||||
f"{KEY_DEVICE_SIGNAL}.rssi": dict(
|
||||
(KEY_DEVICE_SIGNAL, "rssi"): dict(
|
||||
name="RSSI",
|
||||
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
# https://eyesaas.com/wi-fi-signal-strength/
|
||||
|
@ -89,8 +81,9 @@ SENSOR_META = {
|
|||
or x < -60
|
||||
and "mdi:signal-cellular-2"
|
||||
or "mdi:signal-cellular-3",
|
||||
enabled_default=True,
|
||||
),
|
||||
f"{KEY_DEVICE_SIGNAL}.sinr": dict(
|
||||
(KEY_DEVICE_SIGNAL, "sinr"): dict(
|
||||
name="SINR",
|
||||
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
# http://www.lte-anbieter.info/technik/sinr.php
|
||||
|
@ -101,28 +94,38 @@ SENSOR_META = {
|
|||
or x < 10
|
||||
and "mdi:signal-cellular-2"
|
||||
or "mdi:signal-cellular-3",
|
||||
enabled_default=True,
|
||||
),
|
||||
KEY_MONITORING_TRAFFIC_STATISTICS: dict(
|
||||
exclude=re.compile(r"^showtraffic$", re.IGNORECASE)
|
||||
),
|
||||
}
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Optional(CONF_URL): cv.url,
|
||||
vol.Optional(
|
||||
CONF_MONITORED_CONDITIONS, default=DEFAULT_SENSORS
|
||||
): cv.ensure_list,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up Huawei LTE sensor devices."""
|
||||
data = hass.data[DOMAIN].get_data(config)
|
||||
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]]
|
||||
sensors = []
|
||||
for path in config.get(CONF_MONITORED_CONDITIONS):
|
||||
if path == "traffic_statistics": # backwards compatibility
|
||||
path = KEY_MONITORING_TRAFFIC_STATISTICS
|
||||
data.subscribe(path)
|
||||
sensors.append(HuaweiLteSensor(data, path, SENSOR_META.get(path, {})))
|
||||
for key in (
|
||||
KEY_DEVICE_INFORMATION,
|
||||
KEY_DEVICE_SIGNAL,
|
||||
KEY_MONITORING_TRAFFIC_STATISTICS,
|
||||
):
|
||||
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), {}))
|
||||
)
|
||||
|
||||
# Pre-0.97 unique id migration. Old ones used the device serial number
|
||||
# (see comments in HuaweiLteData._setup_lte for more info), as well as
|
||||
|
@ -134,7 +137,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||
if ent.platform != DOMAIN:
|
||||
continue
|
||||
for sensor in sensors:
|
||||
oldsuf = ".".join(sensor.path)
|
||||
oldsuf = ".".join(f"{sensor.key}.{sensor.item}")
|
||||
if ent.unique_id.endswith(f"_{oldsuf}"):
|
||||
entreg.async_update_entity(entid, new_unique_id=sensor.unique_id)
|
||||
_LOGGER.debug(
|
||||
|
@ -162,30 +165,33 @@ def format_default(value):
|
|||
|
||||
|
||||
@attr.s
|
||||
class HuaweiLteSensor(Entity):
|
||||
class HuaweiLteSensor(HuaweiLteBaseEntity):
|
||||
"""Huawei LTE sensor entity."""
|
||||
|
||||
data = attr.ib(type=RouterData)
|
||||
path = attr.ib(type=str)
|
||||
meta = attr.ib(type=dict)
|
||||
key: str = attr.ib()
|
||||
item: str = attr.ib()
|
||||
meta: dict = attr.ib()
|
||||
|
||||
_state = attr.ib(init=False, default=STATE_UNKNOWN)
|
||||
_unit = attr.ib(init=False, type=str)
|
||||
_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}")
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return unique ID for sensor."""
|
||||
return f"{self.data.mac}-{self.path}"
|
||||
def _entity_name(self) -> str:
|
||||
return self.meta.get("name", self.item)
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return sensor name."""
|
||||
try:
|
||||
dname = self.data[f"{KEY_DEVICE_INFORMATION}.DeviceName"]
|
||||
except KeyError:
|
||||
dname = None
|
||||
vname = self.meta.get("name", self.path)
|
||||
return DEFAULT_NAME_TEMPLATE.format(dname or DEFAULT_DEVICE_NAME, vname)
|
||||
def _device_unique_id(self) -> str:
|
||||
return f"{self.key}.{self.item}"
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
@ -210,18 +216,31 @@ class HuaweiLteSensor(Entity):
|
|||
return icon(self.state)
|
||||
return icon
|
||||
|
||||
def update(self):
|
||||
"""Update state."""
|
||||
self.data.update()
|
||||
@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"))
|
||||
|
||||
async def async_update(self):
|
||||
"""Update state."""
|
||||
try:
|
||||
value = self.data[self.path]
|
||||
value = self.router.data[self.key][self.item]
|
||||
except KeyError:
|
||||
_LOGGER.debug("%s not in data", self.path)
|
||||
value = None
|
||||
_LOGGER.debug("%s[%s] not in data", self.key, self.item)
|
||||
self._available = False
|
||||
return
|
||||
self._available = True
|
||||
|
||||
formatter = self.meta.get("formatter")
|
||||
if not callable(formatter):
|
||||
formatter = format_default
|
||||
|
||||
self._state, self._unit = formatter(value)
|
||||
|
||||
|
||||
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"
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue