#Battery Modelling Tool for the NGM and NGU to build a battery model from the discharge log file of your battery
#this version outputs one .csv file for your NGM/NGU
#Version 1.5.6
#Date 07-27-2021



#opens file-explorer for choosing the logging-file
def openFile():
    root = tk.Tk()
    root.withdraw()
    root.update()
    file_path=filedialog.askopenfilename()
    root.destroy()
    return file_path
#opens file-explorer for choosing the save location
def save():
    root = tk.Tk()
    root.withdraw()
    root.update()
    file = filedialog.asksaveasfilename(title="Safe as: ")
    root.destroy()
    return file

#replace NULL with empty string
def fix_nulls(daten):
    for line in daten:
        yield line.replace('\0', '')

#rounds on the next bigger number e.g. round_it(43) = 50
def round_it(num):
    l = len(str(num)) - 1
    rounded = round(num / 10.0 ** l)
    rounded = int(rounded * 10 ** l)
    return rounded
 
#here begins the Fileprocessing       
def open_file(file_path):
   with open(file_path) as f: 
       global delim #store the delimiter in a global variable
       delim = csv.Sniffer().sniff(f.read(1024))
       daten = csv.reader(fix_nulls(f),delim) #clean up the data
       
       list1=[];list2=[];list3=[]
       for i in daten:
               list3.append(i)
               
       list3 = list3[16:] #data begin after line 16
       for row in list3:
           list1.append(row[2]) #current
           list2.append(row[1]) #voltage
        
       voltage=[float(i) for i in list2 if i != 'nan' and i != ""]
       
       #this is a way to find the beginning of the battery discharge
       beginning = 0
       x=0
       k= max(voltage[0:5000])
       for i in voltage:
           if i == k:
               beginning = x
           x+=1
       voltage = voltage[beginning:]
       
       #clean up current data
       current = []
       for i in list1:
           if i != 'nan' and i != "":
               current.append(-float(i))
           else:
               current.append(0)
       current = current[beginning:]

       discharge_current = max(current) #discharge current is the maximum current
       #getting the time from this format 16:20:03.5 into this format 3.5
       z=0    
       time1=[]
       for i in list3:
           time1.append(i[0])  
       time2=[]
       for i in time1:
           time2.append(i[len(i)-4:len(i)])
       time3 = [float(i) for i in time2]

       sample_rate = time3[10]-time3[9]
       sample_rate = round(sample_rate,2)

       time=[]#building a new list of the new time
       for i in range(len(current)):
           time.append(z)
           z+=sample_rate
        
       a = 0
       b = 0
       upper = max(current)
       upper2 = upper*0.9
       #this cycle is about to find the cycle time between discharge and open circuit
       for i in range(100,len(current)):
           if upper2 <= current[i] <= upper:
               a=i
               break   
       lower = 0.1*upper
       for i in range(a,a+1000):
           if current[i]<lower:
               b=i
               break
       cycle_time = round_it((b-a+2)) #round on the next bigger number 44 => 50
       
       print("Battery was discharged with %2.4f A" % discharge_current)
       
       return [voltage,current,discharge_current,cycle_time,sample_rate,time]

#calculates the internal resistance, the SoC and the VoC
def internal_resistance(voltage,current,discharge_current,cycle_time,sample_rate,time):    
    hilfe4=[]
    hilfe5=[]
    hilfe6=[]
    hilfe7=[]
    VoC_neu=[]
    voltage_neu=[]
    current_neu=[]
    current_neu2=[]
    
    #the discharge current is a linear graph over the discharge
    dis_current = it.cumtrapz(current,x=time,initial=0)
    dis_current = [i/3600 for i in dis_current] #change in Ah
    
    #in this cycle, current and voltage are getting shorter
    #both are separated into discharge and open circuit
    #this works with the cycle time
    werte0=0
    werte = int(cycle_time*2)
    x=0
    for i in range(werte0+x,len(voltage)):
        if i>=(werte0+x) and i<=(werte+x):
            hilfe4.append(voltage[i])
            hilfe5.append(voltage[i])
            hilfe6.append(dis_current[i])
            hilfe7.append(current[i])
        else:
            voltage_neu.append(min(hilfe5))     
            current_neu.append(s.mean(hilfe6))
            VoC_neu.append(max(hilfe4))   
            current_neu2.append(max(hilfe7))
            hilfe4=[]
            hilfe5=[]
            hilfe6=[]
            hilfe7=[]
            x+=cycle_time*2
    
    
    ende = len(voltage_neu)
    for i in range(1,len(voltage_neu)):
        if voltage_neu[i]<=(voltage_neu[i-1]//2):
           ende = i
           break
    voltage_neu = voltage_neu[:ende]
    VoC_neu = VoC_neu[:ende]
    current_neu2 = current_neu2[:ende]
    current_neu = current_neu[:ende]    
    
    #as soon as the battery is discharged, the incline of the voltage while discharging is nearly 0
    #this cycle finds the end of the discharge
    length=0
    x=0
    for i in range(0,len(VoC_neu)):
        if VoC_neu[i]==voltage_neu[i]:
            length = x
        else:
            x+=1

    if length >= len(VoC_neu)//2:
        VoC_neu =VoC_neu[:length]      
        voltage_neu =voltage_neu[:length]      
        current_neu = current_neu[:length]        
            
#    del VoC_neu[len(VoC_neu)-1] 
#    del voltage_neu[len(voltage_neu)-1]
    voltage_neu = voltage_neu[:len(VoC_neu)]

    #calculation of the internal resistance       
    R=[]
    for i in range(0,len(voltage_neu)):
            R.append((VoC_neu[i]-voltage_neu[i])/discharge_current)

    nom_capacity = max(dis_current)
    #calculation of the SoC
    SoC = [(1-i/nom_capacity)*100 for i in current_neu]#[:len(R)]
    
    
    #this cycle calculates the actual model
    #for every SoC from 100 to 0 one value of voltage, resistance and current
    voltage_new =[];int_res_new=[];SoC_new=[];dis_cur_new=[];dis_volt=[]
    werte1 = 100.5
    werte2 = 99.5
    help_list=[];help_list2=[];help_list3=[];help_list4=[];help_list5=[]
    x=0;item=0
    #inbetween the SoC like 100 and 99, calculate the max, min or mean for each value

    for i in SoC:
        if i<=(werte1-x) and i>=(werte2-x):
                help_list.append(i) 
                help_list2.append(R[item])
                help_list3.append(VoC_neu[item])
                help_list4.append(current_neu[item])
                help_list5.append(voltage_neu[item])    
        else:
            mean = s.mean(help_list)
            SoC_new.append(mean)
            mean2 = max(help_list2)
            int_res_new.append(mean2)
            mean3 = max(help_list3)
            voltage_new.append(mean3)
            mean4 = s.mean(help_list4)
            dis_cur_new.append(mean4)
            mean5=min(help_list5)
            dis_volt.append(mean5)
            mean=0;mean2=0;mean3=0;mean4=0;mean5=0
            help_list=[];help_list2=[];help_list3=[];help_list4=[];help_list5=[]
            x=x+1
        item+=1
    
    SoC_new = [round(e,0) for e in SoC_new]
    SoC_new.append(0.0)
    r=[];sa=[];t=[];u=[]
    d=0
    for i in SoC:
        if 0.5 >= i >= 0:
            r.append(R[d])
            sa.append(VoC_neu[d])
            t.append(current_neu[d])
            u.append(voltage_neu[d])
        d+=1
    if (len(sa) and len(r) and len(t) and len(u)) != 0:    
        voltage_new.append(min(sa))
        int_res_new.append(s.mean(r))
        dis_cur_new.append(max(t))
        dis_volt.append(min(u))
    
    return [dis_current,nom_capacity,voltage_new,int_res_new,SoC_new,dis_cur_new,dis_volt]

#plots the informations from the battery model 
def plots(dis_current,nom_capacity,voltage_new,int_res_new,SoC_new,dis_cur_new,dis_volt):    
    fig = plt.figure(figsize=(9,10))
    sub1 = fig.add_subplot(221)
    sub1.set_title("Internal Resistance")
    sub1.set_ylabel("Resistance ["+u"\u03A9"+"]")
    sub1.plot(int_res_new[::-1],'-r')
    sub1.grid(True)
    
    sub2 = fig.add_subplot(222)
    sub2.set_title("Battery Voltage")
    sub2.set_ylabel("Voltage [V]")
    sub2.plot(voltage_new[::-1],'-b')
    sub2.grid(True)
    
    sub3 = fig.add_subplot(223)
    sub3.set_title("Capacity")
    sub3.set_xlabel("SoC [%]")
    sub3.set_ylabel("Capacity [Ah]")
    sub3.plot(dis_cur_new,'-g')
    sub3.grid(True)
    
    sub4 = fig.add_subplot(224)
    sub4.set_title("VoC vs. discharge Voltage")
    sub4.set_xlabel("SoC [%]")
    sub4.set_ylabel("VoC [V]")
    sub4.plot(dis_volt[::-1],'-r',label='Discharge Voltage')
    sub4.plot(voltage_new[::-1],'-k',label='VoC')
    sub4.legend()
    sub4.grid(True)
    fig.show()

#writes the calculated data to the .xlsx file    
def write_file(filename,SoC_new,int_res_new,voltage_new,dis_current):
    #looking for the save file and if its already labeled with .csv
    if '.csv' in filename:
        pass
    else:
        filename = filename + '.csv'
    #some file information    
    header = ['#Device','#Format','#Capacity','#InitialSoC','#CurrentLimitEndOfDischarge',
              '#CurrentLimitRegularCharge','#CurrentLimitEndOfCharge']
    first_line =['SoC','Voltage','Resistance']
    
    a = SoC_new[::-1]
    b = voltage_new[::-1]
    c = int_res_new[::-1]
    
    liste2 = ["","BATTERY",max(dis_current),100,0.001,3.010,3.010]
    #writes all the information into the file
    with open(filename, mode='w',newline='') as fw:
        f = csv.writer(fw, delim)
        for i in range(0,len(header)):
            f.writerow([header[i],liste2[i]])
        f.writerow(first_line)
        for i in range(0,len(SoC_new)):
            f.writerow([a[i],b[i],c[i]])
        fw.close()

if __name__ == '__main__':
     import csv
     from matplotlib import interactive
     import matplotlib.pyplot as plt
     import scipy.integrate as it
     import statistics as s
     import tkinter as tk
     from tkinter import filedialog
    
     liste=open_file(openFile())
     liste2=internal_resistance(liste[0],liste[1],liste[2],liste[3],liste[4],liste[5])
     plots(liste2[0],liste2[1],liste2[2],liste2[3],liste2[4],liste2[5],liste2[6])
     interactive(True)
     write_file(save(),liste2[4],liste2[3],liste2[2],liste2[0])

     