From 54dc6a13412fc28b0247cab6eea7182a8044d734 Mon Sep 17 00:00:00 2001 From: shrlnm Date: Sun, 26 May 2019 09:20:05 +0300 Subject: [PATCH] 9p add soh.py --- pyren/soh.py | 592 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 592 insertions(+) create mode 100755 pyren/soh.py diff --git a/pyren/soh.py b/pyren/soh.py new file mode 100755 index 0000000..4cb41c0 --- /dev/null +++ b/pyren/soh.py @@ -0,0 +1,592 @@ +#! /usr/bin/env python +# -*- 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 tkMessageBox +except ImportError: + import tkinter as tk + import tkMessageBox + +try: + import 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 = unicode(desc.encode("ascii", "ignore")) + ret.append(port+u';'+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): + tkMessageBox.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()=='': + tkMessageBox.showinfo("INFO", "Select ELM port and enter the temperature. ") + else: + tkMessageBox.showinfo("INFO", "Select ELM port. ") + return + + if batTemp.get()=='': + tkMessageBox.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: + tkMessageBox.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: + tkMessageBox.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: + tkMessageBox.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 = 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 = 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 34" )) + + 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])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 u0: + 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() + + + + +