Fixing benchmark loopback to work with new v2's of modulators.
[debian/gnuradio] / gnuradio-examples / python / digital / benchmark_qt_loopback2.py
1 #!/usr/bin/env python
2 #
3 # Copyright 2010 Free Software Foundation, Inc.
4
5 # This file is part of GNU Radio
6
7 # GNU Radio is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3, or (at your option)
10 # any later version.
11
12 # GNU Radio is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16
17 # You should have received a copy of the GNU General Public License
18 # along with GNU Radio; see the file COPYING.  If not, write to
19 # the Free Software Foundation, Inc., 51 Franklin Street,
20 # Boston, MA 02110-1301, USA.
21
22
23 from gnuradio import gr, gru, modulation_utils2
24 from gnuradio import eng_notation
25 from gnuradio.eng_option import eng_option
26 from optparse import OptionParser
27 import random, time, struct, sys, os, math
28
29 from threading import Thread
30
31 # from current dir
32 from transmit_path import transmit_path
33 from receive_path import receive_path
34
35 try:
36     from gnuradio.qtgui import qtgui
37     from PyQt4 import QtGui, QtCore
38     import sip
39 except ImportError:
40     print "Please install gr-qtgui."
41     sys.exit(1)
42     
43 try:
44     from qt_digital_window2 import Ui_DigitalWindow
45 except ImportError:
46     print "Error: could not find qt_digital_window2.py:"
47     print "\t\"pyuic4 qt_digital_window2.ui -o qt_digital_window2.py\""
48     sys.exit(1)
49
50
51 #print os.getpid()
52 #raw_input()
53
54
55 # ////////////////////////////////////////////////////////////////////
56 #        Define the QT Interface and Control Dialog
57 # ////////////////////////////////////////////////////////////////////
58
59
60 class dialog_box(QtGui.QMainWindow):
61     def __init__(self, snkTx, snkRx, fg, parent=None):
62
63         QtGui.QWidget.__init__(self, parent)
64         self.gui = Ui_DigitalWindow()
65         self.gui.setupUi(self)
66
67         self.fg = fg
68
69         self.set_sample_rate(self.fg.sample_rate())
70
71         self.set_snr(self.fg.snr())
72         self.set_frequency(self.fg.frequency_offset())
73         self.set_time_offset(self.fg.timing_offset())
74
75         self.set_gain_clock(self.fg.rx_gain_clock())
76         self.set_gain_phase(self.fg.rx_gain_phase())
77         self.set_gain_freq(self.fg.rx_gain_freq())
78
79         # Add the qtsnk widgets to the hlayout box
80         self.gui.sinkLayout.addWidget(snkTx)
81         self.gui.sinkLayout.addWidget(snkRx)
82
83
84         # Connect up some signals
85         self.connect(self.gui.pauseButton, QtCore.SIGNAL("clicked()"),
86                      self.pauseFg)
87
88         self.connect(self.gui.sampleRateEdit, QtCore.SIGNAL("editingFinished()"),
89                      self.sampleRateEditText)
90
91         self.connect(self.gui.snrEdit, QtCore.SIGNAL("editingFinished()"),
92                      self.snrEditText)
93         self.connect(self.gui.freqEdit, QtCore.SIGNAL("editingFinished()"),
94                      self.freqEditText)
95         self.connect(self.gui.timeEdit, QtCore.SIGNAL("editingFinished()"),
96                      self.timeEditText)
97
98         self.connect(self.gui.gainClockEdit, QtCore.SIGNAL("editingFinished()"),
99                      self.gainClockEditText)
100         self.connect(self.gui.gainPhaseEdit, QtCore.SIGNAL("editingFinished()"),
101                      self.gainPhaseEditText)
102         self.connect(self.gui.gainFreqEdit, QtCore.SIGNAL("editingFinished()"),
103                      self.gainFreqEditText)
104
105         # Build a timer to update the packet number and PER fields
106         self.update_delay = 250  # time between updating packet rate fields
107         self.pkt_timer = QtCore.QTimer(self)
108         self.connect(self.pkt_timer, QtCore.SIGNAL("timeout()"),
109                      self.updatePacketInfo)
110         self.pkt_timer.start(self.update_delay)
111
112     def pauseFg(self):
113         if(self.gui.pauseButton.text() == "Pause"):
114             self.fg.stop()
115             self.fg.wait()
116             self.gui.pauseButton.setText("Unpause")
117         else:
118             self.fg.start()
119             self.gui.pauseButton.setText("Pause")
120
121     # Accessor functions for Gui to manipulate system parameters
122     def set_sample_rate(self, sr):
123         ssr = eng_notation.num_to_str(sr)
124         self.gui.sampleRateEdit.setText(QtCore.QString("%1").arg(ssr))
125
126     def sampleRateEditText(self):
127         try:
128             rate = self.gui.sampleRateEdit.text().toAscii()
129             srate = eng_notation.str_to_num(rate)
130             #self.fg.set_sample_rate(srate)
131         except RuntimeError:
132             pass
133
134
135     # Accessor functions for Gui to manipulate channel model
136     def set_snr(self, snr):
137         self.gui.snrEdit.setText(QtCore.QString("%1").arg(snr))
138
139     def set_frequency(self, fo):
140         self.gui.freqEdit.setText(QtCore.QString("%1").arg(fo))
141
142     def set_time_offset(self, to):
143         self.gui.timeEdit.setText(QtCore.QString("%1").arg(to))
144
145     def snrEditText(self):
146         try:
147             snr = self.gui.snrEdit.text().toDouble()[0]
148             self.fg.set_snr(snr)
149         except RuntimeError:
150             pass
151
152     def freqEditText(self):
153         try:
154             freq = self.gui.freqEdit.text().toDouble()[0]
155             self.fg.set_frequency_offset(freq)
156         except RuntimeError:
157             pass
158
159     def timeEditText(self):
160         try:
161             to = self.gui.timeEdit.text().toDouble()[0]
162             self.fg.set_timing_offset(to)
163         except RuntimeError:
164             pass
165
166
167     # Accessor functions for Gui to manipulate receiver parameters
168     def set_gain_clock(self, gain):
169         self.gui.gainClockEdit.setText(QtCore.QString("%1").arg(gain))
170
171     def set_gain_phase(self, gain_phase):
172         self.gui.gainPhaseEdit.setText(QtCore.QString("%1").arg(gain_phase))
173
174     def set_gain_freq(self, gain_freq):
175         self.gui.gainFreqEdit.setText(QtCore.QString("%1").arg(gain_freq))
176         
177
178     def set_alpha_time(self, alpha):
179         self.gui.alphaTimeEdit.setText(QtCore.QString("%1").arg(alpha))
180
181     def set_beta_time(self, beta):
182         self.gui.betaTimeEdit.setText(QtCore.QString("%1").arg(beta))
183
184     def set_alpha_phase(self, alpha):
185         self.gui.alphaPhaseEdit.setText(QtCore.QString("%1").arg(alpha))
186
187     def gainPhaseEditText(self):
188         try:
189             gain_phase = self.gui.gainPhaseEdit.text().toDouble()[0]
190             self.fg.set_rx_gain_phase(gain_phase)
191         except RuntimeError:
192             pass
193
194     def gainClockEditText(self):
195         try:
196             gain = self.gui.gainClockEdit.text().toDouble()[0]
197             self.fg.set_rx_gain_clock(gain)
198         except RuntimeError:
199             pass
200
201     def gainFreqEditText(self):
202         try:
203             gain = self.gui.gainFreqEdit.text().toDouble()[0]
204             self.fg.set_rx_gain_freq(gain)
205         except RuntimeError:
206             pass
207
208     # Accessor functions for packet error reporting
209     def updatePacketInfo(self):
210         # Pull these globals in from the main thread
211         global n_rcvd, n_right, pktno
212
213         if(pktno > 0):
214             per = float(n_rcvd - n_right)/float(pktno)
215         else:
216             per = 0
217         self.gui.pktsRcvdEdit.setText(QtCore.QString("%1").arg(n_rcvd))
218         self.gui.pktsCorrectEdit.setText(QtCore.QString("%1").arg(n_right))
219         self.gui.perEdit.setText(QtCore.QString("%1").arg(float(per), 0, 'e', 4))
220
221
222
223 # ////////////////////////////////////////////////////////////////////
224 #        Define the GNU Radio Top Block
225 # ////////////////////////////////////////////////////////////////////
226
227
228
229 class my_top_block(gr.top_block):
230     def __init__(self, mod_class, demod_class, rx_callback, options):
231         gr.top_block.__init__(self)
232
233         self._sample_rate = options.sample_rate
234
235         channelon = True;
236
237         self.gui_on = options.gui
238
239         self._frequency_offset = options.frequency_offset
240         self._timing_offset = options.timing_offset
241         self._tx_amplitude = options.tx_amplitude
242         self._snr_dB = options.snr
243
244         self._noise_voltage = self.get_noise_voltage(self._snr_dB)
245
246         # With new interface, sps does not get set by default, but
247         # in the loopback, we don't recalculate it; so just force it here
248         if(options.samples_per_symbol == None):
249             options.samples_per_symbol = 2
250
251         self.txpath = transmit_path(mod_class, options)
252         self.throttle = gr.throttle(gr.sizeof_gr_complex, self.sample_rate())
253         self.rxpath = receive_path(demod_class, rx_callback, options)
254
255         # FIXME: do better exposure to lower issues for control
256         self._gain_clock = self.rxpath.packet_receiver._demodulator._timing_alpha
257         self._gain_phase = self.rxpath.packet_receiver._demodulator._phase_alpha
258         self._gain_freq  = self.rxpath.packet_receiver._demodulator._freq_alpha
259
260         if channelon:
261             self.channel = gr.channel_model(self._noise_voltage,
262                                             self.frequency_offset(),
263                                             self.timing_offset())
264             
265             if options.discontinuous:
266                 z = 20000*[0,]
267                 self.zeros = gr.vector_source_c(z, True)
268                 packet_size = 5*((4+8+4+1500+4) * 8)
269                 self.mux = gr.stream_mux(gr.sizeof_gr_complex, [packet_size-0, int(9e5)])
270
271                 # Connect components
272                 self.connect(self.txpath, self.throttle, (self.mux,0))
273                 self.connect(self.zeros, (self.mux,1))
274                 self.connect(self.mux, self.channel, self.rxpath)
275
276             else:
277                 self.connect(self.txpath, self.throttle, self.channel, self.rxpath)
278
279             if self.gui_on:
280                 self.qapp = QtGui.QApplication(sys.argv)
281                 fftsize = 2048
282
283                 self.snk_tx = qtgui.sink_c(fftsize, gr.firdes.WIN_BLACKMAN_hARRIS,
284                                            0, 1,
285                                            "Tx", True, True, False, True, True)
286                 self.snk_rx = qtgui.sink_c(fftsize, gr.firdes.WIN_BLACKMAN_hARRIS,
287                                            0, 1,
288                                            "Rx", True, True, False, True, True)
289
290                 self.snk_tx.set_frequency_axis(-80, 0)
291                 self.snk_rx.set_frequency_axis(-60, 20)
292
293                 self.freq_recov = self.rxpath.packet_receiver._demodulator.freq_recov
294                 self.phase_recov = self.rxpath.packet_receiver._demodulator.phase_recov
295                 self.time_recov = self.rxpath.packet_receiver._demodulator.time_recov
296                 self.freq_recov.set_alpha(self._gain_freq)
297                 self.freq_recov.set_beta(self._gain_freq/10.0)
298                 self.phase_recov.set_alpha(self._gain_phase)
299                 self.phase_recov.set_beta(0.25*self._gain_phase*self._gain_phase)
300                 self.time_recov.set_alpha(self._gain_clock)
301                 self.time_recov.set_beta(0.25*self._gain_clock*self._gain_clock)
302
303                 # Connect to the QT sinks
304                 # FIXME: make better exposure to receiver from rxpath
305                 self.connect(self.channel, self.snk_tx)
306                 self.connect(self.phase_recov, self.snk_rx)
307                 #self.connect(self.freq_recov, self.snk_rx)
308
309                 pyTxQt  = self.snk_tx.pyqwidget()
310                 pyTx = sip.wrapinstance(pyTxQt, QtGui.QWidget)
311                  
312                 pyRxQt  = self.snk_rx.pyqwidget()
313                 pyRx = sip.wrapinstance(pyRxQt, QtGui.QWidget)
314
315                 self.main_box = dialog_box(pyTx, pyRx, self)
316                 self.main_box.show()
317                 
318         else:
319             # Connect components
320             self.connect(self.txpath, self.throttle, self.rxpath)
321
322
323
324     # System Parameters
325     def sample_rate(self):
326         return self._sample_rate
327     
328     def set_sample_rate(self, sr):
329         self._sample_rate = sr
330         #self.throttle.set_samples_per_second(self._sample_rate)
331
332     # Channel Model Parameters
333     def snr(self):
334         return self._snr_dB
335     
336     def set_snr(self, snr):
337         self._snr_dB = snr
338         self._noise_voltage = self.get_noise_voltage(self._snr_dB)
339         self.channel.set_noise_voltage(self._noise_voltage)
340
341     def get_noise_voltage(self, SNR):
342         snr = 10.0**(SNR/10.0)        
343         power_in_signal = abs(self._tx_amplitude)**2
344         noise_power = power_in_signal/snr
345         noise_voltage = math.sqrt(noise_power)
346         return noise_voltage
347
348     def frequency_offset(self):
349         return self._frequency_offset * self.sample_rate()
350
351     def set_frequency_offset(self, fo):
352         self._frequency_offset = fo / self.sample_rate()
353         self.channel.set_frequency_offset(self._frequency_offset)
354
355     def timing_offset(self):
356         return self._timing_offset
357     
358     def set_timing_offset(self, to):
359         self._timing_offset = to
360         self.channel.set_timing_offset(self._timing_offset)
361
362
363     # Receiver Parameters
364     def rx_gain_clock(self):
365         return self._gain_clock
366
367     def rx_gain_clock_beta(self):
368         return self._gain_clock_beta
369
370     def set_rx_gain_clock(self, gain):
371         self._gain_clock = gain
372         self._gain_clock_beta = .25 * self._gain_clock * self._gain_clock
373         self.rxpath.packet_receiver._demodulator.time_recov.set_alpha(self._gain_clock)
374         self.rxpath.packet_receiver._demodulator.time_recov.set_beta(self._gain_clock_beta)
375
376     def rx_gain_phase(self):
377         return self._gain_phase
378
379     def rx_gain_phase_beta(self):
380         return self._gain_phase_beta
381     
382     def set_rx_gain_phase(self, gain_phase):
383         self._gain_phase = gain_phase
384         self._gain_phase_beta = .25 * self._gain_phase * self._gain_phase
385         self.rxpath.packet_receiver._demodulator.phase_recov.set_alpha(self._gain_phase)
386         self.rxpath.packet_receiver._demodulator.phase_recov.set_beta(self._gain_phase_beta)
387
388
389     def rx_gain_freq(self):
390         return self._gain_freq
391
392     def set_rx_gain_freq(self, gain_freq):
393         self._gain_freq = gain_freq
394         #self._gain_freq_beta = .25 * self._gain_freq * self._gain_freq
395         self.rxpath.packet_receiver._demodulator.freq_recov.set_alpha(self._gain_freq)
396         self.rxpath.packet_receiver._demodulator.freq_recov.set_beta(self._gain_freq/10.0)
397         #self.rxpath.packet_receiver._demodulator.freq_recov.set_beta(self._gain_fre_beta)
398
399
400 # /////////////////////////////////////////////////////////////////////////////
401 #       Thread to handle the packet sending procedure
402 #          Operates in parallel with qApp.exec_()       
403 # /////////////////////////////////////////////////////////////////////////////
404
405
406
407 class th_send(Thread):
408     def __init__(self, send_fnc, megs, sz):
409         Thread.__init__(self)
410         self.send = send_fnc
411         self.nbytes = int(1e6 * megs)
412         self.pkt_size = int(sz)
413
414     def run(self):
415         # generate and send packets
416         n = 0
417         pktno = 0
418         
419         while n < self.nbytes:
420             self.send(struct.pack('!H', pktno & 0xffff) +
421                       (self.pkt_size - 2) * chr(pktno & 0xff))
422             n += self.pkt_size
423             pktno += 1
424             
425         self.send(eof=True)
426
427     def stop(self):
428         self.nbytes = 0
429
430
431
432 # /////////////////////////////////////////////////////////////////////////////
433 #                                   main
434 # /////////////////////////////////////////////////////////////////////////////
435
436
437
438 def main():
439
440     global n_rcvd, n_right, pktno
441
442     n_rcvd = 0
443     n_right = 0
444     pktno = 0
445     
446     def rx_callback(ok, payload):
447         global n_rcvd, n_right, pktno
448         (pktno,) = struct.unpack('!H', payload[0:2])
449         n_rcvd += 1
450         if ok:
451             n_right += 1
452
453         if not options.gui:
454             print "ok = %5s  pktno = %4d  n_rcvd = %4d  n_right = %4d" % (
455                 ok, pktno, n_rcvd, n_right)
456         
457
458     def send_pkt(payload='', eof=False):
459         return tb.txpath.send_pkt(payload, eof)
460
461     mods = modulation_utils2.type_1_mods()
462     demods = modulation_utils2.type_1_demods()
463
464     parser = OptionParser(option_class=eng_option, conflict_handler="resolve")
465     expert_grp = parser.add_option_group("Expert")
466     channel_grp = parser.add_option_group("Channel")
467
468     parser.add_option("-m", "--modulation", type="choice", choices=mods.keys(),
469                       default='dbpsk2',
470                       help="Select modulation from: %s [default=%%default]"
471                             % (', '.join(mods.keys()),))
472
473     parser.add_option("-s", "--size", type="eng_float", default=1500,
474                       help="set packet size [default=%default]")
475     parser.add_option("-M", "--megabytes", type="eng_float", default=1.0,
476                       help="set megabytes to transmit [default=%default]")
477     parser.add_option("","--discontinuous", action="store_true", default=False,
478                       help="enable discontinous transmission (bursts of 5 packets)")
479     parser.add_option("-G", "--gui", action="store_true", default=False,
480                       help="Turn on the GUI [default=%default]")
481
482     channel_grp.add_option("", "--sample-rate", type="eng_float", default=1e5,
483                            help="set speed of channel/simulation rate to RATE [default=%default]") 
484     channel_grp.add_option("", "--snr", type="eng_float", default=30,
485                            help="set the SNR of the channel in dB [default=%default]")
486     channel_grp.add_option("", "--frequency-offset", type="eng_float", default=0,
487                            help="set frequency offset introduced by channel [default=%default]")
488     channel_grp.add_option("", "--timing-offset", type="eng_float", default=1.0,
489                            help="set timing offset introduced by channel [default=%default]")
490     channel_grp.add_option("", "--seed", action="store_true", default=False,
491                            help="use a random seed for AWGN noise [default=%default]")
492
493     transmit_path.add_options(parser, expert_grp)
494     receive_path.add_options(parser, expert_grp)
495
496     for mod in mods.values():
497         mod.add_options(expert_grp)
498     for demod in demods.values():
499         demod.add_options(expert_grp)
500
501     (options, args) = parser.parse_args ()
502
503     if len(args) != 0:
504         parser.print_help()
505         sys.exit(1)
506         
507     r = gr.enable_realtime_scheduling()
508     if r != gr.RT_OK:
509         print "Warning: failed to enable realtime scheduling"
510         
511     # Create an instance of a hierarchical block
512     tb = my_top_block(mods[options.modulation],
513                       demods[options.modulation],
514                       rx_callback, options)
515     tb.start()
516
517     packet_sender = th_send(send_pkt, options.megabytes, options.size)
518     packet_sender.start()
519
520     if(options.gui):
521         tb.qapp.exec_()
522         packet_sender.stop()
523     else:
524         # Process until done; hack in to the join to stop on an interrupt
525         while(packet_sender.isAlive()):
526             try:
527                 packet_sender.join(1)
528             except KeyboardInterrupt:
529                 packet_sender.stop()
530         
531     
532 if __name__ == '__main__':
533     try:
534         main()
535     except KeyboardInterrupt:
536         pass