summaryrefslogtreecommitdiff
path: root/grc/python
diff options
context:
space:
mode:
Diffstat (limited to 'grc/python')
-rw-r--r--grc/python/Block.py180
-rw-r--r--grc/python/CMakeLists.txt44
-rw-r--r--grc/python/Connection.py46
-rw-r--r--grc/python/Constants.py95
-rw-r--r--grc/python/FlowGraph.py163
-rw-r--r--grc/python/Generator.py152
-rw-r--r--grc/python/Param.py458
-rw-r--r--grc/python/Platform.py70
-rw-r--r--grc/python/Port.py217
-rw-r--r--grc/python/__init__.py1
-rw-r--r--grc/python/block.dtd57
-rw-r--r--grc/python/convert_hier.py98
-rw-r--r--grc/python/default_flow_graph.grc43
-rw-r--r--grc/python/expr_utils.py153
-rw-r--r--grc/python/extract_docs.py66
-rw-r--r--grc/python/flow_graph.tmpl311
16 files changed, 2154 insertions, 0 deletions
diff --git a/grc/python/Block.py b/grc/python/Block.py
new file mode 100644
index 000000000..2c334dfc2
--- /dev/null
+++ b/grc/python/Block.py
@@ -0,0 +1,180 @@
+"""
+Copyright 2008-2011 Free Software Foundation, Inc.
+This file is part of GNU Radio
+
+GNU Radio Companion 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 2
+of the License, or (at your option) any later version.
+
+GNU Radio Companion 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
+"""
+
+from .. base.Block import Block as _Block
+from .. gui.Block import Block as _GUIBlock
+import extract_docs
+
+class Block(_Block, _GUIBlock):
+
+ def is_virtual_sink(self): return self.get_key() == 'virtual_sink'
+ def is_virtual_source(self): return self.get_key() == 'virtual_source'
+
+ ##for make source to keep track of indexes
+ _source_count = 0
+ ##for make sink to keep track of indexes
+ _sink_count = 0
+
+ def __init__(self, flow_graph, n):
+ """
+ Make a new block from nested data.
+ @param flow graph the parent element
+ @param n the nested odict
+ @return block a new block
+ """
+ #grab the data
+ self._doc = n.find('doc') or ''
+ self._imports = map(lambda i: i.strip(), n.findall('import'))
+ self._make = n.find('make')
+ self._var_make = n.find('var_make')
+ self._checks = n.findall('check')
+ self._callbacks = n.findall('callback')
+ self._throttle = n.find('throttle') or ''
+ #build the block
+ _Block.__init__(
+ self,
+ flow_graph=flow_graph,
+ n=n,
+ )
+ _GUIBlock.__init__(self)
+
+ def throttle(self): return bool(self._throttle)
+
+ def validate(self):
+ """
+ Validate this block.
+ Call the base class validate.
+ Evaluate the checks: each check must evaluate to True.
+ """
+ _Block.validate(self)
+ #evaluate the checks
+ for check in self._checks:
+ check_res = self.resolve_dependencies(check)
+ try:
+ if not self.get_parent().evaluate(check_res):
+ self.add_error_message('Check "%s" failed.'%check)
+ except: self.add_error_message('Check "%s" did not evaluate.'%check)
+
+ def rewrite(self):
+ """
+ Add and remove ports to adjust for the nports.
+ """
+ _Block.rewrite(self)
+
+ def rectify(ports):
+ #restore integer contiguity after insertion
+ #rectify the port names with the index
+ for i, port in enumerate(ports):
+ port._key = str(i)
+ port._name = port._n['name']
+ if len(ports) > 1: port._name += str(i)
+
+ def insert_port(get_ports, get_port, key):
+ prev_port = get_port(str(int(key)-1))
+ get_ports().insert(
+ get_ports().index(prev_port)+1,
+ prev_port.copy(new_key=key),
+ )
+ rectify(get_ports())
+
+ def remove_port(get_ports, get_port, key):
+ port = get_port(key)
+ for connection in port.get_connections():
+ self.get_parent().remove_element(connection)
+ get_ports().remove(port)
+ rectify(get_ports())
+
+ #adjust nports
+ for get_ports, get_port in (
+ (self.get_sources, self.get_source),
+ (self.get_sinks, self.get_sink),
+ ):
+ master_ports = filter(lambda p: p.get_nports(), get_ports())
+ for i, master_port in enumerate(master_ports):
+ nports = master_port.get_nports()
+ index_first = get_ports().index(master_port)
+ try: index_last = get_ports().index(master_ports[i+1])
+ except IndexError: index_last = len(get_ports())
+ num_ports = index_last - index_first
+ #do nothing if nports is already num ports
+ if nports == num_ports: continue
+ #remove excess ports and connections
+ if nports < num_ports:
+ for key in reversed(map(str, range(index_first+nports, index_first+num_ports))):
+ remove_port(get_ports, get_port, key)
+ continue
+ #add more ports
+ if nports > num_ports:
+ for key in map(str, range(index_first+num_ports, index_first+nports)):
+ insert_port(get_ports, get_port, key)
+ continue
+
+ def port_controller_modify(self, direction):
+ """
+ Change the port controller.
+ @param direction +1 or -1
+ @return true for change
+ """
+ changed = False
+ #concat the nports string from the private nports settings of all ports
+ nports_str = ' '.join([port._nports for port in self.get_ports()])
+ #modify all params whose keys appear in the nports string
+ for param in self.get_params():
+ if param.is_enum() or param.get_key() not in nports_str: continue
+ #try to increment the port controller by direction
+ try:
+ value = param.get_evaluated()
+ value = value + direction
+ if 0 < value:
+ param.set_value(value)
+ changed = True
+ except: pass
+ return changed
+
+ def get_doc(self):
+ doc = self._doc.strip('\n').replace('\\\n', '')
+ #merge custom doc with doxygen docs
+ return '\n'.join([doc, extract_docs.extract(self.get_key())]).strip('\n')
+
+ def get_category(self):
+ return _Block.get_category(self)
+
+ def get_imports(self):
+ """
+ Resolve all import statements.
+ Split each import statement at newlines.
+ Combine all import statments into a list.
+ Filter empty imports.
+ @return a list of import statements
+ """
+ return filter(lambda i: i, sum(map(lambda i: self.resolve_dependencies(i).split('\n'), self._imports), []))
+
+ def get_make(self): return self.resolve_dependencies(self._make)
+ def get_var_make(self): return self.resolve_dependencies(self._var_make)
+
+ def get_callbacks(self):
+ """
+ Get a list of function callbacks for this block.
+ @return a list of strings
+ """
+ def make_callback(callback):
+ callback = self.resolve_dependencies(callback)
+ if 'self.' in callback: return callback
+ return 'self.%s.%s'%(self.get_id(), callback)
+ return map(make_callback, self._callbacks)
diff --git a/grc/python/CMakeLists.txt b/grc/python/CMakeLists.txt
new file mode 100644
index 000000000..4832dd897
--- /dev/null
+++ b/grc/python/CMakeLists.txt
@@ -0,0 +1,44 @@
+# Copyright 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.
+
+########################################################################
+GR_PYTHON_INSTALL(FILES
+ convert_hier.py
+ expr_utils.py
+ extract_docs.py
+ Block.py
+ Connection.py
+ Constants.py
+ FlowGraph.py
+ Generator.py
+ Param.py
+ Platform.py
+ Port.py
+ __init__.py
+ DESTINATION ${GR_PYTHON_DIR}/gnuradio/grc/python
+ COMPONENT "grc"
+)
+
+install(FILES
+ block.dtd
+ default_flow_graph.grc
+ flow_graph.tmpl
+ DESTINATION ${GR_PYTHON_DIR}/gnuradio/grc/python
+ COMPONENT "grc"
+)
diff --git a/grc/python/Connection.py b/grc/python/Connection.py
new file mode 100644
index 000000000..341dd2d82
--- /dev/null
+++ b/grc/python/Connection.py
@@ -0,0 +1,46 @@
+"""
+Copyright 2008-2011 Free Software Foundation, Inc.
+This file is part of GNU Radio
+
+GNU Radio Companion 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 2
+of the License, or (at your option) any later version.
+
+GNU Radio Companion 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
+"""
+
+import Constants
+from .. base.Element import Element
+from .. base.Connection import Connection as _Connection
+from .. gui.Connection import Connection as _GUIConnection
+
+class Connection(_Connection, _GUIConnection):
+
+ def __init__(self, **kwargs):
+ _Connection.__init__(self, **kwargs)
+ _GUIConnection.__init__(self)
+
+ def is_msg(self):
+ return self.get_source().get_type() == self.get_sink().get_type() == 'msg'
+
+ def is_message(self):
+ return self.get_source().get_type() == self.get_sink().get_type() == 'message'
+
+ def validate(self):
+ """
+ Validate the connections.
+ The ports must match in io size.
+ """
+ Element.validate(self)
+ source_size = Constants.TYPE_TO_SIZEOF[self.get_source().get_type()] * self.get_source().get_vlen()
+ sink_size = Constants.TYPE_TO_SIZEOF[self.get_sink().get_type()] * self.get_sink().get_vlen()
+ if source_size != sink_size:
+ self.add_error_message('Source IO size "%s" does not match sink IO size "%s".'%(source_size, sink_size))
diff --git a/grc/python/Constants.py b/grc/python/Constants.py
new file mode 100644
index 000000000..b8dc9a96a
--- /dev/null
+++ b/grc/python/Constants.py
@@ -0,0 +1,95 @@
+"""
+Copyright 2008-2011 Free Software Foundation, Inc.
+This file is part of GNU Radio
+
+GNU Radio Companion 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 2
+of the License, or (at your option) any later version.
+
+GNU Radio Companion 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
+"""
+
+import os
+import stat
+from gnuradio import gr
+
+_gr_prefs = gr.prefs()
+
+#setup paths
+PATH_SEP = {'/':':', '\\':';'}[os.path.sep]
+HIER_BLOCKS_LIB_DIR = os.path.join(os.path.expanduser('~'), '.grc_gnuradio')
+BLOCKS_DIRS = filter( #filter blank strings
+ lambda x: x, PATH_SEP.join([
+ os.environ.get('GRC_BLOCKS_PATH', ''),
+ _gr_prefs.get_string('grc', 'local_blocks_path', ''),
+ _gr_prefs.get_string('grc', 'global_blocks_path', ''),
+ ]).split(PATH_SEP),
+) + [HIER_BLOCKS_LIB_DIR]
+
+#file creation modes
+TOP_BLOCK_FILE_MODE = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH
+HIER_BLOCK_FILE_MODE = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH
+
+#data files
+DATA_DIR = os.path.dirname(__file__)
+FLOW_GRAPH_TEMPLATE = os.path.join(DATA_DIR, 'flow_graph.tmpl')
+BLOCK_DTD = os.path.join(DATA_DIR, 'block.dtd')
+DEFAULT_FLOW_GRAPH = os.path.join(DATA_DIR, 'default_flow_graph.grc')
+
+CORE_TYPES = ( #name, key, sizeof, color
+ ('Complex Float 64', 'fc64', 16, '#CC8C69'),
+ ('Complex Float 32', 'fc32', 8, '#3399FF'),
+ ('Complex Integer 64', 'sc64', 16, '#66CC00'),
+ ('Complex Integer 32', 'sc32', 8, '#33cc66'),
+ ('Complex Integer 16', 'sc16', 4, '#cccc00'),
+ ('Complex Integer 8', 'sc8', 2, '#cc00cc'),
+ ('Float 64', 'f64', 8, '#66CCCC'),
+ ('Float 32', 'f32', 4, '#FF8C69'),
+ ('Integer 64', 's64', 8, '#99FF33'),
+ ('Integer 32', 's32', 4, '#00FF99'),
+ ('Integer 16', 's16', 2, '#FFFF66'),
+ ('Integer 8', 's8', 1, '#FF66FF'),
+ ('Message Queue', 'msg', 0, '#777777'),
+ ('Async Message', 'message', 0, '#C0C0C0'),
+ ('Wildcard', '', 0, '#FFFFFF'),
+)
+
+ALIAS_TYPES = {
+ 'complex' : (8, '#3399FF'),
+ 'float' : (4, '#FF8C69'),
+ 'int' : (4, '#00FF99'),
+ 'short' : (2, '#FFFF66'),
+ 'byte' : (1, '#FF66FF'),
+}
+
+TYPE_TO_COLOR = dict()
+TYPE_TO_SIZEOF = dict()
+for name, key, sizeof, color in CORE_TYPES:
+ TYPE_TO_COLOR[key] = color
+ TYPE_TO_SIZEOF[key] = sizeof
+for key, (sizeof, color) in ALIAS_TYPES.iteritems():
+ TYPE_TO_COLOR[key] = color
+ TYPE_TO_SIZEOF[key] = sizeof
+
+#coloring
+COMPLEX_COLOR_SPEC = '#3399FF'
+FLOAT_COLOR_SPEC = '#FF8C69'
+INT_COLOR_SPEC = '#00FF99'
+SHORT_COLOR_SPEC = '#FFFF66'
+BYTE_COLOR_SPEC = '#FF66FF'
+COMPLEX_VECTOR_COLOR_SPEC = '#3399AA'
+FLOAT_VECTOR_COLOR_SPEC = '#CC8C69'
+INT_VECTOR_COLOR_SPEC = '#00CC99'
+SHORT_VECTOR_COLOR_SPEC = '#CCCC33'
+BYTE_VECTOR_COLOR_SPEC = '#CC66CC'
+ID_COLOR_SPEC = '#DDDDDD'
+WILDCARD_COLOR_SPEC = '#FFFFFF'
+MSG_COLOR_SPEC = '#777777'
diff --git a/grc/python/FlowGraph.py b/grc/python/FlowGraph.py
new file mode 100644
index 000000000..376c2e337
--- /dev/null
+++ b/grc/python/FlowGraph.py
@@ -0,0 +1,163 @@
+"""
+Copyright 2008-2011 Free Software Foundation, Inc.
+This file is part of GNU Radio
+
+GNU Radio Companion 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 2
+of the License, or (at your option) any later version.
+
+GNU Radio Companion 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
+"""
+
+import expr_utils
+from .. base.FlowGraph import FlowGraph as _FlowGraph
+from .. gui.FlowGraph import FlowGraph as _GUIFlowGraph
+import re
+
+_variable_matcher = re.compile('^(variable\w*)$')
+_parameter_matcher = re.compile('^(parameter)$')
+
+class FlowGraph(_FlowGraph, _GUIFlowGraph):
+
+ def __init__(self, **kwargs):
+ _FlowGraph.__init__(self, **kwargs)
+ _GUIFlowGraph.__init__(self)
+ self._eval_cache = dict()
+
+ def _eval(self, code, namespace, namespace_hash):
+ """
+ Evaluate the code with the given namespace.
+ @param code a string with python code
+ @param namespace a dict representing the namespace
+ @param namespace_hash a unique hash for the namespace
+ @return the resultant object
+ """
+ if not code: raise Exception, 'Cannot evaluate empty statement.'
+ my_hash = hash(code) ^ namespace_hash
+ #cache if does not exist
+ if not self._eval_cache.has_key(my_hash):
+ self._eval_cache[my_hash] = eval(code, namespace, namespace)
+ #return from cache
+ return self._eval_cache[my_hash]
+
+ def get_io_signaturev(self, direction):
+ """
+ Get a list of io signatures for this flow graph.
+ @param direction a string of 'in' or 'out'
+ @return a list of dicts with: type, label, vlen, size
+ """
+ sorted_pads = {
+ 'in': self.get_pad_sources(),
+ 'out': self.get_pad_sinks(),
+ }[direction]
+ # we only want stream ports
+ sorted_pads = filter(lambda b: b.get_param('type').get_evaluated() != 'message', sorted_pads);
+ #load io signature
+ return [{
+ 'label': str(pad.get_param('label').get_evaluated()),
+ 'type': str(pad.get_param('type').get_evaluated()),
+ 'vlen': str(pad.get_param('vlen').get_evaluated()),
+ 'size': pad.get_param('type').get_opt('size'),
+ 'optional': bool(pad.get_param('optional').get_evaluated()),
+ } for pad in sorted_pads]
+
+ def get_pad_sources(self):
+ """
+ Get a list of pad source blocks sorted by id order.
+ @return a list of pad source blocks in this flow graph
+ """
+ pads = filter(lambda b: b.get_key() == 'pad_source', self.get_enabled_blocks())
+ return sorted(pads, lambda x, y: cmp(x.get_id(), y.get_id()))
+
+ def get_pad_sinks(self):
+ """
+ Get a list of pad sink blocks sorted by id order.
+ @return a list of pad sink blocks in this flow graph
+ """
+ pads = filter(lambda b: b.get_key() == 'pad_sink', self.get_enabled_blocks())
+ return sorted(pads, lambda x, y: cmp(x.get_id(), y.get_id()))
+
+ def get_msg_pad_sources(self):
+ ps = self.get_pad_sources();
+ return filter(lambda b: b.get_param('type').get_evaluated() == 'message', ps);
+
+ def get_msg_pad_sinks(self):
+ ps = self.get_pad_sinks();
+ return filter(lambda b: b.get_param('type').get_evaluated() == 'message', ps);
+
+ def get_imports(self):
+ """
+ Get a set of all import statments in this flow graph namespace.
+ @return a set of import statements
+ """
+ imports = sum([block.get_imports() for block in self.get_enabled_blocks()], [])
+ imports = sorted(set(imports))
+ return imports
+
+ def get_variables(self):
+ """
+ Get a list of all variables in this flow graph namespace.
+ Exclude paramterized variables.
+ @return a sorted list of variable blocks in order of dependency (indep -> dep)
+ """
+ variables = filter(lambda b: _variable_matcher.match(b.get_key()), self.get_enabled_blocks())
+ return expr_utils.sort_objects(variables, lambda v: v.get_id(), lambda v: v.get_var_make())
+
+ def get_parameters(self):
+ """
+ Get a list of all paramterized variables in this flow graph namespace.
+ @return a list of paramterized variables
+ """
+ parameters = filter(lambda b: _parameter_matcher.match(b.get_key()), self.get_enabled_blocks())
+ return parameters
+
+ def rewrite(self):
+ """
+ Flag the namespace to be renewed.
+ """
+ self._renew_eval_ns = True
+ _FlowGraph.rewrite(self)
+
+ def evaluate(self, expr):
+ """
+ Evaluate the expression.
+ @param expr the string expression
+ @throw Exception bad expression
+ @return the evaluated data
+ """
+ if self._renew_eval_ns:
+ self._renew_eval_ns = False
+ #reload namespace
+ n = dict()
+ #load imports
+ for imp in self.get_imports():
+ try: exec imp in n
+ except: pass
+ #load parameters
+ np = dict()
+ for parameter in self.get_parameters():
+ try:
+ e = eval(parameter.get_param('value').to_code(), n, n)
+ np[parameter.get_id()] = e
+ except: pass
+ n.update(np) #merge param namespace
+ #load variables
+ for variable in self.get_variables():
+ try:
+ e = eval(variable.get_param('value').to_code(), n, n)
+ n[variable.get_id()] = e
+ except: pass
+ #make namespace public
+ self.n = n
+ self.n_hash = hash(str(n))
+ #evaluate
+ e = self._eval(expr, self.n, self.n_hash)
+ return e
diff --git a/grc/python/Generator.py b/grc/python/Generator.py
new file mode 100644
index 000000000..616ea00fc
--- /dev/null
+++ b/grc/python/Generator.py
@@ -0,0 +1,152 @@
+"""
+Copyright 2008-2011 Free Software Foundation, Inc.
+This file is part of GNU Radio
+
+GNU Radio Companion 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 2
+of the License, or (at your option) any later version.
+
+GNU Radio Companion 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
+"""
+
+import os
+import sys
+import subprocess
+import tempfile
+from Cheetah.Template import Template
+import expr_utils
+from Constants import \
+ TOP_BLOCK_FILE_MODE, HIER_BLOCK_FILE_MODE, \
+ HIER_BLOCKS_LIB_DIR, FLOW_GRAPH_TEMPLATE
+import convert_hier
+from .. gui import Messages
+
+class Generator(object):
+
+ def __init__(self, flow_graph, file_path):
+ """
+ Initialize the generator object.
+ Determine the file to generate.
+ @param flow_graph the flow graph object
+ @param file_path the path to write the file to
+ """
+ self._flow_graph = flow_graph
+ self._generate_options = self._flow_graph.get_option('generate_options')
+ if self._generate_options == 'hb':
+ self._mode = HIER_BLOCK_FILE_MODE
+ dirname = HIER_BLOCKS_LIB_DIR
+ else:
+ self._mode = TOP_BLOCK_FILE_MODE
+ dirname = os.path.dirname(file_path)
+ #handle the case where the directory is read-only
+ #in this case, use the system's temp directory
+ if not os.access(dirname, os.W_OK):
+ dirname = tempfile.gettempdir()
+ filename = self._flow_graph.get_option('id') + '.py'
+ self._file_path = os.path.join(dirname, filename)
+
+ def get_file_path(self): return self._file_path
+
+ def write(self):
+ #do throttle warning
+ throttled = any(map(lambda b: b.throttle(), self._flow_graph.get_enabled_blocks()))
+ if not throttled and self._generate_options != 'hb':
+ Messages.send_warning('''\
+This flow graph may not have flow control: no audio or usrp blocks found. \
+Add a Misc->Throttle block to your flow graph to avoid CPU congestion.''')
+ #generate
+ open(self.get_file_path(), 'w').write(str(self))
+ if self._generate_options == 'hb':
+ #convert hier block to xml wrapper
+ convert_hier.convert_hier(self._flow_graph, self.get_file_path())
+ os.chmod(self.get_file_path(), self._mode)
+
+ def get_popen(self):
+ """
+ Execute this python flow graph.
+ @return a popen object
+ """
+ #extract the path to the python executable
+ python_exe = sys.executable
+
+ #when using wx gui on mac os, execute with pythonw
+ #using pythonw is not necessary anymore, disabled below
+ #if self._generate_options == 'wx_gui' and 'darwin' in sys.platform.lower():
+ # python_exe = 'pythonw'
+
+ #setup the command args to run
+ cmds = [python_exe, '-u', self.get_file_path()] #-u is unbuffered stdio
+
+ #when in no gui mode on linux, use an xterm (looks nice)
+ if self._generate_options == 'no_gui' and 'linux' in sys.platform.lower():
+ cmds = ['xterm', '-e'] + cmds
+
+ p = subprocess.Popen(args=cmds, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False, universal_newlines=True)
+ return p
+
+ def __str__(self):
+ """
+ Convert the flow graph to python code.
+ @return a string of python code
+ """
+ title = self._flow_graph.get_option('title') or self._flow_graph.get_option('id').replace('_', ' ').title()
+ imports = self._flow_graph.get_imports()
+ variables = self._flow_graph.get_variables()
+ parameters = self._flow_graph.get_parameters()
+ #list of blocks not including variables and imports and parameters and disabled
+ def _get_block_sort_text(block):
+ code = block.get_make().replace(block.get_id(), ' ')
+ try: code += block.get_param('notebook').get_value() #older gui markup w/ wxgui
+ except: pass
+ try: code += block.get_param('gui_hint').get_value() #newer gui markup w/ qtgui
+ except: pass
+ return code
+ blocks = expr_utils.sort_objects(
+ self._flow_graph.get_enabled_blocks(),
+ lambda b: b.get_id(), _get_block_sort_text
+ )
+ #list of regular blocks (all blocks minus the special ones)
+ blocks = filter(lambda b: b not in (imports + parameters), blocks)
+ #list of connections where each endpoint is enabled
+ connections = filter(lambda c: not (c.is_msg() or c.is_message()), self._flow_graph.get_enabled_connections())
+ messages = filter(lambda c: c.is_msg(), self._flow_graph.get_enabled_connections())
+ messages2 = filter(lambda c: c.is_message(), self._flow_graph.get_enabled_connections())
+ #list of variable names
+ var_ids = [var.get_id() for var in parameters + variables]
+ #prepend self.
+ replace_dict = dict([(var_id, 'self.%s'%var_id) for var_id in var_ids])
+ #list of callbacks
+ callbacks = [
+ expr_utils.expr_replace(cb, replace_dict)
+ for cb in sum([block.get_callbacks() for block in self._flow_graph.get_enabled_blocks()], [])
+ ]
+ #map var id to callbacks
+ var_id2cbs = dict(
+ [(var_id, filter(lambda c: expr_utils.get_variable_dependencies(c, [var_id]), callbacks))
+ for var_id in var_ids]
+ )
+ #load the namespace
+ namespace = {
+ 'title': title,
+ 'imports': imports,
+ 'flow_graph': self._flow_graph,
+ 'variables': variables,
+ 'parameters': parameters,
+ 'blocks': blocks,
+ 'connections': connections,
+ 'messages': messages,
+ 'messages2': messages2,
+ 'generate_options': self._generate_options,
+ 'var_id2cbs': var_id2cbs,
+ }
+ #build the template
+ t = Template(open(FLOW_GRAPH_TEMPLATE, 'r').read(), namespace)
+ return str(t)
diff --git a/grc/python/Param.py b/grc/python/Param.py
new file mode 100644
index 000000000..2caca4802
--- /dev/null
+++ b/grc/python/Param.py
@@ -0,0 +1,458 @@
+"""
+Copyright 2008-2011 Free Software Foundation, Inc.
+This file is part of GNU Radio
+
+GNU Radio Companion 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 2
+of the License, or (at your option) any later version.
+
+GNU Radio Companion 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
+"""
+
+from .. base.Param import Param as _Param
+from .. gui.Param import Param as _GUIParam
+from .. gui.Param import EntryParam
+import Constants
+import numpy
+import os
+import pygtk
+pygtk.require('2.0')
+import gtk
+from gnuradio import eng_notation
+import re
+from gnuradio import gr
+
+_check_id_matcher = re.compile('^[a-z|A-Z]\w*$')
+_show_id_matcher = re.compile('^(variable\w*|parameter|options|notebook)$')
+
+class FileParam(EntryParam):
+ """Provide an entry box for filename and a button to browse for a file."""
+
+ def __init__(self, *args, **kwargs):
+ EntryParam.__init__(self, *args, **kwargs)
+ input = gtk.Button('...')
+ input.connect('clicked', self._handle_clicked)
+ self.pack_start(input, False)
+
+ def _handle_clicked(self, widget=None):
+ """
+ If the button was clicked, open a file dialog in open/save format.
+ Replace the text in the entry with the new filename from the file dialog.
+ """
+ #get the paths
+ file_path = self.param.is_valid() and self.param.get_evaluated() or ''
+ (dirname, basename) = os.path.isfile(file_path) and os.path.split(file_path) or (file_path, '')
+ if not os.path.exists(dirname): dirname = os.getcwd() #fix bad paths
+ #build the dialog
+ if self.param.get_type() == 'file_open':
+ file_dialog = gtk.FileChooserDialog('Open a Data File...', None,
+ gtk.FILE_CHOOSER_ACTION_OPEN, ('gtk-cancel',gtk.RESPONSE_CANCEL,'gtk-open',gtk.RESPONSE_OK))
+ elif self.param.get_type() == 'file_save':
+ file_dialog = gtk.FileChooserDialog('Save a Data File...', None,
+ gtk.FILE_CHOOSER_ACTION_SAVE, ('gtk-cancel',gtk.RESPONSE_CANCEL, 'gtk-save',gtk.RESPONSE_OK))
+ file_dialog.set_do_overwrite_confirmation(True)
+ file_dialog.set_current_name(basename) #show the current filename
+ file_dialog.set_current_folder(dirname) #current directory
+ file_dialog.set_select_multiple(False)
+ file_dialog.set_local_only(True)
+ if gtk.RESPONSE_OK == file_dialog.run(): #run the dialog
+ file_path = file_dialog.get_filename() #get the file path
+ self._input.set_text(file_path)
+ self._handle_changed()
+ file_dialog.destroy() #destroy the dialog
+
+#blacklist certain ids, its not complete, but should help
+import __builtin__
+ID_BLACKLIST = ['self', 'options', 'gr', 'blks2', 'wxgui', 'wx', 'math', 'forms', 'firdes'] + \
+ filter(lambda x: not x.startswith('_'), dir(gr.top_block())) + dir(__builtin__)
+#define types, native python + numpy
+VECTOR_TYPES = (tuple, list, set, numpy.ndarray)
+COMPLEX_TYPES = [complex, numpy.complex, numpy.complex64, numpy.complex128]
+REAL_TYPES = [float, numpy.float, numpy.float32, numpy.float64]
+INT_TYPES = [int, long, numpy.int, numpy.int8, numpy.int16, numpy.int32, numpy.uint64,
+ numpy.uint, numpy.uint8, numpy.uint16, numpy.uint32, numpy.uint64]
+#cast to tuple for isinstance, concat subtypes
+COMPLEX_TYPES = tuple(COMPLEX_TYPES + REAL_TYPES + INT_TYPES)
+REAL_TYPES = tuple(REAL_TYPES + INT_TYPES)
+INT_TYPES = tuple(INT_TYPES)
+
+class Param(_Param, _GUIParam):
+
+ def __init__(self, **kwargs):
+ _Param.__init__(self, **kwargs)
+ _GUIParam.__init__(self)
+ self._init = False
+ self._hostage_cells = list()
+
+ def get_types(self): return (
+ 'raw', 'enum',
+ 'complex', 'real', 'int',
+ 'complex_vector', 'real_vector', 'int_vector',
+ 'hex', 'string', 'bool',
+ 'file_open', 'file_save',
+ 'id', 'stream_id',
+ 'grid_pos', 'notebook', 'gui_hint',
+ 'import',
+ )
+
+ def __repr__(self):
+ """
+ Get the repr (nice string format) for this param.
+ @return the string representation
+ """
+ ##################################################
+ # truncate helper method
+ ##################################################
+ def _truncate(string, style=0):
+ max_len = max(27 - len(self.get_name()), 3)
+ if len(string) > max_len:
+ if style < 0: #front truncate
+ string = '...' + string[3-max_len:]
+ elif style == 0: #center truncate
+ string = string[:max_len/2 -3] + '...' + string[-max_len/2:]
+ elif style > 0: #rear truncate
+ string = string[:max_len-3] + '...'
+ return string
+ ##################################################
+ # simple conditions
+ ##################################################
+ if not self.is_valid(): return _truncate(self.get_value())
+ if self.get_value() in self.get_option_keys(): return self.get_option(self.get_value()).get_name()
+ ##################################################
+ # display logic for numbers
+ ##################################################
+ def num_to_str(num):
+ if isinstance(num, COMPLEX_TYPES):
+ num = complex(num) #cast to python complex
+ if num == 0: return '0' #value is zero
+ elif num.imag == 0: return '%s'%eng_notation.num_to_str(num.real) #value is real
+ elif num.real == 0: return '%sj'%eng_notation.num_to_str(num.imag) #value is imaginary
+ elif num.imag < 0: return '%s-%sj'%(eng_notation.num_to_str(num.real), eng_notation.num_to_str(abs(num.imag)))
+ else: return '%s+%sj'%(eng_notation.num_to_str(num.real), eng_notation.num_to_str(num.imag))
+ else: return str(num)
+ ##################################################
+ # split up formatting by type
+ ##################################################
+ truncate = 0 #default center truncate
+ e = self.get_evaluated()
+ t = self.get_type()
+ if isinstance(e, bool): return str(e)
+ elif isinstance(e, COMPLEX_TYPES): dt_str = num_to_str(e)
+ elif isinstance(e, VECTOR_TYPES): #vector types
+ if len(e) > 8:
+ dt_str = self.get_value() #large vectors use code
+ truncate = 1
+ else: dt_str = ', '.join(map(num_to_str, e)) #small vectors use eval
+ elif t in ('file_open', 'file_save'):
+ dt_str = self.get_value()
+ truncate = -1
+ else: dt_str = str(e) #other types
+ ##################################################
+ # done
+ ##################################################
+ return _truncate(dt_str, truncate)
+
+ def get_input(self, *args, **kwargs):
+ if self.get_type() in ('file_open', 'file_save'): return FileParam(self, *args, **kwargs)
+ return _GUIParam.get_input(self, *args, **kwargs)
+
+ def get_color(self):
+ """
+ Get the color that represents this param's type.
+ @return a hex color code.
+ """
+ try:
+ return {
+ #number types
+ 'complex': Constants.COMPLEX_COLOR_SPEC,
+ 'real': Constants.FLOAT_COLOR_SPEC,
+ 'int': Constants.INT_COLOR_SPEC,
+ #vector types
+ 'complex_vector': Constants.COMPLEX_VECTOR_COLOR_SPEC,
+ 'real_vector': Constants.FLOAT_VECTOR_COLOR_SPEC,
+ 'int_vector': Constants.INT_VECTOR_COLOR_SPEC,
+ #special
+ 'bool': Constants.INT_COLOR_SPEC,
+ 'hex': Constants.INT_COLOR_SPEC,
+ 'string': Constants.BYTE_VECTOR_COLOR_SPEC,
+ 'id': Constants.ID_COLOR_SPEC,
+ 'stream_id': Constants.ID_COLOR_SPEC,
+ 'grid_pos': Constants.INT_VECTOR_COLOR_SPEC,
+ 'notebook': Constants.INT_VECTOR_COLOR_SPEC,
+ 'raw': Constants.WILDCARD_COLOR_SPEC,
+ }[self.get_type()]
+ except: return _Param.get_color(self)
+
+ def get_hide(self):
+ """
+ Get the hide value from the base class.
+ Hide the ID parameter for most blocks. Exceptions below.
+ If the parameter controls a port type, vlen, or nports, return part.
+ If the parameter is an empty grid position, return part.
+ These parameters are redundant to display in the flow graph view.
+ @return hide the hide property string
+ """
+ hide = _Param.get_hide(self)
+ if hide: return hide
+ #hide ID in non variable blocks
+ if self.get_key() == 'id' and not _show_id_matcher.match(self.get_parent().get_key()): return 'part'
+ #hide port controllers for type and nports
+ if self.get_key() in ' '.join(map(
+ lambda p: ' '.join([p._type, p._nports]), self.get_parent().get_ports())
+ ): return 'part'
+ #hide port controllers for vlen, when == 1
+ if self.get_key() in ' '.join(map(
+ lambda p: p._vlen, self.get_parent().get_ports())
+ ):
+ try:
+ if int(self.get_evaluated()) == 1: return 'part'
+ except: pass
+ #hide empty grid positions
+ if self.get_key() in ('grid_pos', 'notebook') and not self.get_value(): return 'part'
+ return hide
+
+ def validate(self):
+ """
+ Validate the param.
+ A test evaluation is performed
+ """
+ _Param.validate(self) #checks type
+ self._evaluated = None
+ try: self._evaluated = self.evaluate()
+ except Exception, e: self.add_error_message(str(e))
+
+ def get_evaluated(self): return self._evaluated
+
+ def evaluate(self):
+ """
+ Evaluate the value.
+ @return evaluated type
+ """
+ self._init = True
+ self._lisitify_flag = False
+ self._stringify_flag = False
+ self._hostage_cells = list()
+ def eval_string(v):
+ try:
+ e = self.get_parent().get_parent().evaluate(v)
+ if isinstance(e, str): return e
+ raise Exception #want to stringify
+ except:
+ self._stringify_flag = True
+ return v
+ t = self.get_type()
+ v = self.get_value()
+ #########################
+ # Enum Type
+ #########################
+ if self.is_enum(): return v
+ #########################
+ # Numeric Types
+ #########################
+ elif t in ('raw', 'complex', 'real', 'int', 'hex', 'bool'):
+ #raise exception if python cannot evaluate this value
+ try: e = self.get_parent().get_parent().evaluate(v)
+ except Exception, e: raise Exception, 'Value "%s" cannot be evaluated:\n%s'%(v, e)
+ #raise an exception if the data is invalid
+ if t == 'raw': return e
+ elif t == 'complex':
+ if not isinstance(e, COMPLEX_TYPES):
+ raise Exception, 'Expression "%s" is invalid for type complex.'%str(e)
+ return e
+ elif t == 'real':
+ if not isinstance(e, REAL_TYPES):
+ raise Exception, 'Expression "%s" is invalid for type real.'%str(e)
+ return e
+ elif t == 'int':
+ if not isinstance(e, INT_TYPES):
+ raise Exception, 'Expression "%s" is invalid for type integer.'%str(e)
+ return e
+ elif t == 'hex': return hex(e)
+ elif t == 'bool':
+ if not isinstance(e, bool):
+ raise Exception, 'Expression "%s" is invalid for type bool.'%str(e)
+ return e
+ else: raise TypeError, 'Type "%s" not handled'%t
+ #########################
+ # Numeric Vector Types
+ #########################
+ elif t in ('complex_vector', 'real_vector', 'int_vector'):
+ if not v: v = '()' #turn a blank string into an empty list, so it will eval
+ #raise exception if python cannot evaluate this value
+ try: e = self.get_parent().get_parent().evaluate(v)
+ except Exception, e: raise Exception, 'Value "%s" cannot be evaluated:\n%s'%(v, e)
+ #raise an exception if the data is invalid
+ if t == 'complex_vector':
+ if not isinstance(e, VECTOR_TYPES):
+ self._lisitify_flag = True
+ e = [e]
+ if not all([isinstance(ei, COMPLEX_TYPES) for ei in e]):
+ raise Exception, 'Expression "%s" is invalid for type complex vector.'%str(e)
+ return e
+ elif t == 'real_vector':
+ if not isinstance(e, VECTOR_TYPES):
+ self._lisitify_flag = True
+ e = [e]
+ if not all([isinstance(ei, REAL_TYPES) for ei in e]):
+ raise Exception, 'Expression "%s" is invalid for type real vector.'%str(e)
+ return e
+ elif t == 'int_vector':
+ if not isinstance(e, VECTOR_TYPES):
+ self._lisitify_flag = True
+ e = [e]
+ if not all([isinstance(ei, INT_TYPES) for ei in e]):
+ raise Exception, 'Expression "%s" is invalid for type integer vector.'%str(e)
+ return e
+ #########################
+ # String Types
+ #########################
+ elif t in ('string', 'file_open', 'file_save'):
+ #do not check if file/directory exists, that is a runtime issue
+ e = eval_string(v)
+ return str(e)
+ #########################
+ # Unique ID Type
+ #########################
+ elif t == 'id':
+ #can python use this as a variable?
+ if not _check_id_matcher.match(v):
+ raise Exception, 'ID "%s" must begin with a letter and may contain letters, numbers, and underscores.'%v
+ ids = [param.get_value() for param in self.get_all_params(t)]
+ if ids.count(v) > 1: #id should only appear once, or zero times if block is disabled
+ raise Exception, 'ID "%s" is not unique.'%v
+ if v in ID_BLACKLIST:
+ raise Exception, 'ID "%s" is blacklisted.'%v
+ return v
+ #########################
+ # Stream ID Type
+ #########################
+ elif t == 'stream_id':
+ #get a list of all stream ids used in the virtual sinks
+ ids = [param.get_value() for param in filter(
+ lambda p: p.get_parent().is_virtual_sink(),
+ self.get_all_params(t),
+ )]
+ #check that the virtual sink's stream id is unique
+ if self.get_parent().is_virtual_sink():
+ if ids.count(v) > 1: #id should only appear once, or zero times if block is disabled
+ raise Exception, 'Stream ID "%s" is not unique.'%v
+ #check that the virtual source's steam id is found
+ if self.get_parent().is_virtual_source():
+ if v not in ids:
+ raise Exception, 'Stream ID "%s" is not found.'%v
+ return v
+ #########################
+ # GUI Position/Hint
+ #########################
+ elif t == 'gui_hint':
+ if ':' in v: tab, pos = v.split(':')
+ elif '@' in v: tab, pos = v, ''
+ else: tab, pos = '', v
+
+ if '@' in tab: tab, index = tab.split('@')
+ else: index = '?'
+
+ widget_str = ({
+ (True, True): 'self.%(tab)s_grid_layout_%(index)s.addWidget(%(widget)s, %(pos)s)',
+ (True, False): 'self.%(tab)s_layout_%(index)s.addWidget(%(widget)s)',
+ (False, True): 'self.top_grid_layout.addWidget(%(widget)s, %(pos)s)',
+ (False, False): 'self.top_layout.addWidget(%(widget)s)',
+ }[bool(tab), bool(pos)])%{'tab': tab, 'index': index, 'widget': '%s', 'pos': pos}
+
+ def gui_hint(ws, w):
+ if 'layout' in w: ws = ws.replace('addWidget', 'addLayout')
+ return ws%w
+
+ return lambda w: gui_hint(widget_str, w)
+ #########################
+ # Grid Position Type
+ #########################
+ elif t == 'grid_pos':
+ if not v: return '' #allow for empty grid pos
+ e = self.get_parent().get_parent().evaluate(v)
+ if not isinstance(e, (list, tuple)) or len(e) != 4 or not all([isinstance(ei, int) for ei in e]):
+ raise Exception, 'A grid position must be a list of 4 integers.'
+ row, col, row_span, col_span = e
+ #check row, col
+ if row < 0 or col < 0:
+ raise Exception, 'Row and column must be non-negative.'
+ #check row span, col span
+ if row_span <= 0 or col_span <= 0:
+ raise Exception, 'Row and column span must be greater than zero.'
+ #get hostage cell parent
+ try: my_parent = self.get_parent().get_param('notebook').evaluate()
+ except: my_parent = ''
+ #calculate hostage cells
+ for r in range(row_span):
+ for c in range(col_span):
+ self._hostage_cells.append((my_parent, (row+r, col+c)))
+ #avoid collisions
+ params = filter(lambda p: p is not self, self.get_all_params('grid_pos'))
+ for param in params:
+ for parent, cell in param._hostage_cells:
+ if (parent, cell) in self._hostage_cells:
+ raise Exception, 'Another graphical element is using parent "%s", cell "%s".'%(str(parent), str(cell))
+ return e
+ #########################
+ # Notebook Page Type
+ #########################
+ elif t == 'notebook':
+ if not v: return '' #allow for empty notebook
+ #get a list of all notebooks
+ notebook_blocks = filter(lambda b: b.get_key() == 'notebook', self.get_parent().get_parent().get_enabled_blocks())
+ #check for notebook param syntax
+ try: notebook_id, page_index = map(str.strip, v.split(','))
+ except: raise Exception, 'Bad notebook page format.'
+ #check that the notebook id is valid
+ try: notebook_block = filter(lambda b: b.get_id() == notebook_id, notebook_blocks)[0]
+ except: raise Exception, 'Notebook id "%s" is not an existing notebook id.'%notebook_id
+ #check that page index exists
+ if int(page_index) not in range(len(notebook_block.get_param('labels').evaluate())):
+ raise Exception, 'Page index "%s" is not a valid index number.'%page_index
+ return notebook_id, page_index
+ #########################
+ # Import Type
+ #########################
+ elif t == 'import':
+ n = dict() #new namespace
+ try: exec v in n
+ except ImportError: raise Exception, 'Import "%s" failed.'%v
+ except Exception: raise Exception, 'Bad import syntax: "%s".'%v
+ return filter(lambda k: str(k) != '__builtins__', n.keys())
+ #########################
+ else: raise TypeError, 'Type "%s" not handled'%t
+
+ def to_code(self):
+ """
+ Convert the value to code.
+ For string and list types, check the init flag, call evaluate().
+ This ensures that evaluate() was called to set the xxxify_flags.
+ @return a string representing the code
+ """
+ v = self.get_value()
+ t = self.get_type()
+ if t in ('string', 'file_open', 'file_save'): #string types
+ if not self._init: self.evaluate()
+ if self._stringify_flag: return '"%s"'%v.replace('"', '\"')
+ else: return v
+ elif t in ('complex_vector', 'real_vector', 'int_vector'): #vector types
+ if not self._init: self.evaluate()
+ if self._lisitify_flag: return '(%s, )'%v
+ else: return '(%s)'%v
+ else: return v
+
+ def get_all_params(self, type):
+ """
+ Get all the params from the flowgraph that have the given type.
+ @param type the specified type
+ @return a list of params
+ """
+ return sum([filter(lambda p: p.get_type() == type, block.get_params()) for block in self.get_parent().get_parent().get_enabled_blocks()], [])
diff --git a/grc/python/Platform.py b/grc/python/Platform.py
new file mode 100644
index 000000000..e036361ff
--- /dev/null
+++ b/grc/python/Platform.py
@@ -0,0 +1,70 @@
+__doc__ = """
+Copyright 2008-2011 Free Software Foundation, Inc.
+This file is part of GNU Radio
+
+GNU Radio Companion 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 2
+of the License, or (at your option) any later version.
+
+GNU Radio Companion 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
+"""
+
+import os
+from gnuradio import gr
+from .. base.Platform import Platform as _Platform
+from .. gui.Platform import Platform as _GUIPlatform
+from FlowGraph import FlowGraph as _FlowGraph
+from Connection import Connection as _Connection
+from Block import Block as _Block
+from Port import Port as _Port
+from Param import Param as _Param
+from Generator import Generator
+from Constants import \
+ HIER_BLOCKS_LIB_DIR, BLOCK_DTD, \
+ DEFAULT_FLOW_GRAPH, BLOCKS_DIRS
+import Constants
+
+COLORS = [(name, color) for name, key, sizeof, color in Constants.CORE_TYPES]
+
+class Platform(_Platform, _GUIPlatform):
+
+ def __init__(self):
+ """
+ Make a platform for gnuradio.
+ """
+ #ensure hier dir
+ if not os.path.exists(HIER_BLOCKS_LIB_DIR): os.mkdir(HIER_BLOCKS_LIB_DIR)
+ #convert block paths to absolute paths
+ block_paths = set(map(os.path.abspath, BLOCKS_DIRS))
+ #init
+ _Platform.__init__(
+ self,
+ name='GNU Radio Companion',
+ version=gr.version(),
+ key='grc',
+ license=__doc__.strip(),
+ website='http://gnuradio.org/redmine/wiki/gnuradio/GNURadioCompanion',
+ block_paths=block_paths,
+ block_dtd=BLOCK_DTD,
+ default_flow_graph=DEFAULT_FLOW_GRAPH,
+ generator=Generator,
+ colors=COLORS,
+ )
+ _GUIPlatform.__init__(self)
+
+ ##############################################
+ # Constructors
+ ##############################################
+ FlowGraph = _FlowGraph
+ Connection = _Connection
+ Block = _Block
+ Port = _Port
+ Param = _Param
diff --git a/grc/python/Port.py b/grc/python/Port.py
new file mode 100644
index 000000000..8cc9e9ad0
--- /dev/null
+++ b/grc/python/Port.py
@@ -0,0 +1,217 @@
+"""
+Copyright 2008-2012 Free Software Foundation, Inc.
+This file is part of GNU Radio
+
+GNU Radio Companion 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 2
+of the License, or (at your option) any later version.
+
+GNU Radio Companion 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
+"""
+
+from .. base.Port import Port as _Port
+from .. gui.Port import Port as _GUIPort
+import Constants
+
+def _get_source_from_virtual_sink_port(vsp):
+ """
+ Resolve the source port that is connected to the given virtual sink port.
+ Use the get source from virtual source to recursively resolve subsequent ports.
+ """
+ try: return _get_source_from_virtual_source_port(
+ vsp.get_enabled_connections()[0].get_source())
+ except: raise Exception, 'Could not resolve source for virtual sink port %s'%vsp
+
+def _get_source_from_virtual_source_port(vsp, traversed=[]):
+ """
+ Recursively resolve source ports over the virtual connections.
+ Keep track of traversed sources to avoid recursive loops.
+ """
+ if not vsp.get_parent().is_virtual_source(): return vsp
+ if vsp in traversed: raise Exception, 'Loop found when resolving virtual source %s'%vsp
+ try: return _get_source_from_virtual_source_port(
+ _get_source_from_virtual_sink_port(
+ filter(#get all virtual sinks with a matching stream id
+ lambda vs: vs.get_param('stream_id').get_value() == vsp.get_parent().get_param('stream_id').get_value(),
+ filter(#get all enabled blocks that are also virtual sinks
+ lambda b: b.is_virtual_sink(),
+ vsp.get_parent().get_parent().get_enabled_blocks(),
+ ),
+ )[0].get_sinks()[0]
+ ), traversed + [vsp],
+ )
+ except: raise Exception, 'Could not resolve source for virtual source port %s'%vsp
+
+def _get_sink_from_virtual_source_port(vsp):
+ """
+ Resolve the sink port that is connected to the given virtual source port.
+ Use the get sink from virtual sink to recursively resolve subsequent ports.
+ """
+ try: return _get_sink_from_virtual_sink_port(
+ vsp.get_enabled_connections()[0].get_sink()) # Could have many connections, but use first
+ except: raise Exception, 'Could not resolve source for virtual source port %s'%vsp
+
+def _get_sink_from_virtual_sink_port(vsp, traversed=[]):
+ """
+ Recursively resolve sink ports over the virtual connections.
+ Keep track of traversed sinks to avoid recursive loops.
+ """
+ if not vsp.get_parent().is_virtual_sink(): return vsp
+ if vsp in traversed: raise Exception, 'Loop found when resolving virtual sink %s'%vsp
+ try: return _get_sink_from_virtual_sink_port(
+ _get_sink_from_virtual_source_port(
+ filter(#get all virtual source with a matching stream id
+ lambda vs: vs.get_param('stream_id').get_value() == vsp.get_parent().get_param('stream_id').get_value(),
+ filter(#get all enabled blocks that are also virtual sinks
+ lambda b: b.is_virtual_source(),
+ vsp.get_parent().get_parent().get_enabled_blocks(),
+ ),
+ )[0].get_sources()[0]
+ ), traversed + [vsp],
+ )
+ except: raise Exception, 'Could not resolve source for virtual sink port %s'%vsp
+
+class Port(_Port, _GUIPort):
+
+ def __init__(self, block, n, dir):
+ """
+ Make a new port from nested data.
+ @param block the parent element
+ @param n the nested odict
+ @param dir the direction
+ """
+ self._n = n
+ if n['type'] == 'msg': n['key'] = 'msg'
+ if n['type'] == 'message': n['key'] = n['name']
+ if dir == 'source' and not n.find('key'):
+ n['key'] = str(block._source_count)
+ block._source_count += 1
+ if dir == 'sink' and not n.find('key'):
+ n['key'] = str(block._sink_count)
+ block._sink_count += 1
+ #build the port
+ _Port.__init__(
+ self,
+ block=block,
+ n=n,
+ dir=dir,
+ )
+ _GUIPort.__init__(self)
+ self._nports = n.find('nports') or ''
+ self._vlen = n.find('vlen') or ''
+ self._optional = bool(n.find('optional'))
+
+ def get_types(self): return Constants.TYPE_TO_SIZEOF.keys()
+
+ def is_type_empty(self): return not self._n['type']
+
+ def validate(self):
+ _Port.validate(self)
+ if not self.get_enabled_connections() and not self.get_optional():
+ self.add_error_message('Port is not connected.')
+ is_msg = (not self.get_type()) or (self.get_type() == "message")
+ if not self.is_source() and (not is_msg) and len(self.get_enabled_connections()) > 1:
+ self.add_error_message('Port has too many connections.')
+ #message port logic
+ if self.get_type() == 'msg':
+ if self.get_nports():
+ self.add_error_message('A port of type "msg" cannot have "nports" set.')
+ if self.get_vlen() != 1:
+ self.add_error_message('A port of type "msg" must have a "vlen" of 1.')
+
+ def rewrite(self):
+ """
+ Handle the port cloning for virtual blocks.
+ """
+ _Port.rewrite(self)
+ if self.is_type_empty():
+ try: #clone type and vlen
+ source = self.resolve_empty_type()
+ self._type = str(source.get_type())
+ self._vlen = str(source.get_vlen())
+ except: #reset type and vlen
+ self._type = ''
+ self._vlen = ''
+
+ def resolve_virtual_source(self):
+ if self.get_parent().is_virtual_sink(): return _get_source_from_virtual_sink_port(self)
+ if self.get_parent().is_virtual_source(): return _get_source_from_virtual_source_port(self)
+
+ def resolve_empty_type(self):
+ if self.is_sink():
+ try:
+ src = _get_source_from_virtual_sink_port(self)
+ if not src.is_type_empty(): return src
+ except: pass
+ sink = _get_sink_from_virtual_sink_port(self)
+ if not sink.is_type_empty(): return sink
+ if self.is_source():
+ try:
+ src = _get_source_from_virtual_source_port(self)
+ if not src.is_type_empty(): return src
+ except: pass
+ sink = _get_sink_from_virtual_source_port(self)
+ if not sink.is_type_empty(): return sink
+
+ def get_vlen(self):
+ """
+ Get the vector length.
+ If the evaluation of vlen cannot be cast to an integer, return 1.
+ @return the vector length or 1
+ """
+ vlen = self.get_parent().resolve_dependencies(self._vlen)
+ try: return int(self.get_parent().get_parent().evaluate(vlen))
+ except: return 1
+
+ def get_nports(self):
+ """
+ Get the number of ports.
+ If already blank, return a blank
+ If the evaluation of nports cannot be cast to an integer, return 1.
+ @return the number of ports or 1
+ """
+ nports = self.get_parent().resolve_dependencies(self._nports)
+ #return blank if nports is blank
+ if not nports: return ''
+ try:
+ nports = int(self.get_parent().get_parent().evaluate(nports))
+ if 0 < nports: return nports
+ except: return 1
+
+ def get_optional(self): return bool(self._optional)
+
+ def get_color(self):
+ """
+ Get the color that represents this port's type.
+ Codes differ for ports where the vec length is 1 or greater than 1.
+ @return a hex color code.
+ """
+ try:
+ color = Constants.TYPE_TO_COLOR[self.get_type()]
+ vlen = self.get_vlen()
+ if vlen == 1: return color
+ color_val = int(color[1:], 16)
+ r = (color_val >> 16) & 0xff
+ g = (color_val >> 8) & 0xff
+ b = (color_val >> 0) & 0xff
+ dark = (0, 0, 30, 50, 70)[min(4, vlen)]
+ r = max(r-dark, 0)
+ g = max(g-dark, 0)
+ b = max(b-dark, 0)
+ return '#%.2x%.2x%.2x'%(r, g, b)
+ except: return _Port.get_color(self)
+
+ def copy(self, new_key=None):
+ n = self._n.copy()
+ #remove nports from the key so the copy cannot be a duplicator
+ if n.has_key('nports'): n.pop('nports')
+ if new_key: n['key'] = new_key
+ return self.__class__(self.get_parent(), n, self._dir)
diff --git a/grc/python/__init__.py b/grc/python/__init__.py
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/grc/python/__init__.py
@@ -0,0 +1 @@
+
diff --git a/grc/python/block.dtd b/grc/python/block.dtd
new file mode 100644
index 000000000..292ea06cb
--- /dev/null
+++ b/grc/python/block.dtd
@@ -0,0 +1,57 @@
+<!--
+Copyright 2008 Free Software Foundation, Inc.
+This file is part of GNU Radio
+
+GNU Radio Companion 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 2
+of the License, or (at your option) any later version.
+
+GNU Radio Companion 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
+-->
+<!--
+ gnuradio_python.blocks.dtd
+ Josh Blum
+ The document type definition for blocks.
+ -->
+<!--
+ Top level element.
+ A block contains a name, ...parameters list, and list of IO ports.
+ -->
+<!ELEMENT block (name, key, category?, throttle?, import*, var_make?, make, callback*, param*, check*, sink*, source*, doc?, grc_source?)>
+<!--
+ Sub level elements.
+ -->
+<!ELEMENT param (name, key, value?, type, hide?, option*)>
+<!ELEMENT option (name, key, opt*)>
+<!ELEMENT sink (name, type, vlen?, nports?, optional?)>
+<!ELEMENT source (name, type, vlen?, nports?, optional?)>
+<!--
+ Bottom level elements.
+ Character data only.
+ -->
+<!ELEMENT category (#PCDATA)>
+<!ELEMENT import (#PCDATA)>
+<!ELEMENT doc (#PCDATA)>
+<!ELEMENT grc_source (#PCDATA)>
+<!ELEMENT name (#PCDATA)>
+<!ELEMENT key (#PCDATA)>
+<!ELEMENT check (#PCDATA)>
+<!ELEMENT opt (#PCDATA)>
+<!ELEMENT type (#PCDATA)>
+<!ELEMENT hide (#PCDATA)>
+<!ELEMENT vlen (#PCDATA)>
+<!ELEMENT nports (#PCDATA)>
+<!ELEMENT var_make (#PCDATA)>
+<!ELEMENT make (#PCDATA)>
+<!ELEMENT value (#PCDATA)>
+<!ELEMENT callback (#PCDATA)>
+<!ELEMENT optional (#PCDATA)>
+<!ELEMENT throttle (#PCDATA)>
diff --git a/grc/python/convert_hier.py b/grc/python/convert_hier.py
new file mode 100644
index 000000000..508ec63b2
--- /dev/null
+++ b/grc/python/convert_hier.py
@@ -0,0 +1,98 @@
+"""
+Copyright 2008-2011 Free Software Foundation, Inc.
+This file is part of GNU Radio
+
+GNU Radio Companion 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 2
+of the License, or (at your option) any later version.
+
+GNU Radio Companion 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
+"""
+
+from Constants import BLOCK_DTD
+from .. base import ParseXML
+from .. base import odict
+
+def convert_hier(flow_graph, python_file):
+ #extract info from the flow graph
+ input_sigs = flow_graph.get_io_signaturev('in')
+ output_sigs = flow_graph.get_io_signaturev('out')
+ input_msgp = flow_graph.get_msg_pad_sources();
+ output_msgp = flow_graph.get_msg_pad_sinks();
+ parameters = flow_graph.get_parameters()
+ block_key = flow_graph.get_option('id')
+ block_name = flow_graph.get_option('title') or flow_graph.get_option('id').replace('_', ' ').title()
+ block_category = flow_graph.get_option('category')
+ block_desc = flow_graph.get_option('description')
+ block_author = flow_graph.get_option('author')
+ #build the nested data
+ block_n = odict()
+ block_n['name'] = block_name
+ block_n['key'] = block_key
+ block_n['category'] = block_category
+ block_n['import'] = 'execfile("%s")'%python_file
+ #make data
+ if parameters: block_n['make'] = '%s(\n\t%s,\n)'%(
+ block_key,
+ ',\n\t'.join(['%s=$%s'%(param.get_id(), param.get_id()) for param in parameters]),
+ )
+ else: block_n['make'] = '%s()'%block_key
+ #callback data
+ block_n['callback'] = ['set_%s($%s)'%(param.get_id(), param.get_id()) for param in parameters]
+ #param data
+ params_n = list()
+ for param in parameters:
+ param_n = odict()
+ param_n['name'] = param.get_param('label').get_value() or param.get_id()
+ param_n['key'] = param.get_id()
+ param_n['value'] = param.get_param('value').get_value()
+ param_n['type'] = 'raw'
+ params_n.append(param_n)
+ block_n['param'] = params_n
+ #sink data stream ports
+ block_n['sink'] = list()
+ for input_sig in input_sigs:
+ sink_n = odict()
+ sink_n['name'] = input_sig['label']
+ sink_n['type'] = input_sig['type']
+ sink_n['vlen'] = input_sig['vlen']
+ if input_sig['optional']: sink_n['optional'] = '1'
+ block_n['sink'].append(sink_n)
+ #sink data msg ports
+ for input_sig in input_msgp:
+ sink_n = odict()
+ sink_n['name'] = input_sig.get_param("label").get_value();
+ sink_n['type'] = "message"
+ sink_n['optional'] = input_sig.get_param("optional").get_value();
+ block_n['sink'].append(sink_n)
+ #source data stream ports
+ block_n['source'] = list()
+ for output_sig in output_sigs:
+ source_n = odict()
+ source_n['name'] = output_sig['label']
+ source_n['type'] = output_sig['type']
+ source_n['vlen'] = output_sig['vlen']
+ if output_sig['optional']: source_n['optional'] = '1'
+ block_n['source'].append(source_n)
+ #source data msg ports
+ for output_sig in output_msgp:
+ source_n = odict()
+ source_n['name'] = output_sig.get_param("label").get_value();
+ source_n['type'] = "message"
+ source_n['optional'] = output_sig.get_param("optional").get_value();
+ block_n['source'].append(source_n)
+ #doc data
+ block_n['doc'] = "%s\n%s\n%s"%(block_author, block_desc, python_file)
+ block_n['grc_source'] = "%s"%(flow_graph.grc_file_path)
+ #write the block_n to file
+ xml_file = python_file + '.xml'
+ ParseXML.to_file({'block': block_n}, xml_file)
+ ParseXML.validate_dtd(xml_file, BLOCK_DTD)
diff --git a/grc/python/default_flow_graph.grc b/grc/python/default_flow_graph.grc
new file mode 100644
index 000000000..dea26f3a5
--- /dev/null
+++ b/grc/python/default_flow_graph.grc
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+<!--
+###################################################
+##Default Flow Graph:
+## include an options block and a variable for sample rate
+###################################################
+ -->
+<flow_graph>
+ <block>
+ <key>options</key>
+ <param>
+ <key>id</key>
+ <value>top_block</value>
+ </param>
+ <param>
+ <key>_coordinate</key>
+ <value>(10, 10)</value>
+ </param>
+ <param>
+ <key>_rotation</key>
+ <value>0</value>
+ </param>
+ </block>
+ <block>
+ <key>variable</key>
+ <param>
+ <key>id</key>
+ <value>samp_rate</value>
+ </param>
+ <param>
+ <key>value</key>
+ <value>32000</value>
+ </param>
+ <param>
+ <key>_coordinate</key>
+ <value>(10, 170)</value>
+ </param>
+ <param>
+ <key>_rotation</key>
+ <value>0</value>
+ </param>
+ </block>
+</flow_graph>
diff --git a/grc/python/expr_utils.py b/grc/python/expr_utils.py
new file mode 100644
index 000000000..a2e56eedf
--- /dev/null
+++ b/grc/python/expr_utils.py
@@ -0,0 +1,153 @@
+"""
+Copyright 2008-2011 Free Software Foundation, Inc.
+This file is part of GNU Radio
+
+GNU Radio Companion 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 2
+of the License, or (at your option) any later version.
+
+GNU Radio Companion 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
+"""
+
+import string
+VAR_CHARS = string.letters + string.digits + '_'
+
+class graph(object):
+ """
+ Simple graph structure held in a dictionary.
+ """
+
+ def __init__(self): self._graph = dict()
+
+ def __str__(self): return str(self._graph)
+
+ def add_node(self, node_key):
+ if self._graph.has_key(node_key): return
+ self._graph[node_key] = set()
+
+ def remove_node(self, node_key):
+ if not self._graph.has_key(node_key): return
+ for edges in self._graph.values():
+ if node_key in edges: edges.remove(node_key)
+ self._graph.pop(node_key)
+
+ def add_edge(self, src_node_key, dest_node_key):
+ self._graph[src_node_key].add(dest_node_key)
+
+ def remove_edge(self, src_node_key, dest_node_key):
+ self._graph[src_node_key].remove(dest_node_key)
+
+ def get_nodes(self): return self._graph.keys()
+
+ def get_edges(self, node_key): return self._graph[node_key]
+
+def expr_split(expr):
+ """
+ Split up an expression by non alphanumeric characters, including underscore.
+ Leave strings in-tact.
+ #TODO ignore escaped quotes, use raw strings.
+ @param expr an expression string
+ @return a list of string tokens that form expr
+ """
+ toks = list()
+ tok = ''
+ quote = ''
+ for char in expr:
+ if quote or char in VAR_CHARS:
+ if char == quote: quote = ''
+ tok += char
+ elif char in ("'", '"'):
+ toks.append(tok)
+ tok = char
+ quote = char
+ else:
+ toks.append(tok)
+ toks.append(char)
+ tok = ''
+ toks.append(tok)
+ return filter(lambda t: t, toks)
+
+def expr_replace(expr, replace_dict):
+ """
+ Search for vars in the expression and add the prepend.
+ @param expr an expression string
+ @param replace_dict a dict of find:replace
+ @return a new expression with the prepend
+ """
+ expr_splits = expr_split(expr)
+ for i, es in enumerate(expr_splits):
+ if es in replace_dict.keys():
+ expr_splits[i] = replace_dict[es]
+ return ''.join(expr_splits)
+
+def get_variable_dependencies(expr, vars):
+ """
+ Return a set of variables used in this expression.
+ @param expr an expression string
+ @param vars a list of variable names
+ @return a subset of vars used in the expression
+ """
+ expr_toks = expr_split(expr)
+ return set(filter(lambda v: v in expr_toks, vars))
+
+def get_graph(exprs):
+ """
+ Get a graph representing the variable dependencies
+ @param exprs a mapping of variable name to expression
+ @return a graph of variable deps
+ """
+ vars = exprs.keys()
+ #get dependencies for each expression, load into graph
+ var_graph = graph()
+ for var in vars: var_graph.add_node(var)
+ for var, expr in exprs.iteritems():
+ for dep in get_variable_dependencies(expr, vars):
+ if dep != var: var_graph.add_edge(dep, var)
+ return var_graph
+
+def sort_variables(exprs):
+ """
+ Get a list of variables in order of dependencies.
+ @param exprs a mapping of variable name to expression
+ @return a list of variable names
+ @throws Exception circular dependencies
+ """
+ var_graph = get_graph(exprs)
+ sorted_vars = list()
+ #determine dependency order
+ while var_graph.get_nodes():
+ #get a list of nodes with no edges
+ indep_vars = filter(lambda var: not var_graph.get_edges(var), var_graph.get_nodes())
+ if not indep_vars: raise Exception('circular dependency caught in sort_variables')
+ #add the indep vars to the end of the list
+ sorted_vars.extend(sorted(indep_vars))
+ #remove each edge-less node from the graph
+ for var in indep_vars: var_graph.remove_node(var)
+ return reversed(sorted_vars)
+
+def sort_objects(objects, get_id, get_expr):
+ """
+ Sort a list of objects according to their expressions.
+ @param objects the list of objects to sort
+ @param get_id the function to extract an id from the object
+ @param get_expr the function to extract an expression from the object
+ @return a list of sorted objects
+ """
+ id2obj = dict([(get_id(obj), obj) for obj in objects])
+ #map obj id to expression code
+ id2expr = dict([(get_id(obj), get_expr(obj)) for obj in objects])
+ #sort according to dependency
+ sorted_ids = sort_variables(id2expr)
+ #return list of sorted objects
+ return [id2obj[id] for id in sorted_ids]
+
+if __name__ == '__main__':
+ for i in sort_variables({'x':'1', 'y':'x+1', 'a':'x+y', 'b':'y+1', 'c':'a+b+x+y'}): print i
diff --git a/grc/python/extract_docs.py b/grc/python/extract_docs.py
new file mode 100644
index 000000000..33c404362
--- /dev/null
+++ b/grc/python/extract_docs.py
@@ -0,0 +1,66 @@
+"""
+Copyright 2008-2011 Free Software Foundation, Inc.
+This file is part of GNU Radio
+
+GNU Radio Companion 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 2
+of the License, or (at your option) any later version.
+
+GNU Radio Companion 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
+"""
+
+import re
+
+def _extract(key):
+ """
+ Extract the documentation from the python __doc__ strings.
+ If multiple modules match, combine the docs.
+ @param key the block key
+ @return a string with documentation
+ """
+ #extract matches
+ try:
+ module_name, constructor_name = key.split('_', 1)
+ module = __import__('gnuradio.'+module_name)
+ module = getattr(module, module_name)
+ except ImportError:
+ try:
+ module_name, constructor_name = key.split('_', 1)
+ module = __import__(module_name)
+ except: return ''
+ except:
+ return ''
+ pattern = constructor_name.replace('_', '_*').replace('x', '\w')
+ pattern_matcher = re.compile('^%s\w*$'%pattern)
+ matches = filter(lambda x: pattern_matcher.match(x), dir(module))
+ #combine all matches
+ doc_strs = list()
+ for match in matches:
+ try:
+ title = ' --- ' + match + ' --- '
+ doc_strs.append('\n\n'.join([title, getattr(module, match).__doc__]).strip())
+ except: pass
+ return '\n\n'.join(doc_strs)
+
+_docs_cache = dict()
+def extract(key):
+ """
+ Call the private extract and cache the result.
+ @param key the block key
+ @return a string with documentation
+ """
+ if not _docs_cache.has_key(key):
+ _docs_cache[key] = _extract(key)
+ return _docs_cache[key]
+
+if __name__ == '__main__':
+ import sys
+ print extract(sys.argv[1])
diff --git a/grc/python/flow_graph.tmpl b/grc/python/flow_graph.tmpl
new file mode 100644
index 000000000..163e7f76a
--- /dev/null
+++ b/grc/python/flow_graph.tmpl
@@ -0,0 +1,311 @@
+#!/usr/bin/env python
+########################################################
+##Cheetah template - gnuradio_python
+##
+##@param imports the import statements
+##@param flow_graph the flow_graph
+##@param variables the variable blocks
+##@param parameters the paramater blocks
+##@param blocks the signal blocks
+##@param connections the connections
+##@param messages the msg type connections
+##@param generate_options the type of flow graph
+##@param var_id2cbs variable id map to callback strings
+########################################################
+#def indent($code)
+#set $code = '\n\t\t'.join(str($code).splitlines())
+$code#slurp
+#end def
+#import time
+#set $DIVIDER = '#'*50
+$DIVIDER
+# Gnuradio Python Flow Graph
+# Title: $title
+#if $flow_graph.get_option('author')
+# Author: $flow_graph.get_option('author')
+#end if
+#if $flow_graph.get_option('description')
+# Description: $flow_graph.get_option('description')
+#end if
+# Generated: $time.ctime()
+$DIVIDER
+
+########################################################
+##Create Imports
+########################################################
+#for $imp in $imports
+$imp
+#end for
+
+########################################################
+##Create Class
+## Write the class declaration for a top or hier block.
+## The parameter names are the arguments to __init__.
+## Determine the absolute icon path (wx gui only).
+## Setup the IO signature (hier block only).
+########################################################
+#set $class_name = $flow_graph.get_option('id')
+#set $param_str = ', '.join(['self'] + ['%s=%s'%(param.get_id(), param.get_make()) for param in $parameters])
+#if $generate_options == 'wx_gui'
+ #import gtk
+ #set $icon = gtk.IconTheme().lookup_icon('gnuradio-grc', 32, 0)
+class $(class_name)(grc_wxgui.top_block_gui):
+
+ def __init__($param_str):
+ grc_wxgui.top_block_gui.__init__(self, title="$title")
+ #if $icon
+ _icon_path = "$icon.get_filename()"
+ self.SetIcon(wx.Icon(_icon_path, wx.BITMAP_TYPE_ANY))
+ #end if
+#elif $generate_options == 'qt_gui'
+class $(class_name)(gr.top_block, Qt.QWidget):
+
+ def __init__($param_str):
+ gr.top_block.__init__(self, "$title")
+ Qt.QWidget.__init__(self)
+ self.setWindowTitle("$title")
+ self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))
+ self.top_scroll_layout = Qt.QVBoxLayout()
+ self.setLayout(self.top_scroll_layout)
+ self.top_scroll = Qt.QScrollArea()
+ self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)
+ self.top_scroll_layout.addWidget(self.top_scroll)
+ self.top_scroll.setWidgetResizable(True)
+ self.top_widget = Qt.QWidget()
+ self.top_scroll.setWidget(self.top_widget)
+ self.top_layout = Qt.QVBoxLayout(self.top_widget)
+ self.top_grid_layout = Qt.QGridLayout()
+ self.top_layout.addLayout(self.top_grid_layout)
+
+#elif $generate_options == 'no_gui'
+class $(class_name)(gr.top_block):
+
+ def __init__($param_str):
+ gr.top_block.__init__(self, "$title")
+#elif $generate_options == 'hb'
+ #set $in_sigs = $flow_graph.get_io_signaturev('in')
+ #set $out_sigs = $flow_graph.get_io_signaturev('out')
+class $(class_name)(gr.hier_block2):
+#def make_io_sig($io_sigs)
+ #set $size_strs = ['%s*%s'%(io_sig['size'], io_sig['vlen']) for io_sig in $io_sigs]
+ #if len($io_sigs) == 0
+gr.io_signature(0, 0, 0)#slurp
+ #elif len($io_sigs) == 1
+gr.io_signature(1, 1, $size_strs[0])#slurp
+ #else
+gr.io_signaturev($(len($io_sigs)), $(len($io_sigs)), [$(', '.join($size_strs))])#slurp
+ #end if
+#end def
+
+ def __init__($param_str):
+ gr.hier_block2.__init__(
+ self, "$title",
+ $make_io_sig($in_sigs),
+ $make_io_sig($out_sigs),
+ )
+#end if
+########################################################
+##Create Parameters
+## Set the parameter to a property of self.
+########################################################
+#if $parameters
+
+ $DIVIDER
+ # Parameters
+ $DIVIDER
+#end if
+#for $param in $parameters
+ $indent($param.get_var_make())
+#end for
+########################################################
+##Create Variables
+########################################################
+#if $variables
+
+ $DIVIDER
+ # Variables
+ $DIVIDER
+#end if
+#for $var in $variables
+ $indent($var.get_var_make())
+#end for
+########################################################
+##Create Message Queues
+########################################################
+#if $messages
+
+ $DIVIDER
+ # Message Queues
+ $DIVIDER
+#end if
+#for $msg in $messages
+ $(msg.get_source().get_parent().get_id())_msgq_out = $(msg.get_sink().get_parent().get_id())_msgq_in = gr.msg_queue(2)
+#end for
+########################################################
+##Create Blocks
+########################################################
+#if $blocks
+
+ $DIVIDER
+ # Blocks
+ $DIVIDER
+#end if
+#for $blk in filter(lambda b: b.get_make(), $blocks)
+ #if $blk in $variables
+ $indent($blk.get_make())
+ #else
+ self.$blk.get_id() = $indent($blk.get_make())
+ #end if
+#end for
+########################################################
+##Create Connections
+## The port name should be the id of the parent block.
+## However, port names for IO pads should be self.
+########################################################
+#def make_port_sig($port)
+ #if $port.get_parent().get_key() == 'pad_source'
+(self, $flow_graph.get_pad_sources().index($port.get_parent()))#slurp
+ #elif $port.get_parent().get_key() == 'pad_sink'
+(self, $flow_graph.get_pad_sinks().index($port.get_parent()))#slurp
+ #else
+(self.$port.get_parent().get_id(), $port.get_key())#slurp
+ #end if
+#end def
+#if $connections
+
+ $DIVIDER
+ # Connections
+ $DIVIDER
+#end if
+#for $con in $connections
+ #set $source = $con.get_source()
+ #set $sink = $con.get_sink()
+ ##resolve virtual sources to the actual sources
+ #if $source.get_parent().is_virtual_source()
+ #set $source = $source.resolve_virtual_source()
+ #end if
+ ##do not generate connections with virtual sinks
+ #if not $sink.get_parent().is_virtual_sink()
+ self.connect($make_port_sig($source), $make_port_sig($sink))
+ #end if
+#end for
+
+########################################################
+##Create Asynch Message Connections
+########################################################
+#if $messages2
+ $DIVIDER
+ # Asynch Message Connections
+ $DIVIDER
+#end if
+#for $msg in $messages2
+ #set $sr = $msg.get_source()
+ #set $source = "self.%s"%($sr.get_parent().get_id())
+ #set $source_port = $sr.get_name();
+ #if $sr.get_parent().get_key() == "pad_source"
+ #set $source = "self"
+ #set $source_port = $sr.get_parent().get_param("label").get_value();
+ #end if
+ #set $sk = $msg.get_sink()
+ #set $sink = "self.%s"%($sk.get_parent().get_id())
+ #set $sink_port = $sk.get_name();
+ #if $sk.get_parent().get_key() == "pad_sink"
+ #set $sink = "self"
+ #set $sink_port = $sk.get_parent().get_param("label").get_value();
+ #end if
+ self.msg_connect($source, "$source_port", $sink, "$sink_port")
+#end for
+
+########################################################
+##Create Callbacks
+## Write a set method for this variable that calls the callbacks
+########################################################
+#for $var in $parameters + $variables
+ #set $id = $var.get_id()
+ def get_$(id)(self):
+ return self.$id
+
+ def set_$(id)(self, $id):
+ self.$id = $id
+ #for $callback in $var_id2cbs[$id]
+ $indent($callback)
+ #end for
+
+#end for
+########################################################
+##Create Main
+## For top block code, generate a main routine.
+## Instantiate the top block and run as gui or cli.
+########################################################
+#def make_default($type, $param)
+ #if $type == 'eng_float'
+eng_notation.num_to_str($param.get_make())#slurp
+ #else
+$param.get_make()#slurp
+ #end if
+#end def
+#def make_short_id($param)
+ #set $short_id = $param.get_param('short_id').get_evaluated()
+ #if $short_id
+ #set $short_id = '-' + $short_id
+ #end if
+$short_id#slurp
+#end def
+#if $generate_options != 'hb'
+if __name__ == '__main__':
+ parser = OptionParser(option_class=eng_option, usage="%prog: [options]")
+ #set $params_eq_list = list()
+ #for $param in $parameters
+ #set $type = $param.get_param('type').get_value()
+ #if $type
+ #silent $params_eq_list.append('%s=options.%s'%($param.get_id(), $param.get_id()))
+ parser.add_option("$make_short_id($param)", "--$param.get_id().replace('_', '-')", dest="$param.get_id()", type="$type", default=$make_default($type, $param),
+ help="Set $($param.get_param('label').get_evaluated() or $param.get_id()) [default=%default]")
+ #end if
+ #end for
+ (options, args) = parser.parse_args()
+ #if $flow_graph.get_option('realtime_scheduling')
+ if gr.enable_realtime_scheduling() != gr.RT_OK:
+ print "Error: failed to enable realtime scheduling."
+ #end if
+ #if $generate_options == 'wx_gui'
+ tb = $(class_name)($(', '.join($params_eq_list)))
+ #if $flow_graph.get_option('max_nouts')
+ tb.Run($flow_graph.get_option('run'), $flow_graph.get_option('max_nouts'))
+ #else
+ tb.Run($flow_graph.get_option('run'))
+ #end if
+ #elif $generate_options == 'qt_gui'
+ qapp = Qt.QApplication(sys.argv)
+ tb = $(class_name)($(', '.join($params_eq_list)))
+ #if $flow_graph.get_option('run')
+ #if $flow_graph.get_option('max_nouts')
+ tb.start($flow_graph.get_option('max_nouts'))
+ #else
+ tb.start()
+ #end if
+ #end if
+ tb.show()
+ qapp.exec_()
+ tb.stop()
+ #elif $generate_options == 'no_gui'
+ tb = $(class_name)($(', '.join($params_eq_list)))
+ #set $run_options = $flow_graph.get_option('run_options')
+ #if $run_options == 'prompt'
+ #if $flow_graph.get_option('max_nouts')
+ tb.start($flow_graph.get_option('max_nouts'))
+ #else
+ tb.start()
+ #end if
+ raw_input('Press Enter to quit: ')
+ tb.stop()
+ #elif $run_options == 'run'
+ #if $flow_graph.get_option('max_nouts')
+ tb.run($flow_graph.get_option('max_nouts'))
+ #else
+ tb.run()
+ #end if
+ #end if
+ #end if
+#end if
+