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