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