import pandas as pd import numpy as np import datetime import bisect import matplotlib.pyplot as plt import BatParam class BatSoh: def __init__(self,sn,celltype,df_bms,df_volt,df_temp,df_accum): #参数初始化 self.sn=sn self.celltype=celltype CellVoltNums=int(df_volt.loc[5,'单体电池总数']) if CellVoltNums==110: self.celltype=99 else: self.celltype==100 self.param=BatParam.BatParam(self.celltype) self.df_volt=df_volt self.df_temp=df_temp self.bmsstat=df_bms['充电状态'] self.packcrnt=(df_volt['可充电储能装置电流(A)'].astype('float'))*self.param.PackCrntDec self.packvolt=df_volt['可充电储能装置电压(V)'].astype('float') self.bms_soc=df_bms['SOC'] # self.bms_soh=df_volt['SOH[%]'] self.bmstime= pd.to_datetime(df_volt['上报时间'], format='%Y-%m-%d %H:%M:%S') self.param.CellVoltNums=CellVoltNums self.param.CellTempNums=int(df_temp.loc[5,'可充电储能温度探针个数']) def batsoh(self): if self.celltype==1 or self.celltype==2 or self.celltype==3 or self.celltype==4 or self.celltype==100: df_res=self._ncmsoh_chrg() return df_res elif self.celltype==99: df_res=self._lfpsoh() return df_res else: return pd.DataFrame() #定义滑动滤波函数......................................................................................................................... def _np_move_avg(self,a, n, mode="same"): return (np.convolve(a, np.ones((n,)) / n, mode=mode)) #寻找当前行数据的最小温度值................................................................................................................. def _celltemp_weight(self,num): celltemp = [] for j in range(1, self.param.CellTempNums+1): s = str(j) celltemp.append(self.df_temp.loc[num, s+'.0']) celltemp.remove(min(celltemp)) self.celltemp=celltemp if self.celltype==99: if min(celltemp)>=20: self.tempweight=1 self.StandardStandingTime=1800 elif min(celltemp)>=10: self.tempweight=0.5 self.StandardStandingTime=3600 elif min(celltemp)>=5: self.tempweight=0 self.StandardStandingTime=7200 else: self.tempweight=0 self.StandardStandingTime=10800 else: if min(celltemp)>=20: self.tempweight=1 self.StandardStandingTime=1800 elif min(celltemp)>=10: self.tempweight=0.8 self.StandardStandingTime=3600 elif min(celltemp)>=5: self.tempweight=0.3 self.StandardStandingTime=3600 else: self.tempweight=0.1 self.StandardStandingTime=7200 #获取SOC差对应的SOH权重值................................................................................................................... def _deltsoc_weight(self,deltsoc): 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_volt.loc[num, s+'.0']) return cellvolt #筛选充电数据.............................................................................................................................. def _chrgdata(self): self.ChgStart=[] self.ChgEnd=[] if len(self.packvolt)>100: charging=0 for i in range(3, len(self.bmstime) - 3): if charging==0: if i==3 and self.bmsstat[i]=='停车充电' and self.bmsstat[i+1]=='停车充电': self.ChgStart.append(i) charging=1 elif self.bmsstat[i-1]!='停车充电' and self.bmsstat[i]=='停车充电': self.ChgStart.append(i) charging=1 else: pass else: if (self.bmsstat[i-1]=='停车充电' or '充电完成') and self.packcrnt[i]>0: self.ChgEnd.append(i) charging=0 elif i == (len(self.bmstime) - 4) and (self.bmsstat[i] == '停车充电' or '充电完成') and self.packcrnt[i]<-1: self.ChgEnd.append(len(self.bmstime)-2) charging=0 #dvdq方法计算soh........................................................................................................................... def _dvdq_soh(self, chrg_st, chrg_end,cellvolt): Ah = 0 #参数赋初始值 Volt = cellvolt[chrg_st] DV_Volt=[] DQ_Ah = [] DVDQ = [] time2 = [] soc2 = [] Ah_tatal=[0] xvolt=[] #计算DV和DQ值 for j in range(chrg_st,chrg_end): Step=(self.bmstime[j+1]-self.bmstime[j]).total_seconds() Ah=Ah-self.packcrnt[j]*Step/3600 if (cellvolt[j]-Volt)>0.0019 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(float(self.bms_soc[j].strip('%'))) #切片,去除前后10min的数据 df_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=df_Data1.loc[0,'time'] start_time=start_time+datetime.timedelta(seconds=900) end_time=df_Data1.loc[len(time2)-1,'time'] end_time=end_time-datetime.timedelta(seconds=1200) if soc2[0]<36: df_Data1=df_Data1[(df_Data1['SOC']>40) & (df_Data1['SOC']<80)] else: df_Data1=df_Data1[(df_Data1['time']>start_time) & (df_Data1['SOC']<80)] df_Data1=df_Data1[(df_Data1['XVOLT']>self.param.PeakVoltLowLmt) & (df_Data1['XVOLT']1: PeakIndex=df_Data1['DVDQ'].idxmax() #筛选峰值点附近±0.5%SOC内的数据 df_Data2=df_Data1[(df_Data1['SOC']>(df_Data1['SOC'][PeakIndex]-0.5)) & (df_Data1['SOC']<(df_Data1['SOC'][PeakIndex]+0.5))] if len(df_Data2)>1 and df_Data1.loc[PeakIndex,'XVOLT']40 and soc2[PeakIndex]<80: 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 return cellsoh_init else: return cellsoh_init else: return 0 else: df_Data1=df_Data1.drop([PeakIndex]) PeakIndex = df_Data1['DVDQ'].idxmax() df_Data2 = df_Data1[(df_Data1['SOC'] > (df_Data1['SOC'][PeakIndex] - 0.5)) & (df_Data1['SOC'] < (df_Data1['SOC'][PeakIndex] + 0.5))] if len(df_Data2) > 1 and df_Data1.loc[PeakIndex,'XVOLT']40 and soc2[PeakIndex]<80: 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 return cellsoh_init else: return cellsoh_init else: return 0 else: return 0 else: return 0 #两点法计算三元SOH......................................................................................................................... def _ncmsoh_twopoint(self): standingpoint_st=[] standingpoint_sp=[] tempweightlist=[] standingtime=0 for i in range(3,len(self.df_volt)-3): if abs(self.packcrnt[i]) < 0.2 and abs(self.packcrnt[i-1]) < 0.2 and abs(self.packcrnt[i+1]) < 0.2: #电流为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) #获取当前行电压数据 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 3=30: #当前时刻SOC与开始时刻SOC差>=40 if abs(self.packcrnt[i+2])>=0.2: #如果下一时刻电流>=0.5,则压入当前索引 standingpoint_sp.append(i) standingpoint_st.append(i) tempweightlist.append(self.tempweight) standingtime=0 continue else: if standingtime>7200 or i==len(self.df_volt)-2: #仍处于静置,但静置时间>1h,则直接获取sp时刻,或者到了数据末尾 standingpoint_sp.append(i) tempweightlist.append(self.tempweight) continue else: if abs(self.packcrnt[i+2])>=0.2: standingtime=0 if minocv_socst<50 and minocv_socnow=0.2: standingtime=0 if minocv_socst>=50 and minocv_socnow>minocv_socst: standingpoint_st[-1]=i continue else: continue else: if abs(self.packcrnt[i+2])>=0.5: cellvolt_now=self._cellvolt_get(i) if 30.5: cellvolt_now=self._cellvolt_get(i) if 3=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 Step<60: 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.7 # elif delt_days<=3: # soh_weight1=0.4 # else: # soh_weight1=0 # if ah_packcrnt_dis55 and cellsoh_init<120: #判断soh值的有效区间 # if len(df_res)<1: # if not self.df_soh.empty and 551/abs(cellsoh_init-cellsoh_last[j]): # soh_weight=1/abs(cellsoh_init-cellsoh_last[j]) # cellsoh_cal=cellsoh_init*soh_weight + cellsoh_last[j]*(1-soh_weight) # else: # cellsoh_cal=cellsoh_init*soh_weight + cellsoh_last[j]*(1-soh_weight) # else: # cellsoh_cal=cellsoh_init*soh_weight+100*(1-soh_weight) # else: # cellsoh_last=eval(df_res.loc[len(df_res)-1,'cellsoh']) # if soh_weight>1/abs(cellsoh_init-cellsoh_last[j]): # soh_weight=1/abs(cellsoh_init-cellsoh_last[j]) # cellsoh_cal=cellsoh_init*soh_weight + cellsoh_last[j]*(1-soh_weight) # else: # cellsoh_cal=cellsoh_init*soh_weight + cellsoh_last[j]*(1-soh_weight) # cellsoh_cal=eval(format(cellsoh_cal,'.1f')) cellsoh.append(cellsoh_init) # else: # cellsoh=[] # break if cellsoh: soh=min(cellsoh) soh_list=[timepoint_bms_st, timepoint_bms_sp, self.sn, 1, soh, str(cellsoh),max(cellsoh)-min(cellsoh)] df_res.loc[len(df_res)]=soh_list else: continue if df_res.empty: return pd.DataFrame() else: return df_res return pd.DataFrame() def _ncmsoh_chrg(self): self._chrgdata() print(self.ChgStart,self.ChgEnd) ChgStartValid=[] ChgEndValid=[] for i in range(min(len(self.ChgStart),len(self.ChgEnd))): self._celltemp_weight(self.ChgEnd[i]) #获取温度对应的静置时间及权重 #筛选满足2点法计算的数据 StandingTime=0 StandingTime1=0 for m in range(max(len(self.packcrnt)-self.ChgEnd[i]-2,self.ChgStart[i]-2)): if self.ChgStart[i] - m - 1>0 and 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 self.ChgEnd[i] + m + 1 self.StandardStandingTime and StandingTime1>self.StandardStandingTime: #筛选静置时间>15min且慢充过程丢失数据少 ChgStartValid.append(self.ChgStart[i]) ChgEndValid.append(self.ChgEnd[i]+m) break if abs(self.packcrnt[self.ChgStart[i] - m - 2])>0.5 and abs(self.packcrnt[self.ChgEnd[i] + m + 2])>0.5: StandingTime=0 StandingTime1=0 break print(ChgStartValid,ChgEndValid) if len(ChgStartValid)>0: #两点法计算Soh df_res=pd.DataFrame(columns=('time','sn','soh','cellsoh','deltsoh')) soh2=[] 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() if Step<120: Ah=Ah-self.packcrnt[j+1]*Step/3600 if Ah>10: for j in range(1, self.param.CellVoltNums+1): #计算每个电芯的Soh s = str(j) OCVStart=self.df_volt.loc[ChgStartValid[i]-1,s+'.0'] OCVEnd=self.df_volt.loc[ChgEndValid[i],s+'.0'] #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) delasoh=max(soh2)-min(soh2) df_res.loc[len(df_res)]=[self.bmstime[ChgStartValid[i]],self.sn,soh1,soh2,delasoh] return df_res return pd.DataFrame() #两点法和DVDQ法计算磷酸铁锂电池SOH.................................................................................................................. 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_volt)-3): #获取两点法法所需数据-开始................................................................................................................. if abs(self.packcrnt[i]) < 0.1 and abs(self.packcrnt[i-1]) < 0.1 and abs(self.packcrnt[i+1]) < 0.1: #判断非平台区静置状态 delttime=(self.bmstime[i]-self.bmstime[i-1]).total_seconds() standingtime=standingtime+delttime self._celltemp_weight(i) #获取不同温度对应的静置时间 if standingtime>self.StandardStandingTime: #静置时间满足要求 if abs(self.packcrnt[i+2])>=0.1: #下一时刻电流>0.1A standingtime=0 cellvolt_now=self._cellvolt_get(i) if max(cellvolt_now)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 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 charging==0: if self.packcrnt[i]<=-1 and self.packcrnt[i+1]<=-1 and self.packcrnt[i+2]<=-1 and float(self.bms_soc[i].strip('%'))<40: #充电开始 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: #充电中 if (self.bmstime[i+1]-self.bmstime[i]).total_seconds()>180 or (self.packcrnt[i]>self.param.Capacity/3 and self.packcrnt[i+1]>self.param.Capacity/3): #如果充电过程中时间间隔>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: #判断充电结束 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)-4 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: continue #开始计算SOH............................................................................................................................................. if standingpoint_sp or chrg_end: # self.getdata() #获取已计算的soh column_name=['time_st','time_sp','sn','method','soh','cellsoh'] df_res=pd.DataFrame(columns=column_name) #两点法计算SOH........................................................................................................................................ if standingpoint_sp: 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 Step<60: 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=ah_packcrnt # 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>60 and cellsoh_init<115: #判断soh值的有效区间 soh_list=[timepoint_bms_st, timepoint_bms_sp, self.sn, 1, cellsoh_init, cellsoh_init] df_res.loc[len(df_res)]=soh_list else: pass else: pass #DVDQ法计算SOH....................................................................................................................................... if chrg_end: for i in range(len(chrg_end)): cellvolt_max = self.df_volt[ str(cellmaxvolt_number2[i]+1)+'.0'] #获取最大电压 cellvolt=self._np_move_avg(cellvolt_max, 3, mode="same") #对电压进行滑动平均滤 cellsoh_init=self._dvdq_soh(chrg_start[i],chrg_end[i],cellvolt) #dvdq计算soh soh_weight=tempweightlist2[i]*0.25 if cellsoh_init>60 and cellsoh_init<115: #判断soh值的有效区间 soh_list=[self.bmstime[chrg_start[i]], self.bmstime[chrg_end[i]], self.sn, 2, cellsoh_init, str(cellsoh_init),soh_weight] df_res.loc[len(df_res)]=soh_list else: pass #对SOH结果进行滤波处理................................................................................................................................ return df_res return pd.DataFrame()