Skip to content

Commit 5aa3972

Browse files
author
Bjoern Kerler
committed
Add zmq server and json support
1 parent 84b1ec0 commit 5aa3972

File tree

13 files changed

+338
-100
lines changed

13 files changed

+338
-100
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Sniffle has a number of useful features, including:
1818
* Easy to extend host-side software written in Python
1919
* PCAP export compatible with the Ubertooth
2020
* Wireshark compatible plugin
21+
* ZMQ Publishing server
2122

2223
## Prerequisites
2324

@@ -244,6 +245,8 @@ options:
244245
-d, --decode Decode advertising data
245246
-o OUTPUT, --output OUTPUT
246247
PCAP output file name
248+
-z, --zmq Enable ZMQ server
249+
--zmqsetting Set ZMQ server ip and port (Default 127.0.0.1:4222)
247250
```
248251

249252
The XDS110 debugger on the Launchpad boards creates two serial ports. On

python_cli/advertiser.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@
1111
# global variable to access hardware
1212
hw = None
1313

14+
1415
def main():
1516
aparse = argparse.ArgumentParser(description="Connection initiator test script for Sniffle BLE5 sniffer")
1617
aparse.add_argument("-s", "--serport", default=None, help="Sniffer serial port name")
18+
aparse.add_argument("-b", "--baudrate", default=None, help="Sniffer serial port baudrate")
1719
args = aparse.parse_args()
1820

1921
global hw
20-
hw = SniffleHW(args.serport)
22+
hw = SniffleHW(args.serport, baudrate=args.baudrate)
2123

2224
# set the advertising channel (and return to ad-sniffing mode)
2325
hw.cmd_chan_aa_phy(37, BLE_ADV_AA, 0)
@@ -69,5 +71,6 @@ def main():
6971
if msg is not None:
7072
print(msg, end='\n\n')
7173

74+
7275
if __name__ == "__main__":
7376
main()

python_cli/initiator.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
def main():
1919
aparse = argparse.ArgumentParser(description="Connection initiator test script for Sniffle BLE5 sniffer")
2020
aparse.add_argument("-s", "--serport", default=None, help="Sniffer serial port name")
21+
aparse.add_argument("-b", "--baudrate", default=None, help="Sniffer serial port baudrate")
2122
aparse.add_argument("-c", "--advchan", default=37, choices=[37, 38, 39], type=int,
2223
help="Advertising channel to listen on")
2324
aparse.add_argument("-m", "--mac", default=None, help="Specify target MAC address")
@@ -31,7 +32,7 @@ def main():
3132
args = aparse.parse_args()
3233

3334
global hw
34-
hw = SniffleHW(args.serport)
35+
hw = SniffleHW(args.serport, baudrate=args.baudrate)
3536

3637
targ_specs = bool(args.mac) + bool(args.irk) + bool(args.string)
3738
if targ_specs < 1:

python_cli/requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
pyserial
2+
zmq
3+
numpy
4+
scipy

python_cli/reset.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@
1111
def main():
1212
aparse = argparse.ArgumentParser(description="Firmware reset utility for Sniffle BLE5 sniffer")
1313
aparse.add_argument("-s", "--serport", default=None, help="Sniffer serial port name")
14+
aparse.add_argument("-b", "--baudrate", default=None, help="Sniffer serial port baudrate")
1415
args = aparse.parse_args()
1516

16-
hw = SniffleHW(args.serport)
17+
hw = SniffleHW(args.serport, baudrate=args.baudrate)
1718

1819
# 5 resets seems to work more reliably than fewer
1920
print("Sending reset commands...")

python_cli/scanner.py

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@
1818
advertisers = {}
1919
done_scan = False
2020

21+
2122
def sigint_handler(sig, frame):
2223
global done_scan
2324
done_scan = True
2425
hw.cancel_recv()
2526

27+
2628
class Advertiser:
2729
def __init__(self):
2830
self.adv = None
@@ -42,32 +44,34 @@ def add_hit(self, rssi):
4244
self.rssi_min = rssi
4345
elif rssi > self.rssi_max:
4446
self.rssi_max = rssi
45-
self.rssi_avg = (self.rssi_avg*self.hits + rssi) / (self.hits + 1)
47+
self.rssi_avg = (self.rssi_avg * self.hits + rssi) / (self.hits + 1)
4648
self.hits += 1
4749

50+
4851
def main():
4952
aparse = argparse.ArgumentParser(description="Scanner utility for Sniffle BLE5 sniffer")
5053
aparse.add_argument("-s", "--serport", default=None, help="Sniffer serial port name")
54+
aparse.add_argument("-b", "--baudrate", default=None, help="Sniffer serial port baudrate")
5155
aparse.add_argument("-c", "--advchan", default=37, choices=[37, 38, 39], type=int,
52-
help="Advertising channel to listen on")
56+
help="Advertising channel to listen on")
5357
aparse.add_argument("-r", "--rssi", default=-128, type=int,
54-
help="Filter packets by minimum RSSI")
58+
help="Filter packets by minimum RSSI")
5559
aparse.add_argument("-l", "--longrange", action="store_true",
56-
help="Use long range (coded) PHY for primary advertising")
60+
help="Use long range (coded) PHY for primary advertising")
5761
aparse.add_argument("-d", "--decode", action="store_true",
58-
help="Decode advertising data")
62+
help="Decode advertising data")
5963
aparse.add_argument("-o", "--output", default=None, help="PCAP output file name")
6064
args = aparse.parse_args()
6165

6266
global hw
63-
hw = make_sniffle_hw(args.serport)
67+
hw = make_sniffle_hw(serport=args.serport, baudrate=args.baudrate)
6468

6569
hw.setup_sniffer(
66-
mode=SnifferMode.ACTIVE_SCAN,
67-
chan=args.advchan,
68-
ext_adv=True,
69-
coded_phy=args.longrange,
70-
rssi_min=args.rssi)
70+
mode=SnifferMode.ACTIVE_SCAN,
71+
chan=args.advchan,
72+
ext_adv=True,
73+
coded_phy=args.longrange,
74+
rssi_min=args.rssi)
7175

7276
# zero timestamps and flush old packets
7377
hw.mark_and_flush()
@@ -96,10 +100,10 @@ def main():
96100

97101
print("\n\nScan Results:")
98102
for a in sorted(advertisers.keys(), key=lambda k: advertisers[k].rssi_avg, reverse=True):
99-
print("="*80)
103+
print("=" * 80)
100104
print("AdvA: %s Avg/Min/Max RSSI: %.1f/%i/%i Hits: %i" % (
101-
a, advertisers[a].rssi_avg, advertisers[a].rssi_min, advertisers[a].rssi_max,
102-
advertisers[a].hits))
105+
a, advertisers[a].rssi_avg, advertisers[a].rssi_min, advertisers[a].rssi_max,
106+
advertisers[a].hits))
103107
if advertisers[a].adv:
104108
print("\nAdvertisement:")
105109
print(advertisers[a].adv.str_header())
@@ -120,7 +124,8 @@ def main():
120124
print(advertisers[a].scan_rsp.hexdump())
121125
else:
122126
print("\nScan Response: None")
123-
print("="*80, end="\n\n")
127+
print("=" * 80, end="\n\n")
128+
124129

125130
def handle_packet(dpkt):
126131
# Ignore non-advertisements (shouldn't get any)
@@ -151,5 +156,6 @@ def handle_packet(dpkt):
151156
else:
152157
advertisers[adva].adv = dpkt
153158

159+
154160
if __name__ == "__main__":
155161
main()

python_cli/sniff_receiver.py

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
#!/usr/bin/env python3
22

33
# Written by Sultan Qasim Khan
4+
# OpenDroneID mods (c) by B. Kerler
45
# Copyright (c) 2018-2024, NCC Group plc
56
# Released as open source under GPLv3
67

78
import argparse, sys
9+
import json
10+
import time
811
from binascii import unhexlify
9-
from sniffle.constants import BLE_ADV_AA
1012
from sniffle.pcap import PcapBleWriter
1113
from sniffle.sniffle_hw import (make_sniffle_hw, PacketMessage, DebugMessage, StateMessage,
1214
MeasurementMessage, SnifferMode, PhyMode)
1315
from sniffle.packet_decoder import (AdvaMessage, AdvDirectIndMessage, AdvExtIndMessage,
14-
ScanRspMessage, DataMessage, str_mac)
16+
ScanRspMessage, DataMessage, str_mac, AdvIndMessage)
1517
from sniffle.errors import UsageError, SourceDone
1618
from sniffle.advdata.decoder import decode_adv_data
1719

@@ -24,6 +26,7 @@
2426
def main():
2527
aparse = argparse.ArgumentParser(description="Host-side receiver for Sniffle BLE5 sniffer")
2628
aparse.add_argument("-s", "--serport", default=None, help="Sniffer serial port name")
29+
aparse.add_argument("-b", "--baudrate", default=None, help="Sniffer serial port baudrate")
2730
aparse.add_argument("-c", "--advchan", default=40, choices=[37, 38, 39], type=int,
2831
help="Advertising channel to listen on")
2932
aparse.add_argument("-p", "--pause", action="store_true",
@@ -55,8 +58,41 @@ def main():
5558
aparse.add_argument("-d", "--decode", action="store_true",
5659
help="Decode advertising data")
5760
aparse.add_argument("-o", "--output", default=None, help="PCAP output file name")
61+
aparse.add_argument("-z", "--zmq", action="store_true", help="Enable zmq")
62+
aparse.add_argument("--zmqsetting", default="127.0.0.1:4222", help="Define zmq server settings")
63+
aparse.add_argument("-v", "--verbose", action="store_true", help="Print messages")
5864
args = aparse.parse_args()
5965

66+
if args.zmq:
67+
import zmq
68+
69+
url = f"tcp://{args.zmqsetting}"
70+
71+
context = zmq.Context()
72+
socket = context.socket(zmq.XPUB)
73+
socket.setsockopt(zmq.XPUB_VERBOSE, True)
74+
socket.bind(url)
75+
76+
def zmq_thread(socket):
77+
try:
78+
while True:
79+
event = socket.recv()
80+
# Event is one byte 0=unsub or 1=sub, followed by topic
81+
if event[0] == 1:
82+
log("new subscriber for", event[1:])
83+
elif event[0] == 0:
84+
log("unsubscribed", event[1:])
85+
except zmq.error.ContextTerminated:
86+
pass
87+
88+
def log(*msg):
89+
s = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
90+
print("%s:" % s, *msg, end="\n", file=sys.stderr)
91+
92+
from threading import Thread
93+
zthread = Thread(target=zmq_thread, args=[socket], daemon=True, name='zmq')
94+
zthread.start()
95+
6096
# Sanity check argument combinations
6197
targ_specs = bool(args.mac) + bool(args.irk) + bool(args.string)
6298
if args.hop and targ_specs < 1:
@@ -70,7 +106,7 @@ def main():
70106
raise UsageError("Don't specify an advertising channel if you want advertising channel hopping!")
71107

72108
global hw
73-
hw = make_sniffle_hw(args.serport)
109+
hw = make_sniffle_hw(serport=args.serport, baudrate=args.baudrate)
74110

75111
# if a channel was explicitly specified, don't hop
76112
hop3 = True if targ_specs else False
@@ -137,10 +173,19 @@ def main():
137173
while True:
138174
try:
139175
msg = hw.recv_and_decode()
140-
print_message(msg, args.quiet, args.decode)
176+
if args.zmq:
177+
smsg = msg.to_dict()
178+
smsg = json.dumps(smsg)
179+
socket.send_string(smsg)
180+
if args.verbose:
181+
print_message(msg, args.quiet, args.decode)
182+
else:
183+
print_message(msg, args.quiet, args.decode)
141184
except SourceDone:
142185
break
143186
except KeyboardInterrupt:
187+
if args.zmq:
188+
socket.close()
144189
hw.cancel_recv()
145190
sys.stderr.write("\r")
146191
break

0 commit comments

Comments
 (0)