blob: 4fd7c88eaa180f0a0211bd96a8235b95cb2227ca [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)
80 self.gsm_bcch_ccch_demapper_0 = grgsm.universal_ctrl_chans_demapper(0, ([2,6,12,16,22,26,32,36,42,46]), ([1,2,2,2,2,2,2,2,2,2]))
81
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
204 def __init__(self, rec_len=3, sample_rate=2e6, carrier_frequency=939e6, ppm=0):
205
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
214 self.rtlsdr_source = osmosdr.source( args="numchan=" + str(1) + " " + "" )
215
216 self.rtlsdr_source.set_sample_rate(sample_rate)
217
218 # capture half of GSM channel lower than channel center (-0.1MHz)
219 # this is needed when even number of channels is captured in order to process full captured bandwidth
220
221 self.rtlsdr_source.set_center_freq(carrier_frequency - 0.1e6, 0)
222
223 # correction of central frequency
224 # if the receiver has large frequency offset
225 # the value of this variable should be set close to that offset in ppm
226 self.rtlsdr_source.set_freq_corr(options.ppm, 0)
227
228 self.rtlsdr_source.set_dc_offset_mode(2, 0)
229 self.rtlsdr_source.set_iq_balance_mode(0, 0)
230 self.rtlsdr_source.set_gain_mode(True, 0)
231 self.rtlsdr_source.set_bandwidth(sample_rate, 0)
232
233 self.head = blocks.head(gr.sizeof_gr_complex * 1, int(rec_len * sample_rate))
234
235 # shift again by -0.1MHz in order to align channel center in 0Hz
236 self.blocks_rotator_cc = blocks.rotator_cc(-2 * pi * 0.1e6 / options.samp_rate)
237
238 self.wideband_receiver = wideband_receiver(OSR=4, fc=carrier_frequency, samp_rate=sample_rate)
239 self.gsm_extract_system_info = grgsm.extract_system_info()
240
241
242 self.connect((self.rtlsdr_source, 0), (self.head, 0))
243 self.connect((self.head, 0), (self.blocks_rotator_cc, 0))
244 self.connect((self.blocks_rotator_cc, 0), (self.wideband_receiver,0))
245 self.msg_connect(self.wideband_receiver, 'msgs', self.gsm_extract_system_info, 'msgs')
246
247 def set_carrier_frequency(self, carrier_frequency):
248 self.carrier_frequency = carrier_frequency
249 self.rtlsdr_source.set_center_freq(carrier_frequency - 0.1e6, 0)
250
251
252
Roman Khassrafe2fbb872015-09-29 19:40:04 +0200253class channel_info(object):
Roman Khassraf996103f2015-09-27 11:22:35 +0200254
255 def __init__(self, arfcn, freq, cid, lac, mcc, mnc, ccch_conf, power, neighbours, cell_arfcns):
256 self.arfcn = arfcn
257 self.freq = freq
258 self.cid = cid
259 self.lac = lac
260 self.mcc = mcc
261 self.mnc = mnc
262 self.ccch_conf = ccch_conf
263 self.power = power
264 self.neighbours = neighbours
265 self.cell_arfcns = cell_arfcns
266
267 def get_verbose_info(self):
268 i = " |---- Configuration: %s\n" % self.get_ccch_conf()
269 i += " |---- Cell ARFCNs: " + ", ".join(map(str, self.cell_arfcns)) + "\n"
270 i += " |---- Neighbour Cells: " + ", ".join(map(str, self.neighbours)) + "\n"
271 return i
272
273 def get_ccch_conf(self):
274 if self.ccch_conf == 0:
275 return "1 CCCH, not combined"
276 elif self.ccch_conf == 1:
277 return "1 CCCH, combined"
278 elif self.ccch_conf == 2:
279 return "2 CCCH, not combined"
280 elif self.ccch_conf == 4:
281 return "3 CCCH, not combined"
282 elif self.ccch_conf == 6:
283 return "4 CCCH, not combined"
284 else:
285 return "Unknown"
286
287 def getKey(self):
288 return self.arfcn
289
290 def __cmp__(self, other):
291 if hasattr(other, 'getKey'):
292 return self.getKey().__cmp__(other.getKey())
293
294 def __repr__(self):
295 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)
296
297
298if __name__ == '__main__':
299 parser = OptionParser(option_class=eng_option, usage="%prog: [options]")
300 bands_list = ", ".join(grgsm.arfcn.get_bands())
301 parser.add_option("-b", "--band", dest="band", default="P-GSM",
302 help="Specify the GSM band for the frequency.\nAvailable bands are: " + bands_list)
303 parser.add_option("-s", "--samp-rate", dest="samp_rate", type="float", default=2e6,
304 help="Set sample rate [default=%default] - allowed values even_number*0.2e6")
305 parser.add_option("-p", "--ppm", dest="ppm", type="intx", default=-45,
306 help="Set frequency correction in ppm [default=%default]")
307 parser.add_option("-g", "--gain", dest="gain", type="eng_float", default=24.0,
308 help="Set gain [default=%default]")
309 parser.add_option("--speed", dest="speed", type="intx", default=4,
310 help="Scan speed [default=%default]. Value range 0-5.")
311 parser.add_option("-v", "--verbose", action="store_true",
312 help="If set, verbose information output is printed: ccch configuration, cell ARFCN's, neighbour ARFCN's")
313
314 """
315 Dont forget: sudo sysctl kernel.shmmni=32000
316 """
317
318 (options, args) = parser.parse_args()
319
320 if options.band not in grgsm.arfcn.get_bands():
321 parser.error("Invalid GSM band\n")
322
323 if options.speed < 0 or options.speed > 5:
324 parser.error("Invalid scan speed.\n")
325
Roman Khassrafe2fbb872015-09-29 19:40:04 +0200326 if (options.samp_rate / 0.2e6) % 2 != 0:
327 parser.error("Invalid sample rate. Sample rate must be an even numer * 0.2e6")
328
Roman Khassraf996103f2015-09-27 11:22:35 +0200329 channels_num = int(options.samp_rate/0.2e6)
330
331 first_arfcn = grgsm.arfcn.get_first_arfcn(options.band)
332 last_arfcn = grgsm.arfcn.get_last_arfcn(options.band)
333 last_center_arfcn = last_arfcn - int((channels_num / 2) - 1)
334
335 current_freq = grgsm.arfcn.arfcn2downlink(first_arfcn + int(channels_num / 2) - 1, options.band)
336 last_freq = grgsm.arfcn.arfcn2downlink(last_center_arfcn, options.band)
337 stop_freq = last_freq + 0.2e6 * channels_num
338
339 while current_freq < stop_freq:
340
341 # silence rtl_sdr output:
342 # open 2 fds
343 null_fds = [os.open(os.devnull, os.O_RDWR) for x in xrange(2)]
344 # save the current file descriptors to a tuple
345 save = os.dup(1), os.dup(2)
346 # put /dev/null fds on 1 and 2
347 os.dup2(null_fds[0], 1)
348 os.dup2(null_fds[1], 2)
349
350 # instantiate scanner and processor
Roman Khassrafe2fbb872015-09-29 19:40:04 +0200351 scanner = wideband_scanner(rec_len=6-options.speed,
Roman Khassraf996103f2015-09-27 11:22:35 +0200352 sample_rate=options.samp_rate,
353 carrier_frequency=current_freq,
354 ppm=options.ppm)
355
356 # start recording
357 scanner.start()
358 scanner.wait()
359 scanner.stop()
360
361 # restore file descriptors so we can print the results
362 os.dup2(save[0], 1)
363 os.dup2(save[1], 2)
364 # close the temporary fds
365 os.close(null_fds[0])
366 os.close(null_fds[1])
367
368 freq_offsets = numpy.fft.ifftshift(numpy.array(range(int(-numpy.floor(channels_num/2)),int(numpy.floor((channels_num+1)/2))))*2e5)
369 detected_c0_channels = scanner.gsm_extract_system_info.get_chans()
370
371 if detected_c0_channels:
372 chans = numpy.array(scanner.gsm_extract_system_info.get_chans())
373 found_freqs = current_freq + freq_offsets[(chans)]
374
375 cell_ids = numpy.array(scanner.gsm_extract_system_info.get_cell_id())
376 lacs = numpy.array(scanner.gsm_extract_system_info.get_lac())
377 mccs = numpy.array(scanner.gsm_extract_system_info.get_mcc())
378 mncs = numpy.array(scanner.gsm_extract_system_info.get_mnc())
379 ccch_confs = numpy.array(scanner.gsm_extract_system_info.get_ccch_conf())
380 powers = numpy.array(scanner.gsm_extract_system_info.get_pwrs())
381
382 found_list = []
383 for i in range(0, len(chans)):
384 cell_arfcn_list = scanner.gsm_extract_system_info.get_cell_arfcns(chans[i])
385 neighbour_list = scanner.gsm_extract_system_info.get_neighbours(chans[i])
386
Roman Khassrafe2fbb872015-09-29 19:40:04 +0200387 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 +0200388 found_list.append(info)
389
390 for info in sorted(found_list):
391 print info
392 if options.verbose:
393 print info.get_verbose_info()
394
395 scanner = None
396 current_freq += channels_num * 0.2e6