Skip to content

Commit

Permalink
Add multiap feature (#123)
Browse files Browse the repository at this point in the history
  • Loading branch information
Shutgun authored Apr 13, 2023
1 parent bc89064 commit edfaec6
Show file tree
Hide file tree
Showing 11 changed files with 141 additions and 3 deletions.
2 changes: 2 additions & 0 deletions devolo_plc_api/device_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import re

from .deviceapi import DeviceApi
from .multiap_pb2 import WifiMultiApGetResponse
from .support_pb2 import SupportInfoDump
from .updatefirmware_pb2 import UpdateFirmwareCheck
from .wifinetwork_pb2 import (
Expand Down Expand Up @@ -33,6 +34,7 @@
"RepeatedAPInfo",
"SupportInfoItem",
"WifiGuestAccessGet",
"WifiMultiApGetResponse",
"CONFIGLAYER_FORMAT",
"SERVICE_TYPE",
"UPDATE_AVAILABLE",
Expand Down
14 changes: 14 additions & 0 deletions devolo_plc_api/device_api/deviceapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from .factoryreset_pb2 import FactoryResetStart
from .ledsettings_pb2 import LedSettingsGet, LedSettingsSet, LedSettingsSetResponse
from .multiap_pb2 import WifiMultiApGetResponse
from .restart_pb2 import RestartResponse, UptimeGetResponse
from .support_pb2 import SupportInfoDump, SupportInfoDumpResponse
from .updatefirmware_pb2 import UpdateFirmwareCheck, UpdateFirmwareStart
Expand Down Expand Up @@ -105,6 +106,19 @@ async def async_set_led_setting(self, enable: bool) -> bool:
response.ParseFromString(await query.aread())
return response.result == response.SUCCESS

@_feature("multiap")
async def async_get_wifi_multi_ap(self) -> WifiMultiApGetResponse:
"""
Get MultiAP details asynchronously. This feature only works on devices, that announce the multiap feature.
return: MultiAP details
"""
self._logger.debug("Getting MultiAP details.")
query = await self._async_get("WifiMultiApGet")
response = WifiMultiApGetResponse()
response.ParseFromString(await query.aread())
return response

@_feature("repeater0")
async def async_get_wifi_repeated_access_points(self) -> list[WifiRepeatedAPsGet.RepeatedAPInfo]:
"""
Expand Down
3 changes: 3 additions & 0 deletions devolo_plc_api/device_api/deviceapi.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
isort:skip_file
"""
from __future__ import annotations
from .multiap_pb2 import WifiMultiApGetResponse
from .support_pb2 import SupportInfoDump
from .updatefirmware_pb2 import UpdateFirmwareCheck
from .wifinetwork_pb2 import WifiConnectedStationsGet, WifiGuestAccessGet, WifiNeighborAPsGet, WifiRepeatedAPsGet
Expand All @@ -16,6 +17,7 @@ class DeviceApi(Protobuf):
def __init__(self, ip: str, session: AsyncClient, info: ZeroconfServiceInfo) -> None: ...
async def async_get_led_setting(self) -> bool: ...
async def async_set_led_setting(self, enable: bool) -> bool: ...
async def async_get_wifi_multi_ap(self) -> WifiMultiApGetResponse: ...
async def async_get_wifi_repeated_access_points(self) -> list[WifiRepeatedAPsGet.RepeatedAPInfo]: ...
async def async_start_wps_clone(self) -> bool: ...
async def async_factory_reset(self) -> bool: ...
Expand All @@ -31,6 +33,7 @@ class DeviceApi(Protobuf):
async def async_start_wps(self) -> bool: ...
def get_led_setting(self) -> bool: ...
def set_led_setting(self, enable: bool) -> bool: ...
def get_wifi_multi_ap(self) -> WifiMultiApGetResponse: ...
def get_wifi_repeated_access_points(self) -> list[WifiRepeatedAPsGet.RepeatedAPInfo]: ...
def start_wps_clone(self) -> bool: ...
def factory_reset(self) -> bool: ...
Expand Down
26 changes: 26 additions & 0 deletions devolo_plc_api/device_api/multiap_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 59 additions & 0 deletions devolo_plc_api/device_api/multiap_pb2.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""
@generated by mypy-protobuf. Do not edit manually!
isort:skip_file
"""
import builtins
import google.protobuf.descriptor
import google.protobuf.message
import sys

if sys.version_info >= (3, 8):
import typing as typing_extensions
else:
import typing_extensions

DESCRIPTOR: google.protobuf.descriptor.FileDescriptor

@typing_extensions.final
class WifiMultiApGetResponse(google.protobuf.message.Message):
"""Details about MultiAP as returned by the 'WifiMultiApGet' endpoint."""

DESCRIPTOR: google.protobuf.descriptor.Descriptor

ENABLED_FIELD_NUMBER: builtins.int
CONTROLLER_ID_FIELD_NUMBER: builtins.int
CONTROLLER_IP_FIELD_NUMBER: builtins.int
enabled: builtins.bool
"""Describes if the MultiAP functionality is enabled in the device."""
controller_id: builtins.str
"""The id of the mesh controller, in form of its MAC address,
if a mesh controller is known to the device.
If the device is not aware of a mesh controller, e.g. because
none has been elected yet, it is left empty.
The MAC address is represented as a string of 12 hexadecimal
digits (digits 0-9, letters A-F or a-f) displayed as six pairs of
digits separated by colons.
"""
controller_ip: builtins.str
"""The IP address of the known mesh controller, if the implementation
provides it.
If the device is not aware of a mesh controller or doesn't
know its IP, it is left empty.
The IP can be an IPv4 in dot-separated decimal format, or an IPv6
in colon-separated hexadecimal format. In case multiple IPs are
known, the value can be a comma-separated string of either formats.
Also, an IP can optionally be prefixed with an identifier separated
from the IP with a semicolon.
"""
def __init__(
self,
*,
enabled: builtins.bool = ...,
controller_id: builtins.str = ...,
controller_ip: builtins.str = ...,
) -> None: ...
def ClearField(self, field_name: typing_extensions.Literal["controller_id", b"controller_id", "controller_ip", b"controller_ip", "enabled", b"enabled"]) -> None: ...

global___WifiMultiApGetResponse = WifiMultiApGetResponse
6 changes: 5 additions & 1 deletion docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [v1.3.0] - 2023/04/13

### Added

- Get MultiAP information from the device

### Fixed

Expand Down
6 changes: 6 additions & 0 deletions example_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ async def run():
# If the state was changed successfully, True is returned, otherwise False.
print("success" if await dpa.device.async_set_led_setting(enable=True) else "failed")

# Get MultiAP details. If the device is not aware of a mesh controller or doesn't know its IP, it is left empty.
multi_ap = await dpa.device.async_get_wifi_multi_ap()
print(multi_ap.enabled) # True
print(multi_ap.controller_id) # "AA:BB:CC:DD:EE:FF"
print(multi_ap.controller_ip) # "192.0.2.1"

# Factory reset the device. If the reset will happen shortly, True is returned, otherwise False.
print("success" if await dpa.device.async_factory_reset() else "failed")

Expand Down
6 changes: 6 additions & 0 deletions example_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ def run():
# If the state was changed successfully, True is returned, otherwise False.
print("success" if dpa.device.set_led_setting(enable=True) else "failed")

# Get MultiAP details. If the device is not aware of a mesh controller or doesn't know its IP, it is left empty.
multi_ap = dpa.device.get_wifi_multi_ap()
print(multi_ap.enabled) # True
print(multi_ap.controller_id) # "AA:BB:CC:DD:EE:FF"
print(multi_ap.controller_ip) # "192.0.2.1"

# Factory reset the device. If the reset will happen shortly, True is returned, otherwise False.
print("success" if dpa.device.factory_reset() else "failed")

Expand Down
2 changes: 1 addition & 1 deletion tests/test_data.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@
}
},
"hostname": "device.local",
"ip": "192.168.0.10"
"ip": "192.0.2.1"
}
2 changes: 1 addition & 1 deletion tests/test_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ async def test__get_service_info_alien(self, mock_info_from_service: Mock):
with patch("devolo_plc_api.device.AsyncServiceInfo", StubAsyncServiceInfo), patch(
"devolo_plc_api.device.PlcNetApi"
), pytest.raises(DeviceNotFound):
mock_device = Device(ip="192.168.0.11")
mock_device = Device(ip="192.0.2.2")
await mock_device.async_connect()
assert StubAsyncServiceInfo.async_request.call_count == 1
assert mock_info_from_service.call_count == 0
Expand Down
18 changes: 18 additions & 0 deletions tests/test_deviceapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from devolo_plc_api.device_api import ConnectedStationInfo, DeviceApi, NeighborAPInfo, RepeatedAPInfo, SupportInfoItem
from devolo_plc_api.device_api.factoryreset_pb2 import FactoryResetStart
from devolo_plc_api.device_api.ledsettings_pb2 import LedSettingsGet, LedSettingsSetResponse
from devolo_plc_api.device_api.multiap_pb2 import WifiMultiApGetResponse
from devolo_plc_api.device_api.restart_pb2 import RestartResponse, UptimeGetResponse
from devolo_plc_api.device_api.support_pb2 import SupportInfoDump, SupportInfoDumpResponse
from devolo_plc_api.device_api.updatefirmware_pb2 import UpdateFirmwareCheck, UpdateFirmwareStart
Expand Down Expand Up @@ -67,6 +68,23 @@ def test_set_led_setting(self, device_api: DeviceApi, httpx_mock: HTTPXMock):
httpx_mock.add_response(content=led_setting_set.SerializeToString())
assert device_api.set_led_setting(True)

@pytest.mark.asyncio()
@pytest.mark.parametrize("feature", ["multiap"])
async def test_async_get_wifi_multi_ap(self, device_api: DeviceApi, httpx_mock: HTTPXMock):
"""Test setting LED settings asynchronously."""
multi_ap_details = WifiMultiApGetResponse(enabled=True)
httpx_mock.add_response(content=multi_ap_details.SerializeToString())
details = await device_api.async_get_wifi_multi_ap()
assert details.enabled

@pytest.mark.parametrize("feature", ["multiap"])
def test_get_wifi_multi_ap(self, device_api: DeviceApi, httpx_mock: HTTPXMock):
"""Test setting LED settings synchronously."""
multi_ap_details = WifiMultiApGetResponse(enabled=True)
httpx_mock.add_response(content=multi_ap_details.SerializeToString())
details = device_api.get_wifi_multi_ap()
assert details.enabled

@pytest.mark.asyncio()
@pytest.mark.parametrize("feature", ["repeater0"])
async def test_async_get_wifi_repeated_access_points(
Expand Down

0 comments on commit edfaec6

Please sign in to comment.