chiark / gitweb /
Merge pull request #2 from daid/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 machineCom
16 from Cura.util.printerConnection import printerConnectionBase
17
18 class serialConnectionGroup(printerConnectionBase.printerConnectionGroup):
19         """
20         The serial connection group. Keeps track of all available serial ports,
21         and builds a serialConnection for each port.
22         """
23         def __init__(self):
24                 super(serialConnectionGroup, self).__init__("USB")
25                 self._connectionMap = {}
26
27         def getAvailableConnections(self):
28                 serialList = machineCom.serialList(True)
29                 for port in machineCom.serialList(True):
30                         if port not in self._connectionMap:
31                                 self._connectionMap[port] = serialConnection(port)
32                 for key in self._connectionMap.keys():
33                         if key not in serialList and not self._connectionMap[key].isActiveConnectionOpen():
34                                 self._connectionMap.pop(key)
35                 return self._connectionMap.values()
36
37         def getIconID(self):
38                 return 6
39
40         def getPriority(self):
41                 return 50
42
43 class serialConnection(printerConnectionBase.printerConnectionBase):
44         """
45         A serial connection. Needs to build an active-connection.
46         When an active connection is created, a 2nd python process is spawned which handles the actual serial communication.
47
48         This class communicates with the Cura.serialCommunication module trough stdin/stdout pipes.
49         """
50         def __init__(self, port):
51                 super(serialConnection, self).__init__(port)
52                 self._portName = port
53
54                 self._process = None
55                 self._thread = None
56
57                 self._temperature = []
58                 self._targetTemperature = []
59                 self._bedTemperature = 0
60                 self._targetBedTemperature = 0
61                 self._log = []
62
63                 self._commState = None
64                 self._commStateString = None
65                 self._gcodeData = []
66
67         #Load the data into memory for printing, returns True on success
68         def loadGCodeData(self, dataStream):
69                 if self.isPrinting() is None:
70                         return False
71                 self._gcodeData = []
72                 for line in dataStream:
73                         #Strip out comments, we do not need to send comments
74                         if ';' in line:
75                                 line = line[:line.index(';')]
76                         #Strip out whitespace at the beginning/end this saves data to send.
77                         line = line.strip()
78
79                         if len(line) < 1:
80                                 continue
81                         self._gcodeData.append(line)
82                 return True
83
84         #Start printing the previously loaded file
85         def startPrint(self):
86                 if self.isPrinting() or len(self._gcodeData) < 1 or self._process is None:
87                         return
88                 self._process.stdin.write('STOP\n')
89                 for line in self._gcodeData:
90                         self._process.stdin.write('G:%s\n' % (line))
91                 self._process.stdin.write('START\n')
92                 self._printProgress = 0
93
94         #Abort the previously loaded print file
95         def cancelPrint(self):
96                 if not self.isPrinting()or self._process is None:
97                         return
98                 self._process.stdin.write('STOP\n')
99                 self._printProgress = 0
100
101         def isPrinting(self):
102                 return self._commState == machineCom.MachineCom.STATE_PRINTING
103
104         #Amount of progression of the current print file. 0.0 to 1.0
105         def getPrintProgress(self):
106                 if len(self._gcodeData) < 1:
107                         return 0.0
108                 return float(self._printProgress) / float(len(self._gcodeData))
109
110         # Return if the printer with this connection type is available
111         def isAvailable(self):
112                 return True
113
114         # Get the connection status string. This is displayed to the user and can be used to communicate
115         #  various information to the user.
116         def getStatusString(self):
117                 return "%s" % (self._commStateString)
118
119         #Returns true if we need to establish an active connection. True for serial connections.
120         def hasActiveConnection(self):
121                 return True
122
123         #Open the active connection to the printer so we can send commands
124         def openActiveConnection(self):
125                 self.closeActiveConnection()
126                 self._thread = threading.Thread(target=self._serialCommunicationThread)
127                 self._thread.daemon = True
128                 self._thread.start()
129
130         #Close the active connection to the printer
131         def closeActiveConnection(self):
132                 if self._process is not None:
133                         self._process.terminate()
134                         self._thread.join()
135
136         #Is the active connection open right now.
137         def isActiveConnectionOpen(self):
138                 if self._process is None:
139                         return False
140                 return self._commState == machineCom.MachineCom.STATE_OPERATIONAL or self._commState == machineCom.MachineCom.STATE_PRINTING or self._commState == machineCom.MachineCom.STATE_PAUSED
141
142         #Are we trying to open an active connection right now.
143         def isActiveConnectionOpening(self):
144                 if self._process is None:
145                         return False
146                 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
147
148         def getTemperature(self, extruder):
149                 if extruder >= len(self._temperature):
150                         return None
151                 return self._temperature[extruder]
152
153         def getBedTemperature(self):
154                 return self._bedTemperature
155
156         #Are we able to send a direct command with sendCommand at this moment in time.
157         def isAbleToSendDirectCommand(self):
158                 return self.isActiveConnectionOpen()
159
160         #Directly send a command to the printer.
161         def sendCommand(self, command):
162                 if self._process is None:
163                         return
164                 self._process.stdin.write('C:%s\n' % (command))
165
166         #Returns true if we got some kind of error. The getErrorLog returns all the information to diagnose the problem.
167         def isInErrorState(self):
168                 return self._commState == machineCom.MachineCom.STATE_ERROR or self._commState == machineCom.MachineCom.STATE_CLOSED_WITH_ERROR
169
170         #Returns the error log in case there was an error.
171         def getErrorLog(self):
172                 return '\n'.join(self._log)
173
174         def _serialCommunicationThread(self):
175                 if platform.system() == "Darwin" and hasattr(sys, 'frozen'):
176                         cmdList = [os.path.join(os.path.dirname(sys.executable), 'Cura'), '--serialCommunication']
177                 else:
178                         cmdList = [sys.executable, '-m', 'Cura.serialCommunication']
179                 cmdList += [self._portName]
180                 if platform.system() == "Darwin":
181                         if platform.machine() == 'i386':
182                                 cmdList = ['arch', '-i386'] + cmdList
183                 self._process = subprocess.Popen(cmdList, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
184                 line = self._process.stdout.readline()
185                 while len(line) > 0:
186                         line = line.strip()
187                         line = line.split(':', 1)
188                         if line[0] == '':
189                                 pass
190                         elif line[0] == 'log':
191                                 self._log.append(line[1])
192                                 if len(self._log) > 30:
193                                         self._log.pop(0)
194                         elif line[0] == 'temp':
195                                 line = line[1].split(':')
196                                 self._temperature = json.loads(line[0])
197                                 self._targetTemperature = json.loads(line[1])
198                                 self._bedTemperature = float(line[2])
199                                 self._targetBedTemperature = float(line[3])
200                                 self._doCallback()
201                         elif line[0] == 'message':
202                                 self._doCallback(line[1])
203                         elif line[0] == 'state':
204                                 line = line[1].split(':', 1)
205                                 self._commState = int(line[0])
206                                 self._commStateString = line[1]
207                                 self._doCallback()
208                         elif line[0] == 'progress':
209                                 self._printProgress = int(line[1])
210                                 self._doCallback()
211                         else:
212                                 print line
213                         line = self._process.stdout.readline()
214                 self._process = None