blob: ebfdb7afff0a9673c10317322df2fb6b6f30074d [file] [log] [blame]
Roman Khassraf996103f2015-09-27 11:22:35 +02001#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# @file
4# @author Piotr Krysik <ptrkrysik@gmail.com>
5# @author Roman Khassraf <rkhassraf@gmail.com>
6# @section LICENSE
7#
8# Gr-gsm is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation; either version 3, or (at your option)
11# any later version.
12#
13# Gr-gsm is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with gr-gsm; see the file COPYING. If not, write to
20# the Free Software Foundation, Inc., 51 Franklin Street,
21# Boston, MA 02110-1301, USA.
22#
23#
24from gnuradio import blocks
25from gnuradio import gr
26from gnuradio import eng_notation
27from gnuradio.eng_option import eng_option
28from gnuradio.filter import firdes
29from gnuradio.filter import pfb
30from math import pi
31from optparse import OptionParser
32
33import grgsm
34import numpy
35import os
36import osmosdr
37import pmt
38import time
39
40
41#from wideband_receiver import *
42
Piotr Krysik8715da02016-01-06 22:21:09 +010043class receiver_with_decoder(grgsm.hier_block):
Roman Khassraf996103f2015-09-27 11:22:35 +020044
45 def __init__(self, OSR=4, chan_num=0, fc=939.4e6, ppm=0, samp_rate=0.2e6):
Piotr Krysik8715da02016-01-06 22:21:09 +010046 grgsm.hier_block.__init__(
Roman Khassraf996103f2015-09-27 11:22:35 +020047 self, "Receiver With Decoder",
48 gr.io_signature(1, 1, gr.sizeof_gr_complex*1),
49 gr.io_signature(0, 0, 0),
50 )
Piotr Krysik8715da02016-01-06 22:21:09 +010051 self.message_port_register_hier_out("bursts")
52 self.message_port_register_hier_out("msgs")
Roman Khassraf996103f2015-09-27 11:22:35 +020053
54 ##################################################
55 # Parameters
56 ##################################################
57 self.OSR = OSR
58 self.chan_num = chan_num
59 self.fc = fc
60 self.ppm = ppm
61 self.samp_rate = samp_rate
62
63 ##################################################
64 # Variables
65 ##################################################
66 self.samp_rate_out = samp_rate_out = 1625000.0/6.0*OSR
67
68 ##################################################
69 # Blocks
70 ##################################################
71 self.gsm_receiver_0 = grgsm.receiver(OSR, ([chan_num]), ([]))
72 self.gsm_input_0 = grgsm.gsm_input(
73 ppm=ppm,
74 osr=OSR,
75 fc=fc,
76 samp_rate_in=samp_rate,
77 )
78 self.gsm_control_channels_decoder_0 = grgsm.control_channels_decoder()
79 self.gsm_clock_offset_control_0 = grgsm.clock_offset_control(fc)
Piotr Krysik773a1942016-05-20 12:45:54 +020080 self.gsm_bcch_ccch_demapper_0 = grgsm.gsm_bcch_ccch_demapper(0)
Roman Khassraf996103f2015-09-27 11:22:35 +020081
82 ##################################################
83 # Connections
84 ##################################################
85 self.msg_connect(self.gsm_bcch_ccch_demapper_0, 'bursts', self, 'bursts')
86 self.msg_connect(self.gsm_bcch_ccch_demapper_0, 'bursts', self.gsm_control_channels_decoder_0, 'bursts')
87 self.msg_connect(self.gsm_clock_offset_control_0, 'ppm', self.gsm_input_0, 'ppm_in')
88 self.msg_connect(self.gsm_control_channels_decoder_0, 'msgs', self, 'msgs')
89 self.msg_connect(self.gsm_receiver_0, 'C0', self.gsm_bcch_ccch_demapper_0, 'bursts')
90 self.msg_connect(self.gsm_receiver_0, 'measurements', self.gsm_clock_offset_control_0, 'measurements')
91 self.connect((self.gsm_input_0, 0), (self.gsm_receiver_0, 0))
92 self.connect((self, 0), (self.gsm_input_0, 0))
93
94
95 def get_OSR(self):
96 return self.OSR
97
98 def set_OSR(self, OSR):
99 self.OSR = OSR
100 self.set_samp_rate_out(1625000.0/6.0*self.OSR)
101 self.gsm_input_0.set_osr(self.OSR)
102
103 def get_chan_num(self):
104 return self.chan_num
105
106 def set_chan_num(self, chan_num):
107 self.chan_num = chan_num
108
109 def get_fc(self):
110 return self.fc
111
112 def set_fc(self, fc):
113 self.fc = fc
114 self.gsm_input_0.set_fc(self.fc)
115
116 def get_ppm(self):
117 return self.ppm
118
119 def set_ppm(self, ppm):
120 self.ppm = ppm
121 self.gsm_input_0.set_ppm(self.ppm)
122
123 def get_samp_rate(self):
124 return self.samp_rate
125
126 def set_samp_rate(self, samp_rate):
127 self.samp_rate = samp_rate
128 self.gsm_input_0.set_samp_rate_in(self.samp_rate)
129
130 def get_samp_rate_out(self):
131 return self.samp_rate_out
132
133 def set_samp_rate_out(self, samp_rate_out):
134 self.samp_rate_out = samp_rate_out
135
136
Piotr Krysik8715da02016-01-06 22:21:09 +0100137class wideband_receiver(grgsm.hier_block):
Roman Khassraf996103f2015-09-27 11:22:35 +0200138
139 def __init__(self, OSR=4, fc=939.4e6, samp_rate=0.4e6):
Piotr Krysik8715da02016-01-06 22:21:09 +0100140 grgsm.hier_block.__init__(
Roman Khassraf996103f2015-09-27 11:22:35 +0200141 self, "Wideband receiver",
142 gr.io_signature(1, 1, gr.sizeof_gr_complex*1),
143 gr.io_signature(0, 0, 0),
144 )
Piotr Krysik8715da02016-01-06 22:21:09 +0100145 self.message_port_register_hier_out("bursts")
146 self.message_port_register_hier_out("msgs")
Roman Khassraf996103f2015-09-27 11:22:35 +0200147 self.__init(OSR, fc, samp_rate)
148
149 def __init(self, OSR=4, fc=939.4e6, samp_rate=0.4e6):
150 ##################################################
151 # Parameters
152 ##################################################
153 self.OSR = OSR
154 self.fc = fc
155 self.samp_rate = samp_rate
156 self.channels_num = int(samp_rate/0.2e6)
157 self.OSR_PFB = 2
158
159 ##################################################
160 # Blocks
161 ##################################################
162 self.pfb_channelizer_ccf_0 = pfb.channelizer_ccf(
163 self.channels_num,
164 (),
165 self.OSR_PFB,
166 100)
167 self.pfb_channelizer_ccf_0.set_channel_map(([]))
168 self.create_receivers()
169
170 ##################################################
171 # Connections
172 ##################################################
173 self.connect((self, 0), (self.pfb_channelizer_ccf_0, 0))
174 for chan in xrange(0,self.channels_num):
175 self.connect((self.pfb_channelizer_ccf_0, chan), (self.receivers_with_decoders[chan], 0))
176 self.msg_connect(self.receivers_with_decoders[chan], 'bursts', self, 'bursts')
177 self.msg_connect(self.receivers_with_decoders[chan], 'msgs', self, 'msgs')
178
179 def create_receivers(self):
180 self.receivers_with_decoders = {}
181 for chan in xrange(0,self.channels_num):
182 self.receivers_with_decoders[chan] = receiver_with_decoder(fc=self.fc, OSR=self.OSR, chan_num=chan, samp_rate=self.OSR_PFB*0.2e6)
183
184 def get_OSR(self):
185 return self.OSR
186
187 def set_OSR(self, OSR):
188 self.OSR = OSR
189 self.create_receivers()
190
191 def get_fc(self):
192 return self.fc
193
194 def set_fc(self, fc):
195 self.fc = fc
196 self.create_receivers()
197
198 def get_samp_rate(self):
199 return self.samp_rate
200
201
Roman Khassrafe2fbb872015-09-29 19:40:04 +0200202class wideband_scanner(gr.top_block):
Roman Khassraf996103f2015-09-27 11:22:35 +0200203
Piotr Krysik7185b662016-02-14 20:24:54 +0100204 def __init__(self, rec_len=3, sample_rate=2e6, carrier_frequency=939e6, ppm=0, args=""):
Roman Khassraf996103f2015-09-27 11:22:35 +0200205
206 gr.top_block.__init__(self, "Wideband Scanner")
207
208 self.rec_len = rec_len
209 self.sample_rate = sample_rate
210 self.carrier_frequency = carrier_frequency
211 self.ppm = ppm
212
213 # if no file name is given process data from rtl_sdr source
Piotr Krysik7185b662016-02-14 20:24:54 +0100214 print "Args=",args
215 self.rtlsdr_source = osmosdr.source( args="numchan=" + str(1) + " " + args )
Roman Khassraf996103f2015-09-27 11:22:35 +0200216
217 self.rtlsdr_source.set_sample_rate(sample_rate)
218
219 # capture half of GSM channel lower than channel center (-0.1MHz)
220 # this is needed when even number of channels is captured in order to process full captured bandwidth
221
222 self.rtlsdr_source.set_center_freq(carrier_frequency - 0.1e6, 0)
223
224 # correction of central frequency
225 # if the receiver has large frequency offset
226 # the value of this variable should be set close to that offset in ppm
227 self.rtlsdr_source.set_freq_corr(options.ppm, 0)
228
229 self.rtlsdr_source.set_dc_offset_mode(2, 0)
230 self.rtlsdr_source.set_iq_balance_mode(0, 0)
231 self.rtlsdr_source.set_gain_mode(True, 0)
232 self.rtlsdr_source.set_bandwidth(sample_rate, 0)
233
234 self.head = blocks.head(gr.sizeof_gr_complex * 1, int(rec_len * sample_rate))
235
236 # shift again by -0.1MHz in order to align channel center in 0Hz
237 self.blocks_rotator_cc = blocks.rotator_cc(-2 * pi * 0.1e6 / options.samp_rate)
238
239 self.wideband_receiver = wideband_receiver(OSR=4, fc=carrier_frequency, samp_rate=sample_rate)
240 self.gsm_extract_system_info = grgsm.extract_system_info()
241
242
243 self.connect((self.rtlsdr_source, 0), (self.head, 0))
244 self.connect((self.head, 0), (self.blocks_rotator_cc, 0))
245 self.connect((self.blocks_rotator_cc, 0), (self.wideband_receiver,0))
246 self.msg_connect(self.wideband_receiver, 'msgs', self.gsm_extract_system_info, 'msgs')
247
248 def set_carrier_frequency(self, carrier_frequency):
249 self.carrier_frequency = carrier_frequency
250 self.rtlsdr_source.set_center_freq(carrier_frequency - 0.1e6, 0)
251
252
253
Roman Khassrafe2fbb872015-09-29 19:40:04 +0200254class channel_info(object):
Roman Khassraf996103f2015-09-27 11:22:35 +0200255
256 def __init__(self, arfcn, freq, cid, lac, mcc, mnc, ccch_conf, power, neighbours, cell_arfcns):
257 self.arfcn = arfcn
258 self.freq = freq
259 self.cid = cid
260 self.lac = lac
261 self.mcc = mcc
262 self.mnc = mnc
263 self.ccch_conf = ccch_conf
264 self.power = power
265 self.neighbours = neighbours
266 self.cell_arfcns = cell_arfcns
267
268 def get_verbose_info(self):
269 i = " |---- Configuration: %s\n" % self.get_ccch_conf()
270 i += " |---- Cell ARFCNs: " + ", ".join(map(str, self.cell_arfcns)) + "\n"
271 i += " |---- Neighbour Cells: " + ", ".join(map(str, self.neighbours)) + "\n"
272 return i
273
274 def get_ccch_conf(self):
275 if self.ccch_conf == 0:
276 return "1 CCCH, not combined"
277 elif self.ccch_conf == 1:
278 return "1 CCCH, combined"
279 elif self.ccch_conf == 2:
280 return "2 CCCH, not combined"
281 elif self.ccch_conf == 4:
282 return "3 CCCH, not combined"
283 elif self.ccch_conf == 6:
284 return "4 CCCH, not combined"
285 else:
286 return "Unknown"
287
288 def getKey(self):
289 return self.arfcn
290
291 def __cmp__(self, other):
292 if hasattr(other, 'getKey'):
293 return self.getKey().__cmp__(other.getKey())
294
295 def __repr__(self):
296 return "ARFCN: %4u, Freq: %6.1fM, CID: %5u, LAC: %5u, MCC: %3u, MNC: %3u, Pwr: %3i" % (self.arfcn, self.freq/1e6, self.cid, self.lac, self.mcc, self.mnc, self.power)
297
298
299if __name__ == '__main__':
300 parser = OptionParser(option_class=eng_option, usage="%prog: [options]")
301 bands_list = ", ".join(grgsm.arfcn.get_bands())
302 parser.add_option("-b", "--band", dest="band", default="P-GSM",
303 help="Specify the GSM band for the frequency.\nAvailable bands are: " + bands_list)
304 parser.add_option("-s", "--samp-rate", dest="samp_rate", type="float", default=2e6,
305 help="Set sample rate [default=%default] - allowed values even_number*0.2e6")
Piotr Krysik7185b662016-02-14 20:24:54 +0100306 parser.add_option("-p", "--ppm", dest="ppm", type="intx", default=0,
Roman Khassraf996103f2015-09-27 11:22:35 +0200307 help="Set frequency correction in ppm [default=%default]")
308 parser.add_option("-g", "--gain", dest="gain", type="eng_float", default=24.0,
309 help="Set gain [default=%default]")
Piotr Krysik7185b662016-02-14 20:24:54 +0100310 parser.add_option("", "--args", dest="args", type="string", default="",
311 help="Set device arguments [default=%default]")
Roman Khassraf996103f2015-09-27 11:22:35 +0200312 parser.add_option("--speed", dest="speed", type="intx", default=4,
313 help="Scan speed [default=%default]. Value range 0-5.")
314 parser.add_option("-v", "--verbose", action="store_true",
315 help="If set, verbose information output is printed: ccch configuration, cell ARFCN's, neighbour ARFCN's")
316
317 """
318 Dont forget: sudo sysctl kernel.shmmni=32000
319 """
320
321 (options, args) = parser.parse_args()
322
323 if options.band not in grgsm.arfcn.get_bands():
324 parser.error("Invalid GSM band\n")
325
326 if options.speed < 0 or options.speed > 5:
327 parser.error("Invalid scan speed.\n")
328
Roman Khassrafe2fbb872015-09-29 19:40:04 +0200329 if (options.samp_rate / 0.2e6) % 2 != 0:
330 parser.error("Invalid sample rate. Sample rate must be an even numer * 0.2e6")
331
Roman Khassraf996103f2015-09-27 11:22:35 +0200332 channels_num = int(options.samp_rate/0.2e6)
333
334 first_arfcn = grgsm.arfcn.get_first_arfcn(options.band)
335 last_arfcn = grgsm.arfcn.get_last_arfcn(options.band)
336 last_center_arfcn = last_arfcn - int((channels_num / 2) - 1)
337
338 current_freq = grgsm.arfcn.arfcn2downlink(first_arfcn + int(channels_num / 2) - 1, options.band)
339 last_freq = grgsm.arfcn.arfcn2downlink(last_center_arfcn, options.band)
340 stop_freq = last_freq + 0.2e6 * channels_num
341
342 while current_freq < stop_freq:
343
344 # silence rtl_sdr output:
345 # open 2 fds
346 null_fds = [os.open(os.devnull, os.O_RDWR) for x in xrange(2)]
347 # save the current file descriptors to a tuple
348 save = os.dup(1), os.dup(2)
349 # put /dev/null fds on 1 and 2
350 os.dup2(null_fds[0], 1)
351 os.dup2(null_fds[1], 2)
352
353 # instantiate scanner and processor
Roman Khassrafe2fbb872015-09-29 19:40:04 +0200354 scanner = wideband_scanner(rec_len=6-options.speed,
Roman Khassraf996103f2015-09-27 11:22:35 +0200355 sample_rate=options.samp_rate,
356 carrier_frequency=current_freq,
Piotr Krysik7185b662016-02-14 20:24:54 +0100357 ppm=options.ppm, args=options.args)
Roman Khassraf996103f2015-09-27 11:22:35 +0200358
359 # start recording
360 scanner.start()
361 scanner.wait()
362 scanner.stop()
363
364 # restore file descriptors so we can print the results
365 os.dup2(save[0], 1)
366 os.dup2(save[1], 2)
367 # close the temporary fds
368 os.close(null_fds[0])
369 os.close(null_fds[1])
370
371 freq_offsets = numpy.fft.ifftshift(numpy.array(range(int(-numpy.floor(channels_num/2)),int(numpy.floor((channels_num+1)/2))))*2e5)
372 detected_c0_channels = scanner.gsm_extract_system_info.get_chans()
373
374 if detected_c0_channels:
375 chans = numpy.array(scanner.gsm_extract_system_info.get_chans())
376 found_freqs = current_freq + freq_offsets[(chans)]
377
378 cell_ids = numpy.array(scanner.gsm_extract_system_info.get_cell_id())
379 lacs = numpy.array(scanner.gsm_extract_system_info.get_lac())
380 mccs = numpy.array(scanner.gsm_extract_system_info.get_mcc())
381 mncs = numpy.array(scanner.gsm_extract_system_info.get_mnc())
382 ccch_confs = numpy.array(scanner.gsm_extract_system_info.get_ccch_conf())
383 powers = numpy.array(scanner.gsm_extract_system_info.get_pwrs())
384
385 found_list = []
386 for i in range(0, len(chans)):
387 cell_arfcn_list = scanner.gsm_extract_system_info.get_cell_arfcns(chans[i])
388 neighbour_list = scanner.gsm_extract_system_info.get_neighbours(chans[i])
389
Roman Khassrafe2fbb872015-09-29 19:40:04 +0200390 info = channel_info(grgsm.arfcn.downlink2arfcn(found_freqs[i], options.band), found_freqs[i], cell_ids[i], lacs[i], mccs[i], mncs[i], ccch_confs[i], powers[i], neighbour_list, cell_arfcn_list)
Roman Khassraf996103f2015-09-27 11:22:35 +0200391 found_list.append(info)
392
393 for info in sorted(found_list):
394 print info
395 if options.verbose:
396 print info.get_verbose_info()
397
398 scanner = None
399 current_freq += channels_num * 0.2e6