Skip to content

Commit 6d9a2b6

Browse files
committed
initial commit
0 parents  commit 6d9a2b6

File tree

8 files changed

+1406
-0
lines changed

8 files changed

+1406
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
*.qmlc
2+
__pycache__/
3+
*.py[cod]

DuetRRFOutputDevice.py

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
import os.path
2+
from io import StringIO
3+
from time import time, sleep
4+
import datetime
5+
import base64
6+
import urllib
7+
import json
8+
9+
from PyQt5 import QtNetwork
10+
from PyQt5.QtCore import QFile, QUrl, QCoreApplication, QByteArray, QTimer
11+
from PyQt5.QtGui import QDesktopServices
12+
13+
from UM.Application import Application
14+
from UM.Logger import Logger
15+
from UM.Message import Message
16+
from UM.OutputDevice.OutputDevice import OutputDevice
17+
from UM.OutputDevice import OutputDeviceError
18+
19+
from UM.i18n import i18nCatalog
20+
catalog = i18nCatalog("cura")
21+
22+
23+
from enum import Enum
24+
class OutputStage(Enum):
25+
ready = 0
26+
writing = 1
27+
28+
class DeviceType(Enum):
29+
print = 0
30+
simulate = 1
31+
upload = 2
32+
33+
34+
class DuetRRFOutputDevice(OutputDevice):
35+
def __init__(
36+
self,
37+
name="DuetRRF",
38+
url="http://printer.local",
39+
duet_password="reprap",
40+
http_user=None,
41+
http_password=None,
42+
device_type=DeviceType.print
43+
):
44+
self._device_type = device_type
45+
if device_type == DeviceType.print:
46+
description = catalog.i18nc("@action:button", "Print on {0}").format(name)
47+
name_id = name + "-print"
48+
priority = 30
49+
elif device_type == DeviceType.simulate:
50+
description = catalog.i18nc("@action:button", "Simulate on {0}").format(name)
51+
name_id = name + "-simulate"
52+
priority = 20
53+
elif device_type == DeviceType.upload:
54+
description = catalog.i18nc("@action:button", "Upload to {0}").format(name)
55+
name_id = name + "-upload"
56+
priority = 10
57+
else:
58+
assert False
59+
60+
super().__init__(name_id)
61+
self.setShortDescription(description)
62+
self.setDescription(description)
63+
self.setPriority(priority)
64+
65+
self._stage = OutputStage.ready
66+
self._name = name
67+
68+
if not url.endswith('/'):
69+
url += '/'
70+
self._url = url
71+
72+
self._duet_password = duet_password
73+
self._http_user = http_user
74+
self._http_password = http_password
75+
76+
self._qnam = QtNetwork.QNetworkAccessManager()
77+
78+
self._stream = None
79+
self._cleanupRequest()
80+
81+
def _timestamp(self):
82+
return ("time", datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S'))
83+
84+
def _send(self, command, query, next_stage=None, data=None):
85+
enc_query = urllib.parse.urlencode(query)
86+
self._request = QtNetwork.QNetworkRequest(QUrl(self._url + "rr_" + command + "?" + enc_query))
87+
self._request.setRawHeader(b'User-Agent', b'Cura Plugin DuetRRF')
88+
self._request.setRawHeader(b'Accept', b'application/json, text/javascript')
89+
self._request.setRawHeader(b'Connection', b'keep-alive')
90+
91+
Logger.log("d", "%s %s %s" % (self._http_user, self._http_password, self._duet_password))
92+
if self._http_user and self._http_password:
93+
self._request.setRawHeader(b'Authorization', b'Basic ' + base64.b64encode("{}:{}".format(self._http_user, self._http_password).encode()))
94+
95+
if data:
96+
self._request.setRawHeader(b'Content-Type', b'application/octet-stream')
97+
self._reply = self._qnam.post(self._request, data)
98+
self._reply.uploadProgress.connect(self._onUploadProgress)
99+
else:
100+
self._reply = self._qnam.get(self._request)
101+
102+
self._reply.finished.connect(next_stage)
103+
self._reply.error.connect(self._onNetworkError)
104+
105+
def requestWrite(self, node, fileName = None, *args, **kwargs):
106+
if self._stage != OutputStage.ready:
107+
raise OutputDeviceError.DeviceBusyError()
108+
109+
if fileName:
110+
fileName = os.path.splitext(fileName)[0] + '.gcode'
111+
else:
112+
fileName = "%s.gcode" % Application.getInstance().getPrintInformation().jobName
113+
self._fileName = fileName
114+
115+
# create the temp file for the gcode
116+
self._stream = StringIO()
117+
self._stage = OutputStage.writing
118+
self.writeStarted.emit(self)
119+
120+
# show a progress message
121+
self._message = Message(catalog.i18nc("@info:progress", "Uploading to {}").format(self._name), 0, False, -1)
122+
self._message.show()
123+
124+
# send all the gcode to self._stream
125+
gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list")
126+
lines = len(gcode)
127+
nextYield = time() + 0.05
128+
i = 0
129+
for line in gcode:
130+
i += 1
131+
self._stream.write(line)
132+
if time() > nextYield:
133+
self._onProgress(i / lines)
134+
QCoreApplication.processEvents()
135+
nextYield = time() + 0.05
136+
137+
# start
138+
self._send('connect', [("password", self._duet_password), self._timestamp()], self.onConnected)
139+
140+
def onConnected(self):
141+
self._stream.seek(0)
142+
self._postData = QByteArray()
143+
self._postData.append(self._stream.getvalue().encode())
144+
self._send('upload', [("name", "0:/gcodes/" + self._fileName), self._timestamp()], self.onUploadDone, self._postData)
145+
146+
def onUploadDone(self):
147+
self._stream.close()
148+
self.stream = None
149+
150+
if self._device_type == DeviceType.simulate:
151+
if self._message:
152+
self._message.hide()
153+
text = catalog.i18nc("@info:progress", "Simulating print on {}").format(self._name)
154+
self._message = Message(text, 0, False, -1)
155+
self._message.show()
156+
157+
self._send('gcode', [("gcode", "M37 S1")], self.onReadyToPrint)
158+
elif self._device_type == DeviceType.print:
159+
self.onReadyToPrint()
160+
elif self._device_type == DeviceType.upload:
161+
if self._message:
162+
self._message.hide()
163+
text = "Uploaded file {} to {}.".format(os.path.basename(self._fileName), self._name)
164+
self._message = Message(catalog.i18nc("@info:status", text))
165+
self._message.addAction("open_browser", catalog.i18nc("@action:button", "Open Browser"), "globe", catalog.i18nc("@info:tooltip", "Open browser to DuetWebControl."))
166+
self._message.actionTriggered.connect(self._onMessageActionTriggered)
167+
self._message.show()
168+
169+
self.writeSuccess.emit(self)
170+
self._cleanupRequest()
171+
172+
def onReadyToPrint(self):
173+
self._send('gcode', [("gcode", "M32 /gcodes/" + self._fileName)], self.onPrintStarted)
174+
175+
def onPrintStarted(self):
176+
if self._device_type == DeviceType.simulate:
177+
self.onCheckStatus()
178+
else:
179+
if self._message:
180+
self._message.hide()
181+
182+
text = "Print started on {} with file {}".format(self._name, self._fileName)
183+
self._message = Message(catalog.i18nc("@info:status", text))
184+
self._message.addAction("open_browser", catalog.i18nc("@action:button", "Open Browser"), "globe", catalog.i18nc("@info:tooltip", "Open browser to DuetWebControl."))
185+
self._message.actionTriggered.connect(self._onMessageActionTriggered)
186+
self._message.show()
187+
188+
self.writeSuccess.emit(self)
189+
self._cleanupRequest()
190+
191+
def onSimulatedPrintFinished(self):
192+
self._send('gcode', [("gcode", "M37 S0")], self.onSimulationStopped)
193+
194+
def onCheckStatus(self):
195+
self._send('status', [("type", "3")], self.onStatusReceived)
196+
197+
def onStatusReceived(self):
198+
status_bytes = bytes(self._reply.readAll())
199+
Logger.log("d", status_bytes)
200+
201+
status = json.loads(status_bytes.decode())
202+
if status["status"] == "P":
203+
# still printing
204+
if self._message and "fractionPrinted" in status:
205+
self._message.setProgress(float(status["fractionPrinted"]))
206+
QTimer.singleShot(1000, self.onCheckStatus)
207+
else:
208+
# not printing any more (or error?)
209+
self.onSimulatedPrintFinished()
210+
211+
def onSimulationStopped(self):
212+
self._send('gcode', [("gcode", "M37")], self.onReporting)
213+
214+
def onReporting(self):
215+
self._send('reply', [], self.onReported)
216+
217+
def onReported(self):
218+
if self._message:
219+
self._message.hide()
220+
221+
reply_body = bytes(self._reply.readAll()).decode()
222+
text = "Simulation performed on {} with file {}:\n{}".format(self._name, self._fileName, reply_body)
223+
self._message = Message(catalog.i18nc("@info:status", text))
224+
self._message.addAction("open_browser", catalog.i18nc("@action:button", "Open Browser"), "globe", catalog.i18nc("@info:tooltip", "Open browser to DuetWebControl."))
225+
self._message.actionTriggered.connect(self._onMessageActionTriggered)
226+
self._message.show()
227+
228+
self.writeSuccess.emit(self)
229+
self._cleanupRequest()
230+
231+
def _onProgress(self, progress):
232+
if self._message:
233+
self._message.setProgress(progress)
234+
self.writeProgress.emit(self, progress)
235+
236+
def _cleanupRequest(self):
237+
self._reply = None
238+
self._request = None
239+
if self._stream:
240+
self._stream.close()
241+
self._stream = None
242+
self._stage = OutputStage.ready
243+
self._fileName = None
244+
245+
def _onMessageActionTriggered(self, message, action):
246+
if action == "open_browser":
247+
QDesktopServices.openUrl(QUrl(self._url))
248+
249+
def _onUploadProgress(self, bytesSent, bytesTotal):
250+
if bytesTotal > 0:
251+
self._onProgress(int(bytesSent * 100 / bytesTotal))
252+
253+
def _onNetworkError(self, errorCode):
254+
Logger.log("e", "_onNetworkError: %s", repr(errorCode))
255+
if self._message:
256+
self._message.hide()
257+
self._message = None
258+
259+
if self._reply:
260+
errorString = self._reply.errorString()
261+
else:
262+
errorString = ''
263+
message = Message(catalog.i18nc("@info:status", "There was a network error: {} {}").format(errorCode, errorString))
264+
message.show()
265+
266+
def _cancelUpload(self):
267+
if self._message:
268+
self._message.hide()
269+
self._message = None
270+
self._reply.abort()

0 commit comments

Comments
 (0)