forked from fieldOfView/Cura-OctoPrintPlugin
-
Notifications
You must be signed in to change notification settings - Fork 0
/
DiscoverOctoPrintAction.py
260 lines (206 loc) · 11.1 KB
/
DiscoverOctoPrintAction.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
from UM.i18n import i18nCatalog
from UM.Logger import Logger
from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Application import Application
from UM.Settings.ContainerRegistry import ContainerRegistry
from cura.MachineAction import MachineAction
from PyQt5.QtCore import pyqtSignal, pyqtProperty, pyqtSlot, QUrl, QObject
from PyQt5.QtQml import QQmlComponent, QQmlContext
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager
import os.path
import json
catalog = i18nCatalog("cura")
class DiscoverOctoPrintAction(MachineAction):
def __init__(self, parent = None):
super().__init__("DiscoverOctoPrintAction", catalog.i18nc("@action", "Connect OctoPrint"))
self._qml_url = "DiscoverOctoPrintAction.qml"
self._window = None
self._context = None
self._network_plugin = None
# QNetwork manager needs to be created in advance. If we don't it can happen that it doesn't correctly
# hook itself into the event loop, which results in events never being fired / done.
self._manager = QNetworkAccessManager()
self._manager.finished.connect(self._onRequestFinished)
self._settings_reply = None
# Try to get version information from plugin.json
plugin_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "plugin.json")
try:
with open(plugin_file_path) as plugin_file:
plugin_info = json.load(plugin_file)
plugin_version = plugin_info["version"]
except:
# The actual version info is not critical to have so we can continue
plugin_version = "Unknown"
Logger.logException("w", "Could not get version information for the plugin")
self._user_agent = ("%s/%s %s/%s" % (
Application.getInstance().getApplicationName(),
Application.getInstance().getVersion(),
"OctoPrintPlugin",
Application.getInstance().getVersion()
)).encode()
self._instance_responded = False
self._instance_api_key_accepted = False
self._instance_supports_sd = False
self._instance_supports_camera = False
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded)
Application.getInstance().engineCreatedSignal.connect(self._createAdditionalComponentsView)
@pyqtSlot()
def startDiscovery(self):
if not self._network_plugin:
self._network_plugin = Application.getInstance().getOutputDeviceManager().getOutputDevicePlugin("OctoPrintPlugin")
self._network_plugin.addInstanceSignal.connect(self._onInstanceDiscovery)
self._network_plugin.removeInstanceSignal.connect(self._onInstanceDiscovery)
self._network_plugin.instanceListChanged.connect(self._onInstanceDiscovery)
self.instancesChanged.emit()
else:
# Restart bonjour discovery
self._network_plugin.startDiscovery()
def _onInstanceDiscovery(self, *args):
self.instancesChanged.emit()
@pyqtSlot(str)
def removeManualInstance(self, name):
if not self._network_plugin:
return
self._network_plugin.removeManualInstance(name)
@pyqtSlot(str, str, int, str, bool, str, str)
def setManualInstance(self, name, address, port, path, useHttps, userName, password):
# This manual printer could replace a current manual printer
self._network_plugin.removeManualInstance(name)
self._network_plugin.addManualInstance(name, address, port, path, useHttps, userName, password)
def _onContainerAdded(self, container):
# Add this action as a supported action to all machine definitions
if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine" and container.getMetaDataEntry("supports_usb_connection"):
Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
instancesChanged = pyqtSignal()
@pyqtProperty("QVariantList", notify = instancesChanged)
def discoveredInstances(self):
if self._network_plugin:
instances = list(self._network_plugin.getInstances().values())
instances.sort(key = lambda k: k.name)
return instances
else:
return []
@pyqtSlot(str)
def setKey(self, key):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
if "octoprint_id" in global_container_stack.getMetaData():
global_container_stack.setMetaDataEntry("octoprint_id", key)
else:
global_container_stack.addMetaDataEntry("octoprint_id", key)
if self._network_plugin:
# Ensure that the connection states are refreshed.
self._network_plugin.reCheckConnections()
@pyqtSlot(result = str)
def getStoredKey(self):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
meta_data = global_container_stack.getMetaData()
if "octoprint_id" in meta_data:
return global_container_stack.getMetaDataEntry("octoprint_id")
return ""
@pyqtSlot(str, str, str, str)
def testApiKey(self, base_url, api_key, basic_auth_username = "", basic_auth_password = ""):
self._instance_responded = False
self._instance_api_key_accepted = False
self._instance_supports_sd = False
self._instance_supports_camera = False
self.selectedInstanceSettingsChanged.emit()
if api_key != "":
Logger.log("d", "Trying to access OctoPrint instance at %s with the provided API key." % base_url)
## Request 'settings' dump
url = QUrl(base_url + "api/settings")
settings_request = QNetworkRequest(url)
settings_request.setRawHeader("X-Api-Key".encode(), api_key.encode())
settings_request.setRawHeader("User-Agent".encode(), self._user_agent)
if basic_auth_username and basic_auth_password:
settings_request.setRawHeader("Authentication".encode(), ("%s:%s" % (basic_auth_username, basic_auth_username)).encode())
self._settings_reply = self._manager.get(settings_request)
else:
if self._settings_reply:
self._settings_reply.abort()
self._settings_reply = None
@pyqtSlot(str)
def setApiKey(self, api_key):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
if "octoprint_api_key" in global_container_stack.getMetaData():
global_container_stack.setMetaDataEntry("octoprint_api_key", api_key)
else:
global_container_stack.addMetaDataEntry("octoprint_api_key", api_key)
if self._network_plugin:
# Ensure that the connection states are refreshed.
self._network_plugin.reCheckConnections()
apiKeyChanged = pyqtSignal()
## Get the stored API key of this machine
# \return key String containing the key of the machine.
@pyqtProperty(str, notify = apiKeyChanged)
def apiKey(self):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
return global_container_stack.getMetaDataEntry("octoprint_api_key")
else:
return ""
selectedInstanceSettingsChanged = pyqtSignal()
@pyqtProperty(bool, notify = selectedInstanceSettingsChanged)
def instanceResponded(self):
return self._instance_responded
@pyqtProperty(bool, notify = selectedInstanceSettingsChanged)
def instanceApiKeyAccepted(self):
return self._instance_api_key_accepted
@pyqtProperty(bool, notify = selectedInstanceSettingsChanged)
def instanceSupportsSd(self):
return self._instance_supports_sd
@pyqtProperty(bool, notify = selectedInstanceSettingsChanged)
def instanceSupportsCamera(self):
return self._instance_supports_camera
@pyqtSlot(str, str, str)
def setContainerMetaDataEntry(self, container_id, key, value):
containers = ContainerRegistry.getInstance().findContainers(None, id = container_id)
if not containers:
UM.Logger.log("w", "Could not set metadata of container %s because it was not found.", container_id)
return False
container = containers[0]
if key in container.getMetaData():
container.setMetaDataEntry(key, value)
else:
container.addMetaDataEntry(key, value)
@pyqtSlot(str)
def openWebPage(self, url):
QDesktopServices.openUrl(QUrl(url))
def _createAdditionalComponentsView(self):
Logger.log("d", "Creating additional ui components for OctoPrint-connected printers.")
path = QUrl.fromLocalFile(os.path.join(os.path.dirname(os.path.abspath(__file__)), "OctoPrintComponents.qml"))
self._additional_component = QQmlComponent(Application.getInstance()._engine, path)
# We need access to engine (although technically we can't)
self._additional_components_context = QQmlContext(Application.getInstance()._engine.rootContext())
self._additional_components_context.setContextProperty("manager", self)
self._additional_components_view = self._additional_component.create(self._additional_components_context)
if not self._additional_components_view:
Logger.log("w", "Could not create additional components for OctoPrint-connected printers.")
return
Application.getInstance().addAdditionalComponent("monitorButtons", self._additional_components_view.findChild(QObject, "openOctoPrintButton"))
## Handler for all requests that have finished.
def _onRequestFinished(self, reply):
http_status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
if not http_status_code:
# Received no or empty reply
return
if reply.operation() == QNetworkAccessManager.GetOperation:
if "api/settings" in reply.url().toString(): # OctoPrint settings dump from /settings:
if http_status_code == 200:
Logger.log("d", "API key accepted by OctoPrint.")
self._instance_api_key_accepted = True
json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
if "feature" in json_data:
self._instance_supports_sd = json_data["feature"]["sdSupport"]
if "webcam" in json_data:
stream_url = json_data["webcam"]["streamUrl"]
if stream_url != "":
self._instance_supports_camera = True
elif http_status_code == 401:
Logger.log("d", "Invalid API key for OctoPrint.")
self._instance_api_key_accepted = False
self._instance_responded = True
self.selectedInstanceSettingsChanged.emit()