Merged r10071:10164 from features/cppdb-test into trunk. Implements the fully native...
[debian/gnuradio] / gnuradio-examples / python / ofdm / tunnel.py
1 #!/usr/bin/env python
2 #
3 # Copyright 2005,2006 Free Software Foundation, Inc.
4
5 # This file is part of GNU Radio
6
7 # GNU Radio is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2, or (at your option)
10 # any later version.
11
12 # GNU Radio is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16
17 # You should have received a copy of the GNU General Public License
18 # along with GNU Radio; see the file COPYING.  If not, write to
19 # the Free Software Foundation, Inc., 51 Franklin Street,
20 # Boston, MA 02110-1301, USA.
21
22
23
24 # /////////////////////////////////////////////////////////////////////////////
25 #
26 #    This code sets up up a virtual ethernet interface (typically gr0),
27 #    and relays packets between the interface and the GNU Radio PHY+MAC
28 #
29 #    What this means in plain language, is that if you've got a couple
30 #    of USRPs on different machines, and if you run this code on those
31 #    machines, you can talk between them using normal TCP/IP networking.
32 #
33 # /////////////////////////////////////////////////////////////////////////////
34
35
36 from gnuradio import gr, gru, blks2
37 from gnuradio import usrp
38 from gnuradio import eng_notation
39 from gnuradio.eng_option import eng_option
40 from optparse import OptionParser
41
42 import random
43 import time
44 import struct
45 import sys
46 import os
47
48 # from current dir
49 from transmit_path import transmit_path
50 from receive_path import receive_path
51 import fusb_options
52
53 #print os.getpid()
54 #raw_input('Attach and press enter')
55
56
57 # /////////////////////////////////////////////////////////////////////////////
58 #
59 #   Use the Universal TUN/TAP device driver to move packets to/from kernel
60 #
61 #   See /usr/src/linux/Documentation/networking/tuntap.txt
62 #
63 # /////////////////////////////////////////////////////////////////////////////
64
65 # Linux specific...
66 # TUNSETIFF ifr flags from <linux/tun_if.h>
67
68 IFF_TUN         = 0x0001   # tunnel IP packets
69 IFF_TAP         = 0x0002   # tunnel ethernet frames
70 IFF_NO_PI       = 0x1000   # don't pass extra packet info
71 IFF_ONE_QUEUE   = 0x2000   # beats me ;)
72
73 def open_tun_interface(tun_device_filename):
74     from fcntl import ioctl
75     
76     mode = IFF_TAP | IFF_NO_PI
77     TUNSETIFF = 0x400454ca
78
79     tun = os.open(tun_device_filename, os.O_RDWR)
80     ifs = ioctl(tun, TUNSETIFF, struct.pack("16sH", "gr%d", mode))
81     ifname = ifs[:16].strip("\x00")
82     return (tun, ifname)
83     
84
85 # /////////////////////////////////////////////////////////////////////////////
86 #                             the flow graph
87 # /////////////////////////////////////////////////////////////////////////////
88
89 class usrp_graph(gr.top_block):
90     def __init__(self, callback, options):
91         gr.top_block.__init__(self)
92
93         self._tx_freq            = options.tx_freq         # tranmitter's center frequency
94         self._tx_subdev_spec     = options.tx_subdev_spec  # daughterboard to use
95         self._interp             = options.interp          # interpolating rate for the USRP (prelim)
96         self._rx_freq            = options.rx_freq         # receiver's center frequency
97         self._rx_gain            = options.rx_gain         # receiver's gain
98         self._rx_subdev_spec     = options.rx_subdev_spec  # daughterboard to use
99         self._decim              = options.decim           # Decimating rate for the USRP (prelim)
100         self._fusb_block_size    = options.fusb_block_size # usb info for USRP
101         self._fusb_nblocks       = options.fusb_nblocks    # usb info for USRP
102
103         if self._tx_freq is None:
104             sys.stderr.write("-f FREQ or --freq FREQ or --tx-freq FREQ must be specified\n")
105             raise SystemExit
106
107         if self._rx_freq is None:
108             sys.stderr.write("-f FREQ or --freq FREQ or --rx-freq FREQ must be specified\n")
109             raise SystemExit
110
111         # Set up USRP sink and source
112         self._setup_usrp_sink()
113         self._setup_usrp_source()
114
115         # Set center frequency of USRP
116         ok = self.set_freq(self._tx_freq)
117         if not ok:
118             print "Failed to set Tx frequency to %s" % (eng_notation.num_to_str(self._tx_freq),)
119             raise ValueError
120
121         # copy the final answers back into options for use by modulator
122         #options.bitrate = self._bitrate
123
124         self.txpath = transmit_path(options)
125         self.rxpath = receive_path(callback, options)
126
127         self.connect(self.txpath, self.u_snk)
128         self.connect(self.u_src, self.rxpath)
129
130     def carrier_sensed(self):
131         """
132         Return True if the receive path thinks there's carrier
133         """
134         return self.rxpath.carrier_sensed()
135
136     def _setup_usrp_sink(self):
137         """
138         Creates a USRP sink, determines the settings for best bitrate,
139         and attaches to the transmitter's subdevice.
140         """
141         self.u_snk = usrp.sink_c(fusb_block_size=self._fusb_block_size,
142                                  fusb_nblocks=self._fusb_nblocks)
143
144         self.u_snk.set_interp_rate(self._interp)
145
146         # determine the daughterboard subdevice we're using
147         if self._tx_subdev_spec is None:
148             self._tx_subdev_spec = usrp.pick_tx_subdevice(self.u_snk)
149         self.u_snk.set_mux(usrp.determine_tx_mux_value(self.u_snk, self._tx_subdev_spec))
150         self.subdev = usrp.selected_subdev(self.u_snk, self._tx_subdev_spec)
151
152         # Set the USRP for maximum transmit gain
153         # (Note that on the RFX cards this is a nop.)
154         self.set_gain(self.subdev.gain_range()[1])
155
156         # enable Auto Transmit/Receive switching
157         self.set_auto_tr(True)
158
159     def _setup_usrp_source(self):
160         self.u_src = usrp.source_c (fusb_block_size=self._fusb_block_size,
161                                 fusb_nblocks=self._fusb_nblocks)
162         adc_rate = self.u_src.adc_rate()
163
164         self.u_src.set_decim_rate(self._decim)
165
166         # determine the daughterboard subdevice we're using
167         if self._rx_subdev_spec is None:
168             self._rx_subdev_spec = usrp.pick_rx_subdevice(self.u_src)
169         self.subdev = usrp.selected_subdev(self.u_src, self._rx_subdev_spec)
170
171         self.u_src.set_mux(usrp.determine_rx_mux_value(self.u_src, self._rx_subdev_spec))
172
173     def set_freq(self, target_freq):
174         """
175         Set the center frequency we're interested in.
176
177         @param target_freq: frequency in Hz
178         @rypte: bool
179
180         Tuning is a two step process.  First we ask the front-end to
181         tune as close to the desired frequency as it can.  Then we use
182         the result of that operation and our target_frequency to
183         determine the value for the digital up converter.
184         """
185         r_snk = self.u_snk.tune(self.subdev.which(), self.subdev, target_freq)
186         r_src = self.u_src.tune(self.subdev.which(), self.subdev, target_freq)
187         if r_snk and r_src:
188             return True
189
190         return False
191         
192     def set_gain(self, gain):
193         """
194         Sets the analog gain in the USRP
195         """
196         self.gain = gain
197         self.subdev.set_gain(gain)
198
199     def set_auto_tr(self, enable):
200         """
201         Turns on auto transmit/receive of USRP daughterboard (if exits; else ignored)
202         """
203         return self.subdev.set_auto_tr(enable)
204         
205     def interp(self):
206         return self._interp
207
208     def add_options(normal, expert):
209         """
210         Adds usrp-specific options to the Options Parser
211         """
212         add_freq_option(normal)
213         normal.add_option("-T", "--tx-subdev-spec", type="subdev", default=None,
214                           help="select USRP Tx side A or B")
215         normal.add_option("-v", "--verbose", action="store_true", default=False)
216
217         expert.add_option("", "--tx-freq", type="eng_float", default=None,
218                           help="set transmit frequency to FREQ [default=%default]", metavar="FREQ")
219         expert.add_option("-i", "--interp", type="intx", default=256,
220                           help="set fpga interpolation rate to INTERP [default=%default]")
221         normal.add_option("-R", "--rx-subdev-spec", type="subdev", default=None,
222                           help="select USRP Rx side A or B")
223         normal.add_option("", "--rx-gain", type="eng_float", default=None, metavar="GAIN",
224                           help="set receiver gain in dB [default=midpoint].  See also --show-rx-gain-range")
225         normal.add_option("", "--show-rx-gain-range", action="store_true", default=False, 
226                           help="print min and max Rx gain available on selected daughterboard")
227         normal.add_option("-v", "--verbose", action="store_true", default=False)
228
229         expert.add_option("", "--rx-freq", type="eng_float", default=None,
230                           help="set Rx frequency to FREQ [default=%default]", metavar="FREQ")
231         expert.add_option("-d", "--decim", type="intx", default=128,
232                           help="set fpga decimation rate to DECIM [default=%default]")
233         expert.add_option("", "--snr", type="eng_float", default=30,
234                           help="set the SNR of the channel in dB [default=%default]")
235    
236     # Make a static method to call before instantiation
237     add_options = staticmethod(add_options)
238
239     def _print_verbage(self):
240         """
241         Prints information about the transmit path
242         """
243         print "Using TX d'board %s"    % (self.subdev.side_and_name(),)
244         print "modulation:      %s"    % (self._modulator_class.__name__)
245         print "interp:          %3d"   % (self._interp)
246         print "Tx Frequency:    %s"    % (eng_notation.num_to_str(self._tx_freq))
247         
248 def add_freq_option(parser):
249     """
250     Hackery that has the -f / --freq option set both tx_freq and rx_freq
251     """
252     def freq_callback(option, opt_str, value, parser):
253         parser.values.rx_freq = value
254         parser.values.tx_freq = value
255
256     if not parser.has_option('--freq'):
257         parser.add_option('-f', '--freq', type="eng_float",
258                           action="callback", callback=freq_callback,
259                           help="set Tx and/or Rx frequency to FREQ [default=%default]",
260                           metavar="FREQ")
261
262
263 # /////////////////////////////////////////////////////////////////////////////
264 #                           Carrier Sense MAC
265 # /////////////////////////////////////////////////////////////////////////////
266
267 class cs_mac(object):
268     """
269     Prototype carrier sense MAC
270
271     Reads packets from the TUN/TAP interface, and sends them to the PHY.
272     Receives packets from the PHY via phy_rx_callback, and sends them
273     into the TUN/TAP interface.
274
275     Of course, we're not restricted to getting packets via TUN/TAP, this
276     is just an example.
277     """
278     def __init__(self, tun_fd, verbose=False):
279         self.tun_fd = tun_fd       # file descriptor for TUN/TAP interface
280         self.verbose = verbose
281         self.tb = None             # top block (access to PHY)
282
283     def set_flow_graph(self, tb):
284         self.tb = tb
285
286     def phy_rx_callback(self, ok, payload):
287         """
288         Invoked by thread associated with PHY to pass received packet up.
289
290         @param ok: bool indicating whether payload CRC was OK
291         @param payload: contents of the packet (string)
292         """
293         if self.verbose:
294             print "Rx: ok = %r  len(payload) = %4d" % (ok, len(payload))
295         if ok:
296             os.write(self.tun_fd, payload)
297
298     def main_loop(self):
299         """
300         Main loop for MAC.
301         Only returns if we get an error reading from TUN.
302
303         FIXME: may want to check for EINTR and EAGAIN and reissue read
304         """
305         min_delay = 0.001               # seconds
306
307         while 1:
308             payload = os.read(self.tun_fd, 10*1024)
309             if not payload:
310                 self.tb.txpath.send_pkt(eof=True)
311                 break
312
313             if self.verbose:
314                 print "Tx: len(payload) = %4d" % (len(payload),)
315
316             delay = min_delay
317             while self.tb.carrier_sensed():
318                 sys.stderr.write('B')
319                 time.sleep(delay)
320                 if delay < 0.050:
321                     delay = delay * 2       # exponential back-off
322
323             self.tb.txpath.send_pkt(payload)
324
325
326 # /////////////////////////////////////////////////////////////////////////////
327 #                                   main
328 # /////////////////////////////////////////////////////////////////////////////
329
330 def main():
331
332     parser = OptionParser (option_class=eng_option, conflict_handler="resolve")
333     expert_grp = parser.add_option_group("Expert")
334
335     parser.add_option("-m", "--modulation", type="choice", choices=['bpsk', 'qpsk'],
336                       default='bpsk',
337                       help="Select modulation from: bpsk, qpsk [default=%%default]")
338     
339     parser.add_option("-v","--verbose", action="store_true", default=False)
340     expert_grp.add_option("-c", "--carrier-threshold", type="eng_float", default=30,
341                           help="set carrier detect threshold (dB) [default=%default]")
342     expert_grp.add_option("","--tun-device-filename", default="/dev/net/tun",
343                           help="path to tun device file [default=%default]")
344
345     usrp_graph.add_options(parser, expert_grp)
346     transmit_path.add_options(parser, expert_grp)
347     receive_path.add_options(parser, expert_grp)
348     blks2.ofdm_mod.add_options(parser, expert_grp)
349     blks2.ofdm_demod.add_options(parser, expert_grp)
350
351     fusb_options.add_options(expert_grp)
352
353     (options, args) = parser.parse_args ()
354     if len(args) != 0:
355         parser.print_help(sys.stderr)
356         sys.exit(1)
357
358     if options.rx_freq is None or options.tx_freq is None:
359         sys.stderr.write("You must specify -f FREQ or --freq FREQ\n")
360         parser.print_help(sys.stderr)
361         sys.exit(1)
362
363     # open the TUN/TAP interface
364     (tun_fd, tun_ifname) = open_tun_interface(options.tun_device_filename)
365
366     # Attempt to enable realtime scheduling
367     r = gr.enable_realtime_scheduling()
368     if r == gr.RT_OK:
369         realtime = True
370     else:
371         realtime = False
372         print "Note: failed to enable realtime scheduling"
373
374
375     # If the user hasn't set the fusb_* parameters on the command line,
376     # pick some values that will reduce latency.
377
378     if options.fusb_block_size == 0 and options.fusb_nblocks == 0:
379         if realtime:                        # be more aggressive
380             options.fusb_block_size = gr.prefs().get_long('fusb', 'rt_block_size', 1024)
381             options.fusb_nblocks    = gr.prefs().get_long('fusb', 'rt_nblocks', 16)
382         else:
383             options.fusb_block_size = gr.prefs().get_long('fusb', 'block_size', 4096)
384             options.fusb_nblocks    = gr.prefs().get_long('fusb', 'nblocks', 16)
385     
386     #print "fusb_block_size =", options.fusb_block_size
387     #print "fusb_nblocks    =", options.fusb_nblocks
388
389     # instantiate the MAC
390     mac = cs_mac(tun_fd, verbose=True)
391
392
393     # build the graph (PHY)
394     tb = usrp_graph(mac.phy_rx_callback, options)
395
396     mac.set_flow_graph(tb)    # give the MAC a handle for the PHY
397
398     #if fg.txpath.bitrate() != fg.rxpath.bitrate():
399     #    print "WARNING: Transmit bitrate = %sb/sec, Receive bitrate = %sb/sec" % (
400     #        eng_notation.num_to_str(fg.txpath.bitrate()),
401     #        eng_notation.num_to_str(fg.rxpath.bitrate()))
402              
403     print "modulation:     %s"   % (options.modulation,)
404     print "freq:           %s"      % (eng_notation.num_to_str(options.tx_freq))
405     #print "bitrate:        %sb/sec" % (eng_notation.num_to_str(fg.txpath.bitrate()),)
406     #print "samples/symbol: %3d" % (fg.txpath.samples_per_symbol(),)
407     #print "interp:         %3d" % (fg.txpath.interp(),)
408     #print "decim:          %3d" % (fg.rxpath.decim(),)
409
410     tb.rxpath.set_carrier_threshold(options.carrier_threshold)
411     print "Carrier sense threshold:", options.carrier_threshold, "dB"
412     
413     print
414     print "Allocated virtual ethernet interface: %s" % (tun_ifname,)
415     print "You must now use ifconfig to set its IP address. E.g.,"
416     print
417     print "  $ sudo ifconfig %s 192.168.200.1" % (tun_ifname,)
418     print
419     print "Be sure to use a different address in the same subnet for each machine."
420     print
421
422
423     tb.start()    # Start executing the flow graph (runs in separate threads)
424
425     mac.main_loop()    # don't expect this to return...
426
427     tb.stop()     # but if it does, tell flow graph to stop.
428     tb.wait()     # wait for it to finish
429                 
430
431 if __name__ == '__main__':
432     try:
433         main()
434     except KeyboardInterrupt:
435         pass