[Docs] Documented octoprint.comm.protocol.gcode hook

Experimenting with including examples stored on github, let's see if RTD likes that.
This commit is contained in:
Gina Häußge 2015-03-25 18:24:51 +01:00
parent af7d2bb8c7
commit c6e4057add
4 changed files with 145 additions and 7 deletions

View file

@ -32,7 +32,8 @@ needs_sphinx = '1.3'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['codeblockext', 'sphinx.ext.todo', 'sphinx.ext.autodoc', 'sphinxcontrib.httpdomain', 'sphinx.ext.napoleon']
extensions = ['codeblockext', 'onlineinclude', 'sphinx.ext.todo', 'sphinx.ext.autodoc', 'sphinxcontrib.httpdomain',
'sphinx.ext.napoleon']
todo_include_todos = True
# Add any paths that contain templates here, relative to this directory.

View file

@ -8,12 +8,54 @@ Available plugin hooks
octoprint.comm.protocol.gcode
-----------------------------
.. py:function:: hook(comm_instance, cmd, cmd_type=None, with_checksum=None)
Preprocess and optionally suppress a GCODE command before it is being sent to the printer.
Hook handlers may use this to rewrite or completely suppress certain commands before they enter the send queue of
the communication layer. The hook handler will be called with the ``cmd`` to be sent to the printer as well as
the parameters for sending like ``cmd_type`` and ``with_checksum``.
If the handler does not wish to handle the command, it should simply perform a ``return cmd`` as early as possible,
that will ensure that no changes are applied to the command.
If the handler wishes to suppress sending of the command altogether, it should return None instead. That will tell
OctoPrint that the ``cmd`` has been scraped altogether and not send anything.
More granular manipulation of the sending logic is possible by not just returning ``cmd`` (be it the original, a
rewritten variant or a None value) but a 2-tuple (cmd, cmd_type) or a 3-tuple (cmd, cmd_type, with_checksum). This
allows to also rewrite the ``cmd_type`` and the ``with_checksum`` parameter used for sending. Note that the latter
should only be necessary in very rare circumstances, since usually plugins should not need to have to decide whether
a command should be sent with a checksum or not.
Defining a ``cmd_type`` other than None will make sure OctoPrint takes care of only having one command of that type
in its sending queue. Predefined types are ``temperature_poll`` for temperature polling via ``M105`` and
``sd_status_poll`` for polling the SD printing status via ``M27``.
**Example**
The following hook handler replaces all ``M107`` ("Fan Off", deprecated) with an ``M106 S0`` ("Fan On" with speed
parameter)
.. onlineinclude:: https://raw.githubusercontent.com/OctoPrint/Plugin-Examples/master/rewrite_m107.py
:linenos:
:lines: 3-
:param object comm_instance: The :class:`~octoprint.util.comm.MachineCom` instance which triggered the hook.
:param str cmd: The GCODE command for which the hook was triggered. This is the full command as taken either
from the currently streamed GCODE file or via other means (e.g. user input our status polling).
:param str cmd_type: Type of command, ``temperature_poll`` for temperature polling or ``sd_status_poll`` for SD
printing status polling.
:param boolean with_checksum: Whether the ``cmd`` was to be sent with a checksum (True) or not (False)
:return: A rewritten ``cmd``, a tuple of ``cmd`` and ``cmd_type`` or ``cmd``, ``cmd_type`` and ``with_checksum``
or None to suppress sending of the ``cmd`` to the printer. See above for details.
.. _sec-plugins-hook-comm-protocol-action:
octoprint.comm.protocol.action
------------------------------
.. py:function:: hook(comm_instance, line, action)
.. py:function:: hook(comm_instance, line, action, *args, **kwargs)
React to a :ref:`action command <>` received from the printer.
@ -32,7 +74,7 @@ octoprint.comm.protocol.action
octoprint.comm.protocol.scripts
-------------------------------
.. py:function:: hook(comm_instance, script_type, script_name)
.. py:function:: hook(comm_instance, script_type, script_name, *args, **kwargs)
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
@ -50,7 +92,7 @@ octoprint.comm.protocol.scripts
.. code-block:: python
def hook(comm_instance, script_type, script_name):
def handler(comm_instance, script_type, script_name):
if not script_type == "gcode" or not script_name == "afterPrinterConnected":
return None
@ -58,7 +100,8 @@ octoprint.comm.protocol.scripts
postfix = ["M117 Connected", "M117 to OctoPrint"]
return prefix, postfix
__plugin_hooks__ = {"octoprint.comm.protocol.scripts": hook}
__plugin_name__ = "Example: GCODE script hook demo"
__plugin_hooks__ = {"octoprint.comm.protocol.scripts": handler}
: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.

View file

@ -0,0 +1,56 @@
# coding=utf-8
from __future__ import absolute_import
__author__ = "Gina Häußge <osd@foosel.net>"
__license__ = 'The MIT License <http://opensource.org/licenses/MIT>'
__copyright__ = "Copyright (C) 2015 Gina Häußge - Released under terms of the MIT License"
import codecs
import urllib2
from sphinx.directives import LiteralInclude, dedent_lines
cache = dict()
class OnlineIncludeDirective(LiteralInclude):
def read_with_encoding(self, filename, document, codec_info, encoding):
global cache
f = None
try:
if not self.arguments[0] in cache:
f = codecs.StreamReaderWriter(urllib2.urlopen(self.arguments[0]), codec_info[2],
codec_info[3], 'strict')
lines = f.readlines()
cache[self.arguments[0]] = lines
else:
lines = cache[self.arguments[0]]
lines = dedent_lines(lines, self.options.get('dedent'))
return lines
except (IOError, OSError, urllib2.URLError):
return [document.reporter.warning(
'Include file %r not found or reading it failed' % self.arguments[0],
line=self.lineno)]
except UnicodeError:
return [document.reporter.warning(
'Encoding %r used for reading included file %r seems to '
'be wrong, try giving an :encoding: option' %
(encoding, self.arguments[0]))]
finally:
if f is not None:
f.close()
def visit_onlineinclude(translator, node):
translator.visit_literal_block(node)
def depart_onlineinclude(translator, node):
translator.depart_literal_block(node)
def setup(app):
app.add_directive("onlineinclude", OnlineIncludeDirective)
handler = (visit_onlineinclude, depart_onlineinclude)
app.add_node(OnlineIncludeDirective, html=handler, latex=handler, text=handler)

View file

@ -1362,16 +1362,35 @@ class MachineCom(object):
if not self.isStreaming():
for hook in self._gcode_hooks:
hook_cmd = self._gcode_hooks[hook](self, cmd)
if hook_cmd and isinstance(hook_cmd, basestring):
hook_cmd = self._gcode_hooks[hook](self, cmd, cmd_type=cmd_type, send_checksum=sendChecksum)
# hook might have returned (cmd, sendChecksum) or (cmd, sendChecksum, cmd_type), split that
if isinstance(hook_cmd, tuple):
if len(hook_cmd) == 2:
hook_cmd, cmd_type = hook_cmd
elif len(hook_cmd) == 3:
hook_cmd, cmd_type, sendChecksum = hook_cmd
# hook might have returned None for cmd, if so stop processing, we won't send this command
# to the printer
if hook_cmd is None:
cmd = None
break
# if hook_cmd is a string, we'll replace cmd with it (it's been rewritten by the hook handler
elif isinstance(hook_cmd, basestring):
cmd = hook_cmd
# try to parse the cmd and extract the gcode type
gcode = self._regex_command.search(cmd)
if gcode:
gcode = gcode.group(1)
# fire events if necessary
if gcode in gcodeToEvent:
eventManager().fire(gcodeToEvent[gcode])
# send it through the specific handler if it exists
gcodeHandler = "_gcode_" + gcode
if hasattr(self, gcodeHandler):
cmd = getattr(self, gcodeHandler)(cmd)
@ -1388,6 +1407,25 @@ class MachineCom(object):
else:
self._enqueue_for_sending(cmd, command_type=cmd_type)
def gcode_command_for_cmd(self, cmd):
"""
Tries to parse the provided ``cmd`` and extract the GCODE command identifier from it (e.g. "G0" for "G0 X10.0").
Arguments:
cmd (str): The command to try to parse.
Returns:
str or None: The GCODE command identifier if it could be parsed, or None if not.
"""
if not cmd:
return None
gcode = self._regex_command.search(cmd)
if not gcode:
return None
return gcode.group(1)
##~~ send loop handling
def _enqueue_for_sending(self, command, linenumber=None, command_type=None):