chiark / gitweb /
Revert "Disable pause feature"
[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                 self._printProgress = 0
71                 self._ZPosition = 0
72
73         #Load the data into memory for printing, returns True on success
74         def loadGCodeData(self, dataStream):
75                 if self.isPrinting() is None:
76                         return False
77                 self._gcodeData = []
78                 for line in dataStream:
79                         #Strip out comments, we do not need to send comments
80                         if ';' in line:
81                                 line = line[:line.index(';')]
82                         #Strip out whitespace at the beginning/end this saves data to send.
83                         line = line.strip()
84
85                         if len(line) < 1:
86                                 continue
87                         self._gcodeData.append(line)
88                 return True
89
90         #Start printing the previously loaded file
91         def startPrint(self):
92                 if self.isPrinting() or len(self._gcodeData) < 1 or self._process is None:
93                         return
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
99                 self._ZPosition = 0
100
101         def coolDown(self):
102                 cooldown_toolhead = "M104 S0"
103                 for i in range(0,3):
104                         change_toolhead = "T%d".format(i)
105                         self.sendCommand(change_toolhead)
106                         self.sendCommand(cooldown_toolhead)
107                 self.sendCommand("M140 S0") #Bed
108
109         def disableSteppers(self):
110                 self.sendCommand("M18")
111
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:
116                         return
117                 self._process.stdin.write('STOP\n')
118                 self._printProgress = 0
119                 self.coolDown()
120                 self.disableSteppers()
121
122         def isPrinting(self):
123                 return self._commState == machineCom.MachineCom.STATE_PRINTING
124
125         #Returns true if we have the ability to pause the file printing.
126         def hasPause(self):
127                 return True
128
129         def isPaused(self):
130                 return self._commState == machineCom.MachineCom.STATE_PAUSED
131
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:
135                         return
136                 if value:
137                         self._process.stdin.write("PAUSE\n")
138                 else:
139                         self._process.stdin.write("RESUME\n")
140
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)
144
145         # Return if the printer with this connection type is available
146         def isAvailable(self):
147                 return True
148
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)
153
154         #Returns true if we need to establish an active connection. True for serial connections.
155         def hasActiveConnection(self):
156                 return True
157
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
163                 self._thread.start()
164
165         #Close the active connection to the printer
166         def closeActiveConnection(self):
167                 if self._process is not None:
168                         self._process.terminate()
169                         self._thread.join()
170
171         #Is the active connection open right now.
172         def isActiveConnectionOpen(self):
173                 if self._process is None:
174                         return False
175                 return self._commState == machineCom.MachineCom.STATE_OPERATIONAL or self._commState == machineCom.MachineCom.STATE_PRINTING or self._commState == machineCom.MachineCom.STATE_PAUSED
176
177         #Are we trying to open an active connection right now.
178         def isActiveConnectionOpening(self):
179                 if self._process is None:
180                         return False
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
182
183         def getTemperature(self, extruder):
184                 if extruder >= len(self._temperature):
185                         return None
186                 return self._temperature[extruder]
187
188         def getBedTemperature(self):
189                 return self._bedTemperature
190
191         #Are we able to send a direct command with sendCommand at this moment in time.
192         def isAbleToSendDirectCommand(self):
193                 return self.isActiveConnectionOpen()
194
195         #Directly send a command to the printer.
196         def sendCommand(self, command):
197                 if self._process is None:
198                         return
199                 self._process.stdin.write('C:%s\n' % (command))
200
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
204
205         #Returns the error log in case there was an error.
206         def getErrorLog(self):
207                 return '\n'.join(self._log)
208
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')]
213                 else:
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()
221                 while len(line) > 0:
222                         line = line.strip()
223                         line = line.split(':', 1)
224                         if line[0] == '':
225                                 pass
226                         elif line[0] == 'log':
227                                 self._log.append(line[1])
228                                 if len(self._log) > 30:
229                                         self._log.pop(0)
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])
236                                 self._doCallback()
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]
243                                 self._doCallback('')
244                         elif line[0] == 'progress':
245                                 self._printProgress = int(line[1])
246                                 self._doCallback()
247                         elif line[0] == 'changeZ':
248                                 self._ZPosition = float(line[1])
249                                 self._doCallback()
250                         else:
251                                 print line
252                         line = self._process.stdout.readline()
253                 self._process = None