Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion include/iso15118/ev/d20/context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <iso15118/message/variant.hpp>

#include <iso15118/ev/d20/session.hpp>
#include <iso15118/ev/session/feedback.hpp>
#include <iso15118/io/sha_hash.hpp>

namespace iso15118::ev::d20 {
Expand Down Expand Up @@ -56,7 +57,7 @@ class Session;

class Context {
public:
Context(MessageExchange&);
Context(session::feedback::Callbacks, MessageExchange&);

template <typename StateType, typename... Args> BasePointerType create_state(Args&&... args) {
return std::make_unique<StateType>(*this, std::forward<Args>(args)...);
Expand Down Expand Up @@ -105,6 +106,11 @@ class Context {
return session;
}

// Contains the EVSE received data
EVSESessionInfo evse_session_info;

const iso15118::ev::d20::session::Feedback feedback;

private:
MessageExchange& message_exchange;

Expand Down
17 changes: 17 additions & 0 deletions include/iso15118/ev/d20/evse_session_info.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2025 Pionix GmbH and Contributors to EVerest
#pragma once

#include <iso15118/message/common_types.hpp>

namespace iso15118::ev::d20 {

// Holds information reported by the EVSE that could be different between sessions
struct EVSESessionInfo {
std::vector<message_20::datatypes::Authorization> auth_services{};
bool certificate_installation_service{false};
std::optional<std::vector<message_20::datatypes::Name>> supported_providers{std::nullopt};
message_20::datatypes::GenChallenge gen_challenge{std::array<uint8_t, 16>{}}; // 16 bytes
};

} // namespace iso15118::ev::d20
19 changes: 19 additions & 0 deletions include/iso15118/ev/d20/state/authorization.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2025 Pionix GmbH and Contributors to EVerest
#pragma once

#include "../states.hpp"

namespace iso15118::ev::d20::state {

struct Authorization : public StateBase {
public:
Authorization(Context& ctx) : StateBase(ctx, StateID::Authorization) {
}

void enter() final;

Result feed(Event) final;
};

} // namespace iso15118::ev::d20::state
4 changes: 2 additions & 2 deletions include/iso15118/ev/detail/d20/context_helper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
// Copyright 2025 Pionix GmbH and Contributors to EVerest
#pragma once

// #include <iso15118/d20/context.hpp>
// #include <iso15118/d20/session.hpp>
#include <iso15118/ev/d20/context.hpp>
#include <iso15118/ev/d20/session.hpp>
#include <iso15118/message/common_types.hpp>

// Forward declaration
Expand Down
37 changes: 37 additions & 0 deletions include/iso15118/ev/session/feedback.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#pragma once

#include <cmath>
#include <functional>
#include <optional>
#include <string>
#include <variant>

#include <iso15118/ev/d20/evse_session_info.hpp>
#include <iso15118/ev/d20/session.hpp>
#include <iso15118/message/type.hpp>

namespace iso15118::ev::d20::session {

namespace dt = message_20::datatypes;

namespace feedback {

struct Callbacks {
std::function<void(const EVSESessionInfo&)> evse_session_info;
};

} // namespace feedback

class Feedback {
public:
Feedback(feedback::Callbacks);

void evse_session_info(const EVSESessionInfo&) const;

private:
feedback::Callbacks callbacks;
};

} // namespace iso15118::ev::d20::session
2 changes: 2 additions & 0 deletions src/iso15118/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ target_sources(iso15118
ev/d20/context_helper.cpp
ev/d20/state/session_setup.cpp
ev/d20/state/authorization_setup.cpp
ev/d20/state/authorization.cpp
ev/d20/state/dc_charge_parameter_discovery.cpp
ev/session/feedback.cpp

io/connection_plain.cpp
io/logging.cpp
Expand Down
7 changes: 4 additions & 3 deletions src/iso15118/ev/d20/context.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2025 Pionix GmbH and Contributors to EVerest
#include <iso15118/ev/d20/context.hpp>

#include <iso15118/detail/helper.hpp>
#include <iso15118/ev/d20/context.hpp>
#include <iso15118/ev/detail/d20/context_helper.hpp>

namespace iso15118::ev::d20 {

Expand Down Expand Up @@ -34,7 +34,8 @@ message_20::Type MessageExchange::peek_response_type() const {
return response->get_type();
}

Context::Context(MessageExchange& message_exchange_) : message_exchange(message_exchange_) {
Context::Context(session::feedback::Callbacks feedback_callbacks, MessageExchange& message_exchange_) :
feedback(std::move(feedback_callbacks)), message_exchange(message_exchange_) {
}

std::unique_ptr<message_20::Variant> Context::pull_response() {
Expand Down
27 changes: 27 additions & 0 deletions src/iso15118/ev/d20/state/authorization.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2025 Pionix GmbH and Contributors to EVerest
#include <iso15118/detail/helper.hpp>
#include <iso15118/ev/d20/state/authorization.hpp>
#include <iso15118/ev/detail/d20/context_helper.hpp>
#include <iso15118/message/authorization.hpp>

namespace iso15118::ev::d20::state {

message_20::AuthorizationRequest handle_request() {
// TODO(SL): Implement
return message_20::AuthorizationRequest();
}

void Authorization::enter() {
// TODO(SL): Adding logging
}

Result Authorization::feed(Event ev) {
if (ev != Event::V2GTP_MESSAGE) {
return {};
} else {
return {};
}
}

} // namespace iso15118::ev::d20::state
88 changes: 85 additions & 3 deletions src/iso15118/ev/d20/state/authorization_setup.cpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,96 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2025 Pionix GmbH and Contributors to EVerest
// Copyright 2025 Pionix GmbH, Roger Bedell and Contributors to EVerest
#include <iso15118/detail/helper.hpp>
#include <iso15118/ev/d20/context.hpp>
#include <iso15118/ev/d20/state/authorization.hpp>
#include <iso15118/ev/d20/state/authorization_setup.hpp>
#include <iso15118/ev/detail/d20/context_helper.hpp>
#include <iso15118/ev/session/feedback.hpp>
#include <iso15118/message/authorization.hpp>
#include <iso15118/message/authorization_setup.hpp>

namespace iso15118::ev::d20::state {

namespace {
using ResponseCode = message_20::datatypes::ResponseCode;
bool check_response_code(ResponseCode response_code) {
switch (response_code) {
case ResponseCode::OK:
return true;
[[fallthrough]];
default:
return false;
}
Comment on lines +16 to +23
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other OK_* and Warning_* response codes are also acceptable here and should not lead to immediate termination. Only Failed_* response codes should lead to termination.
In the future every state should check if the OK_* or Warning_* response_code is really used in the state according to the standard and react accordingly.

Copy link
Contributor Author

@cienporcien cienporcien Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was looking at Table 224 in the standard, and for AuthorizationSetupRes it only has OK, FAILED, FAILED_SequenceError, and FAILED_UnknownSession. So I think it is ok as is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is with this approach, that if the charger sends a different OK_ or Warning_ ResponseCode as expected then the ev state machine abort the session. Thats not ideal. I think it is okay that the car can continue the session

}
} // namespace

void AuthorizationSetup::enter() {
// TODO(SL): Adding logging
}

Result AuthorizationSetup::feed([[maybe_unused]] Event ev) {
return {};
}
if (ev != Event::V2GTP_MESSAGE) {
return {};
}

const auto variant = m_ctx.pull_response();

if (const auto res = variant->get_if<message_20::AuthorizationSetupResponse>()) {

if (not check_response_code(res->response_code)) {
m_ctx.stop_session(true); // Tell stack to close the tcp/tls connection
return {};
}

if (res->certificate_installation_service) {
// TODO(RB): Implement certificate installation service
logf_warning("EVSE supports certificate installation service, but this is not implemented in the EV yet.");
// Remember it in the context for later use
m_ctx.evse_session_info.certificate_installation_service = true;
}

// Save the offered authorization services in the session context so we can use it later and also report it to
// the EV.
m_ctx.evse_session_info.auth_services = res->authorization_services;

if (std::holds_alternative<message_20::datatypes::PnC_ASResAuthorizationMode>(res->authorization_mode)) {
const auto& pnc_auth_mode =
std::get<message_20::datatypes::PnC_ASResAuthorizationMode>(res->authorization_mode);
m_ctx.evse_session_info.supported_providers = pnc_auth_mode.supported_providers.value();
m_ctx.evse_session_info.gen_challenge = pnc_auth_mode.gen_challenge;
} else {
// EIM selected, nothing to do here for now since authorization_mode is empty for EIM
}
// Inform the ev about the evse session information
m_ctx.feedback.evse_session_info(m_ctx.evse_session_info);

// Send request and transition to next state
message_20::AuthorizationRequest req;
setup_header(req.header, m_ctx.get_session());
// TODO(RB): Choose the authorization service based on user preference and what the evse supports
// For now, we just pick the first one offered by the evse
if (m_ctx.evse_session_info.auth_services.empty()) {
logf_error("No authorization services offered by the EVSE. Abort the session.");
m_ctx.stop_session(true); // Tell stack to close the tcp/tls connection
return {};
}
req.selected_authorization_service = m_ctx.evse_session_info.auth_services.front();
if (req.selected_authorization_service == message_20::datatypes::Authorization::EIM) {
req.authorization_mode = message_20::datatypes::EIM_ASReqAuthorizationMode{};
} else if (req.selected_authorization_service == message_20::datatypes::Authorization::PnC) {
// TODO(RB): Fill in the PnC authorization mode data
req.authorization_mode = message_20::datatypes::PnC_ASReqAuthorizationMode{};
} else {
logf_error("Unknown authorization service selected. Abort the session.");
m_ctx.stop_session(true); // Tell stack to close the tcp/tls connection
return {};
}
m_ctx.respond(req);
return m_ctx.create_state<Authorization>();
} else {
logf_error("expected AuthorizationSetupResponse! But code type id: %d", variant->get_type());
m_ctx.stop_session(true); // Tell stack to close the tcp/tls connection
return {};
}
}
} // namespace iso15118::ev::d20::state
16 changes: 16 additions & 0 deletions src/iso15118/ev/session/feedback.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/ev/session/feedback.hpp>

#include <iso15118/detail/helper.hpp>
#include <iso15118/ev/d20/evse_session_info.hpp>
namespace iso15118::ev::d20::session {

Feedback::Feedback(feedback::Callbacks callbacks_) : callbacks(std::move(callbacks_)) {
}

void Feedback::evse_session_info(const d20::EVSESessionInfo& info) const {
call_if_available(callbacks.evse_session_info, info);
}

} // namespace iso15118::ev::d20::session
3 changes: 2 additions & 1 deletion test/iso15118/ev/fsm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ function(create_ev_fsm_test_target NAME)
catch_discover_tests(test_ev_fsm_${NAME})
endfunction()

create_ev_fsm_test_target(session_setup)
create_ev_fsm_test_target(session_setup)
create_ev_fsm_test_target(authorization_setup)
88 changes: 88 additions & 0 deletions test/iso15118/ev/fsm/authorization_setup.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2025 Pionix GmbH and Contributors to EVerest
#include <catch2/catch_test_macros.hpp>

#include "helper.hpp"

#include <iso15118/ev/d20/state/authorization_setup.hpp>
#include <iso15118/message/authorization.hpp>
#include <iso15118/message/authorization_setup.hpp>
#include <iso15118/message/type.hpp>

using namespace iso15118;

SCENARIO("ISO15118-20 EV authorization setup state transitions") {

const ev::d20::session::feedback::Callbacks callbacks{};

auto state_helper = FsmStateHelper(callbacks);

auto ctx = state_helper.get_context();

GIVEN("Good case - authorization setup response with OK and EIM") {

// setup the state and context to something reasonable
const auto header = message_20::Header{{0x10, 0x34, 0xAB, 0x7A, 0x01, 0xF3, 0x95, 0x02}, 1691411798};

ctx.get_session().set_id(header.session_id);

fsm::v2::FSM<ev::d20::StateBase> fsm{ctx.create_state<ev::d20::state::AuthorizationSetup>()};

const auto res = message_20::AuthorizationSetupResponse{
header, message_20::datatypes::ResponseCode::OK, {message_20::datatypes::Authorization::EIM}, false};

state_helper.handle_response(res);

const auto result = fsm.feed(ev::d20::Event::V2GTP_MESSAGE);

THEN("Check if passes to authorization state and sends EIM AuthorizationRequest") {
REQUIRE(result.transitioned() == true);
REQUIRE(fsm.get_current_state_id() == ev::d20::StateID::Authorization);

const auto request_message = ctx.get_request<message_20::AuthorizationRequest>();
REQUIRE(request_message.has_value());

const auto& request = request_message.value();
REQUIRE(request.header.session_id == header.session_id);
REQUIRE(request.selected_authorization_service == message_20::datatypes::Authorization::EIM);
REQUIRE(
std::holds_alternative<message_20::datatypes::EIM_ASReqAuthorizationMode>(request.authorization_mode));
}
}

// PnC is not yet implemented properly in the EV, but we can at least test that the state machine transitions
// correctly
GIVEN("Good case - authorization setup response with OK and PnC") {

// setup the state and context to something reasonable
const auto header = message_20::Header{{0x10, 0x34, 0xAB, 0x7A, 0x01, 0xF3, 0x95, 0x02}, 1691411798};

ctx.get_session().set_id(header.session_id);

fsm::v2::FSM<ev::d20::StateBase> fsm{ctx.create_state<ev::d20::state::AuthorizationSetup>()};

const auto res = message_20::AuthorizationSetupResponse{
header, message_20::datatypes::ResponseCode::OK, {message_20::datatypes::Authorization::PnC}, false};

state_helper.handle_response(res);

const auto result = fsm.feed(ev::d20::Event::V2GTP_MESSAGE);

THEN("Check if passes to authorization state and sends Pnc AuthorizationRequest") {
REQUIRE(result.transitioned() == true);
REQUIRE(fsm.get_current_state_id() == ev::d20::StateID::Authorization);

const auto request_message = ctx.get_request<message_20::AuthorizationRequest>();
REQUIRE(request_message.has_value());

const auto& request = request_message.value();
REQUIRE(request.header.session_id == header.session_id);
REQUIRE(request.selected_authorization_service == message_20::datatypes::Authorization::PnC);
REQUIRE(
std::holds_alternative<message_20::datatypes::PnC_ASReqAuthorizationMode>(request.authorization_mode));
// TODO(RB): Add checks for PnC authorization mode
}
}
// TODO(RB): Add more test cases (bad response codes, unsupported authorization modes,
// more than one authorization mode, certificate installation service, etc)
}
2 changes: 1 addition & 1 deletion test/iso15118/ev/fsm/helper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ using namespace iso15118;

class FsmStateHelper {
public:
FsmStateHelper() : ctx(msg_exch){};
FsmStateHelper(const ev::d20::session::feedback::Callbacks& callbacks) : ctx(callbacks, msg_exch){};

ev::d20::Context& get_context();

Expand Down
Loading