[Docs] Started documenting plugin hooks and core concepts.
This commit is contained in:
parent
97c8c7826c
commit
b3739c10cc
5 changed files with 295 additions and 2 deletions
36
docs/features/action_commands.rst
Normal file
36
docs/features/action_commands.rst
Normal 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.
|
||||||
|
|
@ -9,3 +9,4 @@ Features
|
||||||
|
|
||||||
custom_controls.rst
|
custom_controls.rst
|
||||||
gcode_scripts.rst
|
gcode_scripts.rst
|
||||||
|
action_commands.rst
|
||||||
|
|
|
||||||
196
docs/plugins/concepts.rst
Normal file
196
docs/plugins/concepts.rst
Normal 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
|
||||||
|
-------
|
||||||
|
|
@ -3,6 +3,65 @@
|
||||||
Available plugin hooks
|
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
|
||||||
|
|
@ -15,6 +15,7 @@ additional slicers. More plugin types are planned for the future.
|
||||||
:maxdepth: 3
|
:maxdepth: 3
|
||||||
|
|
||||||
using.rst
|
using.rst
|
||||||
|
concepts.rst
|
||||||
gettingstarted.rst
|
gettingstarted.rst
|
||||||
infrastructure.rst
|
infrastructure.rst
|
||||||
templates.rst
|
templates.rst
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue