chiark / gitweb /
Major update on machine communication. Part 1, this breaks realtime speed tuning...
authordaid <daid303@gmail.com>
Thu, 6 Sep 2012 14:52:05 +0000 (16:52 +0200)
committerdaid <daid303@gmail.com>
Thu, 6 Sep 2012 14:52:05 +0000 (16:52 +0200)
Cura/gui/printWindow.py
Cura/util/machineCom.py

index 6eaeb3eac987e8442f4570e132951e190e48eda6..70e373a03611b70e624d04990a67faa92cc99f19 100644 (file)
@@ -68,11 +68,13 @@ class PrintCommandButton(buttons.GenBitmapButton):
                self.Bind(wx.EVT_BUTTON, self.OnClick)\r
 \r
        def OnClick(self, e):\r
-               if self.parent.printIdx != None:\r
+               if self.parent.machineCom == None or self.parent.machineCom.isPrinting():\r
                        return;\r
-               self.parent.sendCommand("G91")\r
-               self.parent.sendCommand(self.command)\r
-               self.parent.sendCommand("G90")\r
+               if self.command.startswith('G1'):\r
+                       self.parent.machineCom.sendCommand("G91")\r
+               self.parent.machineCom.sendCommand(self.command)\r
+               if self.command.startswith('G1'):\r
+                       self.parent.machineCom.sendCommand("G90")\r
                e.Skip()\r
 \r
 class printWindow(wx.Frame):\r
@@ -80,12 +82,9 @@ class printWindow(wx.Frame):
        def __init__(self):\r
                super(printWindow, self).__init__(None, -1, title='Printing')\r
                self.machineCom = None\r
-               self.machineConnected = False\r
-               self.thread = None\r
                self.gcode = None\r
                self.gcodeList = None\r
                self.sendList = []\r
-               self.printIdx = None\r
                self.temp = None\r
                self.bedTemp = None\r
                self.bufferLineCount = 4\r
@@ -114,7 +113,7 @@ class printWindow(wx.Frame):
                \r
                sb = wx.StaticBox(self.panel, label="Statistics")\r
                boxsizer = wx.StaticBoxSizer(sb, wx.VERTICAL)\r
-               self.statsText = wx.StaticText(self.panel, -1, "Filament: ####.##m #.##g\nPrint time: #####:##")\r
+               self.statsText = wx.StaticText(self.panel, -1, "Filament: ####.##m #.##g\nPrint time: #####:##\nMachine state: Detecting baudrate")\r
                boxsizer.Add(self.statsText, flag=wx.LEFT, border=5)\r
                \r
                self.sizer.Add(boxsizer, pos=(0,0), span=(5,1), flag=wx.EXPAND)\r
@@ -276,10 +275,10 @@ class printWindow(wx.Frame):
                self.Centre()\r
 \r
                self.UpdateButtonStates()\r
-               self.UpdateProgress()\r
+               #self.UpdateProgress()\r
        \r
        def OnCameraTimer(self, e):\r
-               if self.printIdx != None:\r
+               if self.machineCom != None and not self.machineCom.isPrinting():\r
                        return\r
                self.cam.takeNewImage()\r
                self.camPage.Refresh()\r
@@ -296,14 +295,14 @@ class printWindow(wx.Frame):
                        dc.DrawBitmap(self.cam.getLastImage(), 0, 0)\r
 \r
        def UpdateButtonStates(self):\r
-               self.connectButton.Enable(not self.machineConnected)\r
+               self.connectButton.Enable(self.machineCom == None or self.machineCom.isClosedOrError())\r
                #self.loadButton.Enable(self.printIdx == None)\r
-               self.printButton.Enable(self.machineConnected and self.gcodeList != None and self.printIdx == None)\r
-               self.pauseButton.Enable(self.printIdx != None)\r
-               self.cancelButton.Enable(self.printIdx != None)\r
-               self.temperatureSelect.Enable(self.machineConnected)\r
-               self.bedTemperatureSelect.Enable(self.machineConnected)\r
-               self.directControlPanel.Enable(self.machineConnected)\r
+               self.printButton.Enable(self.machineCom != None and self.machineCom.isOperational() and not self.machineCom.isPrinting())\r
+               self.pauseButton.Enable(self.machineCom != None and self.machineCom.isPrinting())\r
+               self.cancelButton.Enable(self.machineCom != None and self.machineCom.isPrinting())\r
+               self.temperatureSelect.Enable(self.machineCom != None and self.machineCom.isOperational())\r
+               self.bedTemperatureSelect.Enable(self.machineCom != None and self.machineCom.isOperational())\r
+               self.directControlPanel.Enable(self.machineCom != None and self.machineCom.isOperational())\r
        \r
        def UpdateProgress(self):\r
                status = ""\r
@@ -315,68 +314,62 @@ class printWindow(wx.Frame):
                        if cost != False:\r
                                status += "Filament cost: %s\n" % (cost)\r
                        status += "Print time: %02d:%02d\n" % (int(self.gcode.totalMoveTimeMinute / 60), int(self.gcode.totalMoveTimeMinute % 60))\r
-               if self.printIdx == None:\r
+               if self.machineCom == None or not self.machineCom.isPrinting():\r
                        self.progress.SetValue(0)\r
                        if self.gcodeList != None:\r
                                status += 'Line: -/%d\n' % (len(self.gcodeList))\r
                else:\r
-                       status += 'Line: %d/%d\n' % (self.printIdx, len(self.gcodeList))\r
+                       status += 'Line: %d/%d\n' % (self.machineCom.getPrintPos(), len(self.gcodeList))\r
                        status += 'Height: %f\n' % (self.currentZ)\r
-                       self.progress.SetValue(self.printIdx)\r
-               if self.temp != None:\r
-                       status += 'Temp: %d\n' % (self.temp)\r
-               if self.bedTemp != None and self.bedTemp > 0:\r
-                       status += 'Bed Temp: %d\n' % (self.bedTemp)\r
-                       self.bedTemperatureLabel.Show(True)\r
-                       self.bedTemperatureSelect.Show(True)\r
-                       self.temperaturePanel.Layout()\r
+                       self.progress.SetValue(self.machineCom.getPrintPos())\r
+               if self.machineCom != None:\r
+                       if self.machineCom.getTemp() > 0:\r
+                               status += 'Temp: %d\n' % (self.machineCom.getTemp())\r
+                       if self.machineCom.getBedTemp() > 0:\r
+                               status += 'Bed Temp: %d\n' % (self.machineCom.getBedTemp())\r
+                               self.bedTemperatureLabel.Show(True)\r
+                               self.bedTemperatureSelect.Show(True)\r
+                               self.temperaturePanel.Layout()\r
+                       status += 'Machine state: %s\n' % (self.machineCom.getStateString())\r
+               \r
                self.statsText.SetLabel(status.strip())\r
-               #self.Layout()\r
        \r
        def OnConnect(self, e):\r
                if self.machineCom != None:\r
                        self.machineCom.close()\r
-                       self.thread.join()\r
-               self.machineCom = machineCom.MachineCom()\r
-               self.thread = threading.Thread(target=self.PrinterMonitor)\r
-               self.thread.start()\r
+               self.machineCom = machineCom.MachineCom(callbackObject=self)\r
                self.UpdateButtonStates()\r
        \r
        def OnLoad(self, e):\r
                pass\r
        \r
        def OnPrint(self, e):\r
-               if not self.machineConnected:\r
+               if self.machineCom == None or not self.machineCom.isOperational():\r
                        return\r
                if self.gcodeList == None:\r
                        return\r
-               if self.printIdx != None:\r
+               if self.machineCom.isPrinting():\r
                        return\r
                self.currentZ = -1\r
                if self.cam != None:\r
                        self.cam.startTimelaps(self.filename[: self.filename.rfind('.')] + ".mpg")\r
-               self.printIdx = 1\r
-               self.sendLine(0)\r
-               self.sendCnt = self.bufferLineCount\r
+               self.machineCom.printGCode(self.gcodeList)\r
                self.UpdateButtonStates()\r
        \r
        def OnCancel(self, e):\r
                if self.cam != None:\r
                        self.cam.endTimelaps()\r
-               self.printIdx = None\r
-               self.pause = False\r
                self.pauseButton.SetLabel('Pause')\r
-               self.sendCommand("M84")\r
+               self.machineCom.cancelPrint()\r
+               self.machineCom.sendCommand("M84")\r
                self.UpdateButtonStates()\r
        \r
        def OnPause(self, e):\r
-               if self.pause:\r
-                       self.pause = False\r
-                       self.sendLine(self.printIdx)\r
-                       self.printIdx += 1\r
+               if self.machineCom.isPaused():\r
+                       self.machineCom.setPause(False)\r
                        self.pauseButton.SetLabel('Pause')\r
                else:\r
-                       self.pause = True\r
+                       self.machineCom.setPause(True)\r
                        self.pauseButton.SetLabel('Resume')\r
        \r
        def OnClose(self, e):\r
@@ -384,14 +377,13 @@ class printWindow(wx.Frame):
                printWindowHandle = None\r
                if self.machineCom != None:\r
                        self.machineCom.close()\r
-                       self.thread.join()\r
                self.Destroy()\r
 \r
        def OnTempChange(self, e):\r
-               self.sendCommand("M104 S%d" % (self.temperatureSelect.GetValue()))\r
+               self.machineCom.sendCommand("M104 S%d" % (self.temperatureSelect.GetValue()))\r
 \r
        def OnBedTempChange(self, e):\r
-               self.sendCommand("M140 S%d" % (self.bedTemperatureSelect.GetValue()))\r
+               self.machineCom.sendCommand("M140 S%d" % (self.bedTemperatureSelect.GetValue()))\r
        \r
        def OnSpeedChange(self, e):\r
                self.feedrateRatioOuterWall = self.outerWallSpeedSelect.GetValue() / 100.0\r
@@ -407,7 +399,7 @@ class printWindow(wx.Frame):
                if line == '':\r
                        return\r
                self.termLog.AppendText('>%s\n' % (line))\r
-               self.sendCommand(line)\r
+               self.machineCom.sendCommand(line)\r
                self.termHistory.append(line)\r
                self.termHistoryIdx = len(self.termHistory)\r
                self.termInput.SetValue('')\r
@@ -427,7 +419,7 @@ class printWindow(wx.Frame):
                e.Skip()\r
 \r
        def LoadGCodeFile(self, filename):\r
-               if self.printIdx != None:\r
+               if self.machineCom != None and self.machineCom.isPrinting():\r
                        return\r
                #Send an initial M110 to reset the line counter to zero.\r
                lineType = 'CUSTOM'\r
@@ -454,13 +446,6 @@ class printWindow(wx.Frame):
                wx.CallAfter(self.UpdateButtonStates)\r
                wx.CallAfter(self.UpdateProgress)\r
                \r
-       def sendCommand(self, cmd):\r
-               if self.machineConnected:\r
-                       if self.printIdx == None or self.pause:\r
-                               self.machineCom.sendCommand(cmd)\r
-                       else:\r
-                               self.sendList.append(cmd)\r
-\r
        def sendLine(self, lineNr):\r
                if lineNr >= len(self.gcodeList):\r
                        return False\r
@@ -495,58 +480,22 @@ class printWindow(wx.Frame):
                self.machineCom.sendCommand("N%d%s*%d" % (lineNr, line, checksum))\r
                return True\r
 \r
-       def PrinterMonitor(self):\r
-               while True:\r
-                       line = self.machineCom.readline()\r
-                       if line == None:\r
-                               self.machineConnected = False\r
-                               wx.CallAfter(self.UpdateButtonStates)\r
-                               return\r
-                       if line == '':  #When we have a communication "timeout" and we're not sending gcode, then read the temperature.\r
-                               if self.printIdx == None or self.pause:\r
-                                       self.machineCom.sendCommand("M105")\r
-                               else:\r
-                                       wx.CallAfter(self.AddTermLog, '!!Comm timeout, forcing next line!!\n')\r
-                                       line = 'ok'\r
-                       if self.machineConnected:\r
-                               while self.sendCnt > 0 and not self.pause:\r
-                                       self.sendLine(self.printIdx)\r
-                                       self.printIdx += 1\r
-                                       self.sendCnt -= 1\r
-                       if line.startswith("start"):\r
-                               self.machineConnected = True\r
-                               wx.CallAfter(self.UpdateButtonStates)\r
-                       elif 'T:' in line:\r
-                               self.temp = float(re.search("[0-9\.]*", line.split('T:')[1]).group(0))\r
-                               if 'B:' in line:\r
-                                       self.bedTemp = float(re.search("[0-9\.]*", line.split('B:')[1]).group(0))\r
-                               self.temperatureGraph.addPoint(self.temp, self.temperatureSelect.GetValue(), self.bedTemp, self.bedTemperatureSelect.GetValue())\r
-                               wx.CallAfter(self.UpdateProgress)\r
-                       elif line.strip() != 'ok':\r
-                               wx.CallAfter(self.AddTermLog, line)\r
-                       if self.printIdx != None:\r
-                               if line.startswith("ok"):\r
-                                       if len(self.sendList) > 0:\r
-                                               self.machineCom.sendCommand(self.sendList.pop(0))\r
-                                       elif self.pause:\r
-                                               self.sendCnt += 1\r
-                                       else:\r
-                                               if self.sendLine(self.printIdx):\r
-                                                       self.printIdx += 1\r
-                                               else:\r
-                                                       if self.cam != None:\r
-                                                               self.cam.endTimelaps()\r
-                                                       self.printIdx = None\r
-                                                       wx.CallAfter(self.UpdateButtonStates)\r
-                                               wx.CallAfter(self.UpdateProgress)\r
-                               elif "resend" in line.lower() or "rs" in line:\r
-                                       try:\r
-                                               lineNr=int(line.replace("N:"," ").replace("N"," ").replace(":"," ").split()[-1])\r
-                                       except:\r
-                                               if "rs" in line:\r
-                                                       lineNr=int(line.split()[1])\r
-                                       self.printIdx = lineNr\r
-                                       #we should actually resend the line here, but we also get an "ok" for each error from Marlin. And thus we'll resend on the OK.\r
+       def mcLog(self, message):\r
+               #print message\r
+               pass\r
+       \r
+       def mcTempUpdate(self, temp, bedTemp):\r
+               self.temperatureGraph.addPoint(temp, self.temperatureSelect.GetValue(), bedTemp, self.bedTemperatureSelect.GetValue())\r
+       \r
+       def mcStateChange(self, state):\r
+               wx.CallAfter(self.UpdateButtonStates)\r
+               wx.CallAfter(self.UpdateProgress)\r
+       \r
+       def mcMessage(self, message):\r
+               wx.CallAfter(self.AddTermLog, message)\r
+       \r
+       def mcProgress(self, lineNr):\r
+               wx.CallAfter(self.UpdateProgress)\r
 \r
 class temperatureGraph(wx.Panel):\r
        def __init__(self, parent):\r
index 96143d345efbb281ac73f24ba0d5672ad926325a..dd19231c884c9e030afb7e029adc4e1e1d8b9d11 100644 (file)
@@ -1,7 +1,8 @@
 from __future__ import absolute_import
 import __init__
 
-import os, glob, sys, time, math, traceback
+import os, glob, sys, time, math, re, traceback, threading
+import Queue as queue
 
 from serial import Serial
 
@@ -84,9 +85,33 @@ class VirtualPrinter():
        def close(self):
                self.readList = None
 
-class MachineCom():
-       def __init__(self, port = None, baudrate = None, logCallback = None):
-               self._logCallback = logCallback
+class MachineComPrintCallback(object):
+       def mcLog(self, message):
+               print(message)
+       
+       def mcTempUpdate(self, temp, bedTemp):
+               pass
+       
+       def mcStateChange(self, state):
+               pass
+       
+       def mcMessage(self, message):
+               pass
+       
+       def mcProgress(self, lineNr):
+               pass
+
+class MachineCom(object):
+       STATE_NONE = 0
+       STATE_DETECT_BAUDRATE = 1
+       STATE_CONNECTING = 2
+       STATE_OPERATIONAL = 3
+       STATE_PRINTING = 4
+       STATE_CLOSED = 5
+       STATE_ERROR = 6
+       STATE_CLOSED_WITH_ERROR = 7
+       
+       def __init__(self, port = None, baudrate = None, callbackObject = None):
                if port == None:
                        port = profile.getPreference('serial_port')
                if baudrate == None:
@@ -94,7 +119,21 @@ class MachineCom():
                                baudrate = 0
                        else:
                                baudrate = int(profile.getPreference('serial_baud'))
+               if callbackObject == None:
+                       callbackObject = MachineComPrintCallback()
+
+               self._callback = callbackObject
+               self._state = self.STATE_NONE
                self._serial = None
+               self._baudrateDetectList = baudrateList()
+               self._baudrateDetectRetry = 0
+               self._temp = 0
+               self._bedTemp = 0
+               self._gcodeList = None
+               self._gcodePos = 0
+               self._commandQueue = queue.Queue()
+               self._logQueue = queue.Queue(256)
+               
                if port == 'AUTO':
                        programmer = stk500v2.Stk500v2()
                        self._log("Serial port list: %s" % (str(serialList())))
@@ -103,83 +142,200 @@ class MachineCom():
                                        self._log("Connecting to: %s" % (port))
                                        programmer.connect(port)
                                        self._serial = programmer.leaveISP()
-                                       self._configureSerialWithBaudrate(baudrate)
                                        break
                                except ispBase.IspError as (e):
                                        self._log("Error while connecting to %s: %s" % (port, str(e)))
                                        pass
                                except:
                                        self._log("Unexpected error while connecting to serial port: %s %s" % (port, getExceptionString()))
-                       programmer.close()
+                               programmer.close()
                elif port == 'VIRTUAL':
                        self._serial = VirtualPrinter()
                else:
                        try:
-                               self._serial = Serial(port, 115200, timeout=2)
-                               self._configureSerialWithBaudrate(baudrate)
+                               self._log("Connecting to: %s" % (port))
+                               if baudrate == 0:
+                                       self._serial = Serial(port, 115200, timeout=0.1)
+                               else:
+                                       self._serial = Serial(port, baudrate, timeout=2)
                        except:
                                self._log("Unexpected error while connecting to serial port: %s %s" % (port, getExceptionString()))
-                               print 
-               print self._serial
+               self._log("Connected to: %s, starting monitor" % (self._serial))
+               if baudrate == 0:
+                       self._changeState(self.STATE_DETECT_BAUDRATE)
+               else:
+                       self._changeState(self.STATE_CONNECTING)
+               self.thread = threading.Thread(target=self._monitor)
+               self.thread.daemon = True
+               self.thread.start()
        
-       def _configureSerialWithBaudrate(self, baudrate):
-               if baudrate != 0:
-                       self._serial.baudrate = baudrate
+       def _changeState(self, newState):
+               if self._state == newState:
                        return
-               for baudrate in baudrateList():
-                       try:
-                               self._serial.baudrate = baudrate
-                               self._log("Trying baudrate: %d" % (baudrate))
-                       except:
-                               self._log("Unexpected error while setting baudrate: %d %s" % (baudrate, getExceptionString()))
-                               continue
-                       time.sleep(0.5)
-                       starttime = time.time()
-                       self.sendCommand("\nM105")
-                       for line in self._serial:
-                               self._log("Recv: %s" % (unicode(line, 'utf-8', 'replace').rstrip()))
-                               if 'start' in line:
-                                       return
+               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"
+               if self._state == self.STATE_DETECT_BAUDRATE:
+                       return "Detect baudrate"
+               if self._state == self.STATE_CONNECTING:
+                       return "Connecting"
+               if self._state == self.STATE_OPERATIONAL:
+                       return "Operational"
+               if self._state == self.STATE_PRINTING:
+                       return "Printing"
+               if self._state == self.STATE_CLOSED:
+                       return "Closed"
+               if self._state == self.STATE_ERROR:
+                       return "Error: %s" % (self._errorValue)
+               if self._state == self.STATE_CLOSED_WITH_ERROR:
+                       return "Error: %s" % (self._errorValue)
+               return "?%d?" % (self._state)
+       
+       def isClosedOrError(self):
+               return self._state == self.STATE_ERROR or self._state == self.STATE_CLOSED_WITH_ERROR or self._state == self.STATE_CLOSED
+       
+       def isOperational(self):
+               return self._state == self.STATE_OPERATIONAL or self._state == self.STATE_PRINTING
+       
+       def isPrinting(self):
+               return self._state == self.STATE_PRINTING
+       
+       def getPrintPos(self):
+               return self._gcodePos
+       
+       def isPaused(self):
+               return False
+       
+       def getTemp(self):
+               return self._temp
+       
+       def getBedTemp(self):
+               return self._bedTemp
+       
+       def _monitor(self):
+               self._timeoutTime = time.time() + 5
+               while True:
+                       line = self._readline()
+                       if line == 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: '):
+                               #Oh YEAH, consistency.
+                               # 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):
+                                       line = line.rstrip() + self._readline()
+                               self._errorValue = line
+                               self._changeState(self.STATE_ERROR)
+                       if 'T:' in line:
+                               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)
+                       elif line.strip() != 'ok':
+                               self._callback.mcMessage(line)
+
+                       if self._state == self.STATE_DETECT_BAUDRATE:
+                               if line == '' or time.time() > self._timeoutTime:
+                                       if len(self._baudrateDetectList) < 1:
+                                               self._log("No more baudrates to test, and no suitable baudrate found.")
+                                               self.close()
+                                       elif self._baudrateDetectRetry > 0:
+                                               self._baudrateDetectRetry -= 1
+                                               self._serial.write('\n')
+                                               self._sendCommand("M105")
+                                       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
+                                                       self._timeoutTime = time.time() + 5
+                                                       self._serial.write('\n')
+                                                       self._sendCommand("M105")
+                                               except:
+                                                       self._log("Unexpected error while setting baudrate: %d %s" % (baudrate, getExceptionString()))
+                               elif 'ok' in line:
+                                       self._serial.timeout = 2
+                                       self._changeState(self.STATE_OPERATIONAL)
+                       elif self._state == self.STATE_CONNECTING:
+                               if line == '':
+                                       self._sendCommand("M105")
+                               elif 'ok' in line:
+                                       self._changeState(self.STATE_OPERATIONAL)
+                               if time.time() > self._timeoutTime:
+                                       self.close()
+                       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")
+                       elif self._state == self.STATE_PRINTING:
+                               if line == '' and time.time() > self._timeoutTime:
+                                       self._log("Communication timeout during printing, forcing a line")
+                                       line = 'ok'
                                if 'ok' in line:
-                                       return
-                               #Timeout in case we get a lot of crap data from some random device.
-                               if starttime - time.time() > 5:
-                                       break
-               self._serial.close()
-               self._serial = None
+                                       self._timeoutTime = time.time() + 5
+                                       if not self._commandQueue.empty():
+                                               self._sendCommand(self._commandQueue.get())
+                                       else:
+                                               self._sendNext()
+                               elif "resend" in line.lower() or "rs" in line:
+                                       try:
+                                               self._gcodePos = int(line.replace("N:"," ").replace("N"," ").replace(":"," ").split()[-1])
+                                       except:
+                                               if "rs" in line:
+                                                       self._gcodePos = int(line.split()[1])
+               self._log("Connection closed, closing down monitor")
        
        def _log(self, message):
-               if self._logCallback != None:
-                       self._logCallback(message)
-               else:
-                       print(message)
+               self._callback.mcLog(message)
+               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()
+                       self._logQueue.put(message, False)
 
-       def readline(self):
+       def _readline(self):
                if self._serial == None:
                        return None
                try:
                        ret = self._serial.readline()
                except:
                        self._log("Unexpected error while reading serial port: %s" % (getExceptionString()))
+                       self._errorValue = getExceptionString()
+                       self.close(True)
+                       return None
+               if ret == '':
+                       #self._log("Recv: TIMEOUT")
                        return ''
-               if ret != '':
-                       self._log("Recv: %s" % (unicode(ret, 'utf-8', 'replace').rstrip()))
-               else:
-                       self._log("Recv: TIMEOUT")
+               self._log("Recv: %s" % (unicode(ret, 'ascii', 'replace').rstrip()))
                return ret
        
-       def close(self):
+       def close(self, isError = False):
                if self._serial != None:
                        self._serial.close()
+                       if isError:
+                               self._changeState(self.STATE_CLOSED_WITH_ERROR)
+                       else:
+                               self._changeState(self.STATE_CLOSED)
                self._serial = None
        
        def __del__(self):
                self.close()
        
-       def isOpen(self):
-               return self._serial != None
-       
-       def sendCommand(self, cmd):
+       def _sendCommand(self, cmd):
                if self._serial == None:
                        return
                self._log('Send: %s' % (cmd))
@@ -188,6 +344,38 @@ class MachineCom():
                        self._serial.write('\n')
                except:
                        self._log("Unexpected error while writing serial port: %s" % (getExceptionString()))
+                       self._errorValue = getExceptionString()
+                       self.close(True)
+       
+       def _sendNext(self):
+               if self._gcodePos >= len(self._gcodeList):
+                       self._changeState(self.STATE_OPERATIONAL)
+                       return
+               line = self._gcodeList[self._gcodePos]
+               checksum = reduce(lambda x,y:x^y, map(ord, "N%d%s" % (self._gcodePos, line)))
+               self._sendCommand("N%d%s*%d" % (self._gcodePos, line, checksum))
+               self._gcodePos += 1
+               self._callback.mcProgress(self._gcodePos)
+       
+       def sendCommand(self, cmd):
+               cmd = cmd.encode('ascii', 'replace')
+               if self.isPrinting():
+                       self._commandQueue.put(cmd)
+               elif self.isOperational():
+                       self._sendCommand(cmd)
+       
+       def printGCode(self, gcodeList):
+               if not self.isOperational() or self.isPrinting():
+                       return
+               self._gcodeList = gcodeList
+               self._gcodePos = 0
+               self._changeState(self.STATE_PRINTING)
+               for i in xrange(0, 6):
+                       self._sendNext()
+       
+       def cancelPrint(self):
+               if self.isOperational():
+                       self._changeState(self.STATE_OPERATIONAL)
 
 def getExceptionString():
        locationInfo = traceback.extract_tb(sys.exc_info()[2])[0]