Files

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