diff --git a/scripts/octoprint.init b/scripts/octoprint.init index 19b10f7..ea79205 100644 --- a/scripts/octoprint.init +++ b/scripts/octoprint.init @@ -19,6 +19,7 @@ PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin DESC="Octoprint Daemon" NAME="Octoprint" DAEMON=/usr/local/bin/octoprint +NICE=10 PIDFILE=/var/run/$NAME.pid PKGNAME=octoprint SCRIPTNAME=/etc/init.d/$PKGNAME @@ -72,7 +73,7 @@ do_start() RETVAL="$?" if [ $RETVAL != 0 ]; then - start-stop-daemon --start --background --quiet --pidfile $PIDFILE --make-pidfile \ + start-stop-daemon --start --background --quiet --pidfile $PIDFILE --make-pidfile --nicelevel $NICE\ --exec $DAEMON --chuid $OCTOPRINT_USER --user $OCTOPRINT_USER --umask $UMASK -- $DAEMON_ARGS RETVAL="$?" fi diff --git a/src/octoprint/comm/protocol/__init__.py b/src/octoprint/comm/protocol/__init__.py index 2a15410..16ab4fb 100644 --- a/src/octoprint/comm/protocol/__init__.py +++ b/src/octoprint/comm/protocol/__init__.py @@ -79,8 +79,8 @@ class Protocol(MessageReceiver, StateReceiver, LogReceiver): self._heatup_start_time = None self._heatup_time_lost = 0.0 - self._changeState(State.PRINTING) self._current_file.start() + self._changeState(State.PRINTING) def pause_print(self): if self._state == State.PRINTING: diff --git a/src/octoprint/comm/protocol/reprap/__init__.py b/src/octoprint/comm/protocol/reprap/__init__.py index c0e1806..3bb6fab 100644 --- a/src/octoprint/comm/protocol/reprap/__init__.py +++ b/src/octoprint/comm/protocol/reprap/__init__.py @@ -128,6 +128,13 @@ class RepRapProtocol(Protocol): self._resend_delta = None self._last_resend_request = None + self._force_checksum = False + self._pingpong = False + self._rx_cache_fill = 0 + self._rx_cache_size = 127 + self._nack_lines = deque([]) + self._nack_lock = threading.Lock() + self._send_queue_processing = True self._thread = threading.Thread(target=self._handle_send_queue, name="SendQueueHandler") self._thread.daemon = True @@ -152,6 +159,11 @@ class RepRapProtocol(Protocol): self._resend_delta = None self._last_resend_request = None + self._force_checksum = self._useChecksum() + self._pingpong = self._usePingPong() + self._rx_cache_fill = 0 + self._nack_lines.clear() + self._current_line = 1 self._currentExtruder = 0 @@ -181,8 +193,6 @@ class RepRapProtocol(Protocol): self._send(RepRapProtocol.COMMAND_SD_SET_POS(0)) self._current_file.setFilepos(0) self._send(RepRapProtocol.COMMAND_SD_START()) - else: - self._send_next() def cancel_print(self): if isinstance(self._current_file, PrintingSdFileInformation): @@ -286,9 +296,9 @@ class RepRapProtocol(Protocol): return if isinstance(command, (tuple, list)): for c in command: - self._send(c, highPriority=high_priority, withChecksum=self._useChecksum()) + self._send(c, highPriority=high_priority) else: - self._send(command, highPriority=high_priority, withChecksum=self._useChecksum()) + self._send(command, highPriority=high_priority) def _fileTransferFinished(self, current_file): if isinstance(current_file, StreamingGcodeFileInformation): @@ -306,6 +316,36 @@ class RepRapProtocol(Protocol): message = self._handle_errors(message.strip()) + # resend == roll back time a bit + if RepRapProtocol.MESSAGE_RESEND(message): + # zero cache fill count + with self._nack_lock: + self._rx_cache_fill = 0 + self._nack_lines.clear() + self._handle_resend_request(message) + return + + if self._state == State.CONNECTED and self._startSeen and RepRapProtocol.MESSAGE_OK(message): + # if we are currently connected, have seen start and just gotten an "ok" we are now operational + self._changeState(State.OPERATIONAL) + + # ok == go ahead with sending + if RepRapProtocol.MESSAGE_OK(message): + # lower cache fill count since the command went through + with self._nack_lock: + if len(self._nack_lines) > 0: + self._rx_cache_fill -= self._nack_lines.popleft() + if self.is_heating_up(): + self._heatupDone() + self._clear_for_send.set() + return + + if self._resend_delta is None and not self.is_streaming(): + if time.time() > self._lastTemperatureUpdate + 5: + self._send_temperature_query(withType=True) + elif self.is_sd_printing() and time.time() > self._lastSdProgressUpdate + 5: + self._send_sd_progress_query(withType=True) + # SD file list if self._receivingSdFileList and not RepRapProtocol.MESSAGE_SD_END_FILE_LIST(message): fileinfo = message.strip().split(None, 2) @@ -383,29 +423,6 @@ class RepRapProtocol(Protocol): self._startSeen = True self._clear_for_send.set() - if self._state == State.CONNECTED and self._startSeen and RepRapProtocol.MESSAGE_OK(message): - # if we are currently connected, have seen start and just gotten an "ok" we are now operational - self._changeState(State.OPERATIONAL) - - # resend == roll back time a bit - if RepRapProtocol.MESSAGE_RESEND(message): - self._handle_resend_request(message) - - # ok == go ahead with sending - if RepRapProtocol.MESSAGE_OK(message): - if self.is_heating_up(): - self._heatupDone() - - if self.is_printing() or self.is_streaming(): - if not self.is_sd_printing() and not self._state == State.PAUSED: - self._send_next() - if self._resend_delta is None and not self.is_streaming(): - if time.time() > self._lastTemperatureUpdate + 5: - self._send_temperature_query(withType=True) - elif self.is_sd_printing() and time.time() > self._lastSdProgressUpdate + 5: - self._send_sd_progress_query(withType=True) - self._clear_for_send.set() - def onTimeoutReceived(self, source): if self._transport != source: return @@ -422,7 +439,7 @@ class RepRapProtocol(Protocol): def _send(self, command, highPriority=False, commandType=None, withChecksum=None, withLinenumber=None): if withChecksum is None: - withChecksum = self._useChecksum() + withChecksum = self._force_checksum commandTuple = (command, withChecksum, withLinenumber) priority = 100 @@ -433,6 +450,7 @@ class RepRapProtocol(Protocol): else: self._send_queue.put((priority, commandTuple)) + # Called only from worker thread, not thread safe def _send_next(self): if self._resend_delta is not None: command = self._last_lines[-self._resend_delta] @@ -569,7 +587,10 @@ class RepRapProtocol(Protocol): self._resend_delta = None def _useChecksum(self): - return settings().getBoolean(["feature", "alwaysSendChecksum"]) or (not self._transport_properties[TransportProperties.FLOWCONTROL] and self.is_busy()) + return settings().getBoolean(["feature", "alwaysSendChecksum"]) + + def _usePingPong(self): + return settings().getBoolean(["feature", "pingPong"]) ##~~ specific command actions @@ -640,7 +661,15 @@ class RepRapProtocol(Protocol): def _handle_send_queue(self): while self._send_queue_processing: self._clear_for_send.wait() - self._clear_for_send.clear() + if self.is_printing() or self.is_streaming(): + if not self.is_sd_printing() and not self._state == State.PAUSED: + self._send_next() + + if self._send_queue.qsize() == 0: + time.sleep(0.01) + continue + if self._pingpong == True: + self._clear_for_send.clear() entry = self._send_queue.get() if len(entry) == 3: priority, command_tuple, command_type = entry @@ -652,14 +681,26 @@ class RepRapProtocol(Protocol): command = GcodeCommand.from_line(command) preprocessed_command = self._preprocess_command(command, with_checksum=with_checksum, with_line_number=with_line_number) - if preprocessed_command is not None: - self._transport.send(preprocessed_command) + size = len(preprocessed_command) + 1 + # Wait for space in cache if needed. This uses _clear_for_send to wait for an "ok" + self._nack_lock.acquire() + while self._rx_cache_fill + size > self._rx_cache_size: + if not self._send_queue_processing: + self._nack_lock.release() + return + self._clear_for_send.clear() + self._nack_lock.release() + self._clear_for_send.wait() + self._nack_lock.acquire() + # Put tx size into the list so we can pop it later when we get "ok" + self._nack_lines.append(size) + self._rx_cache_fill += size + self._nack_lock.release() + self._transport.send(preprocessed_command) + + if with_checksum: + self._last_lines.append(command) - if with_checksum: - self._last_lines.append(command) - else: - # reset clear for send flag, since we didn't send a command just now - self._clear_for_send.set() def _preprocess_command(self, command, with_checksum=False, with_line_number=None): if command is None: diff --git a/src/octoprint/server/api/settings.py b/src/octoprint/server/api/settings.py index 63ed8eb..5ace60e 100644 --- a/src/octoprint/server/api/settings.py +++ b/src/octoprint/server/api/settings.py @@ -66,6 +66,7 @@ def getSettings(): "temperatureGraph": s.getBoolean(["feature", "temperatureGraph"]), "waitForStart": s.getBoolean(["feature", "waitForStartOnConnect"]), "alwaysSendChecksum": s.getBoolean(["feature", "alwaysSendChecksum"]), + "pingPong": s.getBoolean(["feature", "pingPong"]), "sdSupport": s.getBoolean(["feature", "sdSupport"]), "sdAlwaysAvailable": s.getBoolean(["feature", "sdAlwaysAvailable"]), "swallowOkAfterResend": s.getBoolean(["feature", "swallowOkAfterResend"]), @@ -163,6 +164,7 @@ def setSettings(): if "temperatureGraph" in data["feature"].keys(): s.setBoolean(["feature", "temperatureGraph"], data["feature"]["temperatureGraph"]) if "waitForStart" in data["feature"].keys(): s.setBoolean(["feature", "waitForStartOnConnect"], data["feature"]["waitForStart"]) if "alwaysSendChecksum" in data["feature"].keys(): s.setBoolean(["feature", "alwaysSendChecksum"], data["feature"]["alwaysSendChecksum"]) + if "pingPong" in data["feature"].keys(): s.setBoolean(["feature", "pingPong"], data["feature"]["pingPong"]) if "sdSupport" in data["feature"].keys(): s.setBoolean(["feature", "sdSupport"], data["feature"]["sdSupport"]) if "sdAlwaysAvailable" in data["feature"].keys(): s.setBoolean(["feature", "sdAlwaysAvailable"], data["feature"]["sdAlwaysAvailable"]) if "swallowOkAfterResend" in data["feature"].keys(): s.setBoolean(["feature", "swallowOkAfterResend"], data["feature"]["swallowOkAfterResend"]) diff --git a/src/octoprint/settings.py b/src/octoprint/settings.py index 75aff7d..783a674 100644 --- a/src/octoprint/settings.py +++ b/src/octoprint/settings.py @@ -99,6 +99,7 @@ default_settings = { "temperatureGraph": True, "waitForStartOnConnect": False, "alwaysSendChecksum": False, + "pingPong": False, "sdSupport": True, "sdAlwaysAvailable": False, "swallowOkAfterResend": True, diff --git a/src/octoprint/static/js/app/viewmodels/settings.js b/src/octoprint/static/js/app/viewmodels/settings.js index 909be61..7bbf3b6 100644 --- a/src/octoprint/static/js/app/viewmodels/settings.js +++ b/src/octoprint/static/js/app/viewmodels/settings.js @@ -134,6 +134,7 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) { self.feature_temperatureGraph = ko.observable(undefined); self.feature_waitForStart = ko.observable(undefined); self.feature_alwaysSendChecksum = ko.observable(undefined); + self.feature_pingPong = ko.observable(undefined); self.feature_sdSupport = ko.observable(undefined); self.feature_sdAlwaysAvailable = ko.observable(undefined); self.feature_swallowOkAfterResend = ko.observable(undefined); @@ -258,6 +259,7 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) { self.feature_temperatureGraph(response.feature.temperatureGraph); self.feature_waitForStart(response.feature.waitForStart); self.feature_alwaysSendChecksum(response.feature.alwaysSendChecksum); + self.feature_pingPong(response.feature.pingPong); self.feature_sdSupport(response.feature.sdSupport); self.feature_sdAlwaysAvailable(response.feature.sdAlwaysAvailable); self.feature_swallowOkAfterResend(response.feature.swallowOkAfterResend); @@ -330,6 +332,7 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) { "temperatureGraph": self.feature_temperatureGraph(), "waitForStart": self.feature_waitForStart(), "alwaysSendChecksum": self.feature_alwaysSendChecksum(), + "pingPong": self.feature_pingPong(), "sdSupport": self.feature_sdSupport(), "sdAlwaysAvailable": self.feature_sdAlwaysAvailable(), "swallowOkAfterResend": self.feature_swallowOkAfterResend(), diff --git a/src/octoprint/templates/settings.jinja2 b/src/octoprint/templates/settings.jinja2 index fee8074..fbeffdc 100644 --- a/src/octoprint/templates/settings.jinja2 +++ b/src/octoprint/templates/settings.jinja2 @@ -294,7 +294,14 @@
+
+
+
+
+