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:
ChiKyun Kim
2025-09-10 16:11:31 +09:00
parent 3ef14d7ee3
commit a6af2aceed
3 changed files with 61 additions and 287 deletions

View File

@@ -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

View File

@@ -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)