summaryrefslogtreecommitdiff
path: root/grc/python
diff options
context:
space:
mode:
Diffstat (limited to 'grc/python')
-rw-r--r--grc/python/Block.py153
-rw-r--r--grc/python/Connection.py34
-rw-r--r--grc/python/Constants.py63
-rw-r--r--grc/python/FlowGraph.py155
-rw-r--r--grc/python/Generator.py125
-rw-r--r--grc/python/Makefile.am43
-rw-r--r--grc/python/Param.py380
-rw-r--r--grc/python/Platform.py81
-rw-r--r--grc/python/Port.py129
-rw-r--r--grc/python/__init__.py1
-rw-r--r--grc/python/block.dtd55
-rw-r--r--grc/python/convert_hier.py79
-rw-r--r--grc/python/default_flow_graph.grc43
-rw-r--r--grc/python/expr_utils.py137
-rw-r--r--grc/python/extract_docs.py90
-rw-r--r--grc/python/flow_graph.tmpl205
16 files changed, 1773 insertions, 0 deletions
diff --git a/grc/python/Block.py b/grc/python/Block.py
new file mode 100644
index 000000000..a9e999491
--- /dev/null
+++ b/grc/python/Block.py
@@ -0,0 +1,153 @@
+"""
+Copyright 2008, 2009 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
+import extract_docs
+
+class Block(_Block):
+
+ ##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')
+ #build the block
+ _Block.__init__(
+ self,
+ flow_graph=flow_graph,
+ n=n,
+ )
+
+ def validate(self):
+ """
+ Validate this block.
+ Call the base class validate.
+ Evaluate the checks: each check must evaluate to True.
+ Adjust the nports.
+ """
+ _Block.validate(self)
+ #evaluate the checks
+ for check in self._checks:
+ check_res = self.resolve_dependencies(check)
+ try:
+ check_eval = self.get_parent().evaluate(check_res)
+ try: assert check_eval
+ except AssertionError: self.add_error_message('Check "%s" failed.'%check)
+ except: self.add_error_message('Check "%s" did not evaluate.'%check)
+ #adjust nports
+ for ports, Port in (
+ (self._sources, self.get_parent().get_parent().Source),
+ (self._sinks, self.get_parent().get_parent().Sink),
+ ):
+ #how many ports?
+ num_ports = len(ports)
+ #do nothing for 0 ports
+ if not num_ports: continue
+ #get the nports setting
+ port0 = ports[str(0)]
+ nports = port0.get_nports()
+ #do nothing for no nports
+ if not nports: continue
+ #do nothing if nports is already num ports
+ if nports == num_ports: continue
+ #remove excess ports and connections
+ if nports < num_ports:
+ #remove the connections
+ for key in map(str, range(nports, num_ports)):
+ port = ports[key]
+ for connection in port.get_connections():
+ self.get_parent().remove_element(connection)
+ #remove the ports
+ for key in map(str, range(nports, num_ports)): ports.pop(key)
+ continue
+ #add more ports
+ if nports > num_ports:
+ for key in map(str, range(num_ports, nports)):
+ n = port0._n
+ n['key'] = key
+ port = Port(self, n)
+ ports[key] = port
+ 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 both port0
+ nports_str = \
+ (self.get_sinks() and self.get_sinks()[0]._nports or '') + \
+ (self.get_sources() and self.get_sources()[0]._nports or '')
+ #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
+ assert 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_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 callback.startswith('self.'): return callback
+ return 'self.%s.%s'%(self.get_id(), callback)
+ return map(make_callback, self._callbacks)
diff --git a/grc/python/Connection.py b/grc/python/Connection.py
new file mode 100644
index 000000000..d8a894bb1
--- /dev/null
+++ b/grc/python/Connection.py
@@ -0,0 +1,34 @@
+"""
+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
+"""
+
+from .. base.Connection import Connection as _Connection
+
+class Connection(_Connection):
+
+ def validate(self):
+ """
+ Validate the connections.
+ The ports must match in type and vector length.
+ """
+ _Connection.validate(self) #checks type
+ #check vector length
+ source_vlen = self.get_source().get_vlen()
+ sink_vlen = self.get_sink().get_vlen()
+ try: assert source_vlen == sink_vlen
+ except AssertionError: self.add_error_message('Source vector length "%s" does not match sink vector length "%s".'%(source_vlen, sink_vlen))
diff --git a/grc/python/Constants.py b/grc/python/Constants.py
new file mode 100644
index 000000000..5f203237f
--- /dev/null
+++ b/grc/python/Constants.py
@@ -0,0 +1,63 @@
+"""
+Copyright 2008, 2009 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 stat
+from gnuradio import gr
+
+_gr_prefs = gr.prefs()
+
+PYEXEC = os.environ.get('PYTHONW', _gr_prefs.get_string('grc', 'pythonw', ''))
+
+#setup paths
+PATH_SEP = ':'
+DOCS_DIR = os.environ.get('GR_DOC_DIR', _gr_prefs.get_string('grc', 'doc_dir', ''))
+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')
+
+#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'
diff --git a/grc/python/FlowGraph.py b/grc/python/FlowGraph.py
new file mode 100644
index 000000000..47089a305
--- /dev/null
+++ b/grc/python/FlowGraph.py
@@ -0,0 +1,155 @@
+"""
+Copyright 2008, 2009 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 Block import Block
+from Connection import Connection
+import re
+
+_variable_matcher = re.compile('^(variable\w*)$')
+_parameter_matcher = re.compile('^(parameter)$')
+
+class FlowGraph(_FlowGraph):
+
+ _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
+ """
+ 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_signature(self, pad_key):
+ """
+ Get an io signature for this flow graph.
+ The pad key determines the directionality of the io signature.
+ @param pad_key a string of pad_source or pad_sink
+ @return a dict with: type, nports, vlen, size
+ """
+ pads = filter(lambda b: b.get_key() == pad_key, self.get_enabled_blocks())
+ if not pads: return {
+ 'nports': '0',
+ 'type': '',
+ 'vlen': '0',
+ 'size': '0',
+ }
+ pad = pads[0] #take only the first, user should not have more than 1
+ #load io signature
+ return {
+ 'nports': str(pad.get_param('nports').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'),
+ }
+
+ def get_input_signature(self):
+ """
+ Get the io signature for the input side of this flow graph.
+ The io signature with be "0", "0" if no pad source is present.
+ @return a string tuple of type, num_ports, port_size
+ """
+ return self._get_io_signature('pad_source')
+
+ def get_output_signature(self):
+ """
+ Get the io signature for the output side of this flow graph.
+ The io signature with be "0", "0" if no pad sink is present.
+ @return a string tuple of type, num_ports, port_size
+ """
+ return self._get_io_signature('pad_sink')
+
+ 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())
+ #map var id to variable block
+ id2var = dict([(var.get_id(), var) for var in variables])
+ #map var id to variable code
+ #variable code is a concatenation of all param code (without the id param)
+ id2expr = dict([(var.get_id(), var.get_var_make()) for var in variables])
+ #sort according to dependency
+ sorted_ids = expr_utils.sort_variables(id2expr)
+ #create list of sorted variable blocks
+ variables = [id2var[id] for id in sorted_ids]
+ return variables
+
+ 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 evaluate(self, expr):
+ """
+ Evaluate the expression.
+ @param expr the string expression
+ @throw Exception bad expression
+ @return the evaluated data
+ """
+ if self.is_flagged():
+ self.deflag()
+ #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..cde7dc3d4
--- /dev/null
+++ b/grc/python/Generator.py
@@ -0,0 +1,125 @@
+"""
+Copyright 2008, 2009 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 subprocess
+from Cheetah.Template import Template
+import expr_utils
+from Constants import \
+ TOP_BLOCK_FILE_MODE, HIER_BLOCK_FILE_MODE, \
+ HIER_BLOCKS_LIB_DIR, PYEXEC, \
+ 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)
+ 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
+ all_keys = ' '.join(map(lambda b: b.get_key(), self._flow_graph.get_enabled_blocks()))
+ if ('usrp' not in all_keys) and ('audio' not in all_keys) and ('throttle' not in all_keys) 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
+ """
+ #execute
+ cmds = [PYEXEC, '-u', self.get_file_path()] #-u is unbuffered stdio
+ if self._generate_options == 'no_gui':
+ 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 variables with controls
+ controls = filter(lambda v: v.get_make(), variables)
+ #list of blocks not including variables and imports and parameters and disabled
+ blocks = sorted(self._flow_graph.get_enabled_blocks(), lambda x, y: cmp(x.get_id(), y.get_id()))
+ probes = filter(lambda b: b.get_key().startswith('probe_'), blocks) #ensure probes are last in the block list
+ blocks = filter(lambda b: b not in (imports + parameters + variables + probes), blocks) + probes
+ #list of connections where each endpoint is enabled
+ connections = 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,
+ 'controls': controls,
+ 'parameters': parameters,
+ 'blocks': blocks,
+ 'connections': connections,
+ '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/Makefile.am b/grc/python/Makefile.am
new file mode 100644
index 000000000..e6d253f5c
--- /dev/null
+++ b/grc/python/Makefile.am
@@ -0,0 +1,43 @@
+#
+# 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.
+#
+
+include $(top_srcdir)/grc/Makefile.inc
+
+ourpythondir = $(grc_src_prefix)/python
+ourpython_PYTHON = \
+ 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
+
+ourdatadir = $(grc_src_prefix)/python
+dist_ourdata_DATA = \
+ block.dtd \
+ default_flow_graph.grc \
+ flow_graph.tmpl
diff --git a/grc/python/Param.py b/grc/python/Param.py
new file mode 100644
index 000000000..8b5efc97f
--- /dev/null
+++ b/grc/python/Param.py
@@ -0,0 +1,380 @@
+"""
+Copyright 2008, 2009 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.Param import Param as _Param, 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)$')
+
+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.entry.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):
+
+ _init = False
+ _hostage_cells = list()
+
+ ##possible param types
+ TYPES = _Param.TYPES + [
+ 'complex', 'real', 'int',
+ 'complex_vector', 'real_vector', 'int_vector',
+ 'hex', 'string', 'bool',
+ 'file_open', 'file_save',
+ 'id',
+ 'grid_pos', 'import',
+ ]
+
+ def __repr__(self):
+ """
+ Get the repr (nice string format) for this param.
+ @return the string representation
+ """
+ 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
+ max_len = max(27 - len(self.get_name()), 3)
+ 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
+ ##################################################
+ # truncate
+ ##################################################
+ if len(dt_str) > max_len:
+ if truncate < 0: #front truncate
+ dt_str = '...' + dt_str[3-max_len:]
+ elif truncate == 0: #center truncate
+ dt_str = dt_str[:max_len/2 -3] + '...' + dt_str[-max_len/2:]
+ elif truncate > 0: #rear truncate
+ dt_str = dt_str[:max_len-3] + '...'
+ return dt_str
+
+ def get_input_class(self):
+ if self.get_type() in ('file_open', 'file_save'): return FileParam
+ return _Param.get_input_class(self)
+
+ 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,
+ 'grid_pos': 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:
+ assert int(self.get_evaluated()) == 1
+ return 'part'
+ except: pass
+ #hide empty grid positions
+ if self.get_key() == 'grid_pos' 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)
+ assert isinstance(e, str)
+ return e
+ 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', 'complex_vector', 'real_vector', 'int_vector', '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: %s'%(v, e)
+ #raise an exception if the data is invalid
+ if t == 'raw': return e
+ elif t == 'complex':
+ try: assert isinstance(e, COMPLEX_TYPES)
+ except AssertionError: raise Exception, 'Expression "%s" is invalid for type complex.'%str(e)
+ return e
+ elif t == 'real':
+ try: assert isinstance(e, REAL_TYPES)
+ except AssertionError: raise Exception, 'Expression "%s" is invalid for type real.'%str(e)
+ return e
+ elif t == 'int':
+ try: assert isinstance(e, INT_TYPES)
+ except AssertionError: raise Exception, 'Expression "%s" is invalid for type integer.'%str(e)
+ return e
+ #########################
+ # Numeric Vector Types
+ #########################
+ elif t == 'complex_vector':
+ if not isinstance(e, VECTOR_TYPES):
+ self._lisitify_flag = True
+ e = [e]
+ try:
+ for ei in e: assert isinstance(ei, COMPLEX_TYPES)
+ except AssertionError: 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]
+ try:
+ for ei in e: assert isinstance(ei, REAL_TYPES)
+ except AssertionError: 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]
+ try:
+ for ei in e: assert isinstance(ei, INT_TYPES)
+ except AssertionError: raise Exception, 'Expression "%s" is invalid for type integer vector.'%str(e)
+ return e
+ elif t == 'hex': return hex(e)
+ elif t == 'bool':
+ try: assert isinstance(e, bool)
+ except AssertionError: raise Exception, 'Expression "%s" is invalid for type bool.'%str(e)
+ return e
+ else: raise TypeError, 'Type "%s" not handled'%t
+ #########################
+ # 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?
+ try: assert _check_id_matcher.match(v)
+ except AssertionError: raise Exception, 'ID "%s" must begin with a letter and may contain letters, numbers, and underscores.'%v
+ params = self.get_all_params('id')
+ keys = [param.get_value() for param in params]
+ try: assert keys.count(v) <= 1 #id should only appear once, or zero times if block is disabled
+ except: raise Exception, 'ID "%s" is not unique.'%v
+ try: assert v not in ID_BLACKLIST
+ except: raise Exception, 'ID "%s" is blacklisted.'%v
+ return v
+ #########################
+ # Grid Position Type
+ #########################
+ elif t == 'grid_pos':
+ if not v: return '' #allow for empty grid pos
+ e = self.get_parent().get_parent().evaluate(v)
+ try:
+ assert isinstance(e, (list, tuple)) and len(e) == 4
+ for ei in e: assert isinstance(ei, int)
+ except AssertionError: raise Exception, 'A grid position must be a list of 4 integers.'
+ row, col, row_span, col_span = e
+ #check row, col
+ try: assert row >= 0 and col >= 0
+ except AssertionError: raise Exception, 'Row and column must be non-negative.'
+ #check row span, col span
+ try: assert row_span > 0 and col_span > 0
+ except AssertionError: raise Exception, 'Row and column span must be greater than zero.'
+ #calculate hostage cells
+ for r in range(row_span):
+ for c in range(col_span):
+ self._hostage_cells.append((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 cell in param._hostage_cells:
+ if cell in self._hostage_cells: raise Exception, 'Another graphical element is using cell "%s".'%str(cell)
+ return e
+ #########################
+ # 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.
+ @return a string representing the code
+ """
+ #run init tasks in evaluate
+ #such as setting flags
+ if not self._init: self.evaluate()
+ v = self.get_value()
+ t = self.get_type()
+ if t in ('string', 'file_open', 'file_save'): #string types
+ if self._stringify_flag: return '"%s"'%v.replace('"', '\"')
+ else: return v
+ elif t in ('complex_vector', 'real_vector', 'int_vector'): #vector types
+ 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..8718fe955
--- /dev/null
+++ b/grc/python/Platform.py
@@ -0,0 +1,81 @@
+"""
+Copyright 2008, 2009 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 .. import VERSION #TEMP: until gnuradio has __version__
+from .. base.Platform import Platform as _Platform
+from FlowGraph import FlowGraph as _FlowGraph
+from Connection import Connection as _Connection
+from Block import Block as _Block
+from Port import Source,Sink
+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 = (#title, #color spec
+ ('Complex', Constants.COMPLEX_COLOR_SPEC),
+ ('Float', Constants.FLOAT_COLOR_SPEC),
+ ('Integer', Constants.INT_COLOR_SPEC),
+ ('Short', Constants.SHORT_COLOR_SPEC),
+ ('Byte', Constants.BYTE_COLOR_SPEC),
+ ('Complex Vector', Constants.COMPLEX_VECTOR_COLOR_SPEC),
+ ('Float Vector', Constants.FLOAT_VECTOR_COLOR_SPEC),
+ ('Integer Vector', Constants.INT_VECTOR_COLOR_SPEC),
+ ('Short Vector', Constants.SHORT_VECTOR_COLOR_SPEC),
+ ('Byte Vector', Constants.BYTE_VECTOR_COLOR_SPEC),
+ ('Wildcard Type', Constants.WILDCARD_COLOR_SPEC),
+)
+
+class Platform(_Platform):
+
+ 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=VERSION,
+ key='grc',
+ license=__doc__.strip(),
+ website='http://gnuradio.org/trac/wiki/GNURadioCompanion',
+ block_paths=block_paths,
+ block_dtd=BLOCK_DTD,
+ default_flow_graph=DEFAULT_FLOW_GRAPH,
+ generator=Generator,
+ colors=COLORS,
+ )
+
+ ##############################################
+ # Constructors
+ ##############################################
+ FlowGraph = _FlowGraph
+ Connection = _Connection
+ Block = _Block
+ Source = Source
+ Sink = Sink
+ Param = _Param
diff --git a/grc/python/Port.py b/grc/python/Port.py
new file mode 100644
index 000000000..5a2b047f0
--- /dev/null
+++ b/grc/python/Port.py
@@ -0,0 +1,129 @@
+"""
+Copyright 2008, 2009 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
+import Constants
+
+class Port(_Port):
+
+ ##possible port types
+ TYPES = ['complex', 'float', 'int', 'short', 'byte']
+
+ def __init__(self, block, n):
+ """
+ Make a new port from nested data.
+ @param block the parent element
+ @param n the nested odict
+ @return a new port
+ """
+ vlen = n.find('vlen') or '1'
+ nports = n.find('nports') or ''
+ optional = n.find('optional') or ''
+ #build the port
+ _Port.__init__(
+ self,
+ block=block,
+ n=n,
+ )
+ self._nports = nports
+ self._vlen = vlen
+ self._optional = bool(optional)
+
+ def validate(self):
+ _Port.validate(self)
+ try: assert self.get_enabled_connections() or self.get_optional()
+ except AssertionError: self.add_error_message('Port is not connected.')
+ try: assert self.is_source() or len(self.get_enabled_connections()) <= 1
+ except AssertionError: self.add_error_message('Port has too many connections.')
+
+ 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))
+ assert 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:
+ if self.get_vlen() == 1:
+ return {#vlen is 1
+ 'complex': Constants.COMPLEX_COLOR_SPEC,
+ 'float': Constants.FLOAT_COLOR_SPEC,
+ 'int': Constants.INT_COLOR_SPEC,
+ 'short': Constants.SHORT_COLOR_SPEC,
+ 'byte': Constants.BYTE_COLOR_SPEC,
+ }[self.get_type()]
+ return {#vlen is non 1
+ 'complex': Constants.COMPLEX_VECTOR_COLOR_SPEC,
+ 'float': Constants.FLOAT_VECTOR_COLOR_SPEC,
+ 'int': Constants.INT_VECTOR_COLOR_SPEC,
+ 'short': Constants.SHORT_VECTOR_COLOR_SPEC,
+ 'byte': Constants.BYTE_VECTOR_COLOR_SPEC,
+ }[self.get_type()]
+ except: return _Port.get_color(self)
+
+class Source(Port):
+
+ def __init__(self, block, n):
+ self._n = n #save n
+ #key is port index
+ n['key'] = str(block._source_count)
+ block._source_count = block._source_count + 1
+ Port.__init__(self, block, n)
+
+ def __del__(self):
+ self.get_parent()._source_count = self.get_parent()._source_count - 1
+
+class Sink(Port):
+
+ def __init__(self, block, n):
+ self._n = n #save n
+ #key is port index
+ n['key'] = str(block._sink_count)
+ block._sink_count = block._sink_count + 1
+ Port.__init__(self, block, n)
+
+ def __del__(self):
+ self.get_parent()._sink_count = self.get_parent()._sink_count - 1
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..7c6c39811
--- /dev/null
+++ b/grc/python/block.dtd
@@ -0,0 +1,55 @@
+<!--
+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?, import*, var_make?, make, callback*, param*, check*, sink*, source*, doc?)>
+<!--
+ 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 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)>
diff --git a/grc/python/convert_hier.py b/grc/python/convert_hier.py
new file mode 100644
index 000000000..bdafbcbc1
--- /dev/null
+++ b/grc/python/convert_hier.py
@@ -0,0 +1,79 @@
+"""
+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
+"""
+
+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_sig = flow_graph.get_input_signature()
+ output_sig = flow_graph.get_output_signature()
+ parameters = flow_graph.get_parameters()
+ block_key = flow_graph.get_option('id')
+ block_name = flow_graph.get_option('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
+ if int(input_sig['nports']):
+ sink_n = odict()
+ sink_n['name'] = 'in'
+ sink_n['type'] = input_sig['type']
+ sink_n['vlen'] = input_sig['vlen']
+ sink_n['nports'] = input_sig['nports']
+ block_n['sink'] = sink_n
+ #source data
+ if int(output_sig['nports']):
+ source_n = odict()
+ source_n['name'] = 'out'
+ source_n['type'] = output_sig['type']
+ source_n['vlen'] = output_sig['vlen']
+ source_n['nports'] = output_sig['nports']
+ block_n['source'] = source_n
+ #doc data
+ block_n['doc'] = "%s\n%s\n%s"%(block_author, block_desc, python_file)
+ #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..1880c8f9c
--- /dev/null
+++ b/grc/python/expr_utils.py
@@ -0,0 +1,137 @@
+"""
+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
+"""
+
+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 AssertionError 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())
+ assert indep_vars
+ #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)
+
+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..fa9140bdf
--- /dev/null
+++ b/grc/python/extract_docs.py
@@ -0,0 +1,90 @@
+"""
+Copyright 2008, 2009 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 DOCS_DIR
+from lxml import etree
+import os
+import re
+
+DOXYGEN_NAME_XPATH = '/doxygen/compounddef/compoundname'
+DOXYGEN_BRIEFDESC_GR_XPATH = '/doxygen/compounddef/briefdescription'
+DOXYGEN_DETAILDESC_GR_XPATH = '/doxygen/compounddef/detaileddescription'
+
+def extract_txt(xml):
+ """
+ Recursivly pull the text out of an xml tree.
+ @param xml the xml tree
+ @return a string
+ """
+ text = (xml.text or '').replace('\n', '')
+ tail = (xml.tail or '').replace('\n', '')
+ if xml.tag == 'para': tail += '\n\n'
+ if xml.tag == 'linebreak': text += '\n'
+ if xml.tag == 'parametername': text += ': '
+ return text + ''.join(
+ map(lambda x: extract_txt(x), xml)
+ ) + tail
+
+def _extract(key):
+ """
+ Extract the documentation from the doxygen generated xml files.
+ If multiple files match, combine the docs.
+ @param key the block key
+ @return a string with documentation
+ """
+ docs_dir = os.path.join(DOCS_DIR, 'xml')
+ if not os.path.exists(docs_dir): return ''
+ #extract matches
+ pattern = key.replace('_', '_*').replace('x', '\w')
+ prog = re.compile('^class%s\..*$'%pattern)
+ matches = filter(lambda f: prog.match(f), os.listdir(docs_dir))
+ #combine all matches
+ doc_strs = list()
+ for match in matches:
+ try:
+ xml_file = os.path.join(docs_dir, match)
+ xml = etree.parse(xml_file)
+ #extract descriptions
+ comp_name = extract_txt(xml.xpath(DOXYGEN_NAME_XPATH)[0]).strip()
+ comp_name = ' --- ' + comp_name + ' --- '
+ if re.match('(gr|usrp2|trellis)_.*', key):
+ brief_desc = extract_txt(xml.xpath(DOXYGEN_BRIEFDESC_GR_XPATH)[0]).strip()
+ detailed_desc = extract_txt(xml.xpath(DOXYGEN_DETAILDESC_GR_XPATH)[0]).strip()
+ else:
+ brief_desc = ''
+ detailed_desc = ''
+ #combine
+ doc_strs.append('\n\n'.join([comp_name, brief_desc, detailed_desc]).strip())
+ except IndexError: pass #bad format
+ 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
+ """
+ try: assert _docs_cache.has_key(key)
+ except: _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..a45a918d0
--- /dev/null
+++ b/grc/python/flow_graph.tmpl
@@ -0,0 +1,205 @@
+#!/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 controls the variables with gui controls
+##@param parameters the paramater blocks
+##@param blocks the signal blocks
+##@param connections the 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="$icon.get_filename()",
+ #end if
+ )
+#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_sig = $flow_graph.get_input_signature()
+ #set $out_sig = $flow_graph.get_output_signature()
+class $(class_name)(gr.hier_block2):
+
+ def __init__($param_str):
+ gr.hier_block2.__init__(
+ self,
+ "$title",
+ gr.io_signature($in_sig.nports, $in_sig.nports, $in_sig.size*$in_sig.vlen),
+ gr.io_signature($out_sig.nports, $out_sig.nports, $out_sig.size*$out_sig.vlen),
+ )
+#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
+## Set the variable to a property of self.
+## Write the first line of the variable make.
+########################################################
+#if $variables
+
+ $DIVIDER
+ # Variables
+ $DIVIDER
+#end if
+#for $var in $variables
+ $indent($var.get_var_make())
+#end for
+########################################################
+##Create Controls
+## Write the variable make (excluding first line).
+## Indent each line with 2 tabs.
+########################################################
+#if $controls
+
+ $DIVIDER
+ # Controls
+ $DIVIDER
+#end if
+#for $ctrl in $controls
+ $indent($ctrl.get_make())
+#end for
+########################################################
+##Create Blocks
+## Write the block make, and indent with 2 tabs.
+########################################################
+#if $blocks
+
+ $DIVIDER
+ # Blocks
+ $DIVIDER
+#end if
+#for $blk in filter(lambda b: b.get_make(), $blocks)
+ self.$blk.get_id() = $indent($blk.get_make())
+#end for
+########################################################
+##Create Connections
+## The port name should be the id of the parent block.
+## However, port names for IO pads should be self.
+########################################################
+#if $connections
+
+ $DIVIDER
+ # Connections
+ $DIVIDER
+#end if
+#for $con in $connections
+ #set $source = $con.get_source()
+ #set $sink = $con.get_sink()
+ #if $source.get_parent().get_key() == 'pad_source'
+ #set $source_name = 'self'
+ #else
+ #set $source_name = 'self.' + $source.get_parent().get_id()
+ #end if
+ #if $sink.get_parent().get_key() == 'pad_sink'
+ #set $sink_name = 'self'
+ #else
+ #set $sink_name = 'self.' + $sink.get_parent().get_id()
+ #end if
+ self.connect(($source_name, $source.get_key()), ($sink_name, $sink.get_key()))
+#end for
+
+########################################################
+##Create Callbacks
+## Write a set method for this variable that calls the callbacks
+## and sets the direct variable dependencies.
+########################################################
+#for $var in $parameters + $variables
+ #set $id = $var.get_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.
+########################################################
+#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("--$param.get_id()", dest="$param.get_id()", type="$type", default=$param.get_make())
+ #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
+ tb = $(class_name)($(', '.join($params_eq_list)))
+ #if $generate_options == 'wx_gui'
+ tb.Run($flow_graph.get_option('autostart'))
+ #elif $generate_options == 'no_gui'
+ tb.start()
+ raw_input('Press Enter to quit: ')
+ tb.stop()
+ #end if
+#end if
+