pyren/pyren3/soh.py
2022-08-21 10:49:41 +03:00

593 lines
19 KiB
Python
Executable File

#! /usr/bin/env python3
# -*- coding: utf-8 -*-
#
# GUI module generated by PAGE version 4.19
# in conjunction with Tcl version 8.6
# Dec 11, 2018 11:56:10 PM MSK platform: Darwin
help = ''' Battary SOH (State Of Health)
1) Use only USB ELM327 adapter. BT and WiFi adapters are not compatible.
2) Connect an adapter to the OBD outlet
3) Turn on ignition but not start the engine
4) Select the port with adapter
5) Enter current outdoor temperature in celsious
6) Press "Start" button. The voltage should be shown.
7) Calibrate the voltage if needed
8) Start the engine
9) Now the SOH coefficient should be shown instead of voltage
SOH > 0 The battery is good
SOH < 0 The battery is bad
'''
import sys
import time
import datetime
try:
import tkinter as tk
import tkinter.messagebox
except ImportError:
import tkinter as tk
import tkinter.messagebox
try:
import tkinter.ttk
py3 = False
except ImportError:
import tkinter.ttk as ttk
py3 = True
try:
import serial
from serial.tools import list_ports
except ImportError:
print("\n\n\n\tPlease install pyserial module")
sys.exit()
from mod_elm import ELM
import mod_globals
def set_Tk_var():
global combobox
global atcv
global batTemp
combobox = tk.StringVar()
atcv = tk.StringVar()
atcv.set("12.00")
batTemp = tk.StringVar()
batTemp.set("")
def init(top, gui, *args, **kwargs):
global w, top_level, root
w = gui
top_level = top
root = top
def destroy_window():
# Function which closes the window.
global top_level
top_level.destroy()
top_level = None
def vp_start_gui():
'''Starting point when module is the main routine.'''
global val, w, root
root = tk.Tk()
root.resizable(width=False, height=False)
set_Tk_var()
top = tl (root)
init(root, top)
root.mainloop()
def vp_exit_gui():
global val, w, root
root.destroy()
root = None
w = None
def getPortList():
ret = []
iterator = sorted(list(list_ports.comports()))
for port, desc, hwid in iterator:
try:
de = str(desc.encode("ascii", "ignore"))
ret.append(port+';'+de)
except:
ret.append(port+';')
#if '192.168.0.10:35000;WiFi' not in ret:
# ret.append('192.168.0.10:35000;WiFi')
#else:
# ret = ['BT','192.168.0.10:35000']
return ret
class tl:
cw = 630
ch = 415
tab = 20
xmax = 2
ymax = 15.0
pre_len = 0.2 # seconds
startThr = 1 # start threshold
v0 = 0
v1 = 0
v2 = 0
T0 = 0 # T0 = T1 - pre_len
T1 = 0 # Time of engine start (first valley)
T2 = 0 # second valley
def cmd_Help(self):
tkinter.messagebox.showinfo("INFO", help )
def volt_extr(self, s):
s = s.upper()
r = 0.0
for l in s.split('\n')[1:]:
if '.' in l and l[-1]=='V':
r = float(l[:-1])
return r
return r
def cmd_Start(self):
global batTemp
p_name = self.port_name.get().split(';')[0]
if p_name.strip()=='':
if batTemp.get()=='':
tkinter.messagebox.showinfo("INFO", "Select ELM port and enter the temperature. ")
else:
tkinter.messagebox.showinfo("INFO", "Select ELM port. ")
return
if batTemp.get()=='':
tkinter.messagebox.showinfo("INFO", "Enter the temperature. ")
return
self.l_volt.config(fg='black', bg = "#d9d9d9")
self.CV.delete("all")
self.axis( self.top )
self.showHistory()
# start ELM
try:
mod_globals.opt_speed = 38400
mod_globals.opt_rate = 230400
self.elm = ELM(p_name, mod_globals.opt_speed, '')
self.elm.port.soft_boudrate(mod_globals.opt_rate)
except:
tkinter.messagebox.showinfo("INFO", "ELM is not connected or incompatible. ")
return
rsp = self.elm.send_raw('ATWS')
t0 = int(round(time.time() * 1000))
rsp = self.elm.send_raw('ATRV')
t2 = int(round(time.time() * 1000))
rt = t2 - t0
if rt > 50:
tkinter.messagebox.showinfo("ERROR", "Connection is too slow. Use USB-ELM327 ")
return
self.BTN_Start.config(state='disabled')
self.BTN_Calib.config(state='normal')
phase = 0
prefix = [0]*256
u = 256
data = []
cvolt = 0.0
while phase<2:
try:
pvolt = cvolt
cvolt = self.volt_extr(self.elm.send_raw('ATRV'))
t1 = t2
t2 = int(round(time.time() * 1000))
except:
tkinter.messagebox.showinfo("ERROR", "Unknown response from ELM ")
self.BTN_Start.config(state='normal')
self.BTN_Calib.config(state='disabled')
del(self.elm)
return
if phase==0:
u = u + 2
if u>255:
u = 0
prefix[u] = t2 / 1000.
prefix[u+1] = cvolt
if u % 10 == 0:
self.v_volt.set('%.2f'%cvolt+'V')
self.FR1.update()
if (t2-t1) < 50 and (pvolt-cvolt)>self.startThr:
# engine start detected
self.TS = t1
u_beg = u
while prefix[u] >= (t2/1000.-self.pre_len):
u = u - 2
if u_beg < 0:
u_beg = 254
if u == u_beg:
u = u + 2
if u == 256:
u = 0
break
t0 = prefix[u] * 1000
while u != u_beg:
data.append(prefix[u])
data.append(prefix[u+1])
u = u + 2
if u == 256:
u = 0
data.append(prefix[u])
data.append(prefix[u+1])
phase = 1
elif phase==1:
if (t2-t0)>self.xmax*1000:
break
data.append(t2 / 1000.)
data.append(cvolt)
self.BTN_Start.config(state='normal')
self.BTN_Calib.config(state='disabled')
del(self.elm)
new_line = {}
new_line['at'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
new_line['temp'] = int(batTemp.get())
new_line['volt'] = data
self.drowLine(new_line, False)
new_line['soh'] = self.SOH
fo = open( "history.txt", 'a' )
fo.write("{'at':'"+str(new_line['at'])+"','soh':"+str(new_line['soh'])+",'temp':"+\
str(new_line['temp'])+",'volt':"+str(new_line['volt'])+"}\n")
fo.close()
def cmd_Exit(self):
vp_exit_gui()
def cmd_Calibrate(self):
global atcv
cv = atcv.get()
self.elm.send_raw('ATCV'+cv.replace('.',''))
self.elm.send_raw('ATRV')
def __init__(self, top=None):
'''This class configures and populates the toplevel window.
top is the toplevel containing window.'''
_bgcolor = '#d9d9d9' # X11 color: 'gray85'
_fgcolor = '#000000' # X11 color: 'black'
_compcolor = '#d9d9d9' # X11 color: 'gray85'
_ana2color = '#ececec' # Closest X11 color: 'gray92'
self.style = tkinter.ttk.Style()
if sys.platform == "win32":
self.style.theme_use('winnative')
self.style.configure('.',background=_bgcolor)
self.style.configure('.',foreground=_fgcolor)
self.style.configure('.',font="TkDefaultFont")
self.style.map('.',background=
[('selected', _compcolor), ('active',_ana2color)])
self.top = top
top.geometry("640x480+268+100")
top.title("SOH check")
top.configure(background="#d9d9d9")
top.configure(height="480")
top.configure(highlightbackground="#d9d9d9")
top.configure(highlightcolor="black")
top.configure(width="640")
self.FR1 = tk.Frame(top)
self.FR1.place(relx=0.0, rely=0.0, relheight=0.125, relwidth=1.0)
self.FR1.configure(relief='groove')
self.FR1.configure(borderwidth="2")
self.FR1.configure(relief='groove')
self.FR1.configure(background="#d9d9d9")
self.FR1.configure(highlightbackground="#d9d9d9")
self.FR1.configure(highlightcolor="black")
self.FR1.configure(width=685)
self.Label1 = tk.Label(self.FR1)
self.Label1.place(relx=0.0, rely=0.04, height=24, width=45)
self.Label1.configure(activebackground="#f9f9f9")
self.Label1.configure(activeforeground="black")
self.Label1.configure(background="#d9d9d9")
self.Label1.configure(foreground="#000000")
self.Label1.configure(highlightbackground="#d9d9d9")
self.Label1.configure(highlightcolor="black")
self.Label1.configure(text='''ELM''')
self.Label1.configure(width=45)
self.port_name = tk.StringVar()
self.CB_Port = tkinter.ttk.Combobox(self.FR1)
self.CB_Port.place(relx=0.07, rely=0.03, relheight=0.45, relwidth=0.3)
self.CB_Port.configure(textvariable=combobox)
self.CB_Port.configure(values=getPortList())
self.CB_Port.configure(textvariable=self.port_name)
self.CB_Port.configure(width=116)
self.CB_Port.configure(takefocus="")
self.v_volt = tk.StringVar()
self.v_volt.set("00.00V")
self.l_volt = tk.Label(self.FR1, textvariable=self.v_volt, bg = "#d9d9d9", fg='black')
self.l_volt.place(relx=0.55, rely=0.0, height=55, width=130)
self.l_volt.config(font=("Courier 28" ))
self.Label2 = tk.Label(self.FR1)
self.Label2.place(relx=0.23, rely=0.583, height=14, width=55)
self.Label2.configure(activebackground="#f9f9f9")
self.Label2.configure(activeforeground="black")
self.Label2.configure(background="#d9d9d9")
self.Label2.configure(foreground="#000000")
self.Label2.configure(highlightbackground="#d9d9d9")
self.Label2.configure(highlightcolor="black")
self.Label2.configure(text='''ATCV''')
self.Label2.configure(width=55)
self.ENT_Calib = tk.Entry(self.FR1)
self.ENT_Calib.place(relx=0.3, rely=0.46, height=27, relwidth=0.072)
self.ENT_Calib.configure(background="white")
self.ENT_Calib.configure(font="TkFixedFont")
self.ENT_Calib.configure(foreground="#000000")
self.ENT_Calib.configure(highlightbackground="#d9d9d9")
self.ENT_Calib.configure(highlightcolor="black")
self.ENT_Calib.configure(insertbackground="black")
self.ENT_Calib.configure(selectbackground="#c4c4c4")
self.ENT_Calib.configure(selectforeground="black")
self.ENT_Calib.configure(textvariable=atcv)
self.BTN_Calib = tk.Button(self.FR1)
self.BTN_Calib.place(relx=0.38, rely=0.5, height=22, width=81)
self.BTN_Calib.configure(activebackground="#ececec")
self.BTN_Calib.configure(activeforeground="#000000")
self.BTN_Calib.configure(background="#d9d9d9")
self.BTN_Calib.configure(foreground="#000000")
self.BTN_Calib.configure(highlightbackground="#d9d9d9")
self.BTN_Calib.configure(highlightcolor="black")
self.BTN_Calib.configure(text='''Calibrate''')
self.BTN_Calib.config(state='disabled')
self.BTN_Calib.configure(command=self.cmd_Calibrate)
self.BTN_Start = tk.Button(self.FR1)
self.BTN_Start.place(relx=0.773, rely=0.083, height=22, width=51)
self.BTN_Start.configure(activebackground="#ececec")
self.BTN_Start.configure(activeforeground="#000000")
self.BTN_Start.configure(background="#d9d9d9")
self.BTN_Start.configure(foreground="#000000")
self.BTN_Start.configure(highlightbackground="#d9d9d9")
self.BTN_Start.configure(highlightcolor="black")
self.BTN_Start.configure(text='''Start''')
self.BTN_Start.configure(command=self.cmd_Start)
self.BTN_Exit = tk.Button(self.FR1)
self.BTN_Exit.place(relx=0.875, rely=0.083, height=22, width=51)
self.BTN_Exit.configure(activebackground="#ececec")
self.BTN_Exit.configure(activeforeground="#000000")
self.BTN_Exit.configure(background="#d9d9d9")
self.BTN_Exit.configure(foreground="#000000")
self.BTN_Exit.configure(highlightbackground="#d9d9d9")
self.BTN_Exit.configure(highlightcolor="black")
self.BTN_Exit.configure(text='''Exit''')
self.BTN_Exit.configure(command=self.cmd_Exit)
self.BTN_Help = tk.Button(self.FR1)
self.BTN_Help.place(relx=0.875, rely=0.5, height=22, width=51)
self.BTN_Help.configure(activebackground="#ececec")
self.BTN_Help.configure(activeforeground="#000000")
self.BTN_Help.configure(background="#d9d9d9")
self.BTN_Help.configure(foreground="#000000")
self.BTN_Help.configure(highlightbackground="#d9d9d9")
self.BTN_Help.configure(highlightcolor="black")
self.BTN_Help.configure(text='''Help''')
self.BTN_Help.config(state='active')
self.BTN_Help.configure(command=self.cmd_Help)
self.Label3 = tk.Label(self.FR1)
self.Label3.place(relx=0., rely=0.583, height=14, width=95)
self.Label3.configure(background="#d9d9d9")
self.Label3.configure(foreground="#000000")
self.Label3.configure(text='''Temperature''')
self.Label3.configure(width=95)
self.ENT_Temper = tk.Entry(self.FR1)
self.ENT_Temper.place(relx=0.14, rely=0.46, height=27, relwidth=0.072)
self.ENT_Temper.configure(background="white")
self.ENT_Temper.configure(font="TkFixedFont")
self.ENT_Temper.configure(foreground="#000000")
self.ENT_Temper.configure(highlightbackground="#d9d9d9")
self.ENT_Temper.configure(highlightcolor="black")
self.ENT_Temper.configure(insertbackground="black")
self.ENT_Temper.configure(selectbackground="#c4c4c4")
self.ENT_Temper.configure(selectforeground="black")
self.ENT_Temper.configure(textvariable=batTemp)
self.CV = tk.Canvas(top)
self.CV.place(relx=0.0, rely=0.125, relheight=0.871, relwidth=1.0)
self.CV.configure(background="#ffffff")
self.CV.configure(borderwidth="2")
self.CV.configure(highlightbackground="#d9d9d9")
self.CV.configure(highlightcolor="black")
self.CV.configure(insertbackground="black")
self.CV.configure(relief='ridge')
self.CV.configure(selectbackground="#c4c4c4")
self.CV.configure(selectforeground="black")
self.CV.configure(width=640)
self.axis( top )
self.showHistory()
def showHistory(self):
try:
hf = open('./history.txt','r')
except:
return
line_num = 0
for l in hf.readlines():
line_num = line_num + 1
if l.strip().startswith('{'):
try:
hist_obj = eval(l.strip())
except:
print('ERROR in line: ', line_num)
continue
self.drowLine( hist_obj, True)
def drowLine( self, line, hist = True ):
if hist:
f = 'lightgray'
else:
f = 'blue'
if len(line['volt'])<4:
return
ty = self.tab
tx = self.tab
xmax = self.xmax
ymax = self.ymax
xm = (self.cw - 2. * tx ) // xmax
ym = (self.ch - 2. * ty ) // ymax
i = 0
count = len(line['volt'])//2 - 1
st = float(line['volt'][0])
while i<(count-1):
i = i + 1
x0 = float(line['volt'][(i-1)*2]) - st
y0 = float(line['volt'][(i-1)*2+1])
x1 = float(line['volt'][i*2]) - st
y1 = float(line['volt'][i*2+1])
self.CV.create_line(tx + x0 * xm, ty + (ymax - y0) * ym, tx + x1 * xm, ty + (ymax - y1) * ym, width=1, fill=f )
if hist:
return
self.findPoints( line )
x = tx + (self.T1 - st) * xm
y = ty + (ymax - self.V1) * ym
self.CV.create_oval(x-5, y-5, x+5, y+5, fill="red")
x = tx + (self.T2 - st) * xm
y = ty + (ymax - self.V2) * ym
self.CV.create_oval(x-5, y-5, x+5, y+5, fill="red")
def findPoints(self, line ):
global batTemp
try:
Tc = int(batTemp.get())
except:
Tc = int(line['temp'])
self.T0 = line['volt'][0]
u = 2
mean = 0
count = 0
while (line['volt'][u-1]-line['volt'][u+1])<self.startThr and u<len(line['volt']):
mean += line['volt'][u-1]
count += 1
u += 2
self.V0 = mean // count
# find first min
min = line['volt'][u-1]
while line['volt'][u+1]-min < 0.25 and u<len(line['volt']):
if line['volt'][u+1]<min:
min = line['volt'][u+1]
self.T1 = line['volt'][u]
u += 2
self.V1 = min
# find first max
max = min
while max - line['volt'][u+1] < 0.25 and u<len(line['volt']):
if line['volt'][u+1]>max:
max = line['volt'][u+1]
u += 2
# find second min
min = line['volt'][u-1]
while line['volt'][u+1]-min < 0.25 and u<len(line['volt']):
if line['volt'][u+1]<min:
min = line['volt'][u+1]
self.T2 = line['volt'][u]
u += 2
self.V2 = min
self.vth1 = 0
tpol = [ 2.40384615e-09, -7.79428904e-08, -6.37383450e-06, 8.12208625e-05, 1.06745338e-02, 1.97290210e-01]
for i in range(6):
self.vth1 = self.vth1 + tpol[i] * (Tc ** (5 - i))
self.vth2 = (self.V0 - self.V1) * .07 - .23
self.vth = self.vth1 + self.vth2
self.SOH = (self.V2 - self.V1) - self.vth
self.v_volt.set('%.2f' % self.SOH )
self.FR1.update()
if self.SOH>0:
self.l_volt.config(fg='black', bg = "green")
else:
self.l_volt.config(fg='black', bg = "red")
return
def axis( self, top ):
ty = self.tab
tx = self.tab
xmax = self.xmax
ymax = self.ymax
xm = (self.cw - 2. * tx ) // xmax
ym = (self.ch - 2. * ty ) // ymax
x = 0
while x<=self.xmax:
if x == 0:
self.CV.create_line( tx+x*xm, ty, tx+x*xm, self.ch-ty, width=1)
else:
self.CV.create_line( tx+x*xm, ty, tx+x*xm, self.ch-ty, width=0, dash=(5,5), fill='lightgray')
self.CV.create_text(tx+x*xm, self.ch-ty//2., text=str(x), justify=tk.CENTER, font="Verdana 8")
x = x + 0.2
y = 0
while y<=15:
if y == 0:
self.CV.create_line( tx, ty+(ymax-y)*ym, self.cw-ty, ty+(ymax-y)*ym, width=1)
else:
self.CV.create_line( tx, ty+(ymax-y)*ym, self.cw-ty, ty+(ymax-y)*ym, width=0, dash=(5,5), fill='lightgray')
self.CV.create_text( ty//2, ty+(ymax-y)*ym, text=str(y), font="Verdana 8")
y = y + 1
if __name__ == '__main__':
vp_start_gui()