Skip to content

Commit 0f1fe7c

Browse files
committed
Add a new command for concurrent packet capture
1 parent e29c3dd commit 0f1fe7c

File tree

2 files changed

+59
-5
lines changed

2 files changed

+59
-5
lines changed

zigpy_cli/pcap.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
from __future__ import annotations
22

3+
import datetime
4+
import json
35
import logging
6+
import sys
47

58
import click
9+
import zigpy.types as t
610
from scapy.config import conf as scapy_conf
711
from scapy.layers.dot15d4 import Dot15d4 # NOQA: F401
812
from scapy.utils import PcapReader, PcapWriter
913

1014
from zigpy_cli.cli import cli
1115

16+
from .helpers import PcapWriter as ZigpyPcapWriter
17+
1218
scapy_conf.dot15d4_protocol = "zigbee"
1319

1420
LOGGER = logging.getLogger(__name__)
@@ -29,3 +35,26 @@ def fix_fcs(input, output):
2935
for packet in reader:
3036
packet.fcs = None
3137
writer.write(packet)
38+
39+
40+
@pcap.command()
41+
@click.option("-o", "--output", type=click.File("wb"), required=True)
42+
def interleave_combine(output):
43+
if output.name == "<stdout>":
44+
output = sys.stdout.buffer.raw
45+
46+
writer = ZigpyPcapWriter(output)
47+
writer.write_header()
48+
49+
while True:
50+
line = sys.stdin.readline()
51+
data = json.loads(line)
52+
packet = t.CapturedPacket(
53+
timestamp=datetime.datetime.fromisoformat(data["timestamp"]),
54+
rssi=data["rssi"],
55+
lqi=data["lqi"],
56+
channel=data["channel"],
57+
data=bytes.fromhex(data["data"]),
58+
)
59+
60+
writer.write_packet(packet)

zigpy_cli/radio.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import json
99
import logging
1010
import random
11+
import sys
1112

1213
import click
1314
import zigpy.state
@@ -251,10 +252,19 @@ async def change_channel(app, channel):
251252
)
252253
@click.option("-p", "--channel-hop-period", type=float, default=5.0)
253254
@click.option("-o", "--output", type=click.File("wb"), required=True)
255+
@click.option("--interleave", is_flag=True, type=bool, default=False)
254256
@click_coroutine
255257
async def packet_capture(
256-
app, channel_hop_randomly, channels, channel_hop_period, output
258+
app,
259+
channel_hop_randomly,
260+
channels,
261+
channel_hop_period,
262+
output,
263+
interleave,
257264
):
265+
if output.name == "<stdout>" and not interleave:
266+
output = sys.stdout.buffer.raw
267+
258268
if not channel_hop_randomly:
259269
channels_iter = itertools.cycle(channels)
260270
else:
@@ -270,8 +280,9 @@ def channels_iter_func():
270280

271281
await app.connect()
272282

273-
writer = PcapWriter(output)
274-
writer.write_header()
283+
if not interleave:
284+
writer = PcapWriter(output)
285+
writer.write_header()
275286

276287
async with asyncio.TaskGroup() as tg:
277288
channel_hopper_task = None
@@ -287,7 +298,21 @@ async def channel_hopper():
287298
channel_hopper_task = tg.create_task(channel_hopper())
288299

289300
LOGGER.debug("Got a packet %s", packet)
290-
writer.write_packet(packet)
291301

292-
if output.name == "<stdout>": # Surely there's a better way?
302+
if not interleave:
303+
writer.write_packet(packet)
304+
else:
305+
# To do line interleaving, encode the packets as JSON
306+
output.write(
307+
json.dumps(
308+
{
309+
"timestamp": packet.timestamp.isoformat(),
310+
"rssi": packet.rssi,
311+
"lqi": packet.lqi,
312+
"channel": packet.channel,
313+
"data": packet.data.hex(),
314+
}
315+
).encode("ascii")
316+
+ b"\n"
317+
)
293318
output.flush()

0 commit comments

Comments
 (0)