Browse Source

Initial commit

Thomas Buck 8 years ago
commit
343f245097
2 changed files with 708 additions and 0 deletions
  1. 1
    0
      .gitignore
  2. 707
    0
      bed-leveling.py

+ 1
- 0
.gitignore View File

@@ -0,0 +1 @@
1
+.DS_Store

+ 707
- 0
bed-leveling.py View File

@@ -0,0 +1,707 @@
1
+#!/usr/bin/env python
2
+
3
+#
4
+# bed-leveling.py
5
+#
6
+# Written in June 2016 by:
7
+# Thomas Buck <xythobuz@xythobuz.de>
8
+#
9
+# Manual Mesh Bed Leveling GUI utility for 3D printers with Marlin Firmware.
10
+# At the time of me writing this program, Marlin had a bug that caused the
11
+# manual mesh bed leveling process to not work using the connected LCD display,
12
+# leaving G-Codes sent over the serial port as the only option. Because manually
13
+# inserting these in a terminal program proved to be very tedious, and none of the
14
+# G-Code senders I've tested had the features I wanted (mainly easily configurable
15
+# step sizes), I've wrote this simple little program.
16
+#
17
+# After starting the program, select the serial port connected to your printer,
18
+# the correct baudrate for communicating with it and press Connect.
19
+# The X, Y and Z value displayed are periodically polled from the printer and
20
+# are not very accurate. Zc is what is manipulated using the up- and down-arrow
21
+# buttons or keys. The value shown there will be more accurate.
22
+#
23
+# Before starting with the leveling procedure, the current Mesh info has to be
24
+# polled from the printer. This will happen automatically after connecting.
25
+# When ready, press Start, then adjust the Z-height for each measuring point
26
+# and press Next or the right-arrow key to move on. After you're finished,
27
+# you should store the new mesh data in the EEPROM using the Save button.
28
+#
29
+# Dependencies:
30
+#  - pySerial (>= v3.0)
31
+#  - wxPython
32
+#
33
+# This has been developed and tested on a Mac OS X 10.10.5 machine
34
+# with Python 2.7 and dependencies installed with MacPorts,
35
+# connected to a modified Fabrikator Mini V1.5 with Marlin 1.1.0-RC6.
36
+#
37
+
38
+import wx
39
+import serial
40
+import serial.threaded
41
+import serial.tools.list_ports
42
+import re
43
+
44
+POLL_SERIAL_PORTS_INTERVALL = 4000
45
+POLL_COORDINATES_INTERVALL = 2500
46
+POLL_TEMPERATURE_INTERVALL = 3000
47
+MESH_DATA_RETRY_TIMEOUT = 2000
48
+BUSY_PROCESSING_STEPS = 6;
49
+AVAILABLE_BAUD_RATES = ["2400", "9600", "19200", "38400", "57600", "115200", "250000"]
50
+DEFAULT_BAUD_RATE = "115200"
51
+DEFAULT_SERIAL_PORTS = ["/dev/cu.usbserial-AI02LQH7", "/dev/cu.SLAB_USBtoUART"]
52
+
53
+GCODE_EEPROM_SAVE = "M500"
54
+GCODE_STOP_IDLE_HOLD = "M84"
55
+GCODE_MOVE_TO_ORIGIN = "G28"
56
+GCODE_GET_CURRENT_POSITION = "M114"
57
+GCODE_MESH_INFO = "G29 S0"
58
+GCODE_MESH_START = "G29 S1"
59
+GCODE_MESH_NEXT = "G29 S2"
60
+GCODE_MOVE_Z = "G1 Z{}"
61
+GCODE_SET_BED_TEMP = "M140 S{}"
62
+GCODE_GET_TEMP = "M105"
63
+
64
+PRINTER_RESPONSE_OK = "ok"
65
+PRINTER_RESPONSE_BUSY = "echo:busy: processing"
66
+PRINTER_REGEX_COORDINATES = "^X:\d+\.\d+ Y:\d+\.\d+ Z:\d+\.\d+ E:\d+\.\d+ Count X:\s*\d+ Y:\s*\d+ Z:\s*\d+$"
67
+PRINTER_REGEX_MESH_POINTS = "^Num X,Y: \d,\d$"
68
+PRINTER_REGEX_MESH_Z = "^Z search height: \d\.*\d*$"
69
+PRINTER_REGEX_TEMP = "^ok T:\d\d*\.*\d* \/\d\d*\.*\d* B:\d\d*\.*\d* \/\d\d*\.*\d* B@:\d\d*\.*\d* @:\d\d*\.*\d*$"
70
+
71
+# Event for sending new XYZ coordinates from the serial thread to the GUI thread
72
+myEVT_COORDINATES = wx.NewEventType()
73
+EVT_COORDINATES = wx.PyEventBinder(myEVT_COORDINATES, 1)
74
+class CoordinatesEvent(wx.PyCommandEvent):
75
+    def __init__(self, etype, eid, value = None):
76
+        wx.PyCommandEvent.__init__(self, etype, eid)
77
+        self.value = value
78
+
79
+    def GetValue(self):
80
+        return self.value
81
+
82
+# Event for sending new mesh point info
83
+myEVT_MESH_POINTS = wx.NewEventType()
84
+EVT_MESH_POINTS = wx.PyEventBinder(myEVT_MESH_POINTS, 1)
85
+class MeshPointEvent(wx.PyCommandEvent):
86
+    def __init__(self, etype, eid, x = None, y = None):
87
+        wx.PyCommandEvent.__init__(self, etype, eid)
88
+        self.x = x
89
+        self.y = y
90
+
91
+    def GetX(self):
92
+        return self.x
93
+
94
+    def GetY(self):
95
+        return self.y
96
+
97
+# Event for sending new mesh point info
98
+myEVT_MESH_Z = wx.NewEventType()
99
+EVT_MESH_Z = wx.PyEventBinder(myEVT_MESH_Z, 1)
100
+class MeshZEvent(wx.PyCommandEvent):
101
+    def __init__(self, etype, eid, z = None):
102
+        wx.PyCommandEvent.__init__(self, etype, eid)
103
+        self.z = z
104
+
105
+    def GetZ(self):
106
+        return self.z
107
+
108
+# Event for sending temperature info
109
+myEVT_TEMPERATURE = wx.NewEventType()
110
+EVT_TEMPERATURE = wx.PyEventBinder(myEVT_TEMPERATURE, 1)
111
+class TemperatureEvent(wx.PyCommandEvent):
112
+    def __init__(self, etype, eid, bed = None):
113
+        wx.PyCommandEvent.__init__(self, etype, eid)
114
+        self.bed = bed
115
+
116
+    def GetBed(self):
117
+        return self.bed
118
+
119
+# Event for sending a new busy indicator status update
120
+myEVT_STATUS = wx.NewEventType()
121
+EVT_STATUS = wx.PyEventBinder(myEVT_STATUS, 1)
122
+class StatusEvent(wx.PyCommandEvent):
123
+    def __init__(self, etype, eid, value = None):
124
+        wx.PyCommandEvent.__init__(self, etype, eid)
125
+        self.value = value
126
+
127
+    def GetValue(self):
128
+        return self.value
129
+
130
+# Serial Thread reading from and writing to the printer, line-based
131
+class GCodeReader(serial.threaded.LineReader):
132
+
133
+    TERMINATOR = b'\n'
134
+
135
+    regexCoordinates = re.compile(PRINTER_REGEX_COORDINATES, re.M)
136
+    regexMeshPoints = re.compile(PRINTER_REGEX_MESH_POINTS, re.M)
137
+    regexMeshZ = re.compile(PRINTER_REGEX_MESH_Z, re.M)
138
+    regexTemp = re.compile(PRINTER_REGEX_TEMP, re.M)
139
+
140
+    def setParent(self, parent):
141
+        self.parent = parent
142
+
143
+    def handle_line(self, data):
144
+        if data == PRINTER_RESPONSE_OK:
145
+            event = StatusEvent(myEVT_STATUS, -1, 0)
146
+            wx.PostEvent(self.parent, event)
147
+            return
148
+
149
+        if data == PRINTER_RESPONSE_BUSY:
150
+            event = StatusEvent(myEVT_STATUS, -1, 1)
151
+            wx.PostEvent(self.parent, event)
152
+            return
153
+
154
+        if self.regexCoordinates.match(data):
155
+            afterX = data.split("X:")[1]
156
+            x = afterX.split(" ")[0]
157
+            afterY = data.split("Y:")[1]
158
+            y = afterY.split(" ")[0]
159
+            afterZ = data.split("Z:")[1]
160
+            z = afterZ.split(" ")[0]
161
+            str = "X: {} Y: {} Z: {}".format(x, y, z)
162
+            event = CoordinatesEvent(myEVT_COORDINATES, -1, str)
163
+            wx.PostEvent(self.parent, event)
164
+            return
165
+
166
+        if self.regexMeshPoints.match(data):
167
+            firstDigit = data.split("X,Y: ")[1]
168
+            x = firstDigit.split(",")[0]
169
+            y = firstDigit.split(",")[1]
170
+            event = MeshPointEvent(myEVT_MESH_POINTS, -1, x, y)
171
+            wx.PostEvent(self.parent, event)
172
+            return
173
+
174
+        if self.regexMeshZ.match(data):
175
+            num = data.split("Z search height: ")[1]
176
+            event = MeshZEvent(myEVT_MESH_Z, -1, float(num))
177
+            wx.PostEvent(self.parent, event)
178
+            return
179
+
180
+        if self.regexTemp.match(data):
181
+            bed = data.split(" B:")[1]
182
+            temp = bed.split(" /")[0]
183
+            event = TemperatureEvent(myEVT_TEMPERATURE, -1, float(temp))
184
+            wx.PostEvent(self.parent, event)
185
+            return
186
+
187
+# Main Window class
188
+class WizardFrame(wx.Frame):
189
+    def __init__(self, *args, **kwargs):
190
+        wx.Frame.__init__(self, *args, **kwargs)
191
+
192
+        self.hasQuit = False
193
+        self.isConnected = False
194
+        self.serial = serial.Serial()
195
+        self.thread = serial.threaded.ReaderThread(self.serial, GCodeReader)
196
+        self.isLeveling = False
197
+        self.currentPoint = 0
198
+        self.meshPoints = -1
199
+        self.step = 0.025
200
+        self.currentZ = 0.0
201
+        self.startZ = 0.0
202
+
203
+        self.Bind(EVT_COORDINATES, self.OnCoordinates)
204
+        self.Bind(EVT_STATUS, self.OnStatus)
205
+        self.Bind(EVT_MESH_POINTS, self.OnMeshPoints)
206
+        self.Bind(EVT_MESH_Z, self.OnMeshZ)
207
+        self.Bind(EVT_TEMPERATURE, self.OnTemperature)
208
+
209
+        # Menubar and Items
210
+        MenuBar = wx.MenuBar()
211
+        FileMenu = wx.Menu()
212
+        MenuBar.Append(FileMenu, "&File")
213
+        self.SetMenuBar(MenuBar)
214
+        item = FileMenu.Append(wx.ID_EXIT, text = "&Exit")
215
+        self.Bind(wx.EVT_MENU, self.OnQuit, item)
216
+        self.Bind(wx.EVT_CLOSE, self.OnQuit)
217
+        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
218
+
219
+        outerPanelSizer = wx.BoxSizer(wx.VERTICAL)
220
+
221
+        # Panel for serial control widgets
222
+        self.panelSerial = wx.Panel(self)
223
+        panelSerialSizer = wx.BoxSizer(wx.HORIZONTAL)
224
+        self.panelSerial.SetSizer(panelSerialSizer)
225
+        outerPanelSizer.Add(self.panelSerial, 0, wx.TOP | wx.EXPAND, 4)
226
+        self.panelSerial.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
227
+
228
+        # List all available serial ports
229
+        serialPortList = serial.tools.list_ports.comports()
230
+        serialPortNames = []
231
+        for port in serialPortList:
232
+            serialPortNames.append(port[0])
233
+
234
+        # Combobox for serial ports
235
+        self.comboBoxSerial = wx.ComboBox(parent = self.panelSerial, choices = serialPortNames, style = wx.CB_READONLY)
236
+        self.comboBoxSerial.SetMinSize((200, -1))
237
+        panelSerialSizer.Add(self.comboBoxSerial, 1, wx.LEFT, 5)
238
+        self.comboBoxSerial.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
239
+
240
+        # Select default serial port(s)
241
+        for port in serialPortList:
242
+            if port[0] in DEFAULT_SERIAL_PORTS:
243
+                self.comboBoxSerial.SetStringSelection(port[0])
244
+
245
+        # Regularly update serial port list
246
+        wx.FutureCall(POLL_SERIAL_PORTS_INTERVALL, self.EnumerateSerialPorts)
247
+
248
+        # Combobox for baudrates
249
+        self.comboBoxBaud = wx.ComboBox(parent = self.panelSerial, choices = AVAILABLE_BAUD_RATES, style = wx.CB_READONLY)
250
+        self.comboBoxBaud.SetMinSize((80, -1))
251
+        panelSerialSizer.Add(self.comboBoxBaud, 0)
252
+        self.comboBoxBaud.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
253
+
254
+        for baud in AVAILABLE_BAUD_RATES:
255
+            if baud == DEFAULT_BAUD_RATE:
256
+                self.comboBoxBaud.SetStringSelection(DEFAULT_BAUD_RATE)
257
+
258
+        # Button for connecting and disconnecting
259
+        self.buttonSerial = wx.Button(parent = self.panelSerial, label = "Connect")
260
+        self.buttonSerial.Bind(wx.EVT_BUTTON, self.OnConnectDisconnect)
261
+        panelSerialSizer.Add(self.buttonSerial, 0, wx.TOP, 2)
262
+        panelSerialSizer.AddSpacer(5)
263
+        self.buttonSerial.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
264
+
265
+        # Panel for status labels
266
+        self.panelStatus = wx.Panel(self)
267
+        panelStatusSizer = wx.BoxSizer(wx.HORIZONTAL)
268
+        self.panelStatus.SetSizer(panelStatusSizer)
269
+        outerPanelSizer.Add(self.panelStatus, 0, wx.EXPAND | wx.TOP, 3)
270
+        self.panelStatus.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
271
+
272
+        # Labels for coordinates
273
+        self.labelX = wx.StaticText(parent = self.panelStatus, label = "X: ??.??")
274
+        panelStatusSizer.Add(self.labelX, 0, wx.LEFT, 5)
275
+        panelStatusSizer.AddStretchSpacer(1)
276
+        self.labelX.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
277
+
278
+        self.labelY = wx.StaticText(parent = self.panelStatus, label = "Y: ??.??")
279
+        panelStatusSizer.Add(self.labelY, 0)
280
+        panelStatusSizer.AddStretchSpacer(1)
281
+        self.labelY.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
282
+
283
+        self.labelZ = wx.StaticText(parent = self.panelStatus, label = "Z: ??.??")
284
+        panelStatusSizer.Add(self.labelZ, 0, wx.RIGHT, 5)
285
+        panelStatusSizer.AddStretchSpacer(1)
286
+        self.labelZ.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
287
+
288
+        self.labelZc = wx.StaticText(parent = self.panelStatus, label = "Zc: ??.???")
289
+        panelStatusSizer.Add(self.labelZc, 0, wx.RIGHT, 5)
290
+        self.labelZc.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
291
+
292
+        # Panel for general actions
293
+        self.panelAction = wx.Panel(self)
294
+        panelActionSizer = wx.BoxSizer(wx.HORIZONTAL)
295
+        self.panelAction.SetSizer(panelActionSizer)
296
+        outerPanelSizer.Add(self.panelAction, 0, wx.EXPAND | wx.TOP, 5)
297
+        self.panelAction.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
298
+
299
+        # Button for homing
300
+        self.buttonHome = wx.Button(parent = self.panelAction, label = "Home")
301
+        self.buttonHome.Bind(wx.EVT_BUTTON, self.OnHome)
302
+        self.buttonHome.Enable(False)
303
+        panelActionSizer.AddSpacer(5)
304
+        panelActionSizer.Add(self.buttonHome, 0, wx.BOTTOM, 1)
305
+        self.buttonHome.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
306
+
307
+        # Button for steppers off
308
+        self.buttonOff = wx.Button(parent = self.panelAction, label = "Off")
309
+        self.buttonOff.Bind(wx.EVT_BUTTON, self.OnStepperOff)
310
+        self.buttonOff.Enable(False)
311
+        panelActionSizer.AddSpacer(5)
312
+        panelActionSizer.Add(self.buttonOff, 0, wx.BOTTOM, 1)
313
+        self.buttonOff.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
314
+
315
+        # Status Gauge
316
+        self.gauge = wx.Gauge(parent = self.panelAction)
317
+        self.gauge.SetRange(BUSY_PROCESSING_STEPS)
318
+        panelActionSizer.Add(self.gauge, 1, wx.LEFT | wx.RIGHT, 5)
319
+        self.gauge.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
320
+
321
+        # Bottom panel
322
+        self.panelBottom = wx.Panel(self)
323
+        panelBottomSizer = wx.BoxSizer(wx.HORIZONTAL)
324
+        self.panelBottom.SetSizer(panelBottomSizer)
325
+        outerPanelSizer.Add(self.panelBottom, 0, wx.EXPAND | wx.TOP, 5)
326
+        self.panelBottom.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
327
+
328
+        # Config panel
329
+        self.panelConfig = wx.Panel(self.panelBottom)
330
+        panelConfigSizer = wx.BoxSizer(wx.VERTICAL)
331
+        self.panelConfig.SetSizer(panelConfigSizer)
332
+        panelBottomSizer.Add(self.panelConfig, 2, wx.EXPAND | wx.LEFT, 5)
333
+        self.panelConfig.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
334
+
335
+        # Step Size Label
336
+        stepSizeLabel = wx.StaticText(self.panelConfig, label="Step:")
337
+        panelConfigSizer.Add(stepSizeLabel, 0, wx.LEFT, 5)
338
+        stepSizeLabel.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
339
+
340
+        # Step Size Text Input
341
+        self.stepSize = wx.TextCtrl(parent = self.panelConfig, value="0.025", style = wx.TE_DONTWRAP | wx.TE_PROCESS_ENTER)
342
+        self.stepSize.SetMinSize((65, -1))
343
+        self.stepSize.Enable(False)
344
+        self.stepSize.Bind(wx.EVT_TEXT_ENTER, self.OnNewStepSize)
345
+        panelConfigSizer.Add(self.stepSize, 0, wx.LEFT | wx.TOP, 5)
346
+
347
+        # Bed Temperature Label
348
+        self.bedTemperatureLabel = wx.StaticText(self.panelConfig, label="Bed: ??.?")
349
+        panelConfigSizer.Add(self.bedTemperatureLabel, 0, wx.LEFT | wx.TOP, 5)
350
+        self.bedTemperatureLabel.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
351
+
352
+        # Bed Temperature Text Input
353
+        self.bedTemperature = wx.TextCtrl(parent = self.panelConfig, value="0", style = wx.TE_DONTWRAP | wx.TE_PROCESS_ENTER)
354
+        self.bedTemperature.SetMinSize((65, -1))
355
+        self.bedTemperature.Enable(False)
356
+        self.bedTemperature.Bind(wx.EVT_TEXT_ENTER, self.OnNewBedTemperature)
357
+        panelConfigSizer.Add(self.bedTemperature, 0, wx.LEFT | wx.TOP | wx.BOTTOM, 5)
358
+
359
+        # Control-Buttons Panel
360
+        self.panelControl = wx.Panel(self.panelBottom)
361
+        panelControlSizer = wx.BoxSizer(wx.VERTICAL)
362
+        self.panelControl.SetSizer(panelControlSizer)
363
+        panelBottomSizer.Add(self.panelControl, 2, wx.EXPAND | wx.LEFT, 5)
364
+        self.panelControl.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
365
+
366
+        # Up Button
367
+        self.buttonUp = wx.Button(parent = self.panelControl, label = "/\\")
368
+        self.buttonUp.Bind(wx.EVT_BUTTON, self.OnUp)
369
+        self.buttonUp.Enable(False)
370
+        panelControlSizer.AddStretchSpacer(1)
371
+        panelControlSizer.Add(self.buttonUp, 2, wx.ALIGN_CENTER_HORIZONTAL)
372
+        self.buttonUp.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
373
+
374
+        # Down Button
375
+        self.buttonDown = wx.Button(parent = self.panelControl, label = "\\/")
376
+        self.buttonDown.Bind(wx.EVT_BUTTON, self.OnDown)
377
+        self.buttonDown.Enable(False)
378
+        panelControlSizer.Add(self.buttonDown, 2, wx.ALIGN_CENTER_HORIZONTAL | wx.BOTTOM, 5)
379
+        panelControlSizer.AddStretchSpacer(1)
380
+        self.buttonDown.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
381
+
382
+        # Level Status Panel
383
+        self.panelLevelStatus = wx.Panel(self.panelBottom)
384
+        panelLevelStatusSizer = wx.BoxSizer(wx.VERTICAL)
385
+        self.panelLevelStatus.SetSizer(panelLevelStatusSizer)
386
+        panelBottomSizer.Add(self.panelLevelStatus, 1, wx.EXPAND)
387
+        self.panelLevelStatus.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
388
+
389
+        # Level Status Gauge
390
+        self.gaugeLevel = wx.Gauge(parent = self.panelLevelStatus, style = wx.GA_VERTICAL)
391
+        self.gaugeLevel.SetRange(1)
392
+        self.gaugeLevel.SetValue(1)
393
+        panelLevelStatusSizer.Add(self.gaugeLevel, 1, wx.ALIGN_CENTER_HORIZONTAL | wx.BOTTOM | wx.LEFT, 10)
394
+        self.gaugeLevel.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
395
+
396
+        # Level Panel
397
+        self.panelLevel = wx.Panel(self.panelBottom)
398
+        panelLevelSizer = wx.BoxSizer(wx.VERTICAL)
399
+        self.panelLevel.SetSizer(panelLevelSizer)
400
+        panelBottomSizer.Add(self.panelLevel, 2, wx.EXPAND)
401
+        self.panelLevel.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
402
+
403
+        # Start Button
404
+        self.buttonStart = wx.Button(parent = self.panelLevel, label = "Start")
405
+        self.buttonStart.Bind(wx.EVT_BUTTON, self.OnStart)
406
+        self.buttonStart.Enable(False)
407
+        panelLevelSizer.Add(self.buttonStart, 0, wx.ALIGN_RIGHT | wx.RIGHT | wx.TOP, 5)
408
+        self.buttonStart.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
409
+
410
+        # Next Button
411
+        self.buttonNext = wx.Button(parent = self.panelLevel, label = "Next")
412
+        self.buttonNext.Bind(wx.EVT_BUTTON, self.OnNext)
413
+        self.buttonNext.Enable(False)
414
+        panelLevelSizer.Add(self.buttonNext, 0, wx.ALIGN_RIGHT | wx.RIGHT | wx.TOP, 5)
415
+        self.buttonNext.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
416
+
417
+        self.buttonSave = wx.Button(parent = self.panelLevel, label = "Save")
418
+        self.buttonSave.Bind(wx.EVT_BUTTON, self.OnSave)
419
+        self.buttonSave.Enable(False)
420
+        panelLevelSizer.Add(self.buttonSave, 0, wx.ALIGN_RIGHT | wx.RIGHT | wx.TOP | wx.BOTTOM, 5)
421
+        self.buttonSave.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
422
+
423
+        self.SetSizerAndFit(outerPanelSizer)
424
+        #self.SetSizer(outerPanelSizer)
425
+
426
+    def EnumerateSerialPorts(self):
427
+        # List all available serial ports
428
+        serialPortList = serial.tools.list_ports.comports()
429
+
430
+        # Fill Combobox, remember selected value
431
+        selection = self.comboBoxSerial.GetValue()
432
+        self.comboBoxSerial.Clear()
433
+        for port in serialPortList:
434
+            self.comboBoxSerial.Append(port[0])
435
+            if port[0] == selection:
436
+                self.comboBoxSerial.SetStringSelection(selection)
437
+
438
+        # Select first port if nothing is selected
439
+        if not selection:
440
+            self.comboBoxSerial.SetStringSelection(serialPortList[0][0])
441
+
442
+        self.panelSerial.Layout()
443
+
444
+        # Check every 5 seconds for new available serial ports
445
+        wx.FutureCall(POLL_SERIAL_PORTS_INTERVALL, self.EnumerateSerialPorts)
446
+
447
+    def EnableDisableUI(self):
448
+        if not self.isConnected:
449
+            self.buttonSerial.SetLabel("Connect")
450
+            self.labelX.SetLabel("X: ??.??")
451
+            self.labelY.SetLabel("Y: ??.??")
452
+            self.labelZ.SetLabel("Z: ??.??")
453
+            self.labelZc.SetLabel("Zc: ??.???")
454
+        else:
455
+            self.buttonSerial.SetLabel("Disconnect")
456
+
457
+        self.panelStatus.Layout()
458
+        self.panelSerial.Layout()
459
+
460
+        self.comboBoxSerial.Enable(not self.isConnected)
461
+        self.comboBoxBaud.Enable(not self.isConnected)
462
+
463
+        self.buttonHome.Enable(self.isConnected)
464
+        self.buttonOff.Enable(self.isConnected)
465
+        self.buttonUp.Enable(self.isConnected)
466
+        self.buttonDown.Enable(self.isConnected)
467
+        self.stepSize.Enable(self.isConnected)
468
+        self.bedTemperature.Enable(self.isConnected)
469
+
470
+        self.buttonStart.Enable(False)
471
+        self.buttonNext.Enable(False)
472
+        self.buttonSave.Enable(False)
473
+        if self.isConnected:
474
+            if not self.isLeveling:
475
+                self.buttonSave.Enable(True)
476
+                if self.meshPoints > 0:
477
+                    self.buttonStart.Enable(True)
478
+
479
+            if self.isLeveling:
480
+                self.buttonNext.Enable(True)
481
+
482
+    def OnSave(self, event):
483
+        if self.isLeveling:
484
+            print "Leveling is already in progress!"
485
+            return
486
+
487
+        self.protocol.write_line(GCODE_EEPROM_SAVE)
488
+
489
+    def OnStart(self, event):
490
+        if self.isLeveling:
491
+            print "Leveling is already in progress!"
492
+            return
493
+
494
+        self.isLeveling = True
495
+        self.currentPoint = 0
496
+        self.gauge.SetValue(0)
497
+        self.protocol.write_line(GCODE_MESH_START)
498
+        self.gaugeLevel.SetValue(0)
499
+        self.EnableDisableUI()
500
+
501
+    def OnNext(self, event):
502
+        if not self.isLeveling:
503
+            print "Start leveling before proceeding!"
504
+            return
505
+
506
+        self.gauge.SetValue(0)
507
+        self.protocol.write_line(GCODE_MESH_NEXT)
508
+        self.currentPoint += 1
509
+        if self.currentPoint >= self.meshPoints:
510
+            self.isLeveling = False
511
+        self.gaugeLevel.SetValue(self.currentPoint)
512
+        self.EnableDisableUI()
513
+        self.currentZ = self.startZ
514
+        self.labelZc.SetLabel("Zc: {}".format(self.currentZ))
515
+        self.panelStatus.Layout()
516
+
517
+    def OnNewStepSize(self, event):
518
+        print "New Step Size: {}".format(self.stepSize.GetValue())
519
+        self.step = float(self.stepSize.GetValue())
520
+
521
+    def OnKeyDown(self, event):
522
+        key = event.GetKeyCode()
523
+
524
+        if self.isConnected:
525
+            if key == wx.WXK_DOWN:
526
+                self.OnDown(event)
527
+            elif key == wx.WXK_UP:
528
+                self.OnUp(event)
529
+            elif key == wx.WXK_RIGHT:
530
+                self.OnNext(event)
531
+
532
+    def OnUp(self, event):
533
+        self.currentZ += self.step
534
+        print "Moving up to {}".format(self.currentZ)
535
+        self.gauge.SetValue(0)
536
+        self.protocol.write_line(GCODE_MOVE_Z.format(self.currentZ))
537
+        self.labelZc.SetLabel("Zc: {}".format(self.currentZ))
538
+        self.panelStatus.Layout()
539
+
540
+    def OnDown(self, event):
541
+        if self.currentZ <= 0.0:
542
+            print "Can't move further down!"
543
+            return
544
+
545
+        self.currentZ -= self.step
546
+        print "Moving down to {}".format(self.currentZ)
547
+        self.gauge.SetValue(0)
548
+        self.protocol.write_line(GCODE_MOVE_Z.format(self.currentZ))
549
+        self.labelZc.SetLabel("Zc: {}".format(self.currentZ))
550
+        self.panelStatus.Layout()
551
+
552
+    def OnStepperOff(self, event):
553
+        self.gauge.SetValue(0)
554
+        self.protocol.write_line(GCODE_STOP_IDLE_HOLD)
555
+
556
+    def OnHome(self, event):
557
+        self.gauge.SetValue(0)
558
+        self.protocol.write_line(GCODE_MOVE_TO_ORIGIN)
559
+
560
+    def OnStatus(self, event):
561
+        if event.GetValue() == 0:
562
+            self.OnDone(event)
563
+        else:
564
+            self.OnBusy(event)
565
+
566
+    def OnBusy(self, event):
567
+        val = self.gauge.GetValue()
568
+        if val < self.gauge.GetRange():
569
+            self.gauge.SetValue(val + 1)
570
+
571
+    def OnDone(self, event):
572
+        self.gauge.SetValue(self.gauge.GetRange())
573
+
574
+    def PollMeshData(self):
575
+        if self.isConnected:
576
+            self.protocol.write_line(GCODE_MESH_INFO)
577
+
578
+    def PollMeshDataRetry(self):
579
+        if self.meshPoints <= 0:
580
+            self.PollMeshData()
581
+            wx.FutureCall(MESH_DATA_RETRY_TIMEOUT, self.PollMeshDataRetry)
582
+
583
+    def OnMeshPoints(self, event):
584
+        x = int(event.GetX())
585
+        y = int(event.GetY())
586
+        self.meshPoints = x * y
587
+        print "Mesh Points: X: {} Y: {} --> {}".format(x, y, self.meshPoints)
588
+        self.gaugeLevel.SetRange(self.meshPoints)
589
+        self.gaugeLevel.SetValue(0)
590
+        self.EnableDisableUI()
591
+
592
+    def OnMeshZ(self, event):
593
+        self.startZ = float(event.GetZ())
594
+        self.currentZ = float(event.GetZ())
595
+        self.labelZc.SetLabel("Zc: {}".format(self.currentZ))
596
+        self.gauge.SetValue(BUSY_PROCESSING_STEPS)
597
+        print "Mesh starting height: {}".format(self.currentZ)
598
+
599
+    def PollPosition(self):
600
+        if self.isConnected:
601
+            #if not self.isLeveling:
602
+            self.protocol.write_line(GCODE_GET_CURRENT_POSITION)
603
+            wx.FutureCall(POLL_COORDINATES_INTERVALL, self.PollPosition)
604
+
605
+    def OnCoordinates(self, event):
606
+        data = event.GetValue()
607
+        print "New coordinates {}".format(data)
608
+        afterX = data.split("X: ")[1]
609
+        x = afterX.split(" ")[0]
610
+        afterY = data.split("Y: ")[1]
611
+        y = afterY.split(" ")[0]
612
+        afterZ = data.split("Z: ")[1]
613
+        z = afterZ.split(" ")[0]
614
+
615
+        self.labelX.SetLabel("X: {}".format(x))
616
+        self.labelY.SetLabel("Y: {}".format(y))
617
+        self.labelZ.SetLabel("Z: {}".format(z))
618
+
619
+        self.panelStatus.Layout()
620
+
621
+    def OnTemperature(self, event):
622
+        print "Current Bed Temperature: {}".format(event.GetBed())
623
+        self.bedTemperatureLabel.SetLabel("Bed: {}".format(event.GetBed()))
624
+
625
+    def OnNewBedTemperature(self, event):
626
+        self.protocol.write_line(GCODE_SET_BED_TEMP.format(float(self.bedTemperature.GetValue())))
627
+
628
+    def PollTemperature(self):
629
+        if self.isConnected:
630
+            self.protocol.write_line(GCODE_GET_TEMP)
631
+            wx.FutureCall(POLL_TEMPERATURE_INTERVALL, self.PollTemperature)
632
+
633
+    def OnConnectDisconnect(self, Event):
634
+        self.isLeveling = False
635
+        self.currentPoint = 0
636
+        if self.isConnected:
637
+            self.thread.close()
638
+            self.serial.close()
639
+            self.isConnected = False
640
+            self.gauge.SetValue(0)
641
+            self.gaugeLevel.SetRange(1)
642
+            self.gaugeLevel.SetValue(1)
643
+            self.EnableDisableUI()
644
+
645
+            # Recreate thread, so we can start it again next time
646
+            self.thread = serial.threaded.ReaderThread(self.serial, GCodeReader)
647
+        else:
648
+            self.serial.baudrate = self.comboBoxBaud.GetValue()
649
+            self.serial.port = self.comboBoxSerial.GetValue()
650
+            try:
651
+                self.serial.open()
652
+            except serial.SerialException as msg:
653
+                wx.MessageBox("SerialException: {}".format(msg), "Error", wx.OK | wx.ICON_ERROR)
654
+            except OSError as msg:
655
+                wx.MessageBox("OSError: {}".format(msg), "Error", wx.OK | wx.ICON_ERROR)
656
+            if self.serial.isOpen():
657
+                self.isConnected = True
658
+                self.thread.start()
659
+                self.transport, self.protocol = self.thread.connect()
660
+                self.protocol.setParent(self)
661
+                self.meshPoints = -1
662
+                self.EnableDisableUI()
663
+                self.PollPosition()
664
+                self.PollMeshData()
665
+                self.PollTemperature()
666
+                wx.FutureCall(MESH_DATA_RETRY_TIMEOUT, self.PollMeshDataRetry)
667
+
668
+    def OnQuit(self, Event):
669
+        if not self.hasQuit:
670
+            self.hasQuit = True
671
+            if self.isConnected:
672
+                print("Closing serial port...")
673
+                self.thread.close()
674
+                self.serial.close()
675
+                self.isConnected = False
676
+        self.Destroy()
677
+
678
+# App wrapper class
679
+class WizardApp(wx.App):
680
+    def __init__(self, *args, **kwargs):
681
+        wx.App.__init__(self, *args, **kwargs)
682
+        self.Bind(wx.EVT_ACTIVATE_APP, self.OnActivate)
683
+
684
+    def OnInit(self):
685
+        frame = WizardFrame(parent = None, title = "Bed Leveling Wizard", style = wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX | wx.CLIP_CHILDREN)
686
+        frame.Centre()
687
+        frame.Show()
688
+        return True
689
+
690
+    def BringWindowToFront(self):
691
+        self.GetTopWindow().Raise()
692
+
693
+    def OnActivate(self, event):
694
+        if event.GetActive():
695
+            self.BringWindowToFront()
696
+        event.Skip()
697
+
698
+    def MacReopenApp(self):
699
+        self.BringWindowToFront()
700
+
701
+def main():
702
+    app = WizardApp(False)
703
+    app.MainLoop()
704
+
705
+if __name__ == '__main__':
706
+    main()
707
+

Loading…
Cancel
Save