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 BMS : arRS232 { /// /// 데이터조회간격(초) /// public float ScanInterval { get; set; } public BMS() { ScanInterval = 1000; MinRecvLength = 34; } /// /// 시리얼포트의 핀 상태값이 변경될 때 발생합니다. /// 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); var queylen = QueryIndex == 0 ? 34 : 23; if (tempBuffer.Count == queylen) { if (incomByte != 0x77) { //종단기호가 맞지 않다. 이자료는 폐기한다. tempBuffer.Clear(); } else { //데이터가 맞게 수신됨 LastReceiveBuffer = tempBuffer.ToArray(); bComplete = true; } findSTX = false; } else { //아직 모자르므로 대기한다 } } } } remainBuffer = remain.ToArray(); return bComplete; } bool Recv0 = false; int Recv0Cnt = 0; bool Recv1 = false; int Recv1Cnt = 0; 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; } if (VerifyChecksum(data) == false) { RaiseMessage(MessageType.Error, "Checksum error"); return false; } if (data[1] == 0x03) { return ParseBMSInfo(); } else if (data[1] == 0x04) { return ParseBMSCellVoltage(); } else return false; } bool ParseBMSCellVoltage() { if (UseCmdLogging == true) RaiseMessage(MessageType.Recv, ByteListToHexString(LastReceiveBuffer.ToList())); //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) / 1000.0; var v2 = BitConverter.ToUInt16(LastReceiveBuffer.Skip(6).Take(2).Reverse().ToArray(), 0) / 1000.0; var v3 = BitConverter.ToUInt16(LastReceiveBuffer.Skip(8).Take(2).Reverse().ToArray(), 0) / 1000.0; var v4 = BitConverter.ToUInt16(LastReceiveBuffer.Skip(10).Take(2).Reverse().ToArray(), 0) / 1000.0; var v5 = BitConverter.ToUInt16(LastReceiveBuffer.Skip(12).Take(2).Reverse().ToArray(), 0) / 1000.0; var v6 = BitConverter.ToUInt16(LastReceiveBuffer.Skip(14).Take(2).Reverse().ToArray(), 0) / 1000.0; var v7 = BitConverter.ToUInt16(LastReceiveBuffer.Skip(16).Take(2).Reverse().ToArray(), 0) / 1000.0; var v8 = BitConverter.ToUInt16(LastReceiveBuffer.Skip(18).Take(2).Reverse().ToArray(), 0) / 1000.0; var idx = 0; CellVoltage[idx++] = v1; CellVoltage[idx++] = v2; CellVoltage[idx++] = v3; CellVoltage[idx++] = v4; CellVoltage[idx++] = v5; CellVoltage[idx++] = v6; CellVoltage[idx++] = v7; CellVoltage[idx++] = v8; Recv1 = true; try { BMSCellDataReceive?.Invoke(this, new BMSCelvoltageEventArgs(v1, v2, v3, v4, v5, v6, v7, v8)); return true; } catch (Exception ex) { RaiseMessage(MessageType.Error, ex.Message); return false; } } else return false; } public static bool VerifyChecksum(byte[] dataBytes) { // 길이 바이트 (byte[3]) byte length = dataBytes[3]; if (dataBytes[1] == 0x03) { // 데이터 바이트 (byte[4] ~ byte[30]) byte[] data = dataBytes.Skip(4).Take(27).ToArray(); // 길이와 데이터의 합을 계산 ushort sum = (ushort)(length + data.Sum(b => b)); // 반전시키고 높은 위치부터 낮은 위치로 더한 후 1을 더함 ushort checksum = (ushort)((~sum + 1) & 0xFFFF); // 받은 체크섬 (byte[31] ~ byte[32]) ushort receivedChecksum = (ushort)((dataBytes[31] << 8) | dataBytes[32]); //252 1: 64514 // 계산된 체크섬과 받은 체크섬을 비교 return checksum == receivedChecksum; } else if (dataBytes[1] == 0x04) { // 데이터 바이트 (byte[4] ~ byte[30]) byte[] data = dataBytes.Skip(4).Take(16).ToArray(); // 길이와 데이터의 합을 계산 ushort sum = (ushort)(length + data.Sum(b => b)); // 반전시키고 높은 위치부터 낮은 위치로 더한 후 1을 더함 ushort checksum = (ushort)((~sum + 1) & 0xFFFF); // 받은 체크섬 (byte[31] ~ byte[32]) ushort receivedChecksum = (ushort)((dataBytes[20] << 8) | dataBytes[21]); //252 1: 64514 // 계산된 체크섬과 받은 체크섬을 비교 return checksum == receivedChecksum; } return false; } bool ParseBMSInfo() { if(UseCmdLogging == true) RaiseMessage(MessageType.Recv, ByteListToHexString(LastReceiveBuffer.ToList())); //전압확인 UInt16 batH = (UInt16)LastReceiveBuffer[4]; UInt16 batL = (UInt16)LastReceiveBuffer[5]; batH = (UInt16)(batH << 8); batH = (UInt16)(batH | batL); Current_Volt = (float)(batH / 100.0); //잔량확인 batH = (UInt16)LastReceiveBuffer[8]; batL = (UInt16)LastReceiveBuffer[9]; batH = (UInt16)(batH << 8); batH = (UInt16)(batH | batL); var newamp = (int)batH; var Changed = newamp != Current_Amp; Current_Amp = newamp; //총량확인 batH = (UInt16)LastReceiveBuffer[10]; batL = (UInt16)LastReceiveBuffer[11]; batH = (UInt16)(batH << 8); batH = (UInt16)(batH | batL); Current_MaxAmp = (int)batH; //남은 % 계산 float levraw = (float)(Current_Amp / (Current_MaxAmp * 1.0)); Current_Level = (float)(levraw * 100.0);// (float)(map(remain, 0, total, 0, 100)); Current_LevelA = LastReceiveBuffer[23]; //<- 23번자료는 byte이므로 소수점이잇는 데이터로 직접 계산한다 Current_DataTime = DateTime.Now; //250620 jwlee 추가 int temp1 = (LastReceiveBuffer[27] << 8) | LastReceiveBuffer[28]; int temp2 = (LastReceiveBuffer[29] << 8) | LastReceiveBuffer[30]; Current_temp1 = (temp1 - 2731) / 10f; Current_temp2 = (temp2 - 2731) / 10f; CheckManualCharge(); Recv0 = true; try { BMSDataReceive?.Invoke(this, new BMSInformationEventArgs(Current_Volt, Current_Amp, Current_MaxAmp, Current_Level, Changed)); return true; } catch (Exception ex) { RaiseMessage(MessageType.Error, ex.Message); return false; } } private bool _autocharge = false; public Boolean AutoCharge { get { return _autocharge; } set { _autocharge = false; } } //public void ClearManualChargeCheckValue() //{ // chk_timee = new DateTime(1982, 11, 23); // chk_times = new DateTime(1982, 11, 23); // chk_valuee = 0f; // chk_values = 0f; //} void CheckManualCharge() { if (AutoCharge) { if (chk_timee.Year != 1982) { chk_timee = new DateTime(1982, 11, 23); chk_valuee = 999f; } if (chk_times.Year != 1982) { chk_times = new DateTime(1982, 11, 23); chk_values = 999f; } } if (chk_times.Year == 1982) { chk_times = DateTime.Now; chk_values = Current_Level; } else { if (chk_timee.Year == 1982) { if ((Current_Level - chk_values) >= 0.1) { //충전중이다 chk_timee = DateTime.Now; chk_valuee = Current_Level; try { ChargeDetect?.Invoke(this, new ChargetDetectArgs(chk_times, chk_values, chk_timee, chk_valuee)); } catch (Exception ex) { RaiseMessage(MessageType.Error, ex.Message); } } else if ((Current_Level - chk_values) <= -0.1) { //방전중이다 if (chk_times.Year != 1982) chk_times = new DateTime(1982, 11, 23); if (chk_timee.Year != 1982) chk_timee = new DateTime(1982, 11, 23); } else { //아직 변화가 없으니 종료일을 기록하지 않는다 } } else { //이미 종료일이 셋팅된 상태이다 if ((Current_Level - chk_valuee) >= 0.1) { //종료시간을 시작값에 넣는다 chk_times = chk_timee; chk_values = chk_valuee; chk_timee = DateTime.Now; chk_valuee = Current_Level; try { ChargeDetect?.Invoke(this, new ChargetDetectArgs(chk_times, chk_values, chk_timee, chk_valuee)); } catch (Exception ex) { RaiseMessage(MessageType.Error, ex.Message); } } else if ((Current_Level - chk_valuee) <= -0.1) { //방전중이다 if (chk_times.Year != 1982) chk_times = new DateTime(1982, 11, 23); if (chk_timee.Year != 1982) chk_timee = new DateTime(1982, 11, 23); } 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 float Current_Volt { get; set; } /// /// 남은 잔량(%) /// public float Current_Level { get; set; } public double[] CellVoltage = new double[] { 0, 0, 0, 0, 0, 0, 0, 0 }; public float Current_LevelA { get; set; } /// /// 남은 전류량 /// public int Current_Amp { get; set; } /// /// BMS 온도값1 /// public double Current_temp1 { get; set; } /// /// BMS 온도값2 /// public double Current_temp2 { get; set; } /// /// 총 전류량 /// public int Current_MaxAmp { get; set; } public DateTime Current_DataTime = DateTime.Parse("1982-11-23"); public int QueryIndex { get; set; } = 0; public int CMDOutCnt { get; set; } = 3; public bool UseCmdLogging { get; set; } = false; /// /// 상태읽기와 전압읽기명령을 반복합니다 /// /// public Boolean SendQuery() { if (QueryIndex == 0) { if (Recv0 == true) { QueryIndex = 1; Recv1 = false; return true; } else { if (Recv0Cnt > CMDOutCnt) { Recv0Cnt = 0; RaiseMessage(MessageType.Error, "0 명령 Count Out"); QueryIndex = 1; Recv1 = false; return true; } else { return SendQuery_ReadStatue(); } } } else { if (Recv1 == true) { QueryIndex = 0; Recv0 = false; return true; } else { if (Recv1Cnt > CMDOutCnt) { Recv1Cnt = 0; RaiseMessage(MessageType.Error, "1 명령 Count Out"); QueryIndex = 0; Recv0 = false; return true; } else { return SendQuery_ReadCellvoltage(); } } } } public Boolean SendQuery_ReadStatue() { Recv0Cnt++; 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); if (UseCmdLogging == true) RaiseMessage(MessageType.Normal, ByteListToHexString(cmd)); return WriteData(cmd.ToArray()); } public Boolean SendQuery_ReadCellvoltage() { Recv1Cnt++; 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); if (UseCmdLogging == true) RaiseMessage(MessageType.Normal, ByteListToHexString(cmd)); return WriteData(cmd.ToArray()); } public string ByteListToHexString(List byteList) { StringBuilder hex = new StringBuilder(byteList.Count * 2); foreach (byte b in byteList) { hex.AppendFormat("{0:X2} ", b); } return hex.ToString(); } } }