chiark / gitweb /
5220e796faafde08076866a0470cb2c61c621d0b
[cura.git] / Cura / gui / firmwareInstall.py
1 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
2
3 import os
4 import wx
5 import threading
6 import sys
7 import time
8 import serial
9
10 from Cura.avr_isp import stk500v2
11 from Cura.avr_isp import ispBase
12 from Cura.avr_isp import intelHex
13
14 from Cura.gui.util import taskbar
15 from Cura.util import machineCom
16 from Cura.util import profile
17 from Cura.util import resources
18
19 def getDefaultFirmware(machineIndex = None):
20         firmwareDict = {
21                         'ultimaker2go':"MarlinUltimaker2go.hex",
22                         'Witbox':"MarlinWitbox.hex",
23                         'lulzbot_mini': "Mini-Single-or-Flexystruder-LBHexagon-2015Q2.hex",
24                         'lulzbot_mini_flexystruder': "Mini-Single-or-Flexystruder-LBHexagon-2015Q2.hex",
25                         'lulzbot_TAZ_4_SingleV1': "Taz4-5-Single-or-Flexystruder-Budaschnozzle-2014Q3.hex",
26                         'lulzbot_TAZ_5_SingleV1': "Taz4-5-Single-or-Flexystruder-Budaschnozzle-2014Q3.hex",
27                         'lulzbot_TAZ_4_05nozzle': "Taz4-Single-Extruder-LBHexagon-2015Q3.hex",
28                         'lulzbot_TAZ_5_05nozzle': "Taz5-Single-Extruder-LBHexagon-2015Q3.hex",
29                         'lulzbot_TAZ_4_035nozzle': "Taz4-Single-Extruder-LBHexagon-2015Q3.hex",
30                         'lulzbot_TAZ_5_035nozzle': "Taz5-Single-Extruder-LBHexagon-2015Q3.hex",
31                         'lulzbot_TAZ_4_FlexystruderV1': "Taz4-5-Single-or-Flexystruder-Budaschnozzle-2014Q3.hex",
32                         'lulzbot_TAZ_5_FlexystruderV1': "Taz4-5-Single-or-Flexystruder-Budaschnozzle-2014Q3.hex",
33                         'lulzbot_TAZ_4_FlexystruderV2': "Taz4-5-Flexystruder-LBHexagon-2015Q3.hex",
34                         'lulzbot_TAZ_5_FlexystruderV2': "Taz4-5-Flexystruder-LBHexagon-2015Q3.hex",
35                         'lulzbot_TAZ_4_DualV1': "Taz4-5-Dual-or-FlexyDually-Budaschnozzle-2015Q1.hex",
36                         'lulzbot_TAZ_5_DualV1': "Taz4-5-Dual-or-FlexyDually-Budaschnozzle-2015Q1.hex",
37                         'lulzbot_TAZ_4_DualV2': "Taz4-5-Dual-LBHexagon-2015Q3.hex",
38                         'lulzbot_TAZ_5_DualV2': "Taz4-5-Dual-LBHexagon-2015Q3.hex",
39                         'lulzbot_TAZ_4_FlexyDuallyV1': "Taz4-5-Dual-or-FlexyDually-Budaschnozzle-2015Q1.hex",
40                         'lulzbot_TAZ_5_FlexyDuallyV1': "Taz4-5-Dual-or-FlexyDually-Budaschnozzle-2015Q1.hex",
41                         'lulzbot_TAZ_4_FlexyDuallyV2': "Taz4-5-FlexyDually-LBHexagon-2015Q3.hex",
42                         'lulzbot_TAZ_5_FlexyDuallyV2': "Taz4-5-FlexyDually-LBHexagon-2015Q3.hex"
43         }
44         machine_type = profile.getMachineSetting('machine_type', machineIndex)
45         extruders = profile.getMachineSettingFloat('extruder_amount', machineIndex)
46         heated_bed = profile.getMachineSetting('has_heated_bed', machineIndex) == 'True'
47         baudrate = 250000
48         if sys.platform.startswith('linux'):
49                 baudrate = 115200
50         if machine_type == 'ultimaker':
51                 name = 'MarlinUltimaker'
52                 if extruders > 2:
53                         return None
54                 if heated_bed:
55                         name += '-HBK'
56                 name += '-%d' % (baudrate)
57                 if extruders > 1:
58                         name += '-dual'
59                 return resources.getPathForFirmware(name + '.hex')
60
61         if machine_type == 'ultimaker_plus':
62                 name = 'MarlinUltimaker-UMOP-%d' % (baudrate)
63                 if extruders > 2:
64                         return None
65                 if extruders > 1:
66                         name += '-dual'
67                 return resources.getPathForFirmware(name + '.hex')
68         if machine_type == 'ultimaker2':
69                 if extruders > 2:
70                         return None
71                 if extruders > 1:
72                         return resources.getPathForFirmware("MarlinUltimaker2-dual.hex")
73                 return resources.getPathForFirmware("MarlinUltimaker2.hex")
74         if machine_type == 'ultimaker2extended':
75                 if extruders > 2:
76                         return None
77                 if extruders > 1:
78                         return resources.getPathForFirmware("MarlinUltimaker2extended-dual.hex")
79                 return resources.getPathForFirmware("MarlinUltimaker2extended.hex")
80         if firmwareDict.has_key(machine_type):
81                 return resources.getPathForFirmware(firmwareDict[machine_type])
82         return None
83
84 def InstallFirmware(parent = None, filename = None, port = None, machineIndex = None):
85         dlg = InstallFirmwareDialog(parent, filename, port, machineIndex)
86         result = dlg.Run()
87         dlg.Destroy()
88         return result
89
90 class InstallFirmwareDialog(wx.Dialog):
91         def __init__(self, parent = None, filename = None, port = None, machineIndex = None):
92                 super(InstallFirmwareDialog, self).__init__(parent=parent, title=_("Firmware install for %s") % (profile.getMachineName(machineIndex).title()), size=(250, 100))
93                 if port is None:
94                         port = profile.getMachineSetting('serial_port')
95                 if filename is None:
96                         filename = getDefaultFirmware(machineIndex)
97                 self._machine_type = profile.getMachineSetting('machine_type', machineIndex)
98                 if self._machine_type == 'reprap':
99                         wx.MessageBox(_("Cura only supports firmware updates for ATMega2560 based hardware.\nSo updating your RepRap with Cura might or might not work."), _("Firmware update"), wx.OK | wx.ICON_INFORMATION)
100
101                 sizer = wx.BoxSizer(wx.VERTICAL)
102
103                 self.progressLabel = wx.StaticText(self, -1, 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\nX\nX')
104                 sizer.Add(self.progressLabel, 0, flag=wx.ALIGN_CENTER|wx.ALL, border=5)
105                 self.progressGauge = wx.Gauge(self, -1)
106                 sizer.Add(self.progressGauge, 0, flag=wx.EXPAND)
107                 self.okButton = wx.Button(self, -1, _("OK"))
108                 self.okButton.Disable()
109                 self.okButton.Bind(wx.EVT_BUTTON, self.OnOk)
110                 sizer.Add(self.okButton, 0, flag=wx.ALIGN_CENTER|wx.ALL, border=5)
111                 self.SetSizer(sizer)
112
113                 self.filename = filename
114                 self.port = port
115
116                 self.Layout()
117                 self.Fit()
118                 self.success = False
119                 self.show_connect_dialog = False
120
121         def Run(self):
122                 if self.filename is None:
123                         wx.MessageBox(_("I am sorry, but Cura does not ship with a default firmware for your machine configuration."), _("Firmware update"), wx.OK | wx.ICON_ERROR)
124                         return False
125                 self.success = False
126                 self.thread = threading.Thread(target=self.OnRun)
127                 self.thread.daemon = True
128                 self.thread.start()
129
130                 self.ShowModal()
131                 # Creating a MessageBox in a separate thread while main thread is locked inside a ShowModal
132                 # will cause Python to crash with X errors. So we need to show the dialog here instead
133                 if self.show_connect_dialog:
134                         wx.MessageBox(_("Failed to find machine for firmware upgrade\nIs your machine connected to the PC?"),
135                                                   _("Firmware update"), wx.OK | wx.ICON_ERROR)
136                 return self.success
137
138         def OnRun(self):
139                 wx.CallAfter(self.updateLabel, _("Reading firmware..."))
140                 hexFile = intelHex.readHex(self.filename)
141                 wx.CallAfter(self.updateLabel, _("Connecting to machine..."))
142                 programmer = stk500v2.Stk500v2()
143                 programmer.progressCallback = self.OnProgress
144                 if self.port == 'AUTO':
145                         wx.CallAfter(self.updateLabel, _("Please connect the printer to your\ncomputer with a USB cable and power it on."))
146                         while not programmer.isConnected():
147                                 for self.port in machineCom.serialList(True):
148                                         try:
149                                                 programmer.connect(self.port)
150                                                 break
151                                         except ispBase.IspError:
152                                                 programmer.close()
153                                 time.sleep(1)
154                                 if not self:
155                                         #Window destroyed
156                                         return
157                 else:
158                         try:
159                                 programmer.connect(self.port)
160                         except ispBase.IspError:
161                                 programmer.close()
162                         if not self:
163                                 #Window destroyed
164                                 return
165
166                 if not programmer.isConnected():
167                         self.show_connect_dialog = True
168                         wx.CallAfter(self.Close)
169                         return
170
171                 if self._machine_type == 'ultimaker':
172                         if programmer.hasChecksumFunction():
173                                 wx.CallAfter(self.updateLabel, _("Failed to install firmware:\nThis firmware is not compatible with this machine.\nTrying to install UMO firmware on an UM2 or UMO+?"))
174                                 programmer.close()
175                                 wx.CallAfter(self.okButton.Enable)
176                                 return
177                 if self._machine_type == 'ultimaker_plus' or self._machine_type == 'ultimaker2':
178                         if not programmer.hasChecksumFunction():
179                                 wx.CallAfter(self.updateLabel, _("Failed to install firmware:\nThis firmware is not compatible with this machine.\nTrying to install UM2 or UMO+ firmware on an UMO?"))
180                                 programmer.close()
181                                 wx.CallAfter(self.okButton.Enable)
182                                 return
183
184                 wx.CallAfter(self.updateLabel, _("Uploading firmware..."))
185                 try:
186                         programmer.programChip(hexFile)
187                         self.success = True
188                         wx.CallAfter(self.updateLabel, _("Done!\nInstalled firmware: %s") % (os.path.basename(self.filename)))
189                 except ispBase.IspError as e:
190                         wx.CallAfter(self.updateLabel, _("Failed to write firmware.\n") + str(e))
191
192                 programmer.close()
193                 wx.CallAfter(self.okButton.Enable)
194
195         def updateLabel(self, text):
196                 self.progressLabel.SetLabel(text)
197                 self.Layout()
198
199         def OnProgress(self, value, max):
200                 if self:
201                         wx.CallAfter(self.progressGauge.SetRange, max)
202                         wx.CallAfter(self.progressGauge.SetValue, value)
203                         taskbar.setProgress(self.GetParent(), value, max)
204
205         def OnOk(self, e):
206                 self.Close()
207                 taskbar.setBusy(self.GetParent(), False)
208
209         def OnClose(self, e):
210                 self.Destroy()
211
212
213 class AutoUpdateFirmware(wx.Dialog):
214         def __init__(self, parent, filename = None, port = None, machineIndex = None):
215                 super(AutoUpdateFirmware, self).__init__(parent=parent, title=_("Auto Firmware install"), size=(250, 500))
216                 if port is None:
217                         port = profile.getMachineSetting('serial_port')
218                 self._serial = None
219
220                 sizer = wx.BoxSizer(wx.VERTICAL)
221
222                 self.progressLabel = wx.StaticText(self, -1, 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\nX\nX')
223                 sizer.Add(self.progressLabel, 0, flag=wx.ALIGN_CENTER|wx.ALL, border=5)
224                 self.progressGauge = wx.Gauge(self, -1)
225                 sizer.Add(self.progressGauge, 0, flag=wx.EXPAND)
226                 self.okButton = wx.Button(self, -1, _("OK"))
227                 self.okButton.Bind(wx.EVT_BUTTON, self.OnOk)
228                 sizer.Add(self.okButton, 0, flag=wx.ALIGN_CENTER|wx.ALL, border=5)
229
230                 f = wx.Font(8, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
231                 self._termLog = wx.TextCtrl(self, style=wx.TE_MULTILINE | wx.TE_DONTWRAP)
232                 self._termLog.SetFont(f)
233                 self._termLog.SetEditable(0)
234                 self._termLog.SetMinSize((1, 400))
235                 self._termInput = wx.TextCtrl(self, style=wx.TE_PROCESS_ENTER)
236                 self._termInput.SetFont(f)
237                 sizer.Add(self._termLog, 0, flag=wx.ALIGN_CENTER|wx.ALL|wx.EXPAND)
238                 sizer.Add(self._termInput, 0, flag=wx.ALIGN_CENTER|wx.ALL|wx.EXPAND)
239
240                 self.Bind(wx.EVT_TEXT_ENTER, self.OnTermEnterLine, self._termInput)
241
242                 self.SetSizer(sizer)
243
244                 self.filename = filename
245                 self.port = port
246
247                 self.Layout()
248                 self.Fit()
249
250                 self.thread = threading.Thread(target=self.OnRun)
251                 self.thread.daemon = True
252                 self.thread.start()
253
254                 self.read_thread = threading.Thread(target=self.OnSerialRead)
255                 self.read_thread.daemon = True
256                 self.read_thread.start()
257
258                 self.ShowModal()
259                 self.Destroy()
260                 return
261
262         def _addTermLog(self, line):
263                 if self._termLog is not None:
264                         if len(self._termLog.GetValue()) > 10000:
265                                 self._termLog.SetValue(self._termLog.GetValue()[-10000:])
266                         self._termLog.SetInsertionPointEnd()
267                         if type(line) != unicode:
268                                 line = unicode(line, 'utf-8', 'replace')
269                         self._termLog.AppendText(line.encode('utf-8', 'replace'))
270
271         def OnTermEnterLine(self, e):
272                 lines = self._termInput.GetValue().split(';')
273                 for line in lines:
274                         if line == '':
275                                 continue
276                         self._addTermLog('> %s\n' % (line))
277                         if self._serial is not None:
278                                 self._serial.write(line + '\n')
279
280         def OnRun(self):
281                 mtime = 0
282                 while bool(self):
283                         new_mtime = os.stat(self.filename).st_mtime
284                         if mtime != new_mtime:
285                                 mtime = new_mtime
286                                 if self._serial is not None:
287                                         self._serial.close()
288                                         self._serial = None
289                                 time.sleep(0.5)
290                                 self.OnInstall()
291                                 try:
292                                         self._serial = serial.Serial(self.port, 115200)
293                                 except:
294                                         pass
295                         time.sleep(0.5)
296
297         def OnSerialRead(self):
298                 while bool(self):
299                         if self._serial is None:
300                                 time.sleep(0.5)
301                         else:
302                                 try:
303                                         line = self._serial.readline()
304                                         wx.CallAfter(self._addTermLog, line)
305                                 except:
306                                         pass
307
308         def OnInstall(self):
309                 wx.CallAfter(self.okButton.Disable)
310                 wx.CallAfter(self.updateLabel, _("Reading firmware..."))
311                 hexFile = intelHex.readHex(self.filename)
312                 wx.CallAfter(self.updateLabel, _("Connecting to machine..."))
313                 programmer = stk500v2.Stk500v2()
314                 programmer.progressCallback = self.OnProgress
315                 if self.port == 'AUTO':
316                         wx.CallAfter(self.updateLabel, _("Please connect the printer to your\ncomputer with a USB cable and power it on."))
317                         while not programmer.isConnected():
318                                 for self.port in machineCom.serialList(True):
319                                         try:
320                                                 programmer.connect(self.port)
321                                                 break
322                                         except ispBase.IspError:
323                                                 pass
324                                 time.sleep(1)
325                                 if not self:
326                                         #Window destroyed
327                                         return
328                 else:
329                         try:
330                                 programmer.connect(self.port)
331                         except ispBase.IspError:
332                                 pass
333
334                 if not programmer.isConnected():
335                         wx.CallAfter(self.updateLabel, _("Failed to connect to programmer.\n"))
336                         return
337
338                 wx.CallAfter(self.updateLabel, _("Uploading firmware..."))
339                 try:
340                         programmer.programChip(hexFile)
341                         wx.CallAfter(self.updateLabel, _("Done!\nInstalled firmware: %s") % (os.path.basename(self.filename)))
342                 except ispBase.IspError as e:
343                         wx.CallAfter(self.updateLabel, _("Failed to write firmware.\n") + str(e))
344
345                 programmer.close()
346                 wx.CallAfter(self.okButton.Enable)
347
348         def updateLabel(self, text):
349                 self.progressLabel.SetLabel(text)
350                 self.Layout()
351
352         def OnProgress(self, value, max):
353                 wx.CallAfter(self.progressGauge.SetRange, max)
354                 wx.CallAfter(self.progressGauge.SetValue, value)
355                 taskbar.setProgress(self.GetParent(), value, max)
356
357         def OnOk(self, e):
358                 self.Close()
359                 taskbar.setBusy(self.GetParent(), False)
360
361         def OnClose(self, e):
362                 self.Destroy()
363