Fixing benchmark program to work with original dbpsk/dqpsk modulations again (and...
[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         if(pktno > 0):
171             per = float(n_rcvd - n_right)/float(pktno)
172         else:
173             per = 0
174         self.gui.pktsRcvdEdit.setText(QtCore.QString("%1").arg(n_rcvd))
175         self.gui.pktsCorrectEdit.setText(QtCore.QString("%1").arg(n_right))
176         self.gui.perEdit.setText(QtCore.QString("%1").arg(per))
177
178
179
180 # ////////////////////////////////////////////////////////////////////
181 #        Define the GNU Radio Top Block
182 # ////////////////////////////////////////////////////////////////////
183
184
185
186 class my_top_block(gr.top_block):
187     def __init__(self, mod_class, demod_class, rx_callback, options):
188         gr.top_block.__init__(self)
189
190         self._sample_rate = options.sample_rate
191
192         if(options.samples_per_symbol is None):
193             options.samples_per_symbol = 2
194
195         channelon = True;
196
197         self.gui_on = options.gui
198
199         self._frequency_offset = options.frequency_offset
200         self._timing_offset = options.timing_offset
201         self._tx_amplitude = options.tx_amplitude
202         self._snr_dB = options.snr
203
204         self._noise_voltage = self.get_noise_voltage(self._snr_dB)
205
206         self.txpath = transmit_path(mod_class, options)
207         self.throttle = gr.throttle(gr.sizeof_gr_complex, self.sample_rate())
208         self.rxpath = receive_path(demod_class, rx_callback, options)
209
210         # FIXME: do better exposure to lower issues for control
211         self._timing_gain_alpha = self.rxpath.packet_receiver._demodulator._mm_gain_mu
212         self._alpha = self.rxpath.packet_receiver._demodulator._costas_alpha
213
214         if channelon:
215             self.channel = gr.channel_model(self._noise_voltage,
216                                             self.frequency_offset(),
217                                             self.timing_offset())
218             
219             if options.discontinuous:
220                 z = 20000*[0,]
221                 self.zeros = gr.vector_source_c(z, True)
222                 packet_size = 5*((4+8+4+1500+4) * 8)
223                 self.mux = gr.stream_mux(gr.sizeof_gr_complex, [packet_size-0, int(9e5)])
224
225                 # Connect components
226                 self.connect(self.txpath, self.throttle, (self.mux,0))
227                 self.connect(self.zeros, (self.mux,1))
228                 self.connect(self.mux, self.channel, self.rxpath)
229
230             else:
231                 self.connect(self.txpath, self.throttle, self.channel, self.rxpath)
232
233             if self.gui_on:
234                 self.qapp = QtGui.QApplication(sys.argv)
235                 fftsize = 2048
236
237                 self.snk_tx = qtgui.sink_c(fftsize, gr.firdes.WIN_BLACKMAN_hARRIS,
238                                            0, self._sample_rate,
239                                            "Tx", True, True, False, True, True)
240                 self.snk_rx = qtgui.sink_c(fftsize, gr.firdes.WIN_BLACKMAN_hARRIS,
241                                            0, self._sample_rate,
242                                            "Rx", True, True, False, True, True)
243
244                 self.snk_tx.set_frequency_axis(-80, 0)
245                 self.snk_rx.set_frequency_axis(-60, 20)
246             
247                 # Connect to the QT sinks
248                 # FIXME: make better exposure to receiver from rxpath
249                 self.receiver = self.rxpath.packet_receiver._demodulator.receiver
250                 self.receiver.set_alpha(2)
251                 self.receiver.set_beta(0.02)
252                 self.connect(self.channel, self.snk_tx)
253                 self.connect(self.receiver, self.snk_rx)
254
255                 pyTxQt  = self.snk_tx.pyqwidget()
256                 pyTx = sip.wrapinstance(pyTxQt, QtGui.QWidget)
257                  
258                 pyRxQt  = self.snk_rx.pyqwidget()
259                 pyRx = 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_timing_gain_alpha(self):
311         return self._timing_gain_alpha
312
313     def rx_timing_gain_beta(self):
314         return self._timing_gain_beta
315     
316     def set_rx_timing_gain_alpha(self, gain):
317         self._timing_gain_alpha = gain
318         self.receiver.set_gain_mu(self._timing_gain_alpha)
319
320     def rx_alpha(self):
321         return self._alpha
322
323     def rx_beta(self):
324         return self.beta
325     
326     def set_rx_alpha(self, alpha):
327         self._alpha = alpha
328         self.beta = .25 * self._alpha * self._alpha
329         self.receiver.set_alpha(self._alpha)
330         self.receiver.set_beta(self.beta)
331
332
333
334 # /////////////////////////////////////////////////////////////////////////////
335 #       Thread to handle the packet sending procedure
336 #          Operates in parallel with qApp.exec_()       
337 # /////////////////////////////////////////////////////////////////////////////
338
339
340
341 class th_send(Thread):
342     def __init__(self, send_fnc, megs, sz):
343         Thread.__init__(self)
344         self.send = send_fnc
345         self.nbytes = int(1e6 * megs)
346         self.pkt_size = int(sz)
347
348     def run(self):
349         # generate and send packets
350         n = 0
351         pktno = 0
352         
353         while n < self.nbytes:
354             self.send(struct.pack('!H', pktno & 0xffff) +
355                       (self.pkt_size - 2) * chr(pktno & 0xff))
356             n += self.pkt_size
357             pktno += 1
358             
359         self.send(eof=True)
360
361     def stop(self):
362         self.nbytes = 0
363
364
365
366 # /////////////////////////////////////////////////////////////////////////////
367 #                                   main
368 # /////////////////////////////////////////////////////////////////////////////
369
370
371
372 def main():
373
374     global n_rcvd, n_right, pktno
375
376     n_rcvd = 0
377     n_right = 0
378     pktno = 0
379     
380     def rx_callback(ok, payload):
381         global n_rcvd, n_right, pktno
382         (pktno,) = struct.unpack('!H', payload[0:2])
383         n_rcvd += 1
384         if ok:
385             n_right += 1
386
387         if not options.gui:
388             print "ok = %5s  pktno = %4d  n_rcvd = %4d  n_right = %4d" % (
389                 ok, pktno, n_rcvd, n_right)
390         
391
392     def send_pkt(payload='', eof=False):
393         return tb.txpath.send_pkt(payload, eof)
394
395     mods = modulation_utils.type_1_mods()
396     demods = modulation_utils.type_1_demods()
397
398     parser = OptionParser(option_class=eng_option, conflict_handler="resolve")
399     expert_grp = parser.add_option_group("Expert")
400     channel_grp = parser.add_option_group("Channel")
401
402     parser.add_option("-m", "--modulation", type="choice", choices=mods.keys(),
403                       default='dbpsk',
404                       help="Select modulation from: %s [default=%%default]"
405                             % (', '.join(mods.keys()),))
406
407     parser.add_option("-s", "--size", type="eng_float", default=1500,
408                       help="set packet size [default=%default]")
409     parser.add_option("-M", "--megabytes", type="eng_float", default=1.0,
410                       help="set megabytes to transmit [default=%default]")
411     parser.add_option("","--discontinuous", action="store_true", default=False,
412                       help="enable discontinous transmission (bursts of 5 packets)")
413     parser.add_option("-G", "--gui", action="store_true", default=False,
414                       help="Turn on the GUI [default=%default]")
415
416     channel_grp.add_option("", "--sample-rate", type="eng_float", default=1e5,
417                            help="set speed of channel/simulation rate to RATE [default=%default]") 
418     channel_grp.add_option("", "--snr", type="eng_float", default=30,
419                            help="set the SNR of the channel in dB [default=%default]")
420     channel_grp.add_option("", "--frequency-offset", type="eng_float", default=0,
421                            help="set frequency offset introduced by channel [default=%default]")
422     channel_grp.add_option("", "--timing-offset", type="eng_float", default=1.0,
423                            help="set timing offset introduced by channel [default=%default]")
424     channel_grp.add_option("", "--seed", action="store_true", default=False,
425                            help="use a random seed for AWGN noise [default=%default]")
426
427     transmit_path.add_options(parser, expert_grp)
428     receive_path.add_options(parser, expert_grp)
429
430     for mod in mods.values():
431         mod.add_options(expert_grp)
432     for demod in demods.values():
433         demod.add_options(expert_grp)
434
435     (options, args) = parser.parse_args ()
436
437     if len(args) != 0:
438         parser.print_help()
439         sys.exit(1)
440         
441     r = gr.enable_realtime_scheduling()
442     if r != gr.RT_OK:
443         print "Warning: failed to enable realtime scheduling"
444         
445     # Create an instance of a hierarchical block
446     tb = my_top_block(mods[options.modulation],
447                       demods[options.modulation],
448                       rx_callback, options)
449     tb.start()
450
451     packet_sender = th_send(send_pkt, options.megabytes, options.size)
452     packet_sender.start()
453
454     if(options.gui):
455         tb.qapp.exec_()
456         packet_sender.stop()
457     else:
458         # Process until done; hack in to the join to stop on an interrupt
459         while(packet_sender.isAlive()):
460             try:
461                 packet_sender.join(1)
462             except KeyboardInterrupt:
463                 packet_sender.stop()
464         
465     
466 if __name__ == '__main__':
467     try:
468         main()
469     except KeyboardInterrupt:
470         pass