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 CellFrameOffsets = new Dictionary { [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? 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 DecodeFaults(byte first, byte second) { var result = new List(); 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; } }