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 profile
16 from Cura.util import machineCom
17 from Cura.util.printerConnection import printerConnectionBase
19 class serialConnectionGroup(printerConnectionBase.printerConnectionGroup):
21 The serial connection group. Keeps track of all available serial ports,
22 and builds a serialConnection for each port.
25 super(serialConnectionGroup, self).__init__("USB")
26 self._connectionMap = {}
28 def getAvailableConnections(self):
29 if profile.getMachineSetting('serial_port') == 'AUTO':
30 serialList = machineCom.serialList(True)
32 serialList = [profile.getMachineSetting('serial_port')]
33 for port in serialList:
34 if port not in self._connectionMap:
35 self._connectionMap[port] = serialConnection(port)
36 for key in self._connectionMap.keys():
37 if key not in serialList and not self._connectionMap[key].isActiveConnectionOpen():
38 self._connectionMap.pop(key)
39 return self._connectionMap.values()
44 def getPriority(self):
47 class serialConnection(printerConnectionBase.printerConnectionBase):
49 A serial connection. Needs to build an active-connection.
50 When an active connection is created, a 2nd python process is spawned which handles the actual serial communication.
52 This class communicates with the Cura.serialCommunication module trough stdin/stdout pipes.
54 def __init__(self, port):
55 super(serialConnection, self).__init__(port)
61 self._temperature = []
62 self._targetTemperature = []
63 self._bedTemperature = 0
64 self._targetBedTemperature = 0
67 self._commState = None
68 self._commStateString = None
70 self._printProgress = 0
73 #Load the data into memory for printing, returns True on success
74 def loadGCodeData(self, dataStream):
75 if self.isPrinting() is None:
78 for line in dataStream:
79 #Strip out comments, we do not need to send comments
81 line = line[:line.index(';')]
82 #Strip out whitespace at the beginning/end this saves data to send.
87 self._gcodeData.append(line)
90 #Start printing the previously loaded file
92 if self.isPrinting() or len(self._gcodeData) < 1 or self._process is None:
94 self._process.stdin.write('STOP\n')
95 for line in self._gcodeData:
96 self._process.stdin.write('G:%s\n' % (line))
97 self._process.stdin.write('START\n')
98 self._printProgress = 0
102 cooldown_toolhead = "M104 S0"
104 change_toolhead = "T%d".format(i)
105 self.sendCommand(change_toolhead)
106 self.sendCommand(cooldown_toolhead)
107 self.sendCommand("M140 S0") #Bed
109 def disableSteppers(self):
110 self.sendCommand("M18")
112 #Abort the previously loaded print file
113 def cancelPrint(self):
114 if (not self.isPrinting() and not self.isPaused()) or \
115 self._process is None:
117 self._process.stdin.write('STOP\n')
118 self._printProgress = 0
120 self.disableSteppers()
122 def isPrinting(self):
123 return self._commState == machineCom.MachineCom.STATE_PRINTING
125 #Returns true if we have the ability to pause the file printing.
130 return self._commState == machineCom.MachineCom.STATE_PAUSED
132 #Pause or unpause the printing depending on the value, if supported.
133 def pause(self, value):
134 if not (self.isPrinting() or self.isPaused()) or self._process is None:
137 self._process.stdin.write("PAUSE\n")
139 self._process.stdin.write("RESUME\n")
141 #Amount of progression of the current print file. 0.0 to 1.0
142 def getPrintProgress(self):
143 return (self._printProgress, len(self._gcodeData), self._ZPosition)
145 # Return if the printer with this connection type is available
146 def isAvailable(self):
149 # Get the connection status string. This is displayed to the user and can be used to communicate
150 # various information to the user.
151 def getStatusString(self):
152 return "%s" % (self._commStateString)
154 #Returns true if we need to establish an active connection. True for serial connections.
155 def hasActiveConnection(self):
158 #Open the active connection to the printer so we can send commands
159 def openActiveConnection(self):
160 self.closeActiveConnection()
161 self._thread = threading.Thread(target=self._serialCommunicationThread)
162 self._thread.daemon = True
165 #Close the active connection to the printer
166 def closeActiveConnection(self):
167 if self._process is not None:
168 self._process.terminate()
171 #Is the active connection open right now.
172 def isActiveConnectionOpen(self):
173 if self._process is None:
175 return self._commState == machineCom.MachineCom.STATE_OPERATIONAL or self._commState == machineCom.MachineCom.STATE_PRINTING or self._commState == machineCom.MachineCom.STATE_PAUSED
177 #Are we trying to open an active connection right now.
178 def isActiveConnectionOpening(self):
179 if self._process is None:
181 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
183 def getTemperature(self, extruder):
184 if extruder >= len(self._temperature):
186 return self._temperature[extruder]
188 def getBedTemperature(self):
189 return self._bedTemperature
191 #Are we able to send a direct command with sendCommand at this moment in time.
192 def isAbleToSendDirectCommand(self):
193 return self.isActiveConnectionOpen()
195 #Directly send a command to the printer.
196 def sendCommand(self, command):
197 if self._process is None:
199 self._process.stdin.write('C:%s\n' % (command))
201 #Returns true if we got some kind of error. The getErrorLog returns all the information to diagnose the problem.
202 def isInErrorState(self):
203 return self._commState == machineCom.MachineCom.STATE_ERROR or self._commState == machineCom.MachineCom.STATE_CLOSED_WITH_ERROR
205 #Returns the error log in case there was an error.
206 def getErrorLog(self):
207 return '\n'.join(self._log)
209 def _serialCommunicationThread(self):
210 if platform.system() == "Darwin" and hasattr(sys, 'frozen'):
211 cmdList = [os.path.join(os.path.dirname(sys.executable), 'Cura'), '--serialCommunication']
212 cmdList += [self._portName + ':' + profile.getMachineSetting('serial_baud')]
214 cmdList = [sys.executable, '-m', 'Cura.serialCommunication']
215 cmdList += [self._portName, profile.getMachineSetting('serial_baud')]
216 if platform.system() == "Darwin":
217 if platform.machine() == 'i386':
218 cmdList = ['arch', '-i386'] + cmdList
219 self._process = subprocess.Popen(cmdList, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
220 line = self._process.stdout.readline()
223 line = line.split(':', 1)
226 elif line[0] == 'log':
227 self._log.append(line[1])
228 if len(self._log) > 30:
230 elif line[0] == 'temp':
231 line = line[1].split(':')
232 self._temperature = json.loads(line[0])
233 self._targetTemperature = json.loads(line[1])
234 self._bedTemperature = float(line[2])
235 self._targetBedTemperature = float(line[3])
237 elif line[0] == 'message':
238 self._doCallback(line[1])
239 elif line[0] == 'state':
240 line = line[1].split(':', 1)
241 self._commState = int(line[0])
242 self._commStateString = line[1]
244 elif line[0] == 'progress':
245 self._printProgress = int(line[1])
247 elif line[0] == 'changeZ':
248 self._ZPosition = float(line[1])
252 line = self._process.stdout.readline()