using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; using System.Threading; using System.Threading.Tasks; using System.CodeDom; namespace arDev { public class BMSCellInformation { public float[] Voltage = new float[8]; public float Average { get { if (Voltage.Any() == false) return 0f; return Voltage.Average(); } } public float Delta { get { if (Voltage.Any() == false) return 0f; var value = Math.Abs(Voltage.Max() - Voltage.Min()); return value * 1000f; } } } public class BMSBasicInformation { public float packVoltage { get; set; } public float current { get; set; } public float remainingCapacity { get; set; } public float fullCapacity { get; set; } public UInt16 cycleCount { get; set; } public DateTime productionDate { get; set; } public UInt32 fullBalance { get; set; } public BMSProtectionStatus protectionStatus { get; set; } public byte version { get; set; } public byte rsoc { get; set; } public BMSMosfetStatus mosfetStatus { get; set; } public byte ntcCount { get; set; } public float[] ntcTemp { get; set; } public ushort raw_protection { get; set; } public float watt { get { return (this.current * packVoltage); } } /// /// Remain / Full * 100 /// public float RawLevel { get { if (fullCapacity < 1) return 0f; return (remainingCapacity / fullCapacity) * 100f; } } public BMSBasicInformation() { protectionStatus = new BMSProtectionStatus(); mosfetStatus = new BMSMosfetStatus(); productionDate = new DateTime(); ntcTemp = new float[] { 0f, 0f }; } public void Clear() { packVoltage = 0; current = 0; remainingCapacity = 0; fullBalance = 0; fullCapacity = 0; cycleCount = 0; productionDate = new DateTime(); protectionStatus = new BMSProtectionStatus(); } public override string ToString() { return $"packVoltage:{packVoltage}\n" + $"current:{current}\n" + $"remainingCapacity:{remainingCapacity}\n" + $"fullCapacity:{fullCapacity}\n" + $"cycleCount:{cycleCount}\n" + $"productionDate:{productionDate}\n" + $"fullBalance:{fullBalance}\n" + $"protectionStatus:{protectionStatus}\n" + $"version:{version}\n" + $"rsoc:{rsoc}\n" + $"mosfetStatus:{mosfetStatus}\n" + $"ntcCount:{ntcCount}\n" + $"ntcTemp:{ntcTemp}\n" + $"watt:{watt}\n" + $"RawLevel:{RawLevel}"; } } public class BMSProtectionStatus { public bool covp { get; set; }// Cell Over Voltage Protection public bool cuvp { get; set; } // Cell Under Voltage Protection public bool povp { get; set; } // Pack Over Voltage Protection public bool puvp { get; set; } // Pack Under Voltage Protection public bool chgot { get; set; }// Charge Over Temp public bool chgut { get; set; } // Charge Under Temp public bool dsgot { get; set; } // Discharge Over Temp public bool dsgut { get; set; } // Discharge Under Temp public bool chgoc { get; set; } // Charge Over Current public bool dsgoc { get; set; } // Discharge Over Current public bool sc { get; set; } // Short Circuit public bool afe { get; set; } // AFE Error public override string ToString() { return "BMSProtectionStatus\n" + $"covp={covp}\n" +// { get; set; }// Cell Over Voltage Protection $"cuvp={cuvp}\n" +// { get; set; } // Cell Under Voltage Protection $"povp={povp}\n" +// { get; set; } // Pack Over Voltage Protection $"puvp={puvp}\n" +// { get; set; } // Pack Under Voltage Protection $"chgot={chgot}\n" +// { get; set; }// Charge Over Temp $"chgut={chgut}\n" +// { get; set; } // Charge Under Temp $"dsgot={dsgot}\n" +// { get; set; } // Discharge Over Temp $"dsgut={dsgut}\n" +// { get; set; } // Discharge Under Temp $"chgoc={chgoc}\n" +// { get; set; } // Charge Over Current $"dsgoc={dsgoc}\n" +// { get; set; } // Discharge Over Current $"sc={sc}\n" +// { get; set; } // Short Circuit $"afe={afe}";// +// { get; set; } // AFE Error } } public class BMSMosfetStatus { public bool charge { get; set; } public bool discharge { get; set; } public override string ToString() { return $"charge:{charge}\n" + $"discharge:{discharge}"; } } public class BMS : BMSSerialComm { public BMS() { } /// /// 시리얼포트의 핀 상태값이 변경될 때 발생합니다. /// public event EventHandler BMSDataReceive; public event EventHandler BMSCellDataReceive; protected override bool CustomParser(byte[] buf, out byte[] remainBuffer) { List remain = new List(); //데이터는 총 34byt 이며 , DD~77로 구성됨 Boolean bComplete = false; for (int i = 0; i < buf.Length; i++) { var incomByte = buf[i]; if (bComplete == true) { remain.Add(incomByte); } else { if (findSTX == false) { if (incomByte != 0xDD) { //버리는데이터 } else { findSTX = true; tempBuffer.Clear(); tempBuffer.Add(incomByte); } } else { tempBuffer.Add(incomByte); if (tempBuffer.Count > 7) { byte len = tempBuffer[3]; if (tempBuffer.Count >= 4 + len + 3) // Start+Reg+Status+Len + Data + Chk(2) + End { if (tempBuffer.Last() == 0x77) { //데이터가 맞게 수신됨 LastReceiveBuffer = tempBuffer.ToArray(); bComplete = true; } else { //종단기호가 맞지 않다. 이자료는 폐기한다. var hexstr = string.Join(" ", tempBuffer.Select(t => t.ToString("X2"))); RaiseMessage(MessageType.Error, $"discard : {hexstr}"); tempBuffer.Clear(); } findSTX = false; } } // [22 - 12 - 27 14:32:49] open: True //[22 - 12 - 27 14:32:49] Send: DD A5 03 00 FF FD 77 0D //[22 - 12 - 27 14:32:50] Send: DD A5 03 00 FF FD 77 0D //[22 - 12 - 27 14:32:50] Recv: 26.61v,81.4 % //[22 - 12 - 27 14:32:50] Recv: DD 03 00 1B 0A 65 00 00 21 63 29 04 00 00 2C 92 00 00 00 00 00 00 28 51 03 08 02 0B 69 0B 66 FC 9C 77 //[22 - 12 - 27 14:32:50] Send: DD A5 03 00 FF FD 77 0D //[22 - 12 - 27 14:32:51] Recv: 26.61v,81.4 % //[22 - 12 - 27 14:32:51] Recv: DD 03 00 1B 0A 65 00 00 21 63 29 04 00 00 2C 92 00 00 00 00 00 00 28 51 03 08 02 0B 69 0B 66 FC 9C 77 //var queylen = QueryIndex == 0 ? 34 : 23; //if (tempBuffer.Count == queylen) //{ // if (incomByte != 0x77) // { // //종단기호가 맞지 않다. 이자료는 폐기한다. // var hexstr = string.Join(" ", tempBuffer.Select(t => t.ToString("X2"))); // RaiseMessage(MessageType.Error, $"discard : {hexstr}"); // tempBuffer.Clear(); // } // else // { // //데이터가 맞게 수신됨 // LastReceiveBuffer = tempBuffer.ToArray(); // bComplete = true; // } // findSTX = false; //} //else //{ // //아직 모자르므로 대기한다 //} } } } remainBuffer = remain.ToArray(); return bComplete; } bool Recv0 = false; bool Recv1 = false; public event EventHandler ChargeDetect; public override bool ProcessRecvData(byte[] data) { //LastReceiveBuffer if (data == null) { RaiseMessage(MessageType.Error, "수신 데이터가 없습니다"); return false; } var datalne = QueryIndex == 0 ? 34 : 23; if (data.Length != datalne) { RaiseMessage(MessageType.Error, $"데이터의 길이가 {MinRecvLength}가 아닙니다 길이={data.Length}"); return false; } else { var rxstr = string.Join(" ", data.Select(t => t.ToString("X2"))); RaiseMessage(MessageType.Recv, rxstr); } if (QueryIndex == 0) { return ParseBMSInfo(); } else { return ParseBMSCellVoltage(); } } bool ParseBMSCellVoltage() { //var i = 0; var BatteryCell_Checksum = 0xffff;// - (LastReceiveBuffer[i + 3] + LastReceiveBuffer[i + 4] + LastReceiveBuffer[i + 5] + LastReceiveBuffer[i + 6] + LastReceiveBuffer[i + 7] + LastReceiveBuffer[i + 8] + LastReceiveBuffer[i + 9] + LastReceiveBuffer[i + 10] + LastReceiveBuffer[i + 11] + LastReceiveBuffer[i + 12] + LastReceiveBuffer[i + 13] + LastReceiveBuffer[i + 14] + LastReceiveBuffer[i + 15] + LastReceiveBuffer[i + 16] + LastReceiveBuffer[i + 17] + LastReceiveBuffer[i + 18] + LastReceiveBuffer[i + 19])) + 1; for (int i = 3; i < 20; i++) { BatteryCell_Checksum -= LastReceiveBuffer[i];// + LastReceiveBuffer[i + 4] + LastReceiveBuffer[i + 5] + LastReceiveBuffer[i + 6] + LastReceiveBuffer[i + 7] + LastReceiveBuffer[i + 8] + LastReceiveBuffer[i + 9] + LastReceiveBuffer[i + 10] + LastReceiveBuffer[i + 11] + LastReceiveBuffer[i + 12] + LastReceiveBuffer[i + 13] + LastReceiveBuffer[i + 14] + LastReceiveBuffer[i + 15] + LastReceiveBuffer[i + 16] + LastReceiveBuffer[i + 17] + LastReceiveBuffer[i + 18] + LastReceiveBuffer[i + 19])) + 1; } BatteryCell_Checksum += 1; var recvchecksum = BitConverter.ToUInt16(LastReceiveBuffer.Skip(20).Take(2).Reverse().ToArray(), 0); if (recvchecksum == BatteryCell_Checksum) { var v1 = BitConverter.ToUInt16(LastReceiveBuffer.Skip(4).Take(2).Reverse().ToArray(), 0) / 1000f; var v2 = BitConverter.ToUInt16(LastReceiveBuffer.Skip(6).Take(2).Reverse().ToArray(), 0) / 1000f; var v3 = BitConverter.ToUInt16(LastReceiveBuffer.Skip(8).Take(2).Reverse().ToArray(), 0) / 1000f; var v4 = BitConverter.ToUInt16(LastReceiveBuffer.Skip(10).Take(2).Reverse().ToArray(), 0) / 1000f; var v5 = BitConverter.ToUInt16(LastReceiveBuffer.Skip(12).Take(2).Reverse().ToArray(), 0) / 1000f; var v6 = BitConverter.ToUInt16(LastReceiveBuffer.Skip(14).Take(2).Reverse().ToArray(), 0) / 1000f; var v7 = BitConverter.ToUInt16(LastReceiveBuffer.Skip(16).Take(2).Reverse().ToArray(), 0) / 1000f; var v8 = BitConverter.ToUInt16(LastReceiveBuffer.Skip(18).Take(2).Reverse().ToArray(), 0) / 1000f; var idx = 0; BMSCellVoltage.Voltage[idx++] = v1; BMSCellVoltage.Voltage[idx++] = v2; BMSCellVoltage.Voltage[idx++] = v3; BMSCellVoltage.Voltage[idx++] = v4; BMSCellVoltage.Voltage[idx++] = v5; BMSCellVoltage.Voltage[idx++] = v6; BMSCellVoltage.Voltage[idx++] = v7; BMSCellVoltage.Voltage[idx++] = v8; Recv1 = true; try { BMSCellDataReceive?.Invoke(this, new BMSCelvoltageEventArgs(v1, v2, v3, v4, v5, v6, v7, v8)); Current_CellTime = DateTime.Now; return true; } catch (Exception ex) { RaiseMessage(MessageType.Error, ex.Message); return false; } } else return false; } bool ParseBMSInfo() { var newinfo = new BMSBasicInformation(); //전압확인 var offset = 4; UInt16 batH = (UInt16)LastReceiveBuffer[offset + 0]; UInt16 batL = (UInt16)LastReceiveBuffer[offset + 1]; batH = (UInt16)(batH << 8); batH = (UInt16)(batH | batL); newinfo.packVoltage = (float)(batH / 100.0); //충방전전류 Int16 batHi = (Int16)LastReceiveBuffer[offset + 2]; Int16 batLi = (Int16)LastReceiveBuffer[offset + 3]; batHi = (Int16)(batHi << 8); batHi = (Int16)(batHi | batLi); newinfo.current = (float)(batHi / 100.0); //잔량확인 batH = (UInt16)LastReceiveBuffer[offset + 4]; batL = (UInt16)LastReceiveBuffer[offset + 5]; batH = (UInt16)(batH << 8); batH = (UInt16)(batH | batL); newinfo.remainingCapacity = (float)(batH / 100.0); //총량확인 batH = (UInt16)LastReceiveBuffer[offset + 6]; batL = (UInt16)LastReceiveBuffer[offset + 7]; batH = (UInt16)(batH << 8); batH = (UInt16)(batH | batL); newinfo.fullCapacity = (float)(batH / 100.0); //cycle batH = (UInt16)LastReceiveBuffer[offset + 8]; batL = (UInt16)LastReceiveBuffer[offset + 9]; batH = (UInt16)(batH << 8); batH = (UInt16)(batH | batL); newinfo.cycleCount = batH; //productiondate batH = (UInt16)LastReceiveBuffer[offset + 10]; batL = (UInt16)LastReceiveBuffer[offset + 11]; batH = (UInt16)(batH << 8); batH = (UInt16)(batH | batL); var info_productiondateint = batH; var date_year = (info_productiondateint >> 9) + 2000; var date_month = (info_productiondateint >> 5) & 0x0F; var date_day = info_productiondateint & 0x1F; newinfo.productionDate = new DateTime(date_year, date_month, date_day); //balnace status batH = (UInt16)LastReceiveBuffer[offset + 12]; batL = (UInt16)LastReceiveBuffer[offset + 13]; batH = (UInt16)(batH << 8); var balanceStatus = (UInt16)(batH | batL); //balnace status(HIGH) batH = (UInt16)LastReceiveBuffer[offset + 14]; batL = (UInt16)LastReceiveBuffer[offset + 15]; batH = (UInt16)(batH << 8); var balanceStatusHigh = (UInt16)(batH | batL); newinfo.fullBalance = (UInt32)(balanceStatus | (balanceStatusHigh << 16)); //protectionStatusRaw batH = (UInt16)LastReceiveBuffer[offset + 16]; batL = (UInt16)LastReceiveBuffer[offset + 17]; batH = (UInt16)(batH << 8); newinfo.raw_protection = batH;// view.getUint16(16, false); var protectionStatusRaw = newinfo.raw_protection; newinfo.protectionStatus.covp = (protectionStatusRaw & 1) > 0; newinfo.protectionStatus.cuvp = ((protectionStatusRaw >> 1) & 1) > 0; newinfo.protectionStatus.povp = ((protectionStatusRaw >> 2) & 1) > 0; newinfo.protectionStatus.puvp = ((protectionStatusRaw >> 3) & 1) > 0; newinfo.protectionStatus.chgot = ((protectionStatusRaw >> 4) & 1) > 0; newinfo.protectionStatus.chgut = ((protectionStatusRaw >> 5) & 1) > 0; newinfo.protectionStatus.dsgot = ((protectionStatusRaw >> 6) & 1) > 0; newinfo.protectionStatus.dsgut = ((protectionStatusRaw >> 7) & 1) > 0; newinfo.protectionStatus.chgoc = ((protectionStatusRaw >> 8) & 1) > 0; newinfo.protectionStatus.dsgoc = ((protectionStatusRaw >> 9) & 1) > 0; newinfo.protectionStatus.sc = ((protectionStatusRaw >> 10) & 1) > 0; newinfo.protectionStatus.afe = ((protectionStatusRaw >> 11) & 1) > 0; //version newinfo.version = LastReceiveBuffer[offset + 18]; newinfo.rsoc = LastReceiveBuffer[offset + 19]; var mosfetRaw = LastReceiveBuffer[offset + 20]; newinfo.mosfetStatus.charge = (mosfetRaw & 1) == 1; newinfo.mosfetStatus.discharge = ((mosfetRaw >> 1) & 1) == 1; //250620 jwlee 추가 newinfo.ntcCount = LastReceiveBuffer[offset + 22]; //센서갯수 int temp1 = (LastReceiveBuffer[offset + 23] << 8) | LastReceiveBuffer[offset + 24]; int temp2 = (LastReceiveBuffer[offset + 25] << 8) | LastReceiveBuffer[offset + 26]; var Current_temp1 = (temp1 - 2731) / 10f; var Current_temp2 = (temp2 - 2731) / 10f; newinfo.ntcTemp = new float[] { (temp1 - 2731) / 10f, (temp2 - 2731) / 10f }; CheckManualCharge(newinfo); Recv0 = true; try { this.BMSInformation = newinfo; BMSDataReceive?.Invoke(this, new BMSInformationEventArgs(BMSInformation)); Current_DataTime = DateTime.Now; return true; } catch (Exception ex) { RaiseMessage(MessageType.Error, ex.Message); return false; } } public BMSBasicInformation BMSInformation = new BMSBasicInformation(); public BMSCellInformation BMSCellVoltage = new BMSCellInformation(); /// /// 현재 충전중인지? /// public bool IsCharging { get; private set; } DateTime ChargeStart = DateTime.Now; DateTime ChargeEnd = DateTime.Now; void CheckManualCharge(BMSBasicInformation info) { //충방전전력이 1보다 크면 충전으로 한다. if (this.BMSInformation.current > 0.1) { //기존에 충전상태가 OFF였다면 충전중으로 알려준다 if (IsCharging == false) { IsCharging = true; ChargeStart = DateTime.Now; ChargeEnd = new DateTime(1982, 11, 23); try { ChargeDetect?.Invoke(this, new ChargetDetectArgs(ChargeStart, true, info.RawLevel)); } catch (Exception ex) { RaiseMessage(MessageType.Error, ex.Message); } } else { //충전상태가 유지되고 있다. } } else { //충전이해제되었다.. 단 바로 해제하지않고 1초정도 텀을 주고 OFF한다. if (IsCharging) { if (ChargeEnd.Year == 1982) { ChargeEnd = DateTime.Now; } else { var ts = DateTime.Now - ChargeEnd; if (ts.TotalSeconds > 2) //충전종료시그널후 2초후에 충전off를 알린다. { ChargeEnd = DateTime.Now; IsCharging = false; try { ChargeDetect?.Invoke(this, new ChargetDetectArgs(ChargeEnd, false, info.RawLevel)); } catch (Exception ex) { RaiseMessage(MessageType.Error, ex.Message); } } } } else { //방전상태가 유지되고 있다. } } } public DateTime chk_times { get; set; } = new DateTime(1982, 11, 23); public DateTime chk_timee { get; set; } = new DateTime(1982, 11, 23); public float chk_values { get; set; } = 0f; public float chk_valuee { get; set; } = 0f; public DateTime Current_DataTime = DateTime.Parse("1982-11-23"); public DateTime Current_CellTime = DateTime.Parse("1982-11-23"); public int QueryIndex { get; set; } = 0; /// /// 상태읽기와 전압읽기명령을 반복합니다 /// /// public Boolean SendQuery() { if (QueryIndex == 0) { if (Recv0 == true) { QueryIndex = 1; Recv1 = false; return true; } else { return SendQuery_ReadStatue(); } } else { if (Recv1 == true) { QueryIndex = 0; Recv0 = false; return true; } else { return SendQuery_ReadCellvoltage(); } } } public Boolean SendQuery_ReadStatue() { Recv0 = false; var cmd = new List(); cmd.Add(0xDD); cmd.Add(0xA5); cmd.Add(0x03); cmd.Add(0x00); cmd.Add(0xFF); cmd.Add(0xFD); cmd.Add(0x77); //cmd.Add(0x0D); //_device.DiscardInBuffer(); return WriteData(cmd.ToArray()); } public Boolean SendQuery_ReadCellvoltage() { Recv1 = false; var cmd = new List(); cmd.Add(0xDD); cmd.Add(0xA5); cmd.Add(0x04); cmd.Add(0x00); cmd.Add(0xFF); cmd.Add(0xFC); cmd.Add(0x77); //cmd.Add(0x0D); //_device.DiscardInBuffer(); return WriteData(cmd.ToArray()); } } }