88from typing import cast
99
1010from PyQt5 import QtNetwork
11+ from PyQt5 .QtNetwork import QNetworkReply
12+
1113from PyQt5 .QtCore import QFile , QUrl , QObject , QCoreApplication , QByteArray , QTimer , pyqtProperty , pyqtSignal , pyqtSlot
1214from PyQt5 .QtGui import QDesktopServices
1315from PyQt5 .QtQml import QQmlComponent , QQmlContext
@@ -69,6 +71,8 @@ def __init__(self, name, url, duet_password, http_user, http_password, device_ty
6971 self ._http_user = http_user
7072 self ._http_password = http_password
7173
74+ self ._use_rrf_http_api = True # by default we try to connect to the RRF HTTP API via rr_connect
75+
7276 Logger .log ("d" , self ._name_id + " | New DuetRRFOutputDevice created" )
7377 Logger .log ("d" , self ._name_id + " | URL: " + self ._url )
7478 Logger .log ("d" , self ._name_id + " | Duet password: " + ("set." if self ._duet_password else "empty." ))
@@ -88,8 +92,8 @@ def __init__(self, name, url, duet_password, http_user, http_password, device_ty
8892 def _timestamp (self ):
8993 return ("time" , datetime .datetime .now ().strftime ('%Y-%m-%dT%H:%M:%S' ))
9094
91- def _send (self , command , query = None , next_stage = None , data = None ):
92- url = "{}rr_{}" . format ( self ._url , command )
95+ def _send (self , command , query = None , next_stage = None , data = None , on_error = None , method = 'POST' ):
96+ url = self ._url + command
9397
9498 if not query :
9599 query = dict ()
@@ -107,14 +111,21 @@ def _send(self, command, query=None, next_stage=None, data=None):
107111
108112 if data :
109113 self ._request .setRawHeader (b'Content-Type' , b'application/octet-stream' )
110- self ._reply = self ._qnam .post (self ._request , data )
114+ if method == 'PUT' :
115+ self ._reply = self ._qnam .put (self ._request , data )
116+ else :
117+ self ._reply = self ._qnam .post (self ._request , data )
111118 self ._reply .uploadProgress .connect (self ._onUploadProgress )
112119 else :
113120 self ._reply = self ._qnam .get (self ._request )
114121
115122 if next_stage :
116123 self ._reply .finished .connect (next_stage )
117- self ._reply .error .connect (self ._onNetworkError )
124+
125+ if on_error :
126+ self ._reply .error .connect (on_error )
127+ else :
128+ self ._reply .error .connect (self ._onNetworkError )
118129
119130 def requestWrite (self , node , fileName = None , * args , ** kwargs ):
120131 if self ._stage != OutputStage .ready :
@@ -182,17 +193,49 @@ def onFilenameAccepted(self):
182193
183194 # start
184195 Logger .log ("d" , self ._name_id + " | Connecting..." )
185- self ._send ('connect' , [("password" , self ._duet_password ), self ._timestamp ()], self .onUploadReady )
196+ self ._send ('rr_connect' ,
197+ query = [("password" , self ._duet_password ), self ._timestamp ()],
198+ next_stage = self .onUploadReady ,
199+ on_error = self ._check_duet3_sbc ,
200+ )
201+
202+ def _check_duet3_sbc (self , errorCode ):
203+ Logger .log ("d" , self ._name_id + " | rr_connect failed with errorCode " + str (errorCode ))
204+ if errorCode == QNetworkReply .ContentNotFoundError :
205+ Logger .log ("d" , self ._name_id + " | errorCode indicates Duet3+SBC - let's try the DuetSoftwareFramework API instead..." )
206+ self ._use_rrf_http_api = False # let's try the newer DuetSoftwareFramework for Duet3+SBC API instead
207+ self ._send ('machine/status' ,
208+ next_stage = self .onUploadReady
209+ )
210+ else :
211+ self ._onNetworkError (errorCode )
186212
187213 def onUploadReady (self ):
188214 if self ._stage != OutputStage .writing :
189215 return
190216
191217 Logger .log ("d" , self ._name_id + " | Uploading..." )
218+
219+ if not self ._stream :
220+ Logger .log ("d" , self ._name_id + " | Upload failed because stream is already None - THIS SHOULD NOT HAPPEN!" )
221+ return
222+
192223 self ._stream .seek (0 )
193224 self ._postData = QByteArray ()
194225 self ._postData .append (self ._stream .getvalue ().encode ())
195- self ._send ('upload' , [("name" , "0:/gcodes/" + self ._fileName ), self ._timestamp ()], self .onUploadDone , self ._postData )
226+
227+ if self ._use_rrf_http_api :
228+ self ._send ('rr_upload' ,
229+ query = [("name" , "0:/gcodes/" + self ._fileName ), self ._timestamp ()],
230+ next_stage = self .onUploadDone ,
231+ data = self ._postData ,
232+ )
233+ else :
234+ self ._send ('machine/file/gcodes/' + self ._fileName ,
235+ next_stage = self .onUploadDone ,
236+ data = self ._postData ,
237+ method = 'PUT' ,
238+ )
196239
197240 def onUploadDone (self ):
198241 if self ._stage != OutputStage .writing :
@@ -201,7 +244,7 @@ def onUploadDone(self):
201244 Logger .log ("d" , self ._name_id + " | Upload done" )
202245
203246 self ._stream .close ()
204- self .stream = None
247+ self ._stream = None
205248
206249 if self ._device_type == DeviceType .simulate :
207250 Logger .log ("d" , self ._name_id + " | Simulating..." )
@@ -210,11 +253,23 @@ def onUploadDone(self):
210253 self ._message = Message (catalog .i18nc ("@info:progress" , "Simulating print on {}...\n PLEASE CLOSE DWC AND DO NOT INTERACT WITH THE PRINTER!" ).format (self ._name ), 0 , False , - 1 )
211254 self ._message .show ()
212255
213- self ._send ('gcode' , [("gcode" , 'M37 P"0:/gcodes/' + self ._fileName + '"' )], self .onSimulationPrintStarted )
256+ gcode = 'M37 P"0:/gcodes/' + self ._fileName + '"'
257+ Logger .log ("d" , self ._name_id + " | Sending gcode:" + gcode )
258+ if self ._use_rrf_http_api :
259+ self ._send ('rr_gcode' ,
260+ query = [("gcode" , gcode )],
261+ next_stage = self .onSimulationPrintStarted ,
262+ )
263+ else :
264+ self ._send ('machine/code' ,
265+ data = gcode .encode (),
266+ next_stage = self .onSimulationPrintStarted ,
267+ )
214268 elif self ._device_type == DeviceType .print :
215269 self .onReadyToPrint ()
216270 elif self ._device_type == DeviceType .upload :
217- self ._send ('disconnect' )
271+ if self ._use_rrf_http_api :
272+ self ._send ('rr_disconnect' )
218273 if self ._message :
219274 self ._message .hide ()
220275 text = "Uploaded file {} to {}." .format (os .path .basename (self ._fileName ), self ._name )
@@ -231,15 +286,28 @@ def onReadyToPrint(self):
231286 return
232287
233288 Logger .log ("d" , self ._name_id + " | Ready to print" )
234- self ._send ('gcode' , [("gcode" , 'M32 "0:/gcodes/' + self ._fileName + '"' )], self .onPrintStarted )
289+
290+ gcode = 'M32 "0:/gcodes/' + self ._fileName + '"'
291+ Logger .log ("d" , self ._name_id + " | Sending gcode:" + gcode )
292+ if self ._use_rrf_http_api :
293+ self ._send ('rr_gcode' ,
294+ query = [("gcode" , gcode )],
295+ next_stage = self .onPrintStarted ,
296+ )
297+ else :
298+ self ._send ('machine/code' ,
299+ data = gcode .encode (),
300+ next_stage = self .onPrintStarted ,
301+ )
235302
236303 def onPrintStarted (self ):
237304 if self ._stage != OutputStage .writing :
238305 return
239306
240307 Logger .log ("d" , self ._name_id + " | Print started" )
241308
242- self ._send ('disconnect' )
309+ if self ._use_rrf_http_api :
310+ self ._send ('rr_disconnect' )
243311 if self ._message :
244312 self ._message .hide ()
245313 text = "Print started on {} with file {}" .format (self ._name , self ._fileName )
@@ -258,39 +326,66 @@ def onSimulationPrintStarted(self):
258326 Logger .log ("d" , self ._name_id + " | Simulation print started for file " + self ._fileName )
259327
260328 # give it some to start the simulation
261- QTimer .singleShot (15000 , self .onCheckStatus )
329+ QTimer .singleShot (2000 , self .onCheckStatus )
262330
263331 def onCheckStatus (self ):
264332 if self ._stage != OutputStage .writing :
265333 return
266334
267335 Logger .log ("d" , self ._name_id + " | Checking status..." )
268336
269- self ._send ('status' , [("type" , "3" )], self .onStatusReceived )
337+ if self ._use_rrf_http_api :
338+ self ._send ('rr_status' ,
339+ query = [("type" , "3" )],
340+ next_stage = self .onStatusReceived ,
341+ )
342+ else :
343+ self ._send ('machine/status' ,
344+ next_stage = self .onStatusReceived ,
345+ )
270346
271347 def onStatusReceived (self ):
272348 if self ._stage != OutputStage .writing :
273349 return
274350
351+ Logger .log ("d" , self ._name_id + " | Status received - decoding..." )
275352 reply_body = bytes (self ._reply .readAll ()).decode ()
276- Logger .log ("d" , self ._name_id + " | Status received | " + reply_body )
353+ Logger .log ("d" , self ._name_id + " | Status: " + reply_body )
277354
278355 status = json .loads (reply_body )
279- if status ["status" ] in ['P' , 'M' ] :
280- # still simulating
356+ if self ._use_rrf_http_api :
281357 # RRF 1.21RC2 and earlier used P while simulating
282358 # RRF 1.21RC3 and later uses M while simulating
359+ busy = status ["status" ] in ['P' , 'M' ]
360+ else :
361+ busy = status ["result" ]["state" ]["status" ] == 'simulating'
362+
363+ if busy :
364+ # still simulating
283365 if self ._message and "fractionPrinted" in status :
284366 self ._message .setProgress (float (status ["fractionPrinted" ]))
285- QTimer .singleShot (5000 , self .onCheckStatus )
367+ QTimer .singleShot (1000 , self .onCheckStatus )
286368 else :
287369 Logger .log ("d" , self ._name_id + " | Simulation print finished" )
288- self ._send ('reply' , [], self .onReported )
370+
371+ gcode = 'M37'
372+ Logger .log ("d" , self ._name_id + " | Sending gcode:" + gcode )
373+ if self ._use_rrf_http_api :
374+ self ._send ('rr_gcode' ,
375+ query = [("gcode" , gcode )],
376+ next_stage = self .onReported ,
377+ )
378+ else :
379+ self ._send ('machine/code' ,
380+ data = gcode .encode (),
381+ next_stage = self .onReported ,
382+ )
289383
290384 def onReported (self ):
291385 if self ._stage != OutputStage .writing :
292386 return
293387
388+ Logger .log ("d" , self ._name_id + " | Simulation status received - decoding..." )
294389 reply_body = bytes (self ._reply .readAll ()).decode ().strip ()
295390 Logger .log ("d" , self ._name_id + " | Reported | " + reply_body )
296391
@@ -303,7 +398,8 @@ def onReported(self):
303398 self ._message .actionTriggered .connect (self ._onMessageActionTriggered )
304399 self ._message .show ()
305400
306- self ._send ('disconnect' )
401+ if self ._use_rrf_http_api :
402+ self ._send ('rr_disconnect' )
307403 self .writeSuccess .emit (self )
308404 self ._cleanupRequest ()
309405
@@ -313,6 +409,7 @@ def _onProgress(self, progress):
313409 self .writeProgress .emit (self , progress )
314410
315411 def _cleanupRequest (self ):
412+ Logger .log ("e" , "_cleanupRequest called" )
316413 self ._reply = None
317414 self ._request = None
318415 if self ._stream :
@@ -333,6 +430,7 @@ def _onUploadProgress(self, bytesSent, bytesTotal):
333430 self ._onProgress (int (bytesSent * 100 / bytesTotal ))
334431
335432 def _onNetworkError (self , errorCode ):
433+ # https://doc.qt.io/qt-5/qnetworkreply.html#NetworkError-enum
336434 Logger .log ("e" , "_onNetworkError: %s" , repr (errorCode ))
337435 if self ._message :
338436 self ._message .hide ()
0 commit comments