├── .gitignore ├── Makefile ├── README.md ├── convenience.c ├── convenience.h ├── gpsheatmap.py ├── hack.c ├── hackrf_buffer.c ├── hackrf_buffer.h └── hackrf_power.c /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | # Debug files 32 | *.dSYM/ 33 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS = -DGPS_POWER -O -g -Wall 2 | 3 | OBJ = hackrf_power.o hackrf_buffer.o convenience.o 4 | 5 | 6 | hackrf_power: $(OBJ) 7 | $(CC) -o hackrf_power $(OBJ) -lhackrf -lpthread -lm -lgps 8 | 9 | clean: 10 | -rm -f hackrf_power *.o 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hackrf-power 2 | a version of rtlsdr_power for hackrf, with gps logging 3 | -------------------------------------------------------------------------------- /convenience.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2014 by Kyle Keen 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | /* a collection of user friendly tools 19 | * todo: use strtol for more flexible int parsing 20 | * */ 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #ifndef _WIN32 27 | #include 28 | #else 29 | #include 30 | #include 31 | #include 32 | #define _USE_MATH_DEFINES 33 | #endif 34 | 35 | #include 36 | 37 | #include "rtl-sdr.h" 38 | 39 | double atofs(char *s) 40 | /* standard suffixes */ 41 | { 42 | char last; 43 | int len; 44 | double suff = 1.0; 45 | len = strlen(s); 46 | last = s[len-1]; 47 | s[len-1] = '\0'; 48 | switch (last) { 49 | case 'g': 50 | case 'G': 51 | suff *= 1e3; 52 | case 'm': 53 | case 'M': 54 | suff *= 1e3; 55 | case 'k': 56 | case 'K': 57 | suff *= 1e3; 58 | suff *= atof(s); 59 | s[len-1] = last; 60 | return suff; 61 | } 62 | s[len-1] = last; 63 | return atof(s); 64 | } 65 | 66 | double atoft(char *s) 67 | /* time suffixes, returns seconds */ 68 | { 69 | char last; 70 | int len; 71 | double suff = 1.0; 72 | len = strlen(s); 73 | last = s[len-1]; 74 | s[len-1] = '\0'; 75 | switch (last) { 76 | case 'h': 77 | case 'H': 78 | suff *= 60; 79 | case 'm': 80 | case 'M': 81 | suff *= 60; 82 | case 's': 83 | case 'S': 84 | suff *= atof(s); 85 | s[len-1] = last; 86 | return suff; 87 | } 88 | s[len-1] = last; 89 | return atof(s); 90 | } 91 | 92 | double atofp(char *s) 93 | /* percent suffixes */ 94 | { 95 | char last; 96 | int len; 97 | double suff = 1.0; 98 | len = strlen(s); 99 | last = s[len-1]; 100 | s[len-1] = '\0'; 101 | switch (last) { 102 | case '%': 103 | suff *= 0.01; 104 | suff *= atof(s); 105 | s[len-1] = last; 106 | return suff; 107 | } 108 | s[len-1] = last; 109 | return atof(s); 110 | } 111 | // vim: tabstop=8:softtabstop=8:shiftwidth=8:noexpandtab 112 | -------------------------------------------------------------------------------- /convenience.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2014 by Kyle Keen 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | /* a collection of user friendly tools */ 19 | 20 | /*! 21 | * Convert standard suffixes (k, M, G) to double 22 | * 23 | * \param s a string to be parsed 24 | * \return double 25 | */ 26 | 27 | double atofs(char *s); 28 | 29 | /*! 30 | * Convert time suffixes (s, m, h) to double 31 | * 32 | * \param s a string to be parsed 33 | * \return seconds as double 34 | */ 35 | 36 | double atoft(char *s); 37 | 38 | /*! 39 | * Convert percent suffixe (%) to double 40 | * 41 | * \param s a string to be parsed 42 | * \return double 43 | */ 44 | 45 | double atofp(char *s); 46 | 47 | 48 | -------------------------------------------------------------------------------- /gpsheatmap.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | from PIL import Image, ImageDraw, ImageFont 4 | import os, sys, gzip, math, argparse, colorsys, datetime 5 | from collections import defaultdict 6 | from itertools import * 7 | 8 | urlretrieve = lambda a, b: None 9 | try: 10 | import urllib.request 11 | urlretrieve = urllib.request.urlretrieve 12 | except: 13 | import urllib 14 | urlretrieve = urllib.urlretrieve 15 | 16 | # todo: 17 | # matplotlib powered --interactive 18 | # arbitrary freq marker spacing 19 | # ppm 20 | # blue-less marker grid 21 | # fast summary thing 22 | # gain normalization 23 | 24 | vera_url = "https://github.com/keenerd/rtl-sdr-misc/raw/master/heatmap/Vera.ttf" 25 | vera_path = os.path.join(sys.path[0], "Vera.ttf") 26 | 27 | parser = argparse.ArgumentParser(description='Convert rtl_power CSV files into graphics.') 28 | parser.add_argument('input_path', metavar='INPUT', type=str, 29 | help='Input CSV file. (may be a .csv.gz)') 30 | parser.add_argument('output_path', metavar='OUTPUT', type=str, 31 | help='Output image. (various extensions supported)') 32 | parser.add_argument('--offset', dest='offset_freq', default=None, 33 | help='Shift the entire frequency range, for up/down converters.') 34 | parser.add_argument('--ytick', dest='time_tick', default=None, 35 | help='Place ticks along the Y axis every N seconds/minutes/hours/days.') 36 | parser.add_argument('--db', dest='db_limit', nargs=2, default=None, 37 | help='Minimum and maximum db values.') 38 | parser.add_argument('--compress', dest='compress', default=0, 39 | help='Apply a gradual asymptotic time compression. Values > 1 are the new target height, values < 1 are a scaling factor.') 40 | slicegroup = parser.add_argument_group('Slicing', 41 | 'Efficiently render a portion of the data. (optional) Frequencies can take G/M/k suffixes. Timestamps look like "YYYY-MM-DD HH:MM:SS" Durations take d/h/m/s suffixes.') 42 | slicegroup.add_argument('--low', dest='low_freq', default=None, 43 | help='Minimum frequency for a subrange.') 44 | slicegroup.add_argument('--high', dest='high_freq', default=None, 45 | help='Maximum frequency for a subrange.') 46 | slicegroup.add_argument('--begin', dest='begin_time', default=None, 47 | help='Timestamp to start at.') 48 | slicegroup.add_argument('--end', dest='end_time', default=None, 49 | help='Timestamp to stop at.') 50 | slicegroup.add_argument('--head', dest='head_time', default=None, 51 | help='Duration to use, starting at the beginning.') 52 | slicegroup.add_argument('--tail', dest='tail_time', default=None, 53 | help='Duration to use, stopping at the end.') 54 | 55 | # hack, http://stackoverflow.com/questions/9025204/ 56 | for i, arg in enumerate(sys.argv): 57 | if (arg[0] == '-') and arg[1].isdigit(): 58 | sys.argv[i] = ' ' + arg 59 | args = parser.parse_args() 60 | 61 | if not os.path.isfile(vera_path): 62 | urlretrieve(vera_url, vera_path) 63 | 64 | try: 65 | font = ImageFont.truetype(vera_path, 10) 66 | except: 67 | print('Please download the Vera.ttf font and place it in the current directory.') 68 | sys.exit(1) 69 | 70 | 71 | COL_DATE = 0 72 | COL_TIME = 1 73 | COL_LAT = 2 74 | COL_LON = 3 75 | COL_ALT = 4 76 | COL_FLO = 5 77 | COL_FHI = 6 78 | COL_FSTEP= 7 79 | COL_NSAMP = 8 80 | COL_DATA = 9 81 | 82 | 83 | def frange(start, stop, step): 84 | i = 0 85 | while (i*step + start <= stop): 86 | yield i*step + start 87 | i += 1 88 | 89 | def min_filter(row): 90 | size = 3 91 | result = [] 92 | for i in range(size): 93 | here = row[i] 94 | near = row[0:i] + row[i+1:size] 95 | if here > min(near): 96 | result.append(here) 97 | continue 98 | result.append(min(near)) 99 | for i in range(size-1, len(row)): 100 | here = row[i] 101 | near = row[i-(size-1):i] 102 | if here > min(near): 103 | result.append(here) 104 | continue 105 | result.append(min(near)) 106 | return result 107 | 108 | def floatify(zs): 109 | # nix errors with -inf, windows errors with -1.#J 110 | zs2 = [] 111 | previous = 0 # awkward for single-column rows 112 | for z in zs: 113 | try: 114 | z = float(z) 115 | except ValueError: 116 | z = previous 117 | if math.isinf(z): 118 | z = previous 119 | if math.isnan(z): 120 | z = previous 121 | zs2.append(z) 122 | previous = z 123 | return zs2 124 | 125 | def freq_parse(s): 126 | suffix = 1 127 | if s.lower().endswith('k'): 128 | suffix = 1e3 129 | if s.lower().endswith('m'): 130 | suffix = 1e6 131 | if s.lower().endswith('g'): 132 | suffix = 1e9 133 | if suffix != 1: 134 | s = s[:-1] 135 | return float(s) * suffix 136 | 137 | def duration_parse(s): 138 | suffix = 1 139 | if s.lower().endswith('s'): 140 | suffix = 1 141 | if s.lower().endswith('m'): 142 | suffix = 60 143 | if s.lower().endswith('h'): 144 | suffix = 60 * 60 145 | if s.lower().endswith('d'): 146 | suffix = 24 * 60 * 60 147 | if suffix != 1 or s.lower().endswith('s'): 148 | s = s[:-1] 149 | return float(s) * suffix 150 | 151 | def date_parse(s): 152 | if '-' not in s: 153 | return datetime.datetime.fromtimestamp(int(s)) 154 | return datetime.datetime.strptime(s, '%Y-%m-%d %H:%M:%S') 155 | 156 | def gzip_wrap(path): 157 | "hides silly CRC errors" 158 | iterator = gzip.open(path, 'rb') 159 | running = True 160 | while running: 161 | try: 162 | line = next(iterator) 163 | if type(line) == bytes: 164 | line = line.decode('utf-8') 165 | yield line 166 | except IOError: 167 | running = False 168 | 169 | def reparse(label, fn): 170 | if args.__getattribute__(label) is None: 171 | return 172 | args.__setattr__(label, fn(args.__getattribute__(label))) 173 | 174 | path = args.input_path 175 | output = args.output_path 176 | 177 | raw_data = lambda: open(path) 178 | if path.endswith('.gz'): 179 | raw_data = lambda: gzip_wrap(path) 180 | 181 | reparse('low_freq', freq_parse) 182 | reparse('high_freq', freq_parse) 183 | reparse('offset_freq', freq_parse) 184 | if args.offset_freq is None: 185 | args.offset_freq = 0 186 | reparse('time_tick', duration_parse) 187 | reparse('begin_time', date_parse) 188 | reparse('end_time', date_parse) 189 | reparse('head_time', duration_parse) 190 | reparse('tail_time', duration_parse) 191 | reparse('head_time', lambda s: datetime.timedelta(seconds=s)) 192 | reparse('tail_time', lambda s: datetime.timedelta(seconds=s)) 193 | args.compress = float(args.compress) 194 | 195 | if args.begin_time and args.tail_time: 196 | print("Can't combine --begin and --tail") 197 | sys.exit(2) 198 | if args.end_time and args.head_time: 199 | print("Can't combine --end and --head") 200 | sys.exit(2) 201 | if args.head_time and args.tail_time: 202 | print("Can't combine --head and --tail") 203 | sys.exit(2) 204 | 205 | print("loading") 206 | 207 | def slice_columns(columns, low_freq, high_freq): 208 | start_col = 0 209 | stop_col = len(columns) 210 | if args.low_freq is not None and low <= args.low_freq <= high: 211 | start_col = sum(f args.end_time: 247 | break 248 | times.add(t) 249 | columns = list(frange(low, high, step)) 250 | start_col, stop_col = slice_columns(columns, args.low_freq, args.high_freq) 251 | f_key = (columns[start_col], columns[stop_col], step) 252 | zs = line[COL_DATA+start_col:COL_DATA+stop_col+1] 253 | if not zs: 254 | continue 255 | if f_key not in f_cache: 256 | freq2 = list(frange(*f_key))[:len(zs)] 257 | freqs.update(freq2) 258 | #freqs.add(f_key[1]) # high 259 | #labels.add(f_key[0]) # low 260 | f_cache.add(f_key) 261 | 262 | if not args.db_limit: 263 | zs = floatify(zs) 264 | min_z = min(min_z, min(zs)) 265 | max_z = max(max_z, max(zs)) 266 | 267 | if start is None: 268 | start = date_parse(t) 269 | stop = date_parse(t) 270 | if args.head_time is not None and args.end_time is None: 271 | args.end_time = start + args.head_time 272 | 273 | if args.tail_time is not None: 274 | times = [t for t in times if date_parse(t) >= (stop - args.tail_time)] 275 | start = date_parse(min(times)) 276 | 277 | freqs = list(sorted(list(freqs))) 278 | times = list(sorted(list(times))) 279 | labels = list(sorted(list(labels))) 280 | 281 | if len(labels) == 1: 282 | delta = (max(freqs) - min(freqs)) / (len(freqs) / 500.0) 283 | delta = round(delta / 10**int(math.log10(delta))) * 10**int(math.log10(delta)) 284 | delta = int(delta) 285 | lower = int(math.ceil(min(freqs) / delta) * delta) 286 | labels = list(range(lower, int(max(freqs)), delta)) 287 | 288 | def compression(y, decay): 289 | return int(round((1/decay)*math.exp(y*decay) - 1/decay)) 290 | 291 | height = len(times) 292 | height2 = height 293 | if args.compress: 294 | if args.compress > height: 295 | args.compress = 0 296 | print("Image too short, disabling compression") 297 | if 0 < args.compress < 1: 298 | args.compress *= height 299 | if args.compress: 300 | args.compress = -1 / args.compress 301 | height2 = compression(height, args.compress) 302 | 303 | print("x: %i, y: %i, z: (%f, %f)" % (len(freqs), height2, min_z, max_z)) 304 | 305 | def rgb2(z): 306 | g = (z - min_z) / (max_z - min_z) 307 | # print g, z, min_z, max_z 308 | return (int(g*255), int(g*255), 50) 309 | 310 | def rgb3(z): 311 | g = (z - min_z) / (max_z - min_z) 312 | c = colorsys.hsv_to_rgb(0.65-(g-0.08), 1, 0.2+g) 313 | return (int(c[0]*256),int(c[1]*256),int(c[2]*256)) 314 | 315 | def collate_row(x_size): 316 | # this is more fragile than the old code 317 | # sensitive to timestamps that are out of order 318 | old_t = None 319 | row = [0.0] * x_size 320 | for line in raw_data(): 321 | line = [s.strip() for s in line.strip().split(',')] 322 | #line = [line[0], line[1]] + [float(s) for s in line[2:] if s] 323 | line = [s for s in line if s] 324 | t = line[0] + ' ' + line[1] 325 | if '-' not in line[0]: 326 | t = line[0] 327 | if t not in times: 328 | continue # happens with live files and time cropping 329 | if old_t is None: 330 | old_t = t 331 | low = int(line[COL_FLO]) + args.offset_freq 332 | high = int(line[COL_FHI]) + args.offset_freq 333 | step = float(line[COL_FSTEP]) 334 | columns = list(frange(low, high, step)) 335 | start_col, stop_col = slice_columns(columns, args.low_freq, args.high_freq) 336 | if args.low_freq and columns[stop_col] < args.low_freq: 337 | continue 338 | if args.high_freq and columns[start_col] > args.high_freq: 339 | continue 340 | start_freq = columns[start_col] 341 | if args.low_freq: 342 | start_freq = max(args.low_freq, start_freq) 343 | # sometimes fails? skip or abort? 344 | x_start = freqs.index(start_freq) 345 | zs = floatify(line[COL_DATA+start_col:COL_DATA+stop_col+1]) 346 | if t != old_t: 347 | yield old_t, row 348 | row = [0.0] * x_size 349 | old_t = t 350 | for i in range(len(zs)): 351 | x = x_start + i 352 | if x >= x_size: 353 | continue 354 | row[x] = zs[i] 355 | yield old_t, row 356 | 357 | print("drawing") 358 | tape_height = 25 359 | img = Image.new("RGB", (len(freqs), tape_height + height2)) 360 | pix = img.load() 361 | x_size = img.size[0] 362 | average = [0.0] * len(freqs) 363 | tally = 0 364 | old_y = None 365 | for t, zs in collate_row(x_size): 366 | y = times.index(t) 367 | if not args.compress: 368 | for x in range(len(zs)): 369 | pix[x,y+tape_height] = rgb2(zs[x]) 370 | continue 371 | # ugh 372 | y = height2 - compression(height - y, args.compress) 373 | if old_y is None: 374 | old_y = y 375 | if old_y != y: 376 | for x in range(len(average)): 377 | pix[x,old_y+tape_height] = rgb2(average[x]/tally) 378 | tally = 0 379 | average = [0.0] * len(freqs) 380 | old_y = y 381 | for x in range(len(zs)): 382 | average[x] += zs[x] 383 | tally += 1 384 | 385 | 386 | def closest_index(n, m_list, interpolate=False): 387 | "assumes sorted m_list, returns two points for interpolate" 388 | i = len(m_list) // 2 389 | jump = len(m_list) // 2 390 | while jump > 1: 391 | i_down = i - jump 392 | i_here = i 393 | i_up = i + jump 394 | if i_down < 0: 395 | i_down = i 396 | if i_up >= len(m_list): 397 | i_up = i 398 | e_down = abs(m_list[i_down] - n) 399 | e_here = abs(m_list[i_here] - n) 400 | e_up = abs(m_list[i_up] - n) 401 | e_best = min([e_down, e_here, e_up]) 402 | if e_down == e_best: 403 | i = i_down 404 | if e_up == e_best: 405 | i = i_up 406 | if e_here == e_best: 407 | i = i_here 408 | jump = jump // 2 409 | if not interpolate: 410 | return i 411 | if n < m_list[i] and i > 0: 412 | return i-1, i 413 | if n > m_list[i] and i < len(m_list)-1: 414 | return i, i+1 415 | return i, i 416 | 417 | def word_aa(label, pt, fg_color, bg_color): 418 | f = ImageFont.truetype(vera_path, pt*3) 419 | s = f.getsize(label) 420 | s = (s[0], pt*3 + 3) # getsize lies, manually compute 421 | w_img = Image.new("RGB", s, bg_color) 422 | w_draw = ImageDraw.Draw(w_img) 423 | w_draw.text((0, 0), label, font=f, fill=fg_color) 424 | return w_img.resize((s[0]//3, s[1]//3), Image.ANTIALIAS) 425 | 426 | def blend(percent, c1, c2): 427 | "c1 and c2 are RGB tuples" 428 | # probably isn't gamma correct 429 | r = c1[0] * percent + c2[0] * (1 - percent) 430 | g = c1[1] * percent + c2[1] * (1 - percent) 431 | b = c1[2] * percent + c2[2] * (1 - percent) 432 | c3 = map(int, map(round, [r,g,b])) 433 | return tuple(c3) 434 | 435 | def tape_lines(interval, y1, y2, used=set()): 436 | "returns the number of lines" 437 | low_f = (min(freqs) // interval) * interval 438 | high_f = (1 + max(freqs) // interval) * interval 439 | hits = 0 440 | blur = lambda p: blend(p, (255, 255, 0), (0, 0, 0)) 441 | for i in range(int(low_f), int(high_f), int(interval)): 442 | if not (min(freqs) < i < max(freqs)): 443 | continue 444 | hits += 1 445 | if i in used: 446 | continue 447 | x1,x2 = closest_index(i, freqs, interpolate=True) 448 | if x1 == x2: 449 | draw.line([x1,y1,x1,y2], fill='black') 450 | else: 451 | percent = (i - freqs[x1]) / float(freqs[x2] - freqs[x1]) 452 | draw.line([x1,y1,x1,y2], fill=blur(percent)) 453 | draw.line([x2,y1,x2,y2], fill=blur(1-percent)) 454 | used.add(i) 455 | return hits 456 | 457 | def tape_text(interval, y, used=set()): 458 | low_f = (min(freqs) // interval) * interval 459 | high_f = (1 + max(freqs) // interval) * interval 460 | for i in range(int(low_f), int(high_f), int(interval)): 461 | if i in used: 462 | continue 463 | if not (min(freqs) < i < max(freqs)): 464 | continue 465 | x = closest_index(i, freqs) 466 | s = str(i) 467 | if interval >= 1e6: 468 | s = '%iM' % (i/1e6) 469 | elif interval > 1000: 470 | s = '%ik' % ((i/1e3) % 1000) 471 | if s.startswith('0'): 472 | s = '%iM' % (i/1e6) 473 | else: 474 | s = '%i' % (i%1000) 475 | if s.startswith('0'): 476 | s = '%ik' % ((i/1e3) % 1000) 477 | if s.startswith('0'): 478 | s = '%iM' % (i/1e6) 479 | w = word_aa(s, tape_pt, 'black', 'yellow') 480 | img.paste(w, (x - w.size[0]//2, y)) 481 | used.add(i) 482 | 483 | def shadow_text(x, y, s, font, fg_color='white', bg_color='black'): 484 | draw.text((x+1, y+1), s, font=font, fill=bg_color) 485 | draw.text((x, y), s, font=font, fill=fg_color) 486 | 487 | print("labeling") 488 | tape_pt = 10 489 | draw = ImageDraw.Draw(img) 490 | font = ImageFont.load_default() 491 | pixel_width = step 492 | 493 | draw.rectangle([0,0,img.size[0],tape_height], fill='yellow') 494 | min_freq = min(freqs) 495 | max_freq = max(freqs) 496 | delta = max_freq - min_freq 497 | width = len(freqs) 498 | label_base = 9 499 | 500 | for i in range(label_base, 0, -1): 501 | interval = int(10**i) 502 | low_f = (min_freq // interval) * interval 503 | high_f = (1 + max_freq // interval) * interval 504 | hits = len(range(int(low_f), int(high_f), interval)) 505 | if hits >= 4: 506 | label_base = i 507 | break 508 | label_base = 10**label_base 509 | 510 | for scale,y in [(1,10), (5,15), (10,19), (50,22), (100,24), (500, 25)]: 511 | hits = tape_lines(label_base/scale, y, tape_height) 512 | pixels_per_hit = width / hits 513 | if pixels_per_hit > 50: 514 | tape_text(label_base/scale, y-tape_pt) 515 | if pixels_per_hit < 10: 516 | break 517 | 518 | if args.time_tick: 519 | label_format = "%H:%M:%S" 520 | if args.time_tick % (60*60*24) == 0: 521 | label_format = "%Y-%m-%d" 522 | elif args.time_tick % 60 == 0: 523 | label_format = "%H:%M" 524 | label_next = datetime.datetime(start.year, start.month, start.day, start.hour) 525 | tick_delta = datetime.timedelta(seconds = args.time_tick) 526 | while label_next < start: 527 | label_next += tick_delta 528 | last_y = -100 529 | for y,t in enumerate(times): 530 | label_time = date_parse(t) 531 | if label_time < label_next: 532 | continue 533 | if args.compress: 534 | y = height2 - compression(height - y, args.compress) 535 | if y - last_y > 15: 536 | shadow_text(2, y+tape_height, label_next.strftime(label_format), font) 537 | last_y = y 538 | label_next += tick_delta 539 | 540 | 541 | duration = stop - start 542 | duration = duration.days * 24*60*60 + duration.seconds + 30 543 | pixel_height = duration / len(times) 544 | hours = int(duration / 3600) 545 | minutes = int((duration - 3600*hours) / 60) 546 | margin = 2 547 | if args.time_tick: 548 | margin = 60 549 | shadow_text(margin, img.size[1] - 45, 'Duration: %i:%02i' % (hours, minutes), font) 550 | shadow_text(margin, img.size[1] - 35, 'Range: %.2fMHz - %.2fMHz' % (min(freqs)/1e6, (max(freqs)+pixel_width)/1e6), font) 551 | shadow_text(margin, img.size[1] - 25, 'Pixel: %.2fHz x %is' % (pixel_width, int(round(pixel_height))), font) 552 | shadow_text(margin, img.size[1] - 15, 'Started: {0}'.format(start), font) 553 | # bin size 554 | 555 | print("saving") 556 | img.save(output) 557 | -------------------------------------------------------------------------------- /hack.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | 15 | #define U64TOA_MAX_DIGIT (31) 16 | typedef struct 17 | { 18 | char data[U64TOA_MAX_DIGIT+1]; 19 | } t_u64toa; 20 | 21 | 22 | #define DEFAULT_SAMPLE_RATE_HZ (10000000) /* 10MHz default sample rate */ 23 | #define DEFAULT_BASEBAND_FILTER_BANDWIDTH (5000000) /* 5MHz default */ 24 | #define FREQ_ONE_MHZ (1000000ull) 25 | 26 | static hackrf_device* device = NULL; 27 | 28 | static char *stringrev(char *str) 29 | { 30 | char *p1, *p2; 31 | 32 | if(! str || ! *str) 33 | return str; 34 | 35 | for(p1 = str, p2 = str + strlen(str) - 1; p2 > p1; ++p1, --p2) 36 | { 37 | *p1 ^= *p2; 38 | *p2 ^= *p1; 39 | *p1 ^= *p2; 40 | } 41 | return str; 42 | } 43 | 44 | 45 | char* u64toa(uint64_t val, t_u64toa* str) 46 | { 47 | #define BASE (10ull) /* Base10 by default */ 48 | uint64_t sum; 49 | int pos; 50 | int digit; 51 | int max_len; 52 | char* res; 53 | 54 | sum = val; 55 | max_len = U64TOA_MAX_DIGIT; 56 | pos = 0; 57 | 58 | do 59 | { 60 | digit = (sum % BASE); 61 | str->data[pos] = digit + '0'; 62 | pos++; 63 | 64 | sum /= BASE; 65 | }while( (sum>0) && (pos < max_len) ); 66 | 67 | if( (pos == max_len) && (sum>0) ) 68 | return NULL; 69 | 70 | str->data[pos] = '\0'; 71 | res = stringrev(str->data); 72 | 73 | return res; 74 | } 75 | 76 | int rx_callback(hackrf_transfer* transfer) { 77 | int i; 78 | fprintf(stderr,"rx_callback %d bytes\n", transfer -> valid_length); 79 | fprintf(stderr,"samples: "); 80 | for (i = 0; i < 10; i++) { 81 | fprintf(stderr, " %d", (int) transfer -> buffer[i]); 82 | } 83 | fprintf(stderr,"\n"); 84 | return 0; 85 | } 86 | 87 | main() 88 | { 89 | int vga_gain = 20; 90 | int lna_gain = 8; 91 | int result; 92 | const char* serial_number = NULL; 93 | int sample_rate_hz = DEFAULT_SAMPLE_RATE_HZ; 94 | int i; 95 | unsigned long long freq_hz = 91500000; 96 | t_u64toa ascii_u64_data1; 97 | 98 | result = hackrf_init(); 99 | if( result != HACKRF_SUCCESS ) { 100 | printf("hackrf_init() failed: %s (%d)\n", hackrf_error_name(result), result); 101 | return EXIT_FAILURE; 102 | } 103 | 104 | result = hackrf_open(&device); 105 | if( result != HACKRF_SUCCESS ) { 106 | printf("hackrf_open() failed: %s (%d)\n", hackrf_error_name(result), result); 107 | return EXIT_FAILURE; 108 | } 109 | 110 | 111 | printf("call hackrf_sample_rate_set(%u Hz/%.03f MHz)\n", 112 | sample_rate_hz,((float)sample_rate_hz/(float)FREQ_ONE_MHZ)); 113 | result = hackrf_set_sample_rate_manual(device, sample_rate_hz, 1); 114 | 115 | if( result != HACKRF_SUCCESS ) { 116 | printf("hackrf_sample_rate_set() failed: %s (%d)\n", hackrf_error_name(result), result); 117 | return EXIT_FAILURE; 118 | } 119 | 120 | int baseband_filter_bw_hz = hackrf_compute_baseband_filter_bw_round_down_lt(sample_rate_hz); 121 | 122 | printf("call hackrf_baseband_filter_bandwidth_set(%d Hz/%.03f MHz)\n", 123 | baseband_filter_bw_hz, ((float)baseband_filter_bw_hz/(float)FREQ_ONE_MHZ)); 124 | 125 | result = hackrf_set_baseband_filter_bandwidth(device, baseband_filter_bw_hz); 126 | 127 | if( result != HACKRF_SUCCESS ) { 128 | printf("hackrf_baseband_filter_bandwidth_set() failed: %s (%d)\n", 129 | hackrf_error_name(result), result); 130 | return EXIT_FAILURE; 131 | } 132 | 133 | result = hackrf_set_vga_gain(device, vga_gain); 134 | result |= hackrf_set_lna_gain(device, lna_gain); 135 | result |= hackrf_start_rx(device, rx_callback, NULL); 136 | 137 | fprintf(stderr,"Result is %d\n", result); 138 | 139 | printf("call hackrf_set_freq(%s Hz/%.03f MHz)\n", 140 | u64toa(freq_hz, &ascii_u64_data1),((double)freq_hz/(double)FREQ_ONE_MHZ) ); 141 | result = hackrf_set_freq(device, freq_hz); 142 | if( result != HACKRF_SUCCESS ) { 143 | printf("hackrf_set_freq() failed: %s (%d)\n", hackrf_error_name(result), result); 144 | return EXIT_FAILURE; 145 | } 146 | 147 | for(i = 0; i < 30; i++) { 148 | fprintf(stderr,"wait...\n"); 149 | sleep(1); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /hackrf_buffer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "hackrf_buffer.h" 9 | 10 | static pthread_mutex_t hackrfBufferLock; 11 | static int hackrfBufferHead; 12 | static int hackrfBufferTail; 13 | static int hackrfBufferEnd; 14 | #define HACKRF_BUFFER_SIZE (1024 * 1024 * 8) 15 | static uint8_t *hackrfBuffer; 16 | 17 | static int verbose = 0; 18 | 19 | void hackrf_buffer_init(int size, int _verbose) 20 | { 21 | if (size == 0) { 22 | size = 1024 * 1024 *8; 23 | } 24 | verbose = _verbose; 25 | 26 | hackrfBuffer = malloc(size); 27 | if ( !hackrfBuffer ) { 28 | fprintf(stderr,"Unable to allocate hackrfBuffer\n"); 29 | exit(1); 30 | } 31 | hackrfBufferHead = 0; 32 | hackrfBufferTail = 0; 33 | hackrfBufferEnd = size; 34 | 35 | pthread_mutex_init(&hackrfBufferLock, NULL); 36 | } 37 | 38 | int hackrf_rx_callback(hackrf_transfer* transfer) { 39 | int i; 40 | if ( verbose ) { 41 | fprintf(stderr,"rx_callback %d bytes\n", transfer -> valid_length); 42 | } 43 | 44 | int overflow = 0; 45 | pthread_mutex_lock(&hackrfBufferLock); 46 | /* 47 | * If there isn't enough room, move things to the front 48 | */ 49 | int inBuffer = hackrfBufferTail - hackrfBufferHead; 50 | int tailLeft = hackrfBufferEnd - hackrfBufferTail; 51 | if ( inBuffer + transfer -> valid_length > hackrfBufferEnd ) { 52 | /* reader not keeping up, signal overflow and reset buffer */ 53 | overflow = 1; 54 | hackrfBufferHead = 0; 55 | hackrfBufferTail = 0; 56 | } else if (tailLeft < transfer -> valid_length) { 57 | memcpy(&hackrfBuffer[0], &hackrfBuffer[hackrfBufferHead], inBuffer); 58 | hackrfBufferHead = 0; 59 | hackrfBufferTail = inBuffer; 60 | } 61 | 62 | memcpy(&hackrfBuffer[hackrfBufferTail], 63 | transfer -> buffer, transfer -> valid_length); 64 | hackrfBufferTail += transfer -> valid_length; 65 | 66 | pthread_mutex_unlock(&hackrfBufferLock); 67 | 68 | if ( overflow && verbose ) { 69 | fprintf(stderr,"hackrf_buffer overflow!\n"); 70 | } 71 | 72 | if ( verbose ) { 73 | fprintf(stderr,"Exit hackrf_rx_callback\n"); 74 | } 75 | 76 | return 0; /* always be happy */ 77 | } 78 | 79 | void 80 | hackrf_buffer_reset() 81 | { 82 | pthread_mutex_lock(&hackrfBufferLock); 83 | hackrfBufferHead = 0; 84 | hackrfBufferTail = 0; 85 | pthread_mutex_unlock(&hackrfBufferLock); 86 | } 87 | 88 | void hackrf_read_sync(uint8_t *buffer, int bufsize, int *bytesread) 89 | { 90 | 91 | while (hackrfBufferTail - hackrfBufferHead < bufsize) { 92 | if ( verbose ) { 93 | fprintf(stderr,"wait for buffered data..\n"); 94 | } 95 | usleep(1000); 96 | } 97 | 98 | pthread_mutex_lock(&hackrfBufferLock); 99 | /* 100 | * If there isn't enough room, move things to the front 101 | */ 102 | int inBuffer = hackrfBufferTail - hackrfBufferHead; 103 | int tailLeft = hackrfBufferEnd - hackrfBufferTail; 104 | int toMove = bufsize; 105 | 106 | if (inBuffer < bufsize) { 107 | toMove = inBuffer; 108 | } 109 | if (toMove > 0) { 110 | memcpy(buffer, &hackrfBuffer[hackrfBufferHead], toMove); 111 | hackrfBufferHead += toMove; 112 | } 113 | pthread_mutex_unlock(&hackrfBufferLock); 114 | 115 | *bytesread = toMove; 116 | } 117 | -------------------------------------------------------------------------------- /hackrf_buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef hackrf_buffer_h 2 | #define hackrf_buffer_h 3 | /* 4 | * Allocate buffer, use zero for default size 5 | */ 6 | 7 | void hackrf_buffer_init(int size, int verbose); 8 | int hackrf_rx_callback(hackrf_transfer* transfer); 9 | void hackrf_buffer_reset(); 10 | void hackrf_read_sync(uint8_t *buffer, int bufsize, int *bytesread); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /hackrf_power.c: -------------------------------------------------------------------------------- 1 | /* 2 | * rtl-sdr, turns your Realtek RTL2832 based DVB dongle into a SDR receiver 3 | * Copyright (C) 2012 by Steve Markgraf 4 | * Copyright (C) 2012 by Hoernchen 5 | * Copyright (C) 2012 by Kyle Keen 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program. If not, see . 19 | */ 20 | 21 | 22 | /* 23 | * rtl_power: general purpose FFT integrator 24 | * -f low_freq:high_freq:max_bin_size 25 | * -i seconds 26 | * outputs CSV 27 | * time, low, high, step, db, db, db ... 28 | * db optional? raw output might be better for noise correction 29 | * todo: 30 | * threading 31 | * randomized hopping 32 | * noise correction 33 | * continuous IIR 34 | * general astronomy usefulness 35 | * multiple dongles 36 | * multiple FFT workers 37 | * check edge cropping for off-by-one and rounding errors 38 | * 1.8MS/s for hiding xtal harmonics 39 | */ 40 | 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | 48 | #ifdef GPS_POWER 49 | # include 50 | static struct gps_data_t gpsdata; 51 | #endif 52 | 53 | #include 54 | #include 55 | 56 | #include 57 | #include 58 | #include 59 | 60 | #include "hackrf_buffer.h" 61 | #include "convenience.h" 62 | 63 | #define MAX(x, y) (((x) > (y)) ? (x) : (y)) 64 | 65 | #define DEFAULT_BUF_LENGTH (1 * 16384) 66 | #define BUFFER_DUMP (1<<12) 67 | 68 | #define AUTO_GAIN (-100) 69 | #define GAIN_LNA_DEFAULT (32) 70 | #define GAIN_VGA_DEFAULT (24) 71 | 72 | /* 73 | * Sample rates - these need to be adjusted for HackRF 74 | */ 75 | #define MAXIMUM_RATE 20000000 76 | #define MINIMUM_RATE 2000000 77 | #define DEFAULT_TARGET 2000000 78 | 79 | static int hackRFclosestTargetRate(int rate) 80 | { 81 | if ( rate < MINIMUM_RATE || rate < 9000000) { 82 | return MINIMUM_RATE; 83 | } else if (rate < 11500000 ) { 84 | return 10000000; 85 | } else if (rate < 15000000 ) { 86 | return 12500000; 87 | } else if (rate < 18000000) { 88 | return 16000000; 89 | } else { 90 | return 20000000; 91 | } 92 | } 93 | 94 | 95 | #define MAXIMUM_FFT 32 96 | 97 | static volatile int do_exit = 0; 98 | 99 | static hackrf_device* dev = NULL; 100 | 101 | FILE *file; 102 | 103 | struct sine_table 104 | { 105 | int16_t* Sinewave; 106 | int N_WAVE; 107 | int LOG2_N_WAVE; 108 | }; 109 | 110 | struct sine_table s_tables[MAXIMUM_FFT]; 111 | 112 | struct tuning_state 113 | /* one per tuning range */ 114 | { 115 | int freq; 116 | int rate; 117 | int lnaGain; 118 | int vgaGain; 119 | int bin_e; 120 | int16_t *fft_buf; 121 | int64_t *avg; /* length == 2^bin_e */ 122 | int samples; 123 | int downsample; 124 | int downsample_passes; /* for the recursive filter */ 125 | int comp_fir_size; 126 | int peak_hold; 127 | int linear; 128 | int bin_spec; 129 | double crop; 130 | int crop_i1, crop_i2; 131 | int freq_low, freq_high; 132 | //pthread_rwlock_t avg_lock; 133 | //pthread_mutex_t avg_mutex; 134 | /* having the iq buffer here is wasteful, but will avoid contention */ 135 | uint8_t *buf8; 136 | int buf_len; 137 | //int *comp_fir; 138 | //pthread_rwlock_t buf_lock; 139 | //pthread_mutex_t buf_mutex; 140 | int *window_coefs; 141 | struct sine_table *sine; /* points to an element of s_tables */ 142 | }; 143 | 144 | struct channel_solve 145 | /* details required to find optimal tuning */ 146 | { 147 | int upper, lower, bin_spec; 148 | int hops, bw_wanted, bw_needed; 149 | int bin_e, downsample, downsample_passes; 150 | double crop, crop_tmp; 151 | }; 152 | 153 | enum time_modes { VERBOSE_TIME, EPOCH_TIME }; 154 | 155 | struct misc_settings 156 | { 157 | int boxcar; 158 | int comp_fir_size; 159 | int peak_hold; 160 | int linear; 161 | int target_rate; 162 | double crop; 163 | int lnaGain; 164 | int vgaGain; 165 | int gpsWait; 166 | double (*window_fn)(int, int); 167 | int smoothing; 168 | enum time_modes time_mode; 169 | }; 170 | 171 | static int verbose = 0; 172 | 173 | /* 3000 is enough for 3GHz b/w worst case */ 174 | #define MAX_TUNES 4000 175 | struct tuning_state tunes[MAX_TUNES]; 176 | int tune_count = 0; 177 | 178 | void usage(void) 179 | { 180 | fprintf(stderr, 181 | "rtl_power, a simple FFT logger for RTL2832 based DVB-T receivers\n\n" 182 | "Use:\trtl_power -f freq_range [-options] [-f freq2 -opts2] [filename]\n" 183 | "\t-f lower:upper:bin_size [Hz]\n" 184 | "\t valid range for bin_size is 1Hz - 2.8MHz\n" 185 | "\t multiple frequency ranges are supported\n" 186 | "\t[-i integration_interval (default: 10 seconds)]\n" 187 | "\t buggy if a full sweep takes longer than the interval\n" 188 | "\t[-1 enables single-shot mode (default: off)]\n" 189 | "\t[-e exit_timer (default: off/0)]\n" 190 | //"\t[-s avg/iir smoothing (default: avg)]\n" 191 | //"\t[-t threads (default: 1)]\n" 192 | "\t[-d device_index (default: 0)]\n" 193 | "\t[-g vga tuner_gain (default: 30)]\n" 194 | "\t[-G lna_tuner_gain (default: 16)]\n" 195 | "\t[-p ppm_error (default: 0)]\n" 196 | "\tfilename (a '-' dumps samples to stdout)\n" 197 | "\t omitting the filename also uses stdout\n" 198 | "\n" 199 | "Experimental options:\n" 200 | "\t[-w window (default: rectangle)]\n" 201 | "\t hamming, blackman, blackman-harris, hann-poisson, bartlett, youssef\n" 202 | // kaiser 203 | "\t[-c crop_percent (default: 0%% suggested: 20%%)]\n" 204 | "\t discards data at the edges, 100%% discards everything\n" 205 | "\t has no effect for bins larger than 1MHz\n" 206 | "\t this value is a minimum crop size, more may be discarded\n" 207 | "\t[-F fir_size (default: disabled)]\n" 208 | "\t enables low-leakage downsample filter,\n" 209 | "\t fir_size can be 0 or 9. 0 has bad roll off,\n" 210 | "\t try -F 0 with '-c 50%%' to hide the roll off\n" 211 | "\t[-r max_sample_rate (default: 2.4M)]\n" 212 | "\t possible values are 2M to 3.2M\n" 213 | "\t[-E enables epoch timestamps (default: off/verbose)]\n" 214 | "\t[-P enables peak hold (default: off/averaging)]\n" 215 | "\t[-L enable linear output (default: off/dB)]\n" 216 | "\t[-D direct_sampling_mode, 0 (default/off), 1 (I), 2 (Q), 3 (no-mod)]\n" 217 | "\t[-O enable offset tuning (default: off)]\n" 218 | "\n" 219 | "CSV FFT output columns:\n" 220 | "\tdate, time, Hz low, Hz high, Hz step, samples, dbm, dbm, ...\n\n" 221 | "Examples:\n" 222 | "\trtl_power -f 88M:108M:125k fm_stations.csv\n" 223 | "\t creates 160 bins across the FM band,\n" 224 | "\t individual stations should be visible\n" 225 | "\trtl_power -f 100M:1G:1M -i 5m -1 survey.csv\n" 226 | "\t a five minute low res scan of nearly everything\n" 227 | "\trtl_power -f ... -i 15m -1 log.csv\n" 228 | "\t integrate for 15 minutes and exit afterwards\n" 229 | "\trtl_power -f ... -e 1h | gzip > log.csv.gz\n" 230 | "\t collect data for one hour and compress it on the fly\n\n" 231 | "\tIf you have issues writing +2GB logs on a 32bit platform\n" 232 | "\tuse redirection (rtl_power ... > filename.csv) instead\n\n" 233 | "Convert CSV to a waterfall graphic with:\n" 234 | " https://github.com/keenerd/rtl-sdr-misc/blob/master/heatmap/heatmap.py \n" 235 | "More examples at http://kmkeen.com/rtl-power/\n"); 236 | exit(1); 237 | } 238 | 239 | void multi_bail(void) 240 | { 241 | if (do_exit == 1) 242 | { 243 | fprintf(stderr, "Signal caught, finishing scan pass.\n"); 244 | } 245 | if (do_exit >= 2) 246 | { 247 | fprintf(stderr, "Signal caught, aborting immediately.\n"); 248 | } 249 | } 250 | 251 | static void sighandler(int signum) 252 | { 253 | do_exit++; 254 | multi_bail(); 255 | } 256 | 257 | /* more cond dumbness */ 258 | #define safe_cond_signal(n, m) pthread_mutex_lock(m); pthread_cond_signal(n); pthread_mutex_unlock(m) 259 | #define safe_cond_wait(n, m) pthread_mutex_lock(m); pthread_cond_wait(n, m); pthread_mutex_unlock(m) 260 | 261 | /* {length, coef, coef, coef} and scaled by 2^15 262 | for now, only length 9, optimal way to get +85% bandwidth */ 263 | #define CIC_TABLE_MAX 10 264 | int cic_9_tables[][10] = { 265 | {0,}, 266 | {9, -156, -97, 2798, -15489, 61019, -15489, 2798, -97, -156}, 267 | {9, -128, -568, 5593, -24125, 74126, -24125, 5593, -568, -128}, 268 | {9, -129, -639, 6187, -26281, 77511, -26281, 6187, -639, -129}, 269 | {9, -122, -612, 6082, -26353, 77818, -26353, 6082, -612, -122}, 270 | {9, -120, -602, 6015, -26269, 77757, -26269, 6015, -602, -120}, 271 | {9, -120, -582, 5951, -26128, 77542, -26128, 5951, -582, -120}, 272 | {9, -119, -580, 5931, -26094, 77505, -26094, 5931, -580, -119}, 273 | {9, -119, -578, 5921, -26077, 77484, -26077, 5921, -578, -119}, 274 | {9, -119, -577, 5917, -26067, 77473, -26067, 5917, -577, -119}, 275 | {9, -199, -362, 5303, -25505, 77489, -25505, 5303, -362, -199}, 276 | }; 277 | 278 | /* FFT based on fix_fft.c by Roberts, Slaney and Bouras 279 | http://www.jjj.de/fft/fftpage.html 280 | 16 bit ints for everything 281 | -32768..+32768 maps to -1.0..+1.0 282 | */ 283 | 284 | void sine_table(int size) 285 | { 286 | int i; 287 | double d; 288 | struct sine_table *sine; 289 | if (size > (MAXIMUM_FFT-1)) { 290 | fprintf(stderr, "Maximum FFT is 2^%i\n", MAXIMUM_FFT-1); 291 | exit(1); 292 | } 293 | sine = &s_tables[size]; 294 | if (sine->LOG2_N_WAVE == size) { 295 | return;} 296 | sine->LOG2_N_WAVE = size; 297 | sine->N_WAVE = 1 << sine->LOG2_N_WAVE; 298 | sine->Sinewave = malloc(sizeof(int16_t) * sine->N_WAVE*3/4); 299 | for (i=0; iN_WAVE*3/4; i++) 300 | { 301 | d = (double)i * 2.0 * M_PI / sine->N_WAVE; 302 | sine->Sinewave[i] = (int)round(32767*sin(d)); 303 | //printf("%i\n", sine->Sinewave[i]); 304 | } 305 | } 306 | 307 | void generate_sine_tables(void) 308 | { 309 | struct tuning_state *ts; 310 | int i; 311 | for (i=0; i < tune_count; i++) { 312 | ts = &tunes[i]; 313 | sine_table(ts->bin_e); 314 | ts->sine = &s_tables[ts->bin_e]; 315 | ts->fft_buf = malloc(ts->buf_len * sizeof(int16_t)); 316 | } 317 | } 318 | 319 | inline int16_t FIX_MPY(int16_t a, int16_t b) 320 | /* fixed point multiply and scale */ 321 | { 322 | int c = ((int)a * (int)b) >> 14; 323 | b = c & 0x01; 324 | return (c >> 1) + b; 325 | } 326 | 327 | int fix_fft(int16_t iq[], int m, struct sine_table *sine) 328 | /* interleaved iq[], 0 <= n < 2**m, changes in place */ 329 | { 330 | int mr, nn, i, j, l, k, istep, n, shift; 331 | int16_t qr, qi, tr, ti, wr, wi; 332 | n = 1 << m; 333 | if (n > sine->N_WAVE) 334 | {return -1;} 335 | mr = 0; 336 | nn = n - 1; 337 | /* decimation in time - re-order data */ 338 | for (m=1; m<=nn; ++m) { 339 | l = n; 340 | do 341 | {l >>= 1;} 342 | while (mr+l > nn); 343 | mr = (mr & (l-1)) + l; 344 | if (mr <= m) 345 | {continue;} 346 | // real = 2*m, imag = 2*m+1 347 | tr = iq[2*m]; 348 | iq[2*m] = iq[2*mr]; 349 | iq[2*mr] = tr; 350 | ti = iq[2*m+1]; 351 | iq[2*m+1] = iq[2*mr+1]; 352 | iq[2*mr+1] = ti; 353 | } 354 | l = 1; 355 | k = sine->LOG2_N_WAVE-1; 356 | while (l < n) { 357 | shift = 1; 358 | istep = l << 1; 359 | for (m=0; mSinewave[j+sine->N_WAVE/4]; 362 | wi = -sine->Sinewave[j]; 363 | if (shift) { 364 | wr >>= 1; wi >>= 1;} 365 | for (i=m; i>= 1; qi >>= 1;} 373 | iq[2*j] = qr - tr; 374 | iq[2*j+1] = qi - ti; 375 | iq[2*i] = qr + tr; 376 | iq[2*i+1] = qi + ti; 377 | } 378 | } 379 | --k; 380 | l = istep; 381 | } 382 | return 0; 383 | } 384 | 385 | double rectangle(int i, int length) 386 | { 387 | return 1.0; 388 | } 389 | 390 | double hamming(int i, int length) 391 | { 392 | double a, b, w, N1; 393 | a = 25.0/46.0; 394 | b = 21.0/46.0; 395 | N1 = (double)(length-1); 396 | w = a - b*cos(2*i*M_PI/N1); 397 | return w; 398 | } 399 | 400 | double blackman(int i, int length) 401 | { 402 | double a0, a1, a2, w, N1; 403 | a0 = 7938.0/18608.0; 404 | a1 = 9240.0/18608.0; 405 | a2 = 1430.0/18608.0; 406 | N1 = (double)(length-1); 407 | w = a0 - a1*cos(2*i*M_PI/N1) + a2*cos(4*i*M_PI/N1); 408 | return w; 409 | } 410 | 411 | double blackman_harris(int i, int length) 412 | { 413 | double a0, a1, a2, a3, w, N1; 414 | a0 = 0.35875; 415 | a1 = 0.48829; 416 | a2 = 0.14128; 417 | a3 = 0.01168; 418 | N1 = (double)(length-1); 419 | w = a0 - a1*cos(2*i*M_PI/N1) + a2*cos(4*i*M_PI/N1) - a3*cos(6*i*M_PI/N1); 420 | return w; 421 | } 422 | 423 | double hann_poisson(int i, int length) 424 | { 425 | double a, N1, w; 426 | a = 2.0; 427 | N1 = (double)(length-1); 428 | w = 0.5 * (1 - cos(2*M_PI*i/N1)) * \ 429 | pow(M_E, (-a*(double)abs((int)(N1-1-2*i)))/N1); 430 | return w; 431 | } 432 | 433 | double youssef(int i, int length) 434 | /* really a blackman-harris-poisson window, but that is a mouthful */ 435 | { 436 | double a, a0, a1, a2, a3, w, N1; 437 | a0 = 0.35875; 438 | a1 = 0.48829; 439 | a2 = 0.14128; 440 | a3 = 0.01168; 441 | N1 = (double)(length-1); 442 | w = a0 - a1*cos(2*i*M_PI/N1) + a2*cos(4*i*M_PI/N1) - a3*cos(6*i*M_PI/N1); 443 | a = 0.0025; 444 | w *= pow(M_E, (-a*(double)abs((int)(N1-1-2*i)))/N1); 445 | return w; 446 | } 447 | 448 | double kaiser(int i, int length) 449 | // todo, become more smart 450 | { 451 | return 1.0; 452 | } 453 | 454 | double bartlett(int i, int length) 455 | { 456 | double N1, L, w; 457 | L = (double)length; 458 | N1 = L - 1; 459 | w = (i - N1/2) / (L/2); 460 | if (w < 0) { 461 | w = -w;} 462 | w = 1 - w; 463 | return w; 464 | } 465 | 466 | void rms_power(struct tuning_state *ts) 467 | /* for bins between 1MHz and 2MHz */ 468 | { 469 | int i, s; 470 | uint8_t *buf = ts->buf8; 471 | int buf_len = ts->buf_len; 472 | int64_t p, t; 473 | double dc, err; 474 | 475 | p = t = 0L; 476 | for (i=0; ipeak_hold) { 487 | ts->avg[0] += p; 488 | } else { 489 | ts->avg[0] = MAX(ts->avg[0], p); 490 | } 491 | ts->samples += 1; 492 | } 493 | 494 | /* todo, add errors to parse_freq, solve_foo */ 495 | 496 | int parse_frequency(char *arg, struct channel_solve *c) 497 | { 498 | char *start, *stop, *step; 499 | /* hacky string parsing */ 500 | start = arg; 501 | stop = strchr(start, ':') + 1; 502 | stop[-1] = '\0'; 503 | step = strchr(stop, ':') + 1; 504 | step[-1] = '\0'; 505 | c->lower = (int)atofs(start); 506 | c->upper = (int)atofs(stop); 507 | c->bin_spec = (int)atofs(step); 508 | stop[-1] = ':'; 509 | step[-1] = ':'; 510 | return 0; 511 | } 512 | 513 | int solve_giant_bins(struct channel_solve *c) 514 | { 515 | c->bw_wanted = c->bin_spec; 516 | c->bw_needed = c->bin_spec; 517 | c->hops = (c->upper - c->lower) / c->bin_spec; 518 | c->bin_e = 0; 519 | c->crop_tmp = 0; 520 | return 0; 521 | } 522 | 523 | int solve_downsample(struct channel_solve *c, int target_rate, int boxcar) 524 | { 525 | int scan_size, bins_wanted, bins_needed, ds_next, bw; 526 | 527 | scan_size = c->upper - c->lower; 528 | c->hops = 1; 529 | c->bw_wanted = scan_size; 530 | 531 | bins_wanted = (int)ceil((double)scan_size / (double)c->bin_spec); 532 | c->bin_e = (int)ceil(log2(bins_wanted)); 533 | while (1) { 534 | bins_needed = 1 << c->bin_e; 535 | c->crop_tmp = (double)(bins_needed - bins_wanted) / (double)bins_needed; 536 | if (c->crop_tmp >= c->crop) { 537 | break;} 538 | c->bin_e++; 539 | } 540 | 541 | c->downsample = 1; 542 | c->downsample_passes = 0; 543 | while (1) { 544 | bw = (int)((double)scan_size / (1.0 - c->crop_tmp)); 545 | c->bw_needed = bw * c->downsample; 546 | 547 | if (boxcar) { 548 | ds_next = c->downsample + 1; 549 | } else { 550 | ds_next = c->downsample * 2; 551 | } 552 | if ((bw * ds_next) > target_rate) { 553 | break;} 554 | 555 | c->downsample = ds_next; 556 | if (!boxcar) { 557 | c->downsample_passes++;} 558 | } 559 | 560 | return 0; 561 | } 562 | 563 | int solve_single(struct channel_solve *c, int target_rate) 564 | { 565 | int scan_size, bins_all, bins_crop, bin_e, bins_2, bw_needed; 566 | scan_size = c->upper - c->lower; 567 | bins_all = scan_size / c->bin_spec; 568 | bins_crop = (int)ceil((double)bins_all * (1.0 + c->crop)); 569 | bin_e = (int)ceil(log2(bins_crop)); 570 | bins_2 = 1 << bin_e; 571 | bw_needed = bins_2 * c->bin_spec; 572 | 573 | if (bw_needed > target_rate) { 574 | /* actually multi-hop */ 575 | return 1;} 576 | 577 | c->bw_wanted = scan_size; 578 | c->bw_needed = bw_needed; 579 | c->hops = 1; 580 | c->bin_e = bin_e; 581 | /* crop will always be bigger than specified crop */ 582 | c->crop_tmp = (double)(bins_2 - bins_all) / (double)bins_2; 583 | return 0; 584 | } 585 | 586 | int solve_hopping(struct channel_solve *c, int target_rate) 587 | { 588 | int i, scan_size, bins_all, bins_sub, bins_crop, bins_2, min_hops; 589 | scan_size = c->upper - c->lower; 590 | min_hops = scan_size / MAXIMUM_RATE - 1; 591 | if (min_hops < 1) { 592 | min_hops = 1;} 593 | /* evenly sized ranges, as close to target_rate as possible */ 594 | for (i=min_hops; ibw_wanted = scan_size / i; 596 | bins_all = scan_size / c->bin_spec; 597 | bins_sub = (int)ceil((double)bins_all / (double)i); 598 | bins_crop = (int)ceil((double)bins_sub * (1.0 + c->crop)); 599 | c->bin_e = (int)ceil(log2(bins_crop)); 600 | bins_2 = 1 << c->bin_e; 601 | c->bw_needed = bins_2 * c->bin_spec; 602 | c->crop_tmp = (double)(bins_2 - bins_sub) / (double)bins_2; 603 | if (c->bw_needed > target_rate) { 604 | continue;} 605 | if (c->crop_tmp < c->crop) { 606 | continue;} 607 | c->hops = i; 608 | break; 609 | } 610 | return 0; 611 | } 612 | 613 | void frequency_range(char *arg, struct misc_settings *ms) 614 | /* flesh out the tunes[] for scanning */ 615 | { 616 | struct channel_solve c; 617 | struct tuning_state *ts; 618 | int r, i, j, buf_len, length, hop_bins, logged_bins, planned_bins; 619 | int lower_edge, actual_bw, upper_perfect, remainder; 620 | 621 | fprintf(stderr, "Range: %s\n", arg); 622 | parse_frequency(arg, &c); 623 | c.downsample = 1; 624 | c.downsample_passes = 0; 625 | c.crop = ms->crop; 626 | 627 | if (ms->target_rate < 2 * MINIMUM_RATE) { 628 | ms->target_rate = 2 * MINIMUM_RATE; 629 | } 630 | if (ms->target_rate > MAXIMUM_RATE) { 631 | ms->target_rate = MAXIMUM_RATE; 632 | } 633 | if ((ms->crop < 0.0) || (ms->crop > 1.0)) { 634 | fprintf(stderr, "Crop value outside of 0 to 1.\n"); 635 | exit(1); 636 | } 637 | 638 | r = -1; 639 | if (c.bin_spec >= MINIMUM_RATE) { 640 | fprintf(stderr, "Mode: rms power\n"); 641 | solve_giant_bins(&c); 642 | } else if ((c.upper - c.lower) < MINIMUM_RATE) { 643 | fprintf(stderr, "Mode: downsampling\n"); 644 | solve_downsample(&c, ms->target_rate, ms->boxcar); 645 | } else if ((c.upper - c.lower) < MAXIMUM_RATE) { 646 | r = solve_single(&c, ms->target_rate); 647 | } else { 648 | fprintf(stderr, "Mode: hopping\n"); 649 | solve_hopping(&c, ms->target_rate); 650 | } 651 | 652 | if (r == 0) { 653 | fprintf(stderr, "Mode: single\n"); 654 | } else if (r == 1) { 655 | fprintf(stderr, "Mode: hopping\n"); 656 | solve_hopping(&c, ms->target_rate); 657 | } 658 | c.crop = c.crop_tmp; 659 | 660 | if ((tune_count+c.hops) > MAX_TUNES) { 661 | fprintf(stderr, "Error: bandwidth too wide.\n"); 662 | exit(1); 663 | } 664 | buf_len = 2 * (1<rate = c.bw_needed; 676 | ts->lnaGain = ms->lnaGain; 677 | ts->vgaGain = ms->vgaGain; 678 | ts->bin_e = c.bin_e; 679 | ts->samples = 0; 680 | ts->bin_spec = c.bin_spec; 681 | ts->crop = c.crop; 682 | ts->downsample = c.downsample; 683 | ts->downsample_passes = c.downsample_passes; 684 | ts->comp_fir_size = ms->comp_fir_size; 685 | ts->peak_hold = ms->peak_hold; 686 | ts->linear = ms->linear; 687 | ts->avg = (int64_t*)malloc((1<avg) { 689 | fprintf(stderr, "Error: malloc.\n"); 690 | exit(1); 691 | } 692 | for (j=0; j<(1<avg[j] = 0L; 694 | } 695 | ts->buf8 = (uint8_t*)malloc(buf_len * sizeof(uint8_t)); 696 | if (!ts->buf8) { 697 | fprintf(stderr, "Error: malloc.\n"); 698 | exit(1); 699 | } 700 | ts->buf_len = buf_len; 701 | length = 1 << c.bin_e; 702 | ts->window_coefs = malloc(length * sizeof(int)); 703 | for (j=0; jwindow_coefs[j] = (int)(256*ms->window_fn(j, length)); 705 | } 706 | /* calculate unique values */ 707 | ts->freq_low = lower_edge; 708 | hop_bins = c.bw_wanted / c.bin_spec; 709 | actual_bw = hop_bins * c.bin_spec; 710 | ts->freq_high = lower_edge + actual_bw; 711 | upper_perfect = c.lower + (i+1) * c.bw_wanted; 712 | if (ts->freq_high + c.bin_spec <= upper_perfect) { 713 | hop_bins += 1; 714 | actual_bw = hop_bins * c.bin_spec; 715 | ts->freq_high = lower_edge + actual_bw; 716 | } 717 | remainder = planned_bins - logged_bins - hop_bins; 718 | if (i == c.hops-1 && remainder > 0) { 719 | hop_bins += remainder; 720 | actual_bw = hop_bins * c.bin_spec; 721 | ts->freq_high = lower_edge + actual_bw; 722 | } 723 | logged_bins += hop_bins; 724 | ts->crop_i1 = (length - hop_bins) / 2; 725 | ts->crop_i2 = ts->crop_i1 + hop_bins - 1; 726 | ts->freq = (lower_edge - ts->crop_i1 * c.bin_spec) + c.bw_needed/(2*c.downsample); 727 | /* prep for next hop */ 728 | lower_edge = ts->freq_high; 729 | } 730 | tune_count += c.hops; 731 | /* report */ 732 | fprintf(stderr, "Number of frequency hops: %i\n", c.hops); 733 | fprintf(stderr, "Dongle bandwidth: %iHz\n", c.bw_needed); 734 | fprintf(stderr, "Downsampling by: %ix\n", c.downsample); 735 | fprintf(stderr, "Cropping by: %0.2f%%\n", c.crop*100); 736 | fprintf(stderr, "Total FFT bins: %i\n", c.hops * (1<> 4; 790 | data[2] = ((b+c)*10 + (a+d)*5 + e + f) >> 4; 791 | data[4] = (a + (b+e)*5 + (c+d)*10 + f) >> 4; 792 | for (i=12; i> 4; 800 | } 801 | } 802 | 803 | void remove_dc(int16_t *data, int length) 804 | /* works on interleaved data */ 805 | { 806 | int i; 807 | int16_t ave; 808 | int64_t sum = 0L; 809 | for (i=0; i < length; i+=2) { 810 | sum += data[i]; 811 | } 812 | ave = (int16_t)(sum / (int64_t)(length)); 813 | if (ave == 0) { 814 | return;} 815 | for (i=0; i < length; i+=2) { 816 | data[i] -= ave; 817 | } 818 | } 819 | 820 | void generic_fir(int16_t *data, int length, int *fir) 821 | /* Okay, not at all generic. Assumes length 9, fix that eventually. */ 822 | { 823 | int d, temp, sum; 824 | int hist[9] = {0,}; 825 | /* cheat on the beginning, let it go unfiltered */ 826 | for (d=0; d<18; d+=2) { 827 | hist[d/2] = data[d]; 828 | } 829 | for (d=18; d> 15) ; 838 | hist[0] = hist[1]; 839 | hist[1] = hist[2]; 840 | hist[2] = hist[3]; 841 | hist[3] = hist[4]; 842 | hist[4] = hist[5]; 843 | hist[5] = hist[6]; 844 | hist[6] = hist[7]; 845 | hist[7] = hist[8]; 846 | hist[8] = temp; 847 | } 848 | } 849 | 850 | void downsample_iq(int16_t *data, int length) 851 | { 852 | fifth_order(data, length); 853 | //remove_dc(data, length); 854 | fifth_order(data+1, length-1); 855 | //remove_dc(data+1, length-1); 856 | } 857 | 858 | int64_t real_conj(int16_t real, int16_t imag) 859 | /* real(n * conj(n)) */ 860 | { 861 | return ((int64_t)real*(int64_t)real + (int64_t)imag*(int64_t)imag); 862 | } 863 | 864 | void scanner(void) 865 | { 866 | int i, j, j2, n_read, offset, bin_e, bin_len, buf_len, ds, ds_p; 867 | int32_t w; 868 | struct tuning_state *ts; 869 | int16_t *fft_buf; 870 | bin_e = tunes[0].bin_e; 871 | bin_len = 1 << bin_e; 872 | buf_len = tunes[0].buf_len; 873 | for (i=0; i= 2) 875 | {return;} 876 | ts = &tunes[i]; 877 | fft_buf = ts->fft_buf; 878 | regain(dev, ts->lnaGain, ts->vgaGain); 879 | rerate(dev, ts->rate); 880 | retune(dev, ts->freq); 881 | 882 | hackrf_read_sync(ts->buf8, buf_len, &n_read); 883 | 884 | if ( verbose ) { 885 | int i; 886 | fprintf(stderr,"Samples: "); 887 | for(i = 0; i < 20; i++) { 888 | fprintf(stderr,"%d ", ts->buf8[i]); 889 | } 890 | fprintf(stderr,"\n"); 891 | } 892 | 893 | if (n_read != buf_len) { 894 | fprintf(stderr, "Error: dropped samples.\n");} 895 | /* rms */ 896 | if (bin_len == 1) { 897 | rms_power(ts); 898 | continue; 899 | } 900 | /* prep for fft */ 901 | for (j=0; jbuf8[j] - 127; 903 | } 904 | ds = ts->downsample; 905 | ds_p = ts->downsample_passes; 906 | if (ds_p) { /* recursive */ 907 | for (j=0; j < ds_p; j++) { 908 | downsample_iq(fft_buf, buf_len >> j); 909 | } 910 | /* droop compensation */ 911 | if (ts->comp_fir_size == 9 && ds_p <= CIC_TABLE_MAX) { 912 | generic_fir(fft_buf, buf_len >> j, cic_9_tables[ds_p]); 913 | generic_fir(fft_buf+1, (buf_len >> j)-1, cic_9_tables[ds_p]); 914 | } 915 | } else if (ds > 1) { /* boxcar */ 916 | j=2, j2=0; 917 | while (j < buf_len) { 918 | fft_buf[j2] += fft_buf[j]; 919 | fft_buf[j2+1] += fft_buf[j+1]; 920 | fft_buf[j] = 0; 921 | fft_buf[j+1] = 0; 922 | j += 2; 923 | if (j % (ds*2) == 0) { 924 | j2 += 2;} 925 | } 926 | } 927 | remove_dc(fft_buf, buf_len / ds); 928 | remove_dc(fft_buf+1, (buf_len / ds) - 1); 929 | /* window function and fft */ 930 | for (offset=0; offset<(buf_len/ds); offset+=(2*bin_len)) { 931 | // todo, let rect skip this 932 | for (j=0; jwindow_coefs[j]); 935 | //w /= (int32_t)(ds); 936 | fft_buf[offset+j*2] = (int16_t)w; 937 | w = (int32_t)fft_buf[offset+j*2+1]; 938 | w *= (int32_t)(ts->window_coefs[j]); 939 | //w /= (int32_t)(ds); 940 | fft_buf[offset+j*2+1] = (int16_t)w; 941 | } 942 | fix_fft(fft_buf+offset, bin_e, ts->sine); 943 | if (!ts->peak_hold) { 944 | for (j=0; javg[j] += real_conj(fft_buf[offset+j*2], fft_buf[offset+j*2+1]); 946 | } 947 | } else { 948 | for (j=0; javg[j] = MAX(real_conj(fft_buf[offset+j*2], fft_buf[offset+j*2+1]), ts->avg[j]); 950 | } 951 | } 952 | ts->samples += ds; 953 | } 954 | } 955 | } 956 | 957 | void csv_dbm(struct tuning_state *ts) 958 | { 959 | int i, len; 960 | int64_t tmp; 961 | double dbm; 962 | char *sep = ", "; 963 | len = 1 << ts->bin_e; 964 | /* fix FFT stuff quirks */ 965 | if (ts->bin_e > 0) { 966 | /* nuke DC component (not effective for all windows) */ 967 | ts->avg[0] = ts->avg[1]; 968 | /* FFT is translated by 180 degrees */ 969 | for (i=0; iavg[i]; 971 | ts->avg[i] = ts->avg[i+len/2]; 972 | ts->avg[i+len/2] = tmp; 973 | } 974 | } 975 | /* Hz low, Hz high, Hz step, samples, dbm, dbm, ... */ 976 | fprintf(file, "%i, %i, %i, %i, ", ts->freq_low, ts->freq_high, 977 | ts->bin_spec, ts->samples); 978 | // something seems off with the dbm math 979 | for (i=ts->crop_i1; i<=ts->crop_i2; i++) { 980 | if (i == ts->crop_i2) { 981 | sep = "\n"; 982 | } 983 | dbm = (double)ts->avg[i]; 984 | dbm /= (double)ts->rate; 985 | if (!ts->peak_hold) { 986 | dbm /= (double)ts->samples; 987 | } 988 | if (ts->linear) { 989 | fprintf(file, "%.5g%s", dbm, sep); 990 | } else { 991 | dbm = 10 * log10(dbm); 992 | fprintf(file, "%.2f%s", dbm, sep); 993 | } 994 | } 995 | for (i=0; iavg[i] = 0L; 997 | } 998 | ts->samples = 0; 999 | } 1000 | 1001 | void init_misc(struct misc_settings *ms) 1002 | { 1003 | ms->target_rate = DEFAULT_TARGET; 1004 | ms->boxcar = 1; 1005 | ms->comp_fir_size = 0; 1006 | ms->crop = 0.0; 1007 | ms->lnaGain = GAIN_LNA_DEFAULT; 1008 | ms->vgaGain = GAIN_VGA_DEFAULT; 1009 | ms->gpsWait = 5; 1010 | ms->window_fn = rectangle; 1011 | ms->smoothing = 0; 1012 | ms->peak_hold = 0; 1013 | ms->linear = 0; 1014 | ms->time_mode = VERBOSE_TIME; 1015 | } 1016 | 1017 | #ifdef GPS_POWER 1018 | static void gps_quit_handler(int signum) 1019 | { 1020 | /* don't clutter the logs on Ctrl-C */ 1021 | fprintf(stderr,"ABORT: Exiting GPS monitor...\n"); 1022 | gps_stream(&gpsdata, WATCH_DISABLE, NULL); 1023 | gps_close(&gpsdata); 1024 | exit(0); 1025 | } 1026 | 1027 | void *gps_thread(void *arg) 1028 | { 1029 | 1030 | struct gps_data_t *gpsptr = (struct gps_data_t *)arg; 1031 | 1032 | /* initializes the some gpsptr data structure */ 1033 | gpsptr -> status = STATUS_NO_FIX; 1034 | gpsptr -> satellites_used = 0; 1035 | gps_clear_fix(&(gpsptr -> fix)); 1036 | gps_clear_dop(&(gpsptr -> dop)); 1037 | 1038 | /* catch all interesting signals */ 1039 | (void)signal(SIGTERM, gps_quit_handler); 1040 | (void)signal(SIGQUIT, gps_quit_handler); 1041 | (void)signal(SIGINT, gps_quit_handler); 1042 | 1043 | while (gps_open("localhost", "2947", gpsptr) != 0) { 1044 | (void)fprintf(stderr, 1045 | "no gpsd running or network error: %d, %s -- try again in 10s\n", 1046 | errno, gps_errstr(errno)); 1047 | sleep(10); 1048 | } 1049 | 1050 | gps_stream(gpsptr, WATCH_ENABLE | WATCH_JSON, NULL); 1051 | 1052 | for (;;) { 1053 | if (!gps_waiting(gpsptr, 5000000)) { 1054 | (void)fprintf(stderr, "Timed out waiting for GPS\n"); 1055 | } else { 1056 | if ( gps_read(gpsptr) == -1 ) { 1057 | fprintf(stderr,"Error reading GPS data\n"); 1058 | } 1059 | fprintf(stderr,"Set lat / lon / el to %f, %f, %f\n", 1060 | gpsdata.fix.latitude, 1061 | gpsdata.fix.longitude, 1062 | gpsdata.fix.altitude); 1063 | } 1064 | } 1065 | 1066 | fprintf(stderr,"Exiting GPS monitor...\n"); 1067 | 1068 | gps_stream(gpsptr, WATCH_DISABLE, NULL); 1069 | (void)gps_close(gpsptr); 1070 | return 0; 1071 | } 1072 | 1073 | #endif 1074 | 1075 | 1076 | int main(int argc, char **argv) 1077 | { 1078 | #ifndef _WIN32 1079 | struct sigaction sigact; 1080 | #endif 1081 | char *filename = NULL; 1082 | int i, r, opt; 1083 | int f_set = 0; 1084 | int dev_index = 0; 1085 | char dev_label[255]; 1086 | int dev_given = 0; 1087 | int ppm_error = 0; 1088 | int custom_ppm = 0; 1089 | int interval = 10; 1090 | int fft_threads = 1; 1091 | int single = 0; 1092 | int direct_sampling = 0; 1093 | int offset_tuning = 0; 1094 | char *freq_optarg; 1095 | time_t next_tick; 1096 | time_t time_now; 1097 | time_t exit_time = 0; 1098 | char t_str[50]; 1099 | struct tm *cal_time; 1100 | struct misc_settings ms; 1101 | freq_optarg = ""; 1102 | init_misc(&ms); 1103 | strcpy(dev_label, "DEFAULT"); 1104 | 1105 | while ((opt = getopt(argc, argv, "f:i:s:r:t:d:g:p:e:w:T:c:F:1EPLD:Oh")) != -1) { 1106 | switch (opt) { 1107 | case 'f': // lower:upper:bin_size 1108 | if (f_set) { 1109 | frequency_range(freq_optarg, &ms);} 1110 | freq_optarg = strdup(optarg); 1111 | f_set = 1; 1112 | break; 1113 | case 'd': 1114 | // dev_index = verbose_device_search(optarg); 1115 | // strncpy(dev_label, optarg, 255); 1116 | fprintf(stderr,"To be implemented..\n"); exit(1); 1117 | dev_given = 1; 1118 | break; 1119 | case 'g': 1120 | ms.vgaGain = (int)(atof(optarg)); 1121 | break; 1122 | case 'G': 1123 | ms.lnaGain = (int)(atof(optarg)); 1124 | break; 1125 | case 'c': 1126 | ms.crop = atofp(optarg); 1127 | break; 1128 | case 'i': 1129 | interval = (int)round(atoft(optarg)); 1130 | break; 1131 | case 'e': 1132 | exit_time = (time_t)((int)round(atoft(optarg))); 1133 | break; 1134 | case 's': 1135 | if (strcmp("avg", optarg) == 0) { 1136 | ms.smoothing = 0;} 1137 | if (strcmp("iir", optarg) == 0) { 1138 | ms.smoothing = 1;} 1139 | break; 1140 | case 'w': 1141 | if (strcmp("rectangle", optarg) == 0) { 1142 | ms.window_fn = rectangle;} 1143 | if (strcmp("hamming", optarg) == 0) { 1144 | ms.window_fn = hamming;} 1145 | if (strcmp("blackman", optarg) == 0) { 1146 | ms.window_fn = blackman;} 1147 | if (strcmp("blackman-harris", optarg) == 0) { 1148 | ms.window_fn = blackman_harris;} 1149 | if (strcmp("hann-poisson", optarg) == 0) { 1150 | ms.window_fn = hann_poisson;} 1151 | if (strcmp("youssef", optarg) == 0) { 1152 | ms.window_fn = youssef;} 1153 | if (strcmp("kaiser", optarg) == 0) { 1154 | ms.window_fn = kaiser;} 1155 | if (strcmp("bartlett", optarg) == 0) { 1156 | ms.window_fn = bartlett;} 1157 | break; 1158 | case 't': 1159 | fft_threads = atoi(optarg); 1160 | break; 1161 | case 'p': 1162 | ppm_error = atoi(optarg); 1163 | custom_ppm = 1; 1164 | break; 1165 | case 'r': 1166 | ms.target_rate = (int)atofs(optarg); 1167 | break; 1168 | case '1': 1169 | single = 1; 1170 | break; 1171 | case 'E': 1172 | ms.time_mode = EPOCH_TIME; 1173 | break; 1174 | case 'P': 1175 | ms.peak_hold = 1; 1176 | break; 1177 | case 'L': 1178 | ms.linear = 1; 1179 | break; 1180 | case 'D': 1181 | direct_sampling = atoi(optarg); 1182 | break; 1183 | case 'O': 1184 | offset_tuning = 1; 1185 | break; 1186 | case 'F': 1187 | ms.boxcar = 0; 1188 | ms.comp_fir_size = atoi(optarg); 1189 | break; 1190 | case 'T': 1191 | ms.gpsWait = atoi(optarg); 1192 | break; 1193 | case 'h': 1194 | default: 1195 | usage(); 1196 | break; 1197 | } 1198 | } 1199 | 1200 | if (!f_set) { 1201 | fprintf(stderr, "No frequency range provided.\n"); 1202 | exit(1); 1203 | } 1204 | 1205 | frequency_range(freq_optarg, &ms); 1206 | 1207 | if (tune_count == 0) { 1208 | usage();} 1209 | 1210 | if (argc <= optind) { 1211 | filename = "-"; 1212 | } else { 1213 | filename = argv[optind]; 1214 | } 1215 | 1216 | if (interval < 1) { 1217 | interval = 1;} 1218 | 1219 | fprintf(stderr, "Reporting every %i seconds\n", interval); 1220 | 1221 | //if (!dev_given) { 1222 | // dev_index = verbose_device_search("0"); 1223 | // } 1224 | 1225 | // if (dev_index < 0) { 1226 | // exit(1); 1227 | // } 1228 | 1229 | hackrf_buffer_init( 0, 0 ); 1230 | r = hackrf_init(); 1231 | if (r < 0) { 1232 | fprintf(stderr, "Failed to init hackrf device.\n"); 1233 | exit(1); 1234 | } 1235 | 1236 | r = hackrf_open(&dev); 1237 | if (r < 0) { 1238 | fprintf(stderr, "Failed to open hackrf device #%d.\n", dev_index); 1239 | exit(1); 1240 | } 1241 | 1242 | #ifndef _WIN32 1243 | sigact.sa_handler = sighandler; 1244 | sigemptyset(&sigact.sa_mask); 1245 | sigact.sa_flags = 0; 1246 | sigaction(SIGINT, &sigact, NULL); 1247 | sigaction(SIGTERM, &sigact, NULL); 1248 | sigaction(SIGQUIT, &sigact, NULL); 1249 | sigaction(SIGPIPE, &sigact, NULL); 1250 | signal(SIGPIPE, SIG_IGN); 1251 | #else 1252 | SetConsoleCtrlHandler( (PHANDLER_ROUTINE) sighandler, TRUE ); 1253 | #endif 1254 | 1255 | #ifdef GPS_POWER 1256 | { 1257 | pthread_t pthread_gps; 1258 | int gpsWait = 0; 1259 | /* initializes the some gpsdata data structure */ 1260 | 1261 | fprintf(stderr,"Start GPS..\n"); 1262 | 1263 | gpsdata.status = STATUS_NO_FIX; 1264 | gpsdata.satellites_used = 0; 1265 | gps_clear_fix(&(gpsdata.fix)); 1266 | gps_clear_dop(&(gpsdata.dop)); 1267 | 1268 | pthread_create(&pthread_gps, NULL, gps_thread, &gpsdata); 1269 | while(! gpsdata.status && gpsWait < ms.gpsWait) { 1270 | fprintf(stderr,"Waiting for gps...\n"); 1271 | sleep(1); 1272 | gpsWait++; 1273 | } 1274 | fprintf(stderr,"Acquired gps..\n"); 1275 | } 1276 | #endif 1277 | 1278 | regain(dev, ms.lnaGain, ms.vgaGain); 1279 | 1280 | if (strcmp(filename, "-") == 0) { /* Write log to stdout */ 1281 | file = stdout; 1282 | #ifdef _WIN32 1283 | // Is this necessary? Output is ascii. 1284 | _setmode(_fileno(file), _O_BINARY); 1285 | #endif 1286 | } else { 1287 | file = fopen(filename, "wb"); 1288 | if (!file) { 1289 | fprintf(stderr, "Failed to open %s\n", filename); 1290 | exit(1); 1291 | } 1292 | } 1293 | 1294 | generate_sine_tables(); 1295 | 1296 | /* Reset endpoint before we start reading from it (mandatory) */ 1297 | hackrf_buffer_reset(); 1298 | 1299 | /* actually do stuff */ 1300 | rerate(dev, tunes[0].rate); 1301 | 1302 | /* 1303 | * Enable hackrf and transfers.. 1304 | */ 1305 | hackrf_start_rx(dev, hackrf_rx_callback, NULL); 1306 | // if ( ! hackrf_start_rx(dev, hackrf_rx_callback, NULL) ) { 1307 | // fprintf(stderr,"Can't start callback\n"); 1308 | // exit(1); 1309 | // } 1310 | 1311 | 1312 | next_tick = time(NULL) + interval; 1313 | if (exit_time) { 1314 | exit_time = time(NULL) + exit_time;} 1315 | while (!do_exit) { 1316 | scanner(); 1317 | time_now = time(NULL); 1318 | if (time_now < next_tick) { 1319 | continue;} 1320 | // time, Hz low, Hz high, Hz step, samples, dbm, dbm, ... 1321 | cal_time = localtime(&time_now); 1322 | if (ms.time_mode == VERBOSE_TIME) { 1323 | strftime(t_str, 50, "%Y-%m-%d, %H:%M:%S", cal_time); 1324 | } 1325 | if (ms.time_mode == EPOCH_TIME) { 1326 | snprintf(t_str, 50, "%u, %s", (unsigned)time_now, dev_label); 1327 | } 1328 | for (i=0; i= next_tick) { 1340 | next_tick += interval;} 1341 | if (single) { 1342 | do_exit = 1;} 1343 | if (exit_time && time(NULL) >= exit_time) { 1344 | do_exit = 1;} 1345 | } 1346 | 1347 | /* clean up */ 1348 | 1349 | if (do_exit) { 1350 | fprintf(stderr, "\nUser cancel, exiting...\n");} 1351 | else { 1352 | fprintf(stderr, "\nLibrary error %d, exiting...\n", r);} 1353 | 1354 | if (file != stdout) { 1355 | fclose(file);} 1356 | 1357 | hackrf_close(dev); 1358 | hackrf_exit(); 1359 | //free(fft_buf); 1360 | //for (i=0; i= 0 ? r : -r; 1365 | } 1366 | 1367 | // vim: tabstop=8:softtabstop=8:shiftwidth=8:noexpandtab 1368 | --------------------------------------------------------------------------------