Skip to content

Commit edfaec6

Browse files
authored
Add multiap feature (#123)
1 parent bc89064 commit edfaec6

11 files changed

Lines changed: 141 additions & 3 deletions

File tree

devolo_plc_api/device_api/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import re
33

44
from .deviceapi import DeviceApi
5+
from .multiap_pb2 import WifiMultiApGetResponse
56
from .support_pb2 import SupportInfoDump
67
from .updatefirmware_pb2 import UpdateFirmwareCheck
78
from .wifinetwork_pb2 import (
@@ -33,6 +34,7 @@
3334
"RepeatedAPInfo",
3435
"SupportInfoItem",
3536
"WifiGuestAccessGet",
37+
"WifiMultiApGetResponse",
3638
"CONFIGLAYER_FORMAT",
3739
"SERVICE_TYPE",
3840
"UPDATE_AVAILABLE",

devolo_plc_api/device_api/deviceapi.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
from .factoryreset_pb2 import FactoryResetStart
1414
from .ledsettings_pb2 import LedSettingsGet, LedSettingsSet, LedSettingsSetResponse
15+
from .multiap_pb2 import WifiMultiApGetResponse
1516
from .restart_pb2 import RestartResponse, UptimeGetResponse
1617
from .support_pb2 import SupportInfoDump, SupportInfoDumpResponse
1718
from .updatefirmware_pb2 import UpdateFirmwareCheck, UpdateFirmwareStart
@@ -105,6 +106,19 @@ async def async_set_led_setting(self, enable: bool) -> bool:
105106
response.ParseFromString(await query.aread())
106107
return response.result == response.SUCCESS
107108

109+
@_feature("multiap")
110+
async def async_get_wifi_multi_ap(self) -> WifiMultiApGetResponse:
111+
"""
112+
Get MultiAP details asynchronously. This feature only works on devices, that announce the multiap feature.
113+
114+
return: MultiAP details
115+
"""
116+
self._logger.debug("Getting MultiAP details.")
117+
query = await self._async_get("WifiMultiApGet")
118+
response = WifiMultiApGetResponse()
119+
response.ParseFromString(await query.aread())
120+
return response
121+
108122
@_feature("repeater0")
109123
async def async_get_wifi_repeated_access_points(self) -> list[WifiRepeatedAPsGet.RepeatedAPInfo]:
110124
"""

devolo_plc_api/device_api/deviceapi.pyi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
isort:skip_file
44
"""
55
from __future__ import annotations
6+
from .multiap_pb2 import WifiMultiApGetResponse
67
from .support_pb2 import SupportInfoDump
78
from .updatefirmware_pb2 import UpdateFirmwareCheck
89
from .wifinetwork_pb2 import WifiConnectedStationsGet, WifiGuestAccessGet, WifiNeighborAPsGet, WifiRepeatedAPsGet
@@ -16,6 +17,7 @@ class DeviceApi(Protobuf):
1617
def __init__(self, ip: str, session: AsyncClient, info: ZeroconfServiceInfo) -> None: ...
1718
async def async_get_led_setting(self) -> bool: ...
1819
async def async_set_led_setting(self, enable: bool) -> bool: ...
20+
async def async_get_wifi_multi_ap(self) -> WifiMultiApGetResponse: ...
1921
async def async_get_wifi_repeated_access_points(self) -> list[WifiRepeatedAPsGet.RepeatedAPInfo]: ...
2022
async def async_start_wps_clone(self) -> bool: ...
2123
async def async_factory_reset(self) -> bool: ...
@@ -31,6 +33,7 @@ class DeviceApi(Protobuf):
3133
async def async_start_wps(self) -> bool: ...
3234
def get_led_setting(self) -> bool: ...
3335
def set_led_setting(self, enable: bool) -> bool: ...
36+
def get_wifi_multi_ap(self) -> WifiMultiApGetResponse: ...
3437
def get_wifi_repeated_access_points(self) -> list[WifiRepeatedAPsGet.RepeatedAPInfo]: ...
3538
def start_wps_clone(self) -> bool: ...
3639
def factory_reset(self) -> bool: ...

devolo_plc_api/device_api/multiap_pb2.py

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""
2+
@generated by mypy-protobuf. Do not edit manually!
3+
isort:skip_file
4+
"""
5+
import builtins
6+
import google.protobuf.descriptor
7+
import google.protobuf.message
8+
import sys
9+
10+
if sys.version_info >= (3, 8):
11+
import typing as typing_extensions
12+
else:
13+
import typing_extensions
14+
15+
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
16+
17+
@typing_extensions.final
18+
class WifiMultiApGetResponse(google.protobuf.message.Message):
19+
"""Details about MultiAP as returned by the 'WifiMultiApGet' endpoint."""
20+
21+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
22+
23+
ENABLED_FIELD_NUMBER: builtins.int
24+
CONTROLLER_ID_FIELD_NUMBER: builtins.int
25+
CONTROLLER_IP_FIELD_NUMBER: builtins.int
26+
enabled: builtins.bool
27+
"""Describes if the MultiAP functionality is enabled in the device."""
28+
controller_id: builtins.str
29+
"""The id of the mesh controller, in form of its MAC address,
30+
if a mesh controller is known to the device.
31+
If the device is not aware of a mesh controller, e.g. because
32+
none has been elected yet, it is left empty.
33+
34+
The MAC address is represented as a string of 12 hexadecimal
35+
digits (digits 0-9, letters A-F or a-f) displayed as six pairs of
36+
digits separated by colons.
37+
"""
38+
controller_ip: builtins.str
39+
"""The IP address of the known mesh controller, if the implementation
40+
provides it.
41+
If the device is not aware of a mesh controller or doesn't
42+
know its IP, it is left empty.
43+
44+
The IP can be an IPv4 in dot-separated decimal format, or an IPv6
45+
in colon-separated hexadecimal format. In case multiple IPs are
46+
known, the value can be a comma-separated string of either formats.
47+
Also, an IP can optionally be prefixed with an identifier separated
48+
from the IP with a semicolon.
49+
"""
50+
def __init__(
51+
self,
52+
*,
53+
enabled: builtins.bool = ...,
54+
controller_id: builtins.str = ...,
55+
controller_ip: builtins.str = ...,
56+
) -> None: ...
57+
def ClearField(self, field_name: typing_extensions.Literal["controller_id", b"controller_id", "controller_ip", b"controller_ip", "enabled", b"enabled"]) -> None: ...
58+
59+
global___WifiMultiApGetResponse = WifiMultiApGetResponse

docs/CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ All notable changes to this project will be documented in this file.
44

55
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).
66

7-
## [Unreleased]
7+
## [v1.3.0] - 2023/04/13
8+
9+
### Added
10+
11+
- Get MultiAP information from the device
812

913
### Fixed
1014

example_async.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ async def run():
2222
# If the state was changed successfully, True is returned, otherwise False.
2323
print("success" if await dpa.device.async_set_led_setting(enable=True) else "failed")
2424

25+
# Get MultiAP details. If the device is not aware of a mesh controller or doesn't know its IP, it is left empty.
26+
multi_ap = await dpa.device.async_get_wifi_multi_ap()
27+
print(multi_ap.enabled) # True
28+
print(multi_ap.controller_id) # "AA:BB:CC:DD:EE:FF"
29+
print(multi_ap.controller_ip) # "192.0.2.1"
30+
2531
# Factory reset the device. If the reset will happen shortly, True is returned, otherwise False.
2632
print("success" if await dpa.device.async_factory_reset() else "failed")
2733

example_sync.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ def run():
2020
# If the state was changed successfully, True is returned, otherwise False.
2121
print("success" if dpa.device.set_led_setting(enable=True) else "failed")
2222

23+
# Get MultiAP details. If the device is not aware of a mesh controller or doesn't know its IP, it is left empty.
24+
multi_ap = dpa.device.get_wifi_multi_ap()
25+
print(multi_ap.enabled) # True
26+
print(multi_ap.controller_id) # "AA:BB:CC:DD:EE:FF"
27+
print(multi_ap.controller_ip) # "192.0.2.1"
28+
2329
# Factory reset the device. If the reset will happen shortly, True is returned, otherwise False.
2430
print("success" if dpa.device.factory_reset() else "failed")
2531

tests/test_data.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,5 @@
2424
}
2525
},
2626
"hostname": "device.local",
27-
"ip": "192.168.0.10"
27+
"ip": "192.0.2.1"
2828
}

tests/test_device.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ async def test__get_service_info_alien(self, mock_info_from_service: Mock):
205205
with patch("devolo_plc_api.device.AsyncServiceInfo", StubAsyncServiceInfo), patch(
206206
"devolo_plc_api.device.PlcNetApi"
207207
), pytest.raises(DeviceNotFound):
208-
mock_device = Device(ip="192.168.0.11")
208+
mock_device = Device(ip="192.0.2.2")
209209
await mock_device.async_connect()
210210
assert StubAsyncServiceInfo.async_request.call_count == 1
211211
assert mock_info_from_service.call_count == 0

0 commit comments

Comments
 (0)