|
@@ -0,0 +1,732 @@
|
|
|
+import pandas as pd
|
|
|
+import numpy as np
|
|
|
+import datetime
|
|
|
+import bisect
|
|
|
+import matplotlib.pyplot as plt
|
|
|
+import BatParam
|
|
|
+import DBDownload
|
|
|
+
|
|
|
+class Soh:
|
|
|
+ def __init__(self,sn,df_bms,df_accum): #参数初始化
|
|
|
+
|
|
|
+ self.sn=sn
|
|
|
+ self.param=BatParam.BatParam(sn)
|
|
|
+ self.df_bms=df_bms
|
|
|
+ self.packcrnt=df_bms['总电流[A]']
|
|
|
+ self.packvolt=df_bms['总电压[V]']
|
|
|
+ self.bms_soc=df_bms['SOC[%]']
|
|
|
+ self.bms_soh=df_bms['SOH[%]']
|
|
|
+ self.bmsstat=df_bms['充电状态']
|
|
|
+ self.bmstime= pd.to_datetime(df_bms['时间戳'], format='%Y-%m-%d %H:%M:%S')
|
|
|
+
|
|
|
+ self.df_accum=df_accum
|
|
|
+ self.accumtime=pd.to_datetime(df_accum['时间戳'], format='%Y-%m-%d %H:%M:%S')
|
|
|
+
|
|
|
+ def soh(self):
|
|
|
+ if self.param.celltype==1 or self.param.celltype==2:
|
|
|
+ df_res=self._ncmsoh_twopoint()
|
|
|
+ return df_res
|
|
|
+
|
|
|
+ elif self.param.celltype==99:
|
|
|
+ df_res=self._lfpsoh()
|
|
|
+ return df_res
|
|
|
+
|
|
|
+ def getdata(self): #获取已有soh结果
|
|
|
+ host='rm-bp10j10qy42bzy0q77o.mysql.rds.aliyuncs.com'
|
|
|
+ port=3306
|
|
|
+ db='qx_cas'
|
|
|
+ user='qx_read'
|
|
|
+ password='Qx@123456'
|
|
|
+
|
|
|
+ now_time=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
|
+ now_time=datetime.datetime.strptime(now_time,'%Y-%m-%d %H:%M:%S')
|
|
|
+ start_time=now_time-datetime.timedelta(days=60)
|
|
|
+ end_time=str(now_time)
|
|
|
+ start_time=str(start_time)
|
|
|
+
|
|
|
+ DBManager=DBDownload.DBDownload(host, port, db, user, password)
|
|
|
+ with DBManager as DBManager:
|
|
|
+ df_soh=DBManager.getdata('sn', 'time', 'bms_soh', 'soh', 'soh_err', tablename='soh_result',st=start_time,sp=end_time)
|
|
|
+ self.df_soh=df_soh[df_soh['sn']==self.sn]
|
|
|
+
|
|
|
+ def np_move_avg(self,a, n, mode="same"): #定义滑动滤波函数
|
|
|
+ return (np.convolve(a, np.ones((n,)) / n, mode=mode))
|
|
|
+
|
|
|
+ def _chrgdata(self): #筛选充电数据
|
|
|
+ self.ChgStart=[]
|
|
|
+ self.ChgEnd=[]
|
|
|
+ if len(self.packvolt)>100:
|
|
|
+ for i in range(3, len(self.bmstime) - 3):
|
|
|
+ if i==3 and self.bmsstat[i]==2 and self.bmsstat[i+1]==2 and self.bmsstat[i+2]==2:
|
|
|
+ self.ChgStart.append(i)
|
|
|
+ elif self.bmsstat[i-2]!=2 and self.bmsstat[i-1]!=2 and self.bmsstat[i]==2:
|
|
|
+ self.ChgStart.append(i)
|
|
|
+ elif self.bmsstat[i-1]==2 and self.bmsstat[i]!=2 and self.bmsstat[i+1]!=2:
|
|
|
+ self.ChgEnd.append(i-1)
|
|
|
+ elif i == (len(self.bmstime) - 4) and self.bmsstat[len(self.bmsstat)-1] == 2 and self.bmsstat[len(self.bmsstat)-2] == 2:
|
|
|
+ self.ChgEnd.append(len(self.bmstime)-2)
|
|
|
+
|
|
|
+ def _celltemp_weight(self,num): #寻找当前行数据的最小温度值
|
|
|
+ celltemp = []
|
|
|
+ for j in range(1, self.param.CellTempNums+1):
|
|
|
+ s = str(j)
|
|
|
+ celltemp.append(self.df_bms.loc[num,'单体温度' + s])
|
|
|
+ celltemp.remove(min(celltemp))
|
|
|
+ if self.param.celltype==99:
|
|
|
+ if min(celltemp)>=20:
|
|
|
+ self.tempweight=1
|
|
|
+ self.StandardStandingTime=1800
|
|
|
+ elif min(celltemp)>=10:
|
|
|
+ self.tempweight=0.6
|
|
|
+ self.StandardStandingTime=3600
|
|
|
+ elif min(celltemp)>=5:
|
|
|
+ self.tempweight=0.
|
|
|
+ self.StandardStandingTime=7200
|
|
|
+ else:
|
|
|
+ self.tempweight=0.1
|
|
|
+ self.StandardStandingTime=10800
|
|
|
+ else:
|
|
|
+ if min(celltemp)>=20:
|
|
|
+ self.tempweight=1
|
|
|
+ self.StandardStandingTime=900
|
|
|
+ elif min(celltemp)>=10:
|
|
|
+ self.tempweight=0.8
|
|
|
+ self.StandardStandingTime=1200
|
|
|
+ elif min(celltemp)>=5:
|
|
|
+ self.tempweight=0.6
|
|
|
+ self.StandardStandingTime=1800
|
|
|
+ else:
|
|
|
+ self.tempweight=0.2
|
|
|
+ self.StandardStandingTime=3600
|
|
|
+
|
|
|
+ def _deltsoc_weight(self,deltsoc): #获取SOC差对应的SOH权重值
|
|
|
+ if deltsoc>60:
|
|
|
+ deltsoc_weight=1
|
|
|
+ elif deltsoc>50:
|
|
|
+ deltsoc_weight=0.9
|
|
|
+ elif deltsoc>40:
|
|
|
+ deltsoc_weight=0.7
|
|
|
+ elif deltsoc>30:
|
|
|
+ deltsoc_weight=0.5
|
|
|
+ elif deltsoc>20:
|
|
|
+ deltsoc_weight=0.3
|
|
|
+ else:
|
|
|
+ deltsoc_weight=0
|
|
|
+ return deltsoc_weight
|
|
|
+
|
|
|
+ def _cellvolt_get(self,num): #获取当前行所有电压数据
|
|
|
+ cellvolt=[]
|
|
|
+ for j in range(1, self.param.CellVoltNums+1):
|
|
|
+ s = str(j)
|
|
|
+ cellvolt.append(self.df_bms.loc[num,'单体电压' + s]/1000)
|
|
|
+ return(cellvolt)
|
|
|
+
|
|
|
+ def _ncmsoh_chrg(self): #NCM充电数据soh计算
|
|
|
+ self._chrgdata()
|
|
|
+ self.getdata()
|
|
|
+ ChgStartValid=[]
|
|
|
+ ChgEndValid=[]
|
|
|
+ tempweightlist=[]
|
|
|
+ for i in range(min(len(self.ChgStart),len(self.ChgEnd))):
|
|
|
+ self._celltemp_weight(self.ChgEnd[i]) #获取温度对应的静置时间及权重
|
|
|
+ for k in range(self.ChgStart[i],self.ChgEnd[i]): #去除电流0点
|
|
|
+ if self.packcrnt[k]<-0.5 and self.packcrnt[k+1]>-0.5 and self.packcrnt[k+2]>-0.5 and self.packcrnt[k+3]>-0.5:
|
|
|
+ self.ChgEnd[i]=k
|
|
|
+
|
|
|
+ #筛选满足2点法计算的数据
|
|
|
+ StandingTime=0
|
|
|
+ StandingTime1=0
|
|
|
+ if self.bms_soc[self.ChgEnd[i]]>70 and self.bms_soc[self.ChgStart[i]]<50:
|
|
|
+ for m in range(min(len(self.packcrnt)-self.ChgEnd[i]-2,self.ChgStart[i]-2)):
|
|
|
+ if abs(self.packcrnt[self.ChgStart[i] - m - 1]) < 0.5:
|
|
|
+ StandingTime = StandingTime + (self.bmstime[self.ChgStart[i] - m] - self.bmstime[self.ChgStart[i] - m - 1]).total_seconds()
|
|
|
+ if abs(self.packcrnt[self.ChgEnd[i] + m + 1]) < 0.5:
|
|
|
+ StandingTime1 = StandingTime1 + (self.bmstime[self.ChgEnd[i] + m + 1] - self.bmstime[self.ChgEnd[i] + m]).total_seconds()
|
|
|
+ if StandingTime > self.StandardStandingTime and StandingTime1>self.StandardStandingTime and ((self.bmstime[self.ChgEnd[i]]-self.bmstime[self.ChgStart[i]]).total_seconds())/(self.ChgEnd[i]-self.ChgStart[i])<60: #筛选静置时间>15min且慢充过程丢失数据少
|
|
|
+ if abs(self.packcrnt[self.ChgEnd[i] + m + 2])>0.5 or m==len(self.packcrnt)-self.ChgEnd[i]-3: #如果电流<0.5,继续寻找充电后的静置电压,直到末尾
|
|
|
+ ChgStartValid.append(self.ChgStart[i])
|
|
|
+ ChgEndValid.append(self.ChgEnd[i]+m)
|
|
|
+ tempweightlist.append(self.tempweight)
|
|
|
+ break
|
|
|
+ if abs(self.packcrnt[self.ChgStart[i] - m - 2])>0.5 and abs(self.packcrnt[self.ChgEnd[i] + m + 2])>0.5:
|
|
|
+ break
|
|
|
+
|
|
|
+ if len(ChgStartValid)>0: #两点法计算Soh
|
|
|
+ df_res=pd.DataFrame(columns=('time','sn','soh','soh1'))
|
|
|
+ soh2=[]
|
|
|
+ if not self.df_soh.empty: #获取数据库中上次计算的Soh值
|
|
|
+ soh_init=list(self.df_soh['soh'])[-1]
|
|
|
+ else:
|
|
|
+ soh_init=list(self.bms_soh)[-1]
|
|
|
+
|
|
|
+ for i in range(len(ChgStartValid)):
|
|
|
+ Ah=0
|
|
|
+ for j in range(ChgStartValid[i],ChgEndValid[i]): #计算Ah
|
|
|
+ Step=(self.bmstime[j+1]-self.bmstime[j]).total_seconds()
|
|
|
+ Ah=Ah-self.packcrnt[j+1]*Step/3600
|
|
|
+
|
|
|
+ for j in range(1, self.param.CellVoltNums+1): #计算每个电芯的Soh
|
|
|
+ s = str(j)
|
|
|
+ OCVStart=self.df_bms.loc[ChgStartValid[i]-2,'单体电压' + s]/1000
|
|
|
+ OCVEnd=self.df_bms.loc[ChgEndValid[i]-1,'单体电压' + s]/1000
|
|
|
+ #soh
|
|
|
+ ocv_Soc1=np.interp(OCVStart,self.param.LookTab_OCV,self.param.LookTab_SOC)
|
|
|
+ ocv_Soc2=np.interp(OCVEnd,self.param.LookTab_OCV,self.param.LookTab_SOC)
|
|
|
+
|
|
|
+ soh2.append(Ah*100/((ocv_Soc2-ocv_Soc1)*0.01*self.param.Capacity))
|
|
|
+ soh1=np.mean(soh2)
|
|
|
+ delt_ocv_soc=ocv_Soc2-ocv_Soc1
|
|
|
+ self._deltsoc_weight(delt_ocv_soc)
|
|
|
+ soh_res=soh_init*(1-self.deltsoc_weight*tempweightlist[i])+soh1*self.deltsoc_weight*tempweightlist[i]
|
|
|
+ soh_init=soh_res
|
|
|
+ df_res.loc[i]=[self.bmstime[ChgStartValid[i]],self.sn,soh_res,soh1]
|
|
|
+
|
|
|
+ return df_res
|
|
|
+ return pd.DataFrame()
|
|
|
+
|
|
|
+ def _ncmsoh_twopoint(self):
|
|
|
+ standingpoint_st=[]
|
|
|
+ standingpoint_sp=[]
|
|
|
+ tempweightlist=[]
|
|
|
+ standingtime=0
|
|
|
+ for i in range(3,len(self.df_bms)-3):
|
|
|
+
|
|
|
+ if abs(self.packcrnt[i]) < 0.3: #电流为0
|
|
|
+ delttime=(self.bmstime[i]-self.bmstime[i-1]).total_seconds()
|
|
|
+ standingtime=standingtime+delttime
|
|
|
+ self._celltemp_weight(i) #获取不同温度对应的静置时间
|
|
|
+
|
|
|
+ if standingtime>self.StandardStandingTime: #静置时间满足要求
|
|
|
+ if standingpoint_st:
|
|
|
+ if len(standingpoint_st)>len(standingpoint_sp): #开始时刻已获取,结束时刻未获取
|
|
|
+ cellvolt_now=self._cellvolt_get(i-1) #获取当前行电压数据
|
|
|
+ minocv_socnow=np.interp(min(cellvolt_now),self.param.LookTab_OCV,self.param.LookTab_SOC)
|
|
|
+ cellvolt_st=self._cellvolt_get(standingpoint_st[-1]) #获取开始时刻静置后的电压数据
|
|
|
+ minocv_socst=np.interp(min(cellvolt_st),self.param.LookTab_OCV,self.param.LookTab_SOC)
|
|
|
+
|
|
|
+ if abs(minocv_socst-minocv_socnow)>=40: #当前时刻SOC与开始时刻SOC差>=20
|
|
|
+ if abs(self.packcrnt[i+2])>=0.3: #如果下一时刻电流>=0.5,则压入当前索引
|
|
|
+ standingpoint_sp.append(i)
|
|
|
+ standingpoint_st.append(i)
|
|
|
+ tempweightlist.append(self.tempweight)
|
|
|
+ standingtime=0
|
|
|
+ continue
|
|
|
+ else:
|
|
|
+ if standingtime>3600 or i==len(self.df_bms)-2: #仍处于静置,但静置时间>1h,则直接获取sp时刻,或者到了数据末尾
|
|
|
+ standingpoint_sp.append(i)
|
|
|
+ tempweightlist.append(self.tempweight)
|
|
|
+ continue
|
|
|
+ else:
|
|
|
+ if minocv_socst<50 and minocv_socnow<minocv_socst and abs(self.packcrnt[i+2])>=0.3:
|
|
|
+ standingpoint_st[-1]=i
|
|
|
+ standingtime=0
|
|
|
+ continue
|
|
|
+ elif minocv_socst>=50 and minocv_socnow>minocv_socst and abs(self.packcrnt[i+2])>=0.3:
|
|
|
+ standingpoint_st[-1]=i
|
|
|
+ standingtime=0
|
|
|
+ continue
|
|
|
+ else:
|
|
|
+ continue
|
|
|
+ else:
|
|
|
+ if abs(self.packcrnt[i+2])>=0.5:
|
|
|
+ standingpoint_st.append(i)
|
|
|
+ standingtime=0
|
|
|
+ continue
|
|
|
+ else:
|
|
|
+ continue
|
|
|
+ else:
|
|
|
+ if abs(self.packcrnt[i+2])>0.5:
|
|
|
+ standingpoint_st.append(i)
|
|
|
+ standingtime=0
|
|
|
+ continue
|
|
|
+ else:
|
|
|
+ continue
|
|
|
+ else:
|
|
|
+ continue
|
|
|
+ else:
|
|
|
+ standingtime=0
|
|
|
+ continue
|
|
|
+
|
|
|
+ if standingpoint_sp:
|
|
|
+ self.getdata() #获取已计算的soh
|
|
|
+ column_name=['time_st','time_sp','sn','method','soh','cellsoh1','cellsoh2','cellsoh3','cellsoh4','cellsoh5','cellsoh6','cellsoh7','cellsoh8','cellsoh9','cellsoh10','cellsoh11','cellsoh12','cellsoh13','cellsoh14','cellsoh15','cellsoh16','cellsoh17','cellsoh18','cellsoh19','cellsoh20']
|
|
|
+ df_res=pd.DataFrame(columns=column_name)
|
|
|
+
|
|
|
+ for i in range(len(standingpoint_sp)):
|
|
|
+ cellocv_st=self._cellvolt_get(standingpoint_st[i]) #获取静置点所有电芯的电压
|
|
|
+ cellocv_sp=self._cellvolt_get(standingpoint_sp[i])
|
|
|
+ accumtime=self.accumtime.to_list() #累计量的时间列表
|
|
|
+ timepoint_bms_st=self.bmstime[standingpoint_st[i]] #获取静置点的时间
|
|
|
+ timepoint_bms_sp=self.bmstime[standingpoint_sp[i]]
|
|
|
+ timepoint_accum_st=bisect.bisect(accumtime,timepoint_bms_st) #获取最接近静置点时间的累计量时间点
|
|
|
+ timepoint_accum_sp=bisect.bisect(accumtime,timepoint_bms_sp)
|
|
|
+ if timepoint_accum_sp>=len(accumtime): #防止指针超出数据范围
|
|
|
+ timepoint_accum_sp=len(accumtime)-1
|
|
|
+
|
|
|
+ ah_packcrnt_dis=0
|
|
|
+ ah_packcrnt_chg=0
|
|
|
+ for j in range(standingpoint_st[i]+2,standingpoint_sp[i]): #计算累计Ah
|
|
|
+ Step=(self.bmstime[j+1]-self.bmstime[j]).total_seconds()
|
|
|
+ if self.packcrnt[j+1]>=0:
|
|
|
+ ah_packcrnt_dis=ah_packcrnt_dis+self.packcrnt[j+1]*Step
|
|
|
+ else:
|
|
|
+ ah_packcrnt_chg=ah_packcrnt_chg-self.packcrnt[j+1]*Step
|
|
|
+ ah_packcrnt_chg=ah_packcrnt_chg/3600
|
|
|
+ ah_packcrnt_dis=ah_packcrnt_dis/3600
|
|
|
+ ah_packcrnt=ah_packcrnt_chg-ah_packcrnt_dis #两个静置点的总累计AH,负值代表放电,正值代表充电
|
|
|
+
|
|
|
+ ah_accum_dis=self.df_accum.loc[timepoint_accum_sp,'累计放电电量']-self.df_accum.loc[timepoint_accum_st,'累计放电电量'] #两个静置点之间的放电电量
|
|
|
+ ah_accum_chg=self.df_accum.loc[timepoint_accum_sp,'累计充电电量']-self.df_accum.loc[timepoint_accum_st,'累计充电电量'] #两个静置点之间的充电电量
|
|
|
+ ah_accum_tatol=ah_accum_chg-ah_accum_dis #两个静置点的总累计AH,负值代表放电,正值代表充电
|
|
|
+ ah_accum=ah_packcrnt
|
|
|
+
|
|
|
+ delt_days=(self.bmstime[standingpoint_sp[i]]-self.bmstime[standingpoint_st[i]]).total_seconds()/(3600*24)
|
|
|
+ if delt_days<=1: #两次时间间隔对计算结果的影响
|
|
|
+ soh_weight1=1
|
|
|
+ elif delt_days<=2:
|
|
|
+ soh_weight1=0.9
|
|
|
+ elif delt_days<=3:
|
|
|
+ soh_weight1=0.4
|
|
|
+ else:
|
|
|
+ soh_weight1=0
|
|
|
+
|
|
|
+ if ah_packcrnt_dis<self.param.Capacity: #放电ah数对结果的影响
|
|
|
+ soh_weight1=(1-ah_packcrnt_dis/self.param.Capacity*1.5)*soh_weight1
|
|
|
+ else:
|
|
|
+ soh_weight1=0
|
|
|
+
|
|
|
+ if self.param.Capacity**0.7*0.4 < abs(ah_accum_tatol) < self.param.Capacity: #累计量的权重
|
|
|
+ if abs(ah_accum_tatol-ah_packcrnt)<self.param.Capacity/20:
|
|
|
+ soh_weight1=soh_weight1*1
|
|
|
+ elif abs(ah_accum_tatol-ah_packcrnt) < self.param.Capacity/10:
|
|
|
+ soh_weight1=soh_weight1*0.8
|
|
|
+ else:
|
|
|
+ soh_weight1=0.5
|
|
|
+ else:
|
|
|
+ if self.param.Capacity*0.7*0.4< abs(ah_packcrnt) <self.param.Capacity:
|
|
|
+ soh_weight1=soh_weight1*0.5
|
|
|
+ else:
|
|
|
+ soh_weight1=0
|
|
|
+
|
|
|
+ if ah_packcrnt_chg + ah_packcrnt_dis > self.param.Capacity: #总Ah数对结果的影响
|
|
|
+ soh_weight1=soh_weight1*0.6
|
|
|
+ else:
|
|
|
+ soh_weight1=soh_weight1
|
|
|
+
|
|
|
+ cellsoh=[timepoint_bms_st, timepoint_bms_sp, self.sn, 1]
|
|
|
+ for j in range(20): #计算每个电芯的SOH值
|
|
|
+ if j<self.param.CellVoltNums:
|
|
|
+ ocv_soc1=np.interp(cellocv_st[j],self.param.LookTab_OCV,self.param.LookTab_SOC)
|
|
|
+ ocv_soc2=np.interp(cellocv_sp[j],self.param.LookTab_OCV,self.param.LookTab_SOC)
|
|
|
+ delt_ocv_soc=ocv_soc2-ocv_soc1
|
|
|
+ delt_ocv_soc_weight=self._deltsoc_weight(abs(delt_ocv_soc))
|
|
|
+ soh_weight=soh_weight1*tempweightlist[i]*delt_ocv_soc_weight
|
|
|
+ cellsoh_init=ah_accum*100/((ocv_soc2-ocv_soc1)*0.01*self.param.Capacity)
|
|
|
+
|
|
|
+ if cellsoh_init>55 and cellsoh_init<120: #判断soh值的有效区间
|
|
|
+ if len(df_res)<1:
|
|
|
+ if not self.df_soh.empty and self.df_soh.shape[1]>10:
|
|
|
+ cellsoh_cal=cellsoh_init*soh_weight + list(self.df_soh['cellsoh'+str(j+1)])[-1]*(1-soh_weight)
|
|
|
+ else:
|
|
|
+ cellsoh_cal=cellsoh_init
|
|
|
+ else:
|
|
|
+ if abs(cellsoh_init-df_res.iloc[-1]['cellsoh'+str(j+1)])<15:
|
|
|
+ cellsoh_cal=cellsoh_init*soh_weight*0.5 + df_res.iloc[-1]['cellsoh'+str(j+1)]*(1-soh_weight*0.5)
|
|
|
+ else:
|
|
|
+ soh_weight=soh_weight*0.2
|
|
|
+ cellsoh_cal=cellsoh_init*soh_weight*0.5 + df_res.iloc[-1]['cellsoh'+str(j+1)]*(1-soh_weight*0.5)
|
|
|
+ cellsoh.append(cellsoh_cal)
|
|
|
+ else:
|
|
|
+ cellsoh=[]
|
|
|
+ break
|
|
|
+ else:
|
|
|
+ cellsoh.append(0)
|
|
|
+ if len(cellsoh)>4:
|
|
|
+ soh_value=cellsoh[4:4+self.param.CellVoltNums]
|
|
|
+ soh=min(soh_value)
|
|
|
+ cellsoh.insert(4,soh)
|
|
|
+ if len(df_res)<1:
|
|
|
+ df_res.loc[0]=cellsoh
|
|
|
+ else:
|
|
|
+ df_res.loc[df_res.index.values[-1]+1]=cellsoh
|
|
|
+ print(df_res)
|
|
|
+ else:
|
|
|
+ continue
|
|
|
+ if df_res.empty:
|
|
|
+ return pd.DataFrame()
|
|
|
+ else:
|
|
|
+ return df_res
|
|
|
+ return pd.DataFrame()
|
|
|
+
|
|
|
+ def _lfpsoh(self):
|
|
|
+ standingpoint_st=[]
|
|
|
+ standingpoint_sp=[]
|
|
|
+ tempweightlist1=[]
|
|
|
+ cellmaxvolt_number1=[]
|
|
|
+ standingtime=0
|
|
|
+ chrg_start=[]
|
|
|
+ chrg_end=[]
|
|
|
+ tempweightlist2=[]
|
|
|
+ cellmaxvolt_number2=[]
|
|
|
+ charging=0
|
|
|
+
|
|
|
+ for i in range(3,len(self.df_bms)-3):
|
|
|
+
|
|
|
+ #获取两点法法所需数据-开始
|
|
|
+ if abs(self.packcrnt[i]) < 0.2: #判断非平台区静置状态
|
|
|
+ delttime=(self.bmstime[i]-self.bmstime[i-1]).total_seconds()
|
|
|
+ standingtime=standingtime+delttime
|
|
|
+ self._celltemp_weight(i) #获取不同温度对应的静置时间
|
|
|
+
|
|
|
+ if standingtime>self.StandardStandingTime and abs(self.packcrnt[i+2])>0.1: #静置时间满足要求,且下一时刻电流>0.1A
|
|
|
+ standingtime=0
|
|
|
+ cellvolt_now=self._cellvolt_get(i)
|
|
|
+ if max(cellvolt_now)<self.param.OcvInflexionBelow: #当前最大电芯电压<OCV下拐点
|
|
|
+ if standingpoint_st:
|
|
|
+ if len(standingpoint_st)>len(standingpoint_sp):
|
|
|
+ if self.packcrnt[standingpoint_st[-1]]<-1: #判断上一次静置点的是否为满充
|
|
|
+ standingpoint_sp.append(i)
|
|
|
+ standingpoint_st.append(i)
|
|
|
+ tempweightlist1.append(self.tempweight)
|
|
|
+ else:
|
|
|
+ standingpoint_st[-1]=i
|
|
|
+ tempweightlist1[-1]=self.tempweight
|
|
|
+ else:
|
|
|
+ standingpoint_st.append(i)
|
|
|
+ tempweightlist1.append(self.tempweight)
|
|
|
+ else:
|
|
|
+ standingpoint_st.append(i)
|
|
|
+ tempweightlist1.append(self.tempweight)
|
|
|
+ else:
|
|
|
+ pass
|
|
|
+ else:
|
|
|
+ pass
|
|
|
+
|
|
|
+ elif self.packcrnt[i]<=-1 and self.packcrnt[i-1]<=-1 and self.packcrnt[i+1]<=-1 and self.packcrnt[i+2]>-1: #判读满充状态
|
|
|
+ standingtime=0
|
|
|
+ self._celltemp_weight(i)
|
|
|
+ cellvolt_now=self._cellvolt_get(i)
|
|
|
+ if max(cellvolt_now)>self.param.CellFullChrgVolt:
|
|
|
+ if standingpoint_st:
|
|
|
+ if len(standingpoint_st)>len(standingpoint_sp):
|
|
|
+ if abs(self.packcrnt[standingpoint_st[-1]])<0.5: #判断上一次静置点是否为下拐点
|
|
|
+ standingpoint_sp.append(i)
|
|
|
+ standingpoint_st.append(i)
|
|
|
+ tempweightlist1.append(self.tempweight)
|
|
|
+ cellmaxvolt_number1.append(cellvolt_now.index(max(cellvolt_now))) #获取最大电压索引
|
|
|
+ cellmaxvolt_number1.append(cellvolt_now.index(max(cellvolt_now))) #获取最大电压索引
|
|
|
+ else:
|
|
|
+ standingpoint_st[-1]=i
|
|
|
+ tempweightlist1[-1]=self.tempweight
|
|
|
+ cellmaxvolt_number1[-1]=cellvolt_now.index(max(cellvolt_now))
|
|
|
+ else:
|
|
|
+ standingpoint_st.append(i)
|
|
|
+ tempweightlist1.append(self.tempweight)
|
|
|
+ cellmaxvolt_number1.append(cellvolt_now.index(max(cellvolt_now)))
|
|
|
+ else:
|
|
|
+ pass
|
|
|
+
|
|
|
+ else:
|
|
|
+ standingtime=0
|
|
|
+ pass
|
|
|
+ #获取两点法所需数据-结束
|
|
|
+
|
|
|
+ #获取DVDQ算法所需数据——开始
|
|
|
+ if i==3 and self.packcrnt[1]<=-1 and self.packcrnt[2]<=-1 and self.packcrnt[3]<=-1:
|
|
|
+ chrg_start.append(i)
|
|
|
+ charging=1
|
|
|
+ elif self.packcrnt[i-1]>-1 and self.packcrnt[i]<=-1 and self.packcrnt[i+1]<=-1 and self.packcrnt[i+2]<=-1: #判断充电开始
|
|
|
+ if self.bms_soc[i]<45:
|
|
|
+ self._celltemp_weight(i)
|
|
|
+ charging=1
|
|
|
+ if len(chrg_start)>len(chrg_end):
|
|
|
+ chrg_start[-1]=i
|
|
|
+ tempweightlist2[-1]=self.tempweight
|
|
|
+ else:
|
|
|
+ chrg_start.append(i)
|
|
|
+ tempweightlist2.append(self.tempweight)
|
|
|
+ else:
|
|
|
+ pass
|
|
|
+ else:
|
|
|
+ pass
|
|
|
+
|
|
|
+ if charging==1: #充电中
|
|
|
+ if (self.bmstime[i+1]-self.bmstime[i]).total_seconds()>180: #如果充电过程中时间间隔>180s,则舍弃该次充电
|
|
|
+ chrg_start.remove(chrg_start[-1])
|
|
|
+ tempweightlist2.remove(tempweightlist2[-1])
|
|
|
+ charging=0
|
|
|
+ continue
|
|
|
+ elif self.packcrnt[i]<=-1 and self.packcrnt[i+1]<=-1 and self.packcrnt[i+2]>-1: #判断电流波动时刻
|
|
|
+ cellvolt_now=self._cellvolt_get(i+1)
|
|
|
+ if max(cellvolt_now)>self.param.CellFullChrgVolt: #电压>满充电压
|
|
|
+ chrg_end.append(i+1)
|
|
|
+ cellmaxvolt_number2.append(cellvolt_now.index(max(cellvolt_now))) #获取最大电压索引
|
|
|
+ charging=0
|
|
|
+ continue
|
|
|
+ else:
|
|
|
+ pass
|
|
|
+ elif self.packcrnt[i+1]>-0.1 and self.packcrnt[i+2]>-0.1 and self.packcrnt[i+3]>-0.1: #判断充电结束
|
|
|
+ charging=0
|
|
|
+ if len(chrg_start)>len(chrg_end):
|
|
|
+ chrg_start.remove(chrg_start[-1])
|
|
|
+ tempweightlist2.remove(tempweightlist2[-1])
|
|
|
+ continue
|
|
|
+ else:
|
|
|
+ continue
|
|
|
+ elif i==len(self.packcrnt)-3 and self.packcrnt[i+1]<-1 and self.packcrnt[i+2]<-1:
|
|
|
+ charging=0
|
|
|
+ if len(chrg_start)>len(chrg_end):
|
|
|
+ cellvolt_now=self._cellvolt_get(i)
|
|
|
+ if max(cellvolt_now)>self.param.CellFullChrgVolt: #电压>满充电压
|
|
|
+ chrg_end.append(i)
|
|
|
+ cellmaxvolt_number2.append(cellvolt_now.index(max(cellvolt_now))) #获取最大电压索引
|
|
|
+ continue
|
|
|
+ else:
|
|
|
+ chrg_start.remove(chrg_start[-1])
|
|
|
+ tempweightlist2.remove(tempweightlist2[-1])
|
|
|
+ continue
|
|
|
+ else:
|
|
|
+ continue
|
|
|
+ else:
|
|
|
+ pass
|
|
|
+ #获取DVDQ算法所需数据——结束
|
|
|
+
|
|
|
+ if standingpoint_sp or chrg_end: #开始计算SOH
|
|
|
+ self.getdata() #获取已计算的soh
|
|
|
+ column_name=['time_st','time_sp','sn','method','soh','cellsoh1','cellsoh2','cellsoh3','cellsoh4','cellsoh5','cellsoh6','cellsoh7','cellsoh8','cellsoh9','cellsoh10','cellsoh11','cellsoh12','cellsoh13','cellsoh14','cellsoh15','cellsoh16','cellsoh17','cellsoh18','cellsoh19','cellsoh20']
|
|
|
+ df_res=pd.DataFrame(columns=column_name)
|
|
|
+
|
|
|
+ if standingpoint_sp: #两点法计算SOH
|
|
|
+ for i in range(len(standingpoint_sp)): #判断为满充点或者下拐点
|
|
|
+ if self.packcrnt[standingpoint_sp[i]]<=-1:
|
|
|
+ cellocv_st=self._cellvolt_get(standingpoint_st[i])
|
|
|
+ ocv_soc1=np.interp(cellocv_st[cellmaxvolt_number1[i]],self.param.LookTab_OCV,self.param.LookTab_SOC)
|
|
|
+ ocv_soc2=self.param.FullChrgSoc
|
|
|
+ else:
|
|
|
+ cellocv_sp=self._cellvolt_get(standingpoint_sp[i])
|
|
|
+ ocv_soc1=self.param.FullChrgSoc
|
|
|
+ ocv_soc2=np.interp(cellocv_sp[cellmaxvolt_number1[i]],self.param.LookTab_OCV,self.param.LookTab_SOC)
|
|
|
+
|
|
|
+ cellocv_sp=self._cellvolt_get(standingpoint_sp[i])
|
|
|
+ accumtime=self.accumtime.to_list() #累计量的时间列表
|
|
|
+ timepoint_bms_st=self.bmstime[standingpoint_st[i]] #获取静置点的时间
|
|
|
+ timepoint_bms_sp=self.bmstime[standingpoint_sp[i]]
|
|
|
+ timepoint_accum_st=bisect.bisect(accumtime,timepoint_bms_st) #获取最接近静置点时间的累计量时间点
|
|
|
+ timepoint_accum_sp=bisect.bisect(accumtime,timepoint_bms_sp)
|
|
|
+ if timepoint_accum_sp>=len(accumtime): #防止指针超出数据范围
|
|
|
+ timepoint_accum_sp=len(accumtime)-1
|
|
|
+
|
|
|
+ ah_packcrnt_dis=0
|
|
|
+ ah_packcrnt_chg=0
|
|
|
+ for j in range(standingpoint_st[i]+2,standingpoint_sp[i]+1): #计算累计Ah
|
|
|
+ Step=(self.bmstime[j+1]-self.bmstime[j]).total_seconds()
|
|
|
+ if self.packcrnt[j+1]>=0:
|
|
|
+ ah_packcrnt_dis=ah_packcrnt_dis+self.packcrnt[j+1]*Step
|
|
|
+ else:
|
|
|
+ ah_packcrnt_chg=ah_packcrnt_chg-self.packcrnt[j+1]*Step
|
|
|
+ ah_packcrnt_chg=ah_packcrnt_chg/3600
|
|
|
+ ah_packcrnt_dis=ah_packcrnt_dis/3600
|
|
|
+ ah_packcrnt=ah_packcrnt_chg-ah_packcrnt_dis #两个静置点的总累计AH,负值代表放电,正值代表充电
|
|
|
+
|
|
|
+ ah_accum_dis=self.df_accum.loc[timepoint_accum_sp,'累计放电电量']-self.df_accum.loc[timepoint_accum_st,'累计放电电量'] #两个静置点之间的放电电量
|
|
|
+ ah_accum_chg=self.df_accum.loc[timepoint_accum_sp,'累计充电电量']-self.df_accum.loc[timepoint_accum_st,'累计充电电量'] #两个静置点之间的充电电量
|
|
|
+ ah_accum_tatol=ah_accum_chg-ah_accum_dis #两个静置点的总累计AH,负值代表放电,正值代表充电
|
|
|
+ ah_accum=ah_accum_tatol
|
|
|
+
|
|
|
+ delt_days=(self.bmstime[standingpoint_sp[i]]-self.bmstime[standingpoint_st[i]]).total_seconds()/(3600*24)
|
|
|
+ if delt_days<=1: #两次时间间隔对计算结果的影响
|
|
|
+ soh_weight=1
|
|
|
+ elif delt_days<=2:
|
|
|
+ soh_weight=0.9
|
|
|
+ elif delt_days<=3:
|
|
|
+ soh_weight=0.4
|
|
|
+ else:
|
|
|
+ soh_weight=0
|
|
|
+
|
|
|
+ if self.param.Capacity*0.65*0.7 < abs(ah_packcrnt) < self.param.Capacity: #累计量的权重
|
|
|
+ if abs(ah_accum_tatol-ah_packcrnt)<self.param.Capacity/20:
|
|
|
+ soh_weight=soh_weight*1
|
|
|
+ elif abs(ah_accum_tatol-ah_packcrnt)<self.param.Capacity/10:
|
|
|
+ soh_weight=soh_weight*0.8
|
|
|
+ else:
|
|
|
+ soh_weight=soh_weight*0.5
|
|
|
+ else:
|
|
|
+ if self.param.Capacity*0.65*0.7 < abs(ah_accum) < self.param.Capacity:
|
|
|
+ soh_weight=soh_weight*0.5
|
|
|
+ else:
|
|
|
+ soh_weight=0
|
|
|
+ if ah_accum_dis+ah_accum_chg>self.param.Capacity:
|
|
|
+ soh_weight=soh_weight*0.6
|
|
|
+ else:
|
|
|
+ soh_weight=soh_weight
|
|
|
+
|
|
|
+ cellsoh=[timepoint_bms_st, timepoint_bms_sp, self.sn]
|
|
|
+ delt_ocv_soc=ocv_soc2-ocv_soc1
|
|
|
+ delt_ocv_soc_weight=self._deltsoc_weight(abs(delt_ocv_soc))
|
|
|
+ soh_weight=soh_weight*tempweightlist1[i]*delt_ocv_soc_weight*0.5
|
|
|
+ cellsoh_init=ah_accum*100/((ocv_soc2-ocv_soc1)*0.01*self.param.Capacity)
|
|
|
+
|
|
|
+ if cellsoh_init>65 and cellsoh_init<115: #判断soh值的有效区间
|
|
|
+ if len(df_res)<1:
|
|
|
+ if not self.df_soh.empty and self.df_soh.shape[1]>10:
|
|
|
+ cellsoh_cal=cellsoh_init*soh_weight + list(self.df_soh['soh'])[-1]*(1-soh_weight)
|
|
|
+ else:
|
|
|
+ cellsoh_cal=cellsoh_init
|
|
|
+ else:
|
|
|
+ if abs(cellsoh_init-df_res.iloc[-1]['soh'])<10:
|
|
|
+ cellsoh_cal=cellsoh_init*soh_weight + df_res.iloc[-1]['soh']*(1-soh_weight)
|
|
|
+ else:
|
|
|
+ soh_weight=soh_weight*0.1
|
|
|
+ cellsoh_cal=cellsoh_init*soh_weight + df_res.iloc[-1]['soh']*(1-soh_weight)
|
|
|
+
|
|
|
+ cellsoh.append(1)
|
|
|
+ cellsoh.append(cellsoh_cal)
|
|
|
+ for j in range(20): #计算每个电芯的SOH值
|
|
|
+ cellsoh.append(cellsoh_cal)
|
|
|
+ if len(df_res)<1:
|
|
|
+ df_res.loc[0]=cellsoh
|
|
|
+ else:
|
|
|
+ df_res.loc[df_res.index.values[-1]+1]=cellsoh
|
|
|
+ else:
|
|
|
+ continue
|
|
|
+ else:
|
|
|
+ pass
|
|
|
+
|
|
|
+ if chrg_end:
|
|
|
+ for i in range(len(chrg_end)):
|
|
|
+ cellvolt_max = self.df_bms['单体电压' + str(cellmaxvolt_number2[i]+1)] / 1000 #获取最大电压
|
|
|
+ cellvolt=self.np_move_avg(cellvolt_max, 3, mode="same") #对电压进行滑动平均滤波
|
|
|
+
|
|
|
+ Ah = 0 #参数赋初始值
|
|
|
+ Volt = cellvolt[chrg_start[i]]
|
|
|
+ DV_Volt=[]
|
|
|
+ DQ_Ah = []
|
|
|
+ DVDQ = []
|
|
|
+ time2 = []
|
|
|
+ soc2 = []
|
|
|
+ Ah_tatal=[0]
|
|
|
+ xvolt=[]
|
|
|
+ #计算DV和DQ值
|
|
|
+ for j in range(chrg_start[i],chrg_end[i]):
|
|
|
+ Step=(self.bmstime[j+1]-self.bmstime[j]).total_seconds()
|
|
|
+ Ah=Ah-self.packcrnt[j]*Step/3600
|
|
|
+ if (cellvolt[j]-Volt)>0.0009 and Ah>0:
|
|
|
+ Ah_tatal.append(Ah_tatal[-1]+Ah)
|
|
|
+ DQ_Ah.append(Ah)
|
|
|
+ DV_Volt.append(cellvolt[j]-Volt)
|
|
|
+ DVDQ.append((DV_Volt[-1])/DQ_Ah[-1])
|
|
|
+ xvolt.append(cellvolt[j])
|
|
|
+ Volt=cellvolt[j]
|
|
|
+ Ah = 0
|
|
|
+ time2.append(self.bmstime[j])
|
|
|
+ soc2.append(self.bms_soc[j])
|
|
|
+
|
|
|
+ #切片,去除前后10min的数据
|
|
|
+ Data1 = pd.DataFrame({'time': time2,
|
|
|
+ 'SOC': soc2,
|
|
|
+ 'DVDQ': DVDQ,
|
|
|
+ 'Ah_tatal': Ah_tatal[:-1],
|
|
|
+ 'DQ_Ah':DQ_Ah,
|
|
|
+ 'DV_Volt':DV_Volt,
|
|
|
+ 'XVOLT':xvolt})
|
|
|
+ start_time=Data1.loc[0,'time']
|
|
|
+ start_time=start_time+datetime.timedelta(seconds=600)
|
|
|
+ end_time=Data1.loc[len(time2)-1,'time']
|
|
|
+ end_time=end_time-datetime.timedelta(seconds=1200)
|
|
|
+ if soc2[0]<37:
|
|
|
+ Data1=Data1[(Data1['SOC']>39) & (Data1['time']<end_time)]
|
|
|
+ else:
|
|
|
+ Data1=Data1[(Data1['time']>start_time) & (Data1['time']<end_time)]
|
|
|
+
|
|
|
+ # ax1 = plt.subplot(3, 1, 1)
|
|
|
+ # plt.plot(Data1['XVOLT'],Data1['DVDQ'],'r*-')
|
|
|
+ # ax1 = plt.subplot(3, 1, 2)
|
|
|
+ # plt.plot(Data1['SOC'],Data1['XVOLT'],'y*-')
|
|
|
+ # ax1 = plt.subplot(3, 1, 3)
|
|
|
+ # plt.plot(Data1['SOC'], Data1['DVDQ'], 'r*-')
|
|
|
+ # plt.show()
|
|
|
+
|
|
|
+ #寻找峰值并计算Soh和置信度
|
|
|
+ if len(Data1['DVDQ'])>1:
|
|
|
+ PeakIndex=Data1['DVDQ'].idxmax()
|
|
|
+ #筛选峰值点附近±0.5%SOC内的数据
|
|
|
+ Data2=Data1[(Data1['SOC']>(Data1['SOC'][PeakIndex]-0.5)) & (Data1['SOC']<(Data1['SOC'][PeakIndex]+0.5))]
|
|
|
+ if len(Data2)>2:
|
|
|
+ Ah_tatal1 = Data1['Ah_tatal']
|
|
|
+ DVDQ = Data1['DVDQ']
|
|
|
+ soc2 = Data1['SOC']
|
|
|
+ xvolt = Data1['XVOLT']
|
|
|
+ if soc2[PeakIndex]>40 and soc2[PeakIndex]<90:
|
|
|
+ cellsoh_init=(Ah_tatal[-1]-Ah_tatal1[PeakIndex]) * 100 / ((self.param.FullChrgSoc - self.param.PeakSoc) * 0.01 * self.param.Capacity)
|
|
|
+ if cellsoh_init<95:
|
|
|
+ cellsoh_init=cellsoh_init*0.3926+58.14
|
|
|
+ else:
|
|
|
+ pass
|
|
|
+ else:
|
|
|
+ continue
|
|
|
+ else:
|
|
|
+ Data1=Data1.drop([PeakIndex])
|
|
|
+ PeakIndex = Data1['DVDQ'].idxmax()
|
|
|
+ Data2 = Data1[(Data1['SOC'] > (Data1['SOC'][PeakIndex] - 0.5)) & (Data1['SOC'] < (Data1['SOC'][PeakIndex] + 0.5))]
|
|
|
+ if len(Data2) > 2:
|
|
|
+ Ah_tatal1 = Data1['Ah_tatal']
|
|
|
+ DVDQ = Data1['DVDQ']
|
|
|
+ soc2 = Data1['SOC']
|
|
|
+ xvolt = Data1['XVOLT']
|
|
|
+ if soc2[PeakIndex]>40 and soc2[PeakIndex]<90:
|
|
|
+ cellsoh_init=(Ah_tatal[-1]-Ah_tatal1[PeakIndex]) * 100 / ((self.param.FullChrgSoc - self.param.PeakSoc) * 0.01 * self.param.Capacity)
|
|
|
+ if cellsoh_init<95:
|
|
|
+ cellsoh_init=cellsoh_init*0.3926+58.14
|
|
|
+ else:
|
|
|
+ pass
|
|
|
+ else:
|
|
|
+ continue
|
|
|
+ else:
|
|
|
+ continue
|
|
|
+
|
|
|
+ cellsoh=[self.bmstime[chrg_start[i]], self.bmstime[chrg_end[i]], self.sn]
|
|
|
+ soh_weight=tempweightlist2[i]*0.5
|
|
|
+ if cellsoh_init>65 and cellsoh_init<115: #判断soh值的有效区间
|
|
|
+ if len(df_res)<1:
|
|
|
+ if not self.df_soh.empty and self.df_soh.shape[1]>10:
|
|
|
+ cellsoh_cal=cellsoh_init*soh_weight + list(self.df_soh['soh'])[-1]*(1-soh_weight)
|
|
|
+ else:
|
|
|
+ cellsoh_cal=cellsoh_init
|
|
|
+ else:
|
|
|
+ if abs(cellsoh_init-df_res.iloc[-1]['soh'])<10:
|
|
|
+ cellsoh_cal=cellsoh_init*soh_weight + df_res.iloc[-1]['soh']*(1-soh_weight)
|
|
|
+ else:
|
|
|
+ soh_weight=soh_weight*0.2
|
|
|
+ cellsoh_cal=cellsoh_init*soh_weight + df_res.iloc[-1]['soh']*(1-soh_weight)
|
|
|
+
|
|
|
+ cellsoh.append(2)
|
|
|
+ cellsoh.append(cellsoh_cal)
|
|
|
+ for j in range(20): #计算每个电芯的SOH值
|
|
|
+ cellsoh.append(cellsoh_cal)
|
|
|
+ if len(df_res)<1:
|
|
|
+ df_res.loc[0]=cellsoh
|
|
|
+ else:
|
|
|
+ df_res.loc[df_res.index.values[-1]+1]=cellsoh
|
|
|
+ else:
|
|
|
+ continue
|
|
|
+ else:
|
|
|
+ continue
|
|
|
+ else:
|
|
|
+ pass
|
|
|
+
|
|
|
+ if df_res.empty:
|
|
|
+ return pd.DataFrame()
|
|
|
+ else:
|
|
|
+ return df_res
|
|
|
+ return pd.DataFrame()
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|