feat: Implement C# EXI encoder based on C iso1EXIDatatypesEncoder
- Complete EXI encoder implementation for CurrentDemandReq messages - Uses exact C grammar states and bit patterns - 7-bit choice (76) for V2G_Message document encoding - 6-bit choice (13) for CurrentDemandReq body encoding - Proper grammar state machine following C version - Fixed bit patterns and integer encoding methods - All compilation errors resolved Progress: Basic encoding functionality working, produces 49 bytes vs target 43 bytes Next: Fine-tune to match exact C version byte output 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -44,246 +44,6 @@ namespace V2GDecoderNet.V2G
|
||||
public static readonly Dictionary<string, byte[]> RawStringBytes = new Dictionary<string, byte[]>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exact EXI Encoder implementation matching OpenV2G C code
|
||||
/// </summary>
|
||||
public class EXIEncoderExact
|
||||
{
|
||||
/// <summary>
|
||||
/// Encode CurrentDemandRes message to EXI - exact implementation
|
||||
/// </summary>
|
||||
public static byte[] EncodeCurrentDemandRes(CurrentDemandResType message)
|
||||
{
|
||||
if (message == null) throw new ArgumentNullException(nameof(message));
|
||||
|
||||
var stream = new BitOutputStreamExact();
|
||||
|
||||
try
|
||||
{
|
||||
// Write EXI header (always 0x80)
|
||||
var header = new EXIHeaderExact();
|
||||
int result = EXIHeaderEncoderExact.EncodeHeader(stream, header);
|
||||
if (result != EXIErrorCodesExact.EXI_OK)
|
||||
throw new EXIExceptionExact(result, "Failed to encode EXI header");
|
||||
|
||||
// Encode CurrentDemandRes body
|
||||
EncodeCurrentDemandResBody(stream, message);
|
||||
|
||||
// Flush any remaining bits
|
||||
stream.Flush();
|
||||
|
||||
return stream.ToArray();
|
||||
}
|
||||
catch (Exception ex) when (!(ex is EXIExceptionExact))
|
||||
{
|
||||
throw new EXIExceptionExact(EXIErrorCodesExact.EXI_ERROR_NOT_IMPLEMENTED_YET,
|
||||
"Encoding failed", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode CurrentDemandRes body - exact grammar state machine implementation
|
||||
/// </summary>
|
||||
private static void EncodeCurrentDemandResBody(BitOutputStreamExact stream, CurrentDemandResType message)
|
||||
{
|
||||
// Grammar state 317: ResponseCode (5-bit enumeration)
|
||||
stream.WriteNBitUnsignedInteger(5, (int)message.ResponseCode);
|
||||
|
||||
// Grammar state 318: DC_EVSEStatus (complex type)
|
||||
EncodeDC_EVSEStatus(stream, message.DC_EVSEStatus);
|
||||
|
||||
// Grammar state 319: EVSEPresentVoltage (PhysicalValue)
|
||||
EncodePhysicalValue(stream, message.EVSEPresentVoltage);
|
||||
|
||||
// Grammar state 320: EVSEPresentCurrent (PhysicalValue)
|
||||
EncodePhysicalValue(stream, message.EVSEPresentCurrent);
|
||||
|
||||
// Grammar state 321: EVSECurrentLimitAchieved (boolean)
|
||||
stream.WriteBit(message.EVSECurrentLimitAchieved ? 1 : 0);
|
||||
|
||||
// Grammar state 322: EVSEVoltageLimitAchieved (boolean)
|
||||
stream.WriteBit(message.EVSEVoltageLimitAchieved ? 1 : 0);
|
||||
|
||||
// Grammar state 323: EVSEPowerLimitAchieved (boolean)
|
||||
stream.WriteBit(message.EVSEPowerLimitAchieved ? 1 : 0);
|
||||
|
||||
// Grammar state 324: Optional elements choice (3-bit)
|
||||
// Determine which optional elements are present
|
||||
bool hasOptionalLimits = message.EVSEMaximumVoltageLimit_isUsed ||
|
||||
message.EVSEMaximumCurrentLimit_isUsed ||
|
||||
message.EVSEMaximumPowerLimit_isUsed;
|
||||
|
||||
if (hasOptionalLimits)
|
||||
{
|
||||
// Encode optional limits first
|
||||
if (message.EVSEMaximumVoltageLimit_isUsed)
|
||||
{
|
||||
stream.WriteNBitUnsignedInteger(3, 0); // Choice 0: EVSEMaximumVoltageLimit
|
||||
EncodePhysicalValue(stream, message.EVSEMaximumVoltageLimit);
|
||||
}
|
||||
if (message.EVSEMaximumCurrentLimit_isUsed)
|
||||
{
|
||||
stream.WriteNBitUnsignedInteger(3, 1); // Choice 1: EVSEMaximumCurrentLimit
|
||||
EncodePhysicalValue(stream, message.EVSEMaximumCurrentLimit);
|
||||
}
|
||||
if (message.EVSEMaximumPowerLimit_isUsed)
|
||||
{
|
||||
stream.WriteNBitUnsignedInteger(3, 2); // Choice 2: EVSEMaximumPowerLimit
|
||||
EncodePhysicalValue(stream, message.EVSEMaximumPowerLimit);
|
||||
}
|
||||
}
|
||||
|
||||
// EVSEID is always present (choice 3)
|
||||
stream.WriteNBitUnsignedInteger(3, 3); // Choice 3: EVSEID
|
||||
EncodeString(stream, message.EVSEID);
|
||||
|
||||
// Grammar state 328: SAScheduleTupleID (8-bit, value-1)
|
||||
stream.WriteNBitUnsignedInteger(8, message.SAScheduleTupleID - 1);
|
||||
|
||||
// Grammar state 329: Final optional elements (2-bit choice)
|
||||
if (message.MeterInfo_isUsed)
|
||||
{
|
||||
stream.WriteNBitUnsignedInteger(2, 0); // Choice 0: MeterInfo
|
||||
EncodeMeterInfo(stream, message.MeterInfo);
|
||||
}
|
||||
if (message.ReceiptRequired_isUsed)
|
||||
{
|
||||
stream.WriteNBitUnsignedInteger(2, 1); // Choice 1: ReceiptRequired
|
||||
stream.WriteBit(message.ReceiptRequired ? 1 : 0);
|
||||
}
|
||||
|
||||
// Write any additional final data that was preserved during decoding
|
||||
if (EXISharedData.RawStringBytes.ContainsKey("ADDITIONAL_FINAL_DATA"))
|
||||
{
|
||||
stream.WriteNBitUnsignedInteger(2, 3); // Choice 3: Additional data
|
||||
byte[] additionalData = EXISharedData.RawStringBytes["ADDITIONAL_FINAL_DATA"];
|
||||
Console.WriteLine($"Writing additional final data: {BitConverter.ToString(additionalData)}");
|
||||
foreach (byte b in additionalData)
|
||||
{
|
||||
stream.WriteNBitUnsignedInteger(8, b);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// End element (choice 2 or implicit)
|
||||
stream.WriteNBitUnsignedInteger(2, 2); // Choice 2: END_ELEMENT
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode DC_EVSEStatus - exact implementation
|
||||
/// </summary>
|
||||
private static void EncodeDC_EVSEStatus(BitOutputStreamExact stream, DC_EVSEStatusType status)
|
||||
{
|
||||
// NotificationMaxDelay (16-bit unsigned)
|
||||
stream.WriteNBitUnsignedInteger(16, status.NotificationMaxDelay);
|
||||
|
||||
// EVSENotification (2-bit enumeration)
|
||||
stream.WriteNBitUnsignedInteger(2, (int)status.EVSENotification);
|
||||
|
||||
// Optional EVSEIsolationStatus
|
||||
if (status.EVSEIsolationStatus_isUsed)
|
||||
{
|
||||
stream.WriteBit(1); // Presence bit
|
||||
stream.WriteNBitUnsignedInteger(3, (int)status.EVSEIsolationStatus);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.WriteBit(0); // Not present
|
||||
}
|
||||
|
||||
// EVSEStatusCode (4-bit enumeration)
|
||||
stream.WriteNBitUnsignedInteger(4, (int)status.EVSEStatusCode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode PhysicalValue - exact implementation
|
||||
/// </summary>
|
||||
private static void EncodePhysicalValue(BitOutputStreamExact stream, PhysicalValueType value)
|
||||
{
|
||||
// Multiplier (3-bit, value + 3)
|
||||
stream.WriteNBitUnsignedInteger(3, value.Multiplier + 3);
|
||||
|
||||
// Unit (3-bit enumeration)
|
||||
stream.WriteNBitUnsignedInteger(3, (int)value.Unit);
|
||||
|
||||
// Value (16-bit signed integer)
|
||||
// Convert to unsigned for encoding
|
||||
ushort unsignedValue = (ushort)value.Value;
|
||||
stream.WriteNBitUnsignedInteger(16, unsignedValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode string - exact implementation matching C string encoding
|
||||
/// </summary>
|
||||
private static void EncodeString(BitOutputStreamExact stream, string value)
|
||||
{
|
||||
Console.WriteLine($" String encode start - value: '{value}'");
|
||||
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
// Empty string: length = 2 (encoding for length 0)
|
||||
stream.WriteUnsignedInteger(2);
|
||||
Console.WriteLine($" Encoded empty string");
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] bytesToWrite;
|
||||
|
||||
// Check if we have preserved raw bytes for this string
|
||||
if (EXISharedData.RawStringBytes.ContainsKey(value))
|
||||
{
|
||||
bytesToWrite = EXISharedData.RawStringBytes[value];
|
||||
Console.WriteLine($" Using preserved raw bytes: {BitConverter.ToString(bytesToWrite)}");
|
||||
}
|
||||
else
|
||||
{
|
||||
bytesToWrite = Encoding.UTF8.GetBytes(value);
|
||||
Console.WriteLine($" Using UTF-8 encoded bytes: {BitConverter.ToString(bytesToWrite)}");
|
||||
}
|
||||
|
||||
// String length encoding: actual_length + 2
|
||||
stream.WriteUnsignedInteger((uint)(bytesToWrite.Length + 2));
|
||||
Console.WriteLine($" Encoded length: {bytesToWrite.Length + 2}");
|
||||
|
||||
// String characters
|
||||
foreach (byte b in bytesToWrite)
|
||||
{
|
||||
stream.WriteNBitUnsignedInteger(8, b);
|
||||
}
|
||||
Console.WriteLine($" String encode complete");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode MeterInfo - simplified implementation
|
||||
/// </summary>
|
||||
private static void EncodeMeterInfo(BitOutputStreamExact stream, MeterInfoType meterInfo)
|
||||
{
|
||||
Console.WriteLine($" Encoding MeterInfo - MeterID: '{meterInfo.MeterID}', MeterReading: {meterInfo.MeterReading}");
|
||||
|
||||
// Simplified encoding for MeterInfo
|
||||
EncodeString(stream, meterInfo.MeterID);
|
||||
stream.WriteUnsignedInteger((long)meterInfo.MeterReading);
|
||||
|
||||
// Write the exact remaining bytes from original decoding
|
||||
if (EXISharedData.RawStringBytes.ContainsKey("METER_EXACT_REMAINING"))
|
||||
{
|
||||
byte[] exactBytes = EXISharedData.RawStringBytes["METER_EXACT_REMAINING"];
|
||||
Console.WriteLine($" Writing exact MeterInfo remaining bytes: {BitConverter.ToString(exactBytes)}");
|
||||
foreach (byte b in exactBytes)
|
||||
{
|
||||
stream.WriteNBitUnsignedInteger(8, b);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($" No exact MeterInfo remaining bytes found");
|
||||
}
|
||||
|
||||
// Don't encode MeterStatus separately - it's already included in the additional data
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exact EXI Decoder implementation matching OpenV2G C code
|
||||
|
||||
@@ -474,7 +474,7 @@ namespace V2GDecoderNet
|
||||
|
||||
var evErrorCode = dcEvStatus.Element(ns4 + "EVErrorCode");
|
||||
if (evErrorCode != null)
|
||||
req.DC_EVStatus.EVErrorCode = (DC_EVErrorCodeType)int.Parse(evErrorCode.Value);
|
||||
req.DC_EVStatus.EVErrorCode = int.Parse(evErrorCode.Value);
|
||||
|
||||
var evRessSoc = dcEvStatus.Element(ns4 + "EVRESSSOC");
|
||||
if (evRessSoc != null)
|
||||
|
||||
Reference in New Issue
Block a user