Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding ISO15118-20 pause/resume feature #99

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
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
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ The following table shows the current support for the listed EVSE ISO15118-20 fe
| TCP, TLS 1.2 & 1.3 | :heavy_check_mark: |
| DC, DC_BPT | :heavy_check_mark: |
| AC, AC_BPT | WIP |
| MCS (Amd.) | |
| AC DER (Amd.) | |
| WPT | |
| ACDP | |
| ExternalPayment | :heavy_check_mark: |
Expand All @@ -21,7 +23,8 @@ The following table shows the current support for the listed EVSE ISO15118-20 fe
| Scheduled Mode | :heavy_check_mark: |
| Dynamic Mode (+ MobilityNeedsMode) | :heavy_check_mark: |
| Private Env | |
| Pause/Resume & Standby | WIP |
| Pause/Resume | :heavy_check_mark: (dynamic mode) |
| Standby | |
| Schedule Renegotation | |
| Smart Charging | |
| Multiplex messages | |
Expand Down
16 changes: 15 additions & 1 deletion include/iso15118/d20/context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
class Context {
public:
// FIXME (aw): bundle arguments
Context(session::feedback::Callbacks, session::SessionLogger&, d20::SessionConfig,
Context(session::feedback::Callbacks, session::SessionLogger&, d20::SessionConfig, std::optional<PauseContext>&,
const std::optional<ControlEvent>&, MessageExchange&);

template <typename StateType, typename... Args> BasePointerType create_state(Args&&... args) {
Expand Down Expand Up @@ -89,6 +89,14 @@
return &std::get<T>(*current_control_event);
}

void set_new_vehicle_cert_hash(std::optional<io::sha512_hash_t> hash) {
vehicle_cert_hash = hash;
}

auto get_new_vehicle_cert_hash() const {
return vehicle_cert_hash;
}

const session::Feedback feedback;

session::SessionLogger& log;
Expand All @@ -100,11 +108,17 @@
// Contains the EV received data
EVSessionInfo session_ev_info;

std::optional<d20::PauseContext>& pause_ctx;

bool session_stopped{false};
bool session_paused{false};

Check notice on line 114 in include/iso15118/d20/context.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

include/iso15118/d20/context.hpp#L114

class member 'Context::session_paused' is never used.
bool session_resumed{false};

Check notice on line 115 in include/iso15118/d20/context.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

include/iso15118/d20/context.hpp#L115

class member 'Context::session_resumed' is never used.

private:
const std::optional<ControlEvent>& current_control_event;
MessageExchange& message_exchange;

std::optional<io::sha512_hash_t> vehicle_cert_hash{std::nullopt};
};

} // namespace iso15118::d20
15 changes: 14 additions & 1 deletion include/iso15118/d20/control_event.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,20 @@ class StopCharging {
bool stop;
};

class PauseCharging {
public:
explicit PauseCharging(bool stop_) : stop(stop_) {
}

operator bool() const {
return stop;
}

private:
bool stop;
};

using ControlEvent = std::variant<CableCheckFinished, PresentVoltageCurrent, AuthorizationResponse, StopCharging,
DcTransferLimits, UpdateDynamicModeParameters>;
PauseCharging, DcTransferLimits, UpdateDynamicModeParameters>;

} // namespace iso15118::d20
10 changes: 10 additions & 0 deletions include/iso15118/d20/session.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <variant>
#include <vector>

#include <iso15118/io/sha_hash.hpp>
#include <iso15118/message/common_types.hpp>

namespace iso15118::d20 {
Expand Down Expand Up @@ -57,13 +58,22 @@
dt::ParkingStatus parking_status;
};

// TODO(SL): How to handle d2 pause? Move Struct to a seperate header file?
// TODO(SL): Missing handling scheduletuple in schedule mode [V2G20-1058]
struct PauseContext {
io::sha512_hash_t vehicle_cert_session_id_hash{};
std::array<uint8_t, 8> old_session_id{};

Check notice on line 65 in include/iso15118/d20/session.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

include/iso15118/d20/session.hpp#L65

struct member 'PauseContext::old_session_id' is never used.
SelectedServiceParameters selected_service_parameters{};
};

class Session {

// todo(sl): move to a common defs file
static constexpr auto ID_LENGTH = 8;

public:
Session();
Session(const PauseContext& pause_ctx);

Check notice on line 76 in include/iso15118/d20/session.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

include/iso15118/d20/session.hpp#L76

Class 'Session' has a constructor with 1 argument that is not explicit.
Session(SelectedServiceParameters);
Session(OfferedServices);

Expand Down
1 change: 1 addition & 0 deletions include/iso15118/d20/state/dc_charge_loop.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
float present_voltage{0};
float present_current{0};
bool stop{false};
bool pause{false};

Check notice on line 26 in include/iso15118/d20/state/dc_charge_loop.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

include/iso15118/d20/state/dc_charge_loop.hpp#L26

struct member 'DC_ChargeLoop::pause' is never used.

UpdateDynamicModeParameters dynamic_parameters;

Expand Down
2 changes: 1 addition & 1 deletion include/iso15118/detail/d20/state/dc_charge_loop.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace iso15118::d20::state {

message_20::DC_ChargeLoopResponse handle_request(const message_20::DC_ChargeLoopRequest& req,
const d20::Session& session, const float present_voltage,
const float present_current, const bool stop,
const float present_current, const bool stop, const bool pause,
const DcTransferLimits& dc_limits,
const UpdateDynamicModeParameters& dynamic_parameters);

Expand Down
5 changes: 3 additions & 2 deletions include/iso15118/session/iso.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,15 @@ struct SessionState {

class Session {
public:
Session(std::unique_ptr<io::IConnection>, d20::SessionConfig, const session::feedback::Callbacks&);
Session(std::unique_ptr<io::IConnection>, d20::SessionConfig, const session::feedback::Callbacks&,
std::optional<d20::PauseContext>&);
~Session();

TimePoint const& poll();
void push_control_event(const d20::ControlEvent&);

bool is_finished() const {
return ctx.session_stopped;
return (ctx.session_stopped or ctx.session_paused);
}

private:
Expand Down
3 changes: 3 additions & 0 deletions include/iso15118/tbd_controller.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <list>
#include <memory>
#include <optional>
#include <string>
#include <vector>

Expand Down Expand Up @@ -53,6 +54,8 @@ class TbdController {
d20::EvseSetupConfig evse_setup;

std::string interface_name;

std::optional<d20::PauseContext> pause_ctx{std::nullopt};
};

} // namespace iso15118
5 changes: 3 additions & 2 deletions src/iso15118/d20/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,12 @@ message_20::Type MessageExchange::peek_request_type() const {
}

Context::Context(session::feedback::Callbacks feedback_callbacks, session::SessionLogger& logger,
d20::SessionConfig session_config_, const std::optional<ControlEvent>& current_control_event_,
MessageExchange& message_exchange_) :
d20::SessionConfig session_config_, std::optional<d20::PauseContext>& pause_ctx_,
const std::optional<ControlEvent>& current_control_event_, MessageExchange& message_exchange_) :
feedback(std::move(feedback_callbacks)),
log(logger),
session_config(std::move(session_config_)),
pause_ctx(pause_ctx_),
current_control_event{current_control_event_},
message_exchange(message_exchange_) {
}
Expand Down
3 changes: 3 additions & 0 deletions src/iso15118/d20/session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ Session::Session() {
}
}

Session::Session(const PauseContext& pause_ctx) :
id(pause_ctx.old_session_id), selected_services(pause_ctx.selected_service_parameters){};

Session::Session(SelectedServiceParameters service_parameters_) : selected_services(service_parameters_) {
std::random_device rd;
std::mt19937 generator(rd());
Expand Down
13 changes: 11 additions & 2 deletions src/iso15118/d20/state/dc_charge_loop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ void set_dynamic_parameters_in_res(T& res_mode, const UpdateDynamicModeParameter

message_20::DC_ChargeLoopResponse handle_request(const message_20::DC_ChargeLoopRequest& req,
const d20::Session& session, const float present_voltage,
const float present_current, const bool stop,
const float present_current, const bool stop, const bool pause,
const DcTransferLimits& dc_limits,
const UpdateDynamicModeParameters& dynamic_parameters) {

Expand Down Expand Up @@ -175,6 +175,12 @@ message_20::DC_ChargeLoopResponse handle_request(const message_20::DC_ChargeLoop
res.status = {0, dt::EvseNotification::Terminate};
}

if (pause) {
const uint16_t notification_max_delay =
(selected_control_mode == dt::ControlMode::Dynamic) ? 60 : 0; // [V2G20-1850]
res.status = {notification_max_delay, dt::EvseNotification::Pause};
}

return response_with_code(res, dt::ResponseCode::OK);
}

Expand All @@ -191,6 +197,8 @@ Result DC_ChargeLoop::feed(Event ev) {
present_current = control_data->current;
} else if (const auto* control_data = m_ctx.get_control_event<StopCharging>()) {
stop = *control_data;
} else if (const auto* control_data = m_ctx.get_control_event<PauseCharging>()) {
pause = *control_data;
} else if (const auto* control_data = m_ctx.get_control_event<UpdateDynamicModeParameters>()) {
dynamic_parameters = *control_data;
}
Expand Down Expand Up @@ -219,6 +227,7 @@ Result DC_ChargeLoop::feed(Event ev) {
first_entry_in_charge_loop = true;

// Todo(sl): React properly to Start, Stop, Standby and ScheduleRenegotiation
// TODO(Sl): How to check if the EV wants do a pause in dynamic mode (This should not happen)
if (req->charge_progress == dt::Progress::Stop) {
m_ctx.feedback.signal(session::feedback::Signal::CHARGE_LOOP_FINISHED);
m_ctx.feedback.signal(session::feedback::Signal::DC_OPEN_CONTACTOR);
Expand All @@ -232,7 +241,7 @@ Result DC_ChargeLoop::feed(Event ev) {
first_entry_in_charge_loop = false;
}

const auto res = handle_request(*req, m_ctx.session, present_voltage, present_current, stop,
const auto res = handle_request(*req, m_ctx.session, present_voltage, present_current, stop, pause,
m_ctx.session_config.dc_limits, dynamic_parameters);

m_ctx.respond(res);
Expand Down
96 changes: 85 additions & 11 deletions src/iso15118/d20/state/session_setup.cpp
Original file line number Diff line number Diff line change
@@ -1,19 +1,59 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <algorithm>
#include <iomanip>
#include <openssl/evp.h>
#include <sstream>

#include <iso15118/d20/state/authorization_setup.hpp>
#include <iso15118/d20/state/dc_charge_parameter_discovery.hpp>
#include <iso15118/d20/state/session_setup.hpp>

#include <iso15118/detail/d20/context_helper.hpp>
#include <iso15118/detail/d20/state/session_setup.hpp>
#include <iso15118/detail/helper.hpp>
#include <iso15118/io/sha_hash.hpp>

static bool session_is_zero(const iso15118::message_20::Header& header) {
return std::all_of(header.session_id.begin(), header.session_id.end(), [](int i) { return i == 0; });
namespace iso15118::d20::state {

namespace {

std::string session_id_to_string(const message_20::datatypes::SessionId& session_id) {
std::stringstream ss;
ss << "0x" << std::uppercase << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(session_id[0]);
for (unsigned int i = 1; i < session_id.size(); ++i) {
ss << ", 0x" << std::uppercase << std::hex << std::setw(2) << std::setfill('0')
<< (int)static_cast<int>(session_id[i]);
}
return ss.str();
}

namespace iso15118::d20::state {
bool session_is_zero(const message_20::datatypes::SessionId& session_id) {
return std::all_of(session_id.begin(), session_id.end(), [](int i) { return i == 0; });
}

io::sha512_hash_t calculate_new_cert_session_id_hash(const io::sha512_hash_t& vehicle_cert_hash,
const message_20::datatypes::SessionId& session_id) {
io::sha512_hash_t session_id_vehicle_hash{};
std::array<std::uint8_t, 64 + 8> concated_session_id_vehicle{};

std::copy(session_id.begin(), session_id.end(), concated_session_id_vehicle.begin());
std::copy(vehicle_cert_hash.begin(), vehicle_cert_hash.end(),
concated_session_id_vehicle.begin() + session_id.size());

unsigned int digestlen{0};

// Note(SL): Move to helper file?
const auto result = EVP_Digest(concated_session_id_vehicle.data(), concated_session_id_vehicle.size(),
session_id_vehicle_hash.data(), &digestlen, EVP_sha512(), nullptr);
if (not result) {
logf_error("X509_digest failed");
return std::array<std::uint8_t, 64>{};
}

return session_id_vehicle_hash;
}
} // namespace

namespace dt = message_20::datatypes;

Expand All @@ -22,7 +62,6 @@ message_20::SessionSetupResponse handle_request([[maybe_unused]] const message_2
bool new_session) {

message_20::SessionSetupResponse res;
// FIXME(sl): Check req
setup_header(res.header, session);

res.evseid = evse_id;
Expand Down Expand Up @@ -51,14 +90,38 @@ Result SessionSetup::feed(Event ev) {
logf_info("Received session setup with evccid: %s", req->evccid.c_str());
m_ctx.feedback.evcc_id(req->evccid);

bool new_session{true};
bool new_session{false};

if (session_is_zero(req->header)) {
const auto vehicle_cert_hash = m_ctx.get_new_vehicle_cert_hash();

if (session_is_zero(req->header.session_id) or not vehicle_cert_hash.has_value() or
not m_ctx.pause_ctx.has_value()) {
m_ctx.session = Session();
} else if (req->header.session_id == m_ctx.session.get_id()) {
new_session = false;
new_session = true;
} else {
m_ctx.session = Session();
const auto& pause_ctx = m_ctx.pause_ctx.value();
const auto new_vehicle_cert_session_hash =
calculate_new_cert_session_id_hash(vehicle_cert_hash.value(), req->header.session_id);

if (pause_ctx.vehicle_cert_session_id_hash == new_vehicle_cert_session_hash) {
logf_info("Old session resumed with session_id: %s",
session_id_to_string(req->header.session_id).c_str());
m_ctx.session_resumed = true;
m_ctx.session = Session(pause_ctx);
} else {
m_ctx.session = Session();
new_session = true;
}
}

if (new_session) {
logf_info("New session created with session_id: %s", session_id_to_string(m_ctx.session.get_id()).c_str());
if (vehicle_cert_hash) {
auto& pause_ctx = m_ctx.pause_ctx.emplace();
pause_ctx.vehicle_cert_session_id_hash =
calculate_new_cert_session_id_hash(vehicle_cert_hash.value(), m_ctx.session.get_id());
pause_ctx.old_session_id = m_ctx.session.get_id();
}
}

evse_id = m_ctx.session_config.evse_id;
Expand All @@ -67,10 +130,21 @@ Result SessionSetup::feed(Event ev) {

m_ctx.respond(res);

if (not new_session) {
const auto& pause_selected_energy_service = m_ctx.session.get_selected_services().selected_energy_service;
if (pause_selected_energy_service == message_20::datatypes::ServiceCategory::AC or
pause_selected_energy_service == message_20::datatypes::ServiceCategory::AC_BPT) {
// TODO(sl): Missing AC charge parameter discovery state
return {};
} else if (pause_selected_energy_service == message_20::datatypes::ServiceCategory::DC or
pause_selected_energy_service == message_20::datatypes::ServiceCategory::DC_BPT) {
return m_ctx.create_state<DC_ChargeParameterDiscovery>();
}
// TODO(sl): Error handling
return {};
}
return m_ctx.create_state<AuthorizationSetup>();

// Todo(sl): Going straight to ChargeParameterDiscovery?

} else {
m_ctx.log("expected SessionSetupReq! But code type id: %d", variant->get_type());

Expand Down
Loading