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