chiark / gitweb /
If programmer based auto-detection of serial port fails, try all ports and baudrates...
[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._serialDetectList = []
175                 self._baudrateDetectList = baudrateList()
176                 self._baudrateDetectRetry = 0
177                 self._extruderCount = int(profile.getMachineSetting('extruder_amount'))
178                 self._temperatureRequestExtruder = 0
179                 self._temp = [0] * self._extruderCount
180                 self._targetTemp = [0] * self._extruderCount
181                 self._bedTemp = 0
182                 self._bedTargetTemp = 0
183                 self._gcodeList = None
184                 self._gcodePos = 0
185                 self._commandQueue = queue.Queue()
186                 self._logQueue = queue.Queue(256)
187                 self._feedRateModifier = {}
188                 self._currentZ = -1
189                 self._heatupWaitStartTime = 0
190                 self._heatupWaitTimeLost = 0.0
191                 self._printStartTime100 = None
192                 
193                 self.thread = threading.Thread(target=self._monitor)
194                 self.thread.daemon = True
195                 self.thread.start()
196         
197         def _changeState(self, newState):
198                 if self._state == newState:
199                         return
200                 oldState = self.getStateString()
201                 self._state = newState
202                 self._log('Changing monitoring state from \'%s\' to \'%s\'' % (oldState, self.getStateString()))
203                 self._callback.mcStateChange(newState)
204         
205         def getState(self):
206                 return self._state
207         
208         def getStateString(self):
209                 if self._state == self.STATE_NONE:
210                         return "Offline"
211                 if self._state == self.STATE_OPEN_SERIAL:
212                         return "Opening serial port"
213                 if self._state == self.STATE_DETECT_SERIAL:
214                         return "Detecting serial port"
215                 if self._state == self.STATE_DETECT_BAUDRATE:
216                         return "Detecting baudrate"
217                 if self._state == self.STATE_CONNECTING:
218                         return "Connecting"
219                 if self._state == self.STATE_OPERATIONAL:
220                         return "Operational"
221                 if self._state == self.STATE_PRINTING:
222                         return "Printing"
223                 if self._state == self.STATE_PAUSED:
224                         return "Paused"
225                 if self._state == self.STATE_CLOSED:
226                         return "Closed"
227                 if self._state == self.STATE_ERROR:
228                         return "Error: %s" % (self.getShortErrorString())
229                 if self._state == self.STATE_CLOSED_WITH_ERROR:
230                         return "Error: %s" % (self.getShortErrorString())
231                 return "?%d?" % (self._state)
232         
233         def getShortErrorString(self):
234                 if len(self._errorValue) < 20:
235                         return self._errorValue
236                 return self._errorValue[:20] + "..."
237
238         def getErrorString(self):
239                 return self._errorValue
240         
241         def isClosedOrError(self):
242                 return self._state == self.STATE_ERROR or self._state == self.STATE_CLOSED_WITH_ERROR or self._state == self.STATE_CLOSED
243
244         def isError(self):
245                 return self._state == self.STATE_ERROR or self._state == self.STATE_CLOSED_WITH_ERROR
246         
247         def isOperational(self):
248                 return self._state == self.STATE_OPERATIONAL or self._state == self.STATE_PRINTING or self._state == self.STATE_PAUSED
249         
250         def isPrinting(self):
251                 return self._state == self.STATE_PRINTING
252         
253         def isPaused(self):
254                 return self._state == self.STATE_PAUSED
255
256         def getPrintPos(self):
257                 return self._gcodePos
258         
259         def getPrintTime(self):
260                 return time.time() - self._printStartTime
261
262         def getPrintTimeRemainingEstimate(self):
263                 if self._printStartTime100 is None or self.getPrintPos() < 200:
264                         return None
265                 printTime = (time.time() - self._printStartTime100) / 60
266                 printTimeTotal = printTime * (len(self._gcodeList) - 100) / (self.getPrintPos() - 100)
267                 printTimeLeft = printTimeTotal - printTime
268                 return printTimeLeft
269         
270         def getTemp(self):
271                 return self._temp
272         
273         def getBedTemp(self):
274                 return self._bedTemp
275         
276         def getLog(self):
277                 ret = []
278                 while not self._logQueue.empty():
279                         ret.append(self._logQueue.get())
280                 for line in ret:
281                         self._logQueue.put(line, False)
282                 return ret
283         
284         def _monitor(self):
285                 #Open the serial port.
286                 if self._port == 'AUTO':
287                         self._changeState(self.STATE_DETECT_SERIAL)
288                         self._log("Serial port list: %s" % (str(serialList(True))))
289                         programmer = stk500v2.Stk500v2()
290                         for p in serialList(True):
291                                 try:
292                                         self._log("Connecting to: %s" % (p))
293                                         programmer.connect(p)
294                                         self._serial = programmer.leaveISP()
295                                         profile.putMachineSetting('serial_port_auto', p)
296                                         break
297                                 except ispBase.IspError as (e):
298                                         self._log("Error while connecting to %s: %s" % (p, str(e)))
299                                         pass
300                                 except:
301                                         self._log("Unexpected error while connecting to serial port: %s %s" % (p, getExceptionString()))
302                                 programmer.close()
303                         if self._serial is None:
304                                 self._serialDetectList = serialList(True)
305                 elif self._port == 'VIRTUAL':
306                         self._changeState(self.STATE_OPEN_SERIAL)
307                         self._serial = VirtualPrinter()
308                 else:
309                         self._changeState(self.STATE_OPEN_SERIAL)
310                         try:
311                                 self._log("Connecting to: %s" % (self._port))
312                                 if self._baudrate == 0:
313                                         self._serial = serial.Serial(str(self._port), 115200, timeout=0.1, writeTimeout=10000)
314                                 else:
315                                         self._serial = serial.Serial(str(self._port), self._baudrate, timeout=2, writeTimeout=10000)
316                         except:
317                                 self._log("Unexpected error while connecting to serial port: %s %s" % (self._port, getExceptionString()))
318                 if self._serial is None:
319                         baudrate = self._baudrate
320                         if baudrate == 0:
321                                 baudrate = self._baudrateDetectList.pop(0)
322                         self._serial = serial.Serial(self._serialDetectList.pop(0), baudrate, timeout=0.1, writeTimeout=10000)
323                 else:
324                         self._log("Connected to: %s, starting monitor" % (self._serial))
325                         if self._baudrate == 0:
326                                 self._changeState(self.STATE_DETECT_BAUDRATE)
327                         else:
328                                 self._changeState(self.STATE_CONNECTING)
329                 #Start monitoring the serial port.
330                 if self._state == self.STATE_CONNECTING:
331                         timeout = time.time() + 15
332                 else:
333                         timeout = time.time() + 5
334                 tempRequestTimeout = timeout
335                 while True:
336                         line = self._readline()
337                         if line is None:
338                                 break
339                         
340                         #No matter the state, if we see an fatal error, goto the error state and store the error for reference.
341                         # Only goto error on known fatal errors.
342                         if line.startswith('Error:'):
343                                 #Oh YEAH, consistency.
344                                 # Marlin reports an MIN/MAX temp error as "Error:x\n: Extruder switched off. MAXTEMP triggered !\n"
345                                 #       But a bed temp error is reported as "Error: Temperature heated bed switched off. MAXTEMP triggered !!"
346                                 #       So we can have an extra newline in the most common case. Awesome work people.
347                                 if re.match('Error:[0-9]\n', line):
348                                         line = line.rstrip() + self._readline()
349                                 #Skip the communication errors, as those get corrected.
350                                 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:
351                                         if not self.isError():
352                                                 self._errorValue = line[6:]
353                                                 self._changeState(self.STATE_ERROR)
354                         if ' T:' in line or line.startswith('T:'):
355                                 try:
356                                         self._temp[self._temperatureRequestExtruder] = float(re.search("T: *([0-9\.]*)", line).group(1))
357                                 except:
358                                         pass
359                                 if 'B:' in line:
360                                         try:
361                                                 self._bedTemp = float(re.search("B: *([0-9\.]*)", line).group(1))
362                                         except:
363                                                 pass
364                                 self._callback.mcTempUpdate(self._temp, self._bedTemp, self._targetTemp, self._bedTargetTemp)
365                                 #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.
366                                 if not 'ok' in line and self._heatupWaitStartTime != 0:
367                                         t = time.time()
368                                         self._heatupWaitTimeLost = t - self._heatupWaitStartTime
369                                         self._heatupWaitStartTime = t
370                         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():
371                                 self._callback.mcMessage(line)
372
373                         if self._state == self.STATE_DETECT_BAUDRATE or self._state == self.STATE_DETECT_SERIAL:
374                                 if line == '' or time.time() > timeout:
375                                         if len(self._baudrateDetectList) < 1:
376                                                 self.close()
377                                                 self._errorValue = "No more baudrates to test, and no suitable baudrate found."
378                                                 self._changeState(self.STATE_ERROR)
379                                         elif self._baudrateDetectRetry > 0:
380                                                 self._baudrateDetectRetry -= 1
381                                                 self._serial.write('\n')
382                                                 self._log("Baudrate test retry: %d" % (self._baudrateDetectRetry))
383                                                 self._sendCommand("M105")
384                                                 self._testingBaudrate = True
385                                         else:
386                                                 if self._state == self.STATE_DETECT_SERIAL:
387                                                         if len(self._serialDetectList) == 0:
388                                                                 if len(self._baudrateDetectList) == 0:
389                                                                         self._log("Tried all serial ports and baudrates, but still not printer found that responds to M105.")
390                                                                         self._errorValue = 'Failed to autodetect serial port.'
391                                                                         self._changeState(self.STATE_ERROR)
392                                                                         return
393                                                                 else:
394                                                                         self._serialDetectList = serialList(True)
395                                                                         baudrate = self._baudrateDetectList.pop(0)
396                                                         self._serial.close()
397                                                         self._serial = serial.Serial(self._serialDetectList.pop(0), baudrate, timeout=0.5, writeTimeout=10000)
398                                                 else:
399                                                         baudrate = self._baudrateDetectList.pop(0)
400                                                 try:
401                                                         self._setBaudrate(baudrate)
402                                                         self._serial.timeout = 0.5
403                                                         self._log("Trying baudrate: %d" % (baudrate))
404                                                         self._baudrateDetectRetry = 5
405                                                         self._baudrateDetectTestOk = 0
406                                                         timeout = time.time() + 5
407                                                         self._serial.write('\n')
408                                                         self._sendCommand("M105")
409                                                         self._testingBaudrate = True
410                                                 except:
411                                                         self._log("Unexpected error while setting baudrate: %d %s" % (baudrate, getExceptionString()))
412                                 elif 'T:' in line:
413                                         self._baudrateDetectTestOk += 1
414                                         if self._baudrateDetectTestOk < 10:
415                                                 self._log("Baudrate test ok: %d" % (self._baudrateDetectTestOk))
416                                                 self._sendCommand("M105")
417                                         else:
418                                                 self._sendCommand("M999")
419                                                 self._serial.timeout = 2
420                                                 profile.putMachineSetting('serial_baud_auto', self._serial.baudrate)
421                                                 self._changeState(self.STATE_OPERATIONAL)
422                                 else:
423                                         self._testingBaudrate = False
424                         elif self._state == self.STATE_CONNECTING:
425                                 if line == '' or 'wait' in line:        # 'wait' needed for Repetier (kind of watchdog)
426                                         self._sendCommand("M105")
427                                 elif 'ok' in line:
428                                         self._changeState(self.STATE_OPERATIONAL)
429                                 if time.time() > timeout:
430                                         self.close()
431                         elif self._state == self.STATE_OPERATIONAL:
432                                 #Request the temperature on comm timeout (every 2 seconds) when we are not printing.
433                                 if line == '':
434                                         if self._extruderCount > 0:
435                                                 self._temperatureRequestExtruder = (self._temperatureRequestExtruder + 1) % self._extruderCount
436                                                 self._sendCommand("M105 T%d" % (self._temperatureRequestExtruder))
437                                         else:
438                                                 self._sendCommand("M105")
439                                         tempRequestTimeout = time.time() + 5
440                         elif self._state == self.STATE_PRINTING:
441                                 if line == '' and time.time() > timeout:
442                                         self._log("Communication timeout during printing, forcing a line")
443                                         line = 'ok'
444                                 #Even when printing request the temperature every 5 seconds.
445                                 if time.time() > tempRequestTimeout:
446                                         if self._extruderCount > 0:
447                                                 self._temperatureRequestExtruder = (self._temperatureRequestExtruder + 1) % self._extruderCount
448                                                 self._sendCommand("M105 T%d" % (self._temperatureRequestExtruder))
449                                         else:
450                                                 self._sendCommand("M105")
451                                         tempRequestTimeout = time.time() + 5
452                                 if 'ok' in line:
453                                         timeout = time.time() + 5
454                                         if not self._commandQueue.empty():
455                                                 self._sendCommand(self._commandQueue.get())
456                                         else:
457                                                 self._sendNext()
458                                 elif "resend" in line.lower() or "rs" in line:
459                                         try:
460                                                 self._gcodePos = int(line.replace("N:"," ").replace("N"," ").replace(":"," ").split()[-1])
461                                         except:
462                                                 if "rs" in line:
463                                                         self._gcodePos = int(line.split()[1])
464                 self._log("Connection closed, closing down monitor")
465
466         def _setBaudrate(self, baudrate):
467                 #For linux the pyserial implementation lacks TCGETS2 support. So do that ourselves
468                 if sys.platform.startswith('linux'):
469                         try:
470                                 self._serial.baudrate = baudrate
471                         except:
472                                 try:
473                                         # set custom speed
474                                         import fcntl, array, termios
475                                         TCGETS2 = 0x802C542A
476                                         TCSETS2 = 0x402C542B
477                                         BOTHER = 0o010000
478                                         buf = array.array('i', [0] * 64)
479                                         fcntl.ioctl(self._serial.fd, TCGETS2, buf)
480                                         buf[2] &= ~termios.CBAUD
481                                         buf[2] |= BOTHER
482                                         buf[9] = buf[10] = baudrate
483                                         fcntl.ioctl(self._serial.fd, TCSETS2, buf)
484                                 except:
485                                         print getExceptionString()
486                 else:
487                         self._serial.baudrate = baudrate
488
489         def _log(self, message):
490                 self._callback.mcLog(message)
491                 try:
492                         self._logQueue.put(message, False)
493                 except:
494                         #If the log queue is full, remove the first message and append the new message again
495                         self._logQueue.get()
496                         try:
497                                 self._logQueue.put(message, False)
498                         except:
499                                 pass
500
501         def _readline(self):
502                 if self._serial == None:
503                         return None
504                 try:
505                         ret = self._serial.readline()
506                 except:
507                         self._log("Unexpected error while reading serial port: %s" % (getExceptionString()))
508                         self._errorValue = getExceptionString()
509                         self.close(True)
510                         return None
511                 if ret == '':
512                         #self._log("Recv: TIMEOUT")
513                         return ''
514                 self._log("Recv: %s" % (unicode(ret, 'ascii', 'replace').encode('ascii', 'replace').rstrip()))
515                 return ret
516         
517         def close(self, isError = False):
518                 if self._serial != None:
519                         self._serial.close()
520                         if isError:
521                                 self._changeState(self.STATE_CLOSED_WITH_ERROR)
522                         else:
523                                 self._changeState(self.STATE_CLOSED)
524                 self._serial = None
525         
526         def __del__(self):
527                 self.close()
528         
529         def _sendCommand(self, cmd):
530                 if self._serial is None:
531                         return
532                 if 'M109' in cmd or 'M190' in cmd:
533                         self._heatupWaitStartTime = time.time()
534                 if 'M104' in cmd or 'M109' in cmd:
535                         try:
536                                 t = 0
537                                 if 'T' in cmd:
538                                         t = int(re.search('T([0-9]+)', cmd).group(1))
539                                 self._targetTemp[t] = float(re.search('S([0-9]+)', cmd).group(1))
540                         except:
541                                 pass
542                 if 'M140' in cmd or 'M190' in cmd:
543                         try:
544                                 self._bedTargetTemp = float(re.search('S([0-9]+)', cmd).group(1))
545                         except:
546                                 pass
547                 self._log('Send: %s' % (cmd))
548                 try:
549                         self._serial.write(cmd + '\n')
550                 except serial.SerialTimeoutException:
551                         self._log("Serial timeout while writing to serial port, trying again.")
552                         try:
553                                 time.sleep(0.5)
554                                 self._serial.write(cmd + '\n')
555                         except:
556                                 self._log("Unexpected error while writing serial port: %s" % (getExceptionString()))
557                                 self._errorValue = getExceptionString()
558                                 self.close(True)
559                 except:
560                         self._log("Unexpected error while writing serial port: %s" % (getExceptionString()))
561                         self._errorValue = getExceptionString()
562                         self.close(True)
563         
564         def _sendNext(self):
565                 if self._gcodePos >= len(self._gcodeList):
566                         self._changeState(self.STATE_OPERATIONAL)
567                         return
568                 if self._gcodePos == 100:
569                         self._printStartTime100 = time.time()
570                 line = self._gcodeList[self._gcodePos]
571                 if type(line) is tuple:
572                         self._printSection = line[1]
573                         line = line[0]
574                 try:
575                         if line == 'M0' or line == 'M1':
576                                 self.setPause(True)
577                                 line = 'M105'   #Don't send the M0 or M1 to the machine, as M0 and M1 are handled as an LCD menu pause.
578                         if self._printSection in self._feedRateModifier:
579                                 line = re.sub('F([0-9]*)', lambda m: 'F' + str(int(int(m.group(1)) * self._feedRateModifier[self._printSection])), line)
580                         if ('G0' in line or 'G1' in line) and 'Z' in line:
581                                 z = float(re.search('Z([0-9\.]*)', line).group(1))
582                                 if self._currentZ != z:
583                                         self._currentZ = z
584                                         self._callback.mcZChange(z)
585                 except:
586                         self._log("Unexpected error: %s" % (getExceptionString()))
587                 checksum = reduce(lambda x,y:x^y, map(ord, "N%d%s" % (self._gcodePos, line)))
588                 self._sendCommand("N%d%s*%d" % (self._gcodePos, line, checksum))
589                 self._gcodePos += 1
590                 self._callback.mcProgress(self._gcodePos)
591         
592         def sendCommand(self, cmd):
593                 cmd = cmd.encode('ascii', 'replace')
594                 if self.isPrinting():
595                         self._commandQueue.put(cmd)
596                 elif self.isOperational():
597                         self._sendCommand(cmd)
598         
599         def printGCode(self, gcodeList):
600                 if not self.isOperational() or self.isPrinting():
601                         return
602                 self._gcodeList = gcodeList
603                 self._gcodePos = 0
604                 self._printStartTime100 = None
605                 self._printSection = 'CUSTOM'
606                 self._changeState(self.STATE_PRINTING)
607                 self._printStartTime = time.time()
608                 for i in xrange(0, 4):
609                         self._sendNext()
610         
611         def cancelPrint(self):
612                 if self.isOperational():
613                         self._changeState(self.STATE_OPERATIONAL)
614         
615         def setPause(self, pause):
616                 if not pause and self.isPaused():
617                         self._changeState(self.STATE_PRINTING)
618                         for i in xrange(0, 6):
619                                 self._sendNext()
620                 if pause and self.isPrinting():
621                         self._changeState(self.STATE_PAUSED)
622         
623         def setFeedrateModifier(self, type, value):
624                 self._feedRateModifier[type] = value
625
626 def getExceptionString():
627         locationInfo = traceback.extract_tb(sys.exc_info()[2])[0]
628         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])