From cedb323aef9068efdfb938a0b2145dc65eed87ce Mon Sep 17 00:00:00 2001 From: Sebastian Lukas Date: Mon, 15 Dec 2025 16:25:49 +0100 Subject: [PATCH 1/5] Adding first iso2 skeleton for further development Signed-off-by: Sebastian Lukas --- .../include/iso15118/detail/cb_exi.hpp | 19 ++++ .../iso15118/detail/variant_access.hpp | 34 +++++++ .../iso15118/message/d2/msg_data_types.hpp | 71 ++++++++++++++ .../iso15118/message/d2/session_setup.hpp | 30 ++++++ .../include/iso15118/message/d2/type.hpp | 49 ++++++++++ .../include/iso15118/message/d2/variant.hpp | 58 ++++++++++++ .../iso15118/src/iso15118/CMakeLists.txt | 5 + .../iso15118/message/d2/msg_data_types.cpp | 31 ++++++ .../src/iso15118/message/d2/session_setup.cpp | 49 ++++++++++ .../src/iso15118/message/d2/variant.cpp | 94 +++++++++++++++++++ .../iso15118/test/exi/cb/CMakeLists.txt | 1 + .../iso15118/test/exi/cb/iso2/CMakeLists.txt | 14 +++ .../iso15118/test/exi/cb/iso2/helper.hpp | 19 ++++ .../test/exi/cb/iso2/session_setup.cpp | 48 ++++++++++ 14 files changed, 522 insertions(+) create mode 100644 lib/everest/iso15118/include/iso15118/message/d2/msg_data_types.hpp create mode 100644 lib/everest/iso15118/include/iso15118/message/d2/session_setup.hpp create mode 100644 lib/everest/iso15118/include/iso15118/message/d2/type.hpp create mode 100644 lib/everest/iso15118/include/iso15118/message/d2/variant.hpp create mode 100644 lib/everest/iso15118/src/iso15118/message/d2/msg_data_types.cpp create mode 100644 lib/everest/iso15118/src/iso15118/message/d2/session_setup.cpp create mode 100644 lib/everest/iso15118/src/iso15118/message/d2/variant.cpp create mode 100644 lib/everest/iso15118/test/exi/cb/iso2/CMakeLists.txt create mode 100644 lib/everest/iso15118/test/exi/cb/iso2/helper.hpp create mode 100644 lib/everest/iso15118/test/exi/cb/iso2/session_setup.cpp diff --git a/lib/everest/iso15118/include/iso15118/detail/cb_exi.hpp b/lib/everest/iso15118/include/iso15118/detail/cb_exi.hpp index 99d5d5452c..84b23e803a 100644 --- a/lib/everest/iso15118/include/iso15118/detail/cb_exi.hpp +++ b/lib/everest/iso15118/include/iso15118/detail/cb_exi.hpp @@ -97,3 +97,22 @@ size_t serialize_helper(const MessageType& in, const io::StreamOutputView& strea } } // namespace iso15118::message_20 + +namespace iso15118::d2::msg { + +template int serialize_to_exi(const MessageType& in, exi_bitstream_t& out); + +template +size_t serialize_helper(const MessageType& in, const io::StreamOutputView& stream_view) { + auto out = get_exi_output_stream(stream_view); + + const auto error = serialize_to_exi(in, out); + + if (error != 0) { + throw std::runtime_error("Could not encode exi: " + std::to_string(error)); + } + + return exi_bitstream_get_length(&out); +} + +} // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/include/iso15118/detail/variant_access.hpp b/lib/everest/iso15118/include/iso15118/detail/variant_access.hpp index 22e4bbfb9b..84f0cf6e4a 100644 --- a/lib/everest/iso15118/include/iso15118/detail/variant_access.hpp +++ b/lib/everest/iso15118/include/iso15118/detail/variant_access.hpp @@ -4,6 +4,7 @@ #include +#include #include #include "cb_exi.hpp" @@ -34,3 +35,36 @@ struct VariantAccess { template void insert_type(VariantAccess& va, const CbExiMessageType&); } // namespace iso15118::message_20 + +#include + +namespace iso15118::d2::msg { + +struct VariantAccess { + // input + exi_bitstream_t input_stream; + + // output + void*& data; + iso15118::d2::msg::Type& type; + iso15118::d2::msg::Variant::CustomDeleter& custom_deleter; + std::string& error; + + template + void insert_type(const CbExiMessageType& in, const iso2_MessageHeaderType& header) { + assert(data == nullptr); + + data = new MessageType; + type = iso15118::d2::msg::TypeTrait::type; + custom_deleter = [](void* ptr) { delete static_cast(ptr); }; + + auto msg = static_cast(data); + convert(header, msg->header); + convert(in, *msg); + }; +}; + +template +void insert_type(VariantAccess& va, const CbExiMessageType&, const iso2_MessageHeaderType& header); + +} // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/include/iso15118/message/d2/msg_data_types.hpp b/lib/everest/iso15118/include/iso15118/message/d2/msg_data_types.hpp new file mode 100644 index 0000000000..9a64a1ead5 --- /dev/null +++ b/lib/everest/iso15118/include/iso15118/message/d2/msg_data_types.hpp @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Pionix GmbH and Contributors to EVerest +#pragma once + +#include +#include +#include + +#include + +namespace iso15118::d2::msg { + +template void convert(const InType&, OutType&); + +namespace data_types { + +constexpr auto SESSION_ID_LENGTH = 8; +using SESSION_ID = std::array; // hexBinary, max length 8 + +enum class ResponseCode { + OK, + OK_NewSessionEstablished, + OK_OldSessionJoined, + OK_CertificateExpiresSoon, + FAILED, + FAILED_SequenceError, + FAILED_ServiceIDInvalid, + FAILED_UnknownSession, + FAILED_ServiceSelectionInvalid, + FAILED_PaymentSelectionInvalid, + FAILED_CertificateExpired, + FAILED_SignatureError, + FAILED_NoCertificateAvailable, + FAILED_CertChainError, + FAILED_ChallengeInvalid, + FAILED_ContractCanceled, + FAILED_WrongChargeParameter, + FAILED_PowerDeliveryNotApplied, + FAILED_TariffSelectionInvalid, + FAILED_ChargingProfileInvalid, + FAILED_MeteringSignatureNotValid, + FAILED_NoChargeServiceSelected, + FAILED_WrongEnergyTransferMode, + FAILED_ContactorError, + FAILED_CertificateNotAllowedAtThisEVSE, + FAILED_CertificateRevoked, +}; + +enum class FaultCode { + ParsingError, + NoTLSRootCertificatAvailable, + UnknownError, +}; + +struct Notification { + FaultCode fault_code; + std::optional fault_msg; +}; + +} // namespace data_types + +struct Header { + data_types::SESSION_ID session_id; + std::optional notification; + // TODO: Missing xml signature +}; + +void convert(const struct iso2_MessageHeaderType& in, Header& out); +void convert(const Header& in, struct iso2_MessageHeaderType& out); + +} // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/include/iso15118/message/d2/session_setup.hpp b/lib/everest/iso15118/include/iso15118/message/d2/session_setup.hpp new file mode 100644 index 0000000000..9f67c5eaf7 --- /dev/null +++ b/lib/everest/iso15118/include/iso15118/message/d2/session_setup.hpp @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Pionix GmbH and Contributors to EVerest +#pragma once + +#include + +#include +#include +#include + +namespace iso15118::d2::msg { + +namespace data_types { +constexpr auto EVCC_ID_LENGTH = 6; +using EVCCID = std::array; // hexBinary, max length 6 +} // namespace data_types + +struct SessionSetupRequest { + Header header; + data_types::EVCCID evcc_id; +}; + +struct SessionSetupResponse { + Header header; + data_types::ResponseCode response_code; + std::string evse_id; + std::optional timestamp; +}; + +} // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/include/iso15118/message/d2/type.hpp b/lib/everest/iso15118/include/iso15118/message/d2/type.hpp new file mode 100644 index 0000000000..b5f7a1633d --- /dev/null +++ b/lib/everest/iso15118/include/iso15118/message/d2/type.hpp @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Pionix GmbH and Contributors to EVerest +#pragma once + +#include + +namespace iso15118::d2::msg { + +enum class Type { + None, + SupportedAppProtocolReq, + SupportedAppProtocolRes, + SessionSetupReq, + SessionSetupRes, +}; + +template struct TypeTrait { + static const Type type = Type::None; +}; + +template void convert(const InType&, OutType&); + +template size_t serialize(const MessageType&, const io::StreamOutputView&); + +// +// definitions of type traits +// +#ifdef CREATE_TYPE_TRAIT +#define CREATE_TYPE_TRAIT_PUSHED CREATE_TYPE_TRAIT +#endif + +#define CREATE_TYPE_TRAIT(struct_name, enum_name) \ + struct struct_name; \ + template <> struct TypeTrait { \ + static const Type type = Type::enum_name; \ + } + +CREATE_TYPE_TRAIT(SupportedAppProtocolRequest, SupportedAppProtocolReq); +CREATE_TYPE_TRAIT(SupportedAppProtocolResponse, SupportedAppProtocolRes); +CREATE_TYPE_TRAIT(SessionSetupRequest, SessionSetupReq); +CREATE_TYPE_TRAIT(SessionSetupResponse, SessionSetupRes); + +#ifdef CREATE_TYPE_TRAIT_PUSHED +#define CREATE_TYPE_TRAIT CREATE_TYPE_TRAIT_PUSHED +#else +#undef CREATE_TYPE_TRAIT +#endif + +} // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/include/iso15118/message/d2/variant.hpp b/lib/everest/iso15118/include/iso15118/message/d2/variant.hpp new file mode 100644 index 0000000000..4775ab11e3 --- /dev/null +++ b/lib/everest/iso15118/include/iso15118/message/d2/variant.hpp @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 Pionix GmbH and Contributors to EVerest +#pragma once + +#include +#include + +// FIXME (aw): we only need the payload types from sdp.hpp, this could be shared in a separate header file +#include +#include + +#include "type.hpp" + +namespace iso15118::d2::msg { + +class Variant { +public: + using CustomDeleter = void (*)(void*); + Variant(io::v2gtp::PayloadType, const io::StreamInputView&, bool supported_app_protocol_msg); + template Variant(const MessageType& in) { + static_assert(TypeTrait::type != Type::None, "Unhandled type!"); + + data = new MessageType; + *static_cast(data) = in; + custom_deleter = [](void* ptr) { delete static_cast(ptr); }; + type = TypeTrait::type; + } + ~Variant(); + + Type get_type() const; + + const std::string& get_error() const; + + template const T& get() const { + static_assert(TypeTrait::type != Type::None, "Unhandled type!"); + if (TypeTrait::type != type) { + throw std::runtime_error("Illegal message type access"); + } + + return *static_cast(data); + } + + template T const* get_if() const { + static_assert(TypeTrait::type != Type::None, "Unhandled type!"); + if (TypeTrait::type != type) { + return nullptr; + } + + return static_cast(data); + } + +private: + CustomDeleter custom_deleter{nullptr}; + void* data{nullptr}; + Type type{Type::None}; + std::string error; +}; +} // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/src/iso15118/CMakeLists.txt b/lib/everest/iso15118/src/iso15118/CMakeLists.txt index fc33f8a3a5..ebe2e8dbf3 100644 --- a/lib/everest/iso15118/src/iso15118/CMakeLists.txt +++ b/lib/everest/iso15118/src/iso15118/CMakeLists.txt @@ -68,6 +68,10 @@ target_sources(iso15118 message/dc_welding_detection.cpp message/session_stop.cpp + message/d2/variant.cpp + message/d2/msg_data_types.cpp + message/d2/session_setup.cpp + tbd_controller.cpp ) @@ -76,6 +80,7 @@ target_link_libraries(iso15118 # FIXME (aw): would be nice if we could make this private! cbv2g::tp cbv2g::iso20 + cbv2g::iso2 PRIVATE Threads::Threads ) diff --git a/lib/everest/iso15118/src/iso15118/message/d2/msg_data_types.cpp b/lib/everest/iso15118/src/iso15118/message/d2/msg_data_types.cpp new file mode 100644 index 0000000000..a45dd7f6d3 --- /dev/null +++ b/lib/everest/iso15118/src/iso15118/message/d2/msg_data_types.cpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Pionix GmbH and Contributors to EVerest + +#include + +#include + +namespace iso15118::d2::msg { + +void convert(const iso2_NotificationType& in, data_types::Notification& out) { + cb_convert_enum(in.FaultCode, out.fault_code); + CB2CPP_STRING_IF_USED(in.FaultMsg, out.fault_msg); +} + +void convert(const struct iso2_MessageHeaderType& in, Header& out) { + CB2CPP_BYTES(in.SessionID, out.session_id); + CB2CPP_CONVERT_IF_USED(in.Notification, out.notification); +} + +void convert(const data_types::Notification& in, struct iso2_NotificationType& out) { + cb_convert_enum(in.fault_code, out.FaultCode); + CPP2CB_STRING_IF_USED(in.fault_msg, out.FaultMsg); +} + +void convert(const Header& in, struct iso2_MessageHeaderType& out) { + init_iso2_MessageHeaderType(&out); + CPP2CB_BYTES(in.session_id, out.SessionID); + CPP2CB_CONVERT_IF_USED(in.notification, out.Notification); +} + +} // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/src/iso15118/message/d2/session_setup.cpp b/lib/everest/iso15118/src/iso15118/message/d2/session_setup.cpp new file mode 100644 index 0000000000..c546c40933 --- /dev/null +++ b/lib/everest/iso15118/src/iso15118/message/d2/session_setup.cpp @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Pionix GmbH and Contributors to EVerest +#include + +#include + +#include +#include + +#include + +namespace iso15118::d2::msg { + +template <> void convert(const struct iso2_SessionSetupReqType& in, SessionSetupRequest& out) { + std::copy(in.EVCCID.bytes, in.EVCCID.bytes + in.EVCCID.bytesLen, out.evcc_id.begin()); +} + +template <> +void insert_type(VariantAccess& va, const struct iso2_SessionSetupReqType& in, + const struct iso2_MessageHeaderType& header) { + va.insert_type(in, header); +} + +template <> void convert(const SessionSetupResponse& in, struct iso2_SessionSetupResType& out) { + init_iso2_SessionSetupResType(&out); + + cb_convert_enum(in.response_code, out.ResponseCode); + CPP2CB_STRING(in.evse_id, out.EVSEID); + CPP2CB_ASSIGN_IF_USED(in.timestamp, out.EVSETimeStamp); +} + +template <> int serialize_to_exi(const SessionSetupResponse& in, exi_bitstream_t& out) { + + iso2_exiDocument doc; + init_iso2_exiDocument(&doc); + + convert(in.header, doc.V2G_Message.Header); + + CB_SET_USED(doc.V2G_Message.Body.SessionSetupRes); + convert(in, doc.V2G_Message.Body.SessionSetupRes); + + return encode_iso2_exiDocument(&out, &doc); +} + +template <> size_t serialize(const SessionSetupResponse& in, const io::StreamOutputView& out) { + return serialize_helper(in, out); +} + +} // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/src/iso15118/message/d2/variant.cpp b/lib/everest/iso15118/src/iso15118/message/d2/variant.cpp new file mode 100644 index 0000000000..aad3eed972 --- /dev/null +++ b/lib/everest/iso15118/src/iso15118/message/d2/variant.cpp @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 Pionix GmbH and Contributors to EVerest +#include + +#include +#include + +#include +#include + +#include +#include + +using PayloadType = iso15118::io::v2gtp::PayloadType; + +namespace iso15118::d2::msg { + +namespace { + +void handle_sap(VariantAccess& va) { + appHand_exiDocument doc; + + const auto decode_status = decode_appHand_exiDocument(&va.input_stream, &doc); + + if (decode_status != 0) { + va.error = "decode_appHand_exiDocument failed with " + std::to_string(decode_status); + return; + } + + if (doc.supportedAppProtocolReq_isUsed) { + // insert_type(va, doc.supportedAppProtocolReq); + } else { + va.error = "chosen message type unhandled"; + } +} + +void handle_v2g(VariantAccess& va) { + iso2_exiDocument doc; + + const auto decode_status = decode_iso2_exiDocument(&va.input_stream, &doc); + + if (decode_status != 0) { + return; + } + + if (doc.V2G_Message.Body.SessionSetupReq_isUsed) { + insert_type(va, doc.V2G_Message.Body.SessionSetupReq, doc.V2G_Message.Header); + } else { + va.error = "chosen message type unhandled"; + } +} +} // namespace + +Variant::Variant(io::v2gtp::PayloadType payload_type, const io::StreamInputView& buffer_view, + bool supported_app_protocol_msg) { + + VariantAccess va{ + get_exi_input_stream(buffer_view), this->data, this->type, this->custom_deleter, this->error, + }; + + if (payload_type == PayloadType::SAP) { + if (supported_app_protocol_msg) { + handle_sap(va); + } else { + handle_v2g(va); + } + } else { + logf_warning("Unknown type"); + } + + if (data) { + // in case data was set, make sure the custom deleter and the type were set! + assert(custom_deleter != nullptr); + assert(type != Type::None); + } else { + logf_error("Failed due to: %s\n", error.c_str()); + } +} + +Variant::~Variant() { + if (data) { + custom_deleter(data); + } +} + +Type Variant::get_type() const { + return type; +} + +const std::string& Variant::get_error() const { + return error; +} + +} // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/test/exi/cb/CMakeLists.txt b/lib/everest/iso15118/test/exi/cb/CMakeLists.txt index 90b6af7068..178bd2060e 100644 --- a/lib/everest/iso15118/test/exi/cb/CMakeLists.txt +++ b/lib/everest/iso15118/test/exi/cb/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(app_hand) +add_subdirectory(iso2) add_subdirectory(iso20) diff --git a/lib/everest/iso15118/test/exi/cb/iso2/CMakeLists.txt b/lib/everest/iso15118/test/exi/cb/iso2/CMakeLists.txt new file mode 100644 index 0000000000..6d34bde21e --- /dev/null +++ b/lib/everest/iso15118/test/exi/cb/iso2/CMakeLists.txt @@ -0,0 +1,14 @@ +include(Catch) + +function(create_exi_test_target NAME) + add_executable(test_exi_d2_${NAME} ${NAME}.cpp) + target_link_libraries(test_exi_d2_${NAME} + PRIVATE + iso15118 + Catch2::Catch2WithMain + ) + catch_discover_tests(test_exi_d2_${NAME}) +endfunction() + +create_exi_test_target(session_setup) + diff --git a/lib/everest/iso15118/test/exi/cb/iso2/helper.hpp b/lib/everest/iso15118/test/exi/cb/iso2/helper.hpp new file mode 100644 index 0000000000..6caaef8f5b --- /dev/null +++ b/lib/everest/iso15118/test/exi/cb/iso2/helper.hpp @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Pionix GmbH and Contributors to EVerest +#pragma once + +#include +#include + +#include + +using namespace iso15118; + +template std::vector serialize_helper(const Message& message) { + uint8_t serialization_buffer[1024]; + io::StreamOutputView out({serialization_buffer, sizeof(serialization_buffer)}); + + const auto size = d2::msg::serialize(message, out); + + return std::vector(serialization_buffer, serialization_buffer + size); +} diff --git a/lib/everest/iso15118/test/exi/cb/iso2/session_setup.cpp b/lib/everest/iso15118/test/exi/cb/iso2/session_setup.cpp new file mode 100644 index 0000000000..2ca9fd35d5 --- /dev/null +++ b/lib/everest/iso15118/test/exi/cb/iso2/session_setup.cpp @@ -0,0 +1,48 @@ +#include + +#include +#include + +#include "helper.hpp" + +#include +#include + +using namespace iso15118; + +SCENARIO("Ser/Deserialize d2 session setup messages") { + GIVEN("Deserialize session setup req") { + uint8_t doc_raw[] = {0x80, 0x98, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x11, 0xd0, 0x18, 0x4b, 0x88, 0xf8, 0x43, 0x4d, 0x20, 0x00}; + + const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)}; + + d2::msg::Variant variant(io::v2gtp::PayloadType::SAP, stream_view, false); + + THEN("It should be deserialized successfully") { + REQUIRE(variant.get_type() == d2::msg::Type::SessionSetupReq); + + const auto& msg = variant.get(); + const auto& header = msg.header; + + REQUIRE(header.session_id == std::array{0}); + + REQUIRE(msg.evcc_id == std::array{0x12, 0xE2, 0x3E, 0x10, 0xD3, 0x48}); + } + } + GIVEN("Serialize session_setup_res") { + + const auto header = d2::msg::Header{{0x06, 0xD0, 0x7F, 0xBF, 0x17, 0x4B, 0x5E, 0xFF}, std::nullopt}; + + const auto res = d2::msg::SessionSetupResponse{ + header, d2::msg::data_types::ResponseCode::OK_NewSessionEstablished, "DE*PNX*E12345*1", std::nullopt}; + + std::vector expected = {0x80, 0x98, 0x02, 0x01, 0xB4, 0x1F, 0xEF, 0xC5, 0xD2, 0xD7, 0xBF, + 0xD1, 0xE0, 0x20, 0x45, 0x11, 0x14, 0xA9, 0x41, 0x39, 0x60, 0xA9, + 0x14, 0xC4, 0xC8, 0xCC, 0xD0, 0xD4, 0xA8, 0xC4, 0x80}; + + THEN("It should be serialized successfully") { + REQUIRE(serialize_helper(res) == expected); // FIXME: Failing right now + } + } +} From 2b6f480ba4efe1ba7225c11d1dc3e44dffc312fd Mon Sep 17 00:00:00 2001 From: Kacper Dalach Date: Tue, 13 Jan 2026 10:18:44 +0100 Subject: [PATCH 2/5] libiso15118: Add -2 authorization messages (#1668) Signed-off-by: Kacper Dalach Co-authored-by: Kacper Dalach --- .../iso15118/message/d2/authorization.hpp | 24 +++++++++ .../iso15118/message/d2/msg_data_types.hpp | 9 ++++ .../include/iso15118/message/d2/type.hpp | 4 ++ .../iso15118/src/iso15118/CMakeLists.txt | 1 + .../src/iso15118/message/d2/authorization.cpp | 49 +++++++++++++++++++ .../src/iso15118/message/d2/variant.cpp | 2 + .../iso15118/test/exi/cb/iso2/CMakeLists.txt | 1 + .../test/exi/cb/iso2/authorization.cpp | 46 +++++++++++++++++ 8 files changed, 136 insertions(+) create mode 100644 lib/everest/iso15118/include/iso15118/message/d2/authorization.hpp create mode 100644 lib/everest/iso15118/src/iso15118/message/d2/authorization.cpp create mode 100644 lib/everest/iso15118/test/exi/cb/iso2/authorization.cpp diff --git a/lib/everest/iso15118/include/iso15118/message/d2/authorization.hpp b/lib/everest/iso15118/include/iso15118/message/d2/authorization.hpp new file mode 100644 index 0000000000..57e99d16d4 --- /dev/null +++ b/lib/everest/iso15118/include/iso15118/message/d2/authorization.hpp @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Pionix GmbH and Contributors to EVerest +#pragma once + +#include + +#include +#include + +namespace iso15118::d2::msg { + +struct AuthorizationRequest { + Header header; + std::string id; + std::optional gen_challenge; +}; + +struct AuthorizationResponse { + Header header; + data_types::ResponseCode response_code; + data_types::EvseProcessing evse_processing; +}; + +} // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/include/iso15118/message/d2/msg_data_types.hpp b/lib/everest/iso15118/include/iso15118/message/d2/msg_data_types.hpp index 9a64a1ead5..9682a76a0c 100644 --- a/lib/everest/iso15118/include/iso15118/message/d2/msg_data_types.hpp +++ b/lib/everest/iso15118/include/iso15118/message/d2/msg_data_types.hpp @@ -17,6 +17,9 @@ namespace data_types { constexpr auto SESSION_ID_LENGTH = 8; using SESSION_ID = std::array; // hexBinary, max length 8 +constexpr auto GEN_CHALLENGE_LENGTH = 16; +using GenChallenge = std::array; // base64 binary + enum class ResponseCode { OK, OK_NewSessionEstablished, @@ -52,6 +55,12 @@ enum class FaultCode { UnknownError, }; +enum class EvseProcessing { + Finished, + Ongoing, + Ongoing_WaitingForCustomerInteraction +}; + struct Notification { FaultCode fault_code; std::optional fault_msg; diff --git a/lib/everest/iso15118/include/iso15118/message/d2/type.hpp b/lib/everest/iso15118/include/iso15118/message/d2/type.hpp index b5f7a1633d..7b2511af41 100644 --- a/lib/everest/iso15118/include/iso15118/message/d2/type.hpp +++ b/lib/everest/iso15118/include/iso15118/message/d2/type.hpp @@ -12,6 +12,8 @@ enum class Type { SupportedAppProtocolRes, SessionSetupReq, SessionSetupRes, + AuthorizationReq, + AuthorizationRes, }; template struct TypeTrait { @@ -39,6 +41,8 @@ CREATE_TYPE_TRAIT(SupportedAppProtocolRequest, SupportedAppProtocolReq); CREATE_TYPE_TRAIT(SupportedAppProtocolResponse, SupportedAppProtocolRes); CREATE_TYPE_TRAIT(SessionSetupRequest, SessionSetupReq); CREATE_TYPE_TRAIT(SessionSetupResponse, SessionSetupRes); +CREATE_TYPE_TRAIT(AuthorizationRequest, AuthorizationReq); +CREATE_TYPE_TRAIT(AuthorizationResponse, AuthorizationRes); #ifdef CREATE_TYPE_TRAIT_PUSHED #define CREATE_TYPE_TRAIT CREATE_TYPE_TRAIT_PUSHED diff --git a/lib/everest/iso15118/src/iso15118/CMakeLists.txt b/lib/everest/iso15118/src/iso15118/CMakeLists.txt index ebe2e8dbf3..8581ec8652 100644 --- a/lib/everest/iso15118/src/iso15118/CMakeLists.txt +++ b/lib/everest/iso15118/src/iso15118/CMakeLists.txt @@ -68,6 +68,7 @@ target_sources(iso15118 message/dc_welding_detection.cpp message/session_stop.cpp + message/d2/authorization.cpp message/d2/variant.cpp message/d2/msg_data_types.cpp message/d2/session_setup.cpp diff --git a/lib/everest/iso15118/src/iso15118/message/d2/authorization.cpp b/lib/everest/iso15118/src/iso15118/message/d2/authorization.cpp new file mode 100644 index 0000000000..ccb5d7f003 --- /dev/null +++ b/lib/everest/iso15118/src/iso15118/message/d2/authorization.cpp @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Pionix GmbH and Contributors to EVerest +#include + +#include + +#include +#include + +#include + +namespace iso15118::d2::msg { + +template <> void convert(const struct iso2_AuthorizationReqType& in, AuthorizationRequest& out) { + out.id = CB2CPP_STRING(in.Id); + CB2CPP_BYTES_IF_USED(in.GenChallenge, out.gen_challenge); +} + +template <> +void insert_type(VariantAccess& va, const struct iso2_AuthorizationReqType& in, + const struct iso2_MessageHeaderType& header) { + va.insert_type(in, header); +} + +template <> void convert(const AuthorizationResponse& in, struct iso2_AuthorizationResType& out) { + init_iso2_AuthorizationResType(&out); + + cb_convert_enum(in.response_code, out.ResponseCode); + cb_convert_enum(in.evse_processing, out.EVSEProcessing); +} + +template <> int serialize_to_exi(const AuthorizationResponse& in, exi_bitstream_t& out) { + + iso2_exiDocument doc; + init_iso2_exiDocument(&doc); + + convert(in.header, doc.V2G_Message.Header); + + CB_SET_USED(doc.V2G_Message.Body.AuthorizationRes); + convert(in, doc.V2G_Message.Body.AuthorizationRes); + + return encode_iso2_exiDocument(&out, &doc); +} + +template <> size_t serialize(const AuthorizationResponse& in, const io::StreamOutputView& out) { + return serialize_helper(in, out); +} + +} // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/src/iso15118/message/d2/variant.cpp b/lib/everest/iso15118/src/iso15118/message/d2/variant.cpp index aad3eed972..0fd4dc6936 100644 --- a/lib/everest/iso15118/src/iso15118/message/d2/variant.cpp +++ b/lib/everest/iso15118/src/iso15118/message/d2/variant.cpp @@ -45,6 +45,8 @@ void handle_v2g(VariantAccess& va) { if (doc.V2G_Message.Body.SessionSetupReq_isUsed) { insert_type(va, doc.V2G_Message.Body.SessionSetupReq, doc.V2G_Message.Header); + } else if (doc.V2G_Message.Body.AuthorizationReq_isUsed) { + insert_type(va, doc.V2G_Message.Body.AuthorizationReq, doc.V2G_Message.Header); } else { va.error = "chosen message type unhandled"; } diff --git a/lib/everest/iso15118/test/exi/cb/iso2/CMakeLists.txt b/lib/everest/iso15118/test/exi/cb/iso2/CMakeLists.txt index 6d34bde21e..93d498acb1 100644 --- a/lib/everest/iso15118/test/exi/cb/iso2/CMakeLists.txt +++ b/lib/everest/iso15118/test/exi/cb/iso2/CMakeLists.txt @@ -11,4 +11,5 @@ function(create_exi_test_target NAME) endfunction() create_exi_test_target(session_setup) +create_exi_test_target(authorization) diff --git a/lib/everest/iso15118/test/exi/cb/iso2/authorization.cpp b/lib/everest/iso15118/test/exi/cb/iso2/authorization.cpp new file mode 100644 index 0000000000..52411eea6a --- /dev/null +++ b/lib/everest/iso15118/test/exi/cb/iso2/authorization.cpp @@ -0,0 +1,46 @@ +#include + +#include +#include +#include + +#include "helper.hpp" + +using namespace iso15118; + +SCENARIO("Ser/Deserialize d2 authorization messages") { + GIVEN("Deserialize authorization req") { + // TODO(kd): Test deserialization of GenChallenge field + + uint8_t doc_raw[] = {0x80, 0x98, 0x2, 0x0, 0xb6, 0xc8, 0x81, 0xce, 0xc2, 0x13, 0x4b, 0x50, 0x8}; + + const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)}; + + d2::msg::Variant variant(io::v2gtp::PayloadType::SAP, stream_view, false); + + THEN("It should be deserialized successfully") { + REQUIRE(variant.get_type() == d2::msg::Type::AuthorizationReq); + + const auto& msg = variant.get(); + const auto& header = msg.header; + + REQUIRE(header.session_id == std::array{0x02, 0xDB, 0x22, 0x07, 0x3B, 0x08, 0x4D, 0x2D}); + + REQUIRE(msg.id == ""); + } + } + GIVEN("Serialize authorization res") { + + const auto header = d2::msg::Header{{0x02, 0xDB, 0x22, 0x07, 0x3B, 0x08, 0x4D, 0x2D}, std::nullopt}; + + const auto res = d2::msg::AuthorizationResponse{header, d2::msg::data_types::ResponseCode::OK, + d2::msg::data_types::EvseProcessing::Ongoing}; + + std::vector expected = {0x80, 0x98, 0x2, 0x0, 0xb6, 0xc8, 0x81, 0xce, + 0xc2, 0x13, 0x4b, 0x50, 0x10, 0x1, 0x0}; + + THEN("It should be serialized successfully") { + REQUIRE(serialize_helper(res) == expected); + } + } +} From 45c74578fc1adc6f66d733df4c49da5ba77fde1e Mon Sep 17 00:00:00 2001 From: Sebastian Lukas Date: Tue, 13 Jan 2026 14:44:28 +0100 Subject: [PATCH 3/5] Fixing encoding session_setup_res Signed-off-by: Sebastian Lukas --- lib/everest/iso15118/src/iso15118/message/d2/authorization.cpp | 1 + lib/everest/iso15118/src/iso15118/message/d2/session_setup.cpp | 2 ++ lib/everest/iso15118/test/exi/cb/iso2/session_setup.cpp | 3 --- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/everest/iso15118/src/iso15118/message/d2/authorization.cpp b/lib/everest/iso15118/src/iso15118/message/d2/authorization.cpp index ccb5d7f003..4feaf4ac30 100644 --- a/lib/everest/iso15118/src/iso15118/message/d2/authorization.cpp +++ b/lib/everest/iso15118/src/iso15118/message/d2/authorization.cpp @@ -33,6 +33,7 @@ template <> int serialize_to_exi(const AuthorizationResponse& in, exi_bitstream_ iso2_exiDocument doc; init_iso2_exiDocument(&doc); + init_iso2_BodyType(&doc.V2G_Message.Body); convert(in.header, doc.V2G_Message.Header); diff --git a/lib/everest/iso15118/src/iso15118/message/d2/session_setup.cpp b/lib/everest/iso15118/src/iso15118/message/d2/session_setup.cpp index c546c40933..9310e4aa04 100644 --- a/lib/everest/iso15118/src/iso15118/message/d2/session_setup.cpp +++ b/lib/everest/iso15118/src/iso15118/message/d2/session_setup.cpp @@ -34,6 +34,8 @@ template <> int serialize_to_exi(const SessionSetupResponse& in, exi_bitstream_t iso2_exiDocument doc; init_iso2_exiDocument(&doc); + init_iso2_BodyType(&doc.V2G_Message.Body); + convert(in.header, doc.V2G_Message.Header); CB_SET_USED(doc.V2G_Message.Body.SessionSetupRes); diff --git a/lib/everest/iso15118/test/exi/cb/iso2/session_setup.cpp b/lib/everest/iso15118/test/exi/cb/iso2/session_setup.cpp index 2ca9fd35d5..be13150dfa 100644 --- a/lib/everest/iso15118/test/exi/cb/iso2/session_setup.cpp +++ b/lib/everest/iso15118/test/exi/cb/iso2/session_setup.cpp @@ -5,9 +5,6 @@ #include "helper.hpp" -#include -#include - using namespace iso15118; SCENARIO("Ser/Deserialize d2 session setup messages") { From 26677a916c608e11ca9ea12c6467f8fe0acb6c65 Mon Sep 17 00:00:00 2001 From: Kacper Dalach Date: Wed, 14 Jan 2026 15:38:51 +0100 Subject: [PATCH 4/5] libiso15118: -2 dc messages (#1679) * libiso15118: Add EV/EVSE status structs. These structs are a common part in many messages. * libiso15118: Add EVSEProcessing * libiso15118: Add -2 cable check messages * libiso15118: d2: Add PhysicalValue * libiso15118: d2: Add common DC datatypes. Added datatypes that are common among DC messages. * libiso15118: d2: Add WeldingDetection messages * libiso15118: d2: Add PreCharge messages * libiso15118: d2: Add CurrentDemand messages * libiso15118: d2: Apply requested changes. Applied all of the requested changes. Except for file and struct rename. * libiso15118: d2: Rename files and structs. Renamed all files and structs to have the `DC_` prefix. * libiso15118: d2: Minor fixes in dc messages. Added additional check for optional values in tests. Renamed types to pascal case. --------- Signed-off-by: Kacper Dalach Co-authored-by: Kacper Dalach --- .../iso15118/message/d2/dc_cable_check.hpp | 21 +++ .../iso15118/message/d2/dc_current_demand.hpp | 42 +++++ .../iso15118/message/d2/dc_pre_charge.hpp | 23 +++ .../message/d2/dc_welding_detection.hpp | 21 +++ .../iso15118/message/d2/msg_data_types.hpp | 101 ++++++++++++ .../include/iso15118/message/d2/type.hpp | 16 ++ .../iso15118/src/iso15118/CMakeLists.txt | 4 + .../iso15118/message/d2/dc_cable_check.cpp | 50 ++++++ .../iso15118/message/d2/dc_current_demand.cpp | 70 ++++++++ .../src/iso15118/message/d2/dc_pre_charge.cpp | 52 ++++++ .../message/d2/dc_welding_detection.cpp | 50 ++++++ .../iso15118/message/d2/msg_data_types.cpp | 80 +++++++++ .../src/iso15118/message/d2/variant.cpp | 8 + .../iso15118/test/exi/cb/iso2/CMakeLists.txt | 4 + .../test/exi/cb/iso2/dc_cable_check.cpp | 55 ++++++ .../test/exi/cb/iso2/dc_current_demand.cpp | 156 ++++++++++++++++++ .../test/exi/cb/iso2/dc_pre_charge.cpp | 59 +++++++ .../test/exi/cb/iso2/dc_welding_detection.cpp | 57 +++++++ 18 files changed, 869 insertions(+) create mode 100644 lib/everest/iso15118/include/iso15118/message/d2/dc_cable_check.hpp create mode 100644 lib/everest/iso15118/include/iso15118/message/d2/dc_current_demand.hpp create mode 100644 lib/everest/iso15118/include/iso15118/message/d2/dc_pre_charge.hpp create mode 100644 lib/everest/iso15118/include/iso15118/message/d2/dc_welding_detection.hpp create mode 100644 lib/everest/iso15118/src/iso15118/message/d2/dc_cable_check.cpp create mode 100644 lib/everest/iso15118/src/iso15118/message/d2/dc_current_demand.cpp create mode 100644 lib/everest/iso15118/src/iso15118/message/d2/dc_pre_charge.cpp create mode 100644 lib/everest/iso15118/src/iso15118/message/d2/dc_welding_detection.cpp create mode 100644 lib/everest/iso15118/test/exi/cb/iso2/dc_cable_check.cpp create mode 100644 lib/everest/iso15118/test/exi/cb/iso2/dc_current_demand.cpp create mode 100644 lib/everest/iso15118/test/exi/cb/iso2/dc_pre_charge.cpp create mode 100644 lib/everest/iso15118/test/exi/cb/iso2/dc_welding_detection.cpp diff --git a/lib/everest/iso15118/include/iso15118/message/d2/dc_cable_check.hpp b/lib/everest/iso15118/include/iso15118/message/d2/dc_cable_check.hpp new file mode 100644 index 0000000000..d0a79e7245 --- /dev/null +++ b/lib/everest/iso15118/include/iso15118/message/d2/dc_cable_check.hpp @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Pionix GmbH and Contributors to EVerest +#pragma once + +#include + +namespace iso15118::d2::msg { + +struct DC_CableCheckRequest { + Header header; + data_types::DcEvStatus ev_status; +}; + +struct DC_CableCheckResponse { + Header header; + data_types::ResponseCode response_code; + data_types::DcEvseStatus evse_status; + data_types::EvseProcessing evse_processing; +}; + +} // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/include/iso15118/message/d2/dc_current_demand.hpp b/lib/everest/iso15118/include/iso15118/message/d2/dc_current_demand.hpp new file mode 100644 index 0000000000..1b4cb0aa0b --- /dev/null +++ b/lib/everest/iso15118/include/iso15118/message/d2/dc_current_demand.hpp @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Pionix GmbH and Contributors to EVerest +#pragma once + +#include +#include + +namespace iso15118::d2::msg { + +struct DC_CurrentDemandRequest { + Header header; + data_types::DcEvStatus ev_status; + data_types::PhysicalValue ev_target_current; + data_types::PhysicalValue ev_target_voltage; + std::optional ev_maximum_voltage_limit{std::nullopt}; + std::optional ev_maximum_current_limit{std::nullopt}; + std::optional ev_maximum_power_limit{std::nullopt}; + std::optional bulk_charging_complete{std::nullopt}; + std::optional charging_complete{std::nullopt}; + std::optional remaining_time_to_full_soc{std::nullopt}; + std::optional remaining_time_to_bulk_soc{std::nullopt}; +}; + +struct DC_CurrentDemandResponse { + Header header; + data_types::ResponseCode response_code; + data_types::DcEvseStatus evse_status; + data_types::PhysicalValue evse_present_voltage; + data_types::PhysicalValue evse_present_current; + bool evse_current_limit_achieved; + bool evse_voltage_limit_achieved; + bool evse_power_limit_achieved; + data_types::EVSEID evse_id; + data_types::SAScheduleTupleID sa_schedule_tuple_id; + std::optional evse_maximum_voltage_limit{std::nullopt}; + std::optional evse_maximum_current_limit{std::nullopt}; + std::optional evse_maximum_power_limit{std::nullopt}; + std::optional meter_info{std::nullopt}; + std::optional receipt_required{std::nullopt}; +}; + +} // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/include/iso15118/message/d2/dc_pre_charge.hpp b/lib/everest/iso15118/include/iso15118/message/d2/dc_pre_charge.hpp new file mode 100644 index 0000000000..928a844281 --- /dev/null +++ b/lib/everest/iso15118/include/iso15118/message/d2/dc_pre_charge.hpp @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Pionix GmbH and Contributors to EVerest +#pragma once + +#include + +namespace iso15118::d2::msg { + +struct DC_PreChargeRequest { + Header header; + data_types::DcEvStatus ev_status; + data_types::PhysicalValue ev_target_voltage; + data_types::PhysicalValue ev_target_current; +}; + +struct DC_PreChargeResponse { + Header header; + data_types::ResponseCode response_code; + data_types::DcEvseStatus evse_status; + data_types::PhysicalValue evse_present_voltage; +}; + +} // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/include/iso15118/message/d2/dc_welding_detection.hpp b/lib/everest/iso15118/include/iso15118/message/d2/dc_welding_detection.hpp new file mode 100644 index 0000000000..56ed0ddcea --- /dev/null +++ b/lib/everest/iso15118/include/iso15118/message/d2/dc_welding_detection.hpp @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Pionix GmbH and Contributors to EVerest +#pragma once + +#include + +namespace iso15118::d2::msg { + +struct DC_WeldingDetectionRequest { + Header header; + data_types::DcEvStatus ev_status; +}; + +struct DC_WeldingDetectionResponse { + Header header; + data_types::ResponseCode response_code; + data_types::DcEvseStatus evse_status; + data_types::PhysicalValue evse_present_voltage; +}; + +} // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/include/iso15118/message/d2/msg_data_types.hpp b/lib/everest/iso15118/include/iso15118/message/d2/msg_data_types.hpp index 9682a76a0c..2b16395772 100644 --- a/lib/everest/iso15118/include/iso15118/message/d2/msg_data_types.hpp +++ b/lib/everest/iso15118/include/iso15118/message/d2/msg_data_types.hpp @@ -3,8 +3,10 @@ #pragma once #include +#include #include #include +#include #include @@ -19,6 +21,14 @@ using SESSION_ID = std::array; // hexBinary, max len constexpr auto GEN_CHALLENGE_LENGTH = 16; using GenChallenge = std::array; // base64 binary +using PercentValue = uint8_t; // [0 - 100] +using SAScheduleTupleID = int16_t; // [1-255] +using MeterID = std::string; // MaxLength: 32 +using EVSEID = std::string; // Length: 7-37 +using MeterReading = uint64_t; // Wh +using SigMeterReading = std::vector; // MaxLength: 64 +using MeterStatus = int16_t; +using TMeter = int64_t; // Unix timestamp format enum class ResponseCode { OK, @@ -61,11 +71,102 @@ enum class EvseProcessing { Ongoing_WaitingForCustomerInteraction }; +enum class DcEvErrorCode { + NO_ERROR, + FAILED_RESSTemperatureInhibit, + FAILED_EVShiftPosition, + FAILED_ChargerConnectorLockFault, + FAILED_EVRESSMalfunction, + FAILED_ChargingCurrentdifferential, + FAILED_ChargingVoltageOutOfRange, + Reserved_A, + Reserved_B, + Reserved_C, + FAILED_ChargingSystemIncompatibility, + NoData, +}; + +enum class EvseNotification { + None, + StopCharging, + ReNegotiation, +}; + +enum class UnitSymbol { + h, + m, + s, + A, + V, + W, + Wh +}; + +enum class DcEvseStatusCode { + EVSE_NotReady, + EVSE_Ready, + EVSE_Shutdown, + EVSE_UtilityInterruptEvent, + EVSE_IsolationMonitoringActive, + EVSE_EmergencyShutdown, + EVSE_Malfunction, + Reserved_8, + Reserved_9, + Reserved_A, + Reserved_B, + Reserved_C +}; + +enum class IsolationLevel { + Invalid, + Valid, + Warning, + Fault, + No_IMD +}; + +struct MeterInfo { + MeterID meter_id; + std::optional meter_reading{std::nullopt}; + std::optional sig_meter_reading{std::nullopt}; + std::optional meter_status{std::nullopt}; + std::optional t_meter{std::nullopt}; +}; + +struct PhysicalValue { + int16_t value{0}; + int8_t multiplier{0}; // [-3 - 3] + UnitSymbol unit; +}; + struct Notification { FaultCode fault_code; std::optional fault_msg; }; +struct EvseStatus { + uint16_t notification_max_delay{0}; + EvseNotification evse_notification{EvseNotification::None}; +}; + +struct AcEvseStatus : EvseStatus { + bool rcd; +}; + +struct DcEvseStatus : EvseStatus { + std::optional evse_isolation_status; + DcEvseStatusCode evse_status_code; +}; + +struct DcEvStatus { + bool ev_ready; + DcEvErrorCode ev_error_code; + PercentValue ev_ress_soc; +}; + +float from_PhysicalValue(const PhysicalValue& in); +PhysicalValue from_float(const float in, const data_types::UnitSymbol unit); + } // namespace data_types struct Header { diff --git a/lib/everest/iso15118/include/iso15118/message/d2/type.hpp b/lib/everest/iso15118/include/iso15118/message/d2/type.hpp index 7b2511af41..d87872d6f9 100644 --- a/lib/everest/iso15118/include/iso15118/message/d2/type.hpp +++ b/lib/everest/iso15118/include/iso15118/message/d2/type.hpp @@ -14,6 +14,14 @@ enum class Type { SessionSetupRes, AuthorizationReq, AuthorizationRes, + CableCheckReq, + CableCheckRes, + PreChargeReq, + PreChargeRes, + CurrentDemandReq, + CurrentDemandRes, + WeldingDetectionReq, + WeldingDetectionRes }; template struct TypeTrait { @@ -43,6 +51,14 @@ CREATE_TYPE_TRAIT(SessionSetupRequest, SessionSetupReq); CREATE_TYPE_TRAIT(SessionSetupResponse, SessionSetupRes); CREATE_TYPE_TRAIT(AuthorizationRequest, AuthorizationReq); CREATE_TYPE_TRAIT(AuthorizationResponse, AuthorizationRes); +CREATE_TYPE_TRAIT(DC_CableCheckRequest, CableCheckReq); +CREATE_TYPE_TRAIT(DC_CableCheckResponse, CableCheckRes); +CREATE_TYPE_TRAIT(DC_PreChargeRequest, PreChargeReq); +CREATE_TYPE_TRAIT(DC_PreChargeResponse, PreChargeRes); +CREATE_TYPE_TRAIT(DC_CurrentDemandRequest, CurrentDemandReq); +CREATE_TYPE_TRAIT(DC_CurrentDemandResponse, CurrentDemandRes); +CREATE_TYPE_TRAIT(DC_WeldingDetectionRequest, WeldingDetectionReq); +CREATE_TYPE_TRAIT(DC_WeldingDetectionResponse, WeldingDetectionRes); #ifdef CREATE_TYPE_TRAIT_PUSHED #define CREATE_TYPE_TRAIT CREATE_TYPE_TRAIT_PUSHED diff --git a/lib/everest/iso15118/src/iso15118/CMakeLists.txt b/lib/everest/iso15118/src/iso15118/CMakeLists.txt index 8581ec8652..e7e35dadc9 100644 --- a/lib/everest/iso15118/src/iso15118/CMakeLists.txt +++ b/lib/everest/iso15118/src/iso15118/CMakeLists.txt @@ -69,6 +69,10 @@ target_sources(iso15118 message/session_stop.cpp message/d2/authorization.cpp + message/d2/dc_cable_check.cpp + message/d2/dc_current_demand.cpp + message/d2/dc_pre_charge.cpp + message/d2/dc_welding_detection.cpp message/d2/variant.cpp message/d2/msg_data_types.cpp message/d2/session_setup.cpp diff --git a/lib/everest/iso15118/src/iso15118/message/d2/dc_cable_check.cpp b/lib/everest/iso15118/src/iso15118/message/d2/dc_cable_check.cpp new file mode 100644 index 0000000000..3a9f604777 --- /dev/null +++ b/lib/everest/iso15118/src/iso15118/message/d2/dc_cable_check.cpp @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Pionix GmbH and Contributors to EVerest +#include + +#include + +#include +#include + +#include + +namespace iso15118::d2::msg { + +template <> void convert(const struct iso2_CableCheckReqType& in, DC_CableCheckRequest& out) { + convert(in.DC_EVStatus, out.ev_status); +} + +template <> +void insert_type(VariantAccess& va, const struct iso2_CableCheckReqType& in, + const struct iso2_MessageHeaderType& header) { + va.insert_type(in, header); +} + +template <> void convert(const DC_CableCheckResponse& in, struct iso2_CableCheckResType& out) { + init_iso2_CableCheckResType(&out); + + cb_convert_enum(in.response_code, out.ResponseCode); + convert(in.evse_status, out.DC_EVSEStatus); + cb_convert_enum(in.evse_processing, out.EVSEProcessing); +} + +template <> int serialize_to_exi(const DC_CableCheckResponse& in, exi_bitstream_t& out) { + + iso2_exiDocument doc; + init_iso2_exiDocument(&doc); + init_iso2_BodyType(&doc.V2G_Message.Body); + + convert(in.header, doc.V2G_Message.Header); + + CB_SET_USED(doc.V2G_Message.Body.CableCheckRes); + convert(in, doc.V2G_Message.Body.CableCheckRes); + + return encode_iso2_exiDocument(&out, &doc); +} + +template <> size_t serialize(const DC_CableCheckResponse& in, const io::StreamOutputView& out) { + return serialize_helper(in, out); +} + +} // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/src/iso15118/message/d2/dc_current_demand.cpp b/lib/everest/iso15118/src/iso15118/message/d2/dc_current_demand.cpp new file mode 100644 index 0000000000..4e59ec0af6 --- /dev/null +++ b/lib/everest/iso15118/src/iso15118/message/d2/dc_current_demand.cpp @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Pionix GmbH and Contributors to EVerest +#include + +#include + +#include +#include + +#include + +namespace iso15118::d2::msg { + +template <> void convert(const struct iso2_CurrentDemandReqType& in, DC_CurrentDemandRequest& out) { + convert(in.DC_EVStatus, out.ev_status); + convert(in.EVTargetCurrent, out.ev_target_current); + CB2CPP_CONVERT_IF_USED(in.EVMaximumCurrentLimit, out.ev_maximum_current_limit); + CB2CPP_CONVERT_IF_USED(in.EVMaximumVoltageLimit, out.ev_maximum_voltage_limit); + CB2CPP_CONVERT_IF_USED(in.EVMaximumPowerLimit, out.ev_maximum_power_limit); + CB2CPP_ASSIGN_IF_USED(in.BulkChargingComplete, out.bulk_charging_complete); + out.charging_complete = in.ChargingComplete; + CB2CPP_CONVERT_IF_USED(in.RemainingTimeToFullSoC, out.remaining_time_to_full_soc); + CB2CPP_CONVERT_IF_USED(in.RemainingTimeToBulkSoC, out.remaining_time_to_bulk_soc); + convert(in.EVTargetVoltage, out.ev_target_voltage); +} + +template <> +void insert_type(VariantAccess& va, const struct iso2_CurrentDemandReqType& in, + const struct iso2_MessageHeaderType& header) { + va.insert_type(in, header); +} + +template <> void convert(const DC_CurrentDemandResponse& in, struct iso2_CurrentDemandResType& out) { + init_iso2_CurrentDemandResType(&out); + + cb_convert_enum(in.response_code, out.ResponseCode); + convert(in.evse_status, out.DC_EVSEStatus); + convert(in.evse_present_voltage, out.EVSEPresentVoltage); + convert(in.evse_present_current, out.EVSEPresentCurrent); + out.EVSECurrentLimitAchieved = in.evse_current_limit_achieved; + out.EVSEVoltageLimitAchieved = in.evse_voltage_limit_achieved; + out.EVSEPowerLimitAchieved = in.evse_power_limit_achieved; + CPP2CB_CONVERT_IF_USED(in.evse_maximum_voltage_limit, out.EVSEMaximumVoltageLimit); + CPP2CB_CONVERT_IF_USED(in.evse_maximum_current_limit, out.EVSEMaximumCurrentLimit); + CPP2CB_CONVERT_IF_USED(in.evse_maximum_power_limit, out.EVSEMaximumPowerLimit); + CPP2CB_STRING(in.evse_id, out.EVSEID); + out.SAScheduleTupleID = in.sa_schedule_tuple_id; + CPP2CB_CONVERT_IF_USED(in.meter_info, out.MeterInfo); + CPP2CB_ASSIGN_IF_USED(in.receipt_required, out.ReceiptRequired); +} + +template <> int serialize_to_exi(const DC_CurrentDemandResponse& in, exi_bitstream_t& out) { + + iso2_exiDocument doc; + init_iso2_exiDocument(&doc); + init_iso2_BodyType(&doc.V2G_Message.Body); + + convert(in.header, doc.V2G_Message.Header); + + CB_SET_USED(doc.V2G_Message.Body.CurrentDemandRes); + convert(in, doc.V2G_Message.Body.CurrentDemandRes); + + return encode_iso2_exiDocument(&out, &doc); +} + +template <> size_t serialize(const DC_CurrentDemandResponse& in, const io::StreamOutputView& out) { + return serialize_helper(in, out); +} + +} // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/src/iso15118/message/d2/dc_pre_charge.cpp b/lib/everest/iso15118/src/iso15118/message/d2/dc_pre_charge.cpp new file mode 100644 index 0000000000..b2734442bb --- /dev/null +++ b/lib/everest/iso15118/src/iso15118/message/d2/dc_pre_charge.cpp @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Pionix GmbH and Contributors to EVerest +#include + +#include + +#include +#include + +#include + +namespace iso15118::d2::msg { + +template <> void convert(const struct iso2_PreChargeReqType& in, DC_PreChargeRequest& out) { + convert(in.DC_EVStatus, out.ev_status); + convert(in.EVTargetVoltage, out.ev_target_voltage); + convert(in.EVTargetCurrent, out.ev_target_current); +} + +template <> +void insert_type(VariantAccess& va, const struct iso2_PreChargeReqType& in, + const struct iso2_MessageHeaderType& header) { + va.insert_type(in, header); +} + +template <> void convert(const DC_PreChargeResponse& in, struct iso2_PreChargeResType& out) { + init_iso2_PreChargeResType(&out); + + cb_convert_enum(in.response_code, out.ResponseCode); + convert(in.evse_status, out.DC_EVSEStatus); + convert(in.evse_present_voltage, out.EVSEPresentVoltage); +} + +template <> int serialize_to_exi(const DC_PreChargeResponse& in, exi_bitstream_t& out) { + + iso2_exiDocument doc; + init_iso2_exiDocument(&doc); + init_iso2_BodyType(&doc.V2G_Message.Body); + + convert(in.header, doc.V2G_Message.Header); + + CB_SET_USED(doc.V2G_Message.Body.PreChargeRes); + convert(in, doc.V2G_Message.Body.PreChargeRes); + + return encode_iso2_exiDocument(&out, &doc); +} + +template <> size_t serialize(const DC_PreChargeResponse& in, const io::StreamOutputView& out) { + return serialize_helper(in, out); +} + +} // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/src/iso15118/message/d2/dc_welding_detection.cpp b/lib/everest/iso15118/src/iso15118/message/d2/dc_welding_detection.cpp new file mode 100644 index 0000000000..3390495f41 --- /dev/null +++ b/lib/everest/iso15118/src/iso15118/message/d2/dc_welding_detection.cpp @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Pionix GmbH and Contributors to EVerest +#include + +#include + +#include +#include + +#include + +namespace iso15118::d2::msg { + +template <> void convert(const struct iso2_WeldingDetectionReqType& in, DC_WeldingDetectionRequest& out) { + convert(in.DC_EVStatus, out.ev_status); +} + +template <> +void insert_type(VariantAccess& va, const struct iso2_WeldingDetectionReqType& in, + const struct iso2_MessageHeaderType& header) { + va.insert_type(in, header); +} + +template <> void convert(const DC_WeldingDetectionResponse& in, struct iso2_WeldingDetectionResType& out) { + init_iso2_WeldingDetectionResType(&out); + + cb_convert_enum(in.response_code, out.ResponseCode); + convert(in.evse_status, out.DC_EVSEStatus); + convert(in.evse_present_voltage, out.EVSEPresentVoltage); +} + +template <> int serialize_to_exi(const DC_WeldingDetectionResponse& in, exi_bitstream_t& out) { + + iso2_exiDocument doc; + init_iso2_exiDocument(&doc); + init_iso2_BodyType(&doc.V2G_Message.Body); + + convert(in.header, doc.V2G_Message.Header); + + CB_SET_USED(doc.V2G_Message.Body.WeldingDetectionRes); + convert(in, doc.V2G_Message.Body.WeldingDetectionRes); + + return encode_iso2_exiDocument(&out, &doc); +} + +template <> size_t serialize(const DC_WeldingDetectionResponse& in, const io::StreamOutputView& out) { + return serialize_helper(in, out); +} + +} // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/src/iso15118/message/d2/msg_data_types.cpp b/lib/everest/iso15118/src/iso15118/message/d2/msg_data_types.cpp index a45dd7f6d3..6bcc9f6d00 100644 --- a/lib/everest/iso15118/src/iso15118/message/d2/msg_data_types.cpp +++ b/lib/everest/iso15118/src/iso15118/message/d2/msg_data_types.cpp @@ -1,5 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2025 Pionix GmbH and Contributors to EVerest +#include #include @@ -7,6 +8,28 @@ namespace iso15118::d2::msg { +namespace data_types { + +float from_PhysicalValue(const PhysicalValue& in) { + return in.value * pow(10, in.multiplier); +} + +PhysicalValue from_float(const float in, const data_types::UnitSymbol unit) { + PhysicalValue out; + out.unit = unit; + if (in == 0.0) { + out.multiplier = 0; + out.value = 0; + return out; + } + out.multiplier = static_cast(floor(log10(fabs(in)))); + out.multiplier -= 3; // add 3 digits of precision + out.value = static_cast(in * pow(10, -out.multiplier)); + return out; +} + +}; // namespace data_types + void convert(const iso2_NotificationType& in, data_types::Notification& out) { cb_convert_enum(in.FaultCode, out.fault_code); CB2CPP_STRING_IF_USED(in.FaultMsg, out.fault_msg); @@ -28,4 +51,61 @@ void convert(const Header& in, struct iso2_MessageHeaderType& out) { CPP2CB_CONVERT_IF_USED(in.notification, out.Notification); } +template <> void convert(const iso2_MeterInfoType& in, data_types::MeterInfo& out) { + out.meter_id = CB2CPP_STRING(in.MeterID); + CB2CPP_ASSIGN_IF_USED(in.MeterReading, out.meter_reading); + CB2CPP_BYTES_IF_USED(in.SigMeterReading, out.sig_meter_reading); + CB2CPP_ASSIGN_IF_USED(in.MeterStatus, out.meter_status); + CB2CPP_ASSIGN_IF_USED(in.TMeter, out.t_meter); +} + +template <> void convert(const data_types::MeterInfo& in, iso2_MeterInfoType& out) { + init_iso2_MeterInfoType(&out); + + CPP2CB_STRING(in.meter_id, out.MeterID); + CPP2CB_ASSIGN_IF_USED(in.meter_reading, out.MeterReading); + if (in.sig_meter_reading) { + CPP2CB_BYTES(in.sig_meter_reading.value(), out.SigMeterReading); + CB_SET_USED(out.SigMeterReading); + } + CPP2CB_ASSIGN_IF_USED(in.meter_status, out.MeterStatus); + CPP2CB_ASSIGN_IF_USED(in.t_meter, out.TMeter); +} + +template <> void convert(const iso2_DC_EVStatusType& in, data_types::DcEvStatus& out) { + out.ev_ready = in.EVReady; + cb_convert_enum(in.EVErrorCode, out.ev_error_code); + out.ev_ress_soc = in.EVRESSSOC; +} + +template <> void convert(const data_types::AcEvseStatus& in, iso2_AC_EVSEStatusType& out) { + init_iso2_AC_EVSEStatusType(&out); + cb_convert_enum(in.evse_notification, out.EVSENotification); + out.NotificationMaxDelay = in.notification_max_delay; + out.RCD = in.rcd; +} + +template <> void convert(const data_types::DcEvseStatus& in, iso2_DC_EVSEStatusType& out) { + init_iso2_DC_EVSEStatusType(&out); + cb_convert_enum(in.evse_notification, out.EVSENotification); + out.NotificationMaxDelay = in.notification_max_delay; + if (in.evse_isolation_status) { + cb_convert_enum(in.evse_isolation_status.value(), out.EVSEIsolationStatus); + CB_SET_USED(out.EVSEIsolationStatus); + } + cb_convert_enum(in.evse_status_code, out.EVSEStatusCode); +} + +template <> void convert(const iso2_PhysicalValueType& in, data_types::PhysicalValue& out) { + out.multiplier = in.Multiplier; + out.value = in.Value; + cb_convert_enum(in.Unit, out.unit); +} + +template <> void convert(const data_types::PhysicalValue& in, iso2_PhysicalValueType& out) { + out.Multiplier = in.multiplier; + out.Value = in.value; + cb_convert_enum(in.unit, out.Unit); +} + } // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/src/iso15118/message/d2/variant.cpp b/lib/everest/iso15118/src/iso15118/message/d2/variant.cpp index 0fd4dc6936..e82d8a5b14 100644 --- a/lib/everest/iso15118/src/iso15118/message/d2/variant.cpp +++ b/lib/everest/iso15118/src/iso15118/message/d2/variant.cpp @@ -47,6 +47,14 @@ void handle_v2g(VariantAccess& va) { insert_type(va, doc.V2G_Message.Body.SessionSetupReq, doc.V2G_Message.Header); } else if (doc.V2G_Message.Body.AuthorizationReq_isUsed) { insert_type(va, doc.V2G_Message.Body.AuthorizationReq, doc.V2G_Message.Header); + } else if (doc.V2G_Message.Body.CableCheckReq_isUsed) { + insert_type(va, doc.V2G_Message.Body.CableCheckReq, doc.V2G_Message.Header); + } else if (doc.V2G_Message.Body.CurrentDemandReq_isUsed) { + insert_type(va, doc.V2G_Message.Body.CurrentDemandReq, doc.V2G_Message.Header); + } else if (doc.V2G_Message.Body.PreChargeReq_isUsed) { + insert_type(va, doc.V2G_Message.Body.PreChargeReq, doc.V2G_Message.Header); + } else if (doc.V2G_Message.Body.WeldingDetectionReq_isUsed) { + insert_type(va, doc.V2G_Message.Body.WeldingDetectionReq, doc.V2G_Message.Header); } else { va.error = "chosen message type unhandled"; } diff --git a/lib/everest/iso15118/test/exi/cb/iso2/CMakeLists.txt b/lib/everest/iso15118/test/exi/cb/iso2/CMakeLists.txt index 93d498acb1..6a9b91885b 100644 --- a/lib/everest/iso15118/test/exi/cb/iso2/CMakeLists.txt +++ b/lib/everest/iso15118/test/exi/cb/iso2/CMakeLists.txt @@ -10,6 +10,10 @@ function(create_exi_test_target NAME) catch_discover_tests(test_exi_d2_${NAME}) endfunction() +create_exi_test_target(dc_cable_check) +create_exi_test_target(dc_current_demand) +create_exi_test_target(dc_pre_charge) +create_exi_test_target(dc_welding_detection) create_exi_test_target(session_setup) create_exi_test_target(authorization) diff --git a/lib/everest/iso15118/test/exi/cb/iso2/dc_cable_check.cpp b/lib/everest/iso15118/test/exi/cb/iso2/dc_cable_check.cpp new file mode 100644 index 0000000000..fb29b836cd --- /dev/null +++ b/lib/everest/iso15118/test/exi/cb/iso2/dc_cable_check.cpp @@ -0,0 +1,55 @@ +#include + +#include +#include +#include + +#include "helper.hpp" + +#include + +using namespace iso15118; +namespace dt = d2::msg::data_types; + +SCENARIO("Ser/Deserialize d2 cable check messages") { + GIVEN("Deserialize cable check req") { + uint8_t doc_raw[] = {0x80, 0x98, 0x2, 0x0, 0xb6, 0xc8, 0x81, 0xce, 0xc2, 0x13, 0x4b, 0x50, 0x31, 0x0, 0x0, 0x0}; + + const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)}; + + d2::msg::Variant variant(io::v2gtp::PayloadType::SAP, stream_view, false); + + THEN("It should be deserialized successfully") { + REQUIRE(variant.get_type() == d2::msg::Type::CableCheckReq); + + const auto& msg = variant.get(); + const auto& header = msg.header; + + REQUIRE(header.session_id == std::array{0x02, 0xDB, 0x22, 0x07, 0x3B, 0x08, 0x4D, 0x2D}); + + REQUIRE(msg.ev_status.ev_ready == true); + REQUIRE(msg.ev_status.ev_error_code == dt::DcEvErrorCode::NO_ERROR); + REQUIRE(msg.ev_status.ev_ress_soc == 0); + } + } + GIVEN("Serialize cable check res") { + + const auto header = d2::msg::Header{{0x02, 0xDB, 0x22, 0x07, 0x3B, 0x08, 0x4D, 0x2D}, std::nullopt}; + + auto res = d2::msg::DC_CableCheckResponse{}; + res.header = header; + res.response_code = dt::ResponseCode::OK; + auto status = dt::DcEvseStatus{}; + status.evse_isolation_status = dt::IsolationLevel::Invalid; + status.evse_status_code = dt::DcEvseStatusCode::EVSE_IsolationMonitoringActive; + res.evse_status = status; + res.evse_processing = dt::EvseProcessing::Ongoing; + + std::vector expected = {0x80, 0x98, 0x2, 0x0, 0xb6, 0xc8, 0x81, 0xce, 0xc2, 0x13, + 0x4b, 0x50, 0x40, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0}; + + THEN("It should be serialized successfully") { + REQUIRE(serialize_helper(res) == expected); + } + } +} diff --git a/lib/everest/iso15118/test/exi/cb/iso2/dc_current_demand.cpp b/lib/everest/iso15118/test/exi/cb/iso2/dc_current_demand.cpp new file mode 100644 index 0000000000..2605fa8eee --- /dev/null +++ b/lib/everest/iso15118/test/exi/cb/iso2/dc_current_demand.cpp @@ -0,0 +1,156 @@ +#include + +#include +#include +#include + +#include "helper.hpp" + +#include + +using namespace iso15118; +namespace dt = d2::msg::data_types; + +SCENARIO("Ser/Deserialize d2 current demand messages") { + GIVEN("Deserialize current demand req - minimal") { + uint8_t doc_raw[] = {0x80, 0x98, 0x02, 0x00, 0xB6, 0xC8, 0x81, 0xCE, 0xC2, 0x13, 0x4B, 0x50, 0xD1, + 0x00, 0x28, 0x01, 0x06, 0x19, 0xA0, 0x24, 0x50, 0x84, 0x0E, 0x31, 0xB0}; + + const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)}; + + d2::msg::Variant variant(io::v2gtp::PayloadType::SAP, stream_view, false); + + THEN("It should be deserialized successfully") { + REQUIRE(variant.get_type() == d2::msg::Type::CurrentDemandReq); + + const auto& msg = variant.get(); + const auto& header = msg.header; + + REQUIRE(header.session_id == std::array{0x02, 0xDB, 0x22, 0x07, 0x3B, 0x08, 0x4D, 0x2D}); + + REQUIRE(msg.ev_status.ev_ready == true); + REQUIRE(msg.ev_status.ev_error_code == dt::DcEvErrorCode::NO_ERROR); + REQUIRE(msg.ev_status.ev_ress_soc == 80); + + REQUIRE(dt::from_PhysicalValue(msg.ev_target_current) == 20.5); + REQUIRE(msg.ev_maximum_voltage_limit == std::nullopt); + REQUIRE(msg.ev_maximum_current_limit == std::nullopt); + REQUIRE(msg.ev_maximum_power_limit == std::nullopt); + REQUIRE(msg.bulk_charging_complete == std::nullopt); + REQUIRE(msg.charging_complete == true); + REQUIRE(msg.remaining_time_to_full_soc == std::nullopt); + REQUIRE(msg.remaining_time_to_bulk_soc == std::nullopt); + REQUIRE(dt::from_PhysicalValue(msg.ev_target_voltage) == 355.5); + } + } + GIVEN("Deserialize current demand req - all fields present") { + uint8_t doc_raw[] = {0x80, 0x98, 0x02, 0x00, 0xB6, 0xC8, 0x81, 0xCE, 0xC2, 0x13, 0x4B, 0x50, + 0xD1, 0x00, 0x28, 0x01, 0x06, 0x19, 0xA0, 0x20, 0x18, 0x81, 0xD0, 0x0E, + 0x01, 0x86, 0x19, 0x00, 0x20, 0x31, 0x42, 0x41, 0x38, 0x11, 0x04, 0x08, + 0x3C, 0x0B, 0x80, 0x81, 0x05, 0x00, 0xF8, 0x10, 0x81, 0xC6, 0x36}; + + const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)}; + + d2::msg::Variant variant(io::v2gtp::PayloadType::SAP, stream_view, false); + + THEN("It should be deserialized successfully") { + REQUIRE(variant.get_type() == d2::msg::Type::CurrentDemandReq); + + const auto& msg = variant.get(); + const auto& header = msg.header; + + REQUIRE(header.session_id == std::array{0x02, 0xDB, 0x22, 0x07, 0x3B, 0x08, 0x4D, 0x2D}); + + REQUIRE(msg.ev_status.ev_ready == true); + REQUIRE(msg.ev_status.ev_error_code == dt::DcEvErrorCode::NO_ERROR); + REQUIRE(msg.ev_status.ev_ress_soc == 80); + + REQUIRE(dt::from_PhysicalValue(msg.ev_target_current) == 20.5); + REQUIRE(msg.ev_maximum_voltage_limit.has_value()); + REQUIRE(dt::from_PhysicalValue(msg.ev_maximum_voltage_limit.value()) == 1000); + REQUIRE(msg.ev_maximum_current_limit.has_value()); + REQUIRE(dt::from_PhysicalValue(msg.ev_maximum_current_limit.value()) == 200); + REQUIRE(msg.ev_maximum_power_limit.has_value()); + REQUIRE(dt::from_PhysicalValue(msg.ev_maximum_power_limit.value()) == 10000); + REQUIRE(msg.bulk_charging_complete.has_value()); + REQUIRE(msg.bulk_charging_complete.value() == true); + REQUIRE(msg.charging_complete.has_value()); + REQUIRE(msg.charging_complete.value() == true); + REQUIRE(msg.remaining_time_to_bulk_soc.has_value()); + REQUIRE(dt::from_PhysicalValue(msg.remaining_time_to_full_soc.value()) == 60000); + REQUIRE(msg.remaining_time_to_full_soc.has_value()); + REQUIRE(dt::from_PhysicalValue(msg.remaining_time_to_bulk_soc.value()) == 40000); + REQUIRE(dt::from_PhysicalValue(msg.ev_target_voltage) == 355.5); + } + } + GIVEN("Serialize current demand res - minimal") { + + const auto header = d2::msg::Header{{0x02, 0xDB, 0x22, 0x07, 0x3B, 0x08, 0x4D, 0x2D}, std::nullopt}; + + auto res = d2::msg::DC_CurrentDemandResponse{}; + res.header = header; + res.response_code = dt::ResponseCode::OK; + auto status = dt::DcEvseStatus{}; + status.evse_isolation_status = dt::IsolationLevel::Valid; + status.evse_status_code = dt::DcEvseStatusCode::EVSE_Ready; + res.evse_status = status; + res.evse_present_voltage = dt::from_float(300.5, d2::msg::data_types::UnitSymbol::V); + res.evse_present_current = dt::from_float(5.5, d2::msg::data_types::UnitSymbol::A); + res.evse_current_limit_achieved = true; + res.evse_voltage_limit_achieved = true; + res.evse_power_limit_achieved = true; + res.evse_id = "000000"; + res.sa_schedule_tuple_id = 3; + + std::vector expected = {0x80, 0x98, 0x02, 0x00, 0xB6, 0xC8, 0x81, 0xCE, 0xC2, 0x13, 0x4B, 0x50, 0xE0, + 0x00, 0x00, 0x00, 0x20, 0x40, 0x84, 0x0B, 0xD1, 0x70, 0x00, 0xC3, 0xF0, 0xA8, + 0x22, 0x26, 0x08, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x48}; + + THEN("It should be serialized successfully") { + REQUIRE(serialize_helper(res) == expected); + } + } + GIVEN("Serialize current demand res - all fields present") { + + const auto header = d2::msg::Header{{0x02, 0xDB, 0x22, 0x07, 0x3B, 0x08, 0x4D, 0x2D}, std::nullopt}; + + auto res = d2::msg::DC_CurrentDemandResponse{}; + res.header = header; + res.response_code = dt::ResponseCode::OK; + auto status = dt::DcEvseStatus{}; + status.evse_isolation_status = dt::IsolationLevel::Valid; + status.evse_status_code = dt::DcEvseStatusCode::EVSE_Ready; + res.evse_status = status; + res.evse_present_voltage = dt::from_float(300.5, d2::msg::data_types::UnitSymbol::V); + res.evse_present_current = dt::from_float(5.5, d2::msg::data_types::UnitSymbol::A); + res.evse_current_limit_achieved = true; + res.evse_voltage_limit_achieved = true; + res.evse_power_limit_achieved = true; + res.evse_id = "000000"; + res.sa_schedule_tuple_id = 3; + res.evse_maximum_voltage_limit = dt::from_float(1000, d2::msg::data_types::UnitSymbol::V); + res.evse_maximum_current_limit = dt::from_float(100, d2::msg::data_types::UnitSymbol::A); + res.evse_maximum_power_limit = dt::from_float(100000, d2::msg::data_types::UnitSymbol::W); + auto meterInfo = dt::MeterInfo{}; + meterInfo.meter_id = "FOO_METER"; + meterInfo.meter_reading = 999; + meterInfo.sig_meter_reading = std::vector{ + 0xAA, + 0xBB, + }; + meterInfo.meter_status = 444; + meterInfo.t_meter = 100; + res.meter_info = meterInfo; + res.receipt_required = true; + + std::vector expected = { + 0x80, 0x98, 0x02, 0x00, 0xB6, 0xC8, 0x81, 0xCE, 0xC2, 0x13, 0x4B, 0x50, 0xE0, 0x00, 0x00, 0x00, 0x20, 0x40, + 0x84, 0x0B, 0xD1, 0x70, 0x00, 0xC3, 0xF0, 0xA8, 0x22, 0x20, 0x31, 0x03, 0xA0, 0x1C, 0x04, 0x18, 0x74, 0x03, + 0x81, 0x45, 0x0E, 0x80, 0x70, 0x08, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x40, 0x0B, 0x46, 0x4F, 0x4F, + 0x5F, 0x4D, 0x45, 0x54, 0x45, 0x52, 0x07, 0x38, 0x38, 0x00, 0xAA, 0xAE, 0xC1, 0x78, 0x06, 0x06, 0x40, 0x40}; + + THEN("It should be serialized successfully") { + REQUIRE(serialize_helper(res) == expected); // FIXME: Cannot get this to work. + } + } +} diff --git a/lib/everest/iso15118/test/exi/cb/iso2/dc_pre_charge.cpp b/lib/everest/iso15118/test/exi/cb/iso2/dc_pre_charge.cpp new file mode 100644 index 0000000000..0e3cfe1c8d --- /dev/null +++ b/lib/everest/iso15118/test/exi/cb/iso2/dc_pre_charge.cpp @@ -0,0 +1,59 @@ +#include + +#include +#include +#include + +#include "helper.hpp" + +#include + +using namespace iso15118; +namespace dt = d2::msg::data_types; + +SCENARIO("Ser/Deserialize d2 pre charge messages") { + GIVEN("Deserialize pre charge req") { + uint8_t doc_raw[] = {0x80, 0x98, 0x2, 0x0, 0xb6, 0xc8, 0x81, 0xce, 0xc2, 0x13, 0x4b, 0x51, + 0x71, 0x0, 0x0, 0x2, 0x88, 0x0, 0xa0, 0x61, 0x80, 0x0, 0x0}; + + const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)}; + + d2::msg::Variant variant(io::v2gtp::PayloadType::SAP, stream_view, false); + + THEN("It should be deserialized successfully") { + REQUIRE(variant.get_type() == d2::msg::Type::PreChargeReq); + + const auto& msg = variant.get(); + const auto& header = msg.header; + + REQUIRE(header.session_id == std::array{0x02, 0xDB, 0x22, 0x07, 0x3B, 0x08, 0x4D, 0x2D}); + + REQUIRE(msg.ev_status.ev_ready == true); + REQUIRE(msg.ev_status.ev_error_code == dt::DcEvErrorCode::NO_ERROR); + REQUIRE(msg.ev_status.ev_ress_soc == 0); + + REQUIRE(dt::from_PhysicalValue(msg.ev_target_voltage) == 500); + REQUIRE(dt::from_PhysicalValue(msg.ev_target_current) == 0); + } + } + GIVEN("Serialize pre charge res") { + + const auto header = d2::msg::Header{{0x02, 0xDB, 0x22, 0x07, 0x3B, 0x08, 0x4D, 0x2D}, std::nullopt}; + + auto res = d2::msg::DC_PreChargeResponse{}; + res.header = header; + res.response_code = dt::ResponseCode::OK; + auto status = dt::DcEvseStatus{}; + status.evse_isolation_status = dt::IsolationLevel::Valid; + status.evse_status_code = dt::DcEvseStatusCode::EVSE_Ready; + res.evse_status = status; + res.evse_present_voltage = dt::from_float(498.5, d2::msg::data_types::UnitSymbol::V); + + std::vector expected = {0x80, 0x98, 0x2, 0x0, 0xb6, 0xc8, 0x81, 0xce, 0xc2, 0x13, 0x4b, 0x51, + 0x80, 0x0, 0x0, 0x0, 0x20, 0x40, 0x84, 0xf, 0x92, 0x60, 0x0}; + + THEN("It should be serialized successfully") { + REQUIRE(serialize_helper(res) == expected); + } + } +} diff --git a/lib/everest/iso15118/test/exi/cb/iso2/dc_welding_detection.cpp b/lib/everest/iso15118/test/exi/cb/iso2/dc_welding_detection.cpp new file mode 100644 index 0000000000..84f22424fa --- /dev/null +++ b/lib/everest/iso15118/test/exi/cb/iso2/dc_welding_detection.cpp @@ -0,0 +1,57 @@ +#include + +#include +#include +#include + +#include "helper.hpp" + +#include +#include + +using namespace iso15118; +namespace dt = d2::msg::data_types; + +SCENARIO("Ser/Deserialize d2 welding detection messages") { + GIVEN("Deserialize welding detection req - minimal") { + uint8_t doc_raw[] = {0x80, 0x98, 0x02, 0x00, 0xB6, 0xC8, 0x81, 0xCE, + 0xC2, 0x13, 0x4B, 0x52, 0x11, 0x00, 0x00, 0x00}; + + const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)}; + + d2::msg::Variant variant(io::v2gtp::PayloadType::SAP, stream_view, false); + + THEN("It should be deserialized successfully") { + REQUIRE(variant.get_type() == d2::msg::Type::WeldingDetectionReq); + + const auto& msg = variant.get(); + const auto& header = msg.header; + + REQUIRE(header.session_id == std::array{0x02, 0xDB, 0x22, 0x07, 0x3B, 0x08, 0x4D, 0x2D}); + + REQUIRE(msg.ev_status.ev_ready == true); + REQUIRE(msg.ev_status.ev_error_code == dt::DcEvErrorCode::NO_ERROR); + REQUIRE(msg.ev_status.ev_ress_soc == 0); + } + } + GIVEN("Serialize welding detection res") { + + const auto header = d2::msg::Header{{0x02, 0xDB, 0x22, 0x07, 0x3B, 0x08, 0x4D, 0x2D}, std::nullopt}; + + auto res = d2::msg::DC_WeldingDetectionResponse{}; + res.header = header; + res.response_code = dt::ResponseCode::OK; + auto status = dt::DcEvseStatus{}; + status.evse_isolation_status = std::nullopt; + status.evse_status_code = dt::DcEvseStatusCode::EVSE_Ready; + res.evse_status = status; + res.evse_present_voltage = dt::from_float(50, d2::msg::data_types::UnitSymbol::V); + + std::vector expected = {0x80, 0x98, 0x02, 0x00, 0xB6, 0xC8, 0x81, 0xCE, 0xC2, 0x13, 0x4B, + 0x52, 0x20, 0x00, 0x00, 0x02, 0x10, 0x11, 0x02, 0x20, 0x9C, 0x00}; + + THEN("It should be serialized successfully") { + REQUIRE(serialize_helper(res) == expected); + } + } +} From da4fbc700a3f77a0a4b944db3ef38171270592e2 Mon Sep 17 00:00:00 2001 From: Kacper Dalach Date: Fri, 16 Jan 2026 12:32:47 +0100 Subject: [PATCH 5/5] libiso15118: d2: Add SessionStop (#1729) Signed-off-by: Kacper Dalach --- .../iso15118/message/d2/session_stop.hpp | 26 ++++++++++ .../include/iso15118/message/d2/type.hpp | 4 ++ .../iso15118/src/iso15118/CMakeLists.txt | 1 + .../src/iso15118/message/d2/session_stop.cpp | 48 +++++++++++++++++++ .../src/iso15118/message/d2/variant.cpp | 2 + .../iso15118/test/exi/cb/iso2/CMakeLists.txt | 1 + .../test/exi/cb/iso2/session_stop.cpp | 48 +++++++++++++++++++ 7 files changed, 130 insertions(+) create mode 100644 lib/everest/iso15118/include/iso15118/message/d2/session_stop.hpp create mode 100644 lib/everest/iso15118/src/iso15118/message/d2/session_stop.cpp create mode 100644 lib/everest/iso15118/test/exi/cb/iso2/session_stop.cpp diff --git a/lib/everest/iso15118/include/iso15118/message/d2/session_stop.hpp b/lib/everest/iso15118/include/iso15118/message/d2/session_stop.hpp new file mode 100644 index 0000000000..574fabf067 --- /dev/null +++ b/lib/everest/iso15118/include/iso15118/message/d2/session_stop.hpp @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2026 Pionix GmbH and Contributors to EVerest +#pragma once + +#include + +namespace iso15118::d2::msg { + +namespace data_types { +enum ChargingSession { + Terminate, + Pause +}; +}; // namespace data_types + +struct SessionStopRequest { + Header header; + data_types::ChargingSession charging_session; +}; + +struct SessionStopResponse { + Header header; + data_types::ResponseCode response_code; +}; + +} // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/include/iso15118/message/d2/type.hpp b/lib/everest/iso15118/include/iso15118/message/d2/type.hpp index d87872d6f9..ba172824fa 100644 --- a/lib/everest/iso15118/include/iso15118/message/d2/type.hpp +++ b/lib/everest/iso15118/include/iso15118/message/d2/type.hpp @@ -14,6 +14,8 @@ enum class Type { SessionSetupRes, AuthorizationReq, AuthorizationRes, + SessionStopReq, + SessionStopRes, CableCheckReq, CableCheckRes, PreChargeReq, @@ -51,6 +53,8 @@ CREATE_TYPE_TRAIT(SessionSetupRequest, SessionSetupReq); CREATE_TYPE_TRAIT(SessionSetupResponse, SessionSetupRes); CREATE_TYPE_TRAIT(AuthorizationRequest, AuthorizationReq); CREATE_TYPE_TRAIT(AuthorizationResponse, AuthorizationRes); +CREATE_TYPE_TRAIT(SessionStopRequest, SessionStopReq); +CREATE_TYPE_TRAIT(SessionStopResponse, SessionStopRes); CREATE_TYPE_TRAIT(DC_CableCheckRequest, CableCheckReq); CREATE_TYPE_TRAIT(DC_CableCheckResponse, CableCheckRes); CREATE_TYPE_TRAIT(DC_PreChargeRequest, PreChargeReq); diff --git a/lib/everest/iso15118/src/iso15118/CMakeLists.txt b/lib/everest/iso15118/src/iso15118/CMakeLists.txt index e7e35dadc9..4bfbb16d33 100644 --- a/lib/everest/iso15118/src/iso15118/CMakeLists.txt +++ b/lib/everest/iso15118/src/iso15118/CMakeLists.txt @@ -76,6 +76,7 @@ target_sources(iso15118 message/d2/variant.cpp message/d2/msg_data_types.cpp message/d2/session_setup.cpp + message/d2/session_stop.cpp tbd_controller.cpp ) diff --git a/lib/everest/iso15118/src/iso15118/message/d2/session_stop.cpp b/lib/everest/iso15118/src/iso15118/message/d2/session_stop.cpp new file mode 100644 index 0000000000..ccd92d3393 --- /dev/null +++ b/lib/everest/iso15118/src/iso15118/message/d2/session_stop.cpp @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2026 Pionix GmbH and Contributors to EVerest +#include + +#include + +#include +#include + +#include + +namespace iso15118::d2::msg { + +template <> void convert(const struct iso2_SessionStopReqType& in, SessionStopRequest& out) { + cb_convert_enum(in.ChargingSession, out.charging_session); +} + +template <> +void insert_type(VariantAccess& va, const struct iso2_SessionStopReqType& in, + const struct iso2_MessageHeaderType& header) { + va.insert_type(in, header); +} + +template <> void convert(const SessionStopResponse& in, struct iso2_SessionStopResType& out) { + init_iso2_SessionStopResType(&out); + + cb_convert_enum(in.response_code, out.ResponseCode); +} + +template <> int serialize_to_exi(const SessionStopResponse& in, exi_bitstream_t& out) { + + iso2_exiDocument doc; + init_iso2_exiDocument(&doc); + init_iso2_BodyType(&doc.V2G_Message.Body); + + convert(in.header, doc.V2G_Message.Header); + + CB_SET_USED(doc.V2G_Message.Body.SessionStopRes); + convert(in, doc.V2G_Message.Body.SessionStopRes); + + return encode_iso2_exiDocument(&out, &doc); +} + +template <> size_t serialize(const SessionStopResponse& in, const io::StreamOutputView& out) { + return serialize_helper(in, out); +} + +} // namespace iso15118::d2::msg diff --git a/lib/everest/iso15118/src/iso15118/message/d2/variant.cpp b/lib/everest/iso15118/src/iso15118/message/d2/variant.cpp index e82d8a5b14..f3a2c14420 100644 --- a/lib/everest/iso15118/src/iso15118/message/d2/variant.cpp +++ b/lib/everest/iso15118/src/iso15118/message/d2/variant.cpp @@ -47,6 +47,8 @@ void handle_v2g(VariantAccess& va) { insert_type(va, doc.V2G_Message.Body.SessionSetupReq, doc.V2G_Message.Header); } else if (doc.V2G_Message.Body.AuthorizationReq_isUsed) { insert_type(va, doc.V2G_Message.Body.AuthorizationReq, doc.V2G_Message.Header); + } else if (doc.V2G_Message.Body.SessionStopReq_isUsed) { + insert_type(va, doc.V2G_Message.Body.SessionStopReq, doc.V2G_Message.Header); } else if (doc.V2G_Message.Body.CableCheckReq_isUsed) { insert_type(va, doc.V2G_Message.Body.CableCheckReq, doc.V2G_Message.Header); } else if (doc.V2G_Message.Body.CurrentDemandReq_isUsed) { diff --git a/lib/everest/iso15118/test/exi/cb/iso2/CMakeLists.txt b/lib/everest/iso15118/test/exi/cb/iso2/CMakeLists.txt index 6a9b91885b..79ee048df0 100644 --- a/lib/everest/iso15118/test/exi/cb/iso2/CMakeLists.txt +++ b/lib/everest/iso15118/test/exi/cb/iso2/CMakeLists.txt @@ -16,4 +16,5 @@ create_exi_test_target(dc_pre_charge) create_exi_test_target(dc_welding_detection) create_exi_test_target(session_setup) create_exi_test_target(authorization) +create_exi_test_target(session_stop) diff --git a/lib/everest/iso15118/test/exi/cb/iso2/session_stop.cpp b/lib/everest/iso15118/test/exi/cb/iso2/session_stop.cpp new file mode 100644 index 0000000000..d84c6778a2 --- /dev/null +++ b/lib/everest/iso15118/test/exi/cb/iso2/session_stop.cpp @@ -0,0 +1,48 @@ +#include + +#include +#include +#include + +#include "helper.hpp" + +using namespace iso15118; +namespace dt = d2::msg::data_types; + +SCENARIO("Ser/Deserialize d2 session stop messages") { + GIVEN("Deserialize session stop req") { + + uint8_t doc_raw[] = {0x80, 0x98, 0x02, 0x00, 0xB6, 0xC8, 0x81, 0xCE, 0xC2, 0x13, 0x4B, 0x51, 0xF0, 0x00}; + + const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)}; + + d2::msg::Variant variant(io::v2gtp::PayloadType::SAP, stream_view, false); + + THEN("It should be deserialized successfully") { + REQUIRE(variant.get_type() == d2::msg::Type::SessionStopReq); + + const auto& msg = variant.get(); + const auto& header = msg.header; + + REQUIRE(header.session_id == std::array{0x02, 0xDB, 0x22, 0x07, 0x3B, 0x08, 0x4D, 0x2D}); + + REQUIRE(msg.charging_session == dt::ChargingSession::Terminate); + } + } + GIVEN("Serialize session stop res") { + + const auto header = d2::msg::Header{{0x02, 0xDB, 0x22, 0x07, 0x3B, 0x08, 0x4D, 0x2D}, std::nullopt}; + + const auto res = d2::msg::SessionStopResponse{ + header, + d2::msg::data_types::ResponseCode::OK, + }; + + std::vector expected = {0x80, 0x98, 0x02, 0x00, 0xB6, 0xC8, 0x81, + 0xCE, 0xC2, 0x13, 0x4B, 0x52, 0x00, 0x00}; + + THEN("It should be serialized successfully") { + REQUIRE(serialize_helper(res) == expected); + } + } +}