summaryrefslogtreecommitdiff
path: root/gnuradio-runtime/python
diff options
context:
space:
mode:
authorJohnathan Corgan <johnathan@corganlabs.com>2013-03-26 20:18:53 -0700
committerJohnathan Corgan <johnathan@corganlabs.com>2013-03-26 20:18:53 -0700
commit26dceecc80390f10cedb94bd9e4fd655827d7f17 (patch)
treef88cb2995133048b4a2191bae688fa09b33a19ae /gnuradio-runtime/python
parent9bbbda510c265b211b5b571db79ba259c67ee049 (diff)
runtime: migrate remaining gnuradio-core contents into gnuradio-runtime
Diffstat (limited to 'gnuradio-runtime/python')
-rw-r--r--gnuradio-runtime/python/CMakeLists.txt23
-rw-r--r--gnuradio-runtime/python/build_utils.py226
-rw-r--r--gnuradio-runtime/python/build_utils_codes.py52
-rw-r--r--gnuradio-runtime/python/gnuradio/CMakeLists.txt37
-rw-r--r--gnuradio-runtime/python/gnuradio/__init__.py12
-rw-r--r--gnuradio-runtime/python/gnuradio/ctrlport/CMakeLists.txt97
-rw-r--r--gnuradio-runtime/python/gnuradio/ctrlport/GrDataPlotter.py428
-rw-r--r--gnuradio-runtime/python/gnuradio/ctrlport/IceRadioClient.py102
-rw-r--r--gnuradio-runtime/python/gnuradio/ctrlport/__init__.py30
-rwxr-xr-xgnuradio-runtime/python/gnuradio/ctrlport/gr-ctrlport-curses268
-rwxr-xr-xgnuradio-runtime/python/gnuradio/ctrlport/gr-ctrlport-monitor721
-rwxr-xr-xgnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitor591
-rwxr-xr-xgnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitorx727
-rw-r--r--gnuradio-runtime/python/gnuradio/ctrlport/icon.pngbin0 -> 1532 bytes
-rw-r--r--gnuradio-runtime/python/gnuradio/ctrlport/monitor.py59
-rw-r--r--gnuradio-runtime/python/gnuradio/gr/CMakeLists.txt45
-rw-r--r--gnuradio-runtime/python/gnuradio/gr/__init__.py39
-rw-r--r--gnuradio-runtime/python/gnuradio/gr/exceptions.py27
-rw-r--r--gnuradio-runtime/python/gnuradio/gr/gateway.py243
-rw-r--r--gnuradio-runtime/python/gnuradio/gr/gr_threading.py35
-rw-r--r--gnuradio-runtime/python/gnuradio/gr/gr_threading_23.py724
-rw-r--r--gnuradio-runtime/python/gnuradio/gr/gr_threading_24.py793
-rw-r--r--gnuradio-runtime/python/gnuradio/gr/hier_block2.py132
-rw-r--r--gnuradio-runtime/python/gnuradio/gr/prefs.py127
-rw-r--r--gnuradio-runtime/python/gnuradio/gr/pubsub.py153
-rwxr-xr-xgnuradio-runtime/python/gnuradio/gr/qa_feval.py110
-rwxr-xr-xgnuradio-runtime/python/gnuradio/gr/qa_kludged_imports.py39
-rwxr-xr-xgnuradio-runtime/python/gnuradio/gr/qa_tag_utils.py55
-rw-r--r--gnuradio-runtime/python/gnuradio/gr/tag_utils.py57
-rw-r--r--gnuradio-runtime/python/gnuradio/gr/top_block.py170
-rwxr-xr-xgnuradio-runtime/python/gnuradio/gr_unittest.py170
-rw-r--r--gnuradio-runtime/python/gnuradio/gr_xmlrunner.py387
-rw-r--r--gnuradio-runtime/python/gnuradio/gru/CMakeLists.txt36
-rw-r--r--gnuradio-runtime/python/gnuradio/gru/__init__.py13
-rw-r--r--gnuradio-runtime/python/gnuradio/gru/daemon.py102
-rw-r--r--gnuradio-runtime/python/gnuradio/gru/freqz.py344
-rwxr-xr-xgnuradio-runtime/python/gnuradio/gru/gnuplot_freqz.py102
-rw-r--r--gnuradio-runtime/python/gnuradio/gru/hexint.py44
-rw-r--r--gnuradio-runtime/python/gnuradio/gru/listmisc.py29
-rw-r--r--gnuradio-runtime/python/gnuradio/gru/mathmisc.py33
-rw-r--r--gnuradio-runtime/python/gnuradio/gru/msgq_runner.py82
-rw-r--r--gnuradio-runtime/python/gnuradio/gru/os_read_exactly.py36
-rw-r--r--gnuradio-runtime/python/gnuradio/gru/seq_with_cursor.py77
-rw-r--r--gnuradio-runtime/python/gnuradio/gru/socket_stuff.py62
44 files changed, 7639 insertions, 0 deletions
diff --git a/gnuradio-runtime/python/CMakeLists.txt b/gnuradio-runtime/python/CMakeLists.txt
new file mode 100644
index 0000000000..74adec3f11
--- /dev/null
+++ b/gnuradio-runtime/python/CMakeLists.txt
@@ -0,0 +1,23 @@
+# Copyright 2012 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+
+########################################################################
+include(GrPython)
+
+add_subdirectory(gnuradio)
diff --git a/gnuradio-runtime/python/build_utils.py b/gnuradio-runtime/python/build_utils.py
new file mode 100644
index 0000000000..cf58a97637
--- /dev/null
+++ b/gnuradio-runtime/python/build_utils.py
@@ -0,0 +1,226 @@
+#
+# Copyright 2004,2009,2012 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+"""Misc utilities used at build time
+"""
+
+import re, os, os.path
+from build_utils_codes import *
+
+
+# set srcdir to the directory that contains Makefile.am
+try:
+ srcdir = os.environ['srcdir']
+except KeyError, e:
+ srcdir = "."
+srcdir = srcdir + '/'
+
+# set do_makefile to either true or false dependeing on the environment
+try:
+ if os.environ['do_makefile'] == '0':
+ do_makefile = False
+ else:
+ do_makefile = True
+except KeyError, e:
+ do_makefile = False
+
+# set do_sources to either true or false dependeing on the environment
+try:
+ if os.environ['do_sources'] == '0':
+ do_sources = False
+ else:
+ do_sources = True
+except KeyError, e:
+ do_sources = True
+
+name_dict = {}
+
+def log_output_name (name):
+ (base, ext) = os.path.splitext (name)
+ ext = ext[1:] # drop the leading '.'
+
+ entry = name_dict.setdefault (ext, [])
+ entry.append (name)
+
+def open_and_log_name (name, dir):
+ global do_sources
+ if do_sources:
+ f = open (name, dir)
+ else:
+ f = None
+ log_output_name (name)
+ return f
+
+def expand_template (d, template_filename, extra = ""):
+ '''Given a dictionary D and a TEMPLATE_FILENAME, expand template into output file
+ '''
+ global do_sources
+ output_extension = extract_extension (template_filename)
+ template = open_src (template_filename, 'r')
+ output_name = d['NAME'] + extra + '.' + output_extension
+ log_output_name (output_name)
+ if do_sources:
+ output = open (output_name, 'w')
+ do_substitution (d, template, output)
+ output.close ()
+ template.close ()
+
+def output_glue (dirname):
+ output_makefile_fragment ()
+ output_ifile_include (dirname)
+
+def output_makefile_fragment ():
+ global do_makefile
+ if not do_makefile:
+ return
+# overwrite the source, which must be writable; this should have been
+# checked for beforehand in the top-level Makefile.gen.gen .
+ f = open (os.path.join (os.environ.get('gendir', os.environ.get('srcdir', '.')), 'Makefile.gen'), 'w')
+ f.write ('#\n# This file is machine generated. All edits will be overwritten\n#\n')
+ output_subfrag (f, 'h')
+ output_subfrag (f, 'i')
+ output_subfrag (f, 'cc')
+ f.close ()
+
+def output_ifile_include (dirname):
+ global do_sources
+ if do_sources:
+ f = open ('%s_generated.i' % (dirname,), 'w')
+ f.write ('//\n// This file is machine generated. All edits will be overwritten\n//\n')
+ files = name_dict.setdefault ('i', [])
+ files.sort ()
+ f.write ('%{\n')
+ for file in files:
+ f.write ('#include <%s>\n' % (file[0:-1] + 'h',))
+ f.write ('%}\n\n')
+ for file in files:
+ f.write ('%%include <%s>\n' % (file,))
+
+def output_subfrag (f, ext):
+ files = name_dict.setdefault (ext, [])
+ files.sort ()
+ f.write ("GENERATED_%s =" % (ext.upper ()))
+ for file in files:
+ f.write (" \\\n\t%s" % (file,))
+ f.write ("\n\n")
+
+def extract_extension (template_name):
+ # template name is something like: GrFIRfilterXXX.h.t
+ # we return everything between the penultimate . and .t
+ mo = re.search (r'\.([a-z]+)\.t$', template_name)
+ if not mo:
+ raise ValueError, "Incorrectly formed template_name '%s'" % (template_name,)
+ return mo.group (1)
+
+def open_src (name, mode):
+ global srcdir
+ return open (os.path.join (srcdir, name), mode)
+
+def do_substitution (d, in_file, out_file):
+ def repl (match_obj):
+ key = match_obj.group (1)
+ # print key
+ return d[key]
+
+ inp = in_file.read ()
+ out = re.sub (r"@([a-zA-Z0-9_]+)@", repl, inp)
+ out_file.write (out)
+
+
+
+copyright = '''/* -*- c++ -*- */
+/*
+ * Copyright 2003,2004 Free Software Foundation, Inc.
+ *
+ * This file is part of GNU Radio
+ *
+ * GNU Radio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3, or (at your option)
+ * any later version.
+ *
+ * GNU Radio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Radio; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+'''
+
+def is_complex (code3):
+ if i_code (code3) == 'c' or o_code (code3) == 'c':
+ return '1'
+ else:
+ return '0'
+
+
+def standard_dict (name, code3, package='gr'):
+ d = {}
+ d['NAME'] = name
+ d['NAME_IMPL'] = name+'_impl'
+ d['GUARD_NAME'] = 'INCLUDED_%s_%s_H' % (package.upper(), name.upper())
+ d['GUARD_NAME_IMPL'] = 'INCLUDED_%s_%s_IMPL_H' % (package.upper(), name.upper())
+ d['BASE_NAME'] = re.sub ('^' + package + '_', '', name)
+ d['SPTR_NAME'] = '%s_sptr' % name
+ d['WARNING'] = 'WARNING: this file is machine generated. Edits will be overwritten'
+ d['COPYRIGHT'] = copyright
+ d['TYPE'] = i_type (code3)
+ d['I_TYPE'] = i_type (code3)
+ d['O_TYPE'] = o_type (code3)
+ d['TAP_TYPE'] = tap_type (code3)
+ d['IS_COMPLEX'] = is_complex (code3)
+ return d
+
+
+def standard_dict2 (name, code3, package):
+ d = {}
+ d['NAME'] = name
+ d['BASE_NAME'] = name
+ d['GUARD_NAME'] = 'INCLUDED_%s_%s_H' % (package.upper(), name.upper())
+ d['WARNING'] = 'WARNING: this file is machine generated. Edits will be overwritten'
+ d['COPYRIGHT'] = copyright
+ d['TYPE'] = i_type (code3)
+ d['I_TYPE'] = i_type (code3)
+ d['O_TYPE'] = o_type (code3)
+ d['TAP_TYPE'] = tap_type (code3)
+ d['IS_COMPLEX'] = is_complex (code3)
+ return d
+
+def standard_impl_dict2 (name, code3, package):
+ d = {}
+ d['NAME'] = name
+ d['IMPL_NAME'] = name
+ d['BASE_NAME'] = name.rstrip("impl").rstrip("_")
+ d['GUARD_NAME'] = 'INCLUDED_%s_%s_H' % (package.upper(), name.upper())
+ d['WARNING'] = 'WARNING: this file is machine generated. Edits will be overwritten'
+ d['COPYRIGHT'] = copyright
+ d['FIR_TYPE'] = "fir_filter_" + code3
+ d['CFIR_TYPE'] = "fir_filter_" + code3[0:2] + 'c'
+ d['TYPE'] = i_type (code3)
+ d['I_TYPE'] = i_type (code3)
+ d['O_TYPE'] = o_type (code3)
+ d['TAP_TYPE'] = tap_type (code3)
+ d['IS_COMPLEX'] = is_complex (code3)
+ return d
diff --git a/gnuradio-runtime/python/build_utils_codes.py b/gnuradio-runtime/python/build_utils_codes.py
new file mode 100644
index 0000000000..9ea96baae4
--- /dev/null
+++ b/gnuradio-runtime/python/build_utils_codes.py
@@ -0,0 +1,52 @@
+#
+# Copyright 2004 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+def i_code (code3):
+ return code3[0]
+
+def o_code (code3):
+ if len (code3) >= 2:
+ return code3[1]
+ else:
+ return code3[0]
+
+def tap_code (code3):
+ if len (code3) >= 3:
+ return code3[2]
+ else:
+ return code3[0]
+
+def i_type (code3):
+ return char_to_type[i_code (code3)]
+
+def o_type (code3):
+ return char_to_type[o_code (code3)]
+
+def tap_type (code3):
+ return char_to_type[tap_code (code3)]
+
+
+char_to_type = {}
+char_to_type['s'] = 'short'
+char_to_type['i'] = 'int'
+char_to_type['f'] = 'float'
+char_to_type['c'] = 'gr_complex'
+char_to_type['b'] = 'unsigned char'
diff --git a/gnuradio-runtime/python/gnuradio/CMakeLists.txt b/gnuradio-runtime/python/gnuradio/CMakeLists.txt
new file mode 100644
index 0000000000..bf5ec331d3
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/CMakeLists.txt
@@ -0,0 +1,37 @@
+# Copyright 2012 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+
+########################################################################
+include(GrPython)
+
+add_subdirectory(gr)
+add_subdirectory(gru)
+add_subdirectory(ctrlport)
+
+if(ENABLE_GR_CTRLPORT)
+#add_subdirectory(ctrlport)
+endif(ENABLE_GR_CTRLPORT)
+
+GR_PYTHON_INSTALL(FILES
+ __init__.py
+ gr_unittest.py
+ gr_xmlrunner.py
+ DESTINATION ${GR_PYTHON_DIR}/gnuradio
+ COMPONENT "runtime_python"
+)
diff --git a/gnuradio-runtime/python/gnuradio/__init__.py b/gnuradio-runtime/python/gnuradio/__init__.py
new file mode 100644
index 0000000000..d55dac79db
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/__init__.py
@@ -0,0 +1,12 @@
+"""
+GNU Radio is a free & open-source software development toolkit that provides signal processing blocks to implement software radios. It can be used with readily-available low-cost external RF hardware to create software-defined radios, or without hardware in a simulation-like environment. It is widely used in hobbyist, academic and commercial environments to support both wireless communications research and real-world radio systems.
+
+GNU Radio applications are primarily written using the Python programming language, while the supplied performance-critical signal-processing path is implemented in C++ using processor floating-point extensions, where available. Thus, the developer is able to implement real-time, high-throughput radio systems in a simple-to-use, rapid-application-development environment.
+
+While not primarily a simulation tool, GNU Radio does support development of signal processing algorithms using pre-recorded or generated data, avoiding the need for actual RF hardware.
+
+GNU Radio is licensed under the GNU General Public License (GPL) version 3. All of the code is copyright of the Free Software Foundation.
+"""
+
+# This file makes gnuradio a package
+# The docstring will be associated with the top level of the package.
diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/CMakeLists.txt b/gnuradio-runtime/python/gnuradio/ctrlport/CMakeLists.txt
new file mode 100644
index 0000000000..c68694785f
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/ctrlport/CMakeLists.txt
@@ -0,0 +1,97 @@
+# Copyright 2012 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+
+########################################################################
+include(GrPython)
+
+EXECUTE_PROCESS(
+ COMMAND ${ICE_SLICE2PY} -I${CMAKE_SOURCE_DIR}/gnuradio-runtime/lib
+ --output-dir=${CMAKE_BINARY_DIR}/gnuradio-runtime/python
+ ${CMAKE_SOURCE_DIR}/gnuradio-runtime/lib/gnuradio.ice
+)
+
+EXECUTE_PROCESS(
+ COMMAND ${ICE_SLICE2PY} -I${CMAKE_SOURCE_DIR}/gnuradio-runtime/lib
+ --output-dir=${CMAKE_BINARY_DIR}/gnuradio-runtime/python
+ ${CMAKE_SOURCE_DIR}/gnuradio-runtime/lib/frontend.ice
+)
+
+GR_PYTHON_INSTALL(
+ FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/__init__.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/IceRadioClient.py
+ DESTINATION ${GR_PYTHON_DIR}/gnuradio/ctrlport/
+ COMPONENT "core_python"
+)
+
+# We don't want to install these in the root Python directory, but we
+# aren't given a choice based on the way slice2py generates the
+# information.
+GR_PYTHON_INSTALL(
+ FILES
+ ${CMAKE_BINARY_DIR}/gnuradio-runtime/python/gnuradio_ice.py
+ ${CMAKE_BINARY_DIR}/gnuradio-runtime/python/frontend_ice.py
+ DESTINATION ${GR_PYTHON_DIR}
+ COMPONENT "core_python"
+)
+
+GR_PYTHON_INSTALL(
+ FILES
+ ${CMAKE_CURRENT_BINARY_DIR}/GNURadio/__init__.py
+ DESTINATION ${GR_PYTHON_DIR}/gnuradio/ctrlport/GNURadio
+ COMPONENT "core_python"
+)
+
+GR_PYTHON_INSTALL(
+ FILES
+ ${CMAKE_CURRENT_BINARY_DIR}/GNURadio/Booter/__init__.py
+ DESTINATION ${GR_PYTHON_DIR}/gnuradio/ctrlport/GNURadio/Booter
+ COMPONENT "core_python"
+)
+
+GR_PYTHON_INSTALL(
+ FILES
+ ${CMAKE_CURRENT_BINARY_DIR}/GNURadio/Frontend/__init__.py
+ DESTINATION ${GR_PYTHON_DIR}/gnuradio/ctrlport/GNURadio/Frontend
+ COMPONENT "core_python"
+)
+
+install(
+ FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/icon.png
+ DESTINATION ${GR_PYTHON_DIR}/gnuradio/ctrlport
+ COMPONENT "core_python"
+)
+
+GR_PYTHON_INSTALL(
+ FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/GrDataPlotter.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/monitor.py
+ DESTINATION ${GR_PYTHON_DIR}/gnuradio/ctrlport/
+ COMPONENT "core_python"
+)
+
+GR_PYTHON_INSTALL(
+ FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/gr-ctrlport-monitor
+ ${CMAKE_CURRENT_SOURCE_DIR}/gr-ctrlport-curses
+ DESTINATION ${GR_RUNTIME_DIR}
+ PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
+ COMPONENT "core_python"
+)
diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/GrDataPlotter.py b/gnuradio-runtime/python/gnuradio/ctrlport/GrDataPlotter.py
new file mode 100644
index 0000000000..8597ca6497
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/ctrlport/GrDataPlotter.py
@@ -0,0 +1,428 @@
+#!/usr/bin/env python
+#
+# Copyright 2012,2013 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from gnuradio import gr
+from gnuradio import blocks
+from gnuradio import filter
+import sys, time
+
+try:
+ from gnuradio import qtgui
+ from PyQt4 import QtGui, QtCore
+ import sip
+except ImportError:
+ print "Error: Program requires PyQt4 and gr-qtgui."
+ sys.exit(1)
+
+class GrDataPlotParent(gr.top_block, QtGui.QWidget):
+ # Setup signals
+ plotupdated = QtCore.pyqtSignal(QtGui.QWidget)
+
+ def __init__(self, name, rate, pmin=None, pmax=None):
+ gr.top_block.__init__(self)
+ QtGui.QWidget.__init__(self, None)
+
+ self._name = name
+ self._npts = 500
+ self._rate = rate
+ self.knobnames = [name,]
+
+ self.layout = QtGui.QVBoxLayout()
+ self.setLayout(self.layout)
+
+ self.setAcceptDrops(True)
+
+ def _setup(self, nconnections):
+ self.stop()
+ self.wait()
+
+ if(self.layout.count() > 0):
+ # Remove and disconnect. Making sure no references to snk
+ # remain so that the plot gets deleted.
+ self.layout.removeWidget(self.py_window)
+ self.disconnect(self.thr, (self.snk, 0))
+ self.disconnect(self.src[0], self.thr)
+ for n in xrange(1, self._ncons):
+ self.disconnect(self.src[n], (self.snk,n))
+
+ self._ncons = nconnections
+ self._data_len = self._ncons*[0,]
+
+ self.thr = blocks.throttle(self._datasize, self._rate)
+ self.snk = self.get_qtsink()
+
+ self.connect(self.thr, (self.snk, 0))
+
+ self._last_data = []
+ self.src = []
+ for n in xrange(self._ncons):
+ self.set_line_label(n, self.knobnames[n])
+
+ self._last_data.append(int(self._npts)*[0,])
+ self.src.append(self.get_vecsource())
+
+ if(n == 0):
+ self.connect(self.src[n], self.thr)
+ else:
+ self.connect(self.src[n], (self.snk,n))
+
+ self.py_window = sip.wrapinstance(self.snk.pyqwidget(), QtGui.QWidget)
+
+ self.layout.addWidget(self.py_window)
+
+ def __del__(self):
+ pass
+
+ def close(self):
+ self.snk.close()
+
+ def qwidget(self):
+ return self.py_window
+
+ def name(self):
+ return self._name
+
+ def semilogy(self, en=True):
+ self.snk.enable_semilogy(en)
+
+ def dragEnterEvent(self, e):
+ e.acceptProposedAction()
+
+ def dropEvent(self, e):
+ if(e.mimeData().hasFormat("text/plain")):
+ data = str(e.mimeData().text())
+
+ #"PlotData:{0}:{1}".format(tag, iscomplex)
+ datalst = data.split(":::")
+ tag = datalst[0]
+ name = datalst[1]
+ cpx = datalst[2] != "0"
+
+ if(tag == "PlotData" and cpx == self._iscomplex):
+ self.knobnames.append(name)
+
+ # create a new qwidget plot with the new data stream.
+ self._setup(len(self.knobnames))
+
+ # emit that this plot has been updated with a new qwidget.
+ self.plotupdated.emit(self)
+
+ e.acceptProposedAction()
+
+ def data_to_complex(self, data):
+ if(self._iscomplex):
+ data_r = data[0::2]
+ data_i = data[1::2]
+ data = [complex(r,i) for r,i in zip(data_r, data_i)]
+ return data
+
+ def update(self, data):
+ # Ask GUI if there has been a change in nsamps
+ npts = self.get_npts()
+ if(self._npts != npts):
+
+ # Adjust buffers to accomodate new settings
+ for n in xrange(self._ncons):
+ if(npts < self._npts):
+ if(self._data_len[n] < npts):
+ self._last_data[n] = self._last_data[n][0:npts]
+ else:
+ self._last_data[n] = self._last_data[n][self._data_len[n]-npts:self._data_len[n]]
+ self._data_len[n] = npts
+ else:
+ self._last_data[n] += (npts - self._npts)*[0,]
+ self._npts = npts
+ self.snk.reset()
+
+ if(self._stripchart):
+ # Update the plot data depending on type
+ for n in xrange(self._ncons):
+ if(type(data[n]) == list):
+ data[n] = self.data_to_complex(data[n])
+ if(len(data[n]) > self._npts):
+ self.src[n].set_data(data[n])
+ self._last_data[n] = data[n][-self._npts:]
+ else:
+ newdata = self._last_data[n][-(self._npts-len(data)):]
+ newdata += data[n]
+ self.src[n].set_data(newdata)
+ self._last_data[n] = newdata
+
+ else: # single value update
+ if(self._iscomplex):
+ data[n] = complex(data[n][0], data[n][1])
+ if(self._data_len[n] < self._npts):
+ self._last_data[n][self._data_len[n]] = data[n]
+ self._data_len[n] += 1
+ else:
+ self._last_data[n] = self._last_data[n][1:]
+ self._last_data[n].append(data[n])
+ self.src[n].set_data(self._last_data[n])
+ else:
+ for n in xrange(self._ncons):
+ if(type(data[n]) != list):
+ data[n] = [data[n],]
+ data[n] = self.data_to_complex(data[n])
+ self.src[n].set_data(data[n])
+
+
+
+class GrDataPlotterC(GrDataPlotParent):
+ def __init__(self, name, rate, pmin=None, pmax=None, stripchart=False):
+ GrDataPlotParent.__init__(self, name, rate, pmin, pmax)
+
+ self._stripchart = stripchart
+ self._datasize = gr.sizeof_gr_complex
+ self._iscomplex = True
+
+ self._setup(1)
+
+ def stem(self, en=True):
+ self.snk.enable_stem_plot(en)
+
+ def get_qtsink(self):
+ snk = qtgui.time_sink_c(self._npts, 1.0,
+ self._name, self._ncons)
+ snk.enable_autoscale(True)
+ return snk
+
+ def get_vecsource(self):
+ return blocks.vector_source_c([])
+
+ def get_npts(self):
+ self._npts = self.snk.nsamps()
+ return self._npts
+
+ def set_line_label(self, n, name):
+ self.snk.set_line_label(2*n+0, "Re{" + self.knobnames[n] + "}")
+ self.snk.set_line_label(2*n+1, "Im{" + self.knobnames[n] + "}")
+
+
+class GrDataPlotterF(GrDataPlotParent):
+ def __init__(self, name, rate, pmin=None, pmax=None, stripchart=False):
+ GrDataPlotParent.__init__(self, name, rate, pmin, pmax)
+
+ self._stripchart = stripchart
+ self._datasize = gr.sizeof_float
+ self._iscomplex = False
+
+ self._setup(1)
+
+ def stem(self, en=True):
+ self.snk.enable_stem_plot(en)
+
+ def get_qtsink(self):
+ snk = qtgui.time_sink_f(self._npts, 1.0,
+ self._name, self._ncons)
+ snk.enable_autoscale(True)
+ return snk
+
+ def get_vecsource(self):
+ return blocks.vector_source_f([])
+
+ def get_npts(self):
+ self._npts = self.snk.nsamps()
+ return self._npts
+
+ def set_line_label(self, n, name):
+ self.snk.set_line_label(n, self.knobnames[n])
+
+
+class GrDataPlotterConst(GrDataPlotParent):
+ def __init__(self, name, rate, pmin=None, pmax=None):
+ GrDataPlotParent.__init__(self, name, rate, pmin, pmax)
+
+ self._datasize = gr.sizeof_gr_complex
+ self._stripchart = False
+ self._iscomplex = True
+
+ self._setup(1)
+
+ def get_qtsink(self):
+ snk = qtgui.const_sink_c(self._npts,
+ self._name,
+ self._ncons)
+ snk.enable_autoscale(True)
+ return snk
+
+ def get_vecsource(self):
+ return blocks.vector_source_c([])
+
+ def get_npts(self):
+ self._npts = self.snk.nsamps()
+ return self._npts
+
+ def scatter(self, en=True):
+ if(en):
+ self.snk.set_line_style(0, 0)
+ else:
+ self.snk.set_line_style(0, 1)
+
+ def set_line_label(self, n, name):
+ self.snk.set_line_label(n, self.knobnames[n])
+
+
+class GrDataPlotterPsdC(GrDataPlotParent):
+ def __init__(self, name, rate, pmin=None, pmax=None):
+ GrDataPlotParent.__init__(self, name, rate, pmin, pmax)
+
+ self._datasize = gr.sizeof_gr_complex
+ self._stripchart = True
+ self._iscomplex = True
+
+ self._npts = 2048
+ self._wintype = filter.firdes.WIN_BLACKMAN_hARRIS
+ self._fc = 0
+
+ self._setup(1)
+
+ def get_qtsink(self):
+ snk = qtgui.freq_sink_c(self._npts, self._wintype,
+ self._fc, 1.0,
+ self._name,
+ self._ncons)
+ snk.enable_autoscale(True)
+ return snk
+
+ def get_vecsource(self):
+ return blocks.vector_source_c([])
+
+ def get_npts(self):
+ self._npts = self.snk.fft_size()
+ return self._npts
+
+ def set_line_label(self, n, name):
+ self.snk.set_line_label(n, self.knobnames[n])
+
+
+class GrDataPlotterPsdF(GrDataPlotParent):
+ def __init__(self, name, rate, pmin=None, pmax=None):
+ GrDataPlotParent.__init__(self, name, rate, pmin, pmax)
+
+ self._datasize = gr.sizeof_float
+ self._stripchart = True
+ self._iscomplex = False
+
+ self._npts = 2048
+ self._wintype = filter.firdes.WIN_BLACKMAN_hARRIS
+ self._fc = 0
+
+ self._setup(1)
+
+ def get_qtsink(self):
+ snk = qtgui.freq_sink_f(self._npts, self._wintype,
+ self._fc, 1.0,
+ self._name,
+ self._ncons)
+ snk.enable_autoscale(True)
+ return snk
+
+ def get_vecsource(self):
+ return blocks.vector_source_f([])
+
+ def get_npts(self):
+ self._npts = self.snk.fft_size()
+ return self._npts
+
+ def set_line_label(self, n, name):
+ self.snk.set_line_label(n, self.knobnames[n])
+
+
+class GrTimeRasterF(GrDataPlotParent):
+ def __init__(self, name, rate, pmin=None, pmax=None):
+ GrDataPlotParent.__init__(self, name, rate, pmin, pmax)
+
+ self._npts = 10
+ self._rows = 40
+
+ self._datasize = gr.sizeof_float
+ self._stripchart = False
+ self._iscomplex = False
+
+ self._setup(1)
+
+ def get_qtsink(self):
+ snk = qtgui.time_raster_sink_f(1.0, self._npts, self._rows,
+ [], [], self._name,
+ self._ncons)
+ return snk
+
+ def get_vecsource(self):
+ return blocks.vector_source_f([])
+
+ def get_npts(self):
+ self._npts = self.snk.num_cols()
+ return self._npts
+
+ def set_line_label(self, n, name):
+ self.snk.set_line_label(n, self.knobnames[n])
+
+class GrTimeRasterB(GrDataPlotParent):
+ def __init__(self, name, rate, pmin=None, pmax=None):
+ GrDataPlotParent.__init__(self, name, rate, pmin, pmax)
+
+ self._npts = 10
+ self._rows = 40
+
+ self._datasize = gr.sizeof_char
+ self._stripchart = False
+ self._iscomplex = False
+
+ self._setup(1)
+
+ def get_qtsink(self):
+ snk = qtgui.time_raster_sink_b(1.0, self._npts, self._rows,
+ [], [], self._name,
+ self._ncons)
+ return snk
+
+ def get_vecsource(self):
+ return blocks.vector_source_b([])
+
+ def get_npts(self):
+ self._npts = self.snk.num_cols()
+ return self._npts
+
+ def set_line_label(self, n, name):
+ self.snk.set_line_label(n, self.knobnames[n])
+
+
+class GrDataPlotterValueTable:
+ def __init__(self, uid, parent, x, y, xsize, ysize,
+ headers=['Statistic Key ( Source Block :: Stat Name ) ',
+ 'Curent Value', 'Units', 'Description']):
+ # must encapsulate, cuz Qt's bases are not classes
+ self.uid = uid
+ self.treeWidget = QtGui.QTreeWidget(parent)
+ self.treeWidget.setColumnCount(len(headers))
+ self.treeWidget.setGeometry(x,y,xsize,ysize)
+ self.treeWidget.setHeaderLabels(headers)
+ self.treeWidget.resizeColumnToContents(0)
+
+ def updateItems(self, knobs, knobprops):
+ items = [];
+ self.treeWidget.clear()
+ for k, v in knobs.iteritems():
+ items.append(QtGui.QTreeWidgetItem([str(k), str(v.value),
+ knobprops[k].units,
+ knobprops[k].description]))
+ self.treeWidget.insertTopLevelItems(0, items)
diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/IceRadioClient.py b/gnuradio-runtime/python/gnuradio/ctrlport/IceRadioClient.py
new file mode 100644
index 0000000000..0964b5a4ba
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/ctrlport/IceRadioClient.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+#
+# Copyright 2012 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+import Ice, Glacier2
+from PyQt4 import QtGui, QtCore
+import sys, time, Ice
+from gnuradio import gr
+from gnuradio.ctrlport import GNURadio
+
+class IceRadioClient(Ice.Application):
+ def __init__(self, parentClass):
+ self.parentClass = parentClass
+
+ def getRadio(self, host, port):
+ radiostr = "gnuradio -t:tcp -h " + host + " -p " + port + " -t 3000"
+ base = self.communicator().stringToProxy(radiostr).ice_twoway()
+ radio = GNURadio.ControlPortPrx.checkedCast(base)
+
+ if not radio:
+ sys.stderr.write("{0} : invalid proxy.\n".format(args[0]))
+ return None
+
+ return radio
+
+ def run(self,args):
+ if len(args) < 2:
+ print "useage: [glacierinstance glacierhost glacierport] host port"
+ return
+ if len(args) == 8:
+ self.useglacier = True
+ guser = args[1]
+ gpass = args[2]
+ ginst = args[3]
+ ghost = args[4]
+ gport = args[5]
+ host = args[6]
+ port = args[7]
+ else:
+ self.useglacier = False
+ host = args[1]
+ port = args[2]
+
+ if self.useglacier:
+ gstring = ginst + "/router -t:tcp -h " + ghost + " -p " + gport
+ print "GLACIER: {0}".format(gstring)
+
+ setrouter = Glacier2.RouterPrx.checkedCast(self.communicator().stringToProxy(gstring))
+ self.communicator().setDefaultRouter(setrouter)
+ defaultRouter = self.communicator().getDefaultRouter()
+ #defaultRouter = self.communicator().stringToProxy(gstring)
+ if not defaultRouter:
+ print self.appName() + ": no default router set"
+ return 1
+ else:
+ print str(defaultRouter)
+ router = Glacier2.RouterPrx.checkedCast(defaultRouter)
+ if not router:
+ print self.appName() + ": configured router is not a Glacier2 router"
+ return 1
+
+ while True:
+ print "This demo accepts any user-id / password combination."
+ if not guser == '' and not gpass == '':
+ id = guser
+ pw = gpass
+ else:
+ id = raw_input("user id: ")
+ pw = raw_input("password: ")
+
+ try:
+ router.createSession(id, pw)
+ break
+ except Glacier2.PermissionDeniedException, ex:
+ print "permission denied:\n" + ex.reason
+
+ radio = self.getRadio(host, port)
+ if(radio is None):
+ return 1
+
+ app = QtGui.QApplication(sys.argv)
+ ex = self.parentClass(radio, port, self)
+ ex.show();
+ app.exec_()
diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/__init__.py b/gnuradio-runtime/python/gnuradio/ctrlport/__init__.py
new file mode 100644
index 0000000000..031c3b424e
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/ctrlport/__init__.py
@@ -0,0 +1,30 @@
+#
+# Copyright 2012 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+# The presence of this file turns this directory into a Python package
+
+import Ice, IcePy
+
+# import swig generated symbols into the ctrlport namespace
+#from ctrlport_swig import *
+from monitor import *
+
+# import any pure python here
+#import GNURadio
diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/gr-ctrlport-curses b/gnuradio-runtime/python/gnuradio/ctrlport/gr-ctrlport-curses
new file mode 100755
index 0000000000..1bee3b1a1e
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/ctrlport/gr-ctrlport-curses
@@ -0,0 +1,268 @@
+#!/usr/bin/env python
+#
+# Copyright 2012 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+import threading
+import curses
+import os, sys, time
+from optparse import OptionParser
+
+import Ice
+from gnuradio.ctrlport import GNURadio
+
+ENTER = chr(10)
+UP_ARROW = chr(65)
+DOWN_ARROW = chr(66)
+
+class modem_monitor(threading.Thread):
+ def __init__(self, cb_live, cb_exit, interface):
+ threading.Thread.__init__(self)
+ self.cb_live = cb_live
+ self.cb_exit = cb_exit
+
+ self.running = True
+
+ def __del__(self):
+ rx.close()
+
+ def run(self):
+ while self.running:
+ time.sleep(0.5)
+
+ def shutdown(self):
+ self.running = False
+ self.rx.close()
+
+ def cb(self,contents):
+ (op, sep, payload) = contents.partition(":")
+ if(op == "live"):
+ print "live"
+ self.cb_live(payload)
+ elif(op == "exit"):
+ self.cb_exit(payload)
+ else:
+ print "unknown op arrived! garbage on multicast adx?"
+
+class modem_window(threading.Thread):
+ def __init__(self, locator):
+ threading.Thread.__init__(self)
+ self.locator = locator
+
+ # curses init
+ self.win = curses.newwin(30,100,4,4)
+
+ # Ice/GRCP init
+ self.comm = Ice.initialize()
+ proxy = self.comm.stringToProxy(locator)
+ self.radio = GNURadio.ControlPortPrx.checkedCast(proxy)
+ self.updateKnobs()
+
+ # GUI init
+ self.running = True
+ self.ssel = 0
+ self.start()
+ #self.updateGUI()
+
+ # input loop
+ while(self.running):
+ self.getInput()
+
+ # wait for update thread exit
+ self.join()
+
+ def updateKnobs(self):
+ self.knobs = self.radio.get([])
+
+ def getInput(self):
+ a = self.win.getch()
+ if(a <= 256):
+ a = chr(a)
+ if(a == 'q'):
+ self.running = False
+ elif(a == UP_ARROW):
+ self.ssel = max(self.ssel-1, 0)
+ self.updateGUI()
+ elif(a == DOWN_ARROW):
+ self.ssel = max(min(self.ssel+1, len(self.knobs.keys())-1),0)
+ self.updateGUI()
+ self.updateGUI()
+
+ def updateGUI(self):
+ self.win.clear()
+ self.win.border(0)
+ self.win.addstr(1, 2, "Modem Statistics :: %s"%(self.locator))
+ self.win.addstr(2, 2, "---------------------------------------------------")
+
+ maxnb = 0
+ maxk = 0
+ for k in self.knobs.keys():
+ (nb,k) = k.split("::", 2)
+ maxnb = max(maxnb,len(nb))
+ maxk = max(maxk,len(k))
+
+ offset = 3
+ keys = self.knobs.keys()
+ keys.sort()
+ for k in keys:
+ (nb,sk) = k.split("::", 2)
+ v = self.knobs[k].value
+ sv = str(v)
+ if(len(sv) > 20):
+ sv = sv[0:20]
+ props = 0
+ if(self.ssel == offset-3):
+ props = props | curses.A_REVERSE
+ self.win.addstr(offset, 2, "%s %s %s" % \
+ (nb.rjust(maxnb," "), sk.ljust(maxk," "), sv),props)
+ offset = offset + 1
+ self.win.refresh()
+
+ def run(self):
+ while(self.running):
+ self.updateKnobs()
+ self.updateGUI()
+ time.sleep(1)
+
+class monitor_gui:
+ def __init__(self, interfaces, options):
+
+ locator = None
+
+ # Extract options into a locator
+ if(options.host and options.port):
+ locator = "{0} -t:{1} -h {2} -p {3}".format(
+ options.app, options.protocol,
+ options.host, options.port)
+
+ # Set up GUI
+ self.locators = {}
+
+ self.mode = 0 # modem index screen (modal keyboard input)
+ self.lsel = 0 # selected locator
+ self.scr = curses.initscr()
+ self.updateGUI()
+
+ # Kick off initial monitors
+ self.monitors = []
+ for i in interfaces:
+ self.monitors.append( modem_monitor(self.addModem, self.delModem, i) )
+ self.monitors[-1].start()
+
+ if not ((locator == None) or (locator == "")):
+ self.addModem(locator)
+
+ # wait for user input
+ while(True):
+ self.getInput()
+
+ def addModem(self, locator):
+ if(not self.locators.has_key(locator)):
+ self.locators[locator] = {}
+ self.locators[locator]["update_time"] = time.time()
+ self.locators[locator]["status"] = "live"
+
+ self.updateGUI();
+
+ def delModem(self, locator):
+ #if(self.locators.has_key(locator)):
+ if(not self.locators.has_key(locator)):
+ self.locators[locator] = {}
+ self.locators[locator]["update_time"] = time.time()
+ self.locators[locator]["status"] = "exit"
+
+ self.updateGUI()
+
+ def updateGUI(self):
+ if(self.mode == 0): #redraw locators
+ self.scr.clear()
+ self.scr.border(0)
+ self.scr.addstr(1, 2, " GRCP-Curses Modem Monitor :: (A)dd (R)efresh, (Q)uit, ...")
+ for i in range(len(self.locators.keys())):
+ locator = self.locators.keys()[i]
+ lhash = self.locators[locator]
+ #self.scr.addstr(3+i, 5, locator + str(lhash))
+ props = 0
+ if(self.lsel == i):
+ props = props | curses.A_REVERSE
+ self.scr.addstr(3+i, 5, locator + str(lhash), props)
+ self.scr.refresh()
+
+ def connectGUI(self):
+ self.scr.clear()
+ self.scr.addstr(1, 1, "Connect to radio:")
+ locator = self.scr.getstr(200)
+ self.addModem(locator)
+ self.updateGUI()
+
+ def getInput(self):
+ a = self.scr.getch()
+ self.scr.addstr(20, 2, "got key (%d) " % (int(a)))
+ if(a <= 256):
+ a = chr(a)
+ if(a =='r'):
+ self.updateGUI()
+ elif(a == 'q'):
+ self.shutdown()
+ elif(a == 'a'):
+ self.connectGUI()
+ elif(a == UP_ARROW):
+ self.lsel = max(self.lsel-1, 0)
+ self.updateGUI()
+ elif(a == DOWN_ARROW):
+ self.lsel = max(min(self.lsel+1, len(self.locators.keys())-1),0)
+ self.updateGUI()
+ elif(a == ENTER):
+ try:
+ locator = self.locators.keys()[self.lsel]
+ self.mode = 1
+ mwin = modem_window(locator)
+ self.mode = 0
+ # pop up a new modem display ...
+ self.updateGUI()
+ except:
+ pass
+
+ def shutdown(self):
+ curses.endwin()
+ os._exit(0)
+
+if __name__ == "__main__":
+ parser = OptionParser()
+ parser.add_option("-H", "--host", type="string",
+ help="Hostname of ControlPort server.")
+ parser.add_option("-p", "--port", type="int",
+ help="Port number of host's ControlPort endpoint.")
+ parser.add_option("-i", "--interfaces", type="string",
+ action="append", default=["lo"],
+ help="Interfaces to use. [default=%default]")
+ parser.add_option("-P", "--protocol", type="string", default="tcp",
+ help="Type of protocol to use (usually tcp). [default=%default]")
+ parser.add_option("-a", "--app", type="string", default="gnuradio",
+ help="Name of application [default=%default]")
+ (options, args) = parser.parse_args()
+
+ if((options.host == None) ^ (options.port == None)):
+ print "Please set both a hostname and a port number.\n"
+ parser.print_help()
+ sys.exit(1)
+
+ mg = monitor_gui(options.interfaces, options)
+
diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/gr-ctrlport-monitor b/gnuradio-runtime/python/gnuradio/ctrlport/gr-ctrlport-monitor
new file mode 100755
index 0000000000..e71cd92ab7
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/ctrlport/gr-ctrlport-monitor
@@ -0,0 +1,721 @@
+#!/usr/bin/env python
+#
+# Copyright 2012,2013 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from gnuradio import gr, ctrlport
+
+from PyQt4 import QtCore,Qt
+import PyQt4.QtGui as QtGui
+import os, sys, time
+
+import Ice
+from gnuradio.ctrlport.IceRadioClient import *
+from gnuradio.ctrlport.GrDataPlotter import *
+from gnuradio.ctrlport import GNURadio
+
+class RateDialog(QtGui.QDialog):
+ def __init__(self, delay, parent=None):
+ super(RateDialog, self).__init__(parent)
+ self.gridLayout = QtGui.QGridLayout(self)
+ self.setWindowTitle("Update Delay (ms)");
+ self.delay = QtGui.QLineEdit(self);
+ self.delay.setText(str(delay));
+ self.buttonBox = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)
+ self.gridLayout.addWidget(self.delay);
+ self.gridLayout.addWidget(self.buttonBox);
+ self.buttonBox.accepted.connect(self.accept)
+ self.buttonBox.rejected.connect(self.reject)
+ def accept(self):
+ self.done(1);
+ def reject(self):
+ self.done(0);
+
+class MAINWindow(QtGui.QMainWindow):
+ def minimumSizeHint(self):
+ return Qtgui.QSize(800,600)
+
+ def __init__(self, radio, port, interface):
+
+ super(MAINWindow, self).__init__()
+ self.updateRate = 1000;
+ self.conns = []
+ self.plots = []
+ self.knobprops = []
+ self.interface = interface
+
+ self.mdiArea = QtGui.QMdiArea()
+ self.mdiArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
+ self.mdiArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
+ self.setCentralWidget(self.mdiArea)
+
+ self.mdiArea.subWindowActivated.connect(self.updateMenus)
+ self.windowMapper = QtCore.QSignalMapper(self)
+ self.windowMapper.mapped[QtGui.QWidget].connect(self.setActiveSubWindow)
+
+ self.createActions()
+ self.createMenus()
+ self.createToolBars()
+ self.createStatusBar()
+ self.updateMenus()
+
+ self.setWindowTitle("GNU Radio Control Port Monitor")
+ self.setUnifiedTitleAndToolBarOnMac(True)
+
+ self.newCon(radio, port)
+ icon = QtGui.QIcon(ctrlport.__path__[0] + "/icon.png" )
+ self.setWindowIcon(icon)
+
+ # Locally turn off ControlPort export from GR. This prevents
+ # our GR-based plotters from launching their own ControlPort
+ # instance (and possibly causing a port collision if one has
+ # been specified).
+ os.environ['GR_CONF_CONTROLPORT_ON'] = 'False'
+
+ def setUpdateRate(self,nur):
+ self.updateRate = int(nur);
+ for c in self.conns:
+ c.updateRate = self.updateRate;
+ c.timer.setInterval(self.updateRate);
+
+ def newCon(self, radio=None, port=None):
+ child = MForm(radio, port, len(self.conns), self.updateRate, self)
+ if(child.radio is not None):
+ child.setWindowTitle(str(child.radio))
+ self.mdiArea.addSubWindow(child)
+ child.showMaximized()
+ self.conns.append(child)
+ self.plots.append([])
+
+ def propertiesMenu(self, key, radio, uid):
+ r = str(radio).split(" ")
+ title = "{0}:{1}".format(r[3], r[5])
+
+ props = radio.properties([key])
+ pmin = props[key].min.value
+ pmax = props[key].max.value
+ if pmin == []:
+ pmin = None
+ else:
+ pmin = 1.1*pmin
+ if pmax == []:
+ pmax = None
+ else:
+ pmax = 1.1*pmax
+
+ # Use display option mask of item to set up available plot
+ # types and default options.
+ disp = self.knobprops[uid][key].display
+ cplx = disp & gr.DISPOPTCPLX | disp & gr.DISPXY
+ strip = disp & gr.DISPOPTSTRIP
+ stem = disp & gr.DISPOPTSTEM
+ log = disp & gr.DISPOPTLOG
+ scatter = disp & gr.DISPOPTSCATTER
+
+ def newUpdaterProxy():
+ self.newUpdater(key, radio)
+
+ def newPlotterFProxy():
+ self.newPlotF(key, uid, title, pmin, pmax,
+ log, strip, stem)
+
+ def newPlotterCProxy():
+ self.newPlotC(key, uid, title, pmin, pmax,
+ log, strip, stem)
+
+ def newPlotterConstProxy():
+ self.newPlotConst(key, uid, title, pmin, pmax, scatter)
+
+ def newPlotterPsdFProxy():
+ self.newPlotPsdF(key, uid, title)
+
+ def newPlotterPsdCProxy():
+ self.newPlotPsdC(key, uid, title)
+
+ def newPlotterRasterFProxy():
+ self.newPlotRasterF(key, uid, title, pmin, pmax)
+
+ def newPlotterRasterBProxy():
+ self.newPlotRasterB(key, uid, title, pmin, pmax)
+
+ menu = QtGui.QMenu(self)
+ menu.setTitle("Item Actions")
+ menu.setTearOffEnabled(False)
+
+ # object properties
+ menu.addAction("Properties", newUpdaterProxy)
+
+ # displays available
+ if(cplx == 0):
+ menu.addAction("Plot Time", newPlotterFProxy)
+ menu.addAction("Plot PSD", newPlotterPsdFProxy)
+ menu.addAction("Plot Raster (real)", newPlotterRasterFProxy)
+ #menu.addAction("Plot Raster (bits)", newPlotterRasterBProxy)
+ else:
+ menu.addAction("Plot Time", newPlotterCProxy)
+ menu.addAction("Plot PSD", newPlotterPsdCProxy)
+ menu.addAction("Plot Constellation", newPlotterConstProxy)
+
+ menu.popup(QtGui.QCursor.pos())
+
+ def newUpdater(self, key, radio):
+ updater = UpdaterWindow(key, radio, None)
+ updater.setWindowTitle("Updater: " + key)
+ updater.setModal(False)
+ updater.exec_()
+
+ def newSub(self, e):
+ tag = str(e.text(0))
+ tree = e.treeWidget().parent()
+ uid = tree.uid
+ knobprop = self.knobprops[uid][tag]
+
+ r = str(tree.radio).split(" ")
+ title = "{0}:{1}".format(r[3], r[5])
+ pmin = knobprop.min.value
+ pmax = knobprop.max.value
+ if pmin == []:
+ pmin = None
+ else:
+ pmin = 1.1*pmin
+ if pmax == []:
+ pmax = None
+ else:
+ pmax = 1.1*pmax
+
+ disp = knobprop.display
+ if(disp & gr.DISPTIME):
+ strip = disp & gr.DISPOPTSTRIP
+ stem = disp & gr.DISPOPTSTEM
+ log = disp & gr.DISPOPTLOG
+ if(disp & gr.DISPOPTCPLX == 0):
+ self.newPlotF(tag, uid, title, pmin, pmax,
+ log, strip, stem)
+ else:
+ self.newPlotC(tag, uid, title, pmin, pmax,
+ log, strip, stem)
+
+ elif(disp & gr.DISPXY):
+ scatter = disp & gr.DISPOPTSCATTER
+ self.newPlotConst(tag, uid, title, pmin, pmax, scatter)
+
+ elif(disp & gr.DISPPSD):
+ if(disp & gr.DISPOPTCPLX == 0):
+ self.newPlotPsdF(tag, uid, title)
+ else:
+ self.newPlotPsdC(tag, uid, title)
+
+ def startDrag(self, e):
+ drag = QtGui.QDrag(self)
+ mime_data = QtCore.QMimeData()
+
+ tag = str(e.text(0))
+ tree = e.treeWidget().parent()
+ knobprop = self.knobprops[tree.uid][tag]
+ disp = knobprop.display
+ iscomplex = (disp & gr.DISPOPTCPLX) or (disp & gr.DISPXY)
+
+ if(disp != gr.DISPNULL):
+ data = "PlotData:::{0}:::{1}".format(tag, iscomplex)
+ else:
+ data = "OtherData:::{0}:::{1}".format(tag, iscomplex)
+
+ mime_data.setText(data)
+ drag.setMimeData(mime_data)
+
+ drop = drag.start()
+
+ def createPlot(self, plot, uid, title):
+ plot.start()
+ self.plots[uid].append(plot)
+
+ self.mdiArea.addSubWindow(plot)
+ plot.setWindowTitle("{0}: {1}".format(title, plot.name()))
+ self.connect(plot.qwidget(),
+ QtCore.SIGNAL('destroyed(QObject*)'),
+ self.destroyPlot)
+
+ # when the plot is updated via drag-and-drop, we need to be
+ # notified of the new qwidget that's created so we can
+ # properly destroy it.
+ plot.plotupdated.connect(self.plotUpdated)
+
+ plot.show()
+
+ def plotUpdated(self, q):
+ # the plot has been updated with a new qwidget; make sure this
+ # gets dies to the destroyPlot function.
+ for i, plots in enumerate(self.plots):
+ for p in plots:
+ if(p == q):
+ #plots.remove(p)
+ #plots.append(q)
+ self.connect(q.qwidget(),
+ QtCore.SIGNAL('destroyed(QObject*)'),
+ self.destroyPlot)
+ break
+
+ def destroyPlot(self, obj):
+ for plots in self.plots:
+ for p in plots:
+ if p.qwidget() == obj:
+ plots.remove(p)
+ break
+
+ def newPlotConst(self, tag, uid, title="", pmin=None, pmax=None,
+ scatter=False):
+ plot = GrDataPlotterConst(tag, 32e6, pmin, pmax)
+ plot.scatter(scatter)
+ self.createPlot(plot, uid, title)
+
+ def newPlotF(self, tag, uid, title="", pmin=None, pmax=None,
+ logy=False, stripchart=False, stem=False):
+ plot = GrDataPlotterF(tag, 32e6, pmin, pmax, stripchart)
+ plot.semilogy(logy)
+ plot.stem(stem)
+ self.createPlot(plot, uid, title)
+
+ def newPlotC(self, tag, uid, title="", pmin=None, pmax=None,
+ logy=False, stripchart=False, stem=False):
+ plot = GrDataPlotterC(tag, 32e6, pmin, pmax, stripchart)
+ plot.semilogy(logy)
+ plot.stem(stem)
+ self.createPlot(plot, uid, title)
+
+ def newPlotPsdF(self, tag, uid, title="", pmin=None, pmax=None):
+ plot = GrDataPlotterPsdF(tag, 32e6, pmin, pmax)
+ self.createPlot(plot, uid, title)
+
+ def newPlotPsdC(self, tag, uid, title="", pmin=None, pmax=None):
+ plot = GrDataPlotterPsdC(tag, 32e6, pmin, pmax)
+ self.createPlot(plot, uid, title)
+
+ def newPlotRasterF(self, tag, uid, title="", pmin=None, pmax=None):
+ plot = GrTimeRasterF(tag, 32e6, pmin, pmax)
+ self.createPlot(plot, uid, title)
+
+ def newPlotRasterB(self, tag, uid, title="", pmin=None, pmax=None):
+ plot = GrTimeRasterB(tag, 32e6, pmin, pmax)
+ self.createPlot(plot, uid, title)
+
+ def update(self, knobs, uid):
+ #sys.stderr.write("KNOB KEYS: {0}\n".format(knobs.keys()))
+ for plot in self.plots[uid]:
+ data = []
+ for n in plot.knobnames:
+ data.append(knobs[n].value)
+ plot.update(data)
+ plot.stop()
+ plot.wait()
+ plot.start()
+
+ def setActiveSubWindow(self, window):
+ if window:
+ self.mdiArea.setActiveSubWindow(window)
+
+
+ def createActions(self):
+ self.newConAct = QtGui.QAction("&New Connection",
+ self, shortcut=QtGui.QKeySequence.New,
+ statusTip="Create a new file", triggered=self.newCon)
+ #self.newAct = QtGui.QAction(QtGui.QIcon(':/images/new.png'), "&New Plot",
+ self.newPlotAct = QtGui.QAction("&New Plot",
+ self,
+ statusTip="Create a new file", triggered=self.newPlotF)
+
+ self.exitAct = QtGui.QAction("E&xit", self, shortcut="Ctrl+Q",
+ statusTip="Exit the application",
+ triggered=QtGui.qApp.closeAllWindows)
+
+ self.closeAct = QtGui.QAction("Cl&ose", self, shortcut="Ctrl+F4",
+ statusTip="Close the active window",
+ triggered=self.mdiArea.closeActiveSubWindow)
+
+ self.closeAllAct = QtGui.QAction("Close &All", self,
+ statusTip="Close all the windows",
+ triggered=self.mdiArea.closeAllSubWindows)
+
+ self.urAct = QtGui.QAction("Update Rate", self, shortcut="F5",
+ statusTip="Change Update Rate",
+ triggered=self.updateRateShow)
+
+ qks = QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_T);
+ self.tileAct = QtGui.QAction("&Tile", self,
+ statusTip="Tile the windows",
+ triggered=self.mdiArea.tileSubWindows,
+ shortcut=qks)
+
+ qks = QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_C);
+ self.cascadeAct = QtGui.QAction("&Cascade", self,
+ statusTip="Cascade the windows", shortcut=qks,
+ triggered=self.mdiArea.cascadeSubWindows)
+
+ self.nextAct = QtGui.QAction("Ne&xt", self,
+ shortcut=QtGui.QKeySequence.NextChild,
+ statusTip="Move the focus to the next window",
+ triggered=self.mdiArea.activateNextSubWindow)
+
+ self.previousAct = QtGui.QAction("Pre&vious", self,
+ shortcut=QtGui.QKeySequence.PreviousChild,
+ statusTip="Move the focus to the previous window",
+ triggered=self.mdiArea.activatePreviousSubWindow)
+
+ self.separatorAct = QtGui.QAction(self)
+ self.separatorAct.setSeparator(True)
+
+ self.aboutAct = QtGui.QAction("&About", self,
+ statusTip="Show the application's About box",
+ triggered=self.about)
+
+ self.aboutQtAct = QtGui.QAction("About &Qt", self,
+ statusTip="Show the Qt library's About box",
+ triggered=QtGui.qApp.aboutQt)
+
+ def createMenus(self):
+ self.fileMenu = self.menuBar().addMenu("&File")
+ self.fileMenu.addAction(self.newConAct)
+ self.fileMenu.addAction(self.newPlotAct)
+ self.fileMenu.addAction(self.urAct)
+ self.fileMenu.addSeparator()
+ self.fileMenu.addAction(self.exitAct)
+
+ self.windowMenu = self.menuBar().addMenu("&Window")
+ self.updateWindowMenu()
+ self.windowMenu.aboutToShow.connect(self.updateWindowMenu)
+
+ self.menuBar().addSeparator()
+
+ self.helpMenu = self.menuBar().addMenu("&Help")
+ self.helpMenu.addAction(self.aboutAct)
+ self.helpMenu.addAction(self.aboutQtAct)
+
+ def updateRateShow(self):
+ askrate = RateDialog(self.updateRate, self);
+ if askrate.exec_():
+ ur = float(str(askrate.delay.text()));
+ self.setUpdateRate(ur);
+ return;
+ else:
+ return;
+
+ def createToolBars(self):
+ self.fileToolBar = self.addToolBar("File")
+ self.fileToolBar.addAction(self.newConAct)
+ self.fileToolBar.addAction(self.newPlotAct)
+ self.fileToolBar.addAction(self.urAct)
+
+ self.fileToolBar = self.addToolBar("Window")
+ self.fileToolBar.addAction(self.tileAct)
+ self.fileToolBar.addAction(self.cascadeAct)
+
+ def createStatusBar(self):
+ self.statusBar().showMessage("Ready")
+
+
+ def activeMdiChild(self):
+ activeSubWindow = self.mdiArea.activeSubWindow()
+ if activeSubWindow:
+ return activeSubWindow.widget()
+ return None
+
+ def updateMenus(self):
+ hasMdiChild = (self.activeMdiChild() is not None)
+ self.closeAct.setEnabled(hasMdiChild)
+ self.closeAllAct.setEnabled(hasMdiChild)
+ self.tileAct.setEnabled(hasMdiChild)
+ self.cascadeAct.setEnabled(hasMdiChild)
+ self.nextAct.setEnabled(hasMdiChild)
+ self.previousAct.setEnabled(hasMdiChild)
+ self.separatorAct.setVisible(hasMdiChild)
+
+ def updateWindowMenu(self):
+ self.windowMenu.clear()
+ self.windowMenu.addAction(self.closeAct)
+ self.windowMenu.addAction(self.closeAllAct)
+ self.windowMenu.addSeparator()
+ self.windowMenu.addAction(self.tileAct)
+ self.windowMenu.addAction(self.cascadeAct)
+ self.windowMenu.addSeparator()
+ self.windowMenu.addAction(self.nextAct)
+ self.windowMenu.addAction(self.previousAct)
+ self.windowMenu.addAction(self.separatorAct)
+
+ def about(self):
+ about_info = \
+'''Copyright 2012 Free Software Foundation, Inc.\n
+This program is part of GNU Radio.\n
+GNU Radio is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version.\n
+GNU Radio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\n
+You should have received a copy of the GNU General Public License along with GNU Radio; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Boston, MA 02110-1301, USA.'''
+
+ QtGui.QMessageBox.about(None, "gr-ctrlport-monitor", about_info)
+
+
+class ConInfoDialog(QtGui.QDialog):
+ def __init__(self, parent=None):
+ super(ConInfoDialog, self).__init__(parent)
+
+ self.gridLayout = QtGui.QGridLayout(self)
+
+
+ self.host = QtGui.QLineEdit(self);
+ self.port = QtGui.QLineEdit(self);
+ self.host.setText("localhost");
+ self.port.setText("43243");
+
+ self.buttonBox = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)
+
+ self.gridLayout.addWidget(self.host);
+ self.gridLayout.addWidget(self.port);
+ self.gridLayout.addWidget(self.buttonBox);
+
+ self.buttonBox.accepted.connect(self.accept)
+ self.buttonBox.rejected.connect(self.reject)
+
+
+ def accept(self):
+ self.done(1);
+
+ def reject(self):
+ self.done(0);
+
+
+class UpdaterWindow(QtGui.QDialog):
+ def __init__(self, key, radio, parent):
+ QtGui.QDialog.__init__(self, parent)
+
+ self.key = key;
+ self.radio = radio
+
+ self.resize(300,200)
+ self.layout = QtGui.QVBoxLayout()
+
+ self.props = radio.properties([key])[key]
+ info = str(self.props)
+
+ self.infoLabel = QtGui.QLabel(info)
+ self.layout.addWidget(self.infoLabel)
+
+ # Test here to make sure that a 'set' function
+ try:
+ a = radio.set(radio.get([key]))
+ has_set = True
+ except Ice.UnknownException:
+ has_set = False
+
+ if(has_set is False):
+ self.cancelButton = QtGui.QPushButton("Ok")
+ self.cancelButton.connect(self.cancelButton, QtCore.SIGNAL('clicked()'), self.reject)
+
+ self.buttonlayout = QtGui.QHBoxLayout()
+ self.buttonlayout.addWidget(self.cancelButton)
+ self.layout.addLayout(self.buttonlayout)
+
+ else: # we have a set function
+ self.textInput = QtGui.QLineEdit()
+ self.layout.addWidget(self.textInput)
+
+ self.applyButton = QtGui.QPushButton("Apply")
+ self.setButton = QtGui.QPushButton("OK")
+ self.cancelButton = QtGui.QPushButton("Cancel")
+
+ rv = radio.get([key])
+ self.textInput.setText(str(rv[key].value))
+ self.sv = rv[key]
+
+ self.applyButton.connect(self.applyButton, QtCore.SIGNAL('clicked()'), self._apply)
+ self.setButton.connect(self.setButton, QtCore.SIGNAL('clicked()'), self._set)
+ self.cancelButton.connect(self.cancelButton, QtCore.SIGNAL('clicked()'), self.reject)
+
+ self.is_num = ((type(self.sv.value)==float) or (type(self.sv.value)==int))
+ if(self.is_num):
+ self.sliderlayout = QtGui.QHBoxLayout()
+
+ self.slider = QtGui.QSlider(QtCore.Qt.Horizontal)
+
+ self.sliderlayout.addWidget(QtGui.QLabel(str(self.props.min.value)))
+ self.sliderlayout.addWidget(self.slider)
+ self.sliderlayout.addWidget(QtGui.QLabel(str(self.props.max.value)))
+
+ self.steps = 10000
+ self.valspan = self.props.max.value - self.props.min.value
+
+ self.slider.setRange(0, 10000)
+ self._set_slider_value(self.sv.value)
+
+ self.connect(self.slider, QtCore.SIGNAL("sliderReleased()"), self._slide)
+
+ self.layout.addLayout(self.sliderlayout)
+
+ self.buttonlayout = QtGui.QHBoxLayout()
+ self.buttonlayout.addWidget(self.applyButton)
+ self.buttonlayout.addWidget(self.setButton)
+ self.buttonlayout.addWidget(self.cancelButton)
+ self.layout.addLayout(self.buttonlayout)
+
+ # set layout and go...
+ self.setLayout(self.layout)
+
+ def _set_slider_value(self, val):
+ self.slider.setValue(self.steps*(val-self.props.min.value)/self.valspan)
+
+ def _slide(self):
+ val = (self.slider.value()*self.valspan + self.props.min.value)/float(self.steps)
+ self.textInput.setText(str(val))
+
+ def _apply(self):
+ if(type(self.sv.value) == str):
+ val = str(self.textInput.text())
+ elif(type(self.sv.value) == int):
+ val = int(round(float(self.textInput.text())))
+ elif(type(self.sv.value) == float):
+ val = float(self.textInput.text())
+ else:
+ sys.stderr.write("set type not supported! ({0})\n".format(type(self.sv.value)))
+ sys.exit(-1)
+
+ self.sv.value = val
+ km = {}
+ km[self.key] = self.sv
+ self.radio.set(km)
+ self._set_slider_value(self.sv.value)
+
+ def _set(self):
+ self._apply()
+ self.done(0)
+
+
+class MForm(QtGui.QWidget):
+ def update(self):
+ try:
+ st = time.time();
+ knobs = self.radio.get([]);
+ ft = time.time();
+ latency = ft-st;
+ self.parent.statusBar().showMessage("Current GNU Radio Control Port Query Latency: %f ms"%(latency*1000))
+
+ except Exception, e:
+ sys.stderr.write("ctrlport-monitor: radio.get threw exception ({0}).\n".format(e))
+ if(type(self.parent) is MAINWindow):
+ # Find window of connection
+ remove = []
+ for p in self.parent.mdiArea.subWindowList():
+ if self.parent.conns[self.uid] == p.widget():
+ remove.append(p)
+
+ # Find any subplot windows of connection
+ for p in self.parent.mdiArea.subWindowList():
+ for plot in self.parent.plots[self.uid]:
+ if plot.qwidget() == p.widget():
+ remove.append(p)
+
+ # Clean up local references to these
+ self.parent.conns.remove(self.parent.conns[self.uid])
+ self.parent.plots.remove(self.parent.plots[self.uid])
+
+ # Remove subwindows for connection and plots
+ for r in remove:
+ self.parent.mdiArea.removeSubWindow(r)
+
+ # Clean up self
+ self.close()
+ else:
+ sys.exit(1)
+ return
+
+ tableitems = knobs.keys()
+
+ #UPDATE TABLE:
+ self.table.updateItems(knobs, self.knobprops)
+
+ #UPDATE PLOTS
+ self.parent.update(knobs, self.uid)
+
+
+ def __init__(self, radio=None, port=None, uid=0, updateRate=2000, parent=None):
+
+ super(MForm, self).__init__()
+
+ if(radio == None or port == None):
+ askinfo = ConInfoDialog(self);
+ if askinfo.exec_():
+ host = str(askinfo.host.text());
+ port = str(askinfo.port.text());
+ radio = parent.interface.getRadio(host, port)
+ else:
+ self.radio = None
+ return
+
+ self.uid = uid
+ self.parent = parent
+ self.horizontalLayout = QtGui.QVBoxLayout(self)
+ self.gridLayout = QtGui.QGridLayout()
+
+ self.radio = radio
+ self.knobprops = self.radio.properties([])
+ self.parent.knobprops.append(self.knobprops)
+ self.resize(775,500)
+ self.timer = QtCore.QTimer()
+ self.constupdatediv = 0
+ self.tableupdatediv = 0
+ plotsize=250
+
+ # make table
+ self.table = GrDataPlotterValueTable(uid, self, 0, 0, 400, 200)
+ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
+ self.table.treeWidget.setSizePolicy(sizePolicy)
+ self.table.treeWidget.setEditTriggers(QtGui.QAbstractItemView.EditKeyPressed)
+ self.table.treeWidget.setSortingEnabled(True)
+ self.table.treeWidget.setDragEnabled(True)
+
+ # add things to layouts
+ self.horizontalLayout.addWidget(self.table.treeWidget)
+
+ # set up timer
+ self.connect(self.timer, QtCore.SIGNAL('timeout()'), self.update)
+ self.updateRate = updateRate;
+ self.timer.start(self.updateRate)
+
+ # set up context menu ..
+ self.table.treeWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+ self.table.treeWidget.customContextMenuRequested.connect(self.openMenu)
+
+ # Set up double-click to launch default plotter
+ self.connect(self.table.treeWidget,
+ QtCore.SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'),
+ self.parent.newSub);
+
+ # Allow drag/drop event from table item to plotter
+ self.connect(self.table.treeWidget,
+ QtCore.SIGNAL('itemPressed(QTreeWidgetItem*, int)'),
+ self.parent.startDrag)
+
+ def openMenu(self, pos):
+ index = self.table.treeWidget.selectedIndexes()
+ item = self.table.treeWidget.itemFromIndex(index[0])
+ itemname = str(item.text(0))
+ self.parent.propertiesMenu(itemname, self.radio, self.uid)
+
+
+class MyClient(IceRadioClient):
+ def __init__(self):
+ IceRadioClient.__init__(self, MAINWindow)
+
+sys.exit(MyClient().main(sys.argv))
diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitor b/gnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitor
new file mode 100755
index 0000000000..f2c01691a1
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitor
@@ -0,0 +1,591 @@
+#!/usr/bin/env python
+#
+# Copyright 2012-2013 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from gnuradio import gr, ctrlport
+
+from PyQt4 import QtCore,Qt,Qwt5
+import PyQt4.QtGui as QtGui
+import sys, time, re, pprint
+import itertools
+import scipy
+
+import Ice
+from gnuradio.ctrlport.IceRadioClient import *
+from gnuradio.ctrlport.GrDataPlotter import *
+from gnuradio.ctrlport import GNURadio
+
+class MAINWindow(QtGui.QMainWindow):
+ def minimumSizeHint(self):
+ return QtGui.QSize(800,600)
+
+ def __init__(self, radio, port, interface):
+
+ super(MAINWindow, self).__init__()
+ self.conns = []
+ self.plots = []
+ self.knobprops = []
+ self.interface = interface
+
+ self.mdiArea = QtGui.QMdiArea()
+ self.mdiArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
+ self.mdiArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
+ self.setCentralWidget(self.mdiArea)
+
+ self.mdiArea.subWindowActivated.connect(self.updateMenus)
+ self.windowMapper = QtCore.QSignalMapper(self)
+ self.windowMapper.mapped[QtGui.QWidget].connect(self.setActiveSubWindow)
+
+ self.createActions()
+ self.createMenus()
+ self.createToolBars()
+ self.createStatusBar()
+ self.updateMenus()
+
+ self.setWindowTitle("GNU Radio Performance Monitor")
+ self.setUnifiedTitleAndToolBarOnMac(True)
+
+ self.newCon(radio, port)
+ icon = QtGui.QIcon(ctrlport.__path__[0] + "/icon.png" )
+ self.setWindowIcon(icon)
+
+ def newCon(self, radio=None, port=None):
+ child = MForm(radio, port, len(self.conns), self)
+ if(child.radio is not None):
+ child.setWindowTitle(str(child.radio))
+ horizbar = QtGui.QScrollArea()
+ horizbar.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
+ horizbar.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
+ horizbar.setWidget(child)
+ self.mdiArea.addSubWindow(horizbar)
+ self.mdiArea.currentSubWindow().showMaximized()
+
+ self.conns.append(child)
+ self.plots.append([])
+
+ def newUpdater(self, key, radio):
+ updater = UpdaterWindow(key, radio, None)
+ updater.setWindowTitle("Updater: " + key)
+ updater.setModal(False)
+ updater.exec_()
+
+ def update(self, knobs, uid):
+ #sys.stderr.write("KNOB KEYS: {0}\n".format(knobs.keys()))
+ for plot in self.plots[uid]:
+ data = knobs[plot.name()].value
+ plot.update(data)
+ plot.stop()
+ plot.wait()
+ plot.start()
+
+ def setActiveSubWindow(self, window):
+ if window:
+ self.mdiArea.setActiveSubWindow(window)
+
+
+ def createActions(self):
+ self.newConAct = QtGui.QAction("&New Connection",
+ self, shortcut=QtGui.QKeySequence.New,
+ statusTip="Create a new file", triggered=self.newCon)
+
+ self.exitAct = QtGui.QAction("E&xit", self, shortcut="Ctrl+Q",
+ statusTip="Exit the application",
+ triggered=QtGui.qApp.closeAllWindows)
+
+ self.closeAct = QtGui.QAction("Cl&ose", self, shortcut="Ctrl+F4",
+ statusTip="Close the active window",
+ triggered=self.mdiArea.closeActiveSubWindow)
+
+ self.closeAllAct = QtGui.QAction("Close &All", self,
+ statusTip="Close all the windows",
+ triggered=self.mdiArea.closeAllSubWindows)
+
+
+ qks = QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_T);
+ self.tileAct = QtGui.QAction("&Tile", self,
+ statusTip="Tile the windows",
+ triggered=self.mdiArea.tileSubWindows,
+ shortcut=qks)
+
+ qks = QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_C);
+ self.cascadeAct = QtGui.QAction("&Cascade", self,
+ statusTip="Cascade the windows", shortcut=qks,
+ triggered=self.mdiArea.cascadeSubWindows)
+
+ self.nextAct = QtGui.QAction("Ne&xt", self,
+ shortcut=QtGui.QKeySequence.NextChild,
+ statusTip="Move the focus to the next window",
+ triggered=self.mdiArea.activateNextSubWindow)
+
+ self.previousAct = QtGui.QAction("Pre&vious", self,
+ shortcut=QtGui.QKeySequence.PreviousChild,
+ statusTip="Move the focus to the previous window",
+ triggered=self.mdiArea.activatePreviousSubWindow)
+
+ self.separatorAct = QtGui.QAction(self)
+ self.separatorAct.setSeparator(True)
+
+ self.aboutAct = QtGui.QAction("&About", self,
+ statusTip="Show the application's About box",
+ triggered=self.about)
+
+ self.aboutQtAct = QtGui.QAction("About &Qt", self,
+ statusTip="Show the Qt library's About box",
+ triggered=QtGui.qApp.aboutQt)
+
+ def createMenus(self):
+ self.fileMenu = self.menuBar().addMenu("&File")
+ self.fileMenu.addAction(self.newConAct)
+ self.fileMenu.addSeparator()
+ self.fileMenu.addAction(self.exitAct)
+
+ self.windowMenu = self.menuBar().addMenu("&Window")
+ self.updateWindowMenu()
+ self.windowMenu.aboutToShow.connect(self.updateWindowMenu)
+
+ self.menuBar().addSeparator()
+
+ self.helpMenu = self.menuBar().addMenu("&Help")
+ self.helpMenu.addAction(self.aboutAct)
+ self.helpMenu.addAction(self.aboutQtAct)
+
+ def createToolBars(self):
+ self.fileToolBar = self.addToolBar("File")
+ self.fileToolBar.addAction(self.newConAct)
+
+ self.fileToolBar = self.addToolBar("Window")
+ self.fileToolBar.addAction(self.tileAct)
+ self.fileToolBar.addAction(self.cascadeAct)
+
+ def createStatusBar(self):
+ self.statusBar().showMessage("Ready")
+
+
+ def activeMdiChild(self):
+ activeSubWindow = self.mdiArea.activeSubWindow()
+ if activeSubWindow:
+ return activeSubWindow.widget()
+ return None
+
+ def updateMenus(self):
+ hasMdiChild = (self.activeMdiChild() is not None)
+ self.closeAct.setEnabled(hasMdiChild)
+ self.closeAllAct.setEnabled(hasMdiChild)
+ self.tileAct.setEnabled(hasMdiChild)
+ self.cascadeAct.setEnabled(hasMdiChild)
+ self.nextAct.setEnabled(hasMdiChild)
+ self.previousAct.setEnabled(hasMdiChild)
+ self.separatorAct.setVisible(hasMdiChild)
+
+ def updateWindowMenu(self):
+ self.windowMenu.clear()
+ self.windowMenu.addAction(self.closeAct)
+ self.windowMenu.addAction(self.closeAllAct)
+ self.windowMenu.addSeparator()
+ self.windowMenu.addAction(self.tileAct)
+ self.windowMenu.addAction(self.cascadeAct)
+ self.windowMenu.addSeparator()
+ self.windowMenu.addAction(self.nextAct)
+ self.windowMenu.addAction(self.previousAct)
+ self.windowMenu.addAction(self.separatorAct)
+
+ def about(self):
+ about_info = \
+'''Copyright 2012 Free Software Foundation, Inc.\n
+This program is part of GNU Radio.\n
+GNU Radio is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version.\n
+GNU Radio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\n
+You should have received a copy of the GNU General Public License along with GNU Radio; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Boston, MA 02110-1301, USA.'''
+
+ QtGui.QMessageBox.about(None, "gr-ctrlport-monitor", about_info)
+
+
+class ConInfoDialog(QtGui.QDialog):
+ def __init__(self, parent=None):
+ super(ConInfoDialog, self).__init__(parent)
+
+ self.gridLayout = QtGui.QGridLayout(self)
+
+
+ self.host = QtGui.QLineEdit(self);
+ self.port = QtGui.QLineEdit(self);
+ self.host.setText("localhost");
+ self.port.setText("43243");
+
+ self.buttonBox = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok |
+ QtGui.QDialogButtonBox.Cancel)
+
+ self.gridLayout.addWidget(self.host);
+ self.gridLayout.addWidget(self.port);
+ self.gridLayout.addWidget(self.buttonBox);
+
+ self.buttonBox.accepted.connect(self.accept)
+ self.buttonBox.rejected.connect(self.reject)
+
+
+ def accept(self):
+ self.done(1);
+
+ def reject(self):
+ self.done(0);
+
+
+class UpdaterWindow(QtGui.QDialog):
+ def __init__(self, key, radio, parent):
+ QtGui.QDialog.__init__(self, parent)
+
+ self.key = key;
+ self.radio = radio
+
+ self.resize(300,200)
+ self.layout = QtGui.QVBoxLayout()
+
+ self.props = radio.properties([key])[key]
+ info = str(self.props)
+
+ self.infoLabel = QtGui.QLabel(info)
+ self.layout.addWidget(self.infoLabel)
+
+ self.cancelButton = QtGui.QPushButton("Ok")
+ self.cancelButton.connect(self.cancelButton, QtCore.SIGNAL('clicked()'), self.reject)
+
+ self.buttonlayout = QtGui.QHBoxLayout()
+ self.buttonlayout.addWidget(self.cancelButton)
+ self.layout.addLayout(self.buttonlayout)
+
+ # set layout and go...
+ self.setLayout(self.layout)
+
+ def _set_slider_value(self, val):
+ self.slider.setValue(self.steps*(val-self.props.min.value)/self.valspan)
+
+ def _slide(self):
+ val = (self.slider.value()*self.valspan + self.props.min.value)/float(self.steps)
+ self.textInput.setText(str(val))
+
+ def _apply(self):
+ if(type(self.sv.value) == str):
+ val = str(self.textInput.text())
+ elif(type(self.sv.value) == int):
+ val = int(round(float(self.textInput.text())))
+ elif(type(self.sv.value) == float):
+ val = float(self.textInput.text())
+ else:
+ sys.stderr.write("set type not supported! ({0})\n".format(type(self.sv.value)))
+ sys.exit(-1)
+
+ self.sv.value = val
+ km = {}
+ km[self.key] = self.sv
+ self.radio.set(km)
+ self._set_slider_value(self.sv.value)
+
+ def _set(self):
+ self._apply()
+ self.done(0)
+
+
+def build_edge_graph(sources, sinks, edges):
+ '''
+ Starting from the sources, walks through all of the edges to find
+ the next connected block. The output is stored in 'allblocks'
+ where each row starts with a source and follows one path down
+ until it terminates in either a sink or as an input to a block
+ that is part of another chain.
+ '''
+ def find_edge(src, sinks, edges, row, col):
+ #print "\n\nAll blocks: "
+ #printer.pprint(allblocks)
+ #print "\nLooking for: ", src
+
+ src0 = src.split(":")[0]
+ if(src0 in sinks):
+ if(len(allblocks) <= row):
+ allblocks.append(col*[""])
+ allblocks[row].append(src)
+ return row+1
+
+ for edge in edges:
+ if(re.match(src0, edge)):
+ s = edge.split("->")[0]
+ b = edge.split("->")[1]
+ if(len(allblocks) <= row):
+ allblocks.append(col*[""])
+ allblocks[row].append(s)
+ #print "Source: {0} Sink: {1}".format(s, b)
+ row = find_edge(b, sinks, edges, row, col+1)
+ return row
+
+ # Recursively get all edges as a matrix of source->sink
+ n = 0
+ allblocks = []
+ for src in sources:
+ n = find_edge(src, sinks, edges, n, 0)
+
+ # Sort by longest list
+ allblocks = sorted(allblocks, key=len)
+ allblocks.reverse()
+
+ # Make all rows same length by padding '' in front of sort rows
+ maxrowlen = len(allblocks[0])
+ for i,a in enumerate(allblocks):
+ rowlen = len(a)
+ allblocks[i] = (maxrowlen-rowlen)*[''] + a
+
+ # Dedup rows
+ allblocks = sorted(allblocks)
+ allblocks = list(k for k,_ in itertools.groupby(allblocks))
+ allblocks.reverse()
+
+ return allblocks
+
+
+class MForm(QtGui.QWidget):
+ def update(self):
+ try:
+ st = time.time()
+ knobs = self.radio.get([b[0] for b in self.block_dict])
+
+ ft = time.time()
+ latency = ft-st
+ self.parent.statusBar().showMessage("Current GNU Radio Control Port Query Latency: %f ms"%\
+ (latency*1000))
+
+ except Exception, e:
+ sys.stderr.write("ctrlport-monitor: radio.get threw exception ({0}).\n".format(e))
+ if(type(self.parent) is MAINWindow):
+ # Find window of connection
+ remove = []
+ for p in self.parent.mdiArea.subWindowList():
+ if self.parent.conns[self.uid] == p.widget():
+ remove.append(p)
+
+ # Find any subplot windows of connection
+ for p in self.parent.mdiArea.subWindowList():
+ for plot in self.parent.plots[self.uid]:
+ if plot.qwidget() == p.widget():
+ remove.append(p)
+
+ # Clean up local references to these
+ self.parent.conns.remove(self.parent.conns[self.uid])
+ self.parent.plots.remove(self.parent.plots[self.uid])
+
+ # Remove subwindows for connection and plots
+ for r in remove:
+ self.parent.mdiArea.removeSubWindow(r)
+
+ # Clean up self
+ self.close()
+ else:
+ sys.exit(1)
+ return
+
+ #UPDATE TABLE:
+ self.updateItems(knobs)
+
+ #UPDATE PLOTS
+ self.parent.update(knobs, self.uid)
+
+ def updateItems(self, knobs):
+ for b in self.block_dict:
+ if(knobs[b[0]].ice_id.im_class == GNURadio.KnobVecF):
+ b[1].setText("{0:.4f}".format(knobs[b[0]].value[b[2]]))
+ else:
+ b[1].setText("{0:.4f}".format(knobs[b[0]].value))
+
+ def __init__(self, radio=None, port=None, uid=0, parent=None):
+
+ super(MForm, self).__init__()
+
+ if(radio == None or port == None):
+ askinfo = ConInfoDialog(self);
+ if askinfo.exec_():
+ host = str(askinfo.host.text());
+ port = str(askinfo.port.text());
+ radio = parent.interface.getRadio(host, port)
+ else:
+ self.radio = None
+ return
+
+ self.uid = uid
+ self.parent = parent
+ self.layout = QtGui.QGridLayout(self)
+ self.layout.setSizeConstraint(QtGui.QLayout.SetFixedSize)
+
+ self.radio = radio
+ self.knobprops = self.radio.properties([])
+ self.parent.knobprops.append(self.knobprops)
+ self.resize(775,500)
+ self.timer = QtCore.QTimer()
+ self.constupdatediv = 0
+ self.tableupdatediv = 0
+ plotsize=250
+
+
+ # Set up the graph of blocks
+ input_name = lambda x: x+"::avg input % full"
+ output_name = lambda x: x+"::avg output % full"
+ wtime_name = lambda x: x+"::avg work time"
+ nout_name = lambda x: x+"::avg noutput_items"
+ nprod_name = lambda x: x+"::avg nproduced"
+
+ tmplist = []
+ knobs = self.radio.get([])
+ edgelist = None
+ for k in knobs:
+ propname = k.split("::")
+ blockname = propname[0]
+ keyname = propname[1]
+ if(keyname == "edge list"):
+ edgelist = knobs[k].value
+ elif(blockname not in tmplist):
+ # only take gr_blocks (no hier_block2)
+ if(knobs.has_key(input_name(blockname))):
+ tmplist.append(blockname)
+
+ if not edgelist:
+ sys.stderr.write("Could not find list of edges from flowgraph. " + \
+ "Make sure the option 'edges_list' is enabled " + \
+ "in the ControlPort configuration.\n\n")
+ sys.exit(1)
+
+ edges = edgelist.split("\n")[0:-1]
+ producers = []
+ consumers = []
+ for e in edges:
+ _e = e.split("->")
+ producers.append(_e[0])
+ consumers.append(_e[1])
+
+ # Get producers and consumers as sets while ignoring the
+ # ports.
+ prods = set(map(lambda x: x.split(":")[0], producers))
+ cons = set(map(lambda x: x.split(":")[0], consumers))
+
+ # Split out all blocks, sources, and sinks based on how they
+ # appear as consumers and/or producers.
+ blocks = prods.intersection(cons)
+ sources = prods.difference(blocks)
+ sinks = cons.difference(blocks)
+
+ nblocks = len(prods) + len(cons)
+
+ allblocks = build_edge_graph(sources, sinks, edges)
+ nrows = len(allblocks)
+ ncols = len(allblocks[0])
+
+ col_width = 120
+
+ self.block_dict = []
+
+ for row, blockrow in enumerate(allblocks):
+ for col, block in enumerate(blockrow):
+ if(block == ''):
+ continue
+
+ bgroup = QtGui.QGroupBox(block)
+ playout = QtGui.QFormLayout()
+ bgroup.setLayout(playout)
+ self.layout.addWidget(bgroup, row, 2*col)
+
+ blockname = block.split(":")[0]
+
+ name = wtime_name(blockname)
+ wtime = knobs[name].value
+ newtime = QtGui.QLineEdit()
+ newtime.setMinimumWidth(col_width)
+ newtime.setText("{0:.4f}".format(wtime))
+ self.block_dict.append((name, newtime))
+
+ name = nout_name(blockname)
+ nout = knobs[name].value
+ newnout = QtGui.QLineEdit()
+ newnout.setText("{0:.4f}".format(nout))
+ newnout.setMinimumWidth(col_width)
+ self.block_dict.append((name, newnout))
+
+ name = nprod_name(blockname)
+ nprod = knobs[name].value
+ newnprod = QtGui.QLineEdit()
+ newnprod.setMinimumWidth(col_width)
+ newnprod.setText("{0:.4f}".format(nprod))
+ self.block_dict.append((name, newnprod))
+
+ playout.addRow("Work time", newtime)
+ playout.addRow("noutput_items", newnout)
+ playout.addRow("nproduced", newnprod)
+
+ if blockname in blocks or blockname in sources:
+ # Add a buffer between blocks
+ buffgroup = QtGui.QGroupBox("Buffer")
+ bufflayout = QtGui.QFormLayout()
+ buffgroup.setLayout(bufflayout)
+ self.layout.addWidget(buffgroup, row, 2*col+1)
+
+ i = int(block.split(":")[1])
+ name = output_name(blockname)
+ obuff = knobs[name].value
+ for i,o in enumerate(obuff):
+ newobuff = QtGui.QLineEdit()
+ newobuff.setMinimumWidth(col_width)
+ newobuff.setText("{0:.4f}".format(o))
+ self.block_dict.append((name, newobuff, i))
+ bufflayout.addRow("Out Buffer {0}".format(i),
+ newobuff)
+
+ if blockname in blocks or blockname in sinks:
+ item = self.layout.itemAtPosition(row, 2*col-1)
+ if(item):
+ buffgroup = item.widget()
+ bufflayout = buffgroup.layout()
+ else:
+ buffgroup = QtGui.QGroupBox("Buffer")
+ bufflayout = QtGui.QFormLayout()
+ buffgroup.setLayout(bufflayout)
+ self.layout.addWidget(buffgroup, row, 2*col-1)
+
+ i = int(block.split(":")[1])
+ name = input_name(blockname)
+ ibuff = knobs[name].value[i]
+ newibuff = QtGui.QLineEdit()
+ newibuff.setMinimumWidth(col_width)
+ newibuff.setText("{0:.4f}".format(ibuff))
+ self.block_dict.append((name, newibuff, i))
+ bufflayout.addRow("In Buffer {0}".format(i),
+ newibuff)
+
+ # set up timer
+ self.timer = QtCore.QTimer()
+ self.connect(self.timer, QtCore.SIGNAL('timeout()'), self.update)
+ self.timer.start(1000)
+
+ def openMenu(self, pos):
+ index = self.table.treeWidget.selectedIndexes()
+ item = self.table.treeWidget.itemFromIndex(index[0])
+ itemname = str(item.text(0))
+ self.parent.propertiesMenu(itemname, self.radio, self.uid)
+
+
+class MyClient(IceRadioClient):
+ def __init__(self):
+ IceRadioClient.__init__(self, MAINWindow)
+
+sys.exit(MyClient().main(sys.argv))
diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitorx b/gnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitorx
new file mode 100755
index 0000000000..a65b0406e4
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitorx
@@ -0,0 +1,727 @@
+#!/usr/bin/env python
+#
+# Copyright 2012-2013 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+import random,math,operator
+import networkx as nx;
+import matplotlib.pyplot as plt
+
+from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
+from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
+from matplotlib.figure import Figure
+
+from gnuradio import gr, ctrlport
+
+from PyQt4 import QtCore,Qt,Qwt5
+import PyQt4.QtGui as QtGui
+import sys, time, re, pprint
+import itertools
+import scipy
+from scipy import spatial
+
+import Ice
+from gnuradio.ctrlport.IceRadioClient import *
+from gnuradio.ctrlport.GrDataPlotter import *
+from gnuradio.ctrlport import GNURadio
+
+class MAINWindow(QtGui.QMainWindow):
+ def minimumSizeHint(self):
+ return QtGui.QSize(800,600)
+
+ def __init__(self, radio, port, interface):
+
+ super(MAINWindow, self).__init__()
+ self.conns = []
+ self.plots = []
+ self.knobprops = []
+ self.interface = interface
+
+ self.mdiArea = QtGui.QMdiArea()
+ self.mdiArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
+ self.mdiArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
+ self.setCentralWidget(self.mdiArea)
+
+ self.mdiArea.subWindowActivated.connect(self.updateMenus)
+ self.windowMapper = QtCore.QSignalMapper(self)
+ self.windowMapper.mapped[QtGui.QWidget].connect(self.setActiveSubWindow)
+
+ self.createActions()
+ self.createMenus()
+ self.createToolBars()
+ self.createStatusBar()
+ self.updateMenus()
+
+ self.setWindowTitle("GNU Radio Performance Monitor")
+ self.setUnifiedTitleAndToolBarOnMac(True)
+
+ self.newCon(radio, port)
+ icon = QtGui.QIcon(ctrlport.__path__[0] + "/icon.png" )
+ self.setWindowIcon(icon)
+
+
+ def newSubWindow(self, window, title):
+ child = window;
+ child.setWindowTitle(title)
+ self.mdiArea.addSubWindow(child)
+ self.conns.append(child)
+ child.show();
+ self.mdiArea.currentSubWindow().showMaximized()
+
+
+ def newCon(self, radio=None, port=None):
+ child = MForm(radio, port, len(self.conns), self)
+ if(child.radio is not None):
+ child.setWindowTitle(str(child.radio))
+# horizbar = QtGui.QScrollArea()
+# horizbar.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
+# horizbar.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
+# horizbar.setWidget(child)
+# self.mdiArea.addSubWindow(horizbar)
+ self.mdiArea.addSubWindow(child)
+ self.mdiArea.currentSubWindow().showMaximized()
+
+ self.conns.append(child)
+ self.plots.append([])
+
+ def update(self, knobs, uid):
+ #sys.stderr.write("KNOB KEYS: {0}\n".format(knobs.keys()))
+ for plot in self.plots[uid]:
+ data = knobs[plot.name()].value
+ plot.update(data)
+ plot.stop()
+ plot.wait()
+ plot.start()
+
+ def setActiveSubWindow(self, window):
+ if window:
+ self.mdiArea.setActiveSubWindow(window)
+
+
+ def createActions(self):
+ self.newConAct = QtGui.QAction("&New Connection",
+ self, shortcut=QtGui.QKeySequence.New,
+ statusTip="Create a new file", triggered=self.newCon)
+
+ self.exitAct = QtGui.QAction("E&xit", self, shortcut="Ctrl+Q",
+ statusTip="Exit the application",
+ triggered=QtGui.qApp.closeAllWindows)
+
+ self.closeAct = QtGui.QAction("Cl&ose", self, shortcut="Ctrl+F4",
+ statusTip="Close the active window",
+ triggered=self.mdiArea.closeActiveSubWindow)
+
+ self.closeAllAct = QtGui.QAction("Close &All", self,
+ statusTip="Close all the windows",
+ triggered=self.mdiArea.closeAllSubWindows)
+
+
+ qks = QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_T);
+ self.tileAct = QtGui.QAction("&Tile", self,
+ statusTip="Tile the windows",
+ triggered=self.mdiArea.tileSubWindows,
+ shortcut=qks)
+
+ qks = QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_C);
+ self.cascadeAct = QtGui.QAction("&Cascade", self,
+ statusTip="Cascade the windows", shortcut=qks,
+ triggered=self.mdiArea.cascadeSubWindows)
+
+ self.nextAct = QtGui.QAction("Ne&xt", self,
+ shortcut=QtGui.QKeySequence.NextChild,
+ statusTip="Move the focus to the next window",
+ triggered=self.mdiArea.activateNextSubWindow)
+
+ self.previousAct = QtGui.QAction("Pre&vious", self,
+ shortcut=QtGui.QKeySequence.PreviousChild,
+ statusTip="Move the focus to the previous window",
+ triggered=self.mdiArea.activatePreviousSubWindow)
+
+ self.separatorAct = QtGui.QAction(self)
+ self.separatorAct.setSeparator(True)
+
+ self.aboutAct = QtGui.QAction("&About", self,
+ statusTip="Show the application's About box",
+ triggered=self.about)
+
+ self.aboutQtAct = QtGui.QAction("About &Qt", self,
+ statusTip="Show the Qt library's About box",
+ triggered=QtGui.qApp.aboutQt)
+
+ def createMenus(self):
+ self.fileMenu = self.menuBar().addMenu("&File")
+ self.fileMenu.addAction(self.newConAct)
+ self.fileMenu.addSeparator()
+ self.fileMenu.addAction(self.exitAct)
+
+ self.windowMenu = self.menuBar().addMenu("&Window")
+ self.updateWindowMenu()
+ self.windowMenu.aboutToShow.connect(self.updateWindowMenu)
+
+ self.menuBar().addSeparator()
+
+ self.helpMenu = self.menuBar().addMenu("&Help")
+ self.helpMenu.addAction(self.aboutAct)
+ self.helpMenu.addAction(self.aboutQtAct)
+
+ def createToolBars(self):
+ self.fileToolBar = self.addToolBar("File")
+ self.fileToolBar.addAction(self.newConAct)
+
+ self.fileToolBar = self.addToolBar("Window")
+ self.fileToolBar.addAction(self.tileAct)
+ self.fileToolBar.addAction(self.cascadeAct)
+
+ def createStatusBar(self):
+ self.statusBar().showMessage("Ready")
+
+
+ def activeMdiChild(self):
+ activeSubWindow = self.mdiArea.activeSubWindow()
+ if activeSubWindow:
+ return activeSubWindow.widget()
+ return None
+
+ def updateMenus(self):
+ hasMdiChild = (self.activeMdiChild() is not None)
+ self.closeAct.setEnabled(hasMdiChild)
+ self.closeAllAct.setEnabled(hasMdiChild)
+ self.tileAct.setEnabled(hasMdiChild)
+ self.cascadeAct.setEnabled(hasMdiChild)
+ self.nextAct.setEnabled(hasMdiChild)
+ self.previousAct.setEnabled(hasMdiChild)
+ self.separatorAct.setVisible(hasMdiChild)
+
+ def updateWindowMenu(self):
+ self.windowMenu.clear()
+ self.windowMenu.addAction(self.closeAct)
+ self.windowMenu.addAction(self.closeAllAct)
+ self.windowMenu.addSeparator()
+ self.windowMenu.addAction(self.tileAct)
+ self.windowMenu.addAction(self.cascadeAct)
+ self.windowMenu.addSeparator()
+ self.windowMenu.addAction(self.nextAct)
+ self.windowMenu.addAction(self.previousAct)
+ self.windowMenu.addAction(self.separatorAct)
+
+ def about(self):
+ about_info = \
+'''Copyright 2012 Free Software Foundation, Inc.\n
+This program is part of GNU Radio.\n
+GNU Radio is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version.\n
+GNU Radio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\n
+You should have received a copy of the GNU General Public License along with GNU Radio; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Boston, MA 02110-1301, USA.'''
+
+ QtGui.QMessageBox.about(None, "gr-ctrlport-monitor", about_info)
+
+
+class ConInfoDialog(QtGui.QDialog):
+ def __init__(self, parent=None):
+ super(ConInfoDialog, self).__init__(parent)
+
+ self.gridLayout = QtGui.QGridLayout(self)
+
+
+ self.host = QtGui.QLineEdit(self);
+ self.port = QtGui.QLineEdit(self);
+ self.host.setText("localhost");
+ self.port.setText("43243");
+
+ self.buttonBox = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok |
+ QtGui.QDialogButtonBox.Cancel)
+
+ self.gridLayout.addWidget(self.host);
+ self.gridLayout.addWidget(self.port);
+ self.gridLayout.addWidget(self.buttonBox);
+
+ self.buttonBox.accepted.connect(self.accept)
+ self.buttonBox.rejected.connect(self.reject)
+
+
+ def accept(self):
+ self.done(1);
+
+ def reject(self):
+ self.done(0);
+
+
+class DataTable(QtGui.QWidget):
+ def update(self):
+ print "update"
+
+ def __init__(self, radio, G):
+ QtGui.QWidget.__init__( self)
+
+ self.layout = QtGui.QVBoxLayout(self);
+ self.hlayout = QtGui.QHBoxLayout();
+ self.layout.addLayout(self.hlayout);
+
+ self.G = G;
+ self.radio = radio;
+
+ self._keymap = None
+
+ # Create a combobox to set the type of statistic we want.
+ self._statistic = "Instantaneous"
+ self._statistics_table = {"Instantaneous": "",
+ "Average": "avg ",
+ "Variance": "var "}
+ self.stattype = QtGui.QComboBox()
+ self.stattype.addItem("Instantaneous")
+ self.stattype.addItem("Average")
+ self.stattype.addItem("Variance")
+ self.stattype.setMaximumWidth(200)
+ self.hlayout.addWidget(self.stattype);
+ self.stattype.currentIndexChanged.connect(self.stat_changed)
+
+ # Create a checkbox to toggle sorting of graphs
+ self._sort = False
+ self.checksort = QtGui.QCheckBox("Sort")
+ self.checksort.setCheckState(self._sort)
+ self.hlayout.addWidget(self.checksort);
+ self.checksort.stateChanged.connect(self.checksort_changed)
+
+ # set up table
+ self.perfTable = Qt.QTableWidget();
+ self.perfTable.setColumnCount(2)
+ self.perfTable.verticalHeader().hide();
+ self.perfTable.setHorizontalHeaderLabels( ["Block Name", "Percent Runtime"] );
+ self.perfTable.horizontalHeader().setStretchLastSection(True);
+ self.perfTable.setSortingEnabled(True)
+ nodes = self.G.nodes(data=True)
+
+ # set up plot
+ self.f = plt.figure(figsize=(10,8), dpi=90)
+ self.sp = self.f.add_subplot(111);
+ self.sp.autoscale_view(True,True,True);
+ self.sp.set_autoscale_on(True)
+ self.canvas = FigureCanvas(self.f)
+
+ # set up tabs
+ self.tabber = QtGui.QTabWidget();
+ self.layout.addWidget(self.tabber);
+ self.tabber.addTab(self.perfTable,"Table View");
+ self.tabber.addTab(self.canvas, "Graph View");
+
+ # set up timer
+ self.timer = QtCore.QTimer()
+ self.connect(self.timer, QtCore.SIGNAL('timeout()'), self.update)
+ self.timer.start(500)
+
+ for i in range(0,len(nodes)):
+ self.perfTable.setItem(
+ i,0,
+ Qt.QTableWidgetItem(nodes[i][0]))
+
+ def table_update(self,data):
+ for k in data.keys():
+ weight = data[k]
+ existing = self.perfTable.findItems(str(k),QtCore.Qt.MatchFixedString)
+ if(len(existing) == 0):
+ i = self.perfTable.rowCount();
+ self.perfTable.setRowCount( i+1)
+ self.perfTable.setItem( i,0, Qt.QTableWidgetItem(str(k)))
+ self.perfTable.setItem( i,1, Qt.QTableWidgetItem(str(weight)))
+ else:
+ self.perfTable.setItem( self.perfTable.row(existing[0]),1, Qt.QTableWidgetItem(str(weight)))
+
+ def stat_changed(self, index):
+ self._statistic = str(self.stattype.currentText())
+
+ def checksort_changed(self, state):
+ self._sort = state > 0
+
+class DataTableBuffers(DataTable):
+ def __init__(self, radio, G):
+ DataTable.__init__(self,radio,G)
+ self.perfTable.setHorizontalHeaderLabels( ["Block Name", "Percent Buffer Full"] );
+
+ def update(self):
+ nodes = self.G.nodes();
+
+ # get buffer fullness for all blocks
+ kl = map(lambda x: "%s::%soutput %% full" % \
+ (x, self._statistics_table[self._statistic]),
+ nodes);
+ buf_knobs = self.radio.get(kl)
+
+ # strip values out of ctrlport response
+ buffer_fullness = dict(zip(
+ map(lambda x: x.split("::")[0], buf_knobs.keys()),
+ map(lambda x: x.value, buf_knobs.values())))
+
+ blockport_fullness = {}
+ for blk in buffer_fullness:
+ for port in range(0,len(buffer_fullness[blk])):
+ blockport_fullness["%s:%d"%(blk,port)] = buffer_fullness[blk][port];
+
+ self.table_update(blockport_fullness);
+
+ if(self._sort):
+ sorted_fullness = sorted(blockport_fullness.iteritems(), key=operator.itemgetter(1))
+ self._keymap = map(operator.itemgetter(0), sorted_fullness)
+ else:
+ if self._keymap:
+ sorted_fullness = len(self._keymap)*['',]
+ for b in blockport_fullness:
+ sorted_fullness[self._keymap.index(b)] = (b, blockport_fullness[b])
+ else:
+ sorted_fullness = blockport_fullness.items()
+
+ self.sp.clear();
+ plt.figure(self.f.number)
+ plt.subplot(111);
+ self.sp.bar(range(0,len(sorted_fullness)), map(lambda x: x[1], sorted_fullness),
+ alpha=0.5)
+ self.sp.set_ylabel("% Buffers Full");
+ self.sp.set_xticks( map(lambda x: x+0.5, range(0,len(sorted_fullness))))
+ self.sp.set_xticklabels( map(lambda x: " " + x, map(lambda x: x[0], sorted_fullness)),
+ rotation="vertical", verticalalignment="bottom" )
+ self.canvas.draw();
+ self.canvas.show();
+
+class DataTableRuntimes(DataTable):
+ def __init__(self, radio, G):
+ DataTable.__init__(self,radio,G)
+ #self.perfTable.setRowCount(len( self.G.nodes() ))
+
+ def update(self):
+ nodes = self.G.nodes();
+
+ # get work time for all blocks
+ kl = map(lambda x: "%s::%swork time" % \
+ (x, self._statistics_table[self._statistic]),
+ nodes);
+ wrk_knobs = self.radio.get(kl)
+
+ # strip values out of ctrlport response
+ total_work = sum(map(lambda x: x.value, wrk_knobs.values()))
+ work_times = dict(zip(
+ map(lambda x: x.split("::")[0], wrk_knobs.keys()),
+ map(lambda x: x.value/total_work, wrk_knobs.values())))
+
+ # update table view
+ self.table_update(work_times)
+
+ if(self._sort):
+ sorted_work = sorted(work_times.iteritems(), key=operator.itemgetter(1))
+ self._keymap = map(operator.itemgetter(0), sorted_work)
+ else:
+ if self._keymap:
+ sorted_work = len(self._keymap)*['',]
+ for b in work_times:
+ sorted_work[self._keymap.index(b)] = (b, work_times[b])
+ else:
+ sorted_work = work_times.items()
+
+ self.sp.clear();
+ plt.figure(self.f.number)
+ plt.subplot(111);
+ self.sp.bar(range(0,len(sorted_work)), map(lambda x: x[1], sorted_work),
+ alpha=0.5)
+ self.sp.set_ylabel("% Runtime");
+ self.sp.set_xticks( map(lambda x: x+0.5, range(0,len(sorted_work))))
+ self.sp.set_xticklabels( map(lambda x: " " + x[0], sorted_work),
+ rotation="vertical", verticalalignment="bottom" )
+
+ self.canvas.draw();
+ self.canvas.show();
+
+class MForm(QtGui.QWidget):
+ def update(self):
+ try:
+
+ nodes = self.G.nodes();
+
+ # get current buffer depths of all output buffers
+ kl = map(lambda x: "%s::%soutput %% full" % \
+ (x, self._statistics_table[self._statistic]),
+ nodes);
+
+ st = time.time()
+ buf_knobs = self.radio.get(kl)
+ td1 = time.time() - st;
+
+ # strip values out of ctrlport response
+ buf_vals = dict(zip(
+ map(lambda x: x.split("::")[0], buf_knobs.keys()),
+ map(lambda x: x.value, buf_knobs.values())))
+
+ # get work time for all blocks
+ kl = map(lambda x: "%s::%swork time" % \
+ (x, self._statistics_table[self._statistic]),
+ nodes);
+ st = time.time()
+ wrk_knobs = self.radio.get(kl)
+ td2 = time.time() - st;
+
+ # strip values out of ctrlport response
+ total_work = sum(map(lambda x: x.value, wrk_knobs.values()))
+ work_times = dict(zip(
+ map(lambda x: x.split("::")[0], wrk_knobs.keys()),
+ map(lambda x: x.value/total_work, wrk_knobs.values())))
+
+ for n in nodes:
+ # ne is the list of edges away from this node!
+ ne = self.G.edges([n],True);
+ for e in ne: # iterate over edges from this block
+ # get the right output buffer/port weight for each edge
+ sourceport = e[2]["sourceport"];
+ newweight = buf_vals[n][sourceport]
+ e[2]["weight"] = newweight;
+
+ # set updated weights
+ self.node_weights = map(lambda x: 20+2000*work_times[x], nodes);
+ self.edge_weights = map(lambda x: 100.0*x[2]["weight"], self.G.edges(data=True));
+
+ # draw graph updates
+ self.updateGraph();
+
+ latency = td1 + td2;
+ self.parent.statusBar().showMessage("Current GNU Radio Control Port Query Latency: %f ms"%\
+ (latency*1000))
+
+ except Exception, e:
+ sys.stderr.write("ctrlport-monitor: radio.get threw exception ({0}).\n".format(e))
+ if(type(self.parent) is MAINWindow):
+ # Find window of connection
+ remove = []
+ for p in self.parent.mdiArea.subWindowList():
+ if self.parent.conns[self.uid] == p.widget():
+ remove.append(p)
+
+ # Remove subwindows for connection and plots
+ for r in remove:
+ self.parent.mdiArea.removeSubWindow(r)
+
+ # Clean up self
+ self.close()
+ else:
+ sys.exit(1)
+ return
+
+ def rtt(self):
+ self.parent.newSubWindow( DataTableRuntimes(self.radio, self.G), "Runtime Table" );
+
+ def bpt(self):
+ self.parent.newSubWindow( DataTableBuffers(self.radio, self.G), "Buffers Table" );
+
+ def stat_changed(self, index):
+ self._statistic = str(self.stattype.currentText())
+
+ def __init__(self, radio=None, port=None, uid=0, parent=None):
+
+ super(MForm, self).__init__()
+
+ if(radio == None or port == None):
+ askinfo = ConInfoDialog(self);
+ if askinfo.exec_():
+ host = str(askinfo.host.text());
+ port = str(askinfo.port.text());
+ radio = parent.interface.getRadio(host, port)
+ else:
+ self.radio = None
+ return
+
+
+ self.uid = uid
+ self.parent = parent
+
+ self.layoutTop = QtGui.QVBoxLayout(self)
+ self.ctlBox = QtGui.QHBoxLayout();
+ self.layout = QtGui.QHBoxLayout()
+
+ self.layoutTop.addLayout(self.ctlBox);
+ self.layoutTop.addLayout(self.layout);
+
+ self.rttAct = QtGui.QAction("Runtime Table",
+ self, statusTip="Runtime Table", triggered=self.rtt)
+ self.rttBut = Qt.QToolButton()
+ self.rttBut.setDefaultAction(self.rttAct);
+ self.ctlBox.addWidget(self.rttBut);
+
+ self.bptAct = QtGui.QAction("Buffer Table",
+ self, statusTip="Buffer Table", triggered=self.bpt)
+ self.bptBut = Qt.QToolButton()
+ self.bptBut.setDefaultAction(self.bptAct);
+ self.ctlBox.addWidget(self.bptBut);
+
+ self._statistic = "Instantaneous"
+ self._statistics_table = {"Instantaneous": "",
+ "Average": "avg ",
+ "Variance": "var "}
+ self.stattype = QtGui.QComboBox()
+ self.stattype.addItem("Instantaneous")
+ self.stattype.addItem("Average")
+ self.stattype.addItem("Variance")
+ self.stattype.setMaximumWidth(200)
+ self.ctlBox.addWidget(self.stattype);
+ self.stattype.currentIndexChanged.connect(self.stat_changed)
+
+# self.setLayout(self.layout);
+
+ self.radio = radio
+ self.knobprops = self.radio.properties([])
+ self.parent.knobprops.append(self.knobprops)
+
+ self.timer = QtCore.QTimer()
+ self.constupdatediv = 0
+ self.tableupdatediv = 0
+ plotsize=250
+
+
+ # Set up the graph of blocks
+ input_name = lambda x: x+"::avg input % full"
+ output_name = lambda x: x+"::avg output % full"
+ wtime_name = lambda x: x+"::avg work time"
+ nout_name = lambda x: x+"::avg noutput_items"
+ nprod_name = lambda x: x+"::avg nproduced"
+
+ tmplist = []
+ knobs = self.radio.get([])
+ edgelist = None
+ for k in knobs:
+ propname = k.split("::")
+ blockname = propname[0]
+ keyname = propname[1]
+ if(keyname == "edge list"):
+ edgelist = knobs[k].value
+ elif(blockname not in tmplist):
+ # only take gr_blocks (no hier_block2)
+ if(knobs.has_key(input_name(blockname))):
+ tmplist.append(blockname)
+
+ if not edgelist:
+ sys.stderr.write("Could not find list of edges from flowgraph. " + \
+ "Make sure the option 'edges_list' is enabled " + \
+ "in the ControlPort configuration.\n\n")
+ sys.exit(1)
+
+ edges = edgelist.split("\n")[0:-1]
+ edgepairs = [];
+ for e in edges:
+ _e = e.split("->")
+ edgepairs.append( (_e[0].split(":")[0], _e[1].split(":")[0],
+ {"sourceport":int(_e[0].split(":")[1])}) );
+
+ self.G = nx.MultiDiGraph();
+ self.G.add_edges_from(edgepairs);
+
+ n_edges = self.G.edges(data=True);
+ for e in n_edges:
+ e[2]["weight"] = 5+random.random()*10;
+
+ self.G.clear();
+ self.G.add_edges_from(n_edges);
+
+
+ self.f = plt.figure(figsize=(10,8), dpi=90)
+ self.sp = self.f.add_subplot(111);
+ self.sp.autoscale_view(True,True,True);
+ self.sp.set_autoscale_on(True)
+
+ self.canvas = FigureCanvas(self.f)
+ self.layout.addWidget(self.canvas);
+
+ self.pos = nx.graphviz_layout(self.G);
+ #self.pos = nx.pygraphviz_layout(self.G);
+ #self.pos = nx.spectral_layout(self.G);
+ #self.pos = nx.circular_layout(self.G);
+ #self.pos = nx.shell_layout(self.G);
+ #self.pos = nx.spring_layout(self.G);
+
+ # generate weights and plot
+ self.update();
+
+ # set up timer
+ self.timer = QtCore.QTimer()
+ self.connect(self.timer, QtCore.SIGNAL('timeout()'), self.update)
+ self.timer.start(1000)
+
+ # Set up mouse callback functions to move blocks around.
+ self._grabbed = False
+ self._current_block = ''
+ self.f.canvas.mpl_connect('button_press_event',
+ self.button_press)
+ self.f.canvas.mpl_connect('motion_notify_event',
+ self.mouse_move)
+ self.f.canvas.mpl_connect('button_release_event',
+ self.button_release)
+
+ def button_press(self, event):
+ x, y = event.xdata, event.ydata
+ thrsh = 100
+
+ if(x is not None and y is not None):
+ nearby = map(lambda z: spatial.distance.euclidean((x,y), z), self.pos.values())
+ i = nearby.index(min(nearby))
+ if(abs(self.pos.values()[i][0] - x) < thrsh and
+ abs(self.pos.values()[i][1]-y) < thrsh):
+ self._current_block = self.pos.keys()[i]
+ #print "MOVING BLOCK: ", self._current_block
+ #print "CUR POS: ", self.pos.values()[i]
+ self._grabbed = True
+
+ def mouse_move(self, event):
+ if self._grabbed:
+ x, y = event.xdata, event.ydata
+ if(x is not None and y is not None):
+ #print "NEW POS: ", (x,y)
+ self.pos[self._current_block] = (x,y)
+ self.updateGraph();
+
+ def button_release(self, event):
+ self._grabbed = False
+
+
+ def openMenu(self, pos):
+ index = self.table.treeWidget.selectedIndexes()
+ item = self.table.treeWidget.itemFromIndex(index[0])
+ itemname = str(item.text(0))
+ self.parent.propertiesMenu(itemname, self.radio, self.uid)
+
+ def updateGraph(self):
+
+ self.canvas.updateGeometry()
+ self.sp.clear();
+ plt.figure(self.f.number)
+ plt.subplot(111);
+ nx.draw(self.G, self.pos,
+ edge_color=self.edge_weights,
+ node_color='#A0CBE2',
+ width=map(lambda x: 3+math.log(x), self.edge_weights),
+ node_shape="s",
+ node_size=self.node_weights,
+ #edge_cmap=plt.cm.Blues,
+ edge_cmap=plt.cm.Reds,
+ ax=self.sp,
+ arrows=False
+ )
+ nx.draw_networkx_labels(self.G, self.pos,
+ font_size=12)
+
+ self.canvas.draw();
+ self.canvas.show();
+
+class MyClient(IceRadioClient):
+ def __init__(self):
+ IceRadioClient.__init__(self, MAINWindow)
+
+sys.exit(MyClient().main(sys.argv))
diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/icon.png b/gnuradio-runtime/python/gnuradio/ctrlport/icon.png
new file mode 100644
index 0000000000..4beb204428
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/ctrlport/icon.png
Binary files differ
diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/monitor.py b/gnuradio-runtime/python/gnuradio/ctrlport/monitor.py
new file mode 100644
index 0000000000..53a571a698
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/ctrlport/monitor.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+#
+# Copyright 2012 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+import sys, subprocess, re, signal, time, atexit, os
+from gnuradio import gr
+
+class monitor:
+ def __init__(self):
+ print "ControlPort Monitor running."
+ self.started = False
+ atexit.register(self.shutdown)
+
+ def __del__(self):
+ if(self.started):
+ self.stop()
+
+ def start(self):
+ print "monitor::endpoints() = %s" % (gr.rpcmanager_get().endpoints())
+ try:
+ self.proc = subprocess.Popen(map(lambda a: ["gr-ctrlport-monitor",
+ re.search("\d+\.\d+\.\d+\.\d+",a).group(0),
+ re.search("-p (\d+)",a).group(1)],
+ gr.rpcmanager_get().endpoints())[0])
+ self.started = True
+ except:
+ self.proc = None
+ print "failed to to start ControlPort Monitor on specified port"
+
+ def stop(self):
+ if(self.proc):
+ if(self.proc.returncode == None):
+ print "\tcalling stop on shutdown"
+ self.proc.terminate()
+ else:
+ print "\tno proc to shut down, exiting"
+
+ def shutdown(self):
+ print "ctrlport.monitor received shutdown signal"
+ if(self.started):
+ self.stop()
diff --git a/gnuradio-runtime/python/gnuradio/gr/CMakeLists.txt b/gnuradio-runtime/python/gnuradio/gr/CMakeLists.txt
new file mode 100644
index 0000000000..343577deb8
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gr/CMakeLists.txt
@@ -0,0 +1,45 @@
+# Copyright 2012 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+
+########################################################################
+include(GrPython)
+
+GR_PYTHON_INSTALL(FILES
+ __init__.py
+ gr_threading.py
+ gr_threading_23.py
+ gr_threading_24.py
+ hier_block2.py
+ tag_utils.py
+ top_block.py
+ DESTINATION ${GR_PYTHON_DIR}/gnuradio/gr
+ COMPONENT "runtime_python"
+)
+
+########################################################################
+# Handle the unit tests
+########################################################################
+if(ENABLE_TESTING)
+include(GrTest)
+file(GLOB py_qa_test_files "qa_*.py")
+foreach(py_qa_test_file ${py_qa_test_files})
+ get_filename_component(py_qa_test_name ${py_qa_test_file} NAME_WE)
+ GR_ADD_TEST(${py_qa_test_name} ${PYTHON_EXECUTABLE} ${PYTHON_DASH_B} ${py_qa_test_file})
+endforeach(py_qa_test_file)
+endif(ENABLE_TESTING)
diff --git a/gnuradio-runtime/python/gnuradio/gr/__init__.py b/gnuradio-runtime/python/gnuradio/gr/__init__.py
new file mode 100644
index 0000000000..0a6c859bdd
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gr/__init__.py
@@ -0,0 +1,39 @@
+#
+# Copyright 2003-2012 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+# The presence of this file turns this directory into a Python package
+
+"""
+Core contents.
+"""
+
+# This is the main GNU Radio python module.
+# We pull the swig output and the other modules into the gnuradio.gr namespace
+
+from runtime_swig import *
+from exceptions import *
+from top_block import *
+from hier_block2 import *
+from tag_utils import *
+#from gateway import basic_block, sync_block, decim_block, interp_block
+
+# Force the preference database to be initialized
+#from prefs import prefs
diff --git a/gnuradio-runtime/python/gnuradio/gr/exceptions.py b/gnuradio-runtime/python/gnuradio/gr/exceptions.py
new file mode 100644
index 0000000000..dba04750bc
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gr/exceptions.py
@@ -0,0 +1,27 @@
+#
+# Copyright 2004 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+
+class NotDAG (Exception):
+ """Not a directed acyclic graph"""
+ pass
+
+class CantHappen (Exception):
+ """Can't happen"""
+ pass
diff --git a/gnuradio-runtime/python/gnuradio/gr/gateway.py b/gnuradio-runtime/python/gnuradio/gr/gateway.py
new file mode 100644
index 0000000000..b595959494
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gr/gateway.py
@@ -0,0 +1,243 @@
+#
+# Copyright 2011-2012 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+import runtime_swig as gr
+from runtime_swig import io_signature, io_signaturev
+from runtime_swig import gr_block_gw_message_type
+from runtime_swig import block_gateway
+import numpy
+
+########################################################################
+# Magic to turn pointers into numpy arrays
+# http://docs.scipy.org/doc/numpy/reference/arrays.interface.html
+########################################################################
+def pointer_to_ndarray(addr, dtype, nitems):
+ class array_like:
+ __array_interface__ = {
+ 'data' : (int(addr), False),
+ 'typestr' : dtype.base.str,
+ 'descr' : dtype.base.descr,
+ 'shape' : (nitems,) + dtype.shape,
+ 'strides' : None,
+ 'version' : 3
+ }
+ return numpy.asarray(array_like()).view(dtype.base)
+
+########################################################################
+# Handler that does callbacks from C++
+########################################################################
+class gateway_handler(gr.feval_ll):
+
+ #dont put a constructor, it wont work
+
+ def init(self, callback):
+ self._callback = callback
+
+ def eval(self, arg):
+ try: self._callback()
+ except Exception as ex:
+ print("handler caught exception: %s"%ex)
+ import traceback; traceback.print_exc()
+ raise ex
+ return 0
+
+########################################################################
+# Handler that does callbacks from C++
+########################################################################
+class msg_handler(gr.feval_p):
+
+ #dont put a constructor, it wont work
+
+ def init(self, callback):
+ self._callback = callback
+
+ def eval(self, arg):
+ try: self._callback(arg)
+ except Exception as ex:
+ print("handler caught exception: %s"%ex)
+ import traceback; traceback.print_exc()
+ raise ex
+ return 0
+
+########################################################################
+# The guts that make this into a gr block
+########################################################################
+class gateway_block(object):
+
+ def __init__(self, name, in_sig, out_sig, work_type, factor):
+
+ #ensure that the sigs are iterable dtypes
+ def sig_to_dtype_sig(sig):
+ if sig is None: sig = ()
+ return map(numpy.dtype, sig)
+ self.__in_sig = sig_to_dtype_sig(in_sig)
+ self.__out_sig = sig_to_dtype_sig(out_sig)
+
+ #cache the ranges to iterate when dispatching work
+ self.__in_indexes = range(len(self.__in_sig))
+ self.__out_indexes = range(len(self.__out_sig))
+
+ #convert the signatures into gr.io_signatures
+ def sig_to_gr_io_sigv(sig):
+ if not len(sig): return io_signature(0, 0, 0)
+ return io_signaturev(len(sig), len(sig), [s.itemsize for s in sig])
+ gr_in_sig = sig_to_gr_io_sigv(self.__in_sig)
+ gr_out_sig = sig_to_gr_io_sigv(self.__out_sig)
+
+ #create internal gateway block
+ self.__handler = gateway_handler()
+ self.__handler.init(self.__gr_block_handle)
+ self.__gateway = block_gateway(
+ self.__handler, name, gr_in_sig, gr_out_sig, work_type, factor)
+ self.__message = self.__gateway.gr_block_message()
+
+ #dict to keep references to all message handlers
+ self.__msg_handlers = {}
+
+ #register gr_block functions
+ prefix = 'gr_block__'
+ for attr in [x for x in dir(self.__gateway) if x.startswith(prefix)]:
+ setattr(self, attr.replace(prefix, ''), getattr(self.__gateway, attr))
+ self.pop_msg_queue = lambda: gr.gr_block_gw_pop_msg_queue_safe(self.__gateway)
+
+ def to_basic_block(self):
+ """
+ Makes this block connectable by hier/top block python
+ """
+ return self.__gateway.to_basic_block()
+
+ def __gr_block_handle(self):
+ """
+ Dispatch tasks according to the action type specified in the message.
+ """
+ if self.__message.action == gr_block_gw_message_type.ACTION_GENERAL_WORK:
+ self.__message.general_work_args_return_value = self.general_work(
+
+ input_items=[pointer_to_ndarray(
+ self.__message.general_work_args_input_items[i],
+ self.__in_sig[i],
+ self.__message.general_work_args_ninput_items[i]
+ ) for i in self.__in_indexes],
+
+ output_items=[pointer_to_ndarray(
+ self.__message.general_work_args_output_items[i],
+ self.__out_sig[i],
+ self.__message.general_work_args_noutput_items
+ ) for i in self.__out_indexes],
+ )
+
+ elif self.__message.action == gr_block_gw_message_type.ACTION_WORK:
+ self.__message.work_args_return_value = self.work(
+
+ input_items=[pointer_to_ndarray(
+ self.__message.work_args_input_items[i],
+ self.__in_sig[i],
+ self.__message.work_args_ninput_items
+ ) for i in self.__in_indexes],
+
+ output_items=[pointer_to_ndarray(
+ self.__message.work_args_output_items[i],
+ self.__out_sig[i],
+ self.__message.work_args_noutput_items
+ ) for i in self.__out_indexes],
+ )
+
+ elif self.__message.action == gr_block_gw_message_type.ACTION_FORECAST:
+ self.forecast(
+ noutput_items=self.__message.forecast_args_noutput_items,
+ ninput_items_required=self.__message.forecast_args_ninput_items_required,
+ )
+
+ elif self.__message.action == gr_block_gw_message_type.ACTION_START:
+ self.__message.start_args_return_value = self.start()
+
+ elif self.__message.action == gr_block_gw_message_type.ACTION_STOP:
+ self.__message.stop_args_return_value = self.stop()
+
+ def forecast(self, noutput_items, ninput_items_required):
+ """
+ forecast is only called from a general block
+ this is the default implementation
+ """
+ for ninput_item in ninput_items_required:
+ ninput_item = noutput_items + self.history() - 1;
+ return
+
+ def general_work(self, *args, **kwargs):
+ """general work to be overloaded in a derived class"""
+ raise NotImplementedError("general work not implemented")
+
+ def work(self, *args, **kwargs):
+ """work to be overloaded in a derived class"""
+ raise NotImplementedError("work not implemented")
+
+ def start(self): return True
+ def stop(self): return True
+
+ def set_msg_handler(self, which_port, handler_func):
+ handler = msg_handler()
+ handler.init(handler_func)
+ self.__gateway.set_msg_handler_feval(which_port, handler)
+ # Save handler object in class so it's not garbage collected
+ self.__msg_handlers[which_port] = handler
+
+########################################################################
+# Wrappers for the user to inherit from
+########################################################################
+class basic_block(gateway_block):
+ def __init__(self, name, in_sig, out_sig):
+ gateway_block.__init__(self,
+ name=name,
+ in_sig=in_sig,
+ out_sig=out_sig,
+ work_type=gr.GR_BLOCK_GW_WORK_GENERAL,
+ factor=1, #not relevant factor
+ )
+
+class sync_block(gateway_block):
+ def __init__(self, name, in_sig, out_sig):
+ gateway_block.__init__(self,
+ name=name,
+ in_sig=in_sig,
+ out_sig=out_sig,
+ work_type=gr.GR_BLOCK_GW_WORK_SYNC,
+ factor=1,
+ )
+
+class decim_block(gateway_block):
+ def __init__(self, name, in_sig, out_sig, decim):
+ gateway_block.__init__(self,
+ name=name,
+ in_sig=in_sig,
+ out_sig=out_sig,
+ work_type=gr.GR_BLOCK_GW_WORK_DECIM,
+ factor=decim,
+ )
+
+class interp_block(gateway_block):
+ def __init__(self, name, in_sig, out_sig, interp):
+ gateway_block.__init__(self,
+ name=name,
+ in_sig=in_sig,
+ out_sig=out_sig,
+ work_type=gr.GR_BLOCK_GW_WORK_INTERP,
+ factor=interp,
+ )
diff --git a/gnuradio-runtime/python/gnuradio/gr/gr_threading.py b/gnuradio-runtime/python/gnuradio/gr/gr_threading.py
new file mode 100644
index 0000000000..5d6f0fdaf9
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gr/gr_threading.py
@@ -0,0 +1,35 @@
+#
+# Copyright 2005 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from sys import version_info as _version_info
+
+# import patched version of standard threading module
+
+if _version_info[0:2] == (2, 3):
+ #print "Importing gr_threading_23"
+ from gr_threading_23 import *
+elif _version_info[0:2] == (2, 4):
+ #print "Importing gr_threading_24"
+ from gr_threading_24 import *
+else:
+ # assume the patch was applied...
+ #print "Importing system provided threading"
+ from threading import *
diff --git a/gnuradio-runtime/python/gnuradio/gr/gr_threading_23.py b/gnuradio-runtime/python/gnuradio/gr/gr_threading_23.py
new file mode 100644
index 0000000000..dee8034c1c
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gr/gr_threading_23.py
@@ -0,0 +1,724 @@
+"""Thread module emulating a subset of Java's threading model."""
+
+# This started life as the threading.py module of Python 2.3
+# It's been patched to fix a problem with join, where a KeyboardInterrupt
+# caused a lock to be left in the acquired state.
+
+import sys as _sys
+
+try:
+ import thread
+except ImportError:
+ del _sys.modules[__name__]
+ raise
+
+from StringIO import StringIO as _StringIO
+from time import time as _time, sleep as _sleep
+from traceback import print_exc as _print_exc
+
+# Rename some stuff so "from threading import *" is safe
+__all__ = ['activeCount', 'Condition', 'currentThread', 'enumerate', 'Event',
+ 'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Thread',
+ 'Timer', 'setprofile', 'settrace']
+
+_start_new_thread = thread.start_new_thread
+_allocate_lock = thread.allocate_lock
+_get_ident = thread.get_ident
+ThreadError = thread.error
+del thread
+
+
+# Debug support (adapted from ihooks.py).
+# All the major classes here derive from _Verbose. We force that to
+# be a new-style class so that all the major classes here are new-style.
+# This helps debugging (type(instance) is more revealing for instances
+# of new-style classes).
+
+_VERBOSE = False
+
+if __debug__:
+
+ class _Verbose(object):
+
+ def __init__(self, verbose=None):
+ if verbose is None:
+ verbose = _VERBOSE
+ self.__verbose = verbose
+
+ def _note(self, format, *args):
+ if self.__verbose:
+ format = format % args
+ format = "%s: %s\n" % (
+ currentThread().getName(), format)
+ _sys.stderr.write(format)
+
+else:
+ # Disable this when using "python -O"
+ class _Verbose(object):
+ def __init__(self, verbose=None):
+ pass
+ def _note(self, *args):
+ pass
+
+# Support for profile and trace hooks
+
+_profile_hook = None
+_trace_hook = None
+
+def setprofile(func):
+ global _profile_hook
+ _profile_hook = func
+
+def settrace(func):
+ global _trace_hook
+ _trace_hook = func
+
+# Synchronization classes
+
+Lock = _allocate_lock
+
+def RLock(*args, **kwargs):
+ return _RLock(*args, **kwargs)
+
+class _RLock(_Verbose):
+
+ def __init__(self, verbose=None):
+ _Verbose.__init__(self, verbose)
+ self.__block = _allocate_lock()
+ self.__owner = None
+ self.__count = 0
+
+ def __repr__(self):
+ return "<%s(%s, %d)>" % (
+ self.__class__.__name__,
+ self.__owner and self.__owner.getName(),
+ self.__count)
+
+ def acquire(self, blocking=1):
+ me = currentThread()
+ if self.__owner is me:
+ self.__count = self.__count + 1
+ if __debug__:
+ self._note("%s.acquire(%s): recursive success", self, blocking)
+ return 1
+ rc = self.__block.acquire(blocking)
+ if rc:
+ self.__owner = me
+ self.__count = 1
+ if __debug__:
+ self._note("%s.acquire(%s): initial succes", self, blocking)
+ else:
+ if __debug__:
+ self._note("%s.acquire(%s): failure", self, blocking)
+ return rc
+
+ def release(self):
+ me = currentThread()
+ assert self.__owner is me, "release() of un-acquire()d lock"
+ self.__count = count = self.__count - 1
+ if not count:
+ self.__owner = None
+ self.__block.release()
+ if __debug__:
+ self._note("%s.release(): final release", self)
+ else:
+ if __debug__:
+ self._note("%s.release(): non-final release", self)
+
+ # Internal methods used by condition variables
+
+ def _acquire_restore(self, (count, owner)):
+ self.__block.acquire()
+ self.__count = count
+ self.__owner = owner
+ if __debug__:
+ self._note("%s._acquire_restore()", self)
+
+ def _release_save(self):
+ if __debug__:
+ self._note("%s._release_save()", self)
+ count = self.__count
+ self.__count = 0
+ owner = self.__owner
+ self.__owner = None
+ self.__block.release()
+ return (count, owner)
+
+ def _is_owned(self):
+ return self.__owner is currentThread()
+
+
+def Condition(*args, **kwargs):
+ return _Condition(*args, **kwargs)
+
+class _Condition(_Verbose):
+
+ def __init__(self, lock=None, verbose=None):
+ _Verbose.__init__(self, verbose)
+ if lock is None:
+ lock = RLock()
+ self.__lock = lock
+ # Export the lock's acquire() and release() methods
+ self.acquire = lock.acquire
+ self.release = lock.release
+ # If the lock defines _release_save() and/or _acquire_restore(),
+ # these override the default implementations (which just call
+ # release() and acquire() on the lock). Ditto for _is_owned().
+ try:
+ self._release_save = lock._release_save
+ except AttributeError:
+ pass
+ try:
+ self._acquire_restore = lock._acquire_restore
+ except AttributeError:
+ pass
+ try:
+ self._is_owned = lock._is_owned
+ except AttributeError:
+ pass
+ self.__waiters = []
+
+ def __repr__(self):
+ return "<Condition(%s, %d)>" % (self.__lock, len(self.__waiters))
+
+ def _release_save(self):
+ self.__lock.release() # No state to save
+
+ def _acquire_restore(self, x):
+ self.__lock.acquire() # Ignore saved state
+
+ def _is_owned(self):
+ # Return True if lock is owned by currentThread.
+ # This method is called only if __lock doesn't have _is_owned().
+ if self.__lock.acquire(0):
+ self.__lock.release()
+ return False
+ else:
+ return True
+
+ def wait(self, timeout=None):
+ currentThread() # for side-effect
+ assert self._is_owned(), "wait() of un-acquire()d lock"
+ waiter = _allocate_lock()
+ waiter.acquire()
+ self.__waiters.append(waiter)
+ saved_state = self._release_save()
+ try: # restore state no matter what (e.g., KeyboardInterrupt)
+ if timeout is None:
+ waiter.acquire()
+ if __debug__:
+ self._note("%s.wait(): got it", self)
+ else:
+ # Balancing act: We can't afford a pure busy loop, so we
+ # have to sleep; but if we sleep the whole timeout time,
+ # we'll be unresponsive. The scheme here sleeps very
+ # little at first, longer as time goes on, but never longer
+ # than 20 times per second (or the timeout time remaining).
+ endtime = _time() + timeout
+ delay = 0.0005 # 500 us -> initial delay of 1 ms
+ while True:
+ gotit = waiter.acquire(0)
+ if gotit:
+ break
+ remaining = endtime - _time()
+ if remaining <= 0:
+ break
+ delay = min(delay * 2, remaining, .05)
+ _sleep(delay)
+ if not gotit:
+ if __debug__:
+ self._note("%s.wait(%s): timed out", self, timeout)
+ try:
+ self.__waiters.remove(waiter)
+ except ValueError:
+ pass
+ else:
+ if __debug__:
+ self._note("%s.wait(%s): got it", self, timeout)
+ finally:
+ self._acquire_restore(saved_state)
+
+ def notify(self, n=1):
+ currentThread() # for side-effect
+ assert self._is_owned(), "notify() of un-acquire()d lock"
+ __waiters = self.__waiters
+ waiters = __waiters[:n]
+ if not waiters:
+ if __debug__:
+ self._note("%s.notify(): no waiters", self)
+ return
+ self._note("%s.notify(): notifying %d waiter%s", self, n,
+ n!=1 and "s" or "")
+ for waiter in waiters:
+ waiter.release()
+ try:
+ __waiters.remove(waiter)
+ except ValueError:
+ pass
+
+ def notifyAll(self):
+ self.notify(len(self.__waiters))
+
+
+def Semaphore(*args, **kwargs):
+ return _Semaphore(*args, **kwargs)
+
+class _Semaphore(_Verbose):
+
+ # After Tim Peters' semaphore class, but not quite the same (no maximum)
+
+ def __init__(self, value=1, verbose=None):
+ assert value >= 0, "Semaphore initial value must be >= 0"
+ _Verbose.__init__(self, verbose)
+ self.__cond = Condition(Lock())
+ self.__value = value
+
+ def acquire(self, blocking=1):
+ rc = False
+ self.__cond.acquire()
+ while self.__value == 0:
+ if not blocking:
+ break
+ if __debug__:
+ self._note("%s.acquire(%s): blocked waiting, value=%s",
+ self, blocking, self.__value)
+ self.__cond.wait()
+ else:
+ self.__value = self.__value - 1
+ if __debug__:
+ self._note("%s.acquire: success, value=%s",
+ self, self.__value)
+ rc = True
+ self.__cond.release()
+ return rc
+
+ def release(self):
+ self.__cond.acquire()
+ self.__value = self.__value + 1
+ if __debug__:
+ self._note("%s.release: success, value=%s",
+ self, self.__value)
+ self.__cond.notify()
+ self.__cond.release()
+
+
+def BoundedSemaphore(*args, **kwargs):
+ return _BoundedSemaphore(*args, **kwargs)
+
+class _BoundedSemaphore(_Semaphore):
+ """Semaphore that checks that # releases is <= # acquires"""
+ def __init__(self, value=1, verbose=None):
+ _Semaphore.__init__(self, value, verbose)
+ self._initial_value = value
+
+ def release(self):
+ if self._Semaphore__value >= self._initial_value:
+ raise ValueError, "Semaphore released too many times"
+ return _Semaphore.release(self)
+
+
+def Event(*args, **kwargs):
+ return _Event(*args, **kwargs)
+
+class _Event(_Verbose):
+
+ # After Tim Peters' event class (without is_posted())
+
+ def __init__(self, verbose=None):
+ _Verbose.__init__(self, verbose)
+ self.__cond = Condition(Lock())
+ self.__flag = False
+
+ def isSet(self):
+ return self.__flag
+
+ def set(self):
+ self.__cond.acquire()
+ try:
+ self.__flag = True
+ self.__cond.notifyAll()
+ finally:
+ self.__cond.release()
+
+ def clear(self):
+ self.__cond.acquire()
+ try:
+ self.__flag = False
+ finally:
+ self.__cond.release()
+
+ def wait(self, timeout=None):
+ self.__cond.acquire()
+ try:
+ if not self.__flag:
+ self.__cond.wait(timeout)
+ finally:
+ self.__cond.release()
+
+# Helper to generate new thread names
+_counter = 0
+def _newname(template="Thread-%d"):
+ global _counter
+ _counter = _counter + 1
+ return template % _counter
+
+# Active thread administration
+_active_limbo_lock = _allocate_lock()
+_active = {}
+_limbo = {}
+
+
+# Main class for threads
+
+class Thread(_Verbose):
+
+ __initialized = False
+
+ def __init__(self, group=None, target=None, name=None,
+ args=(), kwargs={}, verbose=None):
+ assert group is None, "group argument must be None for now"
+ _Verbose.__init__(self, verbose)
+ self.__target = target
+ self.__name = str(name or _newname())
+ self.__args = args
+ self.__kwargs = kwargs
+ self.__daemonic = self._set_daemon()
+ self.__started = False
+ self.__stopped = False
+ self.__block = Condition(Lock())
+ self.__initialized = True
+
+ def _set_daemon(self):
+ # Overridden in _MainThread and _DummyThread
+ return currentThread().isDaemon()
+
+ def __repr__(self):
+ assert self.__initialized, "Thread.__init__() was not called"
+ status = "initial"
+ if self.__started:
+ status = "started"
+ if self.__stopped:
+ status = "stopped"
+ if self.__daemonic:
+ status = status + " daemon"
+ return "<%s(%s, %s)>" % (self.__class__.__name__, self.__name, status)
+
+ def start(self):
+ assert self.__initialized, "Thread.__init__() not called"
+ assert not self.__started, "thread already started"
+ if __debug__:
+ self._note("%s.start(): starting thread", self)
+ _active_limbo_lock.acquire()
+ _limbo[self] = self
+ _active_limbo_lock.release()
+ _start_new_thread(self.__bootstrap, ())
+ self.__started = True
+ _sleep(0.000001) # 1 usec, to let the thread run (Solaris hack)
+
+ def run(self):
+ if self.__target:
+ self.__target(*self.__args, **self.__kwargs)
+
+ def __bootstrap(self):
+ try:
+ self.__started = True
+ _active_limbo_lock.acquire()
+ _active[_get_ident()] = self
+ del _limbo[self]
+ _active_limbo_lock.release()
+ if __debug__:
+ self._note("%s.__bootstrap(): thread started", self)
+
+ if _trace_hook:
+ self._note("%s.__bootstrap(): registering trace hook", self)
+ _sys.settrace(_trace_hook)
+ if _profile_hook:
+ self._note("%s.__bootstrap(): registering profile hook", self)
+ _sys.setprofile(_profile_hook)
+
+ try:
+ self.run()
+ except SystemExit:
+ if __debug__:
+ self._note("%s.__bootstrap(): raised SystemExit", self)
+ except:
+ if __debug__:
+ self._note("%s.__bootstrap(): unhandled exception", self)
+ s = _StringIO()
+ _print_exc(file=s)
+ _sys.stderr.write("Exception in thread %s:\n%s\n" %
+ (self.getName(), s.getvalue()))
+ else:
+ if __debug__:
+ self._note("%s.__bootstrap(): normal return", self)
+ finally:
+ self.__stop()
+ try:
+ self.__delete()
+ except:
+ pass
+
+ def __stop(self):
+ self.__block.acquire()
+ self.__stopped = True
+ self.__block.notifyAll()
+ self.__block.release()
+
+ def __delete(self):
+ _active_limbo_lock.acquire()
+ del _active[_get_ident()]
+ _active_limbo_lock.release()
+
+ def join(self, timeout=None):
+ assert self.__initialized, "Thread.__init__() not called"
+ assert self.__started, "cannot join thread before it is started"
+ assert self is not currentThread(), "cannot join current thread"
+ if __debug__:
+ if not self.__stopped:
+ self._note("%s.join(): waiting until thread stops", self)
+ self.__block.acquire()
+ try:
+ if timeout is None:
+ while not self.__stopped:
+ self.__block.wait()
+ if __debug__:
+ self._note("%s.join(): thread stopped", self)
+ else:
+ deadline = _time() + timeout
+ while not self.__stopped:
+ delay = deadline - _time()
+ if delay <= 0:
+ if __debug__:
+ self._note("%s.join(): timed out", self)
+ break
+ self.__block.wait(delay)
+ else:
+ if __debug__:
+ self._note("%s.join(): thread stopped", self)
+ finally:
+ self.__block.release()
+
+ def getName(self):
+ assert self.__initialized, "Thread.__init__() not called"
+ return self.__name
+
+ def setName(self, name):
+ assert self.__initialized, "Thread.__init__() not called"
+ self.__name = str(name)
+
+ def isAlive(self):
+ assert self.__initialized, "Thread.__init__() not called"
+ return self.__started and not self.__stopped
+
+ def isDaemon(self):
+ assert self.__initialized, "Thread.__init__() not called"
+ return self.__daemonic
+
+ def setDaemon(self, daemonic):
+ assert self.__initialized, "Thread.__init__() not called"
+ assert not self.__started, "cannot set daemon status of active thread"
+ self.__daemonic = daemonic
+
+# The timer class was contributed by Itamar Shtull-Trauring
+
+def Timer(*args, **kwargs):
+ return _Timer(*args, **kwargs)
+
+class _Timer(Thread):
+ """Call a function after a specified number of seconds:
+
+ t = Timer(30.0, f, args=[], kwargs={})
+ t.start()
+ t.cancel() # stop the timer's action if it's still waiting
+ """
+
+ def __init__(self, interval, function, args=[], kwargs={}):
+ Thread.__init__(self)
+ self.interval = interval
+ self.function = function
+ self.args = args
+ self.kwargs = kwargs
+ self.finished = Event()
+
+ def cancel(self):
+ """Stop the timer if it hasn't finished yet"""
+ self.finished.set()
+
+ def run(self):
+ self.finished.wait(self.interval)
+ if not self.finished.isSet():
+ self.function(*self.args, **self.kwargs)
+ self.finished.set()
+
+# Special thread class to represent the main thread
+# This is garbage collected through an exit handler
+
+class _MainThread(Thread):
+
+ def __init__(self):
+ Thread.__init__(self, name="MainThread")
+ self._Thread__started = True
+ _active_limbo_lock.acquire()
+ _active[_get_ident()] = self
+ _active_limbo_lock.release()
+ import atexit
+ atexit.register(self.__exitfunc)
+
+ def _set_daemon(self):
+ return False
+
+ def __exitfunc(self):
+ self._Thread__stop()
+ t = _pickSomeNonDaemonThread()
+ if t:
+ if __debug__:
+ self._note("%s: waiting for other threads", self)
+ while t:
+ t.join()
+ t = _pickSomeNonDaemonThread()
+ if __debug__:
+ self._note("%s: exiting", self)
+ self._Thread__delete()
+
+def _pickSomeNonDaemonThread():
+ for t in enumerate():
+ if not t.isDaemon() and t.isAlive():
+ return t
+ return None
+
+
+# Dummy thread class to represent threads not started here.
+# These aren't garbage collected when they die,
+# nor can they be waited for.
+# Their purpose is to return *something* from currentThread().
+# They are marked as daemon threads so we won't wait for them
+# when we exit (conform previous semantics).
+
+class _DummyThread(Thread):
+
+ def __init__(self):
+ Thread.__init__(self, name=_newname("Dummy-%d"))
+ self._Thread__started = True
+ _active_limbo_lock.acquire()
+ _active[_get_ident()] = self
+ _active_limbo_lock.release()
+
+ def _set_daemon(self):
+ return True
+
+ def join(self, timeout=None):
+ assert False, "cannot join a dummy thread"
+
+
+# Global API functions
+
+def currentThread():
+ try:
+ return _active[_get_ident()]
+ except KeyError:
+ ##print "currentThread(): no current thread for", _get_ident()
+ return _DummyThread()
+
+def activeCount():
+ _active_limbo_lock.acquire()
+ count = len(_active) + len(_limbo)
+ _active_limbo_lock.release()
+ return count
+
+def enumerate():
+ _active_limbo_lock.acquire()
+ active = _active.values() + _limbo.values()
+ _active_limbo_lock.release()
+ return active
+
+# Create the main thread object
+
+_MainThread()
+
+
+# Self-test code
+
+def _test():
+
+ class BoundedQueue(_Verbose):
+
+ def __init__(self, limit):
+ _Verbose.__init__(self)
+ self.mon = RLock()
+ self.rc = Condition(self.mon)
+ self.wc = Condition(self.mon)
+ self.limit = limit
+ self.queue = []
+
+ def put(self, item):
+ self.mon.acquire()
+ while len(self.queue) >= self.limit:
+ self._note("put(%s): queue full", item)
+ self.wc.wait()
+ self.queue.append(item)
+ self._note("put(%s): appended, length now %d",
+ item, len(self.queue))
+ self.rc.notify()
+ self.mon.release()
+
+ def get(self):
+ self.mon.acquire()
+ while not self.queue:
+ self._note("get(): queue empty")
+ self.rc.wait()
+ item = self.queue.pop(0)
+ self._note("get(): got %s, %d left", item, len(self.queue))
+ self.wc.notify()
+ self.mon.release()
+ return item
+
+ class ProducerThread(Thread):
+
+ def __init__(self, queue, quota):
+ Thread.__init__(self, name="Producer")
+ self.queue = queue
+ self.quota = quota
+
+ def run(self):
+ from random import random
+ counter = 0
+ while counter < self.quota:
+ counter = counter + 1
+ self.queue.put("%s.%d" % (self.getName(), counter))
+ _sleep(random() * 0.00001)
+
+
+ class ConsumerThread(Thread):
+
+ def __init__(self, queue, count):
+ Thread.__init__(self, name="Consumer")
+ self.queue = queue
+ self.count = count
+
+ def run(self):
+ while self.count > 0:
+ item = self.queue.get()
+ print item
+ self.count = self.count - 1
+
+ NP = 3
+ QL = 4
+ NI = 5
+
+ Q = BoundedQueue(QL)
+ P = []
+ for i in range(NP):
+ t = ProducerThread(Q, NI)
+ t.setName("Producer-%d" % (i+1))
+ P.append(t)
+ C = ConsumerThread(Q, NI*NP)
+ for t in P:
+ t.start()
+ _sleep(0.000001)
+ C.start()
+ for t in P:
+ t.join()
+ C.join()
+
+if __name__ == '__main__':
+ _test()
diff --git a/gnuradio-runtime/python/gnuradio/gr/gr_threading_24.py b/gnuradio-runtime/python/gnuradio/gr/gr_threading_24.py
new file mode 100644
index 0000000000..8539bfc047
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gr/gr_threading_24.py
@@ -0,0 +1,793 @@
+"""Thread module emulating a subset of Java's threading model."""
+
+# This started life as the threading.py module of Python 2.4
+# It's been patched to fix a problem with join, where a KeyboardInterrupt
+# caused a lock to be left in the acquired state.
+
+import sys as _sys
+
+try:
+ import thread
+except ImportError:
+ del _sys.modules[__name__]
+ raise
+
+from time import time as _time, sleep as _sleep
+from traceback import format_exc as _format_exc
+from collections import deque
+
+# Rename some stuff so "from threading import *" is safe
+__all__ = ['activeCount', 'Condition', 'currentThread', 'enumerate', 'Event',
+ 'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Thread',
+ 'Timer', 'setprofile', 'settrace', 'local']
+
+_start_new_thread = thread.start_new_thread
+_allocate_lock = thread.allocate_lock
+_get_ident = thread.get_ident
+ThreadError = thread.error
+del thread
+
+
+# Debug support (adapted from ihooks.py).
+# All the major classes here derive from _Verbose. We force that to
+# be a new-style class so that all the major classes here are new-style.
+# This helps debugging (type(instance) is more revealing for instances
+# of new-style classes).
+
+_VERBOSE = False
+
+if __debug__:
+
+ class _Verbose(object):
+
+ def __init__(self, verbose=None):
+ if verbose is None:
+ verbose = _VERBOSE
+ self.__verbose = verbose
+
+ def _note(self, format, *args):
+ if self.__verbose:
+ format = format % args
+ format = "%s: %s\n" % (
+ currentThread().getName(), format)
+ _sys.stderr.write(format)
+
+else:
+ # Disable this when using "python -O"
+ class _Verbose(object):
+ def __init__(self, verbose=None):
+ pass
+ def _note(self, *args):
+ pass
+
+# Support for profile and trace hooks
+
+_profile_hook = None
+_trace_hook = None
+
+def setprofile(func):
+ global _profile_hook
+ _profile_hook = func
+
+def settrace(func):
+ global _trace_hook
+ _trace_hook = func
+
+# Synchronization classes
+
+Lock = _allocate_lock
+
+def RLock(*args, **kwargs):
+ return _RLock(*args, **kwargs)
+
+class _RLock(_Verbose):
+
+ def __init__(self, verbose=None):
+ _Verbose.__init__(self, verbose)
+ self.__block = _allocate_lock()
+ self.__owner = None
+ self.__count = 0
+
+ def __repr__(self):
+ return "<%s(%s, %d)>" % (
+ self.__class__.__name__,
+ self.__owner and self.__owner.getName(),
+ self.__count)
+
+ def acquire(self, blocking=1):
+ me = currentThread()
+ if self.__owner is me:
+ self.__count = self.__count + 1
+ if __debug__:
+ self._note("%s.acquire(%s): recursive success", self, blocking)
+ return 1
+ rc = self.__block.acquire(blocking)
+ if rc:
+ self.__owner = me
+ self.__count = 1
+ if __debug__:
+ self._note("%s.acquire(%s): initial succes", self, blocking)
+ else:
+ if __debug__:
+ self._note("%s.acquire(%s): failure", self, blocking)
+ return rc
+
+ def release(self):
+ me = currentThread()
+ assert self.__owner is me, "release() of un-acquire()d lock"
+ self.__count = count = self.__count - 1
+ if not count:
+ self.__owner = None
+ self.__block.release()
+ if __debug__:
+ self._note("%s.release(): final release", self)
+ else:
+ if __debug__:
+ self._note("%s.release(): non-final release", self)
+
+ # Internal methods used by condition variables
+
+ def _acquire_restore(self, (count, owner)):
+ self.__block.acquire()
+ self.__count = count
+ self.__owner = owner
+ if __debug__:
+ self._note("%s._acquire_restore()", self)
+
+ def _release_save(self):
+ if __debug__:
+ self._note("%s._release_save()", self)
+ count = self.__count
+ self.__count = 0
+ owner = self.__owner
+ self.__owner = None
+ self.__block.release()
+ return (count, owner)
+
+ def _is_owned(self):
+ return self.__owner is currentThread()
+
+
+def Condition(*args, **kwargs):
+ return _Condition(*args, **kwargs)
+
+class _Condition(_Verbose):
+
+ def __init__(self, lock=None, verbose=None):
+ _Verbose.__init__(self, verbose)
+ if lock is None:
+ lock = RLock()
+ self.__lock = lock
+ # Export the lock's acquire() and release() methods
+ self.acquire = lock.acquire
+ self.release = lock.release
+ # If the lock defines _release_save() and/or _acquire_restore(),
+ # these override the default implementations (which just call
+ # release() and acquire() on the lock). Ditto for _is_owned().
+ try:
+ self._release_save = lock._release_save
+ except AttributeError:
+ pass
+ try:
+ self._acquire_restore = lock._acquire_restore
+ except AttributeError:
+ pass
+ try:
+ self._is_owned = lock._is_owned
+ except AttributeError:
+ pass
+ self.__waiters = []
+
+ def __repr__(self):
+ return "<Condition(%s, %d)>" % (self.__lock, len(self.__waiters))
+
+ def _release_save(self):
+ self.__lock.release() # No state to save
+
+ def _acquire_restore(self, x):
+ self.__lock.acquire() # Ignore saved state
+
+ def _is_owned(self):
+ # Return True if lock is owned by currentThread.
+ # This method is called only if __lock doesn't have _is_owned().
+ if self.__lock.acquire(0):
+ self.__lock.release()
+ return False
+ else:
+ return True
+
+ def wait(self, timeout=None):
+ assert self._is_owned(), "wait() of un-acquire()d lock"
+ waiter = _allocate_lock()
+ waiter.acquire()
+ self.__waiters.append(waiter)
+ saved_state = self._release_save()
+ try: # restore state no matter what (e.g., KeyboardInterrupt)
+ if timeout is None:
+ waiter.acquire()
+ if __debug__:
+ self._note("%s.wait(): got it", self)
+ else:
+ # Balancing act: We can't afford a pure busy loop, so we
+ # have to sleep; but if we sleep the whole timeout time,
+ # we'll be unresponsive. The scheme here sleeps very
+ # little at first, longer as time goes on, but never longer
+ # than 20 times per second (or the timeout time remaining).
+ endtime = _time() + timeout
+ delay = 0.0005 # 500 us -> initial delay of 1 ms
+ while True:
+ gotit = waiter.acquire(0)
+ if gotit:
+ break
+ remaining = endtime - _time()
+ if remaining <= 0:
+ break
+ delay = min(delay * 2, remaining, .05)
+ _sleep(delay)
+ if not gotit:
+ if __debug__:
+ self._note("%s.wait(%s): timed out", self, timeout)
+ try:
+ self.__waiters.remove(waiter)
+ except ValueError:
+ pass
+ else:
+ if __debug__:
+ self._note("%s.wait(%s): got it", self, timeout)
+ finally:
+ self._acquire_restore(saved_state)
+
+ def notify(self, n=1):
+ assert self._is_owned(), "notify() of un-acquire()d lock"
+ __waiters = self.__waiters
+ waiters = __waiters[:n]
+ if not waiters:
+ if __debug__:
+ self._note("%s.notify(): no waiters", self)
+ return
+ self._note("%s.notify(): notifying %d waiter%s", self, n,
+ n!=1 and "s" or "")
+ for waiter in waiters:
+ waiter.release()
+ try:
+ __waiters.remove(waiter)
+ except ValueError:
+ pass
+
+ def notifyAll(self):
+ self.notify(len(self.__waiters))
+
+
+def Semaphore(*args, **kwargs):
+ return _Semaphore(*args, **kwargs)
+
+class _Semaphore(_Verbose):
+
+ # After Tim Peters' semaphore class, but not quite the same (no maximum)
+
+ def __init__(self, value=1, verbose=None):
+ assert value >= 0, "Semaphore initial value must be >= 0"
+ _Verbose.__init__(self, verbose)
+ self.__cond = Condition(Lock())
+ self.__value = value
+
+ def acquire(self, blocking=1):
+ rc = False
+ self.__cond.acquire()
+ while self.__value == 0:
+ if not blocking:
+ break
+ if __debug__:
+ self._note("%s.acquire(%s): blocked waiting, value=%s",
+ self, blocking, self.__value)
+ self.__cond.wait()
+ else:
+ self.__value = self.__value - 1
+ if __debug__:
+ self._note("%s.acquire: success, value=%s",
+ self, self.__value)
+ rc = True
+ self.__cond.release()
+ return rc
+
+ def release(self):
+ self.__cond.acquire()
+ self.__value = self.__value + 1
+ if __debug__:
+ self._note("%s.release: success, value=%s",
+ self, self.__value)
+ self.__cond.notify()
+ self.__cond.release()
+
+
+def BoundedSemaphore(*args, **kwargs):
+ return _BoundedSemaphore(*args, **kwargs)
+
+class _BoundedSemaphore(_Semaphore):
+ """Semaphore that checks that # releases is <= # acquires"""
+ def __init__(self, value=1, verbose=None):
+ _Semaphore.__init__(self, value, verbose)
+ self._initial_value = value
+
+ def release(self):
+ if self._Semaphore__value >= self._initial_value:
+ raise ValueError, "Semaphore released too many times"
+ return _Semaphore.release(self)
+
+
+def Event(*args, **kwargs):
+ return _Event(*args, **kwargs)
+
+class _Event(_Verbose):
+
+ # After Tim Peters' event class (without is_posted())
+
+ def __init__(self, verbose=None):
+ _Verbose.__init__(self, verbose)
+ self.__cond = Condition(Lock())
+ self.__flag = False
+
+ def isSet(self):
+ return self.__flag
+
+ def set(self):
+ self.__cond.acquire()
+ try:
+ self.__flag = True
+ self.__cond.notifyAll()
+ finally:
+ self.__cond.release()
+
+ def clear(self):
+ self.__cond.acquire()
+ try:
+ self.__flag = False
+ finally:
+ self.__cond.release()
+
+ def wait(self, timeout=None):
+ self.__cond.acquire()
+ try:
+ if not self.__flag:
+ self.__cond.wait(timeout)
+ finally:
+ self.__cond.release()
+
+# Helper to generate new thread names
+_counter = 0
+def _newname(template="Thread-%d"):
+ global _counter
+ _counter = _counter + 1
+ return template % _counter
+
+# Active thread administration
+_active_limbo_lock = _allocate_lock()
+_active = {}
+_limbo = {}
+
+
+# Main class for threads
+
+class Thread(_Verbose):
+
+ __initialized = False
+ # Need to store a reference to sys.exc_info for printing
+ # out exceptions when a thread tries to use a global var. during interp.
+ # shutdown and thus raises an exception about trying to perform some
+ # operation on/with a NoneType
+ __exc_info = _sys.exc_info
+
+ def __init__(self, group=None, target=None, name=None,
+ args=(), kwargs={}, verbose=None):
+ assert group is None, "group argument must be None for now"
+ _Verbose.__init__(self, verbose)
+ self.__target = target
+ self.__name = str(name or _newname())
+ self.__args = args
+ self.__kwargs = kwargs
+ self.__daemonic = self._set_daemon()
+ self.__started = False
+ self.__stopped = False
+ self.__block = Condition(Lock())
+ self.__initialized = True
+ # sys.stderr is not stored in the class like
+ # sys.exc_info since it can be changed between instances
+ self.__stderr = _sys.stderr
+
+ def _set_daemon(self):
+ # Overridden in _MainThread and _DummyThread
+ return currentThread().isDaemon()
+
+ def __repr__(self):
+ assert self.__initialized, "Thread.__init__() was not called"
+ status = "initial"
+ if self.__started:
+ status = "started"
+ if self.__stopped:
+ status = "stopped"
+ if self.__daemonic:
+ status = status + " daemon"
+ return "<%s(%s, %s)>" % (self.__class__.__name__, self.__name, status)
+
+ def start(self):
+ assert self.__initialized, "Thread.__init__() not called"
+ assert not self.__started, "thread already started"
+ if __debug__:
+ self._note("%s.start(): starting thread", self)
+ _active_limbo_lock.acquire()
+ _limbo[self] = self
+ _active_limbo_lock.release()
+ _start_new_thread(self.__bootstrap, ())
+ self.__started = True
+ _sleep(0.000001) # 1 usec, to let the thread run (Solaris hack)
+
+ def run(self):
+ if self.__target:
+ self.__target(*self.__args, **self.__kwargs)
+
+ def __bootstrap(self):
+ try:
+ self.__started = True
+ _active_limbo_lock.acquire()
+ _active[_get_ident()] = self
+ del _limbo[self]
+ _active_limbo_lock.release()
+ if __debug__:
+ self._note("%s.__bootstrap(): thread started", self)
+
+ if _trace_hook:
+ self._note("%s.__bootstrap(): registering trace hook", self)
+ _sys.settrace(_trace_hook)
+ if _profile_hook:
+ self._note("%s.__bootstrap(): registering profile hook", self)
+ _sys.setprofile(_profile_hook)
+
+ try:
+ self.run()
+ except SystemExit:
+ if __debug__:
+ self._note("%s.__bootstrap(): raised SystemExit", self)
+ except:
+ if __debug__:
+ self._note("%s.__bootstrap(): unhandled exception", self)
+ # If sys.stderr is no more (most likely from interpreter
+ # shutdown) use self.__stderr. Otherwise still use sys (as in
+ # _sys) in case sys.stderr was redefined since the creation of
+ # self.
+ if _sys:
+ _sys.stderr.write("Exception in thread %s:\n%s\n" %
+ (self.getName(), _format_exc()))
+ else:
+ # Do the best job possible w/o a huge amt. of code to
+ # approximate a traceback (code ideas from
+ # Lib/traceback.py)
+ exc_type, exc_value, exc_tb = self.__exc_info()
+ try:
+ print>>self.__stderr, (
+ "Exception in thread " + self.getName() +
+ " (most likely raised during interpreter shutdown):")
+ print>>self.__stderr, (
+ "Traceback (most recent call last):")
+ while exc_tb:
+ print>>self.__stderr, (
+ ' File "%s", line %s, in %s' %
+ (exc_tb.tb_frame.f_code.co_filename,
+ exc_tb.tb_lineno,
+ exc_tb.tb_frame.f_code.co_name))
+ exc_tb = exc_tb.tb_next
+ print>>self.__stderr, ("%s: %s" % (exc_type, exc_value))
+ # Make sure that exc_tb gets deleted since it is a memory
+ # hog; deleting everything else is just for thoroughness
+ finally:
+ del exc_type, exc_value, exc_tb
+ else:
+ if __debug__:
+ self._note("%s.__bootstrap(): normal return", self)
+ finally:
+ self.__stop()
+ try:
+ self.__delete()
+ except:
+ pass
+
+ def __stop(self):
+ self.__block.acquire()
+ self.__stopped = True
+ self.__block.notifyAll()
+ self.__block.release()
+
+ def __delete(self):
+ "Remove current thread from the dict of currently running threads."
+
+ # Notes about running with dummy_thread:
+ #
+ # Must take care to not raise an exception if dummy_thread is being
+ # used (and thus this module is being used as an instance of
+ # dummy_threading). dummy_thread.get_ident() always returns -1 since
+ # there is only one thread if dummy_thread is being used. Thus
+ # len(_active) is always <= 1 here, and any Thread instance created
+ # overwrites the (if any) thread currently registered in _active.
+ #
+ # An instance of _MainThread is always created by 'threading'. This
+ # gets overwritten the instant an instance of Thread is created; both
+ # threads return -1 from dummy_thread.get_ident() and thus have the
+ # same key in the dict. So when the _MainThread instance created by
+ # 'threading' tries to clean itself up when atexit calls this method
+ # it gets a KeyError if another Thread instance was created.
+ #
+ # This all means that KeyError from trying to delete something from
+ # _active if dummy_threading is being used is a red herring. But
+ # since it isn't if dummy_threading is *not* being used then don't
+ # hide the exception.
+
+ _active_limbo_lock.acquire()
+ try:
+ try:
+ del _active[_get_ident()]
+ except KeyError:
+ if 'dummy_threading' not in _sys.modules:
+ raise
+ finally:
+ _active_limbo_lock.release()
+
+ def join(self, timeout=None):
+ assert self.__initialized, "Thread.__init__() not called"
+ assert self.__started, "cannot join thread before it is started"
+ assert self is not currentThread(), "cannot join current thread"
+ if __debug__:
+ if not self.__stopped:
+ self._note("%s.join(): waiting until thread stops", self)
+ self.__block.acquire()
+ try:
+ if timeout is None:
+ while not self.__stopped:
+ self.__block.wait()
+ if __debug__:
+ self._note("%s.join(): thread stopped", self)
+ else:
+ deadline = _time() + timeout
+ while not self.__stopped:
+ delay = deadline - _time()
+ if delay <= 0:
+ if __debug__:
+ self._note("%s.join(): timed out", self)
+ break
+ self.__block.wait(delay)
+ else:
+ if __debug__:
+ self._note("%s.join(): thread stopped", self)
+ finally:
+ self.__block.release()
+
+ def getName(self):
+ assert self.__initialized, "Thread.__init__() not called"
+ return self.__name
+
+ def setName(self, name):
+ assert self.__initialized, "Thread.__init__() not called"
+ self.__name = str(name)
+
+ def isAlive(self):
+ assert self.__initialized, "Thread.__init__() not called"
+ return self.__started and not self.__stopped
+
+ def isDaemon(self):
+ assert self.__initialized, "Thread.__init__() not called"
+ return self.__daemonic
+
+ def setDaemon(self, daemonic):
+ assert self.__initialized, "Thread.__init__() not called"
+ assert not self.__started, "cannot set daemon status of active thread"
+ self.__daemonic = daemonic
+
+# The timer class was contributed by Itamar Shtull-Trauring
+
+def Timer(*args, **kwargs):
+ return _Timer(*args, **kwargs)
+
+class _Timer(Thread):
+ """Call a function after a specified number of seconds:
+
+ t = Timer(30.0, f, args=[], kwargs={})
+ t.start()
+ t.cancel() # stop the timer's action if it's still waiting
+ """
+
+ def __init__(self, interval, function, args=[], kwargs={}):
+ Thread.__init__(self)
+ self.interval = interval
+ self.function = function
+ self.args = args
+ self.kwargs = kwargs
+ self.finished = Event()
+
+ def cancel(self):
+ """Stop the timer if it hasn't finished yet"""
+ self.finished.set()
+
+ def run(self):
+ self.finished.wait(self.interval)
+ if not self.finished.isSet():
+ self.function(*self.args, **self.kwargs)
+ self.finished.set()
+
+# Special thread class to represent the main thread
+# This is garbage collected through an exit handler
+
+class _MainThread(Thread):
+
+ def __init__(self):
+ Thread.__init__(self, name="MainThread")
+ self._Thread__started = True
+ _active_limbo_lock.acquire()
+ _active[_get_ident()] = self
+ _active_limbo_lock.release()
+ import atexit
+ atexit.register(self.__exitfunc)
+
+ def _set_daemon(self):
+ return False
+
+ def __exitfunc(self):
+ self._Thread__stop()
+ t = _pickSomeNonDaemonThread()
+ if t:
+ if __debug__:
+ self._note("%s: waiting for other threads", self)
+ while t:
+ t.join()
+ t = _pickSomeNonDaemonThread()
+ if __debug__:
+ self._note("%s: exiting", self)
+ self._Thread__delete()
+
+def _pickSomeNonDaemonThread():
+ for t in enumerate():
+ if not t.isDaemon() and t.isAlive():
+ return t
+ return None
+
+
+# Dummy thread class to represent threads not started here.
+# These aren't garbage collected when they die,
+# nor can they be waited for.
+# Their purpose is to return *something* from currentThread().
+# They are marked as daemon threads so we won't wait for them
+# when we exit (conform previous semantics).
+
+class _DummyThread(Thread):
+
+ def __init__(self):
+ Thread.__init__(self, name=_newname("Dummy-%d"))
+ self._Thread__started = True
+ _active_limbo_lock.acquire()
+ _active[_get_ident()] = self
+ _active_limbo_lock.release()
+
+ def _set_daemon(self):
+ return True
+
+ def join(self, timeout=None):
+ assert False, "cannot join a dummy thread"
+
+
+# Global API functions
+
+def currentThread():
+ try:
+ return _active[_get_ident()]
+ except KeyError:
+ ##print "currentThread(): no current thread for", _get_ident()
+ return _DummyThread()
+
+def activeCount():
+ _active_limbo_lock.acquire()
+ count = len(_active) + len(_limbo)
+ _active_limbo_lock.release()
+ return count
+
+def enumerate():
+ _active_limbo_lock.acquire()
+ active = _active.values() + _limbo.values()
+ _active_limbo_lock.release()
+ return active
+
+# Create the main thread object
+
+_MainThread()
+
+# get thread-local implementation, either from the thread
+# module, or from the python fallback
+
+try:
+ from thread import _local as local
+except ImportError:
+ from _threading_local import local
+
+
+# Self-test code
+
+def _test():
+
+ class BoundedQueue(_Verbose):
+
+ def __init__(self, limit):
+ _Verbose.__init__(self)
+ self.mon = RLock()
+ self.rc = Condition(self.mon)
+ self.wc = Condition(self.mon)
+ self.limit = limit
+ self.queue = deque()
+
+ def put(self, item):
+ self.mon.acquire()
+ while len(self.queue) >= self.limit:
+ self._note("put(%s): queue full", item)
+ self.wc.wait()
+ self.queue.append(item)
+ self._note("put(%s): appended, length now %d",
+ item, len(self.queue))
+ self.rc.notify()
+ self.mon.release()
+
+ def get(self):
+ self.mon.acquire()
+ while not self.queue:
+ self._note("get(): queue empty")
+ self.rc.wait()
+ item = self.queue.popleft()
+ self._note("get(): got %s, %d left", item, len(self.queue))
+ self.wc.notify()
+ self.mon.release()
+ return item
+
+ class ProducerThread(Thread):
+
+ def __init__(self, queue, quota):
+ Thread.__init__(self, name="Producer")
+ self.queue = queue
+ self.quota = quota
+
+ def run(self):
+ from random import random
+ counter = 0
+ while counter < self.quota:
+ counter = counter + 1
+ self.queue.put("%s.%d" % (self.getName(), counter))
+ _sleep(random() * 0.00001)
+
+
+ class ConsumerThread(Thread):
+
+ def __init__(self, queue, count):
+ Thread.__init__(self, name="Consumer")
+ self.queue = queue
+ self.count = count
+
+ def run(self):
+ while self.count > 0:
+ item = self.queue.get()
+ print item
+ self.count = self.count - 1
+
+ NP = 3
+ QL = 4
+ NI = 5
+
+ Q = BoundedQueue(QL)
+ P = []
+ for i in range(NP):
+ t = ProducerThread(Q, NI)
+ t.setName("Producer-%d" % (i+1))
+ P.append(t)
+ C = ConsumerThread(Q, NI*NP)
+ for t in P:
+ t.start()
+ _sleep(0.000001)
+ C.start()
+ for t in P:
+ t.join()
+ C.join()
+
+if __name__ == '__main__':
+ _test()
diff --git a/gnuradio-runtime/python/gnuradio/gr/hier_block2.py b/gnuradio-runtime/python/gnuradio/gr/hier_block2.py
new file mode 100644
index 0000000000..31e4065a25
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gr/hier_block2.py
@@ -0,0 +1,132 @@
+#
+# Copyright 2006,2007 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from runtime_swig import hier_block2_swig
+
+try:
+ import pmt
+except ImportError:
+ from gruel import pmt
+
+#
+# This hack forces a 'has-a' relationship to look like an 'is-a' one.
+#
+# It allows Python classes to subclass this one, while passing through
+# method calls to the C++ class shared pointer from SWIG.
+#
+# It also allows us to intercept method calls if needed
+#
+class hier_block2(object):
+ """
+ Subclass this to create a python hierarchical block.
+
+ This is a python wrapper around the C++ hierarchical block implementation.
+ Provides convenience functions and allows proper Python subclassing.
+ """
+
+ def __init__(self, name, input_signature, output_signature):
+ """
+ Create a hierarchical block with a given name and I/O signatures.
+ """
+ self._hb = hier_block2_swig(name, input_signature, output_signature)
+
+ def __getattr__(self, name):
+ """
+ Pass-through member requests to the C++ object.
+ """
+ if not hasattr(self, "_hb"):
+ raise RuntimeError("hier_block2: invalid state--did you forget to call gr.hier_block2.__init__ in a derived class?")
+ return getattr(self._hb, name)
+
+ def connect(self, *points):
+ """
+ Connect two or more block endpoints. An endpoint is either a (block, port)
+ tuple or a block instance. In the latter case, the port number is assumed
+ to be zero.
+
+ To connect the hierarchical block external inputs or outputs to internal block
+ inputs or outputs, use 'self' in the connect call.
+
+ If multiple arguments are provided, connect will attempt to wire them in series,
+ interpreting the endpoints as inputs or outputs as appropriate.
+ """
+
+ if len (points) < 1:
+ raise ValueError, ("connect requires at least one endpoint; %d provided." % (len (points),))
+ else:
+ if len(points) == 1:
+ self._hb.primitive_connect(points[0].to_basic_block())
+ else:
+ for i in range (1, len (points)):
+ self._connect(points[i-1], points[i])
+
+ def _connect(self, src, dst):
+ (src_block, src_port) = self._coerce_endpoint(src)
+ (dst_block, dst_port) = self._coerce_endpoint(dst)
+ self._hb.primitive_connect(src_block.to_basic_block(), src_port,
+ dst_block.to_basic_block(), dst_port)
+
+ def _coerce_endpoint(self, endp):
+ if hasattr(endp, 'to_basic_block'):
+ return (endp, 0)
+ else:
+ if hasattr(endp, "__getitem__") and len(endp) == 2:
+ return endp # Assume user put (block, port)
+ else:
+ raise ValueError("unable to coerce endpoint")
+
+ def disconnect(self, *points):
+ """
+ Disconnect two endpoints in the flowgraph.
+
+ To disconnect the hierarchical block external inputs or outputs to internal block
+ inputs or outputs, use 'self' in the connect call.
+
+ If more than two arguments are provided, they are disconnected successively.
+ """
+
+ if len (points) < 1:
+ raise ValueError, ("disconnect requires at least one endpoint; %d provided." % (len (points),))
+ else:
+ if len (points) == 1:
+ self._hb.primitive_disconnect(points[0].to_basic_block())
+ else:
+ for i in range (1, len (points)):
+ self._disconnect(points[i-1], points[i])
+
+ def _disconnect(self, src, dst):
+ (src_block, src_port) = self._coerce_endpoint(src)
+ (dst_block, dst_port) = self._coerce_endpoint(dst)
+ self._hb.primitive_disconnect(src_block.to_basic_block(), src_port,
+ dst_block.to_basic_block(), dst_port)
+
+ def msg_connect(self, src, srcport, dst, dstport):
+ self.primitive_msg_connect(src.to_basic_block(), srcport, dst.to_basic_block(), dstport);
+
+ def msg_disconnect(self, src, srcport, dst, dstport):
+ self.primitive_msg_disconnect(src.to_basic_block(), srcport, dst.to_basic_block(), dstport);
+
+ def message_port_register_hier_in(self, portname):
+ self.primitive_message_port_register_hier_in(pmt.intern(portname));
+
+ def message_port_register_hier_out(self, portname):
+ self.primitive_message_port_register_hier_out(pmt.intern(portname));
+
diff --git a/gnuradio-runtime/python/gnuradio/gr/prefs.py b/gnuradio-runtime/python/gnuradio/gr/prefs.py
new file mode 100644
index 0000000000..25fa8cd6ae
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gr/prefs.py
@@ -0,0 +1,127 @@
+#
+# Copyright 2006,2009 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+import gnuradio_core as gsp
+_prefs_base = gsp.gr_prefs
+
+
+import ConfigParser
+import os
+import os.path
+import sys
+import glob
+
+
+def _user_prefs_filename():
+ return os.path.expanduser('~/.gnuradio/config.conf')
+
+def _sys_prefs_dirname():
+ return gsp.prefsdir()
+
+def _bool(x):
+ """
+ Try to coerce obj to a True or False
+ """
+ if isinstance(x, bool):
+ return x
+ if isinstance(x, (float, int)):
+ return bool(x)
+ raise TypeError, x
+
+
+class _prefs(_prefs_base):
+ """
+ Derive our 'real class' from the stubbed out base class that has support
+ for SWIG directors. This allows C++ code to magically and transparently
+ invoke the methods in this python class.
+ """
+ def __init__(self):
+ _prefs_base.__init__(self)
+ self.cp = ConfigParser.RawConfigParser()
+ self.__getattr__ = lambda self, name: getattr(self.cp, name)
+
+ def _sys_prefs_filenames(self):
+ dir = _sys_prefs_dirname()
+ try:
+ fnames = glob.glob(os.path.join(dir, '*.conf'))
+ except (IOError, OSError):
+ return []
+ fnames.sort()
+ return fnames
+
+ def _read_files(self):
+ filenames = self._sys_prefs_filenames()
+ filenames.append(_user_prefs_filename())
+ #print "filenames: ", filenames
+ self.cp.read(filenames)
+
+ # ----------------------------------------------------------------
+ # These methods override the C++ virtual methods of the same name
+ # ----------------------------------------------------------------
+ def has_section(self, section):
+ return self.cp.has_section(section)
+
+ def has_option(self, section, option):
+ return self.cp.has_option(section, option)
+
+ def get_string(self, section, option, default_val):
+ try:
+ return self.cp.get(section, option)
+ except:
+ return default_val
+
+ def get_bool(self, section, option, default_val):
+ try:
+ return self.cp.getboolean(section, option)
+ except:
+ return default_val
+
+ def get_long(self, section, option, default_val):
+ try:
+ return self.cp.getint(section, option)
+ except:
+ return default_val
+
+ def get_double(self, section, option, default_val):
+ try:
+ return self.cp.getfloat(section, option)
+ except:
+ return default_val
+ # ----------------------------------------------------------------
+ # End override of C++ virtual methods
+ # ----------------------------------------------------------------
+
+
+_prefs_db = _prefs()
+
+# if GR_DONT_LOAD_PREFS is set, don't load them.
+# (make check uses this to avoid interactions.)
+if os.getenv("GR_DONT_LOAD_PREFS", None) is None:
+ _prefs_db._read_files()
+
+
+_prefs_base.set_singleton(_prefs_db) # tell C++ what instance to use
+
+def prefs():
+ """
+ Return the global preference data base
+ """
+ return _prefs_db
diff --git a/gnuradio-runtime/python/gnuradio/gr/pubsub.py b/gnuradio-runtime/python/gnuradio/gr/pubsub.py
new file mode 100644
index 0000000000..90568418fc
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gr/pubsub.py
@@ -0,0 +1,153 @@
+#!/usr/bin/env python
+#
+# Copyright 2008,2009 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+"""
+Abstract GNU Radio publisher/subscriber interface
+
+This is a proof of concept implementation, will likely change significantly.
+"""
+
+class pubsub(dict):
+ def __init__(self):
+ self._publishers = { }
+ self._subscribers = { }
+ self._proxies = { }
+
+ def __missing__(self, key, value=None):
+ dict.__setitem__(self, key, value)
+ self._publishers[key] = None
+ self._subscribers[key] = []
+ self._proxies[key] = None
+
+ def __setitem__(self, key, val):
+ if not self.has_key(key):
+ self.__missing__(key, val)
+ elif self._proxies[key] is not None:
+ (p, newkey) = self._proxies[key]
+ p[newkey] = val
+ else:
+ dict.__setitem__(self, key, val)
+ for sub in self._subscribers[key]:
+ # Note this means subscribers will get called in the thread
+ # context of the 'set' caller.
+ sub(val)
+
+ def __getitem__(self, key):
+ if not self.has_key(key): self.__missing__(key)
+ if self._proxies[key] is not None:
+ (p, newkey) = self._proxies[key]
+ return p[newkey]
+ elif self._publishers[key] is not None:
+ return self._publishers[key]()
+ else:
+ return dict.__getitem__(self, key)
+
+ def publish(self, key, publisher):
+ if not self.has_key(key): self.__missing__(key)
+ if self._proxies[key] is not None:
+ (p, newkey) = self._proxies[key]
+ p.publish(newkey, publisher)
+ else:
+ self._publishers[key] = publisher
+
+ def subscribe(self, key, subscriber):
+ if not self.has_key(key): self.__missing__(key)
+ if self._proxies[key] is not None:
+ (p, newkey) = self._proxies[key]
+ p.subscribe(newkey, subscriber)
+ else:
+ self._subscribers[key].append(subscriber)
+
+ def unpublish(self, key):
+ if self._proxies[key] is not None:
+ (p, newkey) = self._proxies[key]
+ p.unpublish(newkey)
+ else:
+ self._publishers[key] = None
+
+ def unsubscribe(self, key, subscriber):
+ if self._proxies[key] is not None:
+ (p, newkey) = self._proxies[key]
+ p.unsubscribe(newkey, subscriber)
+ else:
+ self._subscribers[key].remove(subscriber)
+
+ def proxy(self, key, p, newkey=None):
+ if not self.has_key(key): self.__missing__(key)
+ if newkey is None: newkey = key
+ self._proxies[key] = (p, newkey)
+
+ def unproxy(self, key):
+ self._proxies[key] = None
+
+# Test code
+if __name__ == "__main__":
+ import sys
+ o = pubsub()
+
+ # Non-existent key gets auto-created with None value
+ print "Auto-created key 'foo' value:", o['foo']
+
+ # Add some subscribers
+ # First is a bare function
+ def print_len(x):
+ print "len=%i" % (len(x), )
+ o.subscribe('foo', print_len)
+
+ # The second is a class member function
+ class subber(object):
+ def __init__(self, param):
+ self._param = param
+ def printer(self, x):
+ print self._param, `x`
+ s = subber('param')
+ o.subscribe('foo', s.printer)
+
+ # The third is a lambda function
+ o.subscribe('foo', lambda x: sys.stdout.write('val='+`x`+'\n'))
+
+ # Update key 'foo', will notify subscribers
+ print "Updating 'foo' with three subscribers:"
+ o['foo'] = 'bar';
+
+ # Remove first subscriber
+ o.unsubscribe('foo', print_len)
+
+ # Update now will only trigger second and third subscriber
+ print "Updating 'foo' after removing a subscriber:"
+ o['foo'] = 'bar2';
+
+ # Publish a key as a function, in this case, a lambda function
+ o.publish('baz', lambda : 42)
+ print "Published value of 'baz':", o['baz']
+
+ # Unpublish the key
+ o.unpublish('baz')
+
+ # This will return None, as there is no publisher
+ print "Value of 'baz' with no publisher:", o['baz']
+
+ # Set 'baz' key, it gets cached
+ o['baz'] = 'bazzz'
+
+ # Now will return cached value, since no provider
+ print "Cached value of 'baz' after being set:", o['baz']
diff --git a/gnuradio-runtime/python/gnuradio/gr/qa_feval.py b/gnuradio-runtime/python/gnuradio/gr/qa_feval.py
new file mode 100755
index 0000000000..9018e12f36
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gr/qa_feval.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python
+#
+# Copyright 2006,2007,2010 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from gnuradio import gr, gr_unittest
+
+class my_add2_dd(gr.feval_dd):
+ def eval(self, x):
+ return x + 2
+
+class my_add2_ll(gr.feval_ll):
+ def eval(self, x):
+ return x + 2
+
+class my_add2_cc(gr.feval_cc):
+ def eval(self, x):
+ return x + (2 - 2j)
+
+class my_feval(gr.feval):
+ def __init__(self):
+ gr.feval.__init__(self)
+ self.fired = False
+ def eval(self):
+ self.fired = True
+
+class test_feval(gr_unittest.TestCase):
+
+ def test_dd_1(self):
+ f = my_add2_dd()
+ src_data = (0.0, 1.0, 2.0, 3.0, 4.0)
+ expected_result = (2.0, 3.0, 4.0, 5.0, 6.0)
+ # this is all in python...
+ actual_result = tuple([f.eval(x) for x in src_data])
+ self.assertEqual(expected_result, actual_result)
+
+ def test_dd_2(self):
+ f = my_add2_dd()
+ src_data = (0.0, 1.0, 2.0, 3.0, 4.0)
+ expected_result = (2.0, 3.0, 4.0, 5.0, 6.0)
+ # this is python -> C++ -> python and back again...
+ actual_result = tuple([gr.feval_dd_example(f, x) for x in src_data])
+ self.assertEqual(expected_result, actual_result)
+
+
+ def test_ll_1(self):
+ f = my_add2_ll()
+ src_data = (0, 1, 2, 3, 4)
+ expected_result = (2, 3, 4, 5, 6)
+ # this is all in python...
+ actual_result = tuple([f.eval(x) for x in src_data])
+ self.assertEqual(expected_result, actual_result)
+
+ def test_ll_2(self):
+ f = my_add2_ll()
+ src_data = (0, 1, 2, 3, 4)
+ expected_result = (2, 3, 4, 5, 6)
+ # this is python -> C++ -> python and back again...
+ actual_result = tuple([gr.feval_ll_example(f, x) for x in src_data])
+ self.assertEqual(expected_result, actual_result)
+
+
+ def test_cc_1(self):
+ f = my_add2_cc()
+ src_data = (0+1j, 2+3j, 4+5j, 6+7j)
+ expected_result = (2-1j, 4+1j, 6+3j, 8+5j)
+ # this is all in python...
+ actual_result = tuple([f.eval(x) for x in src_data])
+ self.assertEqual(expected_result, actual_result)
+
+ def test_cc_2(self):
+ f = my_add2_cc()
+ src_data = (0+1j, 2+3j, 4+5j, 6+7j)
+ expected_result = (2-1j, 4+1j, 6+3j, 8+5j)
+ # this is python -> C++ -> python and back again...
+ actual_result = tuple([gr.feval_cc_example(f, x) for x in src_data])
+ self.assertEqual(expected_result, actual_result)
+
+ def test_void_1(self):
+ # this is all in python
+ f = my_feval()
+ f.eval()
+ self.assertEqual(True, f.fired)
+
+ def test_void_2(self):
+ # this is python -> C++ -> python and back again
+ f = my_feval()
+ gr.feval_example(f)
+ self.assertEqual(True, f.fired)
+
+
+if __name__ == '__main__':
+ gr_unittest.run(test_feval, "test_feval.xml")
diff --git a/gnuradio-runtime/python/gnuradio/gr/qa_kludged_imports.py b/gnuradio-runtime/python/gnuradio/gr/qa_kludged_imports.py
new file mode 100755
index 0000000000..f80188c9fc
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gr/qa_kludged_imports.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+#
+# Copyright 2005,2008,2010 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from gnuradio import gr, gr_unittest
+
+class test_kludged_imports (gr_unittest.TestCase):
+
+ def setUp(self):
+ pass
+
+ def tearDown(self):
+ pass
+
+ def test_gru_import(self):
+ # make sure that this somewhat magic import works
+ from gnuradio import gru
+
+
+if __name__ == '__main__':
+ gr_unittest.run(test_kludged_imports, "test_kludged_imports.xml")
diff --git a/gnuradio-runtime/python/gnuradio/gr/qa_tag_utils.py b/gnuradio-runtime/python/gnuradio/gr/qa_tag_utils.py
new file mode 100755
index 0000000000..de1b5aa002
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gr/qa_tag_utils.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+#
+# Copyright 2007,2010 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from gnuradio import gr, gr_unittest
+import tag_utils
+
+try:
+ import pmt_swig as pmt
+except ImportError:
+ import pmt
+
+class test_tag_utils (gr_unittest.TestCase):
+
+ def setUp (self):
+ self.tb = gr.top_block ()
+
+
+ def tearDown (self):
+ self.tb = None
+
+ def test_001(self):
+ t = gr.gr_tag_t()
+ t.offset = 10
+ t.key = pmt.string_to_symbol('key')
+ t.value = pmt.from_long(23)
+ t.srcid = pmt.from_bool(False)
+ pt = tag_utils.tag_to_python(t)
+ self.assertEqual(pt.key, 'key')
+ self.assertEqual(pt.value, 23)
+ self.assertEqual(pt.offset, 10)
+
+
+if __name__ == '__main__':
+ print 'hi'
+ gr_unittest.run(test_tag_utils, "test_tag_utils.xml")
+
diff --git a/gnuradio-runtime/python/gnuradio/gr/tag_utils.py b/gnuradio-runtime/python/gnuradio/gr/tag_utils.py
new file mode 100644
index 0000000000..1c9594d6d0
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gr/tag_utils.py
@@ -0,0 +1,57 @@
+#
+# Copyright 2003-2012 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+""" Conversion tools between stream tags and Python objects """
+
+try: import pmt
+except: from gruel import pmt
+
+try:
+ from gnuradio import gr
+except ImportError:
+ from runtime_swig import gr_tag_t
+
+class PythonTag(object):
+ " Python container for tags "
+ def __init__(self):
+ self.offset = None
+ self.key = None
+ self.value = None
+ self.srcid = None
+
+def tag_to_python(tag):
+ """ Convert a stream tag to a Python-readable object """
+ newtag = PythonTag()
+ newtag.offset = tag.offset
+ newtag.key = pmt.to_python(tag.key)
+ newtag.value = pmt.to_python(tag.value)
+ newtag.srcid = pmt.to_python(tag.srcid)
+ return newtag
+
+def tag_to_pmt(tag):
+ """ Convert a Python-readable object to a stream tag """
+ newtag = gr_tag_t()
+ newtag.offset = tag.offset
+ newtag.key = pmt.to_python(tag.key)
+ newtag.value = pmt.from_python(tag.value)
+ newtag.srcid = pmt.from_python(tag.srcid)
+ return newtag
+
+
diff --git a/gnuradio-runtime/python/gnuradio/gr/top_block.py b/gnuradio-runtime/python/gnuradio/gr/top_block.py
new file mode 100644
index 0000000000..944e95e5ae
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gr/top_block.py
@@ -0,0 +1,170 @@
+#
+# Copyright 2007 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from runtime_swig import top_block_swig, \
+ top_block_wait_unlocked, top_block_run_unlocked
+
+#import gnuradio.gr.gr_threading as _threading
+import gr_threading as _threading
+
+#
+# There is no problem that can't be solved with an additional
+# level of indirection...
+#
+# This kludge allows ^C to interrupt top_block.run and top_block.wait
+#
+# The problem that we are working around is that Python only services
+# signals (e.g., KeyboardInterrupt) in its main thread. If the main
+# thread is blocked in our C++ version of wait, even though Python's
+# SIGINT handler fires, and even though there may be other python
+# threads running, no one will know. Thus instead of directly waiting
+# in the thread that calls wait (which is likely to be the Python main
+# thread), we create a separate thread that does the blocking wait,
+# and then use the thread that called wait to do a slow poll of an
+# event queue. That thread, which is executing "wait" below is
+# interruptable, and if it sees a KeyboardInterrupt, executes a stop
+# on the top_block, then goes back to waiting for it to complete.
+# This ensures that the unlocked wait that was in progress (in the
+# _top_block_waiter thread) can complete, release its mutex and back
+# out. If we don't do that, we are never able to clean up, and nasty
+# things occur like leaving the USRP transmitter sending a carrier.
+#
+# See also top_block.wait (below), which uses this class to implement
+# the interruptable wait.
+#
+class _top_block_waiter(_threading.Thread):
+ def __init__(self, tb):
+ _threading.Thread.__init__(self)
+ self.setDaemon(1)
+ self.tb = tb
+ self.event = _threading.Event()
+ self.start()
+
+ def run(self):
+ top_block_wait_unlocked(self.tb)
+ self.event.set()
+
+ def wait(self):
+ try:
+ while not self.event.isSet():
+ self.event.wait(0.100)
+ except KeyboardInterrupt:
+ self.tb.stop()
+ self.wait()
+
+
+#
+# This hack forces a 'has-a' relationship to look like an 'is-a' one.
+#
+# It allows Python classes to subclass this one, while passing through
+# method calls to the C++ class shared pointer from SWIG.
+#
+# It also allows us to intercept method calls if needed.
+#
+# This allows the 'run_locked' methods, which are defined in gr_top_block.i,
+# to release the Python global interpreter lock before calling the actual
+# method in gr_top_block
+#
+class top_block(object):
+ """
+ Top-level hierarchical block representing a flow-graph.
+
+ This is a python wrapper around the C++ implementation to allow
+ python subclassing.
+ """
+ def __init__(self, name="top_block"):
+ self._tb = top_block_swig(name)
+
+ def __getattr__(self, name):
+ if not hasattr(self, "_tb"):
+ raise RuntimeError("top_block: invalid state--did you forget to call gr.top_block.__init__ in a derived class?")
+ return getattr(self._tb, name)
+
+ def start(self, max_noutput_items=10000000):
+ self._tb.start(max_noutput_items)
+
+ def stop(self):
+ self._tb.stop()
+
+ def run(self, max_noutput_items=10000000):
+ self.start(max_noutput_items)
+ self.wait()
+
+ def wait(self):
+ _top_block_waiter(self._tb).wait()
+
+
+ # FIXME: these are duplicated from hier_block2.py; they should really be implemented
+ # in the original C++ class (gr_hier_block2), then they would all be inherited here
+
+ def connect(self, *points):
+ '''connect requires one or more arguments that can be coerced to endpoints.
+ If more than two arguments are provided, they are connected together successively.
+ '''
+ if len (points) < 1:
+ raise ValueError, ("connect requires at least one endpoint; %d provided." % (len (points),))
+ else:
+ if len(points) == 1:
+ self._tb.primitive_connect(points[0].to_basic_block())
+ else:
+ for i in range (1, len (points)):
+ self._connect(points[i-1], points[i])
+
+ def msg_connect(self, src, srcport, dst, dstport):
+ self.primitive_msg_connect(src.to_basic_block(), srcport, dst.to_basic_block(), dstport);
+
+ def msg_disconnect(self, src, srcport, dst, dstport):
+ self.primitive_msg_disconnect(src.to_basic_block(), srcport, dst.to_basic_block(), dstport);
+
+ def _connect(self, src, dst):
+ (src_block, src_port) = self._coerce_endpoint(src)
+ (dst_block, dst_port) = self._coerce_endpoint(dst)
+ self._tb.primitive_connect(src_block.to_basic_block(), src_port,
+ dst_block.to_basic_block(), dst_port)
+
+ def _coerce_endpoint(self, endp):
+ if hasattr(endp, 'to_basic_block'):
+ return (endp, 0)
+ else:
+ if hasattr(endp, "__getitem__") and len(endp) == 2:
+ return endp # Assume user put (block, port)
+ else:
+ raise ValueError("unable to coerce endpoint")
+
+ def disconnect(self, *points):
+ '''disconnect requires one or more arguments that can be coerced to endpoints.
+ If more than two arguments are provided, they are disconnected successively.
+ '''
+ if len (points) < 1:
+ raise ValueError, ("disconnect requires at least one endpoint; %d provided." % (len (points),))
+ else:
+ if len(points) == 1:
+ self._tb.primitive_disconnect(points[0].to_basic_block())
+ else:
+ for i in range (1, len (points)):
+ self._disconnect(points[i-1], points[i])
+
+ def _disconnect(self, src, dst):
+ (src_block, src_port) = self._coerce_endpoint(src)
+ (dst_block, dst_port) = self._coerce_endpoint(dst)
+ self._tb.primitive_disconnect(src_block.to_basic_block(), src_port,
+ dst_block.to_basic_block(), dst_port)
+
diff --git a/gnuradio-runtime/python/gnuradio/gr_unittest.py b/gnuradio-runtime/python/gnuradio/gr_unittest.py
new file mode 100755
index 0000000000..c729566e88
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gr_unittest.py
@@ -0,0 +1,170 @@
+#!/usr/bin/env python
+#
+# Copyright 2004,2010 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+"""
+GNU radio specific extension of unittest.
+"""
+
+import unittest
+import gr_xmlrunner
+import sys, os, stat
+
+class TestCase(unittest.TestCase):
+ """A subclass of unittest.TestCase that adds additional assertions
+
+ Adds new methods assertComplexAlmostEqual,
+ assertComplexTuplesAlmostEqual and assertFloatTuplesAlmostEqual
+ """
+
+ def assertComplexAlmostEqual (self, first, second, places=7, msg=None):
+ """Fail if the two complex objects are unequal as determined by their
+ difference rounded to the given number of decimal places
+ (default 7) and comparing to zero.
+
+ Note that decimal places (from zero) is usually not the same
+ as significant digits (measured from the most signficant digit).
+ """
+ if round(second.real-first.real, places) != 0:
+ raise self.failureException, \
+ (msg or '%s != %s within %s places' % (`first`, `second`, `places` ))
+ if round(second.imag-first.imag, places) != 0:
+ raise self.failureException, \
+ (msg or '%s != %s within %s places' % (`first`, `second`, `places` ))
+
+ def assertComplexAlmostEqual2 (self, ref, x, abs_eps=1e-12, rel_eps=1e-6, msg=None):
+ """
+ Fail if the two complex objects are unequal as determined by...
+ """
+ if abs(ref - x) < abs_eps:
+ return
+
+ if abs(ref) > abs_eps:
+ if abs(ref-x)/abs(ref) > rel_eps:
+ raise self.failureException, \
+ (msg or '%s != %s rel_error = %s rel_limit = %s' % (
+ `ref`, `x`, abs(ref-x)/abs(ref), `rel_eps` ))
+ else:
+ raise self.failureException, \
+ (msg or '%s != %s rel_error = %s rel_limit = %s' % (
+ `ref`, `x`, abs(ref-x)/abs(ref), `rel_eps` ))
+
+
+
+ def assertComplexTuplesAlmostEqual (self, a, b, places=7, msg=None):
+ self.assertEqual (len(a), len(b))
+ for i in xrange (len(a)):
+ self.assertComplexAlmostEqual (a[i], b[i], places, msg)
+
+ def assertComplexTuplesAlmostEqual2 (self, ref, x,
+ abs_eps=1e-12, rel_eps=1e-6, msg=None):
+ self.assertEqual (len(ref), len(x))
+ for i in xrange (len(ref)):
+ try:
+ self.assertComplexAlmostEqual2 (ref[i], x[i], abs_eps, rel_eps, msg)
+ except self.failureException, e:
+ #sys.stderr.write("index = %d " % (i,))
+ #sys.stderr.write("%s\n" % (e,))
+ raise
+
+ def assertFloatTuplesAlmostEqual (self, a, b, places=7, msg=None):
+ self.assertEqual (len(a), len(b))
+ for i in xrange (len(a)):
+ self.assertAlmostEqual (a[i], b[i], places, msg)
+
+
+ def assertFloatTuplesAlmostEqual2 (self, ref, x,
+ abs_eps=1e-12, rel_eps=1e-6, msg=None):
+ self.assertEqual (len(ref), len(x))
+ for i in xrange (len(ref)):
+ try:
+ self.assertComplexAlmostEqual2 (ref[i], x[i], abs_eps, rel_eps, msg)
+ except self.failureException, e:
+ #sys.stderr.write("index = %d " % (i,))
+ #sys.stderr.write("%s\n" % (e,))
+ raise
+
+
+TestResult = unittest.TestResult
+TestSuite = unittest.TestSuite
+FunctionTestCase = unittest.FunctionTestCase
+TestLoader = unittest.TestLoader
+TextTestRunner = unittest.TextTestRunner
+TestProgram = unittest.TestProgram
+main = TestProgram
+
+def run(PUT, filename=None):
+ '''
+ Runs the unittest on a TestCase and produces an optional XML report
+ PUT: the program under test and should be a gr_unittest.TestCase
+ filename: an optional filename to save the XML report of the tests
+ this will live in ./.unittests/python
+ '''
+
+ # Run this is given a file name
+ if(filename is not None):
+ basepath = "./.unittests"
+ path = basepath + "/python"
+
+ if not os.path.exists(basepath):
+ os.makedirs(basepath, 0750)
+
+ xmlrunner = None
+ # only proceed if .unittests is writable
+ st = os.stat(basepath)[stat.ST_MODE]
+ if(st & stat.S_IWUSR > 0):
+ # Test if path exists; if not, build it
+ if not os.path.exists(path):
+ os.makedirs(path, 0750)
+
+ # Just for safety: make sure we can write here, too
+ st = os.stat(path)[stat.ST_MODE]
+ if(st & stat.S_IWUSR > 0):
+ # Create an XML runner to filename
+ fout = file(path+"/"+filename, "w")
+ xmlrunner = gr_xmlrunner.XMLTestRunner(fout)
+
+ txtrunner = TextTestRunner(verbosity=1)
+
+ # Run the test; runner also creates XML output file
+ # FIXME: make xmlrunner output to screen so we don't have to do run and main
+ suite = TestLoader().loadTestsFromTestCase(PUT)
+
+ # use the xmlrunner if we can write the the directory
+ if(xmlrunner is not None):
+ xmlrunner.run(suite)
+
+ main()
+
+ # This will run and fail make check if problem
+ # but does not output to screen.
+ #main(testRunner = xmlrunner)
+
+ else:
+ # If no filename is given, just run the test
+ main()
+
+
+##############################################################################
+# Executing this module from the command line
+##############################################################################
+
+if __name__ == "__main__":
+ main(module=None)
diff --git a/gnuradio-runtime/python/gnuradio/gr_xmlrunner.py b/gnuradio-runtime/python/gnuradio/gr_xmlrunner.py
new file mode 100644
index 0000000000..31298197ff
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gr_xmlrunner.py
@@ -0,0 +1,387 @@
+"""
+XML Test Runner for PyUnit
+"""
+
+# Written by Sebastian Rittau <srittau@jroger.in-berlin.de> and placed in
+# the Public Domain. With contributions by Paolo Borelli and others.
+# Added to GNU Radio Oct. 3, 2010
+
+__version__ = "0.1"
+
+import os.path
+import re
+import sys
+import time
+import traceback
+import unittest
+from xml.sax.saxutils import escape
+
+try:
+ from StringIO import StringIO
+except ImportError:
+ from io import StringIO
+
+
+class _TestInfo(object):
+
+ """Information about a particular test.
+
+ Used by _XMLTestResult.
+
+ """
+
+ def __init__(self, test, time):
+ (self._class, self._method) = test.id().rsplit(".", 1)
+ self._time = time
+ self._error = None
+ self._failure = None
+
+ @staticmethod
+ def create_success(test, time):
+ """Create a _TestInfo instance for a successful test."""
+ return _TestInfo(test, time)
+
+ @staticmethod
+ def create_failure(test, time, failure):
+ """Create a _TestInfo instance for a failed test."""
+ info = _TestInfo(test, time)
+ info._failure = failure
+ return info
+
+ @staticmethod
+ def create_error(test, time, error):
+ """Create a _TestInfo instance for an erroneous test."""
+ info = _TestInfo(test, time)
+ info._error = error
+ return info
+
+ def print_report(self, stream):
+ """Print information about this test case in XML format to the
+ supplied stream.
+
+ """
+ stream.write(' <testcase classname="%(class)s" name="%(method)s" time="%(time).4f">' % \
+ {
+ "class": self._class,
+ "method": self._method,
+ "time": self._time,
+ })
+ if self._failure is not None:
+ self._print_error(stream, 'failure', self._failure)
+ if self._error is not None:
+ self._print_error(stream, 'error', self._error)
+ stream.write('</testcase>\n')
+
+ def _print_error(self, stream, tagname, error):
+ """Print information from a failure or error to the supplied stream."""
+ text = escape(str(error[1]))
+ stream.write('\n')
+ stream.write(' <%s type="%s">%s\n' \
+ % (tagname, _clsname(error[0]), text))
+ tb_stream = StringIO()
+ traceback.print_tb(error[2], None, tb_stream)
+ stream.write(escape(tb_stream.getvalue()))
+ stream.write(' </%s>\n' % tagname)
+ stream.write(' ')
+
+
+def _clsname(cls):
+ return cls.__module__ + "." + cls.__name__
+
+
+class _XMLTestResult(unittest.TestResult):
+
+ """A test result class that stores result as XML.
+
+ Used by XMLTestRunner.
+
+ """
+
+ def __init__(self, classname):
+ unittest.TestResult.__init__(self)
+ self._test_name = classname
+ self._start_time = None
+ self._tests = []
+ self._error = None
+ self._failure = None
+
+ def startTest(self, test):
+ unittest.TestResult.startTest(self, test)
+ self._error = None
+ self._failure = None
+ self._start_time = time.time()
+
+ def stopTest(self, test):
+ time_taken = time.time() - self._start_time
+ unittest.TestResult.stopTest(self, test)
+ if self._error:
+ info = _TestInfo.create_error(test, time_taken, self._error)
+ elif self._failure:
+ info = _TestInfo.create_failure(test, time_taken, self._failure)
+ else:
+ info = _TestInfo.create_success(test, time_taken)
+ self._tests.append(info)
+
+ def addError(self, test, err):
+ unittest.TestResult.addError(self, test, err)
+ self._error = err
+
+ def addFailure(self, test, err):
+ unittest.TestResult.addFailure(self, test, err)
+ self._failure = err
+
+ def print_report(self, stream, time_taken, out, err):
+ """Prints the XML report to the supplied stream.
+
+ The time the tests took to perform as well as the captured standard
+ output and standard error streams must be passed in.a
+
+ """
+ stream.write('<testsuite errors="%(e)d" failures="%(f)d" ' % \
+ { "e": len(self.errors), "f": len(self.failures) })
+ stream.write('name="%(n)s" tests="%(t)d" time="%(time).3f">\n' % \
+ {
+ "n": self._test_name,
+ "t": self.testsRun,
+ "time": time_taken,
+ })
+ for info in self._tests:
+ info.print_report(stream)
+ stream.write(' <system-out><![CDATA[%s]]></system-out>\n' % out)
+ stream.write(' <system-err><![CDATA[%s]]></system-err>\n' % err)
+ stream.write('</testsuite>\n')
+
+
+class XMLTestRunner(object):
+
+ """A test runner that stores results in XML format compatible with JUnit.
+
+ XMLTestRunner(stream=None) -> XML test runner
+
+ The XML file is written to the supplied stream. If stream is None, the
+ results are stored in a file called TEST-<module>.<class>.xml in the
+ current working directory (if not overridden with the path property),
+ where <module> and <class> are the module and class name of the test class.
+
+ """
+
+ def __init__(self, stream=None):
+ self._stream = stream
+ self._path = "."
+
+ def run(self, test):
+ """Run the given test case or test suite."""
+ class_ = test.__class__
+ classname = class_.__module__ + "." + class_.__name__
+ if self._stream == None:
+ filename = "TEST-%s.xml" % classname
+ stream = file(os.path.join(self._path, filename), "w")
+ stream.write('<?xml version="1.0" encoding="utf-8"?>\n')
+ else:
+ stream = self._stream
+
+ result = _XMLTestResult(classname)
+ start_time = time.time()
+
+ fss = _fake_std_streams()
+ fss.__enter__()
+ try:
+ test(result)
+ try:
+ out_s = sys.stdout.getvalue()
+ except AttributeError:
+ out_s = ""
+ try:
+ err_s = sys.stderr.getvalue()
+ except AttributeError:
+ err_s = ""
+ finally:
+ fss.__exit__(None, None, None)
+
+ time_taken = time.time() - start_time
+ result.print_report(stream, time_taken, out_s, err_s)
+ if self._stream is None:
+ stream.close()
+
+ return result
+
+ def _set_path(self, path):
+ self._path = path
+
+ path = property(lambda self: self._path, _set_path, None,
+ """The path where the XML files are stored.
+
+ This property is ignored when the XML file is written to a file
+ stream.""")
+
+
+class _fake_std_streams(object):
+
+ def __enter__(self):
+ self._orig_stdout = sys.stdout
+ self._orig_stderr = sys.stderr
+ #sys.stdout = StringIO()
+ #sys.stderr = StringIO()
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ sys.stdout = self._orig_stdout
+ sys.stderr = self._orig_stderr
+
+
+class XMLTestRunnerTest(unittest.TestCase):
+
+ def setUp(self):
+ self._stream = StringIO()
+
+ def _try_test_run(self, test_class, expected):
+
+ """Run the test suite against the supplied test class and compare the
+ XML result against the expected XML string. Fail if the expected
+ string doesn't match the actual string. All time attributes in the
+ expected string should have the value "0.000". All error and failure
+ messages are reduced to "Foobar".
+
+ """
+
+ runner = XMLTestRunner(self._stream)
+ runner.run(unittest.makeSuite(test_class))
+
+ got = self._stream.getvalue()
+ # Replace all time="X.YYY" attributes by time="0.000" to enable a
+ # simple string comparison.
+ got = re.sub(r'time="\d+\.\d+"', 'time="0.000"', got)
+ # Likewise, replace all failure and error messages by a simple "Foobar"
+ # string.
+ got = re.sub(r'(?s)<failure (.*?)>.*?</failure>', r'<failure \1>Foobar</failure>', got)
+ got = re.sub(r'(?s)<error (.*?)>.*?</error>', r'<error \1>Foobar</error>', got)
+ # And finally Python 3 compatibility.
+ got = got.replace('type="builtins.', 'type="exceptions.')
+
+ self.assertEqual(expected, got)
+
+ def test_no_tests(self):
+ """Regression test: Check whether a test run without any tests
+ matches a previous run.
+
+ """
+ class TestTest(unittest.TestCase):
+ pass
+ self._try_test_run(TestTest, """<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="0" time="0.000">
+ <system-out><![CDATA[]]></system-out>
+ <system-err><![CDATA[]]></system-err>
+</testsuite>
+""")
+
+ def test_success(self):
+ """Regression test: Check whether a test run with a successful test
+ matches a previous run.
+
+ """
+ class TestTest(unittest.TestCase):
+ def test_foo(self):
+ pass
+ self._try_test_run(TestTest, """<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="1" time="0.000">
+ <testcase classname="__main__.TestTest" name="test_foo" time="0.000"></testcase>
+ <system-out><![CDATA[]]></system-out>
+ <system-err><![CDATA[]]></system-err>
+</testsuite>
+""")
+
+ def test_failure(self):
+ """Regression test: Check whether a test run with a failing test
+ matches a previous run.
+
+ """
+ class TestTest(unittest.TestCase):
+ def test_foo(self):
+ self.assert_(False)
+ self._try_test_run(TestTest, """<testsuite errors="0" failures="1" name="unittest.TestSuite" tests="1" time="0.000">
+ <testcase classname="__main__.TestTest" name="test_foo" time="0.000">
+ <failure type="exceptions.AssertionError">Foobar</failure>
+ </testcase>
+ <system-out><![CDATA[]]></system-out>
+ <system-err><![CDATA[]]></system-err>
+</testsuite>
+""")
+
+ def test_error(self):
+ """Regression test: Check whether a test run with a erroneous test
+ matches a previous run.
+
+ """
+ class TestTest(unittest.TestCase):
+ def test_foo(self):
+ raise IndexError()
+ self._try_test_run(TestTest, """<testsuite errors="1" failures="0" name="unittest.TestSuite" tests="1" time="0.000">
+ <testcase classname="__main__.TestTest" name="test_foo" time="0.000">
+ <error type="exceptions.IndexError">Foobar</error>
+ </testcase>
+ <system-out><![CDATA[]]></system-out>
+ <system-err><![CDATA[]]></system-err>
+</testsuite>
+""")
+
+ def test_stdout_capture(self):
+ """Regression test: Check whether a test run with output to stdout
+ matches a previous run.
+
+ """
+ class TestTest(unittest.TestCase):
+ def test_foo(self):
+ sys.stdout.write("Test\n")
+ self._try_test_run(TestTest, """<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="1" time="0.000">
+ <testcase classname="__main__.TestTest" name="test_foo" time="0.000"></testcase>
+ <system-out><![CDATA[Test
+]]></system-out>
+ <system-err><![CDATA[]]></system-err>
+</testsuite>
+""")
+
+ def test_stderr_capture(self):
+ """Regression test: Check whether a test run with output to stderr
+ matches a previous run.
+
+ """
+ class TestTest(unittest.TestCase):
+ def test_foo(self):
+ sys.stderr.write("Test\n")
+ self._try_test_run(TestTest, """<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="1" time="0.000">
+ <testcase classname="__main__.TestTest" name="test_foo" time="0.000"></testcase>
+ <system-out><![CDATA[]]></system-out>
+ <system-err><![CDATA[Test
+]]></system-err>
+</testsuite>
+""")
+
+ class NullStream(object):
+ """A file-like object that discards everything written to it."""
+ def write(self, buffer):
+ pass
+
+ def test_unittests_changing_stdout(self):
+ """Check whether the XMLTestRunner recovers gracefully from unit tests
+ that change stdout, but don't change it back properly.
+
+ """
+ class TestTest(unittest.TestCase):
+ def test_foo(self):
+ sys.stdout = XMLTestRunnerTest.NullStream()
+
+ runner = XMLTestRunner(self._stream)
+ runner.run(unittest.makeSuite(TestTest))
+
+ def test_unittests_changing_stderr(self):
+ """Check whether the XMLTestRunner recovers gracefully from unit tests
+ that change stderr, but don't change it back properly.
+
+ """
+ class TestTest(unittest.TestCase):
+ def test_foo(self):
+ sys.stderr = XMLTestRunnerTest.NullStream()
+
+ runner = XMLTestRunner(self._stream)
+ runner.run(unittest.makeSuite(TestTest))
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/gnuradio-runtime/python/gnuradio/gru/CMakeLists.txt b/gnuradio-runtime/python/gnuradio/gru/CMakeLists.txt
new file mode 100644
index 0000000000..c147981472
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gru/CMakeLists.txt
@@ -0,0 +1,36 @@
+# Copyright 2010-2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+
+include(GrPython)
+
+GR_PYTHON_INSTALL(FILES
+ __init__.py
+ freqz.py
+ gnuplot_freqz.py
+ hexint.py
+ listmisc.py
+ mathmisc.py
+ msgq_runner.py
+ os_read_exactly.py
+ seq_with_cursor.py
+ socket_stuff.py
+ daemon.py
+ DESTINATION ${GR_PYTHON_DIR}/gnuradio/gru
+ COMPONENT "core_python"
+)
diff --git a/gnuradio-runtime/python/gnuradio/gru/__init__.py b/gnuradio-runtime/python/gnuradio/gru/__init__.py
new file mode 100644
index 0000000000..4e41d03a74
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gru/__init__.py
@@ -0,0 +1,13 @@
+# make this a package
+
+# Import gru stuff
+from daemon import *
+from freqz import *
+from gnuplot_freqz import *
+from hexint import *
+from listmisc import *
+from mathmisc import *
+from msgq_runner import *
+from os_read_exactly import *
+from seq_with_cursor import *
+from socket_stuff import *
diff --git a/gnuradio-runtime/python/gnuradio/gru/daemon.py b/gnuradio-runtime/python/gnuradio/gru/daemon.py
new file mode 100644
index 0000000000..e04702152d
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gru/daemon.py
@@ -0,0 +1,102 @@
+#
+# Copyright 2008 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+import os, sys, signal
+
+# Turn application into a background daemon process.
+#
+# When this function returns:
+#
+# 1) The calling process is disconnected from its controlling terminal
+# and will not exit when the controlling session exits
+# 2) If a pidfile name is provided, it is created and the new pid is
+# written into it.
+# 3) If a logfile name is provided, it is opened and stdout/stderr are
+# redirected to it.
+# 4) The process current working directory is changed to '/' to avoid
+# pinning any filesystem mounts.
+# 5) The process umask is set to 0111.
+#
+# The return value is the new pid.
+#
+# To create GNU Radio applications that operate as daemons, add a call to this
+# function after all initialization but just before calling gr.top_block.run()
+# or .start().
+#
+# Daemonized GNU Radio applications may be stopped by sending them a
+# SIGINT, SIGKILL, or SIGTERM, e.g., using 'kill pid' from the command line.
+#
+# If your application uses gr.top_block.run(), the flowgraph will be stopped
+# and the function will return. You should allow your daemon program to exit
+# at this point.
+#
+# If your application uses gr.top_block.start(), you are responsible for hooking
+# the Python signal handler (see 'signal' module) and calling gr.top_block.stop()
+# on your top block, and otherwise causing your daemon process to exit.
+#
+
+def daemonize(pidfile=None, logfile=None):
+ # fork() into background
+ try:
+ pid = os.fork()
+ except OSError, e:
+ raise Exception, "%s [%d]" % (e.strerror, e.errno)
+
+ if pid == 0: # First child of first fork()
+ # Become session leader of new session
+ os.setsid()
+
+ # fork() into background again
+ try:
+ pid = os.fork()
+ except OSError, e:
+ raise Exception, "%s [%d]" % (e.strerror, e.errno)
+
+ if pid != 0:
+ os._exit(0) # Second child of second fork()
+
+ else: # Second child of first fork()
+ os._exit(0)
+
+ os.umask(0111)
+
+ # Write pid
+ pid = os.getpid()
+ if pidfile is not None:
+ open(pidfile, 'w').write('%d\n'%pid)
+
+ # Redirect streams
+ if logfile is not None:
+ lf = open(logfile, 'a+')
+ sys.stdout = lf
+ sys.stderr = lf
+
+ # Prevent pinning any filesystem mounts
+ os.chdir('/')
+
+ # Tell caller what pid to send future signals to
+ return pid
+
+if __name__ == "__main__":
+ import time
+ daemonize()
+ print "Hello, world, from daemon process."
+ time.sleep(20)
+ print "Goodbye, world, from daemon process."
diff --git a/gnuradio-runtime/python/gnuradio/gru/freqz.py b/gnuradio-runtime/python/gnuradio/gru/freqz.py
new file mode 100644
index 0000000000..60dca64a58
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gru/freqz.py
@@ -0,0 +1,344 @@
+#!/usr/bin/env python
+#
+# Copyright 2005,2007 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+# This code lifted from various parts of www.scipy.org -eb 2005-01-24
+
+# Copyright (c) 2001, 2002 Enthought, Inc.
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# a. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# b. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# c. Neither the name of the Enthought nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+# DAMAGE.
+#
+
+__all__ = ['freqz']
+
+import numpy
+from numpy import *
+Num=numpy
+
+def atleast_1d(*arys):
+ """ Force a sequence of arrays to each be at least 1D.
+
+ Description:
+ Force an array to be at least 1D. If an array is 0D, the
+ array is converted to a single row of values. Otherwise,
+ the array is unaltered.
+ Arguments:
+ *arys -- arrays to be converted to 1 or more dimensional array.
+ Returns:
+ input array converted to at least 1D array.
+ """
+ res = []
+ for ary in arys:
+ ary = asarray(ary)
+ if len(ary.shape) == 0:
+ result = numpy.array([ary[0]])
+ else:
+ result = ary
+ res.append(result)
+ if len(res) == 1:
+ return res[0]
+ else:
+ return res
+
+
+def polyval(p,x):
+ """Evaluate the polynomial p at x. If x is a polynomial then composition.
+
+ Description:
+
+ If p is of length N, this function returns the value:
+ p[0]*(x**N-1) + p[1]*(x**N-2) + ... + p[N-2]*x + p[N-1]
+
+ x can be a sequence and p(x) will be returned for all elements of x.
+ or x can be another polynomial and the composite polynomial p(x) will be
+ returned.
+ """
+ p = asarray(p)
+ if isinstance(x,poly1d):
+ y = 0
+ else:
+ x = asarray(x)
+ y = numpy.zeros(x.shape,x.typecode())
+ for i in range(len(p)):
+ y = x * y + p[i]
+ return y
+
+class poly1d:
+ """A one-dimensional polynomial class.
+
+ p = poly1d([1,2,3]) constructs the polynomial x**2 + 2 x + 3
+
+ p(0.5) evaluates the polynomial at the location
+ p.r is a list of roots
+ p.c is the coefficient array [1,2,3]
+ p.order is the polynomial order (after leading zeros in p.c are removed)
+ p[k] is the coefficient on the kth power of x (backwards from
+ sequencing the coefficient array.
+
+ polynomials can be added, substracted, multplied and divided (returns
+ quotient and remainder).
+ asarray(p) will also give the coefficient array, so polynomials can
+ be used in all functions that accept arrays.
+ """
+ def __init__(self, c_or_r, r=0):
+ if isinstance(c_or_r,poly1d):
+ for key in c_or_r.__dict__.keys():
+ self.__dict__[key] = c_or_r.__dict__[key]
+ return
+ if r:
+ c_or_r = poly(c_or_r)
+ c_or_r = atleast_1d(c_or_r)
+ if len(c_or_r.shape) > 1:
+ raise ValueError, "Polynomial must be 1d only."
+ c_or_r = trim_zeros(c_or_r, trim='f')
+ if len(c_or_r) == 0:
+ c_or_r = numpy.array([0])
+ self.__dict__['coeffs'] = c_or_r
+ self.__dict__['order'] = len(c_or_r) - 1
+
+ def __array__(self,t=None):
+ if t:
+ return asarray(self.coeffs,t)
+ else:
+ return asarray(self.coeffs)
+
+ def __coerce__(self,other):
+ return None
+
+ def __repr__(self):
+ vals = repr(self.coeffs)
+ vals = vals[6:-1]
+ return "poly1d(%s)" % vals
+
+ def __len__(self):
+ return self.order
+
+ def __str__(self):
+ N = self.order
+ thestr = "0"
+ for k in range(len(self.coeffs)):
+ coefstr ='%.4g' % abs(self.coeffs[k])
+ if coefstr[-4:] == '0000':
+ coefstr = coefstr[:-5]
+ power = (N-k)
+ if power == 0:
+ if coefstr != '0':
+ newstr = '%s' % (coefstr,)
+ else:
+ if k == 0:
+ newstr = '0'
+ else:
+ newstr = ''
+ elif power == 1:
+ if coefstr == '0':
+ newstr = ''
+ elif coefstr == '1':
+ newstr = 'x'
+ else:
+ newstr = '%s x' % (coefstr,)
+ else:
+ if coefstr == '0':
+ newstr = ''
+ elif coefstr == '1':
+ newstr = 'x**%d' % (power,)
+ else:
+ newstr = '%s x**%d' % (coefstr, power)
+
+ if k > 0:
+ if newstr != '':
+ if self.coeffs[k] < 0:
+ thestr = "%s - %s" % (thestr, newstr)
+ else:
+ thestr = "%s + %s" % (thestr, newstr)
+ elif (k == 0) and (newstr != '') and (self.coeffs[k] < 0):
+ thestr = "-%s" % (newstr,)
+ else:
+ thestr = newstr
+ return _raise_power(thestr)
+
+
+ def __call__(self, val):
+ return polyval(self.coeffs, val)
+
+ def __mul__(self, other):
+ if isscalar(other):
+ return poly1d(self.coeffs * other)
+ else:
+ other = poly1d(other)
+ return poly1d(polymul(self.coeffs, other.coeffs))
+
+ def __rmul__(self, other):
+ if isscalar(other):
+ return poly1d(other * self.coeffs)
+ else:
+ other = poly1d(other)
+ return poly1d(polymul(self.coeffs, other.coeffs))
+
+ def __add__(self, other):
+ other = poly1d(other)
+ return poly1d(polyadd(self.coeffs, other.coeffs))
+
+ def __radd__(self, other):
+ other = poly1d(other)
+ return poly1d(polyadd(self.coeffs, other.coeffs))
+
+ def __pow__(self, val):
+ if not isscalar(val) or int(val) != val or val < 0:
+ raise ValueError, "Power to non-negative integers only."
+ res = [1]
+ for k in range(val):
+ res = polymul(self.coeffs, res)
+ return poly1d(res)
+
+ def __sub__(self, other):
+ other = poly1d(other)
+ return poly1d(polysub(self.coeffs, other.coeffs))
+
+ def __rsub__(self, other):
+ other = poly1d(other)
+ return poly1d(polysub(other.coeffs, self.coeffs))
+
+ def __div__(self, other):
+ if isscalar(other):
+ return poly1d(self.coeffs/other)
+ else:
+ other = poly1d(other)
+ return map(poly1d,polydiv(self.coeffs, other.coeffs))
+
+ def __rdiv__(self, other):
+ if isscalar(other):
+ return poly1d(other/self.coeffs)
+ else:
+ other = poly1d(other)
+ return map(poly1d,polydiv(other.coeffs, self.coeffs))
+
+ def __setattr__(self, key, val):
+ raise ValueError, "Attributes cannot be changed this way."
+
+ def __getattr__(self, key):
+ if key in ['r','roots']:
+ return roots(self.coeffs)
+ elif key in ['c','coef','coefficients']:
+ return self.coeffs
+ elif key in ['o']:
+ return self.order
+ else:
+ return self.__dict__[key]
+
+ def __getitem__(self, val):
+ ind = self.order - val
+ if val > self.order:
+ return 0
+ if val < 0:
+ return 0
+ return self.coeffs[ind]
+
+ def __setitem__(self, key, val):
+ ind = self.order - key
+ if key < 0:
+ raise ValueError, "Does not support negative powers."
+ if key > self.order:
+ zr = numpy.zeros(key-self.order,self.coeffs.typecode())
+ self.__dict__['coeffs'] = numpy.concatenate((zr,self.coeffs))
+ self.__dict__['order'] = key
+ ind = 0
+ self.__dict__['coeffs'][ind] = val
+ return
+
+ def integ(self, m=1, k=0):
+ return poly1d(polyint(self.coeffs,m=m,k=k))
+
+ def deriv(self, m=1):
+ return poly1d(polyder(self.coeffs,m=m))
+
+def freqz(b, a, worN=None, whole=0, plot=None):
+ """Compute frequency response of a digital filter.
+
+ Description:
+
+ Given the numerator (b) and denominator (a) of a digital filter compute
+ its frequency response.
+
+ jw -jw -jmw
+ jw B(e) b[0] + b[1]e + .... + b[m]e
+ H(e) = ---- = ------------------------------------
+ jw -jw -jnw
+ A(e) a[0] + a[2]e + .... + a[n]e
+
+ Inputs:
+
+ b, a --- the numerator and denominator of a linear filter.
+ worN --- If None, then compute at 512 frequencies around the unit circle.
+ If a single integer, the compute at that many frequencies.
+ Otherwise, compute the response at frequencies given in worN
+ whole -- Normally, frequencies are computed from 0 to pi (upper-half of
+ unit-circle. If whole is non-zero compute frequencies from 0
+ to 2*pi.
+
+ Outputs: (h,w)
+
+ h -- The frequency response.
+ w -- The frequencies at which h was computed.
+ """
+ b, a = map(atleast_1d, (b,a))
+ if whole:
+ lastpoint = 2*pi
+ else:
+ lastpoint = pi
+ if worN is None:
+ N = 512
+ w = Num.arange(0,lastpoint,lastpoint/N)
+ elif isinstance(worN, types.IntType):
+ N = worN
+ w = Num.arange(0,lastpoint,lastpoint/N)
+ else:
+ w = worN
+ w = atleast_1d(w)
+ zm1 = exp(-1j*w)
+ h = polyval(b[::-1], zm1) / polyval(a[::-1], zm1)
+ # if not plot is None:
+ # plot(w, h)
+ return h, w
diff --git a/gnuradio-runtime/python/gnuradio/gru/gnuplot_freqz.py b/gnuradio-runtime/python/gnuradio/gru/gnuplot_freqz.py
new file mode 100755
index 0000000000..dd483e4277
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gru/gnuplot_freqz.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+#
+# Copyright 2005,2007 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+__all__ = ['gnuplot_freqz']
+
+import tempfile
+import os
+import math
+import numpy
+
+from gnuradio import gr
+from gnuradio.gru.freqz import freqz
+
+
+def gnuplot_freqz (hw, Fs=None, logfreq=False):
+
+ """hw is a tuple of the form (h, w) where h is sequence of complex
+ freq responses, and w is a sequence of corresponding frequency
+ points. Plot the frequency response using gnuplot. If Fs is
+ provide, use it as the sampling frequency, else use 2*pi.
+
+ Returns a handle to the gnuplot graph. When the handle is reclaimed
+ the graph is torn down."""
+
+ data_file = tempfile.NamedTemporaryFile ()
+ cmd_file = os.popen ('gnuplot', 'w')
+
+ h, w = hw
+ ampl = 20 * numpy.log10 (numpy.absolute (h) + 1e-9)
+ phase = map (lambda x: math.atan2 (x.imag, x.real), h)
+
+ if Fs:
+ w *= (Fs/(2*math.pi))
+
+ for freq, a, ph in zip (w, ampl, phase):
+ data_file.write ("%g\t%g\t%g\n" % (freq, a, ph))
+
+ data_file.flush ()
+
+ cmd_file.write ("set grid\n")
+ if logfreq:
+ cmd_file.write ("set logscale x\n")
+ else:
+ cmd_file.write ("unset logscale x\n")
+ cmd_file.write ("plot '%s' using 1:2 with lines\n" % (data_file.name,))
+ cmd_file.flush ()
+
+ return (cmd_file, data_file)
+
+
+def test_plot ():
+ sample_rate = 2.0e6
+ #taps = firdes.low_pass(1, sample_rate, 200000, 100000, firdes.WIN_HAMMING)
+ taps = (0.0007329441141337156, 0.0007755281985737383, 0.0005323155201040208,
+ -7.679847761841656e-19, -0.0007277769618667662, -0.001415981911122799,
+ -0.0017135187517851591, -0.001282231998629868, 1.61239866282397e-18,
+ 0.0018589380197227001, 0.0035909228026866913, 0.004260237794369459,
+ 0.00310456077568233, -3.0331308923229716e-18, -0.004244099836796522,
+ -0.007970594801008701, -0.009214458055794239, -0.006562007591128349,
+ 4.714311174044374e-18, 0.008654761128127575, 0.01605774275958538,
+ 0.01841980405151844, 0.013079923577606678, -6.2821650235090215e-18,
+ -0.017465557903051376, -0.032989680767059326, -0.03894065320491791,
+ -0.028868533670902252, 7.388111706347014e-18, 0.04517475143074989,
+ 0.09890196472406387, 0.14991308748722076, 0.18646684288978577,
+ 0.19974154233932495, 0.18646684288978577, 0.14991308748722076,
+ 0.09890196472406387, 0.04517475143074989, 7.388111706347014e-18,
+ -0.028868533670902252, -0.03894065320491791, -0.032989680767059326,
+ -0.017465557903051376, -6.2821650235090215e-18, 0.013079923577606678,
+ 0.01841980405151844, 0.01605774275958538, 0.008654761128127575,
+ 4.714311174044374e-18, -0.006562007591128349, -0.009214458055794239,
+ -0.007970594801008701, -0.004244099836796522, -3.0331308923229716e-18,
+ 0.00310456077568233, 0.004260237794369459, 0.0035909228026866913,
+ 0.0018589380197227001, 1.61239866282397e-18, -0.001282231998629868,
+ -0.0017135187517851591, -0.001415981911122799, -0.0007277769618667662,
+ -7.679847761841656e-19, 0.0005323155201040208, 0.0007755281985737383,
+ 0.0007329441141337156)
+
+ # print len (taps)
+ return gnuplot_freqz (freqz (taps, 1), sample_rate)
+
+if __name__ == '__main__':
+ handle = test_plot ()
+ raw_input ('Press Enter to continue: ')
diff --git a/gnuradio-runtime/python/gnuradio/gru/hexint.py b/gnuradio-runtime/python/gnuradio/gru/hexint.py
new file mode 100644
index 0000000000..0fb5ecde04
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gru/hexint.py
@@ -0,0 +1,44 @@
+#
+# Copyright 2005 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+def hexint(mask):
+ """
+ Convert unsigned masks into signed ints.
+
+ This allows us to use hex constants like 0xf0f0f0f2 when talking to
+ our hardware and not get screwed by them getting treated as python
+ longs.
+ """
+ if mask >= 2**31:
+ return int(mask-2**32)
+ return mask
+
+def hexshort(mask):
+ """
+ Convert unsigned masks into signed shorts.
+
+ This allows us to use hex constants like 0x8000 when talking to
+ our hardware and not get screwed by them getting treated as python
+ longs.
+ """
+ if mask >= 2**15:
+ return int(mask-2**16)
+ return mask
diff --git a/gnuradio-runtime/python/gnuradio/gru/listmisc.py b/gnuradio-runtime/python/gnuradio/gru/listmisc.py
new file mode 100644
index 0000000000..9e70eb863c
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gru/listmisc.py
@@ -0,0 +1,29 @@
+#
+# Copyright 2005 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+def list_reverse(x):
+ """
+ Return a copy of x that is reverse order.
+ """
+ r = list(x)
+ r.reverse()
+ return r
+
diff --git a/gnuradio-runtime/python/gnuradio/gru/mathmisc.py b/gnuradio-runtime/python/gnuradio/gru/mathmisc.py
new file mode 100644
index 0000000000..7e6f23a346
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gru/mathmisc.py
@@ -0,0 +1,33 @@
+#
+# Copyright 2005 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+import math
+
+def gcd(a,b):
+ while b:
+ a,b = b, a % b
+ return a
+
+def lcm(a,b):
+ return a * b / gcd(a, b)
+
+def log2(x):
+ return math.log(x)/math.log(2)
diff --git a/gnuradio-runtime/python/gnuradio/gru/msgq_runner.py b/gnuradio-runtime/python/gnuradio/gru/msgq_runner.py
new file mode 100644
index 0000000000..767a74a717
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gru/msgq_runner.py
@@ -0,0 +1,82 @@
+#
+# Copyright 2009 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+"""
+Convenience class for dequeuing messages from a gr.msg_queue and
+invoking a callback.
+
+Creates a Python thread that does a blocking read on the supplied
+gr.msg_queue, then invokes callback each time a msg is received.
+
+If the msg type is not 0, then it is treated as a signal to exit
+its loop.
+
+If the callback raises an exception, and the runner was created
+with 'exit_on_error' equal to True, then the runner will store the
+exception and exit its loop, otherwise the exception is ignored.
+
+To get the exception that the callback raised, if any, call
+exit_error() on the object.
+
+To manually stop the runner, call stop() on the object.
+
+To determine if the runner has exited, call exited() on the object.
+"""
+
+from gnuradio import gr
+import gnuradio.gr.gr_threading as _threading
+
+class msgq_runner(_threading.Thread):
+
+ def __init__(self, msgq, callback, exit_on_error=False):
+ _threading.Thread.__init__(self)
+
+ self._msgq = msgq
+ self._callback = callback
+ self._exit_on_error = exit_on_error
+ self._done = False
+ self._exited = False
+ self._exit_error = None
+ self.setDaemon(1)
+ self.start()
+
+ def run(self):
+ while not self._done:
+ msg = self._msgq.delete_head()
+ if msg.type() != 0:
+ self.stop()
+ else:
+ try:
+ self._callback(msg)
+ except Exception, e:
+ if self._exit_on_error:
+ self._exit_error = e
+ self.stop()
+ self._exited = True
+
+ def stop(self):
+ self._done = True
+
+ def exited(self):
+ return self._exited
+
+ def exit_error(self):
+ return self._exit_error
diff --git a/gnuradio-runtime/python/gnuradio/gru/os_read_exactly.py b/gnuradio-runtime/python/gnuradio/gru/os_read_exactly.py
new file mode 100644
index 0000000000..40b053770e
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gru/os_read_exactly.py
@@ -0,0 +1,36 @@
+#
+# Copyright 2005 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+import os
+
+def os_read_exactly(file_descriptor, nbytes):
+ """
+ Replacement for os.read that blocks until it reads exactly nbytes.
+
+ """
+ s = ''
+ while nbytes > 0:
+ sbuf = os.read(file_descriptor, nbytes)
+ if not(sbuf):
+ return ''
+ nbytes -= len(sbuf)
+ s = s + sbuf
+ return s
diff --git a/gnuradio-runtime/python/gnuradio/gru/seq_with_cursor.py b/gnuradio-runtime/python/gnuradio/gru/seq_with_cursor.py
new file mode 100644
index 0000000000..def3299b69
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gru/seq_with_cursor.py
@@ -0,0 +1,77 @@
+#
+# Copyright 2003,2004 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+# misc utilities
+
+import types
+import exceptions
+
+class seq_with_cursor (object):
+ __slots__ = [ 'items', 'index' ]
+
+ def __init__ (self, items, initial_index = None, initial_value = None):
+ assert len (items) > 0, "seq_with_cursor: len (items) == 0"
+ self.items = items
+ self.set_index (initial_index)
+ if initial_value is not None:
+ self.set_index_by_value(initial_value)
+
+ def set_index (self, initial_index):
+ if initial_index is None:
+ self.index = len (self.items) / 2
+ elif initial_index >= 0 and initial_index < len (self.items):
+ self.index = initial_index
+ else:
+ raise exceptions.ValueError
+
+ def set_index_by_value(self, v):
+ """
+ Set index to the smallest value such that items[index] >= v.
+ If there is no such item, set index to the maximum value.
+ """
+ self.set_index(0) # side effect!
+ cv = self.current()
+ more = True
+ while cv < v and more:
+ cv, more = self.next() # side effect!
+
+ def next (self):
+ new_index = self.index + 1
+ if new_index < len (self.items):
+ self.index = new_index
+ return self.items[new_index], True
+ else:
+ return self.items[self.index], False
+
+ def prev (self):
+ new_index = self.index - 1
+ if new_index >= 0:
+ self.index = new_index
+ return self.items[new_index], True
+ else:
+ return self.items[self.index], False
+
+ def current (self):
+ return self.items[self.index]
+
+ def get_seq (self):
+ return self.items[:] # copy of items
+
diff --git a/gnuradio-runtime/python/gnuradio/gru/socket_stuff.py b/gnuradio-runtime/python/gnuradio/gru/socket_stuff.py
new file mode 100644
index 0000000000..489b6ab255
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/gru/socket_stuff.py
@@ -0,0 +1,62 @@
+#
+# Copyright 2005 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+# random socket related stuff
+
+import socket
+import os
+import sys
+
+def tcp_connect_or_die(sock_addr):
+ """
+
+ Args:
+ sock_addr: (host, port) to connect to (tuple)
+
+ Returns:
+ : socket or exits
+ """
+ s = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
+ try:
+ s.connect(sock_addr)
+ except socket.error, err:
+ sys.stderr.write('Failed to connect to %s: %s\n' %
+ (sock_addr, os.strerror (err.args[0]),))
+ sys.exit(1)
+ return s
+
+def udp_connect_or_die(sock_addr):
+ """
+
+ Args:
+ sock_addr: (host, port) to connect to (tuple)
+
+ Returns:
+ : socket or exits
+ """
+ s = socket.socket (socket.AF_INET, socket.SOCK_DGRAM)
+ try:
+ s.connect(sock_addr)
+ except socket.error, err:
+ sys.stderr.write('Failed to connect to %s: %s\n' %
+ (sock_addr, os.strerror (err.args[0]),))
+ sys.exit(1)
+ return s