Merge branch 'devel' of github.com:foosel/OctoPrint into fixControl

This commit is contained in:
Marc 2015-04-27 19:39:37 +02:00
commit d4a96c53c3
12 changed files with 213 additions and 96 deletions

View file

@ -75,6 +75,12 @@ Issue a connection command
disconnect
Instructs OctoPrint to disconnect from the printer.
fake_ack
Fakes an acknowledgement message for OctoPrint in case one got lost on the serial line and the communication
with the printer since stalled. This should only be used in "emergencies" (e.g. to save prints), the reason
for the lost acknowledgement should always be properly investigated and removed instead of depending on this
"symptom solver".
**Example Connect Request**
.. sourcecode:: http
@ -114,7 +120,24 @@ Issue a connection command
HTTP/1.1 204 No Content
:json string command: The command to issue, either ``connect`` or ``disconnect``
**Example FakeAck Request**
.. sourcecode:: http
POST /api/connection HTTP/1.1
Host: example.com
Content-Type: application/json
X-Api-Key: abcdef...
{
"command": "fake_ack"
}
.. sourcecode:: http
HTTP/1.1 204 No Content
:json string command: The command to issue, either ``connect``, ``disconnect`` or ``fake_ack``.
:json string port: ``connect`` command: The port to connect to. If left out either the existing ``portPreference``
will be used, or if that is not available OctoPrint will attempt auto detection. Must be part
of the available ports.

View file

@ -373,10 +373,18 @@ Use the following settings to enable or disable OctoPrint features:
# during connect.
waitForStartOnConnect: false
# Specifies whether OctoPrint should send linenumber + checksum with every command. Needed for
# Specifies whether OctoPrint should send linenumber + checksum with every printer command. Needed for
# successful communication with Repetier firmware
alwaysSendChecksum: false
# Specifies whether OctoPrint should also send linenumber + checksum with commands that are *not*
# detected as valid GCODE (as in, they do not match the regular expression "^\s*([GM]\d+|T)").
sendChecksumWithUnknownCommands: false
# Specifies whether OctoPrint should also use up acknowledgments ("ok") for commands that are *not*
# detected as valid GCODE (as in, they do not match the regular expression "^\s*([GM]\d+|T)").
unknownCommandsNeedAck: false
# Whether to ignore the first ok after a resend response. Needed for successful communication with
# Repetier firmware
swallowOkAfterResend: false

View file

@ -542,10 +542,11 @@ class PluginManager(object):
url=module_pkginfo.home_page,
license=module_pkginfo.license
))
package_name = module_pkginfo.name
plugin = self._import_plugin_from_module(key, **kwargs)
if plugin:
plugin.origin = ("entry_point", group, module_name)
plugin.origin = ("entry_point", group, module_name, package_name)
plugin.enabled = False
result[key] = plugin

View file

@ -181,7 +181,7 @@ class TemplatePlugin(OctoPrintPlugin, ReloadNeedingPlugin):
The right part of the navigation bar located at the top of the UI can be enriched with additional links. Note that
with the current implementation, plugins will always be located *to the left* of the existing links.
The included template must be called ``<pluginname>_navbar.jinja2`` (e.g. ``myplugin_navbar.jinja2``) unless
The included template must be called ``<plugin identifier>_navbar.jinja2`` (e.g. ``myplugin_navbar.jinja2``) unless
overridden by the configuration supplied through :func:`get_template_configs`.
The template will be already wrapped into the necessary structure, plugins just need to supply the pure content. The
@ -192,7 +192,7 @@ class TemplatePlugin(OctoPrintPlugin, ReloadNeedingPlugin):
The left side bar containing Connection, State and Files sections can be enriched with additional sections. Note
that with the current implementations, plugins will always be located *beneath* the existing sections.
The included template must be called ``<pluginname>_sidebar.jinja2`` (e.g. ``myplugin_sidebar.jinja2``) unless
The included template must be called ``<plugin identifier>_sidebar.jinja2`` (e.g. ``myplugin_sidebar.jinja2``) unless
overridden by the configuration supplied through :func:`get_template_configs`.
The template will be already wrapped into the necessary structure, plugins just need to supply the pure content. The
@ -204,7 +204,7 @@ class TemplatePlugin(OctoPrintPlugin, ReloadNeedingPlugin):
plugins. Note that with the current implementation, plugins will always be located *to the right* of the existing
tabs.
The included template must be called ``<pluginname>_tab.jinja2`` (e.g. ``myplugin_tab.jinja2``) unless
The included template must be called ``<plugin identifier>_tab.jinja2`` (e.g. ``myplugin_tab.jinja2``) unless
overridden by the configuration supplied through :func:`get_template_configs`.
The template will be already wrapped into the necessary structure, plugins just need to supply the pure content. The
@ -216,7 +216,7 @@ class TemplatePlugin(OctoPrintPlugin, ReloadNeedingPlugin):
will always be listed beneath the "Plugins" header in the settings link list, ordered alphabetically after
their displayed name.
The included template must be called ``<pluginname>_settings.jinja2`` (e.g. ``myplugin_settings.jinja2``) unless
The included template must be called ``<plugin identifier>_settings.jinja2`` (e.g. ``myplugin_settings.jinja2``) unless
overridden by the configuration supplied through :func:`get_template_configs`.
The template will be already wrapped into the necessary structure, plugins just need to supply the pure content. The
@ -272,11 +272,11 @@ class TemplatePlugin(OctoPrintPlugin, ReloadNeedingPlugin):
* - template
- Name of the template to inject, default value depends on the ``type``:
* ``navbar``: ``<pluginname>_navbar.jinja2``
* ``sidebar``: ``<pluginname>_sidebar.jinja2``
* ``tab``: ``<pluginname>_tab.jinja2``
* ``settings``: ``<pluginname>_settings.jinja2``
* ``generic``: ``<pluginname>.jinja2``
* ``navbar``: ``<plugin identifier>_navbar.jinja2``
* ``sidebar``: ``<plugin identifier>_sidebar.jinja2``
* ``tab``: ``<plugin identifier>_tab.jinja2``
* ``settings``: ``<plugin identifier>_settings.jinja2``
* ``generic``: ``<plugin identifier>.jinja2``
* - suffix
- Suffix to attach to the component identifier and the div identifier of the injected template. Will be
@ -311,7 +311,7 @@ class TemplatePlugin(OctoPrintPlugin, ReloadNeedingPlugin):
(and the divs will be ``tab_plugin_myplugin_1st`` and ``tab_plugin_myplugin_2nd``).
* - div
- Id for the div containing the component. If not provided, defaults to ``<type>_plugin_<pluginname>`` plus
- Id for the div containing the component. If not provided, defaults to ``<type>_plugin_<plugin identifier>`` plus
the ``suffix`` if provided or required.
* - replaces
- Id of the component this one replaces, might be either one of the core components or a component
@ -370,7 +370,7 @@ class TemplatePlugin(OctoPrintPlugin, ReloadNeedingPlugin):
.. note::
As already outlined above, each template type has a default template name (i.e. the default navbar template
of a plugin is called ``<pluginname>_navbar.jinja2``), which may be overridden using the template configuration.
of a plugin is called ``<plugin identifier>_navbar.jinja2``), which may be overridden using the template configuration.
If a plugin needs to include more than one template of a given type, it needs to provide an entry for each of
those, since the implicit default template will only be included automatically if no other templates of that
type are defined.

View file

@ -149,6 +149,11 @@ class VirtualPrinter():
self._sendOk()
continue
if data.strip() == "version":
from octoprint._version import get_versions
self.outgoing.put("OctoPrint VirtualPrinter v" + get_versions()["version"])
continue
if len(data.strip()) > 0 and self._okBeforeCommandOutput:
self._sendOk()
@ -394,7 +399,6 @@ class VirtualPrinter():
pass
if tool >= settings().getInt(["devel", "virtualPrinter", "numExtruders"]):
self._sendOk()
return
try:
@ -406,7 +410,6 @@ class VirtualPrinter():
self._waitForHeatup("tool%d" % tool)
if settings().getBoolean(["devel", "virtualPrinter", "repetierStyleTargetTemperature"]):
self.outgoing.put("TargetExtr%d:%d" % (tool, self.targetTemp[tool]))
self._sendOk()
def _parseBedCommand(self, line):
try:
@ -418,7 +421,6 @@ class VirtualPrinter():
self._waitForHeatup("bed")
if settings().getBoolean(["devel", "virtualPrinter", "repetierStyleTargetTemperature"]):
self.outgoing.put("TargetBed:%d" % self.bedTargetTemp)
self._sendOk()
def _performMove(self, line):
matchX = re.search("X([0-9.]+)", line)

View file

@ -97,6 +97,14 @@ class PrinterInterface(object):
"""
raise NotImplementedError()
def fake_ack(self):
"""
Fakes an acknowledgement for the communication layer. If the communication between OctoPrint and the printer
gets stuck due to lost "ok" responses from the server due to communication issues, this can be used to get
things going again.
"""
raise NotImplementedError()
def commands(self, commands):
"""
Sends the provided ``commands`` to the printer.

View file

@ -212,6 +212,12 @@ class Printer(PrinterInterface, comm.MachineComPrintCallback):
return self._comm.getTransport()
getTransport = util.deprecated("getTransport has been renamed to get_transport", since="1.2.0-dev-590", includedoc="Replaced by :func:`get_transport`")
def fake_ack(self):
if self._comm is None:
return
self._comm.fakeOk()
def commands(self, commands):
"""
Sends one or more gcode commands to the printer.

View file

@ -33,7 +33,8 @@ def connectionState():
def connectionCommand():
valid_commands = {
"connect": [],
"disconnect": []
"disconnect": [],
"fake_ack": []
}
command, data, response = get_json_command_from_request(request, valid_commands)
@ -68,6 +69,8 @@ def connectionCommand():
printer.connect(port=port, baudrate=baudrate, profile=printerProfile)
elif command == "disconnect":
printer.disconnect()
elif command == "fake_ack":
printer.fake_ack()
return NO_CONTENT

View file

@ -132,6 +132,8 @@ default_settings = {
"temperatureGraph": True,
"waitForStartOnConnect": False,
"alwaysSendChecksum": False,
"sendChecksumWithUnknownCommands": False,
"unknownCommandsNeedAck": False,
"sdSupport": True,
"sdAlwaysAvailable": False,
"swallowOkAfterResend": True,

View file

@ -173,6 +173,16 @@ $(function() {
}
};
self.fakeAck = function() {
$.ajax({
url: API_BASEURL + "connection",
type: "POST",
dataType: "json",
contentType: "application/json; charset=UTF-8",
data: JSON.stringify({"command": "fake_ack"})
});
};
self.handleKeyDown = function(event) {
var keyCode = event.keyCode;

View file

@ -20,3 +20,12 @@
<small class="muted">{{ _('Hint: Use the arrow up/down keys to recall commands sent previously') }}</small>
</div>
</div>
<div>
<div><small><a href="#" class="muted" onclick="$(this).children().toggleClass('icon-caret-right icon-caret-down').parent().parent().parent().next().slideToggle('fast')"><i class="icon-caret-right"></i> {{ _('Advanced options') }}</a></small></div>
<div class="hide">
<button class="btn btn-block" type="button" data-bind="click: fakeAck, enable: isOperational() && loginState.isUser()">{{ _("Fake Acknowledgement") }}</button>
<small class="muted">{{ _("If acknowledgements (\"ok\"s) sent by the firmware get lost due to issues with the serial communication to your printer, OctoPrint's communication with it can become stuck. If that happens, this can help. Please be advised that such occurences hint at general communication issues with your printer which will probably negatively influence your printing results and which you should therefore try to resolve!") }}</small>
</div>
</div>

View file

@ -155,6 +155,8 @@ class MachineCom(object):
self._timeout = None
self._alwaysSendChecksum = settings().getBoolean(["feature", "alwaysSendChecksum"])
self._sendChecksumWithUnknownCommands = settings().getBoolean(["feature", "sendChecksumWithUnknownCommands"])
self._unknownCommandsNeedAck = settings().getBoolean(["feature", "unknownCommandsNeedAck"])
self._currentLine = 1
self._resendDelta = None
self._lastLines = deque([], 50)
@ -410,6 +412,9 @@ class MachineCom(object):
def setTemperatureOffset(self, offsets):
self._tempOffsets.update(offsets)
def fakeOk(self):
self._clear_to_send.set()
def sendCommand(self, cmd, cmd_type=None, processed=False):
cmd = cmd.encode('ascii', 'replace')
if not processed:
@ -420,7 +425,7 @@ class MachineCom(object):
if self.isPrinting() and not self.isSdFileSelected():
self._commandQueue.put((cmd, cmd_type))
elif self.isOperational():
self._sendCommand((cmd, cmd_type))
self._sendCommand(cmd, cmd_type=cmd_type)
def sendGcodeScript(self, scriptName, replacements=None):
context = dict()
@ -520,7 +525,7 @@ class MachineCom(object):
self.sendCommand(line)
# now make sure we actually do something, up until now we only filled up the queue
self._sendFromQueue(sendChecksum=True)
self._sendFromQueue()
except:
self._logger.exception("Error while trying to start printing")
self._errorValue = get_exception_string()
@ -621,7 +626,7 @@ class MachineCom(object):
self.sendCommand(line)
# now make sure we actually do something, up until now we only filled up the queue
self._sendFromQueue(sendChecksum=True)
self._sendFromQueue()
eventManager().fire(Events.PRINT_RESUMED, payload)
elif pause and self.isPrinting():
@ -786,12 +791,12 @@ class MachineCom(object):
#Start monitoring the serial port.
self._timeout = get_new_timeout("communication")
startSeen = not settings().getBoolean(["feature", "waitForStartOnConnect"])
startSeen = False
supportRepetierTargetTemp = settings().getBoolean(["feature", "repetierTargetTemp"])
# enqueue an M105 first thing
self._sendCommand("M105")
if startSeen:
if not settings().getBoolean(["feature", "waitForStartOnConnect"]):
self._sendCommand("M110")
self._clear_to_send.set()
while self._monitoring_active:
@ -1013,7 +1018,7 @@ class MachineCom(object):
self._baudrateDetectRetry -= 1
self._serial.write('\n')
self._log("Baudrate test retry: %d" % (self._baudrateDetectRetry))
self._sendCommand("M105")
self._sendCommand("M110")
self._clear_to_send.set()
else:
baudrate = self._baudrateDetectList.pop(0)
@ -1024,11 +1029,11 @@ class MachineCom(object):
self._baudrateDetectRetry = 5
self._timeout = get_new_timeout("communication")
self._serial.write('\n')
self._sendCommand("M105")
self._sendCommand("M110")
self._clear_to_send.set()
except:
self._log("Unexpected error while setting baudrate: %d %s" % (baudrate, get_exception_string()))
elif 'start' in line or ('ok' in line and 'T:' in line):
elif 'start' in line or 'ok' in line:
self._onConnected()
self._clear_to_send.set()
@ -1036,8 +1041,9 @@ class MachineCom(object):
elif self._state == self.STATE_CONNECTING:
if "start" in line and not startSeen:
startSeen = True
self._sendCommand("M110")
self._clear_to_send.set()
elif "ok" in line and startSeen:
elif "ok" in line:
self._onConnected()
elif time.time() > self._timeout:
self.close()
@ -1074,7 +1080,7 @@ class MachineCom(object):
self._resendNextCommand()
else:
if self._sendFromQueue(sendChecksum=True):
if self._sendFromQueue():
pass
elif not self.isSdPrinting():
self._sendNext()
@ -1164,9 +1170,18 @@ class MachineCom(object):
eventManager().fire(Events.CONNECTED, payload)
self.sendGcodeScript("afterPrinterConnected", replacements=dict(event=payload))
def _sendFromQueue(self, sendChecksum=False):
def _sendFromQueue(self):
if not self._commandQueue.empty() and not self.isStreaming():
self._sendCommand(self._commandQueue.get(), sendChecksum)
entry = self._commandQueue.get()
if isinstance(entry, tuple):
if not len(entry) == 2:
return False
cmd, cmd_type = entry
else:
cmd = entry
cmd_type = None
self._sendCommand(cmd, cmd_type=cmd_type)
return True
else:
return False
@ -1325,7 +1340,7 @@ class MachineCom(object):
with self._sendNextLock:
line = self._getNext()
if line is not None:
self._sendCommand(line, True)
self._sendCommand(line)
self._callback.on_comm_progress()
def _handleResendRequest(self, line):
@ -1385,64 +1400,53 @@ class MachineCom(object):
self._lastResendNumber = None
self._currentResendCount = 0
def _sendCommand(self, cmd_tuple, sendChecksum=False):
if isinstance(cmd_tuple, tuple):
cmd, cmd_type = cmd_tuple
else:
cmd = cmd_tuple
cmd_type = None
def _sendCommand(self, cmd, cmd_type=None):
# Make sure we are only handling one sending job at a time
with self._sendingLock:
if self._serial is None:
return
if not self.isStreaming():
for hook in self._gcode_hooks:
hook_cmd = self._gcode_hooks[hook](self, cmd, cmd_type=cmd_type, send_checksum=sendChecksum)
for name, hook in self._gcode_hooks.items():
hook_cmd = hook(self, cmd, cmd_type=cmd_type)
if hook_cmd is None:
cmd = None
# hook might have returned (cmd, sendChecksum) or (cmd, sendChecksum, cmd_type), split that
if isinstance(hook_cmd, tuple):
elif 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
# legacy hook handler, ignore returned send_checksum
hook_cmd, cmd_type, _ = hook_cmd
# 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):
if isinstance(hook_cmd, basestring):
cmd = hook_cmd
else:
self._logger.warn("Hook {name} returned unintelligible result, ignoring it: {hook_cmd!r}".format(**locals()))
continue
if cmd is None:
break
# try to parse the cmd and extract the gcode type
gcode = self._regex_command.search(cmd)
if gcode:
gcode = gcode.group(1)
if cmd is not None:
gcode = self._regex_command.search(cmd)
if gcode:
gcode = gcode.group(1)
# fire events if necessary
if gcode in gcodeToEvent:
eventManager().fire(gcodeToEvent[gcode])
# 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)
# send it through the specific handler if it exists
cmd = self._process_command_phase("queue", cmd, gcode=gcode)
if cmd is not None:
self._doSend(cmd, send_checksum=sendChecksum, cmd_type=cmd_type)
def _doSend(self, cmd, send_checksum=False, cmd_type=None):
if send_checksum or self._alwaysSendChecksum:
lineNumber = self._currentLine
self._addToLastLines(cmd)
self._currentLine += 1
self._enqueue_for_sending(cmd, linenumber=lineNumber, command_type=cmd_type)
else:
self._enqueue_for_sending(cmd, command_type=cmd_type)
self._enqueue_for_sending(cmd, command_type=cmd_type)
def gcode_command_for_cmd(self, cmd):
"""
@ -1497,20 +1501,62 @@ class MachineCom(object):
if not self._send_queue_active:
break
# fetch command and optional linenumber from queue, send it
# fetch command and optional linenumber from queue
command, linenumber, _ = entry
# some firmwares (e.g. Smoothie) might support additional in-band communication that will not
# stick to the acknowledgement behaviour of GCODE, so we check here if we have a GCODE command
# at hand here and only clear our clear_to_send flag later if that's the case
gcode_match = self._regex_command.search(command)
is_gcode = gcode_match is not None
if is_gcode:
# trigger "sending" phase
command = self._process_command_phase("sending", command, gcode=gcode_match.group(1))
if linenumber is not None:
# line number predetermined, use that
self._doSendWithChecksum(command, linenumber)
else:
self._doSendWithoutChecksum(command)
if (is_gcode or self._sendChecksumWithUnknownCommands) and (self.isPrinting() or self._alwaysSendChecksum):
linenumber = self._currentLine
self._addToLastLines(command)
self._currentLine += 1
self._doSendWithChecksum(command, linenumber)
else:
self._doSendWithoutChecksum(command)
# we just used up one ok, clear it, wait for the next clear
self._clear_to_send.clear()
use_up_clear = not self._unknownCommandsNeedAck
if is_gcode:
# trigger "sent" phase and use up one "ok"
self._process_command_phase("sent", command, gcode=gcode_match.group(1))
use_up_clear = True
# if we need to use up a clear, do that now
if use_up_clear:
self._clear_to_send.clear()
# wait for the next clear
self._clear_to_send.wait()
except:
self._logger.exception("Caught an exception in the send loop")
self._log("Closing down send loop")
def _process_command_phase(self, phase, command, gcode=None):
if gcode is None:
gcode_match = self._regex_command.search(command)
if gcode_match is None:
return command
gcode = gcode.group(1)
# send it through the specific handler if it exists
gcodeHandler = "_gcode_" + gcode + "_" + phase
if hasattr(self, gcodeHandler):
command = getattr(self, gcodeHandler)(command)
return command
##~~ actual sending via serial
def _doSendWithChecksum(self, cmd, lineNumber):
@ -1538,13 +1584,13 @@ class MachineCom(object):
##~~ command handlers
def _gcode_T(self, cmd):
def _gcode_T_sent(self, cmd):
toolMatch = self._regex_paramTInt.search(cmd)
if toolMatch:
self._currentTool = int(toolMatch.group(1))
return cmd
def _gcode_G0(self, cmd):
def _gcode_G0_sent(self, cmd):
if 'Z' in cmd:
match = self._regex_paramZFloat.search(cmd)
if match:
@ -1556,14 +1602,14 @@ class MachineCom(object):
except ValueError:
pass
return cmd
_gcode_G1 = _gcode_G0
_gcode_G0_sent = _gcode_G0_sent
def _gcode_M0(self, cmd):
def _gcode_M0_queue(self, cmd):
self.setPause(True)
return "M105" # Don't send the M0 or M1 to the machine, as M0 and M1 are handled as an LCD menu pause.
_gcode_M1 = _gcode_M0
return None # Don't send the M0 or M1 to the machine, as M0 and M1 are handled as an LCD menu pause.
_gcode_M1_queued = _gcode_M0_queue
def _gcode_M104(self, cmd):
def _gcode_M104_sent(self, cmd):
toolNum = self._currentTool
toolMatch = self._regex_paramTInt.search(cmd)
if toolMatch:
@ -1581,7 +1627,7 @@ class MachineCom(object):
pass
return cmd
def _gcode_M140(self, cmd):
def _gcode_M140_sent(self, cmd):
match = self._regex_paramSInt.search(cmd)
if match:
try:
@ -1595,19 +1641,19 @@ class MachineCom(object):
pass
return cmd
def _gcode_M109(self, cmd):
def _gcode_M109_sent(self, cmd):
self._heatupWaitStartTime = time.time()
self._blocking_command = True
self._heating = True
return self._gcode_M104(cmd)
return self._gcode_M104_sent(cmd)
def _gcode_M190(self, cmd):
def _gcode_M190_sent(self, cmd):
self._heatupWaitStartTime = time.time()
self._blocking_command = True
self._heating = True
return self._gcode_M140(cmd)
return self._gcode_M140_sent(cmd)
def _gcode_M110(self, cmd):
def _gcode_M110_sending(self, cmd):
newLineNumber = None
match = self._regex_paramNInt.search(cmd)
if match:
@ -1619,20 +1665,19 @@ class MachineCom(object):
newLineNumber = 0
# send M110 command with new line number
self._enqueue_for_sending(cmd, linenumber=newLineNumber)
self._currentLine = newLineNumber + 1
self._currentLine = newLineNumber
# after a reset of the line number we have no way to determine what line exactly the printer now wants
self._lastLines.clear()
self._resendDelta = None
return None
return cmd
def _gcode_M112(self, cmd): # It's an emergency what todo? Canceling the print should be the minimum
def _gcode_M112_queue(self, cmd): # It's an emergency what todo? Canceling the print should be the minimum
self.cancelPrint()
return cmd
def _gcode_G4(self, cmd):
def _gcode_G4_sent(self, cmd):
# we are intending to dwell for a period of time, increase the timeout to match
cmd = cmd.upper()
p_idx = cmd.find('P')
@ -1648,12 +1693,12 @@ class MachineCom(object):
self._blocking_command = True
return cmd
def _gcode_G28(self, cmd):
def _gcode_G28_sending(self, cmd):
self._blocking_command = True
return cmd
_gcode_G29 = _gcode_G28
_gcode_G30 = _gcode_G28
_gcode_G32 = _gcode_G28
_gcode_G29_sending = _gcode_G28_sending
_gcode_G30_sending = _gcode_G28_sending
_gcode_G32_sending = _gcode_G28_sending
### MachineCom callback ################################################################################################