diff --git a/tools/buildFlashtool.sh b/tools/buildFlashtool.sh new file mode 100755 index 0000000..a7a43e0 --- /dev/null +++ b/tools/buildFlashtool.sh @@ -0,0 +1,6 @@ +#! /bin/sh +pdir=`dirname $0` +cd $pdir || exit 1 +rm -rf flashtool/.idea +python3 -m zipapp flashtool -m flashtool:main + diff --git a/tools/esp32n2k-flashtool.ps1 b/tools/esp32n2k-flashtool.ps1 new file mode 100644 index 0000000..f785347 --- /dev/null +++ b/tools/esp32n2k-flashtool.ps1 @@ -0,0 +1,8 @@ +@echo off +Rem Make powershell read this file, skip a number of lines, and execute it. +Rem This works around .ps1 bad file association as non executables. +PowerShell -Command "Get-Content '%~dpnx0' | Select-Object -Skip 6 | Out-String | Invoke-Expression" +pause +goto :eof +# Start of PowerShell script here +Write-Host "Hello World!" diff --git a/tools/flashtool.exe b/tools/flashtool.exe deleted file mode 100644 index 0ff9ce0..0000000 Binary files a/tools/flashtool.exe and /dev/null differ diff --git a/tools/flashtool.py b/tools/flashtool.py deleted file mode 100755 index 32d6a34..0000000 --- a/tools/flashtool.py +++ /dev/null @@ -1,240 +0,0 @@ -#! /usr/bin/env python3 -import tkinter as tk -from tkinter import ttk -import tkinter.font as tkFont - -import os -import serial.tools.list_ports -from tkinter import filedialog as FileDialog - -import builtins - -VERSION="Version 1.1, esptool 3.2" - -oldprint=builtins.print -def print(*args, **kwargs): - app.addText(*args,**kwargs) - - -builtins.print=print -import esptool - -class App: - def __init__(self, root): - root.title("ESP32 NMEA2000 Flash Tool") - root.geometry("800x600") - root.resizable(width=True, height=True) - root.configure(background='lightgrey') - root.columnconfigure(0, weight=1) - root.rowconfigure(0, weight=1) - frame=tk.Frame(root) - row=0 - frame.grid(row=0,column=0,sticky='news',padx=10,pady=5) - DUMMY = "prevent to handled as virus" - #frame.configure(background='lightblue') - frame.columnconfigure(0,weight=1) - frame.columnconfigure(1, weight=3) - tk.Label(frame,text="ESP32 NMEA2000 Flash Tool").grid(row=row,column=0,columnspan=2,sticky='ew') - row+=1 - tk.Label(frame, text=VERSION).grid(row=row,column=0,columnspan=2,sticky="ew",pady=10) - row+=1 - self.mode=tk.IntVar() - self.mode.set(1) - rdFrame=tk.Frame(frame) - rdFrame.grid(row=row,column=1,sticky="ew",pady=20) - tk.Radiobutton(rdFrame,text="Initial Flash",value=1,variable=self.mode,command=self.changeMode).grid(row=0,column=0) - tk.Radiobutton(rdFrame, text="Update Flash", value=2, variable=self.mode,command=self.changeMode).grid(row=0,column=1) - row+=1 - tk.Label(frame, text="Com Port").grid(row=row,column=0,sticky='ew') - ttk.Style().configure("TCombobox",padding=8,arrowsize=28) - ttk.Style().configure("TEntry", padding=8) - self.port=ttk.Combobox(frame) - self.port.grid(row=row,column=1,sticky="ew",pady=5) - row+=1 - tk.Label(frame,text="Select Firmware").grid(row=row,column=0,sticky='ew') - self.filename=tk.StringVar() - fn=ttk.Entry(frame,textvariable=self.filename) - fn.grid(row=row,column=1,sticky='ew',pady=5) - fn.bind("<1>",self.fileDialog) - row+=1 - self.fileInfo=tk.StringVar() - tk.Label(frame,textvariable=self.fileInfo).grid(row=row,column=0,columnspan=2,sticky="ew") - row+=1 - self.flashInfo=tk.StringVar() - self.flashInfo.set("Address 0x1000") - tk.Label(frame,textvariable=self.flashInfo).grid(row=row,column=0,columnspan=2,sticky='ew',pady=10) - row+=1 - btFrame=tk.Frame(frame) - btFrame.grid(row=row,column=0,columnspan=2,pady=15) - self.actionButtons=[] - bt=tk.Button(btFrame,text="Check",command=self.buttonCheck) - bt.grid(row=0,column=0) - self.actionButtons.append(bt) - bt=tk.Button(btFrame, text="Flash", command=self.buttonFlash) - bt.grid(row=0, column=1) - self.actionButtons.append(bt) - self.cancelButton=tk.Button(btFrame,text="Cancel",state=tk.DISABLED,command=self.buttonCancel) - self.cancelButton.grid(row=0,column=2) - row+=1 - self.text_widget = tk.Text(frame) - frame.rowconfigure(row,weight=1) - self.text_widget.grid(row=row,column=0,columnspan=2,sticky='news') - self.readDevices() - self.interrupt=False - - def updateFlashInfo(self): - if self.mode.get() == 1: - #full - self.flashInfo.set("Address 0x1000") - else: - self.flashInfo.set("Erase(otadata): 0xe000...0xffff, Address 0x10000") - def changeMode(self): - m=self.mode.get() - self.updateFlashInfo() - self.filename.set('') - self.fileInfo.set('') - def fileDialog(self,ev): - fn=FileDialog.askopenfilename() - if fn: - self.filename.set(fn) - info=self.checkImageFile(fn,self.mode.get() == 1) - if info['error']: - self.fileInfo.set("***ERROR: %s"%info['info']) - else: - self.fileInfo.set(info['info']) - def readDevices(self): - self.serialDevices=[] - names=[] - for dev in serial.tools.list_ports.comports(False): - self.serialDevices.append(dev.device) - if dev.description != 'n/a': - label=dev.description+"("+dev.device+")" - else: - label=dev.name+"("+dev.device+")" - names.append(label) - self.port.configure(values=names) - def addText(self,*args,**kwargs): - first=True - for k in args: - self.text_widget.insert(tk.END,k) - if not first: - self.text_widget.insert(tk.END, ',') - first=False - if kwargs.get('end') is None: - self.text_widget.insert(tk.END,"\n") - else: - self.text_widget.insert(tk.END,kwargs.get('end')) - self.text_widget.see('end') - root.update() - if self.interrupt: - self.interrupt=False - raise Exception("User cancel") - - FULLOFFSET=61440 - HDROFFSET = 288 - VERSIONOFFSET = 16 - NAMEOFFSET = 48 - MINSIZE = HDROFFSET + NAMEOFFSET + 32 - CHECKBYTES = { - 0: 0xe9, # image magic - 288: 0x32, # app header magic - 289: 0x54, - 290: 0xcd, - 291: 0xab - } - - def getString(self,buffer, offset, len): - return buffer[offset:offset + len].rstrip(b'\0').decode('utf-8') - - def getFirmwareInfo(self,ih,imageFile,offset): - buffer = ih.read(self.MINSIZE) - if len(buffer) != self.MINSIZE: - return self.setErr("invalid image file %s, to short"%imageFile) - for k, v in self.CHECKBYTES.items(): - if buffer[k] != v: - return self.setErr("invalid magic at %d, expected %d got %d" - % (k+offset, v, buffer[k])) - name = self.getString(buffer, self.HDROFFSET + self.NAMEOFFSET, 32) - version = self.getString(buffer, self.HDROFFSET + self.VERSIONOFFSET, 32) - return {'error':False,'info':"%s:%s"%(name,version)} - - def setErr(self,err): - return {'error':True,'info':err} - def checkImageFile(self,filename,isFull): - if not os.path.exists(filename): - return self.setErr("file %s not found"%filename) - with open(filename,"rb") as fh: - offset=0 - if isFull: - b=fh.read(1) - if len(b) != 1: - return self.setErr("unable to read header") - if b[0] != 0xe9: - return self.setErr("invalid magic in file, expected 0xe9 got 0x%02x"%b[0]) - st=fh.seek(self.FULLOFFSET) - offset=self.FULLOFFSET - return self.getFirmwareInfo(fh,filename,offset) - - def runCheck(self): - self.text_widget.delete("1.0", "end") - idx = self.port.current() - isFull = self.mode.get() == 1 - if idx < 0: - self.addText("ERROR: no com port selected") - return - port = self.serialDevices[idx] - fn = self.filename.get() - if fn is None or fn == '': - self.addText("ERROR: no filename selected") - return - info = self.checkImageFile(fn, isFull) - if info['error']: - print("ERROR: %s" % info['info']) - return - return {'port':port,'isFull':isFull} - - def runEspTool(self,command): - for b in self.actionButtons: - b.configure(state=tk.DISABLED) - self.cancelButton.configure(state=tk.NORMAL) - print("run esptool: %s" % " ".join(command)) - root.update() - root.update_idletasks() - try: - esptool.main(command) - print("esptool done") - except Exception as e: - print("Exception in esptool %s" % e) - for b in self.actionButtons: - b.configure(state=tk.NORMAL) - self.cancelButton.configure(state=tk.DISABLED) - def buttonCheck(self): - param = self.runCheck() - if not param: - return - print("Settings OK") - command = ['--chip', 'ESP32', '--port', param['port'], 'chip_id'] - self.runEspTool(command) - - def buttonFlash(self): - param=self.runCheck() - if not param: - return - if param['isFull']: - command=['--chip','ESP32','--port',param['port'],'write_flash','0x1000',self.filename.get()] - self.runEspTool(command) - else: - command=['--chip','ESP32','--port',param['port'],'erase_region','0xe000','0x2000'] - self.runEspTool(command) - command = ['--chip', 'ESP32', '--port', param['port'], 'write_flash', '0x10000', self.filename.get()] - self.runEspTool(command) - - - def buttonCancel(self): - self.interrupt=True - - -if __name__ == "__main__": - root = tk.Tk() - app = App(root) - root.mainloop() diff --git a/tools/esptool.py b/tools/flashtool/esptool.py similarity index 100% rename from tools/esptool.py rename to tools/flashtool/esptool.py diff --git a/tools/flashtool/flashtool.py b/tools/flashtool/flashtool.py new file mode 100755 index 0000000..b4ee337 --- /dev/null +++ b/tools/flashtool/flashtool.py @@ -0,0 +1,243 @@ +#! /usr/bin/env python3 +import tkinter as tk +from tkinter import ttk +import tkinter.font as tkFont + +import os +import serial.tools.list_ports +from tkinter import filedialog as FileDialog + +import builtins + +def main(): + VERSION="Version 1.1, esptool 3.2" + + oldprint=builtins.print + def print(*args, **kwargs): + app.addText(*args,**kwargs) + + + builtins.print=print + import esptool + + class App: + def __init__(self, root): + root.title("ESP32 NMEA2000 Flash Tool") + root.geometry("800x600") + root.resizable(width=True, height=True) + root.configure(background='lightgrey') + root.columnconfigure(0, weight=1) + root.rowconfigure(0, weight=1) + frame=tk.Frame(root) + row=0 + frame.grid(row=0,column=0,sticky='news',padx=10,pady=5) + DUMMY = "prevent to handled as virus" + #frame.configure(background='lightblue') + frame.columnconfigure(0,weight=1) + frame.columnconfigure(1, weight=3) + tk.Label(frame,text="ESP32 NMEA2000 Flash Tool").grid(row=row,column=0,columnspan=2,sticky='ew') + row+=1 + tk.Label(frame, text=VERSION).grid(row=row,column=0,columnspan=2,sticky="ew",pady=10) + row+=1 + self.mode=tk.IntVar() + self.mode.set(1) + rdFrame=tk.Frame(frame) + rdFrame.grid(row=row,column=1,sticky="ew",pady=20) + tk.Radiobutton(rdFrame,text="Initial Flash",value=1,variable=self.mode,command=self.changeMode).grid(row=0,column=0) + tk.Radiobutton(rdFrame, text="Update Flash", value=2, variable=self.mode,command=self.changeMode).grid(row=0,column=1) + row+=1 + tk.Label(frame, text="Com Port").grid(row=row,column=0,sticky='ew') + ttk.Style().configure("TCombobox",padding=8,arrowsize=28) + ttk.Style().configure("TEntry", padding=8) + self.port=ttk.Combobox(frame) + self.port.grid(row=row,column=1,sticky="ew",pady=5) + row+=1 + tk.Label(frame,text="Select Firmware").grid(row=row,column=0,sticky='ew') + self.filename=tk.StringVar() + fn=ttk.Entry(frame,textvariable=self.filename) + fn.grid(row=row,column=1,sticky='ew',pady=5) + fn.bind("<1>",self.fileDialog) + row+=1 + self.fileInfo=tk.StringVar() + tk.Label(frame,textvariable=self.fileInfo).grid(row=row,column=0,columnspan=2,sticky="ew") + row+=1 + self.flashInfo=tk.StringVar() + self.flashInfo.set("Address 0x1000") + tk.Label(frame,textvariable=self.flashInfo).grid(row=row,column=0,columnspan=2,sticky='ew',pady=10) + row+=1 + btFrame=tk.Frame(frame) + btFrame.grid(row=row,column=0,columnspan=2,pady=15) + self.actionButtons=[] + bt=tk.Button(btFrame,text="Check",command=self.buttonCheck) + bt.grid(row=0,column=0) + self.actionButtons.append(bt) + bt=tk.Button(btFrame, text="Flash", command=self.buttonFlash) + bt.grid(row=0, column=1) + self.actionButtons.append(bt) + self.cancelButton=tk.Button(btFrame,text="Cancel",state=tk.DISABLED,command=self.buttonCancel) + self.cancelButton.grid(row=0,column=2) + row+=1 + self.text_widget = tk.Text(frame) + frame.rowconfigure(row,weight=1) + self.text_widget.grid(row=row,column=0,columnspan=2,sticky='news') + self.readDevices() + self.interrupt=False + + def updateFlashInfo(self): + if self.mode.get() == 1: + #full + self.flashInfo.set("Address 0x1000") + else: + self.flashInfo.set("Erase(otadata): 0xe000...0xffff, Address 0x10000") + def changeMode(self): + m=self.mode.get() + self.updateFlashInfo() + self.filename.set('') + self.fileInfo.set('') + def fileDialog(self,ev): + fn=FileDialog.askopenfilename() + if fn: + self.filename.set(fn) + info=self.checkImageFile(fn,self.mode.get() == 1) + if info['error']: + self.fileInfo.set("***ERROR: %s"%info['info']) + else: + self.fileInfo.set(info['info']) + def readDevices(self): + self.serialDevices=[] + names=[] + for dev in serial.tools.list_ports.comports(False): + self.serialDevices.append(dev.device) + if dev.description != 'n/a': + label=dev.description+"("+dev.device+")" + else: + label=dev.name+"("+dev.device+")" + names.append(label) + self.port.configure(values=names) + def addText(self,*args,**kwargs): + first=True + for k in args: + self.text_widget.insert(tk.END,k) + if not first: + self.text_widget.insert(tk.END, ',') + first=False + if kwargs.get('end') is None: + self.text_widget.insert(tk.END,"\n") + else: + self.text_widget.insert(tk.END,kwargs.get('end')) + self.text_widget.see('end') + root.update() + if self.interrupt: + self.interrupt=False + raise Exception("User cancel") + + FULLOFFSET=61440 + HDROFFSET = 288 + VERSIONOFFSET = 16 + NAMEOFFSET = 48 + MINSIZE = HDROFFSET + NAMEOFFSET + 32 + CHECKBYTES = { + 0: 0xe9, # image magic + 288: 0x32, # app header magic + 289: 0x54, + 290: 0xcd, + 291: 0xab + } + + def getString(self,buffer, offset, len): + return buffer[offset:offset + len].rstrip(b'\0').decode('utf-8') + + def getFirmwareInfo(self,ih,imageFile,offset): + buffer = ih.read(self.MINSIZE) + if len(buffer) != self.MINSIZE: + return self.setErr("invalid image file %s, to short"%imageFile) + for k, v in self.CHECKBYTES.items(): + if buffer[k] != v: + return self.setErr("invalid magic at %d, expected %d got %d" + % (k+offset, v, buffer[k])) + name = self.getString(buffer, self.HDROFFSET + self.NAMEOFFSET, 32) + version = self.getString(buffer, self.HDROFFSET + self.VERSIONOFFSET, 32) + return {'error':False,'info':"%s:%s"%(name,version)} + + def setErr(self,err): + return {'error':True,'info':err} + def checkImageFile(self,filename,isFull): + if not os.path.exists(filename): + return self.setErr("file %s not found"%filename) + with open(filename,"rb") as fh: + offset=0 + if isFull: + b=fh.read(1) + if len(b) != 1: + return self.setErr("unable to read header") + if b[0] != 0xe9: + return self.setErr("invalid magic in file, expected 0xe9 got 0x%02x"%b[0]) + st=fh.seek(self.FULLOFFSET) + offset=self.FULLOFFSET + return self.getFirmwareInfo(fh,filename,offset) + + def runCheck(self): + self.text_widget.delete("1.0", "end") + idx = self.port.current() + isFull = self.mode.get() == 1 + if idx < 0: + self.addText("ERROR: no com port selected") + return + port = self.serialDevices[idx] + fn = self.filename.get() + if fn is None or fn == '': + self.addText("ERROR: no filename selected") + return + info = self.checkImageFile(fn, isFull) + if info['error']: + print("ERROR: %s" % info['info']) + return + return {'port':port,'isFull':isFull} + + def runEspTool(self,command): + for b in self.actionButtons: + b.configure(state=tk.DISABLED) + self.cancelButton.configure(state=tk.NORMAL) + print("run esptool: %s" % " ".join(command)) + root.update() + root.update_idletasks() + try: + esptool.main(command) + print("esptool done") + except Exception as e: + print("Exception in esptool %s" % e) + for b in self.actionButtons: + b.configure(state=tk.NORMAL) + self.cancelButton.configure(state=tk.DISABLED) + def buttonCheck(self): + param = self.runCheck() + if not param: + return + print("Settings OK") + command = ['--chip', 'ESP32', '--port', param['port'], 'chip_id'] + self.runEspTool(command) + + def buttonFlash(self): + param=self.runCheck() + if not param: + return + if param['isFull']: + command=['--chip','ESP32','--port',param['port'],'write_flash','0x1000',self.filename.get()] + self.runEspTool(command) + else: + command=['--chip','ESP32','--port',param['port'],'erase_region','0xe000','0x2000'] + self.runEspTool(command) + command = ['--chip', 'ESP32', '--port', param['port'], 'write_flash', '0x10000', self.filename.get()] + self.runEspTool(command) + + + def buttonCancel(self): + self.interrupt=True + + root = tk.Tk() + app = App(root) + root.mainloop() + + +if __name__ == "__main__": + main()