[Docs] Started documenting plugin hooks and core concepts.

This commit is contained in:
Gina Häußge 2015-03-25 09:39:25 +01:00
parent 97c8c7826c
commit b3739c10cc
5 changed files with 295 additions and 2 deletions

View file

@ -0,0 +1,36 @@
.. _sec-features-action_commands:
Action Commands
===============
Action commands are a feature defined for the GCODE based RepRap communication protocol. To quote from the
`GCODE node of the RepRap wiki <http://reprap.org/wiki/Gcode#Replies_from_the_RepRap_machine_to_the_host_computer>`_:
The RepRap machine may also send lines that look like this:
**// This is some debugging or other information on a line on its own. It may be sent at any time.**
Such lines will always be preceded by //.
On the latest version of Pronterface and [...] OctoPrint a special comment of the form:
**// action:command**
is allowed to be sent from the firmware[. T]he command can currently be pause, resume or disconnect which will
execute those commands on the host. As this is also a comment other hosts will just ignored these commands.
OctoPrint out of the box supports handling of the above mentioned commands:
pause
When this command is received from the printer, OctoPrint will pause streaming of a current print job just like if the
"Pause" button had been clicked.
resume
When this command is received from the printer, OctoPrint will resume streaming of a current print job just like if
the "Resume" button had been clicked.
disconnect
When this command is Received from the printer, OctoPrint will immediately disconnect from it.
Support for additional commands may be added by plugins by implementing a handler for the
:ref:`octoprint.comm.protocol.action <sec-plugins-hook-comm-protocol-action>` hook.

View file

@ -9,3 +9,4 @@ Features
custom_controls.rst
gcode_scripts.rst
action_commands.rst

196
docs/plugins/concepts.rst Normal file
View file

@ -0,0 +1,196 @@
.. _sec-plugin-concepts:
Concepts
========
OctoPrint's plugins are `Python Packages <https://docs.python.org/2/tutorial/modules.html#packages>`_ which in their
top-level module define a bunch of :ref:`control properties <sec-plugins-infrastructure-controlproperties>` defining
metadata (like name, version etc of the plugin) as well as information on how to initialize the plugin and into what
parts of the system the plugin will actually plug in to perform its job.
There are three types of ways a plugin might attach itself to the system, through so called
:ref:`mixin <sec-plugin-concepts-mixins>` implementations, by attaching itself to specified
:ref:`hook <sec-plugin-concepts-hooks>` or by offering :ref:`helper <sec-plugin-concepts-helpers>` functionality to be
used by other plugins.
.. _sec-plugin-concepts-mixins:
Mixins
------
Plugin mixins are the heart of OctoPrint's plugin system. They are :ref:`special base classes <sec-plugins-mixins>`
which are to be subclassed and extended to add functionality to OctoPrint. Plugins declare their instances that
implement one or multiple mixins using the ``__plugin_implementations__`` control property. OctoPrint's plugin core
collects those from the plugins and offers methods to access them based on the mixin type, which get used at multiple
locations within OctoPrint.
Using mixins always follows the pattern of retrieving the matching implementations from the plugin subsystem, then
calling the specific mixin's methods as defined and necessary.
The following snippet taken from OctoPrint's code for example shows how all :class:`~octoprint.plugin.AssetPlugin`
implementations are collected and then all assets they return via their ``get_assets`` methods are retrieved and
merged into one big asset map (differing between javascripts and stylesheets of various types) for use during
rendition of the UI.
.. code-block:: python
:linenos:
asset_plugins = pluginManager.get_implementations(octoprint.plugin.AssetPlugin)
for name, implementation in asset_plugins.items():
all_assets = implementation.get_assets()
if "js" in all_assets:
for asset in all_assets["js"]:
assets["js"].append(url_for('plugin_assets', name=name, filename=asset))
if preferred_stylesheet in all_assets:
for asset in all_assets[preferred_stylesheet]:
assets["stylesheets"].append((preferred_stylesheet, url_for('plugin_assets', name=name, filename=asset)))
else:
for stylesheet in supported_stylesheets:
if not stylesheet in all_assets:
continue
for asset in all_assets[stylesheet]:
assets["stylesheets"].append((stylesheet, url_for('plugin_assets', name=name, filename=asset)))
break
.. seealso::
:ref:`Available Mixins <sec-plugins-mixins>`
An overview of all mixin types available for extending OctoPrint.
:ref:`The Getting Started Guide <sec-plugins-gettingstarted>`
Tutorial on how to write a simple OctoPrint module utilizing mixins for various types of extension.
.. _sec-plugin-concepts-hooks:
Hooks
-----
Hooks are the smaller siblings of mixins, allowing to extend functionality or data processing where a custom mixin type
would be too much overhead. Where mixins are based on classes, hooks are based on methods. Like with the mixin
implementations, plugins inform OctoPrint about hook handlers using a control property, ``__plugin_hooks__``.
Each hook defines a contract detailing the call parameters for the hook handler method and the expected return type.
OctoPrint will call the hook with the define parameters and process the result depending on the hook.
An example for a hook within OctoPrint is ``octoprint.comm.protocol.scripts``, which allows adding additional
lines to OctoPrint's :ref:`GCODE scripts <sec-features-gcode_scripts>`, either as ``prefix`` (before the existing lines)
or as ``postfix`` (after the existing lines).
.. code-block:: python
:linenos:
self._gcode_hooks = self._pluginManager.get_hooks("octoprint.comm.protocol.scripts")
# ...
for hook in self._gcodescript_hooks:
try:
retval = self._gcodescript_hooks[hook](self, "gcode", scriptName)
except:
self._logger.exception("Error while processing gcodescript hook %s" % hook)
else:
if retval is None:
continue
if not isinstance(retval, (list, tuple)) or not len(retval) == 2:
continue
def to_list(data):
if isinstance(data, str):
data = map(str.strip, data.split("\n"))
elif isinstance(data, unicode):
data = map(unicode.strip, data.split("\n"))
if isinstance(data, (list, tuple)):
return list(data)
else:
return None
prefix, suffix = map(to_list, retval)
if prefix:
scriptLines = list(prefix) + scriptLines
if suffix:
scriptLines += list(suffix)
As you can see, the hook's method signature is defined to take the current ``self`` (as in, the current comm layer instance),
the general type of script for which to look for additions ("gcode") and the script name for which to look (e.g.
``beforePrintStarted`` for the GCODE script executed before the beginning of a print job). The hook is expected to
return a 2-tuple of prefix and postfix if has something for either of those, otherwise ``None``. OctoPrint will then take
care to add prefix and suffix as necessary after a small round of preprocessing.
.. note::
At the moment there exists no way to determine the execution order of various hook handlers within OctoPrint,
or to prevent the execution of further handlers down the chain.
This is planned for the very near future though.
Plugins can easily add their own hooks too. For example, the `Software Update Plugin <https://github.com/OctoPrint/OctoPrint-SoftwareUpdate>`_
declares a custom hook "octoprint.plugin.softwareupdate.check_config" which other plugins can add handlers for in order
to register themselves with the Software Update Plugin by returning their own update check configuration.
If you want your hook handler to be an instance method of a mixin implementation of your plugin (for example since you
need access to instance variables handed to your implementation via mixin invocations), you can get this work
by using a small trick. Instead of defining it directly via ``__plugin_hooks__`` utilize the ``__plugin_init__``
property instead, manually instantiate your implementation instance and then add its hook handler method to the
``__plugin_hooks__`` property and itself to the ``__plugin_implementations__`` property. Example:
.. code-block:: python
:linenos:
# ...
class MyPluginImplementation(octoprint.plugin.StartupPlugin):
def __init__(self):
self._host = None
self._port = None
def on_startup(self, host, port):
self._host = host
self._port = port
def my_script_hook_handler(self, comm, script_type, script_name):
if not script_type == "gcode" or not script_name == "afterPrinterConnected":
# we only extend the gcode script afterPrinterConnected, ignore this
return None
if not self._host or not self._port:
# oops, we haven't started up yet, shouldn't even happen...
return None
# we only append something to the existing script, we don't prepend
prefix = None
postfix = ["M117 Hello World from %s:%d" % (self._host, self._port)]
return prefix, postfix
# ...
__plugin_implementations__ = []
__plugin_hooks__ = dict()
def __plugin_init__():
# this gets called when OctoPrint initializes the plugin, so let's create the
# instance of our main implementation ...
plugin = MyPluginImplementation()
# ... register it as implementation ...
global __plugin_implementations__
__plugin_implementations__ = [plugin]
# ... and its hook handler method as hook handler.
global __plugin_hooks__
__plugin_hooks__ = {"octoprint.comm.protocol.scripts": plugin.my_script_hook_handler}
.. seealso::
:ref:`Available Hooks <sec-plugins-hooks>`
An overview of all hooks defined in OctoPrint itself.
.. _sec-plugin-concepts-helpers:
Helpers
-------

View file

@ -3,6 +3,65 @@
Available plugin hooks
======================
.. todo::
.. _sec-plugins-hook-comm-protocol-gcode:
Needs to be documented
octoprint.comm.protocol.gcode
-----------------------------
.. _sec-plugins-hook-comm-protocol-action:
octoprint.comm.protocol.action
------------------------------
.. py:function:: hook(comm_instance, line, action)
React to a :ref:`action command <>` received from the printer.
Hook handlers may use this to react to react to custom firmware messages. OctoPrint parses the received action
command ``line`` and provides the parsed ``action`` (so anything after ``// action:``) to the hook handler.
No returned value is expected.
:param object comm_instance: The :class:`~octoprint.util.comm.MachineCom` instance which triggered the hook.
:param str line: The complete line as received from the printer, format ``// action:<command>``
:param str action: The parsed out action command, so for a ``line`` like ``// action:some_command`` this will be
``some_command``
.. _sec-plugins-hook-comm-protocol-scripts:
octoprint.comm.protocol.scripts
-------------------------------
.. py:function:: hook(comm_instance, script_type, script_name)
Return a prefix to prepend and a postfix to append to the script ``script_name`` of type ``type``. Handlers should
make sure to only proceed with returning additional scripts if the ``script_type`` and ``script_name`` match
handled scripts. If not, None should be returned directly.
If the hook handler has something to add to the specified script, it may return a 2-tuple, with the first entry
defining the prefix (what to *prepend* to the script in question) and the last entry defining the postfix (what to
*append* to the script in question). Both prefix and postfix can be None to signify that nothing should be prepended
respectively appended.
The returned entries may be either iterables of script lines or a string including newlines of the script lines (which
will be split by the caller if necessary).
Example:
.. code-block:: python
def hook(comm_instance, script_type, script_name):
if not script_type == "gcode" or not script_name == "afterPrinterConnected":
return None
prefix = "M117 Hello\nM117 Hello World"
postfix = ["M117 Connected", "M117 to OctoPrint"]
return prefix, postfix
__plugin_hooks__ = {"octoprint.comm.protocol.scripts": hook}
:param MachineCom comm_instance: The :class:`~octoprint.util.comm.MachineCom` instance which triggered the hook.
:param str script_type: The type of the script for which the hook was called, currently only "gcode" is supported here.
:param str script_name: The name of the script for which the hook was called.
:return: A 2-tuple in the form ``(prefix, postfix)`` or None
:rtype: tuple or None

View file

@ -15,6 +15,7 @@ additional slicers. More plugin types are planned for the future.
:maxdepth: 3
using.rst
concepts.rst
gettingstarted.rst
infrastructure.rst
templates.rst