diff --git a/csharp/dotnet/EXI/EXIEncoderExact.cs b/csharp/dotnet/EXI/EXIEncoderExact.cs index b5b077c..a535135 100644 --- a/csharp/dotnet/EXI/EXIEncoderExact.cs +++ b/csharp/dotnet/EXI/EXIEncoderExact.cs @@ -92,9 +92,15 @@ namespace V2GDecoderNet.EXI stream.WriteNBitUnsignedInteger(6, 13); EncodeCurrentDemandReqType(stream, body.CurrentDemandReq); } + else if (body.CurrentDemandRes_isUsed) + { + // Choice 14 for CurrentDemandRes - exactly like C version + stream.WriteNBitUnsignedInteger(6, 14); + EncodeCurrentDemandResType(stream, body.CurrentDemandRes); + } else { - throw new Exception("Unsupported message type for encoding"); + throw new Exception("Unsupported message type for encoding. Currently supported: CurrentDemandReq, CurrentDemandRes"); } // End Body element - grammar state 3 @@ -399,23 +405,241 @@ namespace V2GDecoderNet.EXI return bytes; } - public static byte[] EncodeCurrentDemandRes(CurrentDemandResType response) + /// + /// Encode CurrentDemandRes - exact port of C encode_iso1CurrentDemandResType + /// Grammar states 317-329 from C implementation + /// + private static void EncodeCurrentDemandResType(BitOutputStreamExact stream, CurrentDemandResType res) { - try + // Grammar state 317: ResponseCode (mandatory) + stream.WriteNBitUnsignedInteger(1, 0); // START_ELEMENT(ResponseCode) + stream.WriteNBitUnsignedInteger(1, 0); // CHARACTERS[ENUMERATION] + stream.WriteNBitUnsignedInteger(5, (int)res.ResponseCode); // 5-bit ResponseCode + stream.WriteNBitUnsignedInteger(1, 0); // END_ELEMENT + + // Grammar state 318: DC_EVSEStatus (mandatory) + stream.WriteNBitUnsignedInteger(1, 0); // START_ELEMENT(DC_EVSEStatus) + EncodeDC_EVSEStatusType(stream, res.DC_EVSEStatus); + + // Grammar state 319: EVSEPresentVoltage (mandatory) + stream.WriteNBitUnsignedInteger(1, 0); // START_ELEMENT(EVSEPresentVoltage) + EncodePhysicalValueType(stream, res.EVSEPresentVoltage); + + // Grammar state 320: EVSEPresentCurrent (mandatory) + stream.WriteNBitUnsignedInteger(1, 0); // START_ELEMENT(EVSEPresentCurrent) + EncodePhysicalValueType(stream, res.EVSEPresentCurrent); + + // Grammar state 321: EVSECurrentLimitAchieved (mandatory) + stream.WriteNBitUnsignedInteger(1, 0); // START_ELEMENT(EVSECurrentLimitAchieved) + stream.WriteNBitUnsignedInteger(1, 0); // CHARACTERS[BOOLEAN] + stream.WriteNBitUnsignedInteger(1, res.EVSECurrentLimitAchieved ? 1 : 0); + stream.WriteNBitUnsignedInteger(1, 0); // END_ELEMENT + + // Grammar state 322: EVSEVoltageLimitAchieved (mandatory) + stream.WriteNBitUnsignedInteger(1, 0); // START_ELEMENT(EVSEVoltageLimitAchieved) + stream.WriteNBitUnsignedInteger(1, 0); // CHARACTERS[BOOLEAN] + stream.WriteNBitUnsignedInteger(1, res.EVSEVoltageLimitAchieved ? 1 : 0); + stream.WriteNBitUnsignedInteger(1, 0); // END_ELEMENT + + // Grammar state 323: EVSEPowerLimitAchieved (mandatory) + stream.WriteNBitUnsignedInteger(1, 0); // START_ELEMENT(EVSEPowerLimitAchieved) + stream.WriteNBitUnsignedInteger(1, 0); // CHARACTERS[BOOLEAN] + stream.WriteNBitUnsignedInteger(1, res.EVSEPowerLimitAchieved ? 1 : 0); + stream.WriteNBitUnsignedInteger(1, 0); // END_ELEMENT + + // Grammar state 324+: Handle optional elements and mandatory EVSEID + EncodeCurrentDemandResOptionalElements(stream, res); + + // End CurrentDemandRes + stream.WriteNBitUnsignedInteger(1, 0); // END_ELEMENT + } + + /// + /// Encode optional elements and mandatory EVSEID for CurrentDemandRes + /// Based on C grammar states 324-329 + /// + private static void EncodeCurrentDemandResOptionalElements(BitOutputStreamExact stream, CurrentDemandResType res) + { + // Handle optional limits first, then mandatory EVSEID + bool hasOptionalLimits = res.EVSEMaximumVoltageLimit_isUsed || + res.EVSEMaximumCurrentLimit_isUsed || + res.EVSEMaximumPowerLimit_isUsed; + + if (hasOptionalLimits) { - 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(); + // Encode optional limits + if (res.EVSEMaximumVoltageLimit_isUsed) + { + stream.WriteNBitUnsignedInteger(3, 0); // Choice 0: EVSEMaximumVoltageLimit + EncodePhysicalValueType(stream, res.EVSEMaximumVoltageLimit); + } + if (res.EVSEMaximumCurrentLimit_isUsed) + { + stream.WriteNBitUnsignedInteger(3, 1); // Choice 1: EVSEMaximumCurrentLimit + EncodePhysicalValueType(stream, res.EVSEMaximumCurrentLimit); + } + if (res.EVSEMaximumPowerLimit_isUsed) + { + stream.WriteNBitUnsignedInteger(3, 2); // Choice 2: EVSEMaximumPowerLimit + EncodePhysicalValueType(stream, res.EVSEMaximumPowerLimit); + } } - catch (Exception ex) + + // EVSEID is always present (choice 3) + stream.WriteNBitUnsignedInteger(3, 3); // Choice 3: EVSEID + EncodeString(stream, res.EVSEID); + + // SAScheduleTupleID (8-bit, value-1) + stream.WriteNBitUnsignedInteger(8, (int)(res.SAScheduleTupleID - 1)); + + // Handle final optional elements (MeterInfo, ReceiptRequired) + if (res.MeterInfo_isUsed) { - throw new Exception($"Failed to encode CurrentDemandRes: {ex.Message}", ex); + stream.WriteNBitUnsignedInteger(2, 0); // Choice 0: MeterInfo + EncodeMeterInfo(stream, res.MeterInfo); + } + if (res.ReceiptRequired_isUsed) + { + stream.WriteNBitUnsignedInteger(2, 1); // Choice 1: ReceiptRequired + stream.WriteNBitUnsignedInteger(1, 0); // CHARACTERS[BOOLEAN] + stream.WriteNBitUnsignedInteger(1, res.ReceiptRequired ? 1 : 0); + stream.WriteNBitUnsignedInteger(1, 0); // END_ELEMENT + } + else + { + stream.WriteNBitUnsignedInteger(2, 2); // Choice 2: END_ELEMENT (skip optional elements) + } + } + + /// + /// Encode DC_EVSEStatus - exact implementation matching C version + /// + private static void EncodeDC_EVSEStatusType(BitOutputStreamExact stream, DC_EVSEStatusType status) + { + // NotificationMaxDelay (16-bit unsigned) + stream.WriteNBitUnsignedInteger(1, 0); // START_ELEMENT(NotificationMaxDelay) + stream.WriteNBitUnsignedInteger(1, 0); // CHARACTERS[UNSIGNED_INTEGER] + stream.WriteNBitUnsignedInteger(16, status.NotificationMaxDelay); + stream.WriteNBitUnsignedInteger(1, 0); // END_ELEMENT + + // EVSENotification (2-bit enumeration) + stream.WriteNBitUnsignedInteger(1, 0); // START_ELEMENT(EVSENotification) + stream.WriteNBitUnsignedInteger(1, 0); // CHARACTERS[ENUMERATION] + stream.WriteNBitUnsignedInteger(2, (int)status.EVSENotification); + stream.WriteNBitUnsignedInteger(1, 0); // END_ELEMENT + + // Optional EVSEIsolationStatus + if (status.EVSEIsolationStatus_isUsed) + { + stream.WriteNBitUnsignedInteger(2, 0); // Choice 0: EVSEIsolationStatus + stream.WriteNBitUnsignedInteger(1, 0); // CHARACTERS[ENUMERATION] + stream.WriteNBitUnsignedInteger(3, (int)status.EVSEIsolationStatus); + stream.WriteNBitUnsignedInteger(1, 0); // END_ELEMENT + + // EVSEStatusCode after optional element + stream.WriteNBitUnsignedInteger(1, 0); // START_ELEMENT(EVSEStatusCode) + stream.WriteNBitUnsignedInteger(1, 0); // CHARACTERS[ENUMERATION] + stream.WriteNBitUnsignedInteger(4, (int)status.EVSEStatusCode); + stream.WriteNBitUnsignedInteger(1, 0); // END_ELEMENT + } + else + { + stream.WriteNBitUnsignedInteger(2, 1); // Choice 1: Skip to EVSEStatusCode + stream.WriteNBitUnsignedInteger(1, 0); // CHARACTERS[ENUMERATION] + stream.WriteNBitUnsignedInteger(4, (int)status.EVSEStatusCode); + stream.WriteNBitUnsignedInteger(1, 0); // END_ELEMENT + } + + // End DC_EVSEStatus + stream.WriteNBitUnsignedInteger(1, 0); // END_ELEMENT + } + + /// + /// Encode string with length encoding - exact match to C encode_iso1String + /// + private static void EncodeString(BitOutputStreamExact stream, string str) + { + if (string.IsNullOrEmpty(str)) + { + // Empty string - just encode length 0 + stream.WriteNBitUnsignedInteger(1, 0); // CHARACTERS choice + stream.WriteUnsignedInteger(0); // Length 0 + return; + } + + // Convert string to UTF-8 bytes + byte[] stringBytes = Encoding.UTF8.GetBytes(str); + + // Encode as string characters + stream.WriteNBitUnsignedInteger(1, 0); // CHARACTERS choice + stream.WriteUnsignedInteger((uint)stringBytes.Length); // String length + + // Write string bytes + WriteBytes(stream, stringBytes); + } + + /// + /// Encode MeterInfo - exact match to C encode_iso1MeterInfoType + /// + private static void EncodeMeterInfo(BitOutputStreamExact stream, MeterInfoType meterInfo) + { + // MeterID (mandatory) + stream.WriteNBitUnsignedInteger(1, 0); // START_ELEMENT(MeterID) + EncodeString(stream, meterInfo.MeterID); + stream.WriteNBitUnsignedInteger(1, 0); // END_ELEMENT + + // MeterReading (optional) + if (meterInfo.MeterReading != 0) + { + stream.WriteNBitUnsignedInteger(4, 0); // Choice 0: MeterReading + stream.WriteNBitUnsignedInteger(1, 0); // CHARACTERS[UNSIGNED_INTEGER] + stream.WriteUnsignedInteger((uint)meterInfo.MeterReading); + stream.WriteNBitUnsignedInteger(1, 0); // END_ELEMENT + } + + // SigMeterReading (optional) + if (meterInfo.SigMeterReading != 0) + { + stream.WriteNBitUnsignedInteger(4, 1); // Choice 1: SigMeterReading + EncodeInteger8(stream, meterInfo.SigMeterReading); + stream.WriteNBitUnsignedInteger(1, 0); // END_ELEMENT + } + + // MeterStatus (optional) + if (!string.IsNullOrEmpty(meterInfo.MeterStatus)) + { + stream.WriteNBitUnsignedInteger(4, 2); // Choice 2: MeterStatus + EncodeString(stream, meterInfo.MeterStatus); + stream.WriteNBitUnsignedInteger(1, 0); // END_ELEMENT + } + + // TMeter (optional) + if (meterInfo.TMeter != 0) + { + stream.WriteNBitUnsignedInteger(4, 3); // Choice 3: TMeter + stream.WriteNBitUnsignedInteger(1, 0); // CHARACTERS[INTEGER] + EncodeInteger64(stream, meterInfo.TMeter); + stream.WriteNBitUnsignedInteger(1, 0); // END_ELEMENT + } + + // End MeterInfo + stream.WriteNBitUnsignedInteger(4, 4); // Choice 4: END_ELEMENT + } + + /// + /// Encode 64-bit signed integer + /// + private static void EncodeInteger64(BitOutputStreamExact stream, long 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 } } } diff --git a/csharp/dotnet/V2G/V2GMessageProcessor.cs b/csharp/dotnet/V2G/V2GMessageProcessor.cs index d45e5af..407e713 100644 --- a/csharp/dotnet/V2G/V2GMessageProcessor.cs +++ b/csharp/dotnet/V2G/V2GMessageProcessor.cs @@ -446,7 +446,16 @@ namespace V2GDecoderNet } else { - throw new Exception("Unsupported message type for encoding - only CurrentDemandReq supported"); + var currentDemandRes = bodyElement.Element(ns3 + "CurrentDemandRes"); + if (currentDemandRes != null) + { + v2gMessage.Body.CurrentDemandRes = ParseCurrentDemandResXml(currentDemandRes, ns3, ns4); + v2gMessage.Body.CurrentDemandRes_isUsed = true; + } + else + { + throw new Exception("Unsupported message type for encoding - supported: CurrentDemandReq, CurrentDemandRes"); + } } // Encode to EXI @@ -546,6 +555,130 @@ namespace V2GDecoderNet return req; } + + private static CurrentDemandResType ParseCurrentDemandResXml(XElement resElement, XNamespace ns3, XNamespace ns4) + { + var res = new CurrentDemandResType(); + + // Parse ResponseCode + var responseCode = resElement.Element(ns3 + "ResponseCode"); + if (responseCode != null) + { + res.ResponseCode = (ResponseCodeType)Enum.Parse(typeof(ResponseCodeType), responseCode.Value); + } + + // Parse DC_EVSEStatus + var dcEvseStatus = resElement.Element(ns3 + "DC_EVSEStatus"); + if (dcEvseStatus != null) + { + res.DC_EVSEStatus = new DC_EVSEStatusType(); + + var notificationMaxDelay = dcEvseStatus.Element(ns4 + "NotificationMaxDelay"); + if (notificationMaxDelay != null) + res.DC_EVSEStatus.NotificationMaxDelay = ushort.Parse(notificationMaxDelay.Value); + + var evseNotification = dcEvseStatus.Element(ns4 + "EVSENotification"); + if (evseNotification != null) + res.DC_EVSEStatus.EVSENotification = (EVSENotificationType)int.Parse(evseNotification.Value); + + var evseIsolationStatus = dcEvseStatus.Element(ns4 + "EVSEIsolationStatus"); + if (evseIsolationStatus != null) + { + res.DC_EVSEStatus.EVSEIsolationStatus = (IsolationLevelType)int.Parse(evseIsolationStatus.Value); + res.DC_EVSEStatus.EVSEIsolationStatus_isUsed = true; + } + + var evseStatusCode = dcEvseStatus.Element(ns4 + "EVSEStatusCode"); + if (evseStatusCode != null) + res.DC_EVSEStatus.EVSEStatusCode = (DC_EVSEStatusCodeType)int.Parse(evseStatusCode.Value); + } + + // Parse EVSEPresentVoltage + var evsePresentVoltage = resElement.Element(ns3 + "EVSEPresentVoltage"); + if (evsePresentVoltage != null) + { + res.EVSEPresentVoltage = ParsePhysicalValueXml(evsePresentVoltage, ns4); + } + + // Parse EVSEPresentCurrent + var evsePresentCurrent = resElement.Element(ns3 + "EVSEPresentCurrent"); + if (evsePresentCurrent != null) + { + res.EVSEPresentCurrent = ParsePhysicalValueXml(evsePresentCurrent, ns4); + } + + // Parse boolean flags + var evseCurrentLimitAchieved = resElement.Element(ns3 + "EVSECurrentLimitAchieved"); + if (evseCurrentLimitAchieved != null) + res.EVSECurrentLimitAchieved = bool.Parse(evseCurrentLimitAchieved.Value); + + var evseVoltageLimitAchieved = resElement.Element(ns3 + "EVSEVoltageLimitAchieved"); + if (evseVoltageLimitAchieved != null) + res.EVSEVoltageLimitAchieved = bool.Parse(evseVoltageLimitAchieved.Value); + + var evsePowerLimitAchieved = resElement.Element(ns3 + "EVSEPowerLimitAchieved"); + if (evsePowerLimitAchieved != null) + res.EVSEPowerLimitAchieved = bool.Parse(evsePowerLimitAchieved.Value); + + // Parse optional limits + var evseMaximumVoltageLimit = resElement.Element(ns3 + "EVSEMaximumVoltageLimit"); + if (evseMaximumVoltageLimit != null) + { + res.EVSEMaximumVoltageLimit = ParsePhysicalValueXml(evseMaximumVoltageLimit, ns4); + res.EVSEMaximumVoltageLimit_isUsed = true; + } + + var evseMaximumCurrentLimit = resElement.Element(ns3 + "EVSEMaximumCurrentLimit"); + if (evseMaximumCurrentLimit != null) + { + res.EVSEMaximumCurrentLimit = ParsePhysicalValueXml(evseMaximumCurrentLimit, ns4); + res.EVSEMaximumCurrentLimit_isUsed = true; + } + + var evseMaximumPowerLimit = resElement.Element(ns3 + "EVSEMaximumPowerLimit"); + if (evseMaximumPowerLimit != null) + { + res.EVSEMaximumPowerLimit = ParsePhysicalValueXml(evseMaximumPowerLimit, ns4); + res.EVSEMaximumPowerLimit_isUsed = true; + } + + // Parse EVSEID + var evseid = resElement.Element(ns3 + "EVSEID"); + if (evseid != null) + res.EVSEID = evseid.Value; + + // Parse SAScheduleTupleID + var saScheduleTupleId = resElement.Element(ns3 + "SAScheduleTupleID"); + if (saScheduleTupleId != null) + res.SAScheduleTupleID = byte.Parse(saScheduleTupleId.Value); + + // Parse MeterInfo (optional) + var meterInfo = resElement.Element(ns3 + "MeterInfo"); + if (meterInfo != null) + { + res.MeterInfo = new MeterInfoType(); + + var meterID = meterInfo.Element(ns4 + "MeterID"); + if (meterID != null) + res.MeterInfo.MeterID = meterID.Value; + + var meterReading = meterInfo.Element(ns4 + "MeterReading"); + if (meterReading != null) + res.MeterInfo.MeterReading = ulong.Parse(meterReading.Value); + + res.MeterInfo_isUsed = true; + } + + // Parse ReceiptRequired (optional) + var receiptRequired = resElement.Element(ns3 + "ReceiptRequired"); + if (receiptRequired != null) + { + res.ReceiptRequired = bool.Parse(receiptRequired.Value); + res.ReceiptRequired_isUsed = true; + } + + return res; + } private static PhysicalValueType ParsePhysicalValueXml(XElement element, XNamespace ns4) {