Add Bridge module to AsusWRT (#84152)

* Add Bridge module to AsusWRT

* Requested changes

* Requested changes

* Requested changes

* Add check on router attributes value
This commit is contained in:
ollo69 2023-07-01 13:55:28 +02:00 committed by GitHub
parent c81b6255c2
commit 8108a0f947
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 332 additions and 214 deletions

View file

@ -0,0 +1,273 @@
"""aioasuswrt and pyasuswrt bridge classes."""
from __future__ import annotations
from abc import ABC, abstractmethod
from collections import namedtuple
import logging
from typing import Any, cast
from aioasuswrt.asuswrt import AsusWrt as AsusWrtLegacy
from homeassistant.const import (
CONF_HOST,
CONF_MODE,
CONF_PASSWORD,
CONF_PORT,
CONF_PROTOCOL,
CONF_USERNAME,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers.update_coordinator import UpdateFailed
from .const import (
CONF_DNSMASQ,
CONF_INTERFACE,
CONF_REQUIRE_IP,
CONF_SSH_KEY,
DEFAULT_DNSMASQ,
DEFAULT_INTERFACE,
KEY_METHOD,
KEY_SENSORS,
PROTOCOL_TELNET,
SENSORS_BYTES,
SENSORS_LOAD_AVG,
SENSORS_RATES,
SENSORS_TEMPERATURES,
)
SENSORS_TYPE_BYTES = "sensors_bytes"
SENSORS_TYPE_COUNT = "sensors_count"
SENSORS_TYPE_LOAD_AVG = "sensors_load_avg"
SENSORS_TYPE_RATES = "sensors_rates"
SENSORS_TYPE_TEMPERATURES = "sensors_temperatures"
WrtDevice = namedtuple("WrtDevice", ["ip", "name", "connected_to"])
_LOGGER = logging.getLogger(__name__)
def _get_dict(keys: list, values: list) -> dict[str, Any]:
"""Create a dict from a list of keys and values."""
return dict(zip(keys, values))
class AsusWrtBridge(ABC):
"""The Base Bridge abstract class."""
@staticmethod
def get_bridge(
hass: HomeAssistant, conf: dict[str, Any], options: dict[str, Any] | None = None
) -> AsusWrtBridge:
"""Get Bridge instance."""
return AsusWrtLegacyBridge(conf, options)
def __init__(self, host: str) -> None:
"""Initialize Bridge."""
self._host = host
self._firmware: str | None = None
self._label_mac: str | None = None
self._model: str | None = None
@property
def host(self) -> str:
"""Return hostname."""
return self._host
@property
def firmware(self) -> str | None:
"""Return firmware information."""
return self._firmware
@property
def label_mac(self) -> str | None:
"""Return label mac information."""
return self._label_mac
@property
def model(self) -> str | None:
"""Return model information."""
return self._model
@property
@abstractmethod
def is_connected(self) -> bool:
"""Get connected status."""
@abstractmethod
async def async_connect(self) -> None:
"""Connect to the device."""
@abstractmethod
async def async_disconnect(self) -> None:
"""Disconnect to the device."""
@abstractmethod
async def async_get_connected_devices(self) -> dict[str, WrtDevice]:
"""Get list of connected devices."""
@abstractmethod
async def async_get_available_sensors(self) -> dict[str, dict[str, Any]]:
"""Return a dictionary of available sensors for this bridge."""
class AsusWrtLegacyBridge(AsusWrtBridge):
"""The Bridge that use legacy library."""
def __init__(
self, conf: dict[str, Any], options: dict[str, Any] | None = None
) -> None:
"""Initialize Bridge."""
super().__init__(conf[CONF_HOST])
self._protocol: str = conf[CONF_PROTOCOL]
self._api: AsusWrtLegacy = self._get_api(conf, options)
@staticmethod
def _get_api(
conf: dict[str, Any], options: dict[str, Any] | None = None
) -> AsusWrtLegacy:
"""Get the AsusWrtLegacy API."""
opt = options or {}
return AsusWrtLegacy(
conf[CONF_HOST],
conf.get(CONF_PORT),
conf[CONF_PROTOCOL] == PROTOCOL_TELNET,
conf[CONF_USERNAME],
conf.get(CONF_PASSWORD, ""),
conf.get(CONF_SSH_KEY, ""),
conf[CONF_MODE],
opt.get(CONF_REQUIRE_IP, True),
interface=opt.get(CONF_INTERFACE, DEFAULT_INTERFACE),
dnsmasq=opt.get(CONF_DNSMASQ, DEFAULT_DNSMASQ),
)
@property
def is_connected(self) -> bool:
"""Get connected status."""
return cast(bool, self._api.is_connected)
async def async_connect(self) -> None:
"""Connect to the device."""
await self._api.connection.async_connect()
# get main router properties
if self._label_mac is None:
await self._get_label_mac()
if self._firmware is None:
await self._get_firmware()
if self._model is None:
await self._get_model()
async def async_disconnect(self) -> None:
"""Disconnect to the device."""
if self._api is not None and self._protocol == PROTOCOL_TELNET:
self._api.connection.disconnect()
async def async_get_connected_devices(self) -> dict[str, WrtDevice]:
"""Get list of connected devices."""
try:
api_devices = await self._api.async_get_connected_devices()
except OSError as exc:
raise UpdateFailed(exc) from exc
return {
format_mac(mac): WrtDevice(dev.ip, dev.name, None)
for mac, dev in api_devices.items()
}
async def _get_nvram_info(self, info_type: str) -> dict[str, Any]:
"""Get AsusWrt router info from nvram."""
info = {}
try:
info = await self._api.async_get_nvram(info_type)
except OSError as exc:
_LOGGER.warning(
"Error calling method async_get_nvram(%s): %s", info_type, exc
)
return info
async def _get_label_mac(self) -> None:
"""Get label mac information."""
label_mac = await self._get_nvram_info("LABEL_MAC")
if label_mac and "label_mac" in label_mac:
self._label_mac = format_mac(label_mac["label_mac"])
async def _get_firmware(self) -> None:
"""Get firmware information."""
firmware = await self._get_nvram_info("FIRMWARE")
if firmware and "firmver" in firmware:
firmver: str = firmware["firmver"]
if "buildno" in firmware:
firmver += f" (build {firmware['buildno']})"
self._firmware = firmver
async def _get_model(self) -> None:
"""Get model information."""
model = await self._get_nvram_info("MODEL")
if model and "model" in model:
self._model = model["model"]
async def async_get_available_sensors(self) -> dict[str, dict[str, Any]]:
"""Return a dictionary of available sensors for this bridge."""
sensors_temperatures = await self._get_available_temperature_sensors()
sensors_types = {
SENSORS_TYPE_BYTES: {
KEY_SENSORS: SENSORS_BYTES,
KEY_METHOD: self._get_bytes,
},
SENSORS_TYPE_LOAD_AVG: {
KEY_SENSORS: SENSORS_LOAD_AVG,
KEY_METHOD: self._get_load_avg,
},
SENSORS_TYPE_RATES: {
KEY_SENSORS: SENSORS_RATES,
KEY_METHOD: self._get_rates,
},
SENSORS_TYPE_TEMPERATURES: {
KEY_SENSORS: sensors_temperatures,
KEY_METHOD: self._get_temperatures,
},
}
return sensors_types
async def _get_available_temperature_sensors(self) -> list[str]:
"""Check which temperature information is available on the router."""
availability = await self._api.async_find_temperature_commands()
return [SENSORS_TEMPERATURES[i] for i in range(3) if availability[i]]
async def _get_bytes(self) -> dict[str, Any]:
"""Fetch byte information from the router."""
try:
datas = await self._api.async_get_bytes_total()
except (IndexError, OSError, ValueError) as exc:
raise UpdateFailed(exc) from exc
return _get_dict(SENSORS_BYTES, datas)
async def _get_rates(self) -> dict[str, Any]:
"""Fetch rates information from the router."""
try:
rates = await self._api.async_get_current_transfer_rates()
except (IndexError, OSError, ValueError) as exc:
raise UpdateFailed(exc) from exc
return _get_dict(SENSORS_RATES, rates)
async def _get_load_avg(self) -> dict[str, Any]:
"""Fetch load average information from the router."""
try:
avg = await self._api.async_get_loadavg()
except (IndexError, OSError, ValueError) as exc:
raise UpdateFailed(exc) from exc
return _get_dict(SENSORS_LOAD_AVG, avg)
async def _get_temperatures(self) -> dict[str, Any]:
"""Fetch temperatures information from the router."""
try:
temperatures: dict[str, Any] = await self._api.async_get_temperature()
except (OSError, ValueError) as exc:
raise UpdateFailed(exc) from exc
return temperatures

View file

@ -25,13 +25,13 @@ from homeassistant.const import (
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers.schema_config_entry_flow import ( from homeassistant.helpers.schema_config_entry_flow import (
SchemaCommonFlowHandler, SchemaCommonFlowHandler,
SchemaFlowFormStep, SchemaFlowFormStep,
SchemaOptionsFlowHandler, SchemaOptionsFlowHandler,
) )
from .bridge import AsusWrtBridge
from .const import ( from .const import (
CONF_DNSMASQ, CONF_DNSMASQ,
CONF_INTERFACE, CONF_INTERFACE,
@ -47,7 +47,6 @@ from .const import (
PROTOCOL_SSH, PROTOCOL_SSH,
PROTOCOL_TELNET, PROTOCOL_TELNET,
) )
from .router import get_api, get_nvram_info
LABEL_MAC = "LABEL_MAC" LABEL_MAC = "LABEL_MAC"
@ -143,16 +142,15 @@ class AsusWrtFlowHandler(ConfigFlow, domain=DOMAIN):
errors=errors or {}, errors=errors or {},
) )
@staticmethod
async def _async_check_connection( async def _async_check_connection(
user_input: dict[str, Any] self, user_input: dict[str, Any]
) -> tuple[str, str | None]: ) -> tuple[str, str | None]:
"""Attempt to connect the AsusWrt router.""" """Attempt to connect the AsusWrt router."""
host: str = user_input[CONF_HOST] host: str = user_input[CONF_HOST]
api = get_api(user_input) api = AsusWrtBridge.get_bridge(self.hass, user_input)
try: try:
await api.connection.async_connect() await api.async_connect()
except OSError: except OSError:
_LOGGER.error("Error connecting to the AsusWrt router at %s", host) _LOGGER.error("Error connecting to the AsusWrt router at %s", host)
@ -168,14 +166,9 @@ class AsusWrtFlowHandler(ConfigFlow, domain=DOMAIN):
_LOGGER.error("Error connecting to the AsusWrt router at %s", host) _LOGGER.error("Error connecting to the AsusWrt router at %s", host)
return RESULT_CONN_ERROR, None return RESULT_CONN_ERROR, None
label_mac = await get_nvram_info(api, LABEL_MAC) unique_id = api.label_mac
conf_protocol = user_input[CONF_PROTOCOL] await api.async_disconnect()
if conf_protocol == PROTOCOL_TELNET:
api.connection.disconnect()
unique_id = None
if label_mac and "label_mac" in label_mac:
unique_id = format_mac(label_mac["label_mac"])
return RESULT_SUCCESS, unique_id return RESULT_SUCCESS, unique_id
async def async_step_user( async def async_step_user(

View file

@ -13,6 +13,10 @@ DEFAULT_DNSMASQ = "/var/lib/misc"
DEFAULT_INTERFACE = "eth0" DEFAULT_INTERFACE = "eth0"
DEFAULT_TRACK_UNKNOWN = False DEFAULT_TRACK_UNKNOWN = False
KEY_COORDINATOR = "coordinator"
KEY_METHOD = "method"
KEY_SENSORS = "sensors"
MODE_AP = "ap" MODE_AP = "ap"
MODE_ROUTER = "router" MODE_ROUTER = "router"

View file

@ -6,22 +6,12 @@ from datetime import datetime, timedelta
import logging import logging
from typing import Any from typing import Any
from aioasuswrt.asuswrt import AsusWrt, Device as WrtDevice
from homeassistant.components.device_tracker import ( from homeassistant.components.device_tracker import (
CONF_CONSIDER_HOME, CONF_CONSIDER_HOME,
DEFAULT_CONSIDER_HOME, DEFAULT_CONSIDER_HOME,
DOMAIN as TRACKER_DOMAIN, DOMAIN as TRACKER_DOMAIN,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_HOST,
CONF_MODE,
CONF_PASSWORD,
CONF_PORT,
CONF_PROTOCOL,
CONF_USERNAME,
)
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
@ -32,55 +22,36 @@ from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from .bridge import AsusWrtBridge, WrtDevice
from .const import ( from .const import (
CONF_DNSMASQ, CONF_DNSMASQ,
CONF_INTERFACE, CONF_INTERFACE,
CONF_REQUIRE_IP, CONF_REQUIRE_IP,
CONF_SSH_KEY,
CONF_TRACK_UNKNOWN, CONF_TRACK_UNKNOWN,
DEFAULT_DNSMASQ, DEFAULT_DNSMASQ,
DEFAULT_INTERFACE, DEFAULT_INTERFACE,
DEFAULT_TRACK_UNKNOWN, DEFAULT_TRACK_UNKNOWN,
DOMAIN, DOMAIN,
PROTOCOL_TELNET, KEY_COORDINATOR,
SENSORS_BYTES, KEY_METHOD,
KEY_SENSORS,
SENSORS_CONNECTED_DEVICE, SENSORS_CONNECTED_DEVICE,
SENSORS_LOAD_AVG,
SENSORS_RATES,
SENSORS_TEMPERATURES,
) )
CONF_REQ_RELOAD = [CONF_DNSMASQ, CONF_INTERFACE, CONF_REQUIRE_IP] CONF_REQ_RELOAD = [CONF_DNSMASQ, CONF_INTERFACE, CONF_REQUIRE_IP]
DEFAULT_NAME = "Asuswrt" DEFAULT_NAME = "Asuswrt"
KEY_COORDINATOR = "coordinator"
KEY_SENSORS = "sensors"
SCAN_INTERVAL = timedelta(seconds=30) SCAN_INTERVAL = timedelta(seconds=30)
SENSORS_TYPE_BYTES = "sensors_bytes"
SENSORS_TYPE_COUNT = "sensors_count" SENSORS_TYPE_COUNT = "sensors_count"
SENSORS_TYPE_LOAD_AVG = "sensors_load_avg"
SENSORS_TYPE_RATES = "sensors_rates"
SENSORS_TYPE_TEMPERATURES = "sensors_temperatures"
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def _get_dict(keys: list, values: list) -> dict[str, Any]:
"""Create a dict from a list of keys and values."""
ret_dict: dict[str, Any] = dict.fromkeys(keys)
for index, key in enumerate(ret_dict):
ret_dict[key] = values[index]
return ret_dict
class AsusWrtSensorDataHandler: class AsusWrtSensorDataHandler:
"""Data handler for AsusWrt sensor.""" """Data handler for AsusWrt sensor."""
def __init__(self, hass: HomeAssistant, api: AsusWrt) -> None: def __init__(self, hass: HomeAssistant, api: AsusWrtBridge) -> None:
"""Initialize a AsusWrt sensor data handler.""" """Initialize a AsusWrt sensor data handler."""
self._hass = hass self._hass = hass
self._api = api self._api = api
@ -90,42 +61,6 @@ class AsusWrtSensorDataHandler:
"""Return number of connected devices.""" """Return number of connected devices."""
return {SENSORS_CONNECTED_DEVICE[0]: self._connected_devices} return {SENSORS_CONNECTED_DEVICE[0]: self._connected_devices}
async def _get_bytes(self) -> dict[str, Any]:
"""Fetch byte information from the router."""
try:
datas = await self._api.async_get_bytes_total()
except (OSError, ValueError) as exc:
raise UpdateFailed(exc) from exc
return _get_dict(SENSORS_BYTES, datas)
async def _get_rates(self) -> dict[str, Any]:
"""Fetch rates information from the router."""
try:
rates = await self._api.async_get_current_transfer_rates()
except (OSError, ValueError) as exc:
raise UpdateFailed(exc) from exc
return _get_dict(SENSORS_RATES, rates)
async def _get_load_avg(self) -> dict[str, Any]:
"""Fetch load average information from the router."""
try:
avg = await self._api.async_get_loadavg()
except (OSError, ValueError) as exc:
raise UpdateFailed(exc) from exc
return _get_dict(SENSORS_LOAD_AVG, avg)
async def _get_temperatures(self) -> dict[str, Any]:
"""Fetch temperatures information from the router."""
try:
temperatures: dict[str, Any] = await self._api.async_get_temperature()
except (OSError, ValueError) as exc:
raise UpdateFailed(exc) from exc
return temperatures
def update_device_count(self, conn_devices: int) -> bool: def update_device_count(self, conn_devices: int) -> bool:
"""Update connected devices attribute.""" """Update connected devices attribute."""
if self._connected_devices == conn_devices: if self._connected_devices == conn_devices:
@ -134,19 +69,17 @@ class AsusWrtSensorDataHandler:
return True return True
async def get_coordinator( async def get_coordinator(
self, sensor_type: str, should_poll: bool = True self,
sensor_type: str,
update_method: Callable[[], Any] | None = None,
) -> DataUpdateCoordinator: ) -> DataUpdateCoordinator:
"""Get the coordinator for a specific sensor type.""" """Get the coordinator for a specific sensor type."""
should_poll = True
if sensor_type == SENSORS_TYPE_COUNT: if sensor_type == SENSORS_TYPE_COUNT:
should_poll = False
method = self._get_connected_devices method = self._get_connected_devices
elif sensor_type == SENSORS_TYPE_BYTES: elif update_method is not None:
method = self._get_bytes method = update_method
elif sensor_type == SENSORS_TYPE_LOAD_AVG:
method = self._get_load_avg
elif sensor_type == SENSORS_TYPE_RATES:
method = self._get_rates
elif sensor_type == SENSORS_TYPE_TEMPERATURES:
method = self._get_temperatures
else: else:
raise RuntimeError(f"Invalid sensor type: {sensor_type}") raise RuntimeError(f"Invalid sensor type: {sensor_type}")
@ -226,12 +159,6 @@ class AsusWrtRouter:
self.hass = hass self.hass = hass
self._entry = entry self._entry = entry
self._api: AsusWrt = None
self._protocol: str = entry.data[CONF_PROTOCOL]
self._host: str = entry.data[CONF_HOST]
self._model: str = "Asus Router"
self._sw_v: str | None = None
self._devices: dict[str, AsusWrtDevInfo] = {} self._devices: dict[str, AsusWrtDevInfo] = {}
self._connected_devices: int = 0 self._connected_devices: int = 0
self._connect_error: bool = False self._connect_error: bool = False
@ -248,26 +175,19 @@ class AsusWrtRouter:
} }
self._options.update(entry.options) self._options.update(entry.options)
self._api: AsusWrtBridge = AsusWrtBridge.get_bridge(
self.hass, dict(self._entry.data), self._options
)
async def setup(self) -> None: async def setup(self) -> None:
"""Set up a AsusWrt router.""" """Set up a AsusWrt router."""
self._api = get_api(dict(self._entry.data), self._options)
try: try:
await self._api.connection.async_connect() await self._api.async_connect()
except OSError as exp: except OSError as exc:
raise ConfigEntryNotReady from exp raise ConfigEntryNotReady from exc
if not self._api.is_connected: if not self._api.is_connected:
raise ConfigEntryNotReady raise ConfigEntryNotReady
# System
model = await get_nvram_info(self._api, "MODEL")
if model and "model" in model:
self._model = model["model"]
firmware = await get_nvram_info(self._api, "FIRMWARE")
if firmware and "firmver" in firmware and "buildno" in firmware:
self._sw_v = f"{firmware['firmver']} (build {firmware['buildno']})"
# Load tracked entities from registry # Load tracked entities from registry
entity_reg = er.async_get(self.hass) entity_reg = er.async_get(self.hass)
track_entries = er.async_entries_for_config_entry( track_entries = er.async_entries_for_config_entry(
@ -312,24 +232,24 @@ class AsusWrtRouter:
async def update_devices(self) -> None: async def update_devices(self) -> None:
"""Update AsusWrt devices tracker.""" """Update AsusWrt devices tracker."""
new_device = False new_device = False
_LOGGER.debug("Checking devices for ASUS router %s", self._host) _LOGGER.debug("Checking devices for ASUS router %s", self.host)
try: try:
api_devices = await self._api.async_get_connected_devices() wrt_devices = await self._api.async_get_connected_devices()
except OSError as exc: except UpdateFailed as exc:
if not self._connect_error: if not self._connect_error:
self._connect_error = True self._connect_error = True
_LOGGER.error( _LOGGER.error(
"Error connecting to ASUS router %s for device update: %s", "Error connecting to ASUS router %s for device update: %s",
self._host, self.host,
exc, exc,
) )
return return
if self._connect_error: if self._connect_error:
self._connect_error = False self._connect_error = False
_LOGGER.info("Reconnected to ASUS router %s", self._host) _LOGGER.info("Reconnected to ASUS router %s", self.host)
self._connected_devices = len(api_devices) self._connected_devices = len(wrt_devices)
consider_home: int = self._options.get( consider_home: int = self._options.get(
CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME.total_seconds() CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME.total_seconds()
) )
@ -337,7 +257,6 @@ class AsusWrtRouter:
CONF_TRACK_UNKNOWN, DEFAULT_TRACK_UNKNOWN CONF_TRACK_UNKNOWN, DEFAULT_TRACK_UNKNOWN
) )
wrt_devices = {format_mac(mac): dev for mac, dev in api_devices.items()}
for device_mac, device in self._devices.items(): for device_mac, device in self._devices.items():
dev_info = wrt_devices.pop(device_mac, None) dev_info = wrt_devices.pop(device_mac, None)
device.update(dev_info, consider_home) device.update(dev_info, consider_home)
@ -363,19 +282,14 @@ class AsusWrtRouter:
self._sensors_data_handler = AsusWrtSensorDataHandler(self.hass, self._api) self._sensors_data_handler = AsusWrtSensorDataHandler(self.hass, self._api)
self._sensors_data_handler.update_device_count(self._connected_devices) self._sensors_data_handler.update_device_count(self._connected_devices)
sensors_types: dict[str, list[str]] = { sensors_types = await self._api.async_get_available_sensors()
SENSORS_TYPE_BYTES: SENSORS_BYTES, sensors_types[SENSORS_TYPE_COUNT] = {KEY_SENSORS: SENSORS_CONNECTED_DEVICE}
SENSORS_TYPE_COUNT: SENSORS_CONNECTED_DEVICE,
SENSORS_TYPE_LOAD_AVG: SENSORS_LOAD_AVG,
SENSORS_TYPE_RATES: SENSORS_RATES,
SENSORS_TYPE_TEMPERATURES: await self._get_available_temperature_sensors(),
}
for sensor_type, sensor_names in sensors_types.items(): for sensor_type, sensor_def in sensors_types.items():
if not sensor_names: if not (sensor_names := sensor_def.get(KEY_SENSORS)):
continue continue
coordinator = await self._sensors_data_handler.get_coordinator( coordinator = await self._sensors_data_handler.get_coordinator(
sensor_type, sensor_type != SENSORS_TYPE_COUNT sensor_type, update_method=sensor_def.get(KEY_METHOD)
) )
self._sensors_coordinator[sensor_type] = { self._sensors_coordinator[sensor_type] = {
KEY_COORDINATOR: coordinator, KEY_COORDINATOR: coordinator,
@ -392,31 +306,10 @@ class AsusWrtRouter:
if self._sensors_data_handler.update_device_count(self._connected_devices): if self._sensors_data_handler.update_device_count(self._connected_devices):
await coordinator.async_refresh() await coordinator.async_refresh()
async def _get_available_temperature_sensors(self) -> list[str]:
"""Check which temperature information is available on the router."""
try:
availability = await self._api.async_find_temperature_commands()
available_sensors = [
SENSORS_TEMPERATURES[i] for i in range(3) if availability[i]
]
except Exception as exc: # pylint: disable=broad-except
_LOGGER.debug(
(
"Failed checking temperature sensor availability for ASUS router"
" %s. Exception: %s"
),
self._host,
exc,
)
return []
return available_sensors
async def close(self) -> None: async def close(self) -> None:
"""Close the connection.""" """Close the connection."""
if self._api is not None and self._protocol == PROTOCOL_TELNET: if self._api is not None:
self._api.connection.disconnect() await self._api.async_disconnect()
self._api = None
for func in self._on_close: for func in self._on_close:
func() func()
@ -443,14 +336,17 @@ class AsusWrtRouter:
@property @property
def device_info(self) -> DeviceInfo: def device_info(self) -> DeviceInfo:
"""Return the device information.""" """Return the device information."""
return DeviceInfo( info = DeviceInfo(
identifiers={(DOMAIN, self.unique_id or "AsusWRT")}, identifiers={(DOMAIN, self.unique_id or "AsusWRT")},
name=self._host, name=self.host,
model=self._model, model=self._api.model or "Asus Router",
manufacturer="Asus", manufacturer="Asus",
sw_version=self._sw_v, configuration_url=f"http://{self.host}",
configuration_url=f"http://{self._host}",
) )
if self._api.firmware:
info["sw_version"] = self._api.firmware
return info
@property @property
def signal_device_new(self) -> str: def signal_device_new(self) -> str:
@ -465,7 +361,7 @@ class AsusWrtRouter:
@property @property
def host(self) -> str: def host(self) -> str:
"""Return router hostname.""" """Return router hostname."""
return self._host return self._api.host
@property @property
def unique_id(self) -> str | None: def unique_id(self) -> str | None:
@ -475,7 +371,7 @@ class AsusWrtRouter:
@property @property
def name(self) -> str: def name(self) -> str:
"""Return router name.""" """Return router name."""
return self._host if self.unique_id else DEFAULT_NAME return self.host if self.unique_id else DEFAULT_NAME
@property @property
def devices(self) -> dict[str, AsusWrtDevInfo]: def devices(self) -> dict[str, AsusWrtDevInfo]:
@ -486,32 +382,3 @@ class AsusWrtRouter:
def sensors_coordinator(self) -> dict[str, Any]: def sensors_coordinator(self) -> dict[str, Any]:
"""Return sensors coordinators.""" """Return sensors coordinators."""
return self._sensors_coordinator return self._sensors_coordinator
async def get_nvram_info(api: AsusWrt, info_type: str) -> dict[str, Any]:
"""Get AsusWrt router info from nvram."""
info = {}
try:
info = await api.async_get_nvram(info_type)
except OSError as exc:
_LOGGER.warning("Error calling method async_get_nvram(%s): %s", info_type, exc)
return info
def get_api(conf: dict[str, Any], options: dict[str, Any] | None = None) -> AsusWrt:
"""Get the AsusWrt API."""
opt = options or {}
return AsusWrt(
conf[CONF_HOST],
conf.get(CONF_PORT),
conf[CONF_PROTOCOL] == PROTOCOL_TELNET,
conf[CONF_USERNAME],
conf.get(CONF_PASSWORD, ""),
conf.get(CONF_SSH_KEY, ""),
conf[CONF_MODE],
opt.get(CONF_REQUIRE_IP, True),
interface=opt.get(CONF_INTERFACE, DEFAULT_INTERFACE),
dnsmasq=opt.get(CONF_DNSMASQ, DEFAULT_DNSMASQ),
)

View file

@ -26,13 +26,15 @@ from homeassistant.helpers.update_coordinator import (
from .const import ( from .const import (
DATA_ASUSWRT, DATA_ASUSWRT,
DOMAIN, DOMAIN,
KEY_COORDINATOR,
KEY_SENSORS,
SENSORS_BYTES, SENSORS_BYTES,
SENSORS_CONNECTED_DEVICE, SENSORS_CONNECTED_DEVICE,
SENSORS_LOAD_AVG, SENSORS_LOAD_AVG,
SENSORS_RATES, SENSORS_RATES,
SENSORS_TEMPERATURES, SENSORS_TEMPERATURES,
) )
from .router import KEY_COORDINATOR, KEY_SENSORS, AsusWrtRouter from .router import AsusWrtRouter
@dataclass @dataclass

View file

@ -62,7 +62,7 @@ def mock_unique_id_fixture():
@pytest.fixture(name="connect") @pytest.fixture(name="connect")
def mock_controller_connect(mock_unique_id): def mock_controller_connect(mock_unique_id):
"""Mock a successful connection.""" """Mock a successful connection."""
with patch("homeassistant.components.asuswrt.router.AsusWrt") as service_mock: with patch("homeassistant.components.asuswrt.bridge.AsusWrtLegacy") as service_mock:
service_mock.return_value.connection.async_connect = AsyncMock() service_mock.return_value.connection.async_connect = AsyncMock()
service_mock.return_value.is_connected = True service_mock.return_value.is_connected = True
service_mock.return_value.connection.disconnect = Mock() service_mock.return_value.connection.disconnect = Mock()
@ -236,11 +236,12 @@ async def test_on_connect_failed(hass: HomeAssistant, side_effect, error) -> Non
) )
with PATCH_GET_HOST, patch( with PATCH_GET_HOST, patch(
"homeassistant.components.asuswrt.router.AsusWrt" "homeassistant.components.asuswrt.bridge.AsusWrtLegacy"
) as asus_wrt: ) as asus_wrt:
asus_wrt.return_value.connection.async_connect = AsyncMock( asus_wrt.return_value.connection.async_connect = AsyncMock(
side_effect=side_effect side_effect=side_effect
) )
asus_wrt.return_value.async_get_nvram = AsyncMock(return_value={})
asus_wrt.return_value.is_connected = False asus_wrt.return_value.is_connected = False
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(

View file

@ -32,7 +32,7 @@ from homeassistant.util.dt import utcnow
from tests.common import MockConfigEntry, async_fire_time_changed from tests.common import MockConfigEntry, async_fire_time_changed
ASUSWRT_LIB = "homeassistant.components.asuswrt.router.AsusWrt" ASUSWRT_LIB = "homeassistant.components.asuswrt.bridge.AsusWrtLegacy"
HOST = "myrouter.asuswrt.com" HOST = "myrouter.asuswrt.com"
IP_ADDRESS = "192.168.1.1" IP_ADDRESS = "192.168.1.1"
@ -311,28 +311,6 @@ async def test_loadavg_sensors(
assert hass.states.get(f"{sensor_prefix}_load_avg_15m").state == "1.3" assert hass.states.get(f"{sensor_prefix}_load_avg_15m").state == "1.3"
async def test_temperature_sensors_fail(
hass: HomeAssistant,
connect,
mock_available_temps,
) -> None:
"""Test fail creating AsusWRT temperature sensors."""
config_entry, sensor_prefix = _setup_entry(hass, CONFIG_DATA, SENSORS_TEMP)
config_entry.add_to_hass(hass)
# Only length of 3 booleans is valid. Checking the exception handling.
mock_available_temps.pop(2)
# initial devices setup
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
# assert temperature availability exception is handled correctly
assert not hass.states.get(f"{sensor_prefix}_2_4ghz_temperature")
assert not hass.states.get(f"{sensor_prefix}_5ghz_temperature")
assert not hass.states.get(f"{sensor_prefix}_cpu_temperature")
async def test_temperature_sensors( async def test_temperature_sensors(
hass: HomeAssistant, hass: HomeAssistant,
connect, connect,