Hooks
General Concepts
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__
.
This control property is a dictionary consisting of the implemented hooks’ names as keys and either the hook callback or a 2-tuple of hook callback and order value as value.
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 GCODE scripts, either as prefix
(before the existing lines)
or as postfix
(after the existing lines).
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 Exception:
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(x.strip() for x in 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.
Plugins can easily add their own hooks too. For example, the Software Update Plugin 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_load__
property instead, manually instantiate your implementation instance and then add its hook handler method to the
__plugin_hooks__
property and itself to the __plugin_implementation__
property. See the following example.
# coding=utf-8
import octoprint.plugin
class CustomActionCommandPlugin(octoprint.plugin.OctoPrintPlugin):
def custom_action_handler(self, comm, line, action, *args, **kwargs):
if not action == "custom":
return
self._logger.info("Received \"custom\" action from printer")
__plugin_name__ = "Custom action command"
__plugin_pythoncompat__ = ">=2.7,<4"
def __plugin_load__():
plugin = CustomActionCommandPlugin()
global __plugin_implementation__
__plugin_implementation__ = plugin
global __plugin_hooks__
__plugin_hooks__ = {"octoprint.comm.protocol.action": plugin.custom_action_handler}
Execution Order
Hooks may also define an order number to allow influencing the execution order of the registered hook handlers. Instead of registering only a callback as hook handler, it is also possible to register a 2-tuple consisting of a callback and an integer value used for ordering handlers. They way this works is that OctoPrint will first sort all registered hook handlers with a order number, taking their identifier as the second sorting criteria, then after that append all hook handlers without a order number sorted only by their identifier.
An example should help clear this up. Let’s assume we have the following plugin ordertest
which defines a new
hook called octoprint.plugin.ordertest.callback
:
import octoprint.plugin
class OrderTestPlugin(octoprint.plugin.StartupPlugin):
def get_sorting_key(self, sorting_context):
return 10
def on_startup(self, *args, **kwargs):
self._logger.info("############### Order Test Plugin: StartupPlugin.on_startup called")
hooks = self._plugin_manager.get_hooks("octoprint.plugin.ordertest.callback")
for name, hook in hooks.items():
hook()
def on_after_startup(self):
self._logger.info("############### Order Test Plugin: StartupPlugin.on_after_startup called")
__plugin_name__ = "Order Test"
__plugin_version__ = "0.1.0"
__plugin_implementation__ = OrderTestPlugin()
And these three plugins defining handlers for that hook:
import logging
def callback(*args, **kwargs):
logging.getLogger("octoprint.plugins." + __name__).info("Callback called in oneorderedhook")
__plugin_name__ = "One Ordered Hook"
__plugin_version__ = "0.1.0"
__plugin_hooks__ = {
"octoprint.plugin.ordertest.callback": (callback, 1)
}
import logging
def callback(*args, **kwargs):
logging.getLogger("octoprint.plugins." + __name__).info("Callback called in anotherorderedhook")
__plugin_name__ = "Another Ordered Hook"
__plugin_version__ = "0.1.0"
__plugin_hooks__ = {
"octoprint.plugin.ordertest.callback": (callback, 2)
}
import logging
def callback(*args, **kwargs):
logging.getLogger("octoprint.plugins." + __name__).info("Callback called in yetanotherhook")
__plugin_name__ = "Yet Another Hook"
__plugin_version__ = "0.1.0"
__plugin_hooks__ = {
"octoprint.plugin.ordertest.callback": callback
}
Both orderedhook.py
and anotherorderedhook.py
not only define a handler callback in the hook registration,
but actually a 2-tuple consisting of a callback and an order number. yetanotherhook.py
only defines a callback.
OctoPrint will sort these hooks so that orderedhook
will be called first, then anotherorderedhook
, then
yetanotherhook
. Just going by the identifiers, the expected order would be anotherorderedhook
, orderedhook
,
yetanotherhook
, but since orderedhook
defines a lower order number (1
) than anotherorderedhook
(2
),
it will be sorted before anotherorderedhook
. If you copy those files into your ~/.octoprint/plugins
folder
and start up OctoPrint, you’ll see output like this:
[...]
2016-03-24 09:29:21,342 - octoprint.plugins.ordertest - INFO - ############### Order Test Plugin: StartupPlugin.on_startup called
2016-03-24 09:29:21,355 - octoprint.plugins.oneorderedhook - INFO - Callback called in oneorderedhook
2016-03-24 09:29:21,357 - octoprint.plugins.anotherorderedhook - INFO - Callback called in anotherorderedhook
2016-03-24 09:29:21,358 - octoprint.plugins.yetanotherhook - INFO - Callback called in yetanotherhook
[...]
2016-03-24 09:29:21,861 - octoprint.plugins.ordertest - INFO - ############### Order Test Plugin: StartupPlugin.on_after_startup called
[...]
Available plugin hooks
Note
All of the hooks below take at least two parameters, *args
and **kwargs
. Make sure those are
always present in your hook handler declaration.
They will act as placeholders if additional parameters are added to the hooks in the future and will allow
your plugin to stay compatible to OctoPrint without any necessary adjustments from you in these cases.
octoprint.access.permissions
- additional_permissions_hook(*args, **kwargs)
Added in version 1.4.0.
Return a list of additional permissions to register in the system on behalf of the plugin. Use this to add granular permissions to your plugin which can be configured for users and user groups in the general access control settings of OctoPrint.
Additional permissions must be modelled as
dict``s with at least a ``key
andname
field. Possible fields are as follows:key
: A key for the permission to be used for referring to it from source code. This will turned uppercase and prefixed withPLUGIN_<PLUGIN IDENTIFIER>_
before being made available onoctoprint.access.permissions.Permissions
, e.g.my_permission
on the plugin with identifierexample
turns intoPLUGIN_EXAMPLE_MY_PERMISSION
and can be accessed asoctoprint.access.permissions.Permissions.PLUGIN_EXAMPLE_MY_PERMISSION
on the server andpermissions.PLUGIN_EXAMPLE_MY_PERMISSION
on theAccessViewModel
on the client. Must only contain a-z, A-Z, 0-9 and _.name
: A human readable name for the permission.description
: A human readable description of the permission.permissions
: A list of permissions this permission includes, by key.roles
: A list of roles this permission includes. Roles are simple strings you define. Usually one role will suffice.dangerous
: Whether this permission should be considered dangerous (True
) or not (False
)default_groups
: A list of standard groups this permission should be apply to by default. Standard groups areoctoprint.access.ADMIN_GROUP
,octoprint.access.USER_GROUP
,octoprint.access.READONLY_GROUP
andoctoprint.access.GUEST_GROUP
The following example is based on some actual code included in the bundled Application Keys plugin and defines one additional permission called
ADMIN
with a roleadmin
which is marked as dangerous (since it gives access to the management to other user’s application keys) and by default will only be given to the standard admin group:from octoprint.access import ADMIN_GROUP def get_additional_permissions(*args, **kwargs): return [ dict(key="ADMIN", name="Admin access", description=gettext("Allows administrating all application keys"), roles=["admin"], dangerous=True, default_groups=[ADMIN_GROUP]) ] __plugin_hooks__ = { "octoprint.access.permissions": get_additional_permissions }
Once registered it can be referenced under the key
PLUGIN_APPKEYS_ADMIN
.- Returns:
A list of additional permissions to register in the system.
- Return type:
A list of dicts.
octoprint.access.users.factory
- user_manager_factory_hook(components, settings, *args, **kwargs)
Added in version 1.4.0.
Return a
UserManager
instance to use as global user manager object. This will be called only once during initial server startup.The provided
components
is a dictionary containing the already initialized system components:plugin_manager
: ThePluginManager
printer_profile_manager
: ThePrinterProfileManager
event_bus
: TheEventManager
analysis_queue
: TheAnalysisQueue
slicing_manager
: TheSlicingManager
file_manager
: TheFileManager
plugin_lifecycle_manager
: TheLifecycleManager
preemptive_cache
: ThePreemptiveCache
If the factory returns anything but
None
, it will be assigned to the globaluserManager
instance.If none of the registered factories return a user manager instance, the class referenced by the
config.yaml
entryaccessControl.userManager
will be initialized if possible, otherwise a stockFilebasedUserManager
will be instantiated, linked to the default user storage file~/.octoprint/users.yaml
.- Parameters:
components (dict) – System components to use for user manager instance initialization
settings (SettingsManager) – The global settings manager instance to fetch configuration values from if necessary
- Returns:
The
userManager
instance to use globally.- Return type:
UserManager subclass or None
octoprint.accesscontrol.keyvalidator
- acl_keyvalidator_hook(apikey, *args, **kwargs)
Added in version 1.3.6.
Via this hook plugins may validate their own customized API keys to be used to access OctoPrint’s API.
apikey
will be the API key as read from the request headers.Hook handlers are expected to return a
User
instance here that will then be considered that user making the request. By returningNone
or nothing at all, hook handlers signal that they do not handle the provided key.Example:
Allows using a user’s id as their API key (for obvious reasons this is NOT recommended in production environments and merely provided for educational purposes):
# Needs OctoPrint 1.3.6 or newer def hook(apikey, *args, **kwargs): from octoprint.server import userManager return userManager.findUser(userid=apikey) __plugin_pythoncompat__ = ">=2.7,<4" __plugin_hooks__ = { "octoprint.accesscontrol.keyvalidator": hook }
Added in version 1.3.6.
octoprint.cli.commands
- cli_commands_hook(cli_group, pass_octoprint_ctx, *args, **kwargs)
Added in version 1.3.0.
By providing a handler for this hook plugins may register commands on OctoPrint’s command line interface (CLI).
Handlers are expected to return a list of callables annotated as Click commands to register with the CLI.
The custom
MultiCommand
instanceOctoPrintPluginCommands
is provided as parameter. Via that object handlers may access the globalSettings
and thePluginManager
instance ascli_group.settings
andcli_group.plugin_manager
.Example:
Registers two new commands,
custom_cli_command:greet
andcustom_cli_command:random
with OctoPrint:# Needs OctoPrint 1.3.x or newer import click def clitest_commands(*args, **kwargs): @click.command("greet") @click.option("--greeting", "-g", default="Hello", help="The greeting to use") @click.argument("name", default="World") def greet_command(greeting, name): """Greet someone by name, the greeting can be customized.""" click.echo("{} {}!".format(greeting, name)) @click.command("random") @click.argument("name", default="World") @click.pass_context def random_greet_command(ctx, name): """Greet someone by name with a random greeting.""" greetings = [ "Hello", "Buon giorno", "Hola", "Konnichiwa", "Oh hai", "Hey", "Salve" ] from random import randrange greeting = greetings[randrange(0, len(greetings))] ctx.invoke(greet_command, greeting=greeting, name=name) return [greet_command, random_greet_command] __plugin_pythoncompat__ = ">=2.7,<4" __plugin_hooks__ = { "octoprint.cli.commands": clitest_commands }
Calling
octoprint plugins --help
shows the two new commands:$ octoprint plugins --help Usage: octoprint plugins [OPTIONS] COMMAND [ARGS]... Additional commands provided by plugins. Options: --help Show this message and exit. Commands: custom_cli_command:greet Greet someone by name, the greeting can be... custom_cli_command:random Greet someone by name with a random greeting. softwareupdate:check Check for updates. softwareupdate:update Apply updates.
Each also has an individual help output:
$ octoprint plugins custom_cli_command:greet --help Usage: octoprint plugins custom_cli_command:greet [OPTIONS] [NAME] Greet someone by name, the greeting can be customized. Options: -g, --greeting TEXT The greeting to use --help Show this message and exit. $ octoprint plugins custom_cli_command:random --help Usage: octoprint plugins custom_cli_command:random [OPTIONS] [NAME] Greet someone by name with a random greeting. Options: --help Show this message and exit.
And of course they work too:
$ octoprint plugins custom_cli_command:greet Hello World! $ octoprint plugins custom_cli_command:greet --greeting "Good morning" Good morning World! $ octoprint plugins custom_cli_command:random stranger Hola stranger!
Note
If your hook handler is an instance method of a plugin mixin implementation, be aware that the hook will be called without OctoPrint initializing your implementation instance. That means that none of the injected properties will be available and also the
initialize()
method will not be called.Your hook handler will have access to the plugin manager as
cli_group.plugin_manager
and to the global settings ascli_group.settings
. You can have your handler turn the latter into aPluginSettings
instance by usingoctoprint.plugin.plugin_settings_from_settings_plugin()
if your plugin’s implementation implements theSettingsPlugin
mixin and inject that and the plugin manager instance yourself:import octoprint.plugin class MyPlugin(octoprint.plugin.SettingsPlugin): def get_cli_commands(self, cli_group, pass_octoprint_ctx, *args, **kwargs): import logging settings = cli_group._settings plugin_settings = octoprint.plugin.plugin_settings_for_settings_plugin("myplugin", self) if plugin_settings is None: # this can happen if anything goes wrong with preparing the PluginSettings instance return dict() self._settings = plugin_settings self._plugin_manager = cli_group._plugin_manager self._logger = logging.getLogger(__name__) ### command definition starts here # ...
No other platform components will be available - the CLI runs outside of a running, fully initialized OctoPrint server context, so there is absolutely no way to access a printer connection, the event bus or anything else like that. The only things available are the settings and the plugin manager.
- Returns:
A list of Click commands or groups to provide on OctoPrint’s CLI.
- Return type:
octoprint.comm.protocol.firmware.info
- firmware_info_hook(comm_instance, firmware_name, firmware_data, *args, **kwargs)
Added in version 1.3.9.
Be notified of firmware information received from the printer following an
M115
.Hook handlers may use this to react/adjust behaviour based on reported firmware data. OctoPrint parses the received report line and provides the parsed
firmware_name
and additionalfirmware_data
contained therein. A response lineFIRMWARE_NAME:Some Firmware Name FIRMWARE_VERSION:1.2.3 PROTOCOL_VERSION:1.0
for example will be turned into adict
looking like this:dict(FIRMWARE_NAME="Some Firmware Name", FIRMWARE_VERSION="1.2.3", PROTOCOL_VERSION="1.0")
firmware_name
will beSome Firmware Name
in this case.Warning
Make sure to not perform any computationally expensive or otherwise long running actions within these handlers as you will effectively block the receive loop, causing the communication with the printer to stall.
This includes I/O of any kind.
octoprint.comm.protocol.firmware.capabilities
- firmware_capability_hook(comm_instance, capability, enabled, already_defined, *args, **kwargs)
Added in version 1.3.9.
Be notified of capability report entries received from the printer.
Hook handlers may use this to react to custom firmware capabilities. OctoPrint parses the received capability line and provides the parsed
capability
and whether it’senabled
to the handler. Additionally all already parsed capabilities will also be provided.Note that hook handlers will be called once per received capability line.
Warning
Make sure to not perform any computationally expensive or otherwise long running actions within these handlers as you will effectively block the receive loop, causing the communication with the printer to stall.
This includes I/O of any kind.
- Parameters:
octoprint.comm.protocol.firmware.capability_report
- firmware_capability_report_hook(comm_instance, firmware_capabilities, *args, **kwargs)
Added in version 1.9.0.
Be notified when all capability report entries are received from the printer.
Hook handlers may use this to react to the end of the custom firmware capability report. OctoPrint parses the received capability lines and provides a dictionary of all reported capabilities and whether they’re enabled to the handler.
Warning
Make sure to not perform any computationally expensive or otherwise long running actions within these handlers as you will effectively block the receive loop, causing the communication with the printer to stall.
This includes I/O of any kind.
octoprint.comm.protocol.action
- protocol_action_hook(comm_instance, line, action, name='', params='', *args, **kwargs)
Added in version 1.2.0.
React to a action command received from the printer.
Hook handlers may use this to react to custom firmware messages. OctoPrint parses the received action command
line
and provides the parsedaction
(so anything after// action:
) to the hook handler.No returned value is expected.
Warning
Make sure to not perform any computationally expensive or otherwise long running actions within your handlers as you will effectively block the receive loop, causing the communication with the printer to stall.
This includes I/O of any kind.
Example:
Logs if the
custom
action (// action:custom
) is received from the printer’s firmware.# coding=utf-8 import octoprint.plugin class CustomActionCommandPlugin(octoprint.plugin.OctoPrintPlugin): def custom_action_handler(self, comm, line, action, *args, **kwargs): if not action == "custom": return self._logger.info("Received \"custom\" action from printer") __plugin_name__ = "Custom action command" __plugin_pythoncompat__ = ">=2.7,<4" def __plugin_load__(): plugin = CustomActionCommandPlugin() global __plugin_implementation__ __plugin_implementation__ = plugin global __plugin_hooks__ __plugin_hooks__ = {"octoprint.comm.protocol.action": plugin.custom_action_handler}
- Parameters:
comm_instance (object) – The
MachineCom
instance which triggered the hook.line (str) – The complete line as received from the printer, format
// action:<command>
action (str) – The parsed out action command incl. parameters, so for a
line
like// action:some_command key value
this will besome_command key value
name (str) – The action command name, for a
line
like// action:some_command key value
this will besome_command
params (str) – The action command’s parameter, for a
line
like// action:some_command key value
this will bekey value
octoprint.comm.protocol.atcommand.<phase>
This describes actually two hooks:
octoprint.comm.protocol.atcommand.queuing
octoprint.comm.protocol.atcommand.sending
- protocol_atcommandphase_hook(comm_instance, phase, command, parameters, tags=None, *args, **kwargs)
Added in version 1.3.7.
Trigger on @ commands as they progress through the
queuing
andsending
phases of the comm layer. See the gcode phase hook for a detailed description of each of these phases.Hook handlers may use this to react to arbitrary @ commands included in GCODE files streamed to the printer or sent as part of GCODE scripts, through the API or plugins.
Please note that these hooks do not allow to rewrite, suppress or expand @ commands, they are merely callbacks to trigger the actual execution of whatever functionality lies behind a given @ command, similar to the action command hook.
Warning
Make sure to not perform any computationally expensive or otherwise long running actions within your handlers as you will effectively block the send/receive loops, causing the communication with the printer to stall.
This includes I/O of any kind.
Example
Pause the print on
@wait
(this mirrors the implementation of the built-in@pause
command, just with a different name).# coding=utf-8 def custom_atcommand_handler(comm, phase, command, parameters, tags=None, *args, **kwargs): if command != "wait": return if tags is None: tags = set() if "script:afterPrintPaused" in tags: # This makes sure we don't run into an infinite loop if the user included @wait in the afterPrintPaused # GCODE script for whatever reason return comm.setPause(True, tags=tags) __plugin_name__ = "Custom @ command" __plugin_pythoncompat__ = ">=2.7,<4" __plugin_hooks__ = {"octoprint.comm.protocol.atcommand.queuing": custom_atcommand_handler}
- Parameters:
comm_instance (object) – The
MachineCom
instance which triggered the hook.phase (str) – The current phase in the command progression, either
queuing
orsending
. Will always match the<phase>
of the hook.cmd (str) – The @ command without the leading @
parameters (str) – Any parameters provided to the @ command. If none were provided this will be an empty string.
octoprint.comm.protocol.gcode.<phase>
This actually describes four hooks:
octoprint.comm.protocol.gcode.queuing
octoprint.comm.protocol.gcode.queued
octoprint.comm.protocol.gcode.sending
octoprint.comm.protocol.gcode.sent
- protocol_gcodephase_hook(comm_instance, phase, cmd, cmd_type, gcode, subcode=None, tags=None, *args, **kwargs)
Added in version 1.2.0.
Pre- and postprocess commands as they progress through the various phases of being sent to the printer. The phases are the following:
queuing
: This phase is triggered just before the command is added to the send queue of the communication layer. This corresponds to the moment a command is being read from a file that is currently being printed. Handlers may suppress or change commands or their command type here. This is the only phase that supports multi command expansion by having the handler return a list, see below for details.queued
: This phase is triggered just after the command was added to the send queue of the communication layer. No manipulation is possible here anymore (returned values will be ignored).sending
: This phase is triggered just before the command is actually being sent to the printer. Right afterwards a line number will be assigned and the command will be sent. Handlers may suppress or change commands here. The command type is not taken into account anymore.sent
: This phase is triggered just after the command was handed over to the serial connection to the printer. No manipulation is possible here anymore (returned values will be ignored). A command that reaches the sent phase must not necessarily have reached the printer yet and it might also still run into communication problems and a resend might be triggered for it.
Hook handlers may use this to rewrite or completely suppress certain commands before they enter the send queue of the communication layer or before they are actually sent over the serial port, or to react to the queuing or sending of commands after the fact. The hook handler will be called with the processing
phase
, thecmd
to be sent to the printer as well as thecmd_type
parameter used for enqueuing (OctoPrint will make sure that the send queue will never contain more than one line with the samecmd_type
) and the detectedgcode
command (if it is one) as well as itssubcode
(if it has one). OctoPrint will also provide anytags
attached to the command throughout its lifecycle.Tags are arbitrary strings that can be attached to a command as it moves through the various phases and can be used to e.g. distinguish between commands that originated in a printed file (
source:file
) vs. a configured GCODE script (source:script
) vs. an API call (source:api
) vs. a plugin (source:plugin
orsource:rewrite
andplugin:<plugin identifier>
). If during development you want to get an idea of the various possible tags, set the loggeroctoprint.util.comm.command_phases
toDEBUG
, connect to a printer (real or virtual) and take a look at youroctoprint.log
during serial traffic:2018-02-16 18:20:31,213 - octoprint.util.comm.command_phases - DEBUG - phase: queuing | command: T0 | gcode: T | tags: [ api:printer.command, source:api, trigger:printer.commands ] 2018-02-16 18:20:31,216 - octoprint.util.comm.command_phases - DEBUG - phase: queued | command: M117 Before T! | gcode: M117 | tags: [ api:printer.command, phase:queuing, plugin:multi_gcode_test, source:api, source:rewrite, trigger:printer.commands ] 2018-02-16 18:20:31,217 - octoprint.util.comm.command_phases - DEBUG - phase: sending | command: M117 Before T! | gcode: M117 | tags: [ api:printer.command, phase:queuing, plugin:multi_gcode_test, source:api, source:rewrite, trigger:printer.commands ] 2018-02-16 18:20:31,217 - octoprint.util.comm.command_phases - DEBUG - phase: queued | command: T0 | gcode: T | tags: [ api:printer.command, source:api, trigger:printer.commands ] 2018-02-16 18:20:31,219 - octoprint.util.comm.command_phases - DEBUG - phase: queued | command: M117 After T! | gcode: M117 | tags: [ api:printer.command, phase:queuing, plugin:multi_gcode_test, source:api, source:rewrite, trigger:printer.commands ] 2018-02-16 18:20:31,220 - octoprint.util.comm.command_phases - DEBUG - phase: sent | command: M117 Before T! | gcode: M117 | tags: [ api:printer.command, phase:queuing, plugin:multi_gcode_test, source:api, source:rewrite, trigger:printer.commands ] 2018-02-16 18:20:31,230 - tornado.access - INFO - 204 POST /api/printer/command (127.0.0.1) 23.00ms 2018-02-16 18:20:31,232 - tornado.access - INFO - 200 POST /api/printer/command (127.0.0.1) 25.00ms 2018-02-16 18:20:31,232 - octoprint.util.comm.command_phases - DEBUG - phase: sending | command: T0 | gcode: T | tags: [ api:printer.command, source:api, trigger:printer.commands ] 2018-02-16 18:20:31,234 - octoprint.util.comm.command_phases - DEBUG - phase: sent | command: T0 | gcode: T | tags: [ api:printer.command, source:api, trigger:printer.commands ] 2018-02-16 18:20:31,242 - octoprint.util.comm.command_phases - DEBUG - phase: sending | command: M117 After T! | gcode: M117 | tags: [ api:printer.command, phase:queuing, plugin:multi_gcode_test, source:api, source:rewrite, trigger:printer.commands ] 2018-02-16 18:20:31,243 - octoprint.util.comm.command_phases - DEBUG - phase: sent | command: M117 After T! | gcode: M117 | tags: [ api:printer.command, phase:queuing, plugin:multi_gcode_test, source:api, source:rewrite, trigger:printer.commands ] 2018-02-16 18:20:38,552 - octoprint.util.comm.command_phases - DEBUG - phase: queuing | command: G91 | gcode: G91 | tags: [ api:printer.printhead, source:api, trigger:printer.commands, trigger:printer.jog ] 2018-02-16 18:20:38,552 - octoprint.util.comm.command_phases - DEBUG - phase: queued | command: G91 | gcode: G91 | tags: [ api:printer.printhead, source:api, trigger:printer.commands, trigger:printer.jog ] 2018-02-16 18:20:38,553 - octoprint.util.comm.command_phases - DEBUG - phase: sending | command: G91 | gcode: G91 | tags: [ api:printer.printhead, source:api, trigger:printer.commands, trigger:printer.jog ] 2018-02-16 18:20:38,553 - octoprint.util.comm.command_phases - DEBUG - phase: queuing | command: G1 X10 F6000 | gcode: G1 | tags: [ api:printer.printhead, source:api, trigger:printer.commands, trigger:printer.jog ] 2018-02-16 18:20:38,555 - octoprint.util.comm.command_phases - DEBUG - phase: queued | command: G1 X10 F6000 | gcode: G1 | tags: [ api:printer.printhead, source:api, trigger:printer.commands, trigger:printer.jog ] 2018-02-16 18:20:38,556 - octoprint.util.comm.command_phases - DEBUG - phase: sent | command: G91 | gcode: G91 | tags: [ api:printer.printhead, source:api, trigger:printer.commands, trigger:printer.jog ] 2018-02-16 18:20:38,556 - octoprint.util.comm.command_phases - DEBUG - phase: queuing | command: G90 | gcode: G90 | tags: [ api:printer.printhead, source:api, trigger:printer.commands, trigger:printer.jog ] 2018-02-16 18:20:38,558 - octoprint.util.comm.command_phases - DEBUG - phase: queued | command: G90 | gcode: G90 | tags: [ api:printer.printhead, source:api, trigger:printer.commands, trigger:printer.jog ]
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 aretemperature_poll
for temperature polling viaM105
andsd_status_poll
for polling the SD printing status viaM27
.phase
will always match the<phase>
part of the implemented hook (e.g.octoprint.comm.protocol.gcode.queued
handlers will always be called withphase
set toqueued
). This parameter is provided so that plugins may utilize the same hook for multiple phases if required.Handlers are expected to return one of the following result variants:
None
: Don’t change anything. Note that Python functions will also automatically returnNone
if an emptyreturn
statement is used or just nothing is returned explicitly from the handler. Hence, the following examples are all falling into this category and equivalent:def one(*args, **kwargs): print("I return None explicitly") return None def two(*args, **kwargs): print("I just return without any values") return def three(*args, **kwargs): print("I don't explicitly return anything at all")
Handlers which do not wish to modify (or suppress)
cmd
orcmd_type
at all should use this option.A string with the rewritten version of the
cmd
, e.g.return "M110"
. To avoid situations which will be difficult to debug should the returned command be later changed toNone
(with the intent to suppress the command instead but actually causingcmd
andcmd_type
to just staying as-is), this variant should be entirely avoided by handlers.A 1-tuple consisting of a rewritten version of the
cmd
, e.g.return "M110",
, orNone
in order to suppress the command, e.g.return None,
. Handlers which wish to rewrite the command or to suppress it completely should use this option.A 2-tuple consisting of a rewritten version of the
cmd
and thecmd_type
, e.g.return "M105", "temperature_poll"
. Handlers which wish to rewrite both the command and the command type should use this option.A 3-tuple consisting of a rewritten version of the
cmd
, thecmd_type
and any additionaltags
you might want to attach to the lifecycle of the command in a set, e.g.return "M105", "temperature_poll", {"my_custom_tag"}
“queuing” phase only: A list of any of the above to allow for expanding one command into many. The following example shows how any queued command could be turned into a sequence of a temperature query, line number reset, display of the
gcode
on the printer’s display and finally the actual command (this example does not make a lot of sense to be quite honest):def rewrite_foo(self, comm_instance, phase, cmd, cmd_type, gcode, subcode=None, tags=None *args, **kwargs): if gcode or not cmd.startswith("@foo"): return return [("M105", "temperature_poll"), # 2-tuple, command & command type ("M110",), # 1-tuple, just the command "M117 echo foo: {}".format(cmd)] # string, just the command __plugin_hooks__ = { "octoprint.comm.protocol.gcode.queuing": rewrite_foo }
Note: Only one command of a given
cmd_type
(other than None) may be queued at a time. Trying to rewrite thecmd_type
to one already in the queue will give an error.Warning
Make sure to not perform any computationally expensive or otherwise long running actions within these handlers as you will effectively block the send loop, causing the communication with the printer to stall.
This includes I/O of any kind.
Example
The following hook handler replaces all
M107
(“Fan Off”, deprecated) with anM106 S0
(“Fan On” with speed parameter) upon queuing and logs all sentM106
.# coding=utf-8 import octoprint.plugin class RewriteM107Plugin(octoprint.plugin.OctoPrintPlugin): def rewrite_m107(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwargs): if gcode and gcode == "M107": cmd = "M106 S0" return cmd, def sent_m106(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwargs): if gcode and gcode == "M106": self._logger.info("Just sent M106: {cmd}".format(**locals())) __plugin_name__ = "Rewrite M107" __plugin_pythoncompat__ = ">=2.7,<4" def __plugin_load__(): global __plugin_implementation__ __plugin_implementation__ = RewriteM107Plugin() global __plugin_hooks__ __plugin_hooks__ = { "octoprint.comm.protocol.gcode.queuing": __plugin_implementation__.rewrite_m107, "octoprint.comm.protocol.gcode.sent": __plugin_implementation__.sent_m106 }
- Parameters:
comm_instance (object) – The
MachineCom
instance which triggered the hook.phase (str) – The current phase in the command progression, either
queuing
,queued
,sending
orsent
. Will always match the<phase>
of the hook.cmd (str) – 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).
cmd_type (str) – Type of command, e.g.
temperature_poll
for temperature polling orsd_status_poll
for SD printing status polling.gcode (str) – Parsed GCODE command, e.g.
G0
orM110
, may also be None if no known command could be parsedsubcode (str) – Parsed subcode of the GCODE command, e.g.
1
forM80.1
. Will be None if no subcode was provided or no command could be parsed.tags – Tags attached to the command
- Returns:
None, 1-tuple, 2-tuple or string, see the description above for details.
octoprint.comm.protocol.gcode.received
- gcode_received_hook(comm_instance, line, *args, **kwargs)
Added in version 1.3.0.
Get the returned lines sent by the printer. Handlers should return the received line or in any case, the modified version of it. If the handler returns None, processing will be aborted and the communication layer will get an empty string as the received line. Note that Python functions will also automatically return
None
if an emptyreturn
statement is used or just nothing is returned explicitly from the handler.Warning
Make sure to not perform any computationally expensive or otherwise long running actions within these handlers as you will effectively block the receive loop, causing the communication with the printer to stall.
This includes I/O of any kind.
Example:
Looks for the response of an
M115
, which contains information about theMACHINE_TYPE
, among other things.# coding=utf-8 import logging def detect_machine_type(comm, line, *args, **kwargs): if "MACHINE_TYPE" not in line: return line from octoprint.util.comm import parse_firmware_line # Create a dict with all the keys/values returned by the M115 request printer_data = parse_firmware_line(line) logging.getLogger("octoprint.plugin." + __name__).info("Machine type detected: {machine}.".format(machine=printer_data["MACHINE_TYPE"])) return line __plugin_name__ = "Detect Machine Data" __plugin_pythoncompat__ = ">=2.7,<4" __plugin_hooks__ = { "octoprint.comm.protocol.gcode.received": detect_machine_type }
octoprint.comm.protocol.gcode.error
- gcode_error_hook(comm_instance, error_message, *args, **kwargs)
Added in version 1.3.7.
Get the messages of any errors messages sent by the printer, with the leading
Error:
or!!
already stripped. Handlers should return True if they handled that error internally and it should not be processed by the system further. Normal processing of these kinds of errors - depending on the configuration of error handling - involves canceling the ongoing print and possibly also disconnecting.Plugins might utilize this hook to handle errors generated by the printer that are recoverable in one way or the other and should not trigger the normal handling that assumes the worst.
Warning
Make sure to not perform any computationally expensive or otherwise long running actions within these handlers as you will effectively block the receive loop, causing the communication with the printer to stall.
This includes I/O of any kind.
Example:
Looks for error messages containing “fan error” or “bed missing” (ignoring case) and marks them as handled by the plugin.
import logging _HANDLED_ERRORS = ('fan error', 'bed missing') def handle_error(comm, error_message, *args, **kwargs): lower_error = error_message.lower() if any(map(lambda x: x in lower_error, _HANDLED_ERRORS)): logging.getLogger("octoprint.plugin.error_handler_test").info("Error \"{}\" is handled by this plugin".format(error_message)) return True __plugin_name__ = "Comm Error Handler Test" __plugin_pythoncompat__ = ">=2.7,<4" __plugin_hooks__ = { "octoprint.comm.protocol.gcode.error": handle_error }
octoprint.comm.protocol.scripts
- protocol_scripts_hook(comm_instance, script_type, script_name, *args, **kwargs)
Added in version 1.2.0.
Changed in version 1.3.7.
Return a prefix to prepend, postfix to append, and optionally a dictionary of variables to provide to the script
script_name
of typetype
. Handlers should make sure to only proceed with returning additional scripts if thescript_type
andscript_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, a 3-tuple or a 4-tuple with the first entry defining the prefix (what to prepend to the script in question), the second entry defining the postfix (what to append to the script in question), and finally if desired a dictionary of variables to be made available to the script on third and additional tags to set on the commands on fourth position. Both prefix and postfix can be None to signify that nothing should be prepended respectively appended.
The returned prefix and postfix 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 1:
Appends an
M117 OctoPrint connected
to the configuredafterPrinterConnected
GCODE script.# coding=utf-8 def message_on_connect(comm, script_type, script_name, *args, **kwargs): if not script_type == "gcode" or not script_name == "afterPrinterConnected": return None prefix = None postfix = "M117 OctoPrint connected" return prefix, postfix __plugin_name__ = "Message on connect" __plugin_pythoncompat__ = ">=2.7,<4" __plugin_hooks__ = {"octoprint.comm.protocol.scripts": message_on_connect}
Example 2:
Provides the variable
myvariable
to the configuredbeforePrintStarted
GCODE script.# coding=utf-8 def gcode_script_variables(comm, script_type, script_name, *args, **kwargs): if not script_type == "gcode" or not script_name == "beforePrintStarted": return None prefix = None postfix = None variables = dict(myvariable="Hi! I'm a variable!") return prefix, postfix, variables __plugin_name__ = "gcode script variables" __plugin_pythoncompat__ = ">=2.7,<4" __plugin_hooks__ = {"octoprint.comm.protocol.scripts": gcode_script_variables}
- Parameters:
- Returns:
A 2-tuple in the form
(prefix, postfix)
, 3-tuple in the form(prefix, postfix, variables)
, or None- Return type:
tuple or None
octoprint.comm.protocol.temperatures.received
- protocol_temperatures_received_hook(comm_instance, parsed_temperatures, *args, **kwargs)
Added in version 1.3.6.
Get the parsed temperatures returned by the printer, allowing handlers to modify them prior to handing them off to the system. Handlers are expected to either return
parsed_temperatures
as-is or a modified copy thereof.parsed_temperatures
is a dictionary mapping from tool/bed identifier (B
,T0
,T1
) to a 2-tuple of actual and target temperature, e.g.{'B': (45.2, 50.0), 'T0': (178.9, 210.0), 'T1': (21.3, 0.0)}
.This hook can be useful in cases where a printer e.g. is prone to returning garbage data from time to time, allowing additional sanity checking to be applied and invalid values to be filtered out. If a handler returns an empty dictionary or
None
, no further processing will take place.Warning
Make sure to not perform any computationally expensive or otherwise long running actions within these handlers as you will effectively block the receive loop, causing the communication with the printer to stall.
This includes I/O of any kind.
Example
The following example shows how to filter out actual temperatures that are outside a sane range of 1°C to 300°C.
# coding=utf-8 def sanitize_temperatures(comm, parsed_temps): return dict((k, v) for k, v in parsed_temps.items() if isinstance(v, tuple) and len(v) == 2 and is_sane(v[0])) def is_sane(actual): return 1.0 <= actual <= 300.0 __plugin_name__ = "Sanitize Temperatures" __plugin_pythoncompat__ = ">=2.7,<4" __plugin_hooks__ = { "octoprint.comm.protocol.temperatures.received": sanitize_temperatures }
octoprint.comm.transport.serial.additional_port_names
- additional_port_names_hook(candidates, *args, **kwargs)
Added in version 1.4.1.
Return additional port names (not glob patterns!) to use as a serial connection to the printer. Expected to be
list
ofstring
.Useful in combination with octoprint.comm.transport.serial.factory to implement custom serial-like ports through plugins.
For an example of use see the bundled
virtual_printer
plugin.
octoprint.comm.transport.serial.factory
- serial_factory_hook(comm_instance, port, baudrate, read_timeout, *args, **kwargs)
Added in version 1.2.0.
Return a serial object to use as serial connection to the printer. If a handler cannot create a serial object for the specified
port
(andbaudrate
), it should just returnNone
.If the hook handler needs to perform state switches (e.g. for autodetection) or other operations on the
MachineCom
instance, it can use the suppliedcomm_instance
to do so. Plugin authors should keep in mind however that due to a pending change in the communication layer of OctoPrint, that interface will change in the future. Authors are advised to follow OctoPrint’s development closely if directly utilizingMachineCom
functionality.A valid serial instance is expected to provide the following methods, analogue to PySerial’s
serial.Serial
:- readline(size=None, eol=’n’)
Reads a line from the serial connection, compare
serial.Serial.readline()
.- write(data)
Writes data to the serial connection, compare
serial.Serial.write()
.- close()
Closes the serial connection, compare
serial.Serial.close()
.
Additionally setting the following attributes need to be supported if baudrate detection is supposed to work:
- baudrate
An integer describing the baudrate to use for the serial connection, compare
serial.Serial.baudrate
.- timeout
An integer describing the read timeout on the serial connection, compare
serial.Serial.timeout
.
Example:
Serial factory similar to the default one which performs auto detection of the serial port if
port
isNone
orAUTO
.def default(comm_instance, port, baudrate, connection_timeout): if port is None or port == 'AUTO': # no known port, try auto detection comm_instance._changeState(comm_instance.STATE_DETECT_SERIAL) serial_obj = comm_instance._detectPort(False) if serial_obj is None: comm_instance._log("Failed to autodetect serial port") comm_instance._errorValue = 'Failed to autodetect serial port.' comm_instance._changeState(comm_instance.STATE_ERROR) eventManager().fire(Events.ERROR, {"error": comm_instance.getErrorString()}) return None else: # connect to regular serial port comm_instance._log("Connecting to: %s" % port) if baudrate == 0: serial_obj = serial.Serial(str(port), 115200, timeout=connection_timeout, writeTimeout=10000, parity=serial.PARITY_ODD) else: serial_obj = serial.Serial(str(port), baudrate, timeout=connection_timeout, writeTimeout=10000, parity=serial.PARITY_ODD) serial_obj.close() serial_obj.parity = serial.PARITY_NONE serial_obj.open() return serial_obj
- Parameters:
comm_instance (MachineCom) – The
MachineCom
instance which triggered the hook.port (str) – The port for which to construct a serial instance. May be
None
orAUTO
in which case port auto detection is to be performed.baudrate (int) – The baudrate for which to construct a serial instance. May be 0 in which case baudrate auto detection is to be performed.
read_timeout (int) – The read timeout to set on the serial port.
- Returns:
The constructed serial object ready for use, or
None
if the handler could not construct the object.- Return type:
A serial instance implementing the methods
readline(...)
,write(...)
,close()
and optionallybaudrate
andtimeout
attributes as described above.
octoprint.events.register_custom_events
- register_custom_events_hook(*args, **kwargs)
Added in version 1.3.11.
Return a list of custom events to register in the system for your plugin.
Should return a list of strings which represent the custom events. Their name on the octoprint.events.Events object will be the returned value transformed into upper case
CAMEL_CASE
and prefixed withPLUGIN_<IDENTIFIER>
. Their value will be prefixed withplugin_<identifier>_
.Example:
Consider the following hook part of a plugin with the identifier
myplugin
. It will register two custom events in the system,octoprint.events.Events.PLUGIN_MYPLUGIN_MY_CUSTOM_EVENT
with valueplugin_myplugin_my_custom_event
andoctoprint.events.Events.PLUGIN_MYPLUGIN_MY_OTHER_CUSTOM_EVENT
with valueplugin_myplugin_my_other_custom_event
.def register_custom_events(*args, **kwargs): return ["my_custom_event", "my_other_custom_event"]
- Returns:
A list of custom events to register
- Return type:
octoprint.filemanager.analysis.factory
- analysis_queue_factory_hook(*args, **kwargs)
Added in version 1.3.9.
Return additional (or replacement) analysis queue factories used for analysing uploaded files.
Should return a dictionary to merge with the existing dictionary of factories, mapping from extension tree leaf to analysis queue factory. Analysis queue factories are expected to be
AbstractAnalysisQueue
subclasses or factory methods taking one argument (the finish callback to be used by the queue implementation to signal that an analysis has been finished to the system). See the source ofGcodeAnalysisQueue
for an example.By default, only one analysis queue factory is registered in the system, for file type
gcode
:GcodeAnalysisQueue
. This can be replaced by plugins using this hook, allowing other approaches to file analysis.This is useful for plugins wishing to provide (alternative) methods of metadata analysis for printable files.
Example:
The following handler would replace the existing analysis queue for
gcode
files with a custom implementation:from octoprint.filemanager.analysis import AbstractAnalysisQueue class MyCustomGcodeAnalysisQueue(AbstractAnalysisQueue): # ... custom implementation here ... def custom_gcode_analysis_queue(*args, **kwargs): return dict(gcode=MyCustomGcodeAnalysisQueue)
- Returns:
A dictionary of analysis queue factories, mapped by their targeted file type.
- Return type:
octoprint.filemanager.extension_tree
- file_extension_hook(*args, **kwargs)
Added in version 1.2.0.
Return additional entries for the tree of accepted file extensions for uploading/handling by the file manager.
Should return a dictionary to merge with the existing extension tree, adding additional extension groups to
machinecode
ormodel
types.Example:
The following handler would add a new file type “x3g” as accepted
machinecode
format, with extensionsx3g
ands3g
:def support_x3g_machinecode(*args, **kwargs): return dict( machinecode=dict( x3g=["x3g", "s3g"] ) )
Note
This will only add the supplied extensions to the extension tree, allowing the files to be uploaded and managed through the file manager. Plugins will need to add further steps to ensure that the files will be processable in the rest of the system (e.g. handling/preprocessing new machine code file types for printing etc)!
- Returns:
The partial extension tree to merge with the full extension tree.
- Return type:
octoprint.filemanager.preprocessor
- file_preprocessor_hook(path, file_object, links=None, printer_profile=None, allow_overwrite=False, *args, **kwargs)
Added in version 1.2.0.
Replace the
file_object
used for saving added files to storage by callingsave()
.path
will be the future path of the file on the storage. The file’s name is accessible viafilename
.file_object
will be a subclass ofAbstractFileWrapper
. Handlers may access the raw data of the file viastream()
, e.g. to wrap it further. Handlers which do not wish to handle the file_object should just return it untouched.Example
The following plugin example strips all comments from uploaded/generated GCODE files ending on the name postfix
_strip
.# coding=utf-8 import octoprint.plugin import octoprint.filemanager import octoprint.filemanager.util from octoprint.util.comm import strip_comment class CommentStripper(octoprint.filemanager.util.LineProcessorStream): def process_line(self, line): decoded_line = strip_comment(line.decode()).strip() if not len(decoded_line): return None return (decoded_line + "\r\n").encode() def strip_all_comments(path, file_object, links=None, printer_profile=None, allow_overwrite=True, *args, **kwargs): if not octoprint.filemanager.valid_file_type(path, type="gcode"): return file_object import os name, _ = os.path.splitext(file_object.filename) if not name.endswith("_strip"): return file_object return octoprint.filemanager.util.StreamWrapper(file_object.filename, CommentStripper(file_object.stream())) __plugin_name__ = "Strip comments from GCODE" __plugin_description__ = "Strips all comments and empty lines from uploaded/generated GCODE files ending on the name " \ "postfix \"_strip\", e.g. \"some_file_strip.gcode\"." __plugin_pythoncompat__ = ">=3,<4" __plugin_hooks__ = { "octoprint.filemanager.preprocessor": strip_all_comments }
- Parameters:
path (str) – The path on storage the file_object is to be stored
file_object (AbstractFileWrapper) – The
AbstractFileWrapper
instance representing the file object to store.links (dict) – The links that are going to be stored with the file.
printer_profile (dict) – The printer profile associated with the file.
allow_overwrite (boolean) – Whether to allow overwriting an existing file named the same or not.
- Returns:
The file_object as passed in or None, or a replaced version to use instead for further processing.
- Return type:
AbstractFileWrapper or None
octoprint.plugin.backup.additional_excludes
Added in version 1.5.0.
See here.
octoprint.plugin.backup.before_backup
Added in version 1.9.0.
See here.
octoprint.plugin.backup.after_backup
Added in version 1.9.0.
See here.
octoprint.plugin.backup.before_restore
Added in version 1.9.0.
See here.
octoprint.plugin.backup.after_restore
Added in version 1.9.0.
See here.
octoprint.plugin.pluginmanager.reconnect_hooks
Added in version 1.4.0.
See here.
octoprint.plugin.softwareupdate.check_config
Added in version 1.2.0.
See here.
octoprint.printer.additional_state_data
- additional_state_data_hook(initial=False, *args, **kwargs)
Added in version 1.5.0.
Use this to inject additional data into the data structure returned from the printer backend to the frontend on the push socket or other registered
octoprint.printer.PrinterCallback
. Anything you return here will be located beneathplugins.<your plugin id>
in the resulting initial and current data push structure.The
initial
parameter will beTrue
if this the additional update sent to the callback. Your handler should return adict
, orNone
if nothing should be included.Warning
Make sure to not perform any computationally expensive or otherwise long running actions within these handlers as you could stall the whole state monitor and thus updates being pushed to the frontend.
This includes I/O of any kind.
Cache your data!
- Parameters:
initial (boolean) – True if this is the initial update, False otherwise
- Returns:
Additional data to include
- Return type:
octoprint.printer.factory
- printer_factory_hook(components, *args, **kwargs)
Added in version 1.3.0.
Return a
PrinterInstance
instance to use as global printer object. This will be called only once during initial server startup.The provided
components
is a dictionary containing the already initialized system components:plugin_manager
: ThePluginManager
printer_profile_manager
: ThePrinterProfileManager
event_bus
: TheEventManager
analysis_queue
: TheAnalysisQueue
slicing_manager
: TheSlicingManager
file_manager
: TheFileManager
plugin_lifecycle_manager
: TheLifecycleManager
user_manager
: TheUserManager
preemptive_cache
: ThePreemptiveCache
If the factory returns anything but
None
, it will be assigned to the globalprinter
instance.If none of the registered factories return a printer instance, the default
Printer
class will be instantiated.- Parameters:
components (dict) – System components to use for printer instance initialization
- Returns:
The
printer
instance to use globally.- Return type:
PrinterInterface subclass or None
octoprint.printer.handle_connect
- handle_connect(*args, **kwargs):
Added in version 1.6.0.
Allows plugins to perform actions upon connecting to a printer. By returning
True
, plugins may also prevent further processing of the connect command. This hook is of special interest if your plugin needs a connect from going through under certain circumstances or if you need to do something before a connection to the printer is established (e.g. switching on power to the printer).- Parameters:
kwargs – All connection parameters supplied to the
connect
call. Currently this also includesport
,baudrate
andprofile
.- Returns:
True
if OctoPrint should not proceed with the connect- Return type:
boolean or None
octoprint.printer.estimation.factory
- print_time_estimator_factory(*args, **kwargs)
Added in version 1.3.9.
Return a
PrintTimeEstimator
subclass (or factory) to use for print time estimation. This will be called on each start of a print or streaming job with a single parameterjob_type
denoting the type of job that was just started:local
meaning a print of a local file through the serial connection,sdcard
a print of a file stored on the printer’s SD card,stream
the streaming of a local file to the printer’s SD card.This is useful for plugins wishing to provide alternative methods of live print time estimation.
If none of the registered factories return a
PrintTimeEstimator
subclass, the defaultPrintTimeEstimator
will be used.Example:
The following example would replace the stock print time estimator with (a nonsensical) one that always estimates two hours of print time left:
from octoprint.printer.estimation import PrintTimeEstimator class CustomPrintTimeEstimator(PrintTimeEstimator): def __init__(self, job_type): pass def estimate(self, progress, printTime, cleanedPrintTime, statisticalTotalPrintTime, statisticalTotalPrintTimeType): # always reports 2h as printTimeLeft return 2 * 60 * 60, "estimate" def create_estimator_factory(*args, **kwargs): return CustomPrintTimeEstimator __plugin_hooks__ = { "octoprint.printer.estimation.factory": create_estimator_factory }
- Returns:
The
PrintTimeEstimator
class to use, or a factory method- Return type:
class or function
octoprint.printer.sdcardupload
- sd_card_upload_hook(printer, filename, path, start_callback, success_callback, failure_callback, *args, **kwargs)
Added in version 1.3.11.
Via this hook plugins can change the way files are being uploaded to the sd card of the printer.
Implementations must call the provided
start_callback
on start of the file transfer and either thesuccess_callback
orfailure_callback
on the end of the file transfer, depending on whether it was successful or not.The
start_callback
has the following signature:def start_callback(local_filename, remote_filename): # ...
local_filename
must be the name of the file on thelocal
storage,remote_filename
the name of the file to be created on thesdcard
storage.success_callback
andfailure_callback
both have the following signature:def success_or_failure_callback(local_filename, remote_filename, elapsed): # ...
local_filename
must be the name of the file on thelocal
storage,remote_filename
the name of the file to be created on thesdcard
storage.elapsed
is the elapsed time in seconds.If the hook is going to handle the upload, it must return the (future) remote filename of the file on the
sdcard
storage. If it returnsNone
(or an otherwise false-y value), OctoPrint will interpret this as the hook not going to handle the file upload, in which case the next hook or - if no other hook is registered - the default implementation will be called.Example
The following example creates a dummy SD card uploader that does nothing but sleep for ten seconds when a file is supposed to be uploaded. Note that the long running process of sleeping for ten seconds is extracted into its own thread, which is important in order to not block the main application!
import threading import logging import time def nop_upload_to_sd(printer, filename, path, sd_upload_started, sd_upload_succeeded, sd_upload_failed, *args, **kwargs): logger = logging.getLogger(__name__) remote_name = printer._get_free_remote_name(filename) logger.info("Starting dummy SDCard upload from {} to {}".format(filename, remote_name)) sd_upload_started(filename, remote_name) def process(): logger.info("Sleeping 10s...") time.sleep(10) logger.info("And done!") sd_upload_succeeded(filename, remote_name, 10) thread = threading.Thread(target=process) thread.daemon = True thread.start() return remote_name __plugin_name__ = "No-op SDCard Upload Test" __plugin_hooks__ = { "octoprint.printer.sdcardupload": nop_upload_to_sd }
Added in version 1.3.11.
- Parameters:
printer (object) – the
PrinterInterface
instance the hook was called fromfilename (str) – filename on the
local
storagepath (str) – path of the file in the local file system
sd_upload_started (function) – callback for when the upload started
sd_upload_success (function) – callback for successful finish of upload
sd_upload_failure (function) – callback for failure of upload
- Returns:
the name of the file on the
sdcard
storage orNone
- Return type:
string or
None
octoprint.server.api.after_request
- after_request_handlers_hook(*args, **kwargs)
Added in version 1.3.10.
Allows adding additional after-request-handlers to API endpoints defined by OctoPrint itself and installed plugins.
Your plugin might need this to further restrict access to API methods.
Important
Implementing this hook will make your plugin require a restart of OctoPrint for enabling/disabling it fully.
octoprint.server.api.before_request
- before_request_handlers_hook(*args, **kwargs)
Added in version 1.3.10.
Allows adding additional before-request-handlers to API endpoints defined by OctoPrint itself and installed plugins.
Your plugin might need this to further restrict access to API methods.
Important
Implementing this hook will make your plugin require a restart of OctoPrint for enabling/disabling it fully.
octoprint.server.http.access_validator
- access_validator_hook(request, *args, **kwargs)
Added in version 1.3.10.
Allows adding additional access validators to the default tornado routers.
Your plugin might need to this to restrict access to downloads and webcam snapshots further.
Important
Implementing this hook will make your plugin require a restart of OctoPrint for enabling/disabling it fully.
octoprint.server.http.bodysize
- server_bodysize_hook(current_max_body_sizes, *args, **kwargs)
Added in version 1.2.0.
Allows extending the list of custom maximum body sizes on the web server per path and HTTP method with custom entries from plugins.
Your plugin might need this if you want to allow uploading files larger than 100KB (the default maximum upload size for anything but the
/api/files
endpoint).current_max_body_sizes
will be a (read-only) list of the currently configured maximum body sizes, in case you want to check from your plugin if you need to even add a new entry.The hook must return a list of 3-tuples (the list’s length can be 0). Each 3-tuple should have the HTTP method against which to match as first, a regular expression for the path to match against and the maximum body size as an integer as the third entry.
The path of the route will be prefixed by OctoPrint with
/plugin/<plugin identifier>/
(if the path already begins with a/
that will be stripped first).Important
Implementing this hook will make your plugin require a restart of OctoPrint for enabling/disabling it fully.
Example
The following plugin example sets the maximum body size for
POST
requests against four custom URLs to 100, 200, 500 and 1024KB. To test its functionality try uploading files larger or smaller than an endpoint’s configured maximum size (as multipart request with the file upload residing in request parameterfile
) and observe the behaviour.# coding=utf-8 import octoprint.plugin import flask class BodySizePlugin(octoprint.plugin.BlueprintPlugin, octoprint.plugin.SettingsPlugin): def __init__(self): self._sizes = (100, 200, 500, 1024) @octoprint.plugin.BlueprintPlugin.route("/upload/<int:size>", methods=["POST"]) def api_endpoint(self, size): if not size in self._sizes: return flask.make_response(404) input_name = "file" keys = ("name", "size", "content_type", "path") result = dict( found_file=False, ) for key in keys: param = input_name + "." + key if param in flask.request.values: result["found_file"] = True result[key] = flask.request.values[param] return flask.jsonify(result) def bodysize_hook(self, current_max_body_sizes, *args, **kwargs): return [("POST", r"/upload/%i" % size, size * 1024) for size in self._sizes] __plugin_name__ = "Increase upload size" __plugin_description__ = "Increases the body size on some custom API endpoints" __plugin_pythoncompat__ = ">=2.7,<4" def __plugin_load__(): global __plugin_implementation__ global __plugin_hooks__ __plugin_implementation__ = BodySizePlugin() __plugin_hooks__ = { "octoprint.server.http.bodysize": __plugin_implementation__.bodysize_hook }
octoprint.server.http.routes
- server_route_hook(server_routes, *args, **kwargs)
Added in version 1.2.0.
Allows extending the list of routes registered on the web server.
This is interesting for plugins which want to provide their own download URLs which will then be delivered statically following the same path structure as regular downloads.
server_routes
will be a (read-only) list of the currently defined server routes, in case you want to check from your plugin against that.The hook must return a list of 3-tuples (the list’s length can be 0). Each 3-tuple should have the path of the route (a string defining its regular expression) as the first, the RequestHandler class to use for the route as the second and a dictionary with keywords parameters for the defined request handler as the third entry.
The path of the route will be prefixed by OctoPrint with
/plugin/<plugin identifier>/
(if the path already begins with a/
that will be stripped first).Note
Static routes provided through this hook take precedence over routes defined through blueprints.
If your plugin also implements the
BlueprintPlugin
mixin and has defined a route for a view on that which matches one of the paths provided via itsoctoprint.server.http.routes
hook handler, the view of the blueprint will thus not be reachable since processing of the request will directly be handed over to your defined handler class.Important
If you want your route to support CORS if it’s enabled in OctoPrint, your RequestHandler needs to implement the
CorsSupportMixin
for this to work. Note that all ofLargeResponseHandler
,UrlProxyHandler
,StaticDataHandler
andDeprecatedEndpointHandler
already implement this mixin.Important
Implementing this hook will make your plugin require a restart of OctoPrint for enabling/disabling it fully.
Example
The following example registers two new routes
/plugin/add_tornado_route/download
and/plugin/add_tornado_route/forward
in the webserver which roughly replicate the functionality of/downloads/files/local
and/downloads/camera/current
.# coding=utf-8 import octoprint.plugin class TornadoRoutePlugin(octoprint.plugin.SettingsPlugin): def route_hook(self, server_routes, *args, **kwargs): from octoprint.server.util.tornado import LargeResponseHandler, UrlProxyHandler, path_validation_factory from octoprint.util import is_hidden_path return [ (r"/download/(.*)", LargeResponseHandler, dict(path=self._settings.global_get_basefolder("uploads"), as_attachment=True, path_validation=path_validation_factory(lambda path: not is_hidden_path(path), status_code=404))), (r"forward", UrlProxyHandler, dict(url=self._settings.global_get(["webcam", "snapshot"]), as_attachment=True)) ] __plugin_name__ = "Add Tornado Route" __plugin_description__ = "Adds two tornado routes to demonstrate hook usage" __plugin_pythoncompat__ = ">=2.7,<4" def __plugin_load__(): global __plugin_implementation__ global __plugin_hooks__ __plugin_implementation__ = TornadoRoutePlugin() __plugin_hooks__ = { "octoprint.server.http.routes": __plugin_implementation__.route_hook }
See also
LargeResponseHandler
Customized tornado.web.StaticFileHandler that allows delivery of the requested resource as attachment and access validation through an optional callback.
UrlForwardHandler
tornado.web.RequestHandler that proxies requests to a preconfigured URL and returns the response.
octoprint.server.sockjs.authed
- socket_authed_hook(socket, user, *args, **kwargs):
Added in version 1.3.10.
Allows plugins to be notified that a user got authenticated or deauthenticated on the socket (e.g. due to logout).
octoprint.server.sockjs.register
- socket_registration_hook(socket, user, *args, **kwargs):
Added in version 1.3.10.
Allows plugins to prevent a new push socket client to be registered to the system.
Handlers should return either
True
orFalse
.True
signals to proceed with normal registration.False
signals to not register the client.
octoprint.server.sockjs.emit
- socket_emit_hook(socket, user, message, payload, *args, **kwargs):
Added in version 1.3.10.
Allows plugins to prevent any messages to be emitted on an existing push connection.
Handlers should return either
True
to allow the message to be emitted, orFalse
to prevent it.- Parameters:
- Returns:
whether to proceed with sending the message (
True
) or not (False
)- Return type:
boolean
octoprint.system.additional_commands
- additional_commands_hook(*args, **kwargs)
Added in version 1.7.0.
Allows adding additional system commands into the system menu. Handlers must return a list of system command definitions, each definition matching the following data structure:
Name
Multiplicity
Type
Description
name
1
String
The name to display in the menu.
action
1
String
An identifier for the action, must only consist of lower case a-z, numbers,
-
and_
([a-z0-9-_]
).command
1
String
The system command to execute.
confirm
0..1
String
An optional message to show as a confirmation dialog before executing the command.
async
0..1
bool
If
True
, the command will be run asynchronously and the API call will return immediately after enqueuing it for execution.ignore
0..1
bool
If
True
, OctoPrint will ignore the result of the command’s (andbefore
’s, if set) execution and return a successful result regardless. Defaults toFalse
.debug
0..1
bool
If
True
, the command will generate debug output in the log including the command line that’s run. Use with care. Defaults toFalse
before
0..1
callable
Optional callable to execute before the actual
command
is run. Ifignore
is false and this fails in any way, the command will not run and an error returned.def get_additional_commands(*args, **kwargs): return [ { "name": "Just a test", "action": "test", "command": "logger This is just a test of an OctoPrint system command from a plugin", "before": lambda: print("Hello World!") } ] __plugin_hooks__ = { "octoprint.system.additional_commands": get_additional_commands }
- Returns:
a list of command specifications
- Return type:
octoprint.systeminfo.additional_bundle_files
- additional_bundle_files_hook(*args, **kwargs)
Added in version 1.7.0.
Allows bundled plugins to extend the list of files to include in the systeminfo bundle. Note that this hook will ignore third party plugins. Handlers must return a dictionary mapping file names in the bundle to either local log paths on disk or a
callable
that will be called to generate the file’s content inside the bundle.Example
Add a plugin’s
console
log file to the systeminfo bundle:def get_additional_bundle_files(*args, **kwargs): console_log = self._settings.get_plugin_logfile_path(postfix="console") return {os.path.basename(console_log): console_log} __plugin_hooks__ = { "octoprint.systeminfo.additional_bundle_files": get_additional_bundle_files }
- Returns:
a dictionary mapping bundle file names to bundle file content
- Return type:
octoprint.timelapse.extensions
- timelapse_extension_hook(*args, **kwargs)
Added in version 1.3.10.
Allows extending the set of supported file extensions for timelapse files. Handlers must return a list of additional file extensions.
Example
Allow the management of timelapse GIFs with extension
gif
.def get_timelapse_extensions(*args, **kwargs): return ["gif"] __plugin_hooks__ = { "octoprint.timelapse.extensions": get_timelapse_extensions }
- Returns:
a list of additional file extensions
- Return type:
octoprint.ui.web.templatetypes
- templatetype_hook(template_sorting, template_rules, *args, **kwargs)
Added in version 1.2.0.
Allows extending the set of supported template types in the web interface. This is interesting for plugins which want to offer other plugins to hook into their own offered UIs. Handlers must return a list of additional template specifications in form of 3-tuples.
The first entry of the tuple must be the name of the template type and will be automatically prefixed with
plugin_<identifier>_
.The second entry must be a sorting specification that defines how OctoPrint should sort multiple templates injected through plugins of this template type. The sorting specification should be a dict with the following possible entries:
Key
Description
key
The sorting key within the template config to use for sorting the list of template injections. This may be
None
in which case no sorting will be taking place. Defaults toname
.add
Usually irrelevant for custom template types, only listed for the sake of completeness. The method of adding the sorted list of template injections from plugins to the template injections from the core. May be
append
to append the list,prepend
to prepend the list, orcustom_append
orcustom_prepend
to append respectively prepend but going so after preprocessing the entries and order data with custom functions (e.g. to inject additional entries such as the “Plugins” section header in the settings dialog). For custom template types this defaults toappend
.custom_add_entries
Usually irrelevant for custom template types, only listed for the sake of completeness. Custom preprocessor for the entries provided through plugins, before they are added to the general template entries context variable for the current template type.
custom_add_order
Usually irrelevant for custom template types, only listed for the sake of completeness. Custom preprocessor for the template order provided through plugins, before they are added to the general template order context variable for the current template type.
The third entry must be a rule specification in form of a dict which tells OctoPrint how to process the template configuration entries provided by
get_template_configs()
by providing transformation functions of various kinds:Key
Description
div
Function that returns the id of the container for template content if not explicitly provided by the template config, input parameter is the name of the plugin providing the currently processed template config. If not provided this defaults to a lambda function of the form
lambda x: "<plugin identifier>_<template type>_plugin_" + x
withplugin identifier
being the identifier of the plugin providing the additional template type.template
Function that returns the default template filename for a template type to attempt to include in case no template name is explicitly provided by the template config, input parameter is the name of the plugin providing the current processed template config. If not provided this defaults to a lambda function of the form
lambda x: x + "_plugin_<plugin identifier>_<template type>.jinja2"
withplugin identifier
being the identifier of the plugin providing the additional template type.to_entry
Function to transform a template config to the data structure stored in the Jinja context for the injected template. If not provided this defaults to a lambda function returning a 2-tuple of the
name
value of the template config and the template config itself (lambda data: (data["name"], data)
)mandatory
A list of keys that must be included in the template config for this template type. Template configs not containing all of the keys in this list will be ignored. Defaults to an empty list.
OctoPrint will provide all template configs for custom template types in the Jinja rendering context in the same way as it provides the template configs for core template types, through the
templates
context variable which is a dict mapping from the template type name (plugin_<plugin identifier>_<template type>
for custom ones) to a dict withentries
andorder
values, the first containing a dict of all registered template configs, the latter an ordered list of all registered template keys of the type in the order they should be rendered. Plugins should iterate over theorder
list and then render each entry utilizing the template entry as provided for the key in theentries
dict (note that this entry will have the format specified through theto_entry
section in the template rule).Example
The example consists of two plugins, one providing a custom template type and the other consuming it.
First the provider:
# coding=utf-8 import octoprint.plugin class CustomTemplateTypeProvider(octoprint.plugin.TemplatePlugin): def add_templatetype(self, current_order, current_rules, *args, **kwargs): return [ ("awesometemplate", dict(), dict(template=lambda x: x + "_awesometemplate.jinja2")) ] __plugin_name__ = "Custom Template Provider" __plugin_pythoncompat__ = ">=2.7,<4" def __plugin_load__(): global __plugin_implementation__ __plugin_implementation__ = CustomTemplateTypeProvider() global __plugin_hooks__ __plugin_hooks__ = { "octoprint.ui.web.templatetypes": __plugin_implementation__.add_templatetype }
<h3>{{ _('Awesome Template Injections') }}</h3> {% for key in templates.plugin_custom_template_provider_awesometemplate.order %} {% set heading, config = templates.plugin_custom_template_provider_awesometemplate.entries[key] %} <div id="{{ config._div }}"> <h4>{{ heading }}</h4> {% include config.template ignore missing %} </div> {% endfor %}
Then the consumer:
# coding=utf-8 import octoprint.plugin class CustomTemplateTypeConsumer(octoprint.plugin.TemplatePlugin): def get_template_configs(self): if "custom_template_provider" not in self._plugin_manager.enabled_plugins: # if our custom template provider is not registered, we'll act as a regular settings plugin return [ dict(type="settings", template="custom_template_consumer_awesometemplate.jinja2") ] else: # else we'll inject ourselves as an awesometemplate type instead - since we named our jinja2 file # accordingly we don't have to explicitly define that here, it will be picked up automatically return [] __plugin_name__ = "Custom Template Consumer" __plugin_pythoncompat__ = ">=2.7,<4" __plugin_implementation__ = CustomTemplateTypeConsumer()
"Hello World!" from an injected awesome template.
octoprint.theming.<dialog>
This actually describes two hooks:
octoprint.theming.login
octoprint.theming.recovery
- ui_theming_hook(*args, **kwargs)
Added in version 1.5.0.
Support theming of the login or recovery dialog, just in case the core UI is themed as well. Use to return a list of additional CSS file URLs to inject into the dialog HTML.
Example usage by a plugin:
def loginui_theming(): from flask import url_for return [url_for("plugin.myplugin.static", filename="css/loginui_theme.css")] __plugin_hooks__ = { "octoprint.theming.login": loginui_theming }
Only a list of ready-made URLs to CSS files is supported, neither LESS nor JS. Best use
url_for
like in the example above to be prepared for any configured prefix URLs.- Returns:
A list of additional CSS URLs to inject into the login or recovery dialog.
- Return type:
A list of strings.
octoprint.timelapse.capture.pre
octoprint.timelapse.capture.post
- capture_post_hook(filename, success)
Added in version 1.4.0.
Perform specific actions after capturing a timelapse frame.
filename
will be the path of the frame that should have been saved.success
indicates whether the capture was successful or not.- Parameters:
filename (str) – The path of the frame that should have been saved.
success (boolean) – Indicates whether the capture was successful or not.
- Returns:
None
- Return type:
None