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