Files
V2GDecoderC/csharp/dotnet/EXI/EXIEncoderExact.cs
ChiKyun Kim a6af2aceed 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>
2025-09-10 16:11:31 +09:00

422 lines
18 KiB
C#

using System;
using System.Text;
using V2GDecoderNet.V2G;
namespace V2GDecoderNet.EXI
{
/// <summary>
/// EXI Encoder with exact C compatibility
/// Matches iso1EXIDatatypesEncoder.c structure
/// </summary>
public static class EXIEncoderExact
{
public static byte[] EncodeV2GMessage(V2GMessageExact message)
{
try
{
var stream = new BitOutputStreamExact();
// Write EXI header - exactly like C writeEXIHeader()
stream.WriteNBitUnsignedInteger(8, 0x80);
stream.WriteNBitUnsignedInteger(8, 0x98);
// Encode document content - exactly like C encode_iso1ExiDocument
// V2G_Message choice = 76 (7-bit)
stream.WriteNBitUnsignedInteger(7, 76);
// Encode V2G_Message content - matches C encode_iso1AnonType_V2G_Message
EncodeV2GMessageContent(stream, message);
return stream.ToArray();
}
catch (Exception ex)
{
Console.Error.WriteLine($"EXI encoding error: {ex.Message}");
throw new Exception($"Failed to encode V2G message: {ex.Message}", ex);
}
}
/// <summary>
/// Encode V2G_Message content - exact port of C encode_iso1AnonType_V2G_Message
/// </summary>
private static void EncodeV2GMessageContent(BitOutputStreamExact stream, V2GMessageExact message)
{
// Grammar state for V2G_Message: Header is mandatory
stream.WriteNBitUnsignedInteger(1, 0); // START_ELEMENT(Header)
EncodeMessageHeader(stream, message.SessionID);
// Grammar state: Body is mandatory
stream.WriteNBitUnsignedInteger(1, 0); // START_ELEMENT(Body)
EncodeBodyType(stream, message.Body);
// END_ELEMENT for V2G_Message
stream.WriteNBitUnsignedInteger(1, 0);
}
/// <summary>
/// Encode MessageHeader - exact port of C encode_iso1MessageHeaderType
/// </summary>
private static void EncodeMessageHeader(BitOutputStreamExact stream, string sessionId)
{
// Grammar state for MessageHeaderType: SessionID is mandatory
stream.WriteNBitUnsignedInteger(1, 0); // START_ELEMENT(SessionID)
// SessionID encoding - binary hex format exactly like C
// Convert hex string to bytes first
byte[] sessionBytes = ConvertHexStringToBytes(sessionId);
// Encode as binary string (characters[BINARY_HEX])
stream.WriteNBitUnsignedInteger(1, 0); // CHARACTERS choice
stream.WriteUnsignedInteger((uint)sessionBytes.Length); // Length encoding
// Write actual bytes
WriteBytes(stream, sessionBytes);
// End SessionID element
stream.WriteNBitUnsignedInteger(1, 0); // END_ELEMENT
// Grammar allows optional Notification and Signature, but we don't use them
// End Header with 2-bit choice for end
stream.WriteNBitUnsignedInteger(2, 2); // END_ELEMENT choice (skipping optional elements)
}
/// <summary>
/// Encode Body content - exact port of C encode_iso1BodyType
/// </summary>
private static void EncodeBodyType(BitOutputStreamExact stream, BodyType body)
{
// Grammar state for Body: 6-bit choice for message type
if (body.CurrentDemandReq_isUsed)
{
// Choice 13 for CurrentDemandReq - exactly like C version
stream.WriteNBitUnsignedInteger(6, 13);
EncodeCurrentDemandReqType(stream, body.CurrentDemandReq);
}
else
{
throw new Exception("Unsupported message type for encoding");
}
// End Body element - grammar state 3
stream.WriteNBitUnsignedInteger(1, 0); // END_ELEMENT
}
private static void EncodeCurrentDemandReqType(BitOutputStreamExact stream, CurrentDemandReqType req)
{
// Grammar state 273: DC_EVStatus (mandatory)
stream.WriteNBitUnsignedInteger(1, 0); // START_ELEMENT(DC_EVStatus)
EncodeDC_EVStatusType(stream, req.DC_EVStatus);
// Grammar state 274: EVTargetCurrent (mandatory)
stream.WriteNBitUnsignedInteger(1, 0); // START_ELEMENT(EVTargetCurrent)
EncodePhysicalValueType(stream, req.EVTargetCurrent);
// Grammar state 275: Optional elements (3-bit choice)
EncodeOptionalElements275(stream, req);
}
private static void EncodeOptionalElements275(BitOutputStreamExact stream, CurrentDemandReqType req)
{
// Grammar state 275 - handle optional elements in sequence
if (req.EVMaximumVoltageLimit_isUsed)
{
stream.WriteNBitUnsignedInteger(3, 0); // EVMaximumVoltageLimit choice
EncodePhysicalValueType(stream, req.EVMaximumVoltageLimit);
EncodeOptionalElements276(stream, req); // Continue to state 276
}
else if (req.EVMaximumCurrentLimit_isUsed)
{
stream.WriteNBitUnsignedInteger(3, 1); // EVMaximumCurrentLimit choice
EncodePhysicalValueType(stream, req.EVMaximumCurrentLimit);
EncodeOptionalElements277(stream, req); // Continue to state 277
}
else if (req.EVMaximumPowerLimit_isUsed)
{
stream.WriteNBitUnsignedInteger(3, 2); // EVMaximumPowerLimit choice
EncodePhysicalValueType(stream, req.EVMaximumPowerLimit);
EncodeOptionalElements278(stream, req); // Continue to state 278
}
else if (req.BulkChargingComplete_isUsed)
{
stream.WriteNBitUnsignedInteger(3, 3); // BulkChargingComplete choice
stream.WriteNBitUnsignedInteger(1, 0); // boolean start
stream.WriteNBitUnsignedInteger(1, req.BulkChargingComplete ? 1 : 0); // boolean value
stream.WriteNBitUnsignedInteger(1, 0); // EE
EncodeOptionalElements279(stream, req); // Continue to state 279
}
else
{
// ChargingComplete (mandatory)
stream.WriteNBitUnsignedInteger(3, 4); // ChargingComplete choice
stream.WriteNBitUnsignedInteger(1, 0); // boolean start
stream.WriteNBitUnsignedInteger(1, req.ChargingComplete ? 1 : 0); // boolean value
stream.WriteNBitUnsignedInteger(1, 0); // EE
EncodeOptionalElements280(stream, req); // Continue to state 280
}
}
private static void EncodeOptionalElements276(BitOutputStreamExact stream, CurrentDemandReqType req)
{
// After EVMaximumVoltageLimit - states similar to 275 but different grammar
if (req.EVMaximumCurrentLimit_isUsed)
{
stream.WriteNBitUnsignedInteger(3, 0); // EVMaximumCurrentLimit
EncodePhysicalValueType(stream, req.EVMaximumCurrentLimit);
EncodeOptionalElements277(stream, req);
}
else if (req.EVMaximumPowerLimit_isUsed)
{
stream.WriteNBitUnsignedInteger(3, 1); // EVMaximumPowerLimit
EncodePhysicalValueType(stream, req.EVMaximumPowerLimit);
EncodeOptionalElements278(stream, req);
}
else if (req.BulkChargingComplete_isUsed)
{
stream.WriteNBitUnsignedInteger(3, 2); // BulkChargingComplete
stream.WriteNBitUnsignedInteger(1, 0); // boolean start
stream.WriteNBitUnsignedInteger(1, req.BulkChargingComplete ? 1 : 0);
stream.WriteNBitUnsignedInteger(1, 0); // EE
EncodeOptionalElements279(stream, req);
}
else
{
stream.WriteNBitUnsignedInteger(3, 3); // ChargingComplete
stream.WriteNBitUnsignedInteger(1, 0); // boolean start
stream.WriteNBitUnsignedInteger(1, req.ChargingComplete ? 1 : 0);
stream.WriteNBitUnsignedInteger(1, 0); // EE
EncodeOptionalElements280(stream, req);
}
}
private static void EncodeOptionalElements277(BitOutputStreamExact stream, CurrentDemandReqType req)
{
// After EVMaximumCurrentLimit
if (req.EVMaximumPowerLimit_isUsed)
{
stream.WriteNBitUnsignedInteger(2, 0); // EVMaximumPowerLimit
EncodePhysicalValueType(stream, req.EVMaximumPowerLimit);
EncodeOptionalElements278(stream, req);
}
else if (req.BulkChargingComplete_isUsed)
{
stream.WriteNBitUnsignedInteger(2, 1); // BulkChargingComplete
stream.WriteNBitUnsignedInteger(1, 0); // boolean start
stream.WriteNBitUnsignedInteger(1, req.BulkChargingComplete ? 1 : 0);
stream.WriteNBitUnsignedInteger(1, 0); // EE
EncodeOptionalElements279(stream, req);
}
else
{
stream.WriteNBitUnsignedInteger(2, 2); // ChargingComplete
stream.WriteNBitUnsignedInteger(1, 0); // boolean start
stream.WriteNBitUnsignedInteger(1, req.ChargingComplete ? 1 : 0);
stream.WriteNBitUnsignedInteger(1, 0); // EE
EncodeOptionalElements280(stream, req);
}
}
private static void EncodeOptionalElements278(BitOutputStreamExact stream, CurrentDemandReqType req)
{
// After EVMaximumPowerLimit
if (req.BulkChargingComplete_isUsed)
{
stream.WriteNBitUnsignedInteger(1, 0); // BulkChargingComplete
stream.WriteNBitUnsignedInteger(1, 0); // boolean start
stream.WriteNBitUnsignedInteger(1, req.BulkChargingComplete ? 1 : 0);
stream.WriteNBitUnsignedInteger(1, 0); // EE
EncodeOptionalElements279(stream, req);
}
else
{
stream.WriteNBitUnsignedInteger(1, 1); // ChargingComplete
stream.WriteNBitUnsignedInteger(1, 0); // boolean start
stream.WriteNBitUnsignedInteger(1, req.ChargingComplete ? 1 : 0);
stream.WriteNBitUnsignedInteger(1, 0); // EE
EncodeOptionalElements280(stream, req);
}
}
private static void EncodeOptionalElements279(BitOutputStreamExact stream, CurrentDemandReqType req)
{
// After BulkChargingComplete - must have ChargingComplete
stream.WriteNBitUnsignedInteger(1, 0); // ChargingComplete
stream.WriteNBitUnsignedInteger(1, 0); // boolean start
stream.WriteNBitUnsignedInteger(1, req.ChargingComplete ? 1 : 0);
stream.WriteNBitUnsignedInteger(1, 0); // EE
EncodeOptionalElements280(stream, req);
}
private static void EncodeOptionalElements280(BitOutputStreamExact stream, CurrentDemandReqType req)
{
// Grammar state 280: 2-bit choice for remaining optional elements
if (req.RemainingTimeToFullSoC_isUsed)
{
stream.WriteNBitUnsignedInteger(2, 0); // RemainingTimeToFullSoC
EncodePhysicalValueType(stream, req.RemainingTimeToFullSoC);
EncodeOptionalElements281(stream, req);
}
else if (req.RemainingTimeToBulkSoC_isUsed)
{
stream.WriteNBitUnsignedInteger(2, 1); // RemainingTimeToBulkSoC
EncodePhysicalValueType(stream, req.RemainingTimeToBulkSoC);
EncodeOptionalElements282(stream, req);
}
else
{
stream.WriteNBitUnsignedInteger(2, 2); // EVTargetVoltage (mandatory)
EncodePhysicalValueType(stream, req.EVTargetVoltage);
// End CurrentDemandReq
stream.WriteNBitUnsignedInteger(1, 0); // EE
}
}
private static void EncodeOptionalElements281(BitOutputStreamExact stream, CurrentDemandReqType req)
{
// Grammar state 281: 2-bit choice after RemainingTimeToFullSoC
if (req.RemainingTimeToBulkSoC_isUsed)
{
stream.WriteNBitUnsignedInteger(2, 0); // RemainingTimeToBulkSoC
EncodePhysicalValueType(stream, req.RemainingTimeToBulkSoC);
EncodeOptionalElements282(stream, req);
}
else
{
stream.WriteNBitUnsignedInteger(2, 1); // EVTargetVoltage (mandatory)
EncodePhysicalValueType(stream, req.EVTargetVoltage);
// End CurrentDemandReq
stream.WriteNBitUnsignedInteger(1, 0); // EE
}
}
private static void EncodeOptionalElements282(BitOutputStreamExact stream, CurrentDemandReqType req)
{
// Grammar state 282: Must encode EVTargetVoltage
stream.WriteNBitUnsignedInteger(1, 0); // EVTargetVoltage
EncodePhysicalValueType(stream, req.EVTargetVoltage);
// End CurrentDemandReq
stream.WriteNBitUnsignedInteger(1, 0); // EE
}
private static void EncodeDC_EVStatusType(BitOutputStreamExact stream, DC_EVStatusType status)
{
// Grammar state for DC_EVStatusType
// EVReady (mandatory)
stream.WriteNBitUnsignedInteger(1, 0); // START_ELEMENT(EVReady)
stream.WriteNBitUnsignedInteger(1, 0); // boolean start
stream.WriteNBitUnsignedInteger(1, status.EVReady ? 1 : 0); // boolean value
stream.WriteNBitUnsignedInteger(1, 0); // EE
// EVErrorCode (mandatory)
stream.WriteNBitUnsignedInteger(1, 0); // START_ELEMENT(EVErrorCode)
stream.WriteNBitUnsignedInteger(1, 0); // enum start
stream.WriteNBitUnsignedInteger(4, (int)status.EVErrorCode); // 4-bit enum value
stream.WriteNBitUnsignedInteger(1, 0); // EE
// EVRESSSOC (mandatory)
stream.WriteNBitUnsignedInteger(1, 0); // START_ELEMENT(EVRESSSOC)
stream.WriteNBitUnsignedInteger(1, 0); // integer start
stream.WriteNBitUnsignedInteger(7, status.EVRESSSOC); // 7-bit value (0-100)
stream.WriteNBitUnsignedInteger(1, 0); // EE
// End DC_EVStatus
stream.WriteNBitUnsignedInteger(1, 0); // EE
}
private static void EncodePhysicalValueType(BitOutputStreamExact stream, PhysicalValueType value)
{
// Grammar state for PhysicalValueType
// Multiplier (mandatory)
stream.WriteNBitUnsignedInteger(1, 0); // START_ELEMENT(Multiplier)
stream.WriteNBitUnsignedInteger(1, 0); // integer start
EncodeInteger8(stream, value.Multiplier);
stream.WriteNBitUnsignedInteger(1, 0); // EE
// Unit (mandatory)
stream.WriteNBitUnsignedInteger(1, 0); // START_ELEMENT(Unit)
stream.WriteNBitUnsignedInteger(1, 0); // enum start
stream.WriteNBitUnsignedInteger(3, (int)value.Unit); // 3-bit enum
stream.WriteNBitUnsignedInteger(1, 0); // EE
// Value (mandatory)
stream.WriteNBitUnsignedInteger(1, 0); // START_ELEMENT(Value)
stream.WriteNBitUnsignedInteger(1, 0); // integer start
EncodeInteger16(stream, value.Value);
stream.WriteNBitUnsignedInteger(1, 0); // EE
// End PhysicalValue
stream.WriteNBitUnsignedInteger(1, 0); // EE
}
private static void EncodeInteger8(BitOutputStreamExact stream, sbyte value)
{
if (value >= 0)
{
stream.WriteNBitUnsignedInteger(1, 0); // positive sign bit
stream.WriteUnsignedInteger((uint)value);
}
else
{
stream.WriteNBitUnsignedInteger(1, 1); // negative sign bit
stream.WriteUnsignedInteger((uint)(-(value + 1))); // magnitude
}
}
private static void EncodeInteger16(BitOutputStreamExact stream, short value)
{
if (value >= 0)
{
stream.WriteNBitUnsignedInteger(1, 0); // positive sign bit
stream.WriteUnsignedInteger((uint)value);
}
else
{
stream.WriteNBitUnsignedInteger(1, 1); // negative sign bit
stream.WriteUnsignedInteger((uint)(-(value + 1))); // magnitude
}
}
private static void WriteBytes(BitOutputStreamExact stream, byte[] bytes)
{
foreach (byte b in bytes)
{
stream.WriteNBitUnsignedInteger(8, b);
}
}
private static byte[] ConvertHexStringToBytes(string hex)
{
if (hex.Length % 2 != 0)
throw new ArgumentException("Hex string must have even length");
byte[] bytes = new byte[hex.Length / 2];
for (int i = 0; i < hex.Length; i += 2)
{
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
}
return bytes;
}
public static byte[] EncodeCurrentDemandRes(CurrentDemandResType response)
{
try
{
var stream = new BitOutputStreamExact();
// Write EXI header
stream.WriteNBitUnsignedInteger(16, 0x8098);
// Simple CurrentDemandRes encoding for testing
// This is a placeholder - real implementation would need full grammar
return stream.ToArray();
}
catch (Exception ex)
{
throw new Exception($"Failed to encode CurrentDemandRes: {ex.Message}", ex);
}
}
}
}