Merge branch 'maintenance' into devel

This commit is contained in:
Gina Häußge 2017-02-20 14:34:42 +01:00
commit bac972de53
11 changed files with 252 additions and 34 deletions

View file

@ -133,6 +133,12 @@ mapped from the same fields in ``config.yaml`` unless otherwise noted:
-
* - ``feature.modelSizeDetection``
-
* - ``feature.firmwareDetection``
-
* - ``feature.printCancelConfirmation``
-
* - ``feature.blockWhileDwelling``
-
* - ``folder.uploads``
-
* - ``folder.timelapse``

View file

@ -519,6 +519,23 @@ Use the following settings to enable or disable OctoPrint features:
# If ignoredIdenticalResends is true, how many consecutive identical resends to ignore
identicalResendsCount: 7
# Whether to support F on its own as a valid GCODE command (true) or not (false)
supportFAsCommand: false
# Whether to enable model size detection and warning (true) or not (false)
modelSizeDetection: true
# Whether to attempt to auto detect the firmware of the printer and adjust settings
# accordingly (true) or not and rely on manual configuration (false)
firmwareDetection: true
# Whether to show a confirmation on print cancelling (true) or not (false)
printCancelConfirmation: true
# Whether to block all sending to the printer while a G4 (dwell) command is active (true, repetier)
# or not (false)
blockWhileDwelling: false
.. _sec-configuration-config_yaml-folder:
Folder
@ -565,6 +582,45 @@ Use the following settings to set custom paths for folders used by OctoPrint:
# Absolute path where to store (GCODE) scripts
scripts: /path/to/scripts/folder
.. _sec-configuration-config_yaml-gcodeanalysis:
GCODE Analysis
--------------
Settings pertaining to the server side GCODE analysis implementation.
.. code-block:: yaml
# Maximum number of extruders to support/to sanity check for
maxExtruders: 10
# Pause between each processed GCODE line in normal priority mode, seconds
throttle_normalprio: 0.01
# Pause between each processed GCODE line in high priority mode (e.g. on fresh
# uploads), seconds
throttle_highprio: 0.0
.. _sec-configuration-config_yaml-gcodeviewer:
GCODE Viewer
------------
Settings pertaining to the built in GCODE Viewer.
.. code-block:: yaml
# Whether to enable the GCODE viewer in the UI
enabled: true
# Maximum size a GCODE file may have on mobile devices to automatically be loaded
# into the viewer, defaults to 2MB
mobileSizeThreshold: 2097152
# Maximum size a GCODE file may have to automatically be loaded into the viewer,
# defaults to 20MB
sizeThreshold: 20971520
.. _sec-configuration-config_yaml-plugins:
Plugin settings
@ -679,8 +735,34 @@ Use the following settings to configure the serial connection to the printer:
connection: 2
# Timeout during serial communication, in seconds.
# Defaults to 5 sec
communication: 5
# Defaults to 30 sec
communication: 30
# Timeout after which to query temperature when no target is set
temperature: 5
# Timeout after which to query temperature when a target is set
temperatureTargetSet: 2
# Timeout after which to query the SD status while SD printing
sdStatus: 1
# Maximum number of consecutive communication timeouts after which the printer will be considered
# dead and OctoPrint disconnects with an error.
maxCommunicationTimeouts:
# max. timeouts when the printer is idle
idle: 2
# max. timeouts when the printer is printing
printing: 5
# max. timeouts when a long running command is active
long: 5
# Maximum number of write attempts to serial during which nothing can be written before the communication
# with the printer is considered dead and OctoPrint will disconnect with an error
maxWritePasses:
# Use this to define additional patterns to consider for serial port listing. Must be a valid
# "glob" pattern (see http://docs.python.org/2/library/glob.html). Defaults to not set.
@ -702,6 +784,7 @@ Use the following settings to configure the serial connection to the printer:
- G32
- M400
- M226
- M600
# Commands which need to always be send with a checksum. Defaults to only M110
checksumRequiringCommands:
@ -944,10 +1027,12 @@ Use `Javascript regular expressions <https://developer.mozilla.org/en/docs/Web/J
# A list of filters to display in the terminal tab. Defaults to the filters shown below
terminalFilters:
- name: Suppress M105 requests/responses
regex: '(Send: M105)|(Recv: ok T:)'
- name: Suppress M27 requests/responses
regex: '(Send: M27)|(Recv: SD printing byte)'
- name: Suppress temperature messages
regex: '(Send: (N\d+\s+)?M105)|(Recv: ok T:)'
- name: Suppress SD status messages
regex: '(Send: (N\d+\s+)?M27)|(Recv: SD printing byte)'
- name: Suppress wait responses
regex: 'Recv: wait'
.. _sec-configuration-config_yaml-webcam:
@ -981,22 +1066,42 @@ Use the following settings to configure webcam support:
# Whether to include a "created with OctoPrint" watermark in the generated timelapse movies
watermark: true
# Whether to flip the webcam horizontally
flipH: false
# Whether to flip the webcam vertically
flipV: false
# Whether to rotate the webcam 90° counter clockwise
rotate90: false
# The default timelapse settings.
timelapse:
# The timelapse type. Can be either "off", "zchange" or "timed". Defaults to "off"
type: timed
# The framerate at which to render the movie
fps: 25
# The number of seconds in the rendered video to add after a finished print. The exact way how the
# additional images will be recorded depends on timelapse type. Timed timelapses continue to
# record just like at the beginning, so the recording will continue another
# fps * postRoll * interval seconds. Zchange timelapses will take one final picture and add it fps * postRoll
postRoll: 0
# Additional options depending on the timelapse type. All timelapses take a postRoll and an fps setting.
options:
# The number of seconds in the rendered video to add after a finished print. The exact way how the
# additional images will be recorded depends on timelapse type. Timed timelapses continue to
# record just like at the beginning, so the recording will continue another
# fps * postRoll * interval seconds. Zchange timelapses will take one final picture and add it fps * postRoll
postRoll: 0
# The framerate at which to render the movie
fps: 25
# Timed timelapses only: The interval which to leave between images in seconds
interval: 2
# Timed timelapses only: Whether to capture the snapshots for the post roll (true) or just copy
# the last captured snapshot from the print over and over again (false)
capturePostRoll: true
# ZChange timelapses only: Z-hop height during retractions to ignore for capturing snapshots
retractionZHop: 0.0
# After how many days unrendered timelapses will be deleted
cleanTmpAfterDays: 7

View file

@ -25,6 +25,7 @@ class VirtualPrinter(object):
sleep_after_regex = re.compile("sleep_after ([GMTF]\d+) (\d+)")
sleep_after_next_regex = re.compile("sleep_after_next ([GMTF]\d+) (\d+)")
custom_action_regex = re.compile("action_custom ([a-zA-Z0-9_]+)(\s+.*)?")
prepare_ok_regex = re.compile("prepare_ok (.*)?")
def __init__(self, seriallog_handler=None, read_timeout=5.0, write_timeout=10.0):
import logging
@ -50,8 +51,15 @@ class VirtualPrinter(object):
self.outgoing = queue.Queue()
self.buffered = queue.Queue(maxsize=settings().getInt(["devel", "virtualPrinter", "commandBuffer"]))
for item in ['start\n', 'Marlin: Virtual Marlin!\n', '\x80\n', 'SD card ok\n']:
self._send(item)
if settings().getBoolean(["devel", "virtualPrinter", "simulateReset"]):
for item in ['start\n', 'Marlin: Virtual Marlin!\n', '\x80\n', 'SD card ok\n']:
self._send(item)
self._prepared_oks = []
prepared = settings().get(["devel", "virtualPrinter", "preparedOks"])
if prepared and isinstance(prepared, list):
for prep in prepared:
self._prepared_oks.append(prep)
self.currentExtruder = 0
self.extruderCount = settings().getInt(["devel", "virtualPrinter", "numExtruders"])
@ -92,6 +100,7 @@ class VirtualPrinter(object):
self._supportF = settings().getBoolean(["devel", "virtualPrinter", "supportF"])
self._sendWait = settings().getBoolean(["devel", "virtualPrinter", "sendWait"])
self._sendBusy = settings().getBoolean(["devel", "virtualPrinter", "sendBusy"])
self._waitInterval = settings().getFloat(["devel", "virtualPrinter", "waitInterval"])
self._echoOnM117 = settings().getBoolean(["devel", "virtualPrinter", "echoOnM117"])
@ -384,7 +393,7 @@ class VirtualPrinter(object):
def _gcode_M114(self, data):
output = "X:{} Y:{} Z:{} E:{} Count: A:{} B:{} C:{}".format(self._lastX, self._lastY, self._lastZ, self._lastE, int(self._lastX*100), int(self._lastY*100), int(self._lastZ*100))
if not self._okBeforeCommandOutput:
output = "ok " + output
output = "{} {}".format(self._ok(), output)
self._send(output)
return True
@ -451,6 +460,24 @@ class VirtualPrinter(object):
_gcode_G2 = _gcode_G0
_gcode_G3 = _gcode_G0
def _gcode_G4(self, data):
matchS = re.search('S([0-9]+)', data)
matchP = re.search('P([0-9]+)', data)
_timeout = 0
if matchP:
_timeout = float(matchP.group(1)) / 1000.0
elif matchS:
_timeout = float(matchS.group(1))
if self._sendBusy:
until = time.time() + _timeout
while time.time() < until:
time.sleep(1.0)
self._send("busy:processing")
else:
time.sleep(_timeout)
##~~ further helpers
def _calculate_checksum(self, line):
@ -479,7 +506,7 @@ class VirtualPrinter(object):
def request_resend():
self._send("Resend:%d" % expected)
self._send("ok")
self._send(self._ok())
if settings().getBoolean(["devel", "virtualPrinter", "repetierStyleResends"]):
request_resend()
@ -519,6 +546,9 @@ class VirtualPrinter(object):
| Triggers a resend error with a checksum mismatch
drop_connection
| Drops the serial connection
prepare_ok <broken ok>
| Will cause <broken ok> to be enqueued for use,
| will be used instead of actual "ok"
# Reply Timing / Sleeping
@ -527,7 +557,7 @@ class VirtualPrinter(object):
sleep_after <str:command> <int:seconds>
| Sleeps <seconds> s after each execution of <command>
sleep_after_next <str:command> <int:seconds>
| Sleeps <seconds> s after execution of <command>
| Sleeps <seconds> s after execution of next <command>
"""
for line in usage.split("\n"):
self._send("echo: {}".format(line.strip()))
@ -556,6 +586,7 @@ class VirtualPrinter(object):
sleep_after_match = VirtualPrinter.sleep_after_regex.match(data)
sleep_after_next_match = VirtualPrinter.sleep_after_next_regex.match(data)
custom_action_match = VirtualPrinter.custom_action_regex.match(data)
prepare_ok_match = VirtualPrinter.prepare_ok_regex.match(data)
if sleep_match is not None:
interval = int(sleep_match.group(1))
@ -576,6 +607,9 @@ class VirtualPrinter(object):
params = custom_action_match.group(2)
params = params.strip() if params is not None else ""
self._send("// action:{action} {params}".format(**locals()).strip())
elif prepare_ok_match is not None:
ok = prepare_ok_match.group(1)
self._prepared_oks.append(ok)
except:
pass
@ -665,7 +699,7 @@ class VirtualPrinter(object):
output += " @:64\n"
if includeOk:
output = "ok " + output
output = "{} {}".format(self._ok(), output)
self._send(output)
def _parseHotendCommand(self, line, wait=False, support_r=False):
@ -1041,9 +1075,9 @@ class VirtualPrinter(object):
return
if settings().getBoolean(["devel", "virtualPrinter", "okWithLinenumber"]):
self._send("ok %d" % self.lastN)
self._send("{} {}".format(self._ok(), self.lastN))
else:
self._send("ok")
self._send(self._ok())
def _sendWaitAfterTimeout(self, timeout=5):
time.sleep(timeout)
@ -1054,6 +1088,12 @@ class VirtualPrinter(object):
if self.outgoing is not None:
self.outgoing.put(line)
def _ok(self):
ok = "ok"
if self._prepared_oks:
ok = self._prepared_oks.pop(0)
return ok
class CharCountingQueue(queue.Queue):
def __init__(self, maxsize, name=None):

View file

@ -126,7 +126,8 @@ def getSettings():
"ignoreIdenticalResends": s.getBoolean(["feature", "ignoreIdenticalResends"]),
"modelSizeDetection": s.getBoolean(["feature", "modelSizeDetection"]),
"firmwareDetection": s.getBoolean(["feature", "firmwareDetection"]),
"printCancelConfirmation": s.getBoolean(["feature", "printCancelConfirmation"])
"printCancelConfirmation": s.getBoolean(["feature", "printCancelConfirmation"]),
"blockWhileDwelling": s.getBoolean(["feature", "blockWhileDwelling"])
},
"serial": {
"port": connectionOptions["portPreference"],
@ -311,6 +312,7 @@ def _saveSettings(data):
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 "printCancelConfirmation" in data["feature"]: s.setBoolean(["feature", "printCancelConfirmation"], data["feature"]["printCancelConfirmation"])
if "blockWhileDwelling" in data["feature"]: s.setBoolean(["feature", "blockWhileDwelling"], data["feature"]["blockWhileDwelling"])
if "serial" in data.keys():
if "autoconnect" in data["serial"].keys(): s.setBoolean(["serial", "autoconnect"], data["serial"]["autoconnect"])

View file

@ -168,7 +168,6 @@ default_settings = {
"options": {},
"postRoll": 0,
"fps": 25,
"capturePostRoll": True
},
"cleanTmpAfterDays": 7
},
@ -203,7 +202,8 @@ default_settings = {
"supportFAsCommand": False,
"modelSizeDetection": True,
"firmwareDetection": True,
"printCancelConfirmation": True
"printCancelConfirmation": True,
"blockWhileDwelling": False
},
"folder": {
"uploads": None,
@ -357,7 +357,10 @@ default_settings = {
"brokenM29": True,
"supportF": False,
"firmwareName": "Virtual Marlin 1.0",
"sharedNozzle": False
"sharedNozzle": False,
"sendBusy": False,
"simulateReset": True,
"preparedOks": []
}
}
}

View file

@ -883,3 +883,36 @@ var getQueryParameterByName = function(name, url) {
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, " "));
};
/**
* Escapes unprintable ASCII characters in the provided string.
*
* E.g. turns a null byte in the string into "\x00".
*
* Only characters 0 to 31, 127 and 255 will be escaped, that
* should leave printable characters and unicode alone.
*
* Originally based on
* https://gist.github.com/mathiasbynens/1243213#gistcomment-53590
*
* @param str The string to escape
* @returns {string}
*/
var escapeUnprintableCharacters = function(str) {
var result = "";
var index = 0;
var charCode;
while (!isNaN(charCode = str.charCodeAt(index))) {
if (charCode < 32 || charCode == 127 || charCode == 255) {
// special hex chars
result += "\\x" + (charCode > 15 ? "" : "0") + charCode.toString(16)
} else {
// anything else
result += str[index];
}
index++;
}
return result;
};

View file

@ -139,6 +139,7 @@ $(function() {
self.feature_modelSizeDetection = ko.observable(undefined);
self.feature_firmwareDetection = ko.observable(undefined);
self.feature_printCancelConfirmation = ko.observable(undefined);
self.feature_blockWhileDwelling = ko.observable(undefined);
self.serial_port = ko.observable();
self.serial_baudrate = ko.observable();

View file

@ -201,7 +201,7 @@ $(function() {
if (type == undefined) {
type = "line";
}
return {line: line, type: type}
return {line: escapeUnprintableCharacters(line), type: type}
};
self._processStateData = function(data) {

View file

@ -88,6 +88,13 @@
</label>
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="checkbox">
<input type="checkbox" data-bind="checked: feature_blockWhileDwelling" id="settings-featureBlockWhileDwelling"> {{ _('Actively pause communication during <code>G4</code> dwell command') }} <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">

View file

@ -44,7 +44,7 @@
</div>
</div>
</div>
<div class="control-group" title="{{ _('Time after which the communication with your printer will be considered timed out if nothing was sent by your printer (and an attempt to get it talking again will be done). Increase this if your printer takes longer than this for some moves. This is also the interval in which the temperature will be polled from the printer while not printing.') }}">
<div class="control-group" title="{{ _('Time after which the communication with your printer will be considered timed out if nothing was sent by your printer (and an attempt to get it talking again will be done). Increase this if your printer takes longer than this for some moves.') }}">
<label class="control-label" for="settings-serialTimeoutCommunication">{{ _('Communication timeout') }}</label>
<div class="controls">
<div class="input-append">
@ -138,25 +138,28 @@
</div>
<div class="controls">
<label class="checkbox">
<input type="checkbox" data-bind="checked: serial_supportResendsWithoutOk" id="settings-supportResendsWithoutOk"> {{ _('Simulate an additional `ok` for resend requests') }}
<input type="checkbox" data-bind="checked: serial_supportResendsWithoutOk" id="settings-supportResendsWithoutOk"> {{ _('Simulate an additional <code>ok</code> for resend requests') }}
</label>
</div>
<div class="control-group" title="{{ _('Maximum consecutive communication timeouts while idle. More than this and the printer will be considered to be gone. Set to 0 to disable.') }}">
<label class="control-label" for="settings-serialMaxTimeoutsIdle">{{ _('Max. consecutive timeouts while idle') }}</label>
<div class="controls">
<input type="number" min="0" class="input-mini text-right" id="settings-serialMaxTimeoutsIdle" data-bind="value: serial_maxTimeoutsIdle">
<span class="help-block">{{ _('Set to 0 to disable consecutive timeout detection and handling.') }}</span>
</div>
</div>
<div class="control-group" title="{{ _('Maximum consecutive communication timeouts while printing. More than this and the printer will be considered to be gone. Set to 0 to disable.') }}">
<label class="control-label" for="settings-serialMaxTimeoutsPrinting">{{ _('Max. consecutive timeouts while printing') }}</label>
<div class="controls">
<input type="number" min="0" class="input-mini text-right" id="settings-serialMaxTimeoutsPrinting" data-bind="value: serial_maxTimeoutsPrinting">
<span class="help-block">{{ _('Set to 0 to disable consecutive timeout detection and handling.') }}</span>
</div>
</div>
<div class="control-group" title="{{ _('Maximum consecutive communication timeouts while a long running command is active. More than this and the printer will be considered to be gone. Set to 0 to disable.') }}">
<label class="control-label" for="settings-serialMaxTimeoutsLong">{{ _('Max. consecutive timeouts during long running commands') }}</label>
<div class="controls">
<input type="number" min="0" class="input-mini text-right" id="settings-serialMaxTimeoutsLong" data-bind="value: serial_maxTimeoutsLong">
<span class="help-block">{{ _('Set to 0 to disable consecutive timeout detection and handling.') }}</span>
</div>
</div>
</div>

View file

@ -285,6 +285,7 @@ class MachineCom(object):
self._long_running_command = False
self._heating = False
self._dwelling_until = False
self._connection_closing = False
self._timeout = None
@ -316,6 +317,7 @@ class MachineCom(object):
self._unknownCommandsNeedAck = settings().getBoolean(["feature", "unknownCommandsNeedAck"])
self._sdAlwaysAvailable = settings().getBoolean(["feature", "sdAlwaysAvailable"])
self._sdRelativePath = settings().getBoolean(["feature", "sdRelativePath"])
self._blockWhileDwelling = settings().getBoolean(["feature", "blockWhileDwelling"])
self._currentLine = 1
self._line_mutex = threading.RLock()
self._resendDelta = None
@ -1059,6 +1061,7 @@ class MachineCom(object):
self.sayHello()
while self._monitoring_active:
now = time.time()
try:
line = self._readline()
if line is None:
@ -1067,6 +1070,9 @@ class MachineCom(object):
self._consecutive_timeouts = 0
self._timeout = get_new_timeout("communication", self._timeout_intervals)
if self._dwelling_until and now > self._dwelling_until:
self._dwelling_until = False
##~~ debugging output handling
if line.startswith("//"):
debugging_output = line[2:].strip()
@ -1095,7 +1101,7 @@ class MachineCom(object):
def convert_line(line):
if line is None:
return None, None
stripped_line = line.strip()
stripped_line = line.strip().strip("\0")
return stripped_line, stripped_line.lower()
##~~ Error handling
@ -1148,7 +1154,7 @@ class MachineCom(object):
handled = True
# process timeouts
elif line == "" and time.time() > self._timeout:
elif line == "" and (not self._blockWhileDwelling or not self._dwelling_until or now > self._dwelling_until) and now > self._timeout:
# timeout only considered handled if the printer is printing
self._handle_timeout()
handled = self.isPrinting()
@ -1238,6 +1244,7 @@ class MachineCom(object):
self._alwaysSendChecksum = True
self._resendSwallowRepetitions = True
self._blockWhileDwelling = True
supportRepetierTargetTemp = True
disable_external_heatup_detection = True
@ -1377,7 +1384,10 @@ class MachineCom(object):
if "start" in line and not startSeen:
startSeen = True
self.sayHello()
elif line.startswith("ok"):
elif line.startswith("ok") or (supportWait and line == "wait"):
if line == "wait":
# if it was a wait we probably missed an ok, so let's simulate that now
self._handle_ok()
self._onConnected()
elif time.time() > self._timeout:
self._log("There was a timeout while trying to connect to the printer")
@ -1551,7 +1561,7 @@ class MachineCom(object):
command or heating, no poll will be done.
"""
if self.isOperational() and not self._connection_closing and not self.isStreaming() and not self._long_running_command and not self._heating and not self._manualStreaming:
if self.isOperational() and not self._connection_closing and not self.isStreaming() and not self._long_running_command and not self._heating and not self._dwelling_until and not self._manualStreaming:
self.sendCommand("M105", cmd_type="temperature_poll")
def _poll_sd_status(self):
@ -1562,7 +1572,7 @@ class MachineCom(object):
command or heating, no poll will be done.
"""
if self.isOperational() and not self._connection_closing and self.isSdPrinting() and not self._long_running_command and not self._heating:
if self.isOperational() and not self._connection_closing and self.isSdPrinting() and not self._long_running_command and not self._dwelling_until and not self._heating:
self.sendCommand("M27", cmd_type="sd_status_poll")
def _onConnected(self):
@ -2019,6 +2029,12 @@ class MachineCom(object):
if not self._send_queue_active:
break
# sleep if we are dwelling
now = time.time()
if self._blockWhileDwelling and self._dwelling_until and now < self._dwelling_until:
time.sleep(self._dwelling_until - now)
self._dwelling_until = False
# fetch command, command type and optional linenumber and sent callback from queue
command, linenumber, command_type, on_sent = entry
@ -2423,7 +2439,9 @@ class MachineCom(object):
_timeout = float(p_match.group("value")) / 1000.0
elif s_match:
_timeout = float(s_match.group("value"))
self._timeout = get_new_timeout("communication", self._timeout_intervals) + _timeout
self._dwelling_until = time.time() + _timeout
##~~ command phase handlers