205 lines
8.6 KiB
C#
205 lines
8.6 KiB
C#
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);
|
|
ParseAndPrintHexField(response, "Device Code (CID1)", 4);
|
|
ParseAndPrintHexField(response, "Function Code", 4);
|
|
ParseAndPrintHexField(response, "Length Code", 8);
|
|
ParseAndPrintHexField(response, "Data Flag", 4);
|
|
ParseAndPrintHexField(response, "Command Group", 4);
|
|
ParseAndPrintHexField(response, "Number of Cells", 4);
|
|
|
|
var cellVoltages = ExtractCellVoltage(response);
|
|
|
|
// Parse other fields
|
|
ParseAndPrintHexField(response, "Number of Temperature Sensors", 4);
|
|
var cellTemperature = new List<Double>();
|
|
|
|
// 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<Double> ExtractCellVoltage(String response)
|
|
{
|
|
var cellVoltages = new List<Double>();
|
|
// 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<Double, Double> 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);
|
|
}
|
|
} |