2 # Copyright 2007 Free Software Foundation, Inc.
4 # This file is part of GNU Radio
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)
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.
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.
22 from gnuradio_swig_python import top_block_swig, \
23 top_block_wait_unlocked, top_block_run_unlocked
25 #import gnuradio.gr.gr_threading as _threading
26 import gr_threading as _threading
30 # There is no problem that can't be solved with an additional
31 # level of indirection...
33 # This kludge allows ^C to interrupt top_block.run and top_block.wait
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.
51 # See also top_block.wait (below), which uses this class to implement
52 # the interruptable wait.
54 class _top_block_waiter(_threading.Thread):
55 def __init__(self, tb):
56 _threading.Thread.__init__(self)
59 self.event = _threading.Event()
63 top_block_wait_unlocked(self.tb)
68 while not self.event.isSet():
69 self.event.wait(0.100)
70 except KeyboardInterrupt:
76 # This hack forces a 'has-a' relationship to look like an 'is-a' one.
78 # It allows Python classes to subclass this one, while passing through
79 # method calls to the C++ class shared pointer from SWIG.
81 # It also allows us to intercept method calls if needed.
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
87 class top_block(object):
88 def __init__(self, name="top_block"):
89 self._tb = top_block_swig(name)
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)
107 _top_block_waiter(self._tb).wait()
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
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.
118 raise ValueError, ("connect requires at least one endpoint; %d provided." % (len (points),))
121 self._tb.connect(points[0].basic_block())
123 for i in range (1, len (points)):
124 self._connect(points[i-1], points[i])
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)
132 def _coerce_endpoint(self, endp):
133 if hasattr(endp, 'basic_block'):
136 if hasattr(endp, "__getitem__") and len(endp) == 2:
137 return endp # Assume user put (block, port)
139 raise ValueError("unable to coerce endpoint")
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.
146 raise ValueError, ("disconnect requires at least two endpoints; %d provided." % (len (points),))
149 self._tb.disconnect(points[0].basic_block())
151 for i in range (1, len (points)):
152 self._disconnect(points[i-1], points[i])
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)