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