esp32-nmea2000-obp60/tools/flashtool.py

184 lines
6.4 KiB
Python
Executable File

#! /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
oldprint=builtins.print
def print(*args, **kwargs):
app.addText(args[0],kwargs.get('end') == '')
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')
#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
self.mode=tk.IntVar()
self.mode.set(1)
btFrame=tk.Frame(frame)
btFrame.grid(row=row,column=1,sticky="ew",pady=20)
tk.Radiobutton(btFrame,text="Initial Flash",value=1,variable=self.mode,command=self.changeMode).grid(row=0,column=0)
tk.Radiobutton(btFrame, 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')
self.port=ttk.Combobox(frame)
self.port.grid(row=row,column=1,sticky="ew")
row+=1
tk.Label(frame,text="Select Firmware").grid(row=row,column=0,sticky='ew')
self.filename=tk.StringVar()
fn=tk.Entry(frame,textvariable=self.filename)
fn.grid(row=row,column=1,sticky='ew')
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
tk.Button(frame,text="Flash",command=self.buttonAction).grid(row=row,column=0,columnspan=2,pady=10)
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()
def updateFlashInfo(self):
if self.mode.get() == 1:
#full
self.flashInfo.set("Address 0x1000")
else:
self.flashInfo.set("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 is not None:
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,text,preventNl=False):
le="\n"
if preventNl:
le=''
self.text_widget.insert(tk.END,text+le)
root.update()
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 buttonAction(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
command=['--chip','ESP32','--port',port,'chip_id']
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)
if __name__ == "__main__":
root = tk.Tk()
app = App(root)
root.mainloop()