# This file is part of the pyUSBlini project. # # Copyright(c) 2021-2023 Thomas Fischl (https://www.fischl.de) # # pyUSBlini is free software: you can redistribute it and/or modify # it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # pyUSBlini is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU LESSER GENERAL PUBLIC LICENSE for more details. # # You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE # along with pyUSBlini. If not, see import sys import tkinter as tk from tkinter import ttk from tkinter import* from tkinter.filedialog import asksaveasfilename from tkinter.messagebox import showinfo from usblini import USBlini from usblini import LINFrame from usblini import USBliniError import queue import zipfile class App(tk.Tk): def __init__(self, serial): super().__init__() title = 'USBliniGUI v1.1' if serial != None: title = title + ' (' + serial + ')' self.title(title) tabControl = ttk.Notebook(self) tabSettings = ttk.Frame(tabControl) tabMaster = ttk.Frame(tabControl) tabSlave = ttk.Frame(tabControl) tabError = ttk.Frame(tabControl) tabLogic = ttk.Frame(tabControl) tabControl.add(tabSettings, text='Settings') tabControl.add(tabMaster, text='Master') tabControl.add(tabSlave, text='Slave') tabControl.add(tabLogic, text='Logic') tabControl.add(tabError, text='Error') tabControl.pack(side = 'right', fill='both') topframe = Frame(self) clearbutton = tk.Button(topframe, text='Clear', command=self.clear) clearbutton.pack(side='left') logbutton = tk.Button(topframe, text='Save', command=self.savelog) logbutton.pack(side='left') self.follow = IntVar(value=1) ttk.Checkbutton(topframe, text="Follow", variable=self.follow).pack(side='right') topframe.pack(side = 'bottom', fill = 'x', padx='5', pady='5') settingframe = Frame(tabSettings) lbaudrate = Label(settingframe, text='Baudrate (bps):') lbaudrate.grid(row=0, column=0, sticky="W") self.baudratecombo = ttk.Combobox(settingframe, values=[1200, 2400, 9600, 19200], width=6) self.baudratecombo.current(3) self.baudratecombo.grid(row=0, column=1, sticky="W") lautobaud = Label(settingframe, text='Baudrate calibration:') lautobaud.grid(row=1, column=0, sticky="W") self.autobaud = IntVar() self.autobaudbutton = ttk.Checkbutton(settingframe, text="Enable slave autobaud", variable=self.autobaud) self.autobaudbutton.grid(row=1, column=1, sticky="W") setsettingbutton = tk.Button(settingframe, text='Set', command=self.setSettings) setsettingbutton.grid(row=2, column=1, sticky="W") settingframe.pack(side = 'top', fill = 'x', padx = '5', pady=10) columns = ('time', 'id', 'data') self.tree = ttk.Treeview(self, columns=columns, show='headings') self.tree.heading('time', text='Time (ms)') self.tree.heading('id', text='ID') self.tree.heading('data', text='Data') self.tree.column("time", minwidth=0, width=80, stretch=False) self.tree.column("id", minwidth=0, width=50, stretch=False) self.tree.column("data", minwidth=0, width=250) # scrollbar scrollbar = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self.tree.yview) self.tree.configure(yscroll=scrollbar.set) sendframe = Frame(tabMaster) self.identry = tk.Entry(sendframe, width=3) self.identry.insert(END, '10') self.identry.grid(row=0, column=0) self.lengthValue = IntVar() self.sbox = Spinbox(sendframe, from_ = 0, to = 8, width=3, textvariable=self.lengthValue) self.lengthValue.trace('w', self.updateLength) self.sbox.grid(row=0, column=1) sendbutton = tk.Button(sendframe, text='Send', command=self.send) sendbutton.grid(row=0, column=12) self.dataentry = [None] * 8 for x in range(8): self.dataentry[x] = tk.Entry(sendframe, width=3) self.dataentry[x].insert(END, x) self.dataentry[x].config(state='disable') self.dataentry[x].grid(row=0, column=2+x) self.checksummode = StringVar() self.checksummode.set("LIN2") checksummodeOptionMenu = OptionMenu(sendframe, self.checksummode, "None", "LIN1", "LIN2") checksummodeOptionMenu.grid(row=0, column=11) sequenceframe = Frame(tabMaster) lperiod = Label(sequenceframe, text='Period (ms):') lperiod.grid(row=0, column=0, sticky="W") self.periodentry = tk.Entry(sequenceframe, width=5) self.periodentry.insert(END, '1000') self.periodentry.grid(row=0, column=1, sticky="W") lframetime = Label(sequenceframe, text='Frametime (ms):') lframetime.grid(row=1, column=0, sticky="W") self.frametimeentry = tk.Entry(sequenceframe, width=5) self.frametimeentry.insert(END, '200') self.frametimeentry.grid(row=1, column=1, sticky="W") lsequence = Label(sequenceframe, text='List of IDs (hex):') lsequence.grid(row=2, column=0, sticky="W") self.sequence = tk.Entry(sequenceframe, width=3*16) self.sequence.insert(END, '10, 11') self.sequence.grid(row=2, column=1, sticky="W") sequencebuttonframe = Frame(sequenceframe) setseqbutton = tk.Button(sequencebuttonframe, text='Set', command=self.setSequence) setseqbutton.pack(side='left') clearseqbutton = tk.Button(sequencebuttonframe, text='Clear', command=self.clearSequence) clearseqbutton.pack(side='left') sequencebuttonframe.grid(row=3, column=1, sticky="W") lsinglewrite = Label(tabMaster, text="Single master write:") lmastersequence = Label(tabMaster, text="Master sequence:") lsinglewrite.pack(side='top', anchor='w', padx = '5', pady=(10,0)) sendframe.pack(side = 'top', fill = 'x', padx = '5') lmastersequence.pack(side='top', anchor='w', padx = '5', pady=(10,0)) sequenceframe.pack(side='top', anchor='w', padx = '20', pady=(0,10)) scrollbar.pack(side = 'right', fill = 'y', expand = False) self.tree.pack(side = 'top', fill = 'both', expand = True) slaveTableFrame = Frame(tabSlave) Label(slaveTableFrame, text='Id').grid(row=0, column=1) Label(slaveTableFrame, text='Len').grid(row=0, column=2) for x in range(8): Label(slaveTableFrame, text='D{:d}'.format(x)).grid(row=0, column=3+x) Label(slaveTableFrame, text='Chksum').grid(row=0, column=12) Label(slaveTableFrame, text='Reload').grid(row=0, column=13) Label(slaveTableFrame, text='Reset').grid(row=0, column=14) self.slaveTableItem = [None] * 16 for x in range(16): self.slaveTableItem[x] = SlaveTableItem(slaveTableFrame, x) slaveTableFrame.pack(side='top', padx=5, pady=(10,0)) slavetablebuttonframe = Frame(tabSlave) tk.Button(slavetablebuttonframe, text='Set', command=self.setSlaveTable).pack(side='left') tk.Button(slavetablebuttonframe, text='Clear', command=self.clearSlaveTable).pack(side='left') slavetablebuttonframe.pack(side='top', pady=10) self.errorlist = ["EP1 Overflow", "Autobaud Overflow", "Missing Echo (check VBat!)"] self.errorlabel = [None] * len(self.errorlist) for x in range(len(self.errorlist)): self.errorlabel[x] = Label(tabError, text=self.errorlist[x], fg='#999') self.errorlabel[x].pack(side='top', padx=5, pady=5) tk.Button(tabError, text='Clear', command=self.clearErrors).pack(side='top') Label(tabLogic, text='Sample logic level on RX pin with 100 ksps and save it in \nPulseView (sigrok project) compatible file for further analysis.', justify=LEFT).pack(side='top', padx=5, pady=10, anchor='w') filenameFrame = Frame(tabLogic) Label(filenameFrame, text='Filename:').pack(side='left') self.logicfilenameentry = tk.Entry(filenameFrame, width=40) self.logicfilenameentry.insert(END, 'usblini_la_sampled.sr') self.logicfilenameentry.pack(side='left', padx=5) tk.Button(filenameFrame, text='Browse', command=self.browseLogicFilename).pack(side='left') filenameFrame.pack(side='top') logicButtonFrame = Frame(tabLogic) self.logicStartButton = tk.Button(logicButtonFrame, text='Start recording', command=self.startRecording) self.logicStartButton.grid(row=0, column=0) self.logicStopButton = tk.Button(logicButtonFrame, text='Stop recording', command=self.stopRecording, state='disable') self.logicStopButton.grid(row=0, column=1) logicButtonFrame.pack(side='top') self.recordingActive = 0 self.usblini = USBlini() self.usblini.open(serial) self.usblini.reset() self.usblini.set_baudrate(19200, True) self.frame_queue = queue.Queue() self.statusreport_queue = queue.Queue() self.after(10, self.update_frame) self.after(10, self.update_statusreport) self.usblini.frame_listener_add(self.frame_listener) self.usblini.statusreport_listener_add(self.statusreport_listener) self.LR_LUT = [bytearray([(byte & (1 << (7 - i))) and 0x4F or 0x2E for i in range(8)]) for byte in range(256)] Label(tabSettings, text='Firmware version: {}'.format(self.usblini.get_version()), justify=LEFT).pack(side='top', padx=5, pady=(20,5), anchor='w') tk.Button(tabSettings, text='Start bootloader and exit GUI', command=self.startBootloader).pack(side='top', padx=5, anchor='w') def destroy(self): self.stopRecording() self.usblini.close() tk.Tk.destroy(self) def startBootloader(self): self.usblini.start_bootloader() self.destroy() def startRecording(self): self.logicoutfile = open("logic-1-1", "wb") self.usblini.logic_listener_add(self.logic_listener) self.recordingActive = 1 self.logicStopButton.config(state="normal") self.logicStartButton.config(state="disable") self.showMessage('Logic level recording started') def stopRecording(self): if self.recordingActive == 0: return self.recordingActive = 0 self.usblini.logic_listener_remove(self.logic_listener) self.logicStartButton.config(state="normal") self.logicStopButton.config(state="disable") self.showMessage('Logic level recording stopped') with zipfile.ZipFile(self.logicfilenameentry.get(), 'w', zipfile.ZIP_DEFLATED) as zipped_f: zipped_f.write("logic-1-1") zipped_f.writestr("version", "2") zipped_f.writestr("metadata", "[device 1]\ncapturefile=logic-1\ntotal probes=1\nsamplerate=100 kHz\ntotal analog=0\nprobe1=LIN\nunitsize=1") def browseLogicFilename(self): file_name = asksaveasfilename() self.logicfilenameentry.delete(0, END) self.logicfilenameentry.insert(0, file_name) def clearErrors(self): self.usblini.clear_errorflags() self.showMessage('Error flags cleared') def setSlaveTable(self): for x in range(16): self.usblini.slave_set_frame(x, self.slaveTableItem[x].getId(), self.slaveTableItem[x].getChecksummode(), self.slaveTableItem[x].getData(), self.slaveTableItem[x].getReloadvalue(), self.slaveTableItem[x].getResetmask()) self.showMessage('Slave table set') def clearSlaveTable(self): for x in range(16): self.usblini.slave_set_frame(x, 0, 0, []) self.showMessage('Slave table cleared') def setSettings(self): baudrate = int(self.baudratecombo.get()) autobaud = self.autobaud.get() == 1 self.usblini.set_baudrate(baudrate, autobaud) self.showMessage('Baudrate set: {:d}, autobaud: {}'.format(baudrate, autobaud)) def setSequence(self): period = int(self.periodentry.get()) frametime = int(self.frametimeentry.get()) if len(self.sequence.get()) > 0: idlist = [int(e, 16) for e in self.sequence.get().split(',')] else: idlist = [] self.usblini.master_set_sequence(period, frametime, idlist) self.showMessage('Master sequence set: {:d}/{:d} [{}]'.format(period, frametime, ','.join('{:02x}'.format(x) for x in idlist))) def clearSequence(self): self.usblini.master_set_sequence(0, 0, []) self.showMessage('Master sequence cleared') def updateLength(self, a, b, c): length = int(self.sbox.get()) for x in range(8): if x < length: self.dataentry[x].config(state='normal') else: self.dataentry[x].config(state='disable') self.dataentry[x].update_idletasks() def showMessage(self, msg): self.tree.insert('', tk.END, values=('', '', msg)) if self.follow.get(): self.tree.yview_moveto(1) def clear(self): self.tree.delete(*self.tree.get_children()) def savelog(self): file_name = asksaveasfilename() with open(file_name, 'w') as logfile: for child in self.tree.get_children(): values = self.tree.item(child)["values"] logfile.write("{}\t{}\t{}\n".format(values[0], values[1], values[2])) logfile.close() self.showMessage('Saved messages to file') def send(self): length = int(self.sbox.get()) identifier = int(self.identry.get(), 16) data = [None] * length for x in range(length): data[x] = int(self.dataentry[x].get(), 16) modes = {"None": self.usblini.CHECKSUM_MODE_NONE, "LIN1": self.usblini.CHECKSUM_MODE_LIN1, "LIN2": self.usblini.CHECKSUM_MODE_LIN2} checksummode = modes[self.checksummode.get()] try: response = self.usblini.master_write(identifier, checksummode, data) except USBliniError: self.showMessage('Error while sending message') def frame_listener(self, frame): self.frame_queue.put_nowait(frame) def update_frame(self): while not self.frame_queue.empty(): frame = self.frame_queue.get(False) self.tree.insert('', tk.END, values=(frame.timestamp, '{:02x}'.format(frame.frameid), '{}'.format(' '.join('{:02x}'.format(x) for x in frame.data)))) if self.follow.get(): self.tree.yview_moveto(1) self.after(10, self.update_frame) def statusreport_listener(self, statusreport): self.statusreport_queue.put_nowait(statusreport) def update_statusreport(self): while not self.statusreport_queue.empty(): statusreport = self.statusreport_queue.get(False) l = statusreport.getSlaveTableStatusList() for x in range(16): if l[x]: self.slaveTableItem[x].lno.config(bg="green") else: self.slaveTableItem[x].lno.config(bg="#d9d9d9") for x in range(len(self.errorlist)): if statusreport.errorflags & (1< 1: serial = sys.argv[1] else: serial = None app = App(serial) app.mainloop() if __name__ == '__main__': main()