From 9514f491f0cc6bc6e813f8d5b8280c62784f7d33 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Wed, 25 May 2022 08:38:47 +0200 Subject: [PATCH] Add netgear speed test sensor (#72215) * implement speed_test * fix units * restore last speedtest result * fix import * fix restore state is None * fix styling * fix mypy * Use newer notation * correct unit * fix typing * fix pylint * fix issort * use RestoreSensor * fix import * fix sensor restore * do not extend SensorEntity * fix mypy * fix typing 2 --- homeassistant/components/netgear/__init__.py | 14 +++++ homeassistant/components/netgear/const.py | 1 + homeassistant/components/netgear/router.py | 7 +++ homeassistant/components/netgear/sensor.py | 63 ++++++++++++++++++-- 4 files changed, 81 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/netgear/__init__.py b/homeassistant/components/netgear/__init__.py index 518a9051847..7a4d0e7a8cd 100644 --- a/homeassistant/components/netgear/__init__.py +++ b/homeassistant/components/netgear/__init__.py @@ -15,6 +15,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import ( DOMAIN, KEY_COORDINATOR, + KEY_COORDINATOR_SPEED, KEY_COORDINATOR_TRAFFIC, KEY_ROUTER, MODE_ROUTER, @@ -26,6 +27,7 @@ from .router import NetgearRouter _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(seconds=30) +SPEED_TEST_INTERVAL = timedelta(seconds=1800) async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: @@ -78,6 +80,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """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() + # Create update coordinators coordinator = DataUpdateCoordinator( hass, @@ -93,6 +99,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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, + ) if router.mode == MODE_ROUTER: await coordinator.async_config_entry_first_refresh() @@ -102,6 +115,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: KEY_ROUTER: router, KEY_COORDINATOR: coordinator, KEY_COORDINATOR_TRAFFIC: coordinator_traffic_meter, + KEY_COORDINATOR_SPEED: coordinator_speed_test, } hass.config_entries.async_setup_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/netgear/const.py b/homeassistant/components/netgear/const.py index bc4f37114fd..936777a7961 100644 --- a/homeassistant/components/netgear/const.py +++ b/homeassistant/components/netgear/const.py @@ -12,6 +12,7 @@ CONF_CONSIDER_HOME = "consider_home" KEY_ROUTER = "router" KEY_COORDINATOR = "coordinator" KEY_COORDINATOR_TRAFFIC = "coordinator_traffic" +KEY_COORDINATOR_SPEED = "coordinator_speed" DEFAULT_CONSIDER_HOME = timedelta(seconds=180) DEFAULT_NAME = "Netgear router" diff --git a/homeassistant/components/netgear/router.py b/homeassistant/components/netgear/router.py index 6fb44e569d6..301906f22b6 100644 --- a/homeassistant/components/netgear/router.py +++ b/homeassistant/components/netgear/router.py @@ -208,6 +208,13 @@ class NetgearRouter: async with self._api_lock: return await self.hass.async_add_executor_job(self._api.get_traffic_meter) + async def async_get_speed_test(self) -> dict[str, Any] | None: + """Perform a speed test and get the results from the router.""" + async with self._api_lock: + return await self.hass.async_add_executor_job( + self._api.get_new_speed_test_result + ) + async def async_allow_block_device(self, mac: str, allow_block: str) -> None: """Allow or block a device connected to the router.""" async with self._api_lock: diff --git a/homeassistant/components/netgear/sensor.py b/homeassistant/components/netgear/sensor.py index 16c63a8cdcb..9dec1ab3390 100644 --- a/homeassistant/components/netgear/sensor.py +++ b/homeassistant/components/netgear/sensor.py @@ -1,20 +1,37 @@ """Support for Netgear routers.""" +from __future__ import annotations + from collections.abc import Callable from dataclasses import dataclass +from datetime import date, datetime +from decimal import Decimal from homeassistant.components.sensor import ( + RestoreSensor, SensorDeviceClass, SensorEntity, SensorEntityDescription, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import DATA_MEGABYTES, PERCENTAGE +from homeassistant.const import ( + DATA_MEGABYTES, + DATA_RATE_MEGABITS_PER_SECOND, + PERCENTAGE, + TIME_MILLISECONDS, +) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import StateType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from .const import DOMAIN, KEY_COORDINATOR, KEY_COORDINATOR_TRAFFIC, KEY_ROUTER +from .const import ( + DOMAIN, + KEY_COORDINATOR, + KEY_COORDINATOR_SPEED, + KEY_COORDINATOR_TRAFFIC, + KEY_ROUTER, +) from .router import NetgearDeviceEntity, NetgearRouter, NetgearRouterEntity SENSOR_TYPES = { @@ -200,6 +217,30 @@ SENSOR_TRAFFIC_TYPES = [ ), ] +SENSOR_SPEED_TYPES = [ + NetgearSensorEntityDescription( + key="NewOOKLAUplinkBandwidth", + name="Uplink Bandwidth", + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=DATA_RATE_MEGABITS_PER_SECOND, + icon="mdi:upload", + ), + NetgearSensorEntityDescription( + key="NewOOKLADownlinkBandwidth", + name="Downlink Bandwidth", + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=DATA_RATE_MEGABITS_PER_SECOND, + icon="mdi:download", + ), + NetgearSensorEntityDescription( + key="AveragePing", + name="Average Ping", + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=TIME_MILLISECONDS, + icon="mdi:wan", + ), +] + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback @@ -208,6 +249,7 @@ async def async_setup_entry( router = hass.data[DOMAIN][entry.entry_id][KEY_ROUTER] coordinator = hass.data[DOMAIN][entry.entry_id][KEY_COORDINATOR] coordinator_traffic = hass.data[DOMAIN][entry.entry_id][KEY_COORDINATOR_TRAFFIC] + coordinator_speed = hass.data[DOMAIN][entry.entry_id][KEY_COORDINATOR_SPEED] # Router entities router_entities = [] @@ -217,6 +259,11 @@ async def async_setup_entry( NetgearRouterSensorEntity(coordinator_traffic, router, description) ) + for description in SENSOR_SPEED_TYPES: + router_entities.append( + NetgearRouterSensorEntity(coordinator_speed, router, description) + ) + async_add_entities(router_entities) # Entities per network device @@ -288,7 +335,7 @@ class NetgearSensorEntity(NetgearDeviceEntity, SensorEntity): self._state = self._device[self._attribute] -class NetgearRouterSensorEntity(NetgearRouterEntity, SensorEntity): +class NetgearRouterSensorEntity(NetgearRouterEntity, RestoreSensor): """Representation of a device connected to a Netgear router.""" _attr_entity_registry_enabled_default = False @@ -306,7 +353,7 @@ class NetgearRouterSensorEntity(NetgearRouterEntity, SensorEntity): self._name = f"{router.device_name} {entity_description.name}" self._unique_id = f"{router.serial_number}-{entity_description.key}-{entity_description.index}" - self._value = None + self._value: StateType | date | datetime | Decimal = None self.async_update_device() @property @@ -314,6 +361,14 @@ class NetgearRouterSensorEntity(NetgearRouterEntity, SensorEntity): """Return the state of the sensor.""" return self._value + async def async_added_to_hass(self) -> None: + """Handle entity which will be added.""" + await super().async_added_to_hass() + if self.coordinator.data is None: + sensor_data = await self.async_get_last_sensor_data() + if sensor_data is not None: + self._value = sensor_data.native_value + @callback def async_update_device(self) -> None: """Update the Netgear device."""