[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
|
||||
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
|
||||
======================
|
||||
|
||||
.. 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
|
||||
|
||||
using.rst
|
||||
concepts.rst
|
||||
gettingstarted.rst
|
||||
infrastructure.rst
|
||||
templates.rst
|
||||
|
|
|
|||
Loading…
Reference in a new issue