CBMSBatSoh.py 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  1. import pandas as pd
  2. import numpy as np
  3. import datetime
  4. import bisect
  5. import matplotlib.pyplot as plt
  6. from LIB.MIDDLE.CellStateEstimation.Common import BatParam
  7. class BatSoh:
  8. def __init__(self,sn,celltype,df_bms,df_accum,df_soh): #参数初始化
  9. self.sn=sn
  10. self.celltype=celltype
  11. self.param=BatParam.BatParam(celltype)
  12. self.df_bms=df_bms
  13. self.packcrnt=df_bms['总电流[A]']*self.param.PackCrntDec
  14. self.packvolt=df_bms['总电压[V]']
  15. self.bms_soc=df_bms['SOC[%]']
  16. self.bms_soh=df_bms['SOH[%]']
  17. self.bmsstat=df_bms['充电状态']
  18. self.bmstime= pd.to_datetime(df_bms['时间戳'], format='%Y-%m-%d %H:%M:%S')
  19. self.df_accum=df_accum
  20. self.accumtime=pd.to_datetime(df_accum['时间戳'], format='%Y-%m-%d %H:%M:%S')
  21. self.df_soh=df_soh
  22. def batsoh(self):
  23. if self.celltype==1 or self.celltype==2 or self.celltype==3 or self.celltype==4:
  24. df_res=self._ncmsoh_twopoint()
  25. return df_res
  26. elif self.celltype==99:
  27. df_res=self._lfpsoh()
  28. return df_res
  29. else:
  30. return pd.DataFrame()
  31. #定义滑动滤波函数.........................................................................................................................
  32. def _np_move_avg(self,a, n, mode="same"):
  33. return (np.convolve(a, np.ones((n,)) / n, mode=mode))
  34. #筛选充电数据..............................................................................................................................
  35. def _chrgdata(self):
  36. self.ChgStart=[]
  37. self.ChgEnd=[]
  38. if len(self.packvolt)>100:
  39. for i in range(3, len(self.bmstime) - 3):
  40. if i==3 and self.bmsstat[i]==2 and self.bmsstat[i+1]==2 and self.bmsstat[i+2]==2:
  41. self.ChgStart.append(i)
  42. elif self.bmsstat[i-2]!=2 and self.bmsstat[i-1]!=2 and self.bmsstat[i]==2:
  43. self.ChgStart.append(i)
  44. elif self.bmsstat[i-1]==2 and self.bmsstat[i]!=2 and self.bmsstat[i+1]!=2:
  45. self.ChgEnd.append(i-1)
  46. elif i == (len(self.bmstime) - 4) and self.bmsstat[len(self.bmsstat)-1] == 2 and self.bmsstat[len(self.bmsstat)-2] == 2:
  47. self.ChgEnd.append(len(self.bmstime)-2)
  48. #寻找当前行数据的最小温度值.................................................................................................................
  49. def _celltemp_weight(self,num):
  50. celltemp = []
  51. for j in range(1, self.param.CellTempNums+1):
  52. s = str(j)
  53. celltemp.append(self.df_bms.loc[num,'单体温度' + s])
  54. celltemp.remove(min(celltemp))
  55. self.celltemp=celltemp
  56. if self.celltype==99:
  57. if min(celltemp)>=20:
  58. self.tempweight=1
  59. self.StandardStandingTime=1800
  60. elif min(celltemp)>=10:
  61. self.tempweight=0.5
  62. self.StandardStandingTime=3600
  63. elif min(celltemp)>=5:
  64. self.tempweight=0.2
  65. self.StandardStandingTime=7200
  66. else:
  67. self.tempweight=0
  68. self.StandardStandingTime=10800
  69. else:
  70. if min(celltemp)>=20:
  71. self.tempweight=1
  72. self.StandardStandingTime=1200
  73. elif min(celltemp)>=10:
  74. self.tempweight=0.8
  75. self.StandardStandingTime=1800
  76. elif min(celltemp)>=5:
  77. self.tempweight=0.3
  78. self.StandardStandingTime=3600
  79. else:
  80. self.tempweight=0.1
  81. self.StandardStandingTime=7200
  82. #获取SOC差对应的SOH权重值...................................................................................................................
  83. def _deltsoc_weight(self,deltsoc):
  84. if deltsoc>60:
  85. deltsoc_weight=1
  86. elif deltsoc>50:
  87. deltsoc_weight=0.9
  88. elif deltsoc>40:
  89. deltsoc_weight=0.7
  90. elif deltsoc>30:
  91. deltsoc_weight=0.5
  92. elif deltsoc>20:
  93. deltsoc_weight=0.3
  94. else:
  95. deltsoc_weight=0
  96. return deltsoc_weight
  97. #获取当前行所有电压数据......................................................................................................................
  98. def _cellvolt_get(self,num):
  99. cellvolt=[]
  100. for j in range(1, self.param.CellVoltNums+1):
  101. s = str(j)
  102. cellvolt.append(self.df_bms.loc[num,'单体电压' + s]/1000)
  103. return cellvolt
  104. #dvdq方法计算soh...........................................................................................................................
  105. def _dvdq_soh(self, chrg_st, chrg_end,cellvolt):
  106. Ah = 0 #参数赋初始值
  107. Volt = cellvolt[chrg_st]
  108. DV_Volt=[]
  109. DQ_Ah = []
  110. DVDQ = []
  111. time2 = []
  112. soc2 = []
  113. Ah_tatal=[0]
  114. xvolt=[]
  115. #计算DV和DQ值
  116. for j in range(chrg_st,chrg_end):
  117. Step=(self.bmstime[j+1]-self.bmstime[j]).total_seconds()
  118. Ah=Ah-self.packcrnt[j]*Step/3600
  119. if (cellvolt[j]-Volt)>0.0019 and Ah>0:
  120. Ah_tatal.append(Ah_tatal[-1]+Ah)
  121. DQ_Ah.append(Ah)
  122. DV_Volt.append(cellvolt[j]-Volt)
  123. DVDQ.append((DV_Volt[-1])/DQ_Ah[-1])
  124. xvolt.append(cellvolt[j])
  125. Volt=cellvolt[j]
  126. Ah = 0
  127. time2.append(self.bmstime[j])
  128. soc2.append(self.bms_soc[j])
  129. #切片,去除前后10min的数据
  130. df_Data1 = pd.DataFrame({'time': time2,
  131. 'SOC': soc2,
  132. 'DVDQ': DVDQ,
  133. 'Ah_tatal': Ah_tatal[:-1],
  134. 'DQ_Ah':DQ_Ah,
  135. 'DV_Volt':DV_Volt,
  136. 'XVOLT':xvolt})
  137. start_time=df_Data1.loc[0,'time']
  138. start_time=start_time+datetime.timedelta(seconds=900)
  139. end_time=df_Data1.loc[len(time2)-1,'time']
  140. end_time=end_time-datetime.timedelta(seconds=1200)
  141. if soc2[0]<36:
  142. df_Data1=df_Data1[(df_Data1['SOC']>40) & (df_Data1['SOC']<80)]
  143. else:
  144. df_Data1=df_Data1[(df_Data1['time']>start_time) & (df_Data1['SOC']<80)]
  145. df_Data1=df_Data1[(df_Data1['XVOLT']>self.param.PeakVoltLowLmt) & (df_Data1['XVOLT']<self.param.PeakVoltUpLmt)]
  146. # self._celltemp_weight(int((chrg_st+chrg_end)/2))
  147. # print(self.packcrnt[int((chrg_st+chrg_end)/2)], min(self.celltemp))
  148. # ax1 = plt.subplot(3, 1, 1)
  149. # plt.plot(df_Data1['XVOLT'],df_Data1['DVDQ'],'r*-')
  150. # plt.xlabel('Volt/V')
  151. # plt.ylabel('DV/DQ')
  152. # plt.legend()
  153. # ax1 = plt.subplot(3, 1, 2)
  154. # plt.plot(df_Data1['SOC'],df_Data1['XVOLT'],'y*-')
  155. # plt.xlabel('SOC/%')
  156. # plt.ylabel('Volt/V')
  157. # plt.legend()
  158. # ax1 = plt.subplot(3, 1, 3)
  159. # plt.plot(df_Data1['SOC'], df_Data1['DVDQ'], 'r*-')
  160. # plt.xlabel('SOC/%')
  161. # plt.ylabel('DV/DQ')
  162. # plt.legend()
  163. # plt.show()
  164. #寻找峰值并计算Soh
  165. if len(df_Data1)>1:
  166. PeakIndex=df_Data1['DVDQ'].idxmax()
  167. #筛选峰值点附近±0.5%SOC内的数据
  168. df_Data2=df_Data1[(df_Data1['SOC']>(df_Data1['SOC'][PeakIndex]-0.5)) & (df_Data1['SOC']<(df_Data1['SOC'][PeakIndex]+0.5))]
  169. if len(df_Data2)>1 and df_Data1.loc[PeakIndex,'XVOLT']<self.param.PeakVoltUpLmt-0.019:
  170. Ah_tatal1 = df_Data1['Ah_tatal']
  171. DVDQ = df_Data1['DVDQ']
  172. soc2 = df_Data1['SOC']
  173. xvolt = df_Data1['XVOLT']
  174. if soc2[PeakIndex]>40 and soc2[PeakIndex]<80:
  175. cellsoh_init=(Ah_tatal[-1]-Ah_tatal1[PeakIndex]) * 100 / ((self.param.FullChrgSoc - self.param.PeakSoc) * 0.01 * self.param.Capacity)
  176. if cellsoh_init<95:
  177. cellsoh_init=cellsoh_init*0.3926+58.14
  178. return cellsoh_init
  179. else:
  180. return cellsoh_init
  181. else:
  182. return 0
  183. else:
  184. df_Data1=df_Data1.drop([PeakIndex])
  185. PeakIndex = df_Data1['DVDQ'].idxmax()
  186. df_Data2 = df_Data1[(df_Data1['SOC'] > (df_Data1['SOC'][PeakIndex] - 0.5)) & (df_Data1['SOC'] < (df_Data1['SOC'][PeakIndex] + 0.5))]
  187. if len(df_Data2) > 1 and df_Data1.loc[PeakIndex,'XVOLT']<self.param.PeakVoltUpLmt-0.019:
  188. Ah_tatal1 = df_Data1['Ah_tatal']
  189. DVDQ = df_Data1['DVDQ']
  190. soc2 = df_Data1['SOC']
  191. xvolt = df_Data1['XVOLT']
  192. if soc2[PeakIndex]>40 and soc2[PeakIndex]<80:
  193. cellsoh_init=(Ah_tatal[-1]-Ah_tatal1[PeakIndex]) * 100 / ((self.param.FullChrgSoc - self.param.PeakSoc) * 0.01 * self.param.Capacity)
  194. if cellsoh_init<95:
  195. cellsoh_init=cellsoh_init*0.3926+58.14
  196. return cellsoh_init
  197. else:
  198. return cellsoh_init
  199. else:
  200. return 0
  201. else:
  202. return 0
  203. else:
  204. return 0
  205. #NCM充电数据soh计算.........................................................................................................................
  206. def _ncmsoh_chrg(self):
  207. self._chrgdata()
  208. ChgStartValid=[]
  209. ChgEndValid=[]
  210. tempweightlist=[]
  211. for i in range(min(len(self.ChgStart),len(self.ChgEnd))):
  212. self._celltemp_weight(self.ChgEnd[i]) #获取温度对应的静置时间及权重
  213. for k in range(self.ChgStart[i],self.ChgEnd[i]): #去除电流0点
  214. 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:
  215. self.ChgEnd[i]=k
  216. #筛选满足2点法计算的数据
  217. StandingTime=0
  218. StandingTime1=0
  219. if self.bms_soc[self.ChgEnd[i]]>70 and self.bms_soc[self.ChgStart[i]]<50:
  220. for m in range(min(len(self.packcrnt)-self.ChgEnd[i]-2,self.ChgStart[i]-2)):
  221. if abs(self.packcrnt[self.ChgStart[i] - m - 1]) < 0.5:
  222. StandingTime = StandingTime + (self.bmstime[self.ChgStart[i] - m] - self.bmstime[self.ChgStart[i] - m - 1]).total_seconds()
  223. if abs(self.packcrnt[self.ChgEnd[i] + m + 1]) < 0.5:
  224. StandingTime1 = StandingTime1 + (self.bmstime[self.ChgEnd[i] + m + 1] - self.bmstime[self.ChgEnd[i] + m]).total_seconds()
  225. 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且慢充过程丢失数据少
  226. if abs(self.packcrnt[self.ChgEnd[i] + m + 2])>0.5 or m==len(self.packcrnt)-self.ChgEnd[i]-3: #如果电流<0.5,继续寻找充电后的静置电压,直到末尾
  227. ChgStartValid.append(self.ChgStart[i])
  228. ChgEndValid.append(self.ChgEnd[i]+m)
  229. tempweightlist.append(self.tempweight)
  230. break
  231. if abs(self.packcrnt[self.ChgStart[i] - m - 2])>0.5 and abs(self.packcrnt[self.ChgEnd[i] + m + 2])>0.5:
  232. break
  233. if len(ChgStartValid)>0: #两点法计算Soh
  234. df_res=pd.DataFrame(columns=('time','sn','soh','soh1'))
  235. soh2=[]
  236. if not self.df_soh.empty: #获取数据库中上次计算的Soh值
  237. soh_init=list(self.df_soh['soh'])[-1]
  238. else:
  239. soh_init=list(self.bms_soh)[-1]
  240. for i in range(len(ChgStartValid)):
  241. Ah=0
  242. for j in range(ChgStartValid[i],ChgEndValid[i]): #计算Ah
  243. Step=(self.bmstime[j+1]-self.bmstime[j]).total_seconds()
  244. Ah=Ah-self.packcrnt[j+1]*Step/3600
  245. for j in range(1, self.param.CellVoltNums+1): #计算每个电芯的Soh
  246. s = str(j)
  247. OCVStart=self.df_bms.loc[ChgStartValid[i]-2,'单体电压' + s]/1000
  248. OCVEnd=self.df_bms.loc[ChgEndValid[i]-1,'单体电压' + s]/1000
  249. #soh
  250. ocv_Soc1=np.interp(OCVStart,self.param.LookTab_OCV,self.param.LookTab_SOC)
  251. ocv_Soc2=np.interp(OCVEnd,self.param.LookTab_OCV,self.param.LookTab_SOC)
  252. soh2.append(Ah*100/((ocv_Soc2-ocv_Soc1)*0.01*self.param.Capacity))
  253. soh1=np.mean(soh2)
  254. delt_ocv_soc=ocv_Soc2-ocv_Soc1
  255. self._deltsoc_weight(delt_ocv_soc)
  256. soh_res=soh_init*(1-self.deltsoc_weight*tempweightlist[i])+soh1*self.deltsoc_weight*tempweightlist[i]
  257. soh_init=soh_res
  258. df_res.loc[i]=[self.bmstime[ChgStartValid[i]],self.sn,soh_res,soh1]
  259. return df_res
  260. return pd.DataFrame()
  261. #两点法计算三元SOH.........................................................................................................................
  262. def _ncmsoh_twopoint(self):
  263. standingpoint_st=[]
  264. standingpoint_sp=[]
  265. tempweightlist=[]
  266. standingtime=0
  267. for i in range(3,len(self.df_bms)-3):
  268. if abs(self.packcrnt[i]) < 0.1 and abs(self.packcrnt[i-1]) < 0.1 and abs(self.packcrnt[i+1]) < 0.1: #电流为0
  269. delttime=(self.bmstime[i]-self.bmstime[i-1]).total_seconds()
  270. standingtime=standingtime+delttime
  271. self._celltemp_weight(i) #获取不同温度对应的静置时间
  272. if standingtime>self.StandardStandingTime: #静置时间满足要求
  273. if standingpoint_st:
  274. if len(standingpoint_st)>len(standingpoint_sp): #开始时刻已获取,结束时刻未获取
  275. cellvolt_now=self._cellvolt_get(i-1) #获取当前行电压数据
  276. minocv_socnow=np.interp(min(cellvolt_now),self.param.LookTab_OCV,self.param.LookTab_SOC)
  277. cellvolt_st=self._cellvolt_get(standingpoint_st[-1]) #获取开始时刻静置后的电压数据
  278. minocv_socst=np.interp(min(cellvolt_st),self.param.LookTab_OCV,self.param.LookTab_SOC)
  279. if abs(minocv_socst-minocv_socnow)>=40: #当前时刻SOC与开始时刻SOC差>=40
  280. if abs(self.packcrnt[i+2])>=0.1: #如果下一时刻电流>=0.5,则压入当前索引
  281. standingpoint_sp.append(i)
  282. standingpoint_st.append(i)
  283. tempweightlist.append(self.tempweight)
  284. standingtime=0
  285. continue
  286. else:
  287. if standingtime>3600 or i==len(self.df_bms)-2: #仍处于静置,但静置时间>1h,则直接获取sp时刻,或者到了数据末尾
  288. standingpoint_sp.append(i)
  289. tempweightlist.append(self.tempweight)
  290. continue
  291. else:
  292. if abs(self.packcrnt[i+2])>=0.1:
  293. standingtime=0
  294. if minocv_socst<50 and minocv_socnow<minocv_socst:
  295. standingpoint_st[-1]=i
  296. continue
  297. elif abs(self.packcrnt[i+2])>=0.1:
  298. standingtime=0
  299. if minocv_socst>=50 and minocv_socnow>minocv_socst:
  300. standingpoint_st[-1]=i
  301. continue
  302. else:
  303. continue
  304. else:
  305. if abs(self.packcrnt[i+2])>=0.1:
  306. standingpoint_st.append(i)
  307. standingtime=0
  308. continue
  309. else:
  310. continue
  311. else:
  312. if abs(self.packcrnt[i+2])>0.1:
  313. standingpoint_st.append(i)
  314. standingtime=0
  315. continue
  316. else:
  317. continue
  318. else:
  319. continue
  320. else:
  321. standingtime=0
  322. continue
  323. #计算SOH......................................................................................................................
  324. if standingpoint_sp:
  325. column_name=['time_st','time_sp','sn','method','soh','cellsoh']
  326. df_res=pd.DataFrame(columns=column_name)
  327. for i in range(len(standingpoint_sp)):
  328. cellocv_st=self._cellvolt_get(standingpoint_st[i]) #获取静置点所有电芯的电压
  329. cellocv_sp=self._cellvolt_get(standingpoint_sp[i])
  330. accumtime=self.accumtime.to_list() #累计量的时间列表
  331. timepoint_bms_st=self.bmstime[standingpoint_st[i]] #获取静置点的时间
  332. timepoint_bms_sp=self.bmstime[standingpoint_sp[i]]
  333. timepoint_accum_st=bisect.bisect(accumtime,timepoint_bms_st) #获取最接近静置点时间的累计量时间点
  334. timepoint_accum_sp=bisect.bisect(accumtime,timepoint_bms_sp)
  335. if timepoint_accum_sp>=len(accumtime): #防止指针超出数据范围
  336. timepoint_accum_sp=len(accumtime)-1
  337. ah_packcrnt_dis=0
  338. ah_packcrnt_chg=0
  339. for j in range(standingpoint_st[i]+2,standingpoint_sp[i]): #计算累计Ah
  340. Step=(self.bmstime[j+1]-self.bmstime[j]).total_seconds()
  341. if self.packcrnt[j+1]>=0:
  342. ah_packcrnt_dis=ah_packcrnt_dis+self.packcrnt[j+1]*Step
  343. else:
  344. ah_packcrnt_chg=ah_packcrnt_chg-self.packcrnt[j+1]*Step
  345. ah_packcrnt_chg=ah_packcrnt_chg/3600
  346. ah_packcrnt_dis=ah_packcrnt_dis/3600
  347. ah_packcrnt=ah_packcrnt_chg-ah_packcrnt_dis #两个静置点的总累计AH,负值代表放电,正值代表充电
  348. ah_accum_dis=self.df_accum.loc[timepoint_accum_sp,'累计放电电量']-self.df_accum.loc[timepoint_accum_st,'累计放电电量'] #两个静置点之间的放电电量
  349. ah_accum_chg=self.df_accum.loc[timepoint_accum_sp,'累计充电电量']-self.df_accum.loc[timepoint_accum_st,'累计充电电量'] #两个静置点之间的充电电量
  350. ah_accum_tatol=ah_accum_chg-ah_accum_dis #两个静置点的总累计AH,负值代表放电,正值代表充电
  351. ah_accum=ah_packcrnt
  352. delt_days=(self.bmstime[standingpoint_sp[i]]-self.bmstime[standingpoint_st[i]]).total_seconds()/(3600*24)
  353. if delt_days<=1: #两次时间间隔对计算结果的影响
  354. soh_weight1=1
  355. elif delt_days<=2:
  356. soh_weight1=0.7
  357. elif delt_days<=3:
  358. soh_weight1=0.4
  359. else:
  360. soh_weight1=0
  361. if ah_packcrnt_dis<self.param.Capacity: #放电ah数对结果的影响
  362. soh_weight1=(1-ah_packcrnt_dis/(self.param.Capacity*1.5))*soh_weight1
  363. else:
  364. soh_weight1=0.1
  365. if self.param.Capacity**0.7*0.4 < abs(ah_accum_tatol) < self.param.Capacity: #累计量的权重
  366. if abs(ah_accum_tatol-ah_packcrnt)<self.param.Capacity/20:
  367. soh_weight1=soh_weight1*1
  368. elif abs(ah_accum_tatol-ah_packcrnt) < self.param.Capacity/10:
  369. soh_weight1=soh_weight1*0.8
  370. else:
  371. soh_weight1=soh_weight1*0.5
  372. else:
  373. if self.param.Capacity*0.7*0.4< abs(ah_packcrnt) <self.param.Capacity:
  374. soh_weight1=soh_weight1*0.3
  375. else:
  376. soh_weight1=0
  377. cellsoh=[]
  378. for j in range(self.param.CellVoltNums): #计算每个电芯的SOH值
  379. ocv_soc1=np.interp(cellocv_st[j],self.param.LookTab_OCV,self.param.LookTab_SOC)
  380. ocv_soc2=np.interp(cellocv_sp[j],self.param.LookTab_OCV,self.param.LookTab_SOC)
  381. delt_ocv_soc=ocv_soc2-ocv_soc1
  382. delt_ocv_soc_weight=self._deltsoc_weight(abs(delt_ocv_soc))
  383. soh_weight=soh_weight1*tempweightlist[i]*delt_ocv_soc_weight*0.5
  384. cellsoh_init=ah_accum*100/((ocv_soc2-ocv_soc1)*0.01*self.param.Capacity)
  385. if cellsoh_init>55 and cellsoh_init<120: #判断soh值的有效区间
  386. if len(df_res)<1:
  387. if not self.df_soh.empty and 55<self.df_soh.loc[len(self.df_soh)-1,'soh']<120:
  388. cellsoh_last=eval(self.df_soh.loc[len(self.df_soh)-1,'cellsoh'])
  389. if soh_weight>1/abs(cellsoh_init-cellsoh_last[j]):
  390. soh_weight=1/abs(cellsoh_init-cellsoh_last[j])
  391. cellsoh_cal=cellsoh_init*soh_weight + cellsoh_last[j]*(1-soh_weight)
  392. else:
  393. cellsoh_cal=cellsoh_init*soh_weight + cellsoh_last[j]*(1-soh_weight)
  394. else:
  395. cellsoh_cal=cellsoh_init*soh_weight+100*(1-soh_weight)
  396. else:
  397. cellsoh_last=eval(df_res.loc[len(df_res)-1,'cellsoh'])
  398. if soh_weight>1/abs(cellsoh_init-cellsoh_last[j]):
  399. soh_weight=1/abs(cellsoh_init-cellsoh_last[j])
  400. cellsoh_cal=cellsoh_init*soh_weight + cellsoh_last[j]*(1-soh_weight)
  401. else:
  402. cellsoh_cal=cellsoh_init*soh_weight + cellsoh_last[j]*(1-soh_weight)
  403. cellsoh_cal=eval(format(cellsoh_cal,'.1f'))
  404. cellsoh.append(cellsoh_cal)
  405. else:
  406. cellsoh=[]
  407. break
  408. if cellsoh and 55<min(cellsoh)<120:
  409. soh=min(cellsoh)
  410. soh_list=[timepoint_bms_st, timepoint_bms_sp, self.sn, 1, soh, str(cellsoh)]
  411. df_res.loc[len(df_res)]=soh_list
  412. else:
  413. continue
  414. if df_res.empty:
  415. return pd.DataFrame()
  416. else:
  417. return df_res
  418. return pd.DataFrame()
  419. #两点法和DVDQ法计算磷酸铁锂电池SOH..................................................................................................................
  420. def _lfpsoh(self):
  421. standingpoint_st=[]
  422. standingpoint_sp=[]
  423. tempweightlist1=[]
  424. cellmaxvolt_number1=[]
  425. standingtime=0
  426. chrg_start=[]
  427. chrg_end=[]
  428. tempweightlist2=[]
  429. cellmaxvolt_number2=[]
  430. charging=0
  431. for i in range(3,len(self.df_bms)-3):
  432. #获取两点法法所需数据-开始.................................................................................................................
  433. if abs(self.packcrnt[i]) < 0.1 and abs(self.packcrnt[i-1]) < 0.1 and abs(self.packcrnt[i+1]) < 0.1: #判断非平台区静置状态
  434. delttime=(self.bmstime[i]-self.bmstime[i-1]).total_seconds()
  435. standingtime=standingtime+delttime
  436. self._celltemp_weight(i) #获取不同温度对应的静置时间
  437. if standingtime>self.StandardStandingTime: #静置时间满足要求
  438. if abs(self.packcrnt[i+2])>=0.1: #下一时刻电流>0.1A
  439. standingtime=0
  440. cellvolt_now=self._cellvolt_get(i)
  441. if max(cellvolt_now)<self.param.OcvInflexionBelow: #当前最大电芯电压<OCV下拐点
  442. if standingpoint_st:
  443. if len(standingpoint_st)>len(standingpoint_sp):
  444. if self.packcrnt[standingpoint_st[-1]]<-1: #判断上一次静置点的是否为满充
  445. standingpoint_sp.append(i)
  446. standingpoint_st.append(i)
  447. tempweightlist1.append(self.tempweight)
  448. else:
  449. standingpoint_st[-1]=i
  450. tempweightlist1[-1]=self.tempweight
  451. else:
  452. standingpoint_st.append(i)
  453. tempweightlist1.append(self.tempweight)
  454. else:
  455. standingpoint_st.append(i)
  456. tempweightlist1.append(self.tempweight)
  457. else:
  458. pass
  459. else:
  460. pass
  461. else:
  462. pass
  463. elif self.packcrnt[i]<=-1 and self.packcrnt[i-1]<=-1 and self.packcrnt[i+1]<=-1 and self.packcrnt[i+2]>-1: #判读满充状态
  464. standingtime=0
  465. self._celltemp_weight(i)
  466. cellvolt_now=self._cellvolt_get(i)
  467. if max(cellvolt_now)>self.param.CellFullChrgVolt:
  468. if standingpoint_st:
  469. if len(standingpoint_st)>len(standingpoint_sp):
  470. if abs(self.packcrnt[standingpoint_st[-1]])<0.5: #判断上一次静置点是否为下拐点
  471. standingpoint_sp.append(i)
  472. standingpoint_st.append(i)
  473. tempweightlist1.append(self.tempweight)
  474. cellmaxvolt_number1.append(cellvolt_now.index(max(cellvolt_now))) #获取最大电压索引
  475. cellmaxvolt_number1.append(cellvolt_now.index(max(cellvolt_now))) #获取最大电压索引
  476. else:
  477. standingpoint_st[-1]=i
  478. tempweightlist1[-1]=self.tempweight
  479. cellmaxvolt_number1[-1]=cellvolt_now.index(max(cellvolt_now))
  480. else:
  481. standingpoint_st.append(i)
  482. tempweightlist1.append(self.tempweight)
  483. cellmaxvolt_number1.append(cellvolt_now.index(max(cellvolt_now)))
  484. else:
  485. pass
  486. else:
  487. standingtime=0
  488. pass
  489. #获取DVDQ算法所需数据——开始.............................................................................................................
  490. if charging==0:
  491. if self.packcrnt[i]<=-1 and self.packcrnt[i+1]<=-1 and self.packcrnt[i+2]<=-1 and self.bms_soc[i]<40: #充电开始
  492. self._celltemp_weight(i)
  493. charging=1
  494. if len(chrg_start)>len(chrg_end):
  495. chrg_start[-1]=i
  496. tempweightlist2[-1]=self.tempweight
  497. else:
  498. chrg_start.append(i)
  499. tempweightlist2.append(self.tempweight)
  500. else:
  501. pass
  502. else: #充电中
  503. if (self.bmstime[i+1]-self.bmstime[i]).total_seconds()>180: #如果充电过程中时间间隔>180s,则舍弃该次充电
  504. chrg_start.remove(chrg_start[-1])
  505. tempweightlist2.remove(tempweightlist2[-1])
  506. charging=0
  507. continue
  508. elif self.packcrnt[i]<=-1 and self.packcrnt[i+1]<=-1 and self.packcrnt[i+2]>-1: #判断电流波动时刻
  509. cellvolt_now=self._cellvolt_get(i+1)
  510. if max(cellvolt_now)>self.param.CellFullChrgVolt: #电压>满充电压
  511. chrg_end.append(i+1)
  512. cellmaxvolt_number2.append(cellvolt_now.index(max(cellvolt_now))) #获取最大电压索引
  513. charging=0
  514. continue
  515. else:
  516. pass
  517. elif self.packcrnt[i+1]>-0.1 and self.packcrnt[i+2]>-0.1: #判断充电结束
  518. charging=0
  519. if len(chrg_start)>len(chrg_end):
  520. chrg_start.remove(chrg_start[-1])
  521. tempweightlist2.remove(tempweightlist2[-1])
  522. continue
  523. else:
  524. continue
  525. elif i==len(self.packcrnt)-4 and self.packcrnt[i+1]<-1 and self.packcrnt[i+2]<-1:
  526. charging=0
  527. if len(chrg_start)>len(chrg_end):
  528. cellvolt_now=self._cellvolt_get(i)
  529. if max(cellvolt_now)>self.param.CellFullChrgVolt: #电压>满充电压
  530. chrg_end.append(i)
  531. cellmaxvolt_number2.append(cellvolt_now.index(max(cellvolt_now))) #获取最大电压索引
  532. continue
  533. else:
  534. chrg_start.remove(chrg_start[-1])
  535. tempweightlist2.remove(tempweightlist2[-1])
  536. continue
  537. else:
  538. continue
  539. else:
  540. continue
  541. #开始计算SOH.............................................................................................................................................
  542. if standingpoint_sp or chrg_end:
  543. column_name=['time_st','time_sp','sn','method','soh','cellsoh','weight']
  544. df_res=pd.DataFrame(columns=column_name)
  545. #两点法计算SOH........................................................................................................................................
  546. if standingpoint_sp:
  547. for i in range(len(standingpoint_sp)): #判断为满充点或者下拐点
  548. if self.packcrnt[standingpoint_sp[i]]<=-1:
  549. cellocv_st=self._cellvolt_get(standingpoint_st[i])
  550. ocv_soc1=np.interp(cellocv_st[cellmaxvolt_number1[i]],self.param.LookTab_OCV,self.param.LookTab_SOC)
  551. ocv_soc2=self.param.FullChrgSoc
  552. else:
  553. cellocv_sp=self._cellvolt_get(standingpoint_sp[i])
  554. ocv_soc1=self.param.FullChrgSoc
  555. ocv_soc2=np.interp(cellocv_sp[cellmaxvolt_number1[i]],self.param.LookTab_OCV,self.param.LookTab_SOC)
  556. cellocv_sp=self._cellvolt_get(standingpoint_sp[i])
  557. accumtime=self.accumtime.to_list() #累计量的时间列表
  558. timepoint_bms_st=self.bmstime[standingpoint_st[i]] #获取静置点的时间
  559. timepoint_bms_sp=self.bmstime[standingpoint_sp[i]]
  560. timepoint_accum_st=bisect.bisect(accumtime,timepoint_bms_st) #获取最接近静置点时间的累计量时间点
  561. timepoint_accum_sp=bisect.bisect(accumtime,timepoint_bms_sp)
  562. if timepoint_accum_sp>=len(accumtime): #防止指针超出数据范围
  563. timepoint_accum_sp=len(accumtime)-1
  564. ah_packcrnt_dis=0
  565. ah_packcrnt_chg=0
  566. for j in range(standingpoint_st[i]+2,standingpoint_sp[i]+1): #计算累计Ah
  567. Step=(self.bmstime[j+1]-self.bmstime[j]).total_seconds()
  568. if self.packcrnt[j+1]>=0:
  569. ah_packcrnt_dis=ah_packcrnt_dis+self.packcrnt[j+1]*Step
  570. else:
  571. ah_packcrnt_chg=ah_packcrnt_chg-self.packcrnt[j+1]*Step
  572. ah_packcrnt_chg=ah_packcrnt_chg/3600
  573. ah_packcrnt_dis=ah_packcrnt_dis/3600
  574. ah_packcrnt=ah_packcrnt_chg-ah_packcrnt_dis #两个静置点的总累计AH,负值代表放电,正值代表充电
  575. ah_accum_dis=self.df_accum.loc[timepoint_accum_sp,'累计放电电量']-self.df_accum.loc[timepoint_accum_st,'累计放电电量'] #两个静置点之间的放电电量
  576. ah_accum_chg=self.df_accum.loc[timepoint_accum_sp,'累计充电电量']-self.df_accum.loc[timepoint_accum_st,'累计充电电量'] #两个静置点之间的充电电量
  577. ah_accum_tatol=ah_accum_chg-ah_accum_dis #两个静置点的总累计AH,负值代表放电,正值代表充电
  578. ah_accum=ah_accum_tatol
  579. delt_days=(self.bmstime[standingpoint_sp[i]]-self.bmstime[standingpoint_st[i]]).total_seconds()/(3600*24)
  580. if delt_days<=1: #两次时间间隔对计算结果的影响
  581. soh_weight=1
  582. elif delt_days<=2:
  583. soh_weight=0.7
  584. elif delt_days<=3:
  585. soh_weight=0.4
  586. else:
  587. soh_weight=0
  588. if self.param.Capacity*0.65*0.7 < abs(ah_packcrnt) < self.param.Capacity: #累计量的权重
  589. if abs(ah_accum_tatol-ah_packcrnt)<self.param.Capacity/20:
  590. soh_weight=soh_weight*1
  591. elif abs(ah_accum_tatol-ah_packcrnt)<self.param.Capacity/10:
  592. soh_weight=soh_weight*0.8
  593. else:
  594. soh_weight=soh_weight*0.5
  595. else:
  596. if self.param.Capacity*0.65*0.7 < abs(ah_accum) < self.param.Capacity:
  597. soh_weight=soh_weight*0.3
  598. else:
  599. soh_weight=0
  600. delt_ocv_soc=ocv_soc2-ocv_soc1
  601. delt_ocv_soc_weight=self._deltsoc_weight(abs(delt_ocv_soc))
  602. soh_weight=soh_weight*tempweightlist1[i]*delt_ocv_soc_weight*0.5
  603. cellsoh_init=ah_accum*100/((ocv_soc2-ocv_soc1)*0.01*self.param.Capacity)
  604. if cellsoh_init>60 and cellsoh_init<115: #判断soh值的有效区间
  605. soh_list=[timepoint_bms_st, timepoint_bms_sp, self.sn, 1, cellsoh_init, cellsoh_init,soh_weight]
  606. df_res.loc[len(df_res)]=soh_list
  607. else:
  608. pass
  609. else:
  610. pass
  611. #DVDQ法计算SOH.......................................................................................................................................
  612. if chrg_end:
  613. for i in range(len(chrg_end)):
  614. cellvolt_max = self.df_bms['单体电压' + str(cellmaxvolt_number2[i]+1)] / 1000 #获取最大电压
  615. cellvolt=self._np_move_avg(cellvolt_max, 3, mode="same") #对电压进行滑动平均滤
  616. cellsoh_init=self._dvdq_soh(chrg_start[i],chrg_end[i],cellvolt) #dvdq计算soh
  617. soh_weight=tempweightlist2[i]*0.25
  618. if cellsoh_init>60 and cellsoh_init<115: #判断soh值的有效区间
  619. soh_list=[self.bmstime[chrg_start[i]], self.bmstime[chrg_end[i]], self.sn, 2, cellsoh_init, str(cellsoh_init),soh_weight]
  620. df_res.loc[len(df_res)]=soh_list
  621. else:
  622. pass
  623. #对SOH结果进行滤波处理................................................................................................................................
  624. if df_res.empty:
  625. return pd.DataFrame()
  626. else:
  627. df_res=df_res.sort_values(by='time_st',ascending=True)
  628. df_res.index=range(len(df_res))
  629. for i in range(len(df_res)):
  630. cellsoh_init=df_res.loc[i,'soh']
  631. soh_weight=df_res.loc[i,'weight']
  632. if i<1:
  633. if not self.df_soh.empty and 60<self.df_soh.loc[len(self.df_soh)-1,'soh']<115:
  634. cellsoh_last=self.df_soh.loc[len(self.df_soh)-1,'soh']
  635. if soh_weight>1/abs(cellsoh_init-cellsoh_last):
  636. soh_weight=1/abs(cellsoh_init-cellsoh_last)
  637. cellsoh_cal=cellsoh_init*soh_weight + cellsoh_last*(1-soh_weight)
  638. else:
  639. cellsoh_cal=cellsoh_init*soh_weight + cellsoh_last*(1-soh_weight)
  640. else:
  641. cellsoh_cal=cellsoh_init*soh_weight+100*(1-soh_weight)
  642. cellsoh_cal=eval(format(cellsoh_cal,'.1f'))
  643. if 60<cellsoh_cal<115:
  644. df_res.loc[i,'soh']=cellsoh_cal
  645. df_res.loc[i,'cellsoh']=str(cellsoh_cal)
  646. else:
  647. df_res=df_res.drop(i,axis=0)
  648. else:
  649. cellsoh_last=df_res.loc[i-1,'soh']
  650. if soh_weight>1/abs(cellsoh_init-cellsoh_last):
  651. soh_weight=1/abs(cellsoh_init-cellsoh_last)
  652. cellsoh_cal=cellsoh_init*soh_weight + cellsoh_last*(1-soh_weight)
  653. else:
  654. cellsoh_cal=cellsoh_init*soh_weight + cellsoh_last*(1-soh_weight)
  655. cellsoh_cal=eval(format(cellsoh_cal,'.1f'))
  656. if 60<cellsoh_cal<115:
  657. df_res.loc[i,'soh']=cellsoh_cal
  658. df_res.loc[i,'cellsoh']=str(cellsoh_cal)
  659. else:
  660. df_res=df_res.drop(i,axis=0)
  661. df_res=df_res.drop('weight',axis=1)
  662. return df_res
  663. return pd.DataFrame()