#!/bin/env python3 import p import tkinter as tk import tkinter.filedialog import tkinter.simpledialog import serial.tools.list_ports import threading import time import subprocess import sys import socket class internet: def __init__(self): self.socket=socket(family=socket.AF_INET,type=socket.SOCK_DGRAM) def read_until(self): d=socket.recfrom(84[0]) if(len(d)<2): return "++++" print("got:"+d.decode("utf-8")) return d def __enter__(self): self.socket.bind("0.0.0.0","0.0.0.0") return self def write(self,d): n=len(d) #XXX self.socket.sendto(d,"0.0.0.0") print("wrote:"+d.decode("utf-8")+" "+str(n)) return n def __exit__(self): self.close() def close(self): print("TODO") class fakeport: def __init__(self,p): if(hasattr(sys,'_MEIPASS')): p=sys._MEIPASS+"\\"+p+"\\"+p print(p) self.p=subprocess.Popen(p,stdin=subprocess.PIPE,stdout=subprocess.PIPE) else: self.p=subprocess.Popen(p,stdin=subprocess.PIPE,stdout=subprocess.PIPE) def read_until(self): d= self.p.stdout.readline() if(len(d)<2): return "++++" print("got:"+d.decode("utf-8")) return d def __enter__(self): return self.open() return self def write(self,d): n=self.p.stdin.write(d) self.p.stdin.flush() print("wrote:"+d.decode("utf-8")+" "+str(n)) return n def __exit__(self): self.close() def close(self): self.p.terminate() class stpgui: #Automatically generate GUI for STP endpoint #TODO:maybe show time graph of proc state in GUI def getport(self,port): print("returning port for"+port) if(port=="fakefirmware.exe"): return fakeport(port) if(port=="INTERNETSU!!!"): return internet(port) else: return serial.Serial(port) def __init__(self,root,port,fname): self.root=root self.root.title("STP Console") self.port=self.getport(port) self.ep=p.stlendpoint("logger",tx=lambda x:self.port.write(x.encode('utf-8'))) self.pointtab=pointtab(root,self.ep,fname) self.helplabels=[] for i in range(10): self.helplabels=self.helplabels+[tk.Label(self.root,anchor=tk.W)] self.helplabels[1].config(text="connecting...") self.recordbutton=tk.Button(self.root,text="Record Process",command=lambda :self.pointtab.beginrecord(self.recordbutton)) self.ep.onset(self.up) self.running=True; thread = threading.Thread(target = self.serialtask) thread.start() root.protocol("WM_DELETE_WINDOW",self.die) def die(self): self.running=False self.ep.query("__NAME__")#unblock the serial thread with response #its bad but what will you do ? #well ok we could set a read timeout so it periodically unblocks root=self.root root.after(1000,lambda :root.destroy(),sys.exit())#XXX def up(self,k,v): if(len(v)>0): if(not k.find("__HELP_")==-1): self.helplabels[int(k[len("__HELP_"):])].config(text=v) def pack(self): for i in self.helplabels: i.pack(fill=tk.X,expand=True) self.recordbutton.pack() self.pointtab.pack() def serialtask(self): while(self.running): data=self.port.read_until() line=data.decode("utf-8") print("serial got"+line) if(len(line)>1): self.ep.recl(line) class pointtab: #this center point of this multithreaded app could use a mutex def __init__(self,root,stlendpoint,filepath): self.root=root self.psvl=tk.Label(self.root,bd=1,relief=tk.SUNKEN,anchor=tk.W) self.ep=stlendpoint self.scroll=tk.Scrollbar(self.root) #self.namebox=tk.Listbox(self.scroll,yscrollcommand=self.scroll.set) #self.valbox=tk.Listbox(self.scroll,yscrollcommand=self.scroll.set) self.namebox=tk.Listbox(self.scroll,height=20) self.valbox=tk.Listbox(self.scroll,height=20) self.scroll.config(command=lambda *a:(self.namebox.yview(*a),self.valbox.yview(*a))) self.valbox.bind("<>",self.selectval) self.ep.onset(self.up) self.ep.ondname(self.dname) self.recording=False self.procvals={} self.procnames=[] self.recordedlines=0 self.root.after(250,lambda :self.searchnames(self.root,0)) self.csvfile=open(filepath,'w') def dname(self,k): if(k not in self.namebox.get(0,tk.END)and k.find("__HELP_")==-1 ): self.namebox.insert(tk.END,k) self.ep.query(k) def up(self,k,v): print("pointtab up k:"+k+"v:"+v) if((not (k[0] == "_" and k[1].isdigit())) and k in self.namebox.get(0,tk.END)): print("new val") i=self.namebox.get(0,tk.END).index(k) print(i) self.valbox.delete(i) self.valbox.insert(i,v) elif(len(v) >0 and k not in self.namebox.get(0,tk.END)): print("new key") if(k=="__NAME__"): self.root.title(v+" STP Console") self.namebox.insert(tk.END,k) self.ep.query(k) self.namebox.delete(0,tk.END) self.valbox.delete(0,tk.END)#super duper un thread safe for i in self.ep.d: self.namebox.insert(tk.END,i) self.valbox.insert(tk.END,self.ep.d[i]) if("__PROCESS_STATE__" in self.ep.d.keys() and k in self.ep.d["__PROCESS_STATE__"].split(" ")): self.procvals[k]=v if(set(self.procvals.keys())==set(self.ep.d["__PROCESS_STATE__"].split(" "))): self.psvl.config(text="Process Recorded Line:"+str(self.recordedlines)+"\n"+str(self.procvals)) if(self.recording): self.record() self.procvals={} def selectval(self,e): key=(self.namebox.get(e.widget.curselection()[0])) val=(self.valbox.get(e.widget.curselection()[0])) val=tkinter.simpledialog.askstring("Update Point",key,initialvalue=val) self.ep.send(key,val) self.ep.query(key) def record(self):#TODO: this should be moved into encapsulating class print("recorded") self.recordedlines+=1 self.csvfile.write(str(time.time())+"\t") for i in sorted(self.procvals): self.csvfile.write(str(self.procvals[i])+"\t") self.csvfile.write("\n") self.csvfile.flush() def beginrecord(self,w):#TODO: See record print("started recording") self.csvfile.write("TIME\t") self.recording= not self.recording if(self.recording): w.config(text="Pause Recording") else: w.config(text="Resume Recording") if(self.recordedlines==0): self.recordedlines=1 for i in sorted(self.ep.d["__PROCESS_STATE__"].split(" ")): self.csvfile.write(i+"\t") self.csvfile.write("\n") def pack(self): self.scroll.pack(fill=tk.BOTH) self.namebox.pack(side=tk.LEFT,fill=tk.BOTH,expand=True) self.valbox.pack(side=tk.RIGHT,fill=tk.BOTH,expand=True) self.psvl.pack(side=tk.BOTTOM,fill=tk.X) def searchnames(self,r,start):#Such jank print("start:"+str(start)) if(len(self.ep.d)<1 and start <2): start=0 self.ep.query("_1") r.after(10000,lambda :self.searchnames(r,start+1))#heh elif(start <2): start=1 if(start<15 and start>=1): self.ep.query("_"+str(start)) r.after(300,lambda :self.searchnames(r,start+1)) if(start==15): self.ep.query("__REPORT_PERIOD__") r.after(200,lambda :self.searchnames(r,start+1)) if(start==16): self.ep.query("__NAME__") r.after(200,lambda :self.searchnames(r,start+1)) if(start==17): self.ep.query("__PROCESS_STATE__") r.after(200,lambda :self.searchnames(r,start+1)) if(start>17 and start<26): self.ep.query("__HELP_"+str(start-17)) r.after(200,lambda :self.searchnames(r,start+1)) def fillports(p): i=1 p.insert(0,"fakefirmware.exe") p.insert(1,"INTERNETSU!!!!") for j in serial.tools.list_ports.comports(): p.insert(i,j[0]) i+=1 def endapp(e): e.after(500,lambda:(e.destroy(),sys.exit())) def pickfile(e,reroot): #TODO: check if selection is out of range port=(e.widget.get(e.widget.curselection()[0])) e.widget.unbind("<>") root=reroot()[0] fname=tk.filedialog.asksaveasfilename(parent=root,title="Select a file for data") gui=stpgui(root,port,fname) gui.pack() def main(): #TODO: CLI arguments (for shortcuts) root=tk.Tk("STP Console") root.title("Select Device") port_selector=tk.Listbox(root) port_selector.bind("<>", lambda e:(pickfile(e, lambda : (root,port_selector.pack_forget())) )) fillports(port_selector) port_selector.pack() root.protocol("WM_DELETE_WINDOW",lambda :(print("wm_delete"),endapp(root))) root.mainloop() if __name__ == "__main__": main()