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