Skip to content

Commit 024bab9

Browse files
committed
support new HTTP API for Duet3 with SBC running DuetSoftwareFramework
1 parent 10582d9 commit 024bab9

File tree

3 files changed

+121
-20
lines changed

3 files changed

+121
-20
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Changelog of Cura-DuetRRFPlugin
22

3+
## v1.0.8: 2020.04.20
4+
* support new HTTP API for Duet3 with SBC running DuetSoftwareFramework
5+
36
## v1.0.7: 2020-04-04
47
* bump compatibility for Cura 4.5 / API 7.1
58

DuetRRFOutputDevice.py

Lines changed: 117 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from typing import cast
99

1010
from PyQt5 import QtNetwork
11+
from PyQt5.QtNetwork import QNetworkReply
12+
1113
from PyQt5.QtCore import QFile, QUrl, QObject, QCoreApplication, QByteArray, QTimer, pyqtProperty, pyqtSignal, pyqtSlot
1214
from PyQt5.QtGui import QDesktopServices
1315
from 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 {}...\nPLEASE 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()

plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
"name": "DuetRRF",
33
"author": "Thomas Kriechbaumer",
44
"description": "Upload and Print to Duet 2 Wifi / Duet 2 Ethernet / Duet 2 Maestro / Duet 3 with RepRapFirmware.",
5-
"version": "1.0.7",
5+
"version": "1.0.8",
66
"supported_sdk_versions": ["7.1.0"]
77
}

0 commit comments

Comments
 (0)