commit
79f2cd0e31
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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"])));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 -->
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in New Issue