Skip to content
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: 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