diff --git a/CHANGELOG.md b/CHANGELOG.md index f09da2c8..da896aa0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -153,7 +153,8 @@ ``python setup.py sdist`` * [#330](https://github.com/foosel/OctoPrint/issues/330) - Ping pong sending to fix potential acknowledgement errors. Also affects [#166](https://github.com/foosel/OctoPrint/issues/166), [#470](https://github.com/foosel/OctoPrint/issues/470) - and [#490](https://github.com/foosel/OctoPrint/issues/490). + and [#490](https://github.com/foosel/OctoPrint/issues/490). A big thank you to all people involved in these tickets + in getting to the ground of this. * [#825](https://github.com/foosel/OctoPrint/issues/825) - Fixed "please visualize" button of large GCODE files * Various fixes of bugs in newly introduced features and improvements: * [#625](https://github.com/foosel/OctoPrint/pull/625) - Newly added GCODE files were not being added to the analysis diff --git a/src/octoprint/server/api/settings.py b/src/octoprint/server/api/settings.py index e5ffe39d..7bba8a02 100644 --- a/src/octoprint/server/api/settings.py +++ b/src/octoprint/server/api/settings.py @@ -80,7 +80,8 @@ def getSettings(): "timeoutTemperature": s.getFloat(["serial", "timeout", "temperature"]), "timeoutSdStatus": s.getFloat(["serial", "timeout", "sdStatus"]), "log": s.getBoolean(["serial", "log"]), - "additionalPorts": s.get(["serial", "additionalPorts"]) + "additionalPorts": s.get(["serial", "additionalPorts"]), + "longRunningCommands": s.get(["serial", "longRunningCommands"]) }, "folder": { "uploads": s.getBaseFolder("uploads"), @@ -192,6 +193,7 @@ def setSettings(): if "timeoutTemperature" in data["serial"].keys(): s.setFloat(["serial", "timeout", "temperature"], data["serial"]["timeoutTemperature"]) if "timeoutSdStatus" in data["serial"].keys(): s.setFloat(["serial", "timeout", "sdStatus"], data["serial"]["timeoutSdStatus"]) if "additionalPorts" in data["serial"] and isinstance(data["serial"]["additionalPorts"], (list, tuple)): s.set(["serial", "additionalPorts"], data["serial"]["additionalPorts"]) + if "longRunningCommands" in data["serial"] and isinstance(data["serial"]["longRunningCommands"], (list, tuple)): s.set(["serial", "longRunningCommands"], data["serial"]["longRunningCommands"]) oldLog = s.getBoolean(["serial", "log"]) if "log" in data["serial"].keys(): s.setBoolean(["serial", "log"], data["serial"]["log"]) diff --git a/src/octoprint/settings.py b/src/octoprint/settings.py index 954b01e5..6d977027 100644 --- a/src/octoprint/settings.py +++ b/src/octoprint/settings.py @@ -82,7 +82,8 @@ default_settings = { "temperature": 5, "sdStatus": 1 }, - "additionalPorts": [] + "additionalPorts": [], + "longRunningCommands": ["G4", "G28", "G29", "G30", "G32"] }, "server": { "host": "0.0.0.0", diff --git a/src/octoprint/static/js/app/helpers.js b/src/octoprint/static/js/app/helpers.js index b83f315e..e64ee9a1 100644 --- a/src/octoprint/static/js/app/helpers.js +++ b/src/octoprint/static/js/app/helpers.js @@ -447,3 +447,17 @@ function showConfirmationDialog(message, onacknowledge) { }); confirmationDialog.modal("show"); } + +function commentableLinesToArray(lines) { + return splitTextToArray(lines, "\n", true, function(item) {return !_.startsWith(item, "#")}); +} + +function splitTextToArray(text, sep, stripEmpty, filter) { + return _.filter( + _.map( + text.split(sep), + function(item) { return (item) ? item.trim() : ""; } + ), + function(item) { return (stripEmpty ? item : true) && (filter ? filter(item) : true); } + ); +} diff --git a/src/octoprint/static/js/app/viewmodels/settings.js b/src/octoprint/static/js/app/viewmodels/settings.js index 53f924a4..257548a3 100644 --- a/src/octoprint/static/js/app/viewmodels/settings.js +++ b/src/octoprint/static/js/app/viewmodels/settings.js @@ -126,6 +126,7 @@ $(function() { self.serial_timeoutSdStatus = ko.observable(undefined); self.serial_log = ko.observable(undefined); self.serial_additionalPorts = ko.observable(undefined); + self.serial_longRunningCommands = ko.observable(undefined); self.folder_uploads = ko.observable(undefined); self.folder_timelapse = ko.observable(undefined); @@ -385,6 +386,7 @@ $(function() { self.serial_timeoutSdStatus(response.serial.timeoutSdStatus); self.serial_log(response.serial.log); self.serial_additionalPorts(response.serial.additionalPorts.join("\n")); + self.serial_longRunningCommands(response.serial.longRunningCommands.join(", ")); self.folder_uploads(response.folder.uploads); self.folder_timelapse(response.folder.timelapse); @@ -462,13 +464,8 @@ $(function() { "timeoutTemperature": self.serial_timeoutTemperature(), "timeoutSdStatus": self.serial_timeoutSdStatus(), "log": self.serial_log(), - "additionalPorts": _.filter( - _.map( - self.serial_additionalPorts().split("\n"), - function(item) { return (item) ? item.trim() : ""; } - ), - function(item) { return item && !_.startsWith(item, "#"); } - ) + "additionalPorts": commentableLinesToArray(self.serial_additionalPorts()), + "longRunningCommands": splitTextToArray(self.serial_longRunningCommands(), ",", true) }, "folder": { "uploads": self.folder_uploads(), diff --git a/src/octoprint/templates/dialogs/settings/serialconnection.jinja2 b/src/octoprint/templates/dialogs/settings/serialconnection.jinja2 index aacca84b..11c3b559 100644 --- a/src/octoprint/templates/dialogs/settings/serialconnection.jinja2 +++ b/src/octoprint/templates/dialogs/settings/serialconnection.jinja2 @@ -70,6 +70,13 @@ +
+ +
+ + {{ _('Use this to specify the commands known to take a long time to complete without output from your printer and hence might cause timeout issues. Just the G or M code, comma separated.')|format(glob_url="http://docs.python.org/2/library/glob.html") }} +
+
diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index 2f8e0813..04fda534 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -149,7 +149,7 @@ class MachineCom(object): self._pauseWaitTimeLost = 0.0 self._currentTool = 0 - self._blocking_command = False + self._long_running_command = False self._heating = False self._timeout = None @@ -218,7 +218,7 @@ class MachineCom(object): self._regex_repetierTempExtr = re.compile("TargetExtr([0-9]+):(%s)" % positiveFloatPattern) self._regex_repetierTempBed = re.compile("TargetBed:(%s)" % positiveFloatPattern) - self._blocking_commands = ("G28", "G29", "G30", "G32") + self._long_running_commands = settings().get(["serial", "longRunningCommands"]) # multithreading locks self._sendNextLock = threading.Lock() @@ -883,7 +883,7 @@ class MachineCom(object): ##~~ process oks if line.strip().startswith("ok") or (self.isPrinting() and supportWait and line.strip().startswith("wait")): self._clear_to_send.set() - self._blocking_command = False + self._long_running_command = False ##~~ Temperature processing if ' T:' in line or line.startswith('T:') or ' T0:' in line or line.startswith('T0:') or ' B:' in line or line.startswith('B:'): @@ -1088,12 +1088,12 @@ class MachineCom(object): ### Printing elif self._state == self.STATE_PRINTING: if line == "" and time.time() > self._timeout: - if not self._blocking_command: + if not self._long_running_command: self._log("Communication timeout during printing, forcing a line") self._sendCommand("M105") self._clear_to_send.set() else: - self._logger.debug("Ran into a communication timeout, but a blocking command is currently active") + self._logger.debug("Ran into a communication timeout, but a command known to be a long runner is currently active") if "ok" in line or (supportWait and "wait" in line): # a wait while printing means our printer's buffer ran out, probably due to some ok getting @@ -1161,22 +1161,22 @@ class MachineCom(object): """ Polls the temperature after the temperature timeout, re-enqueues itself. - If the printer is not operational, not printing from sd, busy with a blocking command or heating, no poll + If the printer is not operational, not printing from sd, busy with a long running command or heating, no poll will be done. """ - if self.isOperational() and not self.isStreaming() and not self._blocking_command and not self._heating: + if self.isOperational() and not self.isStreaming() and not self._long_running_command and not self._heating: self.sendCommand("M105", cmd_type="temperature_poll") def _poll_sd_status(self): """ Polls the sd printing status after the sd status timeout, re-enqueues itself. - If the printer is not operational, not printing from sd, busy with a blocking command or heating, no poll + If the printer is not operational, not printing from sd, busy with a long running command or heating, no poll will be done. """ - if self.isOperational() and self.isSdPrinting() and not self._blocking_command and not self._heating: + if self.isOperational() and self.isSdPrinting() and not self._long_running_command and not self._heating: self.sendCommand("M27", cmd_type="sd_status_poll") def _onConnected(self): @@ -1693,13 +1693,13 @@ class MachineCom(object): def _gcode_M109_sent(self, cmd, cmd_type=None): self._heatupWaitStartTime = time.time() - self._blocking_command = True + self._long_running_command = True self._heating = True self._gcode_M104_sent(cmd, cmd_type) def _gcode_M190_sent(self, cmd, cmd_type=None): self._heatupWaitStartTime = time.time() - self._blocking_command = True + self._long_running_command = True self._heating = True self._gcode_M140_sent(cmd, cmd_type) @@ -1737,13 +1737,12 @@ class MachineCom(object): # dwell time is specified in seconds _timeout = float(cmd[s_idx+1:]) self._timeout = get_new_timeout("communication") + _timeout - self._blocking_command = True ##~~ command phase handlers def _command_phase_sending(self, cmd, cmd_type=None, gcode=None): - if gcode is not None and gcode in self._blocking_commands: - self._blocking_command = True + if gcode is not None and gcode in self._long_running_commands: + self._long_running_command = True ### MachineCom callback ################################################################################################