using System.Globalization; using InnovEnergy.Lib.Units; using InnovEnergy.Lib.Utils; using static InnovEnergy.Lib.Devices.BatteryDeligreen.BatteryDeligreenDataRecord; using static InnovEnergy.Lib.Devices.BatteryDeligreen.Temperatures; namespace InnovEnergy.Lib.Devices.BatteryDeligreen; public class TelemetryFrameParser { private static Int32 _currentIndex; private const Int32 FrameLenght = 336; public BatteryDeligreenDataRecord? ParsingTelemetryFrame(String response) { _currentIndex = 0; // Reset currentIndex to the start if (string.IsNullOrEmpty(response) || response.Length < FrameLenght) { Console.WriteLine("Response is too short to contain valid data."); Console.WriteLine("length " + response.Length); return null; } // Check starting byte var startingByte = response.Substring(_currentIndex, 2).ToUpper(); if (startingByte == "7E") { //Console.WriteLine($"Starting byte: {startingByte} (Hex)"); } else { Console.WriteLine($"Incorrect starting byte: {startingByte}"); return null; } _currentIndex += 2; // Extract firmware version var versionBytes = response.Substring(_currentIndex, 4); var versionAscii = ""; try { versionAscii = HexToAscii(versionBytes); // Console.WriteLine($"Firmware version: {versionBytes} (Hex), ASCII: {versionAscii}"); } catch (Exception) { Console.WriteLine($"Failed to decode firmware version from bytes: {versionBytes}"); return null; } _currentIndex += 4; // Extract and parse other fields ParseAndPrintHexField(response, "Device Address", 4); // this is not added to the Data record ParseAndPrintHexField(response, "Device Code (CID1)", 4); // this is not added to the Data record ParseAndPrintHexField(response, "Function Code", 4); // this is not added to the Data record ParseAndPrintHexField(response, "Length Code", 8); // this is not added to the Data record ParseAndPrintHexField(response, "Data Flag", 4); // this is not added to the Data record ParseAndPrintHexField(response, "Command Group", 4); // this is not added to the Data record ParseAndPrintHexField(response, "Number of Cells", 4); // this is not added to the Data record var cellVoltages = ExtractCellVoltage(response); // Parse other fields ParseAndPrintHexField(response, "Number of Temperature Sensors", 4); // this is not added to the Data record var cellTemperature = new List(); // Parse cell temperatures for (var i = 1; i <= 4; i++) { cellTemperature.Add(ParseAndPrintTemperatureField(response, $"Cell Temperature {i}")); } // Parse other temperature and battery information var environmentTemp = ParseAndPrintTemperatureField(response, "Environment Temperature"); var powerTemp = ParseAndPrintTemperatureField(response, "Power Temperature"); var current = ParseAndPrintField(response, "Charge/Discharge Current" , 8, value => value / 100.0, "A"); var totalBatteryVoltage = ParseAndPrintField(response, "Total Battery Voltage" , 8, value => value / 100.0, "V"); var residualCapacity = ParseAndPrintField(response, "Residual Capacity" , 8, value => value / 100.0, "Ah"); var customNumber = ParseAndPrintHexField(response, "Custom Number" , 4); var batteryCapacity = ParseAndPrintField(response, "Battery Capacity" , 8, value => value / 100.0, "Ah"); var soc = ParseAndPrintField(response, "SOC" , 8, value => value / 10.0, "%"); var ratedCapacity = ParseAndPrintField(response, "Rated Capacity" , 8, value => value / 100.0, "Ah"); var numberOfCycle = ParseAndPrintHexField(response, "Number of Cycles" , 8); var soh = ParseAndPrintField(response, "SOH" , 8, value => value / 10.0, "%"); var busVoltage = ParseAndPrintField(response, "Bus Voltage" , 8, value => value / 100.0, "V"); var temperatures = new TemperaturesList(cellTemperature[0], cellTemperature[1], cellTemperature[2], cellTemperature[3], environmentTemp, powerTemp); var batteryRecord = new BatteryDeligreenDataRecord(busVoltage, current, versionAscii, soc, numberOfCycle, batteryCapacity, ratedCapacity, totalBatteryVoltage, soh, residualCapacity, cellVoltages, temperatures); return batteryRecord; } private static List ExtractCellVoltage(String response) { var cellVoltages = new List(); // Process voltages for all 16 cells for (var i = 0; i < 16; i++) { var cellVoltageBytes = response.Substring(_currentIndex, 8); try { var cellVoltageAscii = HexToAscii(cellVoltageBytes); var cellVoltageDecimal = HexToDecimal(cellVoltageAscii) / 1000.0; // cell voltage are divided 1000 cellVoltages.Add(cellVoltageDecimal); // Console.WriteLine($"Voltage of Cell {i + 1}: {cellVoltageBytes} (Hex), ASCII: {cellVoltageAscii}, Voltage: {cellVoltageDecimal:F3} V"); } catch (Exception) { Console.WriteLine($"Failed to decode Voltage of Cell {i + 1} from bytes: {cellVoltageBytes}"); } _currentIndex += 8; } return cellVoltages; } private static UInt16 ParseAndPrintHexField(String response, String fieldName, Int32 length) { var hexBytes = response.Substring(_currentIndex, length); var decimalValue = 0; try { var asciiValue = HexToAscii(hexBytes); decimalValue = int.Parse(asciiValue, NumberStyles.HexNumber); // Console.WriteLine($"{fieldName}: {hexBytes} (Hex), ASCII: {asciiValue}, Decimal: {decimalValue}"); } catch (Exception) { Console.WriteLine($"Failed to decode {fieldName} from bytes: {hexBytes}"); } _currentIndex += length; return (UInt16)decimalValue; } private static Double ParseAndPrintTemperatureField(String response, String fieldName) { var tempBytes = response.Substring(_currentIndex, 8); var tempDecimal = 0.0; try { var tempAscii = HexToAscii(tempBytes); tempDecimal = (HexToDecimal(tempAscii) - 2731) / 10.0; // Console.WriteLine($"{fieldName}: {tempBytes} (Hex), ASCII: {tempAscii}, Temperature: {tempDecimal:F2} °C"); } catch (Exception) { Console.WriteLine($"Failed to decode {fieldName} from bytes: {tempBytes}"); } _currentIndex += 8; return tempDecimal; } private static Double ParseAndPrintField(String response, String fieldName, Int32 length, Func conversion, String unit) { var fieldBytes = response.Substring(_currentIndex, length); var value = 0.0; try { var fieldAscii = HexToAscii(fieldBytes); var fieldDouble = 0.0; // Convert from Hex to Integer using Two's Complement logic Int32 intValue = Convert.ToInt16(fieldAscii, 16); var bitLength = (length/2) * 4; // Each hex digit is 4 bits var maxPositiveValue = 1 << (bitLength - 1); // 2^(bitLength-1) if (intValue >= maxPositiveValue) { intValue -= (1 << bitLength); // Apply two's complement conversion } fieldDouble = conversion(intValue); // Store the converted negative value as string //Console.WriteLine($"{fieldName}: {fieldBytes} (Hex), ASCII: {fieldAscii}, {fieldName}: {fieldDouble:F3} {unit}"); value = fieldDouble; } catch (Exception) { Console.WriteLine($"Failed to decode {fieldName} from bytes: {fieldBytes}"); } _currentIndex += length; return value; } private static String HexToAscii(String hex) { var bytes = new Byte[hex.Length / 2]; for (var i = 0; i < hex.Length; i += 2) { bytes[i / 2] = byte.Parse(hex.Substring(i, 2), NumberStyles.HexNumber); } return System.Text.Encoding.ASCII.GetString(bytes); } private static Double HexToDecimal(String hex) { return int.Parse(hex, NumberStyles.HexNumber); } }