summaryrefslogtreecommitdiff
path: root/grc/core/blocks/embedded_python.py
diff options
context:
space:
mode:
authorMarcus Müller <marcus@hostalia.de>2018-09-21 00:00:57 +0200
committerMarcus Müller <marcus@hostalia.de>2018-09-21 00:00:57 +0200
commit267d669eb21c514c18a6ee979f5cf247d251f1ad (patch)
treec6120f5993f82daf13894e8bf905c4169493152e /grc/core/blocks/embedded_python.py
parent896d1c9da31963ecf5b0d90942c2af51ca998a69 (diff)
parent7b20b28a9e5aa4e32ee37e89e7f80d74485344e8 (diff)
Merge branch 'merge_next' (which merges next)
This has been in the making far too long. We finally merge the next branch into master, in preparation of releasing GNU Radio 3.8. There will be breakage. There will be awesomeness. There will be progress in the greatest SDR framework to ever grace the surface of the earth. Hold tight for now.
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',
+ )