35cc2e376d16bda6e51bedd57e9df484e9fae3ff
[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_timing_gain_alpha())
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_timing_gain_alpha(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._timing_gain_alpha = self.rxpath.packet_receiver._demodulator._timing_alpha
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
238                 self.snk_tx.set_frequency_axis(-80, 0)
239                 self.snk_rx.set_frequency_axis(-60, 20)
240             
241                 # Connect to the QT sinks
242                 # FIXME: make better exposure to receiver from rxpath
243                 self.freq_recov = self.rxpath.packet_receiver._demodulator.clock_recov
244                 self.time_recov = self.rxpath.packet_receiver._demodulator.time_recov
245                 self.freq_recov.set_alpha(0)
246                 self.freq_recov.set_beta(0)
247                 self.time_recov.set_alpha(2)
248                 self.time_recov.set_beta(0.02)
249                 self.connect(self.channel, self.snk_tx)
250                 self.connect(self.time_recov, self.snk_rx)
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                 self.main_box = dialog_box(pyTx, pyRx, self)
259                 self.main_box.show()
260                 
261         else:
262             # Connect components
263             self.connect(self.txpath, self.throttle, self.rxpath)
264
265
266
267     # System Parameters
268     def sample_rate(self):
269         return self._sample_rate
270     
271     def set_sample_rate(self, sr):
272         self._sample_rate = sr
273         #self.throttle.set_samples_per_second(self._sample_rate)
274
275     # Channel Model Parameters
276     def snr(self):
277         return self._snr_dB
278     
279     def set_snr(self, snr):
280         self._snr_dB = snr
281         self._noise_voltage = self.get_noise_voltage(self._snr_dB)
282         self.channel.set_noise_voltage(self._noise_voltage)
283
284     def get_noise_voltage(self, SNR):
285         snr = 10.0**(SNR/10.0)        
286         power_in_signal = abs(self._tx_amplitude)**2
287         noise_power = power_in_signal/snr
288         noise_voltage = math.sqrt(noise_power)
289         return noise_voltage
290
291     def frequency_offset(self):
292         return self._frequency_offset * self.sample_rate()
293
294     def set_frequency_offset(self, fo):
295         self._frequency_offset = fo / self.sample_rate()
296         self.channel.set_frequency_offset(self._frequency_offset)
297
298     def timing_offset(self):
299         return self._timing_offset
300     
301     def set_timing_offset(self, to):
302         self._timing_offset = to
303         self.channel.set_timing_offset(self._timing_offset)
304
305
306     # Receiver Parameters
307     def rx_timing_gain_alpha(self):
308         return self._timing_gain_alpha
309
310     def rx_timing_gain_beta(self):
311         return self._timing_gain_beta
312     
313     def set_rx_timing_gain_alpha(self, gain):
314         self._timing_gain_alpha = gain
315         self.time_recov.set_gain(self._timing_gain_alpha)
316
317     def rx_alpha(self):
318         return self._alpha
319
320     def rx_beta(self):
321         return self.beta
322     
323     def set_rx_alpha(self, alpha):
324         self._alpha = alpha
325         self.beta = .25 * self._alpha * self._alpha
326         #self.freq_recov.set_alpha(self._alpha)
327         #self.freq_recov.set_beta(self.beta)
328
329
330
331 # /////////////////////////////////////////////////////////////////////////////
332 #       Thread to handle the packet sending procedure
333 #          Operates in parallel with qApp.exec_()       
334 # /////////////////////////////////////////////////////////////////////////////
335
336
337
338 class th_send(Thread):
339     def __init__(self, send_fnc, megs, sz):
340         Thread.__init__(self)
341         self.send = send_fnc
342         self.nbytes = int(1e6 * megs)
343         self.pkt_size = int(sz)
344
345     def run(self):
346         # generate and send packets
347         n = 0
348         pktno = 0
349         
350         while n < self.nbytes:
351             self.send(struct.pack('!H', pktno & 0xffff) +
352                       (self.pkt_size - 2) * chr(pktno & 0xff))
353             n += self.pkt_size
354             pktno += 1
355             
356         self.send(eof=True)
357
358     def stop(self):
359         self.nbytes = 0
360
361
362
363 # /////////////////////////////////////////////////////////////////////////////
364 #                                   main
365 # /////////////////////////////////////////////////////////////////////////////
366
367
368
369 def main():
370
371     global n_rcvd, n_right, pktno
372
373     n_rcvd = 0
374     n_right = 0
375     pktno = 0
376     
377     def rx_callback(ok, payload):
378         global n_rcvd, n_right, pktno
379         (pktno,) = struct.unpack('!H', payload[0:2])
380         n_rcvd += 1
381         if ok:
382             n_right += 1
383
384         if not options.gui:
385             print "ok = %5s  pktno = %4d  n_rcvd = %4d  n_right = %4d" % (
386                 ok, pktno, n_rcvd, n_right)
387         
388
389     def send_pkt(payload='', eof=False):
390         return tb.txpath.send_pkt(payload, eof)
391
392     mods = modulation_utils.type_1_mods()
393     demods = modulation_utils.type_1_demods()
394
395     parser = OptionParser(option_class=eng_option, conflict_handler="resolve")
396     expert_grp = parser.add_option_group("Expert")
397     channel_grp = parser.add_option_group("Channel")
398
399     parser.add_option("-m", "--modulation", type="choice", choices=mods.keys(),
400                       default='dbpsk',
401                       help="Select modulation from: %s [default=%%default]"
402                             % (', '.join(mods.keys()),))
403
404     parser.add_option("-s", "--size", type="eng_float", default=1500,
405                       help="set packet size [default=%default]")
406     parser.add_option("-M", "--megabytes", type="eng_float", default=1.0,
407                       help="set megabytes to transmit [default=%default]")
408     parser.add_option("","--discontinuous", action="store_true", default=False,
409                       help="enable discontinous transmission (bursts of 5 packets)")
410     parser.add_option("-G", "--gui", action="store_true", default=False,
411                       help="Turn on the GUI [default=%default]")
412
413     channel_grp.add_option("", "--sample-rate", type="eng_float", default=1e5,
414                            help="set speed of channel/simulation rate to RATE [default=%default]") 
415     channel_grp.add_option("", "--snr", type="eng_float", default=30,
416                            help="set the SNR of the channel in dB [default=%default]")
417     channel_grp.add_option("", "--frequency-offset", type="eng_float", default=0,
418                            help="set frequency offset introduced by channel [default=%default]")
419     channel_grp.add_option("", "--timing-offset", type="eng_float", default=1.0,
420                            help="set timing offset introduced by channel [default=%default]")
421     channel_grp.add_option("", "--seed", action="store_true", default=False,
422                            help="use a random seed for AWGN noise [default=%default]")
423
424     transmit_path.add_options(parser, expert_grp)
425     receive_path.add_options(parser, expert_grp)
426
427     for mod in mods.values():
428         mod.add_options(expert_grp)
429     for demod in demods.values():
430         demod.add_options(expert_grp)
431
432     (options, args) = parser.parse_args ()
433
434     if len(args) != 0:
435         parser.print_help()
436         sys.exit(1)
437         
438     r = gr.enable_realtime_scheduling()
439     if r != gr.RT_OK:
440         print "Warning: failed to enable realtime scheduling"
441         
442     # Create an instance of a hierarchical block
443     tb = my_top_block(mods[options.modulation],
444                       demods[options.modulation],
445                       rx_callback, options)
446     tb.start()
447
448     packet_sender = th_send(send_pkt, options.megabytes, options.size)
449     packet_sender.start()
450
451     if(options.gui):
452         tb.qapp.exec_()
453         packet_sender.stop()
454     else:
455         # Process until done; hack in to the join to stop on an interrupt
456         while(packet_sender.isAlive()):
457             try:
458                 packet_sender.join(1)
459             except KeyboardInterrupt:
460                 packet_sender.stop()
461         
462     
463 if __name__ == '__main__':
464     try:
465         main()
466     except KeyboardInterrupt:
467         pass