summaryrefslogtreecommitdiff
path: root/grc/python
diff options
context:
space:
mode:
authorSebastian Koslowski <koslowski@kit.edu>2015-07-20 22:24:53 +0200
committerSebastian Koslowski <koslowski@kit.edu>2015-11-11 21:39:32 +0100
commitc9a5fabe17efe8af1c9435f746ed55c5f5790917 (patch)
tree77242850bfc2ebb159cb9c9d53eff9ea5459f8cb /grc/python
parent8b14a84ef8b8a73db002153cb398f79f1343ebc2 (diff)
grc: add embedded python block definition and support in GRC
Diffstat (limited to 'grc/python')
-rw-r--r--grc/python/Block.py96
-rw-r--r--grc/python/CMakeLists.txt1
-rw-r--r--grc/python/Generator.py27
-rw-r--r--grc/python/Param.py19
-rw-r--r--grc/python/epy_block_io.py97
5 files changed, 224 insertions, 16 deletions
diff --git a/grc/python/Block.py b/grc/python/Block.py
index 5289d5765e..accfd21913 100644
--- a/grc/python/Block.py
+++ b/grc/python/Block.py
@@ -21,11 +21,13 @@ import itertools
import collections
from .. base.Constants import BLOCK_FLAG_NEED_QT_GUI, BLOCK_FLAG_NEED_WX_GUI
+from .. base.odict import odict
+
from .. base.Block import Block as _Block
from .. gui.Block import Block as _GUIBlock
from . FlowGraph import _variable_matcher
-import extract_docs
+from . import epy_block_io, extract_docs
class Block(_Block, _GUIBlock):
@@ -59,6 +61,9 @@ class Block(_Block, _GUIBlock):
)
_GUIBlock.__init__(self)
+ self._epy_source_hash = -1 # for epy blocks
+ self._epy_reload_error = None
+
def get_bus_structure(self, direction):
if direction == 'source':
bus_structure = self._bus_structure_source;
@@ -111,12 +116,16 @@ class Block(_Block, _GUIBlock):
check_generate_mode('WX GUI', BLOCK_FLAG_NEED_WX_GUI, ('wx_gui',))
check_generate_mode('QT GUI', BLOCK_FLAG_NEED_QT_GUI, ('qt_gui', 'hb_qt_gui'))
+ if self._epy_reload_error:
+ self.add_error_message(str(self._epy_reload_error))
def rewrite(self):
"""
Add and remove ports to adjust for the nports.
"""
_Block.rewrite(self)
+ # Check and run any custom rewrite function for this block
+ getattr(self, 'rewrite_' + self._key, lambda: None)()
# adjust nports, disconnect hidden ports
for ports in (self.get_sources(), self.get_sinks()):
@@ -216,3 +225,88 @@ class Block(_Block, _GUIBlock):
def is_virtual_source(self):
return self.get_key() == 'virtual_source'
+
+ ###########################################################################
+ # Custom rewrite functions
+ ###########################################################################
+
+ def rewrite_epy_block(self):
+ flowgraph = self.get_parent()
+ platform = flowgraph.get_parent()
+ param_blk = self.get_param('_io_cache')
+ param_src = self.get_param('_source_code')
+
+ src = param_src.get_value()
+ src_hash = hash(src)
+ if src_hash == self._epy_source_hash:
+ return
+
+ try:
+ blk_io = epy_block_io.extract(src)
+
+ except Exception as e:
+ self._epy_reload_error = ValueError('Source code eval:\n' + str(e))
+ try: # load last working block io
+ blk_io = epy_block_io.BlockIO(*eval(param_blk.get_value()))
+ except:
+ return
+ else:
+ self._epy_reload_error = None # clear previous errors
+ param_blk.set_value(repr(tuple(blk_io)))
+
+ # print "Rewriting embedded python block {!r}".format(self.get_id())
+
+ self._epy_source_hash = src_hash
+ self._name = blk_io.name or blk_io.cls
+ self._doc = blk_io.doc
+ self._imports[0] = 'from {} import {}'.format(self.get_id(), blk_io.cls)
+ self._make = '{}({})'.format(blk_io.cls, ', '.join(
+ '{0}=${0}'.format(key) for key, _ in blk_io.params))
+
+ params = dict()
+ for param in list(self._params):
+ if hasattr(param, '__epy_param__'):
+ params[param.get_key()] = param
+ self._params.remove(param)
+
+ for key, value in blk_io.params:
+ if key in params:
+ param = params[key]
+ if not param.value_is_default():
+ param.set_value(value)
+ else:
+ name = key.replace('_', ' ').title()
+ n = odict(dict(name=name, key=key, type='raw', value=value))
+ param = platform.Param(block=self, n=n)
+ setattr(param, '__epy_param__', True)
+ self._params.append(param)
+
+ def update_ports(label, ports, port_specs, direction):
+ ports_to_remove = list(ports)
+ iter_ports = iter(ports)
+ ports_new = list()
+ port_current = next(iter_ports, None)
+ for key, port_type in port_specs:
+ reuse_port = (
+ port_current is not None and
+ port_current.get_type() == port_type and
+ (key.isdigit() or port_current.get_key() == key)
+ )
+ if reuse_port:
+ ports_to_remove.remove(port_current)
+ port, port_current = port_current, next(iter_ports, None)
+ else:
+ n = odict(dict(name=label + str(key), type=port_type, key=key))
+ port = platform.Port(block=self, n=n, dir=direction)
+ ports_new.append(port)
+ # replace old port list with new one
+ del ports[:]
+ ports.extend(ports_new)
+ # remove excess port connections
+ for port in ports_to_remove:
+ for connection in port.get_connections():
+ flowgraph.remove_element(connection)
+
+ update_ports('in', self.get_sinks(), blk_io.sinks, 'sink')
+ update_ports('out', self.get_sources(), blk_io.sources, 'source')
+ _Block.rewrite(self)
diff --git a/grc/python/CMakeLists.txt b/grc/python/CMakeLists.txt
index 41d965e89c..3f9e273146 100644
--- a/grc/python/CMakeLists.txt
+++ b/grc/python/CMakeLists.txt
@@ -21,6 +21,7 @@
GR_PYTHON_INSTALL(FILES
expr_utils.py
extract_docs.py
+ epy_block_io.py
Block.py
Connection.py
Constants.py
diff --git a/grc/python/Generator.py b/grc/python/Generator.py
index d60befe3fa..0b9469b588 100644
--- a/grc/python/Generator.py
+++ b/grc/python/Generator.py
@@ -79,7 +79,7 @@ class TopBlockGenerator(object):
self._flow_graph = flow_graph
self._generate_options = self._flow_graph.get_option('generate_options')
self._mode = TOP_BLOCK_FILE_MODE
- dirname = os.path.dirname(file_path)
+ dirname = self._dirname = os.path.dirname(file_path)
# handle the case where the directory is read-only
# in this case, use the system's temp directory
if not os.access(dirname, os.W_OK):
@@ -108,12 +108,14 @@ class TopBlockGenerator(object):
"This is usually undesired. Consider "
"removing the throttle block.")
# generate
- with codecs.open(self.get_file_path(), 'w', encoding = 'utf-8') as fp:
- fp.write(self._build_python_code_from_template())
- try:
- os.chmod(self.get_file_path(), self._mode)
- except:
- pass
+ for filename, data in self._build_python_code_from_template():
+ with open(filename, 'w', encoding='utf-8') as fp:
+ fp.write(data)
+ if filename == self.get_file_path():
+ try:
+ os.chmod(filename, self._mode)
+ except:
+ pass
def get_popen(self):
"""
@@ -148,6 +150,8 @@ class TopBlockGenerator(object):
Returns:
a string of python code
"""
+ output = list()
+
title = self._flow_graph.get_option('title') or self._flow_graph.get_option('id').replace('_', ' ').title()
imports = self._flow_graph.get_imports()
variables = self._flow_graph.get_variables()
@@ -174,6 +178,12 @@ class TopBlockGenerator(object):
# List of regular blocks (all blocks minus the special ones)
blocks = filter(lambda b: b not in (imports + parameters), blocks)
+ for block in blocks:
+ if block.get_key() == 'epy_block':
+ file_path = os.path.join(self._dirname, block.get_id() + '.py')
+ src = block.get_param('_source_code').get_value()
+ output.append((file_path, src))
+
# Filter out virtual sink connections
cf = lambda c: not (c.is_bus() or c.is_msg() or c.get_sink().get_parent().is_virtual_sink())
connections = filter(cf, self._flow_graph.get_enabled_connections())
@@ -258,7 +268,8 @@ class TopBlockGenerator(object):
}
# build the template
t = Template(open(FLOW_GRAPH_TEMPLATE, 'r').read(), namespace)
- return str(t)
+ output.append((self.get_file_path(), str(t)))
+ return output
class HierBlockGenerator(TopBlockGenerator):
diff --git a/grc/python/Param.py b/grc/python/Param.py
index 50723ed2cc..746f677e46 100644
--- a/grc/python/Param.py
+++ b/grc/python/Param.py
@@ -17,6 +17,11 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
+import ast
+import re
+
+from gnuradio import gr
+
from .. base.Param import Param as _Param
from .. gui.Param import Param as _GUIParam
@@ -24,8 +29,6 @@ import Constants
from Constants import VECTOR_TYPES, COMPLEX_TYPES, REAL_TYPES, INT_TYPES
from gnuradio import eng_notation
-import re
-from gnuradio import gr
_check_id_matcher = re.compile('^[a-z|A-Z]\w*$')
_show_id_matcher = re.compile('^(variable\w*|parameter|options|notebook)$')
@@ -62,7 +65,7 @@ class Param(_Param, _GUIParam):
'complex', 'real', 'float', 'int',
'complex_vector', 'real_vector', 'float_vector', 'int_vector',
'hex', 'string', 'bool',
- 'file_open', 'file_save', '_multiline',
+ 'file_open', 'file_save', '_multiline', '_multiline_python_external',
'id', 'stream_id',
'grid_pos', 'notebook', 'gui_hint',
'import',
@@ -266,7 +269,7 @@ class Param(_Param, _GUIParam):
#########################
# String Types
#########################
- elif t in ('string', 'file_open', 'file_save', '_multiline'):
+ elif t in ('string', 'file_open', 'file_save', '_multiline', '_multiline_python_external'):
#do not check if file/directory exists, that is a runtime issue
try:
e = self.get_parent().get_parent().evaluate(v)
@@ -274,8 +277,10 @@ class Param(_Param, _GUIParam):
raise Exception()
except:
self._stringify_flag = True
- e = v
- return str(e)
+ e = str(v)
+ if t == '_multiline_python_external':
+ ast.parse(e) # raises SyntaxError
+ return e
#########################
# Unique ID Type
#########################
@@ -405,7 +410,7 @@ class Param(_Param, _GUIParam):
"""
v = self.get_value()
t = self.get_type()
- if t in ('string', 'file_open', 'file_save', '_multiline'): # string types
+ if t in ('string', 'file_open', 'file_save', '_multiline', '_multiline_python_external'): # string types
if not self._init: self.evaluate()
if self._stringify_flag: return '"%s"'%v.replace('"', '\"')
else: return v
diff --git a/grc/python/epy_block_io.py b/grc/python/epy_block_io.py
new file mode 100644
index 0000000000..8d3ce1caa1
--- /dev/null
+++ b/grc/python/epy_block_io.py
@@ -0,0 +1,97 @@
+
+import inspect
+import collections
+
+from gnuradio import gr
+import pmt
+
+
+TYPE_MAP = {
+ 'complex64': 'complex', 'complex': 'complex',
+ 'float32': 'float', 'float': 'float',
+ 'int32': 'int', 'uint32': 'int',
+ 'int16': 'short', 'uint16': 'short',
+ 'int8': 'byte', 'uint8': 'byte',
+}
+
+BlockIO = collections.namedtuple('BlockIO', 'name cls params sinks sources doc')
+
+
+def _ports(sigs, msgs):
+ ports = list()
+ for i, dtype in enumerate(sigs):
+ port_type = TYPE_MAP.get(dtype.name, None)
+ if not port_type:
+ raise ValueError("Can't map {0:!r} to GRC port type".format(dtype))
+ ports.append((str(i), port_type))
+ for msg_key in msgs:
+ if msg_key == 'system':
+ continue
+ ports.append((msg_key, 'message'))
+ return ports
+
+
+def _blk_class(source_code):
+ ns = {}
+ try:
+ exec source_code in ns
+ except Exception as e:
+ raise ValueError("Can't interpret source code: " + str(e))
+ for var in ns.itervalues():
+ if inspect.isclass(var)and issubclass(var, gr.gateway.gateway_block):
+ break
+ else:
+ raise ValueError('No python block class found in code')
+ return var
+
+
+def extract(cls):
+ if not inspect.isclass(cls):
+ cls = _blk_class(cls)
+
+ spec = inspect.getargspec(cls.__init__)
+ defaults = map(repr, spec.defaults or ())
+ doc = cls.__doc__ or cls.__init__.__doc__ or ''
+ cls_name = cls.__name__
+
+ if len(defaults) + 1 != len(spec.args):
+ raise ValueError("Need all default values")
+
+ try:
+ instance = cls()
+ except Exception as e:
+ raise RuntimeError("Can't create an instance of your block: " + str(e))
+
+ name = instance.name()
+ params = list(zip(spec.args[1:], defaults))
+
+ sinks = _ports(instance.in_sig(),
+ pmt.to_python(instance.message_ports_in()))
+ sources = _ports(instance.out_sig(),
+ pmt.to_python(instance.message_ports_out()))
+
+ return BlockIO(name, cls_name, params, sinks, sources, doc)
+
+
+if __name__ == '__main__':
+ blk_code = """
+import numpy as np
+from gnuradio import gr
+import pmt
+
+class blk(gr.sync_block):
+ def __init__(self, param1=None, param2=None):
+ "Test Docu"
+ gr.sync_block.__init__(
+ self,
+ name='Embedded Python Block',
+ in_sig = (np.float32,),
+ out_sig = (np.float32,np.complex64,),
+ )
+ self.message_port_register_in(pmt.intern('msg_in'))
+ self.message_port_register_out(pmt.intern('msg_out'))
+
+ def work(self, inputs_items, output_items):
+ return 10
+ """
+ print extract(blk_code)