Small fixes for loopback with new code.
[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_gain_clock(self.fg.rx_gain_clock())
56         self.set_gain_phase(self.fg.rx_gain_phase())
57         self.set_gain_freq(self.fg.rx_gain_freq())
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.gainClockEdit, QtCore.SIGNAL("editingFinished()"),
79                      self.gainClockEditText)
80         self.connect(self.gui.gainPhaseEdit, QtCore.SIGNAL("editingFinished()"),
81                      self.gainPhaseEditText)
82         self.connect(self.gui.gainFreqEdit, QtCore.SIGNAL("editingFinished()"),
83                      self.gainFreqEditText)
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_gain_clock(self, gain):
149         self.gui.gainClockEdit.setText(QtCore.QString("%1").arg(gain))
150
151     def set_gain_phase(self, gain_phase):
152         self.gui.gainPhaseEdit.setText(QtCore.QString("%1").arg(gain_phase))
153
154     def set_gain_freq(self, gain_freq):
155         self.gui.gainFreqEdit.setText(QtCore.QString("%1").arg(gain_freq))
156         
157
158     def set_alpha_time(self, alpha):
159         self.gui.alphaTimeEdit.setText(QtCore.QString("%1").arg(alpha))
160
161     def set_beta_time(self, beta):
162         self.gui.betaTimeEdit.setText(QtCore.QString("%1").arg(beta))
163
164     def set_alpha_phase(self, alpha):
165         self.gui.alphaPhaseEdit.setText(QtCore.QString("%1").arg(alpha))
166
167     def gainPhaseEditText(self):
168         try:
169             gain_phase = self.gui.gainPhaseEdit.text().toDouble()[0]
170             self.fg.set_rx_gain_phase(gain_phase)
171         except RuntimeError:
172             pass
173
174     def gainClockEditText(self):
175         try:
176             gain = self.gui.gainClockEdit.text().toDouble()[0]
177             self.fg.set_rx_gain_clock(gain)
178         except RuntimeError:
179             pass
180
181     def gainFreqEditText(self):
182         try:
183             gain = self.gui.gainFreqEdit.text().toDouble()[0]
184             self.fg.set_rx_gain_freq(gain)
185         except RuntimeError:
186             pass
187
188     # Accessor functions for packet error reporting
189     def updatePacketInfo(self):
190         # Pull these globals in from the main thread
191         global n_rcvd, n_right, pktno
192
193         if(pktno > 0):
194             per = float(n_rcvd - n_right)/float(pktno)
195         else:
196             per = 0
197         self.gui.pktsRcvdEdit.setText(QtCore.QString("%1").arg(n_rcvd))
198         self.gui.pktsCorrectEdit.setText(QtCore.QString("%1").arg(n_right))
199         self.gui.perEdit.setText(QtCore.QString("%1").arg(float(per), 0, 'e', 4))
200
201
202
203 # ////////////////////////////////////////////////////////////////////
204 #        Define the GNU Radio Top Block
205 # ////////////////////////////////////////////////////////////////////
206
207
208
209 class my_top_block(gr.top_block):
210     def __init__(self, mod_class, demod_class, rx_callback, options):
211         gr.top_block.__init__(self)
212
213         self._sample_rate = options.sample_rate
214
215         channelon = True;
216
217         self.gui_on = options.gui
218
219         self._frequency_offset = options.frequency_offset
220         self._timing_offset = options.timing_offset
221         self._tx_amplitude = options.tx_amplitude
222         self._snr_dB = options.snr
223
224         self._noise_voltage = self.get_noise_voltage(self._snr_dB)
225
226         # With new interface, sps does not get set by default, but
227         # in the loopback, we don't recalculate it; so just force it here
228         if(options.samples_per_symbol == None):
229             options.samples_per_symbol = 2
230
231         self.txpath = transmit_path(mod_class, options)
232         self.throttle = gr.throttle(gr.sizeof_gr_complex, self.sample_rate())
233         self.rxpath = receive_path(demod_class, rx_callback, options)
234
235         # FIXME: do better exposure to lower issues for control
236         self._gain_clock = self.rxpath.packet_receiver._demodulator._timing_alpha
237         self._gain_phase = self.rxpath.packet_receiver._demodulator._costas_alpha
238         self._gain_freq  = self.rxpath.packet_receiver._demodulator._freq_alpha
239
240         if channelon:
241             self.channel = gr.channel_model(self._noise_voltage,
242                                             self.frequency_offset(),
243                                             self.timing_offset())
244             
245             if options.discontinuous:
246                 z = 20000*[0,]
247                 self.zeros = gr.vector_source_c(z, True)
248                 packet_size = 5*((4+8+4+1500+4) * 8)
249                 self.mux = gr.stream_mux(gr.sizeof_gr_complex, [packet_size-0, int(9e5)])
250
251                 # Connect components
252                 self.connect(self.txpath, self.throttle, (self.mux,0))
253                 self.connect(self.zeros, (self.mux,1))
254                 self.connect(self.mux, self.channel, self.rxpath)
255
256             else:
257                 self.connect(self.txpath, self.throttle, self.channel, self.rxpath)
258
259             if self.gui_on:
260                 self.qapp = QtGui.QApplication(sys.argv)
261                 fftsize = 2048
262
263                 self.snk_tx = qtgui.sink_c(fftsize, gr.firdes.WIN_BLACKMAN_hARRIS,
264                                            0, 1,
265                                            "Tx", True, True, False, True, True)
266                 self.snk_rx = qtgui.sink_c(fftsize, gr.firdes.WIN_BLACKMAN_hARRIS,
267                                            0, 1,
268                                            "Rx", True, True, False, True, True)
269
270                 self.snk_tx.set_frequency_axis(-80, 0)
271                 self.snk_rx.set_frequency_axis(-60, 20)
272
273                 self.freq_recov = self.rxpath.packet_receiver._demodulator.freq_recov
274                 self.phase_recov = self.rxpath.packet_receiver._demodulator.phase_recov
275                 self.time_recov = self.rxpath.packet_receiver._demodulator.time_recov
276                 self.freq_recov.set_alpha(self._gain_freq)
277                 self.freq_recov.set_beta(self._gain_freq/10.0)
278                 self.phase_recov.set_alpha(self._gain_phase)
279                 self.phase_recov.set_beta(0.25*self._gain_phase*self._gain_phase)
280                 self.time_recov.set_alpha(self._gain_clock)
281                 self.time_recov.set_beta(0.25*self._gain_clock*self._gain_clock)
282
283                 # Connect to the QT sinks
284                 # FIXME: make better exposure to receiver from rxpath
285                 self.connect(self.channel, self.snk_tx)
286                 self.connect(self.phase_recov, self.snk_rx)
287                 #self.connect(self.freq_recov, self.snk_rx)
288
289                 pyTxQt  = self.snk_tx.pyqwidget()
290                 pyTx = sip.wrapinstance(pyTxQt, QtGui.QWidget)
291                  
292                 pyRxQt  = self.snk_rx.pyqwidget()
293                 pyRx = sip.wrapinstance(pyRxQt, QtGui.QWidget)
294
295                 self.main_box = dialog_box(pyTx, pyRx, self)
296                 self.main_box.show()
297                 
298         else:
299             # Connect components
300             self.connect(self.txpath, self.throttle, self.rxpath)
301
302
303
304     # System Parameters
305     def sample_rate(self):
306         return self._sample_rate
307     
308     def set_sample_rate(self, sr):
309         self._sample_rate = sr
310         #self.throttle.set_samples_per_second(self._sample_rate)
311
312     # Channel Model Parameters
313     def snr(self):
314         return self._snr_dB
315     
316     def set_snr(self, snr):
317         self._snr_dB = snr
318         self._noise_voltage = self.get_noise_voltage(self._snr_dB)
319         self.channel.set_noise_voltage(self._noise_voltage)
320
321     def get_noise_voltage(self, SNR):
322         snr = 10.0**(SNR/10.0)        
323         power_in_signal = abs(self._tx_amplitude)**2
324         noise_power = power_in_signal/snr
325         noise_voltage = math.sqrt(noise_power)
326         return noise_voltage
327
328     def frequency_offset(self):
329         return self._frequency_offset * self.sample_rate()
330
331     def set_frequency_offset(self, fo):
332         self._frequency_offset = fo / self.sample_rate()
333         self.channel.set_frequency_offset(self._frequency_offset)
334
335     def timing_offset(self):
336         return self._timing_offset
337     
338     def set_timing_offset(self, to):
339         self._timing_offset = to
340         self.channel.set_timing_offset(self._timing_offset)
341
342
343     # Receiver Parameters
344     def rx_gain_clock(self):
345         return self._gain_clock
346
347     def rx_gain_clock_beta(self):
348         return self._gain_clock_beta
349
350     def set_rx_gain_clock(self, gain):
351         self._gain_clock = gain
352         self._gain_clock_beta = .25 * self._gain_clock * self._gain_clock
353         self.rxpath.packet_receiver._demodulator.time_recov.set_alpha(self._gain_clock)
354         self.rxpath.packet_receiver._demodulator.time_recov.set_beta(self._gain_clock_beta)
355
356     def rx_gain_phase(self):
357         return self._gain_phase
358
359     def rx_gain_phase_beta(self):
360         return self._gain_phase_beta
361     
362     def set_rx_gain_phase(self, gain_phase):
363         self._gain_phase = gain_phase
364         self._gain_phase_beta = .25 * self._gain_phase * self._gain_phase
365         self.rxpath.packet_receiver._demodulator.phase_recov.set_alpha(self._gain_phase)
366         self.rxpath.packet_receiver._demodulator.phase_recov.set_beta(self._gain_phase_beta)
367
368
369     def rx_gain_freq(self):
370         return self._gain_freq
371
372     def set_rx_gain_freq(self, gain_freq):
373         self._gain_freq = gain_freq
374         #self._gain_freq_beta = .25 * self._gain_freq * self._gain_freq
375         self.rxpath.packet_receiver._demodulator.freq_recov.set_alpha(self._gain_freq)
376         self.rxpath.packet_receiver._demodulator.freq_recov.set_beta(self._gain_freq/10.0)
377         #self.rxpath.packet_receiver._demodulator.freq_recov.set_beta(self._gain_fre_beta)
378
379
380 # /////////////////////////////////////////////////////////////////////////////
381 #       Thread to handle the packet sending procedure
382 #          Operates in parallel with qApp.exec_()       
383 # /////////////////////////////////////////////////////////////////////////////
384
385
386
387 class th_send(Thread):
388     def __init__(self, send_fnc, megs, sz):
389         Thread.__init__(self)
390         self.send = send_fnc
391         self.nbytes = int(1e6 * megs)
392         self.pkt_size = int(sz)
393
394     def run(self):
395         # generate and send packets
396         n = 0
397         pktno = 0
398         
399         while n < self.nbytes:
400             self.send(struct.pack('!H', pktno & 0xffff) +
401                       (self.pkt_size - 2) * chr(pktno & 0xff))
402             n += self.pkt_size
403             pktno += 1
404             
405         self.send(eof=True)
406
407     def stop(self):
408         self.nbytes = 0
409
410
411
412 # /////////////////////////////////////////////////////////////////////////////
413 #                                   main
414 # /////////////////////////////////////////////////////////////////////////////
415
416
417
418 def main():
419
420     global n_rcvd, n_right, pktno
421
422     n_rcvd = 0
423     n_right = 0
424     pktno = 0
425     
426     def rx_callback(ok, payload):
427         global n_rcvd, n_right, pktno
428         (pktno,) = struct.unpack('!H', payload[0:2])
429         n_rcvd += 1
430         if ok:
431             n_right += 1
432
433         if not options.gui:
434             print "ok = %5s  pktno = %4d  n_rcvd = %4d  n_right = %4d" % (
435                 ok, pktno, n_rcvd, n_right)
436         
437
438     def send_pkt(payload='', eof=False):
439         return tb.txpath.send_pkt(payload, eof)
440
441     mods = modulation_utils.type_1_mods()
442     demods = modulation_utils.type_1_demods()
443
444     parser = OptionParser(option_class=eng_option, conflict_handler="resolve")
445     expert_grp = parser.add_option_group("Expert")
446     channel_grp = parser.add_option_group("Channel")
447
448     parser.add_option("-m", "--modulation", type="choice", choices=mods.keys(),
449                       default='dbpsk',
450                       help="Select modulation from: %s [default=%%default]"
451                             % (', '.join(mods.keys()),))
452
453     parser.add_option("-s", "--size", type="eng_float", default=1500,
454                       help="set packet size [default=%default]")
455     parser.add_option("-M", "--megabytes", type="eng_float", default=1.0,
456                       help="set megabytes to transmit [default=%default]")
457     parser.add_option("","--discontinuous", action="store_true", default=False,
458                       help="enable discontinous transmission (bursts of 5 packets)")
459     parser.add_option("-G", "--gui", action="store_true", default=False,
460                       help="Turn on the GUI [default=%default]")
461
462     channel_grp.add_option("", "--sample-rate", type="eng_float", default=1e5,
463                            help="set speed of channel/simulation rate to RATE [default=%default]") 
464     channel_grp.add_option("", "--snr", type="eng_float", default=30,
465                            help="set the SNR of the channel in dB [default=%default]")
466     channel_grp.add_option("", "--frequency-offset", type="eng_float", default=0,
467                            help="set frequency offset introduced by channel [default=%default]")
468     channel_grp.add_option("", "--timing-offset", type="eng_float", default=1.0,
469                            help="set timing offset introduced by channel [default=%default]")
470     channel_grp.add_option("", "--seed", action="store_true", default=False,
471                            help="use a random seed for AWGN noise [default=%default]")
472
473     transmit_path.add_options(parser, expert_grp)
474     receive_path.add_options(parser, expert_grp)
475
476     for mod in mods.values():
477         mod.add_options(expert_grp)
478     for demod in demods.values():
479         demod.add_options(expert_grp)
480
481     (options, args) = parser.parse_args ()
482
483     if len(args) != 0:
484         parser.print_help()
485         sys.exit(1)
486         
487     r = gr.enable_realtime_scheduling()
488     if r != gr.RT_OK:
489         print "Warning: failed to enable realtime scheduling"
490         
491     # Create an instance of a hierarchical block
492     tb = my_top_block(mods[options.modulation],
493                       demods[options.modulation],
494                       rx_callback, options)
495     tb.start()
496
497     packet_sender = th_send(send_pkt, options.megabytes, options.size)
498     packet_sender.start()
499
500     if(options.gui):
501         tb.qapp.exec_()
502         packet_sender.stop()
503     else:
504         # Process until done; hack in to the join to stop on an interrupt
505         while(packet_sender.isAlive()):
506             try:
507                 packet_sender.join(1)
508             except KeyboardInterrupt:
509                 packet_sender.stop()
510         
511     
512 if __name__ == '__main__':
513     try:
514         main()
515     except KeyboardInterrupt:
516         pass