|
7 | 7 | import itertools |
8 | 8 | import json |
9 | 9 | import logging |
| 10 | +import random |
10 | 11 |
|
11 | 12 | import click |
12 | 13 | import zigpy.state |
13 | 14 | import zigpy.types |
14 | 15 | import zigpy.zdo |
15 | 16 | import zigpy.zdo.types |
| 17 | +from zigpy.application import ControllerApplication |
16 | 18 |
|
17 | 19 | from zigpy_cli.cli import cli, click_coroutine |
| 20 | +from zigpy_cli.common import CHANNELS_LIST |
18 | 21 | from zigpy_cli.const import RADIO_LOGGING_CONFIGS, RADIO_TO_PACKAGE, RADIO_TO_PYPI |
| 22 | +from zigpy_cli.helpers import PcapWriter |
19 | 23 |
|
20 | 24 | LOGGER = logging.getLogger(__name__) |
21 | 25 |
|
@@ -234,3 +238,53 @@ async def change_channel(app, channel): |
234 | 238 | LOGGER.info("Current channel is %s", app.state.network_info.channel) |
235 | 239 |
|
236 | 240 | await app.move_network_to_channel(channel) |
| 241 | + |
| 242 | + |
| 243 | +@radio.command() |
| 244 | +@click.pass_obj |
| 245 | +@click.option("-r", "--randomize", is_flag=True, type=bool, default=False) |
| 246 | +@click.option( |
| 247 | + "-c", |
| 248 | + "--channels", |
| 249 | + type=CHANNELS_LIST, |
| 250 | + default=zigpy.types.Channels.ALL_CHANNELS, |
| 251 | +) |
| 252 | +@click.option("-h", "--channel-hop-period", type=int, default=5) |
| 253 | +@click.option("-o", "--output", type=click.File("wb"), required=True) |
| 254 | +@click_coroutine |
| 255 | +async def packet_capture(app, randomize, channels, channel_hop_period, output): |
| 256 | + if not randomize: |
| 257 | + channels_iter = itertools.cycle(channels) |
| 258 | + else: |
| 259 | + |
| 260 | + def channels_iter_func(): |
| 261 | + while True: |
| 262 | + yield random.choice(channels) |
| 263 | + |
| 264 | + channels_iter = channels_iter_func() |
| 265 | + |
| 266 | + if app._packet_capture is ControllerApplication._packet_capture: |
| 267 | + raise click.ClickException("Packet capture is not supported by this radio") |
| 268 | + |
| 269 | + await app.connect() |
| 270 | + |
| 271 | + async with app.packet_capture(channel=next(channels_iter)) as capture: |
| 272 | + async with asyncio.TaskGroup() as tg: |
| 273 | + |
| 274 | + async def channel_hopper(): |
| 275 | + for channel in channels_iter: |
| 276 | + await asyncio.sleep(channel_hop_period) |
| 277 | + LOGGER.debug("Changing channel to %s", channel) |
| 278 | + await capture.change_channel(channel) |
| 279 | + |
| 280 | + tg.create_task(channel_hopper()) |
| 281 | + |
| 282 | + writer = PcapWriter(output) |
| 283 | + writer.write_header() |
| 284 | + |
| 285 | + async for packet in capture: |
| 286 | + LOGGER.debug("Got a packet %s", packet) |
| 287 | + writer.write_packet(packet) |
| 288 | + |
| 289 | + if output.name == "<stdout>": # Surely there's a better way? |
| 290 | + output.flush() |
0 commit comments