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