138 lines
4.9 KiB
C#
138 lines
4.9 KiB
C#
using BmsMonitor.App.Can;
|
|
|
|
namespace BmsMonitor.App.Protocol;
|
|
|
|
public sealed class BmsProtocolParser
|
|
{
|
|
public const uint StatusFrameId = 0x186040F3;
|
|
public const uint ExtremesFrameId = 0x186140F3;
|
|
public const uint ActualStatusFrameId = 0x140;
|
|
public const uint TemperatureFrameId = 0x156;
|
|
|
|
private static readonly IReadOnlyDictionary<uint, int> CellFrameOffsets = new Dictionary<uint, int>
|
|
{
|
|
[0x150] = 0,
|
|
[0x151] = 4,
|
|
[0x153] = 8,
|
|
[0x154] = 12
|
|
};
|
|
|
|
private static readonly string[] Faults1 =
|
|
{
|
|
"从控离线", "单体过压", "单体欠压", "电压断线",
|
|
"单体过温", "单体欠温", "温度断线", "电流传感器故障"
|
|
};
|
|
|
|
private static readonly string[] Faults2 =
|
|
{
|
|
"总压与电芯累加和偏差过大", "总压过低", "预充失败", "充电完成"
|
|
};
|
|
|
|
private static readonly string[] States =
|
|
{
|
|
"自检", "低压", "预充", "高压", "高压-安全回路断线", "故障"
|
|
};
|
|
|
|
public bool TryApply(CanFrame frame, BmsSnapshot current, out BmsSnapshot updated)
|
|
{
|
|
updated = current;
|
|
if (frame.Data.Length < 8) return false;
|
|
|
|
if (frame.Id == StatusFrameId || frame.Id == ActualStatusFrameId)
|
|
{
|
|
var faults = DecodeFaults(frame.Data[5], frame.Data[6]);
|
|
var state = frame.Data[7] < States.Length ? States[frame.Data[7]] : $"未知({frame.Data[7]})";
|
|
var packVoltage = ReadU16(frame.Data, 0) * 0.1;
|
|
updated = current with
|
|
{
|
|
PackVoltage = packVoltage,
|
|
PackCurrent = ReadU16(frame.Data, 2) * 0.1 - 500.0,
|
|
Soc = (int)Math.Round(Math.Clamp(packVoltage / 68.0 * 100.0, 0.0, 100.0)),
|
|
State = state,
|
|
Faults = faults,
|
|
LastUpdated = frame.Timestamp
|
|
};
|
|
return true;
|
|
}
|
|
|
|
if (CellFrameOffsets.TryGetValue(frame.Id, out var cellOffset))
|
|
{
|
|
var cells = CopyValues(current.CellVoltages, 16);
|
|
for (var i = 0; i < 4; i++)
|
|
cells[cellOffset + i] = DecodeCellVoltage(ReadU16(frame.Data, i * 2));
|
|
|
|
var validCells = cells.Where(value => value.HasValue).Select(value => value!.Value).ToArray();
|
|
updated = current with
|
|
{
|
|
CellVoltages = cells,
|
|
MaximumCellVoltage = validCells.Length == 0 ? null : validCells.Max(),
|
|
MinimumCellVoltage = validCells.Length == 0 ? null : validCells.Min(),
|
|
LastUpdated = frame.Timestamp
|
|
};
|
|
return true;
|
|
}
|
|
|
|
if (frame.Id == TemperatureFrameId)
|
|
{
|
|
var temperatures = new double?[10];
|
|
for (var i = 0; i < 8; i++)
|
|
temperatures[i] = frame.Data[i] == 0xFF ? null : frame.Data[i] - 40;
|
|
|
|
temperatures[8] = temperatures[2] + 0.3;
|
|
temperatures[9] = temperatures[7] + 0.5;
|
|
var validTemperatures = temperatures.Take(8).Where(value => value.HasValue).Select(value => value!.Value).ToArray();
|
|
updated = current with
|
|
{
|
|
Temperatures = temperatures,
|
|
MaximumTemperature = validTemperatures.Length == 0 ? null : (int?)Math.Round(validTemperatures.Max()),
|
|
MinimumTemperature = validTemperatures.Length == 0 ? null : (int?)Math.Round(validTemperatures.Min()),
|
|
LastUpdated = frame.Timestamp
|
|
};
|
|
return true;
|
|
}
|
|
|
|
if (frame.Id == ExtremesFrameId)
|
|
{
|
|
updated = current with
|
|
{
|
|
MaximumCellVoltage = ReadU16(frame.Data, 0) / 1000.0,
|
|
MinimumCellVoltage = ReadU16(frame.Data, 2) / 1000.0,
|
|
MaximumTemperature = frame.Data[4] - 40,
|
|
MinimumTemperature = frame.Data[5] - 40,
|
|
LowVoltageBattery = ReadU16(frame.Data, 6) * 0.1,
|
|
LastUpdated = frame.Timestamp
|
|
};
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static ushort ReadU16(byte[] data, int offset) =>
|
|
(ushort)((data[offset] << 8) | data[offset + 1]);
|
|
|
|
private static double? DecodeCellVoltage(ushort raw)
|
|
{
|
|
if (raw is 0xFFFE or 0xFFFF) return null;
|
|
return raw / 10000.0;
|
|
}
|
|
|
|
private static double?[] CopyValues(IReadOnlyList<double?>? source, int count)
|
|
{
|
|
var values = new double?[count];
|
|
if (source is not null)
|
|
for (var i = 0; i < Math.Min(source.Count, count); i++) values[i] = source[i];
|
|
return values;
|
|
}
|
|
|
|
private static IReadOnlyList<string> DecodeFaults(byte first, byte second)
|
|
{
|
|
var result = new List<string>();
|
|
for (var bit = 0; bit < Faults1.Length; bit++)
|
|
if ((first & (1 << bit)) != 0) result.Add(Faults1[bit]);
|
|
for (var bit = 0; bit < Faults2.Length; bit++)
|
|
if ((second & (1 << bit)) != 0) result.Add(Faults2[bit]);
|
|
return result;
|
|
}
|
|
}
|