import wx.lib.newevent try: import thread as _thread except ImportError: import _thread import shlex import subprocess import os import re from os.path import isfile, join from sys import platform if platform.startswith("win"): from subprocess import STARTF_USESHOWWINDOW (scriptEvent, EVT_SCRIPT_UPDATE) = wx.lib.newevent.NewEvent() SCRIPT_RUNNING = 1 SCRIPT_FINISHED = 2 SCRIPT_CANCELLED = 3 TOOLPATHS_INSIDE_ARDUINO = [ "hardware/tools/avr/bin/", "hardware/tools/", ] # avrdude in Arduino 1.0.x if platform.startswith("darwin"): # That's an OS property, the Applicaton Bundle hierarchy. pathsCopy = TOOLPATHS_INSIDE_ARDUINO TOOLPATHS_INSIDE_ARDUINO = [] for path in pathsCopy: TOOLPATHS_INSIDE_ARDUINO.append("Contents/Resources/Java/" + path) TOOLPATHS_INSIDE_ARDUINO.append("Contents/Java/" + path) class ScriptTools: def __init__(self, settings): self.settings = settings def figureCommandPath(self, baseCommand): findConf = False if baseCommand == "avrdude": findConf = True if platform.startswith("win"): baseCommand += ".exe" if self.settings.arduinodir: cmdpath = self.settings.arduinodir for pathOption in TOOLPATHS_INSIDE_ARDUINO: cmdpathTry = cmdpath for dir in pathOption.split("/"): cmdpathTry = os.path.join(cmdpathTry, dir) cmdpathTry = os.path.join(cmdpathTry, baseCommand) if os.path.exists(cmdpathTry): cmdpath = '"' + cmdpathTry + '"' break if findConf: confpath = cmdpath.strip('"') exepos = confpath.rfind(".exe") if exepos >= 0: confpath = confpath[0:exepos] confpath += ".conf" if not os.path.exists(confpath): confpath = os.path.split(confpath)[0] confpath = os.path.split(confpath)[0] confpath = os.path.join(confpath, "etc") confpath = os.path.join(confpath, "avrdude.conf") if os.path.exists(confpath): cmdpath += ' -C "' + confpath + '"' else: cmdpath = baseCommand # No need to search avrdude.conf in this case. return cmdpath class ScriptThread: def __init__(self, win, script): self.win = win self.running = False self.cancelled = False self.script = script def Start(self): self.running = True self.cancelled = False _thread.start_new_thread(self.Run, ()) def Stop(self): self.cancelled = True def IsRunning(self): return self.running def Run(self): if platform.startswith("win"): startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= STARTF_USESHOWWINDOW for cmd in self.script: evt = scriptEvent(msg=cmd, state=SCRIPT_RUNNING) wx.PostEvent(self.win, evt) args = shlex.split(str(cmd)) try: if platform.startswith("win"): p = subprocess.Popen( args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, startupinfo=startupinfo, ) else: p = subprocess.Popen( args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE ) except: evt = scriptEvent( msg="Exception occurred trying to run\n\n%s" % cmd, state=SCRIPT_CANCELLED, ) wx.PostEvent(self.win, evt) self.running = False return obuf = "" while not self.cancelled: o = p.stdout.read(1).decode("utf-8", "ignore") if o == "": break if o == "\r" or o == "\n": if obuf.strip() != "": evt = scriptEvent(msg=obuf, state=SCRIPT_RUNNING) wx.PostEvent(self.win, evt) obuf = "" elif ord(o) < 32: pass else: obuf += o if self.cancelled: evt = scriptEvent(msg=None, state=SCRIPT_CANCELLED) wx.PostEvent(self.win, evt) p.kill() self.running = False p.wait() return rc = p.wait() if rc != 0: msg = "RC = " + str(rc) + " - Build terminated" evt = scriptEvent(msg=msg, state=SCRIPT_CANCELLED) wx.PostEvent(self.win, evt) self.running = False return evt = scriptEvent(msg="", state=SCRIPT_RUNNING) wx.PostEvent(self.win, evt) evt = scriptEvent(msg=None, state=SCRIPT_FINISHED) wx.PostEvent(self.win, evt) self.running = False class Build(wx.Dialog): def __init__(self, parent, settings, f_cpu, cpu): wx.Dialog.__init__( self, parent, wx.ID_ANY, "Build teacup", style=wx.RESIZE_BORDER + wx.DEFAULT_DIALOG_STYLE, ) self.settings = settings self.SetFont(self.settings.font) self.root = self.settings.folder self.f_cpu = f_cpu self.cpu = cpu self.Bind(wx.EVT_CLOSE, self.onExit) self.cancelPending = False hsz = wx.BoxSizer(wx.HORIZONTAL) hsz.Add((10, 10)) sz = wx.BoxSizer(wx.VERTICAL) sz.Add((10, 10)) tc = wx.TextCtrl( self, wx.ID_ANY, size=(900, 300), style=wx.TE_READONLY + wx.TE_MULTILINE ) sz.Add(tc, 1, wx.EXPAND) f = wx.Font(8, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD) tc.SetFont(f) self.log = tc sz.Add((10, 10)) hsz.Add(sz, 1, wx.EXPAND) hsz.Add((10, 10)) self.SetSizer(hsz) self.Fit() builddir = join(self.root, "build") if not os.path.exists(builddir): os.makedirs(builddir) self.log.AppendText("Directory %s created.\n\n" % builddir) self.compile() def compile(self): self.generateCompileScript() if len(self.script) == 0: self.log.AppendText("Nothing to compile!\n") self.active = False else: self.Bind(EVT_SCRIPT_UPDATE, self.compileUpdate) self.t = ScriptThread(self, self.script) self.active = True self.t.Start() def link(self): self.generateLinkScript() if len(self.script) == 0: self.log.AppendText("Nothing to link!\n") self.active = False else: self.Bind(EVT_SCRIPT_UPDATE, self.linkUpdate) t = ScriptThread(self, self.script) self.active = True t.Start() def report(self): self.script = [] self.reportLines = [] cmdpath = ScriptTools(self.settings).figureCommandPath("avr-objdump") elfpath = '"' + join(self.root, "build", "teacup.elf") + '"' cmd = cmdpath + " -h " + elfpath self.script.append(cmd) self.Bind(EVT_SCRIPT_UPDATE, self.reportUpdate) t = ScriptThread(self, self.script) self.active = True t.Start() def generateCompileScript(self): self.script = [] cmdpath = ScriptTools(self.settings).figureCommandPath("avr-gcc") cfiles = [ f for f in os.listdir(self.root) if isfile(join(self.root, f)) and f.endswith(".c") ] for f in cfiles: basename = f[:-2] ofile = basename + ".o" alfile = basename + ".al" opath = '"' + join(self.root, "build", ofile) + '"' alpath = '"' + join(self.root, "build", alfile) + '"' cpath = '"' + join(self.root, f) + '"' opts = self.settings.cflags opts = opts.replace("%ALNAME%", alpath) opts = opts.replace("%F_CPU%", self.f_cpu) opts = opts.replace("%CPU%", self.cpu) cmd = cmdpath + " -c " + opts + " -o " + opath + " " + cpath self.script.append(cmd) def generateLinkScript(self): self.script = [] cmdpath = ScriptTools(self.settings).figureCommandPath("avr-gcc") # This is ugly: # Work around a problem of avr-ld.exe coming with Arduino 1.6.4 for # Windows. Without this it always drops this error message: # collect2.exe: error: ld returned 5 exit status 255 # Just enabling verbose messages allows ld.exe to complete without failure. if platform.startswith("win"): cmdpath += " -Wl,-V" ofiles = [ '"' + join(self.root, "build", f) + '"' for f in os.listdir(join(self.root, "build")) if isfile(join(self.root, "build", f)) and f.endswith(".o") ] opath = " ".join(ofiles) elfpath = '"' + join(self.root, "build", "teacup.elf") + '"' hexpath = '"' + join(self.root, "teacup.hex") + '"' opts = self.settings.cflags opts = opts.replace("%ALNAME%", "teacup.elf") opts = opts.replace("%F_CPU%", self.f_cpu) opts = opts.replace("%CPU%", self.cpu) cmd = ( cmdpath + " " + self.settings.ldflags + " " + opts + " -o " + elfpath + " " + opath + " -lm" ) self.script.append(cmd) cmdpath = ScriptTools(self.settings).figureCommandPath("avr-objcopy") cmd = cmdpath + " " + self.settings.objcopyflags + " " + elfpath + " " + hexpath self.script.append(cmd) def compileUpdate(self, evt): if evt.msg is not None: self.log.AppendText(evt.msg + "\n") if evt.state == SCRIPT_RUNNING: pass if evt.state == SCRIPT_CANCELLED: self.active = False if self.cancelPending: self.EndModal(wx.ID_OK) self.log.AppendText("Build terminated abnormally.\n") if evt.state == SCRIPT_FINISHED: self.log.AppendText("Compile completed normally.\n\n") self.link() def linkUpdate(self, evt): if evt.msg is not None: self.log.AppendText(evt.msg + "\n") if evt.state == SCRIPT_RUNNING: pass if evt.state == SCRIPT_CANCELLED: self.log.AppendText("Link terminated abnormally.\n") self.active = False if evt.state == SCRIPT_FINISHED: self.log.AppendText("Link completed normally.\n") self.report() def reportUpdate(self, evt): if evt.state == SCRIPT_RUNNING: if evt.msg is not None: self.reportLines.append(evt.msg) if evt.state == SCRIPT_CANCELLED: self.log.AppendText(evt.msg + "\n") self.log.AppendText("Report terminated abnormally.\n") self.active = False if evt.state == SCRIPT_FINISHED: self.formatReport() self.log.AppendText("\nBuild completed normally.\n") self.active = False def formatReportLine(self, m, name, v168, v328, v644, v1280): t = m.groups() v = int(t[0], 16) self.log.AppendText( ("%12s: %6d bytes %6.2f%% %6.2f%%" " %6.2f%% %6.2f%%\n") % ( name, v, v / float(v168 * 1024) * 100.0, v / float(v328 * 1024) * 100.0, v / float(v644 * 1024) * 100.0, v / float(v1280 * 1024) * 100.0, ) ) def formatReport(self): reText = re.compile("\.text\s+([0-9a-f]+)") reBss = re.compile("\.bss\s+([0-9a-f]+)") reEEProm = re.compile("\.eeprom\s+([0-9a-f]+)") self.log.AppendText( "\n ATmega... '168 '328(P)" " '644(P) '1280\n" ) for l in self.reportLines: m = reText.search(l) if m: self.formatReportLine(m, "FLASH", 14, 30, 62, 126) else: m = reBss.search(l) if m: self.formatReportLine(m, "RAM", 1, 2, 4, 8) else: m = reEEProm.search(l) if m: self.formatReportLine(m, "EEPROM", 1, 2, 2, 4) def onExit(self, evt): if self.active: dlg = wx.MessageDialog( self, "Are you sure you want to cancel building?", "Build active", wx.YES_NO | wx.NO_DEFAULT | wx.ICON_INFORMATION, ) rc = dlg.ShowModal() dlg.Destroy() if rc == wx.ID_YES: self.cancelPending = True self.log.AppendText("Cancelling...\n") self.t.Stop() return self.EndModal(wx.ID_OK) class Upload(wx.Dialog): def __init__(self, parent, settings, f_cpu, cpu): wx.Dialog.__init__( self, parent, wx.ID_ANY, "Upload teacup", style=wx.RESIZE_BORDER + wx.DEFAULT_DIALOG_STYLE, ) self.settings = settings self.SetFont(self.settings.font) self.root = self.settings.folder self.f_cpu = f_cpu self.cpu = cpu self.baud = self.settings.uploadspeed self.Bind(wx.EVT_CLOSE, self.onExit) self.cancelPending = False hsz = wx.BoxSizer(wx.HORIZONTAL) hsz.Add((10, 10)) sz = wx.BoxSizer(wx.VERTICAL) sz.Add((10, 10)) tc = wx.TextCtrl( self, wx.ID_ANY, size=(900, 300), style=wx.TE_READONLY + wx.TE_MULTILINE ) sz.Add(tc, 1, wx.EXPAND) f = wx.Font(8, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD) tc.SetFont(f) self.log = tc sz.Add((10, 10)) hsz.Add(sz, 1, wx.EXPAND) hsz.Add((10, 10)) self.SetSizer(hsz) self.Fit() self.generateUploadScript() if len(self.script) == 0: self.log.AppendText("Nothing to upload!\n") self.active = False else: self.Bind(EVT_SCRIPT_UPDATE, self.uploadUpdate) self.t = ScriptThread(self, self.script) self.active = True self.t.Start() def generateUploadScript(self): self.script = [] cmdpath = ScriptTools(self.settings).figureCommandPath("avrdude") hexpath = '"' + join(self.root, "teacup.hex") + '"' cmd = cmdpath + " -c %s %s -b %s -p %s -P %s -U flash:w:%s:i" % ( self.settings.programmer, self.settings.programflags, self.baud, self.cpu, self.settings.port, hexpath, ) self.script.append(cmd) def uploadUpdate(self, evt): if evt.msg is not None: self.log.AppendText(evt.msg + "\n") if evt.state == SCRIPT_RUNNING: pass if evt.state == SCRIPT_CANCELLED: self.active = False if self.cancelPending: self.EndModal(wx.ID_OK) self.log.AppendText("Upload terminated abnormally.\n") if evt.state == SCRIPT_FINISHED: self.log.AppendText("Upload completed normally.\n") self.active = False def onExit(self, evt): if self.active: dlg = wx.MessageDialog( self, "Are you sure you want to cancel upload?", "Upload active", wx.YES_NO | wx.NO_DEFAULT | wx.ICON_INFORMATION, ) rc = dlg.ShowModal() dlg.Destroy() if rc == wx.ID_YES: self.cancelPending = True self.log.AppendText("Cancelling...\n") self.t.Stop() return self.EndModal(wx.ID_OK)