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