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();
}
}
}