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