diff --git a/include/iso15118/session/feedback.hpp b/include/iso15118/session/feedback.hpp index 8b5a68dd..222df96e 100644 --- a/include/iso15118/session/feedback.hpp +++ b/include/iso15118/session/feedback.hpp @@ -86,6 +86,7 @@ struct Callbacks { std::function selected_vas_services; std::function ac_limits; std::function ev_termination; + std::function response_code; }; } // namespace feedback @@ -114,6 +115,7 @@ class Feedback { void selected_vas_services(const dt::VasSelectedServiceList&) const; void ac_limits(const feedback::AcLimits&) const; void ev_termination(const std::string&, const std::string&) const; + void response_code(const iso15118::message_20::datatypes::ResponseCode&) const; private: feedback::Callbacks callbacks; diff --git a/src/iso15118/d20/state/ac_charge_loop.cpp b/src/iso15118/d20/state/ac_charge_loop.cpp index 0d4eb9dd..059e04f5 100644 --- a/src/iso15118/d20/state/ac_charge_loop.cpp +++ b/src/iso15118/d20/state/ac_charge_loop.cpp @@ -203,6 +203,7 @@ Result AC_ChargeLoop::feed(Event ev) { const auto res = handle_request(*req, m_ctx.session, false); m_ctx.respond(res); + m_ctx.feedback.response_code(res.response_code); if (res.response_code >= dt::ResponseCode::FAILED) { m_ctx.session_stopped = true; @@ -227,6 +228,7 @@ Result AC_ChargeLoop::feed(Event ev) { present_powers, dynamic_parameters); m_ctx.respond(res); + m_ctx.feedback.response_code(res.response_code); if (res.response_code >= dt::ResponseCode::FAILED) { m_ctx.session_stopped = true; @@ -247,6 +249,7 @@ Result AC_ChargeLoop::feed(Event ev) { const message_20::Type req_type = variant->get_type(); send_sequence_error(req_type, m_ctx); + m_ctx.feedback.response_code(dt::ResponseCode::FAILED_SequenceError); m_ctx.session_stopped = true; return {}; } diff --git a/src/iso15118/d20/state/ac_charge_parameter_discovery.cpp b/src/iso15118/d20/state/ac_charge_parameter_discovery.cpp index b84e1cf8..57f2f476 100644 --- a/src/iso15118/d20/state/ac_charge_parameter_discovery.cpp +++ b/src/iso15118/d20/state/ac_charge_parameter_discovery.cpp @@ -132,6 +132,7 @@ Result AC_ChargeParameterDiscovery::feed(Event ev) { const auto res = handle_request(*req, m_ctx.session, m_ctx.session_config.ac_limits, present_powers); m_ctx.respond(res); + m_ctx.feedback.response_code(res.response_code); if (res.response_code >= message_20::datatypes::ResponseCode::FAILED) { m_ctx.session_stopped = true; @@ -147,6 +148,7 @@ Result AC_ChargeParameterDiscovery::feed(Event ev) { m_ctx.respond(res); m_ctx.session_stopped = true; + m_ctx.feedback.response_code(res.response_code); return {}; } else { @@ -156,6 +158,7 @@ Result AC_ChargeParameterDiscovery::feed(Event ev) { // Sequence Error const message_20::Type req_type = variant->get_type(); send_sequence_error(req_type, m_ctx); + m_ctx.feedback.response_code(dt::ResponseCode::FAILED_SequenceError); return {}; } diff --git a/src/iso15118/d20/state/authorization.cpp b/src/iso15118/d20/state/authorization.cpp index 61140a9e..969c1e29 100644 --- a/src/iso15118/d20/state/authorization.cpp +++ b/src/iso15118/d20/state/authorization.cpp @@ -123,6 +123,7 @@ Result Authorization::feed(Event ev) { const auto res = handle_request(*req, m_ctx.session, authorization_status, timeout_ongoing_reached); m_ctx.respond(res); + m_ctx.feedback.response_code(res.response_code); if (res.response_code >= dt::ResponseCode::FAILED) { m_ctx.session_stopped = true; @@ -141,6 +142,8 @@ Result Authorization::feed(Event ev) { m_ctx.respond(res); m_ctx.session_stopped = true; + m_ctx.feedback.response_code(res.response_code); + return {}; } else { m_ctx.log("expected AuthorizationReq! But code type id: %d", variant->get_type()); @@ -149,6 +152,7 @@ Result Authorization::feed(Event ev) { const message_20::Type req_type = variant->get_type(); send_sequence_error(req_type, m_ctx); + m_ctx.feedback.response_code(dt::ResponseCode::FAILED_SequenceError); m_ctx.session_stopped = true; return {}; } diff --git a/src/iso15118/d20/state/authorization_setup.cpp b/src/iso15118/d20/state/authorization_setup.cpp index 2ac18ad2..2bbc70e3 100644 --- a/src/iso15118/d20/state/authorization_setup.cpp +++ b/src/iso15118/d20/state/authorization_setup.cpp @@ -72,6 +72,7 @@ Result AuthorizationSetup::feed(Event ev) { logf_info("Timestamp: %d", req->header.timestamp); m_ctx.respond(res); + m_ctx.feedback.response_code(res.response_code); if (res.response_code >= dt::ResponseCode::FAILED) { m_ctx.session_stopped = true; @@ -87,6 +88,7 @@ Result AuthorizationSetup::feed(Event ev) { m_ctx.respond(res); m_ctx.session_stopped = true; + m_ctx.feedback.response_code(res.response_code); return {}; } else { @@ -96,6 +98,7 @@ Result AuthorizationSetup::feed(Event ev) { const message_20::Type req_type = variant->get_type(); send_sequence_error(req_type, m_ctx); + m_ctx.feedback.response_code(dt::ResponseCode::FAILED_SequenceError); m_ctx.session_stopped = true; return {}; } diff --git a/src/iso15118/d20/state/dc_cable_check.cpp b/src/iso15118/d20/state/dc_cable_check.cpp index 1b6f0da4..c1ea8e8d 100644 --- a/src/iso15118/d20/state/dc_cable_check.cpp +++ b/src/iso15118/d20/state/dc_cable_check.cpp @@ -63,6 +63,7 @@ Result DC_CableCheck::feed(Event ev) { const auto res = handle_request(*req, m_ctx.session, cable_check_done); m_ctx.respond(res); + m_ctx.feedback.response_code(res.response_code); if (res.response_code >= dt::ResponseCode::FAILED) { m_ctx.session_stopped = true; @@ -79,6 +80,7 @@ Result DC_CableCheck::feed(Event ev) { m_ctx.respond(res); m_ctx.session_stopped = true; + m_ctx.feedback.response_code(res.response_code); return {}; } else { @@ -88,6 +90,7 @@ Result DC_CableCheck::feed(Event ev) { const message_20::Type req_type = variant->get_type(); send_sequence_error(req_type, m_ctx); + m_ctx.feedback.response_code(dt::ResponseCode::FAILED_SequenceError); m_ctx.session_stopped = true; return {}; } diff --git a/src/iso15118/d20/state/dc_charge_loop.cpp b/src/iso15118/d20/state/dc_charge_loop.cpp index e7a93d55..a54fe15a 100644 --- a/src/iso15118/d20/state/dc_charge_loop.cpp +++ b/src/iso15118/d20/state/dc_charge_loop.cpp @@ -222,6 +222,7 @@ Result DC_ChargeLoop::feed(Event ev) { const auto res = handle_request(*req, m_ctx.session, false); m_ctx.respond(res); + m_ctx.feedback.response_code(res.response_code); if (res.response_code >= dt::ResponseCode::FAILED) { m_ctx.session_stopped = true; @@ -250,6 +251,7 @@ Result DC_ChargeLoop::feed(Event ev) { m_ctx.session_config.dc_limits, dynamic_parameters); m_ctx.respond(res); + m_ctx.feedback.response_code(res.response_code); if (res.response_code >= dt::ResponseCode::FAILED) { m_ctx.session_stopped = true; @@ -271,6 +273,7 @@ Result DC_ChargeLoop::feed(Event ev) { const message_20::Type req_type = variant->get_type(); send_sequence_error(req_type, m_ctx); + m_ctx.feedback.response_code(dt::ResponseCode::FAILED_SequenceError); m_ctx.session_stopped = true; return {}; } diff --git a/src/iso15118/d20/state/dc_charge_parameter_discovery.cpp b/src/iso15118/d20/state/dc_charge_parameter_discovery.cpp index 028a2cef..e63d8d24 100644 --- a/src/iso15118/d20/state/dc_charge_parameter_discovery.cpp +++ b/src/iso15118/d20/state/dc_charge_parameter_discovery.cpp @@ -42,6 +42,91 @@ template <> void convert(BPT_DC_ModeRes& out, const d20::DcTransferLimits& in) { } } +template +bool handle_compatibility_check(const d20::DcTransferLimits& evse_dc_limits, const DCMode& ev_limits) { + // In IEC 61851-23-3 a compatibility check is required + + constexpr auto MAX_VOLTAGE_OFFSET = 50.f; + constexpr auto MAX_VOLTAGE_THREASHOLD = 500.f; + constexpr auto MAX_VOLTAGE_FACTOR = 1.1f; + constexpr auto MAX_POWER_LIMIT = 200000.f; + bool compatiblity_flag = true; + float ev_max_power; + float ev_max_current; + float ev_max_voltage; + + if (const auto* mode = std::get_if(&ev_limits)) { + ev_max_power = dt::from_RationalNumber(mode->max_charge_power); + ev_max_current = dt::from_RationalNumber(mode->max_charge_current); + ev_max_voltage = dt::from_RationalNumber(mode->max_voltage); + } else if (const auto* mode = std::get_if(&ev_limits)) { + ev_max_power = dt::from_RationalNumber(mode->max_charge_power); + ev_max_current = dt::from_RationalNumber(mode->max_charge_current); + ev_max_voltage = dt::from_RationalNumber(mode->max_voltage); + } else { + return false; + } + + const float evse_max_voltage = dt::from_RationalNumber(evse_dc_limits.voltage.max); + const float evse_min_voltage = dt::from_RationalNumber(evse_dc_limits.voltage.min); + const float evse_max_current = dt::from_RationalNumber(evse_dc_limits.charge_limits.current.max); + const float evse_min_current = dt::from_RationalNumber(evse_dc_limits.charge_limits.current.min); + const float evse_max_power = dt::from_RationalNumber(evse_dc_limits.charge_limits.power.max); + const float evse_min_power = dt::from_RationalNumber(evse_dc_limits.charge_limits.power.min); + + // CC.5.6 2.a + if (ev_max_voltage <= MAX_VOLTAGE_THREASHOLD) { + if (evse_max_voltage > + std::min({ev_max_voltage + MAX_VOLTAGE_OFFSET, evse_max_voltage, MAX_VOLTAGE_THREASHOLD})) { + if (evse_max_voltage > MAX_VOLTAGE_THREASHOLD) { + logf_error("EVSE max voltage %.1f V > EV max voltage 500V", evse_max_voltage); + } else { + logf_error("EVSE max voltage %.1f V > EV max voltage + 50V: %.1f V", evse_max_voltage, + ev_max_voltage + MAX_VOLTAGE_OFFSET); + } + compatiblity_flag = false; + } + } + if (ev_max_voltage > MAX_VOLTAGE_THREASHOLD) { + if (evse_max_voltage > std::min(ev_max_voltage * MAX_VOLTAGE_FACTOR, evse_max_voltage)) { + logf_error("EVSE max voltage %.1f V > EV max voltage (> 500 V) multiplied by 1,1: %.1f V", evse_max_voltage, + ev_max_voltage * MAX_VOLTAGE_FACTOR); + compatiblity_flag = false; + } + } + + // CC.5.6 2.b + if (evse_max_current > std::min(ev_max_current, evse_max_current)) { + logf_error("EVSE max current %.1f A > EV max current %.1f A", evse_max_current, ev_max_current); + compatiblity_flag = false; + } + + // CC.5.6 2.c + float ev_power_max = ev_max_power; + if (ev_power_max == 0) { + ev_power_max = std::max(ev_max_voltage * ev_max_current, MAX_POWER_LIMIT); + } + if (evse_max_power > std::min(ev_power_max, evse_max_power)) { + logf_error("EVSE max power %.1f W > EV max power %.1f W", evse_max_power, ev_power_max); + compatiblity_flag = false; + } + // CC.5.6 2.d-f here not implemented because we make no difference between CPD and RATED + // CC.5.6 2.g + if (evse_min_power >= ev_power_max) { + logf_error("EVSE min power %.1f W >= EV max power %.1f W!", evse_min_power, ev_max_power); + compatiblity_flag = false; + } + if (evse_min_voltage >= ev_max_voltage) { + logf_error("EVSE min voltage %.1f V >= EV max voltage %.1f V!", evse_min_voltage, ev_max_voltage); + compatiblity_flag = false; + } + if (evse_min_current >= ev_max_current) { + logf_error("EVSE min current %.1f A >= EV max current %.1f A!", evse_min_current, ev_max_current); + compatiblity_flag = false; + } + return compatiblity_flag; +} + message_20::DC_ChargeParameterDiscoveryResponse handle_request(const message_20::DC_ChargeParameterDiscoveryRequest& req, const d20::Session& session, const d20::DcTransferLimits& dc_limits) { @@ -82,6 +167,10 @@ handle_request(const message_20::DC_ChargeParameterDiscoveryRequest& req, const return response_with_code(res, dt::ResponseCode::FAILED_WrongChargeParameter); } + // Do compatibility check IEC61851-23-3 CC.5.6 + if (not handle_compatibility_check(dc_limits, req.transfer_mode)) + return response_with_code(res, dt::ResponseCode::FAILED_WrongChargeParameter); + return response_with_code(res, dt::ResponseCode::OK); } @@ -127,6 +216,7 @@ Result DC_ChargeParameterDiscovery::feed(Event ev) { m_ctx.respond(res); m_ctx.feedback.dc_max_limits(dc_max_limits); + m_ctx.feedback.response_code(res.response_code); if (res.response_code >= dt::ResponseCode::FAILED) { m_ctx.session_stopped = true; @@ -139,6 +229,7 @@ Result DC_ChargeParameterDiscovery::feed(Event ev) { m_ctx.respond(res); m_ctx.session_stopped = true; + m_ctx.feedback.response_code(res.response_code); return {}; } else { @@ -149,6 +240,8 @@ Result DC_ChargeParameterDiscovery::feed(Event ev) { const message_20::Type req_type = variant->get_type(); send_sequence_error(req_type, m_ctx); + m_ctx.feedback.response_code(dt::ResponseCode::FAILED_SequenceError); + return {}; } } diff --git a/src/iso15118/d20/state/dc_pre_charge.cpp b/src/iso15118/d20/state/dc_pre_charge.cpp index 1e63e2ec..3b684a46 100644 --- a/src/iso15118/d20/state/dc_pre_charge.cpp +++ b/src/iso15118/d20/state/dc_pre_charge.cpp @@ -60,6 +60,7 @@ Result DC_PreCharge::feed(Event ev) { m_ctx.feedback.dc_pre_charge_target_voltage(message_20::datatypes::from_RationalNumber(req->target_voltage)); m_ctx.respond(res); + m_ctx.feedback.response_code(res.response_code); if (res.response_code >= dt::ResponseCode::FAILED) { m_ctx.session_stopped = true; @@ -73,6 +74,7 @@ Result DC_PreCharge::feed(Event ev) { m_ctx.respond(res); m_ctx.session_stopped = true; + m_ctx.feedback.response_code(res.response_code); return {}; } else { @@ -82,6 +84,7 @@ Result DC_PreCharge::feed(Event ev) { const message_20::Type req_type = variant->get_type(); send_sequence_error(req_type, m_ctx); + m_ctx.feedback.response_code(dt::ResponseCode::FAILED_SequenceError); m_ctx.session_stopped = true; return {}; } diff --git a/src/iso15118/d20/state/dc_welding_detection.cpp b/src/iso15118/d20/state/dc_welding_detection.cpp index 03a93e46..990430c5 100644 --- a/src/iso15118/d20/state/dc_welding_detection.cpp +++ b/src/iso15118/d20/state/dc_welding_detection.cpp @@ -53,6 +53,7 @@ Result DC_WeldingDetection::feed(Event ev) { const auto res = handle_request(*req, m_ctx.session, present_voltage); m_ctx.respond(res); + m_ctx.feedback.response_code(res.response_code); if (res.response_code >= dt::ResponseCode::FAILED) { m_ctx.session_stopped = true; @@ -76,6 +77,7 @@ Result DC_WeldingDetection::feed(Event ev) { } m_ctx.respond(res); + m_ctx.feedback.response_code(res.response_code); // Todo(sl): Tell the reason why the charger is stopping. Shutdown, Error, etc. if (req->charging_session == message_20::datatypes::ChargingSession::Pause) { @@ -98,6 +100,7 @@ Result DC_WeldingDetection::feed(Event ev) { const message_20::Type req_type = variant->get_type(); send_sequence_error(req_type, m_ctx); + m_ctx.feedback.response_code(dt::ResponseCode::FAILED_SequenceError); m_ctx.session_stopped = true; return {}; } diff --git a/src/iso15118/d20/state/power_delivery.cpp b/src/iso15118/d20/state/power_delivery.cpp index 17e3d562..bb14a41d 100644 --- a/src/iso15118/d20/state/power_delivery.cpp +++ b/src/iso15118/d20/state/power_delivery.cpp @@ -67,6 +67,7 @@ Result PowerDelivery::feed(Event ev) { const auto& res = handle_request(previous_req.value(), m_ctx.session, false); m_ctx.respond(res); + m_ctx.feedback.response_code(res.response_code); if (res.response_code >= dt::ResponseCode::FAILED) { m_ctx.session_stopped = true; @@ -87,6 +88,7 @@ Result PowerDelivery::feed(Event ev) { handle_request(previous_req.value_or(message_20::PowerDeliveryRequest{}), m_ctx.session, true); m_ctx.respond(res); m_ctx.session_stopped = true; + m_ctx.feedback.response_code(res.response_code); } return {}; } @@ -103,6 +105,7 @@ Result PowerDelivery::feed(Event ev) { m_ctx.feedback.dc_pre_charge_target_voltage(dt::from_RationalNumber(req->target_voltage)); m_ctx.respond(res); + m_ctx.feedback.response_code(res.response_code); if (res.response_code >= dt::ResponseCode::FAILED) { m_ctx.session_stopped = true; @@ -129,6 +132,7 @@ Result PowerDelivery::feed(Event ev) { const auto& res = handle_request(*req, m_ctx.session, false); m_ctx.respond(res); + m_ctx.feedback.response_code(res.response_code); if (res.response_code >= dt::ResponseCode::FAILED) { m_ctx.session_stopped = true; @@ -154,6 +158,7 @@ Result PowerDelivery::feed(Event ev) { const message_20::Type req_type = variant->get_type(); send_sequence_error(req_type, m_ctx); + m_ctx.feedback.response_code(dt::ResponseCode::FAILED_SequenceError); m_ctx.session_stopped = true; return {}; } diff --git a/src/iso15118/d20/state/schedule_exchange.cpp b/src/iso15118/d20/state/schedule_exchange.cpp index 9c1393b7..6fd1e0ba 100644 --- a/src/iso15118/d20/state/schedule_exchange.cpp +++ b/src/iso15118/d20/state/schedule_exchange.cpp @@ -180,6 +180,7 @@ Result ScheduleExchange::feed(Event ev) { handle_request(*req, m_ctx.session, max_charge_power, dynamic_parameters, timeout_ongoing_reached); m_ctx.respond(res); + m_ctx.feedback.response_code(res.response_code); if (res.response_code >= dt::ResponseCode::FAILED) { m_ctx.session_stopped = true; @@ -210,6 +211,7 @@ Result ScheduleExchange::feed(Event ev) { m_ctx.respond(res); m_ctx.session_stopped = true; + m_ctx.feedback.response_code(res.response_code); return {}; } else { @@ -219,6 +221,7 @@ Result ScheduleExchange::feed(Event ev) { const message_20::Type req_type = variant->get_type(); send_sequence_error(req_type, m_ctx); + m_ctx.feedback.response_code(dt::ResponseCode::FAILED_SequenceError); m_ctx.session_stopped = true; return {}; } diff --git a/src/iso15118/d20/state/service_detail.cpp b/src/iso15118/d20/state/service_detail.cpp index 2e694f9d..892fa036 100644 --- a/src/iso15118/d20/state/service_detail.cpp +++ b/src/iso15118/d20/state/service_detail.cpp @@ -226,6 +226,7 @@ Result ServiceDetail::feed(Event ev) { const auto res = handle_request(*req, m_ctx.session, m_ctx.session_config, custom_vas_parameters); m_ctx.respond(res); + m_ctx.feedback.response_code(res.response_code); if (res.response_code >= dt::ResponseCode::FAILED) { m_ctx.session_stopped = true; @@ -238,6 +239,7 @@ Result ServiceDetail::feed(Event ev) { m_ctx.respond(res); m_ctx.session_stopped = true; + m_ctx.feedback.response_code(res.response_code); return {}; } else { @@ -247,6 +249,7 @@ Result ServiceDetail::feed(Event ev) { const message_20::Type req_type = variant->get_type(); send_sequence_error(req_type, m_ctx); + m_ctx.feedback.response_code(dt::ResponseCode::FAILED_SequenceError); m_ctx.session_stopped = true; return {}; } diff --git a/src/iso15118/d20/state/service_discovery.cpp b/src/iso15118/d20/state/service_discovery.cpp index d1c91918..a1adc4ca 100644 --- a/src/iso15118/d20/state/service_discovery.cpp +++ b/src/iso15118/d20/state/service_discovery.cpp @@ -137,6 +137,7 @@ Result ServiceDiscovery::feed(Event ev) { m_ctx.session_config.supported_vas_services, m_ctx.session_ev_info.ev_energy_services); m_ctx.respond(res); + m_ctx.feedback.response_code(res.response_code); if (res.response_code >= dt::ResponseCode::FAILED) { m_ctx.session_stopped = true; @@ -149,6 +150,7 @@ Result ServiceDiscovery::feed(Event ev) { m_ctx.respond(res); m_ctx.session_stopped = true; + m_ctx.feedback.response_code(res.response_code); return {}; } else { @@ -158,6 +160,7 @@ Result ServiceDiscovery::feed(Event ev) { const message_20::Type req_type = variant->get_type(); send_sequence_error(req_type, m_ctx); + m_ctx.feedback.response_code(dt::ResponseCode::FAILED_SequenceError); m_ctx.session_stopped = true; return {}; } diff --git a/src/iso15118/d20/state/service_selection.cpp b/src/iso15118/d20/state/service_selection.cpp index 39a35885..0cf75867 100644 --- a/src/iso15118/d20/state/service_selection.cpp +++ b/src/iso15118/d20/state/service_selection.cpp @@ -169,6 +169,7 @@ Result ServiceSelection::feed(Event ev) { const auto res = handle_request(*req, m_ctx.session, m_ctx.session_config, custom_vas_parameters); m_ctx.respond(res); + m_ctx.feedback.response_code(res.response_code); if (res.response_code >= dt::ResponseCode::FAILED) { m_ctx.session_stopped = true; @@ -185,6 +186,7 @@ Result ServiceSelection::feed(Event ev) { } m_ctx.respond(res); + m_ctx.feedback.response_code(res.response_code); if (res.response_code >= dt::ResponseCode::FAILED) { m_ctx.session_stopped = true; @@ -214,6 +216,7 @@ Result ServiceSelection::feed(Event ev) { m_ctx.respond(res); m_ctx.session_stopped = true; + m_ctx.feedback.response_code(res.response_code); return {}; } else { @@ -223,6 +226,7 @@ Result ServiceSelection::feed(Event ev) { const message_20::Type req_type = variant->get_type(); send_sequence_error(req_type, m_ctx); + m_ctx.feedback.response_code(dt::ResponseCode::FAILED_SequenceError); m_ctx.session_stopped = true; return {}; } diff --git a/src/iso15118/d20/state/session_setup.cpp b/src/iso15118/d20/state/session_setup.cpp index 61ddacfb..bc9db92d 100644 --- a/src/iso15118/d20/state/session_setup.cpp +++ b/src/iso15118/d20/state/session_setup.cpp @@ -130,6 +130,7 @@ Result SessionSetup::feed(Event ev) { const auto res = handle_request(*req, m_ctx.session, evse_id, new_session); m_ctx.respond(res); + m_ctx.feedback.response_code(res.response_code); if (not new_session) { const auto& selected_services = m_ctx.session.get_selected_services(); @@ -154,6 +155,7 @@ Result SessionSetup::feed(Event ev) { const message_20::Type req_type = variant->get_type(); send_sequence_error(req_type, m_ctx); + m_ctx.feedback.response_code(dt::ResponseCode::FAILED_SequenceError); m_ctx.session_stopped = true; return {}; } diff --git a/src/iso15118/d20/state/session_stop.cpp b/src/iso15118/d20/state/session_stop.cpp index e680f10d..116a4aa0 100644 --- a/src/iso15118/d20/state/session_stop.cpp +++ b/src/iso15118/d20/state/session_stop.cpp @@ -59,6 +59,7 @@ Result SessionStop::feed(Event ev) { m_ctx.feedback.ev_termination(ev_termination_code, ev_termination_explanation); } m_ctx.respond(res); + m_ctx.feedback.response_code(res.response_code); // Todo(sl): Tell the reason why the charger is stopping. Shutdown, Error, etc. if (req->charging_session == message_20::datatypes::ChargingSession::Pause) { @@ -81,6 +82,7 @@ Result SessionStop::feed(Event ev) { const message_20::Type req_type = variant->get_type(); send_sequence_error(req_type, m_ctx); + m_ctx.feedback.response_code(dt::ResponseCode::FAILED_SequenceError); m_ctx.session_stopped = true; return {}; } diff --git a/src/iso15118/session/feedback.cpp b/src/iso15118/session/feedback.cpp index 596a30bc..c1b0440d 100644 --- a/src/iso15118/session/feedback.cpp +++ b/src/iso15118/session/feedback.cpp @@ -64,6 +64,10 @@ void Feedback::ev_termination(const std::string& ev_termination_code, call_if_available(callbacks.ev_termination, ev_termination_code, ev_termination_explanation); } +void Feedback::response_code(const iso15118::message_20::datatypes::ResponseCode& code) const { + call_if_available(callbacks.response_code, code); +} + std::optional Feedback::get_vas_parameters(uint16_t vas_id) const { logf_warning("Caution: This feedback call can block the entire state machine"); diff --git a/test/iso15118/states/dc_charge_parameter_discovery.cpp b/test/iso15118/states/dc_charge_parameter_discovery.cpp index d122635f..5e442c86 100644 --- a/test/iso15118/states/dc_charge_parameter_discovery.cpp +++ b/test/iso15118/states/dc_charge_parameter_discovery.cpp @@ -172,7 +172,7 @@ SCENARIO("DC charge parameter discovery state handling") { 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}; + powersupply_limits.voltage.max = {400, 0}; dt::RationalNumber power_ramp_limit = {20, 0}; powersupply_limits.power_ramp_limit.emplace<>(power_ramp_limit); @@ -203,7 +203,7 @@ SCENARIO("DC charge parameter discovery state handling") { 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.value == 400); REQUIRE(transfer_mode.max_voltage.exponent == 0); REQUIRE(transfer_mode.min_voltage.value == 0); REQUIRE(transfer_mode.min_voltage.exponent == 0); @@ -225,7 +225,7 @@ SCENARIO("DC charge parameter discovery state handling") { 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}; + powersupply_limits.voltage.max = {300, 0}; auto& discharge_limits = powersupply_limits.discharge_limits.emplace(); discharge_limits.power.max = {11, 3}; @@ -262,7 +262,7 @@ SCENARIO("DC charge parameter discovery state handling") { 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.value == 300); REQUIRE(transfer_mode.max_voltage.exponent == 0); REQUIRE(transfer_mode.min_voltage.value == 0); REQUIRE(transfer_mode.min_voltage.exponent == 0); @@ -278,7 +278,7 @@ SCENARIO("DC charge parameter discovery state handling") { } } - GIVEN("Bad Case: Provided DC charge limits but the ev wants bpt charge parameter - FAILED") { + 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, @@ -289,7 +289,7 @@ SCENARIO("DC charge parameter discovery state handling") { 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}; + powersupply_limits.voltage.max = {450, 0}; message_20::DC_ChargeParameterDiscoveryRequest req; req.header.session_id = session.get_id(); @@ -330,10 +330,1557 @@ SCENARIO("DC charge parameter discovery state handling") { } } - GIVEN("Bad Case: EV Parameter does not fit in evse parameters - FAILED_WrongChargeParameter") { + GIVEN("Bad Case: MCS - EVSE max voltage greater than EV max voltage (<=500) IEC61851 CC.5.6 2a - " + "FAILED_WrongChargeParameter") { + 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 = {500, 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(); + 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: FAILED_WrongChargeParameter") { + REQUIRE(res.response_code == dt::ResponseCode::FAILED_WrongChargeParameter); + + REQUIRE(std::holds_alternative(res.transfer_mode)); + const auto& transfer_mode = std::get(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 == 500); + 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("Bad Case: MCS - EVSE max voltage greater than EV max voltage (>500) IEC61851 CC.5.6 2a - " + "FAILED_WrongChargeParameter") { + 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 = {1000, 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(); + 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 = {800, 0}; + req_out.min_voltage = {0, 0}; + + const auto res = d20::state::handle_request(req, session, powersupply_limits); + + THEN("ResponseCode: FAILED_WrongChargeParameter") { + REQUIRE(res.response_code == dt::ResponseCode::FAILED_WrongChargeParameter); + + REQUIRE(std::holds_alternative(res.transfer_mode)); + const auto& transfer_mode = std::get(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 == 1000); + 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 - EVSE max voltage equal EV max voltage * 1.1 (>500) IEC61851 CC.5.6 2a") { + 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 = {880, 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(); + 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 = {800, 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(res.transfer_mode)); + const auto& transfer_mode = std::get(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 == 880); + 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 - EV max voltage + 50V exceeds 500V (<=500) IEC61851 CC.5.6 2a") { + 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 = {400, 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(); + 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 = {550, 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(res.transfer_mode)); + const auto& transfer_mode = std::get(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 == 400); + 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("BAD Case: MCS - EVSE max current greater than EV max current IEC61851 CC.5.6 2b") { + 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 = {126, 0}; + powersupply_limits.voltage.max = {400, 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(); + 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 = {500, 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::FAILED_WrongChargeParameter); + + REQUIRE(std::holds_alternative(res.transfer_mode)); + const auto& transfer_mode = std::get(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 == 126); + 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 == 400); + 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 - EVSE max power lower than the product of EV max voltage and current (EV max power 0W) " + "IEC61851 CC.5.6 2c") { + 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 = {180, 3}; + powersupply_limits.charge_limits.current.max = {25, 0}; + powersupply_limits.voltage.max = {400, 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(); + req_out.max_charge_power = {0, 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 = {500, 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(res.transfer_mode)); + const auto& transfer_mode = std::get(res.transfer_mode); + REQUIRE(transfer_mode.max_charge_power.value == 180); + 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 == 400); + 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("BAD Case: MCS - EVSE max power higher than 200KW (EV max power 0W) IEC61851 CC.5.6 2c") { + 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 = {220, 3}; + powersupply_limits.charge_limits.current.max = {25, 0}; + powersupply_limits.voltage.max = {400, 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(); + req_out.max_charge_power = {0, 3}; + req_out.min_charge_power = {0, 0}; + req_out.max_charge_current = {180, 0}; + req_out.min_charge_current = {0, 0}; + req_out.max_voltage = {1000, 0}; + req_out.min_voltage = {0, 0}; + + const auto res = d20::state::handle_request(req, session, powersupply_limits); + + THEN("ResponseCode: FAILED_WrongChargeParameter") { + REQUIRE(res.response_code == dt::ResponseCode::FAILED_WrongChargeParameter); + + REQUIRE(std::holds_alternative(res.transfer_mode)); + const auto& transfer_mode = std::get(res.transfer_mode); + REQUIRE(transfer_mode.max_charge_power.value == 220); + 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 == 400); + 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("BAD Case: MCS - EVSE max power higher than EV max power (EV max power != 0W) IEC61851 CC.5.6 2c") { + 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 = {220, 3}; + powersupply_limits.charge_limits.current.max = {25, 0}; + powersupply_limits.voltage.max = {400, 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(); + req_out.max_charge_power = {200, 3}; + req_out.min_charge_power = {0, 0}; + req_out.max_charge_current = {180, 0}; + req_out.min_charge_current = {0, 0}; + req_out.max_voltage = {1000, 0}; + req_out.min_voltage = {0, 0}; + + const auto res = d20::state::handle_request(req, session, powersupply_limits); + + THEN("ResponseCode: FAILED_WrongChargeParameter") { + REQUIRE(res.response_code == dt::ResponseCode::FAILED_WrongChargeParameter); + + REQUIRE(std::holds_alternative(res.transfer_mode)); + const auto& transfer_mode = std::get(res.transfer_mode); + REQUIRE(transfer_mode.max_charge_power.value == 220); + 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 == 400); + 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("BAD Case: MCS - EVSE max power higher than 200KW (EV max power 0W) IEC61851 CC.5.6 2c") { + 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 = {220, 3}; + powersupply_limits.charge_limits.current.max = {25, 0}; + powersupply_limits.voltage.max = {400, 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(); + req_out.max_charge_power = {0, 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 = {500, 0}; + req_out.min_voltage = {0, 0}; + + const auto res = d20::state::handle_request(req, session, powersupply_limits); + + THEN("ResponseCode: FAILED_WrongChargeParameter") { + REQUIRE(res.response_code == dt::ResponseCode::FAILED_WrongChargeParameter); + + REQUIRE(std::holds_alternative(res.transfer_mode)); + const auto& transfer_mode = std::get(res.transfer_mode); + REQUIRE(transfer_mode.max_charge_power.value == 220); + 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 == 400); + 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("BAD Case: MCS - EVSE min voltage higher than EV max voltage IEC61851 CC.5.6 2g") { + 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 = {400, 0}; + powersupply_limits.voltage.min = {500, 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(); + 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: FAILED_WrongChargeParameter") { + REQUIRE(res.response_code == dt::ResponseCode::FAILED_WrongChargeParameter); + + REQUIRE(std::holds_alternative(res.transfer_mode)); + const auto& transfer_mode = std::get(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 == 400); + REQUIRE(transfer_mode.max_voltage.exponent == 0); + REQUIRE(transfer_mode.min_voltage.value == 500); + 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("BAD Case: MCS - EVSE min current higher than EV max current IEC61851 CC.5.6 2g") { + 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.charge_limits.current.min = {126, 0}; + powersupply_limits.voltage.max = {400, 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(); + 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: FAILED_WrongChargeParameter") { + REQUIRE(res.response_code == dt::ResponseCode::FAILED_WrongChargeParameter); + + REQUIRE(std::holds_alternative(res.transfer_mode)); + const auto& transfer_mode = std::get(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 == 126); + REQUIRE(transfer_mode.min_charge_current.exponent == 0); + REQUIRE(transfer_mode.max_voltage.value == 400); + 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("BAD Case: MCS - EVSE min power higher than 200kW (EV max power = 0) IEC61851 CC.5.6 2g") { + 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 = {220, 3}; + powersupply_limits.charge_limits.power.min = {51, 3}; + powersupply_limits.charge_limits.current.max = {25, 0}; + powersupply_limits.voltage.max = {400, 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(); + 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: FAILED_WrongChargeParameter") { + REQUIRE(res.response_code == dt::ResponseCode::FAILED_WrongChargeParameter); + + REQUIRE(std::holds_alternative(res.transfer_mode)); + const auto& transfer_mode = std::get(res.transfer_mode); + REQUIRE(transfer_mode.max_charge_power.value == 220); + REQUIRE(transfer_mode.max_charge_power.exponent == 3); + REQUIRE(transfer_mode.min_charge_power.value == 51); + REQUIRE(transfer_mode.min_charge_power.exponent == 3); + 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 == 400); + 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("BAD Case: MCS - EVSE min power higher than product of EV max voltage/current (EV max power = 0) IEC61851 " + "CC.5.6 2g") { + 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 = {220, 3}; + powersupply_limits.charge_limits.power.min = {200, 3}; + powersupply_limits.charge_limits.current.max = {25, 0}; + powersupply_limits.voltage.max = {400, 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(); + req_out.max_charge_power = {0, 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 = {1000, 0}; + req_out.min_voltage = {0, 0}; + + const auto res = d20::state::handle_request(req, session, powersupply_limits); + + THEN("ResponseCode: FAILED_WrongChargeParameter") { + REQUIRE(res.response_code == dt::ResponseCode::FAILED_WrongChargeParameter); + + REQUIRE(std::holds_alternative(res.transfer_mode)); + const auto& transfer_mode = std::get(res.transfer_mode); + REQUIRE(transfer_mode.max_charge_power.value == 220); + REQUIRE(transfer_mode.max_charge_power.exponent == 3); + REQUIRE(transfer_mode.min_charge_power.value == 200); + REQUIRE(transfer_mode.min_charge_power.exponent == 3); + 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 == 400); + 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("Bad Case: MCS_BPT - EVSE max voltage greater than EV max voltage (<=500) IEC61851 CC.5.6 2a - " + "FAILED_WrongChargeParameter") { + 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); + + d20::DcTransferLimits powersupply_limits; + powersupply_limits.charge_limits.power.max = {22, 3}; + powersupply_limits.charge_limits.current.max = {25, 0}; + powersupply_limits.voltage.max = {500, 0}; + dt::RationalNumber power_ramp_limit = {20, 0}; + powersupply_limits.power_ramp_limit.emplace<>(power_ramp_limit); + + 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(); + 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_WrongChargeParameter") { + REQUIRE(res.response_code == dt::ResponseCode::FAILED_WrongChargeParameter); + + REQUIRE(std::holds_alternative(res.transfer_mode)); + const auto& transfer_mode = std::get(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 == 500); + 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); + 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: MCS_BPT - EVSE max voltage greater than EV max voltage (>500) IEC61851 CC.5.6 2a - " + "FAILED_WrongChargeParameter") { + 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); + + d20::DcTransferLimits powersupply_limits; + powersupply_limits.charge_limits.power.max = {22, 3}; + powersupply_limits.charge_limits.current.max = {25, 0}; + powersupply_limits.voltage.max = {1000, 0}; + dt::RationalNumber power_ramp_limit = {20, 0}; + powersupply_limits.power_ramp_limit.emplace<>(power_ramp_limit); + + 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(); + 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 = {800, 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_WrongChargeParameter") { + REQUIRE(res.response_code == dt::ResponseCode::FAILED_WrongChargeParameter); + + REQUIRE(std::holds_alternative(res.transfer_mode)); + const auto& transfer_mode = std::get(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 == 1000); + 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); + 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("GOOD Case: MCS_BPT - EVSE max voltage equal EV max voltage * 1.1 (>500) IEC61851 CC.5.6 2a") { + 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); + + d20::DcTransferLimits powersupply_limits; + powersupply_limits.charge_limits.power.max = {22, 3}; + powersupply_limits.charge_limits.current.max = {25, 0}; + powersupply_limits.voltage.max = {880, 0}; + dt::RationalNumber power_ramp_limit = {20, 0}; + powersupply_limits.power_ramp_limit.emplace<>(power_ramp_limit); + + 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(); + 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 = {800, 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(res.transfer_mode)); + const auto& transfer_mode = std::get(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 == 880); + 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); + 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("GOOD Case: MCS_BPT - EV max voltage + 50V exceeds 500V (<=500) IEC61851 CC.5.6 2a") { + 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); + + d20::DcTransferLimits powersupply_limits; + powersupply_limits.charge_limits.power.max = {22, 3}; + powersupply_limits.charge_limits.current.max = {25, 0}; + powersupply_limits.voltage.max = {400, 0}; + dt::RationalNumber power_ramp_limit = {20, 0}; + powersupply_limits.power_ramp_limit.emplace<>(power_ramp_limit); + + 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(); + 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 = {550, 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(res.transfer_mode)); + const auto& transfer_mode = std::get(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 == 400); + 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); + 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: MCS_BPT - EVSE max current greater than EV max current IEC61851 CC.5.6 2b") { + 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); + + d20::DcTransferLimits powersupply_limits; + powersupply_limits.charge_limits.power.max = {22, 3}; + powersupply_limits.charge_limits.current.max = {126, 0}; + powersupply_limits.voltage.max = {400, 0}; + dt::RationalNumber power_ramp_limit = {20, 0}; + powersupply_limits.power_ramp_limit.emplace<>(power_ramp_limit); + + 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(); + 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 = {500, 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::FAILED_WrongChargeParameter); + + REQUIRE(std::holds_alternative(res.transfer_mode)); + const auto& transfer_mode = std::get(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 == 126); + 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 == 400); + 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); + 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("GOOD Case: MCS_BPT - EVSE max power lower than the product of EV max voltage and current (EV max power 0W) " + "IEC61851 CC.5.6 2c") { + 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); + + d20::DcTransferLimits powersupply_limits; + powersupply_limits.charge_limits.power.max = {180, 3}; + powersupply_limits.charge_limits.current.max = {25, 0}; + powersupply_limits.voltage.max = {400, 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& discharge_limits = powersupply_limits.discharge_limits.emplace(); + discharge_limits.power.max = {11, 3}; + discharge_limits.current.max = {25, 0}; + + auto& req_out = req.transfer_mode.emplace(); + req_out.max_charge_power = {0, 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 = {500, 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(res.transfer_mode)); + const auto& transfer_mode = std::get(res.transfer_mode); + REQUIRE(transfer_mode.max_charge_power.value == 180); + 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 == 400); + 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); + 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: MCS_BPT - EVSE max power higher than 200KW (EV max power 0W) IEC61851 CC.5.6 2c") { + 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); + + d20::DcTransferLimits powersupply_limits; + powersupply_limits.charge_limits.power.max = {220, 3}; + powersupply_limits.charge_limits.current.max = {25, 0}; + powersupply_limits.voltage.max = {400, 0}; + dt::RationalNumber power_ramp_limit = {20, 0}; + powersupply_limits.power_ramp_limit.emplace<>(power_ramp_limit); + + 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(); + req_out.max_charge_power = {0, 3}; + req_out.min_charge_power = {0, 0}; + req_out.max_charge_current = {180, 0}; + req_out.min_charge_current = {0, 0}; + req_out.max_voltage = {1000, 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_WrongChargeParameter") { + REQUIRE(res.response_code == dt::ResponseCode::FAILED_WrongChargeParameter); + + REQUIRE(std::holds_alternative(res.transfer_mode)); + const auto& transfer_mode = std::get(res.transfer_mode); + REQUIRE(transfer_mode.max_charge_power.value == 220); + 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 == 400); + 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); + 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: MCS_BPT - EVSE max power higher than EV max power (EV max power != 0W) IEC61851 CC.5.6 2c") { + 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); + + d20::DcTransferLimits powersupply_limits; + powersupply_limits.charge_limits.power.max = {220, 3}; + powersupply_limits.charge_limits.current.max = {25, 0}; + powersupply_limits.voltage.max = {400, 0}; + dt::RationalNumber power_ramp_limit = {20, 0}; + powersupply_limits.power_ramp_limit.emplace<>(power_ramp_limit); + + 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(); + req_out.max_charge_power = {200, 3}; + req_out.min_charge_power = {0, 0}; + req_out.max_charge_current = {180, 0}; + req_out.min_charge_current = {0, 0}; + req_out.max_voltage = {1000, 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_WrongChargeParameter") { + REQUIRE(res.response_code == dt::ResponseCode::FAILED_WrongChargeParameter); + + REQUIRE(std::holds_alternative(res.transfer_mode)); + const auto& transfer_mode = std::get(res.transfer_mode); + REQUIRE(transfer_mode.max_charge_power.value == 220); + 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 == 400); + 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); + 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: MCS_BPT - EVSE max power higher than 200KW (EV max power 0W) IEC61851 CC.5.6 2c") { + 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); + + d20::DcTransferLimits powersupply_limits; + powersupply_limits.charge_limits.power.max = {220, 3}; + powersupply_limits.charge_limits.current.max = {25, 0}; + powersupply_limits.voltage.max = {400, 0}; + dt::RationalNumber power_ramp_limit = {20, 0}; + powersupply_limits.power_ramp_limit.emplace<>(power_ramp_limit); + + 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(); + req_out.max_charge_power = {0, 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 = {500, 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_WrongChargeParameter") { + REQUIRE(res.response_code == dt::ResponseCode::FAILED_WrongChargeParameter); + + REQUIRE(std::holds_alternative(res.transfer_mode)); + const auto& transfer_mode = std::get(res.transfer_mode); + REQUIRE(transfer_mode.max_charge_power.value == 220); + 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 == 400); + 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); + 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: MCS_BPT - EVSE min voltage higher than EV max voltage IEC61851 CC.5.6 2g") { + 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); + + d20::DcTransferLimits powersupply_limits; + powersupply_limits.charge_limits.power.max = {22, 3}; + powersupply_limits.charge_limits.current.max = {25, 0}; + powersupply_limits.voltage.max = {400, 0}; + powersupply_limits.voltage.min = {500, 0}; + dt::RationalNumber power_ramp_limit = {20, 0}; + powersupply_limits.power_ramp_limit.emplace<>(power_ramp_limit); + + 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(); + 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_WrongChargeParameter") { + REQUIRE(res.response_code == dt::ResponseCode::FAILED_WrongChargeParameter); + + REQUIRE(std::holds_alternative(res.transfer_mode)); + const auto& transfer_mode = std::get(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 == 400); + REQUIRE(transfer_mode.max_voltage.exponent == 0); + REQUIRE(transfer_mode.min_voltage.value == 500); + 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); + 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: MCS_BPT - EVSE min current higher than EV max current IEC61851 CC.5.6 2g") { + 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); + + d20::DcTransferLimits powersupply_limits; + powersupply_limits.charge_limits.power.max = {22, 3}; + powersupply_limits.charge_limits.current.max = {25, 0}; + powersupply_limits.charge_limits.current.min = {126, 0}; + powersupply_limits.voltage.max = {400, 0}; + dt::RationalNumber power_ramp_limit = {20, 0}; + powersupply_limits.power_ramp_limit.emplace<>(power_ramp_limit); + + 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(); + 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_WrongChargeParameter") { + REQUIRE(res.response_code == dt::ResponseCode::FAILED_WrongChargeParameter); + + REQUIRE(std::holds_alternative(res.transfer_mode)); + const auto& transfer_mode = std::get(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 == 126); + REQUIRE(transfer_mode.min_charge_current.exponent == 0); + REQUIRE(transfer_mode.max_voltage.value == 400); + 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); + 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: MCS_BPT - EVSE min power higher than 200kW (EV max power = 0) IEC61851 CC.5.6 2g") { + 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); + + d20::DcTransferLimits powersupply_limits; + powersupply_limits.charge_limits.power.max = {220, 3}; + powersupply_limits.charge_limits.power.min = {51, 3}; + powersupply_limits.charge_limits.current.max = {25, 0}; + powersupply_limits.voltage.max = {400, 0}; + dt::RationalNumber power_ramp_limit = {20, 0}; + powersupply_limits.power_ramp_limit.emplace<>(power_ramp_limit); + + 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(); + 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_WrongChargeParameter") { + REQUIRE(res.response_code == dt::ResponseCode::FAILED_WrongChargeParameter); + + REQUIRE(std::holds_alternative(res.transfer_mode)); + const auto& transfer_mode = std::get(res.transfer_mode); + REQUIRE(transfer_mode.max_charge_power.value == 220); + REQUIRE(transfer_mode.max_charge_power.exponent == 3); + REQUIRE(transfer_mode.min_charge_power.value == 51); + REQUIRE(transfer_mode.min_charge_power.exponent == 3); + 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 == 400); + 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); + 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: MCS_BPT - EVSE min power higher than product of EV max voltage/current (EV max power = 0) " + "IEC61851 CC.5.6 2g") { + 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); + + d20::DcTransferLimits powersupply_limits; + powersupply_limits.charge_limits.power.max = {220, 3}; + powersupply_limits.charge_limits.power.min = {200, 3}; + powersupply_limits.charge_limits.current.max = {25, 0}; + powersupply_limits.voltage.max = {400, 0}; + dt::RationalNumber power_ramp_limit = {20, 0}; + powersupply_limits.power_ramp_limit.emplace<>(power_ramp_limit); + + 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(); + req_out.max_charge_power = {0, 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 = {1000, 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_WrongChargeParameter") { + REQUIRE(res.response_code == dt::ResponseCode::FAILED_WrongChargeParameter); + + REQUIRE(std::holds_alternative(res.transfer_mode)); + const auto& transfer_mode = std::get(res.transfer_mode); + REQUIRE(transfer_mode.max_charge_power.value == 220); + REQUIRE(transfer_mode.max_charge_power.exponent == 3); + REQUIRE(transfer_mode.min_charge_power.value == 200); + REQUIRE(transfer_mode.min_charge_power.exponent == 3); + 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 == 400); + 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); + 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("Good Case: MCS") { + GIVEN("GOOD Case: MCS") { d20::SelectedServiceParameters service_parameters = d20::SelectedServiceParameters( dt::ServiceCategory::MCS, dt::DcConnector::Extended, dt::ControlMode::Scheduled, @@ -344,7 +1891,7 @@ SCENARIO("DC charge parameter discovery state handling") { 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}; + powersupply_limits.voltage.max = {450, 0}; dt::RationalNumber power_ramp_limit = {20, 0}; powersupply_limits.power_ramp_limit.emplace<>(power_ramp_limit); @@ -375,7 +1922,7 @@ SCENARIO("DC charge parameter discovery state handling") { 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.value == 450); REQUIRE(transfer_mode.max_voltage.exponent == 0); REQUIRE(transfer_mode.min_voltage.value == 0); REQUIRE(transfer_mode.min_voltage.exponent == 0); @@ -397,7 +1944,7 @@ SCENARIO("DC charge parameter discovery state handling") { 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}; + powersupply_limits.voltage.max = {450, 0}; auto& discharge_limits = powersupply_limits.discharge_limits.emplace(); discharge_limits.power.max = {11, 3}; @@ -434,7 +1981,7 @@ SCENARIO("DC charge parameter discovery state handling") { 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.value == 450); REQUIRE(transfer_mode.max_voltage.exponent == 0); REQUIRE(transfer_mode.min_voltage.value == 0); REQUIRE(transfer_mode.min_voltage.exponent == 0);