Merge branch 'repetier' into devel

master
Gina Häußge 2013-05-23 22:45:21 +02:00
commit 08229598e6
6 changed files with 250 additions and 75 deletions

View File

@ -215,7 +215,8 @@ class Printer():
self._setProgressData(None, None, None) self._setProgressData(None, None, None)
# mark print as failure # mark print as failure
self._gcodeManager.printFailed(self._filename) if self._filename is not None:
self._gcodeManager.printFailed(self._filename)
#~~ state monitoring #~~ state monitoring
@ -488,7 +489,7 @@ class GcodeLoader(threading.Thread):
def run(self): def run(self):
#Send an initial M110 to reset the line counter to zero. #Send an initial M110 to reset the line counter to zero.
prevLineType = lineType = "CUSTOM" prevLineType = lineType = "CUSTOM"
gcodeList = ["M110"] gcodeList = ["M110 N0"]
filesize = os.stat(self._filename).st_size filesize = os.stat(self._filename).st_size
with open(self._filename, "r") as file: with open(self._filename, "r") as file:
for line in file: for line in file:

View File

@ -380,7 +380,9 @@ def getSettings():
}, },
"feature": { "feature": {
"gcodeViewer": s.getBoolean(["feature", "gCodeVisualizer"]), "gcodeViewer": s.getBoolean(["feature", "gCodeVisualizer"]),
"waitForStart": s.getBoolean(["feature", "waitForStartOnConnect"]) "waitForStart": s.getBoolean(["feature", "waitForStartOnConnect"]),
"alwaysSendChecksum": s.getBoolean(["feature", "alwaysSendChecksum"]),
"resetLineNumbersWithPrefixedN": s.getBoolean(["feature", "resetLineNumbersWithPrefixedN"])
}, },
"folder": { "folder": {
"uploads": s.getBaseFolder("uploads"), "uploads": s.getBaseFolder("uploads"),
@ -424,6 +426,8 @@ def setSettings():
if "feature" in data.keys(): if "feature" in data.keys():
if "gcodeViewer" in data["feature"].keys(): s.setBoolean(["feature", "gCodeVisualizer"], data["feature"]["gcodeViewer"]) if "gcodeViewer" in data["feature"].keys(): s.setBoolean(["feature", "gCodeVisualizer"], data["feature"]["gcodeViewer"])
if "waitForStart" in data["feature"].keys(): s.setBoolean(["feature", "waitForStartOnConnect"], data["feature"]["waitForStart"]) 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 "resetLineNumbersWithPrefixedN" in data["feature"].keys(): s.setBoolean(["feature", "resetLineNumbersWithPrefixedN"], data["feature"]["resetLineNumbersWithPrefixedN"])
if "folder" in data.keys(): if "folder" in data.keys():
if "uploads" in data["folder"].keys(): s.setBaseFolder("uploads", data["folder"]["uploads"]) if "uploads" in data["folder"].keys(): s.setBaseFolder("uploads", data["folder"]["uploads"])

View File

@ -39,7 +39,10 @@ default_settings = {
}, },
"feature": { "feature": {
"gCodeVisualizer": True, "gCodeVisualizer": True,
"waitForStartOnConnect": False "waitForStartOnConnect": False,
"waitForWaitOnConnect": False,
"alwaysSendChecksum": False,
"resetLineNumbersWithPrefixedN": False
}, },
"folder": { "folder": {
"uploads": None, "uploads": None,

View File

@ -1267,6 +1267,8 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) {
self.feature_gcodeViewer = ko.observable(undefined); self.feature_gcodeViewer = ko.observable(undefined);
self.feature_waitForStart = ko.observable(undefined); self.feature_waitForStart = ko.observable(undefined);
self.feature_alwaysSendChecksum = ko.observable(undefined);
self.feature_resetLineNumbersWithPrefixedN = ko.observable(undefined);
self.folder_uploads = ko.observable(undefined); self.folder_uploads = ko.observable(undefined);
self.folder_timelapse = ko.observable(undefined); self.folder_timelapse = ko.observable(undefined);
@ -1311,6 +1313,8 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) {
self.feature_gcodeViewer(response.feature.gcodeViewer); self.feature_gcodeViewer(response.feature.gcodeViewer);
self.feature_waitForStart(response.feature.waitForStart); self.feature_waitForStart(response.feature.waitForStart);
self.feature_alwaysSendChecksum(response.feature.alwaysSendChecksum);
self.feature_resetLineNumbersWithPrefixedN(response.feature.resetLineNumbersWithPrefixedN);
self.folder_uploads(response.folder.uploads); self.folder_uploads(response.folder.uploads);
self.folder_timelapse(response.folder.timelapse); self.folder_timelapse(response.folder.timelapse);
@ -1343,7 +1347,9 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) {
}, },
"feature": { "feature": {
"gcodeViewer": self.feature_gcodeViewer(), "gcodeViewer": self.feature_gcodeViewer(),
"waitForStart": self.feature_waitForStart() "waitForStart": self.feature_waitForStart(),
"alwaysSendChecksum": self.feature_alwaysSendChecksum(),
"resetLineNumbersWithPrefixedN": self.feature_resetLineNumbersWithPrefixedN()
}, },
"folder": { "folder": {
"uploads": self.folder_uploads(), "uploads": self.folder_uploads(),

View File

@ -103,7 +103,21 @@
<div class="control-group"> <div class="control-group">
<div class="controls"> <div class="controls">
<label class="checkbox"> <label class="checkbox">
<input type="checkbox" data-bind="checked: feature_waitForStart" id="settings-featureWaitForStart"> Wait for start on connect <input type="checkbox" data-bind="checked: feature_waitForStart" id="settings-featureWaitForStart"> Wait for <code>start</code> on connect
</label>
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="checkbox">
<input type="checkbox" data-bind="checked: feature_alwaysSendChecksum" id="settings-featureAlwaysSendChecksum"> Send a checksum with <strong>every</strong> command <span class="label">Repetier</span>
</label>
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="checkbox">
<input type="checkbox" data-bind="checked: feature_resetLineNumbersWithPrefixedN" id="settings-resetLineNumbersWithPrefixedN"> Send M110 commands with target line number as N-prefix <span class="label">Repetier</span>
</label> </label>
</div> </div>
</div> </div>

View File

@ -64,6 +64,11 @@ class VirtualPrinter():
self.bedTemp = 1.0 self.bedTemp = 1.0
self.bedTargetTemp = 1.0 self.bedTargetTemp = 1.0
self.currentLine = 0
waitThread = threading.Thread(target=self._sendWaitAfterTimeout)
waitThread.start()
def write(self, data): def write(self, data):
if self.readList is None: if self.readList is None:
return return
@ -79,10 +84,22 @@ class VirtualPrinter():
except: except:
pass pass
if 'M105' in data: if 'M105' in data:
# send simulated temperature data
self.readList.append("ok T:%.2f /%.2f B:%.2f /%.2f @:64\n" % (self.temp, self.targetTemp, self.bedTemp, self.bedTargetTemp)) self.readList.append("ok T:%.2f /%.2f B:%.2f /%.2f @:64\n" % (self.temp, self.targetTemp, self.bedTemp, self.bedTargetTemp))
elif "M110" in data:
# reset current line
self.currentLine = int(re.search('N([0-9]+)', data).group(1))
self.readList.append("ok\n")
elif self.currentLine == 100:
# 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("rs %d\n" % (self.currentLine - 5))
elif len(data.strip()) > 0: elif len(data.strip()) > 0:
self.readList.append("ok\n") self.readList.append("ok\n")
if "*" in data:
self.currentLine += 1
def readline(self): def readline(self):
if self.readList is None: if self.readList is None:
return '' return ''
@ -111,6 +128,10 @@ class VirtualPrinter():
def close(self): def close(self):
self.readList = None self.readList = None
def _sendWaitAfterTimeout(self, timeout=5):
time.sleep(timeout)
self.readList.append("wait")
class MachineComPrintCallback(object): class MachineComPrintCallback(object):
def mcLog(self, message): def mcLog(self, message):
pass pass
@ -178,7 +199,15 @@ class MachineCom(object):
self._heatupWaitStartTime = 0 self._heatupWaitStartTime = 0
self._heatupWaitTimeLost = 0.0 self._heatupWaitTimeLost = 0.0
self._printStartTime100 = None self._printStartTime100 = None
self._alwaysSendChecksum = settings().getBoolean(["feature", "alwaysSendChecksum"])
self._currentLine = 1
self._resendDelta = None
self._lastLines = []
self._sendNextLock = threading.Lock()
self._sendingLock = threading.Lock()
self.thread = threading.Thread(target=self._monitor) self.thread = threading.Thread(target=self._monitor)
self.thread.daemon = True self.thread.daemon = True
self.thread.start() self.thread.start()
@ -324,6 +353,8 @@ class MachineCom(object):
if line == None: if line == None:
break break
### Error detection
#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.
@ -333,24 +364,41 @@ class MachineCom(object):
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 or 'Line Number is not Last Line Number' in line or 'No Line Number with checksum' in line or 'No Checksum with line number' in line: if 'checksum mismatch' in line \
or 'Wrong checksum' in line \
or 'Line Number is not Last Line Number' in line \
or 'expected line' in line \
or 'No Line Number with checksum' in line \
or 'No Checksum with line number' in line \
or 'Missing checksum' in line:
pass pass
elif not self.isError(): elif not self.isError():
self._errorValue = line[6:] self._errorValue = line[6:]
self._changeState(self.STATE_ERROR) self._changeState(self.STATE_ERROR)
### Evaluate temperature status messages
if ' T:' in line or line.startswith('T:'): if ' T:' in line or line.startswith('T:'):
self._temp = float(re.search("-?[0-9\.]*", line.split('T:')[1]).group(0)) try:
if ' B:' in line: self._temp = float(re.search("-?[0-9\.]*", line.split('T:')[1]).group(0))
self._bedTemp = float(re.search("-?[0-9\.]*", line.split(' B:')[1]).group(0)) if ' B:' in line:
self._callback.mcTempUpdate(self._temp, self._bedTemp, self._targetTemp, self._bedTargetTemp) 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 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 and self._heatupWaitStartTime != 0: if not 'ok' in line and self._heatupWaitStartTime != 0:
t = time.time() t = time.time()
self._heatupWaitTimeLost = t - self._heatupWaitStartTime self._heatupWaitTimeLost = t - self._heatupWaitStartTime
self._heatupWaitStartTime = t self._heatupWaitStartTime = t
elif line.strip() != '' and line.strip() != 'ok' and not line.startswith('Resend:') and line != 'echo:Unknown command:""\n' and self.isOperational():
### Forward messages from the firmware
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) self._callback.mcMessage(line)
### Baudrate detection
if self._state == self.STATE_DETECT_BAUDRATE: if self._state == self.STATE_DETECT_BAUDRATE:
if line == '' or time.time() > timeout: if line == '' or time.time() > timeout:
if len(self._baudrateDetectList) < 1: if len(self._baudrateDetectList) < 1:
@ -388,42 +436,74 @@ class MachineCom(object):
self._changeState(self.STATE_OPERATIONAL) self._changeState(self.STATE_OPERATIONAL)
else: else:
self._testingBaudrate = False self._testingBaudrate = False
### Connection attempt
elif self._state == self.STATE_CONNECTING: elif self._state == self.STATE_CONNECTING:
if line == '' and startSeen: if (line == "" or "wait" in line) and startSeen:
self._sendCommand("M105") self._sendCommand("M105")
elif 'start' in line: elif "start" in line:
startSeen = True startSeen = True
elif 'ok' in line and startSeen: elif "ok" in line and startSeen:
self._changeState(self.STATE_OPERATIONAL) self._changeState(self.STATE_OPERATIONAL)
elif time.time() > timeout: elif time.time() > timeout:
self.close() self.close()
### Operational
elif self._state == self.STATE_OPERATIONAL or self._state == self.STATE_PAUSED: 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. #Request the temperature on comm timeout (every 5 seconds) when we are not printing.
if line == '': if line == "" or "wait" in line:
self._sendCommand("M105") 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 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: elif self._state == self.STATE_PRINTING:
if line == '' and time.time() > timeout: if line == "" and time.time() > timeout:
self._log("Communication timeout during printing, forcing a line") self._log("Communication timeout during printing, forcing a line")
line = 'ok' line = "ok"
#Even when printing request the temperture every 5 seconds. #Even when printing request the temperature every 5 seconds.
if time.time() > tempRequestTimeout: if time.time() > tempRequestTimeout:
self._commandQueue.put("M105") self._commandQueue.put("M105")
tempRequestTimeout = time.time() + 5 tempRequestTimeout = time.time() + 5
if 'ok' in line:
# ok -> send next command
if "ok" in line:
timeout = time.time() + 5 timeout = time.time() + 5
if not self._commandQueue.empty(): if self._resendDelta is not None:
self._resendNextCommand()
elif not self._commandQueue.empty():
self._sendCommand(self._commandQueue.get()) self._sendCommand(self._commandQueue.get())
else: else:
self._sendNext() self._sendNext()
# 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:
try: self._handleResendRequest(line)
self._gcodePos = int(line.replace("N:"," ").replace("N"," ").replace(":"," ").split()[-1])
except:
if "rs" in line:
self._gcodePos = int(line.split()[1])
self._log("Connection closed, closing down monitor") self._log("Connection closed, closing down monitor")
def _handleResendRequest(self, line):
lineToResend = None
try:
lineToResend = int(line.replace("N:"," ").replace("N"," ").replace(":"," ").split()[-1])
except:
if "rs" in line:
lineToResend = int(line.split()[1])
if lineToResend is not None:
self._resendDelta = self._currentLine - lineToResend
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._changeState(self.STATE_ERROR)
self._logger.warn(self._errorValue)
else:
self._resendNextCommand()
def _log(self, message): def _log(self, message):
self._callback.mcLog(message) self._callback.mcLog(message)
self._serialLogger.debug(message) self._serialLogger.debug(message)
@ -464,23 +544,91 @@ class MachineCom(object):
def __del__(self): def __del__(self):
self.close() self.close()
def _sendCommand(self, cmd): def _resendNextCommand(self):
if self._serial is None: # Make sure we are only handling one sending job at a time
return with self._sendingLock:
if 'M109' in cmd or 'M190' in cmd: 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._heatupWaitStartTime = time.time() cmd = self._lastLines[-self._resendDelta]
if 'M104' in cmd or 'M109' in cmd: lineNumber = self._currentLine - self._resendDelta
try:
self._targetTemp = float(re.search('S([0-9]+)', cmd).group(1)) self._doSendWithChecksum(cmd, lineNumber)
except:
pass self._resendDelta -= 1
if 'M140' in cmd or 'M190' in cmd: if self._resendDelta <= 0:
try: self._resendDelta = None
self._bedTargetTemp = float(re.search('S([0-9]+)', cmd).group(1))
except: def _sendCommand(self, cmd, sendChecksum=False):
pass # Make sure we are only handling one sending job at a time
self._log('Send: %s' % (cmd)) with self._sendingLock:
if self._serial is None:
return
if 'M109' in cmd or 'M190' in cmd:
self._heatupWaitStartTime = time.time()
if 'M104' in cmd or 'M109' in cmd:
try:
self._targetTemp = float(re.search('S([0-9]+)', cmd).group(1))
except:
pass
if 'M140' in cmd or 'M190' in cmd:
try:
self._bedTargetTemp = float(re.search('S([0-9]+)', cmd).group(1))
except:
pass
if "M110" in cmd:
newLineNumber = None
if " N" in cmd:
try:
newLineNumber = int(re.search("N([0-9]+)", cmd).group(1))
except:
pass
else:
newLineNumber = 0
if settings().getBoolean(["feature", "resetLineNumbersWithPrefixedN"]) and newLineNumber is not None:
# let's rewrite the M110 command to fit repetier syntax
self._addToLastLines(cmd)
self._doSendWithChecksum("M110", newLineNumber)
else:
self._doSend(cmd, sendChecksum)
if newLineNumber is not None:
self._currentLine = newLineNumber + 1
# after a reset of the line number we have no way to determine what line exactly the printer now wants
self._lastLines = []
self._resendDelta = None
else:
self._doSend(cmd, sendChecksum)
def _addToLastLines(self, cmd):
self._lastLines.append(cmd)
if len(self._lastLines) > 50:
self._lastLines = self._lastLines[-50:] # only keep the last 50 lines in memory
self._logger.debug("Got %d lines of history in memory" % len(self._lastLines))
def _doSend(self, cmd, sendChecksum=False):
if sendChecksum or self._alwaysSendChecksum:
if self._alwaysSendChecksum:
lineNumber = self._currentLine
else:
lineNumber = self._gcodePos
self._addToLastLines(cmd)
self._currentLine += 1
self._doSendWithChecksum(cmd, lineNumber)
else:
self._doSendWithoutChecksum(cmd)
def _doSendWithChecksum(self, cmd, lineNumber):
self._logger.debug("Sending cmd '%s' with lineNumber %r" % (cmd, lineNumber))
checksum = reduce(lambda x,y:x^y, map(ord, "N%d%s" % (lineNumber, cmd)))
commandToSend = "N%d%s*%d" % (lineNumber, cmd, checksum)
self._doSendWithoutChecksum(commandToSend)
def _doSendWithoutChecksum(self, cmd):
self._log("Send: %s" % cmd)
try: try:
self._serial.write(cmd + '\n') self._serial.write(cmd + '\n')
except serial.SerialTimeoutException: except serial.SerialTimeoutException:
@ -495,34 +643,34 @@ class MachineCom(object):
self._log("Unexpected error while writing serial port: %s" % (getExceptionString())) self._log("Unexpected error while writing serial port: %s" % (getExceptionString()))
self._errorValue = getExceptionString() self._errorValue = getExceptionString()
self.close(True) self.close(True)
def _sendNext(self): def _sendNext(self):
if self._gcodePos >= len(self._gcodeList): with self._sendNextLock:
self._changeState(self.STATE_OPERATIONAL) if self._gcodePos >= len(self._gcodeList):
return self._changeState(self.STATE_OPERATIONAL)
if self._gcodePos == 100: return
self._printStartTime100 = time.time() if self._gcodePos == 100:
line = self._gcodeList[self._gcodePos] self._printStartTime100 = time.time()
if type(line) is tuple: line = self._gcodeList[self._gcodePos]
self._printSection = line[1] if type(line) is tuple:
line = line[0] self._printSection = line[1]
try: line = line[0]
if line == 'M0' or line == 'M1': try:
self.setPause(True) if line == 'M0' or line == 'M1':
line = 'M105' #Don't send the M0 or M1 to the machine, as M0 and M1 are handled as an LCD menu pause. self.setPause(True)
if self._printSection in self._feedRateModifier: line = 'M105' #Don't send the M0 or M1 to the machine, as M0 and M1 are handled as an LCD menu pause.
line = re.sub('F([0-9]*)', lambda m: 'F' + str(int(int(m.group(1)) * self._feedRateModifier[self._printSection])), line) if self._printSection in self._feedRateModifier:
if ('G0' in line or 'G1' in line) and 'Z' in line: line = re.sub('F([0-9]*)', lambda m: 'F' + str(int(int(m.group(1)) * self._feedRateModifier[self._printSection])), line)
z = float(re.search('Z([0-9\.]*)', line).group(1)) if ('G0' in line or 'G1' in line) and 'Z' in line:
if self._currentZ != z: z = float(re.search('Z([0-9\.]*)', line).group(1))
self._currentZ = z if self._currentZ != z:
self._callback.mcZChange(z) self._currentZ = z
except: self._callback.mcZChange(z)
self._log("Unexpected error: %s" % (getExceptionString())) except:
checksum = reduce(lambda x,y:x^y, map(ord, "N%d%s" % (self._gcodePos, line))) self._log("Unexpected error: %s" % (getExceptionString()))
self._sendCommand("N%d%s*%d" % (self._gcodePos, line, checksum)) self._sendCommand(line, True)
self._gcodePos += 1 self._gcodePos += 1
self._callback.mcProgress(self._gcodePos) self._callback.mcProgress(self._gcodePos)
def sendCommand(self, cmd): def sendCommand(self, cmd):
cmd = cmd.encode('ascii', 'replace') cmd = cmd.encode('ascii', 'replace')
@ -540,8 +688,7 @@ class MachineCom(object):
self._printSection = 'CUSTOM' self._printSection = 'CUSTOM'
self._changeState(self.STATE_PRINTING) self._changeState(self.STATE_PRINTING)
self._printStartTime = time.time() self._printStartTime = time.time()
for i in xrange(0, 6): self._sendNext()
self._sendNext()
def cancelPrint(self): def cancelPrint(self):
if self.isOperational(): if self.isOperational():