diff options
Diffstat (limited to 'grc/python')
-rw-r--r-- | grc/python/Block.py | 153 | ||||
-rw-r--r-- | grc/python/Connection.py | 34 | ||||
-rw-r--r-- | grc/python/Constants.py | 63 | ||||
-rw-r--r-- | grc/python/FlowGraph.py | 155 | ||||
-rw-r--r-- | grc/python/Generator.py | 125 | ||||
-rw-r--r-- | grc/python/Makefile.am | 43 | ||||
-rw-r--r-- | grc/python/Param.py | 380 | ||||
-rw-r--r-- | grc/python/Platform.py | 81 | ||||
-rw-r--r-- | grc/python/Port.py | 129 | ||||
-rw-r--r-- | grc/python/__init__.py | 1 | ||||
-rw-r--r-- | grc/python/block.dtd | 55 | ||||
-rw-r--r-- | grc/python/convert_hier.py | 79 | ||||
-rw-r--r-- | grc/python/default_flow_graph.grc | 43 | ||||
-rw-r--r-- | grc/python/expr_utils.py | 137 | ||||
-rw-r--r-- | grc/python/extract_docs.py | 90 | ||||
-rw-r--r-- | grc/python/flow_graph.tmpl | 205 |
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 + |