227 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			227 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Python
		
	
	
"""Support for Netgear routers."""
 | 
						|
from __future__ import annotations
 | 
						|
 | 
						|
from datetime import timedelta
 | 
						|
import logging
 | 
						|
from typing import Any
 | 
						|
 | 
						|
from homeassistant.config_entries import ConfigEntry
 | 
						|
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_SSL
 | 
						|
from homeassistant.core import HomeAssistant
 | 
						|
from homeassistant.exceptions import ConfigEntryNotReady
 | 
						|
from homeassistant.helpers import device_registry as dr, entity_registry as er
 | 
						|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
 | 
						|
 | 
						|
from .const import (
 | 
						|
    DOMAIN,
 | 
						|
    KEY_COORDINATOR,
 | 
						|
    KEY_COORDINATOR_FIRMWARE,
 | 
						|
    KEY_COORDINATOR_LINK,
 | 
						|
    KEY_COORDINATOR_SPEED,
 | 
						|
    KEY_COORDINATOR_TRAFFIC,
 | 
						|
    KEY_COORDINATOR_UTIL,
 | 
						|
    KEY_ROUTER,
 | 
						|
    PLATFORMS,
 | 
						|
)
 | 
						|
from .errors import CannotLoginException
 | 
						|
from .router import NetgearRouter
 | 
						|
 | 
						|
_LOGGER = logging.getLogger(__name__)
 | 
						|
 | 
						|
SCAN_INTERVAL = timedelta(seconds=30)
 | 
						|
SPEED_TEST_INTERVAL = timedelta(hours=2)
 | 
						|
SCAN_INTERVAL_FIRMWARE = timedelta(hours=5)
 | 
						|
 | 
						|
 | 
						|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
 | 
						|
    """Set up Netgear component."""
 | 
						|
    router = NetgearRouter(hass, entry)
 | 
						|
    try:
 | 
						|
        if not await router.async_setup():
 | 
						|
            raise ConfigEntryNotReady
 | 
						|
    except CannotLoginException as ex:
 | 
						|
        raise ConfigEntryNotReady from ex
 | 
						|
 | 
						|
    port = entry.data.get(CONF_PORT)
 | 
						|
    ssl = entry.data.get(CONF_SSL)
 | 
						|
    if port != router.port or ssl != router.ssl:
 | 
						|
        data = {**entry.data, CONF_PORT: router.port, CONF_SSL: router.ssl}
 | 
						|
        hass.config_entries.async_update_entry(entry, data=data)
 | 
						|
        _LOGGER.info(
 | 
						|
            (
 | 
						|
                "Netgear port-SSL combination updated from (%i, %r) to (%i, %r), "
 | 
						|
                "this should only occur after a firmware update"
 | 
						|
            ),
 | 
						|
            port,
 | 
						|
            ssl,
 | 
						|
            router.port,
 | 
						|
            router.ssl,
 | 
						|
        )
 | 
						|
 | 
						|
    hass.data.setdefault(DOMAIN, {})
 | 
						|
 | 
						|
    entry.async_on_unload(entry.add_update_listener(update_listener))
 | 
						|
 | 
						|
    assert entry.unique_id
 | 
						|
    device_registry = dr.async_get(hass)
 | 
						|
    device_registry.async_get_or_create(
 | 
						|
        config_entry_id=entry.entry_id,
 | 
						|
        identifiers={(DOMAIN, entry.unique_id)},
 | 
						|
        manufacturer="Netgear",
 | 
						|
        name=router.device_name,
 | 
						|
        model=router.model,
 | 
						|
        sw_version=router.firmware_version,
 | 
						|
        hw_version=router.hardware_version,
 | 
						|
        configuration_url=f"http://{entry.data[CONF_HOST]}/",
 | 
						|
    )
 | 
						|
 | 
						|
    async def async_update_devices() -> bool:
 | 
						|
        """Fetch data from the router."""
 | 
						|
        if router.track_devices:
 | 
						|
            return await router.async_update_device_trackers()
 | 
						|
        return False
 | 
						|
 | 
						|
    async def async_update_traffic_meter() -> dict[str, Any] | None:
 | 
						|
        """Fetch data from the router."""
 | 
						|
        return await router.async_get_traffic_meter()
 | 
						|
 | 
						|
    async def async_update_speed_test() -> dict[str, Any] | None:
 | 
						|
        """Fetch data from the router."""
 | 
						|
        return await router.async_get_speed_test()
 | 
						|
 | 
						|
    async def async_check_firmware() -> dict[str, Any] | None:
 | 
						|
        """Check for new firmware of the router."""
 | 
						|
        return await router.async_check_new_firmware()
 | 
						|
 | 
						|
    async def async_update_utilization() -> dict[str, Any] | None:
 | 
						|
        """Fetch data from the router."""
 | 
						|
        return await router.async_get_utilization()
 | 
						|
 | 
						|
    async def async_check_link_status() -> dict[str, Any] | None:
 | 
						|
        """Fetch data from the router."""
 | 
						|
        return await router.async_get_link_status()
 | 
						|
 | 
						|
    # Create update coordinators
 | 
						|
    coordinator = DataUpdateCoordinator(
 | 
						|
        hass,
 | 
						|
        _LOGGER,
 | 
						|
        name=f"{router.device_name} Devices",
 | 
						|
        update_method=async_update_devices,
 | 
						|
        update_interval=SCAN_INTERVAL,
 | 
						|
    )
 | 
						|
    coordinator_traffic_meter = DataUpdateCoordinator(
 | 
						|
        hass,
 | 
						|
        _LOGGER,
 | 
						|
        name=f"{router.device_name} Traffic meter",
 | 
						|
        update_method=async_update_traffic_meter,
 | 
						|
        update_interval=SCAN_INTERVAL,
 | 
						|
    )
 | 
						|
    coordinator_speed_test = DataUpdateCoordinator(
 | 
						|
        hass,
 | 
						|
        _LOGGER,
 | 
						|
        name=f"{router.device_name} Speed test",
 | 
						|
        update_method=async_update_speed_test,
 | 
						|
        update_interval=SPEED_TEST_INTERVAL,
 | 
						|
    )
 | 
						|
    coordinator_firmware = DataUpdateCoordinator(
 | 
						|
        hass,
 | 
						|
        _LOGGER,
 | 
						|
        name=f"{router.device_name} Firmware",
 | 
						|
        update_method=async_check_firmware,
 | 
						|
        update_interval=SCAN_INTERVAL_FIRMWARE,
 | 
						|
    )
 | 
						|
    coordinator_utilization = DataUpdateCoordinator(
 | 
						|
        hass,
 | 
						|
        _LOGGER,
 | 
						|
        name=f"{router.device_name} Utilization",
 | 
						|
        update_method=async_update_utilization,
 | 
						|
        update_interval=SCAN_INTERVAL,
 | 
						|
    )
 | 
						|
    coordinator_link = DataUpdateCoordinator(
 | 
						|
        hass,
 | 
						|
        _LOGGER,
 | 
						|
        name=f"{router.device_name} Ethernet Link Status",
 | 
						|
        update_method=async_check_link_status,
 | 
						|
        update_interval=SCAN_INTERVAL,
 | 
						|
    )
 | 
						|
 | 
						|
    if router.track_devices:
 | 
						|
        await coordinator.async_config_entry_first_refresh()
 | 
						|
    await coordinator_traffic_meter.async_config_entry_first_refresh()
 | 
						|
    await coordinator_firmware.async_config_entry_first_refresh()
 | 
						|
    await coordinator_utilization.async_config_entry_first_refresh()
 | 
						|
    await coordinator_link.async_config_entry_first_refresh()
 | 
						|
 | 
						|
    hass.data[DOMAIN][entry.entry_id] = {
 | 
						|
        KEY_ROUTER: router,
 | 
						|
        KEY_COORDINATOR: coordinator,
 | 
						|
        KEY_COORDINATOR_TRAFFIC: coordinator_traffic_meter,
 | 
						|
        KEY_COORDINATOR_SPEED: coordinator_speed_test,
 | 
						|
        KEY_COORDINATOR_FIRMWARE: coordinator_firmware,
 | 
						|
        KEY_COORDINATOR_UTIL: coordinator_utilization,
 | 
						|
        KEY_COORDINATOR_LINK: coordinator_link,
 | 
						|
    }
 | 
						|
 | 
						|
    await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
 | 
						|
 | 
						|
    return True
 | 
						|
 | 
						|
 | 
						|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
 | 
						|
    """Unload a config entry."""
 | 
						|
    unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
 | 
						|
 | 
						|
    router = hass.data[DOMAIN][entry.entry_id][KEY_ROUTER]
 | 
						|
 | 
						|
    if unload_ok:
 | 
						|
        hass.data[DOMAIN].pop(entry.entry_id)
 | 
						|
        if not hass.data[DOMAIN]:
 | 
						|
            hass.data.pop(DOMAIN)
 | 
						|
 | 
						|
    if not router.track_devices:
 | 
						|
        router_id = None
 | 
						|
        # Remove devices that are no longer tracked
 | 
						|
        device_registry = dr.async_get(hass)
 | 
						|
        devices = dr.async_entries_for_config_entry(device_registry, entry.entry_id)
 | 
						|
        for device_entry in devices:
 | 
						|
            if device_entry.via_device_id is None:
 | 
						|
                router_id = device_entry.id
 | 
						|
                continue  # do not remove the router itself
 | 
						|
            device_registry.async_update_device(
 | 
						|
                device_entry.id, remove_config_entry_id=entry.entry_id
 | 
						|
            )
 | 
						|
        # Remove entities that are no longer tracked
 | 
						|
        entity_registry = er.async_get(hass)
 | 
						|
        entries = er.async_entries_for_config_entry(entity_registry, entry.entry_id)
 | 
						|
        for entity_entry in entries:
 | 
						|
            if entity_entry.device_id is not router_id:
 | 
						|
                entity_registry.async_remove(entity_entry.entity_id)
 | 
						|
 | 
						|
    return unload_ok
 | 
						|
 | 
						|
 | 
						|
async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
 | 
						|
    """Handle options update."""
 | 
						|
    await hass.config_entries.async_reload(config_entry.entry_id)
 | 
						|
 | 
						|
 | 
						|
async def async_remove_config_entry_device(
 | 
						|
    hass: HomeAssistant, config_entry: ConfigEntry, device_entry: dr.DeviceEntry
 | 
						|
) -> bool:
 | 
						|
    """Remove a device from a config entry."""
 | 
						|
    router = hass.data[DOMAIN][config_entry.entry_id][KEY_ROUTER]
 | 
						|
 | 
						|
    device_mac = None
 | 
						|
    for connection in device_entry.connections:
 | 
						|
        if connection[0] == dr.CONNECTION_NETWORK_MAC:
 | 
						|
            device_mac = connection[1]
 | 
						|
            break
 | 
						|
 | 
						|
    if device_mac is None:
 | 
						|
        return False
 | 
						|
 | 
						|
    if device_mac not in router.devices:
 | 
						|
        return True
 | 
						|
 | 
						|
    return not router.devices[device_mac]["active"]
 |