-
Notifications
You must be signed in to change notification settings - Fork 155
/
sparrowhackrf.py
executable file
·382 lines (300 loc) · 12.7 KB
/
sparrowhackrf.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
#!/usr/bin/python3
#
# Copyright 2017 ghostop14
#
# This is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this software; see the file COPYING. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
#
import os
import subprocess
# import sys
import signal
from time import sleep
import re
from threading import Lock
import datetime
from dateutil import parser
import json
from sparrowcommon import BaseThreadClass
# ------------------ Ubertooth Specan scanning Thread ----------------------------------
class HackrfSweepThread(BaseThreadClass):
def __init__(self, parentHackrf):
super().__init__()
self.parentHackrf= parentHackrf
self.minFreq = 2400
self.maxFreq = 5900
self.binWidth = 250000 # 250 KHz width
self.gain = 40
# mirror qspectrumanalyzer
# In python3 / is a floating point operation whereas // is explicitly integer division. Result is without remainder
self.lna_gain = 8 * (self.gain // 8)
self.vga_gain = 2 * ((self.gain - self.lna_gain) // 2)
def run(self):
self.threadRunning = True
subprocess.run(['pkill', '-9','hackrf_sweep'], stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)
# See this for a good example on threading and reading from a streaming proc
# https://stackoverflow.com/questions/16768290/understanding-popen-communicate
freqRange = str(self.minFreq) + ":" + str(self.maxFreq)
params = ['hackrf_sweep', '-f', freqRange, '-l',str(self.lna_gain),'-g',str(self.vga_gain),'-w', str(self.binWidth)]
#if '/usr/bin' not in sys.path:
# sys.path.append('/usr/bin')
#if '/usr/local/bin' not in sys.path:
# sys.path.append('/usr/local/bin')
#cmd = 'hackrf_sweep -f '+ freqRange + ' -l ' + str(self.lna_gain) + ' -g ' + str(self.vga_gain) + ' -w '+ str(self.binWidth)
hackrfsweepProc = subprocess.Popen(params,stdout=subprocess.PIPE,stderr=subprocess.DEVNULL) # , shell=True)
# hackrf_sweep output:
# strftime(time_str, 50, "%Y-%m-%d, %H:%M:%S", fft_time);
# fprintf(fd, "%s.%06ld, %" PRIu64 ", %" PRIu64 ", %.2f, %u",
# time_str,
# (long int)time_stamp.tv_usec,
# (uint64_t)(frequency),
# (uint64_t)(frequency+DEFAULT_SAMPLE_RATE_HZ/4),
# fft_bin_width,
# fftSize);
# for(i = 0; (fftSize / 4) > i; i++) {
# fprintf(fd, ", %.2f", pwr[i + 1 + (fftSize*5)/8]);
# }
# fprintf(fd, "\n");
# fprintf(fd, "%s.%06ld, %" PRIu64 ", %" PRIu64 ", %.2f, %u",
# time_str,
# (long int)time_stamp.tv_usec,
# (uint64_t)(frequency+(DEFAULT_SAMPLE_RATE_HZ/2)),
# (uint64_t)(frequency+((DEFAULT_SAMPLE_RATE_HZ*3)/4)),
# fft_bin_width,
# fftSize);
# for(i = 0; (fftSize / 4) > i; i++) {
# fprintf(fd, ", %.2f", pwr[i + 1 + (fftSize/8)]);
# }
# fprintf(fd, "\n");
iteration = 0
while not hackrfsweepProc.poll() and not self.signalStop:
dataline = hackrfsweepProc.stdout.readline().decode('UTF-8').replace('\n', '')
dataline = dataline.replace(' ', '')
data = dataline.split(',')
# First 6 fields are setup: date/time, start/end freq, etc.
if len(data) > 6:
try:
startfreq = int(data[2])
# debug:
# print('DEBUG: ' + str(startfreq) + "," + data[3])
numSamples = len(data) - 6
# self.parentHackrf.spectrumLock.acquire()
if numSamples > 0:
for i in range(0, numSamples):
self.parentHackrf.spectrum[startfreq + i * self.binWidth] = float(data[i+6])
# self.parentHackrf.spectrumLock.release()
except:
pass
# Just give the thread a chance to release resources
iteration += 1
if iteration > 50000:
iteration = 0
sleep(0.01)
try:
os.kill(hackrfsweepProc.pid, signal.SIGINT)
except:
pass
self.threadRunning = False
# ------------------ Sparrow HackRF Class ----------------------------------
class SparrowHackrf(object):
def __init__(self):
self.spectrum = {}
self.minFreq = 2400
self.maxFreq = 2500 # 5900
self.binWidth = 500000
self.gain = 40
self.spectrumLock = Lock()
# This scan thread is for the spectrum
self.spectrumScanThread = None
if SparrowHackrf.getNumHackrfDevices() > 0:
self.hasHackrf = True
else:
self.hasHackrf = False
def resetSpectrum(self):
self.spectrum.clear()
# + 1 is for the range loop. range goes 1 less than numNetries
numEntries = int((self.maxFreq - self.minFreq) * 1000000 / self.binWidth) + 1
freqHz = self.minFreq * 1000000
for i in range(0, numEntries):
self.spectrum[freqHz + i * self.binWidth] = -100.0
def getNumHackrfDevices():
result = subprocess.run(['lsusb', '-d', '1d50:6089'], stdout=subprocess.PIPE,stderr=subprocess.DEVNULL)
if result.returncode != 0:
return 0
hciResult = result.stdout.decode('UTF-8')
p = re.compile('^.*(1d50)', re.MULTILINE)
tmpInterfaces = p.findall(hciResult)
retVal = 0
if (len(tmpInterfaces) > 0):
for curInterface in tmpInterfaces:
retVal += 1
return retVal
def startScanning24(self):
if self.scanRunning():
self.stopScanning()
self.minFreq = 2400
self.maxFreq = 2500
self.binWidth = 500000
self.gain = 40
self.startScanning(16, 24)
def startScanning5(self):
self.minFreq = 5170
# self.maxFreq = 5270
self.maxFreq = 5840
#self.binWidth = 2000000
# Settled on this as the most optimal resolution versus update rate setting. (5 GHz uses 52 streams across 20 MHz for about 0.38 MHz
# per stream. Taking into account spacing and such this captures probably about 4 streams per bucket)
self.binWidth = 1600000
self.gain = 48
# 5 GHz needs more gain
self.startScanning(32, 16)
def startScanning(self, lna_gain = 32, vga_gain = 16):
if not self.hasHackrf:
return
if self.spectrumScanThread:
self.stopScanning()
self.spectrumScanThread = HackrfSweepThread(self)
self.spectrumScanThread.minFreq = self.minFreq
self.spectrumScanThread.maxFreq = self.maxFreq
self.spectrumScanThread.binWidth = self.binWidth # 250 KHz width
self.spectrumScanThread.gain = self.gain
# mirror qspectrumanalyzer
self.spectrumScanThread.lna_gain = lna_gain
self.spectrumScanThread.vga_gain = vga_gain
self.spectrum.clear()
# self.resetSpectrum()
self.spectrumScanThread.start()
def scanRunning(self):
if self.spectrumScanThread and self.spectrumScanThread.threadRunning:
return True
else:
return False
def scanRunning24(self):
if self.scanRunning():
if self.minFreq == 2400:
return True
else:
return False
else:
return False
def scanRunning5(self):
if self.scanRunning():
if self.minFreq == 5170:
return True
else:
return False
else:
return False
def stopScanning(self):
if self.spectrumScanThread:
self.spectrumScanThread.stopAndWait()
self.spectrumScanThread = None
subprocess.run(['pkill', '-9','hackrf_sweep'], stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)
def spectrum24ToChannels(self):
retVal = {}
# if len(self.spectrum) < 14001:
# return retVal
# self.spectrumLock.acquire()
try:
for curKey in self.spectrum.keys():
# curKey is frequency
if curKey >= 2400000000 and curKey < 3000000000:
curFreq = float(curKey)/1000000.0
channel = SparrowHackrf.fFreqTo24Channel(curFreq)
power = self.spectrum[curKey] - 25.0
if power <= -100.0:
power = -100.0
retVal[channel] = power
except:
pass
# self.spectrumLock.release()
return retVal
def spectrum5ToChannels(self):
retVal = {}
# self.spectrumLock.acquire()
try:
for curKey in self.spectrum.keys():
# curKey is frequency
if curKey >= 5180000000 and curKey <= 5835000000:
curFreq = float(curKey)/1000000.0
channel = SparrowHackrf.fFreqTo5Channel(curFreq)
power = self.spectrum[curKey]
# 5 Ghz with the gain the noise floor is a bit higher.
power = self.spectrum[curKey] - 30.0
if power <= -100.0:
power = -100.0
retVal[channel] = power
except:
pass
# self.spectrumLock.release()
return retVal
def fFreqTo24Channel(frequency):
# Note: This function returns a float for partial channels
# ch1 center freq is 2412. +- 1 channel is 5 MHz
# 2402 = Ch -1
# Map bluetooth frequency to 2.4 GHz wifi channel
# ch 1 starts at 2401 MHz and ch 14 tops out at 2495
if frequency < 2402:
return float(-1.0)
elif frequency > 2494:
return float(16.0)
channel = -1.0 + (float(frequency) - 2402.0)/5.0
return channel
# Frequency range of 2.4 GHz channels 1 (low end 2402) to 14 (high end 2494)
#frange = 2494.0 - 2402.0
# The top end of 14 is 2494 but that would map to 16 on the chart
#crange = 16.0
#channel = float((float(frequency) - 2402.0) / frange * crange)
#return channel
def fFreqTo5Channel(frequency):
# Note: This function returns a float for partial channels
# ch 36 lower = 5180 MHz
# ch 144 high = 5730
# Gap
# ch 149 low = 5735
# ch 165 high = 5835
if frequency < 5180:
return float(36.0)
elif frequency > 5835:
return float(166.0)
channel = 35.0 + (float(frequency) - 5180.0)/5.0
return channel
# Frequency range of 2.4 GHz channels 1 (low end 2402) to 14 (high end 2494)
#frange = 2494.0 - 2402.0
# The top end of 14 is 2494 but that would map to 16 on the chart
#crange = 16.0
#channel = float((float(frequency) - 2402.0) / frange * crange)
#return channel
if __name__ == '__main__':
hackrf = SparrowHackrf()
if SparrowHackrf.getNumHackrfDevices() == 0:
print("ERROR: No HackRF devices found.")
exit(1)
# Scan 5 GHz
hackrf.minFreq = 5170
hackrf.maxFreq = 5840
hackrf.binWidth = 1000000
hackrf.startScanning(32, 16)
for i in range(0, 30):
sleep(1)
hackrf.stopScanning()
print('Spectrum Length: ' + str(len(hackrf.spectrum)))
i = 0
for curKey in hackrf.spectrum.keys():
print('spectrum[' + str(curKey) + '] = ' + str(hackrf.spectrum[curKey]))
i += 1
if i > 50:
break
print("Test complete.")