Merge branch 'devel' into events

Conflicts:
	octoprint/util/comm.py
master
Gina Häußge 2013-06-22 15:08:53 +02:00
commit 79f2cd0e31
6 changed files with 307 additions and 213 deletions

View File

@ -129,6 +129,11 @@ class Printer():
try: callback.sendUpdateTrigger(type) try: callback.sendUpdateTrigger(type)
except: pass except: pass
def _sendFeedbackCommandOutput(self, name, output):
for callback in self._callbacks:
try: callback.sendFeedbackCommandOutput(name, output)
except: pass
#~~ printer commands #~~ printer commands
def connect(self, port=None, baudrate=None): def connect(self, port=None, baudrate=None):
@ -435,6 +440,9 @@ class Printer():
self._setProgressData(None, None, None, None) self._setProgressData(None, None, None, None)
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()}) self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
def mcReceivedRegisteredMessage(self, command, output):
self._sendFeedbackCommandOutput(command, output)
#~~ sd file handling #~~ sd file handling
def getSdFiles(self): def getSdFiles(self):

View File

@ -107,6 +107,9 @@ class PrinterStateConnection(tornadio2.SocketConnection):
def sendUpdateTrigger(self, type): def sendUpdateTrigger(self, type):
self.emit("updateTrigger", type) self.emit("updateTrigger", type)
def sendFeedbackCommandOutput(self, name, output):
self.emit("feedbackCommandOutput", {"name": name, "output": output})
def addLog(self, data): def addLog(self, data):
with self._logBacklogMutex: with self._logBacklogMutex:
self._logBacklog.append(data) self._logBacklog.append(data)

View File

@ -2,11 +2,11 @@
__author__ = "Gina Häußge <osd@foosel.net>" __author__ = "Gina Häußge <osd@foosel.net>"
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
import ConfigParser
import sys import sys
import os import os
import yaml import yaml
import logging import logging
import re
APPNAME="OctoPrint" APPNAME="OctoPrint"
@ -213,6 +213,29 @@ class Settings(object):
return folder return folder
def getFeedbackControls(self):
feedbackControls = []
for control in self.get(["controls"]):
feedbackControls.extend(self._getFeedbackControls(control))
return feedbackControls
def _getFeedbackControls(self, control=None):
if control["type"] == "feedback_command":
pattern = control["regex"]
try:
matcher = re.compile(pattern)
return [(control["name"], matcher, control["template"])]
except:
# invalid regex or something like this, we'll just skip this entry
pass
elif control["type"] == "section":
result = []
for c in control["children"]:
result.extend(self._getFeedbackControls(c))
return result
else:
return []
#~~ setter #~~ setter
def set(self, path, value, force=False): def set(self, path, value, force=False):

View File

@ -525,6 +525,8 @@ function ControlViewModel(loginStateViewModel) {
self.extrusionAmount = ko.observable(undefined); self.extrusionAmount = ko.observable(undefined);
self.controls = ko.observableArray([]); self.controls = ko.observableArray([]);
self.feedbackControlLookup = {};
self.fromCurrentData = function(data) { self.fromCurrentData = function(data) {
self._processStateData(data.state); self._processStateData(data.state);
} }
@ -543,6 +545,12 @@ function ControlViewModel(loginStateViewModel) {
self.isLoading(data.flags.loading); self.isLoading(data.flags.loading);
} }
self.fromFeedbackCommandData = function(data) {
if (data.name in self.feedbackControlLookup) {
self.feedbackControlLookup[data.name](data.output);
}
}
self.requestData = function() { self.requestData = function() {
$.ajax({ $.ajax({
url: AJAX_BASEURL + "control/custom", url: AJAX_BASEURL + "control/custom",
@ -555,23 +563,26 @@ function ControlViewModel(loginStateViewModel) {
} }
self._fromResponse = function(response) { self._fromResponse = function(response) {
self.controls(self._enhanceControls(response.controls)); self.controls(self._processControls(response.controls));
} }
self._enhanceControls = function(controls) { self._processControls = function(controls) {
for (var i = 0; i < controls.length; i++) { for (var i = 0; i < controls.length; i++) {
controls[i] = self._enhanceControl(controls[i]); controls[i] = self._processControl(controls[i]);
} }
return controls; return controls;
} }
self._enhanceControl = function(control) { self._processControl = function(control) {
if (control.type == "parametric_command" || control.type == "parametric_commands") { if (control.type == "parametric_command" || control.type == "parametric_commands") {
for (var i = 0; i < control.input.length; i++) { for (var i = 0; i < control.input.length; i++) {
control.input[i].value = control.input[i].default; control.input[i].value = control.input[i].default;
} }
} else if (control.type == "feedback_command") {
control.output = ko.observable("");
self.feedbackControlLookup[control.name] = control.output;
} else if (control.type == "section") { } else if (control.type == "section") {
control.children = self._enhanceControls(control.children); control.children = self._processControls(control.children);
} }
return control; return control;
} }
@ -621,7 +632,7 @@ function ControlViewModel(loginStateViewModel) {
return; return;
var data = undefined; var data = undefined;
if (command.type == "command" || command.type == "parametric_command") { if (command.type == "command" || command.type == "parametric_command" || command.type == "feedback_command") {
// single command // single command
data = {"command" : command.command}; data = {"command" : command.command};
} else if (command.type == "commands" || command.type == "parametric_commands") { } else if (command.type == "commands" || command.type == "parametric_commands") {
@ -659,6 +670,8 @@ function ControlViewModel(loginStateViewModel) {
case "parametric_command": case "parametric_command":
case "parametric_commands": case "parametric_commands":
return "customControls_parametricCommandTemplate"; return "customControls_parametricCommandTemplate";
case "feedback_command":
return "customControls_feedbackCommandTemplate";
default: default:
return "customControls_emptyTemplate"; return "customControls_emptyTemplate";
} }
@ -1487,7 +1500,7 @@ function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewM
self.loginStateViewModel.requestData(); self.loginStateViewModel.requestData();
self.gcodeFilesViewModel.requestData(); self.gcodeFilesViewModel.requestData();
} }
}) });
self._socket.on("disconnect", function() { self._socket.on("disconnect", function() {
$("#offline_overlay_message").html( $("#offline_overlay_message").html(
"The server appears to be offline, at least I'm not getting any response from it. I'll try to reconnect " + "The server appears to be offline, at least I'm not getting any response from it. I'll try to reconnect " +
@ -1496,13 +1509,13 @@ function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewM
); );
if (!$("#offline_overlay").is(":visible")) if (!$("#offline_overlay").is(":visible"))
$("#offline_overlay").show(); $("#offline_overlay").show();
}) });
self._socket.on("reconnect_failed", function() { self._socket.on("reconnect_failed", function() {
$("#offline_overlay_message").html( $("#offline_overlay_message").html(
"The server appears to be offline, at least I'm not getting any response from it. I <strong>could not reconnect automatically</strong>, " + "The server appears to be offline, at least I'm not getting any response from it. I <strong>could not reconnect automatically</strong>, " +
"but you may try a manual reconnect using the button below." "but you may try a manual reconnect using the button below."
); );
}) });
self._socket.on("history", function(data) { self._socket.on("history", function(data) {
self.connectionViewModel.fromHistoryData(data); self.connectionViewModel.fromHistoryData(data);
self.printerStateViewModel.fromHistoryData(data); self.printerStateViewModel.fromHistoryData(data);
@ -1512,7 +1525,7 @@ function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewM
self.timelapseViewModel.fromHistoryData(data); self.timelapseViewModel.fromHistoryData(data);
self.gcodeViewModel.fromHistoryData(data); self.gcodeViewModel.fromHistoryData(data);
self.gcodeFilesViewModel.fromCurrentData(data); self.gcodeFilesViewModel.fromCurrentData(data);
}) });
self._socket.on("current", function(data) { self._socket.on("current", function(data) {
self.connectionViewModel.fromCurrentData(data); self.connectionViewModel.fromCurrentData(data);
self.printerStateViewModel.fromCurrentData(data); self.printerStateViewModel.fromCurrentData(data);
@ -1522,14 +1535,17 @@ function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewM
self.timelapseViewModel.fromCurrentData(data); self.timelapseViewModel.fromCurrentData(data);
self.gcodeViewModel.fromCurrentData(data); self.gcodeViewModel.fromCurrentData(data);
self.gcodeFilesViewModel.fromCurrentData(data); self.gcodeFilesViewModel.fromCurrentData(data);
}) });
self._socket.on("updateTrigger", function(type) { self._socket.on("updateTrigger", function(type) {
if (type == "gcodeFiles") { if (type == "gcodeFiles") {
gcodeFilesViewModel.requestData(); gcodeFilesViewModel.requestData();
} else if (type == "timelapseFiles") { } else if (type == "timelapseFiles") {
timelapseViewModel.requestData(); timelapseViewModel.requestData();
} }
}) });
self._socket.on("feedbackCommandOutput", function(data) {
self.controlViewModel.fromFeedbackCommandData(data);
});
self.reconnect = function() { self.reconnect = function() {
self._socket.socket.connect(); self._socket.socket.connect();
@ -1765,7 +1781,7 @@ function ItemListHelper(listType, supportedSorting, supportedFilters, defaultSor
self._loadCurrentFiltersFromLocalStorage = function() { self._loadCurrentFiltersFromLocalStorage = function() {
if ( self._initializeLocalStorage() ) { if ( self._initializeLocalStorage() ) {
self.currentFilters(_.intersection(_.keys(self.supportedFilters), JSON.parse(localStorage[self.listType + "." + "currentFilters"]))); self.currentFilters(_.intersection(_.keys(self.supportedFilters), JSON.parse(localStorage[self.listType + "." + "currentFilters"])));
} }
} }

View File

@ -390,6 +390,11 @@
<button class="btn" data-bind="text: name, enable: $root.isOperational() && $root.loginState.isUser(), click: function() { $root.sendCustomCommand($data) }"></button> <button class="btn" data-bind="text: name, enable: $root.isOperational() && $root.loginState.isUser(), click: function() { $root.sendCustomCommand($data) }"></button>
</form> </form>
</script> </script>
<script type="text/html" id="customControls_feedbackCommandTemplate">
<form class="form-inline">
<button class="btn" data-bind="text: name, enable: $root.isOperational() && $root.loginState.isUser(), click: function() { $root.sendCustomCommand($data) }"></button> <span data-bind="text: output"></span>
</form>
</script>
<script type="text/html" id="customControls_parametricCommandTemplate"> <script type="text/html" id="customControls_parametricCommandTemplate">
<form class="form-inline"> <form class="form-inline">
<!-- ko foreach: input --> <!-- ko foreach: input -->

View File

@ -41,7 +41,13 @@ def serialList():
i+=1 i+=1
except: except:
pass pass
baselist = baselist + glob.glob("/dev/ttyUSB*") + glob.glob("/dev/ttyACM*") + glob.glob("/dev/tty.usb*") + glob.glob("/dev/cu.*") + glob.glob("/dev/rfcomm*") baselist = baselist \
+ glob.glob("/dev/ttyUSB*") \
+ glob.glob("/dev/ttyACM*") \
+ glob.glob("/dev/ttyAMA*") \
+ glob.glob("/dev/tty.usb*") \
+ glob.glob("/dev/cu.*") \
+ glob.glob("/dev/rfcomm*")
prev = settings().get(["serial", "port"]) prev = settings().get(["serial", "port"])
if prev in baselist: if prev in baselist:
baselist.remove(prev) baselist.remove(prev)
@ -162,6 +168,12 @@ class VirtualPrinter():
# reset current line # reset current line
self.currentLine = int(re.search('N([0-9]+)', data).group(1)) self.currentLine = int(re.search('N([0-9]+)', data).group(1))
self.readList.append("ok\n") self.readList.append("ok\n")
elif "M114" in data:
# send dummy position report
self.readList.append("ok C: X:10.00 Y:3.20 Z:5.20 E:1.24")
elif "M999" in data:
# mirror Marlin behaviour
self.readList.append("Resend: 1")
elif self.currentLine == 100: elif self.currentLine == 100:
# simulate a resend at line 100 of the last 5 lines # simulate a resend at line 100 of the last 5 lines
self.readList.append("Error: Line Number is not Last Line Number\n") self.readList.append("Error: Line Number is not Last Line Number\n")
@ -333,6 +345,9 @@ class MachineComPrintCallback(object):
def mcFileTransferStarted(self, filename, filesize): def mcFileTransferStarted(self, filename, filesize):
pass pass
def mcReceivedRegisteredMessage(self, command, message):
pass
class MachineCom(object): class MachineCom(object):
STATE_NONE = 0 STATE_NONE = 0
STATE_OPEN_SERIAL = 1 STATE_OPEN_SERIAL = 1
@ -531,6 +546,8 @@ class MachineCom(object):
return ret return ret
def _monitor(self): def _monitor(self):
feedbackControls = settings().getFeedbackControls()
#Open the serial port. #Open the serial port.
if self._port == 'AUTO': if self._port == 'AUTO':
self._changeState(self.STATE_DETECT_SERIAL) self._changeState(self.STATE_DETECT_SERIAL)
@ -580,150 +597,173 @@ class MachineCom(object):
startSeen = not settings().getBoolean(["feature", "waitForStartOnConnect"]) startSeen = not settings().getBoolean(["feature", "waitForStartOnConnect"])
heatingUp = False heatingUp = False
while True: while True:
line = self._readline() try:
if line == None: line = self._readline()
break if line == None:
break
##~~ Error handling ##~~ Error handling
# No matter the state, if we see an error, goto the error state and store the error for reference. # No matter the state, if we see an error, goto the error state and store the error for reference.
if line.startswith('Error:'): if line.startswith('Error:'):
#Oh YEAH, consistency. #Oh YEAH, consistency.
# Marlin reports an MIN/MAX temp error as "Error:x\n: Extruder switched off. MAXTEMP triggered !\n" # Marlin reports an MIN/MAX temp error as "Error:x\n: Extruder switched off. MAXTEMP triggered !\n"
# But a bed temp error is reported as "Error: Temperature heated bed switched off. MAXTEMP triggered !!" # But a bed temp error is reported as "Error: Temperature heated bed switched off. MAXTEMP triggered !!"
# So we can have an extra newline in the most common case. Awesome work people. # So we can have an extra newline in the most common case. Awesome work people.
if re.match('Error:[0-9]\n', line): if re.match('Error:[0-9]\n', line):
line = line.rstrip() + self._readline() line = line.rstrip() + self._readline()
#Skip the communication errors, as those get corrected. #Skip the communication errors, as those get corrected.
if 'checksum mismatch' in line \ if 'checksum mismatch' in line \
or 'Wrong checksum' in line \ or 'Wrong checksum' in line \
or 'Line Number is not Last Line Number' in line \ or 'Line Number is not Last Line Number' in line \
or 'expected line' in line \ or 'expected line' in line \
or 'No Line Number with checksum' in line \ or 'No Line Number with checksum' in line \
or 'No Checksum with line number' in line \ or 'No Checksum with line number' in line \
or 'Missing checksum' in line: or 'Missing checksum' in line:
pass pass
elif not self.isError(): elif not self.isError():
if self.isPrinting(): self._errorValue = line[6:]
eventManager().fire("PrintFailed")
self._errorValue = line[6:]
self._changeState(self.STATE_ERROR)
eventManager().fire("Error", self.getErrorString())
##~~ SD file list
# if we are currently receiving an sd file list, each line is just a filename, so just read it and abort processing
if self._sdFileList and not 'End file list' in line:
self._sdFiles.append(line.strip().lower())
continue
##~~ Temperature processing
if ' T:' in line or line.startswith('T:'):
try:
self._temp = float(re.search("-?[0-9\.]*", line.split('T:')[1]).group(0))
if ' B:' in line:
self._bedTemp = float(re.search("-?[0-9\.]*", line.split(' B:')[1]).group(0))
self._callback.mcTempUpdate(self._temp, self._bedTemp, self._targetTemp, self._bedTargetTemp)
except ValueError:
# catch conversion issues, we'll rather just not get the temperature update instead of killing the connection
pass
#If we are waiting for an M109 or M190 then measure the time we lost during heatup, so we can remove that time from our printing time estimate.
if not 'ok' in line:
heatingUp = True
if self._heatupWaitStartTime != 0:
t = time.time()
self._heatupWaitTimeLost = t - self._heatupWaitStartTime
self._heatupWaitStartTime = t
##~~ SD Card handling
elif 'SD init fail' in line:
self._sdAvailable = False
self._sdFiles = []
self._callback.mcSdStateChange(self._sdAvailable)
elif 'Not SD printing' in line:
if self.isSdFileSelected() and self.isPrinting():
# something went wrong, printer is reporting that we actually are not printing right now...
self._sdFilePos = 0
self._changeState(self.STATE_OPERATIONAL)
elif 'SD card ok' in line:
self._sdAvailable = True
self.refreshSdFiles()
self._callback.mcSdStateChange(self._sdAvailable)
elif 'Begin file list' in line:
self._sdFiles = []
self._sdFileList = True
elif 'End file list' in line:
self._sdFileList = False
self._callback.mcSdFiles(self._sdFiles)
elif 'SD printing byte' in line:
# answer to M27, at least on Marlin, Repetier and Sprinter: "SD printing byte %d/%d"
match = re.search("([0-9]*)/([0-9]*)", line)
self._currentFile.setFilepos(int(match.group(1)))
self._callback.mcProgress()
elif 'File opened' in line:
# answer to M23, at least on Marlin, Repetier and Sprinter: "File opened:%s Size:%d"
match = re.search("File opened:\s*(.*?)\s+Size:\s*([0-9]*)", line)
self._currentFile = PrintingSdFileInformation(match.group(1), int(match.group(2)))
elif 'File selected' in line:
# final answer to M23, at least on Marlin, Repetier and Sprinter: "File selected"
self._callback.mcFileSelected(self._currentFile.getFilename(), self._currentFile.getFilesize(), True)
eventManager().fire("FileSelected", self._currentFile.getFilename())
elif 'Writing to file' in line:
# anwer to M28, at least on Marlin, Repetier and Sprinter: "Writing to file: %s"
self._printSection = "CUSTOM"
self._changeState(self.STATE_PRINTING)
elif 'Done printing file' in line:
# printer is reporting file finished printing
self._sdFilePos = 0
self._callback.mcPrintjobDone()
self._changeState(self.STATE_OPERATIONAL)
eventManager().fire("PrintDone")
##~~ Message handling
elif line.strip() != '' and line.strip() != 'ok' and not line.startswith("wait") and not line.startswith('Resend:') and line != 'echo:Unknown command:""\n' and self.isOperational():
self._callback.mcMessage(line)
if "ok" in line and heatingUp:
heatingUp = False
### Baudrate detection
if self._state == self.STATE_DETECT_BAUDRATE:
if line == '' or time.time() > timeout:
if len(self._baudrateDetectList) < 1:
self.close()
self._errorValue = "No more baudrates to test, and no suitable baudrate found."
self._changeState(self.STATE_ERROR) self._changeState(self.STATE_ERROR)
eventManager().fire("Error", self.getErrorString()) eventManager().fire("Error", self.getErrorString())
elif self._baudrateDetectRetry > 0:
self._baudrateDetectRetry -= 1 ##~~ SD file list
self._serial.write('\n') # if we are currently receiving an sd file list, each line is just a filename, so just read it and abort processing
self._log("Baudrate test retry: %d" % (self._baudrateDetectRetry)) if self._sdFileList and not 'End file list' in line:
self._sendCommand("M105") self._sdFiles.append(line.strip().lower())
self._testingBaudrate = True continue
else:
baudrate = self._baudrateDetectList.pop(0) ##~~ Temperature processing
if ' T:' in line or line.startswith('T:'):
try:
self._temp = float(re.search("-?[0-9\.]*", line.split('T:')[1]).group(0))
if ' B:' in line:
self._bedTemp = float(re.search("-?[0-9\.]*", line.split(' B:')[1]).group(0))
self._callback.mcTempUpdate(self._temp, self._bedTemp, self._targetTemp, self._bedTargetTemp)
except ValueError:
# catch conversion issues, we'll rather just not get the temperature update instead of killing the connection
pass
#If we are waiting for an M109 or M190 then measure the time we lost during heatup, so we can remove that time from our printing time estimate.
if not 'ok' in line:
heatingUp = True
if self._heatupWaitStartTime != 0:
t = time.time()
self._heatupWaitTimeLost = t - self._heatupWaitStartTime
self._heatupWaitStartTime = t
##~~ SD Card handling
elif 'SD init fail' in line:
self._sdAvailable = False
self._sdFiles = []
self._callback.mcSdStateChange(self._sdAvailable)
elif 'Not SD printing' in line:
if self.isSdFileSelected() and self.isPrinting():
# something went wrong, printer is reporting that we actually are not printing right now...
self._sdFilePos = 0
self._changeState(self.STATE_OPERATIONAL)
elif 'SD card ok' in line:
self._sdAvailable = True
self.refreshSdFiles()
self._callback.mcSdStateChange(self._sdAvailable)
elif 'Begin file list' in line:
self._sdFiles = []
self._sdFileList = True
elif 'End file list' in line:
self._sdFileList = False
self._callback.mcSdFiles(self._sdFiles)
elif 'SD printing byte' in line:
# answer to M27, at least on Marlin, Repetier and Sprinter: "SD printing byte %d/%d"
match = re.search("([0-9]*)/([0-9]*)", line)
self._currentFile.setFilepos(int(match.group(1)))
self._callback.mcProgress()
elif 'File opened' in line:
# answer to M23, at least on Marlin, Repetier and Sprinter: "File opened:%s Size:%d"
match = re.search("File opened:\s*(.*?)\s+Size:\s*([0-9]*)", line)
self._currentFile = PrintingSdFileInformation(match.group(1), int(match.group(2)))
elif 'File selected' in line:
# final answer to M23, at least on Marlin, Repetier and Sprinter: "File selected"
self._callback.mcFileSelected(self._currentFile.getFilename(), self._currentFile.getFilesize(), True)
eventManager().fire("FileSelected", self._currentFile.getFilename())
elif 'Writing to file' in line:
# anwer to M28, at least on Marlin, Repetier and Sprinter: "Writing to file: %s"
self._printSection = "CUSTOM"
self._changeState(self.STATE_PRINTING)
elif 'Done printing file' in line:
# printer is reporting file finished printing
self._sdFilePos = 0
self._callback.mcPrintjobDone()
self._changeState(self.STATE_OPERATIONAL)
eventManager().fire("PrintDone")
##~~ Message handling
elif line.strip() != '' and line.strip() != 'ok' and not line.startswith("wait") and not line.startswith('Resend:') and line != 'echo:Unknown command:""\n' and self.isOperational():
self._callback.mcMessage(line)
##~~ Parsing for feedback commands
if feedbackControls:
for name, matcher, template in feedbackControls:
try: try:
self._serial.baudrate = baudrate match = matcher.search(line)
self._serial.timeout = 0.5 if match is not None:
self._log("Trying baudrate: %d" % (baudrate)) self._callback.mcReceivedRegisteredMessage(name, str.format(template, *(match.groups("n/a"))))
self._baudrateDetectRetry = 5 except:
self._baudrateDetectTestOk = 0 # ignored on purpose
timeout = time.time() + 5 pass
if "ok" in line and heatingUp:
heatingUp = False
### Baudrate detection
if self._state == self.STATE_DETECT_BAUDRATE:
if line == '' or time.time() > timeout:
if len(self._baudrateDetectList) < 1:
self.close()
self._errorValue = "No more baudrates to test, and no suitable baudrate found."
self._changeState(self.STATE_ERROR)
eventManager().fire("Error", self.getErrorString())
elif self._baudrateDetectRetry > 0:
self._baudrateDetectRetry -= 1
self._serial.write('\n') self._serial.write('\n')
self._log("Baudrate test retry: %d" % (self._baudrateDetectRetry))
self._sendCommand("M105") self._sendCommand("M105")
self._testingBaudrate = True self._testingBaudrate = True
except: else:
self._log("Unexpected error while setting baudrate: %d %s" % (baudrate, getExceptionString())) baudrate = self._baudrateDetectList.pop(0)
elif 'ok' in line and 'T:' in line: try:
self._baudrateDetectTestOk += 1 self._serial.baudrate = baudrate
if self._baudrateDetectTestOk < 10: self._serial.timeout = 0.5
self._log("Baudrate test ok: %d" % (self._baudrateDetectTestOk)) self._log("Trying baudrate: %d" % (baudrate))
self._sendCommand("M105") self._baudrateDetectRetry = 5
self._baudrateDetectTestOk = 0
timeout = time.time() + 5
self._serial.write('\n')
self._sendCommand("M105")
self._testingBaudrate = True
except:
self._log("Unexpected error while setting baudrate: %d %s" % (baudrate, getExceptionString()))
elif 'ok' in line and 'T:' in line:
self._baudrateDetectTestOk += 1
if self._baudrateDetectTestOk < 10:
self._log("Baudrate test ok: %d" % (self._baudrateDetectTestOk))
self._sendCommand("M105")
else:
self._sendCommand("M999")
self._serial.timeout = 2
self._changeState(self.STATE_OPERATIONAL)
if self._sdAvailable:
self.refreshSdFiles()
eventManager().fire("Connected", "%s at %s baud" % (self._port, self._baudrate))
else: else:
self._sendCommand("M999") self._testingBaudrate = False
self._serial.timeout = 2
### Connection attempt
elif self._state == self.STATE_CONNECTING:
if (line == "" or "wait" in line) and startSeen:
self._sendCommand("M105")
elif "start" in line:
startSeen = True
elif "ok" in line and startSeen:
self._changeState(self.STATE_OPERATIONAL) self._changeState(self.STATE_OPERATIONAL)
if self._sdAvailable: if self._sdAvailable:
self.refreshSdFiles() self.refreshSdFiles()
@ -731,68 +771,61 @@ class MachineCom(object):
else: else:
self._testingBaudrate = False self._testingBaudrate = False
### Connection attempt ### Operational
elif self._state == self.STATE_CONNECTING: elif self._state == self.STATE_OPERATIONAL or self._state == self.STATE_PAUSED:
if (line == "" or "wait" in line) and startSeen: #Request the temperature on comm timeout (every 5 seconds) when we are not printing.
self._sendCommand("M105") if line == "" or "wait" in line:
elif "start" in line:
startSeen = True
elif "ok" in line and startSeen:
self._changeState(self.STATE_OPERATIONAL)
if self._sdAvailable:
self.refreshSdFiles()
eventManager().fire("Connected", "%s at %s baud" % (self._port, self._baudrate))
elif time.time() > timeout:
self.close()
### Operational
elif self._state == self.STATE_OPERATIONAL or self._state == self.STATE_PAUSED:
#Request the temperature on comm timeout (every 5 seconds) when we are not printing.
if line == "" or "wait" in line:
if self._resendDelta is not None:
self._resendNextCommand()
elif not self._commandQueue.empty():
self._sendCommand(self._commandQueue.get())
else:
self._sendCommand("M105")
tempRequestTimeout = time.time() + 5
# resend -> start resend procedure from requested line
elif "resend" in line.lower() or "rs" in line:
self._handleResendRequest(line)
### Printing
elif self._state == self.STATE_PRINTING:
if line == "" and time.time() > timeout:
self._log("Communication timeout during printing, forcing a line")
line = 'ok'
if self.isSdPrinting():
if time.time() > tempRequestTimeout and not heatingUp:
self._sendCommand("M105")
tempRequestTimeout = time.time() + 5
if time.time() > sdStatusRequestTimeout and not heatingUp:
self._sendCommand("M27")
sdStatusRequestTimeout = time.time() + 1
if 'ok' or 'SD printing byte' in line:
timeout = time.time() + 5
else:
# Even when printing request the temperature every 5 seconds.
if time.time() > tempRequestTimeout and not self.isStreaming():
self._commandQueue.put("M105")
tempRequestTimeout = time.time() + 5
if 'ok' in line:
timeout = time.time() + 5
if self._resendDelta is not None: if self._resendDelta is not None:
self._resendNextCommand() self._resendNextCommand()
elif not self._commandQueue.empty() and not self.isStreaming(): elif not self._commandQueue.empty():
self._sendCommand(self._commandQueue.get()) self._sendCommand(self._commandQueue.get())
else: else:
self._sendNext() self._sendCommand("M105")
tempRequestTimeout = time.time() + 5
# resend -> start resend procedure from requested line
elif "resend" in line.lower() or "rs" in line: elif "resend" in line.lower() or "rs" in line:
self._handleResendRequest(line) self._handleResendRequest(line)
### Printing
elif self._state == self.STATE_PRINTING:
if line == "" and time.time() > timeout:
self._log("Communication timeout during printing, forcing a line")
line = 'ok'
if self.isSdPrinting():
if time.time() > tempRequestTimeout and not heatingUp:
self._sendCommand("M105")
tempRequestTimeout = time.time() + 5
if time.time() > sdStatusRequestTimeout and not heatingUp:
self._sendCommand("M27")
sdStatusRequestTimeout = time.time() + 1
if 'ok' or 'SD printing byte' in line:
timeout = time.time() + 5
else:
# Even when printing request the temperature every 5 seconds.
if time.time() > tempRequestTimeout and not self.isStreaming():
self._commandQueue.put("M105")
tempRequestTimeout = time.time() + 5
if 'ok' in line:
timeout = time.time() + 5
if self._resendDelta is not None:
self._resendNextCommand()
elif not self._commandQueue.empty() and not self.isStreaming():
self._sendCommand(self._commandQueue.get())
else:
self._sendNext()
elif "resend" in line.lower() or "rs" in line:
self._handleResendRequest(line)
except:
self._logger.exception("Something crashed inside the serial connection loop, please report this in OctoPrint's bug tracker:")
errorMsg = "Please see octoprint.log for details"
self._log(errorMsg)
self._errorValue = errorMsg
self._changeState(self.STATE_ERROR)
self._log("Connection closed, closing down monitor") self._log("Connection closed, closing down monitor")
def _handleResendRequest(self, line): def _handleResendRequest(self, line):
@ -806,9 +839,15 @@ class MachineCom(object):
if lineToResend is not None: if lineToResend is not None:
self._resendDelta = self._currentLine - lineToResend self._resendDelta = self._currentLine - lineToResend
if self._resendDelta >= len(self._lastLines): if self._resendDelta >= len(self._lastLines):
self._errorValue = "Printer requested line %d but history is only available up to line %d" % (lineToResend, self._currentLine - len(self._lastLines)) self._errorValue = "Printer requested line %d but no sufficient history is available, can't resend" % lineToResend
self._changeState(self.STATE_ERROR)
self._logger.warn(self._errorValue) self._logger.warn(self._errorValue)
if self.isPrinting():
# abort the print, there's nothing we can do to rescue it now
self._changeState(self.STATE_ERROR)
else:
# reset resend delta, we can't do anything about it
self._resendDelta = None
else: else:
self._resendNextCommand() self._resendNextCommand()
@ -843,7 +882,7 @@ class MachineCom(object):
def close(self, isError = False): def close(self, isError = False):
printing = self.isPrinting() or self.isPaused() printing = self.isPrinting() or self.isPaused()
if self._serial != None: if self._serial is not None:
self._serial.close() self._serial.close()
if isError: if isError:
self._changeState(self.STATE_CLOSED_WITH_ERROR) self._changeState(self.STATE_CLOSED_WITH_ERROR)
@ -865,13 +904,13 @@ class MachineCom(object):
# Make sure we are only handling one sending job at a time # Make sure we are only handling one sending job at a time
with self._sendingLock: with self._sendingLock:
self._logger.debug("Resending line %d, delta is %d, history log is %s items strong" % (self._currentLine - self._resendDelta, self._resendDelta, len(self._lastLines))) self._logger.debug("Resending line %d, delta is %d, history log is %s items strong" % (self._currentLine - self._resendDelta, self._resendDelta, len(self._lastLines)))
cmd = self._lastLines[-self._resendDelta] cmd = self._lastLines[-(self._resendDelta + 1)]
lineNumber = self._currentLine - self._resendDelta lineNumber = self._currentLine - self._resendDelta
self._doSendWithChecksum(cmd, lineNumber) self._doSendWithChecksum(cmd, lineNumber)
self._resendDelta -= 1 self._resendDelta -= 1
if self._resendDelta <= 0: if self._resendDelta < 0:
self._resendDelta = None self._resendDelta = None
def _sendCommand(self, cmd, sendChecksum=False): def _sendCommand(self, cmd, sendChecksum=False):
@ -1279,4 +1318,4 @@ class PrintingGcodeFileInformation(PrintingFileInformation):
return None return None
class StreamingGcodeFileInformation(PrintingGcodeFileInformation): class StreamingGcodeFileInformation(PrintingGcodeFileInformation):
pass pass