CBMSBatSoh.py 35 KB

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