Bump devolo_plc_api to 1.0.0 (#85235)

* Bump devolo_plc_api to 1.0.0

* Fix pylint
This commit is contained in:
Guido Schmitz 2023-01-06 00:49:59 +01:00 committed by GitHub
parent 57f792d88f
commit 21fbe07218
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 127 additions and 123 deletions

View File

@ -2,11 +2,12 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
from typing import Any
import async_timeout import async_timeout
from devolo_plc_api.device import Device from devolo_plc_api import Device
from devolo_plc_api.device_api import ConnectedStationInfo, NeighborAPInfo
from devolo_plc_api.exceptions.device import DeviceNotFound, DeviceUnavailable from devolo_plc_api.exceptions.device import DeviceNotFound, DeviceUnavailable
from devolo_plc_api.plcnet_api import LogicalNetwork
from homeassistant.components import zeroconf from homeassistant.components import zeroconf
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@ -48,27 +49,30 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
f"Unable to connect to {entry.data[CONF_IP_ADDRESS]}" f"Unable to connect to {entry.data[CONF_IP_ADDRESS]}"
) from err ) from err
async def async_update_connected_plc_devices() -> dict[str, Any]: async def async_update_connected_plc_devices() -> LogicalNetwork:
"""Fetch data from API endpoint.""" """Fetch data from API endpoint."""
assert device.plcnet
try: try:
async with async_timeout.timeout(10): async with async_timeout.timeout(10):
return await device.plcnet.async_get_network_overview() # type: ignore[no-any-return, union-attr] return await device.plcnet.async_get_network_overview()
except DeviceUnavailable as err: except DeviceUnavailable as err:
raise UpdateFailed(err) from err raise UpdateFailed(err) from err
async def async_update_wifi_connected_station() -> dict[str, Any]: async def async_update_wifi_connected_station() -> list[ConnectedStationInfo]:
"""Fetch data from API endpoint.""" """Fetch data from API endpoint."""
assert device.device
try: try:
async with async_timeout.timeout(10): async with async_timeout.timeout(10):
return await device.device.async_get_wifi_connected_station() # type: ignore[no-any-return, union-attr] return await device.device.async_get_wifi_connected_station()
except DeviceUnavailable as err: except DeviceUnavailable as err:
raise UpdateFailed(err) from err raise UpdateFailed(err) from err
async def async_update_wifi_neighbor_access_points() -> dict[str, Any]: async def async_update_wifi_neighbor_access_points() -> list[NeighborAPInfo]:
"""Fetch data from API endpoint.""" """Fetch data from API endpoint."""
assert device.device
try: try:
async with async_timeout.timeout(30): async with async_timeout.timeout(30):
return await device.device.async_get_wifi_neighbor_access_points() # type: ignore[no-any-return, union-attr] return await device.device.async_get_wifi_neighbor_access_points()
except DeviceUnavailable as err: except DeviceUnavailable as err:
raise UpdateFailed(err) from err raise UpdateFailed(err) from err

View File

@ -4,7 +4,7 @@ from __future__ import annotations
from collections.abc import Callable from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from devolo_plc_api.device import Device from devolo_plc_api import Device
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass, BinarySensorDeviceClass,
@ -24,9 +24,9 @@ from .entity import DevoloEntity
def _is_connected_to_router(entity: DevoloBinarySensorEntity) -> bool: def _is_connected_to_router(entity: DevoloBinarySensorEntity) -> bool:
"""Check, if device is attached to the router.""" """Check, if device is attached to the router."""
return all( return all(
device["attached_to_router"] device.attached_to_router
for device in entity.coordinator.data["network"]["devices"] for device in entity.coordinator.data.devices
if device["mac_address"] == entity.device.mac if device.mac_address == entity.device.mac
) )

View File

@ -40,7 +40,7 @@ async def validate_input(
return { return {
SERIAL_NUMBER: str(device.serial_number), SERIAL_NUMBER: str(device.serial_number),
TITLE: device.hostname.split(".")[0], TITLE: device.hostname.split(".", maxsplit=1)[0],
} }

View File

@ -2,12 +2,18 @@
from datetime import timedelta from datetime import timedelta
from devolo_plc_api.device_api import (
WIFI_BAND_2G,
WIFI_BAND_5G,
WIFI_VAP_GUEST_AP,
WIFI_VAP_MAIN_AP,
)
from homeassistant.const import Platform from homeassistant.const import Platform
DOMAIN = "devolo_home_network" DOMAIN = "devolo_home_network"
PLATFORMS = [Platform.BINARY_SENSOR, Platform.DEVICE_TRACKER, Platform.SENSOR] PLATFORMS = [Platform.BINARY_SENSOR, Platform.DEVICE_TRACKER, Platform.SENSOR]
MAC_ADDRESS = "mac_address"
PRODUCT = "product" PRODUCT = "product"
SERIAL_NUMBER = "serial_number" SERIAL_NUMBER = "serial_number"
TITLE = "title" TITLE = "title"
@ -16,16 +22,15 @@ LONG_UPDATE_INTERVAL = timedelta(minutes=5)
SHORT_UPDATE_INTERVAL = timedelta(seconds=15) SHORT_UPDATE_INTERVAL = timedelta(seconds=15)
CONNECTED_PLC_DEVICES = "connected_plc_devices" CONNECTED_PLC_DEVICES = "connected_plc_devices"
CONNECTED_STATIONS = "connected_stations"
CONNECTED_TO_ROUTER = "connected_to_router" CONNECTED_TO_ROUTER = "connected_to_router"
CONNECTED_WIFI_CLIENTS = "connected_wifi_clients" CONNECTED_WIFI_CLIENTS = "connected_wifi_clients"
NEIGHBORING_WIFI_NETWORKS = "neighboring_wifi_networks" NEIGHBORING_WIFI_NETWORKS = "neighboring_wifi_networks"
WIFI_APTYPE = { WIFI_APTYPE = {
"WIFI_VAP_MAIN_AP": "Main", WIFI_VAP_MAIN_AP: "Main",
"WIFI_VAP_GUEST_AP": "Guest", WIFI_VAP_GUEST_AP: "Guest",
} }
WIFI_BANDS = { WIFI_BANDS = {
"WIFI_BAND_2G": 2.4, WIFI_BAND_2G: 2.4,
"WIFI_BAND_5G": 5, WIFI_BAND_5G: 5,
} }

View File

@ -1,9 +1,8 @@
"""Platform for device tracker integration.""" """Platform for device tracker integration."""
from __future__ import annotations from __future__ import annotations
from typing import Any
from devolo_plc_api.device import Device from devolo_plc_api.device import Device
from devolo_plc_api.device_api import ConnectedStationInfo
from homeassistant.components.device_tracker import ( from homeassistant.components.device_tracker import (
DOMAIN as DEVICE_TRACKER_DOMAIN, DOMAIN as DEVICE_TRACKER_DOMAIN,
@ -20,14 +19,7 @@ from homeassistant.helpers.update_coordinator import (
DataUpdateCoordinator, DataUpdateCoordinator,
) )
from .const import ( from .const import CONNECTED_WIFI_CLIENTS, DOMAIN, WIFI_APTYPE, WIFI_BANDS
CONNECTED_STATIONS,
CONNECTED_WIFI_CLIENTS,
DOMAIN,
MAC_ADDRESS,
WIFI_APTYPE,
WIFI_BANDS,
)
async def async_setup_entry( async def async_setup_entry(
@ -35,9 +27,9 @@ async def async_setup_entry(
) -> None: ) -> None:
"""Get all devices and sensors and setup them via config entry.""" """Get all devices and sensors and setup them via config entry."""
device: Device = hass.data[DOMAIN][entry.entry_id]["device"] device: Device = hass.data[DOMAIN][entry.entry_id]["device"]
coordinators: dict[str, DataUpdateCoordinator] = hass.data[DOMAIN][entry.entry_id][ coordinators: dict[
"coordinators" str, DataUpdateCoordinator[list[ConnectedStationInfo]]
] ] = hass.data[DOMAIN][entry.entry_id]["coordinators"]
registry = entity_registry.async_get(hass) registry = entity_registry.async_get(hass)
tracked = set() tracked = set()
@ -45,16 +37,16 @@ async def async_setup_entry(
def new_device_callback() -> None: def new_device_callback() -> None:
"""Add new devices if needed.""" """Add new devices if needed."""
new_entities = [] new_entities = []
for station in coordinators[CONNECTED_WIFI_CLIENTS].data[CONNECTED_STATIONS]: for station in coordinators[CONNECTED_WIFI_CLIENTS].data:
if station[MAC_ADDRESS] in tracked: if station.mac_address in tracked:
continue continue
new_entities.append( new_entities.append(
DevoloScannerEntity( DevoloScannerEntity(
coordinators[CONNECTED_WIFI_CLIENTS], device, station[MAC_ADDRESS] coordinators[CONNECTED_WIFI_CLIENTS], device, station.mac_address
) )
) )
tracked.add(station[MAC_ADDRESS]) tracked.add(station.mac_address)
async_add_entities(new_entities) async_add_entities(new_entities)
@callback @callback
@ -90,7 +82,9 @@ async def async_setup_entry(
) )
class DevoloScannerEntity(CoordinatorEntity, ScannerEntity): class DevoloScannerEntity(
CoordinatorEntity[DataUpdateCoordinator[list[ConnectedStationInfo]]], ScannerEntity
):
"""Representation of a devolo device tracker.""" """Representation of a devolo device tracker."""
def __init__( def __init__(
@ -105,22 +99,22 @@ class DevoloScannerEntity(CoordinatorEntity, ScannerEntity):
def extra_state_attributes(self) -> dict[str, str]: def extra_state_attributes(self) -> dict[str, str]:
"""Return the attributes.""" """Return the attributes."""
attrs: dict[str, str] = {} attrs: dict[str, str] = {}
if not self.coordinator.data[CONNECTED_STATIONS]: if not self.coordinator.data:
return {} return {}
station: dict[str, Any] = next( station = next(
( (
station station
for station in self.coordinator.data[CONNECTED_STATIONS] for station in self.coordinator.data
if station[MAC_ADDRESS] == self.mac_address if station.mac_address == self.mac_address
), ),
{}, None,
) )
if station: if station:
attrs["wifi"] = WIFI_APTYPE.get(station["vap_type"], STATE_UNKNOWN) attrs["wifi"] = WIFI_APTYPE.get(station.vap_type, STATE_UNKNOWN)
attrs["band"] = ( attrs["band"] = (
f"{WIFI_BANDS.get(station['band'])} {UnitOfFrequency.GIGAHERTZ}" f"{WIFI_BANDS.get(station.band)} {UnitOfFrequency.GIGAHERTZ}"
if WIFI_BANDS.get(station["band"]) if WIFI_BANDS.get(station.band)
else STATE_UNKNOWN else STATE_UNKNOWN
) )
return attrs return attrs
@ -137,8 +131,8 @@ class DevoloScannerEntity(CoordinatorEntity, ScannerEntity):
"""Return true if the device is connected to the network.""" """Return true if the device is connected to the network."""
return any( return any(
station station
for station in self.coordinator.data[CONNECTED_STATIONS] for station in self.coordinator.data
if station[MAC_ADDRESS] == self.mac_address if station.mac_address == self.mac_address
) )
@property @property

View File

@ -4,7 +4,7 @@
"integration_type": "device", "integration_type": "device",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/devolo_home_network", "documentation": "https://www.home-assistant.io/integrations/devolo_home_network",
"requirements": ["devolo-plc-api==0.9.0"], "requirements": ["devolo-plc-api==1.0.0"],
"zeroconf": [ "zeroconf": [
{ "type": "_dvl-deviceapi._tcp.local.", "properties": { "MT": "*" } } { "type": "_dvl-deviceapi._tcp.local.", "properties": { "MT": "*" } }
], ],

View File

@ -31,7 +31,7 @@ from .entity import DevoloEntity
class DevoloSensorRequiredKeysMixin: class DevoloSensorRequiredKeysMixin:
"""Mixin for required keys.""" """Mixin for required keys."""
value_func: Callable[[dict[str, Any]], int] value_func: Callable[[Any], int]
@dataclass @dataclass
@ -49,7 +49,7 @@ SENSOR_TYPES: dict[str, DevoloSensorEntityDescription] = {
icon="mdi:lan", icon="mdi:lan",
name="Connected PLC devices", name="Connected PLC devices",
value_func=lambda data: len( value_func=lambda data: len(
{device["mac_address_from"] for device in data["network"]["data_rates"]} {device.mac_address_from for device in data.data_rates}
), ),
), ),
CONNECTED_WIFI_CLIENTS: DevoloSensorEntityDescription( CONNECTED_WIFI_CLIENTS: DevoloSensorEntityDescription(
@ -58,7 +58,7 @@ SENSOR_TYPES: dict[str, DevoloSensorEntityDescription] = {
icon="mdi:wifi", icon="mdi:wifi",
name="Connected Wifi clients", name="Connected Wifi clients",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value_func=lambda data: len(data["connected_stations"]), value_func=len,
), ),
NEIGHBORING_WIFI_NETWORKS: DevoloSensorEntityDescription( NEIGHBORING_WIFI_NETWORKS: DevoloSensorEntityDescription(
key=NEIGHBORING_WIFI_NETWORKS, key=NEIGHBORING_WIFI_NETWORKS,
@ -66,7 +66,7 @@ SENSOR_TYPES: dict[str, DevoloSensorEntityDescription] = {
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
icon="mdi:wifi-marker", icon="mdi:wifi-marker",
name="Neighboring Wifi networks", name="Neighboring Wifi networks",
value_func=lambda data: len(data["neighbor_aps"]), value_func=len,
), ),
} }

View File

@ -591,7 +591,7 @@ denonavr==0.10.12
devolo-home-control-api==0.18.2 devolo-home-control-api==0.18.2
# homeassistant.components.devolo_home_network # homeassistant.components.devolo_home_network
devolo-plc-api==0.9.0 devolo-plc-api==1.0.0
# homeassistant.components.directv # homeassistant.components.directv
directv==0.4.0 directv==0.4.0

View File

@ -465,7 +465,7 @@ denonavr==0.10.12
devolo-home-control-api==0.18.2 devolo-home-control-api==0.18.2
# homeassistant.components.devolo_home_network # homeassistant.components.devolo_home_network
devolo-plc-api==0.9.0 devolo-plc-api==1.0.0
# homeassistant.components.directv # homeassistant.components.directv
directv==0.4.0 directv==0.4.0

View File

@ -1,26 +1,31 @@
"""Constants used for mocking data.""" """Constants used for mocking data."""
from homeassistant.components import zeroconf from devolo_plc_api.device_api import (
WIFI_BAND_2G,
WIFI_BAND_5G,
WIFI_VAP_MAIN_AP,
ConnectedStationInfo,
NeighborAPInfo,
)
from devolo_plc_api.plcnet_api import LogicalNetwork
from homeassistant.components.zeroconf import ZeroconfServiceInfo
IP = "1.1.1.1" IP = "1.1.1.1"
CONNECTED_STATIONS = { CONNECTED_STATIONS = [
"connected_stations": [ ConnectedStationInfo(
{ mac_address="AA:BB:CC:DD:EE:FF",
"mac_address": "AA:BB:CC:DD:EE:FF", vap_type=WIFI_VAP_MAIN_AP,
"vap_type": "WIFI_VAP_MAIN_AP", band=WIFI_BAND_5G,
"band": "WIFI_BAND_5G", rx_rate=87800,
"rx_rate": 87800, tx_rate=87800,
"tx_rate": 87800, )
} ]
],
}
NO_CONNECTED_STATIONS = { NO_CONNECTED_STATIONS = []
"connected_stations": [],
}
DISCOVERY_INFO = zeroconf.ZeroconfServiceInfo( DISCOVERY_INFO = ZeroconfServiceInfo(
host=IP, host=IP,
addresses=[IP], addresses=[IP],
port=14791, port=14791,
@ -41,7 +46,7 @@ DISCOVERY_INFO = zeroconf.ZeroconfServiceInfo(
}, },
) )
DISCOVERY_INFO_WRONG_DEVICE = zeroconf.ZeroconfServiceInfo( DISCOVERY_INFO_WRONG_DEVICE = ZeroconfServiceInfo(
host="mock_host", host="mock_host",
addresses=["mock_host"], addresses=["mock_host"],
hostname="mock_hostname", hostname="mock_hostname",
@ -51,46 +56,42 @@ DISCOVERY_INFO_WRONG_DEVICE = zeroconf.ZeroconfServiceInfo(
type="mock_type", type="mock_type",
) )
NEIGHBOR_ACCESS_POINTS = { NEIGHBOR_ACCESS_POINTS = [
"neighbor_aps": [ NeighborAPInfo(
mac_address="AA:BB:CC:DD:EE:FF",
ssid="wifi",
band=WIFI_BAND_2G,
channel=1,
signal=-73,
signal_bars=1,
)
]
PLCNET = LogicalNetwork(
devices=[
{ {
"mac_address": "AA:BB:CC:DD:EE:FF", "mac_address": "AA:BB:CC:DD:EE:FF",
"ssid": "wifi", "attached_to_router": False,
"band": "WIFI_BAND_2G",
"channel": 1,
"signal": -73,
"signal_bars": 1,
} }
] ],
} data_rates=[
{
"mac_address_from": "AA:BB:CC:DD:EE:FF",
"mac_address_to": "11:22:33:44:55:66",
"rx_rate": 0.0,
"tx_rate": 0.0,
},
],
)
PLCNET = {
"network": {
"devices": [
{
"mac_address": "AA:BB:CC:DD:EE:FF",
"attached_to_router": False,
}
],
"data_rates": [
{
"mac_address_from": "AA:BB:CC:DD:EE:FF",
"mac_address_to": "11:22:33:44:55:66",
"rx_rate": 0.0,
"tx_rate": 0.0,
},
],
}
}
PLCNET_ATTACHED = { PLCNET_ATTACHED = LogicalNetwork(
"network": { devices=[
"devices": [ {
{ "mac_address": "AA:BB:CC:DD:EE:FF",
"mac_address": "AA:BB:CC:DD:EE:FF", "attached_to_router": True,
"attached_to_router": True, }
} ],
], data_rates=[],
"data_rates": [], )
}
}

View File

@ -1,8 +1,6 @@
"""Mock of a devolo Home Network device.""" """Mock of a devolo Home Network device."""
from __future__ import annotations from __future__ import annotations
import dataclasses
from typing import Any
from unittest.mock import AsyncMock from unittest.mock import AsyncMock
from devolo_plc_api.device import Device from devolo_plc_api.device import Device
@ -27,12 +25,10 @@ class MockDevice(Device):
def __init__( def __init__(
self, self,
ip: str, ip: str,
plcnetapi: dict[str, Any] | None = None,
deviceapi: dict[str, Any] | None = None,
zeroconf_instance: AsyncZeroconf | Zeroconf | None = None, zeroconf_instance: AsyncZeroconf | Zeroconf | None = None,
) -> None: ) -> None:
"""Bring mock in a well defined state.""" """Bring mock in a well defined state."""
super().__init__(ip, plcnetapi, deviceapi, zeroconf_instance) super().__init__(ip, zeroconf_instance)
self.reset() self.reset()
async def async_connect( async def async_connect(
@ -46,12 +42,12 @@ class MockDevice(Device):
def reset(self): def reset(self):
"""Reset mock to starting point.""" """Reset mock to starting point."""
self.async_disconnect = AsyncMock() self.async_disconnect = AsyncMock()
self.device = DeviceApi(IP, None, dataclasses.asdict(DISCOVERY_INFO)) self.device = DeviceApi(IP, None, DISCOVERY_INFO)
self.device.async_get_wifi_connected_station = AsyncMock( self.device.async_get_wifi_connected_station = AsyncMock(
return_value=CONNECTED_STATIONS return_value=CONNECTED_STATIONS
) )
self.device.async_get_wifi_neighbor_access_points = AsyncMock( self.device.async_get_wifi_neighbor_access_points = AsyncMock(
return_value=NEIGHBOR_ACCESS_POINTS return_value=NEIGHBOR_ACCESS_POINTS
) )
self.plcnet = PlcNetApi(IP, None, dataclasses.asdict(DISCOVERY_INFO)) self.plcnet = PlcNetApi(IP, None, DISCOVERY_INFO)
self.plcnet.async_get_network_overview = AsyncMock(return_value=PLCNET) self.plcnet.async_get_network_overview = AsyncMock(return_value=PLCNET)

View File

@ -11,10 +11,10 @@ from homeassistant.components.devolo_home_network.const import (
WIFI_BANDS, WIFI_BANDS,
) )
from homeassistant.const import ( from homeassistant.const import (
FREQUENCY_GIGAHERTZ,
STATE_HOME, STATE_HOME,
STATE_NOT_HOME, STATE_NOT_HOME,
STATE_UNAVAILABLE, STATE_UNAVAILABLE,
UnitOfFrequency,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry from homeassistant.helpers import entity_registry
@ -26,13 +26,15 @@ from .mock import MockDevice
from tests.common import async_fire_time_changed from tests.common import async_fire_time_changed
STATION = CONNECTED_STATIONS["connected_stations"][0] STATION = CONNECTED_STATIONS[0]
SERIAL = DISCOVERY_INFO.properties["SN"] SERIAL = DISCOVERY_INFO.properties["SN"]
async def test_device_tracker(hass: HomeAssistant, mock_device: MockDevice): async def test_device_tracker(hass: HomeAssistant, mock_device: MockDevice):
"""Test device tracker states.""" """Test device tracker states."""
state_key = f"{PLATFORM}.{DOMAIN}_{SERIAL}_{STATION['mac_address'].lower().replace(':', '_')}" state_key = (
f"{PLATFORM}.{DOMAIN}_{SERIAL}_{STATION.mac_address.lower().replace(':', '_')}"
)
entry = configure_integration(hass) entry = configure_integration(hass)
er = entity_registry.async_get(hass) er = entity_registry.async_get(hass)
await hass.config_entries.async_setup(entry.entry_id) await hass.config_entries.async_setup(entry.entry_id)
@ -49,10 +51,10 @@ async def test_device_tracker(hass: HomeAssistant, mock_device: MockDevice):
state = hass.states.get(state_key) state = hass.states.get(state_key)
assert state is not None assert state is not None
assert state.state == STATE_HOME assert state.state == STATE_HOME
assert state.attributes["wifi"] == WIFI_APTYPE[STATION["vap_type"]] assert state.attributes["wifi"] == WIFI_APTYPE[STATION.vap_type]
assert ( assert (
state.attributes["band"] state.attributes["band"]
== f"{WIFI_BANDS[STATION['band']]} {FREQUENCY_GIGAHERTZ}" == f"{WIFI_BANDS[STATION.band]} {UnitOfFrequency.GIGAHERTZ}"
) )
# Emulate state change # Emulate state change
@ -82,13 +84,15 @@ async def test_device_tracker(hass: HomeAssistant, mock_device: MockDevice):
async def test_restoring_clients(hass: HomeAssistant, mock_device: MockDevice): async def test_restoring_clients(hass: HomeAssistant, mock_device: MockDevice):
"""Test restoring existing device_tracker entities.""" """Test restoring existing device_tracker entities."""
state_key = f"{PLATFORM}.{DOMAIN}_{SERIAL}_{STATION['mac_address'].lower().replace(':', '_')}" state_key = (
f"{PLATFORM}.{DOMAIN}_{SERIAL}_{STATION.mac_address.lower().replace(':', '_')}"
)
entry = configure_integration(hass) entry = configure_integration(hass)
er = entity_registry.async_get(hass) er = entity_registry.async_get(hass)
er.async_get_or_create( er.async_get_or_create(
PLATFORM, PLATFORM,
DOMAIN, DOMAIN,
f"{SERIAL}_{STATION['mac_address']}", f"{SERIAL}_{STATION.mac_address}",
config_entry=entry, config_entry=entry,
) )