summaryrefslogtreecommitdiff
path: root/grc
diff options
context:
space:
mode:
authorJosh Morman <jmorman@perspectalabs.com>2019-08-30 09:47:42 -0400
committerdevnulling <devnulling@users.noreply.github.com>2020-02-10 13:33:07 -0800
commit3232eeb534b5ac1ab503f7f36254227b1cfba1f9 (patch)
treec950d0e841644ed584737e905e315d778fe26f3e /grc
parent838d081918f8e5ee3f28273237d8b74a8c0b9d9b (diff)
grc: add python snippets to GRC
This feature adds the ability to insert arbitrary code into the python flowgraph. It gives a little more low-level flexibility for quickly modifying flowgraphs and adding custom bits of code rather than having to go and edit the generated py file One example is synchronizing multiple USRP objects - sometimes you want different sync than what is offered in the multi-usrp object, so you can put a bit of code in the snippet block to do the custom synchronization
Diffstat (limited to 'grc')
-rw-r--r--grc/blocks/grc.tree.yml1
-rw-r--r--grc/blocks/snippet.block.yml45
-rw-r--r--grc/core/FlowGraph.py41
-rw-r--r--grc/core/base.py1
-rw-r--r--grc/core/blocks/block.py4
-rw-r--r--grc/core/generator/flow_graph.py.mako43
-rw-r--r--grc/core/generator/top_block.py2
-rw-r--r--grc/core/params/param.py10
8 files changed, 141 insertions, 6 deletions
diff --git a/grc/blocks/grc.tree.yml b/grc/blocks/grc.tree.yml
index f27fb6ae47..fc2fded9ab 100644
--- a/grc/blocks/grc.tree.yml
+++ b/grc/blocks/grc.tree.yml
@@ -13,6 +13,7 @@
- epy_module
- note
- import
+ - snippet
- Variables:
- variable
- variable_struct
diff --git a/grc/blocks/snippet.block.yml b/grc/blocks/snippet.block.yml
new file mode 100644
index 0000000000..17d8da0ff8
--- /dev/null
+++ b/grc/blocks/snippet.block.yml
@@ -0,0 +1,45 @@
+id: snippet
+label: Python Snippet
+flags: [ python ]
+
+parameters:
+- id: section
+ label: Section of Flowgraph
+ dtype: string
+ options: ['main_after_init', 'main_after_start', 'main_after_stop' ]
+ option_labels: ['Main - After Init', 'Main - After Start', 'Main - After Stop']
+- id: priority
+ label: Priority
+ dtype: int
+ hide: ${'part' if priority <= 0 else 'none'}
+- id: code
+ label: Code Snippet
+ dtype: _multiline
+
+templates:
+ var_make: ${code}
+
+documentation: |-
+ CAUTION: This is an ADVANCED feature and can lead to unintended consequences in the rendering of a flowgraph. Use at your own risk.
+
+ Insert a snippet of Python code directly into the flowgraph at the end of the specified section. \
+ For each snippet a function is generated with the block name of the snippet (use GRC Show Block IDs option to modify). These functions are\
+ then grouped into their respective sections in the rendered flowgraph.
+
+ The purpose of the python snippets is to be able to exercise features from within GRC that are not entirely supported by the block callbacks, \
+ methods and mechanisms to generate the code. One example of this would be calling UHD timed commands before starting the flowgraph
+
+ Indents will be handled upon insertion into the python flowgraph
+
+ Example 1:
+ epy_mod_0.some_function(self.some_block.some_property)
+
+ Will place the function call in the generated .py file using the name of the appropriate embedded python block in the proper scope
+ The scope is relative to the blocks in the flowgraph, e.g. to reference a block, it should be identified as self.block
+
+ Example 2:
+ print('The flowgraph has been stopped')
+
+ With section selected as 'Main - After Stop', will place the print statement after the flowgraph has been stopped.
+
+file_format: 1
diff --git a/grc/core/FlowGraph.py b/grc/core/FlowGraph.py
index 21c3cdb59d..b0141f8705 100644
--- a/grc/core/FlowGraph.py
+++ b/grc/core/FlowGraph.py
@@ -81,6 +81,47 @@ class FlowGraph(Element):
parameters = [b for b in self.iter_enabled_blocks() if b.key == 'parameter']
return parameters
+ def get_snippets(self):
+ """
+ Get a set of all code snippets (Python) in this flow graph namespace.
+
+ Returns:
+ a list of code snippets
+ """
+ return [b for b in self.iter_enabled_blocks() if b.key == 'snippet']
+
+ def get_snippets_dict(self, section=None):
+ """
+ Get a dictionary of code snippet information for a particular section.
+
+ Args:
+ section: string specifier of section of snippets to return, section=None returns all
+
+ Returns:
+ a list of code snippets dicts
+ """
+ snippets = self.get_snippets()
+ if not snippets:
+ return []
+
+ output = []
+ for snip in snippets:
+ d ={}
+ sect = snip.params['section'].value
+ d['section'] = sect
+ d['priority'] = snip.params['priority'].value
+ d['lines'] = snip.params['code'].value.splitlines()
+ d['def'] = 'def snipfcn_{}(self):'.format(snip.name)
+ d['call'] = 'snipfcn_{}(tb)'.format(snip.name)
+ if not section or sect == section:
+ output.append(d)
+
+ # Sort by descending priority
+ if section:
+ output = sorted(output, key=lambda x: x['priority'], reverse=True)
+
+ return output
+
def get_monitors(self):
"""
Get a list of all ControlPort monitors
diff --git a/grc/core/base.py b/grc/core/base.py
index dae9ea229b..a5de8837d6 100644
--- a/grc/core/base.py
+++ b/grc/core/base.py
@@ -139,6 +139,7 @@ class Element(object):
is_param = False
is_variable = False
is_import = False
+ is_snippet = False
def get_raw(self, name):
descriptor = getattr(self.__class__, name, None)
diff --git a/grc/core/blocks/block.py b/grc/core/blocks/block.py
index ff1c8befc3..e51b730cdd 100644
--- a/grc/core/blocks/block.py
+++ b/grc/core/blocks/block.py
@@ -293,6 +293,10 @@ class Block(Element):
def is_import(self):
return self.key == 'import'
+ @lazy_property
+ def is_snippet(self):
+ return self.key == 'snippet'
+
@property
def comment(self):
return self.params['comment'].value
diff --git a/grc/core/generator/flow_graph.py.mako b/grc/core/generator/flow_graph.py.mako
index 7e731d1a9d..3635c550db 100644
--- a/grc/core/generator/flow_graph.py.mako
+++ b/grc/core/generator/flow_graph.py.mako
@@ -42,6 +42,7 @@ if __name__ == '__main__':
##${imp.replace(" # grc-generated hier_block", "")}
${imp}
% endfor
+
########################################################
##Create Class
## Write the class declaration for a top or hier block.
@@ -219,6 +220,7 @@ gr.io_signaturev(${len(io_sigs)}, ${len(io_sigs)}, [${', '.join(size_strs)}])\
${ connection.rstrip() }
% endfor
% endif
+
########################################################
## QT sink close method reimplementation
########################################################
@@ -266,6 +268,32 @@ gr.io_signaturev(${len(io_sigs)}, ${len(io_sigs)}, [${', '.join(size_strs)}])\
% endfor
% endif
% endfor
+\
+% for snip in flow_graph.get_snippets_dict():
+
+${indent(snip['def'])}
+% for line in snip['lines']:
+ ${indent(line)}
+% endfor
+% endfor
+\
+<%
+snippet_sections = ['main_after_init', 'main_after_start', 'main_after_stop']
+snippets = {}
+for section in snippet_sections:
+ snippets[section] = flow_graph.get_snippets_dict(section)
+%>
+\
+%for section in snippet_sections:
+%if snippets[section]:
+
+def snippets_${section}(tb):
+ % for snip in snippets[section]:
+ ${indent(snip['call'])}
+ % endfor
+%endif
+%endfor
+
########################################################
##Create Main
## For top block code, generate a main routine.
@@ -335,9 +363,11 @@ def main(top_block_cls=${class_name}, options=None):
qapp = Qt.QApplication(sys.argv)
tb = top_block_cls(${ ', '.join(params_eq_list) })
+ ${'snippets_main_after_init(tb)' if snippets['main_after_init'] else ''}
% if flow_graph.get_option('run'):
tb.start(${flow_graph.get_option('max_nouts') or ''})
% endif
+ ${'snippets_main_after_start(tb)' if snippets['main_after_start'] else ''}
% if flow_graph.get_option('qt_qss_theme'):
tb.setStyleSheetFromFile("${ flow_graph.get_option('qt_qss_theme') }")
% endif
@@ -356,6 +386,7 @@ def main(top_block_cls=${class_name}, options=None):
def quitting():
tb.stop()
tb.wait()
+ ${'snippets_main_after_stop(tb)' if snippets['main_after_stop'] else ''}
qapp.aboutToQuit.connect(quitting)
% for m in monitors:
% if m.params['en'].get_value() == 'True':
@@ -368,6 +399,7 @@ def main(top_block_cls=${class_name}, options=None):
def killProc(signum, frame, tb):
tb.stop()
tb.wait()
+ ${'snippets_main_after_stop(tb)' if snippets['main_after_stop'] else ''}
serverProc.terminate()
serverProc.kill()
time.sleep(1)
@@ -383,20 +415,23 @@ def main(top_block_cls=${class_name}, options=None):
url = "http://localhost:" + port + "/bokehgui")
# Create Top Block instance
tb = top_block_cls(doc)
+ ${'snippets_main_after_init(tb)' if snippets['main_after_init'] else ''}
try:
tb.start()
+ ${'snippets_main_after_start(tb)' if snippets['main_after_start'] else ''}
signal.signal(signal.SIGTERM, functools.partial(killProc, tb=tb))
session.loop_until_closed()
finally:
print("Exiting the simulation. Stopping Bokeh Server")
tb.stop()
tb.wait()
+ ${'snippets_main_after_stop(tb)' if snippets['main_after_stop'] else ''}
finally:
serverProc.terminate()
serverProc.kill()
% elif generate_options == 'no_gui':
tb = top_block_cls(${ ', '.join(params_eq_list) })
-
+ ${'snippets_main_after_init(tb)' if snippets['main_after_init'] else ''}
def sig_handler(sig=None, frame=None):
% for m in monitors:
% if m.params['en'].get_value() == 'True':
@@ -405,6 +440,7 @@ def main(top_block_cls=${class_name}, options=None):
% endfor
tb.stop()
tb.wait()
+ ${'snippets_main_after_stop(tb)' if snippets['main_after_stop'] else ''}
sys.exit(0)
signal.signal(signal.SIGINT, sig_handler)
@@ -412,6 +448,7 @@ def main(top_block_cls=${class_name}, options=None):
% if flow_graph.get_option('run_options') == 'prompt':
tb.start(${ flow_graph.get_option('max_nouts') or '' })
+ ${'snippets_main_after_start(tb)' if snippets['main_after_start'] else ''}
% for m in monitors:
% if m.params['en'].get_value() == 'True':
tb.${m.name}.start()
@@ -422,8 +459,10 @@ def main(top_block_cls=${class_name}, options=None):
except EOFError:
pass
tb.stop()
+ ## ${'snippets_main_after_stop(tb)' if snippets['main_after_stop'] else ''}
% elif flow_graph.get_option('run_options') == 'run':
tb.start(${flow_graph.get_option('max_nouts') or ''})
+ ${'snippets_main_after_start(tb)' if snippets['main_after_start'] else ''}
% for m in monitors:
% if m.params['en'].get_value() == 'True':
tb.${m.name}.start()
@@ -431,6 +470,7 @@ def main(top_block_cls=${class_name}, options=None):
% endfor
% endif
tb.wait()
+ ${'snippets_main_after_stop(tb)' if snippets['main_after_stop'] else ''}
% for m in monitors:
% if m.params['en'].get_value() == 'True':
tb.${m.name}.stop()
@@ -438,7 +478,6 @@ def main(top_block_cls=${class_name}, options=None):
% endfor
% endif
-
if __name__ == '__main__':
main()
% endif
diff --git a/grc/core/generator/top_block.py b/grc/core/generator/top_block.py
index 27d2428888..bf7ebcc224 100644
--- a/grc/core/generator/top_block.py
+++ b/grc/core/generator/top_block.py
@@ -191,7 +191,7 @@ class TopBlockGenerator(object):
blocks = [
b for b in fg.blocks
- if b.enabled and not (b.get_bypassed() or b.is_import or b in parameters or b.key == 'options')
+ if b.enabled and not (b.get_bypassed() or b.is_import or b.is_snippet or b in parameters or b.key == 'options')
]
blocks = expr_utils.sort_objects(blocks, operator.attrgetter('name'), _get_block_sort_text)
diff --git a/grc/core/params/param.py b/grc/core/params/param.py
index 816588efe2..ef8d7df291 100644
--- a/grc/core/params/param.py
+++ b/grc/core/params/param.py
@@ -245,9 +245,13 @@ class Param(Element):
elif dtype in ('string', 'file_open', 'file_save', '_multiline', '_multiline_python_external'):
# Do not check if file/directory exists, that is a runtime issue
try:
- value = self.parent_flowgraph.evaluate(expr)
- if not isinstance(value, str):
- raise Exception()
+ # Do not evaluate multiline strings (code snippets or comments)
+ if dtype not in ['_multiline','_multiline_python_external']:
+ value = self.parent_flowgraph.evaluate(expr)
+ if not isinstance(value, str):
+ raise Exception()
+ else:
+ value = str(expr)
except Exception:
self._stringify_flag = True
value = str(expr)