summaryrefslogtreecommitdiff
path: root/grc/core/blocks
diff options
context:
space:
mode:
authorSebastian Koslowski <sebastian.koslowski@gmail.com>2016-05-03 17:13:08 +0200
committerJohnathan Corgan <johnathan@corganlabs.com>2017-06-29 09:16:49 -0700
commit7f7fa2f91467fdb2b11312be8562e7b51fdeb199 (patch)
tree24268bac15b9920d2a15ddbb45eaf3b9b03718a1 /grc/core/blocks
parent44cae388881821942e691a4d69a923bbd8d347db (diff)
grc: added yaml/mako support
Includes basic converter from XML/Cheetah to YAML/Mako based block format.
Diffstat (limited to 'grc/core/blocks')
-rw-r--r--grc/core/blocks/__init__.py37
-rw-r--r--grc/core/blocks/_build.py69
-rw-r--r--grc/core/blocks/_flags.py39
-rw-r--r--grc/core/blocks/_templates.py77
-rw-r--r--grc/core/blocks/block.py416
-rw-r--r--grc/core/blocks/dummy.py54
-rw-r--r--grc/core/blocks/embedded_python.py242
-rw-r--r--grc/core/blocks/virtual.py76
8 files changed, 1010 insertions, 0 deletions
diff --git a/grc/core/blocks/__init__.py b/grc/core/blocks/__init__.py
new file mode 100644
index 0000000000..e4a085d477
--- /dev/null
+++ b/grc/core/blocks/__init__.py
@@ -0,0 +1,37 @@
+# Copyright 2016 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 ._flags import Flags
+from ._templates import MakoTemplates
+
+from .block import Block
+
+from ._build import build
+
+
+build_ins = {}
+
+
+def register_build_in(cls):
+ build_ins[cls.key] = cls
+ return cls
+
+from .dummy import DummyBlock
+from .embedded_python import EPyBlock, EPyModule
+from .virtual import VirtualSink, VirtualSource
diff --git a/grc/core/blocks/_build.py b/grc/core/blocks/_build.py
new file mode 100644
index 0000000000..9a50086cea
--- /dev/null
+++ b/grc/core/blocks/_build.py
@@ -0,0 +1,69 @@
+# Copyright 2016 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
+
+import re
+
+from .block import Block
+from ._flags import Flags
+from ._templates import MakoTemplates
+
+
+def build(id, label='', category='', flags='', documentation='',
+ checks=None, value=None,
+ parameters=None, inputs=None, outputs=None, templates=None, **kwargs):
+ block_id = id
+
+ cls = type(block_id, (Block,), {})
+ cls.key = block_id
+
+ cls.label = label or block_id.title()
+ cls.category = [cat.strip() for cat in category.split('/') if cat.strip()]
+
+ cls.flags = Flags(flags)
+ if re.match(r'options$|variable|virtual', block_id):
+ cls.flags += Flags.NOT_DSP + Flags.DISABLE_BYPASS
+
+ cls.documentation = {'': documentation.strip('\n\t ').replace('\\\n', '')}
+
+ cls.checks = [_single_mako_expr(check, block_id) for check in (checks or [])]
+
+ cls.parameters_data = parameters or []
+ cls.inputs_data = inputs or []
+ cls.outputs_data = outputs or []
+ cls.extra_data = kwargs
+
+ templates = templates or {}
+ cls.templates = MakoTemplates(
+ imports=templates.get('imports', ''),
+ make=templates.get('make', ''),
+ callbacks=templates.get('callbacks', []),
+ var_make=templates.get('var_make', ''),
+ )
+ # todo: MakoTemplates.compile() to check for errors
+
+ cls.value = _single_mako_expr(value, block_id)
+
+ return cls
+
+
+def _single_mako_expr(value, block_id):
+ match = re.match(r'\s*\$\{\s*(.*?)\s*\}\s*', str(value))
+ if value and not match:
+ raise ValueError('{} is not a mako substitution in {}'.format(value, block_id))
+ return match.group(1) if match else None
diff --git a/grc/core/blocks/_flags.py b/grc/core/blocks/_flags.py
new file mode 100644
index 0000000000..ffea2ad569
--- /dev/null
+++ b/grc/core/blocks/_flags.py
@@ -0,0 +1,39 @@
+# Copyright 2016 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
+
+import six
+
+
+class Flags(six.text_type):
+
+ THROTTLE = 'throttle'
+ DISABLE_BYPASS = 'disable_bypass'
+ NEED_QT_GUI = 'need_qt_gui'
+ DEPRECATED = 'deprecated'
+ NOT_DSP = 'not_dsp'
+
+ def __getattr__(self, item):
+ return item in self
+
+ def __add__(self, other):
+ if not isinstance(other, six.string_types):
+ return NotImplemented
+ return self.__class__(str(self) + other)
+
+ __iadd__ = __add__
diff --git a/grc/core/blocks/_templates.py b/grc/core/blocks/_templates.py
new file mode 100644
index 0000000000..0b15166423
--- /dev/null
+++ b/grc/core/blocks/_templates.py
@@ -0,0 +1,77 @@
+# Copyright 2016 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
+"""
+This dict class holds a (shared) cache of compiled mako templates.
+These
+
+"""
+from __future__ import absolute_import, print_function
+
+from mako.template import Template
+from mako.exceptions import SyntaxException
+
+from ..errors import TemplateError
+
+
+class MakoTemplates(dict):
+
+ _template_cache = {}
+
+ def __init__(self, _bind_to=None, *args, **kwargs):
+ self.instance = _bind_to
+ dict.__init__(self, *args, **kwargs)
+
+ def __get__(self, instance, owner):
+ if instance is None or self.instance is not None:
+ return self
+ copy = self.__class__(_bind_to=instance, **self)
+ if getattr(instance.__class__, 'templates', None) is self:
+ setattr(instance, 'templates', copy)
+ return copy
+
+ @classmethod
+ def compile(cls, text):
+ text = str(text)
+ try:
+ template = Template(text)
+ except SyntaxException as error:
+ raise TemplateError(text, *error.args)
+
+ cls._template_cache[text] = template
+ return template
+
+ def _get_template(self, text):
+ try:
+ return self._template_cache[str(text)]
+ except KeyError:
+ return self.compile(text)
+
+ def render(self, item):
+ text = self.get(item)
+ if not text:
+ return ''
+ namespace = self.instance.namespace_templates
+
+ try:
+ if isinstance(text, list):
+ templates = (self._get_template(t) for t in text)
+ return [template.render(**namespace) for template in templates]
+ else:
+ template = self._get_template(text)
+ return template.render(**namespace)
+ except Exception as error:
+ raise TemplateError(error, text)
diff --git a/grc/core/blocks/block.py b/grc/core/blocks/block.py
new file mode 100644
index 0000000000..e6695083a1
--- /dev/null
+++ b/grc/core/blocks/block.py
@@ -0,0 +1,416 @@
+"""
+Copyright 2008-2015 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
+
+import ast
+import collections
+import itertools
+
+import six
+from six.moves import range
+
+from ._templates import MakoTemplates
+from ._flags import Flags
+
+from ..Constants import ADVANCED_PARAM_TAB
+from ..base import Element
+from ..utils.descriptors import lazy_property
+
+
+def _get_elem(iterable, key):
+ items = list(iterable)
+ for item in items:
+ if item.key == key:
+ return item
+ return ValueError('Key "{}" not found in {}.'.format(key, items))
+
+
+class Block(Element):
+
+ is_block = True
+
+ STATE_LABELS = ['disabled', 'enabled', 'bypassed']
+
+ key = ''
+ label = ''
+ category = ''
+ flags = Flags('')
+ documentation = {'': ''}
+
+ value = None
+ checks = []
+
+ templates = MakoTemplates()
+ parameters_data = []
+ inputs_data = []
+ outputs_data = []
+
+ extra_data = {}
+
+ # region Init
+ def __init__(self, parent):
+ """Make a new block from nested data."""
+ super(Block, self).__init__(parent)
+ self.params = self._init_params()
+ self.sinks = self._init_ports(self.inputs_data, direction='sink')
+ self.sources = self._init_ports(self.outputs_data, direction='source')
+
+ self.active_sources = [] # on rewrite
+ self.active_sinks = [] # on rewrite
+
+ self.states = {'state': True}
+
+ def _init_params(self):
+ is_dsp_block = not self.flags.not_dsp
+ has_inputs = bool(self.inputs_data)
+ has_outputs = bool(self.outputs_data)
+
+ params = collections.OrderedDict()
+ param_factory = self.parent_platform.make_param
+
+ def add_param(id, **kwargs):
+ params[id] = param_factory(self, id=id, **kwargs)
+
+ add_param(id='id', name='ID', dtype='id',
+ hide='none' if (self.key == 'options' or self.is_variable) else 'part')
+
+ if is_dsp_block:
+ add_param(id='alias', name='Block Alias', dtype='string',
+ hide='part', category=ADVANCED_PARAM_TAB)
+
+ if has_outputs or has_inputs:
+ add_param(id='affinity', name='Core Affinity', dtype='int_vector',
+ hide='part', category=ADVANCED_PARAM_TAB)
+
+ if has_outputs:
+ add_param(id='minoutbuf', name='Min Output Buffer', dtype='int',
+ hide='part', value='0', category=ADVANCED_PARAM_TAB)
+ add_param(id='maxoutbuf', name='Max Output Buffer', dtype='int',
+ hide='part', value='0', category=ADVANCED_PARAM_TAB)
+
+ base_params_n = {}
+ for param_data in self.parameters_data:
+ param_id = param_data['id']
+ if param_id in params:
+ raise Exception('Param id "{}" is not unique'.format(param_id))
+
+ base_key = param_data.get('base_key', None)
+ param_data_ext = base_params_n.get(base_key, {}).copy()
+ param_data_ext.update(param_data)
+
+ add_param(**param_data_ext)
+ base_params_n[param_id] = param_data_ext
+
+ add_param(id='comment', name='Comment', dtype='_multiline', hide='part',
+ value='', category=ADVANCED_PARAM_TAB)
+ return params
+
+ def _init_ports(self, ports_n, direction):
+ ports = []
+ port_factory = self.parent_platform.make_port
+ port_ids = set()
+
+ def make_stream_port_id(_pool=itertools.count()):
+ return {'sink': 'in', 'source': 'out'}[direction] + str(next(_pool))
+
+ for i, port_data in enumerate(ports_n):
+ port_id = port_data.setdefault('id', make_stream_port_id())
+ if port_id in port_ids:
+ raise Exception('Port id "{}" already exists in {}s'.format(port_id, direction))
+ port_ids.add(port_id)
+
+ port = port_factory(parent=self, direction=direction, **port_data)
+ ports.append(port)
+ return ports
+ # endregion
+
+ # region Rewrite_and_Validation
+ def rewrite(self):
+ """
+ Add and remove ports to adjust for the nports.
+ """
+ Element.rewrite(self)
+
+ def rekey(ports):
+ """Renumber non-message/message ports"""
+ domain_specific_port_index = collections.defaultdict(int)
+ for port in ports:
+ if not port.key.isdigit():
+ continue
+ domain = port.domain
+ port.key = str(domain_specific_port_index[domain])
+ domain_specific_port_index[domain] += 1
+
+ # Adjust nports
+ for ports in (self.sources, self.sinks):
+ self._rewrite_nports(ports)
+ rekey(ports)
+
+ # disconnect hidden ports
+ self.parent_flowgraph.disconnect(*[p for p in self.ports() if p.hidden])
+
+ self.active_sources = [p for p in self.sources if not p.hidden]
+ self.active_sinks = [p for p in self.sinks if not p.hidden]
+
+ def _rewrite_nports(self, ports):
+ for port in ports:
+ if hasattr(port, 'master_port'): # Not a master port and no left-over clones
+ continue
+ nports = port.multiplicity
+ for clone in port.clones[nports-1:]:
+ # Remove excess connections
+ self.parent_flowgraph.disconnect(clone)
+ port.remove_clone(clone)
+ ports.remove(clone)
+ # Add more cloned ports
+ for j in range(1 + len(port.clones), nports):
+ clone = port.add_clone()
+ ports.insert(ports.index(port) + j, clone)
+
+ def validate(self):
+ """
+ Validate this block.
+ Call the base class validate.
+ Evaluate the checks: each check must evaluate to True.
+ """
+ Element.validate(self)
+ self._run_checks()
+ self._validate_generate_mode_compat()
+ self._validate_var_value()
+
+ def _run_checks(self):
+ """Evaluate the checks"""
+ for check in self.checks:
+ try:
+ if not self.evaluate(check):
+ self.add_error_message('Check "{}" failed.'.format(check))
+ except:
+ self.add_error_message('Check "{}" did not evaluate.'.format(check))
+
+ def _validate_generate_mode_compat(self):
+ """check if this is a GUI block and matches the selected generate option"""
+ current_generate_option = self.parent.get_option('generate_options')
+
+ def check_generate_mode(label, flag, valid_options):
+ block_requires_mode = (
+ flag in self.flags or self.label.upper().startswith(label)
+ )
+ if block_requires_mode and current_generate_option not in valid_options:
+ self.add_error_message("Can't generate this block in mode: {} ".format(
+ repr(current_generate_option)))
+
+ check_generate_mode('QT GUI', Flags.NEED_QT_GUI, ('qt_gui', 'hb_qt_gui'))
+
+ def _validate_var_value(self):
+ """or variables check the value (only if var_value is used)"""
+ if self.is_variable and self.value != 'value':
+ try:
+ self.parent_flowgraph.evaluate(self.value, local_namespace=self.namespace)
+ except Exception as err:
+ self.add_error_message('Value "{}" cannot be evaluated:\n{}'.format(self.value, err))
+ # endregion
+
+ # region Properties
+
+ def __str__(self):
+ return 'Block - {} - {}({})'.format(self.name, self.label, self.key)
+
+ def __repr__(self):
+ try:
+ name = self.name
+ except Exception:
+ name = self.key
+ return 'block[' + name + ']'
+
+ @property
+ def name(self):
+ return self.params['id'].value
+
+ @lazy_property
+ def is_virtual_or_pad(self):
+ return self.key in ("virtual_source", "virtual_sink", "pad_source", "pad_sink")
+
+ @lazy_property
+ def is_variable(self):
+ return bool(self.value)
+
+ @lazy_property
+ def is_import(self):
+ return self.key == 'import'
+
+ @property
+ def comment(self):
+ return self.params['comment'].value
+
+ @property
+ def state(self):
+ """Gets the block's current state."""
+ state = self.states['state']
+ return state if state in self.STATE_LABELS else 'enabled'
+
+ @state.setter
+ def state(self, value):
+ """Sets the state for the block."""
+ self.states['state'] = value
+
+ # Enable/Disable Aliases
+ @property
+ def enabled(self):
+ """Get the enabled state of the block"""
+ return self.state != 'disabled'
+
+ # endregion
+
+ ##############################################
+ # Getters (old)
+ ##############################################
+ def get_var_make(self):
+ return self.templates.render('var_make')
+
+ def get_var_value(self):
+ return self.templates.render('var_value')
+
+ def get_callbacks(self):
+ """
+ Get a list of function callbacks for this block.
+
+ Returns:
+ a list of strings
+ """
+ def make_callback(callback):
+ if 'self.' in callback:
+ return callback
+ return 'self.{}.{}'.format(self.name, callback)
+ return [make_callback(c) for c in self.templates.render('callbacks')]
+
+ def is_virtual_sink(self):
+ return self.key == 'virtual_sink'
+
+ def is_virtual_source(self):
+ return self.key == 'virtual_source'
+
+ # Block bypassing
+ def get_bypassed(self):
+ """
+ Check if the block is bypassed
+ """
+ return self.state == 'bypassed'
+
+ def set_bypassed(self):
+ """
+ Bypass the block
+
+ Returns:
+ True if block chagnes state
+ """
+ if self.state != 'bypassed' and self.can_bypass():
+ self.state = 'bypassed'
+ return True
+ return False
+
+ def can_bypass(self):
+ """ Check the number of sinks and sources and see if this block can be bypassed """
+ # Check to make sure this is a single path block
+ # Could possibly support 1 to many blocks
+ if len(self.sources) != 1 or len(self.sinks) != 1:
+ return False
+ if not (self.sources[0].dtype == self.sinks[0].dtype):
+ return False
+ if self.flags.disable_bypass:
+ return False
+ return True
+
+ def ports(self):
+ return itertools.chain(self.sources, self.sinks)
+
+ def active_ports(self):
+ return itertools.chain(self.active_sources, self.active_sinks)
+
+ def children(self):
+ return itertools.chain(six.itervalues(self.params), self.ports())
+
+ ##############################################
+ # Access
+ ##############################################
+
+ def get_sink(self, key):
+ return _get_elem(self.sinks, key)
+
+ def get_source(self, key):
+ return _get_elem(self.sources, key)
+
+ ##############################################
+ # Resolve
+ ##############################################
+ @property
+ def namespace(self):
+ return {key: param.get_evaluated() for key, param in six.iteritems(self.params)}
+
+ @property
+ def namespace_templates(self):
+ return {key: param.template_arg for key, param in six.iteritems(self.params)}
+
+ def evaluate(self, expr):
+ return self.parent_flowgraph.evaluate(expr, self.namespace)
+
+ ##############################################
+ # Import/Export Methods
+ ##############################################
+ def export_data(self):
+ """
+ Export this block's params to nested data.
+
+ Returns:
+ a nested data odict
+ """
+ data = collections.OrderedDict()
+ if self.key != 'options':
+ data['name'] = self.name
+ data['id'] = self.key
+ data['parameters'] = collections.OrderedDict(sorted(
+ (param_id, param.value) for param_id, param in self.params.items()
+ if param_id != 'id'
+ ))
+ data['states'] = collections.OrderedDict(sorted(self.states.items()))
+ return data
+
+ def import_data(self, name, states, parameters, **_):
+ """
+ Import this block's params from nested data.
+ Any param keys that do not exist will be ignored.
+ Since params can be dynamically created based another param,
+ call rewrite, and repeat the load until the params stick.
+ """
+ self.params['id'].value = name
+ self.states.update(states)
+
+ def get_hash():
+ return hash(tuple(hash(v) for v in self.params.values()))
+
+ pre_rewrite_hash = -1
+ while pre_rewrite_hash != get_hash():
+ for key, value in six.iteritems(parameters):
+ try:
+ self.params[key].set_value(value)
+ except KeyError:
+ continue
+ # Store hash and call rewrite
+ pre_rewrite_hash = get_hash()
+ self.rewrite()
diff --git a/grc/core/blocks/dummy.py b/grc/core/blocks/dummy.py
new file mode 100644
index 0000000000..6a5ec2fa72
--- /dev/null
+++ b/grc/core/blocks/dummy.py
@@ -0,0 +1,54 @@
+# Copyright 2016 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 . import Block, register_build_in
+
+
+@register_build_in
+class DummyBlock(Block):
+
+ is_dummy_block = True
+
+ label = 'Missing Block'
+ key = '_dummy'
+
+ def __init__(self, parent, missing_block_id, parameters, **_):
+ self.key = missing_block_id
+ super(DummyBlock, self).__init__(parent=parent)
+
+ param_factory = self.parent_platform.make_param
+ for param_id in parameters:
+ self.params.setdefault(param_id, param_factory(parent=self, id=param_id, dtype='string'))
+
+ def is_valid(self):
+ return False
+
+ @property
+ def enabled(self):
+ return False
+
+ def add_missing_port(self, port_id, direction):
+ port = self.parent_platform.make_port(
+ parent=self, direction=direction, id=port_id, name='?', dtype='',
+ )
+ if port.is_source:
+ self.sources.append(port)
+ else:
+ self.sinks.append(port)
+ return port
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',
+ )
diff --git a/grc/core/blocks/virtual.py b/grc/core/blocks/virtual.py
new file mode 100644
index 0000000000..a10853ad1b
--- /dev/null
+++ b/grc/core/blocks/virtual.py
@@ -0,0 +1,76 @@
+# Copyright 2016 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
+
+import itertools
+
+from . import Block, register_build_in
+
+
+@register_build_in
+class VirtualSink(Block):
+ count = itertools.count()
+
+ key = 'virtual_sink'
+ label = 'Virtual Sink'
+ documentation = {'': ''}
+
+ parameters_data = [dict(
+ label='Stream ID',
+ id='stream_id',
+ dtype='stream_id',
+ )]
+ inputs_data = [dict(
+ domain='stream',
+ dtype=''
+ )]
+
+ def __init__(self, parent, **kwargs):
+ super(VirtualSink, self).__init__(parent, **kwargs)
+ self.params['id'].hide = 'all'
+
+ @property
+ def stream_id(self):
+ return self.params['stream_id'].value
+
+
+@register_build_in
+class VirtualSource(Block):
+ count = itertools.count()
+
+ key = 'virtual_source'
+ label = 'Virtual Source'
+ documentation = {'': ''}
+
+ parameters_data = [dict(
+ label='Stream ID',
+ id='stream_id',
+ dtype='stream_id',
+ )]
+ outputs_data = [dict(
+ domain='stream',
+ dtype=''
+ )]
+
+ def __init__(self, parent, **kwargs):
+ super(VirtualSource, self).__init__(parent, **kwargs)
+ self.params['id'].hide = 'all'
+
+ @property
+ def stream_id(self):
+ return self.params['stream_id'].value