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