chiark / gitweb /
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
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
106         def disableSteppers(self):
107                 self.sendCommand("M18")
108
109         #Abort the previously loaded print file
110         def cancelPrint(self):
111                 if (not self.isPrinting() and not self.isPaused()) or \
112                         self._process is None:
113                         return
114                 self._process.stdin.write('STOP\n')
115                 self._printProgress = 0
116                 self.coolDown()
117                 self.disableSteppers()
118
119         def isPrinting(self):
120                 return self._commState == machineCom.MachineCom.STATE_PRINTING
121
122         #Returns true if we have the ability to pause the file printing.
123         def hasPause(self):
124                 return False
125
126         def isPaused(self):
127                 return self._commState == machineCom.MachineCom.STATE_PAUSED
128
129         #Pause or unpause the printing depending on the value, if supported.
130         def pause(self, value):
131                 if not (self.isPrinting() or self.isPaused()) or self._process is None:
132                         return
133                 self._process.stdin.write('PAUSE\n' if value else "RESUME\n")
134
135         #Amount of progression of the current print file. 0.0 to 1.0
136         def getPrintProgress(self):
137                 if len(self._gcodeData) < 1:
138                         return 0.0
139                 return float(self._printProgress) / float(len(self._gcodeData))
140
141         # Return if the printer with this connection type is available
142         def isAvailable(self):
143                 return True
144
145         # Get the connection status string. This is displayed to the user and can be used to communicate
146         #  various information to the user.
147         def getStatusString(self):
148                 return "%s" % (self._commStateString)
149
150         #Returns true if we need to establish an active connection. True for serial connections.
151         def hasActiveConnection(self):
152                 return True
153
154         #Open the active connection to the printer so we can send commands
155         def openActiveConnection(self):
156                 self.closeActiveConnection()
157                 self._thread = threading.Thread(target=self._serialCommunicationThread)
158                 self._thread.daemon = True
159                 self._thread.start()
160
161         #Close the active connection to the printer
162         def closeActiveConnection(self):
163                 if self._process is not None:
164                         self._process.terminate()
165                         self._thread.join()
166
167         #Is the active connection open right now.
168         def isActiveConnectionOpen(self):
169                 if self._process is None:
170                         return False
171                 return self._commState == machineCom.MachineCom.STATE_OPERATIONAL or self._commState == machineCom.MachineCom.STATE_PRINTING or self._commState == machineCom.MachineCom.STATE_PAUSED
172
173         #Are we trying to open an active connection right now.
174         def isActiveConnectionOpening(self):
175                 if self._process is None:
176                         return False
177                 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
178
179         def getTemperature(self, extruder):
180                 if extruder >= len(self._temperature):
181                         return None
182                 return self._temperature[extruder]
183
184         def getBedTemperature(self):
185                 return self._bedTemperature
186
187         #Are we able to send a direct command with sendCommand at this moment in time.
188         def isAbleToSendDirectCommand(self):
189                 return self.isActiveConnectionOpen()
190
191         #Directly send a command to the printer.
192         def sendCommand(self, command):
193                 if self._process is None:
194                         return
195                 self._process.stdin.write('C:%s\n' % (command))
196
197         #Returns true if we got some kind of error. The getErrorLog returns all the information to diagnose the problem.
198         def isInErrorState(self):
199                 return self._commState == machineCom.MachineCom.STATE_ERROR or self._commState == machineCom.MachineCom.STATE_CLOSED_WITH_ERROR
200
201         #Returns the error log in case there was an error.
202         def getErrorLog(self):
203                 return '\n'.join(self._log)
204
205         def _serialCommunicationThread(self):
206                 if platform.system() == "Darwin" and hasattr(sys, 'frozen'):
207                         cmdList = [os.path.join(os.path.dirname(sys.executable), 'Cura'), '--serialCommunication']
208                         cmdList += [self._portName + ':' + profile.getMachineSetting('serial_baud')]
209                 else:
210                         cmdList = [sys.executable, '-m', 'Cura.serialCommunication']
211                         cmdList += [self._portName, profile.getMachineSetting('serial_baud')]
212                 if platform.system() == "Darwin":
213                         if platform.machine() == 'i386':
214                                 cmdList = ['arch', '-i386'] + cmdList
215                 self._process = subprocess.Popen(cmdList, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
216                 line = self._process.stdout.readline()
217                 while len(line) > 0:
218                         line = line.strip()
219                         line = line.split(':', 1)
220                         if line[0] == '':
221                                 pass
222                         elif line[0] == 'log':
223                                 self._log.append(line[1])
224                                 if len(self._log) > 30:
225                                         self._log.pop(0)
226                         elif line[0] == 'temp':
227                                 line = line[1].split(':')
228                                 self._temperature = json.loads(line[0])
229                                 self._targetTemperature = json.loads(line[1])
230                                 self._bedTemperature = float(line[2])
231                                 self._targetBedTemperature = float(line[3])
232                                 self._doCallback()
233                         elif line[0] == 'message':
234                                 self._doCallback(line[1])
235                         elif line[0] == 'state':
236                                 line = line[1].split(':', 1)
237                                 self._commState = int(line[0])
238                                 self._commStateString = line[1]
239                                 self._doCallback('')
240                         elif line[0] == 'progress':
241                                 self._printProgress = int(line[1])
242                                 self._doCallback()
243                         else:
244                                 print line
245                         line = self._process.stdout.readline()
246                 self._process = None