Huawei LTE misc improvements (#26203)

* Constant and whitespace cleanups

* Upgrade huawei_lte_api to 1.3.0

https://github.com/Salamek/huawei-lte-api/releases

* Hush traceback if device does not support logout
This commit is contained in:
Ville Skyttä 2019-08-26 11:32:50 +03:00 committed by Martin Hjelmare
parent bde572c91a
commit 0c49c82015
9 changed files with 80 additions and 46 deletions

View File

@ -1,4 +1,5 @@
"""Support for Huawei LTE routers.""" """Support for Huawei LTE routers."""
from datetime import timedelta from datetime import timedelta
from functools import reduce from functools import reduce
from urllib.parse import urlparse from urllib.parse import urlparse
@ -22,6 +23,14 @@ from homeassistant.const import (
) )
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.util import Throttle from homeassistant.util import Throttle
from .const import (
DOMAIN,
KEY_DEVICE_INFORMATION,
KEY_DEVICE_SIGNAL,
KEY_MONITORING_TRAFFIC_STATISTICS,
KEY_WLAN_HOST_LIST,
)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -31,9 +40,6 @@ logging.getLogger("dicttoxml").setLevel(logging.WARNING)
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10) MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10)
DOMAIN = "huawei_lte"
DATA_KEY = "huawei_lte"
CONFIG_SCHEMA = vol.Schema( CONFIG_SCHEMA = vol.Schema(
{ {
DOMAIN: vol.All( DOMAIN: vol.All(
@ -107,12 +113,12 @@ class RouterData:
finally: finally:
_LOGGER.debug("%s=%s", path, getattr(self, path)) _LOGGER.debug("%s=%s", path, getattr(self, path))
get_data("device_information", self.client.device.information) get_data(KEY_DEVICE_INFORMATION, self.client.device.information)
get_data("device_signal", self.client.device.signal) get_data(KEY_DEVICE_SIGNAL, self.client.device.signal)
get_data( get_data(
"monitoring_traffic_statistics", self.client.monitoring.traffic_statistics KEY_MONITORING_TRAFFIC_STATISTICS, self.client.monitoring.traffic_statistics
) )
get_data("wlan_host_list", self.client.wlan.host_list) get_data(KEY_WLAN_HOST_LIST, self.client.wlan.host_list)
@attr.s @attr.s
@ -133,8 +139,8 @@ class HuaweiLteData:
def setup(hass, config) -> bool: def setup(hass, config) -> bool:
"""Set up Huawei LTE component.""" """Set up Huawei LTE component."""
if DATA_KEY not in hass.data: if DOMAIN not in hass.data:
hass.data[DATA_KEY] = HuaweiLteData() hass.data[DOMAIN] = HuaweiLteData()
for conf in config.get(DOMAIN, []): for conf in config.get(DOMAIN, []):
_setup_lte(hass, conf) _setup_lte(hass, conf)
return True return True
@ -164,10 +170,13 @@ def _setup_lte(hass, lte_config) -> None:
client = Client(connection) client = Client(connection)
data = RouterData(client, mac) data = RouterData(client, mac)
hass.data[DATA_KEY].data[url] = data hass.data[DOMAIN].data[url] = data
def cleanup(event): def cleanup(event):
"""Clean up resources.""" """Clean up resources."""
client.user.logout() try:
client.user.logout()
except ResponseErrorNotSupportedException as ex:
_LOGGER.debug("Logout not supported by device", exc_info=ex)
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, cleanup) hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, cleanup)

View File

@ -0,0 +1,8 @@
"""Huawei LTE constants."""
DOMAIN = "huawei_lte"
KEY_DEVICE_INFORMATION = "device_information"
KEY_DEVICE_SIGNAL = "device_signal"
KEY_MONITORING_TRAFFIC_STATISTICS = "monitoring_traffic_statistics"
KEY_WLAN_HOST_LIST = "wlan_host_list"

View File

@ -1,4 +1,5 @@
"""Support for device tracking of Huawei LTE routers.""" """Support for device tracking of Huawei LTE routers."""
import logging import logging
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
@ -8,19 +9,20 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import PLATFORM_SCHEMA, DeviceScanner from homeassistant.components.device_tracker import PLATFORM_SCHEMA, DeviceScanner
from homeassistant.const import CONF_URL from homeassistant.const import CONF_URL
from . import DATA_KEY, RouterData from . import RouterData
from .const import DOMAIN, KEY_WLAN_HOST_LIST
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({vol.Optional(CONF_URL): cv.url}) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({vol.Optional(CONF_URL): cv.url})
HOSTS_PATH = "wlan_host_list.Hosts.Host" HOSTS_PATH = f"{KEY_WLAN_HOST_LIST}.Hosts.Host"
def get_scanner(hass, config): def get_scanner(hass, config):
"""Get a Huawei LTE router scanner.""" """Get a Huawei LTE router scanner."""
data = hass.data[DATA_KEY].get_data(config) data = hass.data[DOMAIN].get_data(config)
data.subscribe(HOSTS_PATH) data.subscribe(HOSTS_PATH)
return HuaweiLteScanner(data) return HuaweiLteScanner(data)

View File

@ -4,7 +4,7 @@
"documentation": "https://www.home-assistant.io/components/huawei_lte", "documentation": "https://www.home-assistant.io/components/huawei_lte",
"requirements": [ "requirements": [
"getmac==0.8.1", "getmac==0.8.1",
"huawei-lte-api==1.2.0" "huawei-lte-api==1.3.0"
], ],
"dependencies": [], "dependencies": [],
"codeowners": [ "codeowners": [

View File

@ -1,4 +1,5 @@
"""Support for Huawei LTE router notifications.""" """Support for Huawei LTE router notifications."""
import logging import logging
import voluptuous as vol import voluptuous as vol
@ -12,7 +13,8 @@ from homeassistant.components.notify import (
from homeassistant.const import CONF_RECIPIENT, CONF_URL from homeassistant.const import CONF_RECIPIENT, CONF_URL
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from . import DATA_KEY from .const import DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -44,7 +46,7 @@ class HuaweiLteSmsNotificationService(BaseNotificationService):
if not targets or not message: if not targets or not message:
return return
data = self.hass.data[DATA_KEY].get_data(self.config) data = self.hass.data[DOMAIN].get_data(self.config)
if not data: if not data:
_LOGGER.error("Router not available") _LOGGER.error("Router not available")
return return

View File

@ -1,4 +1,5 @@
"""Support for Huawei LTE sensors.""" """Support for Huawei LTE sensors."""
import logging import logging
import re import re
from typing import Optional from typing import Optional
@ -15,7 +16,14 @@ from homeassistant.helpers import entity_registry
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from . import DATA_KEY, RouterData from . import RouterData
from .const import (
DOMAIN,
KEY_DEVICE_INFORMATION,
KEY_DEVICE_SIGNAL,
KEY_MONITORING_TRAFFIC_STATISTICS,
)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -23,26 +31,30 @@ DEFAULT_NAME_TEMPLATE = "Huawei {} {}"
DEFAULT_DEVICE_NAME = "LTE" DEFAULT_DEVICE_NAME = "LTE"
DEFAULT_SENSORS = [ DEFAULT_SENSORS = [
"device_information.WanIPAddress", f"{KEY_DEVICE_INFORMATION}.WanIPAddress",
"device_signal.rsrq", f"{KEY_DEVICE_SIGNAL}.rsrq",
"device_signal.rsrp", f"{KEY_DEVICE_SIGNAL}.rsrp",
"device_signal.rssi", f"{KEY_DEVICE_SIGNAL}.rssi",
"device_signal.sinr", f"{KEY_DEVICE_SIGNAL}.sinr",
] ]
SENSOR_META = { SENSOR_META = {
"device_information.SoftwareVersion": dict(name="Software version"), f"{KEY_DEVICE_INFORMATION}.SoftwareVersion": dict(name="Software version"),
"device_information.WanIPAddress": dict(name="WAN IP address", icon="mdi:ip"), f"{KEY_DEVICE_INFORMATION}.WanIPAddress": dict(
"device_information.WanIPv6Address": dict(name="WAN IPv6 address", icon="mdi:ip"), name="WAN IP address", icon="mdi:ip"
"device_signal.band": dict(name="Band"), ),
"device_signal.cell_id": dict(name="Cell ID"), f"{KEY_DEVICE_INFORMATION}.WanIPv6Address": dict(
"device_signal.lac": dict(name="LAC"), name="WAN IPv6 address", icon="mdi:ip"
"device_signal.mode": dict( ),
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(
name="Mode", name="Mode",
formatter=lambda x: ({"0": "2G", "2": "3G", "7": "4G"}.get(x, "Unknown"), None), formatter=lambda x: ({"0": "2G", "2": "3G", "7": "4G"}.get(x, "Unknown"), None),
), ),
"device_signal.pci": dict(name="PCI"), f"{KEY_DEVICE_SIGNAL}.pci": dict(name="PCI"),
"device_signal.rsrq": dict( f"{KEY_DEVICE_SIGNAL}.rsrq": dict(
name="RSRQ", name="RSRQ",
device_class=DEVICE_CLASS_SIGNAL_STRENGTH, device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
# http://www.lte-anbieter.info/technik/rsrq.php # http://www.lte-anbieter.info/technik/rsrq.php
@ -54,7 +66,7 @@ SENSOR_META = {
and "mdi:signal-cellular-2" and "mdi:signal-cellular-2"
or "mdi:signal-cellular-3", or "mdi:signal-cellular-3",
), ),
"device_signal.rsrp": dict( f"{KEY_DEVICE_SIGNAL}.rsrp": dict(
name="RSRP", name="RSRP",
device_class=DEVICE_CLASS_SIGNAL_STRENGTH, device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
# http://www.lte-anbieter.info/technik/rsrp.php # http://www.lte-anbieter.info/technik/rsrp.php
@ -66,7 +78,7 @@ SENSOR_META = {
and "mdi:signal-cellular-2" and "mdi:signal-cellular-2"
or "mdi:signal-cellular-3", or "mdi:signal-cellular-3",
), ),
"device_signal.rssi": dict( f"{KEY_DEVICE_SIGNAL}.rssi": dict(
name="RSSI", name="RSSI",
device_class=DEVICE_CLASS_SIGNAL_STRENGTH, device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
# https://eyesaas.com/wi-fi-signal-strength/ # https://eyesaas.com/wi-fi-signal-strength/
@ -78,7 +90,7 @@ SENSOR_META = {
and "mdi:signal-cellular-2" and "mdi:signal-cellular-2"
or "mdi:signal-cellular-3", or "mdi:signal-cellular-3",
), ),
"device_signal.sinr": dict( f"{KEY_DEVICE_SIGNAL}.sinr": dict(
name="SINR", name="SINR",
device_class=DEVICE_CLASS_SIGNAL_STRENGTH, device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
# http://www.lte-anbieter.info/technik/sinr.php # http://www.lte-anbieter.info/technik/sinr.php
@ -104,11 +116,11 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up Huawei LTE sensor devices.""" """Set up Huawei LTE sensor devices."""
data = hass.data[DATA_KEY].get_data(config) data = hass.data[DOMAIN].get_data(config)
sensors = [] sensors = []
for path in config.get(CONF_MONITORED_CONDITIONS): for path in config.get(CONF_MONITORED_CONDITIONS):
if path == "traffic_statistics": # backwards compatibility if path == "traffic_statistics": # backwards compatibility
path = "monitoring_traffic_statistics" path = KEY_MONITORING_TRAFFIC_STATISTICS
data.subscribe(path) data.subscribe(path)
sensors.append(HuaweiLteSensor(data, path, SENSOR_META.get(path, {}))) sensors.append(HuaweiLteSensor(data, path, SENSOR_META.get(path, {})))
@ -119,7 +131,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
# *_d.e.v.i.c.e._.s.i.g.n.a.l...s.i.n.r # *_d.e.v.i.c.e._.s.i.g.n.a.l...s.i.n.r
entreg = await entity_registry.async_get_registry(hass) entreg = await entity_registry.async_get_registry(hass)
for entid, ent in entreg.entities.items(): for entid, ent in entreg.entities.items():
if ent.platform != "huawei_lte": if ent.platform != DOMAIN:
continue continue
for sensor in sensors: for sensor in sensors:
oldsuf = ".".join(sensor.path) oldsuf = ".".join(sensor.path)
@ -169,7 +181,7 @@ class HuaweiLteSensor(Entity):
def name(self) -> str: def name(self) -> str:
"""Return sensor name.""" """Return sensor name."""
try: try:
dname = self.data["device_information.DeviceName"] dname = self.data[f"{KEY_DEVICE_INFORMATION}.DeviceName"]
except KeyError: except KeyError:
dname = None dname = None
vname = self.meta.get("name", self.path) vname = self.meta.get("name", self.path)

View File

@ -643,7 +643,7 @@ horimote==0.4.1
httplib2==0.10.3 httplib2==0.10.3
# homeassistant.components.huawei_lte # homeassistant.components.huawei_lte
huawei-lte-api==1.2.0 huawei-lte-api==1.3.0
# homeassistant.components.hydrawise # homeassistant.components.hydrawise
hydrawiser==0.1.1 hydrawiser==0.1.1

View File

@ -189,7 +189,7 @@ homematicip==0.10.10
httplib2==0.10.3 httplib2==0.10.3
# homeassistant.components.huawei_lte # homeassistant.components.huawei_lte
huawei-lte-api==1.2.0 huawei-lte-api==1.3.0
# homeassistant.components.influxdb # homeassistant.components.influxdb
influxdb==5.2.0 influxdb==5.2.0

View File

@ -4,6 +4,7 @@ from unittest.mock import Mock
import pytest import pytest
from homeassistant.components import huawei_lte from homeassistant.components import huawei_lte
from homeassistant.components.huawei_lte.const import KEY_DEVICE_INFORMATION
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
@ -23,25 +24,25 @@ async def test_routerdata_get_nonexistent_root(routerdata):
async def test_routerdata_get_nonexistent_leaf(routerdata): async def test_routerdata_get_nonexistent_leaf(routerdata):
"""Test that accessing a nonexistent leaf element raises KeyError.""" """Test that accessing a nonexistent leaf element raises KeyError."""
with pytest.raises(KeyError): with pytest.raises(KeyError):
routerdata["device_information.foo"] routerdata[f"{KEY_DEVICE_INFORMATION}.foo"]
async def test_routerdata_get_nonexistent_leaf_path(routerdata): async def test_routerdata_get_nonexistent_leaf_path(routerdata):
"""Test that accessing a nonexistent long path raises KeyError.""" """Test that accessing a nonexistent long path raises KeyError."""
with pytest.raises(KeyError): with pytest.raises(KeyError):
routerdata["device_information.long.path.foo"] routerdata[f"{KEY_DEVICE_INFORMATION}.long.path.foo"]
async def test_routerdata_get_simple(routerdata): async def test_routerdata_get_simple(routerdata):
"""Test that accessing a short, simple path works.""" """Test that accessing a short, simple path works."""
assert routerdata["device_information.SoftwareVersion"] == "1.0" assert routerdata[f"{KEY_DEVICE_INFORMATION}.SoftwareVersion"] == "1.0"
async def test_routerdata_get_longer(routerdata): async def test_routerdata_get_longer(routerdata):
"""Test that accessing a longer path works.""" """Test that accessing a longer path works."""
assert routerdata["device_information.nested.foo"] == "bar" assert routerdata[f"{KEY_DEVICE_INFORMATION}.nested.foo"] == "bar"
async def test_routerdata_get_dict(routerdata): async def test_routerdata_get_dict(routerdata):
"""Test that returning an intermediate dict works.""" """Test that returning an intermediate dict works."""
assert routerdata["device_information.nested"] == {"foo": "bar"} assert routerdata[f"{KEY_DEVICE_INFORMATION}.nested"] == {"foo": "bar"}