Browse Source

put octoprint api functions in own class

Thomas Buck 1 year ago
parent
commit
030e45e6af
7 changed files with 556 additions and 453 deletions
  1. 1
    1
      data/de.xythobuz.octotray.desktop
  2. 2
    2
      dist/PKGBUILD
  3. 1
    1
      dist/setup_mac.py
  4. 410
    0
      src/APIOctoprint.py
  5. 28
    43
      src/CamWindow.py
  6. 55
    383
      src/OctoTray.py
  7. 59
    23
      src/SettingsWindow.py

+ 1
- 1
data/de.xythobuz.octotray.desktop View File

@@ -1,6 +1,6 @@
1 1
 [Desktop Entry]
2 2
 Type=Application
3
-Version=0.4
3
+Version=0.5
4 4
 Name=OctoTray
5 5
 Comment=Control OctoPrint instances from system tray
6 6
 Path=/usr/bin

+ 2
- 2
dist/PKGBUILD View File

@@ -1,12 +1,12 @@
1 1
 # Maintainer: Thomas Buck <thomas@xythobuz.de>
2 2
 pkgname=OctoTray
3
-pkgver=0.4
3
+pkgver=0.5
4 4
 pkgrel=1
5 5
 pkgdesc="Control OctoPrint instances from system tray"
6 6
 arch=('any')
7 7
 license=('unknown')
8 8
 depends=('python-pyqt5')
9
-source=("octotray.py"
9
+source=("octotray"
10 10
         "octotray_icon.png"
11 11
         "de.xythobuz.octotray.desktop")
12 12
 md5sums=(SKIP

+ 1
- 1
dist/setup_mac.py View File

@@ -6,7 +6,7 @@ from setuptools import setup
6 6
 APP_NAME = "OctoTray"
7 7
 APP = [ 'main.py' ]
8 8
 DATA_FILES = [ 'octotray_icon.png' ]
9
-VERSION="0.4.0"
9
+VERSION="0.5.0"
10 10
 
11 11
 OPTIONS = {
12 12
     'argv_emulation': True,

+ 410
- 0
src/APIOctoprint.py View File

@@ -0,0 +1,410 @@
1
+#!/usr/bin/env python3
2
+
3
+# OctoTray Linux Qt System Tray OctoPrint client
4
+#
5
+# APIOctoprint.py
6
+#
7
+# HTTP API for OctoPrint.
8
+
9
+import json
10
+import time
11
+import urllib.parse
12
+import urllib.request
13
+import operator
14
+import socket
15
+
16
+class APIOctoprint():
17
+    statesWithWarning = [
18
+        "printing", "pausing", "paused"
19
+    ]
20
+
21
+    def __init__(self, parent, host, key):
22
+        self.parent = parent
23
+        self.host = host
24
+        self.key = key
25
+
26
+    # return list of tuples ( "name", func(name) )
27
+    # with all available commands.
28
+    # call function in with name of action!
29
+    def getAvailableCommands(self):
30
+        self.method = self.getMethod()
31
+        print("Printer " + self.host + " has method " + self.method)
32
+
33
+        commands = []
34
+
35
+        if self.method == "unknown":
36
+            # nothing available
37
+            return commands
38
+
39
+        # always add available system commands
40
+        systemCommands = self.getSystemCommands()
41
+        for sc in systemCommands:
42
+            commands.append((sc, self.callSystemCommand))
43
+
44
+        if self.method == "psucontrol":
45
+            # support for psucontrol plugin
46
+            commands.append(("Turn On PSU", self.setPower))
47
+            commands.append(("Turn Off PSU", self.setPower))
48
+
49
+        return commands
50
+
51
+    ############
52
+    # HTTP API #
53
+    ############
54
+
55
+    def sendRequest(self, headers, path, content = None):
56
+        url = "http://" + self.host + "/api/" + path
57
+        if content == None:
58
+            request = urllib.request.Request(url, None, headers)
59
+        else:
60
+            data = content.encode('ascii')
61
+            request = urllib.request.Request(url, data, headers)
62
+
63
+        try:
64
+            with urllib.request.urlopen(request, None, self.parent.networkTimeout) as response:
65
+                text = response.read()
66
+                return text
67
+        except (urllib.error.URLError, urllib.error.HTTPError) as error:
68
+            print("Error requesting URL \"" + url + "\": \"" + str(error) + "\"")
69
+            return "error"
70
+        except socket.timeout:
71
+            print("Timeout waiting for response to \"" + url + "\"")
72
+            return "timeout"
73
+
74
+    def sendPostRequest(self, path, content):
75
+        headers = {
76
+            "Content-Type": "application/json",
77
+            "X-Api-Key": self.key
78
+        }
79
+        return self.sendRequest(headers, path, content)
80
+
81
+    def sendGetRequest(self, path):
82
+        headers = {
83
+            "X-Api-Key": self.key
84
+        }
85
+        return self.sendRequest(headers, path)
86
+
87
+    #####################
88
+    # Command discovery #
89
+    #####################
90
+
91
+    def getMethod(self):
92
+        r = self.sendGetRequest("plugin/psucontrol")
93
+        if r == "timeout":
94
+            return "unknown"
95
+
96
+        try:
97
+            rd = json.loads(r)
98
+            if "isPSUOn" in rd:
99
+                return "psucontrol"
100
+        except json.JSONDecodeError:
101
+            pass
102
+
103
+        r = self.sendGetRequest("system/commands/custom")
104
+        if r == "timeout":
105
+            return "unknown"
106
+
107
+        try:
108
+            rd = json.loads(r)
109
+            for c in rd:
110
+                if "action" in c:
111
+                    # we have some custom commands and no psucontrol
112
+                    # so lets try to use that instead of skipping
113
+                    # the printer completely with 'unknown'
114
+                    return "system"
115
+        except json.JSONDecodeError:
116
+            pass
117
+
118
+        return "unknown"
119
+
120
+    def getSystemCommands(self):
121
+        l = []
122
+        r = self.sendGetRequest("system/commands/custom")
123
+        try:
124
+            rd = json.loads(r)
125
+
126
+            if len(rd) > 0:
127
+                print("system commands available for " + self.host + ":")
128
+
129
+            for c in rd:
130
+                if "action" in c:
131
+                    print("  - " + c["action"])
132
+                    l.append(c["action"])
133
+        except json.JSONDecodeError:
134
+            pass
135
+        return l
136
+
137
+    #################
138
+    # Safety Checks #
139
+    #################
140
+
141
+    def stateSafetyCheck(self, actionString):
142
+        state = self.getState()
143
+        if state.lower() in self.statesWithWarning:
144
+            if self.parent.showDialog("OctoTray Warning", "The printer seems to be running currently!", "Do you really want to " + actionString + "?", True, True) == False:
145
+                return True
146
+        return False
147
+
148
+    def tempSafetyCheck(self, actionString):
149
+        if self.getTemperatureIsSafe() == False:
150
+            if self.parent.showDialog("OctoTray Warning", "The printer seems to still be hot!", "Do you really want to " + actionString + "?", True, True) == False:
151
+                return True
152
+        return False
153
+
154
+    def safetyCheck(self, actionString):
155
+        if self.stateSafetyCheck(actionString):
156
+            return True
157
+        if self.tempSafetyCheck(actionString):
158
+            return True
159
+        return False
160
+
161
+    ##################
162
+    # Power Toggling #
163
+    ##################
164
+
165
+    def callSystemCommand(self, name):
166
+        if "off" in name.lower():
167
+            if self.safetyCheck("run '" + name + "'"):
168
+                return
169
+
170
+        cmd = urllib.parse.quote(name)
171
+        self.sendPostRequest("system/commands/custom/" + cmd, '')
172
+
173
+    def setPower(self, name):
174
+        if "off" in name.lower():
175
+            if self.safetyCheck(name):
176
+                return
177
+
178
+        cmd = "turnPSUOff"
179
+        if "on" in name.lower():
180
+            cmd = "turnPSUOn"
181
+
182
+        return self.sendPostRequest("plugin/psucontrol", '{ "command":"' + cmd + '" }')
183
+
184
+    def turnOn(self):
185
+        if self.method == "psucontrol":
186
+            self.setPower("on")
187
+        elif self.method == "system":
188
+            cmds = self.getSystemCommands()
189
+            for cmd in cmds:
190
+                if "on" in cmd:
191
+                    self.callSystemCommand(cmd)
192
+                    break
193
+
194
+    def turnOff(self):
195
+        if self.method == "psucontrol":
196
+            self.setPower("off")
197
+        elif self.method == "system":
198
+            cmds = self.getSystemCommands()
199
+            for cmd in cmds:
200
+                if "off" in cmd:
201
+                    self.callSystemCommand(cmd)
202
+                    break
203
+
204
+    ######################
205
+    # Status Information #
206
+    ######################
207
+
208
+    def getTemperatureIsSafe(self, limit = 50.0):
209
+        r = self.sendGetRequest("printer")
210
+        try:
211
+            rd = json.loads(r)
212
+
213
+            if "temperature" in rd:
214
+                if ("tool0" in rd["temperature"]) and ("actual" in rd["temperature"]["tool0"]):
215
+                    if rd["temperature"]["tool0"]["actual"] > limit:
216
+                        return False
217
+
218
+                if ("tool1" in rd["temperature"]) and ("actual" in rd["temperature"]["tool1"]):
219
+                    if rd["temperature"]["tool1"]["actual"] > limit:
220
+                        return False
221
+        except json.JSONDecodeError:
222
+            pass
223
+        return True
224
+
225
+    def getTemperatureString(self):
226
+        r = self.sendGetRequest("printer")
227
+        s = ""
228
+        try:
229
+            rd = json.loads(r)
230
+        except json.JSONDecodeError:
231
+            return s
232
+
233
+        if ("state" in rd) and ("text" in rd["state"]):
234
+            s += rd["state"]["text"]
235
+            if "temperature" in rd:
236
+                s += " - "
237
+
238
+        if "temperature" in rd:
239
+            if ("bed" in rd["temperature"]) and ("actual" in rd["temperature"]["bed"]):
240
+                s += "B"
241
+                s += "%.1f" % rd["temperature"]["bed"]["actual"]
242
+                if "target" in rd["temperature"]["bed"]:
243
+                    s += "/"
244
+                    s += "%.1f" % rd["temperature"]["bed"]["target"]
245
+                s += " "
246
+
247
+            if ("tool0" in rd["temperature"]) and ("actual" in rd["temperature"]["tool0"]):
248
+                s += "T"
249
+                s += "%.1f" % rd["temperature"]["tool0"]["actual"]
250
+                if "target" in rd["temperature"]["tool0"]:
251
+                    s += "/"
252
+                    s += "%.1f" % rd["temperature"]["tool0"]["target"]
253
+                s += " "
254
+
255
+            if ("tool1" in rd["temperature"]) and ("actual" in rd["temperature"]["tool1"]):
256
+                s += "T"
257
+                s += "%.1f" % rd["temperature"]["tool1"]["actual"]
258
+                if "target" in rd["temperature"]["tool1"]:
259
+                    s += "/"
260
+                    s += "%.1f" % rd["temperature"]["tool1"]["target"]
261
+                s += " "
262
+        return s.strip()
263
+
264
+    def getState(self):
265
+        r = self.sendGetRequest("job")
266
+        try:
267
+            rd = json.loads(r)
268
+            if "state" in rd:
269
+                return rd["state"]
270
+        except json.JSONDecodeError:
271
+            pass
272
+        return "Unknown"
273
+
274
+    def getProgress(self):
275
+        r = self.sendGetRequest("job")
276
+        try:
277
+            rd = json.loads(r)
278
+            if "progress" in rd:
279
+                return rd["progress"]
280
+        except json.JSONDecodeError:
281
+            pass
282
+        return "Unknown"
283
+
284
+    def getName(self):
285
+        r = self.sendGetRequest("printerprofiles")
286
+        try:
287
+            rd = json.loads(r)
288
+            if "profiles" in rd:
289
+                p = next(iter(rd["profiles"]))
290
+                if "name" in rd["profiles"][p]:
291
+                    return rd["profiles"][p]["name"]
292
+        except json.JSONDecodeError:
293
+            pass
294
+        return self.host
295
+
296
+    def getProgressString(self):
297
+        s = ""
298
+        progress = self.getProgress()
299
+        if ("completion" in progress) and ("printTime" in progress) and ("printTimeLeft" in progress) and (progress["completion"] != None) and (progress["printTime"] != None) and (progress["printTimeLeft"] != None):
300
+            s += "%.1f%%" % progress["completion"]
301
+            s += " - runtime "
302
+            s += time.strftime("%H:%M:%S", time.gmtime(progress["printTime"]))
303
+            s += " - "
304
+            s += time.strftime("%H:%M:%S", time.gmtime(progress["printTimeLeft"])) + " left"
305
+        return s
306
+
307
+    ###################
308
+    # Printer Actions #
309
+    ###################
310
+
311
+    def callHoming(self, axes = "xyz"):
312
+        if self.stateSafetyCheck("home it"):
313
+            return
314
+
315
+        axes_string = ''
316
+        for i in range(0, len(axes)):
317
+            axes_string += '"' + str(axes[i]) + '"'
318
+            if i < (len(axes) - 1):
319
+                axes_string += ', '
320
+
321
+        self.sendPostRequest("printer/printhead", '{ "command": "home", "axes": [' + axes_string + '] }')
322
+
323
+    def callMove(self, axis, dist, relative = True):
324
+        if self.stateSafetyCheck("move it"):
325
+            return
326
+
327
+        absolute = ''
328
+        if relative == False:
329
+            absolute = ', "absolute": true'
330
+
331
+        self.sendPostRequest("printer/printhead", '{ "command": "jog", "' + str(axis) + '": ' + str(dist) + ', "speed": ' + str(self.jogMoveSpeed) + absolute + ' }')
332
+
333
+    def callPauseResume(self):
334
+        if self.stateSafetyCheck("pause/resume"):
335
+            return
336
+        self.sendPostRequest("job", '{ "command": "pause", "action": "toggle" }')
337
+
338
+    def callJobCancel(self):
339
+        if self.stateSafetyCheck("cancel"):
340
+            return
341
+        self.sendPostRequest("job", '{ "command": "cancel" }')
342
+
343
+    def statusDialog(self):
344
+        progress = self.getProgress()
345
+        s = self.host + "\n"
346
+        warning = False
347
+        if ("completion" in progress) and ("printTime" in progress) and ("printTimeLeft" in progress) and (progress["completion"] != None) and (progress["printTime"] != None) and (progress["printTimeLeft"] != None):
348
+            s += "%.1f%% Completion\n" % progress["completion"]
349
+            s += "Printing since " + time.strftime("%H:%M:%S", time.gmtime(progress["printTime"])) + "\n"
350
+            s += time.strftime("%H:%M:%S", time.gmtime(progress["printTimeLeft"])) + " left"
351
+        elif ("completion" in progress) and ("printTime" in progress) and ("printTimeLeft" in progress):
352
+            s += "No job is currently running"
353
+        else:
354
+            s += "Could not read printer status!"
355
+            warning = True
356
+        t = self.getTemperatureString()
357
+        if len(t) > 0:
358
+            s += "\n" + t
359
+        self.parent.showDialog("OctoTray Status", s, None, False, warning)
360
+
361
+    #################
362
+    # File Handling #
363
+    #################
364
+
365
+    def getRecentFiles(self, count):
366
+        r = self.sendGetRequest("files?recursive=true")
367
+        files = []
368
+        try:
369
+            rd = json.loads(r)
370
+            if "files" in rd:
371
+                t = [f for f in rd["files"] if "date" in f]
372
+                fs = sorted(t, key=operator.itemgetter("date"), reverse=True)
373
+                for f in fs[:count]:
374
+                    files.append((f["name"], f["origin"] + "/" + f["path"]))
375
+        except json.JSONDecodeError:
376
+            pass
377
+        return files
378
+
379
+    def printFile(self, path):
380
+        self.sendPostRequest("files/" + path, '{ "command": "select", "print": true }')
381
+
382
+    ###############
383
+    # Temperature #
384
+    ###############
385
+
386
+    def setTemperature(self, what, temp):
387
+        path = "printer/bed"
388
+        s = "{\"command\": \"target\", \"target\": " + temp + "}"
389
+
390
+        if "tool" in what:
391
+            path = "printer/tool"
392
+            s = "{\"command\": \"target\", \"targets\": {\"" + what + "\": " + temp + "}}"
393
+
394
+        if temp == None:
395
+            temp = 0
396
+
397
+        self.sendPostRequest(path, s)
398
+
399
+    def printerHeatTool(self, temp):
400
+        self.setTemperature("tool0", temp)
401
+
402
+    def printerHeatBed(self, temp):
403
+        self.setTemperature("bed", temp)
404
+
405
+    def printerCooldown(self):
406
+        if self.stateSafetyCheck("cool it down"):
407
+            return
408
+
409
+        self.setTemperature("tool0", 0)
410
+        self.setTemperature("bed", 0)

+ 28
- 43
src/CamWindow.py View File

@@ -28,8 +28,7 @@ class CamWindow(QWidget):
28 28
         self.manager.finished.connect(self.handleResponse)
29 29
         self.parent = parent
30 30
         self.printer = printer
31
-        self.host = self.printer[0]
32
-        self.url = "http://" + self.host + ":8080/?action=snapshot"
31
+        self.url = "http://" + self.printer.host + ":8080/?action=snapshot"
33 32
 
34 33
         self.setWindowTitle(parent.name + " Webcam Stream")
35 34
         self.setWindowIcon(parent.icon)
@@ -69,7 +68,7 @@ class CamWindow(QWidget):
69 68
         box.addWidget(self.statusLabel, 0)
70 69
         box.setAlignment(self.statusLabel, Qt.AlignHCenter)
71 70
 
72
-        self.method = self.parent.getMethod(self.printer[0], self.printer[1])
71
+        self.method = self.printer.api.getMethod()
73 72
         if self.method != "unknown":
74 73
             controls_power = QHBoxLayout()
75 74
             box.addLayout(controls_power, 0)
@@ -158,72 +157,58 @@ class CamWindow(QWidget):
158 157
         self.loadStatus()
159 158
 
160 159
     def pauseResume(self):
161
-        self.parent.printerPauseResume(self.printer)
160
+        self.printer.api.callPauseResume()
162 161
 
163 162
     def cancelJob(self):
164
-        self.parent.printerJobCancel(self.printer)
163
+        self.printer.api.callJobCancel()
165 164
 
166 165
     def moveXP(self):
167
-        self.parent.printerMoveAction(self.printer, "x", int(self.parent.jogMoveLength), True)
166
+        self.printer.api.callMove("x", int(self.parent.jogMoveLength), True)
168 167
 
169 168
     def moveXM(self):
170
-        self.parent.printerMoveAction(self.printer, "x", -1 * int(self.parent.jogMoveLength), True)
169
+        self.printer.api.callMove("x", -1 * int(self.parent.jogMoveLength), True)
171 170
 
172 171
     def moveYP(self):
173
-        self.parent.printerMoveAction(self.printer, "y", int(self.parent.jogMoveLength), True)
172
+        self.printer.api.callMove("y", int(self.parent.jogMoveLength), True)
174 173
 
175 174
     def moveYM(self):
176
-        self.parent.printerMoveAction(self.printer, "y", -1 * int(self.parent.jogMoveLength), True)
175
+        self.printer.api.callMove("y", -1 * int(self.parent.jogMoveLength), True)
177 176
 
178 177
     def moveZP(self):
179
-        self.parent.printerMoveAction(self.printer, "z", int(self.parent.jogMoveLength), True)
178
+        self.printer.api.callMove("z", int(self.parent.jogMoveLength), True)
180 179
 
181 180
     def moveZM(self):
182
-        self.parent.printerMoveAction(self.printer, "z", -1 * int(self.parent.jogMoveLength), True)
181
+        self.printer.api.callMove("z", -1 * int(self.parent.jogMoveLength), True)
183 182
 
184 183
     def homeX(self):
185
-        self.parent.printerHomingAction(self.printer, "x")
184
+        self.printer.api.callHoming("x")
186 185
 
187 186
     def homeY(self):
188
-        self.parent.printerHomingAction(self.printer, "y")
187
+        self.printer.api.callHoming("y")
189 188
 
190 189
     def homeZ(self):
191
-        self.parent.printerHomingAction(self.printer, "z")
190
+        self.printer.api.callHoming("z")
192 191
 
193 192
     def homeAll(self):
194
-        self.parent.printerHomingAction(self.printer, "xyz")
193
+        self.printer.api.callHoming("xyz")
195 194
 
196 195
     def turnOn(self):
197
-        if self.method == "psucontrol":
198
-            self.parent.printerOnAction(self.printer)
199
-        elif self.method == "system":
200
-            cmds = self.parent.getSystemCommands(self.printer[0], self.printer[1])
201
-            for cmd in cmds:
202
-                if "on" in cmd:
203
-                    self.parent.setSystemCommand(self.printer[0], self.printer[1], cmd)
204
-                    break
196
+        self.printer.api.turnOn()
205 197
 
206 198
     def turnOff(self):
207
-        if self.method == "psucontrol":
208
-            self.parent.printerOffAction(self.printer)
209
-        elif self.method == "system":
210
-            cmds = self.parent.getSystemCommands(self.printer[0], self.printer[1])
211
-            for cmd in cmds:
212
-                if "off" in cmd:
213
-                    self.parent.setSystemCommand(self.printer[0], self.printer[1], cmd)
214
-                    break
199
+        self.printer.api.turnOff()
215 200
 
216 201
     def cooldown(self):
217
-        self.parent.printerCooldown(self.printer)
202
+        self.printer.api.printerCooldown(self.printer)
218 203
 
219 204
     def preheatTool(self):
220
-        self.parent.printerHeatTool(self.printer)
205
+        self.printer.api.printerHeatTool(self.printer.tempTool)
221 206
 
222 207
     def preheatBed(self):
223
-        self.parent.printerHeatBed(self.printer)
208
+        self.printer.api.printerHeatBed(self.printer.tempBed)
224 209
 
225 210
     def getHost(self):
226
-        return self.host
211
+        return self.printer.host
227 212
 
228 213
     def sliderChanged(self):
229 214
         self.slideLabel.setText(str(self.slider.value() * self.sliderFactor) + "ms")
@@ -248,19 +233,19 @@ class CamWindow(QWidget):
248 233
 
249 234
     def loadStatus(self):
250 235
         s = "Status: "
251
-        t = self.parent.getTemperatureString(self.host, self.printer[1])
236
+        t = self.printer.api.getTemperatureString()
252 237
         if len(t) > 0:
253 238
             s += t
254 239
         else:
255 240
             s += "Unknown"
256 241
 
257
-        progress = self.parent.getProgress(self.host, self.printer[1])
258
-        if ("completion" in progress) and ("printTime" in progress) and ("printTimeLeft" in progress) and (progress["completion"] != None) and (progress["printTime"] != None) and (progress["printTimeLeft"] != None):
259
-            s += " - %.1f%%" % progress["completion"]
260
-            s += " - runtime "
261
-            s += time.strftime("%H:%M:%S", time.gmtime(progress["printTime"]))
262
-            s += " - "
263
-            s += time.strftime("%H:%M:%S", time.gmtime(progress["printTimeLeft"])) + " left"
242
+        s += " - "
243
+
244
+        p = self.printer.api.getProgressString()
245
+        if len(p) > 0:
246
+            s += p
247
+        else:
248
+            s += "Unknown"
264 249
 
265 250
         self.statusLabel.setText(s)
266 251
         self.scheduleLoadStatus()

+ 55
- 383
src/OctoTray.py View File

@@ -6,13 +6,7 @@
6 6
 #
7 7
 # Main application logic.
8 8
 
9
-import json
10 9
 import sys
11
-import time
12
-import urllib.parse
13
-import urllib.request
14
-import operator
15
-import socket
16 10
 from os import path
17 11
 from PyQt5 import QtNetwork
18 12
 from PyQt5.QtWidgets import QSystemTrayIcon, QAction, QMenu, QMessageBox, QDesktopWidget
@@ -21,11 +15,17 @@ from PyQt5.QtCore import QCoreApplication, QSettings, QUrl
21 15
 from CamWindow import CamWindow
22 16
 from SettingsWindow import SettingsWindow
23 17
 from MainWindow import MainWindow
18
+from APIOctoprint import APIOctoprint
19
+
20
+class Printer(object):
21
+    # field 'api' for actual I/O
22
+    # field 'host' and 'key' for credentials
23
+    pass
24 24
 
25 25
 class OctoTray():
26 26
     name = "OctoTray"
27 27
     vendor = "xythobuz"
28
-    version = "0.4"
28
+    version = "0.5"
29 29
 
30 30
     iconName = "octotray_icon.png"
31 31
     iconPaths = [
@@ -39,16 +39,9 @@ class OctoTray():
39 39
 
40 40
     networkTimeout = 2.0 # in s
41 41
 
42
-    # list of lists, inner lists contain printer data:
43
-    # first elements as in SettingsWindow.columns
44
-    # 0=host 1=key 2=tool-preheat 3=bed-preheat
45
-    # rest used for system-commands, menu, actions
42
+    # list of Printer objects
46 43
     printers = []
47 44
 
48
-    statesWithWarning = [
49
-        "Printing", "Pausing", "Paused"
50
-    ]
51
-
52 45
     camWindows = []
53 46
     settingsWindow = None
54 47
 
@@ -67,90 +60,81 @@ class OctoTray():
67 60
 
68 61
         unknownCount = 0
69 62
         for p in self.printers:
70
-            method = self.getMethod(p[0], p[1])
71
-            print("Printer " + p[0] + " has method " + method)
72
-            if method == "unknown":
73
-                unknownCount += 1
63
+            p.api = APIOctoprint(self, p.host, p.key)
64
+            p.menus = []
65
+
66
+            commands = p.api.getAvailableCommands()
74 67
 
75
-                action = QAction(p[0])
68
+            # don't populate menu when no methods are available
69
+            if len(commands) == 0:
70
+                unknownCount += 1
71
+                action = QAction(p.host)
76 72
                 action.setEnabled(False)
77
-                p.append(action)
73
+                p.menus.append(action)
78 74
                 self.menu.addAction(action)
79
-
80 75
                 continue
81 76
 
82
-            commands = self.getSystemCommands(p[0], p[1])
83
-            p.append(commands)
84
-
85
-            menu = QMenu(self.getName(p[0], p[1]))
86
-            p.append(menu)
77
+            # top level menu for this printer
78
+            menu = QMenu(p.api.getName())
79
+            p.menus.append(menu)
87 80
             self.menu.addMenu(menu)
88 81
 
89
-            if method == "psucontrol":
90
-                action = QAction("Turn On PSU")
91
-                action.triggered.connect(lambda chk, x=p: self.printerOnAction(x))
92
-                p.append(action)
93
-                menu.addAction(action)
94
-
95
-                action = QAction("Turn Off PSU")
96
-                action.triggered.connect(lambda chk, x=p: self.printerOffAction(x))
97
-                p.append(action)
98
-                menu.addAction(action)
99
-
100
-            for i in range(0, len(commands)):
101
-                action = QAction(commands[i].title())
102
-                action.triggered.connect(lambda chk, x=p, y=i: self.printerSystemCommandAction(x, y))
103
-                p.append(action)
82
+            # create action for all available commands
83
+            for cmd in commands:
84
+                name, func = cmd
85
+                action = QAction(name)
86
+                action.triggered.connect(lambda chk, p=p, n=name, f=func: p.api.f(n))
87
+                p.menus.append(action)
104 88
                 menu.addAction(action)
105 89
 
106
-            if (p[2] != None) or (p[3] != None):
90
+            if (p.tempTool != None) or (p.tempBed != None):
107 91
                 menu.addSeparator()
108 92
 
109
-            if p[2] != None:
93
+            if p.tempTool != None:
110 94
                 action = QAction("Preheat Tool")
111
-                action.triggered.connect(lambda chk, x=p: self.printerHeatTool(x))
112
-                p.append(action)
95
+                action.triggered.connect(lambda chk, p=p: p.api.printerHeatTool(p.tempTool))
96
+                p.menus.append(action)
113 97
                 menu.addAction(action)
114 98
 
115
-            if p[3] != None:
99
+            if p.tempBed != None:
116 100
                 action = QAction("Preheat Bed")
117
-                action.triggered.connect(lambda chk, x=p: self.printerHeatBed(x))
118
-                p.append(action)
101
+                action.triggered.connect(lambda chk, p=p: p.api.printerHeatBed(p.tempBed))
102
+                p.menus.append(action)
119 103
                 menu.addAction(action)
120 104
 
121
-            if (p[2] != None) or (p[3] != None):
105
+            if (p.tempTool != None) or (p.tempBed != None):
122 106
                 action = QAction("Cooldown")
123
-                action.triggered.connect(lambda chk, x=p: self.printerCooldown(x))
124
-                p.append(action)
107
+                action.triggered.connect(lambda chk, p=p: p.api.printerCooldown())
108
+                p.menus.append(action)
125 109
                 menu.addAction(action)
126 110
 
127 111
             menu.addSeparator()
128 112
 
129 113
             fileMenu = QMenu("Recent Files")
130
-            p.append(fileMenu)
114
+            p.menus.append(fileMenu)
131 115
             menu.addMenu(fileMenu)
132 116
 
133
-            files = self.getRecentFiles(p[0], p[1], 10)
117
+            files = p.api.getRecentFiles(10)
134 118
             for f in files:
135 119
                 fileName, filePath = f
136 120
                 action = QAction(fileName)
137
-                action.triggered.connect(lambda chk, x=p, y=filePath: self.printerFilePrint(x, y))
138
-                p.append(action)
121
+                action.triggered.connect(lambda chk, p=p, f=filePath: p.api.printFile(f))
122
+                p.menus.append(action)
139 123
                 fileMenu.addAction(action)
140 124
 
141 125
             action = QAction("Get Status")
142
-            action.triggered.connect(lambda chk, x=p: self.printerStatusAction(x))
143
-            p.append(action)
126
+            action.triggered.connect(lambda chk, p=p: p.api.statusDialog())
127
+            p.menus.append(action)
144 128
             menu.addAction(action)
145 129
 
146 130
             action = QAction("Show Webcam")
147 131
             action.triggered.connect(lambda chk, x=p: self.printerWebcamAction(x))
148
-            p.append(action)
132
+            p.menus.append(action)
149 133
             menu.addAction(action)
150 134
 
151 135
             action = QAction("Open Web UI")
152 136
             action.triggered.connect(lambda chk, x=p: self.printerWebAction(x))
153
-            p.append(action)
137
+            p.menus.append(action)
154 138
             menu.addAction(action)
155 139
 
156 140
         self.menu.addSeparator()
@@ -220,11 +204,11 @@ class OctoTray():
220 204
         l = settings.beginReadArray("printers")
221 205
         for i in range(0, l):
222 206
             settings.setArrayIndex(i)
223
-            p = []
224
-            p.append(settings.value("host"))
225
-            p.append(settings.value("key"))
226
-            p.append(settings.value("tool_preheat"))
227
-            p.append(settings.value("bed_preheat"))
207
+            p = Printer()
208
+            p.host = settings.value("host")
209
+            p.key = settings.value("key")
210
+            p.tempTool = settings.value("tool_preheat")
211
+            p.tempBed = settings.value("bed_preheat")
228 212
             printers.append(p)
229 213
         settings.endArray()
230 214
         return printers
@@ -240,10 +224,10 @@ class OctoTray():
240 224
         for i in range(0, len(printers)):
241 225
             p = printers[i]
242 226
             settings.setArrayIndex(i)
243
-            settings.setValue("host", p[0])
244
-            settings.setValue("key", p[1])
245
-            settings.setValue("tool_preheat", p[2])
246
-            settings.setValue("bed_preheat", p[3])
227
+            settings.setValue("host", p.host)
228
+            settings.setValue("key", p.key)
229
+            settings.setValue("tool_preheat", p.tempTool)
230
+            settings.setValue("bed_preheat", p.tempBed)
247 231
         settings.endArray()
248 232
         del settings
249 233
 
@@ -279,327 +263,15 @@ class OctoTray():
279 263
         else:
280 264
             return False
281 265
 
282
-    def sendRequest(self, host, headers, path, content = None):
283
-        url = "http://" + host + "/api/" + path
284
-        if content == None:
285
-            request = urllib.request.Request(url, None, headers)
286
-        else:
287
-            data = content.encode('ascii')
288
-            request = urllib.request.Request(url, data, headers)
289
-
290
-        try:
291
-            with urllib.request.urlopen(request, None, self.networkTimeout) as response:
292
-                text = response.read()
293
-                return text
294
-        except (urllib.error.URLError, urllib.error.HTTPError) as error:
295
-            print("Error requesting URL \"" + url + "\": \"" + str(error) + "\"")
296
-            return "error"
297
-        except socket.timeout:
298
-            print("Timeout waiting for response to \"" + url + "\"")
299
-            return "timeout"
300
-
301
-    def sendPostRequest(self, host, key, path, content):
302
-        headers = {
303
-            "Content-Type": "application/json",
304
-            "X-Api-Key": key
305
-        }
306
-        return self.sendRequest(host, headers, path, content)
307
-
308
-    def sendGetRequest(self, host, key, path):
309
-        headers = {
310
-            "X-Api-Key": key
311
-        }
312
-        return self.sendRequest(host, headers, path)
313
-
314
-    def getTemperatureIsSafe(self, host, key):
315
-        r = self.sendGetRequest(host, key, "printer")
316
-        try:
317
-            rd = json.loads(r)
318
-
319
-            if "temperature" in rd:
320
-                if ("tool0" in rd["temperature"]) and ("actual" in rd["temperature"]["tool0"]):
321
-                    if rd["temperature"]["tool0"]["actual"] > 50.0:
322
-                        return False
323
-
324
-                if ("tool1" in rd["temperature"]) and ("actual" in rd["temperature"]["tool1"]):
325
-                    if rd["temperature"]["tool1"]["actual"] > 50.0:
326
-                        return False
327
-        except json.JSONDecodeError:
328
-            pass
329
-        return True
330
-
331
-    def getTemperatureString(self, host, key):
332
-        r = self.sendGetRequest(host, key, "printer")
333
-        s = ""
334
-        try:
335
-            rd = json.loads(r)
336
-
337
-            if ("state" in rd) and ("text" in rd["state"]):
338
-                s += rd["state"]["text"]
339
-                if "temperature" in rd:
340
-                    s += " - "
341
-
342
-            if "temperature" in rd:
343
-                if "bed" in rd["temperature"]:
344
-                    if "actual" in rd["temperature"]["bed"]:
345
-                        s += "B"
346
-                        s += "%.1f" % rd["temperature"]["bed"]["actual"]
347
-                        if "target" in rd["temperature"]["bed"]:
348
-                            s += "/"
349
-                            s += "%.1f" % rd["temperature"]["bed"]["target"]
350
-                        s += " "
351
-
352
-                if "tool0" in rd["temperature"]:
353
-                    if "actual" in rd["temperature"]["tool0"]:
354
-                        s += "T"
355
-                        s += "%.1f" % rd["temperature"]["tool0"]["actual"]
356
-                        if "target" in rd["temperature"]["tool0"]:
357
-                            s += "/"
358
-                            s += "%.1f" % rd["temperature"]["tool0"]["target"]
359
-                        s += " "
360
-
361
-                if "tool1" in rd["temperature"]:
362
-                    if "actual" in rd["temperature"]["tool1"]:
363
-                        s += "T"
364
-                        s += "%.1f" % rd["temperature"]["tool1"]["actual"]
365
-                        if "target" in rd["temperature"]["tool1"]:
366
-                            s += "/"
367
-                            s += "%.1f" % rd["temperature"]["tool1"]["target"]
368
-                        s += " "
369
-        except json.JSONDecodeError:
370
-            pass
371
-        return s.strip()
372
-
373
-    def getState(self, host, key):
374
-        r = self.sendGetRequest(host, key, "job")
375
-        try:
376
-            rd = json.loads(r)
377
-            if "state" in rd:
378
-                return rd["state"]
379
-        except json.JSONDecodeError:
380
-            pass
381
-        return "Unknown"
382
-
383
-    def getProgress(self, host, key):
384
-        r = self.sendGetRequest(host, key, "job")
385
-        try:
386
-            rd = json.loads(r)
387
-            if "progress" in rd:
388
-                return rd["progress"]
389
-        except json.JSONDecodeError:
390
-            pass
391
-        return "Unknown"
392
-
393
-    def getName(self, host, key):
394
-        r = self.sendGetRequest(host, key, "printerprofiles")
395
-        try:
396
-            rd = json.loads(r)
397
-            if "profiles" in rd:
398
-                p = next(iter(rd["profiles"]))
399
-                if "name" in rd["profiles"][p]:
400
-                    return rd["profiles"][p]["name"]
401
-        except json.JSONDecodeError:
402
-            pass
403
-        return host
404
-
405
-    def getRecentFiles(self, host, key, count):
406
-        r = self.sendGetRequest(host, key, "files?recursive=true")
407
-        files = []
408
-        try:
409
-            rd = json.loads(r)
410
-            if "files" in rd:
411
-                t = [f for f in rd["files"] if "date" in f]
412
-                fs = sorted(t, key=operator.itemgetter("date"), reverse=True)
413
-                for f in fs[:count]:
414
-                    files.append((f["name"], f["origin"] + "/" + f["path"]))
415
-        except json.JSONDecodeError:
416
-            pass
417
-        return files
418
-
419
-    def getMethod(self, host, key):
420
-        r = self.sendGetRequest(host, key, "plugin/psucontrol")
421
-        if r == "timeout":
422
-            return "unknown"
423
-
424
-        try:
425
-            rd = json.loads(r)
426
-            if "isPSUOn" in rd:
427
-                return "psucontrol"
428
-        except json.JSONDecodeError:
429
-            pass
430
-
431
-        r = self.sendGetRequest(host, key, "system/commands/custom")
432
-        if r == "timeout":
433
-            return "unknown"
434
-
435
-        try:
436
-            rd = json.loads(r)
437
-            for c in rd:
438
-                if "action" in c:
439
-                    # we have some custom commands and no psucontrol
440
-                    # so lets try to use that instead of skipping
441
-                    # the printer completely with 'unknown'
442
-                    return "system"
443
-        except json.JSONDecodeError:
444
-            pass
445
-
446
-        return "unknown"
447
-
448
-    def getSystemCommands(self, host, key):
449
-        l = []
450
-        r = self.sendGetRequest(host, key, "system/commands/custom")
451
-        try:
452
-            rd = json.loads(r)
453
-
454
-            if len(rd) > 0:
455
-                print("system commands available for " + host + ":")
456
-
457
-            for c in rd:
458
-                if "action" in c:
459
-                    print("  - " + c["action"])
460
-                    l.append(c["action"])
461
-        except json.JSONDecodeError:
462
-            pass
463
-        return l
464
-
465
-    def setPSUControl(self, host, key, state):
466
-        cmd = "turnPSUOff"
467
-        if state:
468
-            cmd = "turnPSUOn"
469
-        return self.sendPostRequest(host, key, "plugin/psucontrol", '{ "command":"' + cmd + '" }')
470
-
471
-    def setSystemCommand(self, host, key, cmd):
472
-        cmd = urllib.parse.quote(cmd)
473
-        return self.sendPostRequest(host, key, "system/commands/custom/" + cmd, '')
474
-
475 266
     def exit(self):
476 267
         QCoreApplication.quit()
477 268
 
478
-    def printerSystemCommandAction(self, item, index):
479
-        if "off" in item[2][index].lower():
480
-            state = self.getState(item[0], item[1])
481
-            if state in self.statesWithWarning:
482
-                if self.showDialog("OctoTray Warning", "The printer seems to be running currently!", "Do you really want to run '" + item[2][index] + "'?", True, True) == False:
483
-                    return
484
-
485
-            safe = self.getTemperatureIsSafe(item[0], item[1])
486
-            if safe == False:
487
-                if self.showDialog("OctoTray Warning", "The printer seems to still be hot!", "Do you really want to turn it off?", True, True) == False:
488
-                    return
489
-
490
-        self.setSystemCommand(item[0], item[1], item[2][index])
491
-
492
-    def printerOnAction(self, item):
493
-        self.setPSUControl(item[0], item[1], True)
494
-
495
-    def printerOffAction(self, item):
496
-        state = self.getState(item[0], item[1])
497
-        if state in self.statesWithWarning:
498
-            if self.showDialog("OctoTray Warning", "The printer seems to be running currently!", "Do you really want to turn it off?", True, True) == False:
499
-                return
500
-
501
-        safe = self.getTemperatureIsSafe(item[0], item[1])
502
-        if safe == False:
503
-            if self.showDialog("OctoTray Warning", "The printer seems to still be hot!", "Do you really want to turn it off?", True, True) == False:
504
-                return
505
-
506
-        self.setPSUControl(item[0], item[1], False)
507
-
508
-    def printerHomingAction(self, item, axes = "xyz"):
509
-        state = self.getState(item[0], item[1])
510
-        if state in self.statesWithWarning:
511
-            if self.showDialog("OctoTray Warning", "The printer seems to be running currently!", "Do you really want to home it?", True, True) == False:
512
-                return
513
-
514
-        axes_string = ''
515
-        for i in range(0, len(axes)):
516
-            axes_string += '"' + str(axes[i]) + '"'
517
-            if i < (len(axes) - 1):
518
-                axes_string += ', '
519
-
520
-        self.sendPostRequest(item[0], item[1], "printer/printhead", '{ "command": "home", "axes": [' + axes_string + '] }')
521
-
522
-    def printerMoveAction(self, printer, axis, dist, relative = True):
523
-        state = self.getState(printer[0], printer[1])
524
-        if state in self.statesWithWarning:
525
-            if self.showDialog("OctoTray Warning", "The printer seems to be running currently!", "Do you really want to move it?", True, True) == False:
526
-                return
527
-
528
-        absolute = ''
529
-        if relative == False:
530
-            absolute = ', "absolute": true'
531
-
532
-        self.sendPostRequest(printer[0], printer[1], "printer/printhead", '{ "command": "jog", "' + str(axis) + '": ' + str(dist) + ', "speed": ' + str(self.jogMoveSpeed) + absolute + ' }')
533
-
534
-    def printerPauseResume(self, printer):
535
-        state = self.getState(printer[0], printer[1])
536
-        if state in self.statesWithWarning:
537
-            if self.showDialog("OctoTray Warning", "The printer seems to be running currently!", "Do you really want to pause/resume?", True, True) == False:
538
-                return
539
-        self.sendPostRequest(printer[0], printer[1], "job", '{ "command": "pause", "action": "toggle" }')
540
-
541
-    def printerJobCancel(self, printer):
542
-        state = self.getState(printer[0], printer[1])
543
-        if state in self.statesWithWarning:
544
-            if self.showDialog("OctoTray Warning", "The printer seems to be running currently!", "Do you really want to cancel?", True, True) == False:
545
-                return
546
-        self.sendPostRequest(printer[0], printer[1], "job", '{ "command": "cancel" }')
547
-
548 269
     def printerWebAction(self, item):
549
-        self.openBrowser(item[0])
550
-
551
-    def printerStatusAction(self, item):
552
-        progress = self.getProgress(item[0], item[1])
553
-        s = item[0] + "\n"
554
-        warning = False
555
-        if ("completion" in progress) and ("printTime" in progress) and ("printTimeLeft" in progress) and (progress["completion"] != None) and (progress["printTime"] != None) and (progress["printTimeLeft"] != None):
556
-            s += "%.1f%% Completion\n" % progress["completion"]
557
-            s += "Printing since " + time.strftime("%H:%M:%S", time.gmtime(progress["printTime"])) + "\n"
558
-            s += time.strftime("%H:%M:%S", time.gmtime(progress["printTimeLeft"])) + " left"
559
-        elif ("completion" in progress) and ("printTime" in progress) and ("printTimeLeft" in progress):
560
-            s += "No job is currently running"
561
-        else:
562
-            s += "Could not read printer status!"
563
-            warning = True
564
-        t = self.getTemperatureString(item[0], item[1])
565
-        if len(t) > 0:
566
-            s += "\n" + t
567
-        self.showDialog("OctoTray Status", s, None, False, warning)
568
-
569
-    def printerFilePrint(self, item, path):
570
-        self.sendPostRequest(item[0], item[1], "files/" + path, '{ "command": "select", "print": true }')
571
-
572
-    def setTemperature(self, host, key, what, temp):
573
-        path = "printer/bed"
574
-        s = "{\"command\": \"target\", \"target\": " + temp + "}"
575
-
576
-        if "tool" in what:
577
-            path = "printer/tool"
578
-            s = "{\"command\": \"target\", \"targets\": {\"" + what + "\": " + temp + "}}"
579
-
580
-        if temp == None:
581
-            temp = 0
582
-
583
-        self.sendPostRequest(host, key, path, s)
584
-
585
-    def printerHeatTool(self, p):
586
-        self.setTemperature(p[0], p[1], "tool0", p[2])
587
-
588
-    def printerHeatBed(self, p):
589
-        self.setTemperature(p[0], p[1], "bed", p[3])
590
-
591
-    def printerCooldown(self, p):
592
-        state = self.getState(p[0], p[1])
593
-        if state in self.statesWithWarning:
594
-            if self.showDialog("OctoTray Warning", "The printer seems to be running currently!", "Do you really want to turn it off?", True, True) == False:
595
-                return
596
-
597
-        self.setTemperature(p[0], p[1], "tool0", 0)
598
-        self.setTemperature(p[0], p[1], "bed", 0)
270
+        self.openBrowser(item.host)
599 271
 
600 272
     def printerWebcamAction(self, item):
601 273
         for cw in self.camWindows:
602
-            if cw.getHost() == item[0]:
274
+            if cw.getHost() == item.host:
603 275
                 cw.show()
604 276
                 cw.activateWindow()
605 277
                 return

+ 59
- 23
src/SettingsWindow.py View File

@@ -12,6 +12,9 @@ from PyQt5.QtWidgets import QWidget, QLabel, QVBoxLayout, QHBoxLayout, QTableWid
12 12
 from PyQt5.QtGui import QFontDatabase, QIntValidator
13 13
 from PyQt5.QtCore import Qt
14 14
 
15
+class Printer(object):
16
+    pass
17
+
15 18
 class SettingsWindow(QWidget):
16 19
     columns = [ "Hostname", "API Key", "Tool Preheat", "Bed Preheat" ]
17 20
     presets = [ "octopi.local", "000000000_API_KEY_HERE_000000000", "0", "0" ]
@@ -76,16 +79,27 @@ class SettingsWindow(QWidget):
76 79
 
77 80
         for i in range(0, self.rows):
78 81
             p = printers[i]
79
-            for j in range(0, len(self.columns)):
80
-                text = p[j]
81
-                if (j >= 2) and (j <= 3) and (text == None):
82
-                    text = "0"
83
-                item = QTableWidgetItem(text)
84
-                self.table.setItem(i, j, item)
85
-                if j == 1:
86
-                    font = item.font()
87
-                    font.setFamily(QFontDatabase.systemFont(QFontDatabase.FixedFont).family())
88
-                    item.setFont(font)
82
+
83
+            item = QTableWidgetItem(p.host)
84
+            self.table.setItem(i, 0, item)
85
+
86
+            item = QTableWidgetItem(p.key)
87
+            self.table.setItem(i, 1, item)
88
+            font = item.font()
89
+            font.setFamily(QFontDatabase.systemFont(QFontDatabase.FixedFont).family())
90
+            item.setFont(font)
91
+
92
+            if p.tempTool == None:
93
+                item = QTableWidgetItem("0")
94
+            else:
95
+                item = QTableWidgetItem(p.tempTool)
96
+            self.table.setItem(i, 2, item)
97
+
98
+            if p.tempBed == None:
99
+                item = QTableWidgetItem("0")
100
+            else:
101
+                item = QTableWidgetItem(p.tempBed)
102
+            self.table.setItem(i, 3, item)
89 103
 
90 104
         buttons2 = QHBoxLayout()
91 105
         box.addLayout(buttons2, 0)
@@ -113,26 +127,33 @@ class SettingsWindow(QWidget):
113 127
     def tableToList(self):
114 128
         printers = []
115 129
         for i in range(0, self.rows):
116
-            p = []
117
-            for j in range(0, len(self.columns)):
118
-                text = self.table.item(i, j).text()
119
-                if (j >= 2) and (j <= 3) and (text == "0"):
120
-                    text = None
121
-                p.append(text)
130
+            p = Printer()
131
+
132
+            p.host = self.table.item(i, 0).text()
133
+            p.key = self.table.item(i, 1).text()
134
+            p.tempTool = self.table.item(i, 2).text()
135
+            p.tempBed = self.table.item(i, 3).text()
136
+
137
+            if p.tempTool == "0":
138
+                p.tempTool = None
139
+
140
+            if p.tempBed == "0":
141
+                p.tempBed = None
142
+
122 143
             printers.append(p)
123 144
         return printers
124 145
 
125 146
     def settingsValid(self, printers):
126 147
         for p in printers:
127
-            # p[0] needs to be valid hostname or IP
148
+            # p.host needs to be valid hostname or IP
128 149
             # TODO
129 150
 
130
-            # p[1] needs to be valid API key (hexadecimal, 32 chars)
131
-            if (len(p[1]) != 32) or not all(c in string.hexdigits for c in p[1]):
151
+            # p.key needs to be valid API key (hexadecimal, 32 chars)
152
+            if (len(p.key) != 32) or not all(c in string.hexdigits for c in p.key):
132 153
                 return (False, "API Key not 32-digit hexadecimal")
133 154
 
134
-            # p[2] and p[3] need to be integer temperatures (0...999)
135
-            for s in [ p[2], p[3] ]:
155
+            # p.tempTool and p.tempBed need to be integer temperatures (0...999)
156
+            for s in [ p.tempTool, p.tempBed ]:
136 157
                 if s == None:
137 158
                     s = "0"
138 159
                 if (len(s) < 1) or (len(s) > 3) or not all(c in string.digits for c in s):
@@ -148,8 +169,23 @@ class SettingsWindow(QWidget):
148 169
 
149 170
         return (True, "")
150 171
 
172
+    def printerDiffers(self, a, b):
173
+        if (a.host != b.host) or (a.key != b.key) or (a.tempTool != b.tempTool) or (a.tempBed != b.tempBed):
174
+            return True
175
+        return False
176
+
177
+    def printersDiffer(self, a, b):
178
+        if (len(a) != len(b)):
179
+            return True
180
+
181
+        for i in range(0, len(a)):
182
+            if self.printerDiffers(a[i], b[i]):
183
+                return True
184
+
185
+        return False
186
+
151 187
     def closeEvent(self, event):
152
-        oldPrinters = [item[0:len(self.columns)] for item in self.parent.printers]
188
+        oldPrinters = self.parent.printers
153 189
         newPrinters = self.tableToList()
154 190
 
155 191
         valid, errorText = self.settingsValid(newPrinters)
@@ -165,7 +201,7 @@ class SettingsWindow(QWidget):
165 201
         js = int(self.jogSpeed.text())
166 202
         jl = int(self.jogLength.text())
167 203
 
168
-        if (oldPrinters != newPrinters) or (js != self.parent.jogMoveSpeed) or (jl != self.parent.jogMoveLength):
204
+        if self.printersDiffer(oldPrinters, newPrinters) or (js != self.parent.jogMoveSpeed) or (jl != self.parent.jogMoveLength):
169 205
             r = self.parent.showDialog(self.parent.name + " Settings Changed", "Do you want to save the new configuration?", "This will restart the application!", True, False, False)
170 206
             if r == True:
171 207
                 self.parent.jogMoveSpeed = js

Loading…
Cancel
Save