chiark / gitweb /
Restore pause functionality.
[cura.git] / Cura / util / printerConnection / serialConnection.py
1 """
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.
4 """
5 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
6
7 import threading
8 import time
9 import platform
10 import os
11 import sys
12 import subprocess
13 import json
14
15 from Cura.util import profile
16 from Cura.util import machineCom
17 from Cura.util.printerConnection import printerConnectionBase
18
19 class serialConnectionGroup(printerConnectionBase.printerConnectionGroup):
20         """
21         The serial connection group. Keeps track of all available serial ports,
22         and builds a serialConnection for each port.
23         """
24         def __init__(self):
25                 super(serialConnectionGroup, self).__init__("USB")
26                 self._connectionMap = {}
27
28         def getAvailableConnections(self):
29                 if profile.getMachineSetting('serial_port') == 'AUTO':
30                         serialList = machineCom.serialList(True)
31                 else:
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()
40
41         def getIconID(self):
42                 return 6
43
44         def getPriority(self):
45                 return 50
46
47 class serialConnection(printerConnectionBase.printerConnectionBase):
48         """
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.
51
52         This class communicates with the Cura.serialCommunication module trough stdin/stdout pipes.
53         """
54         def __init__(self, port):
55                 super(serialConnection, self).__init__(port)
56                 self._portName = port
57
58                 self._process = None
59                 self._thread = None
60
61                 self._temperature = []
62                 self._targetTemperature = []
63                 self._bedTemperature = 0
64                 self._targetBedTemperature = 0
65                 self._log = []
66
67                 self._commState = None
68                 self._commStateString = None
69                 self._gcodeData = []
70
71         #Load the data into memory for printing, returns True on success
72         def loadGCodeData(self, dataStream):
73                 if self.isPrinting() is None:
74                         return False
75                 self._gcodeData = []
76                 for line in dataStream:
77                         #Strip out comments, we do not need to send comments
78                         if ';' in line:
79                                 line = line[:line.index(';')]
80                         #Strip out whitespace at the beginning/end this saves data to send.
81                         line = line.strip()
82
83                         if len(line) < 1:
84                                 continue
85                         self._gcodeData.append(line)
86                 return True
87
88         #Start printing the previously loaded file
89         def startPrint(self):
90                 if self.isPrinting() or len(self._gcodeData) < 1 or self._process is None:
91                         return
92                 self._process.stdin.write('STOP\n')
93                 for line in self._gcodeData:
94                         self._process.stdin.write('G:%s\n' % (line))
95                 self._process.stdin.write('START\n')
96                 self._printProgress = 0
97
98         def coolDown(self):
99                 cooldown_toolhead = "M104 S0"
100                 for i in range(0,3):
101                         change_toolhead = "T%d".format(i)
102                         self.sendCommand(change_toolhead)
103                         self.sendCommand(cooldown_toolhead)
104                 self.sendCommand("M140 S0") #Bed
105                 pass
106
107         #Abort the previously loaded print file
108         def cancelPrint(self):
109                 if not self.isPrinting() or self._process is None:
110                         return
111                 self._process.stdin.write('STOP\n')
112                 self._printProgress = 0
113                 self.coolDown()
114
115         def isPrinting(self):
116                 return self._commState == machineCom.MachineCom.STATE_PRINTING or self.isPaused()
117
118         #Returns true if we have the ability to pause the file printing.
119         def hasPause(self):
120                 return True
121
122         def isPaused(self):
123                 return self._commState == machineCom.MachineCom.STATE_PAUSED
124
125         #Pause or unpause the printing depending on the value, if supported.
126         def pause(self, value):
127                 if not self.isPrinting() or self._process is None:
128                         return
129                 self._process.stdin.write('PAUSE\n' if value else "RESUME\n")
130
131         #Amount of progression of the current print file. 0.0 to 1.0
132         def getPrintProgress(self):
133                 if len(self._gcodeData) < 1:
134                         return 0.0
135                 return float(self._printProgress) / float(len(self._gcodeData))
136
137         # Return if the printer with this connection type is available
138         def isAvailable(self):
139                 return True
140
141         # Get the connection status string. This is displayed to the user and can be used to communicate
142         #  various information to the user.
143         def getStatusString(self):
144                 return "%s" % (self._commStateString)
145
146         #Returns true if we need to establish an active connection. True for serial connections.
147         def hasActiveConnection(self):
148                 return True
149
150         #Open the active connection to the printer so we can send commands
151         def openActiveConnection(self):
152                 self.closeActiveConnection()
153                 self._thread = threading.Thread(target=self._serialCommunicationThread)
154                 self._thread.daemon = True
155                 self._thread.start()
156
157         #Close the active connection to the printer
158         def closeActiveConnection(self):
159                 if self._process is not None:
160                         self._process.terminate()
161                         self._thread.join()
162
163         #Is the active connection open right now.
164         def isActiveConnectionOpen(self):
165                 if self._process is None:
166                         return False
167                 return self._commState == machineCom.MachineCom.STATE_OPERATIONAL or self._commState == machineCom.MachineCom.STATE_PRINTING or self._commState == machineCom.MachineCom.STATE_PAUSED
168
169         #Are we trying to open an active connection right now.
170         def isActiveConnectionOpening(self):
171                 if self._process is None:
172                         return False
173                 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
174
175         def getTemperature(self, extruder):
176                 if extruder >= len(self._temperature):
177                         return None
178                 return self._temperature[extruder]
179
180         def getBedTemperature(self):
181                 return self._bedTemperature
182
183         #Are we able to send a direct command with sendCommand at this moment in time.
184         def isAbleToSendDirectCommand(self):
185                 return self.isActiveConnectionOpen()
186
187         #Directly send a command to the printer.
188         def sendCommand(self, command):
189                 if self._process is None:
190                         return
191                 self._process.stdin.write('C:%s\n' % (command))
192
193         #Returns true if we got some kind of error. The getErrorLog returns all the information to diagnose the problem.
194         def isInErrorState(self):
195                 return self._commState == machineCom.MachineCom.STATE_ERROR or self._commState == machineCom.MachineCom.STATE_CLOSED_WITH_ERROR
196
197         #Returns the error log in case there was an error.
198         def getErrorLog(self):
199                 return '\n'.join(self._log)
200
201         def _serialCommunicationThread(self):
202                 if platform.system() == "Darwin" and hasattr(sys, 'frozen'):
203                         cmdList = [os.path.join(os.path.dirname(sys.executable), 'Cura'), '--serialCommunication']
204                         cmdList += [self._portName + ':' + profile.getMachineSetting('serial_baud')]
205                 else:
206                         cmdList = [sys.executable, '-m', 'Cura.serialCommunication']
207                         cmdList += [self._portName, profile.getMachineSetting('serial_baud')]
208                 if platform.system() == "Darwin":
209                         if platform.machine() == 'i386':
210                                 cmdList = ['arch', '-i386'] + cmdList
211                 self._process = subprocess.Popen(cmdList, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
212                 line = self._process.stdout.readline()
213                 while len(line) > 0:
214                         line = line.strip()
215                         line = line.split(':', 1)
216                         if line[0] == '':
217                                 pass
218                         elif line[0] == 'log':
219                                 self._log.append(line[1])
220                                 if len(self._log) > 30:
221                                         self._log.pop(0)
222                         elif line[0] == 'temp':
223                                 line = line[1].split(':')
224                                 self._temperature = json.loads(line[0])
225                                 self._targetTemperature = json.loads(line[1])
226                                 self._bedTemperature = float(line[2])
227                                 self._targetBedTemperature = float(line[3])
228                                 self._doCallback()
229                         elif line[0] == 'message':
230                                 self._doCallback(line[1])
231                         elif line[0] == 'state':
232                                 line = line[1].split(':', 1)
233                                 self._commState = int(line[0])
234                                 self._commStateString = line[1]
235                                 self._doCallback('')
236                         elif line[0] == 'progress':
237                                 self._printProgress = int(line[1])
238                                 self._doCallback()
239                         else:
240                                 print line
241                         line = self._process.stdout.readline()
242                 self._process = None