Imported Upstream version 3.2.2
[debian/gnuradio] / gnuradio-core / src / python / gnuradio / gr / top_block.py
1 #
2 # Copyright 2007 Free Software Foundation, Inc.
3
4 # This file is part of GNU Radio
5
6 # GNU Radio is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3, or (at your option)
9 # any later version.
10
11 # GNU Radio is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15
16 # You should have received a copy of the GNU General Public License
17 # along with GNU Radio; see the file COPYING.  If not, write to
18 # the Free Software Foundation, Inc., 51 Franklin Street,
19 # Boston, MA 02110-1301, USA.
20
21
22 from gnuradio_swig_python import top_block_swig, \
23     top_block_wait_unlocked, top_block_run_unlocked
24
25 #import gnuradio.gr.gr_threading as _threading
26 import gr_threading as _threading
27
28
29 #
30 # There is no problem that can't be solved with an additional
31 # level of indirection...
32 #
33 # This kludge allows ^C to interrupt top_block.run and top_block.wait
34 #
35 # The problem that we are working around is that Python only services
36 # signals (e.g., KeyboardInterrupt) in its main thread.  If the main
37 # thread is blocked in our C++ version of wait, even though Python's
38 # SIGINT handler fires, and even though there may be other python
39 # threads running, no one will know.  Thus instead of directly waiting
40 # in the thread that calls wait (which is likely to be the Python main
41 # thread), we create a separate thread that does the blocking wait,
42 # and then use the thread that called wait to do a slow poll of an
43 # event queue.  That thread, which is executing "wait" below is
44 # interruptable, and if it sees a KeyboardInterrupt, executes a stop
45 # on the top_block, then goes back to waiting for it to complete.
46 # This ensures that the unlocked wait that was in progress (in the
47 # _top_block_waiter thread) can complete, release its mutex and back
48 # out.  If we don't do that, we are never able to clean up, and nasty
49 # things occur like leaving the USRP transmitter sending a carrier.
50 #
51 # See also top_block.wait (below), which uses this class to implement
52 # the interruptable wait.
53 #
54 class _top_block_waiter(_threading.Thread):
55     def __init__(self, tb):
56         _threading.Thread.__init__(self)
57         self.setDaemon(1)
58         self.tb = tb
59         self.event = _threading.Event()
60         self.start()
61
62     def run(self):
63         top_block_wait_unlocked(self.tb)
64         self.event.set()
65
66     def wait(self):
67         try:
68             while not self.event.isSet():
69                 self.event.wait(0.100)
70         except KeyboardInterrupt:
71             self.tb.stop()
72             self.wait()
73
74
75 #
76 # This hack forces a 'has-a' relationship to look like an 'is-a' one.
77 #
78 # It allows Python classes to subclass this one, while passing through
79 # method calls to the C++ class shared pointer from SWIG.
80 #
81 # It also allows us to intercept method calls if needed.
82 #
83 # This allows the 'run_locked' methods, which are defined in gr_top_block.i,
84 # to release the Python global interpreter lock before calling the actual
85 # method in gr_top_block
86 #
87 class top_block(object):
88     def __init__(self, name="top_block"):
89         self._tb = top_block_swig(name)
90
91     def __getattr__(self, name):
92         if not hasattr(self, "_tb"):
93             raise RuntimeError("top_block: invalid state--did you forget to call gr.top_block.__init__ in a derived class?")
94         return getattr(self._tb, name)
95
96     def start(self):
97         self._tb.start()
98         
99     def stop(self):
100         self._tb.stop()
101
102     def run(self):
103         self.start()
104         self.wait()
105
106     def wait(self):
107         _top_block_waiter(self._tb).wait()
108
109
110     # FIXME: these are duplicated from hier_block2.py; they should really be implemented
111     # in the original C++ class (gr_hier_block2), then they would all be inherited here
112
113     def connect(self, *points):
114         '''connect requires one or more arguments that can be coerced to endpoints.
115         If more than two arguments are provided, they are connected together successively.
116         '''
117         if len (points) < 1:
118             raise ValueError, ("connect requires at least one endpoint; %d provided." % (len (points),))
119         else:
120             if len(points) == 1:
121                 self._tb.connect(points[0].basic_block())
122             else:
123                 for i in range (1, len (points)):
124                     self._connect(points[i-1], points[i])
125
126     def _connect(self, src, dst):
127         (src_block, src_port) = self._coerce_endpoint(src)
128         (dst_block, dst_port) = self._coerce_endpoint(dst)
129         self._tb.connect(src_block.basic_block(), src_port,
130                          dst_block.basic_block(), dst_port)
131
132     def _coerce_endpoint(self, endp):
133         if hasattr(endp, 'basic_block'):
134             return (endp, 0)
135         else:
136             if hasattr(endp, "__getitem__") and len(endp) == 2:
137                 return endp # Assume user put (block, port)
138             else:
139                 raise ValueError("unable to coerce endpoint")
140
141     def disconnect(self, *points):
142         '''connect requires one or more arguments that can be coerced to endpoints.
143         If more than two arguments are provided, they are disconnected successively.
144         '''
145         if len (points) < 1:
146             raise ValueError, ("disconnect requires at least two endpoints; %d provided." % (len (points),))
147         else:
148             if len(points) == 1:
149                 self._tb.disconnect(points[0].basic_block())
150             else:
151                 for i in range (1, len (points)):
152                     self._disconnect(points[i-1], points[i])
153
154     def _disconnect(self, src, dst):
155         (src_block, src_port) = self._coerce_endpoint(src)
156         (dst_block, dst_port) = self._coerce_endpoint(dst)
157         self._tb.disconnect(src_block.basic_block(), src_port,
158                             dst_block.basic_block(), dst_port)
159