2 The serial/USB printer connection. Uses a 2nd python process to connect to the printer so we never
3 have locking problems where other threads in python can block the USB printing.
5 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
15 from Cura.util import machineCom
16 from Cura.util.printerConnection import printerConnectionBase
18 class serialConnectionGroup(printerConnectionBase.printerConnectionGroup):
20 super(serialConnectionGroup, self).__init__("USB")
21 self._connectionMap = {}
23 def getAvailableConnections(self):
24 serialList = machineCom.serialList(True)
25 for port in machineCom.serialList(True):
26 if port not in self._connectionMap:
27 self._connectionMap[port] = serialConnection(port)
28 for key in self._connectionMap.keys():
29 if key not in serialList and not self._connectionMap[key].isActiveConnectionOpen():
30 self._connectionMap.pop(key)
31 return self._connectionMap.values()
36 def getPriority(self):
39 class serialConnection(printerConnectionBase.printerConnectionBase):
40 def __init__(self, port):
41 super(serialConnection, self).__init__(port)
47 self._temperature = []
48 self._targetTemperature = []
49 self._bedTemperature = 0
50 self._targetBedTemperature = 0
53 self._commState = None
54 self._commStateString = None
57 #Load the data into memory for printing, returns True on success
58 def loadGCodeData(self, dataStream):
59 if self.isPrinting() is None:
62 for line in dataStream:
63 #Strip out comments, we do not need to send comments
65 line = line[:line.index(';')]
66 #Strip out whitespace at the beginning/end this saves data to send.
71 self._gcodeData.append(line)
74 #Start printing the previously loaded file
76 if self.isPrinting() or len(self._gcodeData) < 1 or self._process is None:
78 self._process.stdin.write('STOP\n')
79 for line in self._gcodeData:
80 self._process.stdin.write('G:%s\n' % (line))
81 self._process.stdin.write('START\n')
82 self._printProgress = 0
84 #Abort the previously loaded print file
85 def cancelPrint(self):
86 if not self.isPrinting()or self._process is None:
88 self._process.stdin.write('STOP\n')
89 self._printProgress = 0
92 return self._commState == machineCom.MachineCom.STATE_PRINTING
94 #Amount of progression of the current print file. 0.0 to 1.0
95 def getPrintProgress(self):
96 if len(self._gcodeData) < 1:
98 return float(self._printProgress) / float(len(self._gcodeData))
100 # Return if the printer with this connection type is available
101 def isAvailable(self):
104 # Get the connection status string. This is displayed to the user and can be used to communicate
105 # various information to the user.
106 def getStatusString(self):
107 return "%s" % (self._commStateString)
109 #Returns true if we need to establish an active connection. True for serial connections.
110 def hasActiveConnection(self):
113 #Open the active connection to the printer so we can send commands
114 def openActiveConnection(self):
115 self.closeActiveConnection()
116 self._thread = threading.Thread(target=self._serialCommunicationThread)
117 self._thread.daemon = True
120 #Close the active connection to the printer
121 def closeActiveConnection(self):
122 if self._process is not None:
123 self._process.terminate()
126 #Is the active connection open right now.
127 def isActiveConnectionOpen(self):
128 if self._process is None:
130 return self._commState == machineCom.MachineCom.STATE_OPERATIONAL or self._commState == machineCom.MachineCom.STATE_PRINTING or self._commState == machineCom.MachineCom.STATE_PAUSED
132 #Are we trying to open an active connection right now.
133 def isActiveConnectionOpening(self):
134 if self._process is None:
136 return self._commState == machineCom.MachineCom.STATE_OPEN_SERIAL or self._commState == machineCom.MachineCom.STATE_CONNECTING or self._commState == machineCom.MachineCom.STATE_DETECT_SERIAL or self._commState == machineCom.MachineCom.STATE_DETECT_BAUDRATE
138 def getTemperature(self, extruder):
139 if extruder >= len(self._temperature):
141 return self._temperature[extruder]
143 def getBedTemperature(self):
144 return self._bedTemperature
146 #Are we able to send a direct coammand with sendCommand at this moment in time.
147 def isAbleToSendDirectCommand(self):
148 return self.isActiveConnectionOpen()
150 #Directly send a command to the printer.
151 def sendCommand(self, command):
152 if self._process is None:
154 self._process.stdin.write('C:%s\n' % (command))
156 #Returns true if we got some kind of error. The getErrorLog returns all the information to diagnose the problem.
157 def isInErrorState(self):
158 return self._commState == machineCom.MachineCom.STATE_ERROR or self._commState == machineCom.MachineCom.STATE_CLOSED_WITH_ERROR
160 #Returns the error log in case there was an error.
161 def getErrorLog(self):
162 return '\n'.join(self._log)
164 def _serialCommunicationThread(self):
165 if platform.system() == "Darwin" and hasattr(sys, 'frozen'):
166 cmdList = [os.path.join(os.path.dirname(sys.executable), 'Cura'), '--serialCommunication']
168 cmdList = [sys.executable, '-m', 'Cura.serialCommunication']
169 cmdList += [self._portName]
170 if platform.system() == "Darwin":
171 if platform.machine() == 'i386':
172 cmdList = ['arch', '-i386'] + cmdList
173 self._process = subprocess.Popen(cmdList, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
174 line = self._process.stdout.readline()
177 line = line.split(':', 1)
180 elif line[0] == 'log':
181 self._log.append(line[1])
182 if len(self._log) > 30:
184 elif line[0] == 'temp':
185 line = line[1].split(':')
186 self._temperature = json.loads(line[0])
187 self._targetTemperature = json.loads(line[1])
188 self._bedTemperature = float(line[2])
189 self._targetBedTemperature = float(line[3])
191 elif line[0] == 'message':
192 self._doCallback(line[1])
193 elif line[0] == 'state':
194 line = line[1].split(':', 1)
195 self._commState = int(line[0])
196 self._commStateString = line[1]
198 elif line[0] == 'progress':
199 self._printProgress = int(line[1])
203 line = self._process.stdout.readline()