from __future__ import absolute_import
-import __init__
+__copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
-import os, glob, sys, time, math, re, traceback, threading
+import os
+import glob
+import sys
+import time
+import math
+import re
+import traceback
+import threading
+import platform
import Queue as queue
-from serial import Serial
+import serial
-from avr_isp import stk500v2
-from avr_isp import ispBase
-from avr_isp import intelHex
+from Cura.avr_isp import stk500v2
+from Cura.avr_isp import ispBase
-from util import profile
-from util import version
+from Cura.util import profile
+from Cura.util import version
try:
import _winreg
except:
pass
-def serialList():
+def serialList(forAutoDetect=False):
baselist=[]
- if os.name=="nt":
+ if platform.system() == "Windows":
try:
key=_winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE,"HARDWARE\\DEVICEMAP\\SERIALCOMM")
i=0
- while(1):
- baselist+=[_winreg.EnumValue(key,i)[1]]
+ while True:
+ values = _winreg.EnumValue(key, i)
+ if not forAutoDetect or 'USBSER' in values[0]:
+ baselist+=[values[1]]
i+=1
except:
pass
- baselist = baselist + glob.glob('/dev/ttyUSB*') + glob.glob('/dev/ttyACM*') + glob.glob("/dev/tty.usb*") + glob.glob("/dev/cu.*") + glob.glob("/dev/rfcomm*")
- prev = profile.getPreference('serial_port_auto')
- if prev in baselist:
- baselist.remove(prev)
- baselist.insert(0, prev)
- if version.isDevVersion():
+ if forAutoDetect:
+ baselist = baselist + glob.glob('/dev/ttyUSB*') + glob.glob('/dev/ttyACM*') + glob.glob("/dev/cu.usb*")
+ baselist = filter(lambda s: not 'Bluetooth' in s, baselist)
+ prev = profile.getMachineSetting('serial_port_auto')
+ if prev in baselist:
+ baselist.remove(prev)
+ baselist.insert(0, prev)
+ else:
+ baselist = baselist + glob.glob('/dev/ttyUSB*') + glob.glob('/dev/ttyACM*') + glob.glob("/dev/cu.*") + glob.glob("/dev/tty.usb*") + glob.glob("/dev/rfcomm*")
+ if version.isDevVersion() and not forAutoDetect:
baselist.append('VIRTUAL')
return baselist
+def machineIsConnected():
+ #UltiGCode is designed for SD-Card printing, so never auto-detect the serial port.
+ port = profile.getMachineSetting('serial_port')
+ if port == 'AUTO':
+ if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
+ return False
+ return len(serialList(True)) > 0
+ if platform.system() == "Windows":
+ return port in serialList()
+ return os.path.isfile(port)
+
def baudrateList():
ret = [250000, 230400, 115200, 57600, 38400, 19200, 9600]
- if profile.getPreference('serial_baud_auto') != '':
- prev = int(profile.getPreference('serial_baud_auto'))
+ if profile.getMachineSetting('serial_baud_auto') != '':
+ prev = int(profile.getMachineSetting('serial_baud_auto'))
if prev in ret:
ret.remove(prev)
ret.insert(0, prev)
self.bedTargetTemp = 1.0
def write(self, data):
- if self.readList == None:
+ if self.readList is None:
return
#print "Send: %s" % (data.rstrip())
if 'M104' in data or 'M109' in data:
try:
- self.targetTemp = float(data[data.find('S')+1:])
+ self.targetTemp = float(re.search('S([0-9]+)', data).group(1))
except:
pass
if 'M140' in data or 'M190' in data:
try:
- self.bedTargetTemp = float(data[data.find('S')+1:])
+ self.bedTargetTemp = float(re.search('S([0-9]+)', data).group(1))
except:
pass
if 'M105' in data:
self.readList.append("ok\n")
def readline(self):
- if self.readList == None:
+ if self.readList is None:
return ''
n = 0
timeDiff = self.lastTempAt - time.time()
self.lastTempAt = time.time()
if abs(self.temp - self.targetTemp) > 1:
- self.temp += math.copysign(timeDiff, self.targetTemp - self.temp)
+ self.temp += math.copysign(timeDiff * 10, self.targetTemp - self.temp)
if abs(self.bedTemp - self.bedTargetTemp) > 1:
- self.bedTemp += math.copysign(timeDiff, self.bedTargetTemp - self.bedTemp)
+ self.bedTemp += math.copysign(timeDiff * 10, self.bedTargetTemp - self.bedTemp)
while len(self.readList) < 1:
time.sleep(0.1)
n += 1
if n == 20:
return ''
- if self.readList == None:
+ if self.readList is None:
return ''
time.sleep(0.001)
#print "Recv: %s" % (self.readList[0].rstrip())
class MachineComPrintCallback(object):
def mcLog(self, message):
- print(message)
+ pass
- def mcTempUpdate(self, temp, bedTemp):
+ def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
pass
def mcStateChange(self, state):
STATE_CLOSED_WITH_ERROR = 10
def __init__(self, port = None, baudrate = None, callbackObject = None):
- if port == None:
- port = profile.getPreference('serial_port')
- if baudrate == None:
- if profile.getPreference('serial_baud') == 'AUTO':
+ if port is None:
+ port = profile.getMachineSetting('serial_port')
+ if baudrate is None:
+ if profile.getMachineSetting('serial_baud') == 'AUTO':
baudrate = 0
else:
- baudrate = int(profile.getPreference('serial_baud'))
- if callbackObject == None:
+ baudrate = int(profile.getMachineSetting('serial_baud'))
+ if callbackObject is None:
callbackObject = MachineComPrintCallback()
self._port = port
self._serial = None
self._baudrateDetectList = baudrateList()
self._baudrateDetectRetry = 0
- self._temp = 0
+ self._extruderCount = int(profile.getMachineSetting('extruder_amount'))
+ self._temperatureRequestExtruder = 0
+ self._temp = [0] * self._extruderCount
+ self._targetTemp = [0] * self._extruderCount
self._bedTemp = 0
+ self._bedTargetTemp = 0
self._gcodeList = None
self._gcodePos = 0
self._commandQueue = queue.Queue()
self._currentZ = -1
self._heatupWaitStartTime = 0
self._heatupWaitTimeLost = 0.0
+ self._printStartTime100 = None
self.thread = threading.Thread(target=self._monitor)
self.thread.daemon = True
if self._state == self.STATE_CLOSED:
return "Closed"
if self._state == self.STATE_ERROR:
- return "Error: %s" % (self._errorValue)
+ return "Error: %s" % (self.getShortErrorString())
if self._state == self.STATE_CLOSED_WITH_ERROR:
- return "Error: %s" % (self._errorValue)
+ return "Error: %s" % (self.getShortErrorString())
return "?%d?" % (self._state)
+ def getShortErrorString(self):
+ if len(self._errorValue) < 20:
+ return self._errorValue
+ return self._errorValue[:20] + "..."
+
+ def getErrorString(self):
+ return self._errorValue
+
def isClosedOrError(self):
return self._state == self.STATE_ERROR or self._state == self.STATE_CLOSED_WITH_ERROR or self._state == self.STATE_CLOSED
def isPrinting(self):
return self._state == self.STATE_PRINTING
+ def isPaused(self):
+ return self._state == self.STATE_PAUSED
+
def getPrintPos(self):
return self._gcodePos
def getPrintTime(self):
- return time.time() - self._printStartTime - self._heatupWaitTimeLost
-
- def isPaused(self):
- return self._state == self.STATE_PAUSED
+ return time.time() - self._printStartTime
+
+ def getPrintTimeRemainingEstimate(self):
+ if self._printStartTime100 is None or self.getPrintPos() < 200:
+ return None
+ printTime = (time.time() - self._printStartTime100) / 60
+ printTimeTotal = printTime * (len(self._gcodeList) - 100) / (self.getPrintPos() - 100)
+ printTimeLeft = printTimeTotal - printTime
+ return printTimeLeft
def getTemp(self):
return self._temp
if self._port == 'AUTO':
self._changeState(self.STATE_DETECT_SERIAL)
programmer = stk500v2.Stk500v2()
- self._log("Serial port list: %s" % (str(serialList())))
- for p in serialList():
+ self._log("Serial port list: %s" % (str(serialList(True))))
+ for p in serialList(True):
try:
self._log("Connecting to: %s" % (p))
programmer.connect(p)
self._serial = programmer.leaveISP()
- profile.putPreference('serial_port_auto', p)
+ profile.putMachineSetting('serial_port_auto', p)
break
except ispBase.IspError as (e):
self._log("Error while connecting to %s: %s" % (p, str(e)))
try:
self._log("Connecting to: %s" % (self._port))
if self._baudrate == 0:
- self._serial = Serial(self._port, 115200, timeout=0.1, writeTimeout=10000)
+ self._serial = serial.Serial(str(self._port), 115200, timeout=0.1, writeTimeout=10000)
else:
- self._serial = Serial(self._port, self._baudrate, timeout=2, writeTimeout=10000)
+ self._serial = serial.Serial(str(self._port), self._baudrate, timeout=2, writeTimeout=10000)
except:
self._log("Unexpected error while connecting to serial port: %s %s" % (self._port, getExceptionString()))
- if self._serial == None:
+ if self._serial is None:
self._log("Failed to open serial port (%s)" % (self._port))
self._errorValue = 'Failed to autodetect serial port.'
self._changeState(self.STATE_ERROR)
self._changeState(self.STATE_CONNECTING)
#Start monitoring the serial port.
- timeout = time.time() + 5
+ if self._state == self.STATE_CONNECTING:
+ timeout = time.time() + 15
+ else:
+ timeout = time.time() + 5
tempRequestTimeout = timeout
while True:
line = self._readline()
- if line == None:
+ if line is None:
break
- #No matter the state, if we see an error, goto the error state and store the error for reference.
- if line.startswith('Error: '):
+ #No matter the state, if we see an fatal error, goto the error state and store the error for reference.
+ # Only goto error on known fatal errors.
+ if line.startswith('Error:'):
#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 !!"
# 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()
- self._errorValue = line
- self._changeState(self.STATE_ERROR)
+ #Skip the communication errors, as those get corrected.
+ 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:
+ if not self.isError():
+ self._errorValue = line[6:]
+ self._changeState(self.STATE_ERROR)
if ' T:' in line or line.startswith('T:'):
- self._temp = float(re.search("[0-9\.]*", line.split('T:')[1]).group(0))
+ self._temp[self._temperatureRequestExtruder] = 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._callback.mcTempUpdate(self._temp, self._bedTemp, self._targetTemp, self._bedTargetTemp)
#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:
t = time.time()
self._heatupWaitTimeLost = t - self._heatupWaitStartTime
self._heatupWaitStartTime = t
- elif line.strip() != 'ok' and self.isOperational():
+ 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():
self._callback.mcMessage(line)
if self._state == self.STATE_DETECT_BAUDRATE:
if line == '' or time.time() > timeout:
if len(self._baudrateDetectList) < 1:
- self._log("No more baudrates to test, and no suitable baudrate found.")
self.close()
+ self._errorValue = "No more baudrates to test, and no suitable baudrate found."
+ self._changeState(self.STATE_ERROR)
elif self._baudrateDetectRetry > 0:
self._baudrateDetectRetry -= 1
self._serial.write('\n')
+ self._log("Baudrate test retry: %d" % (self._baudrateDetectRetry))
self._sendCommand("M105")
+ self._testingBaudrate = True
else:
baudrate = self._baudrateDetectList.pop(0)
try:
- self._serial.baudrate = baudrate
+ self._setBaudrate(baudrate)
self._serial.timeout = 0.5
self._log("Trying baudrate: %d" % (baudrate))
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:
- self._serial.timeout = 2
- profile.putPreference('serial_baud_auto', self._serial.baudrate)
- self._changeState(self.STATE_OPERATIONAL)
+ elif '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
+ profile.putMachineSetting('serial_baud_auto', self._serial.baudrate)
+ self._changeState(self.STATE_OPERATIONAL)
+ else:
+ self._testingBaudrate = False
elif self._state == self.STATE_CONNECTING:
- if line == '':
+ if line == '' or 'wait' in line: # wait needed for Repetier (kind of watchdog)
self._sendCommand("M105")
elif 'ok' in line:
self._changeState(self.STATE_OPERATIONAL)
elif self._state == self.STATE_OPERATIONAL:
#Request the temperature on comm timeout (every 2 seconds) when we are not printing.
if line == '':
- self._sendCommand("M105")
+ if self._extruderCount > 0:
+ self._temperatureRequestExtruder = (self._temperatureRequestExtruder + 1) % self._extruderCount
+ self._sendCommand("M105 T%d" % (self._temperatureRequestExtruder))
+ else:
+ self._sendCommand("M105")
tempRequestTimeout = time.time() + 5
elif self._state == self.STATE_PRINTING:
if line == '' and time.time() > timeout:
self._log("Communication timeout during printing, forcing a line")
line = 'ok'
- #Even when printing request the temperture every 5 seconds.
+ #Even when printing request the temperature every 5 seconds.
if time.time() > tempRequestTimeout:
- self._commandQueue.put("M105")
+ if self._extruderCount > 0:
+ self._temperatureRequestExtruder = (self._temperatureRequestExtruder + 1) % self._extruderCount
+ self._sendCommand("M105 T%d" % (self._temperatureRequestExtruder))
+ else:
+ self._sendCommand("M105")
tempRequestTimeout = time.time() + 5
if 'ok' in line:
timeout = time.time() + 5
if "rs" in line:
self._gcodePos = int(line.split()[1])
self._log("Connection closed, closing down monitor")
-
+
+ def _setBaudrate(self, baudrate):
+ #For linux the pyserial implementation lacks TCGETS2 support. So do that ourselves
+ if sys.platform.startswith('linux'):
+ try:
+ self._serial.baudrate = baudrate
+ except:
+ try:
+ # set custom speed
+ import fcntl, array, termios
+ TCGETS2 = 0x802C542A
+ TCSETS2 = 0x402C542B
+ BOTHER = 0o010000
+ buf = array.array('i', [0] * 64)
+ fcntl.ioctl(self._serial.fd, TCGETS2, buf)
+ buf[2] &= ~termios.CBAUD
+ buf[2] |= BOTHER
+ buf[9] = buf[10] = baudrate
+ fcntl.ioctl(self._serial.fd, TCSETS2, buf)
+ except:
+ print getExceptionString()
+ else:
+ self._serial.baudrate = baudrate
+
def _log(self, message):
self._callback.mcLog(message)
try:
except:
#If the log queue is full, remove the first message and append the new message again
self._logQueue.get()
- self._logQueue.put(message, False)
+ try:
+ self._logQueue.put(message, False)
+ except:
+ pass
def _readline(self):
if self._serial == None:
self.close()
def _sendCommand(self, cmd):
- if self._serial == None:
+ 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:
+ t = 0
+ if 'T' in cmd:
+ t = int(re.search('T([0-9]+)', cmd).group(1))
+ self._targetTemp[t] = 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
self._log('Send: %s' % (cmd))
try:
- #TODO: This can throw a write timeout exception, but we do not want timeout on writes. Find a fix for this.
- # Oddly enough, the write timeout is not even set and thus we should not get a write timeout.
self._serial.write(cmd + '\n')
+ except serial.SerialTimeoutException:
+ self._log("Serial timeout while writing to serial port, trying again.")
+ try:
+ time.sleep(0.5)
+ self._serial.write(cmd + '\n')
+ except:
+ self._log("Unexpected error while writing serial port: %s" % (getExceptionString()))
+ self._errorValue = getExceptionString()
+ self.close(True)
except:
self._log("Unexpected error while writing serial port: %s" % (getExceptionString()))
self._errorValue = getExceptionString()
if self._gcodePos >= len(self._gcodeList):
self._changeState(self.STATE_OPERATIONAL)
return
+ if self._gcodePos == 100:
+ self._printStartTime100 = time.time()
line = self._gcodeList[self._gcodePos]
if type(line) is tuple:
self._printSection = line[1]
return
self._gcodeList = gcodeList
self._gcodePos = 0
+ self._printStartTime100 = None
self._printSection = 'CUSTOM'
self._changeState(self.STATE_PRINTING)
self._printStartTime = time.time()
- for i in xrange(0, 6):
+ for i in xrange(0, 4):
self._sendNext()
def cancelPrint(self):
def getExceptionString():
locationInfo = traceback.extract_tb(sys.exc_info()[2])[0]
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])
-