Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1) - OpenOCPP extracted (firmware OCPP 1.6J/2.0.1) - ShapeShifter library installed (pip install -e) - ShapeShifter specification extracted - EVerest extracted TODO updated with progress
This commit is contained in:
24
tools/EVerest-main/lib/everest/iso15118/test/CMakeLists.txt
Normal file
24
tools/EVerest-main/lib/everest/iso15118/test/CMakeLists.txt
Normal file
@@ -0,0 +1,24 @@
|
||||
add_compile_options(${ISO15118_COMPILE_OPTIONS_WARNING})
|
||||
list(APPEND CMAKE_MODULE_PATH ${CPM_PACKAGE_catch2_SOURCE_DIR}/extras)
|
||||
|
||||
add_subdirectory(exi)
|
||||
add_subdirectory(iso15118)
|
||||
add_subdirectory(v2gtp)
|
||||
|
||||
|
||||
#
|
||||
# code coverage specifics
|
||||
#
|
||||
|
||||
evc_include(CodeCoverage)
|
||||
append_coverage_compiler_flags_to_target(iso15118)
|
||||
|
||||
setup_target_for_coverage_gcovr_html(
|
||||
NAME ${PROJECT_NAME}_gcovr_coverage
|
||||
EXECUTABLE ctest
|
||||
)
|
||||
|
||||
setup_target_for_coverage_gcovr_xml(
|
||||
NAME ${PROJECT_NAME}_gcovr_coverage_xml
|
||||
EXECUTABLE ctest
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
add_subdirectory(cb)
|
||||
@@ -0,0 +1,2 @@
|
||||
add_subdirectory(app_hand)
|
||||
add_subdirectory(iso20)
|
||||
@@ -0,0 +1,10 @@
|
||||
add_executable(test_app_hand app_hand.cpp)
|
||||
|
||||
target_link_libraries(test_app_hand
|
||||
PRIVATE
|
||||
iso15118
|
||||
Catch2::Catch2WithMain
|
||||
)
|
||||
|
||||
include(Catch)
|
||||
catch_discover_tests(test_app_hand)
|
||||
@@ -0,0 +1,43 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/message/supported_app_protocol.hpp>
|
||||
#include <iso15118/message/variant.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
SCENARIO("App Protocol Ser/Des") {
|
||||
GIVEN("A binary representation of an AppProtocolReq document") {
|
||||
|
||||
uint8_t doc_raw[] = "\x80"
|
||||
"\x00\xf3\xab\x93\x71\xd3\x4b\x9b\x79\xd3\x9b\xa3\x21\xd3\x4b\x9b\x79"
|
||||
"\xd1\x89\xa9\x89\x89\xc1\xd1\x69\x91\x81\xd2\x0a\x18\x01\x00\x00\x04"
|
||||
"\x00\x40";
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::SAP, stream_view);
|
||||
|
||||
THEN("It should be decoded succussfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::SupportedAppProtocolReq);
|
||||
|
||||
const auto& msg = variant.get<message_20::SupportedAppProtocolRequest>();
|
||||
|
||||
REQUIRE(msg.app_protocol.size() == 1);
|
||||
|
||||
const auto& ap = msg.app_protocol[0];
|
||||
|
||||
REQUIRE(ap.version_number_major == 1);
|
||||
REQUIRE(ap.version_number_minor == 0);
|
||||
REQUIRE(ap.schema_id == 1);
|
||||
REQUIRE(ap.priority == 1);
|
||||
|
||||
REQUIRE(ap.protocol_namespace == "urn:iso:std:iso:15118:-20:AC");
|
||||
}
|
||||
}
|
||||
|
||||
// Todo(sl): Missing Decode message
|
||||
// 80400040
|
||||
// {"supportedAppProtocolRes": {"ResponseCode": "OK_SuccessfulNegotiation", "SchemaID": 1}}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
include(Catch)
|
||||
|
||||
function(create_exi_test_target NAME)
|
||||
add_executable(test_exi_${NAME} ${NAME}.cpp)
|
||||
target_link_libraries(test_exi_${NAME}
|
||||
PRIVATE
|
||||
iso15118
|
||||
Catch2::Catch2WithMain
|
||||
)
|
||||
catch_discover_tests(test_exi_${NAME})
|
||||
endfunction()
|
||||
|
||||
create_exi_test_target(session_setup)
|
||||
create_exi_test_target(authorization_setup)
|
||||
create_exi_test_target(authorization)
|
||||
create_exi_test_target(service_discovery)
|
||||
create_exi_test_target(service_detail)
|
||||
create_exi_test_target(service_selection)
|
||||
create_exi_test_target(dc_charge_parameter_discovery)
|
||||
create_exi_test_target(schedule_exchange)
|
||||
create_exi_test_target(dc_cable_check)
|
||||
create_exi_test_target(dc_pre_charge)
|
||||
create_exi_test_target(power_delivery)
|
||||
create_exi_test_target(dc_charge_loop)
|
||||
create_exi_test_target(dc_welding_detection)
|
||||
create_exi_test_target(session_stop)
|
||||
create_exi_test_target(ac_charge_parameter_discovery)
|
||||
create_exi_test_target(ac_charge_loop)
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/message/ac_charge_loop.hpp>
|
||||
#include <iso15118/message/variant.hpp>
|
||||
|
||||
#include "helper.hpp"
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
namespace dt = iso15118::message_20::datatypes;
|
||||
|
||||
SCENARIO("Se/Deserialize ac charge loop messages") {
|
||||
|
||||
GIVEN("Deserialize ac_charge_loop_req_scheduled mode") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x08, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d, 0x8c, 0xdb, 0xfe, 0x1b,
|
||||
0x60, 0x62, 0x88, 0x04, 0x00, 0x5c, 0xb0, 0x00, 0x40, 0x07, 0x02, 0xe8, 0x04, 0x00, 0x00,
|
||||
0x80, 0x7e, 0x08, 0x01, 0x90, 0x10, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x3f, 0x06, 0x80,
|
||||
0x78, 0x10, 0x00, 0x00, 0x04, 0x00, 0x00, 0x02, 0x00, 0x38, 0x17, 0x40, 0x40, 0x00, 0x00,
|
||||
0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20AC, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::AC_ChargeLoopReq);
|
||||
|
||||
const auto& msg = variant.get<message_20::AC_ChargeLoopRequest>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B});
|
||||
REQUIRE(header.timestamp == 1725456333);
|
||||
REQUIRE(msg.meter_info_requested == false);
|
||||
|
||||
using ScheduledMode = message_20::datatypes::Scheduled_AC_CLReqControlMode;
|
||||
|
||||
REQUIRE(std::holds_alternative<ScheduledMode>(msg.control_mode));
|
||||
const auto& control_mode = std::get<ScheduledMode>(msg.control_mode);
|
||||
REQUIRE(control_mode.target_energy_request.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*control_mode.target_energy_request) == 12345.0f);
|
||||
REQUIRE(control_mode.max_energy_request.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*control_mode.max_energy_request) == 12000.0f);
|
||||
REQUIRE(control_mode.min_energy_request.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*control_mode.min_energy_request) == 1.0f);
|
||||
REQUIRE(dt::from_RationalNumber(control_mode.present_active_power) == 12000.0f);
|
||||
REQUIRE(control_mode.present_active_power_L2.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*control_mode.present_active_power_L2) == 0.0f);
|
||||
REQUIRE(control_mode.present_active_power_L3.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*control_mode.present_active_power_L3) == 0.0f);
|
||||
REQUIRE(control_mode.present_reactive_power.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*control_mode.present_reactive_power) == 0.0f);
|
||||
REQUIRE(control_mode.present_reactive_power_L2.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*control_mode.present_reactive_power_L2) == 0.0f);
|
||||
REQUIRE(control_mode.present_reactive_power_L3.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*control_mode.present_reactive_power_L3) == 0.0f);
|
||||
REQUIRE(control_mode.max_charge_power.has_value() == true);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(*control_mode.max_charge_power) == 32);
|
||||
REQUIRE(control_mode.max_charge_power_L2.has_value() == true);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(*control_mode.max_charge_power_L2) == 0.0f);
|
||||
REQUIRE(control_mode.max_charge_power_L3.has_value() == true);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(*control_mode.max_charge_power_L3) == 0.0f);
|
||||
REQUIRE(control_mode.min_charge_power.has_value() == true);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(*control_mode.min_charge_power) == 20);
|
||||
REQUIRE(control_mode.min_charge_power_L2.has_value() == true);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(*control_mode.min_charge_power_L2) == 0.0f);
|
||||
REQUIRE(control_mode.min_charge_power_L3.has_value() == true);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(*control_mode.min_charge_power_L3) == 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize ac_charge_loop request_scheduled mode") {
|
||||
|
||||
message_20::AC_ChargeLoopRequest req;
|
||||
|
||||
req.header = message_20::Header{{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B}, 1725456333};
|
||||
req.meter_info_requested = false;
|
||||
auto& control_mode = req.control_mode.emplace<message_20::datatypes::Scheduled_AC_CLReqControlMode>();
|
||||
|
||||
control_mode.target_energy_request = {12345, 0};
|
||||
control_mode.max_energy_request = {12000, 0};
|
||||
control_mode.min_energy_request = {1, 0};
|
||||
control_mode.max_charge_power = {3200, -2};
|
||||
control_mode.max_charge_power_L2 = {0, 0};
|
||||
control_mode.max_charge_power_L3 = {0, 0};
|
||||
control_mode.min_charge_power = {2000, -2};
|
||||
control_mode.min_charge_power_L2 = {0, 0};
|
||||
control_mode.min_charge_power_L3 = {0, 0};
|
||||
control_mode.present_active_power = {12000, 0};
|
||||
control_mode.present_active_power_L2 = {0, 0};
|
||||
control_mode.present_active_power_L3 = {0, 0};
|
||||
control_mode.present_reactive_power = {0, 0};
|
||||
control_mode.present_reactive_power_L2 = {0, 0};
|
||||
control_mode.present_reactive_power_L3 = {0, 0};
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x08, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d, 0x8c, 0xdb,
|
||||
0xfe, 0x1b, 0x60, 0x62, 0x88, 0x04, 0x00, 0x5c, 0xb0, 0x00, 0x40, 0x07, 0x02,
|
||||
0xe8, 0x04, 0x00, 0x00, 0x80, 0x7e, 0x08, 0x01, 0x90, 0x10, 0x00, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x3f, 0x06, 0x80, 0x78, 0x10, 0x00, 0x00, 0x04, 0x00, 0x00,
|
||||
0x02, 0x00, 0x38, 0x17, 0x40, 0x40, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01, 0x00,
|
||||
0x00, 0x00, 0x40, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(req) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Deserialize ac_charge_loop_req dynamic mode") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x08, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d, 0x8c, 0xdb, 0xfe,
|
||||
0x1b, 0x60, 0x62, 0x00, 0x51, 0x84, 0x0c, 0x78, 0x67, 0xed, 0x5b, 0x83, 0x04, 0x08,
|
||||
0x78, 0x17, 0x02, 0x04, 0x3c, 0x0b, 0x81, 0x00, 0x00, 0x20, 0x82, 0x0d, 0xc0, 0xb0,
|
||||
0x20, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40,
|
||||
0x00, 0x00, 0x20, 0x03, 0x81, 0x74, 0x08, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00,
|
||||
0x00, 0x00, 0x40, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20AC, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::AC_ChargeLoopReq);
|
||||
|
||||
const auto& msg = variant.get<message_20::AC_ChargeLoopRequest>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B});
|
||||
REQUIRE(header.timestamp == 1725456333);
|
||||
REQUIRE(msg.meter_info_requested == false);
|
||||
|
||||
REQUIRE(msg.display_parameters.has_value() == true);
|
||||
const auto& display_parameters = msg.display_parameters.value();
|
||||
|
||||
REQUIRE(display_parameters.present_soc.value_or(0) == 10);
|
||||
REQUIRE(display_parameters.charging_complete.value_or(true) == false);
|
||||
|
||||
using DynamicMode = message_20::datatypes::Dynamic_AC_CLReqControlMode;
|
||||
|
||||
REQUIRE(std::holds_alternative<DynamicMode>(msg.control_mode));
|
||||
const auto& control_mode = std::get<DynamicMode>(msg.control_mode);
|
||||
|
||||
REQUIRE(control_mode.departure_time.has_value() == true);
|
||||
REQUIRE(control_mode.departure_time.value() == 1727440880);
|
||||
REQUIRE(dt::from_RationalNumber(control_mode.target_energy_request) == 60000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(control_mode.max_energy_request) == 60000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(control_mode.min_energy_request) == 1.0f);
|
||||
REQUIRE(dt::from_RationalNumber(control_mode.max_charge_power) == 150000.0f);
|
||||
REQUIRE(control_mode.max_charge_power_L2.has_value() == true);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(*control_mode.max_charge_power_L2) == 0.0f);
|
||||
REQUIRE(control_mode.max_charge_power_L3.has_value() == true);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(*control_mode.max_charge_power_L3) == 0.0f);
|
||||
REQUIRE(dt::from_RationalNumber(control_mode.min_charge_power) == 0.0f);
|
||||
REQUIRE(control_mode.min_charge_power_L2.has_value() == true);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(*control_mode.min_charge_power_L2) == 0.0f);
|
||||
REQUIRE(control_mode.min_charge_power_L3.has_value() == true);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(*control_mode.min_charge_power_L3) == 0.0f);
|
||||
REQUIRE(dt::from_RationalNumber(control_mode.present_active_power) == 12000.0f);
|
||||
REQUIRE(control_mode.present_active_power_L2.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*control_mode.present_active_power_L2) == 0.0f);
|
||||
REQUIRE(control_mode.present_active_power_L3.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*control_mode.present_active_power_L3) == 0.0f);
|
||||
REQUIRE(dt::from_RationalNumber(control_mode.present_reactive_power) == 0.0f);
|
||||
REQUIRE(control_mode.present_reactive_power_L2.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*control_mode.present_reactive_power_L2) == 0.0f);
|
||||
REQUIRE(control_mode.present_reactive_power_L3.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*control_mode.present_reactive_power_L3) == 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize ac_charge_loop request_dynamic mode") {
|
||||
|
||||
message_20::AC_ChargeLoopRequest req;
|
||||
|
||||
req.header = message_20::Header{{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B}, 1725456333};
|
||||
req.meter_info_requested = false;
|
||||
auto& disparam = req.display_parameters.emplace();
|
||||
disparam.present_soc = 10;
|
||||
disparam.charging_complete = false;
|
||||
auto& control_mode = req.control_mode.emplace<message_20::datatypes::Dynamic_AC_CLReqControlMode>();
|
||||
control_mode.departure_time = 1727440880;
|
||||
control_mode.target_energy_request = {6000, 1};
|
||||
control_mode.max_energy_request = {6000, 1};
|
||||
control_mode.min_energy_request = {1, 0};
|
||||
control_mode.max_charge_power = {1500, 2};
|
||||
control_mode.max_charge_power_L2 = {0, 0};
|
||||
control_mode.max_charge_power_L3 = {0, 0};
|
||||
control_mode.min_charge_power = {0, 0};
|
||||
control_mode.min_charge_power_L2 = {0, 0};
|
||||
control_mode.min_charge_power_L3 = {0, 0};
|
||||
control_mode.present_active_power = {12000, 0};
|
||||
control_mode.present_active_power_L2 = {0, 0};
|
||||
control_mode.present_active_power_L3 = {0, 0};
|
||||
control_mode.present_reactive_power = {0, 0};
|
||||
control_mode.present_reactive_power_L2 = {0, 0};
|
||||
control_mode.present_reactive_power_L3 = {0, 0};
|
||||
|
||||
std::vector<uint8_t> expected = {
|
||||
0x80, 0x08, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d, 0x8c, 0xdb, 0xfe, 0x1b, 0x60,
|
||||
0x62, 0x00, 0x51, 0x84, 0x0c, 0x78, 0x67, 0xed, 0x5b, 0x83, 0x04, 0x08, 0x78, 0x17, 0x02, 0x04,
|
||||
0x3c, 0x0b, 0x81, 0x00, 0x00, 0x20, 0x82, 0x0d, 0xc0, 0xb0, 0x20, 0x00, 0x00, 0x08, 0x00, 0x00,
|
||||
0x04, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x20, 0x03, 0x81, 0x74, 0x08, 0x00,
|
||||
0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(req) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Deserialize ac_charge_loop_res scheduled mode") {
|
||||
uint8_t doc_raw[] = {0x80, 0x0c, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d, 0x8c, 0xeb,
|
||||
0xfe, 0x1b, 0x60, 0x62, 0x00, 0x10, 0x0c, 0xc4, 0x69, 0x04, 0xb1, 0x20, 0x00,
|
||||
0xc8, 0x80, 0x40, 0x07, 0x02, 0xe8, 0x04, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
|
||||
0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x04, 0x00, 0x70,
|
||||
0x2e, 0x81, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20AC, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::AC_ChargeLoopRes);
|
||||
|
||||
const auto& msg = variant.get<message_20::AC_ChargeLoopResponse>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B});
|
||||
REQUIRE(header.timestamp == 1725456334);
|
||||
|
||||
REQUIRE(msg.meter_info.has_value() == true);
|
||||
const auto& meter_info = msg.meter_info.value();
|
||||
|
||||
REQUIRE(meter_info.meter_id == "1");
|
||||
REQUIRE(meter_info.charged_energy_reading_wh == 1234);
|
||||
|
||||
using ScheduledMode = message_20::datatypes::Scheduled_AC_CLResControlMode;
|
||||
|
||||
REQUIRE(std::holds_alternative<ScheduledMode>(msg.control_mode));
|
||||
const auto& control_mode = std::get<ScheduledMode>(msg.control_mode);
|
||||
|
||||
REQUIRE(control_mode.target_active_power.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*control_mode.target_active_power) == 12000.0f);
|
||||
REQUIRE(control_mode.target_active_power_L2.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*control_mode.target_active_power_L2) == 0.0f);
|
||||
REQUIRE(control_mode.target_active_power_L3.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*control_mode.target_active_power_L3) == 0.0f);
|
||||
REQUIRE(control_mode.target_reactive_power.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*control_mode.target_reactive_power) == 0.0f);
|
||||
REQUIRE(control_mode.target_reactive_power_L2.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*control_mode.target_reactive_power_L2) == 0.0f);
|
||||
REQUIRE(control_mode.target_reactive_power_L3.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*control_mode.target_reactive_power_L3) == 0.0f);
|
||||
REQUIRE(control_mode.present_active_power.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*control_mode.present_active_power) == 12000.0f);
|
||||
REQUIRE(control_mode.present_active_power_L2.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*control_mode.present_active_power_L2) == 0.0f);
|
||||
REQUIRE(control_mode.present_active_power_L3.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*control_mode.present_active_power_L3) == 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize ac_charge_loop response scheduled") {
|
||||
|
||||
message_20::AC_ChargeLoopResponse res;
|
||||
|
||||
res.header = message_20::Header{{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B}, 1725456334};
|
||||
res.response_code = message_20::datatypes::ResponseCode::OK;
|
||||
res.target_frequency = {50, 0};
|
||||
auto& met_info = res.meter_info.emplace();
|
||||
met_info.meter_id = "1";
|
||||
met_info.charged_energy_reading_wh = 1234;
|
||||
|
||||
auto& res_ctrl_mode = res.control_mode.emplace<message_20::datatypes::Scheduled_AC_CLResControlMode>();
|
||||
res_ctrl_mode.target_active_power = {12000, 0};
|
||||
res_ctrl_mode.target_active_power_L2 = {0, 0};
|
||||
res_ctrl_mode.target_active_power_L3 = {0, 0};
|
||||
res_ctrl_mode.target_reactive_power = {0, 0};
|
||||
res_ctrl_mode.target_reactive_power_L2 = {0, 0};
|
||||
res_ctrl_mode.target_reactive_power_L3 = {0, 0};
|
||||
res_ctrl_mode.present_active_power = {12000, 0};
|
||||
res_ctrl_mode.present_active_power_L2 = {0, 0};
|
||||
res_ctrl_mode.present_active_power_L3 = {0, 0};
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x0c, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d, 0x8c, 0xeb,
|
||||
0xfe, 0x1b, 0x60, 0x62, 0x00, 0x10, 0x0c, 0xc4, 0x69, 0x04, 0xb1, 0x20, 0x00,
|
||||
0xc8, 0x80, 0x40, 0x07, 0x02, 0xe8, 0x04, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
|
||||
0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x04, 0x00, 0x70,
|
||||
0x2e, 0x81, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(res) == expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/message/ac_charge_parameter_discovery.hpp>
|
||||
#include <iso15118/message/variant.hpp>
|
||||
|
||||
#include "helper.hpp"
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
SCENARIO("Se/Deserialize ac charge parameter discovery messages") {
|
||||
|
||||
GIVEN("Deserialize ac_charge_parameter_discovery_req") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x10, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d, 0x8c, 0x3b,
|
||||
0xfe, 0x1b, 0x60, 0x62, 0x07, 0xE0, 0x80, 0x19, 0x02, 0x00, 0x00, 0x00, 0x80,
|
||||
0x00, 0x00, 0x3F, 0x06, 0x80, 0x78, 0x10, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20AC, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::AC_ChargeParameterDiscoveryReq);
|
||||
|
||||
const auto& msg = variant.get<message_20::AC_ChargeParameterDiscoveryRequest>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B});
|
||||
REQUIRE(header.timestamp == 1725456323);
|
||||
|
||||
using AC_ModeReq = message_20::datatypes::AC_CPDReqEnergyTransferMode;
|
||||
|
||||
REQUIRE(std::holds_alternative<AC_ModeReq>(msg.transfer_mode));
|
||||
const auto& transfer_mode = std::get<AC_ModeReq>(msg.transfer_mode);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(transfer_mode.max_charge_power) == 32);
|
||||
REQUIRE(transfer_mode.max_charge_power_L2.has_value() == true);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(*transfer_mode.max_charge_power_L2) == 0.0f);
|
||||
REQUIRE(transfer_mode.max_charge_power_L3.has_value() == true);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(*transfer_mode.max_charge_power_L3) == 0.0f);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(transfer_mode.min_charge_power) == 20);
|
||||
REQUIRE(transfer_mode.min_charge_power_L2.has_value() == true);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(*transfer_mode.min_charge_power_L2) == 0.0f);
|
||||
REQUIRE(transfer_mode.min_charge_power_L3.has_value() == true);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(*transfer_mode.min_charge_power_L3) == 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize ac_charge_parameter_discovery_req") {
|
||||
|
||||
using AC_ModeReq = message_20::datatypes::AC_CPDReqEnergyTransferMode;
|
||||
|
||||
message_20::AC_ChargeParameterDiscoveryRequest req;
|
||||
|
||||
req.header = message_20::Header{{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B}, 1725456323};
|
||||
auto& mode = req.transfer_mode.emplace<AC_ModeReq>();
|
||||
mode.max_charge_power = {3200, -2};
|
||||
mode.max_charge_power_L2 = {0, 0};
|
||||
mode.max_charge_power_L3 = {0, 0};
|
||||
mode.min_charge_power = {2000, -2};
|
||||
mode.min_charge_power_L2 = {0, 0};
|
||||
mode.min_charge_power_L3 = {0, 0};
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x10, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d, 0x8c, 0x3b,
|
||||
0xfe, 0x1b, 0x60, 0x62, 0x07, 0xE0, 0x80, 0x19, 0x02, 0x00, 0x00, 0x00, 0x80,
|
||||
0x00, 0x00, 0x3F, 0x06, 0x80, 0x78, 0x10, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(req) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(sl): Adding BPT_AC_CPDReqEnergyTransferMode tests
|
||||
// TODO(rb): Adding BPT_AC_CPDResEnergyTransferMode tests
|
||||
|
||||
GIVEN("Deserialize ac_charge_parameter_discovery_res") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x14, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d, 0x8c, 0x4b, 0xfe, 0x1b,
|
||||
0x60, 0x62, 0x00, 0x04, 0x08, 0x50, 0x08, 0x81, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x20,
|
||||
0x43, 0x40, 0x3c, 0x08, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20AC, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::AC_ChargeParameterDiscoveryRes);
|
||||
|
||||
const auto& msg = variant.get<message_20::AC_ChargeParameterDiscoveryResponse>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B});
|
||||
REQUIRE(header.timestamp == 1725456324);
|
||||
REQUIRE(msg.response_code == message_20::datatypes::ResponseCode::OK);
|
||||
|
||||
using AC_ModeRes = message_20::datatypes::AC_CPDResEnergyTransferMode;
|
||||
|
||||
REQUIRE(std::holds_alternative<AC_ModeRes>(msg.transfer_mode));
|
||||
const auto& transfer_mode = std::get<AC_ModeRes>(msg.transfer_mode);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(transfer_mode.max_charge_power) == 22080);
|
||||
REQUIRE(transfer_mode.max_charge_power_L2.has_value() == true);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(*transfer_mode.max_charge_power_L2) == 0.0f);
|
||||
REQUIRE(transfer_mode.max_charge_power_L3.has_value() == true);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(*transfer_mode.max_charge_power_L3) == 0.0f);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(transfer_mode.min_charge_power) == 20000);
|
||||
REQUIRE(transfer_mode.min_charge_power_L2.has_value() == true);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(*transfer_mode.min_charge_power_L2) == 0.0f);
|
||||
REQUIRE(transfer_mode.min_charge_power_L3.has_value() == true);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(*transfer_mode.min_charge_power_L3) == 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize ac_charge_parameter_discovery_res") {
|
||||
|
||||
using AC_ModeRes = message_20::datatypes::AC_CPDResEnergyTransferMode;
|
||||
|
||||
message_20::AC_ChargeParameterDiscoveryResponse res;
|
||||
|
||||
res.header = message_20::Header{{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B}, 1725456324};
|
||||
res.response_code = message_20::datatypes::ResponseCode::OK;
|
||||
auto& mode = res.transfer_mode.emplace<AC_ModeRes>();
|
||||
mode.max_charge_power = {2208, 1};
|
||||
mode.max_charge_power_L2 = {0, 0};
|
||||
mode.max_charge_power_L3 = {0, 0};
|
||||
mode.min_charge_power = {2000, 1};
|
||||
mode.min_charge_power_L2 = {0, 0};
|
||||
mode.min_charge_power_L3 = {0, 0};
|
||||
std::vector<uint8_t> expected = {0x80, 0x14, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d,
|
||||
0x8c, 0x4b, 0xfe, 0x1b, 0x60, 0x62, 0x00, 0x04, 0x08, 0x50, 0x08,
|
||||
0x81, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x20, 0x43, 0x40, 0x3c,
|
||||
0x08, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(res) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(sl): Adding BPT_AC_CPDResEnergyTransferMode tests
|
||||
// TODO(rb): Adding BPT_AC_CPDReqEnergyTransferMode tests
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/message/authorization.hpp>
|
||||
#include <iso15118/message/variant.hpp>
|
||||
|
||||
#include "helper.hpp"
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
SCENARIO("Se/Deserialize authorization messages") {
|
||||
|
||||
GIVEN("Deserialize authorization_req eim") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x00, 0x04, 0x79, 0x0c, 0x8a, 0xdc, 0xee, 0xee,
|
||||
0x09, 0x68, 0x8d, 0x6c, 0xac, 0x3a, 0x60, 0x62, 0x00};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20Main, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::AuthorizationReq);
|
||||
|
||||
const auto& msg = variant.get<message_20::AuthorizationRequest>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0xF2, 0x19, 0x15, 0xB9, 0xDD, 0xDC, 0x12, 0xD1});
|
||||
REQUIRE(header.timestamp == 1691411798);
|
||||
|
||||
REQUIRE(msg.selected_authorization_service == message_20::datatypes::Authorization::EIM);
|
||||
REQUIRE(std::holds_alternative<message_20::datatypes::EIM_ASReqAuthorizationMode>(msg.authorization_mode));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(sl): Adding authorization_req pnc tests
|
||||
|
||||
GIVEN("Serialize authorization_res") {
|
||||
|
||||
message_20::AuthorizationResponse res;
|
||||
|
||||
res.header = message_20::Header{{0xF2, 0x19, 0x15, 0xB9, 0xDD, 0xDC, 0x12, 0xD1}, 1691411798};
|
||||
res.response_code = message_20::datatypes::ResponseCode::OK;
|
||||
res.evse_processing = message_20::datatypes::Processing::Finished;
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x04, 0x04, 0x79, 0x0c, 0x8a, 0xdc, 0xee, 0xee, 0x09,
|
||||
0x68, 0x8d, 0x6c, 0xac, 0x3a, 0x60, 0x62, 0x00, 0x00};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(res) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Deserialize authorization_res eim") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x04, 0x04, 0x79, 0x0c, 0x8a, 0xdc, 0xee, 0xee, 0x09,
|
||||
0x68, 0x8d, 0x6c, 0xac, 0x3a, 0x60, 0x62, 0x00, 0x00};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20Main, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::AuthorizationRes);
|
||||
|
||||
const auto& msg = variant.get<message_20::AuthorizationResponse>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0xF2, 0x19, 0x15, 0xB9, 0xDD, 0xDC, 0x12, 0xD1});
|
||||
REQUIRE(header.timestamp == 1691411798);
|
||||
|
||||
REQUIRE(msg.evse_processing == message_20::datatypes::Processing::Finished);
|
||||
REQUIRE(msg.response_code == message_20::datatypes::ResponseCode::OK);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize authorization_req eim") {
|
||||
|
||||
message_20::AuthorizationRequest req;
|
||||
|
||||
req.header = message_20::Header{{0xF2, 0x19, 0x15, 0xB9, 0xDD, 0xDC, 0x12, 0xD1}, 1691411798};
|
||||
req.selected_authorization_service = message_20::datatypes::Authorization::EIM;
|
||||
req.authorization_mode = message_20::datatypes::EIM_ASReqAuthorizationMode{};
|
||||
// Todo(sl): Adding certificate
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x00, 0x04, 0x79, 0x0c, 0x8a, 0xdc, 0xee, 0xee,
|
||||
0x09, 0x68, 0x8d, 0x6c, 0xac, 0x3a, 0x60, 0x62, 0x00};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(req) == expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <iso15118/message/authorization_setup.hpp>
|
||||
#include <iso15118/message/variant.hpp>
|
||||
|
||||
#include "helper.hpp"
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
SCENARIO("Se/Deserialize authorization setup messages") {
|
||||
|
||||
GIVEN("Deserialize authorization_setup_req") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x08, 0x04, 0x79, 0x0c, 0x8a, 0xdc, 0xee, 0xee,
|
||||
0x09, 0x68, 0x8d, 0x6c, 0xac, 0x3a, 0x60, 0x62};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20Main, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::AuthorizationSetupReq);
|
||||
|
||||
const auto& msg = variant.get<message_20::AuthorizationSetupRequest>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0xF2, 0x19, 0x15, 0xB9, 0xDD, 0xDC, 0x12, 0xD1});
|
||||
REQUIRE(header.timestamp == 1691411798);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize authorization_setup_req") {
|
||||
|
||||
const auto header = message_20::Header{{0xF2, 0x19, 0x15, 0xB9, 0xDD, 0xDC, 0x12, 0xD1}, 1691411798};
|
||||
|
||||
const auto res = message_20::AuthorizationSetupRequest{header};
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x08, 0x04, 0x79, 0x0c, 0x8a, 0xdc, 0xee, 0xee,
|
||||
0x09, 0x68, 0x8d, 0x6c, 0xac, 0x3a, 0x60, 0x62};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(res) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize authorization_setup_res") {
|
||||
|
||||
message_20::AuthorizationSetupResponse res;
|
||||
|
||||
res.header = message_20::Header{{0xF2, 0x19, 0x15, 0xB9, 0xDD, 0xDC, 0x12, 0xD1}, 1691411798};
|
||||
res.response_code = message_20::datatypes::ResponseCode::OK;
|
||||
res.authorization_services = {message_20::datatypes::Authorization::EIM};
|
||||
res.certificate_installation_service = true;
|
||||
res.authorization_mode = message_20::datatypes::EIM_ASResAuthorizationMode();
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x0c, 0x04, 0x79, 0x0c, 0x8a, 0xdc, 0xee, 0xee, 0x09,
|
||||
0x68, 0x8d, 0x6c, 0xac, 0x3a, 0x60, 0x62, 0x00, 0x05, 0x00};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(res) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize authorization_setup_res pnc") {
|
||||
|
||||
message_20::AuthorizationSetupResponse res;
|
||||
|
||||
res.header = message_20::Header{{0xF2, 0x19, 0x15, 0xB9, 0xDD, 0xDC, 0x12, 0xD1}, 1691411798};
|
||||
res.response_code = message_20::datatypes::ResponseCode::OK;
|
||||
res.authorization_services = {message_20::datatypes::Authorization::EIM,
|
||||
message_20::datatypes::Authorization::PnC};
|
||||
res.certificate_installation_service = true;
|
||||
|
||||
auto& mode = res.authorization_mode.emplace<message_20::datatypes::PnC_ASResAuthorizationMode>();
|
||||
|
||||
mode.gen_challenge = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
|
||||
|
||||
// FIXME(SL): supported_providers missing
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x0c, 0x04, 0x79, 0x0c, 0x8a, 0xdc, 0xee, 0xee, 0x09, 0x68, 0x8d, 0x6c,
|
||||
0xac, 0x3a, 0x60, 0x62, 0x00, 0x01, 0x12, 0x08, 0x00, 0x81, 0x01, 0x82, 0x02,
|
||||
0x83, 0x03, 0x84, 0x04, 0x85, 0x05, 0x86, 0x06, 0x87, 0x07, 0x88, 0x10};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(res) == expected);
|
||||
}
|
||||
}
|
||||
GIVEN("Deserialize authorization_setup_res") {
|
||||
uint8_t doc_raw[] = {0x80, 0x0c, 0x04, 0x79, 0x0c, 0x8a, 0xdc, 0xee, 0xee, 0x09,
|
||||
0x68, 0x8d, 0x6c, 0xac, 0x3a, 0x60, 0x62, 0x00, 0x05, 0x00};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20Main, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::AuthorizationSetupRes);
|
||||
|
||||
const auto& msg = variant.get<message_20::AuthorizationSetupResponse>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0xF2, 0x19, 0x15, 0xB9, 0xDD, 0xDC, 0x12, 0xD1});
|
||||
REQUIRE(header.timestamp == 1691411798);
|
||||
REQUIRE(msg.response_code == message_20::datatypes::ResponseCode::OK);
|
||||
REQUIRE(msg.authorization_services.size() == 1);
|
||||
REQUIRE(msg.authorization_services[0] == message_20::datatypes::Authorization::EIM);
|
||||
REQUIRE(msg.certificate_installation_service == true);
|
||||
REQUIRE(std::holds_alternative<message_20::datatypes::EIM_ASResAuthorizationMode>(msg.authorization_mode));
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Deserialize authorization_setup_res_pnc") {
|
||||
uint8_t doc_raw[] = {0x80, 0x0c, 0x04, 0x79, 0x0c, 0x8a, 0xdc, 0xee, 0xee, 0x09, 0x68, 0x8d, 0x6c,
|
||||
0xac, 0x3a, 0x60, 0x62, 0x00, 0x01, 0x12, 0x08, 0x00, 0x81, 0x01, 0x82, 0x02,
|
||||
0x83, 0x03, 0x84, 0x04, 0x85, 0x05, 0x86, 0x06, 0x87, 0x07, 0x88, 0x10};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20Main, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::AuthorizationSetupRes);
|
||||
|
||||
const auto& msg = variant.get<message_20::AuthorizationSetupResponse>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0xF2, 0x19, 0x15, 0xB9, 0xDD, 0xDC, 0x12, 0xD1});
|
||||
REQUIRE(header.timestamp == 1691411798);
|
||||
REQUIRE(msg.response_code == message_20::datatypes::ResponseCode::OK);
|
||||
REQUIRE(msg.authorization_services.size() == 2);
|
||||
REQUIRE(msg.authorization_services[0] == message_20::datatypes::Authorization::EIM);
|
||||
REQUIRE(msg.authorization_services[1] == message_20::datatypes::Authorization::PnC);
|
||||
REQUIRE(msg.certificate_installation_service == true);
|
||||
const message_20::datatypes::GenChallenge exp_gen_challenge = {1, 2, 3, 4, 5, 6, 7, 8,
|
||||
9, 10, 11, 12, 13, 14, 15, 16};
|
||||
const auto& pnc_auth_mode =
|
||||
std::get<message_20::datatypes::PnC_ASResAuthorizationMode>(msg.authorization_mode);
|
||||
REQUIRE(pnc_auth_mode.gen_challenge == exp_gen_challenge);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/message/dc_cable_check.hpp>
|
||||
#include <iso15118/message/variant.hpp>
|
||||
|
||||
#include "helper.hpp"
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
SCENARIO("Se/Deserialize dc cable check messages") {
|
||||
|
||||
GIVEN("Deserialize dc_cable_check_req") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x2c, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7,
|
||||
0x6c, 0x4d, 0x8c, 0x7b, 0xfe, 0x1b, 0x60, 0x62};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20DC, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::DC_CableCheckReq);
|
||||
|
||||
const auto& msg = variant.get<message_20::DC_CableCheckRequest>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B});
|
||||
REQUIRE(header.timestamp == 1725456327);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize dc_cable_check_req") {
|
||||
|
||||
message_20::DC_CableCheckRequest req;
|
||||
|
||||
req.header = message_20::Header{{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B}, 1725456327};
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x2c, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7,
|
||||
0x6c, 0x4d, 0x8c, 0x7b, 0xfe, 0x1b, 0x60, 0x62};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(req) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize dc_cable_check_res ongoing") {
|
||||
|
||||
message_20::DC_CableCheckResponse res;
|
||||
|
||||
res.header = message_20::Header{{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B}, 1725456328};
|
||||
res.response_code = message_20::datatypes::ResponseCode::OK;
|
||||
res.processing = message_20::datatypes::Processing::Ongoing;
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x30, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c,
|
||||
0x4d, 0x8c, 0x8b, 0xfe, 0x1b, 0x60, 0x62, 0x00, 0x10};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(res) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Deserialize dc_cable_check_res_ongoing") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x30, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c,
|
||||
0x4d, 0x8c, 0x8b, 0xfe, 0x1b, 0x60, 0x62, 0x00, 0x10};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20DC, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::DC_CableCheckRes);
|
||||
|
||||
const auto& msg = variant.get<message_20::DC_CableCheckResponse>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B});
|
||||
REQUIRE(header.timestamp == 1725456328);
|
||||
REQUIRE(msg.response_code == message_20::datatypes::ResponseCode::OK);
|
||||
REQUIRE(msg.processing == message_20::datatypes::Processing::Ongoing);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize dc_cable_check_res finished") {
|
||||
|
||||
message_20::DC_CableCheckResponse res;
|
||||
|
||||
res.header = message_20::Header{{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B}, 1725456331};
|
||||
res.response_code = message_20::datatypes::ResponseCode::OK;
|
||||
res.processing = message_20::datatypes::Processing::Finished;
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x30, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c,
|
||||
0x4d, 0x8c, 0xbb, 0xfe, 0x1b, 0x60, 0x62, 0x00, 0x00};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(res) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Deserialize dc_cable_check_res_finished") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x30, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c,
|
||||
0x4d, 0x8c, 0xbb, 0xfe, 0x1b, 0x60, 0x62, 0x00, 0x00};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20DC, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::DC_CableCheckRes);
|
||||
|
||||
const auto& msg = variant.get<message_20::DC_CableCheckResponse>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B});
|
||||
REQUIRE(header.timestamp == 1725456331);
|
||||
REQUIRE(msg.response_code == message_20::datatypes::ResponseCode::OK);
|
||||
REQUIRE(msg.processing == message_20::datatypes::Processing::Finished);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/message/dc_charge_loop.hpp>
|
||||
#include <iso15118/message/variant.hpp>
|
||||
|
||||
#include "helper.hpp"
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
namespace dt = iso15118::message_20::datatypes;
|
||||
|
||||
SCENARIO("Se/Deserialize dc charge loop messages") {
|
||||
|
||||
GIVEN("Deserialize dc_charge_loop_req") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x34, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d, 0x8c, 0xdb, 0xfe, 0x1b,
|
||||
0x60, 0x62, 0x81, 0x00, 0x12, 0x00, 0x64, 0x64, 0x00, 0x0a, 0x02, 0x00, 0x24, 0x00, 0xca};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20DC, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::DC_ChargeLoopReq);
|
||||
|
||||
const auto& msg = variant.get<message_20::DC_ChargeLoopRequest>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B});
|
||||
REQUIRE(header.timestamp == 1725456333);
|
||||
REQUIRE(msg.meter_info_requested == false);
|
||||
REQUIRE(dt::from_RationalNumber(msg.present_voltage) == 400);
|
||||
|
||||
using ScheduledMode = message_20::datatypes::Scheduled_DC_CLReqControlMode;
|
||||
|
||||
REQUIRE(std::holds_alternative<ScheduledMode>(msg.control_mode));
|
||||
const auto& control_mode = std::get<ScheduledMode>(msg.control_mode);
|
||||
REQUIRE(dt::from_RationalNumber(control_mode.target_current) == 20);
|
||||
REQUIRE(dt::from_RationalNumber(control_mode.target_voltage) == 400);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize dc_charge_loop request") {
|
||||
|
||||
message_20::DC_ChargeLoopRequest req;
|
||||
|
||||
req.header = message_20::Header{{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B}, 1725456333};
|
||||
req.meter_info_requested = false;
|
||||
auto& control_mode = req.control_mode.emplace<message_20::datatypes::Scheduled_DC_CLReqControlMode>();
|
||||
control_mode.target_current = {20, 0};
|
||||
control_mode.target_voltage = {400, 0};
|
||||
|
||||
req.present_voltage = {400, 0};
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x34, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c,
|
||||
0x4d, 0x8c, 0xdb, 0xfe, 0x1b, 0x60, 0x62, 0x81, 0x00, 0x12,
|
||||
0x00, 0x64, 0x64, 0x00, 0x0a, 0x02, 0x00, 0x24, 0x00, 0xca};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(req) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Deserialize dc_charge_loop_req dynamic mode") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x34, 0x04, 0x32, 0x75, 0x76, 0x9e, 0xc7, 0x10, 0x64, 0xac, 0x8f, 0x0c, 0xfd,
|
||||
0xab, 0x70, 0x62, 0x00, 0x51, 0x84, 0x02, 0x00, 0x24, 0x00, 0xc6, 0x90, 0x21, 0xe0,
|
||||
0x5c, 0x08, 0x30, 0x3c, 0x04, 0x00, 0x00, 0x82, 0x04, 0x26, 0x1d, 0x41, 0x00, 0x00,
|
||||
0x00, 0x80, 0x0a, 0xc0, 0x20, 0x40, 0x04, 0x20, 0x38, 0x20, 0x02, 0x58, 0x04, 0x00};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20DC, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::DC_ChargeLoopReq);
|
||||
|
||||
const auto& msg = variant.get<message_20::DC_ChargeLoopRequest>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x64, 0xEA, 0xED, 0x3D, 0x8E, 0x20, 0xC9, 0x59});
|
||||
REQUIRE(header.timestamp == 1727440880);
|
||||
REQUIRE(msg.meter_info_requested == false);
|
||||
REQUIRE(dt::from_RationalNumber(msg.present_voltage) == 400.0f);
|
||||
|
||||
REQUIRE(msg.display_parameters.has_value() == true);
|
||||
const auto& display_parameters = msg.display_parameters.value();
|
||||
|
||||
REQUIRE(display_parameters.present_soc.value_or(0) == 10);
|
||||
REQUIRE(display_parameters.charging_complete.value_or(true) == false);
|
||||
|
||||
using DynamicMode = message_20::datatypes::Dynamic_DC_CLReqControlMode;
|
||||
|
||||
REQUIRE(std::holds_alternative<DynamicMode>(msg.control_mode));
|
||||
const auto& control_mode = std::get<DynamicMode>(msg.control_mode);
|
||||
|
||||
REQUIRE(dt::from_RationalNumber(control_mode.target_energy_request) == 60000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(control_mode.max_energy_request) == 60000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(control_mode.min_energy_request) == 1.0f);
|
||||
REQUIRE(dt::from_RationalNumber(control_mode.max_charge_power) == 150000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(control_mode.min_charge_power) == 0.0f);
|
||||
REQUIRE(dt::from_RationalNumber(control_mode.max_charge_current) == 300.0f);
|
||||
REQUIRE(dt::from_RationalNumber(control_mode.max_voltage) == 900.0f);
|
||||
REQUIRE(dt::from_RationalNumber(control_mode.min_voltage) == 150.0f);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize dc_charge_loop request dynamic mode") {
|
||||
|
||||
message_20::DC_ChargeLoopRequest req;
|
||||
|
||||
req.header = message_20::Header{{0x64, 0xEA, 0xED, 0x3D, 0x8E, 0x20, 0xC9, 0x59}, 1727440880};
|
||||
req.meter_info_requested = false;
|
||||
auto& control_mode = req.control_mode.emplace<message_20::datatypes::Dynamic_DC_CLReqControlMode>();
|
||||
|
||||
req.present_voltage = {400, 0};
|
||||
control_mode.target_energy_request = {6000, 1};
|
||||
control_mode.max_energy_request = {60, 3};
|
||||
control_mode.min_energy_request = {1, 0};
|
||||
control_mode.max_charge_power = {15000, 1};
|
||||
control_mode.min_charge_power = {0, 0};
|
||||
control_mode.max_charge_current = {300, 0};
|
||||
control_mode.max_voltage = {900, 0};
|
||||
control_mode.min_voltage = {150, 0};
|
||||
req.display_parameters.emplace();
|
||||
req.display_parameters->present_soc = 10;
|
||||
req.display_parameters->charging_complete = false;
|
||||
std::vector<uint8_t> expected = {0x80, 0x34, 0x04, 0x32, 0x75, 0x76, 0x9e, 0xc7, 0x10, 0x64, 0xac, 0x8f,
|
||||
0x0c, 0xfd, 0xab, 0x70, 0x62, 0x00, 0x51, 0x84, 0x02, 0x00, 0x24, 0x00,
|
||||
0xc6, 0x90, 0x21, 0xe0, 0x5c, 0x08, 0x30, 0x3c, 0x04, 0x00, 0x00, 0x82,
|
||||
0x04, 0x26, 0x1d, 0x41, 0x00, 0x00, 0x00, 0x80, 0x0a, 0xc0, 0x20, 0x40,
|
||||
0x04, 0x20, 0x38, 0x20, 0x02, 0x58, 0x04, 0x00};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(req) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize dc_charge_loop_res ongoing") {
|
||||
|
||||
message_20::DC_ChargeLoopResponse res;
|
||||
|
||||
res.header = message_20::Header{{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B}, 1725456334};
|
||||
res.response_code = message_20::datatypes::ResponseCode::OK;
|
||||
res.control_mode.emplace<message_20::datatypes::Scheduled_DC_CLResControlMode>();
|
||||
res.current_limit_achieved = true;
|
||||
res.power_limit_achieved = true;
|
||||
res.voltage_limit_achieved = true;
|
||||
res.present_current = {1000, -3};
|
||||
res.present_voltage = {4000, -1};
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x38, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c,
|
||||
0x4d, 0x8c, 0xeb, 0xfe, 0x1b, 0x60, 0x62, 0x00, 0x63, 0xe8,
|
||||
0x74, 0x03, 0x81, 0xfc, 0x28, 0x07, 0xc2, 0x22, 0x90};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(res) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize dc_charge_loop_res dynamic dc_bpt") {
|
||||
|
||||
message_20::DC_ChargeLoopResponse res;
|
||||
|
||||
res.header = message_20::Header{{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B}, 1725456334};
|
||||
res.response_code = message_20::datatypes::ResponseCode::OK;
|
||||
auto& mode = res.control_mode.emplace<message_20::datatypes::Dynamic_DC_CLResControlMode>();
|
||||
|
||||
mode.max_charge_power = {150, 3};
|
||||
mode.min_charge_power = {100, 0};
|
||||
mode.max_charge_current = {60, 1};
|
||||
mode.max_voltage = {9, 2};
|
||||
|
||||
res.current_limit_achieved = true;
|
||||
res.power_limit_achieved = true;
|
||||
res.voltage_limit_achieved = true;
|
||||
res.present_current = {1000, -3};
|
||||
res.present_voltage = {4000, -1};
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x38, 0x04, 0x1E, 0xA6, 0x5F, 0xC9, 0x9B, 0xA7, 0x6C, 0x4D,
|
||||
0x8C, 0xEB, 0xFE, 0x1B, 0x60, 0x62, 0x00, 0x63, 0xE8, 0x74, 0x03,
|
||||
0x81, 0xFC, 0x28, 0x07, 0xC2, 0x22, 0x70, 0x83, 0x09, 0x60, 0x10,
|
||||
0x40, 0x03, 0x20, 0x20, 0x40, 0xF0, 0x10, 0x40, 0x12, 0x00};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(res) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Deserialize dc_charge_loop res ongoing") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x38, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d, 0x8c, 0xeb, 0xfe, 0x1b,
|
||||
0x60, 0x62, 0x00, 0x63, 0xe8, 0x74, 0x03, 0x81, 0xfc, 0x28, 0x07, 0xc2, 0x22, 0x90};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20DC, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::DC_ChargeLoopRes);
|
||||
|
||||
const auto& msg = variant.get<message_20::DC_ChargeLoopResponse>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B});
|
||||
REQUIRE(header.timestamp == 1725456334);
|
||||
REQUIRE(msg.response_code == message_20::datatypes::ResponseCode::OK);
|
||||
REQUIRE(msg.current_limit_achieved == true);
|
||||
REQUIRE(msg.power_limit_achieved == true);
|
||||
REQUIRE(msg.voltage_limit_achieved == true);
|
||||
REQUIRE(dt::from_RationalNumber(msg.present_current) == 1);
|
||||
REQUIRE(dt::from_RationalNumber(msg.present_voltage) == 400);
|
||||
REQUIRE(std::holds_alternative<message_20::datatypes::Scheduled_DC_CLResControlMode>(msg.control_mode));
|
||||
const auto& control_mode = std::get<message_20::datatypes::Scheduled_DC_CLResControlMode>(msg.control_mode);
|
||||
REQUIRE(control_mode.max_charge_current.has_value() == false);
|
||||
REQUIRE(control_mode.max_charge_power.has_value() == false);
|
||||
REQUIRE(control_mode.max_voltage.has_value() == false);
|
||||
REQUIRE(control_mode.min_charge_power.has_value() == false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/message/dc_charge_parameter_discovery.hpp>
|
||||
#include <iso15118/message/variant.hpp>
|
||||
|
||||
#include "helper.hpp"
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
SCENARIO("Se/Deserialize dc charge parameter discovery messages") {
|
||||
|
||||
GIVEN("Deserialize dc_charge_parameter_discovery_req") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x3c, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d, 0x8c, 0x3b, 0xfe,
|
||||
0x1b, 0x60, 0x62, 0x88, 0x10, 0x98, 0x75, 0x04, 0x00, 0x32, 0x02, 0x00, 0x2b, 0x00,
|
||||
0x81, 0x00, 0x01, 0x40, 0x80, 0x08, 0x40, 0x70, 0x40, 0x00, 0x50, 0x80};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20DC, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::DC_ChargeParameterDiscoveryReq);
|
||||
|
||||
const auto& msg = variant.get<message_20::DC_ChargeParameterDiscoveryRequest>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B});
|
||||
REQUIRE(header.timestamp == 1725456323);
|
||||
|
||||
using DC_ModeReq = message_20::datatypes::DC_CPDReqEnergyTransferMode;
|
||||
|
||||
REQUIRE(std::holds_alternative<DC_ModeReq>(msg.transfer_mode));
|
||||
const auto& transfer_mode = std::get<DC_ModeReq>(msg.transfer_mode);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(transfer_mode.max_charge_current) == 300);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(transfer_mode.max_charge_power) == 150000);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(transfer_mode.max_voltage) == 900);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(transfer_mode.min_charge_current) == 10);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(transfer_mode.min_charge_power) == 100);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(transfer_mode.min_voltage) == 10);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize dc_charge_parameter_discovery_req") {
|
||||
|
||||
using DC_ModeReq = message_20::datatypes::DC_CPDReqEnergyTransferMode;
|
||||
|
||||
message_20::DC_ChargeParameterDiscoveryRequest req;
|
||||
|
||||
req.header = message_20::Header{{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B}, 1725456323};
|
||||
auto& mode = req.transfer_mode.emplace<DC_ModeReq>();
|
||||
mode.max_charge_current = {300, 0};
|
||||
mode.max_charge_power = {15000, 1};
|
||||
mode.max_voltage = {900, 0};
|
||||
mode.min_charge_current = {10, 0};
|
||||
mode.min_charge_power = {100, 0};
|
||||
mode.min_voltage = {10, 0};
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x3c, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c,
|
||||
0x4d, 0x8c, 0x3b, 0xfe, 0x1b, 0x60, 0x62, 0x88, 0x10, 0x98,
|
||||
0x75, 0x04, 0x00, 0x32, 0x02, 0x00, 0x2b, 0x00, 0x81, 0x00,
|
||||
0x01, 0x40, 0x80, 0x08, 0x40, 0x70, 0x40, 0x00, 0x50, 0x80};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(req) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(sl): Adding BPT_DC_CPDReqEnergyTransferMode tests
|
||||
// TODO(rb): Adding BPT_DC_CPDResEnergyTransferMode tests
|
||||
|
||||
GIVEN("Deserialize dc_charge_parameter_discovery_res") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x40, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d, 0x8c, 0x4b, 0xfe, 0x1b,
|
||||
0x60, 0x62, 0x00, 0x44, 0x08, 0x50, 0x08, 0x81, 0xfc, 0x34, 0x03, 0xc0, 0xfe, 0x1a, 0x01,
|
||||
0xe0, 0x7d, 0x0e, 0x80, 0x70, 0x3f, 0x85, 0x42, 0x30, 0x1f, 0xc3, 0x40, 0x3c, 0x40};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20DC, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::DC_ChargeParameterDiscoveryRes);
|
||||
|
||||
const auto& msg = variant.get<message_20::DC_ChargeParameterDiscoveryResponse>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B});
|
||||
REQUIRE(header.timestamp == 1725456324);
|
||||
REQUIRE(msg.response_code == message_20::datatypes::ResponseCode::OK);
|
||||
|
||||
using DC_ModeRes = message_20::datatypes::DC_CPDResEnergyTransferMode;
|
||||
|
||||
REQUIRE(std::holds_alternative<DC_ModeRes>(msg.transfer_mode));
|
||||
const auto& transfer_mode = std::get<DC_ModeRes>(msg.transfer_mode);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(transfer_mode.max_charge_current) == 200);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(transfer_mode.max_charge_power) == 22080);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(transfer_mode.max_voltage) == 900);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(transfer_mode.min_charge_current) == 1);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(transfer_mode.min_charge_power) == 200);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(transfer_mode.min_voltage) == 200);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize dc_charge_parameter_discovery_res") {
|
||||
|
||||
using DC_ModeRes = message_20::datatypes::DC_CPDResEnergyTransferMode;
|
||||
|
||||
message_20::DC_ChargeParameterDiscoveryResponse res;
|
||||
|
||||
res.header = message_20::Header{{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B}, 1725456324};
|
||||
res.response_code = message_20::datatypes::ResponseCode::OK;
|
||||
auto& mode = res.transfer_mode.emplace<DC_ModeRes>();
|
||||
mode.max_charge_current = {2000, -1};
|
||||
mode.max_charge_power = {2208, 1};
|
||||
mode.max_voltage = {9000, -1};
|
||||
mode.min_charge_current = {1000, -3};
|
||||
mode.min_charge_power = {2000, -1};
|
||||
mode.min_voltage = {2000, -1};
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x40, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d,
|
||||
0x8c, 0x4b, 0xfe, 0x1b, 0x60, 0x62, 0x00, 0x44, 0x08, 0x50, 0x08,
|
||||
0x81, 0xfc, 0x34, 0x03, 0xc0, 0xfe, 0x1a, 0x01, 0xe0, 0x7d, 0x0e,
|
||||
0x80, 0x70, 0x3f, 0x85, 0x42, 0x30, 0x1f, 0xc3, 0x40, 0x3c, 0x40};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(res) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(sl): Adding BPT_DC_CPDResEnergyTransferMode tests
|
||||
// TODO(rb): Adding BPT_DC_CPDReqEnergyTransferMode tests
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/message/dc_pre_charge.hpp>
|
||||
#include <iso15118/message/variant.hpp>
|
||||
|
||||
#include "helper.hpp"
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
SCENARIO("Se/Deserialize dc pre charge messages") {
|
||||
|
||||
GIVEN("Deserialize dc_pre_charge_req") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x44, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d, 0x8c, 0xbb,
|
||||
0xfe, 0x1b, 0x60, 0x62, 0x21, 0x00, 0x12, 0x00, 0x60, 0x80, 0x09, 0x00, 0x30};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20DC, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::DC_PreChargeReq);
|
||||
|
||||
const auto& msg = variant.get<message_20::DC_PreChargeRequest>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B});
|
||||
REQUIRE(header.timestamp == 1725456331);
|
||||
REQUIRE(msg.processing == message_20::datatypes::Processing::Ongoing);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(msg.present_voltage) == 400);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(msg.target_voltage) == 400);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize dc_pre_charge_req") {
|
||||
|
||||
message_20::DC_PreChargeRequest req;
|
||||
|
||||
req.header = message_20::Header{{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B}, 1725456331};
|
||||
req.processing = message_20::datatypes::Processing::Ongoing;
|
||||
req.target_voltage = {400, 0};
|
||||
req.present_voltage = {400, 0};
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x44, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d, 0x8c, 0xbb,
|
||||
0xfe, 0x1b, 0x60, 0x62, 0x21, 0x00, 0x12, 0x00, 0x60, 0x80, 0x09, 0x00, 0x30};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(req) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize dc_pre_charge_res") {
|
||||
|
||||
message_20::DC_PreChargeResponse res;
|
||||
|
||||
res.header = message_20::Header{{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B}, 1725456332};
|
||||
res.response_code = message_20::datatypes::ResponseCode::OK;
|
||||
res.present_voltage = {4000, -1};
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x48, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d, 0x8c,
|
||||
0xcb, 0xfe, 0x1b, 0x60, 0x62, 0x00, 0x0f, 0xe1, 0x40, 0x3e, 0x00};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(res) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Deserialize dc_pre_charge_res") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x48, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d, 0x8c,
|
||||
0xcb, 0xfe, 0x1b, 0x60, 0x62, 0x00, 0x0f, 0xe1, 0x40, 0x3e, 0x00};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20DC, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::DC_PreChargeRes);
|
||||
|
||||
const auto& msg = variant.get<message_20::DC_PreChargeResponse>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B});
|
||||
REQUIRE(header.timestamp == 1725456332);
|
||||
REQUIRE(msg.response_code == message_20::datatypes::ResponseCode::OK);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(msg.present_voltage) == 400);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/message/dc_welding_detection.hpp>
|
||||
#include <iso15118/message/variant.hpp>
|
||||
|
||||
#include "helper.hpp"
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
SCENARIO("Se/Deserialize dc welding detection messages") {
|
||||
|
||||
GIVEN("Deserialize dc welding detection req") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x4c, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7,
|
||||
0x6c, 0x4d, 0x8d, 0x5b, 0xfe, 0x1b, 0x60, 0x62, 0x20};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20DC, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::DC_WeldingDetectionReq);
|
||||
|
||||
const auto& msg = variant.get<message_20::DC_WeldingDetectionRequest>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B});
|
||||
REQUIRE(header.timestamp == 1725456341);
|
||||
|
||||
REQUIRE(msg.processing == message_20::datatypes::Processing::Ongoing);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize dc welding detection req") {
|
||||
|
||||
message_20::DC_WeldingDetectionRequest req;
|
||||
|
||||
req.header = message_20::Header{{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B}, 1725456341};
|
||||
req.processing = message_20::datatypes::Processing::Ongoing;
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x4c, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7,
|
||||
0x6c, 0x4d, 0x8d, 0x5b, 0xfe, 0x1b, 0x60, 0x62, 0x20};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(req) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize dc_welding_detection res") {
|
||||
|
||||
message_20::DC_WeldingDetectionResponse res;
|
||||
|
||||
res.header = message_20::Header{{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B}, 1725456341};
|
||||
res.response_code = message_20::datatypes::ResponseCode::OK;
|
||||
res.present_voltage = {0, 0};
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x50, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d,
|
||||
0x8d, 0x5b, 0xfe, 0x1b, 0x60, 0x62, 0x00, 0x10, 0x00, 0x00, 0x00};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(res) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Deserialize dc welding detection res") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x50, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d,
|
||||
0x8d, 0x5b, 0xfe, 0x1b, 0x60, 0x62, 0x00, 0x10, 0x00, 0x00, 0x00};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20DC, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::DC_WeldingDetectionRes);
|
||||
|
||||
const auto& msg = variant.get<message_20::DC_WeldingDetectionResponse>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B});
|
||||
REQUIRE(header.timestamp == 1725456341);
|
||||
|
||||
REQUIRE(msg.response_code == message_20::datatypes::ResponseCode::OK);
|
||||
REQUIRE(msg.present_voltage.value == 0);
|
||||
REQUIRE(msg.present_voltage.exponent == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2024 Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include <iso15118/io/stream_view.hpp>
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
template <typename Message> std::vector<uint8_t> serialize_helper(const Message& message) {
|
||||
uint8_t serialization_buffer[1024];
|
||||
io::StreamOutputView out({serialization_buffer, sizeof(serialization_buffer)});
|
||||
|
||||
const auto size = message_20::serialize(message, out);
|
||||
|
||||
return std::vector<uint8_t>(serialization_buffer, serialization_buffer + size);
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/message/power_delivery.hpp>
|
||||
#include <iso15118/message/variant.hpp>
|
||||
|
||||
#include "helper.hpp"
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
SCENARIO("Se/Deserialize power delivery messages") {
|
||||
|
||||
GIVEN("Deserialize power_delivery_req") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x54, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d, 0x8c, 0xdb, 0xfe,
|
||||
0x1b, 0x60, 0x62, 0x00, 0x00, 0x01, 0x00, 0x42, 0x00, 0xb8, 0x41, 0x00, 0x51, 0x24};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20Main, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::PowerDeliveryReq);
|
||||
|
||||
const auto& msg = variant.get<message_20::PowerDeliveryRequest>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B});
|
||||
REQUIRE(header.timestamp == 1725456333);
|
||||
REQUIRE(msg.processing == message_20::datatypes::Processing::Finished);
|
||||
REQUIRE(msg.charge_progress == message_20::datatypes::Progress::Start);
|
||||
|
||||
REQUIRE(msg.power_profile.has_value() == true);
|
||||
auto& power_profile = msg.power_profile.value();
|
||||
REQUIRE(power_profile.time_anchor == 0);
|
||||
REQUIRE(power_profile.entries[0].duration == 23);
|
||||
REQUIRE(message_20::datatypes::from_RationalNumber(power_profile.entries[0].power) == 1000);
|
||||
|
||||
REQUIRE(
|
||||
std::holds_alternative<message_20::datatypes::Scheduled_EVPPTControlMode>(power_profile.control_mode));
|
||||
const auto& mode = std::get<message_20::datatypes::Scheduled_EVPPTControlMode>(power_profile.control_mode);
|
||||
REQUIRE(mode.power_tolerance_acceptance == message_20::datatypes::PowerToleranceAcceptance::Confirmed);
|
||||
REQUIRE(mode.selected_schedule == 1);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize power_delivery_req") {
|
||||
|
||||
message_20::PowerDeliveryRequest req;
|
||||
|
||||
req.header = message_20::Header{{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B}, 1725456333};
|
||||
req.processing = message_20::datatypes::Processing::Finished;
|
||||
req.charge_progress = message_20::datatypes::Progress::Start;
|
||||
req.power_profile = message_20::datatypes::PowerProfile{};
|
||||
req.power_profile->time_anchor = 0;
|
||||
req.power_profile->entries.emplace_back();
|
||||
req.power_profile->entries[0].duration = 23;
|
||||
req.power_profile->entries[0].power = message_20::datatypes::RationalNumber{10, 2};
|
||||
req.power_profile->control_mode.emplace<message_20::datatypes::Scheduled_EVPPTControlMode>();
|
||||
auto& mode = std::get<message_20::datatypes::Scheduled_EVPPTControlMode>(req.power_profile->control_mode);
|
||||
mode.power_tolerance_acceptance = message_20::datatypes::PowerToleranceAcceptance::Confirmed;
|
||||
mode.selected_schedule = 1;
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x54, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c,
|
||||
0x4d, 0x8c, 0xdb, 0xfe, 0x1b, 0x60, 0x62, 0x00, 0x00, 0x01,
|
||||
0x00, 0x42, 0x00, 0xb8, 0x41, 0x00, 0x51, 0x24};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(req) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize power_delivery_res") {
|
||||
|
||||
message_20::PowerDeliveryResponse res;
|
||||
|
||||
res.header = message_20::Header{{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B}, 1725456333};
|
||||
res.response_code = message_20::datatypes::ResponseCode::OK;
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x58, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c,
|
||||
0x4d, 0x8c, 0xdb, 0xfe, 0x1b, 0x60, 0x62, 0x00, 0x40};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(res) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Deserialize power_delivery_res") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x58, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c,
|
||||
0x4d, 0x8c, 0xdb, 0xfe, 0x1b, 0x60, 0x62, 0x00, 0x40};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20Main, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::PowerDeliveryRes);
|
||||
|
||||
const auto& msg = variant.get<message_20::PowerDeliveryResponse>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B});
|
||||
REQUIRE(header.timestamp == 1725456333);
|
||||
REQUIRE(msg.response_code == message_20::datatypes::ResponseCode::OK);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/message/schedule_exchange.hpp>
|
||||
#include <iso15118/message/variant.hpp>
|
||||
|
||||
#include "helper.hpp"
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
namespace dt = iso15118::message_20::datatypes;
|
||||
|
||||
SCENARIO("Se/Deserialize schedule_exchange messages") {
|
||||
|
||||
GIVEN("Deserialize schedule_exchange_req - scheduled mode") {
|
||||
uint8_t doc_raw[] = {0x80, 0x6c, 0x04, 0x23, 0xfe, 0x9d, 0xa7, 0x89, 0x92, 0xab, 0xe5, 0x0c, 0xee, 0x2c, 0x4b,
|
||||
0x70, 0x62, 0x7e, 0x84, 0x28, 0x0e, 0x00, 0x83, 0x00, 0xa0, 0x10, 0x60, 0x28, 0x03, 0xf0,
|
||||
0x02, 0x80, 0x00, 0x04, 0x80, 0xe0, 0x41, 0x80, 0x50, 0x40, 0x00, 0x02, 0xa2, 0xaa, 0xa9,
|
||||
0x03, 0x27, 0x57, 0x26, 0xe3, 0xa6, 0x97, 0x36, 0xf3, 0xa7, 0x37, 0x46, 0x43, 0xa6, 0x97,
|
||||
0x36, 0xf3, 0xa3, 0x13, 0x53, 0x13, 0x13, 0x83, 0xa2, 0xd3, 0x23, 0x03, 0xa5, 0x07, 0x26,
|
||||
0x96, 0x36, 0x54, 0x16, 0xc6, 0x76, 0xf7, 0x26, 0x97, 0x46, 0x86, 0xd3, 0xa3, 0x12, 0xd5,
|
||||
0x06, 0xf7, 0x76, 0x57, 0x20, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x40};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20Main, stream_view);
|
||||
|
||||
THEN("It should be decoded successfully") {
|
||||
|
||||
REQUIRE(variant.get_type() == message_20::Type::ScheduleExchangeReq);
|
||||
|
||||
const auto& msg = variant.get<message_20::ScheduleExchangeRequest>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x47, 0xFD, 0x3B, 0x4F, 0x13, 0x25, 0x57, 0xCA});
|
||||
REQUIRE(header.timestamp == 1727082830);
|
||||
REQUIRE(msg.max_supporting_points == 1024);
|
||||
REQUIRE(std::holds_alternative<dt::Scheduled_SEReqControlMode>(msg.control_mode));
|
||||
auto& control_mode = std::get<dt::Scheduled_SEReqControlMode>(msg.control_mode);
|
||||
REQUIRE(control_mode.departure_time.has_value() == true);
|
||||
REQUIRE(control_mode.departure_time == 7200);
|
||||
REQUIRE(control_mode.target_energy.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*control_mode.target_energy) == 10000.0f);
|
||||
REQUIRE(control_mode.max_energy.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*control_mode.max_energy) == 20000.0f);
|
||||
REQUIRE(control_mode.min_energy.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*control_mode.min_energy) == 0.05f);
|
||||
REQUIRE(control_mode.energy_offer.has_value() == true);
|
||||
auto& ev_energy_offer = control_mode.energy_offer.value();
|
||||
REQUIRE(ev_energy_offer.power_schedule.time_anchor == 0);
|
||||
REQUIRE(ev_energy_offer.power_schedule.entries.size() == 1);
|
||||
REQUIRE(ev_energy_offer.power_schedule.entries.at(0).duration == 3600);
|
||||
REQUIRE(dt::from_RationalNumber(ev_energy_offer.power_schedule.entries.at(0).power) == 10000.0f);
|
||||
REQUIRE(ev_energy_offer.absolute_price_schedule.time_anchor == 0);
|
||||
REQUIRE(ev_energy_offer.absolute_price_schedule.currency == "EUR");
|
||||
REQUIRE(ev_energy_offer.absolute_price_schedule.price_algorithm ==
|
||||
"urn:iso:std:iso:15118:-20:PriceAlgorithm:1-Power");
|
||||
REQUIRE(ev_energy_offer.absolute_price_schedule.price_rule_stacks.size() == 1);
|
||||
REQUIRE(ev_energy_offer.absolute_price_schedule.price_rule_stacks.at(0).duration == 0);
|
||||
REQUIRE(ev_energy_offer.absolute_price_schedule.price_rule_stacks.at(0).price_rules.size() == 1);
|
||||
REQUIRE(dt::from_RationalNumber(
|
||||
ev_energy_offer.absolute_price_schedule.price_rule_stacks.at(0).price_rules.at(0).energy_fee) ==
|
||||
0);
|
||||
REQUIRE(dt::from_RationalNumber(ev_energy_offer.absolute_price_schedule.price_rule_stacks.at(0)
|
||||
.price_rules.at(0)
|
||||
.power_range_start) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize schedule_exchange_req - scheduled mode") {
|
||||
message_20::ScheduleExchangeRequest req;
|
||||
req.header = message_20::Header{{0x47, 0xFD, 0x3B, 0x4F, 0x13, 0x25, 0x57, 0xCA}, 1727082830};
|
||||
req.max_supporting_points = 1024;
|
||||
auto& control_mode = req.control_mode.emplace<dt::Scheduled_SEReqControlMode>();
|
||||
control_mode.departure_time = 7200;
|
||||
control_mode.target_energy = dt::RationalNumber{10, 3};
|
||||
control_mode.max_energy = dt::RationalNumber{20, 3};
|
||||
control_mode.min_energy = dt::RationalNumber{5, -2};
|
||||
control_mode.energy_offer.emplace();
|
||||
control_mode.energy_offer->power_schedule.time_anchor = 0;
|
||||
control_mode.energy_offer->power_schedule.entries.push_back({3600, {10, 3}});
|
||||
control_mode.energy_offer->absolute_price_schedule.time_anchor = 0;
|
||||
control_mode.energy_offer->absolute_price_schedule.currency = "EUR";
|
||||
control_mode.energy_offer->absolute_price_schedule.price_algorithm =
|
||||
"urn:iso:std:iso:15118:-20:PriceAlgorithm:1-Power";
|
||||
dt::EVPriceRuleStack stack;
|
||||
stack.duration = 0;
|
||||
dt::EVPriceRule rule;
|
||||
rule.energy_fee = {0, 0};
|
||||
rule.power_range_start = {0, 0};
|
||||
stack.price_rules.push_back(rule);
|
||||
control_mode.energy_offer->absolute_price_schedule.price_rule_stacks.push_back(stack);
|
||||
|
||||
std::vector<uint8_t> expected = {
|
||||
0x80, 0x6c, 0x04, 0x23, 0xfe, 0x9d, 0xa7, 0x89, 0x92, 0xab, 0xe5, 0x0c, 0xee, 0x2c, 0x4b, 0x70, 0x62, 0x7e,
|
||||
0x84, 0x28, 0x0e, 0x00, 0x83, 0x00, 0xa0, 0x10, 0x60, 0x28, 0x03, 0xf0, 0x02, 0x80, 0x00, 0x04, 0x80, 0xe0,
|
||||
0x41, 0x80, 0x50, 0x40, 0x00, 0x02, 0xa2, 0xaa, 0xa9, 0x03, 0x27, 0x57, 0x26, 0xe3, 0xa6, 0x97, 0x36, 0xf3,
|
||||
0xa7, 0x37, 0x46, 0x43, 0xa6, 0x97, 0x36, 0xf3, 0xa3, 0x13, 0x53, 0x13, 0x13, 0x83, 0xa2, 0xd3, 0x23, 0x03,
|
||||
0xa5, 0x07, 0x26, 0x96, 0x36, 0x54, 0x16, 0xc6, 0x76, 0xf7, 0x26, 0x97, 0x46, 0x86, 0xd3, 0xa3, 0x12, 0xd5,
|
||||
0x06, 0xf7, 0x76, 0x57, 0x20, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x40};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(req) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("DeSerialize schedule_exchange_req - dynamic mode") {
|
||||
uint8_t doc_raw[] = {0x80, 0x6c, 0x04, 0x1c, 0x90, 0x58, 0x02, 0x37, 0x25, 0x7c, 0x84, 0x8d, 0x6b, 0x0c,
|
||||
0x4b, 0x70, 0x62, 0x7e, 0x80, 0xa0, 0x38, 0x03, 0xc1, 0x40, 0x20, 0xc0, 0xa0, 0x10,
|
||||
0x60, 0x78, 0x08, 0x31, 0x13, 0x02, 0x0c, 0x01, 0x40, 0x80, 0x00, 0x00};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20Main, stream_view);
|
||||
|
||||
THEN("It should be decoded successfully") {
|
||||
|
||||
REQUIRE(variant.get_type() == message_20::Type::ScheduleExchangeReq);
|
||||
|
||||
const auto& msg = variant.get<message_20::ScheduleExchangeRequest>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x39, 0x20, 0xB0, 0x04, 0x6E, 0x4A, 0xF9, 0x09});
|
||||
REQUIRE(header.timestamp == 1727076438);
|
||||
|
||||
REQUIRE(msg.max_supporting_points == 1024);
|
||||
|
||||
REQUIRE(std::holds_alternative<dt::Dynamic_SEReqControlMode>(msg.control_mode));
|
||||
auto& control_mode = std::get<dt::Dynamic_SEReqControlMode>(msg.control_mode);
|
||||
|
||||
REQUIRE(control_mode.departure_time == 7200);
|
||||
REQUIRE(control_mode.minimum_soc == 30);
|
||||
REQUIRE(control_mode.target_soc == 80);
|
||||
REQUIRE(dt::from_RationalNumber(control_mode.target_energy) == 40000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(control_mode.max_energy) == 60000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(control_mode.min_energy) == -20000.0f);
|
||||
REQUIRE(control_mode.max_v2x_energy.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*control_mode.max_v2x_energy) == 5000.0f);
|
||||
REQUIRE(control_mode.min_v2x_energy.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*control_mode.min_v2x_energy) == 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize schedule_exchange_req - dynamic mode") {
|
||||
|
||||
message_20::ScheduleExchangeRequest req;
|
||||
|
||||
req.header = message_20::Header{{0x39, 0x20, 0xB0, 0x04, 0x6E, 0x4A, 0xF9, 0x09}, 1727076438};
|
||||
req.max_supporting_points = 1024;
|
||||
auto& control_mode = req.control_mode.emplace<dt::Dynamic_SEReqControlMode>();
|
||||
control_mode.departure_time = 7200;
|
||||
control_mode.minimum_soc = 30;
|
||||
control_mode.target_soc = 80;
|
||||
control_mode.target_energy = dt::RationalNumber{40, 3};
|
||||
control_mode.max_energy = dt::RationalNumber{60, 3};
|
||||
control_mode.min_energy = dt::RationalNumber{-20, 3};
|
||||
control_mode.max_v2x_energy = dt::RationalNumber{5, 3};
|
||||
control_mode.min_v2x_energy = dt::RationalNumber{0, 0};
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x6c, 0x04, 0x1c, 0x90, 0x58, 0x02, 0x37, 0x25, 0x7c,
|
||||
0x84, 0x8d, 0x6b, 0x0c, 0x4b, 0x70, 0x62, 0x7e, 0x80, 0xa0,
|
||||
0x38, 0x03, 0xc1, 0x40, 0x20, 0xc0, 0xa0, 0x10, 0x60, 0x78,
|
||||
0x08, 0x31, 0x13, 0x02, 0x0c, 0x01, 0x40, 0x80, 0x00, 0x00};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(req) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize schedule_exchange_res - scheduled mode - no price") {
|
||||
|
||||
message_20::ScheduleExchangeResponse res;
|
||||
|
||||
res.header = message_20::Header{{0x47, 0xFD, 0x3B, 0x4F, 0x13, 0x25, 0x57, 0xCA}, 1727082831};
|
||||
res.response_code = dt::ResponseCode::OK;
|
||||
res.processing = dt::Processing::Finished;
|
||||
auto& control_mode = res.control_mode.emplace<dt::Scheduled_SEResControlMode>();
|
||||
dt::ScheduleTuple tuple;
|
||||
|
||||
tuple.schedule_tuple_id = 1;
|
||||
tuple.charging_schedule.power_schedule.time_anchor = 1727082831;
|
||||
tuple.charging_schedule.power_schedule.entries.push_back({86400, {2208, 1}, std::nullopt, std::nullopt});
|
||||
|
||||
control_mode.schedule_tuple.push_back(tuple);
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x70, 0x04, 0x23, 0xfe, 0x9d, 0xa7, 0x89, 0x92, 0xab, 0xe5, 0x0c,
|
||||
0xfe, 0x2c, 0x4b, 0x70, 0x62, 0x00, 0x04, 0x00, 0x41, 0x9f, 0xc5, 0x89,
|
||||
0x6e, 0x0c, 0x84, 0x05, 0x18, 0x28, 0x40, 0x85, 0x00, 0x89, 0x29, 0x40};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(res) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Deserialize schedule_exchange_res - scheduled mode - no price") {
|
||||
uint8_t doc_raw[] = {0x80, 0x70, 0x04, 0x23, 0xfe, 0x9d, 0xa7, 0x89, 0x92, 0xab, 0xe5, 0x0c,
|
||||
0xfe, 0x2c, 0x4b, 0x70, 0x62, 0x00, 0x04, 0x00, 0x41, 0x9f, 0xc5, 0x89,
|
||||
0x6e, 0x0c, 0x84, 0x05, 0x18, 0x28, 0x40, 0x85, 0x00, 0x89, 0x29, 0x40};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20Main, stream_view);
|
||||
|
||||
THEN("It should be decoded successfully") {
|
||||
|
||||
REQUIRE(variant.get_type() == message_20::Type::ScheduleExchangeRes);
|
||||
|
||||
const auto& msg = variant.get<message_20::ScheduleExchangeResponse>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x47, 0xFD, 0x3B, 0x4F, 0x13, 0x25, 0x57, 0xCA});
|
||||
REQUIRE(header.timestamp == 1727082831);
|
||||
|
||||
REQUIRE(msg.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(msg.processing == dt::Processing::Finished);
|
||||
REQUIRE(msg.control_mode.index() == 1);
|
||||
REQUIRE(std::holds_alternative<dt::Scheduled_SEResControlMode>(msg.control_mode));
|
||||
auto& control_mode = std::get<dt::Scheduled_SEResControlMode>(msg.control_mode);
|
||||
REQUIRE(control_mode.schedule_tuple.size() == 1);
|
||||
REQUIRE(control_mode.schedule_tuple.at(0).schedule_tuple_id == 1);
|
||||
REQUIRE(control_mode.schedule_tuple.at(0).charging_schedule.power_schedule.time_anchor == 1727082831);
|
||||
REQUIRE(control_mode.schedule_tuple.at(0).charging_schedule.power_schedule.entries.size() == 1);
|
||||
REQUIRE(control_mode.schedule_tuple.at(0).charging_schedule.power_schedule.entries.at(0).duration == 86400);
|
||||
REQUIRE(dt::from_RationalNumber(
|
||||
control_mode.schedule_tuple.at(0).charging_schedule.power_schedule.entries.at(0).power) ==
|
||||
22080.0f);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize schedule_exchange_res - scheduled mode - price level") {
|
||||
message_20::ScheduleExchangeResponse res;
|
||||
|
||||
res.header = message_20::Header{{0x47, 0xFD, 0x3B, 0x4F, 0x13, 0x25, 0x57, 0xCA}, 1727082831};
|
||||
res.response_code = dt::ResponseCode::OK;
|
||||
res.processing = dt::Processing::Finished;
|
||||
auto& control_mode = res.control_mode.emplace<dt::Scheduled_SEResControlMode>();
|
||||
dt::ScheduleTuple tuple;
|
||||
|
||||
tuple.schedule_tuple_id = 1;
|
||||
tuple.charging_schedule.power_schedule.time_anchor = 1727082831;
|
||||
tuple.charging_schedule.power_schedule.entries.push_back({86400, {2208, 1}, std::nullopt, std::nullopt});
|
||||
|
||||
auto& price_level = tuple.charging_schedule.price_schedule.emplace<dt::PriceLevelSchedule>();
|
||||
price_level.time_anchor = 1727082831;
|
||||
price_level.price_schedule_id = 1;
|
||||
price_level.number_of_price_levels = 0;
|
||||
price_level.price_level_schedule_entries.push_back({23, 8});
|
||||
|
||||
control_mode.schedule_tuple.push_back(tuple);
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x70, 0x04, 0x23, 0xfe, 0x9d, 0xa7, 0x89, 0x92, 0xab, 0xe5, 0x0c,
|
||||
0xfe, 0x2c, 0x4b, 0x70, 0x62, 0x00, 0x04, 0x00, 0x41, 0x9f, 0xc5, 0x89,
|
||||
0x6e, 0x0c, 0x84, 0x05, 0x18, 0x28, 0x40, 0x85, 0x00, 0x89, 0x25, 0x67,
|
||||
0xf1, 0x62, 0x5b, 0x83, 0x00, 0x12, 0x00, 0x00, 0xb8, 0x08, 0x11, 0x40};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(res) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("DeSerialize schedule_exchange_res - scheduled mode - price level") {
|
||||
uint8_t doc_raw[] = {0x80, 0x70, 0x04, 0x23, 0xfe, 0x9d, 0xa7, 0x89, 0x92, 0xab, 0xe5, 0x0c,
|
||||
0xfe, 0x2c, 0x4b, 0x70, 0x62, 0x00, 0x04, 0x00, 0x41, 0x9f, 0xc5, 0x89,
|
||||
0x6e, 0x0c, 0x84, 0x05, 0x18, 0x28, 0x40, 0x85, 0x00, 0x89, 0x25, 0x67,
|
||||
0xf1, 0x62, 0x5b, 0x83, 0x00, 0x12, 0x00, 0x00, 0xb8, 0x08, 0x11, 0x40};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20Main, stream_view);
|
||||
|
||||
THEN("It should be decoded successfully") {
|
||||
|
||||
REQUIRE(variant.get_type() == message_20::Type::ScheduleExchangeRes);
|
||||
|
||||
const auto& msg = variant.get<message_20::ScheduleExchangeResponse>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x47, 0xFD, 0x3B, 0x4F, 0x13, 0x25, 0x57, 0xCA});
|
||||
REQUIRE(header.timestamp == 1727082831);
|
||||
|
||||
REQUIRE(msg.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(msg.processing == dt::Processing::Finished);
|
||||
REQUIRE(msg.control_mode.index() == 1);
|
||||
REQUIRE(std::holds_alternative<dt::Scheduled_SEResControlMode>(msg.control_mode));
|
||||
auto& control_mode = std::get<dt::Scheduled_SEResControlMode>(msg.control_mode);
|
||||
REQUIRE(control_mode.schedule_tuple.size() == 1);
|
||||
REQUIRE(control_mode.schedule_tuple.at(0).schedule_tuple_id == 1);
|
||||
REQUIRE(control_mode.schedule_tuple.at(0).charging_schedule.power_schedule.time_anchor == 1727082831);
|
||||
REQUIRE(control_mode.schedule_tuple.at(0).charging_schedule.power_schedule.entries.size() == 1);
|
||||
REQUIRE(control_mode.schedule_tuple.at(0).charging_schedule.power_schedule.entries.at(0).duration == 86400);
|
||||
REQUIRE(dt::from_RationalNumber(
|
||||
control_mode.schedule_tuple.at(0).charging_schedule.power_schedule.entries.at(0).power) ==
|
||||
22080.0f);
|
||||
REQUIRE(control_mode.schedule_tuple.at(0).charging_schedule.price_schedule.index() == 2);
|
||||
auto& price_level =
|
||||
std::get<dt::PriceLevelSchedule>(control_mode.schedule_tuple.at(0).charging_schedule.price_schedule);
|
||||
REQUIRE(price_level.time_anchor == 1727082831);
|
||||
REQUIRE(price_level.price_schedule_id == 1);
|
||||
REQUIRE(price_level.number_of_price_levels == 0);
|
||||
REQUIRE(price_level.price_level_schedule_entries.size() == 1);
|
||||
REQUIRE(price_level.price_level_schedule_entries.at(0).duration == 23);
|
||||
REQUIRE(price_level.price_level_schedule_entries.at(0).price_level == 8);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize schedule_exchange_res - scheduled mode - absolute price") {
|
||||
// TODO(sl): Add test + generate exi stream
|
||||
}
|
||||
|
||||
GIVEN("DeSerialize schedule_exchange_res - scheduled mode - absolute price") {
|
||||
// TODO(rb): Add test + generate exi stream
|
||||
}
|
||||
|
||||
GIVEN("Serialize schedule_exchange_res - dynamic mode") {
|
||||
message_20::ScheduleExchangeResponse res;
|
||||
|
||||
res.header = message_20::Header{{0x39, 0x20, 0xB0, 0x04, 0x6E, 0x4A, 0xF9, 0x09}, 1727076439};
|
||||
res.response_code = dt::ResponseCode::OK;
|
||||
res.processing = dt::Processing::Finished;
|
||||
auto& control_mode = res.control_mode.emplace<dt::Dynamic_SEResControlMode>();
|
||||
control_mode.departure_time = 2000;
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x70, 0x04, 0x1c, 0x90, 0x58, 0x02, 0x37, 0x25, 0x7c, 0x84,
|
||||
0x8d, 0x7b, 0x0c, 0x4b, 0x70, 0x62, 0x00, 0x02, 0x1a, 0x01, 0xe8};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(res) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("DeSerialize schedule_exchange_res - dynamic mode") {
|
||||
uint8_t doc_raw[] = {0x80, 0x70, 0x04, 0x1c, 0x90, 0x58, 0x02, 0x37, 0x25, 0x7c, 0x84,
|
||||
0x8d, 0x7b, 0x0c, 0x4b, 0x70, 0x62, 0x00, 0x02, 0x1a, 0x01, 0xe8};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20Main, stream_view);
|
||||
|
||||
THEN("It should be decoded successfully") {
|
||||
|
||||
REQUIRE(variant.get_type() == message_20::Type::ScheduleExchangeRes);
|
||||
|
||||
const auto& msg = variant.get<message_20::ScheduleExchangeResponse>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x39, 0x20, 0xB0, 0x04, 0x6E, 0x4A, 0xF9, 0x09});
|
||||
REQUIRE(header.timestamp == 1727076439);
|
||||
|
||||
REQUIRE(msg.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(msg.processing == dt::Processing::Finished);
|
||||
REQUIRE(msg.control_mode.index() == 0);
|
||||
REQUIRE(std::holds_alternative<dt::Dynamic_SEResControlMode>(msg.control_mode));
|
||||
auto& control_mode = std::get<dt::Dynamic_SEResControlMode>(msg.control_mode);
|
||||
REQUIRE(control_mode.departure_time == 2000);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/message/service_detail.hpp>
|
||||
#include <iso15118/message/variant.hpp>
|
||||
|
||||
#include "helper.hpp"
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
SCENARIO("Se/Deserialize service_detail messages") {
|
||||
|
||||
GIVEN("Deserialize service_detail_req") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x74, 0x04, 0x02, 0x75, 0xff, 0x96, 0x4a, 0x2c, 0xed,
|
||||
0xa1, 0x0e, 0x38, 0x7e, 0x8a, 0x60, 0x62, 0x02, 0x80};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20Main, stream_view);
|
||||
|
||||
THEN("It should be decoded successfully") {
|
||||
|
||||
REQUIRE(variant.get_type() == message_20::Type::ServiceDetailReq);
|
||||
|
||||
const auto& msg = variant.get<message_20::ServiceDetailRequest>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x04, 0xEB, 0xFF, 0x2C, 0x94, 0x59, 0xDB, 0x42});
|
||||
REQUIRE(header.timestamp == 1692009443);
|
||||
|
||||
REQUIRE(msg.service == message_20::to_underlying_value(message_20::datatypes::ServiceCategory::AC_BPT));
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize service_detail_req") {
|
||||
|
||||
message_20::ServiceDetailRequest req;
|
||||
|
||||
req.header = message_20::Header{{0x04, 0xEB, 0xFF, 0x2C, 0x94, 0x59, 0xDB, 0x42}, 1692009443};
|
||||
req.service = message_20::to_underlying_value(message_20::datatypes::ServiceCategory::AC_BPT);
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x74, 0x04, 0x02, 0x75, 0xff, 0x96, 0x4a, 0x2c, 0xed,
|
||||
0xa1, 0x0e, 0x38, 0x7e, 0x8a, 0x60, 0x62, 0x02, 0x80};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(req) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize service_detail_res") {
|
||||
|
||||
message_20::ServiceDetailResponse res;
|
||||
|
||||
res.header = message_20::Header{{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B}, 1725456323};
|
||||
res.response_code = message_20::datatypes::ResponseCode::OK;
|
||||
res.service = message_20::to_underlying_value(message_20::datatypes::ServiceCategory::DC);
|
||||
|
||||
const auto list = message_20::datatypes::DcParameterList{
|
||||
message_20::datatypes::DcConnector::Extended, message_20::datatypes::ControlMode::Scheduled,
|
||||
message_20::datatypes::MobilityNeedsMode::ProvidedByEvcc, message_20::datatypes::Pricing::NoPricing};
|
||||
res.service_parameter_list = {message_20::datatypes::ParameterSet(0, list)};
|
||||
|
||||
std::vector<uint8_t> expected = {
|
||||
0x80, 0x78, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d, 0x8c, 0x3b, 0xfe, 0x1b, 0x60,
|
||||
0x62, 0x00, 0x00, 0x80, 0x00, 0x02, 0xd0, 0xdb, 0xdb, 0x9b, 0x99, 0x58, 0xdd, 0x1b, 0xdc, 0x98,
|
||||
0x04, 0x00, 0xd4, 0x36, 0xf6, 0xe7, 0x47, 0x26, 0xf6, 0xc4, 0xd6, 0xf6, 0x46, 0x56, 0x00, 0x80,
|
||||
0x4d, 0x35, 0xbd, 0x89, 0xa5, 0xb1, 0xa5, 0xd1, 0xe5, 0x39, 0x95, 0x95, 0x91, 0xcd, 0x35, 0xbd,
|
||||
0x91, 0x95, 0x80, 0x20, 0x09, 0x50, 0x72, 0x69, 0x63, 0x69, 0x6e, 0x67, 0x60, 0x00, 0xa0};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(res) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Deserialize service_detail_res") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x78, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d, 0x8c, 0x3b, 0xfe,
|
||||
0x1b, 0x60, 0x62, 0x00, 0x00, 0x80, 0x00, 0x02, 0xd0, 0xdb, 0xdb, 0x9b, 0x99, 0x58,
|
||||
0xdd, 0x1b, 0xdc, 0x98, 0x04, 0x00, 0xd4, 0x36, 0xf6, 0xe7, 0x47, 0x26, 0xf6, 0xc4,
|
||||
0xd6, 0xf6, 0x46, 0x56, 0x00, 0x80, 0x4d, 0x35, 0xbd, 0x89, 0xa5, 0xb1, 0xa5, 0xd1,
|
||||
0xe5, 0x39, 0x95, 0x95, 0x91, 0xcd, 0x35, 0xbd, 0x91, 0x95, 0x80, 0x20, 0x09, 0x50,
|
||||
0x72, 0x69, 0x63, 0x69, 0x6e, 0x67, 0x60, 0x00, 0xa0};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20Main, stream_view);
|
||||
|
||||
THEN("It should be decoded successfully") {
|
||||
|
||||
REQUIRE(variant.get_type() == message_20::Type::ServiceDetailRes);
|
||||
|
||||
const auto& msg = variant.get<message_20::ServiceDetailResponse>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B});
|
||||
REQUIRE(header.timestamp == 1725456323);
|
||||
|
||||
REQUIRE(msg.service == message_20::to_underlying_value(message_20::datatypes::ServiceCategory::DC));
|
||||
REQUIRE(msg.response_code == message_20::datatypes::ResponseCode::OK);
|
||||
REQUIRE(msg.service_parameter_list.size() == 1);
|
||||
REQUIRE(msg.service_parameter_list[0].id == 0);
|
||||
REQUIRE(msg.service_parameter_list[0].parameter.size() == 4);
|
||||
REQUIRE(msg.service_parameter_list[0].parameter[0].name == "Connector");
|
||||
REQUIRE(std::holds_alternative<int32_t>(msg.service_parameter_list[0].parameter[0].value));
|
||||
REQUIRE(std::get<int32_t>(msg.service_parameter_list[0].parameter[0].value) ==
|
||||
static_cast<int32_t>(message_20::datatypes::DcConnector::Extended));
|
||||
REQUIRE(msg.service_parameter_list[0].parameter[1].name == "ControlMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(msg.service_parameter_list[0].parameter[1].value));
|
||||
REQUIRE(std::get<int32_t>(msg.service_parameter_list[0].parameter[1].value) ==
|
||||
static_cast<int32_t>(message_20::datatypes::ControlMode::Scheduled));
|
||||
REQUIRE(msg.service_parameter_list[0].parameter[2].name == "MobilityNeedsMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(msg.service_parameter_list[0].parameter[2].value));
|
||||
REQUIRE(std::get<int32_t>(msg.service_parameter_list[0].parameter[2].value) ==
|
||||
static_cast<int32_t>(message_20::datatypes::MobilityNeedsMode::ProvidedByEvcc));
|
||||
REQUIRE(msg.service_parameter_list[0].parameter[3].name == "Pricing");
|
||||
REQUIRE(std::holds_alternative<int32_t>(msg.service_parameter_list[0].parameter[3].value));
|
||||
REQUIRE(std::get<int32_t>(msg.service_parameter_list[0].parameter[3].value) ==
|
||||
static_cast<int32_t>(message_20::datatypes::Pricing::NoPricing));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/message/service_discovery.hpp>
|
||||
#include <iso15118/message/variant.hpp>
|
||||
|
||||
#include "helper.hpp"
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
SCENARIO("Se/Deserialize service_discovery messages") {
|
||||
|
||||
GIVEN("Deserialize service_discovery_req") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x7c, 0x04, 0x02, 0x75, 0xff, 0x96, 0x4a, 0x2c,
|
||||
0xed, 0xa1, 0x0e, 0x38, 0x7e, 0x8a, 0x60, 0x62, 0x80};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20Main, stream_view);
|
||||
|
||||
THEN("It should be decoded successfully") {
|
||||
|
||||
REQUIRE(variant.get_type() == message_20::Type::ServiceDiscoveryReq);
|
||||
|
||||
const auto& msg = variant.get<message_20::ServiceDiscoveryRequest>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x04, 0xEB, 0xFF, 0x2C, 0x94, 0x59, 0xDB, 0x42});
|
||||
REQUIRE(header.timestamp == 1692009443);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Deserialize service_discovery_req_with_supported_service_ids") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x7c, 0x04, 0x02, 0x75, 0xff, 0x96, 0x4a, 0x2c, 0xed,
|
||||
0xa1, 0x0e, 0x38, 0x7e, 0x8a, 0x60, 0x62, 0x00, 0x44};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20Main, stream_view);
|
||||
|
||||
THEN("It should be decoded successfully") {
|
||||
|
||||
REQUIRE(variant.get_type() == message_20::Type::ServiceDiscoveryReq);
|
||||
|
||||
const auto& msg = variant.get<message_20::ServiceDiscoveryRequest>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x04, 0xEB, 0xFF, 0x2C, 0x94, 0x59, 0xDB, 0x42});
|
||||
REQUIRE(header.timestamp == 1692009443);
|
||||
REQUIRE(msg.supported_service_ids.has_value() == true);
|
||||
REQUIRE(msg.supported_service_ids.value().size() == 1);
|
||||
REQUIRE(msg.supported_service_ids.value()[0] == 2);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize service_discovery_req") {
|
||||
|
||||
message_20::ServiceDiscoveryRequest req;
|
||||
|
||||
req.header = message_20::Header{{0x04, 0xEB, 0xFF, 0x2C, 0x94, 0x59, 0xDB, 0x42}, 1692009443};
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x7c, 0x04, 0x02, 0x75, 0xff, 0x96, 0x4a, 0x2c,
|
||||
0xed, 0xa1, 0x0e, 0x38, 0x7e, 0x8a, 0x60, 0x62, 0x80};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(req) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize service_discovery_req_with_supported_service_ids") {
|
||||
|
||||
message_20::ServiceDiscoveryRequest req;
|
||||
|
||||
req.header = message_20::Header{{0x04, 0xEB, 0xFF, 0x2C, 0x94, 0x59, 0xDB, 0x42}, 1692009443};
|
||||
req.supported_service_ids = {2};
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x7c, 0x04, 0x02, 0x75, 0xff, 0x96, 0x4a, 0x2c, 0xed,
|
||||
0xa1, 0x0e, 0x38, 0x7e, 0x8a, 0x60, 0x62, 0x00, 0x44};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(req) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize service_discovery_res") {
|
||||
|
||||
message_20::ServiceDiscoveryResponse res;
|
||||
|
||||
res.header = message_20::Header{{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B}, 1725456322};
|
||||
res.response_code = message_20::datatypes::ResponseCode::OK;
|
||||
res.service_renegotiation_supported = false;
|
||||
res.energy_transfer_service_list = {{message_20::datatypes::ServiceCategory::DC, false},
|
||||
{message_20::datatypes::ServiceCategory::DC_BPT, false}};
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x80, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d, 0x8c,
|
||||
0x2b, 0xfe, 0x1b, 0x60, 0x62, 0x00, 0x00, 0x02, 0x00, 0x01, 0x80, 0x50};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(res) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize service_discovery_res_with_vas_list") {
|
||||
|
||||
message_20::ServiceDiscoveryResponse res;
|
||||
|
||||
res.header = message_20::Header{{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B}, 1725456322};
|
||||
res.response_code = message_20::datatypes::ResponseCode::OK;
|
||||
res.service_renegotiation_supported = false;
|
||||
res.energy_transfer_service_list = {{message_20::datatypes::ServiceCategory::DC, false},
|
||||
{message_20::datatypes::ServiceCategory::DC_BPT, false}};
|
||||
res.vas_list = {{message_20::to_underlying_value(message_20::datatypes::ServiceCategory::Internet), true}};
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x80, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d, 0x8c, 0x2b,
|
||||
0xfe, 0x1b, 0x60, 0x62, 0x00, 0x00, 0x02, 0x00, 0x01, 0x80, 0x40, 0x82, 0x22};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(res) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Deserialize service_discovery_res") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x80, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d, 0x8c,
|
||||
0x2b, 0xfe, 0x1b, 0x60, 0x62, 0x00, 0x00, 0x02, 0x00, 0x01, 0x80, 0x50};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20Main, stream_view);
|
||||
|
||||
THEN("It should be decoded successfully") {
|
||||
|
||||
REQUIRE(variant.get_type() == message_20::Type::ServiceDiscoveryRes);
|
||||
|
||||
const auto& msg = variant.get<message_20::ServiceDiscoveryResponse>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B});
|
||||
REQUIRE(header.timestamp == 1725456322);
|
||||
REQUIRE(msg.response_code == message_20::datatypes::ResponseCode::OK);
|
||||
REQUIRE(msg.service_renegotiation_supported == false);
|
||||
REQUIRE(msg.energy_transfer_service_list.size() == 2);
|
||||
REQUIRE(msg.energy_transfer_service_list[0].service_id == message_20::datatypes::ServiceCategory::DC);
|
||||
REQUIRE(msg.energy_transfer_service_list[0].free_service == false);
|
||||
REQUIRE(msg.energy_transfer_service_list[1].service_id == message_20::datatypes::ServiceCategory::DC_BPT);
|
||||
REQUIRE(msg.energy_transfer_service_list[1].free_service == false);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Deserialize service_discovery_res_with_vas_list") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x80, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c, 0x4d, 0x8c, 0x2b,
|
||||
0xfe, 0x1b, 0x60, 0x62, 0x00, 0x00, 0x02, 0x00, 0x01, 0x80, 0x40, 0x82, 0x22};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20Main, stream_view);
|
||||
|
||||
THEN("It should be decoded successfully") {
|
||||
|
||||
REQUIRE(variant.get_type() == message_20::Type::ServiceDiscoveryRes);
|
||||
|
||||
const auto& msg = variant.get<message_20::ServiceDiscoveryResponse>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B});
|
||||
REQUIRE(header.timestamp == 1725456322);
|
||||
REQUIRE(msg.response_code == message_20::datatypes::ResponseCode::OK);
|
||||
REQUIRE(msg.service_renegotiation_supported == false);
|
||||
REQUIRE(msg.energy_transfer_service_list.size() == 2);
|
||||
REQUIRE(msg.energy_transfer_service_list[0].service_id == message_20::datatypes::ServiceCategory::DC);
|
||||
REQUIRE(msg.energy_transfer_service_list[0].free_service == false);
|
||||
REQUIRE(msg.energy_transfer_service_list[1].service_id == message_20::datatypes::ServiceCategory::DC_BPT);
|
||||
REQUIRE(msg.energy_transfer_service_list[1].free_service == false);
|
||||
REQUIRE(msg.vas_list.value().size() == 1);
|
||||
REQUIRE(msg.vas_list.value()[0].service_id ==
|
||||
message_20::to_underlying_value(message_20::datatypes::ServiceCategory::Internet));
|
||||
REQUIRE(msg.vas_list.value()[0].free_service == true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/message/service_selection.hpp>
|
||||
#include <iso15118/message/variant.hpp>
|
||||
|
||||
#include "helper.hpp"
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
namespace dt = message_20::datatypes;
|
||||
|
||||
SCENARIO("Se/Deserialize service_selection messages") {
|
||||
|
||||
GIVEN("Deserialize service_selection_req") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x84, 0x04, 0x02, 0x75, 0xff, 0x96, 0x4a, 0x2c, 0xed, 0xa1,
|
||||
0x0e, 0x38, 0x7e, 0x8a, 0x60, 0x62, 0x01, 0x40, 0x08, 0x80};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20Main, stream_view);
|
||||
|
||||
THEN("It should be decoded successfully") {
|
||||
|
||||
REQUIRE(variant.get_type() == message_20::Type::ServiceSelectionReq);
|
||||
|
||||
const auto& msg = variant.get<message_20::ServiceSelectionRequest>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x04, 0xEB, 0xFF, 0x2C, 0x94, 0x59, 0xDB, 0x42});
|
||||
REQUIRE(header.timestamp == 1692009443);
|
||||
|
||||
REQUIRE(msg.selected_energy_transfer_service.service_id == dt::ServiceCategory::AC_BPT);
|
||||
REQUIRE(msg.selected_energy_transfer_service.parameter_set_id == 1);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Deserialize service_selection_req vas") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x84, 0x04, 0x02, 0x75, 0xFF, 0x96, 0x4A, 0x2C, 0xED, 0xA1, 0x0E,
|
||||
0x38, 0x7E, 0x8A, 0x60, 0x62, 0x01, 0x40, 0x08, 0x04, 0x10, 0x04, 0x20};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20Main, stream_view);
|
||||
|
||||
THEN("It should be decoded successfully") {
|
||||
|
||||
REQUIRE(variant.get_type() == message_20::Type::ServiceSelectionReq);
|
||||
|
||||
const auto& msg = variant.get<message_20::ServiceSelectionRequest>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x04, 0xEB, 0xFF, 0x2C, 0x94, 0x59, 0xDB, 0x42});
|
||||
REQUIRE(header.timestamp == 1692009443);
|
||||
|
||||
REQUIRE(msg.selected_energy_transfer_service.service_id == dt::ServiceCategory::AC_BPT);
|
||||
REQUIRE(msg.selected_energy_transfer_service.parameter_set_id == 1);
|
||||
|
||||
REQUIRE(msg.selected_vas_list.has_value() == true);
|
||||
const auto& selected_vas_list = msg.selected_vas_list.value();
|
||||
REQUIRE(selected_vas_list.size() == 1);
|
||||
REQUIRE(selected_vas_list.at(0).service_id ==
|
||||
message_20::to_underlying_value(dt::ServiceCategory::Internet));
|
||||
REQUIRE(selected_vas_list.at(0).parameter_set_id == 2);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize service_selection_req") {
|
||||
|
||||
message_20::ServiceSelectionRequest req;
|
||||
|
||||
req.header = message_20::Header{{0x04, 0xEB, 0xFF, 0x2C, 0x94, 0x59, 0xDB, 0x42}, 1692009443};
|
||||
req.selected_energy_transfer_service.service_id = dt::ServiceCategory::AC_BPT;
|
||||
req.selected_energy_transfer_service.parameter_set_id = 1;
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x84, 0x04, 0x02, 0x75, 0xff, 0x96, 0x4a, 0x2c, 0xed, 0xa1,
|
||||
0x0e, 0x38, 0x7e, 0x8a, 0x60, 0x62, 0x01, 0x40, 0x08, 0x80};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(req) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize service_selection_req vas") {
|
||||
|
||||
message_20::ServiceSelectionRequest req;
|
||||
|
||||
req.header = message_20::Header{{0x04, 0xEB, 0xFF, 0x2C, 0x94, 0x59, 0xDB, 0x42}, 1692009443};
|
||||
req.selected_energy_transfer_service.service_id = dt::ServiceCategory::AC_BPT;
|
||||
req.selected_energy_transfer_service.parameter_set_id = 1;
|
||||
|
||||
const auto vas = dt::VasSelectedService{message_20::to_underlying_value(dt::ServiceCategory::Internet), 2};
|
||||
|
||||
req.selected_vas_list.emplace({vas});
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x84, 0x04, 0x02, 0x75, 0xFF, 0x96, 0x4A, 0x2C, 0xED, 0xA1, 0x0E,
|
||||
0x38, 0x7E, 0x8A, 0x60, 0x62, 0x01, 0x40, 0x08, 0x04, 0x10, 0x04, 0x20};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(req) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize service_selection_res") {
|
||||
|
||||
message_20::ServiceSelectionResponse res;
|
||||
|
||||
res.header = message_20::Header{{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B}, 1725456323};
|
||||
res.response_code = dt::ResponseCode::OK;
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x88, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c,
|
||||
0x4d, 0x8c, 0x3b, 0xfe, 0x1b, 0x60, 0x62, 0x00, 0x00};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(res) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Deserialize service_selection_res") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x88, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c,
|
||||
0x4d, 0x8c, 0x3b, 0xfe, 0x1b, 0x60, 0x62, 0x00, 0x00};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20Main, stream_view);
|
||||
|
||||
THEN("It should be decoded successfully") {
|
||||
|
||||
REQUIRE(variant.get_type() == message_20::Type::ServiceSelectionRes);
|
||||
|
||||
const auto& msg = variant.get<message_20::ServiceSelectionResponse>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B});
|
||||
REQUIRE(header.timestamp == 1725456323);
|
||||
|
||||
REQUIRE(msg.response_code == dt::ResponseCode::OK);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/message/session_setup.hpp>
|
||||
#include <iso15118/message/variant.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "helper.hpp"
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
SCENARIO("Se/Deserialize session setup messages") {
|
||||
|
||||
GIVEN("Deserialize session_setup_req") {
|
||||
uint8_t doc_raw[] = {0x80, 0x8c, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x9f,
|
||||
0x9c, 0x2b, 0xd0, 0x62, 0xb, 0x2b, 0xa6, 0xa4, 0xab, 0x18, 0x99, 0x19, 0x9a,
|
||||
0x1a, 0x9b, 0x1b, 0x9c, 0x1c, 0x98, 0x20, 0xa1, 0x21, 0xa2, 0x22, 0xac, 0x0};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20Main, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::SessionSetupReq);
|
||||
|
||||
const auto& msg = variant.get<message_20::SessionSetupRequest>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0});
|
||||
REQUIRE(header.timestamp == 1739635913);
|
||||
|
||||
REQUIRE(msg.evccid == "WMIV1234567890ABCDEX");
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize session_setup_res") {
|
||||
|
||||
const auto header = message_20::Header{{0x2E, 0xFA, 0x18, 0x94, 0xDC, 0x7B, 0x90, 0x11}, 1739635913};
|
||||
|
||||
const auto res = message_20::SessionSetupResponse{
|
||||
header, message_20::datatypes::ResponseCode::OK_NewSessionEstablished, "DE*PNX*E12345*1"};
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x90, 0x4, 0x17, 0x7d, 0xc, 0x4a, 0x6e, 0x3d, 0xc8, 0x8, 0x8c,
|
||||
0x9f, 0x9c, 0x2b, 0xd0, 0x62, 0x4, 0x4, 0x51, 0x11, 0x4a, 0x94, 0x13,
|
||||
0x96, 0xa, 0x91, 0x4c, 0x4c, 0x8c, 0xcd, 0xd, 0x4a, 0x8c, 0x40};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(res) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Deserialize session_setup_res") {
|
||||
uint8_t doc_raw[] = {0x80, 0x90, 0x4, 0x17, 0x7d, 0xc, 0x4a, 0x6e, 0x3d, 0xc8, 0x8, 0x8c,
|
||||
0x9f, 0x9c, 0x2b, 0xd0, 0x62, 0x4, 0x4, 0x51, 0x11, 0x4a, 0x94, 0x13,
|
||||
0x96, 0xa, 0x91, 0x4c, 0x4c, 0x8c, 0xcd, 0xd, 0x4a, 0x8c, 0x40};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20Main, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::SessionSetupRes);
|
||||
|
||||
const auto& msg = variant.get<message_20::SessionSetupResponse>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x2E, 0xFA, 0x18, 0x94, 0xDC, 0x7B, 0x90, 0x11});
|
||||
REQUIRE(header.timestamp == 1739635913);
|
||||
|
||||
REQUIRE(msg.evseid == "DE*PNX*E12345*1");
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize session_setup_req") {
|
||||
|
||||
const auto header = message_20::Header{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 1739635913};
|
||||
|
||||
const auto res = message_20::SessionSetupRequest{header, "WMIV1234567890ABCDEX"};
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x8c, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x9f,
|
||||
0x9c, 0x2b, 0xd0, 0x62, 0xb, 0x2b, 0xa6, 0xa4, 0xab, 0x18, 0x99, 0x19, 0x9a,
|
||||
0x1a, 0x9b, 0x1b, 0x9c, 0x1c, 0x98, 0x20, 0xa1, 0x21, 0xa2, 0x22, 0xac, 0x0};
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(res) == expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/message/session_stop.hpp>
|
||||
#include <iso15118/message/variant.hpp>
|
||||
|
||||
#include "helper.hpp"
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
SCENARIO("Se/Deserialize session stop messages") {
|
||||
|
||||
GIVEN("Deserialize session_stop_req") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x94, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7,
|
||||
0x6c, 0x4d, 0x8d, 0x7b, 0xfe, 0x1b, 0x60, 0x62, 0x28};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20Main, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::SessionStopReq);
|
||||
|
||||
const auto& msg = variant.get<message_20::SessionStopRequest>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B});
|
||||
REQUIRE(header.timestamp == 1725456343);
|
||||
|
||||
REQUIRE(msg.charging_session == message_20::datatypes::ChargingSession::Terminate);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize session_stop_req") {
|
||||
|
||||
message_20::SessionStopRequest req;
|
||||
|
||||
req.header = message_20::Header{{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B}, 1725456343};
|
||||
req.charging_session = message_20::datatypes::ChargingSession::Terminate;
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x94, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7,
|
||||
0x6c, 0x4d, 0x8d, 0x7b, 0xfe, 0x1b, 0x60, 0x62, 0x28};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(req) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Serialize session_stop_res") {
|
||||
|
||||
message_20::SessionStopResponse res;
|
||||
|
||||
res.header = message_20::Header{{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B}, 1725456343};
|
||||
res.response_code = message_20::datatypes::ResponseCode::OK;
|
||||
|
||||
std::vector<uint8_t> expected = {0x80, 0x98, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c,
|
||||
0x4d, 0x8d, 0x7b, 0xfe, 0x1b, 0x60, 0x62, 0x00, 0x00};
|
||||
|
||||
THEN("It should be serialized successfully") {
|
||||
REQUIRE(serialize_helper(res) == expected);
|
||||
}
|
||||
}
|
||||
GIVEN("Deserialize session_stop_res") {
|
||||
|
||||
uint8_t doc_raw[] = {0x80, 0x98, 0x04, 0x1e, 0xa6, 0x5f, 0xc9, 0x9b, 0xa7, 0x6c,
|
||||
0x4d, 0x8d, 0x7b, 0xfe, 0x1b, 0x60, 0x62, 0x00, 0x00};
|
||||
|
||||
const io::StreamInputView stream_view{doc_raw, sizeof(doc_raw)};
|
||||
|
||||
message_20::Variant variant(io::v2gtp::PayloadType::Part20Main, stream_view);
|
||||
|
||||
THEN("It should be deserialized successfully") {
|
||||
REQUIRE(variant.get_type() == message_20::Type::SessionStopRes);
|
||||
|
||||
const auto& msg = variant.get<message_20::SessionStopResponse>();
|
||||
const auto& header = msg.header;
|
||||
|
||||
REQUIRE(header.session_id == std::array<uint8_t, 8>{0x3D, 0x4C, 0xBF, 0x93, 0x37, 0x4E, 0xD8, 0x9B});
|
||||
REQUIRE(header.timestamp == 1725456343);
|
||||
|
||||
REQUIRE(msg.response_code == message_20::datatypes::ResponseCode::OK);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
add_subdirectory(d20)
|
||||
add_subdirectory(fsm)
|
||||
add_subdirectory(io)
|
||||
add_subdirectory(session)
|
||||
add_subdirectory(states)
|
||||
|
||||
# add_executable(secc)
|
||||
#
|
||||
# target_sources(secc
|
||||
# PRIVATE
|
||||
# secc.cpp
|
||||
# )
|
||||
#
|
||||
# target_link_libraries(secc
|
||||
# PRIVATE
|
||||
# iso15118::iso15118
|
||||
# )
|
||||
@@ -0,0 +1,11 @@
|
||||
include(Catch)
|
||||
|
||||
add_executable(test_timeouts timeouts.cpp)
|
||||
|
||||
target_link_libraries(test_timeouts
|
||||
PRIVATE
|
||||
iso15118
|
||||
Catch2::Catch2WithMain
|
||||
)
|
||||
|
||||
catch_discover_tests(test_timeouts)
|
||||
@@ -0,0 +1,77 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2025 Pionix GmbH and Contributors to EVerest
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include <iso15118/d20/timeout.hpp>
|
||||
|
||||
SCENARIO("Timeouts Tests") {
|
||||
|
||||
GIVEN("Basic Timeout") {
|
||||
auto timeout = iso15118::Timeout(20);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
REQUIRE(timeout.is_reached() == false);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
REQUIRE(timeout.is_reached() == true);
|
||||
}
|
||||
|
||||
GIVEN("Start Timeout and reset timeout after reaching it") {
|
||||
auto timeouts = iso15118::d20::Timeouts{};
|
||||
|
||||
timeouts.start_timeout(iso15118::d20::TimeoutType::CONTACTOR, 20);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
|
||||
auto timeout_reached = timeouts.check();
|
||||
REQUIRE(timeout_reached.has_value() == false);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
timeout_reached = timeouts.check();
|
||||
REQUIRE(timeout_reached.has_value() == true);
|
||||
auto& reached = timeout_reached.value();
|
||||
REQUIRE(reached.size() == 1);
|
||||
REQUIRE(reached.at(0) == iso15118::d20::TimeoutType::CONTACTOR);
|
||||
|
||||
timeouts.reset_timeout(iso15118::d20::TimeoutType::CONTACTOR);
|
||||
timeout_reached = timeouts.check();
|
||||
REQUIRE(timeout_reached.has_value() == false);
|
||||
}
|
||||
|
||||
GIVEN("Start Timeout and stop timeout before reaching it") {
|
||||
auto timeouts = iso15118::d20::Timeouts{};
|
||||
|
||||
timeouts.start_timeout(iso15118::d20::TimeoutType::CONTACTOR, 20);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
auto timeout_reached = timeouts.check();
|
||||
REQUIRE(timeout_reached.has_value() == false);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
timeouts.stop_timeout(iso15118::d20::TimeoutType::CONTACTOR);
|
||||
|
||||
timeout_reached = timeouts.check();
|
||||
REQUIRE(timeout_reached.has_value() == false);
|
||||
}
|
||||
|
||||
GIVEN("Parallel Timeouts") {
|
||||
|
||||
auto timeouts = iso15118::d20::Timeouts{};
|
||||
|
||||
timeouts.start_timeout(iso15118::d20::TimeoutType::SEQUENCE, 30);
|
||||
timeouts.start_timeout(iso15118::d20::TimeoutType::PERFORMANCE, 10);
|
||||
timeouts.start_timeout(iso15118::d20::TimeoutType::CONTACTOR, 20);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
const auto timeouts_reached = timeouts.check();
|
||||
|
||||
REQUIRE(timeouts_reached.has_value());
|
||||
|
||||
const auto& reached = timeouts_reached.value();
|
||||
|
||||
REQUIRE(reached.at(0) == iso15118::d20::TimeoutType::PERFORMANCE);
|
||||
REQUIRE(reached.at(1) == iso15118::d20::TimeoutType::CONTACTOR);
|
||||
REQUIRE(reached.at(2) == iso15118::d20::TimeoutType::SEQUENCE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
include(Catch)
|
||||
|
||||
function(create_fsm_test_target NAME)
|
||||
add_executable(test_fsm_${NAME} ${NAME}.cpp)
|
||||
target_sources(test_fsm_${NAME}
|
||||
PRIVATE
|
||||
helper.cpp
|
||||
)
|
||||
target_link_libraries(test_fsm_${NAME}
|
||||
PRIVATE
|
||||
iso15118
|
||||
Catch2::Catch2WithMain
|
||||
)
|
||||
catch_discover_tests(test_fsm_${NAME})
|
||||
endfunction()
|
||||
|
||||
create_fsm_test_target(session_setup)
|
||||
create_fsm_test_target(service_detail)
|
||||
|
||||
add_executable(test_d20_transitions d20_transitions.cpp)
|
||||
|
||||
target_sources(test_d20_transitions
|
||||
PRIVATE
|
||||
helper.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(test_d20_transitions
|
||||
PRIVATE
|
||||
iso15118
|
||||
Catch2::Catch2WithMain
|
||||
)
|
||||
|
||||
catch_discover_tests(test_d20_transitions)
|
||||
@@ -0,0 +1,186 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include "helper.hpp"
|
||||
|
||||
#include <iso15118/d20/state/session_setup.hpp>
|
||||
#include <iso15118/d20/state/supported_app_protocol.hpp>
|
||||
|
||||
#include <iso15118/message/supported_app_protocol.hpp>
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
SCENARIO("ISO15118-20 supported app protocol state transitions") {
|
||||
|
||||
// Move to helper function?
|
||||
const auto evse_id = std::string("everest se");
|
||||
const std::vector<message_20::datatypes::ServiceCategory> supported_energy_services = {
|
||||
message_20::datatypes::ServiceCategory::DC};
|
||||
const std::vector<uint16_t> vas_services{};
|
||||
const auto cert_install{false};
|
||||
const std::vector<message_20::datatypes::Authorization> auth_services = {message_20::datatypes::Authorization::EIM};
|
||||
const d20::DcTransferLimits dc_limits;
|
||||
const d20::AcTransferLimits ac_limits;
|
||||
const d20::DcTransferLimits powersupply_limits;
|
||||
const std::vector<d20::ControlMobilityNeedsModes> control_mobility_modes = {
|
||||
{message_20::datatypes::ControlMode::Scheduled, message_20::datatypes::MobilityNeedsMode::ProvidedByEvcc}};
|
||||
const std::string custom_namespace = "urn:iso:std:iso:15118:-20:AABB";
|
||||
|
||||
const d20::EvseSetupConfig evse_setup{
|
||||
evse_id, supported_energy_services, auth_services, vas_services, cert_install, dc_limits,
|
||||
ac_limits, control_mobility_modes, custom_namespace, std::nullopt, std::nullopt, powersupply_limits};
|
||||
|
||||
std::optional<d20::PauseContext> pause_ctx{std::nullopt};
|
||||
|
||||
const session::feedback::Callbacks callbacks{};
|
||||
|
||||
auto state_helper = FsmStateHelper(d20::SessionConfig(evse_setup), pause_ctx, callbacks);
|
||||
auto ctx = state_helper.get_context();
|
||||
|
||||
GIVEN("Good case - DC") {
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::SupportedAppProtocol>()};
|
||||
|
||||
message_20::SupportedAppProtocolRequest req;
|
||||
auto& ap = req.app_protocol.emplace_back();
|
||||
ap.priority = 1;
|
||||
ap.protocol_namespace = "urn:iso:std:iso:15118:-20:DC";
|
||||
ap.schema_id = 1;
|
||||
ap.version_number_major = 1;
|
||||
ap.version_number_minor = 0;
|
||||
|
||||
state_helper.handle_request(req);
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == true);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::SessionSetup);
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::SupportedAppProtocolResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& supported_app_res = response_message.value();
|
||||
|
||||
REQUIRE(supported_app_res.response_code ==
|
||||
message_20::SupportedAppProtocolResponse::ResponseCode::OK_SuccessfulNegotiation);
|
||||
REQUIRE(supported_app_res.schema_id.value_or(0) == 1);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - Custom") {
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::SupportedAppProtocol>()};
|
||||
|
||||
message_20::SupportedAppProtocolRequest req;
|
||||
|
||||
auto& ap = req.app_protocol.emplace_back();
|
||||
ap.priority = 1;
|
||||
ap.protocol_namespace = "urn:iso:std:iso:15118:-20:AABB";
|
||||
ap.schema_id = 1;
|
||||
ap.version_number_major = 1;
|
||||
ap.version_number_minor = 0;
|
||||
|
||||
state_helper.handle_request(req);
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == true);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::SessionSetup);
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::SupportedAppProtocolResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& supported_app_res = response_message.value();
|
||||
|
||||
REQUIRE(supported_app_res.response_code ==
|
||||
message_20::SupportedAppProtocolResponse::ResponseCode::OK_SuccessfulNegotiation);
|
||||
REQUIRE(supported_app_res.schema_id.value_or(0) == 1);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - Priority") {
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::SupportedAppProtocol>()};
|
||||
|
||||
message_20::SupportedAppProtocolRequest req;
|
||||
auto& ap_dc = req.app_protocol.emplace_back();
|
||||
ap_dc.priority = 2;
|
||||
ap_dc.protocol_namespace = "urn:iso:std:iso:15118:-20:DC";
|
||||
ap_dc.schema_id = 1;
|
||||
ap_dc.version_number_major = 1;
|
||||
ap_dc.version_number_minor = 0;
|
||||
|
||||
auto& ap_custom = req.app_protocol.emplace_back();
|
||||
ap_custom.priority = 1;
|
||||
ap_custom.protocol_namespace = "urn:iso:std:iso:15118:-20:AC";
|
||||
ap_custom.schema_id = 3;
|
||||
ap_custom.version_number_major = 1;
|
||||
ap_custom.version_number_minor = 0;
|
||||
|
||||
state_helper.handle_request(req);
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == true);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::SessionSetup);
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::SupportedAppProtocolResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& supported_app_res = response_message.value();
|
||||
|
||||
REQUIRE(supported_app_res.response_code ==
|
||||
message_20::SupportedAppProtocolResponse::ResponseCode::OK_SuccessfulNegotiation);
|
||||
REQUIRE(supported_app_res.schema_id.value_or(0) == 3);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Bad case - Unknown protocol namespace") {
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::SupportedAppProtocol>()};
|
||||
|
||||
message_20::SupportedAppProtocolRequest req;
|
||||
auto& ap = req.app_protocol.emplace_back();
|
||||
ap.priority = 1;
|
||||
ap.protocol_namespace = "Foobar";
|
||||
ap.schema_id = 12;
|
||||
ap.version_number_major = 2;
|
||||
ap.version_number_minor = 11;
|
||||
|
||||
state_helper.handle_request(req);
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == false);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::SupportedAppProtocol);
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::SupportedAppProtocolResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& supported_app_res = response_message.value();
|
||||
|
||||
REQUIRE(supported_app_res.response_code ==
|
||||
message_20::SupportedAppProtocolResponse::ResponseCode::Failed_NoNegotiation);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Bad case - empty app protocol") {
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::SupportedAppProtocol>()};
|
||||
|
||||
message_20::SupportedAppProtocolRequest req;
|
||||
req.app_protocol.emplace_back();
|
||||
|
||||
state_helper.handle_request(req);
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == false);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::SupportedAppProtocol);
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::SupportedAppProtocolResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& supported_app_res = response_message.value();
|
||||
|
||||
REQUIRE(supported_app_res.response_code ==
|
||||
message_20::SupportedAppProtocolResponse::ResponseCode::Failed_NoNegotiation);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include "helper.hpp"
|
||||
|
||||
iso15118::d20::Context& FsmStateHelper::get_context() {
|
||||
return ctx;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
|
||||
#include <everest/util/fsm/fsm.hpp>
|
||||
#include <iso15118/d20/config.hpp>
|
||||
#include <iso15118/d20/context.hpp>
|
||||
#include <iso15118/d20/control_event.hpp>
|
||||
#include <iso15118/d20/states.hpp>
|
||||
#include <iso15118/d20/timeout.hpp>
|
||||
#include <iso15118/detail/cb_exi.hpp>
|
||||
#include <iso15118/io/logging.hpp>
|
||||
#include <iso15118/io/sdp.hpp>
|
||||
#include <iso15118/io/stream_view.hpp>
|
||||
#include <iso15118/message/variant.hpp>
|
||||
#include <iso15118/session/feedback.hpp>
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
class FsmStateHelper {
|
||||
public:
|
||||
FsmStateHelper(const d20::SessionConfig& config, std::optional<d20::PauseContext>& pause_ctx_,
|
||||
const session::feedback::Callbacks& callbacks) :
|
||||
log(this), ctx(callbacks, log, config, pause_ctx_, active_control_event, msg_exch, timeouts) {
|
||||
|
||||
session::logging::set_session_log_callback([](std::size_t, const session::logging::Event& event) {
|
||||
if (const auto* simple_event = std::get_if<session::logging::SimpleEvent>(&event)) {
|
||||
printf("log(session: simple event): %s\n", simple_event->info.c_str());
|
||||
} else {
|
||||
printf("log(session): not decoded\n");
|
||||
}
|
||||
});
|
||||
|
||||
io::set_logging_callback([](LogLevel level, std::string message) {
|
||||
printf("log(%d): %s\n", static_cast<int>(level), message.c_str());
|
||||
});
|
||||
};
|
||||
|
||||
d20::Context& get_context();
|
||||
|
||||
template <typename RequestType> void handle_request(const RequestType& request) {
|
||||
msg_exch.set_request(std::make_unique<message_20::Variant>(request));
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<uint8_t, 1024> output_buffer{};
|
||||
io::StreamOutputView output_stream_view{output_buffer.data(), output_buffer.size()};
|
||||
|
||||
d20::MessageExchange msg_exch{output_stream_view};
|
||||
std::optional<d20::ControlEvent> active_control_event;
|
||||
|
||||
session::SessionLogger log;
|
||||
|
||||
d20::Timeouts timeouts;
|
||||
|
||||
d20::Context ctx;
|
||||
};
|
||||
@@ -0,0 +1,953 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2025 Pionix GmbH and Contributors to EVerest
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include "helper.hpp"
|
||||
|
||||
#include <iso15118/d20/state/service_detail.hpp>
|
||||
#include <iso15118/d20/state/service_selection.hpp>
|
||||
|
||||
#include <iso15118/message/service_detail.hpp>
|
||||
|
||||
#include <iso15118/detail/helper.hpp>
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
SCENARIO("ISO15118-20 service detail state transitions") {
|
||||
|
||||
namespace dt = message_20::datatypes;
|
||||
|
||||
const auto evse_id = std::string("everest se");
|
||||
const std::vector<dt::ServiceCategory> supported_energy_services = {dt::ServiceCategory::DC};
|
||||
const auto cert_install{false};
|
||||
const std::vector<uint16_t> vas_services{}; // TODO(SL): Add Custom service
|
||||
const std::vector<dt::Authorization> auth_services = {dt::Authorization::EIM};
|
||||
const d20::DcTransferLimits dc_limits;
|
||||
const d20::AcTransferLimits ac_limits;
|
||||
const d20::DcTransferLimits powersupply_limits;
|
||||
const std::vector<d20::ControlMobilityNeedsModes> control_mobility_modes = {
|
||||
{dt::ControlMode::Scheduled, dt::MobilityNeedsMode::ProvidedByEvcc}};
|
||||
|
||||
const d20::EvseSetupConfig evse_setup{
|
||||
evse_id, supported_energy_services, auth_services, vas_services, cert_install, dc_limits,
|
||||
ac_limits, control_mobility_modes, std::nullopt, std::nullopt, std::nullopt, powersupply_limits};
|
||||
|
||||
std::optional<d20::PauseContext> pause_ctx{std::nullopt};
|
||||
|
||||
session::feedback::Callbacks callbacks{};
|
||||
|
||||
callbacks.get_vas_parameters = [](uint16_t id) {
|
||||
auto service_parameter_list = dt::ServiceParameterList{};
|
||||
|
||||
if (id == 4599) {
|
||||
auto& parameter_set = service_parameter_list.emplace_back();
|
||||
parameter_set.id = 0;
|
||||
parameter_set.parameter.push_back({"Service1", 40});
|
||||
parameter_set.parameter.push_back({"Service2", "house"});
|
||||
} else if (id == message_20::to_underlying_value(dt::ServiceCategory::ParkingStatus)) {
|
||||
auto& parameter_set = service_parameter_list.emplace_back();
|
||||
parameter_set.id = 0;
|
||||
parameter_set.parameter.push_back({"IntendedService", 1});
|
||||
parameter_set.parameter.push_back({"ParkingStatusType", 4});
|
||||
} else if (id == message_20::to_underlying_value(dt::ServiceCategory::Internet)) {
|
||||
auto& parameter_set = service_parameter_list.emplace_back();
|
||||
parameter_set.id = 3;
|
||||
parameter_set.parameter.push_back({"Protocol", "http"});
|
||||
parameter_set.parameter.push_back({"Port", 80});
|
||||
}
|
||||
|
||||
return std::make_optional(service_parameter_list);
|
||||
};
|
||||
|
||||
auto state_helper = FsmStateHelper(d20::SessionConfig(evse_setup), pause_ctx, callbacks);
|
||||
auto ctx = state_helper.get_context();
|
||||
ctx.session = d20::Session();
|
||||
|
||||
GIVEN("Bad Case - Unknown session") {
|
||||
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::ServiceDetail>()};
|
||||
|
||||
const auto header_req = message_20::Header{d20::Session().get_id(), 1691411798};
|
||||
const auto req =
|
||||
message_20::ServiceDetailRequest{header_req, message_20::to_underlying_value(dt::ServiceCategory::DC)};
|
||||
|
||||
state_helper.handle_request(req);
|
||||
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == false);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::ServiceDetail);
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::ServiceDetailResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& res = response_message.value();
|
||||
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_UnknownSession);
|
||||
REQUIRE(res.service == message_20::to_underlying_value(dt::ServiceCategory::DC));
|
||||
REQUIRE(res.service_parameter_list.size() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Bad Case - FAILED_ServiceIDInvalid") {
|
||||
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::ServiceDetail>()};
|
||||
|
||||
const auto header_req = message_20::Header{ctx.session.get_id(), 1691411798};
|
||||
const auto req =
|
||||
message_20::ServiceDetailRequest{header_req, message_20::to_underlying_value(dt::ServiceCategory::DC_BPT)};
|
||||
|
||||
state_helper.handle_request(req);
|
||||
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == false);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::ServiceDetail);
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::ServiceDetailResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& res = response_message.value();
|
||||
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_ServiceIDInvalid);
|
||||
REQUIRE(res.service == message_20::to_underlying_value(dt::ServiceCategory::DC));
|
||||
REQUIRE(res.service_parameter_list.size() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good Case - DC Service") {
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::ServiceDetail>()};
|
||||
|
||||
const auto header_req = message_20::Header{ctx.session.get_id(), 1691411798};
|
||||
const auto req =
|
||||
message_20::ServiceDetailRequest{header_req, message_20::to_underlying_value(dt::ServiceCategory::DC)};
|
||||
|
||||
state_helper.handle_request(req);
|
||||
|
||||
ctx.session.offered_services.energy_services = {dt::ServiceCategory::DC};
|
||||
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == true);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::ServiceSelection);
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::ServiceDetailResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& res = response_message.value();
|
||||
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.service == message_20::to_underlying_value(dt::ServiceCategory::DC));
|
||||
REQUIRE(res.service_parameter_list.size() == 1);
|
||||
auto& parameters = res.service_parameter_list[0];
|
||||
REQUIRE(parameters.id == 0);
|
||||
REQUIRE(parameters.parameter.size() == 4);
|
||||
|
||||
// Connector == Extended
|
||||
REQUIRE(parameters.parameter[0].name == "Connector");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[0].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[0].value) == 2);
|
||||
// ControlMode == Scheduled
|
||||
REQUIRE(parameters.parameter[1].name == "ControlMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[1].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[1].value) == 1);
|
||||
// MobilityNeedsMode == ProvidedbyEvcc
|
||||
REQUIRE(parameters.parameter[2].name == "MobilityNeedsMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[2].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[2].value) == 1);
|
||||
// Pricing == No Pricing
|
||||
REQUIRE(parameters.parameter[3].name == "Pricing");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[3].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[3].value) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good Case - DC_BPT Service") {
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::ServiceDetail>()};
|
||||
|
||||
const auto header_req = message_20::Header{ctx.session.get_id(), 1691411798};
|
||||
const auto req =
|
||||
message_20::ServiceDetailRequest{header_req, message_20::to_underlying_value(dt::ServiceCategory::DC_BPT)};
|
||||
|
||||
state_helper.handle_request(req);
|
||||
|
||||
ctx.session.offered_services.energy_services = {dt::ServiceCategory::DC_BPT};
|
||||
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == true);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::ServiceSelection);
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::ServiceDetailResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& res = response_message.value();
|
||||
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.service == message_20::to_underlying_value(dt::ServiceCategory::DC_BPT));
|
||||
REQUIRE(res.service_parameter_list.size() == 1);
|
||||
auto& parameters = res.service_parameter_list[0];
|
||||
REQUIRE(parameters.id == 0);
|
||||
REQUIRE(parameters.parameter.size() == 6);
|
||||
|
||||
// Connector == Extended
|
||||
REQUIRE(parameters.parameter[0].name == "Connector");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[0].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[0].value) == 2);
|
||||
// ControlMode == Scheduled
|
||||
REQUIRE(parameters.parameter[1].name == "ControlMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[1].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[1].value) == 1);
|
||||
// MobilityNeedsMode == ProvidedbyEvcc
|
||||
REQUIRE(parameters.parameter[2].name == "MobilityNeedsMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[2].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[2].value) == 1);
|
||||
// Pricing == No Pricing
|
||||
REQUIRE(parameters.parameter[3].name == "Pricing");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[3].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[3].value) == 0);
|
||||
// BPTChannel == Unified
|
||||
REQUIRE(parameters.parameter[4].name == "BPTChannel");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[4].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[4].value) == 1);
|
||||
// GeneratorMode == GridFollowing
|
||||
REQUIRE(parameters.parameter[5].name == "GeneratorMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[5].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[5].value) == 1);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good Case - 2x DC Service") {
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::ServiceDetail>()};
|
||||
|
||||
const auto header_req = message_20::Header{ctx.session.get_id(), 1691411798};
|
||||
const auto req =
|
||||
message_20::ServiceDetailRequest{header_req, message_20::to_underlying_value(dt::ServiceCategory::DC)};
|
||||
|
||||
state_helper.handle_request(req);
|
||||
|
||||
ctx.session.offered_services.energy_services = {dt::ServiceCategory::DC};
|
||||
|
||||
const auto temp = ctx.session_config.dc_parameter_list;
|
||||
ctx.session_config.dc_parameter_list = {{
|
||||
dt::DcConnector::Extended,
|
||||
dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc,
|
||||
dt::Pricing::NoPricing,
|
||||
},
|
||||
{
|
||||
dt::DcConnector::Extended,
|
||||
dt::ControlMode::Dynamic,
|
||||
dt::MobilityNeedsMode::ProvidedBySecc,
|
||||
dt::Pricing::NoPricing,
|
||||
}};
|
||||
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == true);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::ServiceSelection);
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::ServiceDetailResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& res = response_message.value();
|
||||
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.service == message_20::to_underlying_value(dt::ServiceCategory::DC));
|
||||
REQUIRE(res.service_parameter_list.size() == 2);
|
||||
auto& parameters_0 = res.service_parameter_list[0];
|
||||
REQUIRE(parameters_0.id == 0);
|
||||
REQUIRE(parameters_0.parameter.size() == 4);
|
||||
|
||||
// Connector == Extended
|
||||
REQUIRE(parameters_0.parameter[0].name == "Connector");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters_0.parameter[0].value));
|
||||
REQUIRE(std::get<int32_t>(parameters_0.parameter[0].value) == 2);
|
||||
// ControlMode == Scheduled
|
||||
REQUIRE(parameters_0.parameter[1].name == "ControlMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters_0.parameter[1].value));
|
||||
REQUIRE(std::get<int32_t>(parameters_0.parameter[1].value) == 1);
|
||||
// MobilityNeedsMode == ProvidedbyEvcc
|
||||
REQUIRE(parameters_0.parameter[2].name == "MobilityNeedsMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters_0.parameter[2].value));
|
||||
REQUIRE(std::get<int32_t>(parameters_0.parameter[2].value) == 1);
|
||||
// Pricing == No Pricing
|
||||
REQUIRE(parameters_0.parameter[3].name == "Pricing");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters_0.parameter[3].value));
|
||||
REQUIRE(std::get<int32_t>(parameters_0.parameter[3].value) == 0);
|
||||
|
||||
auto& parameters_1 = res.service_parameter_list[1];
|
||||
REQUIRE(parameters_1.id == 1);
|
||||
REQUIRE(parameters_1.parameter.size() == 4);
|
||||
|
||||
// Connector == Extended
|
||||
REQUIRE(parameters_1.parameter[0].name == "Connector");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters_1.parameter[0].value));
|
||||
REQUIRE(std::get<int32_t>(parameters_1.parameter[0].value) == 2);
|
||||
// ControlMode == Scheduled
|
||||
REQUIRE(parameters_1.parameter[1].name == "ControlMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters_1.parameter[1].value));
|
||||
REQUIRE(std::get<int32_t>(parameters_1.parameter[1].value) == 2);
|
||||
// MobilityNeedsMode == ProvidedbyEvcc
|
||||
REQUIRE(parameters_1.parameter[2].name == "MobilityNeedsMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters_1.parameter[2].value));
|
||||
REQUIRE(std::get<int32_t>(parameters_1.parameter[2].value) == 2);
|
||||
// Pricing == No Pricing
|
||||
REQUIRE(parameters_1.parameter[3].name == "Pricing");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters_1.parameter[3].value));
|
||||
REQUIRE(std::get<int32_t>(parameters_1.parameter[3].value) == 0);
|
||||
}
|
||||
|
||||
ctx.session_config.dc_parameter_list = temp;
|
||||
}
|
||||
|
||||
GIVEN("Good Case - DC Service: Scheduled Mode: 1, MobilityNeedsMode: 2 change to 1") {
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::ServiceDetail>()};
|
||||
|
||||
const auto header_req = message_20::Header{ctx.session.get_id(), 1691411798};
|
||||
const auto req =
|
||||
message_20::ServiceDetailRequest{header_req, message_20::to_underlying_value(dt::ServiceCategory::DC)};
|
||||
|
||||
state_helper.handle_request(req);
|
||||
|
||||
ctx.session.offered_services.energy_services = {dt::ServiceCategory::DC};
|
||||
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == true);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::ServiceSelection);
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::ServiceDetailResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& res = response_message.value();
|
||||
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.service == message_20::to_underlying_value(dt::ServiceCategory::DC));
|
||||
REQUIRE(res.service_parameter_list.size() == 1);
|
||||
auto& parameters = res.service_parameter_list[0];
|
||||
REQUIRE(parameters.id == 0);
|
||||
REQUIRE(parameters.parameter.size() == 4);
|
||||
|
||||
// Connector == Extended
|
||||
REQUIRE(parameters.parameter[0].name == "Connector");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[0].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[0].value) == 2);
|
||||
// ControlMode == Scheduled
|
||||
REQUIRE(parameters.parameter[1].name == "ControlMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[1].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[1].value) == 1);
|
||||
// MobilityNeedsMode == ProvidedbyEvcc
|
||||
REQUIRE(parameters.parameter[2].name == "MobilityNeedsMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[2].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[2].value) == 1);
|
||||
// Pricing == No Pricing
|
||||
REQUIRE(parameters.parameter[3].name == "Pricing");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[3].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[3].value) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good Case - Internet Service") {
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::ServiceDetail>()};
|
||||
|
||||
const auto header_req = message_20::Header{ctx.session.get_id(), 1691411798};
|
||||
const auto req = message_20::ServiceDetailRequest{
|
||||
header_req, message_20::to_underlying_value(dt::ServiceCategory::Internet)};
|
||||
|
||||
state_helper.handle_request(req);
|
||||
|
||||
ctx.session_config.internet_parameter_list = {
|
||||
{dt::Protocol::Http, dt::Port::Port80}}; // TODO(SL): Reset to start
|
||||
|
||||
ctx.session.offered_services.energy_services = {dt::ServiceCategory::DC};
|
||||
ctx.session.offered_services.vas_services = {message_20::to_underlying_value(dt::ServiceCategory::Internet)};
|
||||
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == true);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::ServiceSelection);
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::ServiceDetailResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& res = response_message.value();
|
||||
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.service == message_20::to_underlying_value(dt::ServiceCategory::Internet));
|
||||
REQUIRE(res.service_parameter_list.size() == 1);
|
||||
auto& parameters = res.service_parameter_list[0];
|
||||
REQUIRE(parameters.id == 3);
|
||||
REQUIRE(parameters.parameter.size() == 2);
|
||||
|
||||
// Protocol == HTTP
|
||||
REQUIRE(parameters.parameter[0].name == "Protocol");
|
||||
REQUIRE(std::holds_alternative<std::string>(parameters.parameter[0].value));
|
||||
REQUIRE(std::get<std::string>(parameters.parameter[0].value) == "http");
|
||||
// Port == 80
|
||||
REQUIRE(parameters.parameter[1].name == "Port");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[1].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[1].value) == 80);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good Case - Parking status service") {
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::ServiceDetail>()};
|
||||
|
||||
const auto header_req = message_20::Header{ctx.session.get_id(), 1691411798};
|
||||
const auto req = message_20::ServiceDetailRequest{
|
||||
header_req, message_20::to_underlying_value(dt::ServiceCategory::ParkingStatus)};
|
||||
|
||||
state_helper.handle_request(req);
|
||||
|
||||
ctx.session_config.parking_parameter_list = {
|
||||
{dt::IntendedService::VehicleCheckIn, dt::ParkingStatus::ManualExternal}}; // TODO(SL): Reset to start value
|
||||
|
||||
ctx.session.offered_services.energy_services = {dt::ServiceCategory::DC};
|
||||
ctx.session.offered_services.vas_services = {
|
||||
message_20::to_underlying_value(dt::ServiceCategory::ParkingStatus)};
|
||||
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == true);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::ServiceSelection);
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::ServiceDetailResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& res = response_message.value();
|
||||
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.service == message_20::to_underlying_value(dt::ServiceCategory::ParkingStatus));
|
||||
REQUIRE(res.service_parameter_list.size() == 1);
|
||||
auto& parameters = res.service_parameter_list[0];
|
||||
REQUIRE(parameters.id == 0);
|
||||
REQUIRE(parameters.parameter.size() == 2);
|
||||
|
||||
// IntendedService == VehicleCheckIn
|
||||
REQUIRE(parameters.parameter[0].name == "IntendedService");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[0].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[0].value) == 1);
|
||||
// ParkingStatusType == Manual/External
|
||||
REQUIRE(parameters.parameter[1].name == "ParkingStatusType");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[1].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[1].value) == 4);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good Case - AC Service") {
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::ServiceDetail>()};
|
||||
|
||||
const auto header_req = message_20::Header{ctx.session.get_id(), 1691411798};
|
||||
const auto req =
|
||||
message_20::ServiceDetailRequest{header_req, message_20::to_underlying_value(dt::ServiceCategory::AC)};
|
||||
|
||||
state_helper.handle_request(req);
|
||||
|
||||
ctx.session_config.ac_parameter_list = {{
|
||||
dt::AcConnector::ThreePhase,
|
||||
dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc,
|
||||
230,
|
||||
dt::Pricing::NoPricing,
|
||||
}};
|
||||
|
||||
ctx.session.offered_services.energy_services = {dt::ServiceCategory::AC};
|
||||
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == true);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::ServiceSelection);
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::ServiceDetailResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& res = response_message.value();
|
||||
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.service == message_20::to_underlying_value(dt::ServiceCategory::AC));
|
||||
REQUIRE(res.service_parameter_list.size() == 1);
|
||||
auto& parameters = res.service_parameter_list[0];
|
||||
REQUIRE(parameters.id == 0);
|
||||
REQUIRE(parameters.parameter.size() == 5);
|
||||
|
||||
// Connector == ThreePhases
|
||||
REQUIRE(parameters.parameter[0].name == "Connector");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[0].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[0].value) == 3);
|
||||
// ControlMode == Scheduled
|
||||
REQUIRE(parameters.parameter[1].name == "ControlMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[1].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[1].value) == 1);
|
||||
// EVSENominalVoltage == 230
|
||||
REQUIRE(parameters.parameter[2].name == "EVSENominalVoltage");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[2].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[2].value) == 230);
|
||||
// MobilityNeedsMode == ProvidedbyEvcc
|
||||
REQUIRE(parameters.parameter[3].name == "MobilityNeedsMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[3].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[3].value) == 1);
|
||||
// Pricing == No Pricing
|
||||
REQUIRE(parameters.parameter[4].name == "Pricing");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[4].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[4].value) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good Case - AC_BPT Service") {
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::ServiceDetail>()};
|
||||
|
||||
const auto header_req = message_20::Header{ctx.session.get_id(), 1691411798};
|
||||
const auto req =
|
||||
message_20::ServiceDetailRequest{header_req, message_20::to_underlying_value(dt::ServiceCategory::AC_BPT)};
|
||||
|
||||
state_helper.handle_request(req);
|
||||
|
||||
ctx.session.offered_services.energy_services = {dt::ServiceCategory::AC_BPT};
|
||||
|
||||
ctx.session_config.ac_bpt_parameter_list = {{
|
||||
{
|
||||
dt::AcConnector::ThreePhase,
|
||||
dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc,
|
||||
230,
|
||||
dt::Pricing::NoPricing,
|
||||
},
|
||||
dt::BptChannel::Unified,
|
||||
dt::GeneratorMode::GridFollowing,
|
||||
dt::GridCodeIslandingDetectionMethod::Passive,
|
||||
}};
|
||||
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == true);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::ServiceSelection);
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::ServiceDetailResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& res = response_message.value();
|
||||
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.service == message_20::to_underlying_value(dt::ServiceCategory::AC_BPT));
|
||||
REQUIRE(res.service_parameter_list.size() == 1);
|
||||
auto& parameters = res.service_parameter_list[0];
|
||||
REQUIRE(parameters.id == 0);
|
||||
REQUIRE(parameters.parameter.size() == 8);
|
||||
|
||||
// Connector == ThreePhases
|
||||
REQUIRE(parameters.parameter[0].name == "Connector");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[0].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[0].value) == 3);
|
||||
// ControlMode == Scheduled
|
||||
REQUIRE(parameters.parameter[1].name == "ControlMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[1].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[1].value) == 1);
|
||||
// EVSENominalVoltage == 230
|
||||
REQUIRE(parameters.parameter[2].name == "EVSENominalVoltage");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[2].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[2].value) == 230);
|
||||
// MobilityNeedsMode == ProvidedbyEvcc
|
||||
REQUIRE(parameters.parameter[3].name == "MobilityNeedsMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[3].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[3].value) == 1);
|
||||
// Pricing == No Pricing
|
||||
REQUIRE(parameters.parameter[4].name == "Pricing");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[4].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[4].value) == 0);
|
||||
// BPTChannel == Unified
|
||||
REQUIRE(parameters.parameter[5].name == "BPTChannel");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[5].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[5].value) == 1);
|
||||
// GeneratorMode == GridFollowing
|
||||
REQUIRE(parameters.parameter[6].name == "GeneratorMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[6].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[6].value) == 1);
|
||||
// DetectionMethodGridCodeIslanding == Passive
|
||||
REQUIRE(parameters.parameter[7].name == "DetectionMethodGridCodeIslanding");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[7].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[7].value) == 2);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good Case - 2x AC Services") {
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::ServiceDetail>()};
|
||||
|
||||
const auto header_req = message_20::Header{ctx.session.get_id(), 1691411798};
|
||||
const auto req =
|
||||
message_20::ServiceDetailRequest{header_req, message_20::to_underlying_value(dt::ServiceCategory::AC)};
|
||||
|
||||
state_helper.handle_request(req);
|
||||
|
||||
ctx.session.offered_services.energy_services = {dt::ServiceCategory::AC};
|
||||
ctx.session_config.ac_parameter_list = {{
|
||||
dt::AcConnector::ThreePhase,
|
||||
dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc,
|
||||
230,
|
||||
dt::Pricing::NoPricing,
|
||||
},
|
||||
{
|
||||
dt::AcConnector::ThreePhase,
|
||||
dt::ControlMode::Dynamic,
|
||||
dt::MobilityNeedsMode::ProvidedBySecc,
|
||||
230,
|
||||
dt::Pricing::NoPricing,
|
||||
}};
|
||||
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == true);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::ServiceSelection);
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::ServiceDetailResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& res = response_message.value();
|
||||
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.service == message_20::to_underlying_value(dt::ServiceCategory::AC));
|
||||
REQUIRE(res.service_parameter_list.size() == 2);
|
||||
auto& parameters_0 = res.service_parameter_list[0];
|
||||
REQUIRE(parameters_0.id == 0);
|
||||
REQUIRE(parameters_0.parameter.size() == 5);
|
||||
|
||||
// Connector == ThreePhases
|
||||
REQUIRE(parameters_0.parameter[0].name == "Connector");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters_0.parameter[0].value));
|
||||
REQUIRE(std::get<int32_t>(parameters_0.parameter[0].value) == 3);
|
||||
// ControlMode == Scheduled
|
||||
REQUIRE(parameters_0.parameter[1].name == "ControlMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters_0.parameter[1].value));
|
||||
REQUIRE(std::get<int32_t>(parameters_0.parameter[1].value) == 1);
|
||||
// EVSENominalVoltage == 230
|
||||
REQUIRE(parameters_0.parameter[2].name == "EVSENominalVoltage");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters_0.parameter[2].value));
|
||||
REQUIRE(std::get<int32_t>(parameters_0.parameter[2].value) == 230);
|
||||
// MobilityNeedsMode == ProvidedbyEvcc
|
||||
REQUIRE(parameters_0.parameter[3].name == "MobilityNeedsMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters_0.parameter[3].value));
|
||||
REQUIRE(std::get<int32_t>(parameters_0.parameter[3].value) == 1);
|
||||
// Pricing == No Pricing
|
||||
REQUIRE(parameters_0.parameter[4].name == "Pricing");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters_0.parameter[4].value));
|
||||
REQUIRE(std::get<int32_t>(parameters_0.parameter[4].value) == 0);
|
||||
|
||||
auto& parameters_1 = res.service_parameter_list[1];
|
||||
REQUIRE(parameters_1.id == 1);
|
||||
REQUIRE(parameters_1.parameter.size() == 5);
|
||||
|
||||
// Connector == ThreePhases
|
||||
REQUIRE(parameters_1.parameter[0].name == "Connector");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters_1.parameter[0].value));
|
||||
REQUIRE(std::get<int32_t>(parameters_1.parameter[0].value) == 3);
|
||||
// ControlMode == Dynamic
|
||||
REQUIRE(parameters_1.parameter[1].name == "ControlMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters_1.parameter[1].value));
|
||||
REQUIRE(std::get<int32_t>(parameters_1.parameter[1].value) == 2);
|
||||
// EVSENominalVoltage == 230
|
||||
REQUIRE(parameters_1.parameter[2].name == "EVSENominalVoltage");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters_1.parameter[2].value));
|
||||
REQUIRE(std::get<int32_t>(parameters_1.parameter[2].value) == 230);
|
||||
// MobilityNeedsMode == ProvidedbySecc
|
||||
REQUIRE(parameters_1.parameter[3].name == "MobilityNeedsMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters_1.parameter[3].value));
|
||||
REQUIRE(std::get<int32_t>(parameters_1.parameter[3].value) == 2);
|
||||
// Pricing == No Pricing
|
||||
REQUIRE(parameters_1.parameter[4].name == "Pricing");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters_1.parameter[4].value));
|
||||
REQUIRE(std::get<int32_t>(parameters_1.parameter[4].value) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good Case - AC Service: Scheduled Mode: 1, MobilityNeedsMode: 2 change to 1") {
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::ServiceDetail>()};
|
||||
|
||||
const auto header_req = message_20::Header{ctx.session.get_id(), 1691411798};
|
||||
const auto req =
|
||||
message_20::ServiceDetailRequest{header_req, message_20::to_underlying_value(dt::ServiceCategory::AC)};
|
||||
|
||||
state_helper.handle_request(req);
|
||||
|
||||
ctx.session.offered_services.energy_services = {dt::ServiceCategory::AC};
|
||||
ctx.session_config.ac_parameter_list = {{
|
||||
dt::AcConnector::ThreePhase,
|
||||
dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedBySecc,
|
||||
230,
|
||||
dt::Pricing::NoPricing,
|
||||
}};
|
||||
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == true);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::ServiceSelection);
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::ServiceDetailResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& res = response_message.value();
|
||||
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.service == message_20::to_underlying_value(dt::ServiceCategory::AC));
|
||||
REQUIRE(res.service_parameter_list.size() == 1);
|
||||
auto& parameters = res.service_parameter_list[0];
|
||||
REQUIRE(parameters.id == 0);
|
||||
REQUIRE(parameters.parameter.size() == 5);
|
||||
|
||||
// Connector == ThreePhases
|
||||
REQUIRE(parameters.parameter[0].name == "Connector");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[0].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[0].value) == 3);
|
||||
// ControlMode == Scheduled
|
||||
REQUIRE(parameters.parameter[1].name == "ControlMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[1].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[1].value) == 1);
|
||||
// EVSENominalVoltage == 230
|
||||
REQUIRE(parameters.parameter[2].name == "EVSENominalVoltage");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[2].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[2].value) == 230);
|
||||
// MobilityNeedsMode == ProvidedbyEvcc
|
||||
REQUIRE(parameters.parameter[3].name == "MobilityNeedsMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[3].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[3].value) == 1);
|
||||
// Pricing == No Pricing
|
||||
REQUIRE(parameters.parameter[4].name == "Pricing");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[4].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[4].value) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good Case - MCS Service") {
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::ServiceDetail>()};
|
||||
|
||||
const auto header_req = message_20::Header{ctx.session.get_id(), 1691411798};
|
||||
const auto req =
|
||||
message_20::ServiceDetailRequest{header_req, message_20::to_underlying_value(dt::ServiceCategory::MCS)};
|
||||
|
||||
state_helper.handle_request(req);
|
||||
|
||||
ctx.session.offered_services.energy_services = {dt::ServiceCategory::MCS};
|
||||
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == true);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::ServiceSelection);
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::ServiceDetailResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& res = response_message.value();
|
||||
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.service == message_20::to_underlying_value(dt::ServiceCategory::MCS));
|
||||
REQUIRE(res.service_parameter_list.size() == 1);
|
||||
auto& parameters = res.service_parameter_list[0];
|
||||
REQUIRE(parameters.id == 0);
|
||||
REQUIRE(parameters.parameter.size() == 4);
|
||||
|
||||
// Connector == MCS
|
||||
REQUIRE(parameters.parameter[0].name == "Connector");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[0].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[0].value) == 1);
|
||||
// ControlMode == Scheduled
|
||||
REQUIRE(parameters.parameter[1].name == "ControlMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[1].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[1].value) == 1);
|
||||
// MobilityNeedsMode == ProvidedbyEvcc
|
||||
REQUIRE(parameters.parameter[2].name == "MobilityNeedsMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[2].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[2].value) == 1);
|
||||
// Pricing == No Pricing
|
||||
REQUIRE(parameters.parameter[3].name == "Pricing");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[3].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[3].value) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good Case - MCS_BPT Service") {
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::ServiceDetail>()};
|
||||
|
||||
const auto header_req = message_20::Header{ctx.session.get_id(), 1691411798};
|
||||
const auto req =
|
||||
message_20::ServiceDetailRequest{header_req, message_20::to_underlying_value(dt::ServiceCategory::MCS_BPT)};
|
||||
|
||||
state_helper.handle_request(req);
|
||||
|
||||
ctx.session.offered_services.energy_services = {dt::ServiceCategory::MCS_BPT};
|
||||
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == true);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::ServiceSelection);
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::ServiceDetailResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& res = response_message.value();
|
||||
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.service == message_20::to_underlying_value(dt::ServiceCategory::MCS_BPT));
|
||||
REQUIRE(res.service_parameter_list.size() == 1);
|
||||
auto& parameters = res.service_parameter_list[0];
|
||||
REQUIRE(parameters.id == 0);
|
||||
REQUIRE(parameters.parameter.size() == 6);
|
||||
|
||||
// Connector == MCS
|
||||
REQUIRE(parameters.parameter[0].name == "Connector");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[0].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[0].value) == 1);
|
||||
// ControlMode == Scheduled
|
||||
REQUIRE(parameters.parameter[1].name == "ControlMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[1].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[1].value) == 1);
|
||||
// MobilityNeedsMode == ProvidedbyEvcc
|
||||
REQUIRE(parameters.parameter[2].name == "MobilityNeedsMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[2].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[2].value) == 1);
|
||||
// Pricing == No Pricing
|
||||
REQUIRE(parameters.parameter[3].name == "Pricing");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[3].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[3].value) == 0);
|
||||
// BPTChannel == Unified
|
||||
REQUIRE(parameters.parameter[4].name == "BPTChannel");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[4].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[4].value) == 1);
|
||||
// GeneratorMode == GridFollowing
|
||||
REQUIRE(parameters.parameter[5].name == "GeneratorMode");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[5].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[5].value) == 1);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - Ev requests parameter from custom vas") {
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::ServiceDetail>()};
|
||||
|
||||
const auto header_req = message_20::Header{ctx.session.get_id(), 1691411798};
|
||||
const auto req = message_20::ServiceDetailRequest{header_req, 4599};
|
||||
|
||||
state_helper.handle_request(req);
|
||||
|
||||
ctx.session.offered_services.vas_services = {4599};
|
||||
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == true);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::ServiceSelection);
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::ServiceDetailResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& service_detail_res = response_message.value();
|
||||
|
||||
REQUIRE(service_detail_res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(service_detail_res.service == 4599);
|
||||
REQUIRE(service_detail_res.service_parameter_list.size() == 1);
|
||||
auto& parameters = service_detail_res.service_parameter_list[0];
|
||||
REQUIRE(parameters.id == 0);
|
||||
REQUIRE(parameters.parameter.size() == 2);
|
||||
|
||||
REQUIRE(parameters.parameter[0].name == "Service1");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[0].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[0].value) == 40);
|
||||
|
||||
REQUIRE(parameters.parameter[1].name == "Service2");
|
||||
REQUIRE(std::holds_alternative<std::string>(parameters.parameter[1].value));
|
||||
REQUIRE(std::get<std::string>(parameters.parameter[1].value) == "house");
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - Ev requests parameter from parking vas") {
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::ServiceDetail>()};
|
||||
|
||||
const auto header_req = message_20::Header{ctx.session.get_id(), 1691411798};
|
||||
const auto req = message_20::ServiceDetailRequest{
|
||||
header_req, message_20::to_underlying_value(dt::ServiceCategory::ParkingStatus)};
|
||||
|
||||
state_helper.handle_request(req);
|
||||
|
||||
ctx.session.offered_services.vas_services = {
|
||||
message_20::to_underlying_value(dt::ServiceCategory::ParkingStatus)};
|
||||
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == true);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::ServiceSelection);
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::ServiceDetailResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& service_detail_res = response_message.value();
|
||||
|
||||
REQUIRE(service_detail_res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(service_detail_res.service == message_20::to_underlying_value(dt::ServiceCategory::ParkingStatus));
|
||||
REQUIRE(service_detail_res.service_parameter_list.size() == 1);
|
||||
auto& parameters = service_detail_res.service_parameter_list[0];
|
||||
REQUIRE(parameters.id == 0);
|
||||
REQUIRE(parameters.parameter.size() == 2);
|
||||
|
||||
REQUIRE(parameters.parameter[0].name == "IntendedService");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[0].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[0].value) == 1);
|
||||
|
||||
REQUIRE(parameters.parameter[1].name == "ParkingStatusType");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[1].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[1].value) == 4);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - Ev requests parameter from internet vas") {
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::ServiceDetail>()};
|
||||
|
||||
const auto header_req = message_20::Header{ctx.session.get_id(), 1691411798};
|
||||
const auto req = message_20::ServiceDetailRequest{
|
||||
header_req, message_20::to_underlying_value(dt::ServiceCategory::Internet)};
|
||||
|
||||
state_helper.handle_request(req);
|
||||
|
||||
ctx.session.offered_services.vas_services = {message_20::to_underlying_value(dt::ServiceCategory::Internet)};
|
||||
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == true);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::ServiceSelection);
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::ServiceDetailResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& service_detail_res = response_message.value();
|
||||
|
||||
REQUIRE(service_detail_res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(service_detail_res.service == message_20::to_underlying_value(dt::ServiceCategory::Internet));
|
||||
REQUIRE(service_detail_res.service_parameter_list.size() == 1);
|
||||
auto& parameters = service_detail_res.service_parameter_list[0];
|
||||
REQUIRE(parameters.id == 3);
|
||||
REQUIRE(parameters.parameter.size() == 2);
|
||||
|
||||
REQUIRE(parameters.parameter[0].name == "Protocol");
|
||||
REQUIRE(std::holds_alternative<std::string>(parameters.parameter[0].value));
|
||||
REQUIRE(std::get<std::string>(parameters.parameter[0].value) == "http");
|
||||
|
||||
REQUIRE(parameters.parameter[1].name == "Port");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[1].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[1].value) == 80);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2025 Pionix GmbH and Contributors to EVerest
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include "helper.hpp"
|
||||
|
||||
#include <iso15118/d20/state/authorization_setup.hpp>
|
||||
#include <iso15118/d20/state/session_setup.hpp>
|
||||
|
||||
#include <iso15118/message/service_detail.hpp>
|
||||
#include <iso15118/message/session_setup.hpp>
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
SCENARIO("ISO15118-20 session setup state transitions") {
|
||||
|
||||
namespace dt = message_20::datatypes;
|
||||
|
||||
// Move to helper function?
|
||||
const auto evse_id = std::string("everest se");
|
||||
const std::vector<dt::ServiceCategory> supported_energy_services = {dt::ServiceCategory::DC};
|
||||
const auto cert_install{false};
|
||||
const std::vector<uint16_t> vas_services{};
|
||||
const std::vector<dt::Authorization> auth_services = {dt::Authorization::EIM};
|
||||
const d20::DcTransferLimits dc_limits;
|
||||
const d20::AcTransferLimits ac_limits;
|
||||
const d20::DcTransferLimits powersupply_limits;
|
||||
const std::vector<d20::ControlMobilityNeedsModes> control_mobility_modes = {
|
||||
{dt::ControlMode::Scheduled, dt::MobilityNeedsMode::ProvidedByEvcc}};
|
||||
|
||||
const d20::EvseSetupConfig evse_setup{
|
||||
evse_id, supported_energy_services, auth_services, vas_services, cert_install, dc_limits,
|
||||
ac_limits, control_mobility_modes, std::nullopt, std::nullopt, std::nullopt, powersupply_limits};
|
||||
|
||||
std::optional<d20::PauseContext> pause_ctx{std::nullopt};
|
||||
|
||||
const session::feedback::Callbacks callbacks{};
|
||||
|
||||
auto state_helper = FsmStateHelper(d20::SessionConfig(evse_setup), pause_ctx, callbacks);
|
||||
auto ctx = state_helper.get_context();
|
||||
|
||||
const auto session_id = std::array<uint8_t, 8>{0x10, 0x34, 0xAB, 0x7A, 0x01, 0xF3, 0x95, 0x02};
|
||||
|
||||
auto& pause = pause_ctx.emplace();
|
||||
pause.selected_service_parameters =
|
||||
d20::SelectedServiceParameters(dt::ServiceCategory::DC, dt::DcConnector::Extended, dt::ControlMode::Dynamic,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing);
|
||||
pause.vehicle_cert_session_id_hash = {0x58, 0xD6, 0x9A, 0x86, 0xF5, 0xCF, 0x86, 0xC0, 0x2F, 0x06, 0x1A, 0xAC, 0x9B,
|
||||
0x26, 0x83, 0x0F, 0xB1, 0x66, 0xCC, 0x4E, 0xC8, 0x75, 0x68, 0xA2, 0xF2, 0x3D,
|
||||
0x11, 0xC7, 0x61, 0x64, 0x18, 0x34, 0x56, 0x7A, 0x34, 0x30, 0x0C, 0x7E, 0x9A,
|
||||
0xF4, 0x00, 0xF5, 0xBC, 0x61, 0x46, 0xC5, 0xA2, 0x74, 0x52, 0xFE, 0x15, 0x7D,
|
||||
0x28, 0x77, 0xC8, 0x52, 0x06, 0x59, 0x22, 0x45, 0x3A, 0x9F, 0xA6, 0x54};
|
||||
pause.old_session_id = session_id;
|
||||
|
||||
GIVEN("Good case - New session") {
|
||||
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::SessionSetup>()};
|
||||
|
||||
const auto header_req = message_20::Header{{0, 0, 0, 0, 0, 0, 0, 0}, 1691411798};
|
||||
const auto req = message_20::SessionSetupRequest{header_req, "WMIV1234567890ABCDEX"};
|
||||
|
||||
state_helper.handle_request(req);
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == true);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::AuthorizationSetup);
|
||||
REQUIRE(ctx.session.get_id() != std::array<uint8_t, 8>{0});
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::SessionSetupResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& session_setup_res = response_message.value();
|
||||
REQUIRE(session_setup_res.response_code == dt::ResponseCode::OK_NewSessionEstablished);
|
||||
REQUIRE(session_setup_res.evseid == evse_id);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - resume old session") {
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::SessionSetup>()};
|
||||
|
||||
ctx.set_new_vehicle_cert_hash(io::sha512_hash_t{
|
||||
0x3F, 0x66, 0xE4, 0x5F, 0x3A, 0x30, 0x3B, 0x8F, 0x47, 0xCD, 0xD6, 0x86, 0xAD, 0x75, 0x13, 0x6F,
|
||||
0xCE, 0x44, 0xE6, 0xAD, 0xDC, 0x52, 0x8A, 0x6A, 0x3D, 0xAC, 0x5F, 0x8D, 0xCB, 0x5A, 0x67, 0xF3,
|
||||
0xE5, 0xA5, 0xF2, 0x56, 0x74, 0x5A, 0xFA, 0xF2, 0x28, 0x31, 0xCE, 0xAB, 0xE8, 0x3C, 0xD7, 0x3C,
|
||||
0xF2, 0x83, 0x81, 0xAA, 0x5D, 0x87, 0x13, 0xA5, 0x78, 0xA8, 0xB4, 0xAB, 0x0D, 0x62, 0x1F, 0x83});
|
||||
|
||||
const auto header_req = message_20::Header{session_id, 1691411798};
|
||||
const auto req = message_20::SessionSetupRequest{header_req, "WMIV1234567890ABCDEX"};
|
||||
|
||||
state_helper.handle_request(req);
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == true);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::DC_ChargeParameterDiscovery);
|
||||
REQUIRE(ctx.session.get_id() == session_id);
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::SessionSetupResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& session_setup_res = response_message.value();
|
||||
REQUIRE(session_setup_res.response_code == dt::ResponseCode::OK_OldSessionJoined);
|
||||
REQUIRE(session_setup_res.evseid == evse_id);
|
||||
}
|
||||
|
||||
pause_ctx.reset();
|
||||
}
|
||||
|
||||
GIVEN("Try to resume old session with another session id") {
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::SessionSetup>()};
|
||||
|
||||
ctx.set_new_vehicle_cert_hash(io::sha512_hash_t{
|
||||
0x3F, 0x66, 0xE4, 0x5F, 0x3A, 0x30, 0x3B, 0x8F, 0x47, 0xCD, 0xD6, 0x86, 0xAD, 0x75, 0x13, 0x6F,
|
||||
0xCE, 0x44, 0xE6, 0xAD, 0xDC, 0x52, 0x8A, 0x6A, 0x3D, 0xAC, 0x5F, 0x8D, 0xCB, 0x5A, 0x67, 0xF3,
|
||||
0xE5, 0xA5, 0xF2, 0x56, 0x74, 0x5A, 0xFA, 0xF2, 0x28, 0x31, 0xCE, 0xAB, 0xE8, 0x3C, 0xD7, 0x3C,
|
||||
0xF2, 0x83, 0x81, 0xAA, 0x5D, 0x87, 0x13, 0xA5, 0x78, 0xA8, 0xB4, 0xAB, 0x0D, 0x62, 0x1F, 0x83});
|
||||
|
||||
const auto header_req = message_20::Header{{0x10, 0x34, 0xAB, 0x7B, 0x01, 0xF3, 0x95, 0x02}, 1691411798};
|
||||
const auto req = message_20::SessionSetupRequest{header_req, "WMIV1234567890ABCDEX"};
|
||||
|
||||
state_helper.handle_request(req);
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == true);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::AuthorizationSetup);
|
||||
REQUIRE(ctx.session.get_id() != session_id);
|
||||
REQUIRE(ctx.session.get_id() != std::array<uint8_t, 8>{0});
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::SessionSetupResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& session_setup_res = response_message.value();
|
||||
REQUIRE(session_setup_res.response_code == dt::ResponseCode::OK_NewSessionEstablished);
|
||||
REQUIRE(session_setup_res.evseid == evse_id);
|
||||
}
|
||||
|
||||
pause_ctx.reset();
|
||||
}
|
||||
|
||||
GIVEN("Try to resume old session with no vehicle cert hash") {
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::SessionSetup>()};
|
||||
|
||||
const auto header_req = message_20::Header{session_id, 1691411798};
|
||||
const auto req = message_20::SessionSetupRequest{header_req, "WMIV1234567890ABCDEX"};
|
||||
|
||||
state_helper.handle_request(req);
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == true);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::AuthorizationSetup);
|
||||
REQUIRE(ctx.session.get_id() != session_id);
|
||||
REQUIRE(ctx.session.get_id() != std::array<uint8_t, 8>{0});
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::SessionSetupResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& session_setup_res = response_message.value();
|
||||
REQUIRE(session_setup_res.response_code == dt::ResponseCode::OK_NewSessionEstablished);
|
||||
REQUIRE(session_setup_res.evseid == evse_id);
|
||||
}
|
||||
|
||||
pause_ctx.reset();
|
||||
}
|
||||
|
||||
GIVEN("Try to resume old session with different vehicle cert hash") {
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::SessionSetup>()};
|
||||
|
||||
ctx.set_new_vehicle_cert_hash(io::sha512_hash_t{
|
||||
0x3F, 0x66, 0xE4, 0x5F, 0x3A, 0x30, 0x3B, 0x8F, 0x47, 0xCD, 0xD6, 0x86, 0xAD, 0x75, 0x13, 0x6F,
|
||||
0xDF, 0x44, 0xE6, 0xAD, 0xDC, 0x52, 0x8A, 0x6A, 0x3D, 0xAC, 0x5F, 0x8D, 0xCB, 0x5A, 0x67, 0xF3,
|
||||
0xE5, 0xA5, 0xF2, 0x56, 0x74, 0x5A, 0xFA, 0xF2, 0x28, 0x31, 0xCE, 0xAB, 0xE8, 0x3C, 0xD7, 0x3C,
|
||||
0xF2, 0x83, 0x81, 0xAA, 0x5D, 0x87, 0x13, 0xA5, 0x78, 0xA8, 0xB4, 0xAB, 0x0D, 0x62, 0x1F, 0x83});
|
||||
|
||||
const auto header_req = message_20::Header{session_id, 1691411798};
|
||||
const auto req = message_20::SessionSetupRequest{header_req, "WMIV1234567890ABCDEX"};
|
||||
|
||||
state_helper.handle_request(req);
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == true);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::AuthorizationSetup);
|
||||
REQUIRE(ctx.session.get_id() != session_id);
|
||||
REQUIRE(ctx.session.get_id() != std::array<uint8_t, 8>{0});
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::SessionSetupResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& session_setup_res = response_message.value();
|
||||
REQUIRE(session_setup_res.response_code == dt::ResponseCode::OK_NewSessionEstablished);
|
||||
REQUIRE(session_setup_res.evseid == evse_id);
|
||||
}
|
||||
|
||||
pause_ctx.reset();
|
||||
}
|
||||
|
||||
GIVEN("Sequence Error") {
|
||||
fsm::v2::FSM<d20::StateBase> fsm{ctx.create_state<d20::state::SessionSetup>()};
|
||||
|
||||
const auto header_req = message_20::Header{d20::Session().get_id(), 1691411798};
|
||||
const auto req =
|
||||
message_20::ServiceDetailRequest{header_req, message_20::to_underlying_value(dt::ServiceCategory::DC)};
|
||||
|
||||
state_helper.handle_request(req);
|
||||
|
||||
const auto result = fsm.feed(d20::Event::V2GTP_MESSAGE);
|
||||
|
||||
THEN("Check state transition") {
|
||||
REQUIRE(result.transitioned() == false);
|
||||
REQUIRE(fsm.get_current_state_id() == d20::StateID::SessionSetup);
|
||||
|
||||
const auto response_message = ctx.get_response<message_20::ServiceDetailResponse>();
|
||||
REQUIRE(response_message.has_value());
|
||||
|
||||
const auto& res = response_message.value();
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_SequenceError);
|
||||
REQUIRE(res.service == message_20::to_underlying_value(dt::ServiceCategory::DC));
|
||||
REQUIRE(res.service_parameter_list.size() == 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
include(Catch)
|
||||
|
||||
add_executable(test_logging logging.cpp)
|
||||
|
||||
target_link_libraries(test_logging
|
||||
PRIVATE
|
||||
iso15118
|
||||
Catch2::Catch2WithMain
|
||||
)
|
||||
|
||||
catch_discover_tests(test_logging)
|
||||
|
||||
add_executable(test_sdp_server sdp_server_test.cpp)
|
||||
target_link_libraries(test_sdp_server PRIVATE iso15118 Catch2::Catch2WithMain)
|
||||
catch_discover_tests(test_sdp_server)
|
||||
|
||||
add_executable(connection_openssl_test)
|
||||
add_custom_command(
|
||||
TARGET connection_openssl_test POST_BUILD
|
||||
COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/pki
|
||||
COMMAND cd pki && cp pki.sh ${CMAKE_CURRENT_BINARY_DIR}/pki && cp -r configs ${CMAKE_CURRENT_BINARY_DIR}/pki
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
target_sources(connection_openssl_test
|
||||
PRIVATE
|
||||
connection_openssl.cpp
|
||||
)
|
||||
target_link_libraries(connection_openssl_test
|
||||
PRIVATE
|
||||
iso15118::iso15118
|
||||
)
|
||||
@@ -0,0 +1,33 @@
|
||||
# Connection Test
|
||||
|
||||
## Standalone server
|
||||
|
||||
- Run `pki.sh` to build the test certificates and keys
|
||||
- use `openssl s_client` to make test connections
|
||||
- Run from the directory containing the test executable
|
||||
|
||||
### Standalone TLS server
|
||||
|
||||
Tests the TLS Server in isolation.
|
||||
|
||||
- `./connection_openssl -i <interface name>`
|
||||
- gracefully terminates after 30 seconds
|
||||
- requires client certificate
|
||||
- Supports TCP, TLS1.2 and TLS1.3
|
||||
|
||||
### openssl s_client commands
|
||||
|
||||
TLS 1.2 and 1.3:
|
||||
```sh
|
||||
openssl s_client -connect [ipv6%iface]:port -verify 2 -debug -msg -verifyCAfile ./pki/certs/ca/v2g/V2G_ROOT_CA.pem -verify_return_error -cert ./pki/certs/client/vehicle/VEHICLE_LEAF.pem -cert_chain ./pki/certs/ca/vehicle/VEHICLE_CERT_CHAIN.pem -certform PEM -key ./pki/certs/client/vehicle/VEHICLE_LEAF.key -keyform PEM -pass file:./pki/certs/client/vehicle/VEHICLE_LEAF_PASSWORD.txt -ciphersuites "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256" -cipher "ECDHE-ECDSA-AES128-SHA256" -requestCAfile ./pki/certs/ca/v2g/V2G_ROOT_CA.pem
|
||||
```
|
||||
|
||||
TLS 1.2 only:
|
||||
```sh
|
||||
openssl s_client -connect [ipv6%iface]:port -verify 2 -debug -msg -verifyCAfile ./pki/certs/ca/v2g/V2G_ROOT_CA.pem -verify_return_error -tls1_2 -cipher "ECDHE-ECDSA-AES128-SHA256"
|
||||
```
|
||||
|
||||
TLS 1.3 only:
|
||||
```sh
|
||||
openssl s_client -connect [ipv6%iface]:port -verify 2 -debug -msg -verifyCAfile ./pki/certs/ca/v2g/V2G_ROOT_CA.pem -verify_return_error -tls1_3 -cert ./pki/certs/client/vehicle/VEHICLE_LEAF.pem -cert_chain ./pki/certs/ca/vehicle/VEHICLE_CERT_CHAIN.pem -certform PEM -key ./pki/certs/client/vehicle/VEHICLE_LEAF.key -keyform PEM -pass file:./pki/certs/client/vehicle/VEHICLE_LEAF_PASSWORD.txt -ciphersuites "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256" -requestCAfile ./pki/certs/ca/v2g/V2G_ROOT_CA.pem
|
||||
```
|
||||
@@ -0,0 +1,117 @@
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <iso15118/config.hpp>
|
||||
#include <iso15118/io/connection_ssl.hpp>
|
||||
#include <iso15118/io/logging.hpp>
|
||||
#include <iso15118/io/poll_manager.hpp>
|
||||
#include <iso15118/io/time.hpp>
|
||||
|
||||
namespace {
|
||||
|
||||
static constexpr auto DEFAULT_PW{"123456"};
|
||||
static constexpr auto POLL_MANAGER_TIMEOUT_MS = 50;
|
||||
static constexpr auto STOP_TIME = 30;
|
||||
|
||||
const char* short_opts = "hi:";
|
||||
std::string interface {};
|
||||
|
||||
void parse_options(int argc, char** argv) {
|
||||
int c{0};
|
||||
|
||||
while ((c = getopt(argc, argv, short_opts)) != -1) {
|
||||
switch (c) {
|
||||
case 'i':
|
||||
interface = std::string(optarg);
|
||||
break;
|
||||
case 'h':
|
||||
case '?':
|
||||
std::cout << "Usage: " << argv[0] << " [-i]" << std::endl;
|
||||
std::cout << " -i <interface name>" << std::endl;
|
||||
exit(1);
|
||||
default:
|
||||
exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
if (interface.empty()) {
|
||||
std::cerr << "Error: " << argv[0] << " requires -i <interface name>" << std::endl;
|
||||
exit(3);
|
||||
}
|
||||
}
|
||||
|
||||
void handle_connection_event(iso15118::io::ConnectionEvent event) {
|
||||
using namespace iso15118;
|
||||
|
||||
using Event = io::ConnectionEvent;
|
||||
switch (event) {
|
||||
case Event::ACCEPTED:
|
||||
std::cout << "Accepted connection" << std::endl;
|
||||
return;
|
||||
|
||||
case Event::NEW_DATA:
|
||||
std::cout << "New Data" << std::endl;
|
||||
return;
|
||||
|
||||
case Event::OPEN:
|
||||
std::cout << "Connection open" << std::endl;
|
||||
return;
|
||||
|
||||
case Event::CLOSED:
|
||||
std::cout << "Connection is closed" << std::endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
using namespace iso15118;
|
||||
|
||||
parse_options(argc, argv);
|
||||
|
||||
io::set_logging_callback([](LogLevel level, const std::string& message) {
|
||||
std::cout << "log(" << static_cast<int>(level) << "): " << message << std::endl;
|
||||
});
|
||||
|
||||
auto poll_manager = io::PollManager();
|
||||
|
||||
const auto interface_name = interface;
|
||||
|
||||
const config::SSLConfig ssl{iso15118::config::CertificateBackend::EVEREST_LAYOUT,
|
||||
{},
|
||||
"pki/certs/client/cso/CPO_CERT_CHAIN.pem",
|
||||
"pki/certs/client/cso/SECC_LEAF.key",
|
||||
DEFAULT_PW,
|
||||
"pki/certs/ca/v2g/V2G_ROOT_CA.pem",
|
||||
"pki/certs/ca/oem/OEM_ROOT_CA.pem",
|
||||
false, // enable_ssl_logging
|
||||
true, // enable_tls_key_logging
|
||||
false, // enforce_tls_1_3
|
||||
"/tmp"}; // tls_key_log_file_path
|
||||
|
||||
auto connection = io::ConnectionSSL(poll_manager, interface_name, ssl);
|
||||
connection.set_event_callback([](io::ConnectionEvent event) { handle_connection_event(event); });
|
||||
|
||||
auto next_event = get_current_time_point();
|
||||
const auto start_time_point = next_event;
|
||||
|
||||
while (true) {
|
||||
const auto duration =
|
||||
std::chrono::duration_cast<std::chrono::duration<double>>(get_current_time_point() - start_time_point);
|
||||
if (duration.count() >= STOP_TIME) {
|
||||
break;
|
||||
}
|
||||
|
||||
const auto poll_timeout_ms = get_timeout_ms_until(next_event, POLL_MANAGER_TIMEOUT_MS);
|
||||
poll_manager.poll(poll_timeout_ms);
|
||||
|
||||
next_event = offset_time_point_by_ms(get_current_time_point(), POLL_MANAGER_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
connection.close();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2024 Pionix GmbH and Contributors to EVerest
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/detail/helper.hpp>
|
||||
#include <iso15118/io/logging.hpp>
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
SCENARIO("Logging Tests") {
|
||||
|
||||
LogLevel log_level;
|
||||
std::string log_msg;
|
||||
|
||||
io::set_logging_callback([&log_level, &log_msg](const iso15118::LogLevel& level, const std::string& msg) {
|
||||
log_level = level;
|
||||
log_msg = msg;
|
||||
});
|
||||
|
||||
GIVEN("Test logf without LogLevel") {
|
||||
|
||||
const LogLevel expected_log_level{LogLevel::Info};
|
||||
const std::string expected_msg = "TEST!";
|
||||
|
||||
logf("TEST!");
|
||||
|
||||
THEN("log_level & log_msg should be like expected") {
|
||||
REQUIRE(log_level == expected_log_level);
|
||||
REQUIRE(log_msg == expected_msg);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Test logf with LogLevel") {
|
||||
|
||||
const LogLevel expected_log_level{LogLevel::Warning};
|
||||
const std::string expected_msg = "logf with warning";
|
||||
|
||||
logf(LogLevel::Warning, "logf with warning");
|
||||
|
||||
THEN("log_level & log_msg should be like expected") {
|
||||
REQUIRE(log_level == expected_log_level);
|
||||
REQUIRE(log_msg == expected_msg);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Test logf with arguments") {
|
||||
|
||||
const LogLevel expected_log_level{LogLevel::Info};
|
||||
const std::string expected_msg = "logf: 5";
|
||||
|
||||
logf("logf: %d", 5);
|
||||
|
||||
THEN("log_level & log_msg should be like expected") {
|
||||
REQUIRE(log_level == expected_log_level);
|
||||
REQUIRE(log_msg == expected_msg);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Test logf_error") {
|
||||
|
||||
const LogLevel expected_log_level{LogLevel::Error};
|
||||
const std::string expected_msg = "Test logf_error";
|
||||
|
||||
logf_error("Test logf_error");
|
||||
|
||||
THEN("log_level & log_msg should be like expected") {
|
||||
REQUIRE(log_level == expected_log_level);
|
||||
REQUIRE(log_msg == expected_msg);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Test logf_error with arguments") {
|
||||
|
||||
const LogLevel expected_log_level{LogLevel::Error};
|
||||
const std::string expected_msg = "Test logf_error: 8";
|
||||
|
||||
logf_error("Test logf_error: %d", 8);
|
||||
|
||||
THEN("log_level & log_msg should be like expected") {
|
||||
REQUIRE(log_level == expected_log_level);
|
||||
REQUIRE(log_msg == expected_msg);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Test logf_warning") {
|
||||
|
||||
const LogLevel expected_log_level{LogLevel::Warning};
|
||||
const std::string expected_msg = "Test logf_warning";
|
||||
|
||||
logf_warning("Test logf_warning");
|
||||
|
||||
THEN("log_level & log_msg should be like expected") {
|
||||
REQUIRE(log_level == expected_log_level);
|
||||
REQUIRE(log_msg == expected_msg);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Test logf_warning with arguments") {
|
||||
|
||||
const LogLevel expected_log_level{LogLevel::Warning};
|
||||
const std::string expected_msg = "Test logf_warning: 4";
|
||||
|
||||
logf_warning("Test logf_warning: %d", 4);
|
||||
|
||||
THEN("log_level & log_msg should be like expected") {
|
||||
REQUIRE(log_level == expected_log_level);
|
||||
REQUIRE(log_msg == expected_msg);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Test logf_info") {
|
||||
|
||||
const LogLevel expected_log_level{LogLevel::Info};
|
||||
const std::string expected_msg = "Test logf_info";
|
||||
|
||||
logf_info("Test logf_info");
|
||||
|
||||
THEN("log_level & log_msg should be like expected") {
|
||||
REQUIRE(log_level == expected_log_level);
|
||||
REQUIRE(log_msg == expected_msg);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Test logf_info with arguments") {
|
||||
|
||||
const LogLevel expected_log_level{LogLevel::Info};
|
||||
const std::string expected_msg = "Test logf_info: d";
|
||||
|
||||
logf_info("Test logf_info: %c", 'd');
|
||||
|
||||
THEN("log_level & log_msg should be like expected") {
|
||||
REQUIRE(log_level == expected_log_level);
|
||||
REQUIRE(log_msg == expected_msg);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Test logf_debug") {
|
||||
|
||||
const LogLevel expected_log_level{LogLevel::Debug};
|
||||
const std::string expected_msg = "Test logf_debug";
|
||||
|
||||
logf_debug("Test logf_debug");
|
||||
|
||||
THEN("log_level & log_msg should be like expected") {
|
||||
REQUIRE(log_level == expected_log_level);
|
||||
REQUIRE(log_msg == expected_msg);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Test logf_debug with arguments") {
|
||||
|
||||
const LogLevel expected_log_level{LogLevel::Debug};
|
||||
const std::string expected_msg = "Test logf_debug: 23";
|
||||
|
||||
logf_debug("Test logf_debug: %d", 23);
|
||||
|
||||
THEN("log_level & log_msg should be like expected") {
|
||||
REQUIRE(log_level == expected_log_level);
|
||||
REQUIRE(log_msg == expected_msg);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Test logf_trace") {
|
||||
|
||||
const LogLevel expected_log_level{LogLevel::Trace};
|
||||
const std::string expected_msg = "Test logf_trace";
|
||||
|
||||
logf_trace("Test logf_trace");
|
||||
|
||||
THEN("log_level & log_msg should be like expected") {
|
||||
REQUIRE(log_level == expected_log_level);
|
||||
REQUIRE(log_msg == expected_msg);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Test logf_trace with arguments") {
|
||||
|
||||
const LogLevel expected_log_level{LogLevel::Trace};
|
||||
const std::string expected_msg = "Test logf_trace: 20000";
|
||||
|
||||
logf_trace("Test logf_trace: %d", 20000);
|
||||
|
||||
THEN("log_level & log_msg should be like expected") {
|
||||
REQUIRE(log_level == expected_log_level);
|
||||
REQUIRE(log_msg == expected_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
6
tools/EVerest-main/lib/everest/iso15118/test/iso15118/io/pki/.gitignore
vendored
Normal file
6
tools/EVerest-main/lib/everest/iso15118/test/iso15118/io/pki/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
*.pem
|
||||
*.der
|
||||
*.key
|
||||
|
||||
certs/
|
||||
csrs/
|
||||
@@ -0,0 +1,15 @@
|
||||
[req]
|
||||
prompt = no
|
||||
distinguished_name = ca_dn
|
||||
|
||||
[ca_dn]
|
||||
commonName = UKSWI123456791A
|
||||
organizationName = EVerest
|
||||
countryName = DE
|
||||
domainComponent = MO
|
||||
|
||||
[ext]
|
||||
basicConstraints = critical,CA:false
|
||||
keyUsage = critical,digitalSignature,nonRepudiation,keyEncipherment,keyAgreement
|
||||
subjectKeyIdentifier = hash
|
||||
authorityInfoAccess = OCSP;URI:https://www.example.com/, caIssuers;URI:https://www.example.com/Intermediate-CA.cer
|
||||
@@ -0,0 +1,15 @@
|
||||
[req]
|
||||
prompt = no
|
||||
distinguished_name = ca_dn
|
||||
|
||||
[ca_dn]
|
||||
commonName = CPOSubCA1
|
||||
organizationName = EVerest
|
||||
countryName = DE
|
||||
domainComponent = V2G
|
||||
|
||||
[ext]
|
||||
basicConstraints = critical,CA:true,pathlen:1
|
||||
keyUsage = critical,keyCertSign,cRLSign
|
||||
subjectKeyIdentifier = hash
|
||||
authorityInfoAccess = OCSP;URI:https://www.example.com/, caIssuers;URI:https://www.example.com/Intermediate-CA.cer
|
||||
@@ -0,0 +1,15 @@
|
||||
[req]
|
||||
prompt = no
|
||||
distinguished_name = ca_dn
|
||||
|
||||
[ca_dn]
|
||||
commonName = CPOSubCA2
|
||||
organizationName = EVerest
|
||||
countryName = DE
|
||||
domainComponent = V2G
|
||||
|
||||
[ext]
|
||||
basicConstraints = critical,CA:true,pathlen:0
|
||||
keyUsage = critical,keyCertSign,cRLSign
|
||||
subjectKeyIdentifier = hash
|
||||
authorityInfoAccess = OCSP;URI:https://www.example.com/, caIssuers;URI:https://www.example.com/Intermediate-CA.cer
|
||||
@@ -0,0 +1,15 @@
|
||||
[req]
|
||||
prompt = no
|
||||
distinguished_name = ca_dn
|
||||
|
||||
[ca_dn]
|
||||
commonName = CPS Leaf
|
||||
organizationName = EVerest
|
||||
countryName = DE
|
||||
domainComponent = CPS
|
||||
|
||||
[ext]
|
||||
basicConstraints = critical,CA:false
|
||||
keyUsage = critical,digitalSignature
|
||||
subjectKeyIdentifier = hash
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
[req]
|
||||
prompt = no
|
||||
distinguished_name = ca_dn
|
||||
|
||||
[ca_dn]
|
||||
commonName = ProvSubCA1
|
||||
organizationName = EVerest
|
||||
countryName = DE
|
||||
domainComponent = CPS
|
||||
|
||||
[ext]
|
||||
basicConstraints = critical,CA:true,pathlen:1
|
||||
keyUsage = critical,keyCertSign,cRLSign
|
||||
subjectKeyIdentifier = hash
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
[req]
|
||||
prompt = no
|
||||
distinguished_name = ca_dn
|
||||
|
||||
[ca_dn]
|
||||
commonName = ProvSubCA2
|
||||
organizationName = EVerest
|
||||
countryName = DE
|
||||
domainComponent = CPS
|
||||
|
||||
[ext]
|
||||
basicConstraints = critical,CA:true,pathlen:0
|
||||
keyUsage = critical,keyCertSign,cRLSign
|
||||
subjectKeyIdentifier = hash
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
[req]
|
||||
prompt = no
|
||||
distinguished_name = ca_dn
|
||||
|
||||
[ca_dn]
|
||||
commonName = MORootCA
|
||||
organizationName = EVerest
|
||||
countryName = DE
|
||||
domainComponent = MO
|
||||
|
||||
[ext]
|
||||
basicConstraints = critical,CA:true
|
||||
keyUsage = critical,keyCertSign,cRLSign
|
||||
subjectKeyIdentifier = hash
|
||||
authorityInfoAccess = OCSP;URI:https://www.example.com/, caIssuers;URI:https://www.example.com/Intermediate-CA.cer
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
[req]
|
||||
prompt = no
|
||||
distinguished_name = ca_dn
|
||||
|
||||
[ca_dn]
|
||||
commonName = PKI-Ext_CRT_MO_SUB1_VALID
|
||||
organizationName = EVerest
|
||||
countryName = DE
|
||||
domainComponent = MO
|
||||
|
||||
[ext]
|
||||
basicConstraints = critical,CA:true,pathlen:1
|
||||
keyUsage = critical,keyCertSign,cRLSign
|
||||
subjectKeyIdentifier = hash
|
||||
authorityInfoAccess = OCSP;URI:https://www.example.com/, caIssuers;URI:https://www.example.com/Intermediate-CA.cer
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
[req]
|
||||
prompt = no
|
||||
distinguished_name = ca_dn
|
||||
|
||||
[ca_dn]
|
||||
commonName = PKI-Ext_CRT_MO_SUB2_VALID
|
||||
organizationName = EVerest
|
||||
countryName = DE
|
||||
domainComponent = MO
|
||||
|
||||
[ext]
|
||||
basicConstraints = critical,CA:true,pathlen:0
|
||||
keyUsage = critical,digitalSignature,nonRepudiation,keyCertSign,cRLSign
|
||||
subjectKeyIdentifier = hash
|
||||
authorityInfoAccess = OCSP;URI:https://www.example.com/, caIssuers;URI:https://www.example.com/Intermediate-CA.cer
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
[req]
|
||||
prompt = no
|
||||
distinguished_name = ca_dn
|
||||
|
||||
[ca_dn]
|
||||
commonName = OEMProvCert
|
||||
organizationName = EVerest
|
||||
countryName = DE
|
||||
domainComponent = OEM
|
||||
|
||||
[ext]
|
||||
basicConstraints = critical,CA:false
|
||||
keyUsage = critical,digitalSignature,keyAgreement
|
||||
subjectKeyIdentifier = hash
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
[req]
|
||||
prompt = no
|
||||
distinguished_name = ca_dn
|
||||
|
||||
[ca_dn]
|
||||
commonName = OEMRootCA
|
||||
organizationName = EVerest
|
||||
countryName = DE
|
||||
domainComponent = OEM
|
||||
|
||||
[ext]
|
||||
basicConstraints = critical,CA:true
|
||||
keyUsage = critical,keyCertSign,cRLSign
|
||||
subjectKeyIdentifier = hash
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
[req]
|
||||
prompt = no
|
||||
distinguished_name = ca_dn
|
||||
|
||||
[ca_dn]
|
||||
commonName = OEMSubCA1
|
||||
organizationName = EVerest
|
||||
countryName = DE
|
||||
domainComponent = OEM
|
||||
|
||||
[ext]
|
||||
basicConstraints = critical,CA:true,pathlen:1
|
||||
keyUsage = critical,keyCertSign,cRLSign
|
||||
subjectKeyIdentifier = hash
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
[req]
|
||||
prompt = no
|
||||
distinguished_name = ca_dn
|
||||
|
||||
[ca_dn]
|
||||
commonName = OEMSubCA2
|
||||
organizationName = EVerest
|
||||
countryName = DE
|
||||
domainComponent = OEM
|
||||
|
||||
[ext]
|
||||
basicConstraints = critical,CA:true,pathlen:0
|
||||
keyUsage = critical,keyCertSign,cRLSign
|
||||
subjectKeyIdentifier = hash
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
[req]
|
||||
prompt = no
|
||||
distinguished_name = ca_dn
|
||||
|
||||
[ca_dn]
|
||||
commonName = SECCCert
|
||||
organizationName = EVerest
|
||||
countryName = DE
|
||||
domainComponent = CPO
|
||||
|
||||
[ext]
|
||||
basicConstraints = critical,CA:false
|
||||
keyUsage = critical,digitalSignature,keyAgreement
|
||||
subjectKeyIdentifier = hash
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
[req]
|
||||
prompt = no
|
||||
distinguished_name = ca_dn
|
||||
|
||||
[ca_dn]
|
||||
commonName = V2GRootCA
|
||||
organizationName = EVerest
|
||||
countryName = DE
|
||||
domainComponent = V2G
|
||||
|
||||
[ext]
|
||||
basicConstraints = critical,CA:true
|
||||
keyUsage = critical,keyCertSign,cRLSign
|
||||
subjectKeyIdentifier = hash
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
[req]
|
||||
prompt = no
|
||||
distinguished_name = ca_dn
|
||||
|
||||
[ca_dn]
|
||||
commonName = WMIV1234567890ABCDEX
|
||||
organizationName = EVerest
|
||||
countryName = DE
|
||||
domainComponent = OEM
|
||||
|
||||
[ext]
|
||||
basicConstraints = critical,CA:false
|
||||
keyUsage = critical,digitalSignature,keyAgreement
|
||||
subjectKeyIdentifier = hash
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
[req]
|
||||
prompt = no
|
||||
distinguished_name = ca_dn
|
||||
|
||||
[ca_dn]
|
||||
commonName = VehicleSubCA1
|
||||
organizationName = EVerest
|
||||
countryName = DE
|
||||
domainComponent = OEM
|
||||
|
||||
[ext]
|
||||
basicConstraints = critical,CA:true,pathlen:1
|
||||
keyUsage = critical,keyCertSign,cRLSign
|
||||
subjectKeyIdentifier = hash
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
[req]
|
||||
prompt = no
|
||||
distinguished_name = ca_dn
|
||||
|
||||
[ca_dn]
|
||||
commonName = VehicleSubCA2
|
||||
organizationName = EVerest
|
||||
countryName = DE
|
||||
domainComponent = OEM
|
||||
|
||||
[ext]
|
||||
basicConstraints = critical,CA:true,pathlen:0
|
||||
keyUsage = critical,keyCertSign,cRLSign
|
||||
subjectKeyIdentifier = hash
|
||||
|
||||
128
tools/EVerest-main/lib/everest/iso15118/test/iso15118/io/pki/pki.sh
Executable file
128
tools/EVerest-main/lib/everest/iso15118/test/iso15118/io/pki/pki.sh
Executable file
@@ -0,0 +1,128 @@
|
||||
#!/bin/sh
|
||||
VALIDITY_OEM_LEAF_CERT=1460
|
||||
VALIDITY_OEM_SUBCA2_CERT=1460
|
||||
VALIDITY_OEM_SUBCA1_CERT=1460
|
||||
VALIDITY_OEM_ROOT_CERT=3650
|
||||
VALIDITY_SECC_LEAF_CERT=60
|
||||
VALIDITY_CPO_SUBCA1_CERT=1460
|
||||
VALIDITY_CPO_SUBCA2_CERT=365
|
||||
VALIDITY_V2G_ROOT_CERT=3650
|
||||
VALIDITY_VEHICLE_LEAF_CERT=1460
|
||||
VALIDITY_VEHICLE_SUBCA1_CERT=1460
|
||||
VALIDITY_VEHICLE_SUBCA2_CERT=3650
|
||||
|
||||
SYMMETRIC_CIPHER=-aes-128-cbc # TODO Check correct version for ISO 15118-20
|
||||
SYMMETRIC_CIPHER_PKCS12=-aes128 # TODO Check correct version for ISO 15118-20
|
||||
SHA=-sha256 # TODO Check correct version for ISO 15118-20
|
||||
EC_CURVE=prime256v1 # TODO Check correct version for ISO 15118-20
|
||||
|
||||
password=123456
|
||||
|
||||
echo "Password used is: '$password'"
|
||||
|
||||
# 0) Create directories if not yet existing
|
||||
CERT_PATH=certs
|
||||
CSR_PATH=csrs
|
||||
|
||||
CA_CSO_PATH=$CERT_PATH/ca/cso
|
||||
CA_OEM_PATH=$CERT_PATH/ca/oem
|
||||
CA_V2G_PATH=$CERT_PATH/ca/v2g
|
||||
CA_VEHICLE_PATH=$CERT_PATH/ca/vehicle
|
||||
|
||||
CLIENT_CSO_PATH=$CERT_PATH/client/cso
|
||||
CLIENT_OEM_PATH=$CERT_PATH/client/oem
|
||||
CLIENT_V2G_PATH=$CERT_PATH/client/v2g
|
||||
CLIENT_VEHICLE_PATH=$CERT_PATH/client/vehicle
|
||||
|
||||
mkdir -p $CERT_PATH
|
||||
mkdir -p $CSR_PATH
|
||||
mkdir -p $CA_CSO_PATH
|
||||
mkdir -p $CA_OEM_PATH
|
||||
mkdir -p $CA_V2G_PATH
|
||||
mkdir -p $CA_VEHICLE_PATH
|
||||
mkdir -p $CLIENT_CSO_PATH
|
||||
mkdir -p $CLIENT_OEM_PATH
|
||||
mkdir -p $CLIENT_V2G_PATH
|
||||
mkdir -p $CLIENT_VEHICLE_PATH
|
||||
|
||||
# 1) Create a self-signed V2G_ROOT_CA certificate
|
||||
openssl ecparam -genkey -name $EC_CURVE | openssl ec $SYMMETRIC_CIPHER -passout pass:$password -out $CLIENT_V2G_PATH/V2G_ROOT_CA.key
|
||||
openssl req -new -key $CLIENT_V2G_PATH/V2G_ROOT_CA.key -passin pass:$password -config configs/v2gRootCACert.cnf -out $CSR_PATH/V2G_ROOT_CA.csr
|
||||
openssl x509 -req -in $CSR_PATH/V2G_ROOT_CA.csr -extfile configs/v2gRootCACert.cnf -extensions ext -signkey $CLIENT_V2G_PATH/V2G_ROOT_CA.key -passin pass:$password $SHA -set_serial 12345 -out $CA_V2G_PATH/V2G_ROOT_CA.pem -days $VALIDITY_V2G_ROOT_CERT
|
||||
|
||||
# 2) Create an intermediate CPO sub-CA 1 certificate which is directly signed
|
||||
# by the V2G_ROOT_CA certificate
|
||||
openssl ecparam -genkey -name $EC_CURVE | openssl ec $SYMMETRIC_CIPHER -passout pass:$password -out $CLIENT_CSO_PATH/CPO_SUB_CA1.key
|
||||
openssl req -new -key $CLIENT_CSO_PATH/CPO_SUB_CA1.key -passin pass:$password -config configs/cpoSubCA1Cert.cnf -out $CSR_PATH/CPO_SUB_CA1.csr
|
||||
openssl x509 -req -in $CSR_PATH/CPO_SUB_CA1.csr -extfile configs/cpoSubCA1Cert.cnf -extensions ext -CA $CA_V2G_PATH/V2G_ROOT_CA.pem -CAkey $CLIENT_V2G_PATH/V2G_ROOT_CA.key -passin pass:$password -set_serial 12346 -out $CA_CSO_PATH/CPO_SUB_CA1.pem -days $VALIDITY_CPO_SUBCA1_CERT
|
||||
|
||||
# 3) Create a second intermediate CPO sub-CA certificate (sub-CA 2) just the way
|
||||
# the previous intermedia certificate was created, which is directly signed
|
||||
# by the CPO_SUB_CA1
|
||||
openssl ecparam -genkey -name $EC_CURVE | openssl ec $SYMMETRIC_CIPHER -passout pass:$password -out $CLIENT_CSO_PATH/CPO_SUB_CA2.key
|
||||
openssl req -new -key $CLIENT_CSO_PATH/CPO_SUB_CA2.key -passin pass:$password -config configs/cpoSubCA2Cert.cnf -out $CSR_PATH/CPO_SUB_CA2.csr
|
||||
openssl x509 -req -in $CSR_PATH/CPO_SUB_CA2.csr -extfile configs/cpoSubCA2Cert.cnf -extensions ext -CA $CA_CSO_PATH/CPO_SUB_CA1.pem -CAkey $CLIENT_CSO_PATH/CPO_SUB_CA1.key -passin pass:$password -set_serial 12347 -days $VALIDITY_CPO_SUBCA2_CERT -out $CA_CSO_PATH/CPO_SUB_CA2.pem
|
||||
|
||||
# 4) Create an SECC certificate, which is the leaf certificate belonging to
|
||||
# the charging station that authenticates itself to the EVCC during a TLS
|
||||
# handshake, signed by CPO_SUB_CA2
|
||||
openssl ecparam -genkey -name $EC_CURVE | openssl ec $SYMMETRIC_CIPHER -passout pass:$password -out $CLIENT_CSO_PATH/SECC_LEAF.key
|
||||
openssl req -new -key $CLIENT_CSO_PATH/SECC_LEAF.key -passin pass:$password -config configs/seccLeafCert.cnf -out $CSR_PATH/SECC_LEAF.csr
|
||||
openssl x509 -req -in $CSR_PATH/SECC_LEAF.csr -extfile configs/seccLeafCert.cnf -extensions ext -CA $CA_CSO_PATH/CPO_SUB_CA2.pem -CAkey $CLIENT_CSO_PATH/CPO_SUB_CA2.key -passin pass:$password -set_serial 12348 -days $VALIDITY_SECC_LEAF_CERT -out $CLIENT_CSO_PATH/SECC_LEAF.pem
|
||||
cat $CLIENT_CSO_PATH/SECC_LEAF.pem $CA_CSO_PATH/CPO_SUB_CA2.pem $CA_CSO_PATH/CPO_SUB_CA1.pem > $CLIENT_CSO_PATH/CPO_CERT_CHAIN.pem
|
||||
|
||||
# 5) Create a self-signed OEM_ROOT_CA certificate (validity is up to the OEM)
|
||||
openssl ecparam -genkey -name $EC_CURVE | openssl ec $SYMMETRIC_CIPHER -passout pass:$password -out $CLIENT_OEM_PATH/OEM_ROOT_CA.key
|
||||
openssl req -new -key $CLIENT_OEM_PATH/OEM_ROOT_CA.key -passin pass:$password -config configs/oemRootCACert.cnf -out $CSR_PATH/OEM_ROOT_CA.csr
|
||||
openssl x509 -req -in $CSR_PATH/OEM_ROOT_CA.csr -extfile configs/oemRootCACert.cnf -extensions ext -signkey $CLIENT_OEM_PATH/OEM_ROOT_CA.key -passin pass:$password $SHA -set_serial 12349 -out $CA_OEM_PATH/OEM_ROOT_CA.pem -days $VALIDITY_OEM_ROOT_CERT
|
||||
|
||||
# 6) Create an intermediate OEM sub-CA certificate, which is directly signed by
|
||||
# the OEM_ROOT_CA certificate (validity is up to the OEM)
|
||||
openssl ecparam -genkey -name $EC_CURVE | openssl ec $SYMMETRIC_CIPHER -passout pass:$password -out $CLIENT_OEM_PATH/OEM_SUB_CA1.key
|
||||
openssl req -new -key $CLIENT_OEM_PATH/OEM_SUB_CA1.key -passin pass:$password -config configs/oemSubCA1Cert.cnf -out $CSR_PATH/OEM_SUB_CA1.csr
|
||||
openssl x509 -req -in $CSR_PATH/OEM_SUB_CA1.csr -extfile configs/oemSubCA1Cert.cnf -extensions ext -CA $CA_OEM_PATH/OEM_ROOT_CA.pem -CAkey $CLIENT_OEM_PATH/OEM_ROOT_CA.key -passin pass:$password -set_serial 12350 -days $VALIDITY_OEM_SUBCA1_CERT -out $CA_OEM_PATH/OEM_SUB_CA1.pem
|
||||
|
||||
# 7) Create a second intermediate OEM sub-CA certificate, which is directly
|
||||
# signed by the OEM_SUB_CA1 certificate (validity is up to the OEM)
|
||||
openssl ecparam -genkey -name $EC_CURVE | openssl ec $SYMMETRIC_CIPHER -passout pass:$password -out $CLIENT_OEM_PATH/OEM_SUB_CA2.key
|
||||
openssl req -new -key $CLIENT_OEM_PATH/OEM_SUB_CA2.key -passin pass:$password -config configs/oemSubCA2Cert.cnf -out $CSR_PATH/OEM_SUB_CA2.csr
|
||||
openssl x509 -req -in $CSR_PATH/OEM_SUB_CA2.csr -extfile configs/oemSubCA2Cert.cnf -extensions ext -CA $CA_OEM_PATH/OEM_SUB_CA1.pem -CAkey $CLIENT_OEM_PATH/OEM_SUB_CA1.key -passin pass:$password -set_serial 12351 -days $VALIDITY_OEM_SUBCA2_CERT -out $CA_OEM_PATH/OEM_SUB_CA2.pem
|
||||
|
||||
# 8) Create an OEM provisioning certificate, which is the leaf certificate
|
||||
# belonging to the OEM certificate chain (used for contract certificate
|
||||
# installation)
|
||||
openssl ecparam -genkey -name $EC_CURVE | openssl ec $SYMMETRIC_CIPHER -passout pass:$password -out $CLIENT_OEM_PATH/OEM_LEAF.key
|
||||
openssl req -new -key $CLIENT_OEM_PATH/OEM_LEAF.key -passin pass:$password -config configs/oemLeafCert.cnf -out $CSR_PATH/OEM_LEAF.csr
|
||||
openssl x509 -req -in $CSR_PATH/OEM_LEAF.csr -extfile configs/oemLeafCert.cnf -extensions ext -CA $CA_OEM_PATH/OEM_SUB_CA2.pem -CAkey $CLIENT_OEM_PATH/OEM_SUB_CA2.key -passin pass:$password -set_serial 12352 -days $VALIDITY_OEM_LEAF_CERT -out $CLIENT_OEM_PATH/OEM_LEAF.pem
|
||||
cat $CLIENT_OEM_PATH/OEM_LEAF.pem $CA_OEM_PATH/OEM_SUB_CA2.pem $CA_OEM_PATH/OEM_SUB_CA1.pem > $CA_OEM_PATH/OEM_CERT_CHAIN.pem
|
||||
|
||||
# 16) Create an intermediate vehicle sub-CA 1 certificate which is directly signed
|
||||
# by the V2GRootCA certificate
|
||||
# ---------------------------------------------------------------------------
|
||||
openssl ecparam -genkey -name $EC_CURVE | openssl ec $SYMMETRIC_CIPHER -passout pass:$password -out $CLIENT_VEHICLE_PATH/VEHICLE_SUB_CA1.key
|
||||
openssl req -new -key $CLIENT_VEHICLE_PATH/VEHICLE_SUB_CA1.key -passin pass:$password -config configs/vehicleSubCA1Cert.cnf -out $CSR_PATH/VEHICLE_SUB_CA1.csr
|
||||
openssl x509 -req -in $CSR_PATH/VEHICLE_SUB_CA1.csr -extfile configs/vehicleSubCA1Cert.cnf -extensions ext -CA $CA_V2G_PATH/V2G_ROOT_CA.pem -CAkey $CLIENT_V2G_PATH/V2G_ROOT_CA.key -passin pass:$password -set_serial 12360 -out $CA_VEHICLE_PATH/VEHICLE_SUB_CA1.pem -days $VALIDITY_VEHICLE_SUBCA1_CERT
|
||||
|
||||
# 17) Create a second intermediate vehicle sub-CA certificate (sub-CA 2) just the way
|
||||
# the previous intermedia certificate was created, which is directly signed
|
||||
# by the VEHICLE_SUB_CA1
|
||||
openssl ecparam -genkey -name $EC_CURVE | openssl ec $SYMMETRIC_CIPHER -passout pass:$password -out $CLIENT_VEHICLE_PATH/VEHICLE_SUB_CA2.key
|
||||
openssl req -new -key $CLIENT_VEHICLE_PATH/VEHICLE_SUB_CA2.key -passin pass:$password -config configs/vehicleSubCA2Cert.cnf -out $CSR_PATH/VEHICLE_SUB_CA2.csr
|
||||
openssl x509 -req -in $CSR_PATH/VEHICLE_SUB_CA2.csr -extfile configs/vehicleSubCA2Cert.cnf -extensions ext -CA $CA_VEHICLE_PATH/VEHICLE_SUB_CA1.pem -CAkey $CLIENT_VEHICLE_PATH/VEHICLE_SUB_CA1.key -passin pass:$password -set_serial 12361 -days $VALIDITY_VEHICLE_SUBCA2_CERT -out $CA_VEHICLE_PATH/VEHICLE_SUB_CA2.pem
|
||||
|
||||
# 18) Create an vehicle certificate, which is the leaf certificate belonging to
|
||||
# the electric vehicle that authenticates itself to the SECC during a TLS
|
||||
# handshake, signed by vehicleSubCA2
|
||||
openssl ecparam -genkey -name $EC_CURVE | openssl ec $SYMMETRIC_CIPHER -passout pass:$password -out $CLIENT_VEHICLE_PATH/VEHICLE_LEAF.key
|
||||
openssl req -new -key $CLIENT_VEHICLE_PATH/VEHICLE_LEAF.key -passin pass:$password -config configs/vehicleLeafCert.cnf -out $CSR_PATH/VEHICLE_LEAF.csr
|
||||
openssl x509 -req -in $CSR_PATH/VEHICLE_LEAF.csr -extfile configs/vehicleLeafCert.cnf -extensions ext -CA $CA_VEHICLE_PATH/VEHICLE_SUB_CA2.pem -CAkey $CLIENT_VEHICLE_PATH/VEHICLE_SUB_CA2.key -passin pass:$password -set_serial 12362 -days $VALIDITY_VEHICLE_LEAF_CERT -out $CLIENT_VEHICLE_PATH/VEHICLE_LEAF.pem
|
||||
cat $CLIENT_VEHICLE_PATH/VEHICLE_LEAF.pem $CA_VEHICLE_PATH/VEHICLE_SUB_CA2.pem $CA_VEHICLE_PATH/VEHICLE_SUB_CA1.pem > $CA_VEHICLE_PATH/VEHICLE_CERT_CHAIN.pem
|
||||
|
||||
# 19) Place all passwords to generated private keys in separate text files.
|
||||
# In this script, even though we use a single password for all certificates,
|
||||
# certificates from a different source could have been generated with a different
|
||||
# passphrase/passkey/password altogether. Leave them empty if no password is required.
|
||||
echo $password > $CLIENT_CSO_PATH/SECC_LEAF_PASSWORD.txt
|
||||
echo $password > $CLIENT_OEM_PATH/OEM_LEAF_PASSWORD.txt
|
||||
echo $password > $CLIENT_V2G_PATH/V2G_ROOT_CA_PASSWORD.txt
|
||||
echo $password > $CLIENT_VEHICLE_PATH/VEHICLE_LEAF_PASSWORD.txt
|
||||
@@ -0,0 +1,47 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2025 Pionix GmbH and Contributors to EVerest
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
|
||||
#include <iso15118/io/time.hpp>
|
||||
|
||||
SCENARIO("V2G Communication Setup Timeout Pattern") {
|
||||
|
||||
GIVEN("A timeout starts when dlink becomes ready") {
|
||||
std::optional<iso15118::Timeout> timeout;
|
||||
timeout.emplace(100);
|
||||
|
||||
REQUIRE(timeout.has_value());
|
||||
REQUIRE(timeout->is_reached() == false);
|
||||
}
|
||||
|
||||
GIVEN("A timeout fires after duration expires") {
|
||||
std::optional<iso15118::Timeout> timeout;
|
||||
timeout.emplace(15);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
REQUIRE(timeout->is_reached() == true);
|
||||
}
|
||||
|
||||
GIVEN("A timeout is cancelled when session is established") {
|
||||
std::optional<iso15118::Timeout> timeout;
|
||||
timeout.emplace(100);
|
||||
|
||||
REQUIRE(timeout.has_value());
|
||||
// Session established -> cancel timeout
|
||||
timeout.reset();
|
||||
REQUIRE_FALSE(timeout.has_value());
|
||||
}
|
||||
|
||||
GIVEN("A timeout is cancelled when dlink becomes not ready") {
|
||||
std::optional<iso15118::Timeout> timeout;
|
||||
timeout.emplace(100);
|
||||
|
||||
REQUIRE(timeout.has_value());
|
||||
// dlink not ready -> cancel timeout
|
||||
timeout.reset();
|
||||
REQUIRE_FALSE(timeout.has_value());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#include <cstdio>
|
||||
|
||||
#include <iso15118/tbd_controller.hpp>
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
iso15118::TbdController controller;
|
||||
controller.loop();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
include(Catch)
|
||||
|
||||
add_executable(test_feedback feedback.cpp)
|
||||
|
||||
target_link_libraries(test_feedback
|
||||
PRIVATE
|
||||
iso15118
|
||||
Catch2::Catch2WithMain
|
||||
)
|
||||
|
||||
catch_discover_tests(test_feedback)
|
||||
@@ -0,0 +1,321 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2024 Pionix GmbH and Contributors to EVerest
|
||||
#include <algorithm>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/session/feedback.hpp>
|
||||
|
||||
using namespace iso15118::session;
|
||||
|
||||
namespace dt = iso15118::message_20::datatypes;
|
||||
|
||||
struct FeedbackResults {
|
||||
feedback::Signal signal;
|
||||
float target_voltage;
|
||||
feedback::DcChargeLoopReq dc_charge_loop_req;
|
||||
feedback::DcMaximumLimits dc_max_limits;
|
||||
iso15118::message_20::Type v2g_message;
|
||||
std::string evcc_id;
|
||||
std::string selected_protocol;
|
||||
iso15118::d20::EVInformation ev_information;
|
||||
uint16_t id;
|
||||
dt::VasSelectedServiceList selected_vas;
|
||||
};
|
||||
|
||||
SCENARIO("Feedback Tests") {
|
||||
|
||||
FeedbackResults feedback_results;
|
||||
feedback::Callbacks callbacks;
|
||||
|
||||
callbacks.signal = [&feedback_results](feedback::Signal signal_) { feedback_results.signal = signal_; };
|
||||
callbacks.dc_pre_charge_target_voltage = [&feedback_results](float target_voltage_) {
|
||||
feedback_results.target_voltage = target_voltage_;
|
||||
};
|
||||
callbacks.dc_charge_loop_req = [&feedback_results](const feedback::DcChargeLoopReq& dc_charge_loop_req) {
|
||||
feedback_results.dc_charge_loop_req = dc_charge_loop_req;
|
||||
};
|
||||
callbacks.dc_max_limits = [&feedback_results](const feedback::DcMaximumLimits& dc_max_limits_) {
|
||||
feedback_results.dc_max_limits = dc_max_limits_;
|
||||
};
|
||||
callbacks.v2g_message = [&feedback_results](const iso15118::message_20::Type& type) {
|
||||
feedback_results.v2g_message = type;
|
||||
};
|
||||
callbacks.evccid = [&feedback_results](const std::string& evcc_id_) { feedback_results.evcc_id = evcc_id_; };
|
||||
callbacks.selected_protocol = [&feedback_results](const std::string& protocol) {
|
||||
feedback_results.selected_protocol = protocol;
|
||||
};
|
||||
callbacks.ev_information = [&feedback_results](const iso15118::d20::EVInformation& ev_information) {
|
||||
feedback_results.ev_information = ev_information;
|
||||
};
|
||||
callbacks.get_vas_parameters = [&feedback_results](uint16_t id) {
|
||||
feedback_results.id = id;
|
||||
|
||||
auto service_parameter_list = dt::ServiceParameterList{};
|
||||
auto& parameter_set = service_parameter_list.emplace_back();
|
||||
parameter_set.id = 0;
|
||||
parameter_set.parameter.push_back({"Service1", 40});
|
||||
parameter_set.parameter.push_back({"Service2", "house"});
|
||||
|
||||
return std::make_optional(service_parameter_list);
|
||||
};
|
||||
callbacks.selected_vas_services = [&feedback_results](dt::VasSelectedServiceList selected_vas_) {
|
||||
feedback_results.selected_vas = selected_vas_;
|
||||
};
|
||||
|
||||
const auto feedback = Feedback(callbacks);
|
||||
|
||||
GIVEN("Test signal") {
|
||||
const feedback::Signal expected = feedback::Signal::REQUIRE_AUTH_EIM;
|
||||
feedback.signal(feedback::Signal::REQUIRE_AUTH_EIM);
|
||||
|
||||
THEN("signal should be like expected") {
|
||||
REQUIRE(feedback_results.signal == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Test dc_pre_charge_target_voltage") {
|
||||
float expected{421.4};
|
||||
feedback.dc_pre_charge_target_voltage(421.4);
|
||||
|
||||
THEN("dc_pre_charge_target_voltage should be like expected") {
|
||||
REQUIRE(feedback_results.target_voltage == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Test dc_charge_loop_req - bpt scheduled") {
|
||||
|
||||
using BPT_ScheduleReqControlMode = dt::BPT_Scheduled_DC_CLReqControlMode;
|
||||
|
||||
const BPT_ScheduleReqControlMode expected = {{
|
||||
{std::nullopt, std::nullopt, std::nullopt},
|
||||
{4402, -1},
|
||||
{30, 0},
|
||||
std::nullopt,
|
||||
dt::RationalNumber{34, 0},
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
},
|
||||
dt::RationalNumber{11, 3},
|
||||
dt::RationalNumber{32, 1},
|
||||
std::nullopt};
|
||||
|
||||
feedback.dc_charge_loop_req(expected);
|
||||
|
||||
THEN("dc_charge_loop_req should be like expected") {
|
||||
|
||||
const auto* dc_control_mode = std::get_if<feedback::DcReqControlMode>(&feedback_results.dc_charge_loop_req);
|
||||
|
||||
const auto* bpt_scheduled_control_mode = std::get_if<BPT_ScheduleReqControlMode>(dc_control_mode);
|
||||
|
||||
REQUIRE(bpt_scheduled_control_mode->target_energy_request.has_value() == false);
|
||||
REQUIRE(bpt_scheduled_control_mode->max_energy_request.has_value() == false);
|
||||
REQUIRE(bpt_scheduled_control_mode->min_energy_request.has_value() == false);
|
||||
|
||||
REQUIRE(dt::from_RationalNumber(bpt_scheduled_control_mode->target_current) ==
|
||||
dt::from_RationalNumber(expected.target_current));
|
||||
REQUIRE(dt::from_RationalNumber(bpt_scheduled_control_mode->target_voltage) ==
|
||||
dt::from_RationalNumber(expected.target_voltage));
|
||||
REQUIRE(bpt_scheduled_control_mode->max_charge_power.has_value() == false);
|
||||
REQUIRE(bpt_scheduled_control_mode->min_charge_power.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*bpt_scheduled_control_mode->min_charge_power) ==
|
||||
dt::from_RationalNumber(expected.min_charge_power.value_or(dt::RationalNumber{})));
|
||||
REQUIRE(bpt_scheduled_control_mode->max_charge_current.has_value() == false);
|
||||
REQUIRE(bpt_scheduled_control_mode->max_voltage.has_value() == false);
|
||||
REQUIRE(bpt_scheduled_control_mode->min_voltage.has_value() == false);
|
||||
|
||||
REQUIRE(bpt_scheduled_control_mode->max_discharge_power.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*bpt_scheduled_control_mode->max_discharge_power) ==
|
||||
dt::from_RationalNumber(expected.max_discharge_power.value_or(dt::RationalNumber{})));
|
||||
REQUIRE(bpt_scheduled_control_mode->min_discharge_power.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(*bpt_scheduled_control_mode->min_discharge_power) ==
|
||||
dt::from_RationalNumber(expected.min_discharge_power.value_or(dt::RationalNumber{})));
|
||||
REQUIRE(bpt_scheduled_control_mode->max_discharge_current.has_value() == false);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Test dc_charge_loop_req - dynamic") {
|
||||
|
||||
using DynamicReqControlMode = dt::Dynamic_DC_CLReqControlMode;
|
||||
|
||||
const DynamicReqControlMode expected = {
|
||||
{std::nullopt, {2344, 1}, {30, 3}, {10, 3}}, {22, 3}, {0, 0}, {5, 2}, {9, 2}, {25, 1}};
|
||||
|
||||
feedback.dc_charge_loop_req(expected);
|
||||
|
||||
THEN("dc_charge_loop_req should be like expected") {
|
||||
|
||||
const auto* dc_control_mode = std::get_if<feedback::DcReqControlMode>(&feedback_results.dc_charge_loop_req);
|
||||
|
||||
const auto* dynamic_control_mode = std::get_if<DynamicReqControlMode>(dc_control_mode);
|
||||
|
||||
REQUIRE(dynamic_control_mode->departure_time.has_value() == false);
|
||||
|
||||
REQUIRE(dt::from_RationalNumber(dynamic_control_mode->target_energy_request) ==
|
||||
dt::from_RationalNumber(expected.target_energy_request));
|
||||
REQUIRE(dt::from_RationalNumber(dynamic_control_mode->max_energy_request) ==
|
||||
dt::from_RationalNumber(expected.max_energy_request));
|
||||
REQUIRE(dt::from_RationalNumber(dynamic_control_mode->min_energy_request) ==
|
||||
dt::from_RationalNumber(expected.min_energy_request));
|
||||
|
||||
REQUIRE(dt::from_RationalNumber(dynamic_control_mode->max_charge_power) ==
|
||||
dt::from_RationalNumber(expected.max_charge_power));
|
||||
REQUIRE(dt::from_RationalNumber(dynamic_control_mode->min_charge_power) ==
|
||||
dt::from_RationalNumber(expected.min_charge_power));
|
||||
REQUIRE(dt::from_RationalNumber(dynamic_control_mode->max_charge_current) ==
|
||||
dt::from_RationalNumber(expected.max_charge_current));
|
||||
REQUIRE(dt::from_RationalNumber(dynamic_control_mode->max_voltage) ==
|
||||
dt::from_RationalNumber(expected.max_voltage));
|
||||
REQUIRE(dt::from_RationalNumber(dynamic_control_mode->min_voltage) ==
|
||||
dt::from_RationalNumber(expected.min_voltage));
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Test dc_max_limits") {
|
||||
const feedback::DcMaximumLimits expected{803.1, 10.0, 8031};
|
||||
feedback.dc_max_limits({803.1, 10.0, 8031});
|
||||
|
||||
THEN("dc_max_limits should be like expected") {
|
||||
REQUIRE(feedback_results.dc_max_limits.voltage == expected.voltage);
|
||||
REQUIRE(feedback_results.dc_max_limits.current == expected.current);
|
||||
REQUIRE(feedback_results.dc_max_limits.power == expected.power);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Test v2g_message") {
|
||||
using Type = iso15118::message_20::Type;
|
||||
const Type expected = Type::DC_CableCheckReq;
|
||||
feedback.v2g_message(Type::DC_CableCheckReq);
|
||||
|
||||
THEN("v2g_message should be like expected") {
|
||||
REQUIRE(feedback_results.v2g_message == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Test evcc_id") {
|
||||
const std::string expected = "54EA7E40B356";
|
||||
feedback.evcc_id("54EA7E40B356");
|
||||
|
||||
THEN("evcc_id should be like expected") {
|
||||
REQUIRE(feedback_results.evcc_id == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Test selected_protocol") {
|
||||
const std::string expected = "ISO15118-20:DC";
|
||||
feedback.selected_protocol("ISO15118-20:DC");
|
||||
|
||||
THEN("selected_protocol should be like expected") {
|
||||
REQUIRE(feedback_results.selected_protocol == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Test display_parameters") {
|
||||
const dt::DisplayParameters expected{40, std::nullopt, 95, std::nullopt, std::nullopt,
|
||||
std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt};
|
||||
|
||||
feedback.dc_charge_loop_req(expected);
|
||||
|
||||
THEN("display_parameters should be like expected") {
|
||||
const auto* display_parameters = std::get_if<dt::DisplayParameters>(&feedback_results.dc_charge_loop_req);
|
||||
REQUIRE(display_parameters->present_soc.has_value() == true);
|
||||
REQUIRE(*display_parameters->present_soc == expected.present_soc.value_or(0));
|
||||
REQUIRE(display_parameters->min_soc.has_value() == false);
|
||||
REQUIRE(display_parameters->target_soc.has_value() == true);
|
||||
REQUIRE(*display_parameters->target_soc == expected.target_soc.value_or(0));
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Test dc_present_voltage") {
|
||||
feedback::PresentVoltage expected{7044, -1};
|
||||
feedback.dc_charge_loop_req(expected);
|
||||
|
||||
THEN("dc_present_voltage should be like expected") {
|
||||
const auto* present_voltage = std::get_if<feedback::PresentVoltage>(&feedback_results.dc_charge_loop_req);
|
||||
REQUIRE(dt::from_RationalNumber(*present_voltage) == dt::from_RationalNumber(expected));
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Test meter_info_requested") {
|
||||
feedback::MeterInfoRequested expected{true};
|
||||
feedback.dc_charge_loop_req(true);
|
||||
|
||||
THEN("meter_info_requested should be like expected") {
|
||||
REQUIRE(*std::get_if<feedback::MeterInfoRequested>(&feedback_results.dc_charge_loop_req) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
struct SupportedAppProtocol {
|
||||
std::string protocol_namespace;
|
||||
uint32_t version_number_major;
|
||||
uint32_t version_number_minor;
|
||||
uint8_t schema_id;
|
||||
uint8_t priority;
|
||||
};
|
||||
// TODO(SL): Missing tests for notify_ev_charging_needs, selected_service_parameters
|
||||
GIVEN("Test ev_information") {
|
||||
iso15118::d20::EVInformation expected{
|
||||
iso15118::d20::EVSupportedAppProtocols{{"urn:iso:std:iso:15118:-20:DC", 1, 1, 1, 1}},
|
||||
{"urn:iso:std:iso:15118:-20:DC", 1, 1, 1, 1},
|
||||
"54EA7E40B356",
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt};
|
||||
feedback.ev_information({iso15118::d20::EVSupportedAppProtocols{{"urn:iso:std:iso:15118:-20:DC", 1, 1, 1, 1}},
|
||||
{"urn:iso:std:iso:15118:-20:DC", 1, 1, 1, 1},
|
||||
"54EA7E40B356",
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt});
|
||||
|
||||
THEN("ev_information should be like expected") {
|
||||
REQUIRE(feedback_results.ev_information.ev_supported_app_protocols == expected.ev_supported_app_protocols);
|
||||
REQUIRE(feedback_results.ev_information.selected_app_protocol == expected.selected_app_protocol);
|
||||
REQUIRE(feedback_results.ev_information.evcc_id == expected.evcc_id);
|
||||
REQUIRE(feedback_results.ev_information.ev_tls_leaf_cert == expected.ev_tls_leaf_cert);
|
||||
REQUIRE(feedback_results.ev_information.ev_tls_sub_ca_1_cert == expected.ev_tls_sub_ca_1_cert);
|
||||
REQUIRE(feedback_results.ev_information.ev_tls_sub_ca_2_cert == expected.ev_tls_sub_ca_2_cert);
|
||||
REQUIRE(feedback_results.ev_information.ev_tls_root_cert == expected.ev_tls_root_cert);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Test get_vas_parameters") {
|
||||
uint16_t expected{3};
|
||||
const auto result = feedback.get_vas_parameters(3);
|
||||
|
||||
THEN("get_vas_parameters should be like expected") {
|
||||
REQUIRE(result.has_value());
|
||||
|
||||
const auto& vas = result.value();
|
||||
|
||||
REQUIRE(vas.size() == 1);
|
||||
auto& parameters = vas[0];
|
||||
REQUIRE(parameters.id == 0);
|
||||
REQUIRE(parameters.parameter.size() == 2);
|
||||
|
||||
REQUIRE(parameters.parameter[0].name == "Service1");
|
||||
REQUIRE(std::holds_alternative<int32_t>(parameters.parameter[0].value));
|
||||
REQUIRE(std::get<int32_t>(parameters.parameter[0].value) == 40);
|
||||
|
||||
REQUIRE(parameters.parameter[1].name == "Service2");
|
||||
REQUIRE(std::holds_alternative<std::string>(parameters.parameter[1].value));
|
||||
REQUIRE(std::get<std::string>(parameters.parameter[1].value) == "house");
|
||||
|
||||
REQUIRE(feedback_results.id == expected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Test selected_vas_services") {
|
||||
const dt::VasSelectedServiceList expected{{34000, 0}, {5462, 5}};
|
||||
feedback.selected_vas_services(dt::VasSelectedServiceList{{34000, 0}, {5462, 5}});
|
||||
|
||||
THEN("meter_info_requested should be like expected") {
|
||||
REQUIRE(feedback_results.selected_vas.at(0).service_id == expected.at(0).service_id);
|
||||
REQUIRE(feedback_results.selected_vas.at(0).parameter_set_id == expected.at(0).parameter_set_id);
|
||||
REQUIRE(feedback_results.selected_vas.at(1).service_id == expected.at(1).service_id);
|
||||
REQUIRE(feedback_results.selected_vas.at(1).parameter_set_id == expected.at(1).parameter_set_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
include(Catch)
|
||||
|
||||
function(create_states_test_target NAME)
|
||||
add_executable(test_${NAME} ${NAME}.cpp)
|
||||
target_link_libraries(test_${NAME}
|
||||
PRIVATE
|
||||
iso15118
|
||||
Catch2::Catch2WithMain
|
||||
)
|
||||
catch_discover_tests(test_${NAME})
|
||||
endfunction()
|
||||
|
||||
create_states_test_target(ac_charge_loop)
|
||||
create_states_test_target(ac_charge_parameter_discovery)
|
||||
create_states_test_target(authorization_setup)
|
||||
create_states_test_target(authorization)
|
||||
create_states_test_target(service_discovery)
|
||||
create_states_test_target(service_selection)
|
||||
create_states_test_target(dc_charge_parameter_discovery)
|
||||
create_states_test_target(schedule_exchange)
|
||||
create_states_test_target(dc_cable_check)
|
||||
create_states_test_target(dc_pre_charge)
|
||||
create_states_test_target(power_delivery)
|
||||
create_states_test_target(dc_charge_loop)
|
||||
create_states_test_target(dc_welding_detection)
|
||||
create_states_test_target(session_stop)
|
||||
@@ -0,0 +1,433 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/detail/d20/state/ac_charge_loop.hpp>
|
||||
|
||||
#include <iso15118/d20/config.hpp>
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
namespace dt = message_20::datatypes;
|
||||
|
||||
using Scheduled_AC_Req = dt::Scheduled_AC_CLReqControlMode;
|
||||
using Scheduled_BPT_AC_Req = dt::BPT_Scheduled_AC_CLReqControlMode;
|
||||
using Dynamic_AC_Req = dt::Dynamic_AC_CLReqControlMode;
|
||||
using Dynamic_BPT_AC_Req = dt::BPT_Dynamic_AC_CLReqControlMode;
|
||||
|
||||
using Scheduled_AC_Res = dt::Scheduled_AC_CLResControlMode;
|
||||
using Scheduled_BPT_AC_Res = dt::BPT_Scheduled_AC_CLResControlMode;
|
||||
using Dynamic_AC_Res = dt::Dynamic_AC_CLResControlMode;
|
||||
using Dynamic_BPT_AC_Res = dt::BPT_Dynamic_AC_CLResControlMode;
|
||||
|
||||
SCENARIO("AC charge loop state handling") {
|
||||
|
||||
const auto evse_id = std::string("everest se");
|
||||
const std::vector<dt::ServiceCategory> supported_energy_services = {dt::ServiceCategory::AC,
|
||||
dt::ServiceCategory::AC_BPT};
|
||||
const auto cert_install{false};
|
||||
const std::vector<dt::Authorization> auth_services = {dt::Authorization::EIM};
|
||||
const std::vector<uint16_t> vas_services{};
|
||||
|
||||
d20::DcTransferLimits dc_limits;
|
||||
d20::DcTransferLimits powersupply_limits;
|
||||
d20::AcTransferLimits ac_limits;
|
||||
ac_limits.charge_power = {{22, 3}, {10, 0}};
|
||||
ac_limits.nominal_frequency = {50, 0};
|
||||
|
||||
auto& discharge_limits = ac_limits.discharge_power.emplace();
|
||||
discharge_limits.max = {11, 3};
|
||||
discharge_limits.min = {10, 0};
|
||||
|
||||
const std::vector<d20::ControlMobilityNeedsModes> control_mobility_modes = {
|
||||
{dt::ControlMode::Scheduled, dt::MobilityNeedsMode::ProvidedByEvcc},
|
||||
{dt::ControlMode::Dynamic, dt::MobilityNeedsMode::ProvidedByEvcc},
|
||||
{dt::ControlMode::Dynamic, dt::MobilityNeedsMode::ProvidedBySecc}};
|
||||
|
||||
const d20::EvseSetupConfig evse_setup{
|
||||
evse_id, supported_energy_services, auth_services, vas_services, cert_install, dc_limits,
|
||||
ac_limits, control_mobility_modes, std::nullopt, std::nullopt, std::nullopt, powersupply_limits};
|
||||
|
||||
GIVEN("Bad case - Unknown session") {
|
||||
d20::Session session = d20::Session();
|
||||
message_20::AC_ChargeLoopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_control_mode = req.control_mode.emplace<Scheduled_AC_Req>();
|
||||
req_control_mode.present_active_power = {0, 0};
|
||||
|
||||
req.meter_info_requested = false;
|
||||
|
||||
const auto res = d20::state::handle_request(req, d20::Session(), false, false, 50, d20::AcTargetPower(),
|
||||
d20::AcPresentPower(), d20::UpdateDynamicModeParameters());
|
||||
|
||||
THEN("ResponseCode: FAILED_UnknownSession, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_UnknownSession);
|
||||
REQUIRE(res.status.has_value() == false);
|
||||
REQUIRE(res.meter_info.has_value() == false);
|
||||
REQUIRE(res.receipt.has_value() == false);
|
||||
REQUIRE(res.target_frequency.has_value() == false);
|
||||
REQUIRE(std::holds_alternative<Scheduled_AC_Res>(res.control_mode));
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Bad case - false energy mode") {
|
||||
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::AC_BPT, dt::AcConnector::ThreePhase, dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing, dt::BptChannel::Unified,
|
||||
dt::GeneratorMode::GridFollowing, 230, dt::GridCodeIslandingDetectionMethod::Passive);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
message_20::AC_ChargeLoopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_control_mode = req.control_mode.emplace<Scheduled_AC_Req>();
|
||||
req_control_mode.present_active_power = {0, 0};
|
||||
|
||||
req.meter_info_requested = false;
|
||||
|
||||
const auto res = d20::state::handle_request(req, d20::Session(), false, false, 50, d20::AcTargetPower(),
|
||||
d20::AcPresentPower(), d20::UpdateDynamicModeParameters());
|
||||
|
||||
THEN("ResponseCode: FAILED_UnknownSession, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_UnknownSession);
|
||||
REQUIRE(res.status.has_value() == false);
|
||||
REQUIRE(res.meter_info.has_value() == false);
|
||||
REQUIRE(res.receipt.has_value() == false);
|
||||
REQUIRE(res.target_frequency.has_value() == false);
|
||||
REQUIRE(std::holds_alternative<Scheduled_AC_Res>(res.control_mode));
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Bad case - false control mode") {
|
||||
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::AC, dt::AcConnector::ThreePhase, dt::ControlMode::Dynamic,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing, 230);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
message_20::AC_ChargeLoopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_control_mode = req.control_mode.emplace<Scheduled_AC_Req>();
|
||||
req_control_mode.present_active_power = {0, 0};
|
||||
|
||||
req.meter_info_requested = false;
|
||||
|
||||
const auto res = d20::state::handle_request(req, d20::Session(), false, false, 50, d20::AcTargetPower(),
|
||||
d20::AcPresentPower(), d20::UpdateDynamicModeParameters());
|
||||
|
||||
THEN("ResponseCode: FAILED_UnknownSession, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_UnknownSession);
|
||||
REQUIRE(res.status.has_value() == false);
|
||||
REQUIRE(res.meter_info.has_value() == false);
|
||||
REQUIRE(res.receipt.has_value() == false);
|
||||
REQUIRE(res.target_frequency.has_value() == false);
|
||||
REQUIRE(std::holds_alternative<Scheduled_AC_Res>(res.control_mode));
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - AC scheduled mode") {
|
||||
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::AC, dt::AcConnector::ThreePhase, dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing, 230);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
message_20::AC_ChargeLoopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_control_mode = req.control_mode.emplace<Scheduled_AC_Req>();
|
||||
req_control_mode.present_active_power = {11, 3};
|
||||
|
||||
req.meter_info_requested = false;
|
||||
|
||||
const auto ac_target_power = d20::AcTargetPower{};
|
||||
auto ac_present_power = d20::AcPresentPower{};
|
||||
ac_present_power.present_active_power = {11, 3};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, false, false, 50, ac_target_power, ac_present_power,
|
||||
d20::UpdateDynamicModeParameters());
|
||||
|
||||
THEN("ResponseCode: OK, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.status.has_value() == false);
|
||||
REQUIRE(res.meter_info.has_value() == false);
|
||||
REQUIRE(res.receipt.has_value() == false);
|
||||
REQUIRE(dt::from_RationalNumber(res.target_frequency.value_or(dt::RationalNumber{0, 0})) == 50.0f);
|
||||
REQUIRE(std::holds_alternative<Scheduled_AC_Res>(res.control_mode));
|
||||
|
||||
const auto& res_control_mode = std::get<Scheduled_AC_Res>(res.control_mode);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.present_active_power.value_or(dt::RationalNumber{0, 0})) ==
|
||||
11000.0f);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - AC_BPT scheduled mode") {
|
||||
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::AC_BPT, dt::AcConnector::ThreePhase, dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing, dt::BptChannel::Unified,
|
||||
dt::GeneratorMode::GridFollowing, 230, dt::GridCodeIslandingDetectionMethod::Passive);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
message_20::AC_ChargeLoopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_control_mode = req.control_mode.emplace<Scheduled_BPT_AC_Req>();
|
||||
req_control_mode.present_active_power = {0, 0};
|
||||
|
||||
req.meter_info_requested = false;
|
||||
|
||||
const auto ac_target_power = d20::AcTargetPower{};
|
||||
auto ac_present_power = d20::AcPresentPower{};
|
||||
ac_present_power.present_active_power = {11, 3};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, false, false, 50, ac_target_power, ac_present_power,
|
||||
d20::UpdateDynamicModeParameters());
|
||||
|
||||
THEN("ResponseCode: OK, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.status.has_value() == false);
|
||||
REQUIRE(res.meter_info.has_value() == false);
|
||||
REQUIRE(res.receipt.has_value() == false);
|
||||
REQUIRE(dt::from_RationalNumber(res.target_frequency.value_or(dt::RationalNumber{0, 0})) == 50.0f);
|
||||
REQUIRE(std::holds_alternative<Scheduled_BPT_AC_Res>(res.control_mode));
|
||||
|
||||
const auto& res_control_mode = std::get<Scheduled_BPT_AC_Res>(res.control_mode);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.present_active_power.value_or(dt::RationalNumber{0, 0})) ==
|
||||
11000.0f);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - AC dynamic mode") {
|
||||
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::AC, dt::AcConnector::ThreePhase, dt::ControlMode::Dynamic,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing, 230);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
message_20::AC_ChargeLoopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_control_mode = req.control_mode.emplace<Dynamic_AC_Req>();
|
||||
req_control_mode.present_active_power = {11, 3};
|
||||
req_control_mode.max_charge_power = {11, 3};
|
||||
req_control_mode.min_charge_power = {4, 0};
|
||||
req_control_mode.present_reactive_power = {10, 0};
|
||||
|
||||
req.meter_info_requested = false;
|
||||
|
||||
auto ac_target_power = d20::AcTargetPower{};
|
||||
ac_target_power.target_active_power = {11, 3};
|
||||
auto ac_present_power = d20::AcPresentPower{};
|
||||
ac_present_power.present_active_power = {11, 3};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, false, false, 50, ac_target_power, ac_present_power,
|
||||
d20::UpdateDynamicModeParameters());
|
||||
|
||||
THEN("ResponseCode: OK, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.status.has_value() == false);
|
||||
REQUIRE(res.meter_info.has_value() == false);
|
||||
REQUIRE(res.receipt.has_value() == false);
|
||||
REQUIRE(dt::from_RationalNumber(res.target_frequency.value_or(dt::RationalNumber{0, 0})) == 50.0f);
|
||||
REQUIRE(std::holds_alternative<Dynamic_AC_Res>(res.control_mode));
|
||||
|
||||
const auto& res_control_mode = std::get<Dynamic_AC_Res>(res.control_mode);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.present_active_power.value_or(dt::RationalNumber{0, 0})) ==
|
||||
11000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.target_active_power) == 11000.0f);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - AC_BPT dynamic mode") {
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::AC_BPT, dt::AcConnector::ThreePhase, dt::ControlMode::Dynamic,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing, dt::BptChannel::Unified,
|
||||
dt::GeneratorMode::GridFollowing, 230, dt::GridCodeIslandingDetectionMethod::Passive);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
message_20::AC_ChargeLoopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_control_mode = req.control_mode.emplace<Dynamic_BPT_AC_Req>();
|
||||
req_control_mode.present_active_power = {11, 3};
|
||||
req_control_mode.max_charge_power = {11, 3};
|
||||
req_control_mode.min_charge_power = {4, 0};
|
||||
req_control_mode.present_reactive_power = {10, 0};
|
||||
|
||||
req.meter_info_requested = false;
|
||||
|
||||
auto ac_target_power = d20::AcTargetPower{};
|
||||
ac_target_power.target_active_power = {11, 3};
|
||||
auto ac_present_power = d20::AcPresentPower{};
|
||||
ac_present_power.present_active_power = {11, 3};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, false, false, 50, ac_target_power, ac_present_power,
|
||||
d20::UpdateDynamicModeParameters());
|
||||
|
||||
THEN("ResponseCode: OK, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.status.has_value() == false);
|
||||
REQUIRE(res.meter_info.has_value() == false);
|
||||
REQUIRE(res.receipt.has_value() == false);
|
||||
REQUIRE(dt::from_RationalNumber(res.target_frequency.value_or(dt::RationalNumber{0, 0})) == 50.0f);
|
||||
REQUIRE(std::holds_alternative<Dynamic_BPT_AC_Res>(res.control_mode));
|
||||
|
||||
const auto& res_control_mode = std::get<Dynamic_BPT_AC_Res>(res.control_mode);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.present_active_power.value_or(dt::RationalNumber{0, 0})) ==
|
||||
11000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.target_active_power) == 11000.0f);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - AC dynamic mode, mobility_needs_mode = 2") {
|
||||
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::AC, dt::AcConnector::ThreePhase, dt::ControlMode::Dynamic,
|
||||
dt::MobilityNeedsMode::ProvidedBySecc, dt::Pricing::NoPricing, 230);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
message_20::AC_ChargeLoopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_control_mode = req.control_mode.emplace<Dynamic_AC_Req>();
|
||||
req_control_mode.present_active_power = {11, 3};
|
||||
req_control_mode.max_charge_power = {11, 3};
|
||||
req_control_mode.min_charge_power = {4, 0};
|
||||
req_control_mode.present_reactive_power = {10, 0};
|
||||
|
||||
req.meter_info_requested = false;
|
||||
|
||||
auto ac_target_power = d20::AcTargetPower{};
|
||||
ac_target_power.target_active_power = {11, 3};
|
||||
auto ac_present_power = d20::AcPresentPower{};
|
||||
ac_present_power.present_active_power = {11, 3};
|
||||
|
||||
const d20::UpdateDynamicModeParameters dynamic_parameters = {std::time(nullptr) + 40, std::nullopt, 95};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, false, false, 50, ac_target_power, ac_present_power,
|
||||
dynamic_parameters);
|
||||
|
||||
THEN("ResponseCode: OK, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.status.has_value() == false);
|
||||
REQUIRE(res.meter_info.has_value() == false);
|
||||
REQUIRE(res.receipt.has_value() == false);
|
||||
REQUIRE(dt::from_RationalNumber(res.target_frequency.value_or(dt::RationalNumber{0, 0})) == 50.0f);
|
||||
REQUIRE(std::holds_alternative<Dynamic_AC_Res>(res.control_mode));
|
||||
|
||||
const auto& res_control_mode = std::get<Dynamic_AC_Res>(res.control_mode);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.present_active_power.value_or(dt::RationalNumber{0, 0})) ==
|
||||
11000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.target_active_power) == 11000.0f);
|
||||
|
||||
REQUIRE(res_control_mode.departure_time.value_or(0) >= 39);
|
||||
REQUIRE(res_control_mode.minimum_soc.value_or(0) == 95);
|
||||
REQUIRE(res_control_mode.ack_max_delay.value_or(0) == 30);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - AC_BPT dynamic mode, mobility_needs_mode = 2") {
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::AC_BPT, dt::AcConnector::ThreePhase, dt::ControlMode::Dynamic,
|
||||
dt::MobilityNeedsMode::ProvidedBySecc, dt::Pricing::NoPricing, dt::BptChannel::Unified,
|
||||
dt::GeneratorMode::GridFollowing, 230, dt::GridCodeIslandingDetectionMethod::Passive);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
message_20::AC_ChargeLoopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_control_mode = req.control_mode.emplace<Dynamic_BPT_AC_Req>();
|
||||
req_control_mode.present_active_power = {11, 3};
|
||||
req_control_mode.max_charge_power = {11, 3};
|
||||
req_control_mode.min_charge_power = {4, 0};
|
||||
req_control_mode.present_reactive_power = {10, 0};
|
||||
|
||||
req.meter_info_requested = false;
|
||||
|
||||
auto ac_target_power = d20::AcTargetPower{};
|
||||
ac_target_power.target_active_power = {11, 3};
|
||||
auto ac_present_power = d20::AcPresentPower{};
|
||||
ac_present_power.present_active_power = {11, 3};
|
||||
|
||||
const d20::UpdateDynamicModeParameters dynamic_parameters = {std::time(nullptr) + 40, std::nullopt, 95};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, false, false, 50, ac_target_power, ac_present_power,
|
||||
dynamic_parameters);
|
||||
|
||||
THEN("ResponseCode: OK, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.status.has_value() == false);
|
||||
REQUIRE(res.meter_info.has_value() == false);
|
||||
REQUIRE(res.receipt.has_value() == false);
|
||||
REQUIRE(dt::from_RationalNumber(res.target_frequency.value_or(dt::RationalNumber{0, 0})) == 50.0f);
|
||||
REQUIRE(std::holds_alternative<Dynamic_BPT_AC_Res>(res.control_mode));
|
||||
|
||||
const auto& res_control_mode = std::get<Dynamic_BPT_AC_Res>(res.control_mode);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.present_active_power.value_or(dt::RationalNumber{0, 0})) ==
|
||||
11000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.target_active_power) == 11000.0f);
|
||||
|
||||
REQUIRE(res_control_mode.departure_time.value_or(0) >= 39);
|
||||
REQUIRE(res_control_mode.minimum_soc.value_or(0) == 95);
|
||||
REQUIRE(res_control_mode.ack_max_delay.value_or(0) == 30);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - AC dynamic mode & pause from charger") {
|
||||
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::AC, dt::AcConnector::ThreePhase, dt::ControlMode::Dynamic,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing, 230);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
message_20::AC_ChargeLoopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_control_mode = req.control_mode.emplace<Dynamic_AC_Req>();
|
||||
req_control_mode.present_active_power = {11, 3};
|
||||
req_control_mode.max_charge_power = {11, 3};
|
||||
req_control_mode.min_charge_power = {4, 0};
|
||||
req_control_mode.present_reactive_power = {10, 0};
|
||||
|
||||
req.meter_info_requested = false;
|
||||
|
||||
auto ac_target_power = d20::AcTargetPower{};
|
||||
ac_target_power.target_active_power = {11, 3};
|
||||
auto ac_present_power = d20::AcPresentPower{};
|
||||
ac_present_power.present_active_power = {11, 3};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, false, true, 50, ac_target_power, ac_present_power,
|
||||
d20::UpdateDynamicModeParameters());
|
||||
|
||||
THEN("ResponseCode: OK, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.meter_info.has_value() == false);
|
||||
REQUIRE(res.receipt.has_value() == false);
|
||||
REQUIRE(dt::from_RationalNumber(res.target_frequency.value_or(dt::RationalNumber{0, 0})) == 50.0f);
|
||||
REQUIRE(std::holds_alternative<Dynamic_AC_Res>(res.control_mode));
|
||||
|
||||
const auto& res_control_mode = std::get<Dynamic_AC_Res>(res.control_mode);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.present_active_power.value_or(dt::RationalNumber{0, 0})) ==
|
||||
11000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.target_active_power) == 11000.0f);
|
||||
|
||||
REQUIRE(res.status.has_value() == true);
|
||||
REQUIRE(res.status.value().notification == dt::EvseNotification::Pause);
|
||||
REQUIRE(res.status.value().notification_max_delay == 60);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/detail/d20/state/ac_charge_parameter_discovery.hpp>
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
namespace dt = message_20::datatypes;
|
||||
|
||||
using AC_ModeReq = dt::AC_CPDReqEnergyTransferMode;
|
||||
using BPT_AC_ModeReq = dt::BPT_AC_CPDReqEnergyTransferMode;
|
||||
|
||||
using AC_ModeRes = dt::AC_CPDResEnergyTransferMode;
|
||||
using BPT_AC_ModeRes = dt::BPT_AC_CPDResEnergyTransferMode;
|
||||
|
||||
SCENARIO("AC charge parameter discovery state handling") {
|
||||
GIVEN("Bad Case - Unknown session") {
|
||||
|
||||
auto session = d20::Session();
|
||||
|
||||
const auto evse_id = std::string("everest se");
|
||||
const std::vector<dt::ServiceCategory> supported_energy_services = {dt::ServiceCategory::AC};
|
||||
const auto cert_install{false};
|
||||
const std::vector<dt::Authorization> auth_services = {dt::Authorization::EIM};
|
||||
const std::vector<uint16_t> vas_services{};
|
||||
const d20::DcTransferLimits dc_limits;
|
||||
const d20::AcTransferLimits ac_limits;
|
||||
const d20::DcTransferLimits powersupply_limits;
|
||||
const std::vector<d20::ControlMobilityNeedsModes> control_mobility_modes = {
|
||||
{dt::ControlMode::Scheduled, dt::MobilityNeedsMode::ProvidedByEvcc}};
|
||||
|
||||
const d20::EvseSetupConfig evse_setup{
|
||||
evse_id, supported_energy_services, auth_services, vas_services, cert_install, dc_limits,
|
||||
ac_limits, control_mobility_modes, std::nullopt, std::nullopt, std::nullopt, powersupply_limits};
|
||||
|
||||
message_20::AC_ChargeParameterDiscoveryRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_out = req.transfer_mode.emplace<AC_ModeReq>();
|
||||
req_out.max_charge_power = {11, 3};
|
||||
req_out.min_charge_power = {23, 2};
|
||||
|
||||
const auto res = d20::state::handle_request(req, d20::Session(), d20::SessionConfig(evse_setup).ac_limits,
|
||||
iso15118::d20::AcPresentPower{});
|
||||
|
||||
THEN("ResponseCode: FAILED_UnknownSession, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_UnknownSession);
|
||||
|
||||
REQUIRE(std::holds_alternative<AC_ModeRes>(res.transfer_mode));
|
||||
const auto& transfer_mode = std::get<AC_ModeRes>(res.transfer_mode);
|
||||
REQUIRE(dt::from_RationalNumber(transfer_mode.max_charge_power) == 0.0f);
|
||||
REQUIRE(dt::from_RationalNumber(transfer_mode.min_charge_power) == 0.0f);
|
||||
REQUIRE(dt::from_RationalNumber(transfer_mode.nominal_frequency) == 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Bad Case: e.g. ac transfer mod instead of ac_bpt transfer mod - FAILED_WrongChargeParameter") {
|
||||
|
||||
const auto service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::AC_BPT, dt::AcConnector::ThreePhase, dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing, dt::BptChannel::Unified,
|
||||
dt::GeneratorMode::GridFollowing, 230, dt::GridCodeIslandingDetectionMethod::Passive);
|
||||
|
||||
const auto evse_id = std::string("everest se");
|
||||
const std::vector<dt::ServiceCategory> supported_energy_services = {dt::ServiceCategory::AC};
|
||||
const auto cert_install{false};
|
||||
const std::vector<dt::Authorization> auth_services = {dt::Authorization::EIM};
|
||||
const std::vector<uint16_t> vas_services{};
|
||||
const d20::DcTransferLimits dc_limits;
|
||||
const d20::AcTransferLimits ac_limits;
|
||||
const d20::DcTransferLimits powersupply_limits;
|
||||
const std::vector<d20::ControlMobilityNeedsModes> control_mobility_modes = {
|
||||
{dt::ControlMode::Scheduled, dt::MobilityNeedsMode::ProvidedByEvcc}};
|
||||
|
||||
const d20::EvseSetupConfig evse_setup{
|
||||
evse_id, supported_energy_services, auth_services, vas_services, cert_install, dc_limits,
|
||||
ac_limits, control_mobility_modes, std::nullopt, std::nullopt, std::nullopt, powersupply_limits};
|
||||
|
||||
auto session = d20::Session(service_parameters);
|
||||
|
||||
message_20::AC_ChargeParameterDiscoveryRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_out = req.transfer_mode.emplace<AC_ModeReq>();
|
||||
req_out.max_charge_power = {11, 3};
|
||||
req_out.min_charge_power = {23, 2};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, d20::SessionConfig(evse_setup).ac_limits,
|
||||
iso15118::d20::AcPresentPower{});
|
||||
|
||||
THEN("ResponseCode: FAILED_WrongChargeParameter, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_WrongChargeParameter);
|
||||
|
||||
REQUIRE(std::holds_alternative<AC_ModeRes>(res.transfer_mode));
|
||||
const auto& transfer_mode = std::get<AC_ModeRes>(res.transfer_mode);
|
||||
REQUIRE(dt::from_RationalNumber(transfer_mode.max_charge_power) == 0.0f);
|
||||
REQUIRE(dt::from_RationalNumber(transfer_mode.min_charge_power) == 0.0f);
|
||||
REQUIRE(dt::from_RationalNumber(transfer_mode.nominal_frequency) == 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Bad Case: e.g. AC_BPT transfer mod instead of ac transfer mod - FAILED_WrongChargeParameter") {
|
||||
|
||||
const auto service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::AC, dt::AcConnector::ThreePhase, dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing, 230);
|
||||
|
||||
const auto evse_id = std::string("everest se");
|
||||
const std::vector<dt::ServiceCategory> supported_energy_services = {dt::ServiceCategory::AC};
|
||||
const auto cert_install{false};
|
||||
const std::vector<dt::Authorization> auth_services = {dt::Authorization::EIM};
|
||||
const std::vector<uint16_t> vas_services{};
|
||||
const d20::DcTransferLimits dc_limits;
|
||||
const d20::AcTransferLimits ac_limits;
|
||||
const d20::DcTransferLimits powersupply_limits;
|
||||
const std::vector<d20::ControlMobilityNeedsModes> control_mobility_modes = {
|
||||
{dt::ControlMode::Scheduled, dt::MobilityNeedsMode::ProvidedByEvcc}};
|
||||
|
||||
const d20::EvseSetupConfig evse_setup{
|
||||
evse_id, supported_energy_services, auth_services, vas_services, cert_install, dc_limits,
|
||||
ac_limits, control_mobility_modes, std::nullopt, std::nullopt, std::nullopt, powersupply_limits};
|
||||
|
||||
auto session = d20::Session(service_parameters);
|
||||
|
||||
message_20::AC_ChargeParameterDiscoveryRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_out = req.transfer_mode.emplace<BPT_AC_ModeReq>();
|
||||
req_out.max_charge_power = {11, 3};
|
||||
req_out.min_charge_power = {23, 2};
|
||||
req_out.max_discharge_power = {11, 3};
|
||||
req_out.min_discharge_power = {23, 2};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, d20::SessionConfig(evse_setup).ac_limits,
|
||||
iso15118::d20::AcPresentPower{});
|
||||
|
||||
THEN("ResponseCode: FAILED_WrongChargeParameter, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_WrongChargeParameter);
|
||||
|
||||
REQUIRE(std::holds_alternative<AC_ModeRes>(res.transfer_mode));
|
||||
const auto& transfer_mode = std::get<AC_ModeRes>(res.transfer_mode);
|
||||
REQUIRE(dt::from_RationalNumber(transfer_mode.max_charge_power) == 0.0f);
|
||||
REQUIRE(dt::from_RationalNumber(transfer_mode.min_charge_power) == 0.0f);
|
||||
REQUIRE(dt::from_RationalNumber(transfer_mode.nominal_frequency) == 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good Case: AC") {
|
||||
|
||||
const auto service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::AC, dt::AcConnector::ThreePhase, dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing, 230);
|
||||
|
||||
auto session = d20::Session(service_parameters);
|
||||
|
||||
const auto evse_id = std::string("everest se");
|
||||
const std::vector<dt::ServiceCategory> supported_energy_services = {dt::ServiceCategory::AC};
|
||||
const auto cert_install{false};
|
||||
const std::vector<dt::Authorization> auth_services = {dt::Authorization::EIM};
|
||||
const std::vector<uint16_t> vas_services{};
|
||||
const d20::DcTransferLimits dc_limits;
|
||||
const d20::AcTransferLimits ac_limits;
|
||||
const d20::DcTransferLimits powersupply_limits;
|
||||
const std::vector<d20::ControlMobilityNeedsModes> control_mobility_modes = {
|
||||
{dt::ControlMode::Scheduled, dt::MobilityNeedsMode::ProvidedByEvcc}};
|
||||
|
||||
const d20::EvseSetupConfig evse_setup{
|
||||
evse_id, supported_energy_services, auth_services, vas_services, cert_install, dc_limits,
|
||||
ac_limits, control_mobility_modes, std::nullopt, std::nullopt, std::nullopt, powersupply_limits};
|
||||
|
||||
d20::SessionConfig config = d20::SessionConfig(evse_setup);
|
||||
|
||||
float max_charge_power = 11000.0f;
|
||||
float min_charge_power = 11000.0f;
|
||||
float nominal_frequency = 50.0f;
|
||||
float power_ramp_limitation = 2.0f;
|
||||
|
||||
config.ac_limits.charge_power.max = dt::from_float(max_charge_power);
|
||||
config.ac_limits.charge_power.min = dt::from_float(min_charge_power);
|
||||
config.ac_limits.nominal_frequency = dt::from_float(nominal_frequency);
|
||||
config.ac_limits.power_ramp_limitation = dt::from_float(power_ramp_limitation);
|
||||
|
||||
message_20::AC_ChargeParameterDiscoveryRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_out = req.transfer_mode.emplace<AC_ModeReq>();
|
||||
req_out.max_charge_power = {11, 3};
|
||||
req_out.min_charge_power = {23, 2};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, config.ac_limits, iso15118::d20::AcPresentPower{});
|
||||
|
||||
THEN("ResponseCode: OK") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
|
||||
REQUIRE(std::holds_alternative<AC_ModeRes>(res.transfer_mode));
|
||||
const auto& transfer_mode = std::get<AC_ModeRes>(res.transfer_mode);
|
||||
|
||||
REQUIRE(dt::from_RationalNumber(transfer_mode.max_charge_power) == max_charge_power);
|
||||
REQUIRE(dt::from_RationalNumber(transfer_mode.min_charge_power) == min_charge_power);
|
||||
REQUIRE(dt::from_RationalNumber(transfer_mode.nominal_frequency) == nominal_frequency);
|
||||
REQUIRE(transfer_mode.power_ramp_limitation.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(transfer_mode.power_ramp_limitation.value()) == power_ramp_limitation);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good Case: AC_BPT") {
|
||||
|
||||
const auto service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::AC_BPT, dt::AcConnector::ThreePhase, dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing, dt::BptChannel::Unified,
|
||||
dt::GeneratorMode::GridFollowing, 230, dt::GridCodeIslandingDetectionMethod::Passive);
|
||||
|
||||
auto session = d20::Session(service_parameters);
|
||||
|
||||
const auto evse_id = std::string("everest se");
|
||||
const std::vector<dt::ServiceCategory> supported_energy_services = {dt::ServiceCategory::AC};
|
||||
const auto cert_install{false};
|
||||
const std::vector<dt::Authorization> auth_services = {dt::Authorization::EIM};
|
||||
const std::vector<uint16_t> vas_services{};
|
||||
const d20::DcTransferLimits dc_limits;
|
||||
const d20::AcTransferLimits ac_limits;
|
||||
const d20::DcTransferLimits powersupply_limits;
|
||||
const std::vector<d20::ControlMobilityNeedsModes> control_mobility_modes = {
|
||||
{dt::ControlMode::Scheduled, dt::MobilityNeedsMode::ProvidedByEvcc}};
|
||||
|
||||
const d20::EvseSetupConfig evse_setup{
|
||||
evse_id, supported_energy_services, auth_services, vas_services, cert_install, dc_limits,
|
||||
ac_limits, control_mobility_modes, std::nullopt, std::nullopt, std::nullopt, powersupply_limits};
|
||||
|
||||
auto config = d20::SessionConfig(evse_setup);
|
||||
|
||||
float max_charge_power = 11000.0f;
|
||||
float min_charge_power = 11000.0f;
|
||||
float nominal_frequency = 50.0f;
|
||||
float power_ramp_limitation = 2.0f;
|
||||
float max_discharge_power = 6000.0f;
|
||||
float min_discharge_power = 3000.0f;
|
||||
|
||||
config.ac_limits.charge_power.max = dt::from_float(max_charge_power);
|
||||
config.ac_limits.charge_power.min = dt::from_float(min_charge_power);
|
||||
config.ac_limits.nominal_frequency = dt::from_float(nominal_frequency);
|
||||
config.ac_limits.power_ramp_limitation = dt::from_float(power_ramp_limitation);
|
||||
|
||||
d20::Limit<dt::RationalNumber> discharge_power;
|
||||
discharge_power.max = dt::from_float(max_discharge_power);
|
||||
discharge_power.min = dt::from_float(min_discharge_power);
|
||||
config.ac_limits.discharge_power = discharge_power;
|
||||
|
||||
message_20::AC_ChargeParameterDiscoveryRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_out = req.transfer_mode.emplace<BPT_AC_ModeReq>();
|
||||
req_out.max_charge_power = {11, 3};
|
||||
req_out.min_charge_power = {23, 2};
|
||||
req_out.max_discharge_power = {11, 3};
|
||||
req_out.min_discharge_power = {23, 2};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, config.ac_limits, iso15118::d20::AcPresentPower{});
|
||||
|
||||
THEN("ResponseCode: OK") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
|
||||
REQUIRE(std::holds_alternative<BPT_AC_ModeRes>(res.transfer_mode));
|
||||
const auto& transfer_mode = std::get<BPT_AC_ModeRes>(res.transfer_mode);
|
||||
|
||||
REQUIRE(dt::from_RationalNumber(transfer_mode.max_charge_power) == max_charge_power);
|
||||
REQUIRE(dt::from_RationalNumber(transfer_mode.min_charge_power) == min_charge_power);
|
||||
REQUIRE(dt::from_RationalNumber(transfer_mode.nominal_frequency) == nominal_frequency);
|
||||
REQUIRE(transfer_mode.power_ramp_limitation.has_value() == true);
|
||||
REQUIRE(dt::from_RationalNumber(transfer_mode.power_ramp_limitation.value()) == power_ramp_limitation);
|
||||
REQUIRE(dt::from_RationalNumber(transfer_mode.max_discharge_power) == max_discharge_power);
|
||||
REQUIRE(dt::from_RationalNumber(transfer_mode.min_discharge_power) == min_discharge_power);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Bad Case: EV Parameter does not fit in evse parameters - FAILED_WrongChargeParameter") {
|
||||
}
|
||||
|
||||
// GIVEN("Bad Case - sequence error") {} // todo(sl): not here
|
||||
|
||||
// GIVEN("Bad Case - Performance Timeout") {} // todo(sl): not here
|
||||
|
||||
// GIVEN("Bad Case - Sequence Timeout") {} // todo(sl): not here
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/detail/d20/state/authorization.hpp>
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
namespace dt = message_20::datatypes;
|
||||
|
||||
using AuthStatus = message_20::datatypes::AuthStatus;
|
||||
|
||||
SCENARIO("Authorization state handling") {
|
||||
|
||||
GIVEN("Bad Case - Unknown session") {
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
message_20::AuthorizationRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
req.selected_authorization_service = dt::Authorization::EIM;
|
||||
req.authorization_mode.emplace<dt::EIM_ASReqAuthorizationMode>();
|
||||
|
||||
const auto res = d20::state::handle_request(req, d20::Session(), AuthStatus::Pending, false);
|
||||
|
||||
THEN("ResponseCode: FAILED_UnknownSession, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_UnknownSession);
|
||||
REQUIRE(res.evse_processing == dt::Processing::Finished);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Warning - Authorization selection is invalid") {
|
||||
|
||||
d20::Session session = d20::Session();
|
||||
session.offered_services.auth_services = {dt::Authorization::PnC};
|
||||
|
||||
message_20::AuthorizationRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
req.selected_authorization_service = dt::Authorization::EIM;
|
||||
req.authorization_mode.emplace<dt::EIM_ASReqAuthorizationMode>();
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, AuthStatus::Pending, false);
|
||||
|
||||
THEN("ResponseCode: FAILED_UnknownSession, EvseProcessing: Finished") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::WARNING_AuthorizationSelectionInvalid);
|
||||
REQUIRE(res.evse_processing == dt::Processing::Finished);
|
||||
}
|
||||
}
|
||||
|
||||
// EIM test cases
|
||||
|
||||
GIVEN("Warning - EIM Authorization Failure") { // [V2G20-2219]
|
||||
|
||||
d20::Session session = d20::Session();
|
||||
session.offered_services.auth_services = {dt::Authorization::EIM, dt::Authorization::PnC};
|
||||
|
||||
message_20::AuthorizationRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
req.selected_authorization_service = dt::Authorization::EIM;
|
||||
req.authorization_mode.emplace<dt::EIM_ASReqAuthorizationMode>();
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, AuthStatus::Rejected, false);
|
||||
|
||||
THEN("ResponseCode: WARNING_EIMAuthorizationFailure, EvseProcessing: Finished") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::WARNING_EIMAuthorizationFailure);
|
||||
REQUIRE(res.evse_processing == dt::Processing::Finished);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - EIM waiting for authorization") {
|
||||
|
||||
d20::Session session = d20::Session();
|
||||
session.offered_services.auth_services = {dt::Authorization::EIM, dt::Authorization::PnC};
|
||||
|
||||
message_20::AuthorizationRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
req.selected_authorization_service = dt::Authorization::EIM;
|
||||
req.authorization_mode.emplace<dt::EIM_ASReqAuthorizationMode>();
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, AuthStatus::Pending, false);
|
||||
|
||||
THEN("ResponseCode: Ok, EvseProcessing: Ongoing") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.evse_processing == dt::Processing::Ongoing);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - EIM authorized") {
|
||||
|
||||
d20::Session session = d20::Session();
|
||||
session.offered_services.auth_services = {dt::Authorization::EIM, dt::Authorization::PnC};
|
||||
|
||||
message_20::AuthorizationRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
req.selected_authorization_service = dt::Authorization::EIM;
|
||||
req.authorization_mode.emplace<dt::EIM_ASReqAuthorizationMode>();
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, AuthStatus::Accepted, false);
|
||||
|
||||
THEN("ResponseCode: Ok, EvseProcessing: Finished") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.evse_processing == dt::Processing::Finished);
|
||||
}
|
||||
}
|
||||
|
||||
// GIVEN("Bad Case - Ongoing timeout reached") {}
|
||||
|
||||
// PnC test cases
|
||||
|
||||
// GIVEN("Bad Case - sequence error") {} // TODO(sl): not here
|
||||
|
||||
// GIVEN("Performance Timeout") {} // TODO(sl): not here
|
||||
|
||||
// GIVEN("Sequence Timeout") {} // TODO(sl): not here
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/detail/d20/state/authorization_setup.hpp>
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
namespace dt = message_20::datatypes;
|
||||
|
||||
SCENARIO("Authorization setup state handling") {
|
||||
|
||||
GIVEN("Bad Case - Unknown session") {
|
||||
auto session = d20::Session();
|
||||
|
||||
message_20::AuthorizationSetupRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
session = d20::Session();
|
||||
|
||||
const auto cert_install_service = false;
|
||||
const std::vector<dt::Authorization> authorization_services = {dt::Authorization::EIM};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, cert_install_service, authorization_services);
|
||||
|
||||
THEN("ResponseCode: FAILED_UnknownSession, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_UnknownSession);
|
||||
REQUIRE(res.certificate_installation_service == false);
|
||||
REQUIRE(res.authorization_services.size() == 1);
|
||||
REQUIRE(res.authorization_services[0] == dt::Authorization::EIM);
|
||||
REQUIRE(std::holds_alternative<dt::EIM_ASResAuthorizationMode>(res.authorization_mode));
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good Case - EIM only , cert_install_service not provided") {
|
||||
|
||||
auto session = d20::Session();
|
||||
|
||||
message_20::AuthorizationSetupRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
const auto cert_install_service = false;
|
||||
const std::vector<dt::Authorization> authorization_services = {dt::Authorization::EIM};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, cert_install_service, authorization_services);
|
||||
|
||||
THEN("ResponseCode: Ok, cert_install = false, authorization_servie = EIM") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.certificate_installation_service == false);
|
||||
REQUIRE(res.authorization_services.size() == 1);
|
||||
REQUIRE(res.authorization_services[0] == dt::Authorization::EIM);
|
||||
REQUIRE(std::holds_alternative<dt::EIM_ASResAuthorizationMode>(res.authorization_mode));
|
||||
REQUIRE(session.offered_services.auth_services[0] == dt::Authorization::EIM);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good Case - PnC only, cert_install_service not provided") {
|
||||
|
||||
auto session = d20::Session();
|
||||
|
||||
message_20::AuthorizationSetupRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
const auto cert_install_service = false;
|
||||
const std::vector<dt::Authorization> authorization_services = {dt::Authorization::PnC};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, cert_install_service, authorization_services);
|
||||
|
||||
THEN("ResponseCode: Ok, cert_install = false, authorization_servie = PnC, authorization_mode = PnC_Mode") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.certificate_installation_service == false);
|
||||
REQUIRE(res.authorization_services.size() == 1);
|
||||
REQUIRE(res.authorization_services[0] == dt::Authorization::PnC);
|
||||
REQUIRE(std::holds_alternative<dt::PnC_ASResAuthorizationMode>(res.authorization_mode));
|
||||
const auto& auth_mode = std::get<dt::PnC_ASResAuthorizationMode>(res.authorization_mode);
|
||||
REQUIRE(auth_mode.gen_challenge.empty() == false);
|
||||
REQUIRE(session.offered_services.auth_services[0] == dt::Authorization::PnC);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good Case - EIM + PnC, cert_install_service provided") {
|
||||
|
||||
auto session = d20::Session();
|
||||
|
||||
message_20::AuthorizationSetupRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
const auto cert_install_service = true;
|
||||
const std::vector<dt::Authorization> authorization_services = {dt::Authorization::PnC, dt::Authorization::EIM};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, cert_install_service, authorization_services);
|
||||
|
||||
THEN("ResponseCode: Ok, cert_install = true, authorization_servie = EIM & PnC, authorization_mode = PnC_Mode") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.certificate_installation_service == true);
|
||||
REQUIRE(res.authorization_services.size() == 2);
|
||||
REQUIRE((res.authorization_services[0] == dt::Authorization::EIM ||
|
||||
res.authorization_services[0] == dt::Authorization::PnC));
|
||||
REQUIRE((res.authorization_services[1] == dt::Authorization::EIM ||
|
||||
res.authorization_services[1] == dt::Authorization::PnC));
|
||||
REQUIRE(std::holds_alternative<dt::PnC_ASResAuthorizationMode>(res.authorization_mode));
|
||||
const auto& auth_mode = std::get<dt::PnC_ASResAuthorizationMode>(res.authorization_mode);
|
||||
REQUIRE(auth_mode.gen_challenge.empty() == false);
|
||||
|
||||
REQUIRE((session.offered_services.auth_services[0] == dt::Authorization::EIM ||
|
||||
session.offered_services.auth_services[0] == dt::Authorization::PnC));
|
||||
REQUIRE((session.offered_services.auth_services[1] == dt::Authorization::EIM ||
|
||||
session.offered_services.auth_services[1] == dt::Authorization::PnC));
|
||||
}
|
||||
}
|
||||
|
||||
// GIVEN("Bad Case - sequence error") {} // TODO(sl): not here
|
||||
|
||||
// GIVEN("Performance Timeout") {} // TODO(sl): not here
|
||||
|
||||
// GIVEN("Sequence Timeout") {} // TODO(sl): not here
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/detail/d20/state/dc_cable_check.hpp>
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
namespace dt = message_20::datatypes;
|
||||
|
||||
SCENARIO("DC cable check state handling") {
|
||||
GIVEN("Bad case - Unknown session") {
|
||||
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
message_20::DC_CableCheckRequest req;
|
||||
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
const auto res = d20::state::handle_request(req, d20::Session(), false);
|
||||
|
||||
THEN("ResponseCode: FAILED_UnknownSession, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_UnknownSession);
|
||||
REQUIRE(res.processing == dt::Processing::Ongoing);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - ongoing ") {
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
message_20::DC_CableCheckRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, false);
|
||||
|
||||
THEN("ResponseCode: OK, processing: Ongoing") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.processing == dt::Processing::Ongoing);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - finished ") {
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
message_20::DC_CableCheckRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, true);
|
||||
|
||||
THEN("ResponseCode: OK, processing: Finished") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.processing == dt::Processing::Finished);
|
||||
}
|
||||
}
|
||||
|
||||
// GIVEN("Bad Case - sequence error") {} // TODO(sl): not here
|
||||
|
||||
// GIVEN("Bad Case - Performance Timeout") {} // TODO(sl): not here
|
||||
|
||||
// GIVEN("Bad Case - Sequence Timeout") {} // TODO(sl): not here
|
||||
}
|
||||
@@ -0,0 +1,798 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/detail/d20/state/dc_charge_loop.hpp>
|
||||
|
||||
#include <iso15118/d20/config.hpp>
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
namespace dt = message_20::datatypes;
|
||||
|
||||
using Scheduled_DC_Req = message_20::datatypes::Scheduled_DC_CLReqControlMode;
|
||||
using Scheduled_BPT_DC_Req = message_20::datatypes::BPT_Scheduled_DC_CLReqControlMode;
|
||||
using Dynamic_DC_Req = message_20::datatypes::Dynamic_DC_CLReqControlMode;
|
||||
using Dynamic_BPT_DC_Req = message_20::datatypes::BPT_Dynamic_DC_CLReqControlMode;
|
||||
|
||||
using Scheduled_DC_Res = message_20::datatypes::Scheduled_DC_CLResControlMode;
|
||||
using Scheduled_BPT_DC_Res = message_20::datatypes::BPT_Scheduled_DC_CLResControlMode;
|
||||
using Dynamic_DC_Res = message_20::datatypes::Dynamic_DC_CLResControlMode;
|
||||
using Dynamic_BPT_DC_Res = message_20::datatypes::BPT_Dynamic_DC_CLResControlMode;
|
||||
|
||||
SCENARIO("DC charge loop state handling") {
|
||||
|
||||
const auto evse_id = std::string("everest se");
|
||||
const std::vector<dt::ServiceCategory> supported_energy_services = {dt::ServiceCategory::DC,
|
||||
dt::ServiceCategory::DC_BPT};
|
||||
const auto cert_install{false};
|
||||
const std::vector<dt::Authorization> auth_services = {dt::Authorization::EIM};
|
||||
const std::vector<uint16_t> vas_services{};
|
||||
|
||||
d20::DcTransferLimits dc_limits;
|
||||
d20::AcTransferLimits ac_limits;
|
||||
d20::DcTransferLimits powersupply_limits;
|
||||
dc_limits.charge_limits.power.max = {22, 3};
|
||||
dc_limits.charge_limits.power.min = {10, 0};
|
||||
dc_limits.charge_limits.current.max = {250, 0};
|
||||
dc_limits.voltage.max = {900, 0};
|
||||
auto& discharge_limits = dc_limits.discharge_limits.emplace<>();
|
||||
discharge_limits.power.max = {11, 3};
|
||||
discharge_limits.power.min = {10, 0};
|
||||
discharge_limits.current.max = {30, 0};
|
||||
|
||||
const std::vector<d20::ControlMobilityNeedsModes> control_mobility_modes = {
|
||||
{dt::ControlMode::Scheduled, dt::MobilityNeedsMode::ProvidedByEvcc},
|
||||
{dt::ControlMode::Dynamic, dt::MobilityNeedsMode::ProvidedByEvcc},
|
||||
{dt::ControlMode::Dynamic, dt::MobilityNeedsMode::ProvidedBySecc}};
|
||||
|
||||
const d20::EvseSetupConfig evse_setup{
|
||||
evse_id, supported_energy_services, auth_services, vas_services, cert_install, dc_limits,
|
||||
ac_limits, control_mobility_modes, std::nullopt, std::nullopt, std::nullopt, powersupply_limits};
|
||||
|
||||
GIVEN("Bad case - Unknown session") {
|
||||
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
message_20::DC_ChargeLoopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_control_mode = req.control_mode.emplace<Scheduled_DC_Req>();
|
||||
req_control_mode.target_current = {40, 0};
|
||||
req_control_mode.target_voltage = {400, 0};
|
||||
|
||||
req.meter_info_requested = false;
|
||||
req.present_voltage = {330, 0};
|
||||
|
||||
const auto res = d20::state::handle_request(req, d20::Session(), 330, 30, false, false, evse_setup.dc_limits,
|
||||
d20::UpdateDynamicModeParameters());
|
||||
|
||||
THEN("ResponseCode: FAILED_UnknownSession, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_UnknownSession);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_current) == 0.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_voltage) == 0.0f);
|
||||
REQUIRE(res.current_limit_achieved == false);
|
||||
REQUIRE(res.power_limit_achieved == false);
|
||||
REQUIRE(res.voltage_limit_achieved == false);
|
||||
REQUIRE(std::holds_alternative<Scheduled_DC_Res>(res.control_mode));
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Bad case - false energy mode") {
|
||||
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::DC_BPT, dt::DcConnector::Extended, dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing, dt::BptChannel::Unified,
|
||||
dt::GeneratorMode::GridFollowing);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
|
||||
message_20::DC_ChargeLoopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_control_mode = req.control_mode.emplace<Scheduled_DC_Req>();
|
||||
req_control_mode.target_current = {40, 0};
|
||||
req_control_mode.target_voltage = {400, 0};
|
||||
|
||||
req.meter_info_requested = false;
|
||||
req.present_voltage = {330, 0};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, 330, 30, false, false, evse_setup.dc_limits,
|
||||
d20::UpdateDynamicModeParameters());
|
||||
|
||||
THEN("ResponseCode: FAILED, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_current) == 0.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_voltage) == 0.0f);
|
||||
REQUIRE(res.current_limit_achieved == false);
|
||||
REQUIRE(res.power_limit_achieved == false);
|
||||
REQUIRE(res.voltage_limit_achieved == false);
|
||||
REQUIRE(std::holds_alternative<Scheduled_DC_Res>(res.control_mode));
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Bad case - false control mode") {
|
||||
|
||||
d20::SelectedServiceParameters service_parameters =
|
||||
d20::SelectedServiceParameters(dt::ServiceCategory::DC, dt::DcConnector::Extended, dt::ControlMode::Dynamic,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
|
||||
message_20::DC_ChargeLoopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_control_mode = req.control_mode.emplace<Scheduled_DC_Req>();
|
||||
req_control_mode.target_current = {40, 0};
|
||||
req_control_mode.target_voltage = {400, 0};
|
||||
|
||||
req.meter_info_requested = false;
|
||||
req.present_voltage = {330, 0};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, 330, 30, false, false, evse_setup.dc_limits,
|
||||
d20::UpdateDynamicModeParameters());
|
||||
|
||||
THEN("ResponseCode: FAILED, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_current) == 0.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_voltage) == 0.0f);
|
||||
REQUIRE(res.current_limit_achieved == false);
|
||||
REQUIRE(res.power_limit_achieved == false);
|
||||
REQUIRE(res.voltage_limit_achieved == false);
|
||||
REQUIRE(std::holds_alternative<Scheduled_DC_Res>(res.control_mode));
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - DC scheduled mode") {
|
||||
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::DC, dt::DcConnector::Extended, dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
|
||||
message_20::DC_ChargeLoopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_control_mode = req.control_mode.emplace<Scheduled_DC_Req>();
|
||||
req_control_mode.target_current = {40, 0};
|
||||
req_control_mode.target_voltage = {400, 0};
|
||||
|
||||
req.meter_info_requested = false;
|
||||
req.present_voltage = {330, 0};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, 330, 30, false, false, evse_setup.dc_limits,
|
||||
d20::UpdateDynamicModeParameters());
|
||||
|
||||
THEN("ResponseCode: OK, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_current) == 30.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_voltage) == 330.0f);
|
||||
REQUIRE(res.current_limit_achieved == false);
|
||||
REQUIRE(res.power_limit_achieved == false);
|
||||
REQUIRE(res.voltage_limit_achieved == false);
|
||||
|
||||
REQUIRE(std::holds_alternative<Scheduled_DC_Res>(res.control_mode));
|
||||
const auto& res_control_mode = std::get<Scheduled_DC_Res>(res.control_mode);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_charge_power.value_or(dt::RationalNumber{0, 0})) ==
|
||||
22000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.min_charge_power.value_or(dt::RationalNumber{0, 0})) ==
|
||||
10.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_charge_current.value_or(dt::RationalNumber{0, 0})) ==
|
||||
250.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_voltage.value_or(dt::RationalNumber{0, 0})) == 900.0f);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - DC_BPT scheduled mode") {
|
||||
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::DC_BPT, dt::DcConnector::Extended, dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
|
||||
message_20::DC_ChargeLoopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_control_mode = req.control_mode.emplace<Scheduled_BPT_DC_Req>();
|
||||
req_control_mode.target_current = {40, 0};
|
||||
req_control_mode.target_voltage = {400, 0};
|
||||
req_control_mode.max_discharge_power.emplace<dt::RationalNumber>({11, 3});
|
||||
|
||||
req.meter_info_requested = false;
|
||||
req.present_voltage = {330, 0};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, 330, 30, false, false, evse_setup.dc_limits,
|
||||
d20::UpdateDynamicModeParameters());
|
||||
|
||||
THEN("ResponseCode: OK, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_current) == 30.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_voltage) == 330.0f);
|
||||
REQUIRE(res.current_limit_achieved == false);
|
||||
REQUIRE(res.power_limit_achieved == false);
|
||||
REQUIRE(res.voltage_limit_achieved == false);
|
||||
|
||||
REQUIRE(std::holds_alternative<Scheduled_BPT_DC_Res>(res.control_mode));
|
||||
const auto& res_control_mode = std::get<Scheduled_BPT_DC_Res>(res.control_mode);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_charge_power.value_or(dt::RationalNumber{0, 0})) ==
|
||||
22000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.min_charge_power.value_or(dt::RationalNumber{0, 0})) ==
|
||||
10.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_charge_current.value_or(dt::RationalNumber{0, 0})) ==
|
||||
250.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_voltage.value_or(dt::RationalNumber{0, 0})) == 900.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_discharge_power.value_or(dt::RationalNumber{0, 0})) ==
|
||||
11000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.min_discharge_power.value_or(dt::RationalNumber{0, 0})) ==
|
||||
10.0f);
|
||||
REQUIRE(dt::from_RationalNumber(
|
||||
res_control_mode.max_discharge_current.value_or(dt::RationalNumber{0, 0})) == 30.0f);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - DC dynamic mode") {
|
||||
d20::SelectedServiceParameters service_parameters =
|
||||
d20::SelectedServiceParameters(dt::ServiceCategory::DC, dt::DcConnector::Extended, dt::ControlMode::Dynamic,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
|
||||
message_20::DC_ChargeLoopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_control_mode = req.control_mode.emplace<Dynamic_DC_Req>();
|
||||
req_control_mode.target_energy_request = {68, 3};
|
||||
req_control_mode.max_energy_request = {70, 3};
|
||||
req_control_mode.min_energy_request = {40, 3};
|
||||
req_control_mode.max_charge_power = {30, 3};
|
||||
req_control_mode.min_charge_power = {27, 2};
|
||||
req_control_mode.max_charge_current = {400, 0};
|
||||
req_control_mode.max_voltage = {950, 0};
|
||||
req_control_mode.min_voltage = {150, 0};
|
||||
|
||||
req.meter_info_requested = false;
|
||||
req.present_voltage = {330, 0};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, 330, 30, false, false, evse_setup.dc_limits,
|
||||
d20::UpdateDynamicModeParameters());
|
||||
|
||||
THEN("ResponseCode: OK, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_current) == 30.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_voltage) == 330.0f);
|
||||
REQUIRE(res.current_limit_achieved == false);
|
||||
REQUIRE(res.power_limit_achieved == false);
|
||||
REQUIRE(res.voltage_limit_achieved == false);
|
||||
|
||||
REQUIRE(std::holds_alternative<Dynamic_DC_Res>(res.control_mode));
|
||||
const auto& res_control_mode = std::get<Dynamic_DC_Res>(res.control_mode);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_charge_power) == 22000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.min_charge_power) == 10.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_charge_current) == 250.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_voltage) == 900.0f);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - DC_BPT dynamic mode") {
|
||||
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::DC_BPT, dt::DcConnector::Extended, dt::ControlMode::Dynamic,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
|
||||
message_20::DC_ChargeLoopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_control_mode = req.control_mode.emplace<Dynamic_BPT_DC_Req>();
|
||||
req_control_mode.target_energy_request = {68, 3};
|
||||
req_control_mode.max_energy_request = {70, 3};
|
||||
req_control_mode.min_energy_request = {40, 3};
|
||||
req_control_mode.max_charge_power = {30, 3};
|
||||
req_control_mode.min_charge_power = {27, 2};
|
||||
req_control_mode.max_charge_current = {400, 0};
|
||||
req_control_mode.max_voltage = {950, 0};
|
||||
req_control_mode.min_voltage = {150, 0};
|
||||
req_control_mode.max_discharge_power = {11, 3};
|
||||
req_control_mode.min_discharge_power = {10, 0};
|
||||
req_control_mode.max_discharge_current = {30, 0};
|
||||
|
||||
req.meter_info_requested = false;
|
||||
req.present_voltage = {330, 0};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, 330, 30, false, false, evse_setup.dc_limits,
|
||||
d20::UpdateDynamicModeParameters());
|
||||
|
||||
THEN("ResponseCode: OK, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_current) == 30.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_voltage) == 330.0f);
|
||||
REQUIRE(res.current_limit_achieved == false);
|
||||
REQUIRE(res.power_limit_achieved == false);
|
||||
REQUIRE(res.voltage_limit_achieved == false);
|
||||
|
||||
REQUIRE(std::holds_alternative<Dynamic_BPT_DC_Res>(res.control_mode));
|
||||
const auto& res_control_mode = std::get<Dynamic_BPT_DC_Res>(res.control_mode);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_charge_power) == 22000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.min_charge_power) == 10.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_charge_current) == 250.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_voltage) == 900.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_discharge_power) == 11000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.min_discharge_power) == 10.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_discharge_current) == 30.0f);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - DC dynamic mode, mobility_needs_mode = 2") {
|
||||
d20::SelectedServiceParameters service_parameters =
|
||||
d20::SelectedServiceParameters(dt::ServiceCategory::DC, dt::DcConnector::Extended, dt::ControlMode::Dynamic,
|
||||
dt::MobilityNeedsMode::ProvidedBySecc, dt::Pricing::NoPricing);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
|
||||
message_20::DC_ChargeLoopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_control_mode = req.control_mode.emplace<Dynamic_DC_Req>();
|
||||
req_control_mode.target_energy_request = {68, 3};
|
||||
req_control_mode.max_energy_request = {70, 3};
|
||||
req_control_mode.min_energy_request = {40, 3};
|
||||
req_control_mode.max_charge_power = {30, 3};
|
||||
req_control_mode.min_charge_power = {27, 2};
|
||||
req_control_mode.max_charge_current = {400, 0};
|
||||
req_control_mode.max_voltage = {950, 0};
|
||||
req_control_mode.min_voltage = {150, 0};
|
||||
|
||||
req.meter_info_requested = false;
|
||||
req.present_voltage = {330, 0};
|
||||
|
||||
const d20::UpdateDynamicModeParameters dynamic_parameters = {std::time(nullptr) + 60, 95, std::nullopt};
|
||||
|
||||
const auto res =
|
||||
d20::state::handle_request(req, session, 330, 30, false, false, evse_setup.dc_limits, dynamic_parameters);
|
||||
|
||||
THEN("ResponseCode: OK, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_current) == 30.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_voltage) == 330.0f);
|
||||
REQUIRE(res.current_limit_achieved == false);
|
||||
REQUIRE(res.power_limit_achieved == false);
|
||||
REQUIRE(res.voltage_limit_achieved == false);
|
||||
|
||||
REQUIRE(std::holds_alternative<Dynamic_DC_Res>(res.control_mode));
|
||||
const auto& res_control_mode = std::get<Dynamic_DC_Res>(res.control_mode);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_charge_power) == 22000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.min_charge_power) == 10.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_charge_current) == 250.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_voltage) == 900.0f);
|
||||
|
||||
REQUIRE(res_control_mode.departure_time.value_or(0) >= 59);
|
||||
REQUIRE(res_control_mode.target_soc.value_or(0) == 95);
|
||||
REQUIRE(res_control_mode.ack_max_delay.value_or(0) == 30);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - DC_BPT dynamic mode, mobility_needs_mode = 2") {
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::DC_BPT, dt::DcConnector::Extended, dt::ControlMode::Dynamic,
|
||||
dt::MobilityNeedsMode::ProvidedBySecc, dt::Pricing::NoPricing);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
|
||||
message_20::DC_ChargeLoopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_control_mode = req.control_mode.emplace<Dynamic_BPT_DC_Req>();
|
||||
req_control_mode.target_energy_request = {68, 3};
|
||||
req_control_mode.max_energy_request = {70, 3};
|
||||
req_control_mode.min_energy_request = {40, 3};
|
||||
req_control_mode.max_charge_power = {30, 3};
|
||||
req_control_mode.min_charge_power = {27, 2};
|
||||
req_control_mode.max_charge_current = {400, 0};
|
||||
req_control_mode.max_voltage = {950, 0};
|
||||
req_control_mode.min_voltage = {150, 0};
|
||||
req_control_mode.max_discharge_power = {11, 3};
|
||||
req_control_mode.min_discharge_power = {10, 0};
|
||||
req_control_mode.max_discharge_current = {30, 0};
|
||||
|
||||
req.meter_info_requested = false;
|
||||
req.present_voltage = {330, 0};
|
||||
|
||||
const d20::UpdateDynamicModeParameters dynamic_parameters = {std::time(nullptr) + 40, std::nullopt, 95};
|
||||
|
||||
const auto res =
|
||||
d20::state::handle_request(req, session, 330, 30, false, false, evse_setup.dc_limits, dynamic_parameters);
|
||||
|
||||
THEN("ResponseCode: OK, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_current) == 30.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_voltage) == 330.0f);
|
||||
REQUIRE(res.current_limit_achieved == false);
|
||||
REQUIRE(res.power_limit_achieved == false);
|
||||
REQUIRE(res.voltage_limit_achieved == false);
|
||||
|
||||
REQUIRE(std::holds_alternative<Dynamic_BPT_DC_Res>(res.control_mode));
|
||||
const auto& res_control_mode = std::get<Dynamic_BPT_DC_Res>(res.control_mode);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_charge_power) == 22000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.min_charge_power) == 10.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_charge_current) == 250.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_voltage) == 900.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_discharge_power) == 11000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.min_discharge_power) == 10.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_discharge_current) == 30.0f);
|
||||
|
||||
REQUIRE(res_control_mode.departure_time.value_or(0) >= 39);
|
||||
REQUIRE(res_control_mode.minimum_soc.value_or(0) == 95);
|
||||
REQUIRE(res_control_mode.ack_max_delay.value_or(0) == 30);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - DC dynamic mode & pause from charger") {
|
||||
d20::SelectedServiceParameters service_parameters =
|
||||
d20::SelectedServiceParameters(dt::ServiceCategory::DC, dt::DcConnector::Extended, dt::ControlMode::Dynamic,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
|
||||
message_20::DC_ChargeLoopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_control_mode = req.control_mode.emplace<Dynamic_DC_Req>();
|
||||
req_control_mode.target_energy_request = {68, 3};
|
||||
req_control_mode.max_energy_request = {70, 3};
|
||||
req_control_mode.min_energy_request = {40, 3};
|
||||
req_control_mode.max_charge_power = {30, 3};
|
||||
req_control_mode.min_charge_power = {27, 2};
|
||||
req_control_mode.max_charge_current = {400, 0};
|
||||
req_control_mode.max_voltage = {950, 0};
|
||||
req_control_mode.min_voltage = {150, 0};
|
||||
|
||||
req.meter_info_requested = false;
|
||||
req.present_voltage = {330, 0};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, 330, 30, false, true, evse_setup.dc_limits,
|
||||
d20::UpdateDynamicModeParameters());
|
||||
|
||||
THEN("ResponseCode: OK, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_current) == 30.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_voltage) == 330.0f);
|
||||
REQUIRE(res.current_limit_achieved == false);
|
||||
REQUIRE(res.power_limit_achieved == false);
|
||||
REQUIRE(res.voltage_limit_achieved == false);
|
||||
|
||||
REQUIRE(std::holds_alternative<Dynamic_DC_Res>(res.control_mode));
|
||||
const auto& res_control_mode = std::get<Dynamic_DC_Res>(res.control_mode);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_charge_power) == 22000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.min_charge_power) == 10.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_charge_current) == 250.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_voltage) == 900.0f);
|
||||
|
||||
REQUIRE(res.status.has_value() == true);
|
||||
REQUIRE(res.status.value().notification == dt::EvseNotification::Pause);
|
||||
REQUIRE(res.status.value().notification_max_delay == 60);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - MCS scheduled mode") {
|
||||
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::MCS, dt::DcConnector::Extended, dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
|
||||
message_20::DC_ChargeLoopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_control_mode = req.control_mode.emplace<Scheduled_DC_Req>();
|
||||
req_control_mode.target_current = {40, 0};
|
||||
req_control_mode.target_voltage = {400, 0};
|
||||
|
||||
req.meter_info_requested = false;
|
||||
req.present_voltage = {330, 0};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, 330, 30, false, false, evse_setup.dc_limits,
|
||||
d20::UpdateDynamicModeParameters());
|
||||
|
||||
THEN("ResponseCode: OK, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_current) == 30.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_voltage) == 330.0f);
|
||||
REQUIRE(res.current_limit_achieved == false);
|
||||
REQUIRE(res.power_limit_achieved == false);
|
||||
REQUIRE(res.voltage_limit_achieved == false);
|
||||
|
||||
REQUIRE(std::holds_alternative<Scheduled_DC_Res>(res.control_mode));
|
||||
const auto& res_control_mode = std::get<Scheduled_DC_Res>(res.control_mode);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_charge_power.value_or(dt::RationalNumber{0, 0})) ==
|
||||
22000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.min_charge_power.value_or(dt::RationalNumber{0, 0})) ==
|
||||
10.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_charge_current.value_or(dt::RationalNumber{0, 0})) ==
|
||||
250.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_voltage.value_or(dt::RationalNumber{0, 0})) == 900.0f);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - MCS_BPT scheduled mode") {
|
||||
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::MCS_BPT, dt::DcConnector::Extended, dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
|
||||
message_20::DC_ChargeLoopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_control_mode = req.control_mode.emplace<Scheduled_BPT_DC_Req>();
|
||||
req_control_mode.target_current = {40, 0};
|
||||
req_control_mode.target_voltage = {400, 0};
|
||||
req_control_mode.max_discharge_power.emplace<dt::RationalNumber>({11, 3});
|
||||
|
||||
req.meter_info_requested = false;
|
||||
req.present_voltage = {330, 0};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, 330, 30, false, false, evse_setup.dc_limits,
|
||||
d20::UpdateDynamicModeParameters());
|
||||
|
||||
THEN("ResponseCode: OK, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_current) == 30.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_voltage) == 330.0f);
|
||||
REQUIRE(res.current_limit_achieved == false);
|
||||
REQUIRE(res.power_limit_achieved == false);
|
||||
REQUIRE(res.voltage_limit_achieved == false);
|
||||
|
||||
REQUIRE(std::holds_alternative<Scheduled_BPT_DC_Res>(res.control_mode));
|
||||
const auto& res_control_mode = std::get<Scheduled_BPT_DC_Res>(res.control_mode);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_charge_power.value_or(dt::RationalNumber{0, 0})) ==
|
||||
22000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.min_charge_power.value_or(dt::RationalNumber{0, 0})) ==
|
||||
10.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_charge_current.value_or(dt::RationalNumber{0, 0})) ==
|
||||
250.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_voltage.value_or(dt::RationalNumber{0, 0})) == 900.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_discharge_power.value_or(dt::RationalNumber{0, 0})) ==
|
||||
11000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.min_discharge_power.value_or(dt::RationalNumber{0, 0})) ==
|
||||
10.0f);
|
||||
REQUIRE(dt::from_RationalNumber(
|
||||
res_control_mode.max_discharge_current.value_or(dt::RationalNumber{0, 0})) == 30.0f);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - MCS dynamic mode") {
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::MCS, dt::DcConnector::Extended, dt::ControlMode::Dynamic,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
|
||||
message_20::DC_ChargeLoopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_control_mode = req.control_mode.emplace<Dynamic_DC_Req>();
|
||||
req_control_mode.target_energy_request = {68, 3};
|
||||
req_control_mode.max_energy_request = {70, 3};
|
||||
req_control_mode.min_energy_request = {40, 3};
|
||||
req_control_mode.max_charge_power = {30, 3};
|
||||
req_control_mode.min_charge_power = {27, 2};
|
||||
req_control_mode.max_charge_current = {400, 0};
|
||||
req_control_mode.max_voltage = {950, 0};
|
||||
req_control_mode.min_voltage = {150, 0};
|
||||
|
||||
req.meter_info_requested = false;
|
||||
req.present_voltage = {330, 0};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, 330, 30, false, false, evse_setup.dc_limits,
|
||||
d20::UpdateDynamicModeParameters());
|
||||
|
||||
THEN("ResponseCode: OK, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_current) == 30.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_voltage) == 330.0f);
|
||||
REQUIRE(res.current_limit_achieved == false);
|
||||
REQUIRE(res.power_limit_achieved == false);
|
||||
REQUIRE(res.voltage_limit_achieved == false);
|
||||
|
||||
REQUIRE(std::holds_alternative<Dynamic_DC_Res>(res.control_mode));
|
||||
const auto& res_control_mode = std::get<Dynamic_DC_Res>(res.control_mode);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_charge_power) == 22000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.min_charge_power) == 10.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_charge_current) == 250.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_voltage) == 900.0f);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - MCS_BPT dynamic mode") {
|
||||
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::MCS_BPT, dt::DcConnector::Extended, dt::ControlMode::Dynamic,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
|
||||
message_20::DC_ChargeLoopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_control_mode = req.control_mode.emplace<Dynamic_BPT_DC_Req>();
|
||||
req_control_mode.target_energy_request = {68, 3};
|
||||
req_control_mode.max_energy_request = {70, 3};
|
||||
req_control_mode.min_energy_request = {40, 3};
|
||||
req_control_mode.max_charge_power = {30, 3};
|
||||
req_control_mode.min_charge_power = {27, 2};
|
||||
req_control_mode.max_charge_current = {400, 0};
|
||||
req_control_mode.max_voltage = {950, 0};
|
||||
req_control_mode.min_voltage = {150, 0};
|
||||
req_control_mode.max_discharge_power = {11, 3};
|
||||
req_control_mode.min_discharge_power = {10, 0};
|
||||
req_control_mode.max_discharge_current = {30, 0};
|
||||
|
||||
req.meter_info_requested = false;
|
||||
req.present_voltage = {330, 0};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, 330, 30, false, false, evse_setup.dc_limits,
|
||||
d20::UpdateDynamicModeParameters());
|
||||
|
||||
THEN("ResponseCode: OK, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_current) == 30.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_voltage) == 330.0f);
|
||||
REQUIRE(res.current_limit_achieved == false);
|
||||
REQUIRE(res.power_limit_achieved == false);
|
||||
REQUIRE(res.voltage_limit_achieved == false);
|
||||
|
||||
REQUIRE(std::holds_alternative<Dynamic_BPT_DC_Res>(res.control_mode));
|
||||
const auto& res_control_mode = std::get<Dynamic_BPT_DC_Res>(res.control_mode);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_charge_power) == 22000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.min_charge_power) == 10.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_charge_current) == 250.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_voltage) == 900.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_discharge_power) == 11000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.min_discharge_power) == 10.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_discharge_current) == 30.0f);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - MCS dynamic mode, mobility_needs_mode = 2") {
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::MCS, dt::DcConnector::Extended, dt::ControlMode::Dynamic,
|
||||
dt::MobilityNeedsMode::ProvidedBySecc, dt::Pricing::NoPricing);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
|
||||
message_20::DC_ChargeLoopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_control_mode = req.control_mode.emplace<Dynamic_DC_Req>();
|
||||
req_control_mode.target_energy_request = {68, 3};
|
||||
req_control_mode.max_energy_request = {70, 3};
|
||||
req_control_mode.min_energy_request = {40, 3};
|
||||
req_control_mode.max_charge_power = {30, 3};
|
||||
req_control_mode.min_charge_power = {27, 2};
|
||||
req_control_mode.max_charge_current = {400, 0};
|
||||
req_control_mode.max_voltage = {950, 0};
|
||||
req_control_mode.min_voltage = {150, 0};
|
||||
|
||||
req.meter_info_requested = false;
|
||||
req.present_voltage = {330, 0};
|
||||
|
||||
const d20::UpdateDynamicModeParameters dynamic_parameters = {std::time(nullptr) + 60, 95, std::nullopt};
|
||||
|
||||
const auto res =
|
||||
d20::state::handle_request(req, session, 330, 30, false, false, evse_setup.dc_limits, dynamic_parameters);
|
||||
|
||||
THEN("ResponseCode: OK, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_current) == 30.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_voltage) == 330.0f);
|
||||
REQUIRE(res.current_limit_achieved == false);
|
||||
REQUIRE(res.power_limit_achieved == false);
|
||||
REQUIRE(res.voltage_limit_achieved == false);
|
||||
|
||||
REQUIRE(std::holds_alternative<Dynamic_DC_Res>(res.control_mode));
|
||||
const auto& res_control_mode = std::get<Dynamic_DC_Res>(res.control_mode);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_charge_power) == 22000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.min_charge_power) == 10.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_charge_current) == 250.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_voltage) == 900.0f);
|
||||
|
||||
REQUIRE(res_control_mode.departure_time.value_or(0) >= 59);
|
||||
REQUIRE(res_control_mode.target_soc.value_or(0) == 95);
|
||||
REQUIRE(res_control_mode.ack_max_delay.value_or(0) == 30);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - MCS_BPT dynamic mode, mobility_needs_mode = 2") {
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::MCS_BPT, dt::DcConnector::Extended, dt::ControlMode::Dynamic,
|
||||
dt::MobilityNeedsMode::ProvidedBySecc, dt::Pricing::NoPricing);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
|
||||
message_20::DC_ChargeLoopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_control_mode = req.control_mode.emplace<Dynamic_BPT_DC_Req>();
|
||||
req_control_mode.target_energy_request = {68, 3};
|
||||
req_control_mode.max_energy_request = {70, 3};
|
||||
req_control_mode.min_energy_request = {40, 3};
|
||||
req_control_mode.max_charge_power = {30, 3};
|
||||
req_control_mode.min_charge_power = {27, 2};
|
||||
req_control_mode.max_charge_current = {400, 0};
|
||||
req_control_mode.max_voltage = {950, 0};
|
||||
req_control_mode.min_voltage = {150, 0};
|
||||
req_control_mode.max_discharge_power = {11, 3};
|
||||
req_control_mode.min_discharge_power = {10, 0};
|
||||
req_control_mode.max_discharge_current = {30, 0};
|
||||
|
||||
req.meter_info_requested = false;
|
||||
req.present_voltage = {330, 0};
|
||||
|
||||
const d20::UpdateDynamicModeParameters dynamic_parameters = {std::time(nullptr) + 40, std::nullopt, 95};
|
||||
|
||||
const auto res =
|
||||
d20::state::handle_request(req, session, 330, 30, false, false, evse_setup.dc_limits, dynamic_parameters);
|
||||
|
||||
THEN("ResponseCode: OK, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_current) == 30.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res.present_voltage) == 330.0f);
|
||||
REQUIRE(res.current_limit_achieved == false);
|
||||
REQUIRE(res.power_limit_achieved == false);
|
||||
REQUIRE(res.voltage_limit_achieved == false);
|
||||
|
||||
REQUIRE(std::holds_alternative<Dynamic_BPT_DC_Res>(res.control_mode));
|
||||
const auto& res_control_mode = std::get<Dynamic_BPT_DC_Res>(res.control_mode);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_charge_power) == 22000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.min_charge_power) == 10.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_charge_current) == 250.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_voltage) == 900.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_discharge_power) == 11000.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.min_discharge_power) == 10.0f);
|
||||
REQUIRE(dt::from_RationalNumber(res_control_mode.max_discharge_current) == 30.0f);
|
||||
|
||||
REQUIRE(res_control_mode.departure_time.value_or(0) >= 39);
|
||||
REQUIRE(res_control_mode.minimum_soc.value_or(0) == 95);
|
||||
REQUIRE(res_control_mode.ack_max_delay.value_or(0) == 30);
|
||||
}
|
||||
}
|
||||
|
||||
// Note(sl): Only in scheduled mode and if a powertolerance was sent from the secc
|
||||
// TODO(sl): Adding test
|
||||
// GIVEN("Warning case - Warning_EVPowerProfileViolation [V2G20-1864]") {}
|
||||
// GIVEN("Bad case - Failed_EVPowerProfileViolation [V2G20-1864]") {}
|
||||
|
||||
// TODO(sl): Adding test if ScheduleRenegotion is supported
|
||||
// GIVEN("Warning case - Warning_ScheduleRenegotiationFailed") {}
|
||||
// GIVEN("Bad case - Failed_ScheduleRenegotiationFailed") {}
|
||||
|
||||
// Note(sl): Not yet
|
||||
// GIVEN("Bad case - Failed_AssociationError (ACDP only)") {}
|
||||
|
||||
// GIVEN("Bad Case - sequence error") {} // TODO(sl): not here
|
||||
|
||||
// GIVEN("Bad Case - Performance Timeout") {} // TODO(sl): not here
|
||||
|
||||
// GIVEN("Bad Case - Sequence Timeout") {} // TODO(sl): not here
|
||||
}
|
||||
@@ -0,0 +1,458 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/detail/d20/state/dc_charge_parameter_discovery.hpp>
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
namespace dt = message_20::datatypes;
|
||||
|
||||
using DC_ModeReq = message_20::datatypes::DC_CPDReqEnergyTransferMode;
|
||||
using BPT_DC_ModeReq = message_20::datatypes::BPT_DC_CPDReqEnergyTransferMode;
|
||||
|
||||
using DC_ModeRes = message_20::datatypes::DC_CPDResEnergyTransferMode;
|
||||
using BPT_DC_ModeRes = message_20::datatypes::BPT_DC_CPDResEnergyTransferMode;
|
||||
|
||||
SCENARIO("DC charge parameter discovery state handling") {
|
||||
|
||||
const auto evse_id = std::string("everest se");
|
||||
const std::vector<dt::ServiceCategory> supported_energy_services = {dt::ServiceCategory::DC};
|
||||
const auto cert_install{false};
|
||||
const std::vector<dt::Authorization> auth_services = {dt::Authorization::EIM};
|
||||
const std::vector<uint16_t> vas_services{};
|
||||
const d20::DcTransferLimits dc_limits;
|
||||
const d20::AcTransferLimits ac_limits;
|
||||
const d20::DcTransferLimits powersupply_limits;
|
||||
const std::vector<d20::ControlMobilityNeedsModes> control_mobility_modes = {
|
||||
{dt::ControlMode::Scheduled, dt::MobilityNeedsMode::ProvidedByEvcc}};
|
||||
|
||||
const d20::EvseSetupConfig evse_setup{
|
||||
evse_id, supported_energy_services, auth_services, vas_services, cert_install, dc_limits,
|
||||
ac_limits, control_mobility_modes, std::nullopt, std::nullopt, std::nullopt, powersupply_limits};
|
||||
|
||||
GIVEN("Bad Case - Unknown session") {
|
||||
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
message_20::DC_ChargeParameterDiscoveryRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_out = req.transfer_mode.emplace<DC_ModeReq>();
|
||||
req_out.max_charge_power = {50, 3};
|
||||
req_out.min_charge_power = {0, 0};
|
||||
req_out.max_charge_current = {125, 0};
|
||||
req_out.min_charge_current = {0, 0};
|
||||
req_out.max_voltage = {400, 0};
|
||||
req_out.min_voltage = {0, 0};
|
||||
|
||||
const auto res = d20::state::handle_request(req, d20::Session(), evse_setup.powersupply_limits);
|
||||
|
||||
THEN("ResponseCode: FAILED_UnknownSession, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_UnknownSession);
|
||||
|
||||
REQUIRE(std::holds_alternative<DC_ModeRes>(res.transfer_mode));
|
||||
const auto& transfer_mode = std::get<DC_ModeRes>(res.transfer_mode);
|
||||
REQUIRE(transfer_mode.max_charge_power.value == 0);
|
||||
REQUIRE(transfer_mode.max_charge_power.exponent == 0);
|
||||
REQUIRE(transfer_mode.min_charge_power.value == 0);
|
||||
REQUIRE(transfer_mode.min_charge_power.exponent == 0);
|
||||
REQUIRE(transfer_mode.max_charge_current.value == 0);
|
||||
REQUIRE(transfer_mode.max_charge_current.exponent == 0);
|
||||
REQUIRE(transfer_mode.min_charge_current.value == 0);
|
||||
REQUIRE(transfer_mode.min_charge_current.exponent == 0);
|
||||
REQUIRE(transfer_mode.max_voltage.value == 0);
|
||||
REQUIRE(transfer_mode.max_voltage.exponent == 0);
|
||||
REQUIRE(transfer_mode.min_voltage.value == 0);
|
||||
REQUIRE(transfer_mode.min_voltage.exponent == 0);
|
||||
REQUIRE(transfer_mode.power_ramp_limit.has_value() == false);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Bad Case: e.g. dc transfer mod instead of dc_bpt transfer mod - FAILED_WrongChargeParameter") {
|
||||
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::DC_BPT, dt::DcConnector::Extended, dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing, dt::BptChannel::Unified,
|
||||
dt::GeneratorMode::GridFollowing);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
|
||||
message_20::DC_ChargeParameterDiscoveryRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_out = req.transfer_mode.emplace<DC_ModeReq>();
|
||||
req_out.max_charge_power = {50, 3};
|
||||
req_out.min_charge_power = {0, 0};
|
||||
req_out.max_charge_current = {125, 0};
|
||||
req_out.min_charge_current = {0, 0};
|
||||
req_out.max_voltage = {400, 0};
|
||||
req_out.min_voltage = {0, 0};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, evse_setup.powersupply_limits);
|
||||
|
||||
THEN("ResponseCode: FAILED_WrongChargeParameter, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_WrongChargeParameter);
|
||||
|
||||
REQUIRE(std::holds_alternative<DC_ModeRes>(res.transfer_mode));
|
||||
const auto& transfer_mode = std::get<DC_ModeRes>(res.transfer_mode);
|
||||
REQUIRE(transfer_mode.max_charge_power.value == 0);
|
||||
REQUIRE(transfer_mode.max_charge_power.exponent == 0);
|
||||
REQUIRE(transfer_mode.min_charge_power.value == 0);
|
||||
REQUIRE(transfer_mode.min_charge_power.exponent == 0);
|
||||
REQUIRE(transfer_mode.max_charge_current.value == 0);
|
||||
REQUIRE(transfer_mode.max_charge_current.exponent == 0);
|
||||
REQUIRE(transfer_mode.min_charge_current.value == 0);
|
||||
REQUIRE(transfer_mode.min_charge_current.exponent == 0);
|
||||
REQUIRE(transfer_mode.max_voltage.value == 0);
|
||||
REQUIRE(transfer_mode.max_voltage.exponent == 0);
|
||||
REQUIRE(transfer_mode.min_voltage.value == 0);
|
||||
REQUIRE(transfer_mode.min_voltage.exponent == 0);
|
||||
REQUIRE(transfer_mode.power_ramp_limit.has_value() == false);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Bad Case: e.g. DC_BPT transfer mod instead of dc transfer mod - FAILED_WrongChargeParameter") {
|
||||
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::DC, dt::DcConnector::Extended, dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
|
||||
message_20::DC_ChargeParameterDiscoveryRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_out = req.transfer_mode.emplace<BPT_DC_ModeReq>();
|
||||
req_out.max_charge_power = {50, 3};
|
||||
req_out.min_charge_power = {0, 0};
|
||||
req_out.max_charge_current = {125, 0};
|
||||
req_out.min_charge_current = {0, 0};
|
||||
req_out.max_voltage = {400, 0};
|
||||
req_out.min_voltage = {0, 0};
|
||||
req_out.max_discharge_power = {11, 3};
|
||||
req_out.min_discharge_power = {0, 0};
|
||||
req_out.max_discharge_current = {25, 0};
|
||||
req_out.min_discharge_current = {0, 0};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, evse_setup.powersupply_limits);
|
||||
|
||||
THEN("ResponseCode: FAILED_WrongChargeParameter, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_WrongChargeParameter);
|
||||
|
||||
REQUIRE(std::holds_alternative<DC_ModeRes>(res.transfer_mode));
|
||||
const auto& transfer_mode = std::get<DC_ModeRes>(res.transfer_mode);
|
||||
REQUIRE(transfer_mode.max_charge_power.value == 0);
|
||||
REQUIRE(transfer_mode.max_charge_power.exponent == 0);
|
||||
REQUIRE(transfer_mode.min_charge_power.value == 0);
|
||||
REQUIRE(transfer_mode.min_charge_power.exponent == 0);
|
||||
REQUIRE(transfer_mode.max_charge_current.value == 0);
|
||||
REQUIRE(transfer_mode.max_charge_current.exponent == 0);
|
||||
REQUIRE(transfer_mode.min_charge_current.value == 0);
|
||||
REQUIRE(transfer_mode.min_charge_current.exponent == 0);
|
||||
REQUIRE(transfer_mode.max_voltage.value == 0);
|
||||
REQUIRE(transfer_mode.max_voltage.exponent == 0);
|
||||
REQUIRE(transfer_mode.min_voltage.value == 0);
|
||||
REQUIRE(transfer_mode.min_voltage.exponent == 0);
|
||||
REQUIRE(transfer_mode.power_ramp_limit.has_value() == false);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good Case: DC") {
|
||||
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::DC, dt::DcConnector::Extended, dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
|
||||
d20::DcTransferLimits powersupply_limits;
|
||||
powersupply_limits.charge_limits.power.max = {22, 3};
|
||||
powersupply_limits.charge_limits.current.max = {25, 0};
|
||||
powersupply_limits.voltage.max = {900, 0};
|
||||
dt::RationalNumber power_ramp_limit = {20, 0};
|
||||
powersupply_limits.power_ramp_limit.emplace<>(power_ramp_limit);
|
||||
|
||||
message_20::DC_ChargeParameterDiscoveryRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_out = req.transfer_mode.emplace<DC_ModeReq>();
|
||||
req_out.max_charge_power = {50, 3};
|
||||
req_out.min_charge_power = {0, 0};
|
||||
req_out.max_charge_current = {125, 0};
|
||||
req_out.min_charge_current = {0, 0};
|
||||
req_out.max_voltage = {400, 0};
|
||||
req_out.min_voltage = {0, 0};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, powersupply_limits);
|
||||
|
||||
THEN("ResponseCode: OK") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
|
||||
REQUIRE(std::holds_alternative<DC_ModeRes>(res.transfer_mode));
|
||||
const auto& transfer_mode = std::get<DC_ModeRes>(res.transfer_mode);
|
||||
REQUIRE(transfer_mode.max_charge_power.value == 22);
|
||||
REQUIRE(transfer_mode.max_charge_power.exponent == 3);
|
||||
REQUIRE(transfer_mode.min_charge_power.value == 0);
|
||||
REQUIRE(transfer_mode.min_charge_power.exponent == 0);
|
||||
REQUIRE(transfer_mode.max_charge_current.value == 25);
|
||||
REQUIRE(transfer_mode.max_charge_current.exponent == 0);
|
||||
REQUIRE(transfer_mode.min_charge_current.value == 0);
|
||||
REQUIRE(transfer_mode.min_charge_current.exponent == 0);
|
||||
REQUIRE(transfer_mode.max_voltage.value == 900);
|
||||
REQUIRE(transfer_mode.max_voltage.exponent == 0);
|
||||
REQUIRE(transfer_mode.min_voltage.value == 0);
|
||||
REQUIRE(transfer_mode.min_voltage.exponent == 0);
|
||||
REQUIRE(transfer_mode.power_ramp_limit.has_value() == true);
|
||||
REQUIRE(transfer_mode.power_ramp_limit.value().value == 20);
|
||||
REQUIRE(transfer_mode.power_ramp_limit.value().exponent == 0);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good Case: DC_BPT") {
|
||||
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::DC_BPT, dt::DcConnector::Extended, dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing, dt::BptChannel::Unified,
|
||||
dt::GeneratorMode::GridFollowing);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
|
||||
d20::DcTransferLimits powersupply_limits;
|
||||
powersupply_limits.charge_limits.power.max = {22, 3};
|
||||
powersupply_limits.charge_limits.current.max = {25, 0};
|
||||
powersupply_limits.voltage.max = {900, 0};
|
||||
|
||||
auto& discharge_limits = powersupply_limits.discharge_limits.emplace();
|
||||
discharge_limits.power.max = {11, 3};
|
||||
discharge_limits.current.max = {25, 0};
|
||||
|
||||
message_20::DC_ChargeParameterDiscoveryRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_out = req.transfer_mode.emplace<BPT_DC_ModeReq>();
|
||||
req_out.max_charge_power = {50, 3};
|
||||
req_out.min_charge_power = {0, 0};
|
||||
req_out.max_charge_current = {125, 0};
|
||||
req_out.min_charge_current = {0, 0};
|
||||
req_out.max_voltage = {400, 0};
|
||||
req_out.min_voltage = {0, 0};
|
||||
req_out.max_discharge_power = {11, 3};
|
||||
req_out.min_discharge_power = {0, 0};
|
||||
req_out.max_discharge_current = {25, 0};
|
||||
req_out.min_discharge_current = {0, 0};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, powersupply_limits);
|
||||
|
||||
THEN("ResponseCode: OK") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
|
||||
REQUIRE(std::holds_alternative<BPT_DC_ModeRes>(res.transfer_mode));
|
||||
const auto& transfer_mode = std::get<BPT_DC_ModeRes>(res.transfer_mode);
|
||||
REQUIRE(transfer_mode.max_charge_power.value == 22);
|
||||
REQUIRE(transfer_mode.max_charge_power.exponent == 3);
|
||||
REQUIRE(transfer_mode.min_charge_power.value == 0);
|
||||
REQUIRE(transfer_mode.min_charge_power.exponent == 0);
|
||||
REQUIRE(transfer_mode.max_charge_current.value == 25);
|
||||
REQUIRE(transfer_mode.max_charge_current.exponent == 0);
|
||||
REQUIRE(transfer_mode.min_charge_current.value == 0);
|
||||
REQUIRE(transfer_mode.min_charge_current.exponent == 0);
|
||||
REQUIRE(transfer_mode.max_voltage.value == 900);
|
||||
REQUIRE(transfer_mode.max_voltage.exponent == 0);
|
||||
REQUIRE(transfer_mode.min_voltage.value == 0);
|
||||
REQUIRE(transfer_mode.min_voltage.exponent == 0);
|
||||
REQUIRE(transfer_mode.power_ramp_limit.has_value() == false);
|
||||
REQUIRE(transfer_mode.max_discharge_power.value == 11);
|
||||
REQUIRE(transfer_mode.max_discharge_power.exponent == 3);
|
||||
REQUIRE(transfer_mode.min_discharge_power.value == 0);
|
||||
REQUIRE(transfer_mode.min_discharge_power.exponent == 0);
|
||||
REQUIRE(transfer_mode.max_discharge_current.value == 25);
|
||||
REQUIRE(transfer_mode.max_discharge_current.exponent == 0);
|
||||
REQUIRE(transfer_mode.min_discharge_current.value == 0);
|
||||
REQUIRE(transfer_mode.min_discharge_current.exponent == 0);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Bad Case: Provided DC charge limits but the ev wants bpt charge parameter - FAILED") {
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::DC_BPT, dt::DcConnector::Extended, dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing, dt::BptChannel::Unified,
|
||||
dt::GeneratorMode::GridFollowing);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
|
||||
d20::DcTransferLimits powersupply_limits;
|
||||
powersupply_limits.charge_limits.power.max = {22, 3};
|
||||
powersupply_limits.charge_limits.current.max = {25, 0};
|
||||
powersupply_limits.voltage.max = {900, 0};
|
||||
|
||||
message_20::DC_ChargeParameterDiscoveryRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_out = req.transfer_mode.emplace<BPT_DC_ModeReq>();
|
||||
req_out.max_charge_power = {50, 3};
|
||||
req_out.min_charge_power = {0, 0};
|
||||
req_out.max_charge_current = {125, 0};
|
||||
req_out.min_charge_current = {0, 0};
|
||||
req_out.max_voltage = {400, 0};
|
||||
req_out.min_voltage = {0, 0};
|
||||
req_out.max_discharge_power = {11, 3};
|
||||
req_out.min_discharge_power = {0, 0};
|
||||
req_out.max_discharge_current = {25, 0};
|
||||
req_out.min_discharge_current = {0, 0};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, powersupply_limits);
|
||||
|
||||
THEN("ResponseCode: FAILED, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED);
|
||||
|
||||
REQUIRE(std::holds_alternative<DC_ModeRes>(res.transfer_mode));
|
||||
const auto& transfer_mode = std::get<DC_ModeRes>(res.transfer_mode);
|
||||
REQUIRE(transfer_mode.max_charge_power.value == 0);
|
||||
REQUIRE(transfer_mode.max_charge_power.exponent == 0);
|
||||
REQUIRE(transfer_mode.min_charge_power.value == 0);
|
||||
REQUIRE(transfer_mode.min_charge_power.exponent == 0);
|
||||
REQUIRE(transfer_mode.max_charge_current.value == 0);
|
||||
REQUIRE(transfer_mode.max_charge_current.exponent == 0);
|
||||
REQUIRE(transfer_mode.min_charge_current.value == 0);
|
||||
REQUIRE(transfer_mode.min_charge_current.exponent == 0);
|
||||
REQUIRE(transfer_mode.max_voltage.value == 0);
|
||||
REQUIRE(transfer_mode.max_voltage.exponent == 0);
|
||||
REQUIRE(transfer_mode.min_voltage.value == 0);
|
||||
REQUIRE(transfer_mode.min_voltage.exponent == 0);
|
||||
REQUIRE(transfer_mode.power_ramp_limit.has_value() == false);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Bad Case: EV Parameter does not fit in evse parameters - FAILED_WrongChargeParameter") {
|
||||
}
|
||||
|
||||
GIVEN("Good Case: MCS") {
|
||||
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::MCS, dt::DcConnector::Extended, dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
|
||||
d20::DcTransferLimits powersupply_limits;
|
||||
powersupply_limits.charge_limits.power.max = {22, 3};
|
||||
powersupply_limits.charge_limits.current.max = {25, 0};
|
||||
powersupply_limits.voltage.max = {900, 0};
|
||||
dt::RationalNumber power_ramp_limit = {20, 0};
|
||||
powersupply_limits.power_ramp_limit.emplace<>(power_ramp_limit);
|
||||
|
||||
message_20::DC_ChargeParameterDiscoveryRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_out = req.transfer_mode.emplace<DC_ModeReq>();
|
||||
req_out.max_charge_power = {50, 3};
|
||||
req_out.min_charge_power = {0, 0};
|
||||
req_out.max_charge_current = {125, 0};
|
||||
req_out.min_charge_current = {0, 0};
|
||||
req_out.max_voltage = {400, 0};
|
||||
req_out.min_voltage = {0, 0};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, powersupply_limits);
|
||||
|
||||
THEN("ResponseCode: OK") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
|
||||
REQUIRE(std::holds_alternative<DC_ModeRes>(res.transfer_mode));
|
||||
const auto& transfer_mode = std::get<DC_ModeRes>(res.transfer_mode);
|
||||
REQUIRE(transfer_mode.max_charge_power.value == 22);
|
||||
REQUIRE(transfer_mode.max_charge_power.exponent == 3);
|
||||
REQUIRE(transfer_mode.min_charge_power.value == 0);
|
||||
REQUIRE(transfer_mode.min_charge_power.exponent == 0);
|
||||
REQUIRE(transfer_mode.max_charge_current.value == 25);
|
||||
REQUIRE(transfer_mode.max_charge_current.exponent == 0);
|
||||
REQUIRE(transfer_mode.min_charge_current.value == 0);
|
||||
REQUIRE(transfer_mode.min_charge_current.exponent == 0);
|
||||
REQUIRE(transfer_mode.max_voltage.value == 900);
|
||||
REQUIRE(transfer_mode.max_voltage.exponent == 0);
|
||||
REQUIRE(transfer_mode.min_voltage.value == 0);
|
||||
REQUIRE(transfer_mode.min_voltage.exponent == 0);
|
||||
REQUIRE(transfer_mode.power_ramp_limit.has_value() == true);
|
||||
REQUIRE(transfer_mode.power_ramp_limit.value().value == 20);
|
||||
REQUIRE(transfer_mode.power_ramp_limit.value().exponent == 0);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good Case: MCS_BPT") {
|
||||
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::MCS_BPT, dt::DcConnector::Extended, dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing, dt::BptChannel::Unified,
|
||||
dt::GeneratorMode::GridFollowing);
|
||||
|
||||
d20::Session session = d20::Session(service_parameters);
|
||||
|
||||
d20::DcTransferLimits powersupply_limits;
|
||||
powersupply_limits.charge_limits.power.max = {22, 3};
|
||||
powersupply_limits.charge_limits.current.max = {25, 0};
|
||||
powersupply_limits.voltage.max = {900, 0};
|
||||
|
||||
auto& discharge_limits = powersupply_limits.discharge_limits.emplace();
|
||||
discharge_limits.power.max = {11, 3};
|
||||
discharge_limits.current.max = {25, 0};
|
||||
|
||||
message_20::DC_ChargeParameterDiscoveryRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& req_out = req.transfer_mode.emplace<BPT_DC_ModeReq>();
|
||||
req_out.max_charge_power = {50, 3};
|
||||
req_out.min_charge_power = {0, 0};
|
||||
req_out.max_charge_current = {125, 0};
|
||||
req_out.min_charge_current = {0, 0};
|
||||
req_out.max_voltage = {400, 0};
|
||||
req_out.min_voltage = {0, 0};
|
||||
req_out.max_discharge_power = {11, 3};
|
||||
req_out.min_discharge_power = {0, 0};
|
||||
req_out.max_discharge_current = {25, 0};
|
||||
req_out.min_discharge_current = {0, 0};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, powersupply_limits);
|
||||
|
||||
THEN("ResponseCode: OK") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
|
||||
REQUIRE(std::holds_alternative<BPT_DC_ModeRes>(res.transfer_mode));
|
||||
const auto& transfer_mode = std::get<BPT_DC_ModeRes>(res.transfer_mode);
|
||||
REQUIRE(transfer_mode.max_charge_power.value == 22);
|
||||
REQUIRE(transfer_mode.max_charge_power.exponent == 3);
|
||||
REQUIRE(transfer_mode.min_charge_power.value == 0);
|
||||
REQUIRE(transfer_mode.min_charge_power.exponent == 0);
|
||||
REQUIRE(transfer_mode.max_charge_current.value == 25);
|
||||
REQUIRE(transfer_mode.max_charge_current.exponent == 0);
|
||||
REQUIRE(transfer_mode.min_charge_current.value == 0);
|
||||
REQUIRE(transfer_mode.min_charge_current.exponent == 0);
|
||||
REQUIRE(transfer_mode.max_voltage.value == 900);
|
||||
REQUIRE(transfer_mode.max_voltage.exponent == 0);
|
||||
REQUIRE(transfer_mode.min_voltage.value == 0);
|
||||
REQUIRE(transfer_mode.min_voltage.exponent == 0);
|
||||
REQUIRE(transfer_mode.power_ramp_limit.has_value() == false);
|
||||
REQUIRE(transfer_mode.max_discharge_power.value == 11);
|
||||
REQUIRE(transfer_mode.max_discharge_power.exponent == 3);
|
||||
REQUIRE(transfer_mode.min_discharge_power.value == 0);
|
||||
REQUIRE(transfer_mode.min_discharge_power.exponent == 0);
|
||||
REQUIRE(transfer_mode.max_discharge_current.value == 25);
|
||||
REQUIRE(transfer_mode.max_discharge_current.exponent == 0);
|
||||
REQUIRE(transfer_mode.min_discharge_current.value == 0);
|
||||
REQUIRE(transfer_mode.min_discharge_current.exponent == 0);
|
||||
}
|
||||
}
|
||||
|
||||
// GIVEN("Bad Case - sequence error") {} // todo(sl): not here
|
||||
|
||||
// GIVEN("Bad Case - Performance Timeout") {} // TODO(sl): not here
|
||||
|
||||
// GIVEN("Bad Case - Sequence Timeout") {} // TODO(sl): not here
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/detail/d20/state/dc_pre_charge.hpp>
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
namespace dt = message_20::datatypes;
|
||||
|
||||
SCENARIO("DC Pre charge state handling") {
|
||||
|
||||
GIVEN("Bad case - Unknown session") {
|
||||
|
||||
auto session = d20::Session();
|
||||
|
||||
message_20::DC_PreChargeRequest req;
|
||||
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
req.processing = dt::Processing::Ongoing;
|
||||
req.present_voltage = {0, 0};
|
||||
req.target_voltage = {400, 0};
|
||||
|
||||
const float present_voltage = 0.1;
|
||||
|
||||
const auto res = d20::state::handle_request(req, d20::Session(), present_voltage);
|
||||
|
||||
THEN("ResponseCode: FAILED_UnknownSession, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_UnknownSession);
|
||||
REQUIRE(res.present_voltage.value == 0);
|
||||
REQUIRE(res.present_voltage.exponent == 0);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good Case") {
|
||||
auto session = d20::Session();
|
||||
|
||||
message_20::DC_PreChargeRequest req;
|
||||
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
req.processing = dt::Processing::Ongoing;
|
||||
req.present_voltage = {0, 0};
|
||||
req.target_voltage = {400, 0};
|
||||
|
||||
const float present_voltage = 400.1;
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, present_voltage);
|
||||
|
||||
THEN("ResponseCode: OK, present_voltage should be 400.1V") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.present_voltage.value == 4001);
|
||||
REQUIRE(res.present_voltage.exponent == -1);
|
||||
}
|
||||
}
|
||||
|
||||
// GIVEN("Bad Case - sequence error") {} // TODO(sl): not here
|
||||
|
||||
// GIVEN("Bad Case - Performance Timeout") {} // TODO(sl): not here
|
||||
|
||||
// GIVEN("Bad Case - Sequence Timeout") {} // TODO(sl): not here
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/detail/d20/state/dc_welding_detection.hpp>
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
namespace dt = message_20::datatypes;
|
||||
|
||||
SCENARIO("DC Welding Detection state handling") {
|
||||
|
||||
GIVEN("Bad case - Unknown session") {
|
||||
|
||||
auto session = d20::Session();
|
||||
|
||||
message_20::DC_WeldingDetectionRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
req.processing = dt::Processing::Ongoing;
|
||||
|
||||
const float present_voltage = 0.1;
|
||||
|
||||
const auto res = d20::state::handle_request(req, d20::Session(), present_voltage);
|
||||
|
||||
THEN("ResponseCode: FAILED_UnknownSession, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_UnknownSession);
|
||||
REQUIRE(res.present_voltage.value == 0);
|
||||
REQUIRE(res.present_voltage.exponent == 0);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good Case") {
|
||||
|
||||
auto session = d20::Session();
|
||||
|
||||
message_20::DC_WeldingDetectionRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
req.processing = dt::Processing::Ongoing;
|
||||
|
||||
const float present_voltage = 200;
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, present_voltage);
|
||||
|
||||
THEN("ResponseCode: OK, present_voltage should be 200V") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.present_voltage.value == 2000);
|
||||
REQUIRE(res.present_voltage.exponent == -1);
|
||||
}
|
||||
}
|
||||
|
||||
// GIVEN("Bad case - State B was not measuered -> FAILED") {} // TODO(sl): check evse_manager?
|
||||
|
||||
// GIVEN("Bad Case - sequence error") {} // TODO(sl): not here
|
||||
|
||||
// GIVEN("Bad Case - Performance Timeout") {} // TODO(sl): not here
|
||||
|
||||
// GIVEN("Bad Case - Sequence Timeout") {} // TODO(sl): not here
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/detail/d20/state/power_delivery.hpp>
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
namespace dt = message_20::datatypes;
|
||||
|
||||
SCENARIO("Power delivery state handling") {
|
||||
GIVEN("Bad case - Unknown session") {
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
message_20::PowerDeliveryRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
req.processing = dt::Processing::Ongoing;
|
||||
req.charge_progress = dt::Progress::Start;
|
||||
|
||||
const auto res = d20::state::handle_request(req, d20::Session(), false);
|
||||
|
||||
THEN("ResponseCode: FAILED_UnknownSession, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_UnknownSession);
|
||||
REQUIRE(res.status.has_value() == false);
|
||||
}
|
||||
}
|
||||
GIVEN("Not so bad case - WARNING_StandbyNotAllowed") {
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
message_20::PowerDeliveryRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
req.processing = dt::Processing::Ongoing;
|
||||
req.charge_progress = dt::Progress::Standby;
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, false);
|
||||
|
||||
// Right now standby ist not supported
|
||||
|
||||
THEN("ResponseCode: WARNING_StandbyNotAllowed, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::WARNING_StandbyNotAllowed);
|
||||
REQUIRE(res.status.has_value() == false);
|
||||
}
|
||||
}
|
||||
GIVEN("Good case") {
|
||||
}
|
||||
GIVEN("Bad case - EVPowerProfileInvalid") {
|
||||
}
|
||||
GIVEN("Bad case - ScheduleSelectionInvalid") {
|
||||
}
|
||||
GIVEN("Bad case - PowerDeliveryNotApplied") {
|
||||
} // TODO(sl): evse is not able to deliver energy
|
||||
|
||||
GIVEN("Bad case - PowerToleranceNotConfirmed") {
|
||||
} // TODO(sl): Scheduled Mode + Provided PowerTolerance in ScheduleExchangeRes
|
||||
GIVEN("Not so bad case - WARNING_PowerToleranceNotConfirmed") {
|
||||
} // TODO(sl): Scheduled Mode + Provided PowerTolerance in ScheduleExchangeRes
|
||||
GIVEN("Good case - OK_PowerToleranceConfirmed") {
|
||||
} // TODO(sl): Scheduled Mode + Provided PowerTolerance in ScheduleExchangeRes
|
||||
GIVEN("Bad case - AC ContactorError") {
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
message_20::PowerDeliveryRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
req.processing = dt::Processing::Ongoing;
|
||||
req.charge_progress = dt::Progress::Start;
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, true);
|
||||
|
||||
THEN("ResponseCode: FAILED_ContactorError, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_ContactorError);
|
||||
REQUIRE(res.status.has_value() == false);
|
||||
}
|
||||
} // TODO(sl): AC stuff
|
||||
|
||||
// GIVEN("Bad Case - sequence error") {} // TODO(sl): not here
|
||||
|
||||
// GIVEN("Bad Case - Performance Timeout") {} // TODO(sl): not here
|
||||
|
||||
// GIVEN("Bad Case - Sequence Timeout") {} // TODO(sl): not here
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/detail/d20/state/schedule_exchange.hpp>
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
namespace dt = message_20::datatypes;
|
||||
|
||||
SCENARIO("Schedule Exchange state handling") {
|
||||
|
||||
using Scheduled_ModeReq = message_20::datatypes::Scheduled_SEReqControlMode;
|
||||
using Scheduled_ModeRes = message_20::datatypes::Scheduled_SEResControlMode;
|
||||
using Dynamic_ModeReq = message_20::datatypes::Dynamic_SEReqControlMode;
|
||||
using Dynamic_ModeRes = message_20::datatypes::Dynamic_SEResControlMode;
|
||||
|
||||
GIVEN("Bad case - Unknown session") {
|
||||
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
message_20::ScheduleExchangeRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
req.control_mode.emplace<Scheduled_ModeReq>();
|
||||
|
||||
dt::RationalNumber max_power = {0, 0};
|
||||
|
||||
const auto res =
|
||||
d20::state::handle_request(req, d20::Session(), max_power, d20::UpdateDynamicModeParameters(), false);
|
||||
|
||||
THEN("ResponseCode: FAILED_UnknownSession, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_UnknownSession);
|
||||
|
||||
REQUIRE(res.processing == dt::Processing::Finished);
|
||||
|
||||
REQUIRE(std::holds_alternative<Dynamic_ModeRes>(res.control_mode) == true);
|
||||
const auto& res_control_mode = std::get<Dynamic_ModeRes>(res.control_mode);
|
||||
REQUIRE(std::holds_alternative<std::monostate>(res_control_mode.price_schedule) == true);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Bad case - False control mode") {
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::DC, dt::DcConnector::Extended, dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing);
|
||||
|
||||
auto session = d20::Session(service_parameters);
|
||||
|
||||
message_20::ScheduleExchangeRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
req.control_mode.emplace<Dynamic_ModeReq>();
|
||||
|
||||
dt::RationalNumber max_power = {0, 0};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, max_power, d20::UpdateDynamicModeParameters(), false);
|
||||
|
||||
THEN("ResponseCode: FAILED, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED);
|
||||
|
||||
REQUIRE(res.processing == dt::Processing::Finished);
|
||||
|
||||
REQUIRE(std::holds_alternative<Dynamic_ModeRes>(res.control_mode) == true);
|
||||
const auto& res_control_mode = std::get<Dynamic_ModeRes>(res.control_mode);
|
||||
REQUIRE(std::holds_alternative<std::monostate>(res_control_mode.price_schedule) == true);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - Scheduled Mode") {
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::DC, dt::DcConnector::Extended, dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing);
|
||||
|
||||
auto session = d20::Session(service_parameters);
|
||||
|
||||
message_20::ScheduleExchangeRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
req.control_mode.emplace<Scheduled_ModeReq>();
|
||||
|
||||
dt::RationalNumber max_power = {22, 3};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, max_power, d20::UpdateDynamicModeParameters(), false);
|
||||
|
||||
THEN("ResponseCode: OK") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
|
||||
REQUIRE(res.processing == dt::Processing::Finished);
|
||||
|
||||
REQUIRE(std::holds_alternative<Scheduled_ModeRes>(res.control_mode) == true);
|
||||
const auto& res_control_mode = std::get<Scheduled_ModeRes>(res.control_mode);
|
||||
|
||||
REQUIRE(res_control_mode.schedule_tuple.size() == 1);
|
||||
const auto& schedule_tuple = res_control_mode.schedule_tuple[0];
|
||||
|
||||
REQUIRE(dt::from_RationalNumber(schedule_tuple.charging_schedule.power_schedule.entries.at(0).power) ==
|
||||
22000);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - Dynamic Mode") {
|
||||
d20::SelectedServiceParameters service_parameters =
|
||||
d20::SelectedServiceParameters(dt::ServiceCategory::DC, dt::DcConnector::Extended, dt::ControlMode::Dynamic,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing);
|
||||
|
||||
auto session = d20::Session(service_parameters);
|
||||
|
||||
message_20::ScheduleExchangeRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
req.control_mode.emplace<Dynamic_ModeReq>();
|
||||
|
||||
dt::RationalNumber max_power = {22, 3};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, max_power, d20::UpdateDynamicModeParameters(), false);
|
||||
|
||||
THEN("ResponseCode: OK") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.processing == dt::Processing::Finished);
|
||||
|
||||
REQUIRE(std::holds_alternative<Dynamic_ModeRes>(res.control_mode) == true);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - MCS Dynamic Mode") {
|
||||
d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters(
|
||||
dt::ServiceCategory::MCS, dt::DcConnector::Extended, dt::ControlMode::Dynamic,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc, dt::Pricing::NoPricing);
|
||||
|
||||
auto session = d20::Session(service_parameters);
|
||||
|
||||
message_20::ScheduleExchangeRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
req.control_mode.emplace<Dynamic_ModeReq>();
|
||||
|
||||
dt::RationalNumber max_power = {22, 3};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, max_power, d20::UpdateDynamicModeParameters(), false);
|
||||
|
||||
THEN("ResponseCode: OK") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.processing == dt::Processing::Finished);
|
||||
|
||||
REQUIRE(std::holds_alternative<Dynamic_ModeRes>(res.control_mode) == true);
|
||||
}
|
||||
}
|
||||
|
||||
// GIVEN("Good case - setting new departure_time, target_soc & min_soc") // TODO(sl)
|
||||
|
||||
// GIVEN("Bad Case - sequence error") {} // TODO(sl): not here
|
||||
|
||||
// GIVEN("Performance Timeout") {} // TODO(sl): not here
|
||||
|
||||
// GIVEN("Sequence Timeout") {} // TODO(sl): not here
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/detail/d20/state/service_discovery.hpp>
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
namespace dt = message_20::datatypes;
|
||||
|
||||
SCENARIO("Service discovery state handling") {
|
||||
|
||||
GIVEN("Bad Case - Unknown session") {
|
||||
auto session = d20::Session();
|
||||
|
||||
message_20::ServiceDiscoveryRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
session = d20::Session();
|
||||
|
||||
std::vector<dt::ServiceCategory> energy_services = {dt::ServiceCategory::DC};
|
||||
std::vector<dt::ServiceCategory> ev_energy_services{};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, energy_services, {}, ev_energy_services);
|
||||
|
||||
THEN("ResponseCode: FAILED_UnknownSession, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_UnknownSession);
|
||||
REQUIRE(res.service_renegotiation_supported == false);
|
||||
REQUIRE(res.energy_transfer_service_list.size() == 1);
|
||||
REQUIRE(res.energy_transfer_service_list[0].free_service == false);
|
||||
REQUIRE(res.energy_transfer_service_list[0].service_id == dt::ServiceCategory::AC);
|
||||
REQUIRE(res.vas_list.has_value() == false);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good Case - Setting ac services") {
|
||||
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
message_20::ServiceDiscoveryRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
std::vector<dt::ServiceCategory> supported_energy_transfer_services = {dt::ServiceCategory::AC,
|
||||
dt::ServiceCategory::AC_BPT};
|
||||
std::vector<dt::ServiceCategory> ev_energy_services{};
|
||||
|
||||
const auto res =
|
||||
d20::state::handle_request(req, session, supported_energy_transfer_services, {}, ev_energy_services);
|
||||
|
||||
THEN("ResponseCode: OK, energy_transfer_service_list: AC & AC_WPT, vaslist: empty") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.service_renegotiation_supported == false);
|
||||
REQUIRE(res.energy_transfer_service_list.size() == 2);
|
||||
REQUIRE(res.energy_transfer_service_list[0].free_service == false);
|
||||
REQUIRE((res.energy_transfer_service_list[0].service_id == dt::ServiceCategory::AC ||
|
||||
res.energy_transfer_service_list[0].service_id == dt::ServiceCategory::AC_BPT));
|
||||
REQUIRE(res.energy_transfer_service_list[1].free_service == false);
|
||||
REQUIRE((res.energy_transfer_service_list[1].service_id == dt::ServiceCategory::AC ||
|
||||
res.energy_transfer_service_list[1].service_id == dt::ServiceCategory::AC_BPT));
|
||||
REQUIRE(res.vas_list.has_value() == false);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good Case - Setting dc services") {
|
||||
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
message_20::ServiceDiscoveryRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
std::vector<dt::ServiceCategory> supported_energy_transfer_services = {dt::ServiceCategory::DC,
|
||||
dt::ServiceCategory::DC_BPT};
|
||||
std::vector<dt::ServiceCategory> ev_energy_services{};
|
||||
|
||||
const auto res =
|
||||
d20::state::handle_request(req, session, supported_energy_transfer_services, {}, ev_energy_services);
|
||||
|
||||
THEN("ResponseCode: OK, energy_transfer_service_list: DC & DC_WPT, vaslist: empty") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.service_renegotiation_supported == false);
|
||||
REQUIRE(res.energy_transfer_service_list.size() == 2);
|
||||
REQUIRE(res.energy_transfer_service_list[0].free_service == false);
|
||||
REQUIRE((res.energy_transfer_service_list[0].service_id == dt::ServiceCategory::DC ||
|
||||
res.energy_transfer_service_list[0].service_id == dt::ServiceCategory::DC_BPT));
|
||||
REQUIRE(res.energy_transfer_service_list[1].free_service == false);
|
||||
REQUIRE((res.energy_transfer_service_list[1].service_id == dt::ServiceCategory::DC ||
|
||||
res.energy_transfer_service_list[1].service_id == dt::ServiceCategory::DC_BPT));
|
||||
REQUIRE(res.vas_list.has_value() == false);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good Case - Setting MCS services") {
|
||||
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
message_20::ServiceDiscoveryRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
std::vector<dt::ServiceCategory> supported_energy_transfer_services = {dt::ServiceCategory::MCS,
|
||||
dt::ServiceCategory::MCS_BPT};
|
||||
std::vector<dt::ServiceCategory> ev_energy_services{};
|
||||
|
||||
const auto res =
|
||||
d20::state::handle_request(req, session, supported_energy_transfer_services, {}, ev_energy_services);
|
||||
|
||||
THEN("ResponseCode: OK, energy_transfer_service_list: MCS & MCS_BPT, vaslist: empty") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.service_renegotiation_supported == false);
|
||||
REQUIRE(res.energy_transfer_service_list.size() == 2);
|
||||
REQUIRE(res.energy_transfer_service_list[0].free_service == false);
|
||||
REQUIRE((res.energy_transfer_service_list[0].service_id == dt::ServiceCategory::MCS or
|
||||
res.energy_transfer_service_list[0].service_id == dt::ServiceCategory::MCS_BPT));
|
||||
REQUIRE(res.energy_transfer_service_list[1].free_service == false);
|
||||
REQUIRE((res.energy_transfer_service_list[1].service_id == dt::ServiceCategory::MCS or
|
||||
res.energy_transfer_service_list[1].service_id == dt::ServiceCategory::MCS_BPT));
|
||||
REQUIRE(res.vas_list.has_value() == false);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good Case - Setting services + vas list") {
|
||||
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
message_20::ServiceDiscoveryRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
std::vector<dt::ServiceCategory> supported_energy_transfer_services = {dt::ServiceCategory::DC,
|
||||
dt::ServiceCategory::DC_BPT};
|
||||
std::vector<uint16_t> supported_vas_services = {
|
||||
message_20::to_underlying_value(dt::ServiceCategory::ParkingStatus)};
|
||||
std::vector<dt::ServiceCategory> ev_energy_services{};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, supported_energy_transfer_services,
|
||||
supported_vas_services, ev_energy_services);
|
||||
|
||||
THEN("ResponseCode: OK, energy_transfer_service_list: DC & DC_BPT, vaslist: ParkingStatus") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.service_renegotiation_supported == false);
|
||||
REQUIRE(res.energy_transfer_service_list.size() == 2);
|
||||
REQUIRE(res.energy_transfer_service_list[0].free_service == false);
|
||||
REQUIRE((res.energy_transfer_service_list[0].service_id == dt::ServiceCategory::DC ||
|
||||
res.energy_transfer_service_list[0].service_id == dt::ServiceCategory::DC_BPT));
|
||||
REQUIRE(res.energy_transfer_service_list[1].free_service == false);
|
||||
REQUIRE((res.energy_transfer_service_list[1].service_id == dt::ServiceCategory::DC ||
|
||||
res.energy_transfer_service_list[1].service_id == dt::ServiceCategory::DC_BPT));
|
||||
REQUIRE(res.vas_list.has_value() == true);
|
||||
REQUIRE(res.vas_list.value()[0].free_service == false);
|
||||
REQUIRE(res.vas_list.value()[0].service_id ==
|
||||
message_20::to_underlying_value(dt::ServiceCategory::ParkingStatus));
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good Case - Filter supported_service_providers") {
|
||||
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
message_20::ServiceDiscoveryRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
|
||||
auto& supported_service_ids = req.supported_service_ids.emplace();
|
||||
supported_service_ids.push_back(2);
|
||||
supported_service_ids.push_back(65);
|
||||
|
||||
std::vector<dt::ServiceCategory> supported_energy_transfer_services = {dt::ServiceCategory::DC,
|
||||
dt::ServiceCategory::DC_BPT};
|
||||
std::vector<uint16_t> supported_vas_services = {
|
||||
message_20::to_underlying_value(dt::ServiceCategory::ParkingStatus)};
|
||||
std::vector<dt::ServiceCategory> ev_energy_services{};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session, supported_energy_transfer_services,
|
||||
supported_vas_services, ev_energy_services);
|
||||
|
||||
THEN("ResponseCode: OK, energy_transfer_service_list: DC, vaslist: empty") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(res.service_renegotiation_supported == false);
|
||||
REQUIRE(res.energy_transfer_service_list.size() == 1);
|
||||
REQUIRE(res.energy_transfer_service_list[0].free_service == false);
|
||||
REQUIRE(res.energy_transfer_service_list[0].service_id == dt::ServiceCategory::DC);
|
||||
REQUIRE(res.vas_list.has_value() == false);
|
||||
}
|
||||
}
|
||||
|
||||
// [V2G20-1644]
|
||||
GIVEN("Good case - Resuming secc shall provide the same service ids and parameter set ids "
|
||||
"(ServiceRenegotiationSupported: false)") {
|
||||
} // Todo(sl): Fill out
|
||||
|
||||
GIVEN("Bad case - EV supported_service_ids do not match with evse supported services") {
|
||||
} // Todo(sl): Fill out
|
||||
|
||||
// GIVEN("Bad Case - sequence error") {} // TODO(sl): not here
|
||||
|
||||
// GIVEN("Performance Timeout") {} // TODO(sl): not here
|
||||
|
||||
// GIVEN("Sequence Timeout") {} // TODO(sl): not here
|
||||
}
|
||||
@@ -0,0 +1,421 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/detail/d20/state/service_selection.hpp>
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
namespace dt = message_20::datatypes;
|
||||
|
||||
SCENARIO("Service selection state handling") {
|
||||
GIVEN("Bad case - Unknown session") {
|
||||
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
message_20::ServiceSelectionRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
req.selected_energy_transfer_service.service_id = dt::ServiceCategory::DC_BPT;
|
||||
req.selected_energy_transfer_service.parameter_set_id = 0;
|
||||
|
||||
session = d20::Session();
|
||||
|
||||
const auto res = d20::state::handle_request(req, session);
|
||||
|
||||
THEN("ResponseCode: FAILED_UnknownSession, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_UnknownSession);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Bad case: selected_energy_transfer_service false parameter set id - FAILED_ServiceSelectionInvalid") {
|
||||
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
session.offered_services.energy_services = {dt::ServiceCategory::DC};
|
||||
session.offered_services.dc_parameter_list[0] = {
|
||||
dt::DcConnector::Extended,
|
||||
dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc,
|
||||
dt::Pricing::NoPricing,
|
||||
};
|
||||
|
||||
message_20::ServiceSelectionRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
req.selected_energy_transfer_service.service_id = dt::ServiceCategory::DC;
|
||||
req.selected_energy_transfer_service.parameter_set_id = 1;
|
||||
|
||||
const auto res = d20::state::handle_request(req, session);
|
||||
|
||||
THEN("ResponseCode: FAILED_ServiceSelectionInvalid, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_ServiceSelectionInvalid);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Bad case: selected_energy_transfer service is not correct - FAILED_NoEnergyTransferServiceSelected") {
|
||||
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
session.offered_services.energy_services = {dt::ServiceCategory::DC};
|
||||
session.offered_services.dc_parameter_list[0] = {
|
||||
dt::DcConnector::Extended,
|
||||
dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc,
|
||||
dt::Pricing::NoPricing,
|
||||
};
|
||||
|
||||
message_20::ServiceSelectionRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
req.selected_energy_transfer_service.service_id = dt::ServiceCategory::AC;
|
||||
req.selected_energy_transfer_service.parameter_set_id = 0;
|
||||
|
||||
const auto res = d20::state::handle_request(req, session);
|
||||
|
||||
THEN("ResponseCode: FAILED_NoEnergyTransferServiceSelected, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_NoEnergyTransferServiceSelected);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case") {
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
session.offered_services.energy_services = {dt::ServiceCategory::DC};
|
||||
session.offered_services.dc_parameter_list[0] = {
|
||||
dt::DcConnector::Extended,
|
||||
dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc,
|
||||
dt::Pricing::NoPricing,
|
||||
};
|
||||
|
||||
message_20::ServiceSelectionRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
req.selected_energy_transfer_service.service_id = dt::ServiceCategory::DC;
|
||||
req.selected_energy_transfer_service.parameter_set_id = 0;
|
||||
|
||||
const auto res = d20::state::handle_request(req, session);
|
||||
|
||||
THEN("ResponseCode: OK") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - Check if session variables is set") {
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
session.offered_services.energy_services = {dt::ServiceCategory::DC};
|
||||
session.offered_services.dc_parameter_list[0] = {
|
||||
dt::DcConnector::Extended,
|
||||
dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc,
|
||||
dt::Pricing::NoPricing,
|
||||
};
|
||||
|
||||
message_20::ServiceSelectionRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
req.selected_energy_transfer_service.service_id = dt::ServiceCategory::DC;
|
||||
req.selected_energy_transfer_service.parameter_set_id = 0;
|
||||
|
||||
const auto res = d20::state::handle_request(req, session);
|
||||
|
||||
THEN("ResponseCode: OK") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
|
||||
const auto selected_services = session.get_selected_services();
|
||||
REQUIRE(selected_services.selected_energy_service == dt::ServiceCategory::DC);
|
||||
REQUIRE(selected_services.selected_control_mode == dt::ControlMode::Scheduled);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - DC_BPT") {
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
session.offered_services.energy_services = {dt::ServiceCategory::DC_BPT};
|
||||
session.offered_services.dc_bpt_parameter_list[0] = {{
|
||||
dt::DcConnector::Extended,
|
||||
dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc,
|
||||
dt::Pricing::NoPricing,
|
||||
},
|
||||
dt::BptChannel::Unified,
|
||||
dt::GeneratorMode::GridFollowing};
|
||||
|
||||
message_20::ServiceSelectionRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
req.selected_energy_transfer_service.service_id = dt::ServiceCategory::DC_BPT;
|
||||
req.selected_energy_transfer_service.parameter_set_id = 0;
|
||||
|
||||
const auto res = d20::state::handle_request(req, session);
|
||||
|
||||
THEN("ResponseCode: OK") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
|
||||
const auto selected_services = session.get_selected_services();
|
||||
REQUIRE(selected_services.selected_energy_service == dt::ServiceCategory::DC_BPT);
|
||||
REQUIRE(selected_services.selected_control_mode == dt::ControlMode::Scheduled);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Bad case: selected_vas_list false service id - FAILED_ServiceSelectionInvalid") {
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
session.offered_services.energy_services = {dt::ServiceCategory::DC};
|
||||
session.offered_services.dc_parameter_list[0] = {
|
||||
dt::DcConnector::Extended,
|
||||
dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc,
|
||||
dt::Pricing::NoPricing,
|
||||
};
|
||||
|
||||
session.offered_services.vas_services = {message_20::to_underlying_value(dt::ServiceCategory::Internet)};
|
||||
session.offered_services.internet_parameter_list[0] = {
|
||||
dt::Protocol::Http,
|
||||
dt::Port::Port80,
|
||||
};
|
||||
|
||||
message_20::ServiceSelectionRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
req.selected_energy_transfer_service.service_id = dt::ServiceCategory::DC;
|
||||
req.selected_energy_transfer_service.parameter_set_id = 0;
|
||||
|
||||
req.selected_vas_list = {{message_20::to_underlying_value(dt::ServiceCategory::ParkingStatus), 0}};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session);
|
||||
|
||||
THEN("ResponseCode: FAILED_ServiceSelectionInvalid, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_ServiceSelectionInvalid);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Bad case: selected_vas_list false parameter set id - FAILED_ServiceSelectionInvalid") {
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
session.offered_services.energy_services = {dt::ServiceCategory::DC};
|
||||
session.offered_services.dc_parameter_list[0] = {
|
||||
dt::DcConnector::Extended,
|
||||
dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc,
|
||||
dt::Pricing::NoPricing,
|
||||
};
|
||||
|
||||
session.offered_services.vas_services = {message_20::to_underlying_value(dt::ServiceCategory::Internet)};
|
||||
session.offered_services.internet_parameter_list[0] = {
|
||||
dt::Protocol::Http,
|
||||
dt::Port::Port80,
|
||||
};
|
||||
|
||||
message_20::ServiceSelectionRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
req.selected_energy_transfer_service.service_id = dt::ServiceCategory::DC;
|
||||
req.selected_energy_transfer_service.parameter_set_id = 0;
|
||||
|
||||
req.selected_vas_list = {{message_20::to_underlying_value(dt::ServiceCategory::Internet), 1}};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session);
|
||||
|
||||
THEN("ResponseCode: FAILED_ServiceSelectionInvalid, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_ServiceSelectionInvalid);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - DC & Internet & Parking") {
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
session.offered_services.energy_services = {dt::ServiceCategory::DC};
|
||||
session.offered_services.dc_parameter_list[0] = {
|
||||
dt::DcConnector::Extended,
|
||||
dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc,
|
||||
dt::Pricing::NoPricing,
|
||||
};
|
||||
|
||||
session.offered_services.vas_services = {message_20::to_underlying_value(dt::ServiceCategory::Internet),
|
||||
message_20::to_underlying_value(dt::ServiceCategory::ParkingStatus)};
|
||||
session.offered_services.internet_parameter_list[0] = {
|
||||
dt::Protocol::Http,
|
||||
dt::Port::Port80,
|
||||
};
|
||||
|
||||
session.offered_services.parking_parameter_list[0] = {
|
||||
dt::IntendedService::VehicleCheckIn,
|
||||
dt::ParkingStatus::ManualExternal,
|
||||
};
|
||||
|
||||
message_20::ServiceSelectionRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
req.selected_energy_transfer_service.service_id = dt::ServiceCategory::DC;
|
||||
req.selected_energy_transfer_service.parameter_set_id = 0;
|
||||
|
||||
req.selected_vas_list = {{message_20::to_underlying_value(dt::ServiceCategory::Internet), 0},
|
||||
{message_20::to_underlying_value(dt::ServiceCategory::ParkingStatus), 0}};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session);
|
||||
|
||||
THEN("ResponseCode: OK") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - AC") {
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
session.offered_services.energy_services = {dt::ServiceCategory::AC};
|
||||
session.offered_services.ac_parameter_list[0] = {
|
||||
dt::AcConnector::ThreePhase, dt::ControlMode::Scheduled, dt::MobilityNeedsMode::ProvidedByEvcc, 230,
|
||||
dt::Pricing::NoPricing,
|
||||
};
|
||||
|
||||
message_20::ServiceSelectionRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
req.selected_energy_transfer_service.service_id = dt::ServiceCategory::AC;
|
||||
req.selected_energy_transfer_service.parameter_set_id = 0;
|
||||
|
||||
const auto res = d20::state::handle_request(req, session);
|
||||
|
||||
THEN("ResponseCode: OK") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - AC_BPT") {
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
session.offered_services.energy_services = {dt::ServiceCategory::AC_BPT};
|
||||
session.offered_services.ac_bpt_parameter_list[0] = {{
|
||||
dt::AcConnector::ThreePhase,
|
||||
dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc,
|
||||
230,
|
||||
dt::Pricing::NoPricing,
|
||||
},
|
||||
dt::BptChannel::Unified,
|
||||
dt::GeneratorMode::GridFollowing,
|
||||
dt::GridCodeIslandingDetectionMethod::Passive};
|
||||
|
||||
message_20::ServiceSelectionRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
req.selected_energy_transfer_service.service_id = dt::ServiceCategory::AC_BPT;
|
||||
req.selected_energy_transfer_service.parameter_set_id = 0;
|
||||
|
||||
const auto res = d20::state::handle_request(req, session);
|
||||
|
||||
THEN("ResponseCode: OK") {
|
||||
const auto selected_services = session.get_selected_services();
|
||||
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
REQUIRE(selected_services.selected_energy_service == dt::ServiceCategory::AC_BPT);
|
||||
REQUIRE(selected_services.selected_control_mode == dt::ControlMode::Scheduled);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - MCS") {
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
session.offered_services.energy_services = {dt::ServiceCategory::MCS};
|
||||
session.offered_services.mcs_parameter_list[0] = {
|
||||
dt::McsConnector::Mcs,
|
||||
dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc,
|
||||
dt::Pricing::NoPricing,
|
||||
};
|
||||
|
||||
message_20::ServiceSelectionRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
req.selected_energy_transfer_service.service_id = dt::ServiceCategory::MCS;
|
||||
req.selected_energy_transfer_service.parameter_set_id = 0;
|
||||
|
||||
const auto res = d20::state::handle_request(req, session);
|
||||
|
||||
THEN("ResponseCode: OK") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
|
||||
const auto selected_services = session.get_selected_services();
|
||||
REQUIRE(selected_services.selected_energy_service == dt::ServiceCategory::MCS);
|
||||
REQUIRE(selected_services.selected_control_mode == dt::ControlMode::Scheduled);
|
||||
REQUIRE(*std::get_if<dt::McsConnector>(&selected_services.selected_connector) == dt::McsConnector::Mcs);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - MCS_BPT") {
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
session.offered_services.energy_services = {dt::ServiceCategory::MCS_BPT};
|
||||
session.offered_services.mcs_bpt_parameter_list[0] = {{
|
||||
dt::McsConnector::Mcs,
|
||||
dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc,
|
||||
dt::Pricing::NoPricing,
|
||||
},
|
||||
dt::BptChannel::Unified,
|
||||
dt::GeneratorMode::GridFollowing};
|
||||
|
||||
message_20::ServiceSelectionRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
req.selected_energy_transfer_service.service_id = dt::ServiceCategory::MCS_BPT;
|
||||
req.selected_energy_transfer_service.parameter_set_id = 0;
|
||||
|
||||
const auto res = d20::state::handle_request(req, session);
|
||||
|
||||
THEN("ResponseCode: OK") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
|
||||
const auto selected_services = session.get_selected_services();
|
||||
REQUIRE(selected_services.selected_energy_service == dt::ServiceCategory::MCS_BPT);
|
||||
REQUIRE(selected_services.selected_control_mode == dt::ControlMode::Scheduled);
|
||||
REQUIRE(*std::get_if<dt::McsConnector>(&selected_services.selected_connector) == dt::McsConnector::Mcs);
|
||||
const auto bpt_channel = selected_services.selected_bpt_channel.has_value() and
|
||||
selected_services.selected_bpt_channel.value() == dt::BptChannel::Unified;
|
||||
REQUIRE(bpt_channel == true);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good case - DC & Custom VAS") {
|
||||
d20::Session session = d20::Session();
|
||||
|
||||
session.offered_services.energy_services = {dt::ServiceCategory::DC};
|
||||
session.offered_services.dc_parameter_list[0] = {
|
||||
dt::DcConnector::Extended,
|
||||
dt::ControlMode::Scheduled,
|
||||
dt::MobilityNeedsMode::ProvidedByEvcc,
|
||||
dt::Pricing::NoPricing,
|
||||
};
|
||||
|
||||
session.offered_services.vas_services = {4599};
|
||||
session.offered_services.custom_vas_list[4599] = {0, 2};
|
||||
|
||||
message_20::ServiceSelectionRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
req.selected_energy_transfer_service.service_id = dt::ServiceCategory::DC;
|
||||
req.selected_energy_transfer_service.parameter_set_id = 0;
|
||||
|
||||
req.selected_vas_list = {
|
||||
{4599, 0},
|
||||
};
|
||||
|
||||
const auto res = d20::state::handle_request(req, session);
|
||||
|
||||
THEN("ResponseCode: OK") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
}
|
||||
}
|
||||
|
||||
// GIVEN("Bad case - FAILED_NoServiceRenegotiationSupported") {} // todo(sl): pause/resume not supported yet
|
||||
|
||||
// GIVEN("Bad Case - sequence error") {} // TODO(sl): not here
|
||||
|
||||
// GIVEN("Bad Case - Performance Timeout") {} // TODO(sl): not here
|
||||
|
||||
// GIVEN("Bad Case - Sequence Timeout") {} // TODO(sl): not here
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iso15118/detail/d20/state/session_stop.hpp>
|
||||
|
||||
using namespace iso15118;
|
||||
|
||||
namespace dt = message_20::datatypes;
|
||||
|
||||
SCENARIO("Session Stop state handling") {
|
||||
|
||||
GIVEN("Bad case - Unknown session") {
|
||||
|
||||
auto session = d20::Session();
|
||||
|
||||
message_20::SessionStopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
req.charging_session = dt::ChargingSession::Terminate;
|
||||
|
||||
const auto res = d20::state::handle_request(req, d20::Session());
|
||||
|
||||
THEN("ResponseCode: FAILED_UnknownSession, mandatory fields should be set") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_UnknownSession);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Good Case") {
|
||||
|
||||
auto session = d20::Session();
|
||||
|
||||
message_20::SessionStopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
req.charging_session = dt::ChargingSession::Terminate;
|
||||
|
||||
const auto res = d20::state::handle_request(req, session);
|
||||
|
||||
THEN("ResponseCode: OK") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::OK);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Bad case - FAILED_NoServiceRenegotiationSupported") {
|
||||
auto session = d20::Session();
|
||||
|
||||
session.service_renegotiation_supported = false;
|
||||
|
||||
message_20::SessionStopRequest req;
|
||||
req.header.session_id = session.get_id();
|
||||
req.header.timestamp = 1691411798;
|
||||
req.charging_session = dt::ChargingSession::ServiceRenegotiation;
|
||||
|
||||
const auto res = d20::state::handle_request(req, session);
|
||||
|
||||
THEN("ResponseCode: OK") {
|
||||
REQUIRE(res.response_code == dt::ResponseCode::FAILED_NoServiceRenegotiationSupported);
|
||||
}
|
||||
}
|
||||
|
||||
// GIVEN("Bad case - Dynamic mode, FAILED_PauseNotAllowed") {} // TODO(sl): EVCC requests Pause, but Secc didnt
|
||||
// initiated
|
||||
|
||||
// GIVEN("Bad case - Scheduled mode , FAILED_PauseNotAllowed") {} // TODO(sl): Check current EVPowerProfileEntry for
|
||||
// 0kW
|
||||
|
||||
// GIVEN("Bad case - State B was not measuered -> FAILED") {} // TODO(sl): check evse_manager?
|
||||
|
||||
// GIVEN("Bad Case - sequence error") {} // TODO(sl): not here
|
||||
|
||||
// GIVEN("Bad Case - Performance Timeout") {} // TODO(sl): not here
|
||||
|
||||
// GIVEN("Bad Case - Sequence Timeout") {} // TODO(sl): not here
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
add_executable(test_v2gtp v2gtp.cpp)
|
||||
target_link_libraries(test_v2gtp
|
||||
PRIVATE
|
||||
cbv2g::tp
|
||||
Catch2::Catch2WithMain
|
||||
)
|
||||
|
||||
include(Catch)
|
||||
catch_discover_tests(test_v2gtp)
|
||||
17
tools/EVerest-main/lib/everest/iso15118/test/v2gtp/v2gtp.cpp
Normal file
17
tools/EVerest-main/lib/everest/iso15118/test/v2gtp/v2gtp.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <cbv2g/exi_v2gtp.h>
|
||||
|
||||
SCENARIO("V2GTP header test") {
|
||||
GIVEN("A binary representation of a valid (pre -20) V2GTP header") {
|
||||
uint8_t header[] = {0x01, 0xfe, 0x80, 0x01, 0x00, 0x00, 0x00, 0x24};
|
||||
|
||||
THEN("It should be decoded succussfully") {
|
||||
uint32_t payload_len;
|
||||
auto ec = V2GTP_ReadHeader(header, &payload_len);
|
||||
|
||||
REQUIRE(ec == V2GTP_ERROR__NO_ERROR);
|
||||
REQUIRE(payload_len == 0x24);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user