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

Add button platform to devolo Home Network #85834

Merged
merged 11 commits into from
May 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion homeassistant/components/devolo_home_network/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
@callback
def platforms(device: Device) -> set[Platform]:
"""Assemble supported platforms."""
supported_platforms = {Platform.SENSOR, Platform.SWITCH}
supported_platforms = {Platform.BUTTON, Platform.SENSOR, Platform.SWITCH}
if device.plcnet:
supported_platforms.add(Platform.BINARY_SENSOR)
if device.device and "wifi1" in device.device.features:
Expand Down
6 changes: 4 additions & 2 deletions homeassistant/components/devolo_home_network/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator

from .const import CONNECTED_PLC_DEVICES, CONNECTED_TO_ROUTER, DOMAIN
from .entity import DevoloEntity
from .entity import DevoloCoordinatorEntity


def _is_connected_to_router(entity: DevoloBinarySensorEntity) -> bool:
Expand Down Expand Up @@ -79,7 +79,9 @@ async def async_setup_entry(
async_add_entities(entities)


class DevoloBinarySensorEntity(DevoloEntity[LogicalNetwork], BinarySensorEntity):
class DevoloBinarySensorEntity(
DevoloCoordinatorEntity[LogicalNetwork], BinarySensorEntity
):
"""Representation of a devolo binary sensor."""

def __init__(
Expand Down
133 changes: 133 additions & 0 deletions homeassistant/components/devolo_home_network/button.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"""Platform for button integration."""
from __future__ import annotations

from collections.abc import Awaitable, Callable
from dataclasses import dataclass

from devolo_plc_api.device import Device
from devolo_plc_api.exceptions.device import DevicePasswordProtected, DeviceUnavailable

from homeassistant.components.button import (
ButtonDeviceClass,
ButtonEntity,
ButtonEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .const import DOMAIN, IDENTIFY, PAIRING, RESTART, START_WPS
from .entity import DevoloEntity


@dataclass
class DevoloButtonRequiredKeysMixin:
"""Mixin for required keys."""

press_func: Callable[[Device], Awaitable[bool]]


@dataclass
class DevoloButtonEntityDescription(
ButtonEntityDescription, DevoloButtonRequiredKeysMixin
):
"""Describes devolo button entity."""


BUTTON_TYPES: dict[str, DevoloButtonEntityDescription] = {
IDENTIFY: DevoloButtonEntityDescription(
key=IDENTIFY,
entity_category=EntityCategory.DIAGNOSTIC,
icon="mdi:led-on",
press_func=lambda device: device.plcnet.async_identify_device_start(), # type: ignore[union-attr]
),
PAIRING: DevoloButtonEntityDescription(
key=PAIRING,
icon="mdi:plus-network-outline",
press_func=lambda device: device.plcnet.async_pair_device(), # type: ignore[union-attr]
),
RESTART: DevoloButtonEntityDescription(
key=RESTART,
device_class=ButtonDeviceClass.RESTART,
entity_category=EntityCategory.CONFIG,
press_func=lambda device: device.device.async_restart(), # type: ignore[union-attr]
),
START_WPS: DevoloButtonEntityDescription(
key=START_WPS,
icon="mdi:wifi-plus",
press_func=lambda device: device.device.async_start_wps(), # type: ignore[union-attr]
),
}


async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Get all devices and buttons and setup them via config entry."""
device: Device = hass.data[DOMAIN][entry.entry_id]["device"]

entities: list[DevoloButtonEntity] = []
if device.plcnet:
entities.append(
DevoloButtonEntity(
entry,
BUTTON_TYPES[IDENTIFY],
device,
)
)
entities.append(
DevoloButtonEntity(
entry,
BUTTON_TYPES[PAIRING],
device,
)
)
if device.device and "restart" in device.device.features:
entities.append(
DevoloButtonEntity(
entry,
BUTTON_TYPES[RESTART],
device,
)
)
if device.device and "wifi1" in device.device.features:
entities.append(
DevoloButtonEntity(
entry,
BUTTON_TYPES[START_WPS],
device,
)
)
async_add_entities(entities)


class DevoloButtonEntity(DevoloEntity, ButtonEntity):
"""Representation of a devolo button."""

entity_description: DevoloButtonEntityDescription

def __init__(
self,
entry: ConfigEntry,
description: DevoloButtonEntityDescription,
device: Device,
) -> None:
"""Initialize entity."""
self.entity_description = description
super().__init__(entry, device)

async def async_press(self) -> None:
"""Handle the button press."""
try:
await self.entity_description.press_func(self.device)
except DevicePasswordProtected as ex:
self.entry.async_start_reauth(self.hass)
allenporter marked this conversation as resolved.
Show resolved Hide resolved
raise HomeAssistantError(
f"Device {self.entry.title} require re-authenticatication to set or change the password"
) from ex
except DeviceUnavailable as ex:
raise HomeAssistantError(
f"Device {self.entry.title} did not respond"
Shutgun marked this conversation as resolved.
Show resolved Hide resolved
) from ex
5 changes: 4 additions & 1 deletion homeassistant/components/devolo_home_network/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
)

DOMAIN = "devolo_home_network"

PRODUCT = "product"
SERIAL_NUMBER = "serial_number"
TITLE = "title"
Expand All @@ -21,7 +20,11 @@
CONNECTED_PLC_DEVICES = "connected_plc_devices"
CONNECTED_TO_ROUTER = "connected_to_router"
CONNECTED_WIFI_CLIENTS = "connected_wifi_clients"
IDENTIFY = "identify"
NEIGHBORING_WIFI_NETWORKS = "neighboring_wifi_networks"
PAIRING = "pairing"
RESTART = "restart"
START_WPS = "start_wps"
SWITCH_GUEST_WIFI = "switch_guest_wifi"
SWITCH_LEDS = "switch_leds"

Expand Down
23 changes: 18 additions & 5 deletions homeassistant/components/devolo_home_network/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from devolo_plc_api.plcnet_api import LogicalNetwork

from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity import DeviceInfo, Entity
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
Expand All @@ -32,20 +32,17 @@
)


class DevoloEntity(CoordinatorEntity[DataUpdateCoordinator[_DataT]]):
class DevoloEntity(Entity):
"""Representation of a devolo home network device."""

_attr_has_entity_name = True

def __init__(
self,
entry: ConfigEntry,
coordinator: DataUpdateCoordinator[_DataT],
device: Device,
) -> None:
"""Initialize a devolo home network device."""
super().__init__(coordinator)

self.device = device
self.entry = entry

Expand All @@ -59,3 +56,19 @@ def __init__(
)
self._attr_translation_key = self.entity_description.key
self._attr_unique_id = f"{device.serial_number}_{self.entity_description.key}"


class DevoloCoordinatorEntity(
CoordinatorEntity[DataUpdateCoordinator[_DataT]], DevoloEntity
):
"""Representation of a coordinated devolo home network device."""

def __init__(
self,
entry: ConfigEntry,
coordinator: DataUpdateCoordinator[_DataT],
device: Device,
) -> None:
"""Initialize a devolo home network device."""
super().__init__(coordinator)
DevoloEntity.__init__(self, entry, device)
4 changes: 2 additions & 2 deletions homeassistant/components/devolo_home_network/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
DOMAIN,
NEIGHBORING_WIFI_NETWORKS,
)
from .entity import DevoloEntity
from .entity import DevoloCoordinatorEntity

_DataT = TypeVar(
"_DataT",
Expand Down Expand Up @@ -113,7 +113,7 @@ async def async_setup_entry(
async_add_entities(entities)


class DevoloSensorEntity(DevoloEntity[_DataT], SensorEntity):
class DevoloSensorEntity(DevoloCoordinatorEntity[_DataT], SensorEntity):
"""Representation of a devolo sensor."""

entity_description: DevoloSensorEntityDescription[_DataT]
Expand Down
14 changes: 14 additions & 0 deletions homeassistant/components/devolo_home_network/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@
"name": "Connected to router"
}
},
"button": {
"identify": {
"name": "Identify device with a blinking LED"
},
"pairing": {
"name": "Start PLC pairing"
},
"restart": {
"name": "Restart device"
},
"start_wps": {
"name": "Start WPS"
}
},
"sensor": {
"connected_plc_devices": {
"name": "Connected PLC devices"
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/devolo_home_network/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator

from .const import DOMAIN, SWITCH_GUEST_WIFI, SWITCH_LEDS
from .entity import DevoloEntity
from .entity import DevoloCoordinatorEntity

_DataT = TypeVar("_DataT", bound=WifiGuestAccessGet | bool)

Expand Down Expand Up @@ -88,7 +88,7 @@ async def async_setup_entry(
async_add_entities(entities)


class DevoloSwitchEntity(DevoloEntity[_DataT], SwitchEntity):
class DevoloSwitchEntity(DevoloCoordinatorEntity[_DataT], SwitchEntity):
"""Representation of a devolo switch."""

entity_description: DevoloSwitchEntityDescription[_DataT]
Expand Down
2 changes: 1 addition & 1 deletion tests/components/devolo_home_network/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"Path": "abcdefghijkl/deviceapi",
"Version": "v0",
"Product": "dLAN pro 1200+ WiFi ac",
"Features": "reset,update,led,intmtg,wifi1",
"Features": "intmtg1,led,reset,restart,update,wifi1",
"MT": "2730",
"SN": "1234567890",
"FirmwareVersion": "5.6.1",
Expand Down
4 changes: 4 additions & 0 deletions tests/components/devolo_home_network/mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ def reset(self):
self.async_disconnect = AsyncMock()
self.device = DeviceApi(IP, None, DISCOVERY_INFO)
self.device.async_get_led_setting = AsyncMock(return_value=False)
self.device.async_restart = AsyncMock(return_value=True)
self.device.async_start_wps = AsyncMock(return_value=True)
self.device.async_get_wifi_connected_station = AsyncMock(
return_value=CONNECTED_STATIONS
)
Expand All @@ -60,3 +62,5 @@ def reset(self):
)
self.plcnet = PlcNetApi(IP, None, DISCOVERY_INFO)
self.plcnet.async_get_network_overview = AsyncMock(return_value=PLCNET)
self.plcnet.async_identify_device_start = AsyncMock(return_value=True)
self.plcnet.async_pair_device = AsyncMock(return_value=True)
Loading