summaryrefslogtreecommitdiff
path: root/grc/core/blocks/embedded_python.py
diff options
context:
space:
mode:
Diffstat (limited to 'grc/core/blocks/embedded_python.py')
-rw-r--r--grc/core/blocks/embedded_python.py242
1 files changed, 242 insertions, 0 deletions
diff --git a/grc/core/blocks/embedded_python.py b/grc/core/blocks/embedded_python.py
new file mode 100644
index 0000000000..0b5a7a21c5
--- /dev/null
+++ b/grc/core/blocks/embedded_python.py
@@ -0,0 +1,242 @@
+# Copyright 2015-16 Free Software Foundation, Inc.
+# This file is part of GNU Radio
+#
+# GNU Radio Companion is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# GNU Radio Companion is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+
+from __future__ import absolute_import
+
+from ast import literal_eval
+from textwrap import dedent
+
+from . import Block, register_build_in
+from ._templates import MakoTemplates
+
+from .. import utils
+from ..base import Element
+
+
+DEFAULT_CODE = '''\
+"""
+Embedded Python Blocks:
+
+Each time this file is saved, GRC will instantiate the first class it finds
+to get ports and parameters of your block. The arguments to __init__ will
+be the parameters. All of them are required to have default values!
+"""
+
+import numpy as np
+from gnuradio import gr
+
+
+class blk(gr.sync_block): # other base classes are basic_block, decim_block, interp_block
+ """Embedded Python Block example - a simple multiply const"""
+
+ def __init__(self, example_param=1.0): # only default arguments here
+ """arguments to this function show up as parameters in GRC"""
+ gr.sync_block.__init__(
+ self,
+ name='Embedded Python Block', # will show up in GRC
+ in_sig=[np.complex64],
+ out_sig=[np.complex64]
+ )
+ # if an attribute with the same name as a parameter is found,
+ # a callback is registered (properties work, too).
+ self.example_param = example_param
+
+ def work(self, input_items, output_items):
+ """example: multiply with constant"""
+ output_items[0][:] = input_items[0] * self.example_param
+ return len(output_items[0])
+'''
+
+DOC = """
+This block represents an arbitrary GNU Radio Python Block.
+
+Its source code can be accessed through the parameter 'Code' which opens your editor. \
+Each time you save changes in the editor, GRC will update the block. \
+This includes the number, names and defaults of the parameters, \
+the ports (stream and message) and the block name and documentation.
+
+Block Documentation:
+(will be replaced the docstring of your block class)
+"""
+
+
+@register_build_in
+class EPyBlock(Block):
+
+ key = 'epy_block'
+ label = 'Python Block'
+ documentation = {'': DOC}
+
+ parameters_data = [dict(
+ label='Code',
+ id='_source_code',
+ dtype='_multiline_python_external',
+ value=DEFAULT_CODE,
+ hide='part',
+ )]
+ inputs_data = []
+ outputs_data = []
+
+ def __init__(self, flow_graph, **kwargs):
+ super(EPyBlock, self).__init__(flow_graph, **kwargs)
+ self.states['_io_cache'] = ''
+
+ self._epy_source_hash = -1
+ self._epy_reload_error = None
+
+ def rewrite(self):
+ Element.rewrite(self)
+
+ param_src = self.params['_source_code']
+
+ src = param_src.get_value()
+ src_hash = hash((self.name, src))
+ if src_hash == self._epy_source_hash:
+ return
+
+ try:
+ blk_io = utils.epy_block_io.extract(src)
+
+ except Exception as e:
+ self._epy_reload_error = ValueError(str(e))
+ try: # Load last working block io
+ blk_io_args = literal_eval(self.states['_io_cache'])
+ if len(blk_io_args) == 6:
+ blk_io_args += ([],) # add empty callbacks
+ blk_io = utils.epy_block_io.BlockIO(*blk_io_args)
+ except Exception:
+ return
+ else:
+ self._epy_reload_error = None # Clear previous errors
+ self.states['_io_cache'] = repr(tuple(blk_io))
+
+ # print "Rewriting embedded python block {!r}".format(self.name)
+ self._epy_source_hash = src_hash
+
+ self.label = blk_io.name or blk_io.cls
+ self.documentation = {'': blk_io.doc}
+
+ self.templates['imports'] = 'import ' + self.name
+ self.templates['make'] = '{mod}.{cls}({args})'.format(
+ mod=self.name,
+ cls=blk_io.cls,
+ args=', '.join('{0}=${{ {0} }}'.format(key) for key, _ in blk_io.params))
+ self.templates['callbacks'] = [
+ '{0} = ${{ {0} }}'.format(attr) for attr in blk_io.callbacks
+ ]
+
+ self._update_params(blk_io.params)
+ self._update_ports('in', self.sinks, blk_io.sinks, 'sink')
+ self._update_ports('out', self.sources, blk_io.sources, 'source')
+
+ super(EPyBlock, self).rewrite()
+
+ def _update_params(self, params_in_src):
+ param_factory = self.parent_platform.make_param
+ params = {}
+ for param in list(self.params):
+ if hasattr(param, '__epy_param__'):
+ params[param.key] = param
+ del self.params[param.key]
+
+ for id_, value in params_in_src:
+ try:
+ param = params[id_]
+ if param.default == param.value:
+ param.set_value(value)
+ param.default = str(value)
+ except KeyError: # need to make a new param
+ param = param_factory(
+ parent=self, id=id_, dtype='raw', value=value,
+ name=id_.replace('_', ' ').title(),
+ )
+ setattr(param, '__epy_param__', True)
+ self.params[id_] = param
+
+ def _update_ports(self, label, ports, port_specs, direction):
+ port_factory = self.parent_platform.make_port
+ ports_to_remove = list(ports)
+ iter_ports = iter(ports)
+ ports_new = []
+ port_current = next(iter_ports, None)
+ for key, port_type, vlen in port_specs:
+ reuse_port = (
+ port_current is not None and
+ port_current.dtype == port_type and
+ port_current.vlen == vlen and
+ (key.isdigit() or port_current.key == key)
+ )
+ if reuse_port:
+ ports_to_remove.remove(port_current)
+ port, port_current = port_current, next(iter_ports, None)
+ else:
+ n = dict(name=label + str(key), dtype=port_type, id=key)
+ if port_type == 'message':
+ n['name'] = key
+ n['optional'] = '1'
+ if vlen > 1:
+ n['vlen'] = str(vlen)
+ port = port_factory(self, direction=direction, **n)
+ ports_new.append(port)
+ # replace old port list with new one
+ del ports[:]
+ ports.extend(ports_new)
+ # remove excess port connections
+ self.parent_flowgraph.disconnect(*ports_to_remove)
+
+ def validate(self):
+ super(EPyBlock, self).validate()
+ if self._epy_reload_error:
+ self.params['_source_code'].add_error_message(str(self._epy_reload_error))
+
+
+@register_build_in
+class EPyModule(Block):
+ key = 'epy_module'
+ label = 'Python Module'
+ documentation = {'': dedent("""
+ This block lets you embed a python module in your flowgraph.
+
+ Code you put in this module is accessible in other blocks using the ID of this
+ block. Example:
+
+ If you put
+
+ a = 2
+
+ def double(arg):
+ return 2 * arg
+
+ in a Python Module Block with the ID 'stuff' you can use code like
+
+ stuff.a # evals to 2
+ stuff.double(3) # evals to 6
+
+ to set parameters of other blocks in your flowgraph.
+ """)}
+
+ parameters_data = [dict(
+ label='Code',
+ id='source_code',
+ dtype='_multiline_python_external',
+ value='# this module will be imported in the into your flowgraph',
+ hide='part',
+ )]
+
+ templates = MakoTemplates(
+ imports='import ${ id } # embedded python module',
+ )