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

Implement network scanning using the standard zigpy interface #648

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from
Open
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
48 changes: 34 additions & 14 deletions bellows/ezsp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,28 +224,44 @@
self, name, item_frames, completion_frame, spos, *args, **kwargs
):
"""Run a command, returning result callbacks as a list"""
fut = asyncio.Future()
queue = asyncio.Queue()
results = []

def cb(frame_name, response):
if frame_name in item_frames:
with self.callback_for_commands(
commands=set(item_frames) | {completion_frame},
callback=lambda command, response: queue.put_nowait((command, response)),
):
v = await self._command(name, *args, **kwargs)
if t.sl_Status.from_ember_status(v[0]) != t.sl_Status.OK:
raise Exception(v)

Check warning on line 236 in bellows/ezsp/__init__.py

View check run for this annotation

Codecov / codecov/patch

bellows/ezsp/__init__.py#L236

Added line #L236 was not covered by tests

while True:
command, response = await queue.get()
if command == completion_frame:
if t.sl_Status.from_ember_status(response[spos]) != t.sl_Status.OK:
raise Exception(response)

break

results.append(response)
elif frame_name == completion_frame:
fut.set_result(response)

return results

@contextlib.contextmanager
def callback_for_commands(
self, commands: set[str], callback: Callable
) -> Generator[None]:
def cb(frame_name, response):
if frame_name in commands:
callback(frame_name, response)

cbid = self.add_callback(cb)

try:
v = await self._command(name, *args, **kwargs)
if t.sl_Status.from_ember_status(v[0]) != t.sl_Status.OK:
raise Exception(v)
v = await fut
if t.sl_Status.from_ember_status(v[spos]) != t.sl_Status.OK:
raise Exception(v)
yield
finally:
self.remove_callback(cbid)

return results

startScan = functools.partialmethod(
_list_command,
"startScan",
Expand All @@ -254,7 +270,11 @@
1,
)
pollForData = functools.partialmethod(
_list_command, "pollForData", ["pollHandler"], "pollCompleteHandler", 0
_list_command,
"pollForData",
["pollHandler"],
"pollCompleteHandler",
0,
)
zllStartScan = functools.partialmethod(
_list_command,
Expand Down
38 changes: 38 additions & 0 deletions bellows/zigbee/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import os
import statistics
import sys
from typing import AsyncGenerator

if sys.version_info[:2] < (3, 11):
from async_timeout import timeout as asyncio_timeout # pragma: no cover
Expand Down Expand Up @@ -698,6 +699,43 @@
for channel in list(channels)
}

async def network_scan(
self, channels: t.Channels, duration_exp: int
) -> AsyncGenerator[zigpy.types.NetworkBeacon]:
"""Scans for networks and yields network beacons."""
queue = asyncio.Queue()

Check warning on line 706 in bellows/zigbee/application.py

View check run for this annotation

Codecov / codecov/patch

bellows/zigbee/application.py#L706

Added line #L706 was not covered by tests

with self._ezsp.callback_for_commands(

Check warning on line 708 in bellows/zigbee/application.py

View check run for this annotation

Codecov / codecov/patch

bellows/zigbee/application.py#L708

Added line #L708 was not covered by tests
{"networkFoundHandler", "scanCompleteHandler"},
callback=lambda command, response: queue.put_nowait((command, response)),
):
# XXX: replace with normal command invocation once overload is removed
(status,) = await self._ezsp._command(

Check warning on line 713 in bellows/zigbee/application.py

View check run for this annotation

Codecov / codecov/patch

bellows/zigbee/application.py#L713

Added line #L713 was not covered by tests
"startScan",
scanType=t.EzspNetworkScanType.ACTIVE_SCAN,
channelMask=channels,
duration=duration_exp,
)

while True:
command, response = await queue.get()

Check warning on line 721 in bellows/zigbee/application.py

View check run for this annotation

Codecov / codecov/patch

bellows/zigbee/application.py#L721

Added line #L721 was not covered by tests

if command == "scanCompleteHandler":
break

Check warning on line 724 in bellows/zigbee/application.py

View check run for this annotation

Codecov / codecov/patch

bellows/zigbee/application.py#L723-L724

Added lines #L723 - L724 were not covered by tests

(networkFound, lastHopLqi, lastHopRssi) = response

Check warning on line 726 in bellows/zigbee/application.py

View check run for this annotation

Codecov / codecov/patch

bellows/zigbee/application.py#L726

Added line #L726 was not covered by tests

yield zigpy.types.NetworkBeacon(

Check warning on line 728 in bellows/zigbee/application.py

View check run for this annotation

Codecov / codecov/patch

bellows/zigbee/application.py#L728

Added line #L728 was not covered by tests
pan_id=networkFound.panId,
extended_pan_id=networkFound.extendedPanId,
channel=networkFound.channel,
nwk_update_id=networkFound.nwkUpdateId,
permit_joining=bool(networkFound.allowingJoin),
stack_profile=networkFound.stackProfile,
lqi=lastHopLqi,
rssi=lastHopRssi,
)

async def send_packet(self, packet: zigpy.types.ZigbeePacket) -> None:
if not self.is_controller_running:
raise ControllerError("ApplicationController is not running")
Expand Down
Loading