diff options
Diffstat (limited to 'grc/src/grc_gnuradio')
28 files changed, 2989 insertions, 0 deletions
diff --git a/grc/src/grc_gnuradio/Block.py b/grc/src/grc_gnuradio/Block.py new file mode 100644 index 000000000..a14df8ec9 --- /dev/null +++ b/grc/src/grc_gnuradio/Block.py @@ -0,0 +1,131 @@ +""" +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 +""" +##@package grc_gnuradio.Block +#Flow graph block. +#@author Josh Blum + +from grc.elements.Block import Block as _Block +from grc import Utils +from utils 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 + doc = Utils.exists_or_else(n, 'doc', '') + imports = map(lambda i: i.strip(), Utils.listify(n, 'import')) + make = n['make'] + checks = Utils.listify(n, 'check') + callbacks = Utils.listify(n, 'callback') + #build the block + _Block.__init__( + self, + flow_graph=flow_graph, + n=n, + ) + self._doc = doc + self._imports = imports + self._make = make + self._callbacks = callbacks + self._checks = checks + + 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) + 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 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_callbacks(self): + """! + Get a list of function callbacks for this block. + @return a list of strings + """ + return map(lambda c: self.get_id() + '.' + self.resolve_dependencies(c), self._callbacks) diff --git a/grc/src/grc_gnuradio/Connection.py b/grc/src/grc_gnuradio/Connection.py new file mode 100644 index 000000000..c7d6a74c7 --- /dev/null +++ b/grc/src/grc_gnuradio/Connection.py @@ -0,0 +1,42 @@ +""" +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 +""" +##@package grc_gnuradio.Connection +#Flow graph connection. +#A connection exists between 2 ports. +#One port must be input, one output. +#The port decided whether it can have the connection. +#@author Josh Blum + +from grc.elements.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/src/grc_gnuradio/Constants.py.in b/grc/src/grc_gnuradio/Constants.py.in new file mode 100644 index 000000000..66b773e79 --- /dev/null +++ b/grc/src/grc_gnuradio/Constants.py.in @@ -0,0 +1,44 @@ +""" +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 +""" +##@package grc_gnuradio.Constants +#Global constants for grc gnuradio package +#@author Josh Blum + +import os +import sys +import stat + +PYEXEC = '@PYTHONW@' + +#setup paths +DOCS_DIR = os.path.join('@docdir@', 'xml') +DATA_DIR = '@datadir@' +BLOCKS_DIR = '@blocksdir@' +HIER_BLOCKS_LIB_DIR = os.path.join(os.path.expanduser('~'), '.grc_gnuradio') + +#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 +FLOW_GRAPH_TEMPLATE = os.path.join(DATA_DIR, 'flow_graph.tmpl') +BLOCK_DTD = os.path.join(DATA_DIR, 'block.dtd') +BLOCK_TREE = os.path.join(DATA_DIR, 'block_tree.xml') +DEFAULT_FLOW_GRAPH = os.path.join(DATA_DIR, 'default_flow_graph.grc.xml') + diff --git a/grc/src/grc_gnuradio/FlowGraph.py b/grc/src/grc_gnuradio/FlowGraph.py new file mode 100644 index 000000000..3e037305b --- /dev/null +++ b/grc/src/grc_gnuradio/FlowGraph.py @@ -0,0 +1,147 @@ +""" +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 +""" +##@package grc_gnuradio.FlowGraph +#Primative flow graph. +#@author Josh Blum + +from utils import expr_utils +from grc.elements.FlowGraph import FlowGraph as _FlowGraph +from Block import Block +from Connection import Connection + +class FlowGraph(_FlowGraph): + + 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').evaluate()), + 'type': str(pad.get_param('type').evaluate()), + 'vlen': str(pad.get_param('vlen').evaluate()), + '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: b.get_key() in ( + 'variable', 'variable_slider', 'variable_chooser', 'variable_text_box' + ), 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(), + ' '.join([param.to_code() for param in filter(lambda p: p.get_key() != 'id',var.get_params())]) + ) 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: b.get_key() == 'parameter', 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: + if variable.get_key() == 'variable_chooser': + choices = variable.get_param('choices').to_code() + value_index = variable.get_param('value_index').to_code() + e = eval("%s[%s]"%(choices, value_index), n, n) + else: + e = eval(variable.get_param('value').to_code(), n, n) + n[variable.get_id()] = e + except: pass + #make namespace public + self.n = n + #evaluate + e = eval(expr, self.n, self.n) + return e + diff --git a/grc/src/grc_gnuradio/Generator.py b/grc/src/grc_gnuradio/Generator.py new file mode 100644 index 000000000..02c9de291 --- /dev/null +++ b/grc/src/grc_gnuradio/Generator.py @@ -0,0 +1,132 @@ +""" +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 +""" +##@package grc_gnuradio.Generator +#Create python based flow graphs. +#@author Josh Blum + +import os +import subprocess +from Cheetah.Template import Template +from utils import expr_utils +from Constants import * +from utils import convert_hier + +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_PATH + 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): + #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, self.get_file_path()] + 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 + """ + imports = self._flow_graph.get_imports() + variables = self._flow_graph.get_variables() + parameters = self._flow_graph.get_parameters() + #list of blocks not including variables and imports and parameters and disabled + blocks = sorted(self._flow_graph.get_enabled_blocks(), lambda x, y: cmp(x.get_id(), y.get_id())) + blocks = filter(lambda b: b not in (imports + parameters + variables), blocks) + #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] + #list of callbacks (prepend self.) + callbacks = [ + expr_utils.expr_prepend(cb, var_ids, 'self.') + for cb in sum([block.get_callbacks() for block in self._flow_graph.get_blocks()], []) + ] + #map var id to the expression (prepend self.) + var_id2expr = dict( + [(var.get_id(), expr_utils.expr_prepend(var.get_make().split('\n')[0], var_ids, 'self.')) + for var in parameters + variables] + ) + #create graph structure for variables + variable_graph = expr_utils.get_graph(var_id2expr) + #map var id to direct dependents + #for each var id, make a list of all 2nd order edges + #use all edges of that id that are not also 2nd order edges + #meaning: list variables the ONLY depend directly on this variable + #and not variables that also depend indirectly on this variable + var_id2deps = dict( + [(var_id, filter(lambda e: e not in sum([list(variable_graph.get_edges(edge)) + for edge in variable_graph.get_edges(var_id)], []), variable_graph.get_edges(var_id) + ) + ) + for var_id in var_ids] + ) + #map var id to callbacks + var_id2cbs = dict( + [(var_id, filter(lambda c: var_id in expr_utils.expr_split(c), callbacks)) + for var_id in var_ids] + ) + #load the namespace + namespace = { + 'imports': imports, + 'flow_graph': self._flow_graph, + 'variables': variables, + 'parameters': parameters, + 'blocks': blocks, + 'connections': connections, + 'generate_options': self._generate_options, + 'var_id2expr': var_id2expr, + 'var_id2deps': var_id2deps, + 'var_id2cbs': var_id2cbs, + } + #build the template + t = Template(open(FLOW_GRAPH_TEMPLATE, 'r').read(), namespace) + return str(t) + diff --git a/grc/src/grc_gnuradio/Makefile.am b/grc/src/grc_gnuradio/Makefile.am new file mode 100644 index 000000000..a2ef0f4cc --- /dev/null +++ b/grc/src/grc_gnuradio/Makefile.am @@ -0,0 +1,53 @@ +# +# Copyright 2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +include $(top_srcdir)/grc/Makefile.common + +SUBDIRS = blks2 usrp utils wxgui + +ourpythondir = $(pythondir)/grc_gnuradio + +ourpython_PYTHON = \ + __init__.py \ + Constants.py \ + Block.py \ + Connection.py \ + FlowGraph.py \ + Generator.py \ + Platform.py \ + Param.py \ + Port.py + +docdir = $(prefix)/share/doc/@PACKAGE@-@VERSION@ + +BUILT_SOURCES = Constants.py + +Constants.py: Makefile $(srcdir)/Constants.py.in + sed \ + -e 's|@PYTHONW[@]|$(PYTHONW)|g' \ + -e 's|@datadir[@]|$(grc_gnuradio_data_dir)|g' \ + -e 's|@blocksdir[@]|$(grc_gnuradio_blocks_dir)|g' \ + -e 's|@docdir[@]|$(docdir)|g' \ + $(srcdir)/Constants.py.in > $@ + +EXTRA_DIST = Constants.py.in + +MOSTLYCLEANFILES = $(BUILT_SOURCES) diff --git a/grc/src/grc_gnuradio/Param.py b/grc/src/grc_gnuradio/Param.py new file mode 100644 index 000000000..207b29839 --- /dev/null +++ b/grc/src/grc_gnuradio/Param.py @@ -0,0 +1,255 @@ +""" +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 +""" +##@package grc_gnuradio.Param +#Flow graph block parameters. +#@author Josh Blum + +from utils import expr_utils +from grc.elements.Param import Param as _Param +import os + +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', + 'file_open', 'file_save', + 'id', + 'grid_pos', 'import', + ] + + def get_hide(self): + """! + Get the hide value from the base class. + If hide was empty, and this is a type controller, set hide to part. + If hide was empty, and this is an id of a non variable, set hide to part. + @return hide the hide property string + """ + hide = _Param.get_hide(self) + #hide IO controlling params + if not hide and self.get_key() in ( + 'type', 'vlen', 'num_inputs', 'num_outputs' + ): hide = 'part' + #hide ID in non variable blocks + elif not hide and self.get_key() == 'id' and self.get_parent().get_key() not in ( + 'variable', 'variable_slider', 'variable_chooser', 'variable_text_box', 'parameter', 'options' + ): hide = 'part' + return hide + + def evaluate(self): + """! + Evaluate the value. + @return evaluated type + """ + 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 self.get_value() + ######################### + # Numeric Types + ######################### + elif t in ('raw', 'complex', 'real', 'int', 'complex_vector', 'real_vector', 'int_vector', 'hex'): + #raise exception if python cannot evaluate this value + try: e = self.get_parent().get_parent().evaluate(v) + except: + self._add_error_message('Value "%s" cannot be evaluated.'%v) + raise Exception + #raise an exception if the data is invalid + if t == 'raw': return e + elif t == 'complex': + try: assert(isinstance(e, (complex, float, int, long))) + except AssertionError: + self._add_error_message('Expression "%s" is invalid for type complex.'%str(e)) + raise Exception + return e + elif t == 'real': + try: assert(isinstance(e, (float, int, long))) + except AssertionError: + self._add_error_message('Expression "%s" is invalid for type real.'%str(e)) + raise Exception + return e + elif t == 'int': + try: assert(isinstance(e, (int, long))) + except AssertionError: + self._add_error_message('Expression "%s" is invalid for type integer.'%str(e)) + raise Exception + return e + elif t == 'complex_vector': + if not isinstance(e, (tuple, list, set)): + self._lisitify_flag = True + e = [e] + try: + for ei in e: + assert(isinstance(ei, (complex, float, int, long))) + except AssertionError: + self._add_error_message('Expression "%s" is invalid for type complex vector.'%str(e)) + raise Exception + return e + elif t == 'real_vector': + if not isinstance(e, (tuple, list, set)): + self._lisitify_flag = True + e = [e] + try: + for ei in e: + assert(isinstance(ei, (float, int, long))) + except AssertionError: + self._add_error_message('Expression "%s" is invalid for type real vector.'%str(e)) + raise Exception + return e + elif t == 'int_vector': + if not isinstance(e, (tuple, list, set)): + self._lisitify_flag = True + e = [e] + try: + for ei in e: + assert(isinstance(ei, (int, long))) + except AssertionError: + self._add_error_message('Expression "%s" is invalid for type integer vector.'%str(e)) + raise Exception + return e + elif t == 'hex': + return hex(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(len(v) > 0) + assert(v[0].isalpha()) + for c in v: assert(c.isalnum() or c in ('_',)) + except AssertionError: + self._add_error_message('ID "%s" must be alpha-numeric or underscored, and begin with a letter.'%v) + raise Exception + params = self.get_all_params('id') + keys = [param.get_value() for param in params] + try: assert(len(keys) == len(set(keys))) + except: + self._add_error_message('ID "%s" is not unique.'%v) + raise Exception + 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: + self._add_error_message('A grid position must be a list of 4 integers.') + raise Exception + row, col, row_span, col_span = e + #check row, col + try: assert(row >= 0 and col >= 0) + except AssertionError: + self._add_error_message('Row and column must be non-negative.') + raise Exception + #check row span, col span + try: assert(row_span > 0 and col_span > 0) + except AssertionError: + self._add_error_message('Row and column span must be greater than zero.') + raise Exception + #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: + self._add_error_message('Another graphical element is using cell "%s".'%str(cell)) + raise Exception + return e + ######################### + # Import Type + ######################### + elif t == 'import': + n = dict() #new namespace + try: exec v in n + except ImportError: + self._add_error_message('Import "%s" failed.'%v) + raise Exception + except Exception: + self._add_error_message('Bad import syntax: "%s".'%v) + raise Exception + 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 + if not self._init: + self.evaluate() + self._init = True + 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_blocks()], []) + diff --git a/grc/src/grc_gnuradio/Platform.py b/grc/src/grc_gnuradio/Platform.py new file mode 100644 index 000000000..22a4c7ecd --- /dev/null +++ b/grc/src/grc_gnuradio/Platform.py @@ -0,0 +1,74 @@ +""" +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 +""" +##@package grc_gnuradio.Platform +#Gnuradio python specific platform. +#@author Josh Blum + +import os +from grc.Constants import FLOW_GRAPH_FILE_EXTENSION +from grc.elements.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 * + +class Platform(_Platform): + + def __init__(self, block_paths_internal_only=[], block_paths_external=[]): + """! + Make a platform for gnuradio. + The internal only list will replace the current block path. + @param block_paths_internal_only a list of blocks internal to this platform + @param block_paths_external a list of blocks to load in addition to the above blocks + """ + #ensure hier dir + if not os.path.exists(HIER_BLOCKS_LIB_DIR): os.mkdir(HIER_BLOCKS_LIB_DIR) + #handle internal/only + if block_paths_internal_only: + block_paths = map(lambda b: os.path.join(BLOCKS_DIR, b), ['options.xml'] + block_paths_internal_only) + else: block_paths = [BLOCKS_DIR] + #handle external + block_paths.extend(block_paths_external) + #append custom hiers + block_paths.append(HIER_BLOCKS_LIB_DIR) + #init + _Platform.__init__( + self, + name='GNURadio Python', + key='gnuradio_python', + block_paths=block_paths, + block_dtd=BLOCK_DTD, + block_tree=BLOCK_TREE, + default_flow_graph=DEFAULT_FLOW_GRAPH, + generator=Generator, + ) + + ############################################## + # Constructors + ############################################## + FlowGraph = _FlowGraph + Connection = _Connection + Block = _Block + Source = Source + Sink = Sink + Param = _Param + diff --git a/grc/src/grc_gnuradio/Port.py b/grc/src/grc_gnuradio/Port.py new file mode 100644 index 000000000..c8e899781 --- /dev/null +++ b/grc/src/grc_gnuradio/Port.py @@ -0,0 +1,135 @@ +""" +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 +""" +##@package grc_gnuradio.Port +#Flow graph block port (source or sink). +#@author Josh Blum + +from grc.elements.Port import Port as _Port +from grc import Utils +from grc.Constants import MAX_NUM_PORTS + +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 = Utils.exists_or_else(n, 'vlen', '1') + nports = Utils.exists_or_else(n, 'nports', '') + optional = Utils.exists_or_else(n, 'optional', '') + #build the port + _Port.__init__( + self, + block=block, + n=n, + ) + self._nports = nports + self._vlen = vlen + self._optional = bool(optional) + + 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 <= MAX_NUM_PORTS) + 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': '#3399FF', + 'float': '#FF8C69', + 'int': '#00FF99', + 'short': '#FFFF66', + 'byte': '#FF66FF', + }[self.get_type()] + return {#vlen is non 1 + 'complex': '#3399AA', + 'float': '#CC8C69', + 'int': '#00CC99', + 'short': '#CCCC33', + 'byte': '#CC66CC', + }[self.get_type()] + except: return _Port.get_color(self) + + def is_empty(self): + """! + Is this port empty? + An empty port has no connections. + Not empty of optional is set. + @return true if empty + """ + return not self.get_optional() and not self.get_connections() + +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/src/grc_gnuradio/__init__.py b/grc/src/grc_gnuradio/__init__.py new file mode 100644 index 000000000..fe09e0d4b --- /dev/null +++ b/grc/src/grc_gnuradio/__init__.py @@ -0,0 +1,21 @@ +""" +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 +""" +##@package grc_gnuradio +#gnuradio overloaded elements and supplemental python modules +#@author Josh Blum diff --git a/grc/src/grc_gnuradio/blks2/Makefile.am b/grc/src/grc_gnuradio/blks2/Makefile.am new file mode 100644 index 000000000..307ec863f --- /dev/null +++ b/grc/src/grc_gnuradio/blks2/Makefile.am @@ -0,0 +1,31 @@ +# +# Copyright 2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +include $(top_srcdir)/Makefile.common + +ourpythondir = $(pythondir)/grc_gnuradio/blks2 + +ourpython_PYTHON = \ + __init__.py \ + error_rate.py \ + packet.py \ + queue.py \ + selector.py diff --git a/grc/src/grc_gnuradio/blks2/__init__.py b/grc/src/grc_gnuradio/blks2/__init__.py new file mode 100644 index 000000000..cd1b793c4 --- /dev/null +++ b/grc/src/grc_gnuradio/blks2/__init__.py @@ -0,0 +1,28 @@ +# Copyright 2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from queue import queue_sink_thread +from queue import queue_sink_c, queue_sink_f, queue_sink_i, queue_sink_s, queue_sink_b +from queue import queue_source_c, queue_source_f, queue_source_i, queue_source_s, queue_source_b + +from selector import selector, valve +from packet import packet_encoder, packet_decoder +from error_rate import error_rate + diff --git a/grc/src/grc_gnuradio/blks2/error_rate.py b/grc/src/grc_gnuradio/blks2/error_rate.py new file mode 100644 index 000000000..eb09940cb --- /dev/null +++ b/grc/src/grc_gnuradio/blks2/error_rate.py @@ -0,0 +1,138 @@ +# Copyright 2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +default_win_size = 1000 + +from gnuradio import gr +import gnuradio.gr.gr_threading as _threading +import numpy + +#generate 1s counts array +_1s_counts = [sum([1&(i>>j) for j in range(8)]) for i in range(2**8)] + +class input_watcher(_threading.Thread): + """ + Read samples from the message queue and hand them to the callback. + """ + + def __init__(self, msgq, callback): + self._msgq = msgq + self._callback = callback + _threading.Thread.__init__(self) + self.setDaemon(1) + self.keep_running = True + self.start() + + def run(self): + r = '' + while True: + msg = self._msgq.delete_head() + itemsize = int(msg.arg1()) + nitems = int(msg.arg2()) + s = r + msg.to_string() + i = (nitems-nitems%2)*itemsize + r = s[i:] + s = s[:i] + samples = numpy.fromstring(s, numpy.int8) + self._callback(samples) + +class error_rate(gr.hier_block2): + """ + Sample the incoming data streams (byte) and calculate the bit or symbol error rate. + Write the running rate to the output data stream (float). + """ + + def __init__(self, type='BER', win_size=default_win_size, bits_per_symbol=2): + """! + Error rate constructor. + @param type a string 'BER' or 'SER' + @param win_size the number of samples to calculate over + @param bits_per_symbol the number of information bits per symbol (BER only) + """ + #init + gr.hier_block2.__init__( + self, 'error_rate', + gr.io_signature(2, 2, gr.sizeof_char), + gr.io_signature(1, 1, gr.sizeof_float), + ) + assert type in ('BER', 'SER') + self._max_samples = win_size + self._bits_per_symbol = bits_per_symbol + #setup message queue + msg_source = gr.message_source(gr.sizeof_float, 1) + self._msgq_source = msg_source.msgq() + msgq_sink = gr.msg_queue(2) + msg_sink = gr.message_sink(gr.sizeof_char, msgq_sink, False) #False -> blocking + inter = gr.interleave(gr.sizeof_char) + #start thread + self._num_errs = 0 + self._err_index = 0 + self._num_samps = 0 + self._err_array = numpy.zeros(self._max_samples, numpy.int8) + if type == 'BER': + input_watcher(msgq_sink, self._handler_ber) + elif type == 'SER': + input_watcher(msgq_sink, self._handler_ser) + #connect + self.connect(msg_source, self) + self.connect((self, 0), (inter, 0)) + self.connect((self, 1), (inter, 1)) + self.connect(inter, msg_sink) + + def _handler_ber(self, samples): + num = len(samples)/2 + arr = numpy.zeros(num, numpy.float32) + for i in range(num): + old_err = self._err_array[self._err_index] + #record error + self._err_array[self._err_index] = _1s_counts[samples[i*2] ^ samples[i*2 + 1]] + self._num_errs = self._num_errs + self._err_array[self._err_index] - old_err + #increment index + self._err_index = (self._err_index + 1)%self._max_samples + self._num_samps = min(self._num_samps + 1, self._max_samples) + #write sample + arr[i] = float(self._num_errs)/float(self._num_samps*self._bits_per_symbol) + #write message + msg = gr.message_from_string(arr.tostring(), 0, gr.sizeof_float, num) + self._msgq_source.insert_tail(msg) + + def _handler_ser(self, samples): + num = len(samples)/2 + arr = numpy.zeros(num, numpy.float32) + for i in range(num): + old_err = self._err_array[self._err_index] + #record error + ref = samples[i*2] + res = samples[i*2 + 1] + if ref == res: + self._err_array[self._err_index] = 0 + else: + self._err_array[self._err_index] = 1 + #update number of errors + self._num_errs = self._num_errs + self._err_array[self._err_index] - old_err + #increment index + self._err_index = (self._err_index + 1)%self._max_samples + self._num_samps = min(self._num_samps + 1, self._max_samples) + #write sample + arr[i] = float(self._num_errs)/float(self._num_samps) + #write message + msg = gr.message_from_string(arr.tostring(), 0, gr.sizeof_float, num) + self._msgq_source.insert_tail(msg) + diff --git a/grc/src/grc_gnuradio/blks2/packet.py b/grc/src/grc_gnuradio/blks2/packet.py new file mode 100644 index 000000000..5276de109 --- /dev/null +++ b/grc/src/grc_gnuradio/blks2/packet.py @@ -0,0 +1,194 @@ +# Copyright 2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from gnuradio import gr, packet_utils +import gnuradio.gr.gr_threading as _threading + +##payload length in bytes +DEFAULT_PAYLOAD_LEN = 512 + +##how many messages in a queue +DEFAULT_MSGQ_LIMIT = 2 + +##threshold for unmaking packets +DEFAULT_THRESHOLD = 12 + +####################################################################################### +## Packet Encoder +####################################################################################### + +class _packet_encoder_thread(_threading.Thread): + + def __init__(self, msgq, payload_length, send): + self._msgq = msgq + self._payload_length = payload_length + self._send = send + _threading.Thread.__init__(self) + self.setDaemon(1) + self.keep_running = True + self.start() + + def run(self): + sample = '' #residual sample + while self.keep_running: + msg = self._msgq.delete_head() #blocking read of message queue + sample = sample + msg.to_string() #get the body of the msg as a string + while len(sample) >= self._payload_length: + payload = sample[0:self._payload_length] + sample = sample[self._payload_length:] + self._send(payload) + +class packet_encoder(gr.hier_block2): + """ + Hierarchical block for wrapping packet-based modulators. + """ + + def __init__(self, item_size_in, samples_per_symbol, bits_per_symbol, access_code='', pad_for_usrp=True, payload_length=-1): + """! + packet_mod constructor. + @param item_size_in the size of the input data stream in bytes + @param samples_per_symbol number of samples per symbol + @param bits_per_symbol number of bits per symbol + @param access_code AKA sync vector + @param pad_for_usrp If true, packets are padded such that they end up a multiple of 128 samples + @param payload_length number of bytes in a data-stream slice + """ + #setup parameters + self._item_size_in = item_size_in + self._samples_per_symbol = samples_per_symbol + self._bits_per_symbol = bits_per_symbol + self._pad_for_usrp = pad_for_usrp + if not access_code: #get access code + access_code = packet_utils.default_access_code + if not packet_utils.is_1_0_string(access_code): + raise ValueError, "Invalid access_code %r. Must be string of 1's and 0's" % (access_code,) + self._access_code = access_code + self._pad_for_usrp = pad_for_usrp + if payload_length < 0: #get payload length + payload_length = DEFAULT_PAYLOAD_LEN + if payload_length%self._item_size_in != 0: #verify that packet length is a multiple of the stream size + raise ValueError, 'The packet length: "%d" is not a mutiple of the stream size: "%d".'%(payload_length, self._item_size_in) + self._payload_length = payload_length + #create blocks + msg_source = gr.message_source(gr.sizeof_char, DEFAULT_MSGQ_LIMIT) + self._msgq_out = msg_source.msgq() + self._msgq_in = gr.msg_queue(DEFAULT_MSGQ_LIMIT) + msg_sink = gr.message_sink(self._item_size_in, self._msgq_in, False) #False -> blocking + #initialize hier2 + gr.hier_block2.__init__( + self, + "packet_encoder", + gr.io_signature(1, 1, self._item_size_in), # Input signature + gr.io_signature(1, 1, gr.sizeof_char) # Output signature + ) + #connect + self.connect(self, msg_sink) + self.connect(msg_source, self) + #start thread + _packet_encoder_thread(self._msgq_in, self._payload_length, self._send_packet) + + def _send_packet(self, payload): + """! + Wrap the payload in a packet and push onto the message queue. + @param payload string, data to send + """ + packet = packet_utils.make_packet( + payload, + self._samples_per_symbol, + self._bits_per_symbol, + self._access_code, + self._pad_for_usrp + ) + msg = gr.message_from_string(packet) + self._msgq_out.insert_tail(msg) + +####################################################################################### +## Packet Decoder +####################################################################################### + +class _packet_decoder_thread(_threading.Thread): + + def __init__(self, msgq, callback): + _threading.Thread.__init__(self) + self.setDaemon(1) + self._msgq = msgq + self.callback = callback + self.keep_running = True + self.start() + + def run(self): + while self.keep_running: + msg = self._msgq.delete_head() + ok, payload = packet_utils.unmake_packet(msg.to_string(), int(msg.arg1())) + if self.callback: + self.callback(ok, payload) + +class packet_decoder(gr.hier_block2): + """ + Hierarchical block for wrapping packet-based demodulators. + """ + + def __init__(self, item_size_out, access_code='', threshold=-1): + """! + packet_demod constructor. + @param item_size_out the size of the output data stream in bytes + @param access_code AKA sync vector + @param threshold detect access_code with up to threshold bits wrong (-1 -> use default) + """ + #setup + self._item_size_out = item_size_out + #access code + if not access_code: #get access code + access_code = packet_utils.default_access_code + if not packet_utils.is_1_0_string(access_code): + raise ValueError, "Invalid access_code %r. Must be string of 1's and 0's" % (access_code,) + self._access_code = access_code + #threshold + if threshold < 0: threshold = DEFAULT_THRESHOLD + self._threshold = threshold + #blocks + self._msgq_in = gr.msg_queue(DEFAULT_MSGQ_LIMIT) #holds packets from the PHY + correlator = gr.correlate_access_code_bb(self._access_code, self._threshold) + framer_sink = gr.framer_sink_1(self._msgq_in) + msg_source = gr.message_source(self._item_size_out, DEFAULT_MSGQ_LIMIT) + self._msgq_out = msg_source.msgq() + #initialize hier2 + gr.hier_block2.__init__( + self, + "packet_decoder", + gr.io_signature(1, 1, gr.sizeof_char), # Input signature + gr.io_signature(1, 1, self._item_size_out) # Output signature + ) + #connect + self.connect(self, correlator, framer_sink) + self.connect(msg_source, self) + #start thread + _packet_decoder_thread(self._msgq_in, self._recv_packet) + + def _recv_packet(self, ok, payload): + """! + Extract the payload from the packet and push onto message queue. + @param ok boolean ok + @param payload data received + """ + msg = gr.message_from_string(payload, 0, self._item_size_out, len(payload)/self._item_size_out) + if ok: self._msgq_out.insert_tail(msg) + + diff --git a/grc/src/grc_gnuradio/blks2/queue.py b/grc/src/grc_gnuradio/blks2/queue.py new file mode 100644 index 000000000..cec35e52a --- /dev/null +++ b/grc/src/grc_gnuradio/blks2/queue.py @@ -0,0 +1,178 @@ +# Copyright 2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from gnuradio import gr +import gnuradio.gr.gr_threading as _threading +import numpy + +####################################################################################### +## Queue Sink Thread +####################################################################################### +class queue_sink_thread(_threading.Thread): + """! + Read samples from the queue sink and execute the callback. + """ + + def __init__(self, queue_sink, callback): + """! + Queue sink thread contructor. + @param queue_sink the queue to pop messages from + @param callback the function of one argument + """ + self._queue_sink = queue_sink + self._callback = callback + _threading.Thread.__init__(self) + self.setDaemon(1) + self.keep_running = True + self.start() + + def run(self): + while self.keep_running: + self._callback(self._queue_sink.pop()) + +####################################################################################### +## Queue Sink +####################################################################################### +class _queue_sink_base(gr.hier_block2): + """! + Queue sink base, a queue sink for any size queue. + Easy read access to a gnuradio data stream from python. + Call pop to read a sample from a gnuradio data stream. + Samples are cast as python data types, complex, float, or int. + """ + + def __init__(self, vlen=1): + """! + Queue sink base contructor. + @param vlen the vector length + """ + self._vlen = vlen + #initialize hier2 + gr.hier_block2.__init__( + self, + "queue_sink", + gr.io_signature(1, 1, self._item_size*self._vlen), # Input signature + gr.io_signature(0, 0, 0) # Output signature + ) + #create message sink + self._msgq = gr.msg_queue(1) + message_sink = gr.message_sink(self._item_size*self._vlen, self._msgq, False) #False -> blocking + #connect + self.connect(self, message_sink) + self.arr = '' + + def pop(self): + """! + Pop a new sample off the front of the queue. + @return a new sample + """ + while len(self.arr) < self._item_size*self._vlen: + msg = self._msgq.delete_head() + self.arr = self.arr + msg.to_string() + sample = self.arr[:self._item_size*self._vlen] + self.arr = self.arr[self._item_size*self._vlen:] + sample = map(self._cast, numpy.fromstring(sample, self._numpy)) + if self._vlen == 1: return sample[0] + return sample + +class queue_sink_c(_queue_sink_base): + _item_size = gr.sizeof_gr_complex + _numpy = numpy.complex64 + def _cast(self, arg): return complex(arg.real, arg.imag) + +class queue_sink_f(_queue_sink_base): + _item_size = gr.sizeof_float + _numpy = numpy.float32 + _cast = float + +class queue_sink_i(_queue_sink_base): + _item_size = gr.sizeof_int + _numpy = numpy.int32 + _cast = int + +class queue_sink_s(_queue_sink_base): + _item_size = gr.sizeof_short + _numpy = numpy.int16 + _cast = int + +class queue_sink_b(_queue_sink_base): + _item_size = gr.sizeof_char + _numpy = numpy.int8 + _cast = int + +####################################################################################### +## Queue Source +####################################################################################### +class _queue_source_base(gr.hier_block2): + """! + Queue source base, a queue source for any size queue. + Easy write access to a gnuradio data stream from python. + Call push to to write a sample into the gnuradio data stream. + """ + + def __init__(self, vlen=1): + """! + Queue source base contructor. + @param vlen the vector length + """ + self._vlen = vlen + #initialize hier2 + gr.hier_block2.__init__( + self, + "queue_source", + gr.io_signature(0, 0, 0), # Input signature + gr.io_signature(1, 1, self._item_size*self._vlen) # Output signature + ) + #create message sink + message_source = gr.message_source(self._item_size*self._vlen, 1) + self._msgq = message_source.msgq() + #connect + self.connect(message_source, self) + + def push(self, item): + """! + Push an item into the back of the queue. + @param item the item + """ + if self._vlen == 1: item = [item] + arr = numpy.array(item, self._numpy) + msg = gr.message_from_string(arr.tostring(), 0, self._item_size, self._vlen) + self._msgq.insert_tail(msg) + +class queue_source_c(_queue_source_base): + _item_size = gr.sizeof_gr_complex + _numpy = numpy.complex64 + +class queue_source_f(_queue_source_base): + _item_size = gr.sizeof_float + _numpy = numpy.float32 + +class queue_source_i(_queue_source_base): + _item_size = gr.sizeof_int + _numpy = numpy.int32 + +class queue_source_s(_queue_source_base): + _item_size = gr.sizeof_short + _numpy = numpy.int16 + +class queue_source_b(_queue_source_base): + _item_size = gr.sizeof_char + _numpy = numpy.int8 + diff --git a/grc/src/grc_gnuradio/blks2/selector.py b/grc/src/grc_gnuradio/blks2/selector.py new file mode 100644 index 000000000..787f6547f --- /dev/null +++ b/grc/src/grc_gnuradio/blks2/selector.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python +# +# Copyright 2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from gnuradio import gr + +class selector(gr.hier_block2): + """A hier2 block with N inputs and M outputs, where data is only forwarded through input n to output m.""" + def __init__(self, item_size, num_inputs, num_outputs, input_index, output_index): + """! + SelectorHelper constructor. + @param item_size the size of the gr data stream in bytes + @param num_inputs the number of inputs (integer) + @param num_outputs the number of outputs (integer) + @param input_index the index for the source data + @param output_index the index for the destination data + """ + gr.hier_block2.__init__( + self, 'selector', + gr.io_signature(num_inputs, num_inputs, item_size), + gr.io_signature(num_outputs, num_outputs, item_size), + ) + #terminator blocks for unused inputs and outputs + self.input_terminators = [gr.null_sink(item_size)] * num_inputs + self.output_terminators = [gr.head(item_size, 0)] * num_outputs + self.copy = None + #connections + for i in range(num_inputs): self.connect((self, i), self.input_terminators[i]) + for i in range(num_outputs): self.connect(gr.null_source(item_size), self.output_terminators[i], (self, i)) + self.item_size = item_size + self.input_index = input_index + self.output_index = output_index + self.num_inputs = num_inputs + self.num_outputs = num_outputs + self._connect_current() + + def _indexes_valid(self): + """! + Are the input and output indexes within range of the number of inputs and outputs? + @return true if input index and output index are in range + """ + return self.input_index in range(self.num_inputs) and self.output_index in range(self.num_outputs) + + def _connect_current(self): + """If the input and output indexes are valid: + disconnect the blocks at the input and output index from their terminators, + and connect them to one another. Then connect the terminators to one another.""" + if self._indexes_valid(): + self.disconnect((self, self.input_index), self.input_terminators[self.input_index]) + self.disconnect(self.output_terminators[self.output_index], (self, self.output_index)) + self.copy = gr.skiphead(self.item_size, 0) + self.connect((self, self.input_index), self.copy) + self.connect(self.copy, (self, self.output_index)) + self.connect(self.output_terminators[self.output_index], self.input_terminators[self.input_index]) + + def _disconnect_current(self): + """If the input and output indexes are valid: + disconnect the blocks at the input and output index from one another, + and the terminators at the input and output index from one another. + Reconnect the blocks to the terminators.""" + if self._indexes_valid(): + self.disconnect((self, self.input_index), self.copy) + self.disconnect(self.copy, (self, self.output_index)) + self.disconnect(self.output_terminators[self.output_index], self.input_terminators[self.input_index]) + del self.copy + self.copy = None + self.connect((self, self.input_index), self.input_terminators[self.input_index]) + self.connect(self.output_terminators[self.output_index], (self, self.output_index)) + + def set_input_index(self, input_index): + """! + Change the block to the new input index if the index changed. + @param input_index the new input index + """ + if self.input_index != input_index: + self.lock() + self._disconnect_current() + self.input_index = input_index + self._connect_current() + self.unlock() + + def set_output_index(self, output_index): + """! + Change the block to the new output index if the index changed. + @param output_index the new output index + """ + if self.output_index != output_index: + self.lock() + self._disconnect_current() + self.output_index = output_index + self._connect_current() + self.unlock() + +class valve(selector): + """Wrapper for selector with 1 input and 1 output.""" + + def __init__(self, item_size, open): + """! + Constructor for valve. + @param item_size the size of the gr data stream in bytes + @param open true if initial valve state is open + """ + if open: output_index = -1 + else: output_index = 0 + selector.__init__(self, item_size, 1, 1, 0, output_index) + + def set_open(self, open): + """! + Callback to set open state. + @param open true to set valve state to open + """ + if open: output_index = -1 + else: output_index = 0 + self.set_output_index(output_index) + diff --git a/grc/src/grc_gnuradio/usrp/Makefile.am b/grc/src/grc_gnuradio/usrp/Makefile.am new file mode 100644 index 000000000..9689be8e0 --- /dev/null +++ b/grc/src/grc_gnuradio/usrp/Makefile.am @@ -0,0 +1,28 @@ +# +# Copyright 2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +include $(top_srcdir)/Makefile.common + +ourpythondir = $(pythondir)/grc_gnuradio/usrp + +ourpython_PYTHON = \ + __init__.py \ + simple_usrp.py diff --git a/grc/src/grc_gnuradio/usrp/__init__.py b/grc/src/grc_gnuradio/usrp/__init__.py new file mode 100644 index 000000000..0962df14c --- /dev/null +++ b/grc/src/grc_gnuradio/usrp/__init__.py @@ -0,0 +1,25 @@ +# Copyright 2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from simple_usrp import simple_source_c, simple_source_s +from simple_usrp import dual_source_c, dual_source_s +from simple_usrp import simple_sink_c, simple_sink_s +from simple_usrp import dual_sink_c, dual_sink_s + diff --git a/grc/src/grc_gnuradio/usrp/simple_usrp.py b/grc/src/grc_gnuradio/usrp/simple_usrp.py new file mode 100644 index 000000000..118ccc16a --- /dev/null +++ b/grc/src/grc_gnuradio/usrp/simple_usrp.py @@ -0,0 +1,379 @@ +# Copyright 2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import sys +from gnuradio import usrp, gr + +#################################################################### +# Helper Functions +#################################################################### + +def _set_frequency(u, which, subdev, frequency, verbose=False): + """! + Set the carrier frequency for the given subdevice. + @param u the usrp source/sink + @param which specifies the DDC/DUC number + @param frequency the carrier frequency in Hz + @param verbose if true, print usrp tuning information + """ + r = u.tune(which, subdev, frequency) + if not verbose: return + print subdev.side_and_name() + if r: + print " r.baseband_frequency =", r.baseband_freq + print " r.dxc_frequency =", r.dxc_freq + print " r.residual_frequency =", r.residual_freq + print " r.inverted =", r.inverted + else: print >> sys.stderr, 'Error calling tune on subdevice.' + +def _setup_rx_subdev(u, subdev_spec, ddc, gain, frequency, auto_tr=None, rx_ant=None): + """! + Setup a usrp receive subdevice by setting gain and frequency. + Add the gain and frequency callbacks to the flow graph. + FlexRF: Handle auto transmit/receive and set the receive antenna. + @param u the usrp object + @param subdev_spec the sub-device specification + @param ddc which ddc to use: 0 or 1 + @param gain the gain to set + @param frequency the frequency to tune + @param auto_tr auto transmit/receive True, False, or None + @param rx_ant the receive antenna: 'TX/RX', 'RX2', or None + @return the subdevice + """ + subdev = usrp.selected_subdev(u, subdev_spec)#get the subdev + subdev.set_gain(gain) + _set_frequency(u, ddc, subdev, frequency, verbose=True) + if auto_tr is not None: subdev.set_auto_tr(auto_tr) + if rx_ant is not None: subdev.select_rx_antenna(rx_ant) + return subdev + +def _setup_tx_subdev(u, subdev_spec, gain, frequency, auto_tr=None, tx_enb=None): + """! + Setup a usrp receive subdevice by setting gain and frequency. + Add the gain and frequency callbacks to the flow graph. + FlexRF: Handle auto transmit/receive and enable the transmitter. + @param u the usrp object + @param subdev_spec the sub-device specification + @param gain the gain to set + @param frequency the frequency to tune + @param auto_tr auto transmit/receive True, False, or None + @param tx_enb the transmit enable: True, False, or None + @return the subdevice + """ + subdev = usrp.selected_subdev(u, subdev_spec)#get the subdev + subdev.set_gain(gain) + _set_frequency(u, subdev._which, subdev, frequency, verbose=True) + if auto_tr is not None: subdev.set_auto_tr(auto_tr) + if tx_enb is not None: subdev.set_enable(tx_enb) + return subdev + +##map the usrp contructors to IO sizes +constructor_to_size = { + usrp.source_c: gr.sizeof_gr_complex, + usrp.sink_c: gr.sizeof_gr_complex, + usrp.source_s: gr.sizeof_short, + usrp.sink_s: gr.sizeof_short, +} + +#################################################################### +#################################################################### +# Simple USRP Base Classes +#################################################################### +#################################################################### + +class _simple_usrp(object): + """A single usrp source/sink base class.""" + + def __init__(self, u, subdev, which): + """! + Create a simple usrp base class. + @param u the usrp object + @param subdev the subdevice object + @param which specifies the DDC/DUC number when tuning + """ + self._u = u + self._subdev = subdev + self._which = which + + def get_u(self): + """! + Get the underlying usrp object. + @return the usrp source/sink object. + """ + return self._u + + def get_subdev(self): + """! + Get the underlying subdevice. + @return the subdev object. + """ + return self._subdev + + def set_frequency(self, frequency): + """! + Set the frequency of the subdevice. + @param frequency the frequency to tune + """ + _set_frequency(self.get_u(), self._which, self.get_subdev(), frequency) + + def set_gain(self, gain): + """! + Set the gain of the subdevice. + @param gain the gain to set + """ + self.get_subdev().set_gain(gain) + +#################################################################### +# Simple USRP Source +#################################################################### +class _simple_source(gr.hier_block2, _simple_usrp): + """A single usrp source of IO type short or complex.""" + + def __init__(self, number, subdev_spec, frequency, decimation, gain, mux=None, auto_tr=None, rx_ant=None): + """! + USRP simple source contructor. + @param number the unit number + @param subdev_spec the sub-device specification tuple + @param frequency the frequency to tune + @param decimation the device decimation + @param gain the gain to set + @param mux the mux in hex or None + @param auto_tr auto transmit/receive True, False, or None + @param rx_ant the receive antenna: 'TX/RX', 'RX2', or None + """ + #initialize hier2 block + gr.hier_block2.__init__( + self, 'usrp_simple_source', + gr.io_signature(0, 0, 0), + gr.io_signature(1, 1, constructor_to_size[self.constructor]), + ) + #create usrp object + u = self.constructor(number, nchan=1) + if subdev_spec is None: subdev_spec = usrp.pick_rx_subdevice(u) + u.set_decim_rate(decimation) + if mux is None: mux = usrp.determine_rx_mux_value(u, subdev_spec) + u.set_mux(mux) + subdev = _setup_rx_subdev(u, subdev_spec, 0, gain, frequency, auto_tr, rx_ant) + _simple_usrp.__init__(self, u, subdev, 0) + #connect + self.connect(u, self) + + def set_decim_rate(self, decim): self.get_u().set_decim_rate(int(decim)) + +class simple_source_c(_simple_source): constructor = usrp.source_c +class simple_source_s(_simple_source): constructor = usrp.source_s + +#################################################################### +# Simple USRP Sink +#################################################################### +class _simple_sink(gr.hier_block2, _simple_usrp): + """A single usrp sink of IO type short or complex.""" + + def __init__(self, number, subdev_spec, frequency, interpolation, gain, mux=None, auto_tr=None, tx_enb=None): + """! + USRP simple sink contructor. + @param number the unit number + @param subdev_spec the sub-device specification tuple + @param frequency the frequency to tune + @param interpolation the device interpolation + @param gain the gain to set + @param mux the mux in hex or None + @param auto_tr auto transmit/receive True, False, or None + @param tx_enb the transmit enable: True, False, or None + """ + #initialize hier2 block + gr.hier_block2.__init__( + self, 'usrp_simple_sink', + gr.io_signature(1, 1, constructor_to_size[self.constructor]), + gr.io_signature(0, 0, 0), + ) + #create usrp object + u = self.constructor(number, nchan=1) + if subdev_spec is None: subdev_spec = usrp.pick_tx_subdevice(u) + u.set_interp_rate(interpolation) + if mux is None: mux = usrp.determine_tx_mux_value(u, subdev_spec) + u.set_mux(mux) + subdev = _setup_tx_subdev(u, subdev_spec, gain, frequency, auto_tr, tx_enb) + _simple_usrp.__init__(self, u, subdev, subdev._which) + #connect + self.connect(self, u) + + def set_interp_rate(self, interp): self.get_u().set_interp_rate(int(interp)) + +class simple_sink_c(_simple_sink): constructor = usrp.sink_c +class simple_sink_s(_simple_sink): constructor = usrp.sink_s + +#################################################################### +#################################################################### +# Dual USRP Base Classes +#################################################################### +#################################################################### + +class _dual_usrp(object): + """A dual usrp source/sink base class.""" + + def __init__(self, u, subdev_a, subdev_b, which_a, which_b): + """! + Create a dual usrp base class. + @param u the usrp object + @param subdev_a the subdevice object side a + @param subdev_b the subdevice object side b + @param which_a specifies the DDC/DUC number when tuning side a + @param which_b specifies the DDC/DUC number when tuning side b + """ + self._u = u + self._subdev_a = subdev_a + self._subdev_b = subdev_b + self._which_a = which_a + self._which_b = which_b + + def get_u(self): + """! + Get the underlying usrp object. + @return the usrp source/sink object. + """ + return self._u + + def get_subdev_a(self): + """! + Get the underlying subdevice. + @return the subdev object. + """ + return self._subdev_a + + def get_subdev_b(self): + """! + Get the underlying subdevice. + @return the subdev object. + """ + return self._subdev_b + + def set_frequency_a(self, frequency): + """! + Set the frequency of the subdevice. + @param frequency the frequency to tune + """ + _set_frequency(self.get_u(), self._which_a, self.get_subdev_a(), frequency) + + def set_frequency_b(self, frequency): + """! + Set the frequency of the subdevice. + @param frequency the frequency to tune + """ + _set_frequency(self.get_u(), self._which_b, self.get_subdev_b(), frequency) + + def set_gain_a(self, gain): + """! + Set the gain of the subdevice. + @param gain the gain to set + """ + self.get_subdev_a().set_gain(gain) + + def set_gain_b(self, gain): + """! + Set the gain of the subdevice. + @param gain the gain to set + """ + self.get_subdev_b().set_gain(gain) + +#################################################################### +# Dual USRP Source +#################################################################### +class _dual_source(gr.hier_block2, _dual_usrp): + """A dual usrp source of IO type short or complex.""" + + def __init__(self, number, frequency_a, frequency_b, decimation, gain_a, gain_b, mux=0x3210, auto_tr=None, rx_ant_a=None, rx_ant_b=None): + """! + USRP dual source contructor. + @param number the unit number + @param frequency_a the frequency to tune side a + @param frequency_b the frequency to tune side b + @param decimation the device decimation + @param gain_a the gain to set side a + @param gain_b the gain to set side b + @param mux the mux in hex + @param auto_tr auto transmit/receive True, False, or None + @param rx_ant_a the receive antenna side a: 'TX/RX', 'RX2', or None + @param rx_ant_b the receive antenna side b: 'TX/RX', 'RX2', or None + """ + #initialize hier2 block + gr.hier_block2.__init__( + self, 'usrp_dual_source', + gr.io_signature(0, 0, 0), + gr.io_signature(2, 2, constructor_to_size[self.constructor]), + ) + #create usrp object + u = self.constructor(number, nchan=2) + u.set_decim_rate(decimation) + u.set_mux(mux) + subdev_a = _setup_rx_subdev(u, (0, 0), 0, gain_a, frequency_a, auto_tr, rx_ant_a) + subdev_b = _setup_rx_subdev(u, (1, 0), 1, gain_b, frequency_b, auto_tr, rx_ant_b) + _dual_usrp.__init__(self, u, subdev_a, subdev_b, 0, 1) + #connect + deinter = gr.deinterleave(constructor_to_size[self.constructor]) + self.connect(u, deinter) + for i in range(2): self.connect((deinter, i), (self, i)) + + def set_decim_rate(self, decim): self.get_u().set_decim_rate(int(decim)) + +class dual_source_c(_dual_source): constructor = usrp.source_c +class dual_source_s(_dual_source): constructor = usrp.source_s + +#################################################################### +# Dual USRP Sink +#################################################################### +class _dual_sink(gr.hier_block2, _dual_usrp): + """A dual usrp sink of IO type short or complex.""" + + def __init__(self, number, frequency_a, frequency_b, interpolation, gain_a, gain_b, mux=0xba98, auto_tr=None, tx_enb_a=None, tx_enb_b=None): + """! + USRP dual sink contructor. + @param number the unit number + @param subdev_spec the sub-device specification tuple + @param frequency the frequency to tune + @param interpolation the device interpolation + @param gain the gain to set + @param mux the mux in hex or None + @param auto_tr auto transmit/receive True, False, or None + @param tx_enb the transmit enable: True, False, or None + """ + #initialize hier2 block + gr.hier_block2.__init__( + self, 'usrp_dual_sink', + gr.io_signature(2, 2, constructor_to_size[self.constructor]), + gr.io_signature(0, 0, 0), + ) + #create usrp object + u = self.constructor(number, nchan=2) + u.set_interp_rate(interpolation) + u.set_mux(mux) + subdev_a = _setup_tx_subdev(u, (0, 0), gain_a, frequency_a, auto_tr, tx_enb_a) + subdev_b = _setup_tx_subdev(u, (1, 0), gain_b, frequency_b, auto_tr, tx_enb_b) + _dual_usrp.__init__(self, u, subdev_a, subdev_b, subdev_a._which, subdev_b._which) + #connect + inter = gr.interleave(constructor_to_size[self.constructor]) + self.connect(inter, u) + for i in range(2): self.connect((self, i), (inter, i)) + + def set_interp_rate(self, interp): self.get_u().set_interp_rate(int(interp)) + +class dual_sink_c(_dual_sink): constructor = usrp.sink_c +class dual_sink_s(_dual_sink): constructor = usrp.sink_s + diff --git a/grc/src/grc_gnuradio/utils/Makefile.am b/grc/src/grc_gnuradio/utils/Makefile.am new file mode 100644 index 000000000..01020139e --- /dev/null +++ b/grc/src/grc_gnuradio/utils/Makefile.am @@ -0,0 +1,30 @@ +# +# Copyright 2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +include $(top_srcdir)/Makefile.common + +ourpythondir = $(pythondir)/grc_gnuradio/utils + +ourpython_PYTHON = \ + __init__.py \ + convert_hier.py \ + expr_utils.py \ + extract_docs.py diff --git a/grc/src/grc_gnuradio/utils/__init__.py b/grc/src/grc_gnuradio/utils/__init__.py new file mode 100644 index 000000000..b6402601c --- /dev/null +++ b/grc/src/grc_gnuradio/utils/__init__.py @@ -0,0 +1,22 @@ +""" +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 +""" +##@package grc_gnuradio.utils +#utility functions and classes +#@author Josh Blum + diff --git a/grc/src/grc_gnuradio/utils/convert_hier.py b/grc/src/grc_gnuradio/utils/convert_hier.py new file mode 100644 index 000000000..a0f3c2a85 --- /dev/null +++ b/grc/src/grc_gnuradio/utils/convert_hier.py @@ -0,0 +1,81 @@ +""" +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 +""" +##@package grc_gnuradio.utils.convert_hier +#Utility functions to convert a grc hier block to an xml wrapper +#@author Josh Blum + +from grc_gnuradio.Constants import BLOCK_DTD +from grc import ParseXML +from grc.Utils 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 + 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]), + ) + #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/src/grc_gnuradio/utils/expr_utils.py b/grc/src/grc_gnuradio/utils/expr_utils.py new file mode 100644 index 000000000..8253f018a --- /dev/null +++ b/grc/src/grc_gnuradio/utils/expr_utils.py @@ -0,0 +1,140 @@ +""" +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 +""" +##@package grc_gnuradio.utils.expr_utils +#Utility functions to comprehend variable expressions. +#@author Josh Blum + +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_prepend(expr, vars, prepend): + """! + Search for vars in the expression and add the prepend. + @param expr an expression string + @param vars a list of variable names + @param prepend the prepend string + @return a new expression with the prepend + """ + expr_splits = expr_split(expr) + for i, es in enumerate(expr_splits): + if es in vars: expr_splits[i] = prepend + 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): + 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/src/grc_gnuradio/utils/extract_docs.py b/grc/src/grc_gnuradio/utils/extract_docs.py new file mode 100644 index 000000000..bc0dcd446 --- /dev/null +++ b/grc/src/grc_gnuradio/utils/extract_docs.py @@ -0,0 +1,109 @@ +""" +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 +""" +##@package grc_gnuradio.utils.extract_docs +#Extract documentation from the gnuradio doxygen files. +#@author Josh Blum + +from grc_gnuradio.Constants import * +from lxml import etree +import os + +DOXYGEN_NAME_XPATH = '/doxygen/compounddef/compoundname' +DOXYGEN_BRIEFDESC_GR_XPATH = '/doxygen/compounddef/briefdescription' +DOXYGEN_DETAILDESC_GR_XPATH = '/doxygen/compounddef/detaileddescription' +DOXYGEN_BRIEFDESC_BLKS2_XPATH = '/doxygen/compounddef/sectiondef[@kind="public-func"]/memberdef/briefdescription' +DOXYGEN_DETAILDESC_BLKS2_XPATH = '/doxygen/compounddef/sectiondef[@kind="public-func"]/memberdef/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 '' + if not len(xml): return text + return ''.join([text] + map(extract_txt, xml)) + +def is_match(key, file): + """! + Is the block key a match for the given file name? + @param key block key + @param file the xml file name + @return true if matches + """ + if not file.endswith('.xml'): return False + file = file.replace('.xml', '') #remove file ext + file = file.replace('__', '_') #doxygen xml files have 2 underscores + if key.startswith('gr_'): + if not file.startswith('classgr_'): return False + key = key.replace('gr_', 'classgr_') + elif key.startswith('trellis_'): + if not file.startswith('classtrellis_'): return False + key = key.replace('trellis_', 'classtrellis_') + elif key.startswith('blks2_'): + if not file.startswith('classgnuradio_'): return False + if 'blks2' not in file: return False + file = file.replace('_1_1', '_') #weird blks2 doxygen syntax + key = key.replace('blks2_', '') + else: return False + for k, f in zip(*map(reversed, map(lambda x: x.split('_'), [key, file]))): + if k == f: continue + ks = k.split('x') + if len(ks) == 2 and f.startswith(ks[0]) and f.endswith(ks[1]): continue + if len(ks) > 2 and all(ki in ('x', fi) for ki, fi in zip(k, f)): continue + return False + return True + +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 + """ + #get potential xml file matches for the key + if os.path.exists(DOCS_DIR) and os.path.isdir(DOCS_DIR): + matches = filter(lambda f: is_match(key, f), os.listdir(DOCS_DIR)) + else: matches = list() + #combine all matches + doc_strs = list() + for match in matches: + try: + xml_file = DOCS_DIR + '/' + match + xml = etree.parse(xml_file) + #extract descriptions + comp_name = extract_txt(xml.xpath(DOXYGEN_NAME_XPATH)[0]).strip('\n') + comp_name = ' --- ' + comp_name + ' --- ' + if key.startswith('gr_') or key.startswith('trellis_'): + brief_desc = extract_txt(xml.xpath(DOXYGEN_BRIEFDESC_GR_XPATH)[0]).strip('\n') + detailed_desc = extract_txt(xml.xpath(DOXYGEN_DETAILDESC_GR_XPATH)[0]).strip('\n') + elif key.startswith('blks2_'): + brief_desc = extract_txt(xml.xpath(DOXYGEN_BRIEFDESC_BLKS2_XPATH)[0]).strip('\n') + detailed_desc = extract_txt(xml.xpath(DOXYGEN_DETAILDESC_BLKS2_XPATH)[0]).strip('\n') + else: + brief_desc = '' + detailed_desc = '' + #combine + doc_strs.append('\n'.join([comp_name, brief_desc, detailed_desc]).strip('\n')) + except IndexError: pass #bad format + return '\n\n'.join(doc_strs) + +if __name__ == '__main__': + import sys + print extract(sys.argv[1]) diff --git a/grc/src/grc_gnuradio/wxgui/Makefile.am b/grc/src/grc_gnuradio/wxgui/Makefile.am new file mode 100644 index 000000000..2e7072eae --- /dev/null +++ b/grc/src/grc_gnuradio/wxgui/Makefile.am @@ -0,0 +1,29 @@ +# +# Copyright 2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +include $(top_srcdir)/Makefile.common + +ourpythondir = $(pythondir)/grc_gnuradio/wxgui + +ourpython_PYTHON = \ + __init__.py \ + callback_controls.py \ + top_block_gui.py diff --git a/grc/src/grc_gnuradio/wxgui/__init__.py b/grc/src/grc_gnuradio/wxgui/__init__.py new file mode 100644 index 000000000..c0fdca52c --- /dev/null +++ b/grc/src/grc_gnuradio/wxgui/__init__.py @@ -0,0 +1,30 @@ +# Copyright 2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from callback_controls import \ + button_control, \ + drop_down_control, \ + radio_buttons_horizontal_control, \ + radio_buttons_vertical_control, \ + slider_horizontal_control, \ + slider_vertical_control, \ + text_box_control +from top_block_gui import top_block_gui + diff --git a/grc/src/grc_gnuradio/wxgui/callback_controls.py b/grc/src/grc_gnuradio/wxgui/callback_controls.py new file mode 100644 index 000000000..c1ba784eb --- /dev/null +++ b/grc/src/grc_gnuradio/wxgui/callback_controls.py @@ -0,0 +1,281 @@ +# Copyright 2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import wx +import sys + +class LabelText(wx.StaticText): + """Label text class for uniform labels among all controls.""" + + def __init__(self, window, label): + wx.StaticText.__init__(self, window, -1, str(label)) + font = self.GetFont() + font.SetWeight(wx.FONTWEIGHT_BOLD) + self.SetFont(font) + +class _control_base(wx.BoxSizer): + """Control base class""" + + def __init__(self, window, callback): + self.window = window + self.callback = callback + wx.BoxSizer.__init__(self, wx.VERTICAL) + + def get_window(self): return self.window + + def call(self): return self.callback(self.get_value()) + + def get_value(self): raise NotImplementedError + +class _chooser_control_base(_control_base): + """House a drop down or radio buttons for variable control.""" + + def __init__(self, window, callback, label='Label', index=0, choices=[0], labels=[]): + """! + Chooser contructor. + Create the slider, text box, and label. + @param window the wx parent window + @param callback call the callback on changes + @param label the label title + @param index the default choice index + @param choices a list of choices + @param labels the choice labels or empty list + """ + #initialize + _control_base.__init__(self, window, callback) + label_text = LabelText(self.get_window(), label) + self.Add(label_text, 0, wx.ALIGN_CENTER) + self.index = index + self.choices = choices + self.labels = map(str, labels or choices) + self._init() + + def _handle_changed(self, event=None): + """! + A change is detected. Call the callback. + """ + try: self.call() + except Exception, e: print >> sys.stderr, 'Error in exec callback from handle changed.\n', e + + def get_value(self): + """! + Update the chooser. + @return one of the possible choices + """ + self._update() + return self.choices[self.index] + +############################################################################################## +# Button Control +############################################################################################## +class button_control(_chooser_control_base): + """House a button for variable control.""" + + def _init(self): + self.button = wx.Button(self.get_window(), -1, self.labels[self.index]) + self.button.Bind(wx.EVT_BUTTON, self._handle_changed) + self.Add(self.button, 0, wx.ALIGN_CENTER) + + def _update(self): + self.index = (self.index + 1)%len(self.choices) #circularly increment index + self.button.SetLabel(self.labels[self.index]) + +############################################################################################## +# Drop Down Control +############################################################################################## +class drop_down_control(_chooser_control_base): + """House a drop down for variable control.""" + + def _init(self): + self.drop_down = wx.Choice(self.get_window(), -1, choices=self.labels) + self.Add(self.drop_down, 0, wx.ALIGN_CENTER) + self.drop_down.Bind(wx.EVT_CHOICE, self._handle_changed) + self.drop_down.SetSelection(self.index) + + def _update(self): + self.index = self.drop_down.GetSelection() + +############################################################################################## +# Radio Buttons Control +############################################################################################## +class _radio_buttons_control_base(_chooser_control_base): + """House radio buttons for variable control.""" + + def _init(self): + #create box for radio buttons + radio_box = wx.BoxSizer(self.radio_box_orientation) + panel = wx.Panel(self.get_window(), -1) + panel.SetSizer(radio_box) + self.Add(panel, 0, wx.ALIGN_CENTER) + #create radio buttons + self.radio_buttons = list() + for label in self.labels: + radio_button = wx.RadioButton(panel, -1, label) + radio_button.SetValue(False) + self.radio_buttons.append(radio_button) + radio_box.Add(radio_button, 0, self.radio_button_align) + radio_button.Bind(wx.EVT_RADIOBUTTON, self._handle_changed) + #set one radio button active + self.radio_buttons[self.index].SetValue(True) + + def _update(self): + selected_radio_button = filter(lambda rb: rb.GetValue(), self.radio_buttons)[0] + self.index = self.radio_buttons.index(selected_radio_button) + +class radio_buttons_horizontal_control(_radio_buttons_control_base): + radio_box_orientation = wx.HORIZONTAL + radio_button_align = wx.ALIGN_CENTER +class radio_buttons_vertical_control(_radio_buttons_control_base): + radio_box_orientation = wx.VERTICAL + radio_button_align = wx.ALIGN_LEFT + +############################################################################################## +# Slider Control +############################################################################################## +class _slider_control_base(_control_base): + """House a Slider and a Text Box for variable control.""" + + def __init__(self, window, callback, label='Label', value=50, min=0, max=100, num_steps=100): + """! + Slider contructor. + Create the slider, text box, and label. + @param window the wx parent window + @param callback call the callback on changes + @param label the label title + @param value the default value + @param min the min + @param max the max + @param num_steps the number of steps + """ + #initialize + _control_base.__init__(self, window, callback) + self.min = float(min) + self.max = float(max) + self.num_steps = int(num_steps) + #create gui elements + label_text_sizer = wx.BoxSizer(self.label_text_orientation) #label and text box container + label_text = LabelText(self.get_window(), '%s: '%str(label)) + self.text_box = text_box = wx.TextCtrl(self.get_window(), -1, str(value), style=wx.TE_PROCESS_ENTER) + text_box.Bind(wx.EVT_TEXT_ENTER, self._handle_enter) #bind this special enter hotkey event + for obj in (label_text, text_box): #fill the container with label and text entry box + label_text_sizer.Add(obj, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL) + self.Add(label_text_sizer, 0, wx.ALIGN_CENTER) + #make the slider + self.slider = slider = wx.Slider(self.get_window(), -1, size=wx.Size(*self.slider_size), style=self.slider_style) + try: slider.SetRange(0, num_steps) + except Exception, e: + print >> sys.stderr, 'Error in set slider range: "%s".'%e + sys.exit(-1) + slider.Bind(wx.EVT_SCROLL, self._handle_scroll) #bind the scrolling event + self.Add(slider, 0, wx.ALIGN_CENTER) + #init slider and text box + self._value = value + self._set_slider_value(self._value) #sets the slider's value + self.text_box.SetValue(str(self._value)) + + def get_value(self): + """! + Get the current set value. + @return the value (float) + """ + return self._value + + def _set_slider_value(self, real_value): + """! + Translate the real numerical value into a slider value and, + write the value to the slider. + @param real_value the numeric value the slider should represent + """ + slider_value = (float(real_value) - self.min)*self.num_steps/(self.max - self.min) + self.slider.SetValue(slider_value) + + def _handle_scroll(self, event=None): + """! + A scroll event is detected. Read the slider, call the callback. + """ + slider_value = self.slider.GetValue() + new_value = slider_value*(self.max - self.min)/self.num_steps + self.min + self.text_box.SetValue(str(new_value)) + self._value = new_value + try: self.call() + except Exception, e: print >> sys.stderr, 'Error in exec callback from handle scroll.\n', e + + def _handle_enter(self, event=None): + """! + An enter key was pressed. Read the text box, call the callback. + """ + new_value = float(self.text_box.GetValue()) + self._set_slider_value(new_value) + self._value = new_value + try: self.call() + except Exception, e: print >> sys.stderr, 'Error in exec callback from handle enter.\n', e + +class slider_horizontal_control(_slider_control_base): + label_text_orientation = wx.HORIZONTAL + slider_style = wx.SL_HORIZONTAL + slider_size = 200, 20 +class slider_vertical_control(_slider_control_base): + label_text_orientation = wx.VERTICAL + slider_style = wx.SL_VERTICAL + slider_size = 20, 200 + +############################################################################################## +# Text Box Control +############################################################################################## +class text_box_control(_control_base): + """House a Text Box for variable control.""" + + def __init__(self, window, callback, label='Label', value=50): + """! + Text box contructor. + Create the text box, and label. + @param window the wx parent window + @param callback call the callback on changes + @param label the label title + @param value the default value + """ + #initialize + _control_base.__init__(self, window, callback) + #create gui elements + label_text_sizer = wx.BoxSizer(wx.HORIZONTAL) #label and text box container + label_text = LabelText(self.get_window(), '%s: '%str(label)) + self.text_box = text_box = wx.TextCtrl(self.get_window(), -1, str(value), style=wx.TE_PROCESS_ENTER) + text_box.Bind(wx.EVT_TEXT_ENTER, self._handle_enter) #bind this special enter hotkey event + for obj in (label_text, text_box): #fill the container with label and text entry box + label_text_sizer.Add(obj, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL) + self.Add(label_text_sizer, 0, wx.ALIGN_CENTER) + self.text_box.SetValue(str(value)) + + def get_value(self): + """! + Get the current set value. + @return the value (float) + """ + return self._value + + def _handle_enter(self, event=None): + """! + An enter key was pressed. Read the text box, call the callback. + If the text cannot be evaluated, do not try callback. + """ + try: self._value = eval(self.text_box.GetValue()) + except: return + try: self.call() + except Exception, e: print >> sys.stderr, 'Error in exec callback from handle enter.\n', e diff --git a/grc/src/grc_gnuradio/wxgui/top_block_gui.py b/grc/src/grc_gnuradio/wxgui/top_block_gui.py new file mode 100644 index 000000000..d56d20056 --- /dev/null +++ b/grc/src/grc_gnuradio/wxgui/top_block_gui.py @@ -0,0 +1,99 @@ +# Copyright 2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import wx +import sys, os +from gnuradio import gr + +default_gui_size = (200, 100) + +class top_block_gui(gr.top_block): + """gr top block with wx gui app and grid sizer.""" + + def __init__(self, title='', size=default_gui_size, icon=None): + """! + Initialize the gr top block. + Create the wx gui elements. + @param title the main window title + @param size the main window size tuple in pixels + @param icon the file path to an icon or None + """ + #initialize + gr.top_block.__init__(self) + self._size = size + #set the icon + if icon and os.path.isfile(icon): self._icon = icon + else: self._icon = None + #create gui elements + self._wx_app = wx.App() + self._wx_frame = wx.Frame(None , -1, title) + self._wx_grid = wx.GridBagSizer(5, 5) + self._wx_vbox = wx.BoxSizer(wx.VERTICAL) + + def GetWin(self): + """! + Get the window for wx elements to fit within. + @return the wx frame + """ + return self._wx_frame + + def Add(self, win): + """! + Add a window to the wx vbox. + @param win the wx window + """ + self._wx_vbox.Add(win, 0, wx.EXPAND) + + def GridAdd(self, win, row, col, row_span=1, col_span=1): + """! + Add a window to the wx grid at the given position. + @param win the wx window + @param row the row specification (integer >= 0) + @param col the column specification (integer >= 0) + @param row_span the row span specification (integer >= 1) + @param col_span the column span specification (integer >= 1) + """ + self._wx_grid.Add(win, wx.GBPosition(row, col), wx.GBSpan(row_span, col_span), wx.EXPAND) + + def Run(self): + """! + Setup the wx gui elements. + Start the gr top block. + Block with the wx main loop. + """ + #set wx app icon + if self._icon: self._wx_frame.SetIcon(wx.Icon(self._icon, wx.BITMAP_TYPE_ANY)) + #set minimal window size + self._wx_frame.SetSizeHints(*self._size) + #create callback for quit + def _quit(event): + gr.top_block.stop(self) + self._wx_frame.Destroy() + #setup app + self._wx_vbox.Add(self._wx_grid, 0, wx.EXPAND) + self._wx_frame.Bind(wx.EVT_CLOSE, _quit) + self._wx_frame.SetSizerAndFit(self._wx_vbox) + self._wx_frame.Show() + self._wx_app.SetTopWindow(self._wx_frame) + #start flow graph + gr.top_block.start(self) + #blocking main loop + self._wx_app.MainLoop() + |