2012-03-06 16:48:56 +00:00
from __future__ import absolute_import
2012-12-06 13:51:55 +00:00
import os
import glob
import sys
import time
import math
import re
import traceback
import threading
2012-09-06 14:52:05 +00:00
import Queue as queue
2013-03-16 01:24:33 +00:00
import logging
2012-03-06 16:48:56 +00:00
2012-10-04 12:05:09 +00:00
import serial
2012-03-06 16:48:56 +00:00
2013-01-18 22:23:50 +00:00
from octoprint . util . avr_isp import stk500v2
from octoprint . util . avr_isp import ispBase
2012-03-06 16:48:56 +00:00
2013-06-09 16:03:41 +00:00
from octoprint . util import matchesGcode
2013-01-18 22:23:50 +00:00
from octoprint . settings import settings
2012-03-19 10:05:40 +00:00
2012-03-06 16:48:56 +00:00
try :
import _winreg
except :
pass
2012-12-31 12:18:54 +00:00
def isDevVersion ( ) :
gitPath = os . path . abspath ( os . path . join ( os . path . split ( os . path . abspath ( __file__ ) ) [ 0 ] , " ../../.git " ) )
return os . path . exists ( gitPath )
2012-03-06 16:48:56 +00:00
def serialList ( ) :
2012-09-10 13:49:50 +00:00
baselist = [ ]
if os . name == " nt " :
try :
key = _winreg . OpenKey ( _winreg . HKEY_LOCAL_MACHINE , " HARDWARE \\ DEVICEMAP \\ SERIALCOMM " )
i = 0
while ( 1 ) :
baselist + = [ _winreg . EnumValue ( key , i ) [ 1 ] ]
i + = 1
except :
pass
2013-01-01 20:04:00 +00:00
baselist = baselist + glob . glob ( " /dev/ttyUSB* " ) + glob . glob ( " /dev/ttyACM* " ) + glob . glob ( " /dev/tty.usb* " ) + glob . glob ( " /dev/cu.* " ) + glob . glob ( " /dev/rfcomm* " )
2013-02-16 19:28:09 +00:00
prev = settings ( ) . get ( [ " serial " , " port " ] )
2012-09-10 13:49:50 +00:00
if prev in baselist :
baselist . remove ( prev )
baselist . insert ( 0 , prev )
2012-12-31 12:18:54 +00:00
if isDevVersion ( ) :
2013-01-01 20:04:00 +00:00
baselist . append ( " VIRTUAL " )
2012-09-10 13:49:50 +00:00
return baselist
2012-03-06 16:48:56 +00:00
2012-06-30 09:51:43 +00:00
def baudrateList ( ) :
2012-09-10 13:49:50 +00:00
ret = [ 250000 , 230400 , 115200 , 57600 , 38400 , 19200 , 9600 ]
2013-02-16 19:28:09 +00:00
prev = settings ( ) . getInt ( [ " serial " , " baudrate " ] )
2013-01-01 20:04:00 +00:00
if prev in ret :
ret . remove ( prev )
ret . insert ( 0 , prev )
2012-09-10 13:49:50 +00:00
return ret
2012-06-30 09:51:43 +00:00
2012-04-03 09:59:38 +00:00
class VirtualPrinter ( ) :
def __init__ ( self ) :
2013-05-26 16:53:43 +00:00
self . readList = [ ' start \n ' , ' Marlin: Virtual Marlin! \n ' , ' \x80 \n ' , ' SD init fail \n ' ] # no sd card as default startup scenario
2012-04-04 15:02:22 +00:00
self . temp = 0.0
self . targetTemp = 0.0
2012-07-10 15:04:37 +00:00
self . lastTempAt = time . time ( )
2012-06-26 10:05:06 +00:00
self . bedTemp = 1.0
self . bedTargetTemp = 1.0
2013-02-17 16:47:06 +00:00
2013-05-20 17:18:03 +00:00
self . _virtualSd = settings ( ) . getBaseFolder ( " virtualSd " )
2013-05-26 16:53:43 +00:00
self . _sdCardReady = False
2013-05-20 17:18:03 +00:00
self . _sdPrinter = None
self . _sdPrintingSemaphore = threading . Event ( )
self . _selectedSdFile = None
self . _selectedSdFileSize = None
self . _selectedSdFilePos = None
self . _writingToSd = False
2013-05-21 21:30:29 +00:00
self . _newSdFilePos = None
2013-05-20 17:18:03 +00:00
2013-03-16 17:25:39 +00:00
self . currentLine = 0
2013-03-29 21:17:44 +00:00
waitThread = threading . Thread ( target = self . _sendWaitAfterTimeout )
waitThread . start ( )
2012-04-03 09:59:38 +00:00
def write ( self , data ) :
2012-12-17 13:15:40 +00:00
if self . readList is None :
2012-04-03 09:59:38 +00:00
return
2013-05-20 17:18:03 +00:00
# shortcut for writing to SD
if self . _writingToSd and not self . _selectedSdFile is None and not " M29 " in data :
with open ( self . _selectedSdFile , " a " ) as f :
f . write ( data )
2013-05-31 20:41:53 +00:00
self . readList . append ( " ok " )
2013-05-20 17:18:03 +00:00
return
2012-09-02 12:27:24 +00:00
#print "Send: %s" % (data.rstrip())
2012-06-26 10:05:06 +00:00
if ' M104 ' in data or ' M109 ' in data :
2012-04-04 15:02:22 +00:00
try :
2012-12-17 13:15:40 +00:00
self . targetTemp = float ( re . search ( ' S([0-9]+) ' , data ) . group ( 1 ) )
2012-04-04 15:02:22 +00:00
except :
pass
2012-06-26 10:05:06 +00:00
if ' M140 ' in data or ' M190 ' in data :
try :
2012-12-17 13:15:40 +00:00
self . bedTargetTemp = float ( re . search ( ' S([0-9]+) ' , data ) . group ( 1 ) )
2012-06-26 10:05:06 +00:00
except :
pass
2013-05-20 17:18:03 +00:00
2012-04-04 15:02:22 +00:00
if ' M105 ' in data :
2013-03-16 17:25:39 +00:00
# send simulated temperature data
2012-07-06 11:27:06 +00:00
self . readList . append ( " ok T: %.2f / %.2f B: %.2f / %.2f @:64 \n " % ( self . temp , self . targetTemp , self . bedTemp , self . bedTargetTemp ) )
2013-05-20 17:18:03 +00:00
elif ' M20 ' in data :
2013-05-26 16:53:43 +00:00
if self . _sdCardReady :
self . _listSd ( )
elif ' M21 ' in data :
self . _sdCardReady = True
self . readList . append ( " SD card ok " )
elif ' M22 ' in data :
self . _sdCardReady = False
2013-05-20 17:18:03 +00:00
elif ' M23 ' in data :
2013-05-26 16:53:43 +00:00
if self . _sdCardReady :
filename = data . split ( None , 1 ) [ 1 ] . strip ( )
self . _selectSdFile ( filename )
2013-05-20 17:18:03 +00:00
elif ' M24 ' in data :
2013-05-26 16:53:43 +00:00
if self . _sdCardReady :
self . _startSdPrint ( )
2013-05-20 17:18:03 +00:00
elif ' M25 ' in data :
2013-05-26 16:53:43 +00:00
if self . _sdCardReady :
self . _pauseSdPrint ( )
2013-05-21 21:30:29 +00:00
elif ' M26 ' in data :
2013-05-26 16:53:43 +00:00
if self . _sdCardReady :
pos = int ( re . search ( " S([0-9]+) " , data ) . group ( 1 ) )
self . _setSdPos ( pos )
2013-05-20 17:18:03 +00:00
elif ' M27 ' in data :
2013-05-26 16:53:43 +00:00
if self . _sdCardReady :
self . _reportSdStatus ( )
2013-05-20 17:18:03 +00:00
elif ' M28 ' in data :
2013-05-26 16:53:43 +00:00
if self . _sdCardReady :
filename = data . split ( None , 1 ) [ 1 ] . strip ( )
self . _writeSdFile ( filename )
2013-05-20 17:18:03 +00:00
elif ' M29 ' in data :
2013-05-26 16:53:43 +00:00
if self . _sdCardReady :
self . _finishSdFile ( )
2013-05-20 17:18:03 +00:00
elif ' M30 ' in data :
2013-05-26 16:53:43 +00:00
if self . _sdCardReady :
filename = data . split ( None , 1 ) [ 1 ] . strip ( )
self . _deleteSdFile ( filename )
2013-03-16 17:25:39 +00:00
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
2013-03-17 09:58:34 +00:00
self . readList . append ( " Error: Line Number is not Last Line Number \n " )
2013-03-16 17:25:39 +00:00
self . readList . append ( " rs %d \n " % ( self . currentLine - 5 ) )
2012-09-07 12:10:57 +00:00
elif len ( data . strip ( ) ) > 0 :
2012-04-04 15:02:22 +00:00
self . readList . append ( " ok \n " )
2012-04-03 09:59:38 +00:00
2013-03-17 09:58:34 +00:00
if " * " in data :
self . currentLine + = 1
2012-04-03 09:59:38 +00:00
2013-05-20 17:18:03 +00:00
def _listSd ( self ) :
self . readList . append ( " Begin file list " )
for osFile in os . listdir ( self . _virtualSd ) :
self . readList . append ( osFile . upper ( ) )
self . readList . append ( " End file list " )
self . readList . append ( " ok " )
def _selectSdFile ( self , filename ) :
file = os . path . join ( self . _virtualSd , filename ) . lower ( )
if not os . path . exists ( file ) or not os . path . isfile ( file ) :
self . readList . append ( " open failed, File: %s . " % filename )
else :
self . _selectedSdFile = file
self . _selectedSdFileSize = os . stat ( file ) . st_size
self . readList . append ( " File opened: %s Size: %d " % ( filename , self . _selectedSdFileSize ) )
self . readList . append ( " File selected " )
def _startSdPrint ( self ) :
if self . _selectedSdFile is not None :
if self . _sdPrinter is None :
self . _sdPrinter = threading . Thread ( target = self . _sdPrintingWorker )
self . _sdPrinter . start ( )
self . _sdPrintingSemaphore . set ( )
self . readList . append ( " ok " )
def _pauseSdPrint ( self ) :
self . _sdPrintingSemaphore . clear ( )
self . readList . append ( " ok " )
2013-05-21 21:30:29 +00:00
def _setSdPos ( self , pos ) :
self . _newSdFilePos = pos
2013-05-20 17:18:03 +00:00
def _reportSdStatus ( self ) :
if self . _sdPrinter is not None and self . _sdPrintingSemaphore . is_set :
self . readList . append ( " SD printing byte %d / %d " % ( self . _selectedSdFilePos , self . _selectedSdFileSize ) )
else :
self . readList . append ( " Not SD printing " )
def _writeSdFile ( self , filename ) :
file = os . path . join ( self . _virtualSd , filename ) . lower ( )
if os . path . exists ( file ) :
if os . path . isfile ( file ) :
os . remove ( file )
else :
self . readList . append ( " error writing to file " )
self . _writingToSd = True
self . _selectedSdFile = file
2013-05-31 20:41:53 +00:00
self . readList . append ( " Writing to file: %s " % filename )
2013-05-20 17:18:03 +00:00
self . readList . append ( " ok " )
def _finishSdFile ( self ) :
self . _writingToSd = False
self . _selectedSdFile = None
self . readList . append ( " ok " )
def _sdPrintingWorker ( self ) :
self . _selectedSdFilePos = 0
2013-05-26 16:53:43 +00:00
with open ( self . _selectedSdFile , " r " ) as f :
2013-05-20 17:18:03 +00:00
for line in f :
2013-05-21 21:30:29 +00:00
# reset position if requested by client
if self . _newSdFilePos is not None :
f . seek ( self . _newSdFilePos )
self . _newSdFilePos = None
2013-05-20 17:18:03 +00:00
# read current file position
self . _selectedSdFilePos = f . tell ( )
# if we are paused, wait for unpausing
self . _sdPrintingSemaphore . wait ( )
# set target temps
if ' M104 ' in line or ' M109 ' in line :
try :
self . targetTemp = float ( re . search ( ' S([0-9]+) ' , line ) . group ( 1 ) )
except :
pass
if ' M140 ' in line or ' M190 ' in line :
try :
self . bedTargetTemp = float ( re . search ( ' S([0-9]+) ' , line ) . group ( 1 ) )
except :
pass
time . sleep ( 0.01 )
self . _sdPrintingSemaphore . clear ( )
self . _selectedSdFilePos = 0
self . _sdPrinter = None
self . readList . append ( " Done printing file " )
def _deleteSdFile ( self , filename ) :
file = os . path . join ( self . _virtualSd , filename )
if os . path . exists ( file ) and os . path . isfile ( file ) :
os . remove ( file )
self . readList . append ( " ok " )
2012-04-03 09:59:38 +00:00
def readline ( self ) :
2012-12-17 13:15:40 +00:00
if self . readList is None :
2012-04-03 09:59:38 +00:00
return ' '
2012-04-04 15:02:22 +00:00
n = 0
2012-07-10 15:04:37 +00:00
timeDiff = self . lastTempAt - time . time ( )
self . lastTempAt = time . time ( )
if abs ( self . temp - self . targetTemp ) > 1 :
2012-12-17 13:15:40 +00:00
self . temp + = math . copysign ( timeDiff * 10 , self . targetTemp - self . temp )
2012-12-25 10:50:38 +00:00
if self . temp < 0 :
self . temp = 0
2012-07-10 15:04:37 +00:00
if abs ( self . bedTemp - self . bedTargetTemp ) > 1 :
2012-12-17 13:15:40 +00:00
self . bedTemp + = math . copysign ( timeDiff * 10 , self . bedTargetTemp - self . bedTemp )
2012-12-25 10:50:38 +00:00
if self . bedTemp < 0 :
self . bedTemp = 0
2012-04-03 09:59:38 +00:00
while len ( self . readList ) < 1 :
time . sleep ( 0.1 )
2012-04-04 15:02:22 +00:00
n + = 1
if n == 20 :
return ' '
2012-12-17 13:15:40 +00:00
if self . readList is None :
2012-04-03 09:59:38 +00:00
return ' '
2012-10-01 12:36:47 +00:00
time . sleep ( 0.001 )
2012-04-03 09:59:38 +00:00
return self . readList . pop ( 0 )
def close ( self ) :
self . readList = None
2013-03-29 21:17:44 +00:00
def _sendWaitAfterTimeout ( self , timeout = 5 ) :
time . sleep ( timeout )
self . readList . append ( " wait " )
2012-09-06 14:52:05 +00:00
class MachineComPrintCallback ( object ) :
def mcLog ( self , message ) :
2012-10-02 14:06:47 +00:00
pass
2012-09-06 14:52:05 +00:00
2012-10-03 13:11:35 +00:00
def mcTempUpdate ( self , temp , bedTemp , targetTemp , bedTargetTemp ) :
2012-09-06 14:52:05 +00:00
pass
def mcStateChange ( self , state ) :
pass
def mcMessage ( self , message ) :
pass
2013-05-20 17:18:03 +00:00
def mcProgress ( self ) :
2012-09-06 14:52:05 +00:00
pass
2013-05-20 17:18:03 +00:00
2012-09-07 12:10:57 +00:00
def mcZChange ( self , newZ ) :
pass
2012-09-06 14:52:05 +00:00
2013-05-31 20:41:53 +00:00
def mcFileSelected ( self , filename , filesize , sd ) :
pass
2013-05-26 16:53:43 +00:00
def mcSdStateChange ( self , sdReady ) :
pass
2013-05-20 17:18:03 +00:00
def mcSdFiles ( self , files ) :
pass
2013-05-31 20:41:53 +00:00
def mcSdPrintingDone ( self ) :
2013-05-20 17:18:03 +00:00
pass
2013-05-31 20:41:53 +00:00
def mcFileTransferStarted ( self , filename , filesize ) :
2013-05-20 17:18:03 +00:00
pass
2012-09-06 14:52:05 +00:00
class MachineCom ( object ) :
STATE_NONE = 0
2012-10-02 08:58:57 +00:00
STATE_OPEN_SERIAL = 1
STATE_DETECT_SERIAL = 2
STATE_DETECT_BAUDRATE = 3
STATE_CONNECTING = 4
STATE_OPERATIONAL = 5
STATE_PRINTING = 6
STATE_PAUSED = 7
STATE_CLOSED = 8
STATE_ERROR = 9
STATE_CLOSED_WITH_ERROR = 10
2013-05-20 17:18:03 +00:00
STATE_RECEIVING_FILE = 11
2012-09-06 14:52:05 +00:00
def __init__ ( self , port = None , baudrate = None , callbackObject = None ) :
2013-03-16 01:24:33 +00:00
self . _logger = logging . getLogger ( __name__ )
self . _serialLogger = logging . getLogger ( " SERIAL " )
2012-03-19 10:05:40 +00:00
if port == None :
2013-02-16 19:28:09 +00:00
port = settings ( ) . get ( [ " serial " , " port " ] )
2012-03-19 10:05:40 +00:00
if baudrate == None :
2013-02-16 19:28:09 +00:00
settingsBaudrate = settings ( ) . getInt ( [ " serial " , " baudrate " ] )
2013-01-01 20:04:00 +00:00
if settingsBaudrate is None :
2012-06-30 09:51:43 +00:00
baudrate = 0
else :
2013-01-01 20:04:00 +00:00
baudrate = settingsBaudrate
2012-09-06 14:52:05 +00:00
if callbackObject == None :
callbackObject = MachineComPrintCallback ( )
2012-10-02 08:58:57 +00:00
self . _port = port
self . _baudrate = baudrate
2012-09-06 14:52:05 +00:00
self . _callback = callbackObject
self . _state = self . STATE_NONE
2012-09-05 14:10:47 +00:00
self . _serial = None
2012-09-06 14:52:05 +00:00
self . _baudrateDetectList = baudrateList ( )
self . _baudrateDetectRetry = 0
self . _temp = 0
self . _bedTemp = 0
2012-10-03 13:11:35 +00:00
self . _targetTemp = 0
self . _bedTargetTemp = 0
2012-09-06 14:52:05 +00:00
self . _commandQueue = queue . Queue ( )
self . _logQueue = queue . Queue ( 256 )
2013-06-09 16:13:12 +00:00
self . _currentZ = None
2012-10-02 08:58:57 +00:00
self . _heatupWaitStartTime = 0
self . _heatupWaitTimeLost = 0.0
2013-03-16 00:57:05 +00:00
self . _alwaysSendChecksum = settings ( ) . getBoolean ( [ " feature " , " alwaysSendChecksum " ] )
2013-03-16 17:25:39 +00:00
self . _currentLine = 1
self . _resendDelta = None
self . _lastLines = [ ]
2013-03-29 21:17:44 +00:00
2013-05-23 20:37:02 +00:00
self . _sendNextLock = threading . Lock ( )
2013-03-29 21:17:44 +00:00
self . _sendingLock = threading . Lock ( )
2012-09-06 14:52:05 +00:00
self . thread = threading . Thread ( target = self . _monitor )
self . thread . daemon = True
self . thread . start ( )
2013-05-20 17:18:03 +00:00
2013-05-31 20:41:53 +00:00
# SD status data
2013-05-26 16:53:43 +00:00
self . _sdAvailable = False
2013-05-20 17:18:03 +00:00
self . _sdFileList = False
self . _sdFiles = [ ]
2013-05-31 20:41:53 +00:00
# print job
self . _currentFile = None
2012-09-06 14:52:05 +00:00
def _changeState ( self , newState ) :
if self . _state == newState :
2012-09-05 14:10:47 +00:00
return
2013-05-21 21:08:34 +00:00
if newState == self . STATE_CLOSED or newState == self . STATE_CLOSED_WITH_ERROR :
if settings ( ) . get ( [ " feature " , " sdSupport " ] ) :
self . _sdFileList = False
self . _sdFiles = [ ]
self . _callback . mcSdFiles ( [ ] )
2012-09-06 14:52:05 +00:00
oldState = self . getStateString ( )
self . _state = newState
self . _log ( ' Changing monitoring state from \' %s \' to \' %s \' ' % ( oldState , self . getStateString ( ) ) )
self . _callback . mcStateChange ( newState )
def getState ( self ) :
return self . _state
def getStateString ( self ) :
if self . _state == self . STATE_NONE :
return " Offline "
2012-10-02 08:58:57 +00:00
if self . _state == self . STATE_OPEN_SERIAL :
return " Opening serial port "
if self . _state == self . STATE_DETECT_SERIAL :
return " Detecting serial port "
2012-09-06 14:52:05 +00:00
if self . _state == self . STATE_DETECT_BAUDRATE :
2012-10-02 08:58:57 +00:00
return " Detecting baudrate "
2012-09-06 14:52:05 +00:00
if self . _state == self . STATE_CONNECTING :
return " Connecting "
if self . _state == self . STATE_OPERATIONAL :
return " Operational "
if self . _state == self . STATE_PRINTING :
2013-05-31 20:41:53 +00:00
if self . isSdFileSelected ( ) :
2013-05-20 17:18:03 +00:00
return " Printing from SD "
2013-05-31 20:41:53 +00:00
elif self . isStreaming ( ) :
return " Sending file to SD "
2013-05-20 17:18:03 +00:00
else :
return " Printing "
2012-09-06 15:24:34 +00:00
if self . _state == self . STATE_PAUSED :
return " Paused "
2012-09-06 14:52:05 +00:00
if self . _state == self . STATE_CLOSED :
return " Closed "
if self . _state == self . STATE_ERROR :
2012-10-04 12:05:09 +00:00
return " Error: %s " % ( self . getShortErrorString ( ) )
2012-09-06 14:52:05 +00:00
if self . _state == self . STATE_CLOSED_WITH_ERROR :
2012-10-04 12:05:09 +00:00
return " Error: %s " % ( self . getShortErrorString ( ) )
2013-05-20 17:18:03 +00:00
if self . _state == self . STATE_RECEIVING_FILE :
2013-05-21 21:30:29 +00:00
return " Sending file to SD "
2012-09-06 14:52:05 +00:00
return " ? %d ? " % ( self . _state )
2012-10-04 12:05:09 +00:00
def getShortErrorString ( self ) :
if len ( self . _errorValue ) < 20 :
return self . _errorValue
return self . _errorValue [ : 20 ] + " ... "
2012-12-04 07:56:15 +00:00
def getErrorString ( self ) :
return self . _errorValue
2012-10-04 12:05:09 +00:00
2012-09-06 14:52:05 +00:00
def isClosedOrError ( self ) :
return self . _state == self . STATE_ERROR or self . _state == self . STATE_CLOSED_WITH_ERROR or self . _state == self . STATE_CLOSED
2012-10-01 11:47:16 +00:00
def isError ( self ) :
return self . _state == self . STATE_ERROR or self . _state == self . STATE_CLOSED_WITH_ERROR
2012-09-06 14:52:05 +00:00
def isOperational ( self ) :
2013-05-20 17:18:03 +00:00
return self . _state == self . STATE_OPERATIONAL or self . _state == self . STATE_PRINTING or self . _state == self . STATE_PAUSED or self . _state == self . STATE_RECEIVING_FILE
2012-09-06 14:52:05 +00:00
def isPrinting ( self ) :
return self . _state == self . STATE_PRINTING
2013-05-20 17:18:03 +00:00
def isSdPrinting ( self ) :
2013-05-31 20:41:53 +00:00
return self . isSdFileSelected ( ) and self . isPrinting ( )
def isSdFileSelected ( self ) :
return self . _currentFile is not None and isinstance ( self . _currentFile , PrintingSdFileInformation )
def isStreaming ( self ) :
return self . _currentFile is not None and isinstance ( self . _currentFile , StreamingGcodeFileInformation )
2012-10-02 14:06:47 +00:00
def isPaused ( self ) :
return self . _state == self . STATE_PAUSED
2013-05-20 17:18:03 +00:00
def isBusy ( self ) :
2013-05-31 20:41:53 +00:00
return self . isPrinting ( ) or self . isPaused ( )
2013-05-20 17:18:03 +00:00
2013-05-26 16:53:43 +00:00
def isSdReady ( self ) :
return self . _sdAvailable
2013-05-31 20:41:53 +00:00
def getPrintProgress ( self ) :
if self . _currentFile is None :
return None
return self . _currentFile . getProgress ( )
def getPrintFilepos ( self ) :
if self . _currentFile is None :
return None
return self . _currentFile . getFilepos ( )
2012-10-01 12:51:59 +00:00
def getPrintTime ( self ) :
2013-05-31 20:41:53 +00:00
if self . _currentFile is None or self . _currentFile . getStartTime ( ) is None :
return None
2013-01-04 10:08:19 +00:00
else :
2013-05-31 20:41:53 +00:00
return time . time ( ) - self . _currentFile . getStartTime ( )
2012-10-04 13:01:54 +00:00
def getPrintTimeRemainingEstimate ( self ) :
2013-05-31 20:41:53 +00:00
printTime = self . getPrintTime ( )
if printTime is None :
2012-10-04 13:01:54 +00:00
return None
2013-05-20 17:18:03 +00:00
2013-05-31 20:41:53 +00:00
printTime / = 60
progress = self . _currentFile . getProgress ( )
if progress :
printTimeTotal = printTime / progress
return printTimeTotal - printTime
2013-05-20 17:18:03 +00:00
else :
2013-05-31 20:41:53 +00:00
return None
2013-05-20 17:18:03 +00:00
2012-09-06 14:52:05 +00:00
def getTemp ( self ) :
return self . _temp
def getBedTemp ( self ) :
return self . _bedTemp
2012-10-01 11:47:16 +00:00
def getLog ( self ) :
ret = [ ]
while not self . _logQueue . empty ( ) :
ret . append ( self . _logQueue . get ( ) )
for line in ret :
self . _logQueue . put ( line , False )
return ret
2012-09-06 14:52:05 +00:00
def _monitor ( self ) :
2012-10-02 08:58:57 +00:00
#Open the serial port.
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 ( ) :
try :
self . _log ( " Connecting to: %s " % ( p ) )
programmer . connect ( p )
self . _serial = programmer . leaveISP ( )
break
except ispBase . IspError as ( e ) :
self . _log ( " Error while connecting to %s : %s " % ( p , str ( e ) ) )
pass
except :
self . _log ( " Unexpected error while connecting to serial port: %s %s " % ( p , getExceptionString ( ) ) )
programmer . close ( )
elif self . _port == ' VIRTUAL ' :
self . _changeState ( self . STATE_OPEN_SERIAL )
self . _serial = VirtualPrinter ( )
else :
self . _changeState ( self . STATE_OPEN_SERIAL )
try :
self . _log ( " Connecting to: %s " % ( self . _port ) )
if self . _baudrate == 0 :
2012-12-13 09:56:26 +00:00
self . _serial = serial . Serial ( str ( self . _port ) , 115200 , timeout = 0.1 , writeTimeout = 10000 )
2012-10-02 08:58:57 +00:00
else :
2012-12-13 09:56:26 +00:00
self . _serial = serial . Serial ( str ( self . _port ) , self . _baudrate , timeout = 2 , writeTimeout = 10000 )
2012-10-02 08:58:57 +00:00
except :
self . _log ( " Unexpected error while connecting to serial port: %s %s " % ( self . _port , getExceptionString ( ) ) )
if self . _serial == None :
self . _log ( " Failed to open serial port ( %s ) " % ( self . _port ) )
self . _errorValue = ' Failed to autodetect serial port. '
self . _changeState ( self . STATE_ERROR )
return
self . _log ( " Connected to: %s , starting monitor " % ( self . _serial ) )
if self . _baudrate == 0 :
self . _changeState ( self . STATE_DETECT_BAUDRATE )
else :
self . _changeState ( self . STATE_CONNECTING )
#Start monitoring the serial port.
2012-09-07 12:10:57 +00:00
timeout = time . time ( ) + 5
2012-10-01 12:36:47 +00:00
tempRequestTimeout = timeout
2013-05-20 17:18:03 +00:00
sdStatusRequestTimeout = timeout
2013-02-17 16:47:06 +00:00
startSeen = not settings ( ) . getBoolean ( [ " feature " , " waitForStartOnConnect " ] )
2013-06-09 15:08:12 +00:00
heatingUp = False
2012-09-06 14:52:05 +00:00
while True :
line = self . _readline ( )
if line == None :
break
2013-05-20 17:18:03 +00:00
##~~ Error handling
# No matter the state, if we see an error, goto the error state and store the error for reference.
2012-10-03 13:11:35 +00:00
if line . startswith ( ' Error: ' ) :
2012-09-06 14:52:05 +00:00
#Oh YEAH, consistency.
2012-10-03 13:11:35 +00:00
# Marlin reports an MIN/MAX temp error as "Error:x\n: Extruder switched off. MAXTEMP triggered !\n"
2012-09-06 14:52:05 +00:00
# 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.
2012-10-03 13:11:35 +00:00
if re . match ( ' Error:[0-9] \n ' , line ) :
2012-09-06 14:52:05 +00:00
line = line . rstrip ( ) + self . _readline ( )
2012-10-03 13:11:35 +00:00
#Skip the communication errors, as those get corrected.
2013-03-17 09:58:34 +00:00
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 :
2012-10-03 13:11:35 +00:00
pass
2012-10-04 12:05:09 +00:00
elif not self . isError ( ) :
2012-10-03 13:11:35 +00:00
self . _errorValue = line [ 6 : ]
self . _changeState ( self . STATE_ERROR )
2013-05-20 17:18:03 +00:00
##~~ SD file list
# if we are currently receiving an sd file list, each line is just a filename, so just read it and abort processing
if self . _sdFileList and not ' End file list ' in line :
2013-06-09 16:58:03 +00:00
self . _sdFiles . append ( line . strip ( ) . lower ( ) )
2013-05-20 17:18:03 +00:00
continue
##~~ Temperature processing
2012-10-02 08:58:57 +00:00
if ' T: ' in line or line . startswith ( ' T: ' ) :
2013-03-17 09:58:34 +00:00
try :
self . _temp = float ( re . search ( " -?[0-9 \ .]* " , line . split ( ' T: ' ) [ 1 ] ) . group ( 0 ) )
if ' B: ' in line :
self . _bedTemp = float ( re . search ( " -?[0-9 \ .]* " , line . split ( ' B: ' ) [ 1 ] ) . group ( 0 ) )
self . _callback . mcTempUpdate ( self . _temp , self . _bedTemp , self . _targetTemp , self . _bedTargetTemp )
except ValueError :
# catch conversion issues, we'll rather just not get the temperature update instead of killing the connection
pass
2012-10-02 08:58:57 +00:00
#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.
2013-06-09 15:08:12 +00:00
if not ' ok ' in line :
heatingUp = True
if self . _heatupWaitStartTime != 0 :
t = time . time ( )
self . _heatupWaitTimeLost = t - self . _heatupWaitStartTime
self . _heatupWaitStartTime = t
2013-05-20 17:18:03 +00:00
##~~ SD Card handling
2013-05-26 16:53:43 +00:00
elif ' SD init fail ' in line :
self . _sdAvailable = False
self . _sdFiles = [ ]
self . _callback . mcSdStateChange ( self . _sdAvailable )
2013-05-31 20:41:53 +00:00
elif ' Not SD printing ' in line :
if self . isSdFileSelected ( ) and self . isPrinting ( ) :
# something went wrong, printer is reporting that we actually are not printing right now...
self . _sdFilePos = 0
self . _changeState ( self . STATE_OPERATIONAL )
2013-05-26 16:53:43 +00:00
elif ' SD card ok ' in line :
self . _sdAvailable = True
self . refreshSdFiles ( )
self . _callback . mcSdStateChange ( self . _sdAvailable )
2013-05-20 17:18:03 +00:00
elif ' Begin file list ' in line :
self . _sdFiles = [ ]
self . _sdFileList = True
elif ' End file list ' in line :
self . _sdFileList = False
self . _callback . mcSdFiles ( self . _sdFiles )
elif ' SD printing byte ' in line :
# answer to M27, at least on Marlin, Repetier and Sprinter: "SD printing byte %d/%d"
match = re . search ( " ([0-9]*)/([0-9]*) " , line )
2013-05-31 20:41:53 +00:00
self . _currentFile . setFilepos ( int ( match . group ( 1 ) ) )
2013-05-20 17:18:03 +00:00
self . _callback . mcProgress ( )
elif ' File opened ' in line :
2013-05-22 16:25:26 +00:00
# answer to M23, at least on Marlin, Repetier and Sprinter: "File opened:%s Size:%d"
match = re . search ( " File opened: \ s*(.*?) \ s+Size: \ s*([0-9]*) " , line )
2013-05-31 20:41:53 +00:00
self . _currentFile = PrintingSdFileInformation ( match . group ( 1 ) , int ( match . group ( 2 ) ) )
2013-05-20 17:18:03 +00:00
elif ' File selected ' in line :
# final answer to M23, at least on Marlin, Repetier and Sprinter: "File selected"
2013-05-31 20:41:53 +00:00
self . _callback . mcFileSelected ( self . _currentFile . getFilename ( ) , self . _currentFile . getFilesize ( ) , True )
elif ' Writing to file ' in line :
# anwer to M28, at least on Marlin, Repetier and Sprinter: "Writing to file: %s"
self . _printSection = " CUSTOM "
self . _changeState ( self . STATE_PRINTING )
2013-05-20 17:18:03 +00:00
elif ' Done printing file ' in line :
# printer is reporting file finished printing
self . _sdFilePos = 0
self . _changeState ( self . STATE_OPERATIONAL )
2013-05-31 20:41:53 +00:00
self . _callback . mcPrintjobDone ( )
2013-05-20 17:18:03 +00:00
##~~ Message handling
2013-03-16 00:57:05 +00:00
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 ( ) :
2012-09-06 14:52:05 +00:00
self . _callback . mcMessage ( line )
2013-06-09 15:44:06 +00:00
if " ok " in line and heatingUp :
heatingUp = False
2013-03-17 09:58:34 +00:00
### Baudrate detection
2012-09-06 14:52:05 +00:00
if self . _state == self . STATE_DETECT_BAUDRATE :
2012-09-07 12:10:57 +00:00
if line == ' ' or time . time ( ) > timeout :
2012-09-06 14:52:05 +00:00
if len ( self . _baudrateDetectList ) < 1 :
self . close ( )
2012-10-04 12:05:09 +00:00
self . _errorValue = " No more baudrates to test, and no suitable baudrate found. "
self . _changeState ( self . STATE_ERROR )
2012-09-06 14:52:05 +00:00
elif self . _baudrateDetectRetry > 0 :
self . _baudrateDetectRetry - = 1
self . _serial . write ( ' \n ' )
2012-10-04 12:05:09 +00:00
self . _log ( " Baudrate test retry: %d " % ( self . _baudrateDetectRetry ) )
2012-09-06 14:52:05 +00:00
self . _sendCommand ( " M105 " )
2012-10-04 12:05:09 +00:00
self . _testingBaudrate = True
2012-09-06 14:52:05 +00:00
else :
baudrate = self . _baudrateDetectList . pop ( 0 )
try :
self . _serial . baudrate = baudrate
self . _serial . timeout = 0.5
self . _log ( " Trying baudrate: %d " % ( baudrate ) )
self . _baudrateDetectRetry = 5
2012-10-04 12:05:09 +00:00
self . _baudrateDetectTestOk = 0
2012-09-07 12:10:57 +00:00
timeout = time . time ( ) + 5
2012-09-06 14:52:05 +00:00
self . _serial . write ( ' \n ' )
self . _sendCommand ( " M105 " )
2012-10-04 12:05:09 +00:00
self . _testingBaudrate = True
2012-09-06 14:52:05 +00:00
except :
self . _log ( " Unexpected error while setting baudrate: %d %s " % ( baudrate , getExceptionString ( ) ) )
2012-10-04 12:05:09 +00:00
elif ' ok ' in line and ' T: ' in line :
self . _baudrateDetectTestOk + = 1
if self . _baudrateDetectTestOk < 10 :
self . _log ( " Baudrate test ok: %d " % ( self . _baudrateDetectTestOk ) )
self . _sendCommand ( " M105 " )
else :
self . _sendCommand ( " M999 " )
self . _serial . timeout = 2
self . _changeState ( self . STATE_OPERATIONAL )
2013-06-03 20:37:08 +00:00
if self . _sdAvailable :
self . refreshSdFiles ( )
2012-10-04 12:05:09 +00:00
else :
self . _testingBaudrate = False
2013-03-17 09:58:34 +00:00
### Connection attempt
2012-09-06 14:52:05 +00:00
elif self . _state == self . STATE_CONNECTING :
2013-04-01 15:31:02 +00:00
if ( line == " " or " wait " in line ) and startSeen :
2012-09-06 14:52:05 +00:00
self . _sendCommand ( " M105 " )
2013-03-16 00:57:05 +00:00
elif " start " in line :
2013-02-17 16:47:06 +00:00
startSeen = True
2013-04-01 15:31:02 +00:00
elif " ok " in line and startSeen :
2012-09-06 14:52:05 +00:00
self . _changeState ( self . STATE_OPERATIONAL )
2013-06-03 20:37:08 +00:00
if self . _sdAvailable :
self . refreshSdFiles ( )
2013-01-13 14:45:39 +00:00
elif time . time ( ) > timeout :
2012-09-06 14:52:05 +00:00
self . close ( )
2013-03-17 09:58:34 +00:00
### Operational
2013-03-01 22:19:58 +00:00
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.
2013-03-16 00:57:05 +00:00
if line == " " or " wait " in line :
2013-03-29 21:17:44 +00:00
if self . _resendDelta is not None :
self . _resendNextCommand ( )
elif not self . _commandQueue . empty ( ) :
self . _sendCommand ( self . _commandQueue . get ( ) )
else :
self . _sendCommand ( " M105 " )
2012-09-07 12:10:57 +00:00
tempRequestTimeout = time . time ( ) + 5
2013-03-29 21:17:44 +00:00
# resend -> start resend procedure from requested line
elif " resend " in line . lower ( ) or " rs " in line :
self . _handleResendRequest ( line )
2013-03-17 09:58:34 +00:00
### Printing
2012-09-06 14:52:05 +00:00
elif self . _state == self . STATE_PRINTING :
2013-03-16 00:57:05 +00:00
if line == " " and time . time ( ) > timeout :
2013-05-22 16:56:02 +00:00
self . _log ( " Communication timeout during printing, forcing a line " )
line = ' ok '
2013-05-31 20:41:53 +00:00
if self . isSdPrinting ( ) :
2013-06-09 15:08:12 +00:00
if time . time ( ) > tempRequestTimeout and not heatingUp :
2013-05-26 16:53:43 +00:00
self . _sendCommand ( " M105 " )
tempRequestTimeout = time . time ( ) + 5
2013-06-09 15:08:12 +00:00
if time . time ( ) > sdStatusRequestTimeout and not heatingUp :
2013-05-26 16:53:43 +00:00
self . _sendCommand ( " M27 " )
2013-05-20 17:18:03 +00:00
sdStatusRequestTimeout = time . time ( ) + 1
2013-05-26 16:53:43 +00:00
if ' ok ' or ' SD printing byte ' in line :
timeout = time . time ( ) + 5
2013-05-20 17:18:03 +00:00
else :
2013-05-26 16:53:43 +00:00
# Even when printing request the temperature every 5 seconds.
2013-05-31 20:41:53 +00:00
if time . time ( ) > tempRequestTimeout and not self . isStreaming ( ) :
2013-05-26 16:53:43 +00:00
self . _commandQueue . put ( " M105 " )
tempRequestTimeout = time . time ( ) + 5
2013-05-20 17:18:03 +00:00
if ' ok ' in line :
timeout = time . time ( ) + 5
2013-05-26 16:53:43 +00:00
if self . _resendDelta is not None :
self . _resendNextCommand ( )
2013-05-31 20:41:53 +00:00
elif not self . _commandQueue . empty ( ) and not self . isStreaming ( ) :
2013-05-20 17:18:03 +00:00
self . _sendCommand ( self . _commandQueue . get ( ) )
else :
self . _sendNext ( )
elif " resend " in line . lower ( ) or " rs " in line :
2013-05-23 20:53:34 +00:00
self . _handleResendRequest ( line )
2012-09-06 14:52:05 +00:00
self . _log ( " Connection closed, closing down monitor " )
2013-03-29 21:17:44 +00:00
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 ( )
2012-09-05 14:10:47 +00:00
def _log ( self , message ) :
2012-09-06 14:52:05 +00:00
self . _callback . mcLog ( message )
2013-03-16 01:24:33 +00:00
self . _serialLogger . debug ( message )
2012-09-06 14:52:05 +00:00
try :
self . _logQueue . put ( message , False )
except :
#If the log queue is full, remove the first message and append the new message again
self . _logQueue . get ( )
2012-10-10 10:23:53 +00:00
try :
self . _logQueue . put ( message , False )
except :
pass
2012-03-06 16:48:56 +00:00
2012-09-06 14:52:05 +00:00
def _readline ( self ) :
2012-09-05 14:10:47 +00:00
if self . _serial == None :
2012-04-01 17:16:31 +00:00
return None
2012-07-19 12:27:59 +00:00
try :
2012-09-05 14:10:47 +00:00
ret = self . _serial . readline ( )
2012-07-19 12:27:59 +00:00
except :
2012-09-06 09:38:54 +00:00
self . _log ( " Unexpected error while reading serial port: %s " % ( getExceptionString ( ) ) )
2012-09-06 14:52:05 +00:00
self . _errorValue = getExceptionString ( )
self . close ( True )
return None
if ret == ' ' :
#self._log("Recv: TIMEOUT")
2012-09-05 14:10:47 +00:00
return ' '
2012-10-01 13:14:47 +00:00
self . _log ( " Recv: %s " % ( unicode ( ret , ' ascii ' , ' replace ' ) . encode ( ' ascii ' , ' replace ' ) . rstrip ( ) ) )
2012-03-07 21:45:41 +00:00
return ret
2012-03-06 22:11:40 +00:00
2012-09-06 14:52:05 +00:00
def close ( self , isError = False ) :
2012-09-05 14:10:47 +00:00
if self . _serial != None :
self . _serial . close ( )
2012-09-06 14:52:05 +00:00
if isError :
self . _changeState ( self . STATE_CLOSED_WITH_ERROR )
else :
self . _changeState ( self . STATE_CLOSED )
2012-09-05 14:10:47 +00:00
self . _serial = None
2013-05-21 21:08:34 +00:00
if settings ( ) . get ( [ " feature " , " sdSupport " ] ) :
self . _sdFileList = [ ]
2012-05-09 20:47:27 +00:00
def __del__ ( self ) :
self . close ( )
2013-03-16 17:25:39 +00:00
def _resendNextCommand ( self ) :
2013-03-29 21:17:44 +00:00
# Make sure we are only handling one sending job at a time
with self . _sendingLock :
self . _logger . debug ( " Resending line %d , delta is %d , history log is %s items strong " % ( self . _currentLine - self . _resendDelta , self . _resendDelta , len ( self . _lastLines ) ) )
cmd = self . _lastLines [ - self . _resendDelta ]
lineNumber = self . _currentLine - self . _resendDelta
2013-03-16 17:25:39 +00:00
2013-03-29 21:17:44 +00:00
self . _doSendWithChecksum ( cmd , lineNumber )
2013-03-16 17:25:39 +00:00
2013-03-29 21:17:44 +00:00
self . _resendDelta - = 1
if self . _resendDelta < = 0 :
self . _resendDelta = None
2013-03-16 17:25:39 +00:00
2013-03-16 00:57:05 +00:00
def _sendCommand ( self , cmd , sendChecksum = False ) :
2013-03-29 21:17:44 +00:00
# Make sure we are only handling one sending job at a time
with self . _sendingLock :
if self . _serial is None :
return
2013-06-09 16:03:41 +00:00
if matchesGcode ( cmd , " M109 " ) or matchesGcode ( cmd , " M190 " ) :
2013-03-29 21:17:44 +00:00
self . _heatupWaitStartTime = time . time ( )
2013-06-09 16:03:41 +00:00
if matchesGcode ( cmd , " M104 " ) or matchesGcode ( cmd , " M109 " ) :
2013-03-16 17:25:39 +00:00
try :
2013-03-29 21:17:44 +00:00
self . _targetTemp = float ( re . search ( ' S([0-9]+) ' , cmd ) . group ( 1 ) )
except :
pass
2013-06-09 16:03:41 +00:00
if matchesGcode ( cmd , " M140 " ) or matchesGcode ( cmd , " M190 " ) :
2013-03-29 21:17:44 +00:00
try :
self . _bedTargetTemp = float ( re . search ( ' S([0-9]+) ' , cmd ) . group ( 1 ) )
2013-03-16 17:25:39 +00:00
except :
pass
2013-06-09 16:03:41 +00:00
if matchesGcode ( cmd , " M110 " ) :
2013-03-29 21:17:44 +00:00
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 )
2013-03-16 17:25:39 +00:00
if newLineNumber is not None :
self . _currentLine = newLineNumber + 1
2013-03-29 21:17:44 +00:00
# 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 )
2013-03-16 17:25:39 +00:00
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 ) :
2013-03-16 00:57:05 +00:00
if sendChecksum or self . _alwaysSendChecksum :
if self . _alwaysSendChecksum :
lineNumber = self . _currentLine
2013-03-16 17:25:39 +00:00
else :
2013-05-31 20:41:53 +00:00
lineNumber = self . _currentFile . getLineCount ( )
2013-03-16 17:25:39 +00:00
self . _addToLastLines ( cmd )
self . _currentLine + = 1
2013-03-29 21:17:44 +00:00
self . _doSendWithChecksum ( cmd , lineNumber )
2013-03-16 17:25:39 +00:00
else :
self . _doSendWithoutChecksum ( cmd )
2013-03-29 21:17:44 +00:00
def _doSendWithChecksum ( self , cmd , lineNumber ) :
2013-03-16 17:25:39 +00:00
self . _logger . debug ( " Sending cmd ' %s ' with lineNumber %r " % ( cmd , lineNumber ) )
2013-06-09 14:33:59 +00:00
checksum = reduce ( lambda x , y : x ^ y , map ( ord , " N %d %s " % ( lineNumber , cmd ) ) )
commandToSend = " N %d %s * %d " % ( lineNumber , cmd , checksum )
2013-03-16 17:25:39 +00:00
self . _doSendWithoutChecksum ( commandToSend )
2013-03-16 00:57:05 +00:00
2013-03-16 17:25:39 +00:00
def _doSendWithoutChecksum ( self , cmd ) :
self . _log ( " Send: %s " % cmd )
2012-09-05 14:10:47 +00:00
try :
2012-09-20 14:34:08 +00:00
self . _serial . write ( cmd + ' \n ' )
2012-10-04 12:05:09 +00:00
except serial . SerialTimeoutException :
2012-10-03 13:11:35 +00:00
self . _log ( " Serial timeout while writing to serial port, trying again. " )
try :
self . _serial . write ( cmd + ' \n ' )
except :
self . _log ( " Unexpected error while writing serial port: %s " % ( getExceptionString ( ) ) )
self . _errorValue = getExceptionString ( )
self . close ( True )
2012-09-05 14:10:47 +00:00
except :
2012-09-06 09:38:54 +00:00
self . _log ( " Unexpected error while writing serial port: %s " % ( getExceptionString ( ) ) )
2012-09-06 14:52:05 +00:00
self . _errorValue = getExceptionString ( )
self . close ( True )
2013-03-16 00:57:05 +00:00
2012-09-06 14:52:05 +00:00
def _sendNext ( self ) :
2013-05-23 20:37:02 +00:00
with self . _sendNextLock :
2013-05-31 20:41:53 +00:00
line = self . _currentFile . getNext ( )
if line is None :
if self . isStreaming ( ) :
self . _sendCommand ( " M29 " )
self . _currentFile = None
self . _callback . mcFileTransferDone ( )
else :
self . _callback . mcPrintjobDone ( )
2013-05-23 20:37:02 +00:00
self . _changeState ( self . STATE_OPERATIONAL )
return
2013-05-31 20:41:53 +00:00
2013-05-23 20:37:02 +00:00
if type ( line ) is tuple :
self . _printSection = line [ 1 ]
line = line [ 0 ]
2013-05-31 20:41:53 +00:00
if not self . isStreaming ( ) :
2013-06-09 16:29:28 +00:00
try :
if matchesGcode ( line , " M0 " ) or matchesGcode ( line , " M1 " ) :
self . setPause ( True )
line = " M105 " # Don't send the M0 or M1 to the machine, as M0 and M1 are handled as an LCD menu pause.
if ( matchesGcode ( line , " G0 " ) or matchesGcode ( line , " G1 " ) ) and ' Z ' in line :
z = float ( re . search ( ' Z([0-9 \ .]*) ' , line ) . group ( 1 ) )
if self . _currentZ != z :
self . _currentZ = z
self . _callback . mcZChange ( z )
except :
self . _log ( " Unexpected error: %s " % ( getExceptionString ( ) ) )
2013-06-09 17:30:42 +00:00
self . _sendCommand ( line , True )
2013-05-26 16:53:43 +00:00
self . _callback . mcProgress ( )
2012-09-06 14:52:05 +00:00
def sendCommand ( self , cmd ) :
cmd = cmd . encode ( ' ascii ' , ' replace ' )
2013-05-31 20:41:53 +00:00
if self . isPrinting ( ) and not self . isSdFileSelected ( ) :
2012-09-06 14:52:05 +00:00
self . _commandQueue . put ( cmd )
elif self . isOperational ( ) :
self . _sendCommand ( cmd )
2013-05-31 20:41:53 +00:00
def startPrint ( self ) :
2012-09-06 14:52:05 +00:00
if not self . isOperational ( ) or self . isPrinting ( ) :
return
2013-05-31 20:41:53 +00:00
if self . _currentFile is None :
raise ValueError ( " No file selected for printing " )
self . _printSection = " CUSTOM "
2012-09-06 14:52:05 +00:00
self . _changeState ( self . STATE_PRINTING )
2013-05-20 17:18:03 +00:00
2013-05-31 20:41:53 +00:00
try :
self . _currentFile . start ( )
if self . isSdFileSelected ( ) :
if self . isPaused ( ) :
self . sendCommand ( " M26 S0 " )
self . _currentFile . setFilepos ( 0 )
self . sendCommand ( " M24 " )
else :
self . _sendNext ( )
except :
self . _changeState ( self . STATE_ERROR )
self . _errorValue = getExceptionString ( )
def startFileTransfer ( self , filename , remoteFilename ) :
if not self . isOperational ( ) or self . isBusy ( ) :
2013-05-20 17:18:03 +00:00
return
2013-05-31 20:41:53 +00:00
self . _currentFile = StreamingGcodeFileInformation ( filename )
self . _currentFile . start ( )
2013-05-20 17:18:03 +00:00
2013-05-31 20:41:53 +00:00
self . sendCommand ( " M28 %s " % remoteFilename )
self . _callback . mcFileTransferStarted ( remoteFilename , self . _currentFile . getFilesize ( ) )
def selectFile ( self , filename , sd ) :
if self . isBusy ( ) :
return
if sd :
if not self . isOperational ( ) :
# printer is not connected, can't use SD
return
self . sendCommand ( " M23 %s " % filename )
else :
self . _currentFile = PrintingGcodeFileInformation ( filename )
self . _callback . mcFileSelected ( filename , self . _currentFile . getFilesize ( ) , False )
2013-05-20 17:18:03 +00:00
2012-09-06 14:52:05 +00:00
def cancelPrint ( self ) :
2013-05-31 20:41:53 +00:00
if not self . isOperational ( ) or self . isStreaming ( ) :
return
self . _changeState ( self . STATE_OPERATIONAL )
if self . isSdFileSelected ( ) :
2013-05-21 21:30:29 +00:00
self . sendCommand ( " M25 " ) # pause print
self . sendCommand ( " M26 S0 " ) # reset position in file to byte 0
2012-09-06 15:24:34 +00:00
def setPause ( self , pause ) :
2013-05-31 20:41:53 +00:00
if self . isStreaming ( ) :
return
2012-09-06 15:24:34 +00:00
if not pause and self . isPaused ( ) :
self . _changeState ( self . STATE_PRINTING )
2013-05-31 20:41:53 +00:00
if self . isSdFileSelected ( ) :
2013-05-20 17:18:03 +00:00
self . sendCommand ( " M24 " )
else :
2013-05-31 20:41:53 +00:00
self . _sendNext ( )
2012-09-06 15:24:34 +00:00
if pause and self . isPrinting ( ) :
self . _changeState ( self . STATE_PAUSED )
2013-05-31 20:41:53 +00:00
if self . isSdFileSelected ( ) :
2013-05-21 21:30:29 +00:00
self . sendCommand ( " M25 " ) # pause print
2013-05-20 17:18:03 +00:00
2013-05-31 20:41:53 +00:00
##~~ SD card handling
2013-05-20 17:18:03 +00:00
def getSdFiles ( self ) :
return self . _sdFiles
def startSdFileTransfer ( self , filename ) :
2013-05-31 20:41:53 +00:00
if not self . isOperational ( ) or self . isBusy ( ) :
2013-05-20 17:18:03 +00:00
return
2013-05-26 16:53:43 +00:00
2013-05-20 17:18:03 +00:00
self . _changeState ( self . STATE_RECEIVING_FILE )
2013-05-22 16:20:21 +00:00
self . sendCommand ( " M28 %s " % filename . lower ( ) )
2013-05-20 17:18:03 +00:00
def endSdFileTransfer ( self , filename ) :
2013-05-31 20:41:53 +00:00
if not self . isOperational ( ) or self . isBusy ( ) :
2013-05-26 16:53:43 +00:00
return
2013-05-22 16:20:21 +00:00
self . sendCommand ( " M29 %s " % filename . lower ( ) )
2013-05-20 17:18:03 +00:00
self . _changeState ( self . STATE_OPERATIONAL )
2013-05-26 16:53:43 +00:00
self . refreshSdFiles ( )
2013-05-20 17:18:03 +00:00
def deleteSdFile ( self , filename ) :
2013-05-31 20:41:53 +00:00
if not self . isOperational ( ) or ( self . isBusy ( ) and self . _sdFile == filename . lower ( ) ) :
2013-05-26 16:53:43 +00:00
# do not delete a file from sd we are currently printing from
return
2013-05-22 16:20:21 +00:00
self . sendCommand ( " M30 %s " % filename . lower ( ) )
2013-05-26 16:53:43 +00:00
self . refreshSdFiles ( )
def refreshSdFiles ( self ) :
2013-06-03 20:37:08 +00:00
if not self . isOperational ( ) or self . isBusy ( ) :
2013-05-26 16:53:43 +00:00
return
2013-05-20 17:18:03 +00:00
self . sendCommand ( " M20 " )
2013-05-26 16:53:43 +00:00
def initSdCard ( self ) :
if not self . isOperational ( ) :
return
self . sendCommand ( " M21 " )
def releaseSdCard ( self ) :
2013-05-31 20:41:53 +00:00
if not self . isOperational ( ) or ( self . isBusy ( ) and self . isSdFileSelected ( ) ) :
2013-05-26 16:53:43 +00:00
# do not release the sd card if we are currently printing from it
return
self . sendCommand ( " M22 " )
self . _sdAvailable = False
self . _sdFiles = [ ]
self . _callback . mcSdStateChange ( self . _sdAvailable )
self . _callback . mcSdFiles ( self . _sdFiles )
2012-09-06 09:38:54 +00:00
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 ] )
2013-05-31 20:41:53 +00:00
class PrintingFileInformation ( object ) :
"""
Encapsulates information regarding the current file being printed : file name , current position , total size and
time the print started .
Allows to reset the current file position to 0 and to calculate the current progress as a floating point
value between 0 and 1.
"""
def __init__ ( self , filename ) :
self . _filename = filename
self . _filepos = 0
self . _filesize = None
self . _startTime = None
def getStartTime ( self ) :
return self . _startTime
def getFilename ( self ) :
return self . _filename
def getFilesize ( self ) :
return self . _filesize
def getFilepos ( self ) :
return self . _filepos
def getProgress ( self ) :
"""
The current progress of the file , calculated as relation between file position and absolute size . Returns - 1
if file size is None or < 1.
"""
if self . _filesize is None or not self . _filesize > 0 :
return - 1
return float ( self . _filepos ) / float ( self . _filesize )
def reset ( self ) :
"""
Resets the current file position to 0.
"""
self . _filepos = 0
def start ( self ) :
"""
Marks the print job as started and remembers the start time .
"""
self . _startTime = time . time ( )
class PrintingSdFileInformation ( PrintingFileInformation ) :
"""
Encapsulates information regarding an ongoing print from SD .
"""
def __init__ ( self , filename , filesize ) :
PrintingFileInformation . __init__ ( self , filename )
self . _filesize = filesize
def setFilepos ( self , filepos ) :
"""
Sets the current file position .
"""
self . _filepos = filepos
class PrintingGcodeFileInformation ( PrintingFileInformation ) :
"""
Encapsulates information regarding an ongoing direct print . Takes care of the needed file handle and ensures
that the file is closed in case of an error .
"""
def __init__ ( self , filename ) :
PrintingFileInformation . __init__ ( self , filename )
self . _filehandle = None
self . _lineCount = 0
self . _firstLine = None
self . _prevLineType = None
if not os . path . exists ( self . _filename ) or not os . path . isfile ( self . _filename ) :
raise IOError ( " File %s does not exist " % self . _filename )
self . _filesize = os . stat ( self . _filename ) . st_size
def start ( self ) :
"""
Opens the file for reading and determines the file size . Start time won ' t be recorded until 100 lines in
"""
self . _filehandle = open ( self . _filename , " r " )
self . _lineCount = 0
self . _prevLineType = " CUSTOM "
def getNext ( self ) :
"""
Retrieves the next line for printing .
"""
if self . _filehandle is None :
raise ValueError ( " File %s is not open for reading " % self . _filename )
if self . _lineCount == 0 :
self . _lineCount + = 1
return " M110 N0 "
try :
processedLine = None
while processedLine is None :
if self . _filehandle is None :
# file got closed just now
return None
line = self . _filehandle . readline ( )
if not line :
self . _filehandle . close ( )
self . _filehandle = None
processedLine = self . _processLine ( line )
2013-06-09 13:55:55 +00:00
self . _lineCount + = 1
2013-05-31 20:41:53 +00:00
self . _filepos = self . _filehandle . tell ( )
if self . _lineCount > = 100 and self . _startTime is None :
self . _startTime = time . time ( )
return processedLine
except Exception as ( e ) :
if self . _filehandle is not None :
self . _filehandle . close ( )
raise e
def getLineCount ( self ) :
return self . _lineCount
def _processLine ( self , line ) :
lineType = self . _prevLineType
if line . startswith ( " ;TYPE: " ) :
lineType = line [ 6 : ] . strip ( )
if " ; " in line :
line = line [ 0 : line . find ( " ; " ) ]
line = line . strip ( )
if len ( line ) > 0 :
if self . _prevLineType != lineType :
return line , lineType
else :
return line
else :
return None
class StreamingGcodeFileInformation ( PrintingGcodeFileInformation ) :
pass