Add (optional) firmware auto detection
If enabled (which it is by default), OctoPrint will now send an M115 to the printer on initial connection in order to try to figure out what kind of firmware it is. For FIRMWARE_NAME values containing "repetier" (case insensitive), all Repetier- specific flags will be set on the comm layer. For FIRMWARE_NAME values containing "reprapfirmware", all RepRapFirmware- specific flags will be set on the comm layer. For now no other handling will be performed.
This commit is contained in:
parent
ec12e52865
commit
07382d3918
6 changed files with 124 additions and 44 deletions
|
|
@ -393,7 +393,7 @@ class Printer(PrinterInterface, comm.MachineComPrintCallback):
|
|||
|
||||
self._printAfterSelect = printAfterSelect
|
||||
self._posAfterSelect = pos
|
||||
self._comm.selectFile("/" + path if sd and not settings().getBoolean(["feature", "sdRelativePath"]) else path, sd)
|
||||
self._comm.selectFile("/" + path if sd else path, sd)
|
||||
self._setProgressData(completion=0)
|
||||
self._setCurrentZ(None)
|
||||
|
||||
|
|
|
|||
|
|
@ -104,7 +104,8 @@ def getSettings():
|
|||
"keyboardControl": s.getBoolean(["feature", "keyboardControl"]),
|
||||
"pollWatched": s.getBoolean(["feature", "pollWatched"]),
|
||||
"ignoreIdenticalResends": s.getBoolean(["feature", "ignoreIdenticalResends"]),
|
||||
"modelSizeDetection": s.getBoolean(["feature", "modelSizeDetection"])
|
||||
"modelSizeDetection": s.getBoolean(["feature", "modelSizeDetection"]),
|
||||
"firmwareDetection": s.getBoolean(["feature", "firmwareDetection"])
|
||||
},
|
||||
"serial": {
|
||||
"port": connectionOptions["portPreference"],
|
||||
|
|
@ -274,6 +275,7 @@ def _saveSettings(data):
|
|||
if "pollWatched" in data["feature"]: s.setBoolean(["feature", "pollWatched"], data["feature"]["pollWatched"])
|
||||
if "ignoreIdenticalResends" in data["feature"]: s.setBoolean(["feature", "ignoreIdenticalResends"], data["feature"]["ignoreIdenticalResends"])
|
||||
if "modelSizeDetection" in data["feature"]: s.setBoolean(["feature", "modelSizeDetection"], data["feature"]["modelSizeDetection"])
|
||||
if "firmwareDetection" in data["feature"]: s.setBoolean(["feature", "firmwareDetection"], data["feature"]["firmwareDetection"])
|
||||
|
||||
if "serial" in data.keys():
|
||||
if "autoconnect" in data["serial"].keys(): s.setBoolean(["serial", "autoconnect"], data["serial"]["autoconnect"])
|
||||
|
|
|
|||
|
|
@ -197,7 +197,8 @@ default_settings = {
|
|||
"ignoreIdenticalResends": False,
|
||||
"identicalResendsCountdown": 7,
|
||||
"supportFAsCommand": False,
|
||||
"modelSizeDetection": True
|
||||
"modelSizeDetection": True,
|
||||
"firmwareDetection": True
|
||||
},
|
||||
"folder": {
|
||||
"uploads": None,
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@ $(function() {
|
|||
self.feature_pollWatched = ko.observable(undefined);
|
||||
self.feature_ignoreIdenticalResends = ko.observable(undefined);
|
||||
self.feature_modelSizeDetection = ko.observable(undefined);
|
||||
self.feature_firmwareDetection = ko.observable(undefined);
|
||||
|
||||
self.serial_port = ko.observable();
|
||||
self.serial_baudrate = ko.observable();
|
||||
|
|
|
|||
|
|
@ -34,53 +34,67 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_sdRelativePath" id="settings-sdRelativePath"> {{ _('Select SD files by relative path') }} <span class="label">{{ _('RepRap Firmware') }}</span>
|
||||
<input type="checkbox" data-bind="checked: feature_firmwareDetection" id="settings-firmwareDetection"> {{ _('Enable automatic firmware detection') }}
|
||||
<span class="help-inline">{% trans %}
|
||||
If enabled, OctoPrint will try to figure out your printer's firmware automatically and adjust a couple of communication parameters based on that.
|
||||
If that doesn't work out, or you want more granular control, uncheck this and the parameters in question will become visible for you to adjust.
|
||||
{% endtrans %}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_sdAlwaysAvailable" id="settings-featureSdAlwaysAvailable"> {{ _('Always assume SD card is present') }} <span class="label">{{ _('Repetier') }}</span>
|
||||
</label>
|
||||
<div data-bind="visible: !feature_firmwareDetection()">
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_sdRelativePath" id="settings-sdRelativePath"> {{ _('Select SD files by relative path') }} <span class="label">{{ _('RepRap Firmware') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_ignoreIdenticalResends" id="settings-ignoreIdenticalResends"> {{ _('Ignore consecutive resend requests for the same line') }} <span class="label">{{ _('Repetier') }}</span>
|
||||
</label>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_sdAlwaysAvailable" id="settings-featureSdAlwaysAvailable"> {{ _('Always assume SD card is present') }} <span class="label">{{ _('Repetier') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_repetierTargetTemp" id="settings-featureRepetierTargetTemp"> {{ _('Support <code>TargetExtr%%n</code>/<code>TargetBed</code> target temperature format') }} <span class="label">{{ _('Repetier') }}</span>
|
||||
</label>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_ignoreIdenticalResends" id="settings-ignoreIdenticalResends"> {{ _('Ignore consecutive resend requests for the same line') }} <span class="label">{{ _('Repetier') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_disableExternalHeatupDetection" id="settings-featureExternalHeatupDetection"> {{ _('Disable detection of external heatups') }} <span class="label">{{ _('Repetier') }}</span>
|
||||
</label>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_repetierTargetTemp" id="settings-featureRepetierTargetTemp"> {{ _('Support <code>TargetExtr%%n</code>/<code>TargetBed</code> target temperature format') }} <span class="label">{{ _('Repetier') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">{{ _('Send a checksum with the command')}}</label>
|
||||
<div class="controls">
|
||||
<label class="radio">
|
||||
<input type="radio" name="sendChecksumGroup" value="print" data-bind="checked: feature_sendChecksum" id="settings-featureSendChecksumPrint"> {{ _('When printing') }}
|
||||
</label>
|
||||
<label class="radio">
|
||||
<input type="radio" name="sendChecksumGroup" value="always" data-bind="checked: feature_sendChecksum" id="settings-featureSendChecksumAlways"> {{ _('Always') }} <span class="label">{{ _('Repetier') }}</span>
|
||||
</label>
|
||||
<label class="radio">
|
||||
<input type="radio" name="sendChecksumGroup" value="never" data-bind="checked: feature_sendChecksum" id="settings-featureSendChecksumNever"> {{ _('Never') }}
|
||||
</label>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_disableExternalHeatupDetection" id="settings-featureExternalHeatupDetection"> {{ _('Disable detection of external heatups') }} <span class="label">{{ _('Repetier') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">{{ _('Send a checksum with the command')}}</label>
|
||||
<div class="controls">
|
||||
<label class="radio">
|
||||
<input type="radio" name="sendChecksumGroup" value="print" data-bind="checked: feature_sendChecksum" id="settings-featureSendChecksumPrint"> {{ _('When printing') }}
|
||||
</label>
|
||||
<label class="radio">
|
||||
<input type="radio" name="sendChecksumGroup" value="always" data-bind="checked: feature_sendChecksum" id="settings-featureSendChecksumAlways"> {{ _('Always') }} <span class="label">{{ _('Repetier') }}</span>
|
||||
</label>
|
||||
<label class="radio">
|
||||
<input type="radio" name="sendChecksumGroup" value="never" data-bind="checked: feature_sendChecksum" id="settings-featureSendChecksumNever"> {{ _('Never') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ from octoprint.events import eventManager, Events
|
|||
from octoprint.filemanager import valid_file_type
|
||||
from octoprint.filemanager.destinations import FileDestinations
|
||||
from octoprint.util import get_exception_string, sanitize_ascii, filter_non_ascii, CountedEvent, RepeatedTimer, \
|
||||
to_unicode, bom_aware_open, TypedQueue, TypeAlreadyInQueue
|
||||
to_unicode, bom_aware_open, TypedQueue, TypeAlreadyInQueue, chunks
|
||||
|
||||
try:
|
||||
import _winreg
|
||||
|
|
@ -124,6 +124,9 @@ Groups will be as follows:
|
|||
* ``e``: E coordinate
|
||||
"""
|
||||
|
||||
regex_firmware_splitter = re.compile("\s*([A-Z0-9_]+):")
|
||||
"""Regex to use for splitting M115 responses."""
|
||||
|
||||
def serialList():
|
||||
baselist=[]
|
||||
if os.name=="nt":
|
||||
|
|
@ -310,6 +313,8 @@ class MachineCom(object):
|
|||
self._neverSendChecksum = settings().getBoolean(["feature", "neverSendChecksum"])
|
||||
self._sendChecksumWithUnknownCommands = settings().getBoolean(["feature", "sendChecksumWithUnknownCommands"])
|
||||
self._unknownCommandsNeedAck = settings().getBoolean(["feature", "unknownCommandsNeedAck"])
|
||||
self._sdAlwaysAvailable = settings().getBoolean(["feature", "sdAlwaysAvailable"])
|
||||
self._sdRelativePath = settings().getBoolean(["feature", "sdRelativePath"])
|
||||
self._currentLine = 1
|
||||
self._line_mutex = threading.RLock()
|
||||
self._resendDelta = None
|
||||
|
|
@ -319,7 +324,9 @@ class MachineCom(object):
|
|||
self._currentResendCount = 0
|
||||
self._resendSwallowRepetitions = settings().getBoolean(["feature", "ignoreIdenticalResends"])
|
||||
self._resendSwallowRepetitionsCounter = 0
|
||||
self._checksum_requiring_commands = settings().get(["serial", "checksumRequiringCommands"])
|
||||
|
||||
self._firmwareDetection = settings().getBoolean(["feature", "firmwareDetection"])
|
||||
self._firmwareInfoReceived = not self._firmwareDetection
|
||||
|
||||
self._supportResendsWithoutOk = settings().getBoolean(["serial", "supportResendsWithoutOk"])
|
||||
|
||||
|
|
@ -795,6 +802,10 @@ class MachineCom(object):
|
|||
if not self.isOperational():
|
||||
# printer is not connected, can't use SD
|
||||
return
|
||||
|
||||
if filename.startswith("/") and self._sdRelativePath:
|
||||
filename = filename[1:]
|
||||
|
||||
self._sdFileToSelect = filename
|
||||
self.sendCommand("M23 %s" % filename)
|
||||
else:
|
||||
|
|
@ -920,7 +931,7 @@ class MachineCom(object):
|
|||
return
|
||||
|
||||
self.sendCommand("M21")
|
||||
if settings().getBoolean(["feature", "sdAlwaysAvailable"]):
|
||||
if self._sdAlwaysAvailable:
|
||||
self._sdAvailable = True
|
||||
self.refreshSdFiles()
|
||||
self._callback.on_comm_sd_state_change(self._sdAvailable)
|
||||
|
|
@ -1207,6 +1218,33 @@ class MachineCom(object):
|
|||
except ValueError:
|
||||
pass
|
||||
|
||||
##~~ firmware name & version
|
||||
elif "FIRMWARE_NAME:" in line:
|
||||
# looks like a response to M115
|
||||
data = parse_firmware_line(line)
|
||||
firmware_name = data.get("FIRMWARE_NAME")
|
||||
self._logger.info("Printer reports firmware name \"{}\"".format(firmware_name))
|
||||
|
||||
if not self._firmwareInfoReceived and firmware_name:
|
||||
if "repetier" in firmware_name.lower():
|
||||
self._logger.info("Detected Repetier firmware, enabling relevant features for issue free communication")
|
||||
|
||||
self._alwaysSendChecksum = True
|
||||
self._resendSwallowRepetitions = True
|
||||
supportRepetierTargetTemp = True
|
||||
disable_external_heatup_detection = True
|
||||
|
||||
sd_always_available = self._sdAlwaysAvailable
|
||||
self._sdAlwaysAvailable = True
|
||||
if not sd_always_available and not self._sdAvailable:
|
||||
self.initSdCard()
|
||||
|
||||
elif "reprapfirmware" in firmware_name.lower():
|
||||
self._logger.info("Detected RepRapFirmware, enabling relevant features for issue free communication")
|
||||
self._sdRelativePath = True
|
||||
|
||||
self._firmwareInfoReceived = True
|
||||
|
||||
##~~ SD Card handling
|
||||
elif 'SD init fail' in line or 'volume.init failed' in line or 'openRoot failed' in line:
|
||||
self._sdAvailable = False
|
||||
|
|
@ -1528,6 +1566,8 @@ class MachineCom(object):
|
|||
self._changeState(self.STATE_OPERATIONAL)
|
||||
|
||||
self.resetLineNumbers()
|
||||
if self._firmwareDetection:
|
||||
self.sendCommand("M115")
|
||||
|
||||
if self._sdAvailable:
|
||||
self.refreshSdFiles()
|
||||
|
|
@ -2012,7 +2052,9 @@ class MachineCom(object):
|
|||
# now comes the part where we increase line numbers and send stuff - no turning back now
|
||||
command_requiring_checksum = gcode is not None and gcode in self._checksum_requiring_commands
|
||||
command_allowing_checksum = gcode is not None or self._sendChecksumWithUnknownCommands
|
||||
checksum_enabled = self.isPrinting() or self._alwaysSendChecksum
|
||||
checksum_enabled = not self._neverSendChecksum and (self.isPrinting() or
|
||||
self._alwaysSendChecksum or
|
||||
not self._firmwareInfoReceived)
|
||||
|
||||
command_to_send = command.encode("ascii", errors="replace")
|
||||
if command_requiring_checksum or (command_allowing_checksum and checksum_enabled):
|
||||
|
|
@ -2916,6 +2958,26 @@ def parse_temperature_line(line, current):
|
|||
|
||||
return max(maxToolNum, current), canonicalize_temperatures(result, current)
|
||||
|
||||
def parse_firmware_line(line):
|
||||
"""
|
||||
Parses the provided firmware info line.
|
||||
|
||||
The result will be a dictionary mapping from the contained keys to the contained
|
||||
values.
|
||||
|
||||
Arguments:
|
||||
line (str): the line to parse
|
||||
|
||||
Returns:
|
||||
dict: a dictionary with the parsed data
|
||||
"""
|
||||
|
||||
result = dict()
|
||||
split_line = regex_firmware_splitter.split(line.strip())[1:] # first entry is empty start of trimmed string
|
||||
for key, value in chunks(split_line, 2):
|
||||
result[key] = value
|
||||
return result
|
||||
|
||||
def gcode_command_for_cmd(cmd):
|
||||
"""
|
||||
Tries to parse the provided ``cmd`` and extract the GCODE command identifier from it (e.g. "G0" for "G0 X10.0").
|
||||
|
|
|
|||
Loading…
Reference in a new issue