1 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
5 import httplib as httpclient
9 from Cura.util.printerConnection import printerConnectionBase
11 #Class to connect and print files with the doodle3d.com wifi box
12 # Auto-detects if the Doodle3D box is available with a printer
13 class doodle3dConnect(printerConnectionBase.printerConnectionBase):
14 PRINTER_LIST_HOST = 'connect.doodle3d.com'
15 PRINTER_LIST_PATH = '/api/list.php'
18 super(doodle3dConnect, self).__init__()
22 self._isAvailable = False
23 self._printing = False
25 self._commandList = []
26 self._blockIndex = None
28 self._progressLine = 0
29 self._hotendTemperature = [None] * 4
30 self._bedTemperature = None
33 self.checkThread = threading.Thread(target=self._doodle3DThread)
34 self.checkThread.daemon = True
35 self.checkThread.start()
37 #Load the file into memory for printing.
38 def loadFile(self, filename):
45 f = open(filename, "r")
47 #Strip out comments, we do not need to send comments
49 line = line[:line.index(';')]
50 #Strip out whitespace at the beginning/end this saves data to send.
56 #Put the lines in 8k sized blocks, so we can send those blocks as http requests.
57 if blockSize + len(line) > 1024 * 8:
58 self._fileBlocks.append('\n'.join(block) + '\n')
61 blockSize += len(line) + 1
63 self._fileBlocks.append('\n'.join(block) + '\n')
68 #Start printing the previously loaded file
70 if self._printing or len(self._fileBlocks) < 1:
72 self._progressLine = 0
76 #Abort the previously loaded print file
77 def cancelPrint(self):
78 if not self._printing:
80 if self._request('POST', '/d3dapi/printer/stop', {'gcode': 'M104 S0\nG28'}):
81 self._printing = False
86 #Amount of progression of the current print file. 0.0 to 1.0
87 def getPrintProgress(self):
88 if self._lineCount < 1:
90 return float(self._progressLine) / float(self._lineCount)
92 # Return if the printer with this connection type is available
93 def isAvailable(self):
94 return self._isAvailable
96 #Are we able to send a direct coammand with sendCommand at this moment in time.
97 def isAbleToSendDirectCommand(self):
98 #The delay on direct commands is very high and so we disabled it.
99 return False #self._isAvailable and not self._printing
101 #Directly send a command to the printer.
102 def sendCommand(self, command):
103 if not self._isAvailable or self._printing:
105 self._commandList.append(command)
107 # Get the connection status string. This is displayed to the user and can be used to communicate
108 # various information to the user.
109 def getStatusString(self):
110 if not self._isAvailable:
111 return "Doodle3D box not found"
113 ret = "Print progress: %.1f%%" % (self.getPrintProgress() * 100.0)
114 if self._blockIndex < len(self._fileBlocks):
115 ret += "\nSending GCode: %.1f%%" % (float(self._blockIndex) * 100.0 / float(len(self._fileBlocks)))
116 elif len(self._fileBlocks) > 0:
117 ret += "\nFinished sending GCode to Doodle3D box.\nPrint will continue even if you shut down Cura."
119 ret += "\nDifferent print still running..."
120 ret += "\nErrorCount: %d" % (self._errorCount)
122 return "Printer found, waiting for print command."
124 #Get the temperature of an extruder, returns None is no temperature is known for this extruder
125 def getTemperature(self, extruder):
126 return self._hotendTemperature[extruder]
128 #Get the temperature of the heated bed, returns None is no temperature is known for the heated bed
129 def getBedTemperature(self):
130 return self._bedTemperature
132 def _doodle3DThread(self):
135 while self._host is None:
136 printerList = self._request('GET', self.PRINTER_LIST_PATH, host=self.PRINTER_LIST_HOST)
137 if not printerList or type(printerList) is not dict or 'data' not in printerList or type(printerList['data']) is not list:
138 #Check if we are connected to the Doodle3D box in access point mode, as this gives an
139 # invalid reply on the printer list API
140 printerList = {'data': [{'localip': 'draw.doodle3d.com'}]}
142 #Add the 192.168.5.1 IP to the list of printers to check, as this is the LAN port IP, which could also be available.
143 # (connect.doodle3d.com also checks for this IP in the javascript code)
144 printerList['data'].append({'localip': '192.168.5.1'})
146 #Check the status of each possible IP, if we find a valid box with a printer connected. Use that IP.
147 for possiblePrinter in printerList['data']:
148 status = self._request('GET', '/d3dapi/info/status', host=possiblePrinter['localip'])
149 if status and 'data' in status:
150 self._host = possiblePrinter['localip']
153 if self._host is None:
154 #If we cannot find a doodle3d box, delay a minute and request the list again.
155 # This so we do not stress the connect.doodle3d.com api too much
158 time.sleep(waitDelay * 60)
160 #If we found a doodle3D box, reset the wait delay, so we can find it again in case it gets lost
164 stateReply = self._request('GET', '/d3dapi/info/status')
165 if stateReply is None or not stateReply:
166 # No API, wait 15 seconds before looking for Doodle3D again.
167 # API gave back an error (this can happen if the Doodle3D box is connecting to the printer)
168 self._errorCount += 1
169 if self._errorCount > 10:
171 if self._isAvailable:
172 self._printing = False
173 self._isAvailable = False
177 if stateReply['data']['state'] == 'disconnected':
178 # No printer connected
179 if self._isAvailable:
180 self._printing = False
181 self._isAvailable = False
187 #We got a valid status, set the doodle3d printer as available.
188 if not self._isAvailable:
189 self._isAvailable = True
191 if 'hotend' in stateReply['data']:
192 self._hotendTemperature[0] = stateReply['data']['hotend']
193 if 'bed' in stateReply['data']:
194 self._bedTemperature = stateReply['data']['bed']
196 if stateReply['data']['state'] == 'idle' or stateReply['data']['state'] == 'buffering':
198 if self._blockIndex < len(self._fileBlocks):
199 if self._request('POST', '/d3dapi/printer/print', {'gcode': self._fileBlocks[self._blockIndex], 'start': 'True', 'first': 'True'}):
200 self._blockIndex += 1
204 self._printing = False
206 if len(self._commandList) > 0:
207 if self._request('POST', '/d3dapi/printer/print', {'gcode': self._commandList[0], 'start': 'True', 'first': 'True'}):
208 self._commandList.pop(0)
213 elif stateReply['data']['state'] == 'printing':
215 if self._blockIndex < len(self._fileBlocks):
216 for n in xrange(0, 5):
217 if self._blockIndex < len(self._fileBlocks):
218 if self._request('POST', '/d3dapi/printer/print', {'gcode': self._fileBlocks[self._blockIndex]}):
219 self._blockIndex += 1
221 #Cannot send new block, wait a bit, so we do not overload the API
225 #If we are no longer sending new GCode delay a bit so we request the status less often.
227 if 'current_line' in stateReply['data']:
228 self._progressLine = stateReply['data']['current_line']
230 #Got a printing state without us having send the print file, set the state to printing, but make sure we never send anything.
231 if 'current_line' in stateReply['data'] and 'total_lines' in stateReply['data'] and stateReply['data']['total_lines'] > 2:
232 self._printing = True
233 self._fileBlocks = []
235 self._progressLine = stateReply['data']['current_line']
236 self._lineCount = stateReply['data']['total_lines']
240 def _request(self, method, path, postData = None, host = None):
243 if self._http is None or self._http.host != host:
244 self._http = httpclient.HTTPConnection(host, timeout=30)
247 if postData is not None:
248 self._http.request(method, path, urllib.urlencode(postData), {"Content-type": "application/x-www-form-urlencoded", "User-Agent": "Cura Doodle3D connection"})
250 self._http.request(method, path, headers={"Content-type": "application/x-www-form-urlencoded", "User-Agent": "Cura Doodle3D connection"})
255 response = self._http.getresponse()
256 responseText = response.read()
261 response = json.loads(responseText)
265 if response['status'] != 'success':
270 if __name__ == '__main__':
271 d = doodle3dConnect()
272 print 'Searching for Doodle3D box'
273 while not d.isAvailable():
276 while d.isPrinting():
277 print 'Doodle3D already printing! Requesting stop!'
281 print 'Doodle3D box found, printing!'
282 d.loadFile("C:/Models/belt-tensioner-wave_export.gcode")
284 while d.isPrinting() and d.isAvailable():
286 print d.getTemperature(0), d.getStatusString(), d.getPrintProgress(), d._progressLine, d._lineCount, d._blockIndex, len(d._fileBlocks)