Browse Source

put octoprint api functions in own class

Thomas Buck 2 years 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
 [Desktop Entry]
1
 [Desktop Entry]
2
 Type=Application
2
 Type=Application
3
-Version=0.4
3
+Version=0.5
4
 Name=OctoTray
4
 Name=OctoTray
5
 Comment=Control OctoPrint instances from system tray
5
 Comment=Control OctoPrint instances from system tray
6
 Path=/usr/bin
6
 Path=/usr/bin

+ 2
- 2
dist/PKGBUILD View File

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

+ 1
- 1
dist/setup_mac.py View File

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

+ 410
- 0
src/APIOctoprint.py View File

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
         self.manager.finished.connect(self.handleResponse)
28
         self.manager.finished.connect(self.handleResponse)
29
         self.parent = parent
29
         self.parent = parent
30
         self.printer = printer
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
         self.setWindowTitle(parent.name + " Webcam Stream")
33
         self.setWindowTitle(parent.name + " Webcam Stream")
35
         self.setWindowIcon(parent.icon)
34
         self.setWindowIcon(parent.icon)
69
         box.addWidget(self.statusLabel, 0)
68
         box.addWidget(self.statusLabel, 0)
70
         box.setAlignment(self.statusLabel, Qt.AlignHCenter)
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
         if self.method != "unknown":
72
         if self.method != "unknown":
74
             controls_power = QHBoxLayout()
73
             controls_power = QHBoxLayout()
75
             box.addLayout(controls_power, 0)
74
             box.addLayout(controls_power, 0)
158
         self.loadStatus()
157
         self.loadStatus()
159
 
158
 
160
     def pauseResume(self):
159
     def pauseResume(self):
161
-        self.parent.printerPauseResume(self.printer)
160
+        self.printer.api.callPauseResume()
162
 
161
 
163
     def cancelJob(self):
162
     def cancelJob(self):
164
-        self.parent.printerJobCancel(self.printer)
163
+        self.printer.api.callJobCancel()
165
 
164
 
166
     def moveXP(self):
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
     def moveXM(self):
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
     def moveYP(self):
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
     def moveYM(self):
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
     def moveZP(self):
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
     def moveZM(self):
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
     def homeX(self):
183
     def homeX(self):
185
-        self.parent.printerHomingAction(self.printer, "x")
184
+        self.printer.api.callHoming("x")
186
 
185
 
187
     def homeY(self):
186
     def homeY(self):
188
-        self.parent.printerHomingAction(self.printer, "y")
187
+        self.printer.api.callHoming("y")
189
 
188
 
190
     def homeZ(self):
189
     def homeZ(self):
191
-        self.parent.printerHomingAction(self.printer, "z")
190
+        self.printer.api.callHoming("z")
192
 
191
 
193
     def homeAll(self):
192
     def homeAll(self):
194
-        self.parent.printerHomingAction(self.printer, "xyz")
193
+        self.printer.api.callHoming("xyz")
195
 
194
 
196
     def turnOn(self):
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
     def turnOff(self):
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
     def cooldown(self):
201
     def cooldown(self):
217
-        self.parent.printerCooldown(self.printer)
202
+        self.printer.api.printerCooldown(self.printer)
218
 
203
 
219
     def preheatTool(self):
204
     def preheatTool(self):
220
-        self.parent.printerHeatTool(self.printer)
205
+        self.printer.api.printerHeatTool(self.printer.tempTool)
221
 
206
 
222
     def preheatBed(self):
207
     def preheatBed(self):
223
-        self.parent.printerHeatBed(self.printer)
208
+        self.printer.api.printerHeatBed(self.printer.tempBed)
224
 
209
 
225
     def getHost(self):
210
     def getHost(self):
226
-        return self.host
211
+        return self.printer.host
227
 
212
 
228
     def sliderChanged(self):
213
     def sliderChanged(self):
229
         self.slideLabel.setText(str(self.slider.value() * self.sliderFactor) + "ms")
214
         self.slideLabel.setText(str(self.slider.value() * self.sliderFactor) + "ms")
248
 
233
 
249
     def loadStatus(self):
234
     def loadStatus(self):
250
         s = "Status: "
235
         s = "Status: "
251
-        t = self.parent.getTemperatureString(self.host, self.printer[1])
236
+        t = self.printer.api.getTemperatureString()
252
         if len(t) > 0:
237
         if len(t) > 0:
253
             s += t
238
             s += t
254
         else:
239
         else:
255
             s += "Unknown"
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
         self.statusLabel.setText(s)
250
         self.statusLabel.setText(s)
266
         self.scheduleLoadStatus()
251
         self.scheduleLoadStatus()

+ 55
- 383
src/OctoTray.py View File

6
 #
6
 #
7
 # Main application logic.
7
 # Main application logic.
8
 
8
 
9
-import json
10
 import sys
9
 import sys
11
-import time
12
-import urllib.parse
13
-import urllib.request
14
-import operator
15
-import socket
16
 from os import path
10
 from os import path
17
 from PyQt5 import QtNetwork
11
 from PyQt5 import QtNetwork
18
 from PyQt5.QtWidgets import QSystemTrayIcon, QAction, QMenu, QMessageBox, QDesktopWidget
12
 from PyQt5.QtWidgets import QSystemTrayIcon, QAction, QMenu, QMessageBox, QDesktopWidget
21
 from CamWindow import CamWindow
15
 from CamWindow import CamWindow
22
 from SettingsWindow import SettingsWindow
16
 from SettingsWindow import SettingsWindow
23
 from MainWindow import MainWindow
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
 class OctoTray():
25
 class OctoTray():
26
     name = "OctoTray"
26
     name = "OctoTray"
27
     vendor = "xythobuz"
27
     vendor = "xythobuz"
28
-    version = "0.4"
28
+    version = "0.5"
29
 
29
 
30
     iconName = "octotray_icon.png"
30
     iconName = "octotray_icon.png"
31
     iconPaths = [
31
     iconPaths = [
39
 
39
 
40
     networkTimeout = 2.0 # in s
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
     printers = []
43
     printers = []
47
 
44
 
48
-    statesWithWarning = [
49
-        "Printing", "Pausing", "Paused"
50
-    ]
51
-
52
     camWindows = []
45
     camWindows = []
53
     settingsWindow = None
46
     settingsWindow = None
54
 
47
 
67
 
60
 
68
         unknownCount = 0
61
         unknownCount = 0
69
         for p in self.printers:
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
                 action.setEnabled(False)
72
                 action.setEnabled(False)
77
-                p.append(action)
73
+                p.menus.append(action)
78
                 self.menu.addAction(action)
74
                 self.menu.addAction(action)
79
-
80
                 continue
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
             self.menu.addMenu(menu)
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
                 menu.addAction(action)
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
                 menu.addSeparator()
91
                 menu.addSeparator()
108
 
92
 
109
-            if p[2] != None:
93
+            if p.tempTool != None:
110
                 action = QAction("Preheat Tool")
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
                 menu.addAction(action)
97
                 menu.addAction(action)
114
 
98
 
115
-            if p[3] != None:
99
+            if p.tempBed != None:
116
                 action = QAction("Preheat Bed")
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
                 menu.addAction(action)
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
                 action = QAction("Cooldown")
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
                 menu.addAction(action)
109
                 menu.addAction(action)
126
 
110
 
127
             menu.addSeparator()
111
             menu.addSeparator()
128
 
112
 
129
             fileMenu = QMenu("Recent Files")
113
             fileMenu = QMenu("Recent Files")
130
-            p.append(fileMenu)
114
+            p.menus.append(fileMenu)
131
             menu.addMenu(fileMenu)
115
             menu.addMenu(fileMenu)
132
 
116
 
133
-            files = self.getRecentFiles(p[0], p[1], 10)
117
+            files = p.api.getRecentFiles(10)
134
             for f in files:
118
             for f in files:
135
                 fileName, filePath = f
119
                 fileName, filePath = f
136
                 action = QAction(fileName)
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
                 fileMenu.addAction(action)
123
                 fileMenu.addAction(action)
140
 
124
 
141
             action = QAction("Get Status")
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
             menu.addAction(action)
128
             menu.addAction(action)
145
 
129
 
146
             action = QAction("Show Webcam")
130
             action = QAction("Show Webcam")
147
             action.triggered.connect(lambda chk, x=p: self.printerWebcamAction(x))
131
             action.triggered.connect(lambda chk, x=p: self.printerWebcamAction(x))
148
-            p.append(action)
132
+            p.menus.append(action)
149
             menu.addAction(action)
133
             menu.addAction(action)
150
 
134
 
151
             action = QAction("Open Web UI")
135
             action = QAction("Open Web UI")
152
             action.triggered.connect(lambda chk, x=p: self.printerWebAction(x))
136
             action.triggered.connect(lambda chk, x=p: self.printerWebAction(x))
153
-            p.append(action)
137
+            p.menus.append(action)
154
             menu.addAction(action)
138
             menu.addAction(action)
155
 
139
 
156
         self.menu.addSeparator()
140
         self.menu.addSeparator()
220
         l = settings.beginReadArray("printers")
204
         l = settings.beginReadArray("printers")
221
         for i in range(0, l):
205
         for i in range(0, l):
222
             settings.setArrayIndex(i)
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
             printers.append(p)
212
             printers.append(p)
229
         settings.endArray()
213
         settings.endArray()
230
         return printers
214
         return printers
240
         for i in range(0, len(printers)):
224
         for i in range(0, len(printers)):
241
             p = printers[i]
225
             p = printers[i]
242
             settings.setArrayIndex(i)
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
         settings.endArray()
231
         settings.endArray()
248
         del settings
232
         del settings
249
 
233
 
279
         else:
263
         else:
280
             return False
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
     def exit(self):
266
     def exit(self):
476
         QCoreApplication.quit()
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
     def printerWebAction(self, item):
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
     def printerWebcamAction(self, item):
272
     def printerWebcamAction(self, item):
601
         for cw in self.camWindows:
273
         for cw in self.camWindows:
602
-            if cw.getHost() == item[0]:
274
+            if cw.getHost() == item.host:
603
                 cw.show()
275
                 cw.show()
604
                 cw.activateWindow()
276
                 cw.activateWindow()
605
                 return
277
                 return

+ 59
- 23
src/SettingsWindow.py View File

12
 from PyQt5.QtGui import QFontDatabase, QIntValidator
12
 from PyQt5.QtGui import QFontDatabase, QIntValidator
13
 from PyQt5.QtCore import Qt
13
 from PyQt5.QtCore import Qt
14
 
14
 
15
+class Printer(object):
16
+    pass
17
+
15
 class SettingsWindow(QWidget):
18
 class SettingsWindow(QWidget):
16
     columns = [ "Hostname", "API Key", "Tool Preheat", "Bed Preheat" ]
19
     columns = [ "Hostname", "API Key", "Tool Preheat", "Bed Preheat" ]
17
     presets = [ "octopi.local", "000000000_API_KEY_HERE_000000000", "0", "0" ]
20
     presets = [ "octopi.local", "000000000_API_KEY_HERE_000000000", "0", "0" ]
76
 
79
 
77
         for i in range(0, self.rows):
80
         for i in range(0, self.rows):
78
             p = printers[i]
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
         buttons2 = QHBoxLayout()
104
         buttons2 = QHBoxLayout()
91
         box.addLayout(buttons2, 0)
105
         box.addLayout(buttons2, 0)
113
     def tableToList(self):
127
     def tableToList(self):
114
         printers = []
128
         printers = []
115
         for i in range(0, self.rows):
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
             printers.append(p)
143
             printers.append(p)
123
         return printers
144
         return printers
124
 
145
 
125
     def settingsValid(self, printers):
146
     def settingsValid(self, printers):
126
         for p in printers:
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
             # TODO
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
                 return (False, "API Key not 32-digit hexadecimal")
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
                 if s == None:
157
                 if s == None:
137
                     s = "0"
158
                     s = "0"
138
                 if (len(s) < 1) or (len(s) > 3) or not all(c in string.digits for c in s):
159
                 if (len(s) < 1) or (len(s) > 3) or not all(c in string.digits for c in s):
148
 
169
 
149
         return (True, "")
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
     def closeEvent(self, event):
187
     def closeEvent(self, event):
152
-        oldPrinters = [item[0:len(self.columns)] for item in self.parent.printers]
188
+        oldPrinters = self.parent.printers
153
         newPrinters = self.tableToList()
189
         newPrinters = self.tableToList()
154
 
190
 
155
         valid, errorText = self.settingsValid(newPrinters)
191
         valid, errorText = self.settingsValid(newPrinters)
165
         js = int(self.jogSpeed.text())
201
         js = int(self.jogSpeed.text())
166
         jl = int(self.jogLength.text())
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
             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)
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
             if r == True:
206
             if r == True:
171
                 self.parent.jogMoveSpeed = js
207
                 self.parent.jogMoveSpeed = js

Loading…
Cancel
Save