// // this fucntion is called often or very often to process battery data // can be used equally well for live battery data in real time or to process recorded battery data // public bool bValuateBatteryData(strctBattMonData stRecentBattData, int ciIterationNo) { bool bRes = false; stDischrgChrgCycle stdschrgNewCycle = default(stDischrgChrgCycle); // neends init such as in DateTime dteSrc = default(DateTime), TimeSpan tmspnStarterMotorRun; // Debug.WriteLine("++bValuateBatteryData("+ciIterationNo+")"); // compute average current and keep it to eliminate noise in current sensor line if (m_iCurrentAvrgIdx < m_ciCurrAvrgCount) { m_dblTempCurrent += stRecentBattData.dblBatAmperes; m_iCurrentAvrgIdx += 1; } else { m_dblAvrgCurrent100Smpls = m_dblTempCurrent / (double)m_ciCurrAvrgCount; m_iCurrentAvrgIdx = 0; m_dblTempCurrent = 0.0; }; // on the very first iteration save this as beginning of recording if (0 == ciIterationNo) { stbtdtmTempFirstRecord = stRecentBattData; if (null == m_arriTimesBetwItersHistogr) { m_arriTimesBetwItersHistogr = new int [ciMaxHistoSize]; } ; // erase histogram as well Array.Clear(m_arriTimesBetwItersHistogr, 0, ciMaxHistoSize); } else { // next we check for Microcontroller errors. TimeSpan tmspnBetwnIters = stRecentBattData.dtBattDateTime - stcBattPrevData.dtBattDateTime; // Timer tick may be reported by microcontroller as inaccurate time, including jumping backawards if (tmspnBetwnIters.TotalSeconds < 0.0) // ignore very first inetration, since previous date-time is invalid // if(tmspnBetwnIters.Days < 0) { Debug.WriteLine("bValuateBatteryData(ERROR) time jumped backwards! Previous=" + stcBattPrevData.dtBattDateTime.ToString("HH:mm:ss.fff") + " > New=" + stRecentBattData.dtBattDateTime.ToString("HH:mm:ss.fff")); // fix up error - use last known good date-time stRecentBattData.dtBattDateTime = stcBattPrevData.dtBattDateTime; } else { // for good iteration, save data to histogram // Debug.WriteLine("bValuateBatteryData() Curr-Prev duration " + tmspnBetwnIters.Days.ToString() + "."+ tmspnBetwnIters.Hours.ToString("0#") +":"+ tmspnBetwnIters.Minutes.ToString("0#")+":"+tmspnBetwnIters.Seconds.ToString("0#.")+"."+tmspnBetwnIters.Milliseconds.ToString("000")); int iIntervBetwIters = (int)tmspnBetwnIters.TotalMilliseconds; if (iIntervBetwIters < ciMaxHistoSize) { m_arriTimesBetwItersHistogr[iIntervBetwIters] += 1; } ; // for every 1000 iterations, print a histoghram if (0 == ciIterationNo % 999) { for (int j = 0; j < ciMaxHistoSize; j++) { Debug.WriteLine("[" + j.ToString() + "] : " + m_arriTimesBetwItersHistogr[j].ToString()); } ; } ; }; // end IF tmspnBetwnIters.TotalSeconds >0 }; // end IF check for Microcontroller errors. // here we observe a few parameters including min and max of voltage and current // as well as watch for engine starting pattern : starter mtr OFF, ON, OFF sequence // criteria - low discharge current for several seconds - key on ignition on // then high discharge current for a second or two - starter on // then dischanrge current changes to charge current // then battery gets strong charging which should quckly deacrease within 15 seconds if battery is healthy //====================================================================== // -80A -40A -2A -0.1A 0A +0.1A +2A +100A // str mtr | discharging | no load | charging // { --->-------->-------->----} current crosses zero amperes line //====================================================================== //------------------------------------------------------------------------------------------- // BATTERY STRONG DISCHARGE. batt current <-80A //-------------------------------------------------------------------------------------------- if (stRecentBattData.dblBatAmperes < -80.0) // starter surely draws more than that { //check if microcontroller correctly specified battery state if (stRecentBattData.chBattState != 'D') { Debug.WriteLine("bValuateBatteryData() ERR battery state mismatch - D"); } ; // watch for maximum current starter draws - in-rush starter motor current if (stRecentBattData.dblBatAmperes < m_sdblMaxBattDischrgCurrent) { m_sdblMaxBattDischrgCurrent = stRecentBattData.dblBatAmperes; } else { // Debug.WriteLine("bValuateBatteryData() Max discharging current=" + sdblMaxBattDischrgCurrent.ToString("+#.#;-#.#;0")); }; if (true == m_bStarterMotorIsOn) { // starter motor keeps running... Debug.WriteLine("bValuateBatteryData() starter mtr is RUNNING"); } else { // starter motor just started Debug.WriteLine("bValuateBatteryData() starter mtr is now ON !"); m_bStarterMotorIsOn = true; stcBattDatStarterOn = stRecentBattData; stcBattDatStarterOff = stcBattPrevData; // create new discharge-charge cycle stdschrgNewCycle.bIsCycleComplete = false; stdschrgNewCycle.bIsFullyRecharged = false; stdschrgNewCycle.dblMaxChrgCurrent = -99.0; stdschrgNewCycle.dblMaxDischrgCurrent = m_sdblMaxBattDischrgCurrent; stdschrgNewCycle.stbtdtmStarterOn = stRecentBattData; stdschrgNewCycle.stbtdtmFirstRecord = stbtdtmTempFirstRecord; if (null != m_mapDischrgChrgCyles) { // you can use .Add function to ass new starter motor event data try { m_mapDischrgChrgCyles.Add(stdschrgNewCycle.stbtdtmStarterOn.dtBattDateTime, stdschrgNewCycle); } catch (ArgumentException aexc) { Debug.WriteLine("bValuateBatteryData() battery already has this dis-chrg cycle on record, skip." + aexc.ToString()); }; // also can use array notation // m_mapDischrgChrgCyles[stdschrgNewCycle.stbtdtmStarterOn.dtBattDateTime]=stdschrgNewCycle; } ; // end IF m_mapDischrgChrgCyles is not Null }; // end IF true==m_bStarterMotorIsOn } // strong discharge case //------------------------------------------------------------------------------------------- // BATTERY DISCHARGE -80A < batt current < -2A //-------------------------------------------------------------------------------------------- else if (stRecentBattData.dblBatAmperes >= -80.0 && stRecentBattData.dblBatAmperes < -2.0) { // Debug.WriteLine("bValuateBatteryData() battery discharging"); //check if microcontroller correctly specified battery state if (stRecentBattData.chBattState != 'D') { Debug.WriteLine("bValuateBatteryData() ERR battery state mismatch - D"); } ; // when discharge current is not that big, i.e. not a starter mtr case // battery discharging case //------------------------------------------------------------------------------------------- // BATTERY DISCHARGE -40A < batt current <-0.2A //-------------------------------------------------------------------------------------------- if (true == m_bStarterMotorIsOn && stRecentBattData.dblBatAmperes > -40.0 && stRecentBattData.dblBatAmperes < -0.2) { Debug.WriteLine("bValuateBatteryData() starter mtr now OFF."); // starter motor not running now m_bStarterMotorIsOn = false; stcBattDatStarterOffAfterOn = stRecentBattData; stcBattDatCurrentCrossZero = stRecentBattData; tmspnStarterMotorRun = stcBattDatStarterOffAfterOn.dtBattDateTime - stcBattDatStarterOn.dtBattDateTime; // uncommenting line below causes exception in line reader Debug.WriteLine("bValuateBatteryData() starter mtr duration=" + tmspnStarterMotorRun.Seconds.ToString() + "." + tmspnStarterMotorRun.Milliseconds.ToString("000") + " sec"); // compute charge released for engine starting double dblTempmQ = (stcBattDatStarterOffAfterOn.liQOut - stcBattDatStarterOn.liQOut); Debug.WriteLine("bValuateBatteryData() Battery released " + (dblTempmQ / 1000).ToString() + " Q to start engine"); if ((stcBattDatStarterOff.dblBatAmperes - stcBattDatStarterOn.dblBatAmperes) != 0.0) { // compute battery internal resistance and related parameters m_dblBatteryResistance = (stcBattDatStarterOff.dblBatVolts - stcBattDatStarterOn.dblBatVolts) / (stcBattDatStarterOff.dblBatAmperes - stcBattDatStarterOn.dblBatAmperes); Debug.WriteLine("bValuateBatteryData() batt R=" + m_dblBatteryResistance.ToString("0.#000") + " Ohm"); // estimate cranking amperes based on voltage-current // Hot cranking amperes (HCA) is the amount of current a battery can provide at 80°F (26.7°C). // The rating is defined as the current a lead-acid battery at that temperature can deliver for 30 seconds and maintain at least 1.2 V/cell (7.2 volts for a 12-volt battery). // Cranking amperes (CA), also sometimes referred to as marine cranking amperes (MCA), is the amount of current a battery can provide at 32°F (0°C). // The rating is defined as the number of amperes a lead-acid battery at that temperature can deliver for 30 seconds and maintain at least 1.2 V/cell (7.2 volts for a 12 volt battery). // Cold cranking amperes (CCA) is the amount of current a battery can provide at 0°F (−18°C). // The rating is defined as the current a lead-acid battery at that temperature can deliver for 30 seconds and maintain at least 1.2 volts per cell (7.2 volts for a 12-volt battery) if (m_dblBatteryResistance != 0.0) { // example // key on 12.1V,-6.4A; starter on 10.7V, -104A; R=(12.1-10.7)/(104-6)=1.4V/97.6A=0.014 Ohm // knowing R predict CA by computation CA=(12.1-7.2)/R m_dblCA = (stcBattDatStarterOff.dblBatVolts - 7.2) / m_dblBatteryResistance; Debug.WriteLine("bValuateBatteryData() batt CA=" + m_dblCA.ToString("0000") + " Amperes"); } ; // // determine whether it was actual engine starting cyle or maybe just a momentary drop in battry current // // decision made is based on high discharge currnet value and duration of it // Must draw high discharge current for at least 0.5 sec to qualify as engine starting event // AND battery should be discharging : state=D and voltage should be < 12.6 Volts if (tmspnStarterMotorRun.Seconds >= 1 || (tmspnStarterMotorRun.Seconds == 0 && tmspnStarterMotorRun.Milliseconds > 500)) { m_iEngineStartEvtCnt += 1; // modify last incomplete discharge-charge cycle and update its data Debug.WriteLine("bValuateBatteryData() valid cycle duration " + tmspnStarterMotorRun.Seconds.ToString() + "." + tmspnStarterMotorRun.Milliseconds.ToString("000") + " sec detected. Completing dischrg-chrg cycle...."); // find our dis-chrg cycle in array and modify it to store starter off data stDischrgChrgCycle stdschrgCycleInWorks = m_mapDischrgChrgCyles[stcBattDatStarterOn.dtBattDateTime]; // if found, modify data in place // // we have now stdschrgCycleInWorks.stbtdtmStarterOn part filled only. // now populate stdschrgCycleInWorks.stbtdtmStarterOffAfterOn and other members // stdschrgCycleInWorks.bIsCycleComplete = true; stdschrgCycleInWorks.dblMaxChrgCurrent = -99.0; stdschrgCycleInWorks.dblBatteryResistance = m_dblBatteryResistance; stdschrgCycleInWorks.dblCA = m_dblCA; stdschrgCycleInWorks.dblMaxDischrgCurrent = m_sdblMaxBattDischrgCurrent; stdschrgCycleInWorks.stbtdtmStarterOffAfterOn = stRecentBattData; stdschrgCycleInWorks.tmspnStrMotorRunDur = tmspnStarterMotorRun; stdschrgCycleInWorks.dblCoulmbsToStartEngine = dblTempmQ / 1000.0; // save it into array m_mapDischrgChrgCyles[stcBattDatStarterOn.dtBattDateTime] = stdschrgCycleInWorks; } else { // not a engine starting cycle! Just very short current drop, e.g. downspike Debug.WriteLine("bValuateBatteryData() Cycle duration too short: " + tmspnStarterMotorRun.Seconds.ToString() + "." + tmspnStarterMotorRun.Milliseconds.ToString("000") + " sec."); Debug.WriteLine("bValuateBatteryData() Not a starter mtr cycle. Removing incomplete dischrg-chrg cycle...."); // so we delete previous spurious dischr-chrg cycle from our list m_mapDischrgChrgCyles.Remove(stcBattDatStarterOn.dtBattDateTime); }; } ; // end IF of (stcBattDatStarterOff.dblBatAmperes-stcBattDatStarterOn.dblBatAmperes) !=0 Amp // store time of this moment and watch for 15 minutes from it - battery should quckly charge, charge current decrease } ; // end IF of true==m_bStarterMotorIsOn } // // when battery current current crosses zero Ampreres line // this includes NO LOAD case as well - see inner check else if (stRecentBattData.dblBatAmperes >= -2.0 && stRecentBattData.dblBatAmperes <= +2.0) { //------------------------------------------------------------------------------------------- // BATTERY NO LOAD -0.1A < batt current <+0.1A //-------------------------------------------------------------------------------------------- if (stRecentBattData.dblBatAmperes >= -0.1 && stRecentBattData.dblBatAmperes <= +0.1) { // Debug.WriteLine("bValuateBatteryData() batt no load"); // may update SoC bars for NL case here //check if microcontroller correctly specified battery state if (stRecentBattData.chBattState != 'I') { Debug.WriteLine("bValuateBatteryData() ERR battery state mismatch - NL"); } ; } ; // // check to determine if we are coming here just after engine starting cycle // - to find out, we determine if an element is present with date-time of starter motor was switched on bool bHasStarterOnElem = m_mapDischrgChrgCyles.ContainsKey(stcBattDatStarterOn.dtBattDateTime); if (true == bHasStarterOnElem) { // if we have at least one engine starter cycle, then we count form the last engine starter cycle - error happens here -- ! stDischrgChrgCycle stdschrgCycleInWorks = m_mapDischrgChrgCyles[stcBattDatStarterOn.dtBattDateTime]; // and if average battery current is around zero, then we compute battery A*Hrs capacity based on charge accepted // from the moment charging started (starter off) till now if (m_iEngineStartEvtCnt > 0 && Math.Abs(m_dblAvrgCurrent100Smpls) < 1.2 && false == stdschrgCycleInWorks.bIsFullyRecharged) { long liBatteryChargeAccepted = (stRecentBattData.liQIn - stcBattDatCurrentCrossZero.liQIn) / 1000; Debug.WriteLine("bValuateBatteryData() Battery is fully charged. Q in =" + liBatteryChargeAccepted.ToString() + " Q"); Debug.WriteLine("bValuateBatteryData() Battery capacity est. " + ((double)liBatteryChargeAccepted / 3600.0).ToString("#0.0") + " A*Hrs"); // workerObject.bLogDataToFile("Battery fully charged (based on ~0A average battery current"); stdschrgCycleInWorks.bIsFullyRecharged = true; // save time stamp when battery fully charged stdschrgCycleInWorks.stbtdtmWhenFullyCharged = stRecentBattData; // find last discharge-charge cycle we used m_mapDischrgChrgCyles[stcBattDatStarterOn.dtBattDateTime] = stdschrgCycleInWorks; // update last discharge-charge cycle with new data } ; // end IF true==bHasStarterOnEle } else { Debug.WriteLine("bValuateBatteryData() Cannot determine if Battery is fully charged, because starter ON event was not found " + stcBattDatStarterOn.dtBattDateTime.ToString("dd.HH:mm:ss.fff")); }; } // battery is now charging //------------------------------------------------------------------------------------------- // BATTERY CHARGING batt current > +0.2A //-------------------------------------------------------------------------------------------- else if (stRecentBattData.dblBatAmperes > +0.2) { // Debug.WriteLine("bValuateBatteryData() battery charging"); if (stRecentBattData.chBattState != 'C') { Debug.WriteLine("bValuateBatteryData() ERR battery state mismatch - C"); } ; // watch for maximum charging current if (stRecentBattData.dblBatAmperes > m_sdblMaxBattChrgCurrent) { m_sdblMaxBattChrgCurrent = stRecentBattData.dblBatAmperes; stcBattDatMaxChargCurr = stRecentBattData; } else { // once we get here the first time then battery charging current starts to drop, // so note max charging current // Debug.WriteLine("bValuateBatteryData() Max charging current=" + sdblMaxBattChrgCurrent.ToString("+#.#;-#.#;0")); }; } else { // tbd Debug.WriteLine("bValuateBatteryData() TBD case"); }; // just store momentary data for the next run stcBattPrevData = stRecentBattData; // Debug.WriteLine("--bValuateBatteryData()="+bRes.ToString()); return(bRes); } // end of bValuateBatteryData()
// // this fucntion is called often or very often to process battery data // can be used equally well for live battery data in real time or to process recorded battery data // public bool bValuateBatteryData(strctBattMonData stRecentBattData, int ciIterationNo) { bool bRes=false; stDischrgChrgCycle stdschrgNewCycle=default(stDischrgChrgCycle); // neends init such as in DateTime dteSrc = default(DateTime), TimeSpan tmspnStarterMotorRun; // Debug.WriteLine("++bValuateBatteryData("+ciIterationNo+")"); // compute average current and keep it to eliminate noise in current sensor line if(m_iCurrentAvrgIdx<m_ciCurrAvrgCount) { m_dblTempCurrent+=stRecentBattData.dblBatAmperes; m_iCurrentAvrgIdx+=1; } else { m_dblAvrgCurrent100Smpls=m_dblTempCurrent / (double)m_ciCurrAvrgCount; m_iCurrentAvrgIdx=0; m_dblTempCurrent=0.0; }; // on the very first iteration save this as beginning of recording if(0==ciIterationNo) { stbtdtmTempFirstRecord=stRecentBattData; if(null==m_arriTimesBetwItersHistogr) { m_arriTimesBetwItersHistogr=new int [ciMaxHistoSize]; }; // erase histogram as well Array.Clear(m_arriTimesBetwItersHistogr,0,ciMaxHistoSize); } else { // next we check for Microcontroller errors. TimeSpan tmspnBetwnIters=stRecentBattData.dtBattDateTime - stcBattPrevData.dtBattDateTime; // Timer tick may be reported by microcontroller as inaccurate time, including jumping backawards if(tmspnBetwnIters.TotalSeconds < 0.0) // ignore very first inetration, since previous date-time is invalid // if(tmspnBetwnIters.Days < 0) { Debug.WriteLine("bValuateBatteryData(ERROR) time jumped backwards! Previous=" + stcBattPrevData.dtBattDateTime.ToString("HH:mm:ss.fff") + " > New=" + stRecentBattData.dtBattDateTime.ToString("HH:mm:ss.fff")); // fix up error - use last known good date-time stRecentBattData.dtBattDateTime=stcBattPrevData.dtBattDateTime; } else { // for good iteration, save data to histogram // Debug.WriteLine("bValuateBatteryData() Curr-Prev duration " + tmspnBetwnIters.Days.ToString() + "."+ tmspnBetwnIters.Hours.ToString("0#") +":"+ tmspnBetwnIters.Minutes.ToString("0#")+":"+tmspnBetwnIters.Seconds.ToString("0#.")+"."+tmspnBetwnIters.Milliseconds.ToString("000")); int iIntervBetwIters=(int)tmspnBetwnIters.TotalMilliseconds; if(iIntervBetwIters < ciMaxHistoSize) { m_arriTimesBetwItersHistogr[iIntervBetwIters]+=1; }; // for every 1000 iterations, print a histoghram if(0==ciIterationNo%999) { for(int j=0; j<ciMaxHistoSize; j++) { Debug.WriteLine("["+j.ToString()+"] : " + m_arriTimesBetwItersHistogr[j].ToString()); }; }; }; // end IF tmspnBetwnIters.TotalSeconds >0 }; // end IF check for Microcontroller errors. // here we observe a few parameters including min and max of voltage and current // as well as watch for engine starting pattern : starter mtr OFF, ON, OFF sequence // criteria - low discharge current for several seconds - key on ignition on // then high discharge current for a second or two - starter on // then dischanrge current changes to charge current // then battery gets strong charging which should quckly deacrease within 15 seconds if battery is healthy //====================================================================== // -80A -40A -2A -0.1A 0A +0.1A +2A +100A // str mtr | discharging | no load | charging // { --->-------->-------->----} current crosses zero amperes line //====================================================================== //------------------------------------------------------------------------------------------- // BATTERY STRONG DISCHARGE. batt current <-80A //-------------------------------------------------------------------------------------------- if(stRecentBattData.dblBatAmperes<-80.0) // starter surely draws more than that { //check if microcontroller correctly specified battery state if(stRecentBattData.chBattState!='D') { Debug.WriteLine("bValuateBatteryData() ERR battery state mismatch - D"); }; // watch for maximum current starter draws - in-rush starter motor current if(stRecentBattData.dblBatAmperes<m_sdblMaxBattDischrgCurrent) { m_sdblMaxBattDischrgCurrent=stRecentBattData.dblBatAmperes; } else { // Debug.WriteLine("bValuateBatteryData() Max discharging current=" + sdblMaxBattDischrgCurrent.ToString("+#.#;-#.#;0")); }; if(true==m_bStarterMotorIsOn) { // starter motor keeps running... Debug.WriteLine("bValuateBatteryData() starter mtr is RUNNING"); } else { // starter motor just started Debug.WriteLine("bValuateBatteryData() starter mtr is now ON !"); m_bStarterMotorIsOn=true; stcBattDatStarterOn=stRecentBattData; stcBattDatStarterOff=stcBattPrevData; // create new discharge-charge cycle stdschrgNewCycle.bIsCycleComplete=false; stdschrgNewCycle.bIsFullyRecharged=false; stdschrgNewCycle.dblMaxChrgCurrent=-99.0; stdschrgNewCycle.dblMaxDischrgCurrent=m_sdblMaxBattDischrgCurrent; stdschrgNewCycle.stbtdtmStarterOn=stRecentBattData; stdschrgNewCycle.stbtdtmFirstRecord=stbtdtmTempFirstRecord; if(null!=m_mapDischrgChrgCyles) { // you can use .Add function to ass new starter motor event data try { m_mapDischrgChrgCyles.Add(stdschrgNewCycle.stbtdtmStarterOn.dtBattDateTime, stdschrgNewCycle); } catch (ArgumentException aexc) { Debug.WriteLine("bValuateBatteryData() battery already has this dis-chrg cycle on record, skip." + aexc.ToString()); }; // also can use array notation // m_mapDischrgChrgCyles[stdschrgNewCycle.stbtdtmStarterOn.dtBattDateTime]=stdschrgNewCycle; }; // end IF m_mapDischrgChrgCyles is not Null }; // end IF true==m_bStarterMotorIsOn } // strong discharge case //------------------------------------------------------------------------------------------- // BATTERY DISCHARGE -80A < batt current < -2A //-------------------------------------------------------------------------------------------- else if(stRecentBattData.dblBatAmperes>=-80.0 && stRecentBattData.dblBatAmperes<-2.0) { // Debug.WriteLine("bValuateBatteryData() battery discharging"); //check if microcontroller correctly specified battery state if(stRecentBattData.chBattState!='D') { Debug.WriteLine("bValuateBatteryData() ERR battery state mismatch - D"); }; // when discharge current is not that big, i.e. not a starter mtr case // battery discharging case //------------------------------------------------------------------------------------------- // BATTERY DISCHARGE -40A < batt current <-0.2A //-------------------------------------------------------------------------------------------- if(true==m_bStarterMotorIsOn && stRecentBattData.dblBatAmperes>-40.0 && stRecentBattData.dblBatAmperes<-0.2) { Debug.WriteLine("bValuateBatteryData() starter mtr now OFF."); // starter motor not running now m_bStarterMotorIsOn=false; stcBattDatStarterOffAfterOn=stRecentBattData; stcBattDatCurrentCrossZero=stRecentBattData; tmspnStarterMotorRun=stcBattDatStarterOffAfterOn.dtBattDateTime - stcBattDatStarterOn.dtBattDateTime; // uncommenting line below causes exception in line reader Debug.WriteLine("bValuateBatteryData() starter mtr duration="+tmspnStarterMotorRun.Seconds.ToString() +"."+tmspnStarterMotorRun.Milliseconds.ToString("000") + " sec"); // compute charge released for engine starting double dblTempmQ=(stcBattDatStarterOffAfterOn.liQOut-stcBattDatStarterOn.liQOut); Debug.WriteLine("bValuateBatteryData() Battery released " + (dblTempmQ/1000).ToString() + " Q to start engine"); if((stcBattDatStarterOff.dblBatAmperes-stcBattDatStarterOn.dblBatAmperes)!=0.0) { // compute battery internal resistance and related parameters m_dblBatteryResistance=(stcBattDatStarterOff.dblBatVolts-stcBattDatStarterOn.dblBatVolts)/ (stcBattDatStarterOff.dblBatAmperes-stcBattDatStarterOn.dblBatAmperes); Debug.WriteLine("bValuateBatteryData() batt R="+m_dblBatteryResistance.ToString("0.#000")+ " Ohm"); // estimate cranking amperes based on voltage-current // Hot cranking amperes (HCA) is the amount of current a battery can provide at 80°F (26.7°C). // The rating is defined as the current a lead-acid battery at that temperature can deliver for 30 seconds and maintain at least 1.2 V/cell (7.2 volts for a 12-volt battery). // Cranking amperes (CA), also sometimes referred to as marine cranking amperes (MCA), is the amount of current a battery can provide at 32°F (0°C). // The rating is defined as the number of amperes a lead-acid battery at that temperature can deliver for 30 seconds and maintain at least 1.2 V/cell (7.2 volts for a 12 volt battery). // Cold cranking amperes (CCA) is the amount of current a battery can provide at 0°F (−18°C). // The rating is defined as the current a lead-acid battery at that temperature can deliver for 30 seconds and maintain at least 1.2 volts per cell (7.2 volts for a 12-volt battery) if(m_dblBatteryResistance!=0.0) { // example // key on 12.1V,-6.4A; starter on 10.7V, -104A; R=(12.1-10.7)/(104-6)=1.4V/97.6A=0.014 Ohm // knowing R predict CA by computation CA=(12.1-7.2)/R m_dblCA=(stcBattDatStarterOff.dblBatVolts-7.2)/m_dblBatteryResistance; Debug.WriteLine("bValuateBatteryData() batt CA="+m_dblCA.ToString("0000")+" Amperes"); }; // // determine whether it was actual engine starting cyle or maybe just a momentary drop in battry current // // decision made is based on high discharge currnet value and duration of it // Must draw high discharge current for at least 0.5 sec to qualify as engine starting event // AND battery should be discharging : state=D and voltage should be < 12.6 Volts if(tmspnStarterMotorRun.Seconds>=1 || (tmspnStarterMotorRun.Seconds==0 && tmspnStarterMotorRun.Milliseconds>500) ) { m_iEngineStartEvtCnt+=1; // modify last incomplete discharge-charge cycle and update its data Debug.WriteLine("bValuateBatteryData() valid cycle duration " + tmspnStarterMotorRun.Seconds.ToString() +"."+tmspnStarterMotorRun.Milliseconds.ToString("000") + " sec detected. Completing dischrg-chrg cycle...."); // find our dis-chrg cycle in array and modify it to store starter off data stDischrgChrgCycle stdschrgCycleInWorks=m_mapDischrgChrgCyles[stcBattDatStarterOn.dtBattDateTime]; // if found, modify data in place // // we have now stdschrgCycleInWorks.stbtdtmStarterOn part filled only. // now populate stdschrgCycleInWorks.stbtdtmStarterOffAfterOn and other members // stdschrgCycleInWorks.bIsCycleComplete=true; stdschrgCycleInWorks.dblMaxChrgCurrent=-99.0; stdschrgCycleInWorks.dblBatteryResistance=m_dblBatteryResistance; stdschrgCycleInWorks.dblCA=m_dblCA; stdschrgCycleInWorks.dblMaxDischrgCurrent=m_sdblMaxBattDischrgCurrent; stdschrgCycleInWorks.stbtdtmStarterOffAfterOn=stRecentBattData; stdschrgCycleInWorks.tmspnStrMotorRunDur=tmspnStarterMotorRun; stdschrgCycleInWorks.dblCoulmbsToStartEngine=dblTempmQ/1000.0; // save it into array m_mapDischrgChrgCyles[stcBattDatStarterOn.dtBattDateTime]=stdschrgCycleInWorks; } else { // not a engine starting cycle! Just very short current drop, e.g. downspike Debug.WriteLine("bValuateBatteryData() Cycle duration too short: " + tmspnStarterMotorRun.Seconds.ToString() +"."+tmspnStarterMotorRun.Milliseconds.ToString("000") + " sec."); Debug.WriteLine("bValuateBatteryData() Not a starter mtr cycle. Removing incomplete dischrg-chrg cycle...."); // so we delete previous spurious dischr-chrg cycle from our list m_mapDischrgChrgCyles.Remove(stcBattDatStarterOn.dtBattDateTime); }; }; // end IF of (stcBattDatStarterOff.dblBatAmperes-stcBattDatStarterOn.dblBatAmperes) !=0 Amp // store time of this moment and watch for 15 minutes from it - battery should quckly charge, charge current decrease }; // end IF of true==m_bStarterMotorIsOn } // // when battery current current crosses zero Ampreres line // this includes NO LOAD case as well - see inner check else if(stRecentBattData.dblBatAmperes>=-2.0 && stRecentBattData.dblBatAmperes<=+2.0) { //------------------------------------------------------------------------------------------- // BATTERY NO LOAD -0.1A < batt current <+0.1A //-------------------------------------------------------------------------------------------- if(stRecentBattData.dblBatAmperes>=-0.1 && stRecentBattData.dblBatAmperes<=+0.1) { // Debug.WriteLine("bValuateBatteryData() batt no load"); // may update SoC bars for NL case here //check if microcontroller correctly specified battery state if(stRecentBattData.chBattState!='I') { Debug.WriteLine("bValuateBatteryData() ERR battery state mismatch - NL"); }; }; // // check to determine if we are coming here just after engine starting cycle // - to find out, we determine if an element is present with date-time of starter motor was switched on bool bHasStarterOnElem=m_mapDischrgChrgCyles.ContainsKey(stcBattDatStarterOn.dtBattDateTime); if(true==bHasStarterOnElem) { // if we have at least one engine starter cycle, then we count form the last engine starter cycle - error happens here -- ! stDischrgChrgCycle stdschrgCycleInWorks=m_mapDischrgChrgCyles[stcBattDatStarterOn.dtBattDateTime]; // and if average battery current is around zero, then we compute battery A*Hrs capacity based on charge accepted // from the moment charging started (starter off) till now if(m_iEngineStartEvtCnt>0 && Math.Abs(m_dblAvrgCurrent100Smpls)<1.2 && false==stdschrgCycleInWorks.bIsFullyRecharged) { long liBatteryChargeAccepted=(stRecentBattData.liQIn-stcBattDatCurrentCrossZero.liQIn)/1000; Debug.WriteLine("bValuateBatteryData() Battery is fully charged. Q in ="+liBatteryChargeAccepted.ToString()+" Q"); Debug.WriteLine("bValuateBatteryData() Battery capacity est. "+((double)liBatteryChargeAccepted/3600.0).ToString("#0.0")+" A*Hrs"); // workerObject.bLogDataToFile("Battery fully charged (based on ~0A average battery current"); stdschrgCycleInWorks.bIsFullyRecharged=true; // save time stamp when battery fully charged stdschrgCycleInWorks.stbtdtmWhenFullyCharged=stRecentBattData; // find last discharge-charge cycle we used m_mapDischrgChrgCyles[stcBattDatStarterOn.dtBattDateTime]=stdschrgCycleInWorks; // update last discharge-charge cycle with new data }; // end IF true==bHasStarterOnEle } else { Debug.WriteLine("bValuateBatteryData() Cannot determine if Battery is fully charged, because starter ON event was not found "+stcBattDatStarterOn.dtBattDateTime.ToString("dd.HH:mm:ss.fff")); }; } // battery is now charging //------------------------------------------------------------------------------------------- // BATTERY CHARGING batt current > +0.2A //-------------------------------------------------------------------------------------------- else if(stRecentBattData.dblBatAmperes>+0.2) { // Debug.WriteLine("bValuateBatteryData() battery charging"); if(stRecentBattData.chBattState!='C') { Debug.WriteLine("bValuateBatteryData() ERR battery state mismatch - C"); }; // watch for maximum charging current if(stRecentBattData.dblBatAmperes>m_sdblMaxBattChrgCurrent) { m_sdblMaxBattChrgCurrent=stRecentBattData.dblBatAmperes; stcBattDatMaxChargCurr=stRecentBattData; } else { // once we get here the first time then battery charging current starts to drop, // so note max charging current // Debug.WriteLine("bValuateBatteryData() Max charging current=" + sdblMaxBattChrgCurrent.ToString("+#.#;-#.#;0")); }; } else { // tbd Debug.WriteLine("bValuateBatteryData() TBD case"); }; // just store momentary data for the next run stcBattPrevData=stRecentBattData; // Debug.WriteLine("--bValuateBatteryData()="+bRes.ToString()); return bRes; }