Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.33.0 #859

Merged
merged 11 commits into from
Aug 28, 2024
196 changes: 103 additions & 93 deletions README.md

Large diffs are not rendered by default.

82 changes: 60 additions & 22 deletions custom_components/asusrouter/bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 7,18 @@
from typing import Any, Callable, Optional

import aiohttp
from homeassistant.const import (
CONF_HOST,
CONF_PASSWORD,
CONF_PORT,
CONF_SSL,
CONF_USERNAME,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.aiohttp_client import async_create_clientsession
from homeassistant.helpers.update_coordinator import UpdateFailed

from asusrouter import AsusRouter
from asusrouter.error import AsusRouterError
from asusrouter.modules.aimesh import AiMeshDevice
Expand All @@ -20,20 32,10 @@
)
from asusrouter.modules.identity import AsusDevice
from asusrouter.modules.parental_control import ParentalControlRule, PCRuleType
from homeassistant.const import (
CONF_HOST,
CONF_PASSWORD,
CONF_PORT,
CONF_SSL,
CONF_USERNAME,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.aiohttp_client import async_create_clientsession
from homeassistant.helpers.update_coordinator import UpdateFailed

from . import helpers
from .const import (
AURA,
BOOTTIME,
CONF_CACHE_TIME,
CONF_DEFAULT_CACHE_TIME,
Expand Down Expand Up @@ -66,6 68,8 @@
TEMPERATURE,
WLAN,
)
from .modules.aura import aura_to_ha
from .modules.firmware import to_ha as firmware_to_ha

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -100,7 104,9 @@ def __init__(
self._active: bool = False

@staticmethod
def _get_api(configs: dict[str, Any], session: aiohttp.ClientSession) -> AsusRouter:
def _get_api(
configs: dict[str, Any], session: aiohttp.ClientSession
) -> AsusRouter:
"""Get AsusRouter API."""

return AsusRouter(
Expand Down Expand Up @@ -169,14 175,18 @@ async def async_clean(self) -> None:
# <-- Connection
# --------------------

async def async_cleanup_sensors(self, sensors: dict[str, Any]) -> dict[str, Any]:
async def async_cleanup_sensors(
self, sensors: dict[str, Any]
) -> dict[str, Any]:
"""Cleanup sensors depending on the device mode."""

mode = self._configs.get(CONF_MODE, CONF_DEFAULT_MODE)
available = MODE_SENSORS[mode]
_LOGGER.debug("Available sensors for mode=`%s`: %s", mode, available)
sensors = {
group: details for group, details in sensors.items() if group in available
group: details
for group, details in sensors.items()
if group in available
}

return sensors
Expand All @@ -185,7 195,14 @@ async def async_get_available_sensors(self) -> dict[str, dict[str, Any]]:
"""Get available sensors."""

sensors = {
BOOTTIME: {SENSORS: SENSORS_BOOTTIME, METHOD: self._get_data_boottime},
AURA: {
SENSORS: await self._get_sensors_modern(AsusData.AURA),
METHOD: self._get_data_aura,
},
BOOTTIME: {
SENSORS: SENSORS_BOOTTIME,
METHOD: self._get_data_boottime,
},
CPU: {
SENSORS: await self._get_sensors_modern(AsusData.CPU),
METHOD: self._get_data_cpu,
Expand All @@ -207,7 224,9 @@ async def async_get_available_sensors(self) -> dict[str, dict[str, Any]]:
METHOD: self._get_data_network,
},
"ovpn_client": {
SENSORS: await self._get_sensors_modern(AsusData.OPENVPN_CLIENT),
SENSORS: await self._get_sensors_modern(
AsusData.OPENVPN_CLIENT
),
METHOD: self._get_data_ovpn_client,
},
"ovpn_server": {
Expand Down Expand Up @@ -240,11 259,15 @@ async def async_get_available_sensors(self) -> dict[str, dict[str, Any]]:
METHOD: self._get_data_wan,
},
"wireguard_client": {
SENSORS: await self._get_sensors_modern(AsusData.WIREGUARD_CLIENT),
SENSORS: await self._get_sensors_modern(
AsusData.WIREGUARD_CLIENT
),
METHOD: self._get_data_wireguard_client,
},
"wireguard_server": {
SENSORS: await self._get_sensors_modern(AsusData.WIREGUARD_SERVER),
SENSORS: await self._get_sensors_modern(
AsusData.WIREGUARD_SERVER
),
METHOD: self._get_data_wireguard_server,
},
WLAN: {
Expand Down Expand Up @@ -304,6 327,13 @@ async def async_get_clients(self) -> dict[str, AsusClient]:
return await self._get_data(AsusData.CLIENTS, force=True)

# Sensor-specific methods
async def _get_data_aura(self) -> dict[str, Any]:
"""Get Aura data from the device."""

data = await self._get_data_modern(AsusData.AURA)

return aura_to_ha(data)

async def _get_data_boottime(self) -> dict[str, Any]:
"""Get `boottime` data from the device."""

Expand All @@ -317,7 347,9 @@ async def _get_data_cpu(self) -> dict[str, Any]:
async def _get_data_firmware(self) -> dict[str, Any]:
"""Get firmware data from the device."""

return await self._get_data(AsusData.FIRMWARE)
data = await self._get_data_modern(AsusData.FIRMWARE)

return firmware_to_ha(data)

async def _get_data_gwlan(self) -> dict[str, Any]:
"""Get GWLAN data from the device."""
Expand Down Expand Up @@ -492,7 524,9 @@ async def _get_sensors(
"Raw `%s` sensors of type (%s): %s", datatype, type(data), data
)
sensors = (
process(data) if process is not None else self._process_sensors(data)
process(data)
if process is not None
else self._process_sensors(data)
)
_LOGGER.debug("Available `%s` sensors: %s", sensor_type, sensors)
except AsusRouterError as ex:
Expand All @@ -516,7 550,9 @@ async def _get_sensors_modern(self, datatype: AsusData) -> list[str]:
"Raw `%s` sensors of type (%s): %s", datatype, type(data), data
)
sensors = convert_to_ha_sensors_list(data)
_LOGGER.debug("Available `%s` sensors: %s", datatype.value, sensors)
_LOGGER.debug(
"Available `%s` sensors: %s", datatype.value, sensors
)
except AsusRouterError as ex:
if datatype.value in DEFAULT_SENSORS:
sensors = DEFAULT_SENSORS[datatype.value]
Expand Down Expand Up @@ -637,7 673,9 @@ async def async_pc_rule(self, **kwargs: Any) -> bool:
reg_value = entity_reg.async_get(entity)
if not isinstance(reg_value, er.RegistryEntry):
continue
capabilities: dict[str, Any] = helpers.as_dict(reg_value.capabilities)
capabilities: dict[str, Any] = helpers.as_dict(
reg_value.capabilities
)
devices.append(capabilities)

# Convert devices to rules
Expand Down
55 changes: 38 additions & 17 deletions custom_components/asusrouter/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 5,17 @@
from datetime import datetime, timezone
from typing import Any, Callable, Optional

from homeassistant.core import callback
from homeassistant.helpers.device_registry import format_mac

from asusrouter.modules.client import (
AsusClient,
AsusClientConnection,
AsusClientConnectionWlan,
AsusClientDescription,
)
from asusrouter.modules.connection import ConnectionState
from asusrouter.modules.homeassistant import (
convert_to_ha_state_bool,
convert_to_ha_string,
)
from homeassistant.core import callback
from homeassistant.helpers.device_registry import format_mac
from asusrouter.modules.connection import ConnectionState, ConnectionType
from asusrouter.modules.homeassistant import convert_to_ha_state_bool

from .helpers import clean_dict

Expand Down Expand Up @@ -49,6 47,9 @@ def __init__(

# Connection state
self._state: ConnectionState = ConnectionState.UNKNOWN
self._connection_type: ConnectionType = ConnectionType.DISCONNECTED
self._guest: bool = False
self._guest_id: int = 0

# Device last active
self._last_activity: Optional[datetime] = None
Expand All @@ -58,7 59,9 @@ def update(
self,
client_info: Optional[AsusClient] = None,
consider_home: int = 0,
event_call: Optional[Callable[[str, Optional[dict[str, Any]]], None]] = None,
event_call: Optional[
Callable[[str, Optional[dict[str, Any]]], None]
] = None,
):
"""Update client information."""

Expand All @@ -77,7 80,7 @@ def update(
# Connected state
state = client_info.state

self._identity = self.generate_identity()
self._identity = self.generate_identity(state)
self._extra_state_attributes = self.generate_extra_state_attributes()

# If is connected
Expand Down Expand Up @@ -111,7 114,9 @@ def update(
self.identity,
)

def generate_identity(self) -> dict[str, Any]:
def generate_identity(
self, state: Optional[ConnectionState]
) -> dict[str, Any]:
"""Generate client identity."""

identity: dict[str, Any] = {
Expand All @@ -121,22 126,32 @@ def generate_identity(self) -> dict[str, Any]:
}

if isinstance(self.connection, AsusClientConnection):
identity["connection_type"] = convert_to_ha_string(self.connection.type)
# Rewrite guest from last known state if needed
if state == ConnectionState.DISCONNECTED:
identity["guest"] = self._guest
identity["guest_id"] = self._guest_id
if self.connection.type != ConnectionType.DISCONNECTED:
self._connection_type = self.connection.type
identity["connection_type"] = self._connection_type
identity["node"] = (
format_mac(self.connection.node) if self.connection.node else None
format_mac(self.connection.node)
if self.connection.node
else None
)

if isinstance(self.connection, AsusClientConnectionWlan):
identity["guest"] = self.connection.guest
identity["guest_id"] = self.connection.guest_id
identity["guest"] = self._guest = self.connection.guest
identity["guest_id"] = self._guest_id = self.connection.guest_id
identity["connected"] = self.connection.since

return clean_dict(identity)

def generate_extra_state_attributes(self) -> dict[str, Any]:
"""Generate extra state attributes."""

attributes: dict[str, Any] = self._identity.copy() if self._identity else {}
attributes: dict[str, Any] = (
self._identity.copy() if self._identity else {}
)

attributes["last_activity"] = self._last_activity

Expand Down Expand Up @@ -165,7 180,9 @@ def state(self) -> Optional[bool]:
def ip_address(self) -> Optional[str]:
"""Return IP address."""

return self.connection.ip_address if self.connection is not None else None
return (
self.connection.ip_address if self.connection is not None else None
)

@property
def mac_address(self) -> str:
Expand All @@ -177,7 194,11 @@ def mac_address(self) -> str:
def name(self) -> Optional[str]:
"""Return name."""

return self.description.name if self.description is not None else self._name
return (
self.description.name
if self.description is not None
else self._name
)

@property
def extra_state_attributes(self) -> dict[str, Any]:
Expand Down
Loading