No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

bed-leveling.py 27KB

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