chiark / gitweb /
5fa69f6434ade877a48a176986d3442290896367
[cura.git] / Cura / util / printerConnection / doodle3dConnect.py
1 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
2
3 import threading
4 import json
5 import httplib as httpclient
6 import urllib
7 import time
8
9 from Cura.util.printerConnection import printerConnectionBase
10
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'
16
17         def __init__(self):
18                 super(doodle3dConnect, self).__init__()
19
20                 self._http = None
21                 self._host = None
22                 self._isAvailable = False
23                 self._printing = False
24                 self._fileBlocks = []
25                 self._blockIndex = None
26                 self._lineCount = 0
27                 self._progressLine = 0
28                 self._hotendTemperature = [None] * 4
29                 self._bedTemperature = None
30
31                 self.checkThread = threading.Thread(target=self._doodle3DThread)
32                 self.checkThread.daemon = True
33                 self.checkThread.start()
34
35         #Load the file into memory for printing.
36         def loadFile(self, filename):
37                 if self._printing:
38                         return False
39                 self._fileBlocks = []
40                 self._lineCount = 0
41                 block = []
42                 blockSize = 0
43                 f = open(filename, "r")
44                 for line in f:
45                         #Strip out comments, we do not need to send comments
46                         if ';' in line:
47                                 line = line[:line.index(';')]
48                         #Strip out whitespace at the beginning/end this saves data to send.
49                         line = line.strip()
50
51                         if len(line) < 1:
52                                 continue
53                         self._lineCount += 1
54                         #Put the lines in 2k sized blocks, so we can send those blocks as http requests.
55                         if blockSize + len(line) > 2048:
56                                 self._fileBlocks.append('\n'.join(block) + '\n')
57                                 block = []
58                                 blockSize = 0
59                         blockSize += len(line) + 1
60                         block.append(line)
61                 self._fileBlocks.append('\n'.join(block) + '\n')
62                 f.close()
63                 self._doCallback()
64                 return True
65
66         #Start printing the previously loaded file
67         def startPrint(self):
68                 if self._printing or len(self._fileBlocks) < 1:
69                         return
70                 self._progressLine = 0
71                 self._blockIndex = 0
72                 self._printing = True
73
74         #Abort the previously loaded print file
75         def cancelPrint(self):
76                 if not self._printing:
77                         return
78                 if self._request('POST', '/d3dapi/printer/stop', {'gcode': 'M104 S0\nG28'}):
79                         self._printing = False
80
81         def isPrinting(self):
82                 return self._printing
83
84         #Amount of progression of the current print file. 0.0 to 1.0
85         def getPrintProgress(self):
86                 if self._lineCount < 1:
87                         return 0.0
88                 return float(self._progressLine) / float(self._lineCount)
89
90         # Return if the printer with this connection type is available
91         def isAvailable(self):
92                 return self._isAvailable
93
94         # Get the connection status string. This is displayed to the user and can be used to communicate
95         #  various information to the user.
96         def getStatusString(self):
97                 if not self._isAvailable:
98                         return "Doodle3D box not found"
99                 if self._printing:
100                         if self._fileIndex < len(self._fileBlocks):
101                                 return "Sending GCode: %.1f" % (float(self._fileIndex) / float(len(self._fileBlocks)))
102                 return "TODO"
103
104         #Get the temperature of an extruder, returns None is no temperature is known for this extruder
105         def getTemperature(self, extruder):
106                 return self._hotendTemperature[extruder]
107
108         #Get the temperature of the heated bed, returns None is no temperature is known for the heated bed
109         def getBedTemperature(self):
110                 return self._bedTemperature
111
112         def _doodle3DThread(self):
113                 while True:
114                         while self._host is None:
115                                 printerList = self._request('GET', self.PRINTER_LIST_PATH, host=self.PRINTER_LIST_HOST)
116                                 if not printerList or type(printerList) is not dict or 'data' not in printerList or type(printerList['data']) is not list:
117                                         #Check if we are connected to the Doodle3D box in access point mode, as this gives an
118                                         # invalid reply on the printer list API
119                                         printerList = {'data': [{'localip': 'draw.doodle3d.com'}]}
120
121                                 #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.
122                                 # (connect.doodle3d.com also checks for this IP in the javascript code)
123                                 printerList['data'].append({'localip': '192.168.5.1'})
124
125                                 #Check the status of each possible IP, if we find a valid box with a printer connected. Use that IP.
126                                 for possiblePrinter in printerList['data']:
127                                         status = self._request('GET', '/d3dapi/info/status', host=possiblePrinter['localip'])
128                                         if status and 'data' in status and (status['data']['state'] == 'idle' or status['data']['state'] == 'buffering'):
129                                                 self._host = possiblePrinter['localip']
130                                                 break
131
132                                 if self._host is None:
133                                         #If we cannot find a doodle3d box, delay a minute and request the list again.
134                                         # This so we do not stress the connect.doodle3d.com api too much
135                                         time.sleep(60)
136
137                         stateReply = self._request('GET', '/d3dapi/info/status')
138                         if stateReply is None or not stateReply or stateReply['data']['state'] == 'disconnected':
139                                 # No API, wait 5 seconds before looking for Doodle3D again.
140                                 # API gave back an error (this can happen if the Doodle3D box is connecting to the printer)
141                                 # Or no printer connected
142                                 self._host = None
143                                 self._isAvailable = False
144                                 self._doCallback()
145                                 time.sleep(5)
146                                 continue
147
148                         #We got a valid status, set the doodle3d printer as available.
149                         if not self._isAvailable:
150                                 self._isAvailable = True
151
152                         if 'hotend' in stateReply['data']:
153                                 self._hotendTemperature[0] = stateReply['data']['hotend']
154                         if 'bed' in stateReply['data']:
155                                 self._bedTemperature = stateReply['data']['bed']
156
157                         if stateReply['data']['state'] == 'idle' or stateReply['data']['state'] == 'buffering':
158                                 if self._printing:
159                                         if self._blockIndex < len(self._fileBlocks):
160                                                 if self._request('POST', '/d3dapi/printer/print', {'gcode': self._fileBlocks[self._blockIndex], 'start': 'True', 'first': 'True'}):
161                                                         self._blockIndex += 1
162                                                 else:
163                                                         time.sleep(1)
164                                         else:
165                                                 self._printing = False
166                                 else:
167                                         time.sleep(5)
168                         elif stateReply['data']['state'] == 'printing':
169                                 if self._printing:
170                                         if self._blockIndex < len(self._fileBlocks):
171                                                 for n in xrange(0, 5):
172                                                         if self._blockIndex < len(self._fileBlocks):
173                                                                 if self._request('POST', '/d3dapi/printer/print', {'gcode': self._fileBlocks[self._blockIndex]}):
174                                                                         self._blockIndex += 1
175                                                                 else:
176                                                                         time.sleep(1)
177                                         else:
178                                                 #If we are no longer sending new GCode delay a bit so we request the status less often.
179                                                 time.sleep(1)
180                                         if 'current_line' in stateReply['data']:
181                                                 self._progressLine = stateReply['data']['current_line']
182                                 else:
183                                         #Got a printing state without us having send the print file, set the state to printing, but make sure we never send anything.
184                                         if 'current_line' in stateReply['data'] and 'total_lines' in stateReply['data']:
185                                                 self._printing = True
186                                                 self._blockIndex = len(self._fileBlocks)
187                                                 self._progressLine = stateReply['data']['current_line']
188                                                 self._lineCount = stateReply['data']['total_lines']
189                                         time.sleep(1)
190                         self._doCallback()
191
192         def _request(self, method, path, postData = None, host = None):
193                 if host is None:
194                         host = self._host
195                 if self._http is None or self._http.host != host:
196                         self._http = httpclient.HTTPConnection(host)
197
198                 try:
199                         if postData is not None:
200                                 self._http.request(method, path, urllib.urlencode(postData), {"Content-type": "application/x-www-form-urlencoded", "User-Agent": "Cura Doodle3D connection"})
201                         else:
202                                 self._http.request(method, path, headers={"Content-type": "application/x-www-form-urlencoded", "User-Agent": "Cura Doodle3D connection"})
203                 except:
204                         self._http.close()
205                         return None
206                 try:
207                         response = self._http.getresponse()
208                         responseText = response.read()
209                 except:
210                         self._http.close()
211                         return None
212                 try:
213                         response = json.loads(responseText)
214                 except ValueError:
215                         self._http.close()
216                         return None
217                 if response['status'] != 'success':
218                         return False
219
220                 return response
221
222 if __name__ == '__main__':
223         d = doodle3dConnect()
224         print 'Searching for Doodle3D box'
225         while not d.isAvailable():
226                 time.sleep(1)
227
228         while d.isPrinting():
229                 print 'Doodle3D already printing! Requesting stop!'
230                 d.cancelPrint()
231                 time.sleep(5)
232
233         print 'Doodle3D box found, printing!'
234         d.loadFile("C:/Models/belt-tensioner-wave_export.gcode")
235         d.startPrint()
236         while d.isPrinting() and d.isAvailable():
237                 time.sleep(1)
238                 print d.getTemperature(0), d.getStatusString(), d.getPrintProgress(), d._progressLine, d._lineCount, d._blockIndex, len(d._fileBlocks)
239         print 'Done'