blob: acbc3cd10f541bac33b12c5da2b669f14242c01e [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
Vadim Yanitskiy3120ba72019-01-19 11:55:01 +07005# Generic (device independent) flow-graph implementation
Piotr Krysik902f4eb2017-09-19 08:04:33 +02006#
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
36from gnuradio import gr
37
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +070038from gnuradio import filter
39from gnuradio.filter import firdes
40
Vasil Velichkov46c90be2019-09-02 04:06:41 +030041from .dict_toggle_sign import dict_toggle_sign
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +070042
Vadim Yanitskiyf237f1a2018-12-20 09:49:56 +070043class RadioInterface(gr.top_block):
Piotr Krysik1cc264f2018-09-06 19:57:57 +020044 # PHY specific variables
Vadim Yanitskiy1fe28252019-01-19 10:16:27 +070045 rx_freq = None
46 tx_freq = None
Piotr Krysik1cc264f2018-09-06 19:57:57 +020047 osr = 4
Piotr Krysik902f4eb2017-09-19 08:04:33 +020048
Piotr Krysik1cc264f2018-09-06 19:57:57 +020049 # GSM timings (in microseconds [uS])
50 # One timeslot duration is 576.9 μs = 15/26 ms,
51 # or 156.25 symbol periods (a symbol period is 48/13 μs)
52 GSM_SYM_PERIOD_uS = 48.0 / 13.0
53 GSM_TS_PERIOD_uS = GSM_SYM_PERIOD_uS * 156.25
54 GSM_UL_DL_SHIFT_uS = -(GSM_TS_PERIOD_uS * 3)
Vadim Yanitskiy9dded9b2017-12-05 01:00:51 +070055
Piotr Krysikb3d5c572018-09-12 00:35:05 +070056 GSM_SYM_RATE = (1.0 / GSM_SYM_PERIOD_uS) * 1e6
57 SAMPLE_RATE = GSM_SYM_RATE * osr
Vadim Yanitskiy38baac92018-09-07 19:09:12 +070058
Vadim Yanitskiy1fe28252019-01-19 10:16:27 +070059 # Dummy freq. value that is used during initialization
60 # basically, the DL freq. of ARFCN 0
61 DUMMY_FREQ = 935e6
62
Vadim Yanitskiyb6f6f472019-01-19 12:14:28 +070063 # Human-readable description
64 def __str__(self):
65 return "(generic)"
66
Piotr Krysik1cc264f2018-09-06 19:57:57 +020067 def __init__(self, phy_args, phy_sample_rate,
68 phy_rx_gain, phy_tx_gain, phy_ppm,
69 phy_rx_antenna, phy_tx_antenna,
Piotr Krysikb8632ff2018-09-13 15:42:55 +020070 phy_freq_offset, trx_bind_addr,
Piotr Krysik06317672018-09-13 14:41:06 +020071 trx_remote_addr, trx_base_port):
Piotr Krysik902f4eb2017-09-19 08:04:33 +020072
Vadim Yanitskiyb6f6f472019-01-19 12:14:28 +070073 print("[i] Init %s Radio interface (L:%s:%u <-> R:%s:%u)"
74 % (self, trx_bind_addr, trx_base_port + 2,
Piotr Krysik1cc264f2018-09-06 19:57:57 +020075 trx_remote_addr, trx_base_port + 102))
Piotr Krysik902f4eb2017-09-19 08:04:33 +020076
Piotr Krysik1cc264f2018-09-06 19:57:57 +020077 # PHY specific variables
78 self.sample_rate = phy_sample_rate
79 self.rx_gain = phy_rx_gain
80 self.tx_gain = phy_tx_gain
81 self.ppm = phy_ppm
Piotr Krysikb8632ff2018-09-13 15:42:55 +020082 self.freq_offset = phy_freq_offset
Piotr Krysik902f4eb2017-09-19 08:04:33 +020083
Vadim Yanitskiy3120ba72019-01-19 11:55:01 +070084 self.phy_args = phy_args
85 self.rx_antenna = phy_rx_antenna
86 self.tx_antenna = phy_tx_antenna
87
Piotr Krysik1cc264f2018-09-06 19:57:57 +020088 gr.top_block.__init__(self, "GR-GSM TRX")
Piotr Krysik902f4eb2017-09-19 08:04:33 +020089
Piotr Krysik1cc264f2018-09-06 19:57:57 +020090 # TRX Burst Interface
91 self.trx_burst_if = grgsm.trx_burst_if(
92 trx_bind_addr, trx_remote_addr,
93 str(trx_base_port))
Piotr Krysik902f4eb2017-09-19 08:04:33 +020094
Piotr Krysik1cc264f2018-09-06 19:57:57 +020095 # RX path definition
Vadim Yanitskiy3120ba72019-01-19 11:55:01 +070096 self.phy_init_source()
Piotr Krysik902f4eb2017-09-19 08:04:33 +020097
Piotr Krysik1cc264f2018-09-06 19:57:57 +020098 self.msg_to_tag_src = grgsm.msg_to_tag()
Piotr Krysik902f4eb2017-09-19 08:04:33 +020099
Vadim Yanitskiy1fe28252019-01-19 10:16:27 +0700100 self.rotator_src = grgsm.controlled_rotator_cc(0.0)
Piotr Krysik902f4eb2017-09-19 08:04:33 +0200101
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200102 self.lpf = filter.fir_filter_ccf(1, firdes.low_pass(
103 1, phy_sample_rate, 125e3, 5e3, firdes.WIN_HAMMING, 6.76))
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700104
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200105 self.gsm_receiver = grgsm.receiver(self.osr, ([0]), ([]))
Piotr Krysik902f4eb2017-09-19 08:04:33 +0200106
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200107 self.ts_filter = grgsm.burst_timeslot_filter(0)
108 self.ts_filter.set_policy(grgsm.FILTER_POLICY_DROP_ALL)
Vadim Yanitskiy962e2d82017-10-17 09:24:55 +0700109
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200110 # Connections
111 self.connect(
Vadim Yanitskiy3120ba72019-01-19 11:55:01 +0700112 (self._phy_src, 0),
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200113 (self.msg_to_tag_src, 0))
Piotr Krysik902f4eb2017-09-19 08:04:33 +0200114
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200115 self.connect(
116 (self.msg_to_tag_src, 0),
117 (self.rotator_src, 0))
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700118
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200119 self.connect(
120 (self.rotator_src, 0),
121 (self.lpf, 0))
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700122
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200123 self.connect(
124 (self.lpf, 0),
125 (self.gsm_receiver, 0))
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700126
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200127 self.msg_connect(
128 (self.gsm_receiver, 'C0'),
129 (self.ts_filter, 'in'))
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700130
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200131 self.msg_connect(
132 (self.ts_filter, 'out'),
133 (self.trx_burst_if, 'bursts'))
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700134
Vadim Yanitskiy962e2d82017-10-17 09:24:55 +0700135
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200136 # TX Path Definition
Vadim Yanitskiy3120ba72019-01-19 11:55:01 +0700137 self.phy_init_sink()
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700138
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200139 self.tx_time_setter = grgsm.txtime_setter(
140 0xffffffff, 0, 0, 0, 0, 0,
Vadim Yanitskiy3120ba72019-01-19 11:55:01 +0700141 self.phy_proc_delay + self.GSM_UL_DL_SHIFT_uS * 1e-6)
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700142
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200143 self.tx_burst_proc = grgsm.preprocess_tx_burst()
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700144
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200145 self.pdu_to_tagged_stream = blocks.pdu_to_tagged_stream(
146 blocks.byte_t, 'packet_len')
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700147
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200148 self.gmsk_mod = grgsm.gsm_gmsk_mod(
149 BT = 0.3, pulse_duration = 4, sps = self.osr)
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700150
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200151 self.burst_shaper = digital.burst_shaper_cc(
152 (firdes.window(firdes.WIN_HANN, 16, 0)),
153 0, 20, False, "packet_len")
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700154
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200155 self.msg_to_tag_sink = grgsm.msg_to_tag()
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700156
Vadim Yanitskiy1fe28252019-01-19 10:16:27 +0700157 self.rotator_sink = grgsm.controlled_rotator_cc(0.0)
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700158
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200159 # Connections
160 self.msg_connect(
161 (self.trx_burst_if, 'bursts'),
162 (self.tx_time_setter, 'bursts_in'))
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700163
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200164 self.msg_connect(
165 (self.tx_time_setter, 'bursts_out'),
166 (self.tx_burst_proc, 'bursts_in'))
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700167
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200168 self.msg_connect(
169 (self.tx_burst_proc, 'bursts_out'),
170 (self.pdu_to_tagged_stream, 'pdus'))
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700171
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200172 self.connect(
173 (self.pdu_to_tagged_stream, 0),
174 (self.gmsk_mod, 0))
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700175
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200176 self.connect(
177 (self.gmsk_mod, 0),
178 (self.burst_shaper, 0))
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700179
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200180 self.connect(
181 (self.burst_shaper, 0),
182 (self.msg_to_tag_sink, 0))
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700183
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200184 self.connect(
185 (self.msg_to_tag_sink, 0),
186 (self.rotator_sink, 0))
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700187
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200188 self.connect(
189 (self.rotator_sink, 0),
Vadim Yanitskiy3120ba72019-01-19 11:55:01 +0700190 (self._phy_sink, 0))
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700191
192
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200193 # RX & TX synchronization
194 self.bt_filter = grgsm.burst_type_filter([3])
195 self.burst_to_fn_time = grgsm.burst_to_fn_time()
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700196
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200197 # Connections
198 self.msg_connect(
199 (self.gsm_receiver, 'C0'),
200 (self.bt_filter, 'bursts_in'))
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700201
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200202 self.msg_connect(
203 (self.bt_filter, 'bursts_out'),
204 (self.burst_to_fn_time, 'bursts_in'))
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700205
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200206 self.msg_connect(
207 (self.burst_to_fn_time, 'fn_time_out'),
208 (self.tx_time_setter, 'fn_time'))
Piotr Krysik902f4eb2017-09-19 08:04:33 +0200209
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700210
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200211 # AFC (Automatic Frequency Correction)
Vadim Yanitskiy1fe28252019-01-19 10:16:27 +0700212 # NOTE: dummy frequency is used during init
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200213 self.gsm_clck_ctrl = grgsm.clock_offset_control(
Vadim Yanitskiy1fe28252019-01-19 10:16:27 +0700214 self.DUMMY_FREQ, phy_sample_rate, osr = self.osr)
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700215
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200216 self.dict_toggle_sign = dict_toggle_sign()
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700217
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200218 # Connections
219 self.msg_connect(
220 (self.gsm_receiver, 'measurements'),
221 (self.gsm_clck_ctrl, 'measurements'))
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700222
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200223 self.msg_connect(
224 (self.gsm_clck_ctrl, 'ctrl'),
225 (self.msg_to_tag_src, 'msg'))
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700226
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200227 self.msg_connect(
228 (self.gsm_clck_ctrl, 'ctrl'),
229 (self.dict_toggle_sign, 'dict_in'))
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700230
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200231 self.msg_connect(
232 (self.dict_toggle_sign, 'dict_out'),
233 (self.msg_to_tag_sink, 'msg'))
Piotr Krysik902f4eb2017-09-19 08:04:33 +0200234
Vadim Yanitskiy3120ba72019-01-19 11:55:01 +0700235 def phy_init_source(self):
236 raise NotImplementedError
Vadim Yanitskiy0017a352018-03-03 23:37:30 +0700237
Vadim Yanitskiy3120ba72019-01-19 11:55:01 +0700238 def phy_init_sink(self):
239 raise NotImplementedError
Vadim Yanitskiy0017a352018-03-03 23:37:30 +0700240
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200241 def shutdown(self):
242 print("[i] Shutdown Radio interface")
243 self.stop()
244 self.wait()
Piotr Krysik902f4eb2017-09-19 08:04:33 +0200245
Vadim Yanitskiyb7a272e2019-01-19 10:18:10 +0700246 @property
247 def ready(self):
248 # RX / TX frequencies shall be set
249 if self.rx_freq is None:
250 return False
251 if self.tx_freq is None:
252 return False
253
254 return True
255
Vadim Yanitskiybaebe452019-01-19 10:20:59 +0700256 def reset(self):
257 # TODO: do we need to reset both RX / TX freq.?
258 # self.rx_freq = None
259 # self.tx_freq = None
260 self.set_ta(0)
261
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200262 def calc_phase_inc(self, fc):
263 return self.ppm / 1.0e6 * 2 * pi * fc / self.sample_rate
Vadim Yanitskiyd222ee52017-12-03 23:49:09 +0700264
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200265 def set_rx_freq(self, fc):
Vadim Yanitskiy8bd9e152019-01-19 10:35:55 +0700266 if self.freq_offset != 0:
267 fc += self.freq_offset
268 print("[#] Shifting RX freq. to %s (offset is %s)"
269 % (eng_notation.num_to_str(fc),
270 eng_notation.num_to_str(self.freq_offset)))
Vadim Yanitskiy3120ba72019-01-19 11:55:01 +0700271
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200272 self.rotator_src.set_phase_inc(self.calc_phase_inc(fc))
Vadim Yanitskiy5823a412019-01-19 10:39:51 +0700273 self.gsm_clck_ctrl.set_fc(fc)
Vadim Yanitskiy3120ba72019-01-19 11:55:01 +0700274 self.phy_set_rx_freq(fc)
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200275 self.rx_freq = fc
Vadim Yanitskiy89aa4692017-11-14 00:15:20 +0700276
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200277 def set_tx_freq(self, fc):
Vadim Yanitskiy8bd9e152019-01-19 10:35:55 +0700278 if self.freq_offset != 0:
279 fc += self.freq_offset
280 print("[#] Shifting TX freq. to %s (offset is %s)"
281 % (eng_notation.num_to_str(fc),
282 eng_notation.num_to_str(self.freq_offset)))
Vadim Yanitskiy3120ba72019-01-19 11:55:01 +0700283
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200284 self.rotator_sink.set_phase_inc(-self.calc_phase_inc(fc))
Vadim Yanitskiy3120ba72019-01-19 11:55:01 +0700285 self.phy_set_tx_freq(fc)
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200286 self.tx_freq = fc
Piotr Krysik902f4eb2017-09-19 08:04:33 +0200287
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200288 def set_rx_gain(self, gain):
Vadim Yanitskiy3120ba72019-01-19 11:55:01 +0700289 self.phy_set_rx_gain(gain)
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200290 self.rx_gain = gain
Vadim Yanitskiy01c6afd2017-10-19 01:14:24 +0700291
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200292 def set_tx_gain(self, gain):
Vadim Yanitskiy3120ba72019-01-19 11:55:01 +0700293 self.phy_set_tx_gain(gain)
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200294 self.tx_gain = gain
Vadim Yanitskiy34266e72017-12-05 01:01:43 +0700295
Vadim Yanitskiy180a0372019-01-19 10:22:59 +0700296 def set_slot(self, slot, config):
297 print("[i] Configure timeslot filter to: %s"
298 % ("drop all" if config == 0 else "tn=%d" % slot))
299
300 if config == 0:
301 # Value 0 is used for deactivation
302 self.ts_filter.set_policy(grgsm.FILTER_POLICY_DROP_ALL)
303 else:
304 # FIXME: ideally, we should (re)configure the Receiver
305 # block, but there is no API for that, and hard-coded
306 # timeslot configuration is used...
307 self.ts_filter.set_policy(grgsm.FILTER_POLICY_DEFAULT)
308 self.ts_filter.set_tn(slot)
309
Piotr Krysik1cc264f2018-09-06 19:57:57 +0200310 def set_ta(self, ta):
311 print("[i] Setting TA value %d" % ta)
312 advance_time_sec = ta * self.GSM_SYM_PERIOD_uS * 1e-6
313 self.tx_time_setter.set_timing_advance(advance_time_sec)
Vadim Yanitskiy7da82f42019-01-19 09:28:23 +0700314
315 def measure(self, freq):
316 # HACK: generate a random low RSSI value
317 return random.randint(-120, -100)