chiark / gitweb /
Make error detection less "all inclusive" to fix #690
[cura.git] / Cura / util / machineCom.py
1 from __future__ import absolute_import
2 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
3
4 import os
5 import glob
6 import sys
7 import time
8 import math
9 import re
10 import traceback
11 import threading
12 import platform
13 import Queue as queue
14
15 import serial
16
17 from Cura.avr_isp import stk500v2
18 from Cura.avr_isp import ispBase
19
20 from Cura.util import profile
21 from Cura.util import version
22
23 try:
24         import _winreg
25 except:
26         pass
27
28 def serialList(forAutoDetect=False):
29         baselist=[]
30         if platform.system() == "Windows":
31                 try:
32                         key=_winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE,"HARDWARE\\DEVICEMAP\\SERIALCOMM")
33                         i=0
34                         while True:
35                                 values = _winreg.EnumValue(key, i)
36                                 if not forAutoDetect or 'USBSER' in values[0]:
37                                         baselist+=[values[1]]
38                                 i+=1
39                 except:
40                         pass
41         if forAutoDetect:
42                 baselist = baselist + glob.glob('/dev/ttyUSB*') + glob.glob('/dev/ttyACM*') + glob.glob("/dev/cu.usb*")
43                 baselist = filter(lambda s: not 'Bluetooth' in s, baselist)
44                 prev = profile.getMachineSetting('serial_port_auto')
45                 if prev in baselist:
46                         baselist.remove(prev)
47                         baselist.insert(0, prev)
48         else:
49                 baselist = baselist + glob.glob('/dev/ttyUSB*') + glob.glob('/dev/ttyACM*') + glob.glob("/dev/cu.*") + glob.glob("/dev/tty.usb*") + glob.glob("/dev/rfcomm*")
50         if version.isDevVersion() and not forAutoDetect:
51                 baselist.append('VIRTUAL')
52         return baselist
53
54 def machineIsConnected():
55         #UltiGCode is designed for SD-Card printing, so never auto-detect the serial port.
56         port = profile.getMachineSetting('serial_port')
57         if port == 'AUTO':
58                 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
59                         return False
60                 return len(serialList(True)) > 0
61         if platform.system() == "Windows":
62                 return port in serialList()
63         return os.path.isfile(port)
64
65 def baudrateList():
66         ret = [250000, 230400, 115200, 57600, 38400, 19200, 9600]
67         if profile.getMachineSetting('serial_baud_auto') != '':
68                 prev = int(profile.getMachineSetting('serial_baud_auto'))
69                 if prev in ret:
70                         ret.remove(prev)
71                         ret.insert(0, prev)
72         return ret
73
74 class VirtualPrinter():
75         def __init__(self):
76                 self.readList = ['start\n', 'Marlin: Virtual Marlin!\n', '\x80\n']
77                 self.temp = 0.0
78                 self.targetTemp = 0.0
79                 self.lastTempAt = time.time()
80                 self.bedTemp = 1.0
81                 self.bedTargetTemp = 1.0
82         
83         def write(self, data):
84                 if self.readList is None:
85                         return
86                 #print "Send: %s" % (data.rstrip())
87                 if 'M104' in data or 'M109' in data:
88                         try:
89                                 self.targetTemp = float(re.search('S([0-9]+)', data).group(1))
90                         except:
91                                 pass
92                 if 'M140' in data or 'M190' in data:
93                         try:
94                                 self.bedTargetTemp = float(re.search('S([0-9]+)', data).group(1))
95                         except:
96                                 pass
97                 if 'M105' in data:
98                         self.readList.append("ok T:%.2f /%.2f B:%.2f /%.2f @:64\n" % (self.temp, self.targetTemp, self.bedTemp, self.bedTargetTemp))
99                 elif len(data.strip()) > 0:
100                         self.readList.append("ok\n")
101
102         def readline(self):
103                 if self.readList is None:
104                         return ''
105                 n = 0
106                 timeDiff = self.lastTempAt - time.time()
107                 self.lastTempAt = time.time()
108                 if abs(self.temp - self.targetTemp) > 1:
109                         self.temp += math.copysign(timeDiff * 10, self.targetTemp - self.temp)
110                 if abs(self.bedTemp - self.bedTargetTemp) > 1:
111                         self.bedTemp += math.copysign(timeDiff * 10, self.bedTargetTemp - self.bedTemp)
112                 while len(self.readList) < 1:
113                         time.sleep(0.1)
114                         n += 1
115                         if n == 20:
116                                 return ''
117                         if self.readList is None:
118                                 return ''
119                 time.sleep(0.001)
120                 #print "Recv: %s" % (self.readList[0].rstrip())
121                 return self.readList.pop(0)
122         
123         def close(self):
124                 self.readList = None
125
126 class MachineComPrintCallback(object):
127         def mcLog(self, message):
128                 pass
129         
130         def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
131                 pass
132         
133         def mcStateChange(self, state):
134                 pass
135         
136         def mcMessage(self, message):
137                 pass
138         
139         def mcProgress(self, lineNr):
140                 pass
141         
142         def mcZChange(self, newZ):
143                 pass
144
145 class MachineCom(object):
146         STATE_NONE = 0
147         STATE_OPEN_SERIAL = 1
148         STATE_DETECT_SERIAL = 2
149         STATE_DETECT_BAUDRATE = 3
150         STATE_CONNECTING = 4
151         STATE_OPERATIONAL = 5
152         STATE_PRINTING = 6
153         STATE_PAUSED = 7
154         STATE_CLOSED = 8
155         STATE_ERROR = 9
156         STATE_CLOSED_WITH_ERROR = 10
157         
158         def __init__(self, port = None, baudrate = None, callbackObject = None):
159                 if port is None:
160                         port = profile.getMachineSetting('serial_port')
161                 if baudrate is None:
162                         if profile.getMachineSetting('serial_baud') == 'AUTO':
163                                 baudrate = 0
164                         else:
165                                 baudrate = int(profile.getMachineSetting('serial_baud'))
166                 if callbackObject is None:
167                         callbackObject = MachineComPrintCallback()
168
169                 self._port = port
170                 self._baudrate = baudrate
171                 self._callback = callbackObject
172                 self._state = self.STATE_NONE
173                 self._serial = None
174                 self._baudrateDetectList = baudrateList()
175                 self._baudrateDetectRetry = 0
176                 self._extruderCount = int(profile.getMachineSetting('extruder_amount'))
177                 self._temperatureRequestExtruder = 0
178                 self._temp = [0] * self._extruderCount
179                 self._targetTemp = [0] * self._extruderCount
180                 self._bedTemp = 0
181                 self._bedTargetTemp = 0
182                 self._gcodeList = None
183                 self._gcodePos = 0
184                 self._commandQueue = queue.Queue()
185                 self._logQueue = queue.Queue(256)
186                 self._feedRateModifier = {}
187                 self._currentZ = -1
188                 self._heatupWaitStartTime = 0
189                 self._heatupWaitTimeLost = 0.0
190                 self._printStartTime100 = None
191                 
192                 self.thread = threading.Thread(target=self._monitor)
193                 self.thread.daemon = True
194                 self.thread.start()
195         
196         def _changeState(self, newState):
197                 if self._state == newState:
198                         return
199                 oldState = self.getStateString()
200                 self._state = newState
201                 self._log('Changing monitoring state from \'%s\' to \'%s\'' % (oldState, self.getStateString()))
202                 self._callback.mcStateChange(newState)
203         
204         def getState(self):
205                 return self._state
206         
207         def getStateString(self):
208                 if self._state == self.STATE_NONE:
209                         return "Offline"
210                 if self._state == self.STATE_OPEN_SERIAL:
211                         return "Opening serial port"
212                 if self._state == self.STATE_DETECT_SERIAL:
213                         return "Detecting serial port"
214                 if self._state == self.STATE_DETECT_BAUDRATE:
215                         return "Detecting baudrate"
216                 if self._state == self.STATE_CONNECTING:
217                         return "Connecting"
218                 if self._state == self.STATE_OPERATIONAL:
219                         return "Operational"
220                 if self._state == self.STATE_PRINTING:
221                         return "Printing"
222                 if self._state == self.STATE_PAUSED:
223                         return "Paused"
224                 if self._state == self.STATE_CLOSED:
225                         return "Closed"
226                 if self._state == self.STATE_ERROR:
227                         return "Error: %s" % (self.getShortErrorString())
228                 if self._state == self.STATE_CLOSED_WITH_ERROR:
229                         return "Error: %s" % (self.getShortErrorString())
230                 return "?%d?" % (self._state)
231         
232         def getShortErrorString(self):
233                 if len(self._errorValue) < 20:
234                         return self._errorValue
235                 return self._errorValue[:20] + "..."
236
237         def getErrorString(self):
238                 return self._errorValue
239         
240         def isClosedOrError(self):
241                 return self._state == self.STATE_ERROR or self._state == self.STATE_CLOSED_WITH_ERROR or self._state == self.STATE_CLOSED
242
243         def isError(self):
244                 return self._state == self.STATE_ERROR or self._state == self.STATE_CLOSED_WITH_ERROR
245         
246         def isOperational(self):
247                 return self._state == self.STATE_OPERATIONAL or self._state == self.STATE_PRINTING or self._state == self.STATE_PAUSED
248         
249         def isPrinting(self):
250                 return self._state == self.STATE_PRINTING
251         
252         def isPaused(self):
253                 return self._state == self.STATE_PAUSED
254
255         def getPrintPos(self):
256                 return self._gcodePos
257         
258         def getPrintTime(self):
259                 return time.time() - self._printStartTime
260
261         def getPrintTimeRemainingEstimate(self):
262                 if self._printStartTime100 is None or self.getPrintPos() < 200:
263                         return None
264                 printTime = (time.time() - self._printStartTime100) / 60
265                 printTimeTotal = printTime * (len(self._gcodeList) - 100) / (self.getPrintPos() - 100)
266                 printTimeLeft = printTimeTotal - printTime
267                 return printTimeLeft
268         
269         def getTemp(self):
270                 return self._temp
271         
272         def getBedTemp(self):
273                 return self._bedTemp
274         
275         def getLog(self):
276                 ret = []
277                 while not self._logQueue.empty():
278                         ret.append(self._logQueue.get())
279                 for line in ret:
280                         self._logQueue.put(line, False)
281                 return ret
282         
283         def _monitor(self):
284                 #Open the serial port.
285                 if self._port == 'AUTO':
286                         self._changeState(self.STATE_DETECT_SERIAL)
287                         programmer = stk500v2.Stk500v2()
288                         self._log("Serial port list: %s" % (str(serialList(True))))
289                         for p in serialList(True):
290                                 try:
291                                         self._log("Connecting to: %s" % (p))
292                                         programmer.connect(p)
293                                         self._serial = programmer.leaveISP()
294                                         profile.putMachineSetting('serial_port_auto', p)
295                                         break
296                                 except ispBase.IspError as (e):
297                                         self._log("Error while connecting to %s: %s" % (p, str(e)))
298                                         pass
299                                 except:
300                                         self._log("Unexpected error while connecting to serial port: %s %s" % (p, getExceptionString()))
301                                 programmer.close()
302                 elif self._port == 'VIRTUAL':
303                         self._changeState(self.STATE_OPEN_SERIAL)
304                         self._serial = VirtualPrinter()
305                 else:
306                         self._changeState(self.STATE_OPEN_SERIAL)
307                         try:
308                                 self._log("Connecting to: %s" % (self._port))
309                                 if self._baudrate == 0:
310                                         self._serial = serial.Serial(str(self._port), 115200, timeout=0.1, writeTimeout=10000)
311                                 else:
312                                         self._serial = serial.Serial(str(self._port), self._baudrate, timeout=2, writeTimeout=10000)
313                         except:
314                                 self._log("Unexpected error while connecting to serial port: %s %s" % (self._port, getExceptionString()))
315                 if self._serial is None:
316                         self._log("Failed to open serial port (%s)" % (self._port))
317                         self._errorValue = 'Failed to autodetect serial port.'
318                         self._changeState(self.STATE_ERROR)
319                         return
320                 self._log("Connected to: %s, starting monitor" % (self._serial))
321                 if self._baudrate == 0:
322                         self._changeState(self.STATE_DETECT_BAUDRATE)
323                 else:
324                         self._changeState(self.STATE_CONNECTING)
325
326                 #Start monitoring the serial port.
327                 if self._state == self.STATE_CONNECTING:
328                         timeout = time.time() + 15
329                 else:
330                         timeout = time.time() + 5
331                 tempRequestTimeout = timeout
332                 while True:
333                         line = self._readline()
334                         if line is None:
335                                 break
336                         
337                         #No matter the state, if we see an fatal error, goto the error state and store the error for reference.
338                         # Only goto error on known fatal errors.
339                         if line.startswith('Error:'):
340                                 #Oh YEAH, consistency.
341                                 # Marlin reports an MIN/MAX temp error as "Error:x\n: Extruder switched off. MAXTEMP triggered !\n"
342                                 #       But a bed temp error is reported as "Error: Temperature heated bed switched off. MAXTEMP triggered !!"
343                                 #       So we can have an extra newline in the most common case. Awesome work people.
344                                 if re.match('Error:[0-9]\n', line):
345                                         line = line.rstrip() + self._readline()
346                                 #Skip the communication errors, as those get corrected.
347                                 if 'Extruder switched off' in line or 'Temperature heated bed switched off' in line or 'Something is wrong, please turn off the printer.' in line:
348                                         if not self.isError():
349                                                 self._errorValue = line[6:]
350                                                 self._changeState(self.STATE_ERROR)
351                         if ' T:' in line or line.startswith('T:'):
352                                 self._temp[self._temperatureRequestExtruder] = float(re.search("[0-9\.]*", line.split('T:')[1]).group(0))
353                                 if ' B:' in line:
354                                         self._bedTemp = float(re.search("[0-9\.]*", line.split(' B:')[1]).group(0))
355                                 self._callback.mcTempUpdate(self._temp, self._bedTemp, self._targetTemp, self._bedTargetTemp)
356                                 #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.
357                                 if not 'ok' in line and self._heatupWaitStartTime != 0:
358                                         t = time.time()
359                                         self._heatupWaitTimeLost = t - self._heatupWaitStartTime
360                                         self._heatupWaitStartTime = t
361                         elif line.strip() != '' and line.strip() != 'ok' and not line.startswith('Resend:') and not line.startswith('Error:checksum mismatch') and not line.startswith('Error:Line Number is not Last Line Number+1') and line != 'echo:Unknown command:""\n' and self.isOperational():
362                                 self._callback.mcMessage(line)
363
364                         if self._state == self.STATE_DETECT_BAUDRATE:
365                                 if line == '' or time.time() > timeout:
366                                         if len(self._baudrateDetectList) < 1:
367                                                 self.close()
368                                                 self._errorValue = "No more baudrates to test, and no suitable baudrate found."
369                                                 self._changeState(self.STATE_ERROR)
370                                         elif self._baudrateDetectRetry > 0:
371                                                 self._baudrateDetectRetry -= 1
372                                                 self._serial.write('\n')
373                                                 self._log("Baudrate test retry: %d" % (self._baudrateDetectRetry))
374                                                 self._sendCommand("M105")
375                                                 self._testingBaudrate = True
376                                         else:
377                                                 baudrate = self._baudrateDetectList.pop(0)
378                                                 try:
379                                                         self._setBaudrate(baudrate)
380                                                         self._serial.timeout = 0.5
381                                                         self._log("Trying baudrate: %d" % (baudrate))
382                                                         self._baudrateDetectRetry = 5
383                                                         self._baudrateDetectTestOk = 0
384                                                         timeout = time.time() + 5
385                                                         self._serial.write('\n')
386                                                         self._sendCommand("M105")
387                                                         self._testingBaudrate = True
388                                                 except:
389                                                         self._log("Unexpected error while setting baudrate: %d %s" % (baudrate, getExceptionString()))
390                                 elif 'T:' in line:
391                                         self._baudrateDetectTestOk += 1
392                                         if self._baudrateDetectTestOk < 10:
393                                                 self._log("Baudrate test ok: %d" % (self._baudrateDetectTestOk))
394                                                 self._sendCommand("M105")
395                                         else:
396                                                 self._sendCommand("M999")
397                                                 self._serial.timeout = 2
398                                                 profile.putMachineSetting('serial_baud_auto', self._serial.baudrate)
399                                                 self._changeState(self.STATE_OPERATIONAL)
400                                 else:
401                                         self._testingBaudrate = False
402                         elif self._state == self.STATE_CONNECTING:
403                                 if line == '' or 'wait' in line:        # wait needed for Repetier (kind of watchdog)
404                                         self._sendCommand("M105")
405                                 elif 'ok' in line:
406                                         self._changeState(self.STATE_OPERATIONAL)
407                                 if time.time() > timeout:
408                                         self.close()
409                         elif self._state == self.STATE_OPERATIONAL:
410                                 #Request the temperature on comm timeout (every 2 seconds) when we are not printing.
411                                 if line == '':
412                                         if self._extruderCount > 0:
413                                                 self._temperatureRequestExtruder = (self._temperatureRequestExtruder + 1) % self._extruderCount
414                                                 self._sendCommand("M105 T%d" % (self._temperatureRequestExtruder))
415                                         else:
416                                                 self._sendCommand("M105")
417                                         tempRequestTimeout = time.time() + 5
418                         elif self._state == self.STATE_PRINTING:
419                                 if line == '' and time.time() > timeout:
420                                         self._log("Communication timeout during printing, forcing a line")
421                                         line = 'ok'
422                                 #Even when printing request the temperature every 5 seconds.
423                                 if time.time() > tempRequestTimeout:
424                                         if self._extruderCount > 0:
425                                                 self._temperatureRequestExtruder = (self._temperatureRequestExtruder + 1) % self._extruderCount
426                                                 self._sendCommand("M105 T%d" % (self._temperatureRequestExtruder))
427                                         else:
428                                                 self._sendCommand("M105")
429                                         tempRequestTimeout = time.time() + 5
430                                 if 'ok' in line:
431                                         timeout = time.time() + 5
432                                         if not self._commandQueue.empty():
433                                                 self._sendCommand(self._commandQueue.get())
434                                         else:
435                                                 self._sendNext()
436                                 elif "resend" in line.lower() or "rs" in line:
437                                         try:
438                                                 self._gcodePos = int(line.replace("N:"," ").replace("N"," ").replace(":"," ").split()[-1])
439                                         except:
440                                                 if "rs" in line:
441                                                         self._gcodePos = int(line.split()[1])
442                 self._log("Connection closed, closing down monitor")
443
444         def _setBaudrate(self, baudrate):
445                 #For linux the pyserial implementation lacks TCGETS2 support. So do that ourselves
446                 if sys.platform.startswith('linux'):
447                         try:
448                                 self._serial.baudrate = baudrate
449                         except:
450                                 try:
451                                         # set custom speed
452                                         import fcntl, array, termios
453                                         TCGETS2 = 0x802C542A
454                                         TCSETS2 = 0x402C542B
455                                         BOTHER = 0o010000
456                                         buf = array.array('i', [0] * 64)
457                                         fcntl.ioctl(self._serial.fd, TCGETS2, buf)
458                                         buf[2] &= ~termios.CBAUD
459                                         buf[2] |= BOTHER
460                                         buf[9] = buf[10] = baudrate
461                                         fcntl.ioctl(self._serial.fd, TCSETS2, buf)
462                                 except:
463                                         print getExceptionString()
464                 else:
465                         self._serial.baudrate = baudrate
466
467         def _log(self, message):
468                 self._callback.mcLog(message)
469                 try:
470                         self._logQueue.put(message, False)
471                 except:
472                         #If the log queue is full, remove the first message and append the new message again
473                         self._logQueue.get()
474                         try:
475                                 self._logQueue.put(message, False)
476                         except:
477                                 pass
478
479         def _readline(self):
480                 if self._serial == None:
481                         return None
482                 try:
483                         ret = self._serial.readline()
484                 except:
485                         self._log("Unexpected error while reading serial port: %s" % (getExceptionString()))
486                         self._errorValue = getExceptionString()
487                         self.close(True)
488                         return None
489                 if ret == '':
490                         #self._log("Recv: TIMEOUT")
491                         return ''
492                 self._log("Recv: %s" % (unicode(ret, 'ascii', 'replace').encode('ascii', 'replace').rstrip()))
493                 return ret
494         
495         def close(self, isError = False):
496                 if self._serial != None:
497                         self._serial.close()
498                         if isError:
499                                 self._changeState(self.STATE_CLOSED_WITH_ERROR)
500                         else:
501                                 self._changeState(self.STATE_CLOSED)
502                 self._serial = None
503         
504         def __del__(self):
505                 self.close()
506         
507         def _sendCommand(self, cmd):
508                 if self._serial is None:
509                         return
510                 if 'M109' in cmd or 'M190' in cmd:
511                         self._heatupWaitStartTime = time.time()
512                 if 'M104' in cmd or 'M109' in cmd:
513                         try:
514                                 t = 0
515                                 if 'T' in cmd:
516                                         t = int(re.search('T([0-9]+)', cmd).group(1))
517                                 self._targetTemp[t] = float(re.search('S([0-9]+)', cmd).group(1))
518                         except:
519                                 pass
520                 if 'M140' in cmd or 'M190' in cmd:
521                         try:
522                                 self._bedTargetTemp = float(re.search('S([0-9]+)', cmd).group(1))
523                         except:
524                                 pass
525                 self._log('Send: %s' % (cmd))
526                 try:
527                         self._serial.write(cmd + '\n')
528                 except serial.SerialTimeoutException:
529                         self._log("Serial timeout while writing to serial port, trying again.")
530                         try:
531                                 time.sleep(0.5)
532                                 self._serial.write(cmd + '\n')
533                         except:
534                                 self._log("Unexpected error while writing serial port: %s" % (getExceptionString()))
535                                 self._errorValue = getExceptionString()
536                                 self.close(True)
537                 except:
538                         self._log("Unexpected error while writing serial port: %s" % (getExceptionString()))
539                         self._errorValue = getExceptionString()
540                         self.close(True)
541         
542         def _sendNext(self):
543                 if self._gcodePos >= len(self._gcodeList):
544                         self._changeState(self.STATE_OPERATIONAL)
545                         return
546                 if self._gcodePos == 100:
547                         self._printStartTime100 = time.time()
548                 line = self._gcodeList[self._gcodePos]
549                 if type(line) is tuple:
550                         self._printSection = line[1]
551                         line = line[0]
552                 try:
553                         if line == 'M0' or line == 'M1':
554                                 self.setPause(True)
555                                 line = 'M105'   #Don't send the M0 or M1 to the machine, as M0 and M1 are handled as an LCD menu pause.
556                         if self._printSection in self._feedRateModifier:
557                                 line = re.sub('F([0-9]*)', lambda m: 'F' + str(int(int(m.group(1)) * self._feedRateModifier[self._printSection])), line)
558                         if ('G0' in line or 'G1' in line) and 'Z' in line:
559                                 z = float(re.search('Z([0-9\.]*)', line).group(1))
560                                 if self._currentZ != z:
561                                         self._currentZ = z
562                                         self._callback.mcZChange(z)
563                 except:
564                         self._log("Unexpected error: %s" % (getExceptionString()))
565                 checksum = reduce(lambda x,y:x^y, map(ord, "N%d%s" % (self._gcodePos, line)))
566                 self._sendCommand("N%d%s*%d" % (self._gcodePos, line, checksum))
567                 self._gcodePos += 1
568                 self._callback.mcProgress(self._gcodePos)
569         
570         def sendCommand(self, cmd):
571                 cmd = cmd.encode('ascii', 'replace')
572                 if self.isPrinting():
573                         self._commandQueue.put(cmd)
574                 elif self.isOperational():
575                         self._sendCommand(cmd)
576         
577         def printGCode(self, gcodeList):
578                 if not self.isOperational() or self.isPrinting():
579                         return
580                 self._gcodeList = gcodeList
581                 self._gcodePos = 0
582                 self._printStartTime100 = None
583                 self._printSection = 'CUSTOM'
584                 self._changeState(self.STATE_PRINTING)
585                 self._printStartTime = time.time()
586                 for i in xrange(0, 4):
587                         self._sendNext()
588         
589         def cancelPrint(self):
590                 if self.isOperational():
591                         self._changeState(self.STATE_OPERATIONAL)
592         
593         def setPause(self, pause):
594                 if not pause and self.isPaused():
595                         self._changeState(self.STATE_PRINTING)
596                         for i in xrange(0, 6):
597                                 self._sendNext()
598                 if pause and self.isPrinting():
599                         self._changeState(self.STATE_PAUSED)
600         
601         def setFeedrateModifier(self, type, value):
602                 self._feedRateModifier[type] = value
603
604 def getExceptionString():
605         locationInfo = traceback.extract_tb(sys.exc_info()[2])[0]
606         return "%s: '%s' @ %s:%s:%d" % (str(sys.exc_info()[0].__name__), str(sys.exc_info()[1]), os.path.basename(locationInfo[0]), locationInfo[2], locationInfo[1])