From 29a39b2d5ef5cb45a0208b02ffd6b6217c4fd0b8 Mon Sep 17 00:00:00 2001 From: rpp Date: Fri, 11 Sep 2015 16:33:58 +0200 Subject: [PATCH] Created standalone GSM channelizer app for splitting wideband captures into multiple per-channel files --- apps/gsm_channelize.py | 153 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100755 apps/gsm_channelize.py diff --git a/apps/gsm_channelize.py b/apps/gsm_channelize.py new file mode 100755 index 0000000..c0190a5 --- /dev/null +++ b/apps/gsm_channelize.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- +# @file +# @author Pieter Robyns +# @section LICENSE +# +# Gr-gsm 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. +# +# Gr-gsm 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 gr-gsm; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# +# + +################################################## +# gr-gsm channelizer +# +# Standalone application to channelize a wideband +# GSM capture file into multiple seperate capture +# files for the specified ARFCNs. +################################################## + +from gnuradio import blocks +from gnuradio import eng_notation +from gnuradio import filter +from gnuradio import gr +from gnuradio.eng_option import eng_option +from gnuradio.filter import firdes +from argparse import ArgumentParser, ArgumentTypeError, RawDescriptionHelpFormatter +import grgsm.arfcn as arfcn +import os + +EXTRA_HELP = """ +Example usage: +gsm_channelize.py -f my_wideband_capture.cfile -c 925.2e6 990 991 992 993 994 995 1019 1020 1021 1022 1023 + +The above example will channelize my_wideband_capture.cfile, in this case a cfile captured at +925.2 MHz centered (ARFCN 975) and 20 Msps. As a result, 12 files will be generated for +ARFCNs 975 - 1023 at 1 Msps each. +""" + +def eng_float(value): + try: + return eng_notation.str_to_num(value) + except: + raise ArgumentTypeError("invalid engineering notation value: {0}".format(value)) + +def gsm_band(value): + choices = arfcn.get_bands() + if value in choices: + return value + else: + raise ArgumentTypeError("invalid GSM band: {0}. Possible choices are: {1}".format(value, choices)) + +class gsm_channelize(gr.top_block): + def __init__(self, channels, decim, ch_width, ch_twidth, fc, band, samp_rate, path): + gr.top_block.__init__(self, "gsm_channelize") + + ################################################## + # Parameters + ################################################## + self.channels = channels + self.decim = decim + self.ch_width = ch_width + self.ch_twidth = ch_twidth + self.fc = fc + self.band = band + self.samp_rate = samp_rate + self.blocks_fir_filters = {} + self.blocks_file_sinks = {} + + ################################################## + # Blocks and connections + ################################################## + self.blocks_file_source = blocks.file_source(gr.sizeof_gr_complex, path, False) + self.blocks_throttle = blocks.throttle(gr.sizeof_gr_complex, samp_rate,True) + + self.connect((self.blocks_file_source, 0), (self.blocks_throttle, 0)) + + c0_arfcn = arfcn.downlink2arfcn(fc, band) + self.channels.insert(0, c0_arfcn) + print("Extracting channels %s, given that the center frequency is at ARFCN %d (%s)" % (str(channels), c0_arfcn, eng_notation.num_to_str(fc))) + + for channel in channels: + channel_freq = arfcn.arfcn2downlink(channel, band) + if channel_freq is None: + print("Warning: invalid ARFCN %d for band %s" % (channel, band)) + continue + freq_diff = channel_freq - fc + print("ARFCN %d is at C0 %+d KHz" % (channel, int(freq_diff / 1000.0))) + + self.blocks_fir_filters[channel] = filter.freq_xlating_fir_filter_ccc(decim, (firdes.low_pass(1, samp_rate, ch_width, ch_twidth)), freq_diff, samp_rate) + self.connect((self.blocks_throttle, 0), (self.blocks_fir_filters[channel], 0)) + + self.blocks_file_sinks[channel] = blocks.file_sink(gr.sizeof_gr_complex, "./gsm_channelizer/out_" + str(channel) + ".cfile", False) + self.blocks_file_sinks[channel].set_unbuffered(False) + self.connect((self.blocks_fir_filters[channel], 0), (self.blocks_file_sinks[channel], 0)) + + +if __name__ == '__main__': + parser = ArgumentParser(formatter_class=RawDescriptionHelpFormatter, description='Split wideband a GSM capture into seperate files per ARFCN.', epilog=EXTRA_HELP) + parser.add_argument(dest="channel", type=int, nargs='+', + help="List of ARFCNs") + parser.add_argument("-w", "--width", dest="width", type=eng_float, default=eng_notation.num_to_str(200000), + help="Channel lowpass width [default=%(default)s]") + parser.add_argument("-t", "--transition-width", dest="twidth", type=eng_float, default=eng_notation.num_to_str(50000), + help="Channel lowpass transition width [default=%(default)s]") + parser.add_argument("-s", "--samp-rate", dest="samp_rate", type=eng_float, default=eng_notation.num_to_str(2e7), + help="Sample rate of the wideband capture file [default=%(default)s]") + parser.add_argument("-o", "--out-samp-rate", dest="out_samp_rate", type=eng_float, default=eng_notation.num_to_str(1e6), + help="Sample rate of the output capture files [default=%(default)s]") + parser.add_argument("-b", "--band", dest="band", type=gsm_band, default='E-GSM', + help="GSM band [default=%(default)s]") + parser.add_argument("-f", "--cfile", dest="path", type=str, required=True, + help="Path to wideband GSM capture file") + parser.add_argument("-c", "--C0", dest="fc", type=eng_float, default=eng_notation.num_to_str(925.2e6), required=True, + help="C0 (BCCH) channel frequency in Hz [default=%(default)s]") + args = parser.parse_args() + + if not os.path.exists("./gsm_channelizer"): + os.makedirs("./gsm_channelizer") + + if not os.path.exists(args.path): + raise IOError(args.path + " does not exist") + + if args.samp_rate % args.out_samp_rate != 0: + raise Exception("Input sample rate should be multiple of output sample rate in order to get integer decimation.") + + decim = int(args.samp_rate / args.out_samp_rate) + print("Input sample rate: " + eng_notation.num_to_str(args.samp_rate)) + print("Output sample rate: " + eng_notation.num_to_str(args.out_samp_rate)) + print("==> using decimation of " + str(decim)) + + tb = gsm_channelize(channels=args.channel, + decim=decim, + ch_width=args.width, + ch_twidth=args.twidth, + fc=args.fc, + band=args.band, + samp_rate=args.samp_rate, + path=args.path) + tb.start() + tb.wait() + print("Done!")