From b9f524d256bc2d3366a5d5c64770d81d7a1ce5b1 Mon Sep 17 00:00:00 2001 From: jbernardis Date: Mon, 19 Jan 2015 22:56:44 -0500 Subject: [PATCH] Configtool: add the ability to build and upload. --- config.py | 247 +++++++++++++++++++++++++---- config/board.gen7-v1.4.h | 33 ++-- config/board.ramps-v1.3.h | 26 +++- config/config-syntax.txt | 39 +++-- configtool.default.ini | 38 +++++ configtool/boardpanel.py | 127 ++++++++------- configtool/build.py | 309 +++++++++++++++++++++++++++++++++++++ configtool/cpupage.py | 60 +++---- configtool/data.py | 6 +- configtool/printerpanel.py | 32 +++- configtool/settings.py | 47 ++++++ 11 files changed, 781 insertions(+), 183 deletions(-) create mode 100644 configtool.default.ini create mode 100644 configtool/build.py create mode 100644 configtool/settings.py diff --git a/config.py b/config.py index 41b2e55..305b011 100755 --- a/config.py +++ b/config.py @@ -7,8 +7,10 @@ import inspect cmd_folder = os.path.realpath(os.path.abspath(os.path.split(inspect.getfile( inspect.currentframe()))[0])) +from configtool.settings import Settings from configtool.printerpanel import PrinterPanel from configtool.boardpanel import BoardPanel +from configtool.build import Build, Upload from configtool.data import VERSION, reInclude ID_LOAD_PRINTER = 1000 @@ -20,6 +22,8 @@ ID_SAVE_BOARD_AS = 1012 ID_LOAD_CONFIG = 1020 ID_LOAD_DEFAULT = 1021 ID_SAVE_CONFIG = 1022 +ID_BUILD = 1030 +ID_UPLOAD = 1031 class ConfigFrame(wx.Frame): @@ -29,11 +33,13 @@ class ConfigFrame(wx.Frame): size = (880, 550)) self.Bind(wx.EVT_CLOSE, self.onClose) - self.font = wx.Font(8, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, - wx.FONTWEIGHT_BOLD) - panel = wx.Panel(self, -1) + self.settings = Settings(self, cmd_folder) + self.settings.font = wx.Font(8, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, + wx.FONTWEIGHT_BOLD) + self.settings.folder = cmd_folder + self.heaters = [] self.savePrtEna = False self.saveBrdEna = False @@ -42,13 +48,19 @@ class ConfigFrame(wx.Frame): self.nb = wx.Notebook(panel, wx.ID_ANY, size = (880, 550), style = wx.BK_DEFAULT) - self.nb.SetFont(self.font) + self.nb.SetFont(self.settings.font) - self.pgPrinter = PrinterPanel(self, self.nb, self.font, cmd_folder) - self.nb.AddPage(self.pgPrinter, "Printer") + self.printerFileName = None + self.printerTabDecor = "" + self.printerBaseText = "Printer" + self.pgPrinter = PrinterPanel(self, self.nb, self.settings) + self.nb.AddPage(self.pgPrinter, self.printerBaseText) - self.pgBoard = BoardPanel(self, self.nb, self.font, cmd_folder) - self.nb.AddPage(self.pgBoard, "Board") + self.boardFileName = None + self.boardTabDecor = "" + self.boardBaseText = "Board" + self.pgBoard = BoardPanel(self, self.nb, self.settings) + self.nb.AddPage(self.pgBoard, self.boardBaseText) panel.Fit() @@ -65,10 +77,32 @@ class ConfigFrame(wx.Frame): self.Destroy() - def setPrinterTabText(self, txt): + def setPrinterTabFile(self, fn): + self.printerFileName = fn + self.updatePrinterTab() + + def setPrinterTabDecor(self, prefix): + self.printerTabDecor = prefix + self.updatePrinterTab() + + def updatePrinterTab(self): + txt = self.printerTabDecor + self.printerBaseText + if self.printerFileName: + txt += " <%s>" % self.printerFileName self.nb.SetPageText(0, txt) - def setBoardTabText(self, txt): + def setBoardTabFile(self, fn): + self.boardFileName = fn + self.updateBoardTab() + + def setBoardTabDecor(self, prefix): + self.boardTabDecor = prefix + self.updateBoardTab() + + def updateBoardTab(self): + txt = self.boardTabDecor + self.boardBaseText + if self.boardFileName: + txt += " <%s>" % self.boardFileName self.nb.SetPageText(1, txt) def setHeaters(self, ht): @@ -81,10 +115,12 @@ class ConfigFrame(wx.Frame): file_menu.Append(ID_LOAD_CONFIG, "Load config.h", "Load config.h and its named printer and board files.") self.Bind(wx.EVT_MENU, self.onLoadConfig, id = ID_LOAD_CONFIG) + file_menu.Enable(ID_LOAD_CONFIG, False) file_menu.Append(ID_LOAD_DEFAULT, "Load default", "Load default config.h and its named printer and board files.") self.Bind(wx.EVT_MENU, self.onLoadDefault, id = ID_LOAD_DEFAULT) + file_menu.Enable(ID_LOAD_DEFAULT, False) file_menu.Append(ID_SAVE_CONFIG, "Save config.h", "Save config.h file.") self.Bind(wx.EVT_MENU, self.onSaveConfig, id = ID_SAVE_CONFIG) @@ -98,13 +134,12 @@ class ConfigFrame(wx.Frame): file_menu.Append(ID_SAVE_PRINTER, "Save printer", "Save printer configuration.") - self.Bind(wx.EVT_MENU, self.pgPrinter.onSaveConfig, id = ID_SAVE_PRINTER) + self.Bind(wx.EVT_MENU, self.onSavePrinterConfig, id = ID_SAVE_PRINTER) file_menu.Enable(ID_SAVE_PRINTER, False) file_menu.Append(ID_SAVE_PRINTER_AS, "Save printer as...", "Save printer configuration to a new file.") - self.Bind(wx.EVT_MENU, self.pgPrinter.onSaveConfigAs, - id = ID_SAVE_PRINTER_AS) + self.Bind(wx.EVT_MENU, self.onSavePrinterConfigAs, id = ID_SAVE_PRINTER_AS) file_menu.Enable(ID_SAVE_PRINTER_AS, False) file_menu.AppendSeparator() @@ -114,12 +149,12 @@ class ConfigFrame(wx.Frame): self.Bind(wx.EVT_MENU, self.pgBoard.onLoadConfig, id = ID_LOAD_BOARD) file_menu.Append(ID_SAVE_BOARD, "Save board", "Save board configuration.") - self.Bind(wx.EVT_MENU, self.pgBoard.onSaveConfig, id = ID_SAVE_BOARD) + self.Bind(wx.EVT_MENU, self.onSaveBoardConfig, id = ID_SAVE_BOARD) file_menu.Enable(ID_SAVE_BOARD, False) file_menu.Append(ID_SAVE_BOARD_AS, "Save board as...", "Save board configuration to a new file.") - self.Bind(wx.EVT_MENU, self.pgBoard.onSaveConfigAs, id = ID_SAVE_BOARD_AS) + self.Bind(wx.EVT_MENU, self.onSaveBoardConfigAs, id = ID_SAVE_BOARD_AS) file_menu.Enable(ID_SAVE_BOARD_AS, False) file_menu.AppendSeparator() @@ -133,7 +168,59 @@ class ConfigFrame(wx.Frame): menu_bar.Append(file_menu, "&File") + build_menu = wx.Menu() + + build_menu.Append(ID_BUILD, "Build", "Build the executable.") + self.Bind(wx.EVT_MENU, self.onBuild, id = ID_BUILD) + + build_menu.Append(ID_UPLOAD, "Upload", "Upload the executable.") + self.Bind(wx.EVT_MENU, self.onUpload, id = ID_UPLOAD) + + self.buildMenu = build_menu + + menu_bar.Append(build_menu, "&Build") + self.SetMenuBar(menu_bar) + self.checkEnableLoadConfig() + self.checkEnableUpload() + + def onSaveBoardConfig(self, evt): + self.pgBoard.onSaveConfig(evt) + self.checkEnableLoadConfig() + + def onSaveBoardConfigAs(self, evt): + self.pgBoard.onSaveConfigAs(evt) + self.checkEnableLoadConfig() + + def onSavePrinterConfig(self, evt): + self.pgPrinter.onSaveConfig(evt) + self.checkEnableLoadConfig() + + def onSavePrinterConfigAs(self, evt): + self.pgPrinter.onSaveConfigAs(evt) + self.checkEnableLoadConfig() + + def checkEnableLoadConfig(self): + fn = os.path.join(cmd_folder, "config.h") + if os.path.isfile(fn): + self.fileMenu.Enable(ID_LOAD_CONFIG, True) + self.buildMenu.Enable(ID_BUILD, True) + else: + self.fileMenu.Enable(ID_LOAD_CONFIG, False) + self.buildMenu.Enable(ID_BUILD, False) + + fn = os.path.join(cmd_folder, "config.default.h") + if os.path.isfile(fn): + self.fileMenu.Enable(ID_LOAD_DEFAULT, True) + else: + self.fileMenu.Enable(ID_LOAD_DEFAULT, False) + + def checkEnableUpload(self): + fn = os.path.join(cmd_folder, "teacup.hex") + if os.path.isfile(fn): + self.buildMenu.Enable(ID_UPLOAD, True) + else: + self.buildMenu.Enable(ID_UPLOAD, False) def enableSavePrinter(self, flag): self.fileMenu.Enable(ID_SAVE_PRINTER, flag) @@ -168,6 +255,23 @@ class ConfigFrame(wx.Frame): if not self.pgBoard.confirmLoseChanges("load config"): return + pfile, bfile = self.getConfigFileNames(fn) + + if not pfile: + self.message("Config file did not contain a printer file " + "include statement.", "Config error") + return + + if not bfile: + self.message("Config file did not contain a board file " + "include statement.", "Config error") + return + + self.pgPrinter.loadConfigFile(pfile) + + self.pgBoard.loadConfigFile(bfile) + + def getConfigFileNames(self, fn): pfile = None bfile = None path = os.path.join(cmd_folder, fn) @@ -203,19 +307,7 @@ class ConfigFrame(wx.Frame): self.message("Unable to parse include statement:\n%s" % ln, "Config error") - if not pfile: - self.message("Config file did not contain a printer file include " - "statement.", "Config error") - return - - if not bfile: - self.message("Config file did not contain a board file include " - "statement.", "Config error") - return - - self.pgPrinter.loadConfigFile(pfile) - - self.pgBoard.loadConfigFile(bfile) + return pfile, bfile def onSaveConfig(self, evt): fn = os.path.join(cmd_folder, "config.h") @@ -263,6 +355,105 @@ class ConfigFrame(wx.Frame): "%s successfully saved.\nconfig.h successfully saved.") % (rbfn, rpfn) self.message(m, "Save configuration success", wx.OK + wx.ICON_INFORMATION) + self.checkEnableLoadConfig() + + def onBuild(self, evt): + self.onBuildorUpload(True) + + def onUpload(self, evt): + self.onBuildorUpload(False) + + def onBuildorUpload(self, buildFlag): + if not (self.pgPrinter.hasData() or self.pgBoard.hasData()): + dlg = wx.MessageDialog(self, "Data needs to be loaded. " + "Click Yes to load config.h.", + "Data missing", + wx.YES_NO | wx.NO_DEFAULT | wx.ICON_INFORMATION) + rc = dlg.ShowModal() + dlg.Destroy() + if rc != wx.ID_YES: + return + + self.loadConfigFile("config.h") + else: + if self.pgPrinter.isModified(): + dlg = wx.MessageDialog(self, "Printer data needs to be saved. Click " + "Yes to save printer configuration.", + "Changes pending", + wx.YES_NO | wx.NO_DEFAULT | wx.ICON_INFORMATION) + rc = dlg.ShowModal() + dlg.Destroy() + if rc != wx.ID_YES: + return + self.onSavePrinterConfig(None) + + if self.pgBoard.isModified(): + dlg = wx.MessageDialog(self, "Board data needs to be saved. Click " + "Yes to save board configuration.", + "Changes pending", + wx.YES_NO | wx.NO_DEFAULT | wx.ICON_INFORMATION) + rc = dlg.ShowModal() + dlg.Destroy() + if rc != wx.ID_YES: + return + self.onSaveBoardConfig(None) + + if not self.verifyConfigLoaded(): + dlg = wx.MessageDialog(self, "Loaded configuration does not match what " + "the config.h file. Click Yes to load " + "config.h.", + "Incorrect data loaded", + wx.YES_NO | wx.NO_DEFAULT | wx.ICON_INFORMATION) + rc = dlg.ShowModal() + dlg.Destroy() + if rc != wx.ID_YES: + return + + self.loadConfigFile("config.h") + + f_cpu, cpu, baud = self.pgBoard.getCPUInfo() + if not cpu: + dlg = wx.MessageDialog(self, "Unable to determine CPU type.", + "CPU type error", wx.OK | wx.ICON_ERROR) + dlg.ShowModal() + dlg.Destroy() + return + if not f_cpu: + dlg = wx.MessageDialog(self, "Unable to determine CPU clock rate.", + "CPU clock rate error", wx.OK | wx.ICON_ERROR) + dlg.ShowModal() + dlg.Destroy() + return + if not baud: + # TODO: It looks like serial port baud rate is confused with bootloader + # baud rate here. These two can be the same, but don't have to. + # Bootloader baud rate isn't user selectable, it's a property of + # the bootloader and can be changed only by overwriting the + # bootloader. + dlg = wx.MessageDialog(self, "Unable to determine CPU baud rate.", + "CPU baud rate error", wx.OK | wx.ICON_ERROR) + dlg.ShowModal() + dlg.Destroy() + return + + if buildFlag: + # TODO: building the executable needs no baud rate. + dlg = Build(self, self.settings, f_cpu, cpu, baud) + dlg.ShowModal() + dlg.Destroy() + self.checkEnableUpload() + else: + dlg = Upload(self, self.settings, f_cpu, cpu, baud) + dlg.ShowModal() + dlg.Destroy() + + def verifyConfigLoaded(self): + pfile, bfile = self.getConfigFileNames("config.h") + lpfile = self.pgPrinter.getFileName() + lbfile = self.pgBoard.getFileName() + + return ((pfile == lpfile) and (bfile == lbfile)) + def message(self, text, title, style = wx.OK + wx.ICON_ERROR): dlg = wx.MessageDialog(self, text, title, style) dlg.ShowModal() diff --git a/config/board.gen7-v1.4.h b/config/board.gen7-v1.4.h index 71758d9..07bbea9 100644 --- a/config/board.gen7-v1.4.h +++ b/config/board.gen7-v1.4.h @@ -5,20 +5,29 @@ * * \***************************************************************************/ -//PROCESSORS_START -#ifndef __AVR_ATmega644__ - #ifndef __AVR_ATmega644P__ - #ifndef __AVR_ATmega1284__ - #ifndef __AVR_ATmega1284P__ - #error Wrong CPU type. - #endif - #endif - #endif -#endif -//PROCESSORS_END +/** \def CPU_TYPE + CPU types a user should be able to choose from in configtool. All + commented out. +*/ +//#define CPU_TYPE atmega644 +//#define CPU_TYPE atmega644p +//#define CPU_TYPE atmega1284 +//#define CPU_TYPE atmega1284p + +/** \def CPU + CPU actually present on the board. +*/ +#define CPU atmega1284p + +/** \def F_CPU_OPT + CPU clock frequencies a user should be able to choose from in configtool. + All commented out. +*/ +//#define F_CPU_OPT 16000000UL +//#define F_CPU_OPT 20000000UL /** \def F_CPU - CPU clock rate. #ifndef required for Arduino compatibility. + Actual CPU clock rate. #ifndef required for Arduino compatibility. */ #ifndef F_CPU #define F_CPU 20000000UL diff --git a/config/board.ramps-v1.3.h b/config/board.ramps-v1.3.h index f902dc0..3d6bd11 100644 --- a/config/board.ramps-v1.3.h +++ b/config/board.ramps-v1.3.h @@ -5,16 +5,26 @@ * * \***************************************************************************/ -//PROCESSORS_START -#ifndef __AVR_ATmega1280__ - #ifndef __AVR_ATmega2560__ - #error Wrong CPU type. - #endif -#endif -//PROCESSORS_END +/** \def CPU_TYPE + CPU types a user should be able to choose from in configtool. All + commented out. +*/ +//#define CPU_TYPE atmega1280 +//#define CPU_TYPE atmega2560 + +/** \def CPU + CPU actually present on the board. +*/ +#define CPU atmega2560 + +/** \def F_CPU_OPT + CPU clock frequencies a user should be able to choose from in configtool. + All commented out. +*/ +//#define F_CPU_OPT 16000000UL /** \def F_CPU - CPU clock rate. #ifndef required for Arduino compatibility. + Actual CPU clock rate. #ifndef required for Arduino compatibility. */ #ifndef F_CPU #define F_CPU 16000000UL diff --git a/config/config-syntax.txt b/config/config-syntax.txt index 93106f6..fdeea89 100644 --- a/config/config-syntax.txt +++ b/config/config-syntax.txt @@ -67,35 +67,32 @@ The start of a help text block is as follows: All subsequent lines are copied exactly as entered into the help text until the end delimiter is reached. The end delimiter is a line that starts with '*/'. -Note that it is possible to specify multiple names on the start line. This -allows identical help text for similar or related fields. +The names are the actual #define variable names and are used to associate the +help text with a specific field. Note that it is possible to specify multiple +names on the start line. This allows identical help text for similar or related +fields. Processor Preamble - Board file ------------------------------- -The utility parses out the processor types from the config file when it loads -it, and it provides an interface where processors may be added or removed. -When a file is saved, the utility needs to know 1) where to insert the new -processor preamble, and 2) what to remove from the old file. To achieve this, -there is a sequence that identifies the start of the processor preamble, and -a sequence that indicates the end. As follows: +The various CPU_TYPE lines define the options that the user can select from +for CPU type. These must be legal values acceptable to avr-gcc for the -MMCU +parameter. The CPU line is the currently chosen value from these options. -//PROCESSORS_START -#ifndef __AVR_ATmega1280__ - #ifndef __AVR_ATmega2560__ - #error Wrong CPU type. - #endif -#endif -//PROCESSORS_END +Example: -when a file is saved, all of the lines between these two delimiters are removed -and a new preamble is generated based on the values from the screen. The -supported processor types are defined in a python list in the data.py file. As -additional processors are supported, then can be added to this list. +//#define CPU_TYPE atmega1280 +//#define CPU_TYPE atmega2560 +#define CPU atmega2560 -If the START/END delimiters are not present, then the processor preamble will -not be generated and the content from the original file will be retained. +There can be multiple F_CPU_OPT lines specifying the various CPU clock values. +The final F_CPU line shows the currently chose value from the options. + +Example: + +//#define F_CPU_OPT 16000000UL +#define F_CPU 16000000UL Temperature Sensor Considerations - Board file diff --git a/configtool.default.ini b/configtool.default.ini new file mode 100644 index 0000000..5409825 --- /dev/null +++ b/configtool.default.ini @@ -0,0 +1,38 @@ + +[configtool] + +# Where to find the arduino tools (avr-gcc, avrdude, etc). This is only used +# for windows. For linux it is assumed that the tools are available through +# the normal PATH. +arduinodir = C:/Program Files (x86)/Arduino/hardware/tools/avr/bin + +# Flags passed into the avr-gcc compiler. These flags can have 3 different +# variabled embedded within them: +# +# %F_CPU% will be replaced by the value of the CPU Clock Rate entered +# through the GUI. +# +# %CPU% will be replaced by the value of the CPU entered through the GUI. +# +# %ALNAME% is the name of the source file being compiled with the .c +# extension replaced by .al. +# +# Note: the flag -save-temps=obj does not appear to be a valid flag for win32. +# Omit the "=obj", or omit it entirely. +cflags = -DF_CPU=%F_CPU% -mmcu=%CPU% -Wall -Wstrict-prototypes -std=gnu99 + -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums + -Winline -fno-move-loop-invariants -fno-tree-scev-cprop -Os + -ffunction-sections -finline-functions-called-once -mcall-prologues + -Wa,-adhlns=build/%ALNAME% + +# Flags passed to avr-gcc to be passed on to the linker. +ldflags = -Wl,--as-needed -Wl,--gc-sections + +# Flags passed to avr-objcopy. +objcopyflags = -j .text -j .data -O ihex -R .eeprom -R .fuse -R .lock + +# The programmer type - passed to avrdude. +programmer = stk500v2 + +# The port through which the firmware will be uploaded - passed to avrdude. +port = /dev/ttyACM0 diff --git a/configtool/boardpanel.py b/configtool/boardpanel.py index fc915e0..a20529e 100644 --- a/configtool/boardpanel.py +++ b/configtool/boardpanel.py @@ -3,13 +3,13 @@ import os import wx import re -from configtool.data import (supportedCPUs, defineValueFormat, +from configtool.data import (defineValueFormat, defineBoolFormat, defineHeaterFormat, reCommDefBL, reCommDefBoolBL, reHelpTextStart, reHelpTextEnd, reStartSensors, reEndSensors, reStartHeaters, - reEndHeaters, reStartProcessors, reEndProcessors, - reCandHeatPins, reCandThermPins, reFloatAttr, - reAVR, reDefine, reDefineBL, reDefQS, reDefQSm, + reEndHeaters, reCandHeatPins, reCandThermPins, + reCandProcessors, reCandCPUClocks, reFloatAttr, + reDefine, reDefineBL, reDefQS, reDefQSm, reDefQSm2, reDefBool, reDefBoolBL, reDefHT, reDefTS, reHeater, reSensor3, reSensor4) from configtool.pinoutspage import PinoutsPage @@ -20,32 +20,32 @@ from configtool.cpupage import CpuPage class BoardPanel(wx.Panel): - def __init__(self, parent, nb, font, folder): + def __init__(self, parent, nb, settings): wx.Panel.__init__(self, nb, wx.ID_ANY) self.parent = parent + self.settings = settings self.configFile = None self.cfgValues = {} self.heaters = [] self.sensors = [] - self.processors = [] self.candHeatPins = [] self.candThermPins = [] - self.dir = os.path.join(folder, "config") + self.dir = os.path.join(self.settings.folder, "config") sz = wx.BoxSizer(wx.HORIZONTAL) self.nb = wx.Notebook(self, wx.ID_ANY, size = (21, 21), style = wx.BK_DEFAULT) - self.nb.SetFont(font) + self.nb.SetFont(self.settings.font) self.pages = [] self.titles = [] self.pageModified = [] self.pageValid = [] - self.pgCpu = CpuPage(self, self.nb, len(self.pages), font) + self.pgCpu = CpuPage(self, self.nb, len(self.pages), self.settings.font) text = "CPU" self.nb.AddPage(self.pgCpu, text) self.pages.append(self.pgCpu) @@ -53,7 +53,8 @@ class BoardPanel(wx.Panel): self.pageModified.append(False) self.pageValid.append(True) - self.pgPins = PinoutsPage(self, self.nb, len(self.pages), font) + self.pgPins = PinoutsPage(self, self.nb, len(self.pages), + self.settings.font) text = "Pinouts" self.nb.AddPage(self.pgPins, text) self.pages.append(self.pgPins) @@ -61,7 +62,8 @@ class BoardPanel(wx.Panel): self.pageModified.append(False) self.pageValid.append(True) - self.pgSensors = SensorsPage(self, self.nb, len(self.pages), font) + self.pgSensors = SensorsPage(self, self.nb, len(self.pages), + self.settings.font) text = "Temperature Sensors" self.nb.AddPage(self.pgSensors, text) self.pages.append(self.pgSensors) @@ -69,7 +71,8 @@ class BoardPanel(wx.Panel): self.pageModified.append(False) self.pageValid.append(True) - self.pgHeaters = HeatersPage(self, self.nb, len(self.pages), font) + self.pgHeaters = HeatersPage(self, self.nb, len(self.pages), + self.settings.font) text = "Heaters" self.nb.AddPage(self.pgHeaters, text) self.pages.append(self.pgHeaters) @@ -78,7 +81,7 @@ class BoardPanel(wx.Panel): self.pageValid.append(True) self.pgCommunications = CommunicationsPage(self, self.nb, len(self.pages), - font) + self.settings.font) text = "Communications" self.nb.AddPage(self.pgCommunications, text) self.pages.append(self.pgCommunications) @@ -91,6 +94,23 @@ class BoardPanel(wx.Panel): self.SetSizer(sz) self.Fit() + def getCPUInfo(self): + vF_CPU = None + if 'F_CPU' in self.cfgValues.keys(): + vF_CPU = self.cfgValues['F_CPU'] + + vCPU = None + if 'CPU' in self.cfgValues.keys(): + vCPU = self.cfgValues['CPU'] + + # TODO: this is probably obsolete, because the build process doesn't need + # the firmware baud rate, but the bootloader baud rate. + vBaud = None + if 'BAUD' in self.cfgValues.keys(): + vBaud = self.cfgValues['BAUD'] + + return vF_CPU, vCPU, vBaud + def assertModified(self, pg, flag = True): self.pageModified[pg] = flag self.modifyTab(pg) @@ -98,6 +118,9 @@ class BoardPanel(wx.Panel): def isModified(self): return (True in self.pageModified) + def hasData(self): + return (self.configFile != None) + def getFileName(self): return self.configFile @@ -121,6 +144,15 @@ class BoardPanel(wx.Panel): pfx = "" self.nb.SetPageText(pg, pfx + self.titles[pg]) + if True in self.pageModified and False in self.pageValid: + pfx = "?* " + elif True in self.pageModified: + pfx = "* " + elif False in self.pageValid: + pfx = "? " + else: + pfx = "" + self.parent.setBoardTabDecor(pfx) def setHeaters(self, ht): self.parent.setHeaters(ht) @@ -190,6 +222,8 @@ class BoardPanel(wx.Panel): self.heaters = [] self.candHeatPins = [] self.candThermPins = [] + self.candProcessors = [] + self.candClocks = [] gatheringHelpText = False helpTextString = "" helpKey = None @@ -242,33 +276,20 @@ class BoardPanel(wx.Panel): self.candHeatPins.append(t[0]) continue - continue + m = reCandProcessors.match(ln) + if m: + t = m.groups() + if len(t) == 1: + self.candProcessors.append(t[0]) + continue - if ln.lstrip().startswith("#if"): - m = re.findall(reAVR, ln) - inv = [] - for p in m: - if p in supportedCPUs: - self.processors.append(p) - else: - inv.append(p) - if len(inv) > 0: - if len(inv) == 1: - a = " is" - b = "it" - else: - a = "s are" - b = "them" - dlg = wx.MessageDialog(self, - ("The following processor type%s not " - "supported:\n %s\nPlease add %s to " - "\"supportedCPUs\".") % - (a, ", ".join(inv), b), - "Unsupported processor type", - wx.OK + wx.ICON_INFORMATION) + m = reCandCPUClocks.match(ln) + if m: + t = m.groups() + if len(t) == 1: + self.candClocks.append(t[0]) + continue - dlg.ShowModal() - dlg.Destroy() continue if ln.lstrip().startswith("#define"): @@ -322,9 +343,11 @@ class BoardPanel(wx.Panel): continue self.parent.enableSaveBoard(True) - self.parent.setBoardTabText("Board <%s>" % os.path.basename(fn)) + self.parent.setBoardTabFile(os.path.basename(fn)) self.pgHeaters.setCandidatePins(self.candHeatPins) self.pgSensors.setCandidatePins(self.candThermPins) + self.pgCpu.setCandidateProcessors(self.candProcessors) + self.pgCpu.setCandidateClocks(self.candClocks) for pg in self.pages: pg.insertValues(self.cfgValues) @@ -332,7 +355,6 @@ class BoardPanel(wx.Panel): self.pgSensors.setSensors(self.sensors) self.pgHeaters.setHeaters(self.heaters) - self.pgCpu.setProcessors(self.processors) return True @@ -415,11 +437,8 @@ class BoardPanel(wx.Panel): for k in v1.keys(): values[k] = v1[k] - self.processors = self.pgCpu.getProcessors() - skipToSensorEnd = False skipToHeaterEnd = False - skipToProcessorEnd = False for ln in self.cfgBuffer: m = reStartSensors.match(ln) if m: @@ -457,26 +476,6 @@ class BoardPanel(wx.Panel): skipToHeaterEnd = False continue - m = reStartProcessors.match(ln) - if m: - fp.write(ln) - for i in range(len(self.processors)): - fp.write("%s#ifndef __AVR_%s__\n" % (i * " ", self.processors[i])) - fp.write("%s#error Wrong CPU type.\n" % ((i + 1) * " ")) - for s in self.processors: - fp.write("%s#endif\n" % (i * " ")) - i -= 1 - - skipToProcessorEnd = True - continue - - if skipToProcessorEnd: - m = reEndProcessors.match(ln) - if m: - fp.write(ln) - skipToProcessorEnd = False - continue - m = reDefineBL.match(ln) if m: t = m.groups() @@ -525,6 +524,6 @@ class BoardPanel(wx.Panel): fp.close() - self.parent.setBoardTabText("Board <%s>" % os.path.basename(path)) + self.parent.setBoardTabFile(os.path.basename(path)) return True diff --git a/configtool/build.py b/configtool/build.py new file mode 100644 index 0000000..94750be --- /dev/null +++ b/configtool/build.py @@ -0,0 +1,309 @@ + +import wx +import wx.lib.newevent +import thread, shlex, subprocess +import os +from os.path import isfile, join +from sys import platform + +if platform == "win32": + from _subprocess import STARTF_USESHOWWINDOW + +(scriptEvent, EVT_SCRIPT_UPDATE) = wx.lib.newevent.NewEvent() +SCRIPT_RUNNING = 1 +SCRIPT_FINISHED = 2 +SCRIPT_CANCELLED = 3 + + +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 == "win32": + 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 == "win32": + 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) + 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 + 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, baud): + 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.baud = baud + self.Bind(wx.EVT_CLOSE, self.onExit) + + hsz = wx.BoxSizer(wx.HORIZONTAL) + hsz.AddSpacer((10, 10)) + + sz = wx.BoxSizer(wx.VERTICAL) + sz.AddSpacer((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) + self.log = tc + + sz.AddSpacer((10, 10)) + hsz.Add(sz, 1, wx.EXPAND) + hsz.AddSpacer((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) + t = ScriptThread(self, self.script) + self.active = True + 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 generateCompileScript(self): + self.script = [] + if platform == "win32": + cmdpath = "\"" + join(self.settings.arduinodir, "avr-gcc") + "\"" + else: + cmdpath = "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) + "\"" + cpath = "\"" + join(self.root, f) + "\"" + + opts = self.settings.cflags + opts = opts.replace("%ALNAME%", alfile) + 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 = [] + if platform == "win32": + cmdpath = "\"" + join(self.settings.arduinodir, "avr-gcc") + "\"" + else: + cmdpath = "avr-gcc" + + 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") + "\"" + 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) + + if platform == "win32": + cmdpath = "\"" + join(self.settings.arduinodir, "avr-objcopy") + "\"" + else: + cmdpath = "avr-objcopy" + cmd = cmdpath + " " + self.settings.objcopyflags + " " + elfpath + \ + " teacup.hex" + 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.log.AppendText("Compile terminated abnormally.\n\n") + self.active = False + 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.active = False + + def onExit(self, evt): + if self.active: + return + + self.EndModal(wx.ID_OK) + + +class Upload(wx.Dialog): + def __init__(self, parent, settings, f_cpu, cpu, baud): + 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 = baud + self.Bind(wx.EVT_CLOSE, self.onExit) + + hsz = wx.BoxSizer(wx.HORIZONTAL) + hsz.AddSpacer((10, 10)) + + sz = wx.BoxSizer(wx.VERTICAL) + sz.AddSpacer((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) + self.log = tc + + sz.AddSpacer((10, 10)) + hsz.Add(sz, 1, wx.EXPAND) + hsz.AddSpacer((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) + t = ScriptThread(self, self.script) + self.active = True + t.Start() + + def generateUploadScript(self): + self.script = [] + if platform == "win32": + cmdpath = "\"" + join(self.settings.arduinodir, "avrdude") + "\"" + else: + cmdpath = "avrdude" + + cmd = cmdpath + " -c %s -b %s -p %s -P %s -U flash:w:teacup.hex" % \ + (self.settings.programmer, self.baud, self.cpu, self.settings.port) + 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.log.AppendText("Upload terminated abnormally.\n") + self.active = False + if evt.state == SCRIPT_FINISHED: + self.log.AppendText("Upload completed normally.\n") + self.active = False + + def onExit(self, evt): + if self.active: + return + + self.EndModal(wx.ID_OK) diff --git a/configtool/cpupage.py b/configtool/cpupage.py index f8d6570..947b18e 100644 --- a/configtool/cpupage.py +++ b/configtool/cpupage.py @@ -1,7 +1,6 @@ import wx from configtool.page import Page -from configtool.data import supportedCPUs class CpuPage(wx.Panel, Page): @@ -11,64 +10,49 @@ class CpuPage(wx.Panel, Page): self.parent = parent self.id = idPg - self.labels = {'F_CPU': "CPU Clock Rate:"} - self.defaultClock = '16000000UL' - self.clocks = ['8000000UL', self.defaultClock, '20000000UL'] + self.labels = {'F_CPU': "CPU Clock Rate:", 'CPU': "Processor Type:"} + self.clocks = [] self.processors = [] sz = wx.GridBagSizer() sz.AddSpacer((20, 40), pos = (0, 0)) k = 'F_CPU' - ch = self.addChoice(k, self.clocks, self.clocks.index(self.defaultClock), - 100, self.onChoice) + ch = self.addChoice(k, self.clocks, 0, 100, self.onChoice) sz.Add(ch, pos = (1, 1)) sz.AddSpacer((100, 10), pos = (1, 2)) - b = wx.StaticBox(self, wx.ID_ANY, "Processor Type(s)") - sbox = wx.StaticBoxSizer(b, wx.VERTICAL) - - ht = "Choose the processor(s) this configuration will work with." - for k in supportedCPUs: - cb = self.addCheckBox(k, self.onCheckBox) - cb.SetToolTipString(ht) - sbox.Add(cb) - sbox.AddSpacer((120, 5)) - - sbox.AddSpacer((5, 5)) - sz.Add(sbox, pos = (1, 3), span = (3, 1)) + k = 'CPU' + ch = self.addChoice(k, self.processors, 0, 100, self.onChoice) + sz.Add(ch, pos = (1, 3)) self.SetSizer(sz) self.enableAll(False) - def setProcessors(self, plist): + def setCandidateProcessors(self, plist): + k = 'CPU' + self.choices[k].Clear() + for p in plist: + self.choices[k].Append(p) self.processors = plist - for p in supportedCPUs: - if p in self.processors: - self.checkBoxes[p].SetValue(True) - else: - self.checkBoxes[p].SetValue(False) - def getProcessors(self): - plist = [] - for p in supportedCPUs: - if self.checkBoxes[p].IsChecked(): - plist.append(p) - - return plist + def setCandidateClocks(self, clist): + k = 'F_CPU' + self.choices[k].Clear() + for c in clist: + self.choices[k].Append(c) + self.clocks = clist def insertValues(self, cfgValues): self.assertValid(True) self.enableAll(True) + for k in self.fieldValid.keys(): self.fieldValid[k] = True - for k in self.checkBoxes.keys(): - if k in cfgValues.keys() and cfgValues[k]: - self.checkBoxes[k].SetValue(True) - else: - self.checkBoxes[k].SetValue(False) - - self.setChoice('F_CPU', cfgValues, self.defaultClock) + if len(self.clocks) > 0: + self.setChoice('F_CPU', cfgValues, self.clocks[0]) + if len(self.processors) > 0: + self.setChoice('CPU', cfgValues, self.processors[0]) self.assertModified(False) diff --git a/configtool/data.py b/configtool/data.py index 1e104d9..9992806 100644 --- a/configtool/data.py +++ b/configtool/data.py @@ -40,10 +40,10 @@ reStartSensors = re.compile("^\s*//\s*DEFINE_TEMP_SENSORS_START") reEndSensors = re.compile("^\s*//\s*DEFINE_TEMP_SENSORS_END") reStartHeaters = re.compile("^\s*//\s*DEFINE_HEATERS_START") reEndHeaters = re.compile("^\s*//\s*DEFINE_HEATERS_END") -reStartProcessors = re.compile("^\s*//\s*PROCESSORS_START") -reEndProcessors = re.compile("^\s*//\s*PROCESSORS_END") reCandHeatPins = re.compile("^\s*//\s*#define\s+HEATER_PIN\s+(\w+)") reCandThermPins = re.compile("^\s*//\s*#define\s+TEMP_SENSOR_PIN\s+(\w+)") +reCandProcessors = re.compile("^\s*//\s*#define\s+CPU_TYPE\s+(\w+)") +reCandCPUClocks = re.compile("^\s*//\s*#define\s+F_CPU_OPT\s+(\w+)") reHelpTextStart = re.compile("^\s*/\*\*\s+\\\\def\s+(.*)") reHelpTextEnd = re.compile("^\s*\*/") @@ -55,8 +55,6 @@ reHeater = re.compile(".*\\(\s*(\w+)\s*,\s*(\w+)\s*,\s*(\w+)\s*\\)") reInteger = re.compile("^\d+U?L?$") reFloat = re.compile("^\d+(\.\d*)?$") -reAVR = re.compile("__AVR_(\w+)__") - defineValueFormat = "#define %-24s %s\n" defineBoolFormat = "#define %s\n" defineHeaterFormat = "#define HEATER_%s HEATER_%s\n" diff --git a/configtool/printerpanel.py b/configtool/printerpanel.py index 4767b08..79179f6 100644 --- a/configtool/printerpanel.py +++ b/configtool/printerpanel.py @@ -14,28 +14,31 @@ from configtool.miscellaneouspage import MiscellaneousPage class PrinterPanel(wx.Panel): - def __init__(self, parent, nb, font, folder): + def __init__(self, parent, nb, settings): wx.Panel.__init__(self, nb, wx.ID_ANY) self.parent = parent self.configFile = None + self.settings = settings + self.cfgValues = {} self.heaters = [] - self.dir = os.path.join(folder, "config") + self.dir = os.path.join(self.settings.folder, "config") sz = wx.BoxSizer(wx.HORIZONTAL) self.nb = wx.Notebook(self, wx.ID_ANY, size = (21, 21), style = wx.BK_DEFAULT) - self.nb.SetFont(font) + self.nb.SetFont(self.settings.font) self.pages = [] self.titles = [] self.pageModified = [] self.pageValid = [] - self.pgMech = MechanicalPage(self, self.nb, len(self.pages), font) + self.pgMech = MechanicalPage(self, self.nb, len(self.pages), + self.settings.font) text = "Mechanical" self.nb.AddPage(self.pgMech, text) self.pages.append(self.pgMech) @@ -43,7 +46,8 @@ class PrinterPanel(wx.Panel): self.pageModified.append(False) self.pageValid.append(True) - self.pgAcc = AccelerationPage(self, self.nb, len(self.pages), font) + self.pgAcc = AccelerationPage(self, self.nb, len(self.pages), + self.settings.font) text = "Acceleration" self.nb.AddPage(self.pgAcc, text) self.pages.append(self.pgAcc) @@ -52,7 +56,7 @@ class PrinterPanel(wx.Panel): self.pageValid.append(True) self.pgMiscellaneous = MiscellaneousPage(self, self.nb, len(self.pages), - font) + self.settings.font) text = "Miscellaneous" self.nb.AddPage(self.pgMiscellaneous, text) self.pages.append(self.pgMiscellaneous) @@ -75,6 +79,9 @@ class PrinterPanel(wx.Panel): def isModified(self): return (True in self.pageModified) + def hasData(self): + return (self.configFile != None) + def assertValid(self, pg, flag = True): self.pageValid[pg] = flag self.modifyTab(pg) @@ -95,6 +102,15 @@ class PrinterPanel(wx.Panel): pfx = "" self.nb.SetPageText(pg, pfx + self.titles[pg]) + if True in self.pageModified and False in self.pageValid: + pfx = "?* " + elif True in self.pageModified: + pfx = "* " + elif False in self.pageValid: + pfx = "? " + else: + pfx = "" + self.parent.setPrinterTabDecor(pfx) def setHeaters(self, ht): return self.pgMiscellaneous.setHeaters(ht) @@ -229,7 +245,7 @@ class PrinterPanel(wx.Panel): self.cfgValues[t[0]] = True self.parent.enableSavePrinter(True) - self.parent.setPrinterTabText("Printer <%s>" % os.path.basename(fn)) + self.parent.setPrinterTabFile(os.path.basename(fn)) for pg in self.pages: pg.insertValues(self.cfgValues) @@ -274,7 +290,7 @@ class PrinterPanel(wx.Panel): if self.saveConfigFile(path): dlg = wx.MessageDialog(self, "File %s successfully written." % path, "Save successful", wx.OK + wx.ICON_INFORMATION) - self.parent.setPrinterTabText("Printer <%s>" % os.path.basename(path)) + self.parent.setPrinterTabFile(os.path.basename(path)) else: dlg = wx.MessageDialog(self, "Unable to write to file %s." % path, diff --git a/configtool/settings.py b/configtool/settings.py new file mode 100644 index 0000000..252112c --- /dev/null +++ b/configtool/settings.py @@ -0,0 +1,47 @@ + +import ConfigParser +import os + +INIFILE = "configtool.default.ini" + + +class Settings: + def __init__(self, app, folder): + self.app = app + self.cmdfolder = folder + self.inifile = os.path.join(folder, INIFILE) + self.section = "configtool" + + self.arduinodir = "" + self.cflags = "" + self.ldflags = "" + self.objcopyflags = "" + self.programmer = "wiring" + self.port = "/dev/ttyACM0" + + self.cfg = ConfigParser.ConfigParser() + self.cfg.optionxform = str + if not self.cfg.read(self.inifile): + print "Settings file %s does not exist. Using default values." % INIFILE + + return + + if self.cfg.has_section(self.section): + for opt, value in self.cfg.items(self.section): + value = value.replace('\n', ' ') + if opt == "arduinodir": + self.arduinodir = value + elif opt == "cflags": + self.cflags = value + elif opt == "ldflags": + self.ldflags = value + elif opt == "programmer": + self.programmer = value + elif opt == "port": + self.port = value + elif opt == "objcopyflags": + self.objcopyflags = value + else: + print "Unknown %s option: %s - ignoring." % (self.section, opt) + else: + print "Missing %s section - assuming defaults." % self.section