blob: 4c678e7a0e73c19d750a3bc96045a57dab3805c7 [file] [log] [blame]
Piotr Krysik902f4eb2017-09-19 08:04:33 +02001#!/usr/bin/env python2
2# -*- coding: utf-8 -*-
3
4# GR-GSM based transceiver
5# Follow graph implementation
6#
Vadim Yanitskiy1fe28252019-01-19 10:16:27 +07007# (C) 2016-2019 by Vadim Yanitskiy <axilirator@gmail.com>
Piotr Krysikd2f162f2017-11-30 12:50:00 +01008# (C) 2017 by Piotr Krysik <ptrkrysik@gmail.com>
Piotr Krysik902f4eb2017-09-19 08:04:33 +02009#
10# All Rights Reserved
11#
12# This program is free software; you can redistribute it and/or modify
13# it under the terms of the GNU General Public License as published by
14# the Free Software Foundation; either version 2 of the License, or
15# (at your option) any later version.
16#
17# This program is distributed in the hope that it will be useful,
18# but WITHOUT ANY WARRANTY; without even the implied warranty of
19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20# GNU General Public License for more details.
21#
22# You should have received a copy of the GNU General Public License along
23# with this program; if not, write to the Free Software Foundation, Inc.,
24# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25
26import pmt
27import time
28import grgsm
Vadim Yanitskiy7da82f42019-01-19 09:28:23 +070029import random
Piotr Krysik902f4eb2017-09-19 08:04:33 +020030
31from math import pi
32
Vadim Yanitskiy97dc84e2018-08-10 05:29:23 +070033from gnuradio import eng_notation
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +070034from gnuradio import digital
Piotr Krysik902f4eb2017-09-19 08:04:33 +020035from gnuradio import blocks
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +070036from gnuradio import uhd
Piotr Krysik902f4eb2017-09-19 08:04:33 +020037from gnuradio import gr
38
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +070039from gnuradio import filter
40from gnuradio.filter import firdes
41
Vadim Yanitskiybd4daec2018-12-20 09:32:56 +070042from dict_toggle_sign import dict_toggle_sign
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +070043
Vadim Yanitskiyf237f1a2018-12-20 09:49:56 +070044class RadioInterface(gr.top_block):
Piotr Krysik1cc264f2018-09-06 19:57:57 +020045 # PHY specific variables
Vadim Yanitskiy1fe28252019-01-19 10:16:27 +070046 rx_freq = None
47 tx_freq = None
Piotr Krysik1cc264f2018-09-06 19:57:57 +020048 osr = 4
Piotr Krysik902f4eb2017-09-19 08:04:33 +020049
Piotr Krysik1cc264f2018-09-06 19:57:57 +020050 # GSM timings (in microseconds [uS])
51 # One timeslot duration is 576.9 μs = 15/26 ms,
52 # or 156.25 symbol periods (a symbol period is 48/13 μs)
53 GSM_SYM_PERIOD_uS = 48.0 / 13.0
54 GSM_TS_PERIOD_uS = GSM_SYM_PERIOD_uS * 156.25
55 GSM_UL_DL_SHIFT_uS = -(GSM_TS_PERIOD_uS * 3)
Vadim Yanitskiy9dded9b2017-12-05 01:00:51 +070056
Piotr Krysikb3d5c572018-09-12 00:35:05 +070057 GSM_SYM_RATE = (1.0 / GSM_SYM_PERIOD_uS) * 1e6
58 SAMPLE_RATE = GSM_SYM_RATE * osr
Vadim Yanitskiy38baac92018-09-07 19:09:12 +070059
Piotr Krysik1cc264f2018-09-06 19:57:57 +020060 # FIXME: shall be measured (automatically?) for
61 # particular device and particular clock rate.
62 # The current value is measured for USRP B2X0 at 26e6.
63 delay_correction = (285.616 + 2 * GSM_SYM_PERIOD_uS) * 1e-6
Piotr Krysik902f4eb2017-09-19 08:04:33 +020064
Vadim Yanitskiy1fe28252019-01-19 10:16:27 +070065 # Dummy freq. value that is used during initialization
66 # basically, the DL freq. of ARFCN 0
67 DUMMY_FREQ = 935e6
68
Piotr Krysik1cc264f2018-09-06 19:57:57 +020069 def __init__(self, phy_args, phy_sample_rate,
70 phy_rx_gain, phy_tx_gain, phy_ppm,
71 phy_rx_antenna, phy_tx_antenna,
Piotr Krysikb8632ff2018-09-13 15:42:55 +020072 phy_freq_offset, trx_bind_addr,
Piotr Krysik06317672018-09-13 14:41:06 +020073 trx_remote_addr, trx_base_port):
Piotr Krysik902f4eb2017-09-19 08:04:33 +020074
Piotr Krysik1cc264f2018-09-06 19:57:57 +020075 print("[i] Init Radio interface (L:%s:%u <-> R:%s:%u)"
76 % (trx_bind_addr, trx_base_port + 2,
77 trx_remote_addr, trx_base_port + 102))
Piotr Krysik902f4eb2017-09-19 08:04:33 +020078
Piotr Krysik1cc264f2018-09-06 19:57:57 +020079 # PHY specific variables
80 self.sample_rate = phy_sample_rate
81 self.rx_gain = phy_rx_gain
82 self.tx_gain = phy_tx_gain
83 self.ppm = phy_ppm
Piotr Krysikb8632ff2018-09-13 15:42:55 +020084 self.freq_offset = phy_freq_offset
Piotr Krysik902f4eb2017-09-19 08:04:33 +020085
Piotr Krysik1cc264f2018-09-06 19:57:57 +020086 gr.top_block.__init__(self, "GR-GSM TRX")
Piotr Krysik902f4eb2017-09-19 08:04:33 +020087
Piotr Krysik1cc264f2018-09-06 19:57:57 +020088 # TRX Burst Interface
89 self.trx_burst_if = grgsm.trx_burst_if(
90 trx_bind_addr, trx_remote_addr,
91 str(trx_base_port))
Piotr Krysik902f4eb2017-09-19 08:04:33 +020092
Piotr Krysik1cc264f2018-09-06 19:57:57 +020093 # RX path definition
94 self.phy_src = uhd.usrp_source(phy_args,
95 uhd.stream_args(cpu_format="fc32",
96 channels=range(1)))
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +070097
Piotr Krysik1cc264f2018-09-06 19:57:57 +020098 self.phy_src.set_clock_rate(26e6, uhd.ALL_MBOARDS)
Piotr Krysik1cc264f2018-09-06 19:57:57 +020099 self.phy_src.set_antenna(phy_rx_antenna, 0)
100 self.phy_src.set_samp_rate(phy_sample_rate)
101 self.phy_src.set_bandwidth(650e3, 0)
102 self.phy_src.set_gain(phy_rx_gain)
Piotr Krysik902f4eb2017-09-19 08:04:33 +0200103
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200104 self.msg_to_tag_src = grgsm.msg_to_tag()
Piotr Krysik902f4eb2017-09-19 08:04:33 +0200105
Vadim Yanitskiy1fe28252019-01-19 10:16:27 +0700106 self.rotator_src = grgsm.controlled_rotator_cc(0.0)
Piotr Krysik902f4eb2017-09-19 08:04:33 +0200107
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200108 self.lpf = filter.fir_filter_ccf(1, firdes.low_pass(
109 1, phy_sample_rate, 125e3, 5e3, firdes.WIN_HAMMING, 6.76))
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700110
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200111 self.gsm_receiver = grgsm.receiver(self.osr, ([0]), ([]))
Piotr Krysik902f4eb2017-09-19 08:04:33 +0200112
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200113 self.ts_filter = grgsm.burst_timeslot_filter(0)
114 self.ts_filter.set_policy(grgsm.FILTER_POLICY_DROP_ALL)
Vadim Yanitskiy962e2d82017-10-17 09:24:55 +0700115
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200116 # Connections
117 self.connect(
118 (self.phy_src, 0),
119 (self.msg_to_tag_src, 0))
Piotr Krysik902f4eb2017-09-19 08:04:33 +0200120
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200121 self.connect(
122 (self.msg_to_tag_src, 0),
123 (self.rotator_src, 0))
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700124
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200125 self.connect(
126 (self.rotator_src, 0),
127 (self.lpf, 0))
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700128
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200129 self.connect(
130 (self.lpf, 0),
131 (self.gsm_receiver, 0))
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700132
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200133 self.msg_connect(
134 (self.gsm_receiver, 'C0'),
135 (self.ts_filter, 'in'))
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700136
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200137 self.msg_connect(
138 (self.ts_filter, 'out'),
139 (self.trx_burst_if, 'bursts'))
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700140
Vadim Yanitskiy962e2d82017-10-17 09:24:55 +0700141
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200142 # TX Path Definition
143 self.phy_sink = uhd.usrp_sink(phy_args,
144 uhd.stream_args(cpu_format="fc32",
145 channels=range(1)), "packet_len")
Piotr Krysik902f4eb2017-09-19 08:04:33 +0200146
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200147 self.phy_sink.set_clock_rate(26e6, uhd.ALL_MBOARDS)
148 self.phy_sink.set_antenna(phy_tx_antenna, 0)
149 self.phy_sink.set_samp_rate(phy_sample_rate)
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200150 self.phy_sink.set_gain(self.tx_gain)
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700151
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200152 self.tx_time_setter = grgsm.txtime_setter(
153 0xffffffff, 0, 0, 0, 0, 0,
154 self.delay_correction + self.GSM_UL_DL_SHIFT_uS * 1e-6)
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700155
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200156 self.tx_burst_proc = grgsm.preprocess_tx_burst()
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700157
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200158 self.pdu_to_tagged_stream = blocks.pdu_to_tagged_stream(
159 blocks.byte_t, 'packet_len')
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700160
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200161 self.gmsk_mod = grgsm.gsm_gmsk_mod(
162 BT = 0.3, pulse_duration = 4, sps = self.osr)
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700163
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200164 self.burst_shaper = digital.burst_shaper_cc(
165 (firdes.window(firdes.WIN_HANN, 16, 0)),
166 0, 20, False, "packet_len")
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700167
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200168 self.msg_to_tag_sink = grgsm.msg_to_tag()
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700169
Vadim Yanitskiy1fe28252019-01-19 10:16:27 +0700170 self.rotator_sink = grgsm.controlled_rotator_cc(0.0)
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700171
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200172 # Connections
173 self.msg_connect(
174 (self.trx_burst_if, 'bursts'),
175 (self.tx_time_setter, 'bursts_in'))
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700176
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200177 self.msg_connect(
178 (self.tx_time_setter, 'bursts_out'),
179 (self.tx_burst_proc, 'bursts_in'))
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700180
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200181 self.msg_connect(
182 (self.tx_burst_proc, 'bursts_out'),
183 (self.pdu_to_tagged_stream, 'pdus'))
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700184
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200185 self.connect(
186 (self.pdu_to_tagged_stream, 0),
187 (self.gmsk_mod, 0))
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700188
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200189 self.connect(
190 (self.gmsk_mod, 0),
191 (self.burst_shaper, 0))
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700192
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200193 self.connect(
194 (self.burst_shaper, 0),
195 (self.msg_to_tag_sink, 0))
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700196
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200197 self.connect(
198 (self.msg_to_tag_sink, 0),
199 (self.rotator_sink, 0))
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700200
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200201 self.connect(
202 (self.rotator_sink, 0),
203 (self.phy_sink, 0))
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700204
205
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200206 # RX & TX synchronization
207 self.bt_filter = grgsm.burst_type_filter([3])
208 self.burst_to_fn_time = grgsm.burst_to_fn_time()
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700209
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200210 # Connections
211 self.msg_connect(
212 (self.gsm_receiver, 'C0'),
213 (self.bt_filter, 'bursts_in'))
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700214
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200215 self.msg_connect(
216 (self.bt_filter, 'bursts_out'),
217 (self.burst_to_fn_time, 'bursts_in'))
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700218
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200219 self.msg_connect(
220 (self.burst_to_fn_time, 'fn_time_out'),
221 (self.tx_time_setter, 'fn_time'))
Piotr Krysik902f4eb2017-09-19 08:04:33 +0200222
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700223
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200224 # AFC (Automatic Frequency Correction)
Vadim Yanitskiy1fe28252019-01-19 10:16:27 +0700225 # NOTE: dummy frequency is used during init
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200226 self.gsm_clck_ctrl = grgsm.clock_offset_control(
Vadim Yanitskiy1fe28252019-01-19 10:16:27 +0700227 self.DUMMY_FREQ, phy_sample_rate, osr = self.osr)
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700228
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200229 self.dict_toggle_sign = dict_toggle_sign()
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700230
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200231 # Connections
232 self.msg_connect(
233 (self.gsm_receiver, 'measurements'),
234 (self.gsm_clck_ctrl, 'measurements'))
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700235
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200236 self.msg_connect(
237 (self.gsm_clck_ctrl, 'ctrl'),
238 (self.msg_to_tag_src, 'msg'))
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700239
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200240 self.msg_connect(
241 (self.gsm_clck_ctrl, 'ctrl'),
242 (self.dict_toggle_sign, 'dict_in'))
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700243
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200244 self.msg_connect(
245 (self.dict_toggle_sign, 'dict_out'),
246 (self.msg_to_tag_sink, 'msg'))
Piotr Krysik902f4eb2017-09-19 08:04:33 +0200247
Vadim Yanitskiy0017a352018-03-03 23:37:30 +0700248
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200249 # Some UHD devices (such as UmTRX) do start the clock
250 # not from 0, so it's required to reset it manually.
251 # Resetting UHD source will also affect the sink.
252 self.phy_src.set_time_now(uhd.time_spec(0.0))
Vadim Yanitskiy0017a352018-03-03 23:37:30 +0700253
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200254 def shutdown(self):
255 print("[i] Shutdown Radio interface")
256 self.stop()
257 self.wait()
Piotr Krysik902f4eb2017-09-19 08:04:33 +0200258
Vadim Yanitskiyb7a272e2019-01-19 10:18:10 +0700259 @property
260 def ready(self):
261 # RX / TX frequencies shall be set
262 if self.rx_freq is None:
263 return False
264 if self.tx_freq is None:
265 return False
266
267 return True
268
Vadim Yanitskiybaebe452019-01-19 10:20:59 +0700269 def reset(self):
270 # TODO: do we need to reset both RX / TX freq.?
271 # self.rx_freq = None
272 # self.tx_freq = None
273 self.set_ta(0)
274
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200275 def calc_phase_inc(self, fc):
276 return self.ppm / 1.0e6 * 2 * pi * fc / self.sample_rate
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700277
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200278 def set_rx_freq(self, fc):
Vadim Yanitskiy8bd9e152019-01-19 10:35:55 +0700279 if self.freq_offset != 0:
280 fc += self.freq_offset
281 print("[#] Shifting RX freq. to %s (offset is %s)"
282 % (eng_notation.num_to_str(fc),
283 eng_notation.num_to_str(self.freq_offset)))
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200284 self.phy_src.set_center_freq(fc, 0)
285 self.rotator_src.set_phase_inc(self.calc_phase_inc(fc))
Vadim Yanitskiy5823a412019-01-19 10:39:51 +0700286 self.gsm_clck_ctrl.set_fc(fc)
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200287 self.rx_freq = fc
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700288
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200289 def set_tx_freq(self, fc):
Vadim Yanitskiy8bd9e152019-01-19 10:35:55 +0700290 if self.freq_offset != 0:
291 fc += self.freq_offset
292 print("[#] Shifting TX freq. to %s (offset is %s)"
293 % (eng_notation.num_to_str(fc),
294 eng_notation.num_to_str(self.freq_offset)))
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200295 self.phy_sink.set_center_freq(fc, 0)
296 self.rotator_sink.set_phase_inc(-self.calc_phase_inc(fc))
297 self.tx_freq = fc
Piotr Krysik902f4eb2017-09-19 08:04:33 +0200298
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200299 def set_rx_gain(self, gain):
300 self.phy_src.set_gain(gain, 0)
301 self.rx_gain = gain
Vadim Yanitskiy01c6afd2017-10-19 01:14:24 +0700302
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200303 def set_tx_gain(self, gain):
304 self.phy_sink.set_gain(gain, 0)
305 self.tx_gain = gain
Vadim Yanitskiy34266e72017-12-05 01:01:43 +0700306
Vadim Yanitskiy180a0372019-01-19 10:22:59 +0700307 def set_slot(self, slot, config):
308 print("[i] Configure timeslot filter to: %s"
309 % ("drop all" if config == 0 else "tn=%d" % slot))
310
311 if config == 0:
312 # Value 0 is used for deactivation
313 self.ts_filter.set_policy(grgsm.FILTER_POLICY_DROP_ALL)
314 else:
315 # FIXME: ideally, we should (re)configure the Receiver
316 # block, but there is no API for that, and hard-coded
317 # timeslot configuration is used...
318 self.ts_filter.set_policy(grgsm.FILTER_POLICY_DEFAULT)
319 self.ts_filter.set_tn(slot)
320
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200321 def set_ta(self, ta):
322 print("[i] Setting TA value %d" % ta)
323 advance_time_sec = ta * self.GSM_SYM_PERIOD_uS * 1e-6
324 self.tx_time_setter.set_timing_advance(advance_time_sec)
Vadim Yanitskiy7da82f42019-01-19 09:28:23 +0700325
326 def measure(self, freq):
327 # HACK: generate a random low RSSI value
328 return random.randint(-120, -100)