Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter

- CitrineOS core extracted (CSMS OCPP 2.0.1)
- OpenOCPP extracted (firmware OCPP 1.6J/2.0.1)
- ShapeShifter library installed (pip install -e)
- ShapeShifter specification extracted
- EVerest extracted

TODO updated with progress
This commit is contained in:
Eric F
2026-06-08 00:38:27 -04:00
parent 468cfeaa50
commit d398a6ced2
7326 changed files with 1177561 additions and 7 deletions

View File

@@ -0,0 +1 @@
add_subdirectory(iso15118)

View File

@@ -0,0 +1,98 @@
find_package(Threads REQUIRED)
add_library(iso15118)
add_library(iso15118::iso15118 ALIAS iso15118)
target_include_directories(iso15118
PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
)
target_sources(iso15118
PRIVATE
misc/helper.cpp
misc/cb_exi.cpp
io/connection_plain.cpp
io/logging.cpp
io/poll_manager.cpp
io/sdp_packet.cpp
io/sdp_server.cpp
io/socket_helper.cpp
session/feedback.cpp
session/iso.cpp
session/logger.cpp
d20/context.cpp
d20/context_helper.cpp
d20/control_event_queue.cpp
d20/session.cpp
d20/timeout.cpp
d20/config.cpp
d20/state/supported_app_protocol.cpp
d20/state/session_setup.cpp
d20/state/authorization_setup.cpp
d20/state/authorization.cpp
d20/state/service_discovery.cpp
d20/state/service_detail.cpp
d20/state/service_selection.cpp
d20/state/ac_charge_parameter_discovery.cpp
d20/state/dc_charge_parameter_discovery.cpp
d20/state/schedule_exchange.cpp
d20/state/dc_cable_check.cpp
d20/state/dc_pre_charge.cpp
d20/state/power_delivery.cpp
d20/state/dc_charge_loop.cpp
d20/state/dc_welding_detection.cpp
d20/state/ac_charge_loop.cpp
d20/state/session_stop.cpp
message/variant.cpp
message/supported_app_protocol.cpp
message/session_setup.cpp
message/common_types.cpp
message/authorization_setup.cpp
message/authorization.cpp
message/service_discovery.cpp
message/service_detail.cpp
message/service_selection.cpp
message/ac_charge_parameter_discovery.cpp
message/dc_charge_parameter_discovery.cpp
message/schedule_exchange.cpp
message/dc_cable_check.cpp
message/dc_pre_charge.cpp
message/power_delivery.cpp
message/ac_charge_loop.cpp
message/dc_charge_loop.cpp
message/dc_welding_detection.cpp
message/session_stop.cpp
tbd_controller.cpp
)
target_link_libraries(iso15118
PUBLIC
# FIXME (aw): would be nice if we could make this private!
cbv2g::tp
cbv2g::iso20
everest::util
PRIVATE
Threads::Threads
)
target_link_libraries(iso15118
PRIVATE
OpenSSL::SSL
OpenSSL::Crypto
)
target_sources(iso15118
PRIVATE
io/connection_ssl.cpp
misc/helper_ssl.cpp
)
# FIXME (aw): do we want to have this public here?
target_compile_features(iso15118 PUBLIC cxx_std_17)
target_compile_options(iso15118 PRIVATE ${ISO15118_COMPILE_OPTIONS_WARNING})

View File

@@ -0,0 +1,210 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/d20/config.hpp>
#include <algorithm>
#include <iso15118/detail/helper.hpp>
namespace iso15118::d20 {
namespace dt = message_20::datatypes;
namespace {
auto get_mobility_needs_mode(const ControlMobilityNeedsModes& mode) {
using namespace dt;
if (mode.control_mode == ControlMode::Scheduled and mode.mobility_mode == MobilityNeedsMode::ProvidedBySecc) {
logf_info("Setting the mobility needs mode to ProvidedByEvcc. In scheduled mode only ProvidedByEvcc is "
"supported.");
return MobilityNeedsMode::ProvidedByEvcc;
}
return mode.mobility_mode;
}
auto get_default_ac_parameter_list(const std::vector<ControlMobilityNeedsModes>& control_mobility_modes,
const AcSetupConfig& ac_setup_config) {
using namespace dt;
std::vector<AcParameterList> param_list;
for (const auto& mode : control_mobility_modes) {
for (const auto& connector : ac_setup_config.connectors) {
param_list.push_back({
connector,
mode.control_mode,
get_mobility_needs_mode(mode),
ac_setup_config.voltage,
Pricing::NoPricing,
});
}
}
return param_list;
}
auto get_default_ac_bpt_parameter_list(const std::vector<ControlMobilityNeedsModes>& control_mobility_modes,
const AcSetupConfig& ac_setup_config, const BptSetupConfig& bpt_setup_config) {
using namespace dt;
std::vector<AcBptParameterList> param_list;
for (const auto& mode : control_mobility_modes) {
for (const auto& connector : ac_setup_config.connectors) {
param_list.push_back(
{{
connector,
mode.control_mode,
get_mobility_needs_mode(mode),
ac_setup_config.voltage,
Pricing::NoPricing,
},
bpt_setup_config.bpt_channel,
bpt_setup_config.generator_mode,
bpt_setup_config.grid_code_detection_method.value_or(dt::GridCodeIslandingDetectionMethod::Passive)});
}
}
return param_list;
}
auto get_default_dc_parameter_list(const std::vector<ControlMobilityNeedsModes>& control_mobility_modes) {
using namespace dt;
// TODO(sl): Add check if a control mode is more than one in that vector
std::vector<DcParameterList> param_list;
for (const auto& mode : control_mobility_modes) {
param_list.push_back({
DcConnector::Extended,
mode.control_mode,
get_mobility_needs_mode(mode),
Pricing::NoPricing,
});
}
return param_list;
}
auto get_default_dc_bpt_parameter_list(const std::vector<ControlMobilityNeedsModes>& control_mobility_modes,
const BptSetupConfig& bpt_setup_config) {
using namespace dt;
// TODO(sl): Add check if a control mode is more than one in that vector
std::vector<DcBptParameterList> param_list;
for (const auto& mode : control_mobility_modes) {
param_list.push_back({{
DcConnector::Extended,
mode.control_mode,
get_mobility_needs_mode(mode),
Pricing::NoPricing,
},
bpt_setup_config.bpt_channel,
bpt_setup_config.generator_mode});
}
return param_list;
}
auto get_default_mcs_parameter_list(const std::vector<ControlMobilityNeedsModes>& control_mobility_modes) {
using namespace dt;
// TODO(sl): Add check if a control mode is more than one in that vector
std::vector<McsParameterList> param_list;
for (const auto& mode : control_mobility_modes) {
param_list.push_back({
McsConnector::Mcs,
mode.control_mode,
get_mobility_needs_mode(mode),
Pricing::NoPricing,
});
}
return param_list;
}
auto get_default_mcs_bpt_parameter_list(const std::vector<ControlMobilityNeedsModes>& control_mobility_modes,
const BptSetupConfig& bpt_setup_config) {
using namespace dt;
// TODO(sl): Add check if a control mode is more than one in that vector
std::vector<McsBptParameterList> param_list;
for (const auto& mode : control_mobility_modes) {
param_list.push_back({{
McsConnector::Mcs,
mode.control_mode,
get_mobility_needs_mode(mode),
Pricing::NoPricing,
},
bpt_setup_config.bpt_channel,
bpt_setup_config.generator_mode});
}
return param_list;
}
} // namespace
SessionConfig::SessionConfig(EvseSetupConfig config) :
evse_id(std::move(config.evse_id)),
cert_install_service(config.enable_certificate_install_service),
authorization_services(std::move(config.authorization_services)),
supported_energy_transfer_services(std::move(config.supported_energy_services)),
supported_vas_services(std::move(config.supported_vas_services)),
dc_limits(std::move(config.dc_limits)),
ac_limits(std::move(config.ac_limits)),
powersupply_limits(std::move(config.powersupply_limits)),
supported_control_mobility_modes(std::move(config.control_mobility_modes)),
custom_protocol(std::move(config.custom_protocol)) {
// TODO(SL): How to handle this probaly
const auto is_dc_bpt_service = [](dt::ServiceCategory service) {
return service == dt::ServiceCategory::DC_BPT or service == dt::ServiceCategory::MCS_BPT;
};
const auto dc_bpt_found = std::any_of(supported_energy_transfer_services.begin(),
supported_energy_transfer_services.end(), is_dc_bpt_service);
if (dc_bpt_found and not dc_limits.discharge_limits.has_value()) {
logf_warning("The supported energy services contain DC_BPT or MCS_BPT, but dc limits does not contain BPT "
"limits. This can lead to session shutdowns.");
}
const auto is_ac_bpt_service = [](dt::ServiceCategory service) { return service == dt::ServiceCategory::AC_BPT; };
const auto ac_bpt_found = std::any_of(supported_energy_transfer_services.begin(),
supported_energy_transfer_services.end(), is_ac_bpt_service);
if (ac_bpt_found and not ac_limits.discharge_power.has_value()) {
logf_warning("The supported energy services contain AC_BPT, but ac limits does not contain BPT limits. This "
"can lead to session shutdowns.");
}
if (supported_control_mobility_modes.empty()) {
logf_warning("No control modes were provided, set to scheduled mode");
supported_control_mobility_modes = {{dt::ControlMode::Scheduled, dt::MobilityNeedsMode::ProvidedByEvcc}};
}
const auto ac_setup_config = config.ac_setup_config.value_or(AcSetupConfig({230, {dt::AcConnector::SinglePhase}}));
const auto ac_bpt_setup_config = config.bpt_setup_config.value_or(BptSetupConfig(
{dt::BptChannel::Unified, dt::GeneratorMode::GridFollowing, dt::GridCodeIslandingDetectionMethod::Passive}));
const auto dc_bpt_setup_config = config.bpt_setup_config.value_or(
BptSetupConfig({dt::BptChannel::Unified, dt::GeneratorMode::GridFollowing, std::nullopt}));
ac_parameter_list = get_default_ac_parameter_list(supported_control_mobility_modes, ac_setup_config);
ac_bpt_parameter_list =
get_default_ac_bpt_parameter_list(supported_control_mobility_modes, ac_setup_config, ac_bpt_setup_config);
dc_parameter_list = get_default_dc_parameter_list(supported_control_mobility_modes);
dc_bpt_parameter_list = get_default_dc_bpt_parameter_list(supported_control_mobility_modes, dc_bpt_setup_config);
mcs_parameter_list = get_default_mcs_parameter_list(supported_control_mobility_modes);
mcs_bpt_parameter_list = get_default_mcs_bpt_parameter_list(supported_control_mobility_modes, dc_bpt_setup_config);
}
} // namespace iso15118::d20

View File

@@ -0,0 +1,75 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/d20/context.hpp>
#include <stdexcept>
#include <iso15118/detail/helper.hpp>
namespace iso15118::d20 {
std::unique_ptr<MessageExchange> create_message_exchange(uint8_t* buf, const size_t len) {
io::StreamOutputView view = {buf, len};
return std::make_unique<MessageExchange>(std::move(view));
}
MessageExchange::MessageExchange(io::StreamOutputView output_) : response(std::move(output_)) {
}
void MessageExchange::set_request(std::unique_ptr<message_20::Variant> new_request) {
if (request) {
// FIXME (aw): we might want to have a stack here?
throw std::runtime_error("Previous V2G message has not been handled yet");
}
request = std::move(new_request);
}
std::unique_ptr<message_20::Variant> MessageExchange::pull_request() {
if (not request) {
throw std::runtime_error("Tried to access V2G message, but there is none");
}
return std::move(request);
}
std::tuple<bool, size_t, io::v2gtp::PayloadType, message_20::Type> MessageExchange::check_and_clear_response() {
auto retval = std::make_tuple(response_available, response_size, payload_type, response_type);
response_available = false;
response_size = 0;
response_type = message_20::Type::None;
return retval;
}
message_20::Type MessageExchange::peek_request_type() const {
if (not request) {
logf_warning("Tried to access V2G message, but there is none");
return message_20::Type::None;
}
return request->get_type();
}
Context::Context(session::feedback::Callbacks feedback_callbacks, session::SessionLogger& logger,
SessionConfig session_config_, std::optional<PauseContext>& pause_ctx_,
const std::optional<ControlEvent>& current_control_event_, MessageExchange& message_exchange_,
Timeouts& timeouts_) :
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_),
timeouts(timeouts_) {
}
std::unique_ptr<message_20::Variant> Context::pull_request() {
return message_exchange.pull_request();
}
message_20::Type Context::peek_request_type() const {
return message_exchange.peek_request_type();
}
} // namespace iso15118::d20

View File

@@ -0,0 +1,105 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <ctime>
#include <iso15118/detail/d20/context_helper.hpp>
#include <iso15118/detail/helper.hpp>
#include <iso15118/message/ac_charge_parameter_discovery.hpp>
#include <iso15118/message/authorization.hpp>
#include <iso15118/message/authorization_setup.hpp>
#include <iso15118/message/dc_cable_check.hpp>
#include <iso15118/message/dc_charge_loop.hpp>
#include <iso15118/message/dc_charge_parameter_discovery.hpp>
#include <iso15118/message/dc_pre_charge.hpp>
#include <iso15118/message/dc_welding_detection.hpp>
#include <iso15118/message/power_delivery.hpp>
#include <iso15118/message/schedule_exchange.hpp>
#include <iso15118/message/service_detail.hpp>
#include <iso15118/message/service_discovery.hpp>
#include <iso15118/message/service_selection.hpp>
#include <iso15118/message/session_setup.hpp>
#include <iso15118/message/session_stop.hpp>
namespace iso15118::d20 {
static inline void setup_timestamp(message_20::Header& header) {
header.timestamp = static_cast<uint64_t>(std::time(nullptr));
}
bool validate_and_setup_header(message_20::Header& header, const Session& cur_session,
const decltype(message_20::Header::session_id)& req_session_id) {
setup_header(header, cur_session);
return (cur_session.get_id() == req_session_id);
}
void setup_header(message_20::Header& header, const Session& cur_session) {
header.session_id = cur_session.get_id();
setup_timestamp(header);
}
template <typename Response> Response handle_sequence_error(const d20::Session& session) {
Response res;
setup_header(res.header, session);
return response_with_code(res, message_20::datatypes::ResponseCode::FAILED_SequenceError);
}
// Todo(sl): Not happy at all. Need refactoring. Only ctx.respond and Session is needed. Not the whole Context.
void send_sequence_error(const message_20::Type req_type, d20::Context& ctx) {
if (req_type == message_20::Type::SessionSetupReq) {
const auto res = handle_sequence_error<message_20::SessionSetupResponse>(ctx.session);
ctx.respond(res);
} else if (req_type == message_20::Type::AuthorizationSetupReq) {
const auto res = handle_sequence_error<message_20::AuthorizationSetupResponse>(ctx.session);
ctx.respond(res);
} else if (req_type == message_20::Type::AuthorizationReq) {
const auto res = handle_sequence_error<message_20::AuthorizationResponse>(ctx.session);
ctx.respond(res);
} else if (req_type == message_20::Type::ServiceDiscoveryReq) {
const auto res = handle_sequence_error<message_20::ServiceDiscoveryResponse>(ctx.session);
ctx.respond(res);
} else if (req_type == message_20::Type::ServiceDetailReq) {
const auto res = handle_sequence_error<message_20::ServiceDetailResponse>(ctx.session);
ctx.respond(res);
} else if (req_type == message_20::Type::ServiceSelectionReq) {
const auto res = handle_sequence_error<message_20::ServiceSelectionResponse>(ctx.session);
ctx.respond(res);
} else if (req_type == message_20::Type::AC_ChargeParameterDiscoveryReq) {
const auto res = handle_sequence_error<message_20::AC_ChargeParameterDiscoveryResponse>(ctx.session);
ctx.respond(res);
} else if (req_type == message_20::Type::DC_ChargeParameterDiscoveryReq) {
const auto res = handle_sequence_error<message_20::DC_ChargeParameterDiscoveryResponse>(ctx.session);
ctx.respond(res);
} else if (req_type == message_20::Type::ScheduleExchangeReq) {
const auto res = handle_sequence_error<message_20::ScheduleExchangeResponse>(ctx.session);
ctx.respond(res);
} else if (req_type == message_20::Type::DC_CableCheckReq) {
const auto res = handle_sequence_error<message_20::DC_CableCheckResponse>(ctx.session);
ctx.respond(res);
} else if (req_type == message_20::Type::PowerDeliveryReq) {
const auto res = handle_sequence_error<message_20::PowerDeliveryResponse>(ctx.session);
ctx.respond(res);
} else if (req_type == message_20::Type::DC_PreChargeReq) {
const auto res = handle_sequence_error<message_20::DC_PreChargeResponse>(ctx.session);
ctx.respond(res);
} else if (req_type == message_20::Type::DC_ChargeLoopReq) {
const auto res = handle_sequence_error<message_20::DC_ChargeLoopResponse>(ctx.session);
ctx.respond(res);
} else if (req_type == message_20::Type::AC_ChargeLoopReq) {
const auto res = handle_sequence_error<message_20::AC_ChargeLoopResponse>(ctx.session);
ctx.respond(res);
} else if (req_type == message_20::Type::DC_WeldingDetectionReq) {
const auto res = handle_sequence_error<message_20::DC_WeldingDetectionResponse>(ctx.session);
ctx.respond(res);
} else if (req_type == message_20::Type::SessionStopReq) {
const auto res = handle_sequence_error<message_20::SessionStopResponse>(ctx.session);
ctx.respond(res);
} else {
logf_warning("Unknown code type id: %d ", req_type);
}
}
} // namespace iso15118::d20

View File

@@ -0,0 +1,26 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/d20/control_event_queue.hpp>
namespace iso15118::d20 {
std::optional<ControlEvent> ControlEventQueue::pop() {
std::lock_guard<std::mutex> lck(mutex);
if (queue.empty()) {
return std::nullopt;
}
auto event = std::make_optional<ControlEvent>(std::move(queue.front()));
queue.pop();
return event;
}
void ControlEventQueue::push(ControlEvent event) {
std::lock_guard<std::mutex> lck(mutex);
queue.push(std::move(event));
}
} // namespace iso15118::d20

View File

@@ -0,0 +1,327 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/d20/session.hpp>
#include <random>
#include <iso15118/detail/helper.hpp>
namespace iso15118::d20 {
namespace dt = message_20::datatypes;
SelectedServiceParameters::SelectedServiceParameters(dt::ServiceCategory energy_service_, dt::DcConnector dc_connector_,
dt::ControlMode control_mode_, dt::MobilityNeedsMode mobility_,
dt::Pricing pricing_) :
selected_energy_service(energy_service_),
selected_control_mode(control_mode_),
selected_mobility_needs_mode(mobility_),
selected_pricing(pricing_) {
selected_connector.emplace<dt::DcConnector>(dc_connector_);
};
SelectedServiceParameters::SelectedServiceParameters(dt::ServiceCategory energy_service_, dt::DcConnector dc_connector_,
dt::ControlMode control_mode_, dt::MobilityNeedsMode mobility_,
dt::Pricing pricing_, dt::BptChannel channel_,
dt::GeneratorMode generator_) :
selected_energy_service(energy_service_),
selected_control_mode(control_mode_),
selected_mobility_needs_mode(mobility_),
selected_pricing(pricing_),
selected_bpt_channel(channel_),
selected_generator_mode(generator_) {
selected_connector.emplace<dt::DcConnector>(dc_connector_);
};
SelectedServiceParameters::SelectedServiceParameters(dt::ServiceCategory energy_service_, dt::AcConnector ac_connector_,
dt::ControlMode control_mode_, dt::MobilityNeedsMode mobility_,
dt::Pricing pricing_, float nominal_voltage_) :
selected_energy_service(energy_service_),
selected_control_mode(control_mode_),
selected_mobility_needs_mode(mobility_),
selected_pricing(pricing_),
evse_nominal_voltage(nominal_voltage_) {
selected_connector.emplace<dt::AcConnector>(ac_connector_);
};
SelectedServiceParameters::SelectedServiceParameters(dt::ServiceCategory energy_service_, dt::AcConnector ac_connector_,
dt::ControlMode control_mode_, dt::MobilityNeedsMode mobility_,
dt::Pricing pricing_, dt::BptChannel channel_,
dt::GeneratorMode generator_, float nominal_voltage_,
dt::GridCodeIslandingDetectionMethod grid_code_method_) :
selected_energy_service(energy_service_),
selected_control_mode(control_mode_),
selected_mobility_needs_mode(mobility_),
selected_pricing(pricing_),
selected_bpt_channel(channel_),
selected_generator_mode(generator_),
evse_nominal_voltage(nominal_voltage_),
selected_grid_code_method(grid_code_method_) {
selected_connector.emplace<dt::AcConnector>(ac_connector_);
};
SelectedServiceParameters::SelectedServiceParameters(dt::ServiceCategory energy_service_,
dt::McsConnector mcs_connector_, dt::ControlMode control_mode_,
dt::MobilityNeedsMode mobility_, dt::Pricing pricing_) :
selected_energy_service(energy_service_),
selected_control_mode(control_mode_),
selected_mobility_needs_mode(mobility_),
selected_pricing(pricing_) {
selected_connector.emplace<dt::McsConnector>(mcs_connector_);
};
SelectedServiceParameters::SelectedServiceParameters(dt::ServiceCategory energy_service_,
dt::McsConnector mcs_connector_, dt::ControlMode control_mode_,
dt::MobilityNeedsMode mobility_, dt::Pricing pricing_,
dt::BptChannel channel_, dt::GeneratorMode generator_) :
selected_energy_service(energy_service_),
selected_control_mode(control_mode_),
selected_mobility_needs_mode(mobility_),
selected_pricing(pricing_),
selected_bpt_channel(channel_),
selected_generator_mode(generator_) {
selected_connector.emplace<dt::McsConnector>(mcs_connector_);
};
Session::Session() {
std::random_device rd;
std::mt19937 generator(rd());
std::uniform_int_distribution<uint8_t> distribution(0x00, 0xff);
for (auto& item : id) {
item = distribution(generator);
}
}
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());
std::uniform_int_distribution<uint8_t> distribution(0x00, 0xff);
for (auto& item : id) {
item = distribution(generator);
}
}
Session::Session(OfferedServices services_) : offered_services(services_) {
std::random_device rd;
std::mt19937 generator(rd());
std::uniform_int_distribution<uint8_t> distribution(0x00, 0xff);
for (auto& item : id) {
item = distribution(generator);
}
}
Session::~Session() = default;
bool Session::find_energy_parameter_set_id(const dt::ServiceCategory service, int16_t id) {
switch (service) {
case dt::ServiceCategory::AC:
if (this->offered_services.ac_parameter_list.find(id) != this->offered_services.ac_parameter_list.end()) {
return true;
}
break;
case dt::ServiceCategory::AC_BPT:
if (this->offered_services.ac_bpt_parameter_list.find(id) !=
this->offered_services.ac_bpt_parameter_list.end()) {
return true;
}
break;
case dt::ServiceCategory::DC:
if (this->offered_services.dc_parameter_list.find(id) != this->offered_services.dc_parameter_list.end()) {
return true;
}
break;
case dt::ServiceCategory::DC_BPT:
if (this->offered_services.dc_bpt_parameter_list.find(id) !=
this->offered_services.dc_bpt_parameter_list.end()) {
return true;
}
break;
case dt::ServiceCategory::MCS:
if (this->offered_services.mcs_parameter_list.find(id) != this->offered_services.mcs_parameter_list.end()) {
return true;
}
break;
case dt::ServiceCategory::MCS_BPT:
if (this->offered_services.mcs_bpt_parameter_list.find(id) !=
this->offered_services.mcs_bpt_parameter_list.end()) {
return true;
}
break;
case dt::ServiceCategory::WPT:
[[fallthrough]];
case dt::ServiceCategory::DC_ACDP:
[[fallthrough]];
case dt::ServiceCategory::DC_ACDP_BPT:
[[fallthrough]];
case dt::ServiceCategory::AC_DER:
[[fallthrough]];
default:
logf_warning("Service %u is not supported yet", message_20::to_underlying_value(service));
break;
}
return false;
}
bool Session::find_vas_parameter_set_id(const uint16_t vas_service, int16_t id) {
if (vas_service == message_20::to_underlying_value(dt::ServiceCategory::Internet)) {
if (this->offered_services.internet_parameter_list.find(id) !=
this->offered_services.internet_parameter_list.end()) {
return true;
}
} else if (vas_service == message_20::to_underlying_value(dt::ServiceCategory::ParkingStatus)) {
if (this->offered_services.parking_parameter_list.find(id) !=
this->offered_services.parking_parameter_list.end()) {
return true;
}
} else {
logf_info("Find parameter_set_id from service: %u", vas_service);
if (this->offered_services.custom_vas_list.find(vas_service) != this->offered_services.custom_vas_list.end()) {
const auto custom_vas_parameter_set_ids = this->offered_services.custom_vas_list.at(vas_service);
for (auto offered_id : custom_vas_parameter_set_ids) {
if (offered_id == id) {
return true;
}
}
}
}
return false;
}
void Session::selected_service_parameters(const dt::ServiceCategory service, const uint16_t id) {
switch (service) {
case dt::ServiceCategory::AC:
if (this->offered_services.ac_parameter_list.find(id) != this->offered_services.ac_parameter_list.end()) {
const auto& parameters = this->offered_services.ac_parameter_list.at(id);
this->selected_services = SelectedServiceParameters(dt::ServiceCategory::AC, parameters.connector,
parameters.control_mode, parameters.mobility_needs_mode,
parameters.pricing, parameters.evse_nominal_voltage);
} else {
// Todo(sl): Should be not the case -> Raise Error?
}
break;
case dt::ServiceCategory::AC_BPT:
if (this->offered_services.ac_bpt_parameter_list.find(id) !=
this->offered_services.ac_bpt_parameter_list.end()) {
const auto& parameters = this->offered_services.ac_bpt_parameter_list.at(id);
this->selected_services = SelectedServiceParameters(
dt::ServiceCategory::AC_BPT, parameters.connector, parameters.control_mode,
parameters.mobility_needs_mode, parameters.pricing, parameters.bpt_channel, parameters.generator_mode,
parameters.evse_nominal_voltage, parameters.grid_code_detection_method);
} else {
// Todo(sl): Should be not the case -> Raise Error?
}
break;
case dt::ServiceCategory::DC:
if (this->offered_services.dc_parameter_list.find(id) != this->offered_services.dc_parameter_list.end()) {
const auto& parameters = this->offered_services.dc_parameter_list.at(id);
this->selected_services =
SelectedServiceParameters(dt::ServiceCategory::DC, parameters.connector, parameters.control_mode,
parameters.mobility_needs_mode, parameters.pricing);
logf_info("Selected DC service parameters: control mode: %s, mobility needs mode: %s",
dt::from_control_mode(parameters.control_mode).c_str(),
dt::from_mobility_needs_mode(parameters.mobility_needs_mode).c_str());
} else {
// Todo(sl): Should be not the case -> Raise Error?
}
break;
case dt::ServiceCategory::DC_BPT:
if (this->offered_services.dc_bpt_parameter_list.find(id) !=
this->offered_services.dc_bpt_parameter_list.end()) {
const auto& parameters = this->offered_services.dc_bpt_parameter_list.at(id);
this->selected_services = SelectedServiceParameters(
dt::ServiceCategory::DC_BPT, parameters.connector, parameters.control_mode,
parameters.mobility_needs_mode, parameters.pricing, parameters.bpt_channel, parameters.generator_mode);
logf_info("Selected DC_BPT service parameters: control mode: %s, mobility needs mode: %s",
dt::from_control_mode(parameters.control_mode).c_str(),
dt::from_mobility_needs_mode(parameters.mobility_needs_mode).c_str());
} else {
// Todo(sl): Should be not the case -> Raise Error?
}
break;
case dt::ServiceCategory::MCS:
if (this->offered_services.mcs_parameter_list.find(id) != this->offered_services.mcs_parameter_list.end()) {
auto& parameters = this->offered_services.mcs_parameter_list.at(id);
this->selected_services =
SelectedServiceParameters(dt::ServiceCategory::MCS, parameters.connector, parameters.control_mode,
parameters.mobility_needs_mode, parameters.pricing);
logf_info("Selected MCS service parameters: control mode: %s, mobility needs mode: %s",
dt::from_control_mode(parameters.control_mode).c_str(),
dt::from_mobility_needs_mode(parameters.mobility_needs_mode).c_str());
} else {
// Todo(sl): Should be not the case -> Raise Error?
}
break;
case dt::ServiceCategory::MCS_BPT:
if (this->offered_services.mcs_bpt_parameter_list.find(id) !=
this->offered_services.mcs_bpt_parameter_list.end()) {
auto& parameters = this->offered_services.mcs_bpt_parameter_list.at(id);
this->selected_services = SelectedServiceParameters(
dt::ServiceCategory::MCS_BPT, parameters.connector, parameters.control_mode,
parameters.mobility_needs_mode, parameters.pricing, parameters.bpt_channel, parameters.generator_mode);
logf_info("Selected MCS_BPT service parameters: control mode: %s, mobility needs mode: %s",
dt::from_control_mode(parameters.control_mode).c_str(),
dt::from_mobility_needs_mode(parameters.mobility_needs_mode).c_str());
} else {
// Todo(sl): Should be not the case -> Raise Error?
}
break;
case dt::ServiceCategory::WPT:
[[fallthrough]];
case dt::ServiceCategory::DC_ACDP:
[[fallthrough]];
case dt::ServiceCategory::DC_ACDP_BPT:
[[fallthrough]];
case dt::ServiceCategory::AC_DER:
[[fallthrough]];
default:
logf_warning("Service %u is not supported yet", message_20::to_underlying_value(service));
break;
}
}
void Session::selected_service_parameters(const uint16_t vas_service, const uint16_t id) {
if (vas_service == message_20::to_underlying_value(dt::ServiceCategory::Internet)) {
if (this->offered_services.internet_parameter_list.find(id) !=
this->offered_services.internet_parameter_list.end()) {
this->selected_vas_services.vas_services.push_back(dt::ServiceCategory::Internet);
const auto& parameters = this->offered_services.internet_parameter_list.at(id);
this->selected_vas_services.internet_port = parameters.port;
this->selected_vas_services.internet_protocol = parameters.protocol;
}
} else if (vas_service == message_20::to_underlying_value(dt::ServiceCategory::ParkingStatus)) {
if (this->offered_services.parking_parameter_list.find(id) !=
this->offered_services.parking_parameter_list.end()) {
this->selected_vas_services.vas_services.push_back(dt::ServiceCategory::ParkingStatus);
const auto& parameters = this->offered_services.parking_parameter_list.at(id);
this->selected_vas_services.parking_intended_service = parameters.intended_service;
this->selected_vas_services.parking_status = parameters.parking_status;
}
} else {
logf_info("Right now not selecting anything for custom vas service");
}
}
} // namespace iso15118::d20

View File

@@ -0,0 +1,255 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2025 Pionix GmbH and Contributors to EVerest
#include <iso15118/d20/state/ac_charge_loop.hpp>
#include <iso15118/message/ac_charge_loop.hpp>
#include <iso15118/d20/state/power_delivery.hpp>
#include <iso15118/d20/state/session_stop.hpp>
#include <iso15118/detail/d20/context_helper.hpp>
#include <iso15118/detail/d20/state/ac_charge_loop.hpp>
#include <iso15118/detail/d20/state/power_delivery.hpp>
#include <iso15118/detail/helper.hpp>
namespace iso15118::d20::state {
namespace dt = message_20::datatypes;
using Scheduled_AC_Req = dt::Scheduled_AC_CLReqControlMode;
using Scheduled_BPT_AC_Req = dt::BPT_Scheduled_AC_CLReqControlMode;
using Dynamic_AC_Req = dt::Dynamic_AC_CLReqControlMode;
using Dynamic_BPT_AC_Req = dt::BPT_Dynamic_AC_CLReqControlMode;
using Scheduled_AC_Res = dt::Scheduled_AC_CLResControlMode;
using Scheduled_BPT_AC_Res = dt::BPT_Scheduled_AC_CLResControlMode;
using Dynamic_AC_Res = dt::Dynamic_AC_CLResControlMode;
using Dynamic_BPT_AC_Res = dt::BPT_Dynamic_AC_CLResControlMode;
template <typename Out> void convert(Out& out, const AcTargetPower& targets, const d20::AcPresentPower& present_power);
template <>
void convert(Scheduled_AC_Res& out, const AcTargetPower& targets, const d20::AcPresentPower& present_power) {
out.target_active_power = targets.target_active_power;
out.target_active_power_L2 = targets.target_active_power_L2;
out.target_active_power_L3 = targets.target_active_power_L3;
out.target_reactive_power = targets.target_reactive_power;
out.target_reactive_power_L2 = targets.target_reactive_power_L2;
out.target_reactive_power_L3 = targets.target_reactive_power_L3;
out.present_active_power = present_power.present_active_power;
out.present_active_power_L2 = present_power.present_active_power_L2;
out.present_active_power_L3 = present_power.present_active_power_L3;
}
template <>
void convert(Scheduled_BPT_AC_Res& out, const AcTargetPower& targets, const d20::AcPresentPower& present_power) {
convert(static_cast<Scheduled_AC_Res&>(out), targets, present_power);
}
template <> void convert(Dynamic_AC_Res& out, const AcTargetPower& targets, const d20::AcPresentPower& present_power) {
out.target_active_power =
targets.target_active_power.value_or(dt::RationalNumber{0, 0}); // 0kW if no value is available
out.target_active_power_L2 = targets.target_active_power_L2;
out.target_active_power_L3 = targets.target_active_power_L3;
out.target_reactive_power = targets.target_reactive_power;
out.target_reactive_power_L2 = targets.target_reactive_power_L2;
out.target_reactive_power_L3 = targets.target_reactive_power_L3;
out.present_active_power = present_power.present_active_power;
out.present_active_power_L2 = present_power.present_active_power_L2;
out.present_active_power_L3 = present_power.present_active_power_L3;
}
template <>
void convert(Dynamic_BPT_AC_Res& out, const AcTargetPower& targets, const d20::AcPresentPower& present_power) {
convert(static_cast<Dynamic_AC_Res&>(out), targets, present_power);
}
// TODO(sl): Refactor with DcChargeLoop state
namespace {
template <typename T>
void set_dynamic_parameters_in_res(T& res_mode, const UpdateDynamicModeParameters& parameters,
uint64_t header_timestamp) {
if (parameters.departure_time) {
const auto departure_time = static_cast<uint64_t>(parameters.departure_time.value());
if (departure_time > header_timestamp) {
res_mode.departure_time = static_cast<uint32_t>(departure_time - header_timestamp);
}
}
res_mode.target_soc = parameters.target_soc;
res_mode.minimum_soc = parameters.min_soc;
res_mode.ack_max_delay = 30; // TODO(sl) what to send here and define 30 seconds as const
}
} // namespace
message_20::AC_ChargeLoopResponse handle_request(const message_20::AC_ChargeLoopRequest& req,
const d20::Session& session, bool stop, bool pause,
float target_frequency, const AcTargetPower& target_powers,
const AcPresentPower& present_powers,
const UpdateDynamicModeParameters& dynamic_parameters) {
message_20::AC_ChargeLoopResponse res;
if (validate_and_setup_header(res.header, session, req.header.session_id) == false) {
return response_with_code(res, dt::ResponseCode::FAILED_UnknownSession);
}
const auto& selected_services = session.get_selected_services();
const auto selected_control_mode = selected_services.selected_control_mode;
const auto selected_energy_service = selected_services.selected_energy_service;
const auto selected_mobility_needs_mode = selected_services.selected_mobility_needs_mode;
if (std::holds_alternative<Scheduled_AC_Req>(req.control_mode)) {
// If the ev sends a false control mode or a false energy service other than the previous selected ones, then
// the charger should terminate the session
if (selected_control_mode != dt::ControlMode::Scheduled or selected_energy_service != dt::ServiceCategory::AC) {
return response_with_code(res, dt::ResponseCode::FAILED);
}
auto& res_mode = res.control_mode.emplace<Scheduled_AC_Res>();
convert(res_mode, target_powers, present_powers);
} else if (std::holds_alternative<Scheduled_BPT_AC_Req>(req.control_mode)) {
// If the ev sends a false control mode or a false energy service other than the previous selected ones, then
// the charger should terminate the session
if (selected_control_mode != dt::ControlMode::Scheduled or
selected_energy_service != dt::ServiceCategory::AC_BPT) {
return response_with_code(res, dt::ResponseCode::FAILED);
}
auto& res_mode = res.control_mode.emplace<Scheduled_BPT_AC_Res>();
convert(res_mode, target_powers, present_powers);
} else if (std::holds_alternative<Dynamic_AC_Req>(req.control_mode)) {
// If the ev sends a false control mode or a false energy service other than the previous selected ones, then
// the charger should terminate the session
if (selected_control_mode != dt::ControlMode::Dynamic or selected_energy_service != dt::ServiceCategory::AC) {
return response_with_code(res, dt::ResponseCode::FAILED);
}
auto& res_mode = res.control_mode.emplace<Dynamic_AC_Res>();
convert(res_mode, target_powers, present_powers);
if (selected_mobility_needs_mode == dt::MobilityNeedsMode::ProvidedBySecc) {
set_dynamic_parameters_in_res(res_mode, dynamic_parameters, res.header.timestamp);
}
} else if (std::holds_alternative<Dynamic_BPT_AC_Req>(req.control_mode)) {
// If the ev sends a false control mode or a false energy service other than the previous selected ones, then
// the charger should terminate the session
if (selected_control_mode != dt::ControlMode::Dynamic or
selected_energy_service != dt::ServiceCategory::AC_BPT) {
return response_with_code(res, dt::ResponseCode::FAILED);
}
auto& res_mode = res.control_mode.emplace<Dynamic_BPT_AC_Res>();
convert(res_mode, target_powers, present_powers);
if (selected_mobility_needs_mode == dt::MobilityNeedsMode::ProvidedBySecc) {
set_dynamic_parameters_in_res(res_mode, dynamic_parameters, res.header.timestamp);
}
}
res.target_frequency = dt::from_float(target_frequency);
// TODO(sl): Setting EvseStatus, MeterInfo, Receipt
if (stop) {
res.status = {0, dt::EvseNotification::Terminate};
} else 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);
}
void AC_ChargeLoop::enter() {
m_ctx.log.enter_state("AC_ChargeLoop");
dynamic_parameters = m_ctx.cache_dynamic_mode_parameters.value_or(UpdateDynamicModeParameters{});
target_powers = m_ctx.cache_ac_target_power.value_or(AcTargetPower{});
present_powers = m_ctx.cache_ac_present_power.value_or(AcPresentPower{});
}
Result AC_ChargeLoop::feed(Event ev) {
if (ev == Event::CONTROL_MESSAGE) {
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;
} else if (const auto* control_data = m_ctx.get_control_event<AcTargetPower>()) {
target_powers = *control_data;
} else if (const auto* control_data = m_ctx.get_control_event<AcPresentPower>()) {
present_powers = *control_data;
}
// Ignore control message
return {};
}
if (ev != Event::V2GTP_MESSAGE) {
return {};
}
const auto variant = m_ctx.pull_request();
if (const auto req = variant->get_if<message_20::PowerDeliveryRequest>()) {
const auto res = handle_request(*req, m_ctx.session, false);
m_ctx.respond(res);
if (res.response_code >= dt::ResponseCode::FAILED) {
m_ctx.session_stopped = true;
return {};
}
// V2G20-1623 -> state machine direct transition (skipped PowerDelivery)
if (req->charge_progress == dt::Progress::Stop) {
m_ctx.feedback.signal(session::feedback::Signal::CHARGE_LOOP_FINISHED);
m_ctx.feedback.signal(session::feedback::Signal::AC_OPEN_CONTACTOR);
return m_ctx.create_state<SessionStop>();
}
return {};
} else if (const auto req = variant->get_if<message_20::AC_ChargeLoopRequest>()) {
if (first_entry_in_charge_loop) {
m_ctx.feedback.signal(session::feedback::Signal::CHARGE_LOOP_STARTED);
first_entry_in_charge_loop = false;
}
const auto res = handle_request(*req, m_ctx.session, stop, pause, target_frequency, target_powers,
present_powers, dynamic_parameters);
m_ctx.respond(res);
if (res.response_code >= dt::ResponseCode::FAILED) {
m_ctx.session_stopped = true;
return {};
}
m_ctx.feedback.ac_charge_loop_req(req->control_mode);
m_ctx.feedback.ac_charge_loop_req(req->meter_info_requested);
if (req->display_parameters) {
m_ctx.feedback.ac_charge_loop_req(*req->display_parameters);
}
return {};
} else {
m_ctx.log("Expected PowerDeliveryReq or AC_ChargeLoopRequest! But code type id: %d", variant->get_type());
// Sequence Error
const message_20::Type req_type = variant->get_type();
send_sequence_error(req_type, m_ctx);
m_ctx.session_stopped = true;
return {};
}
}
} // namespace iso15118::d20::state

View File

@@ -0,0 +1,164 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Pionix GmbH and Contributors to EVerest
#include <iso15118/d20/state/ac_charge_parameter_discovery.hpp>
#include <iso15118/d20/state/schedule_exchange.hpp>
#include <iso15118/detail/d20/context_helper.hpp>
#include <iso15118/detail/d20/state/ac_charge_parameter_discovery.hpp>
#include <iso15118/detail/d20/state/session_stop.hpp>
#include <iso15118/detail/helper.hpp>
namespace iso15118::d20::state {
namespace dt = message_20::datatypes;
using AC_ModeReq = dt::AC_CPDReqEnergyTransferMode;
using BPT_AC_ModeReq = dt::BPT_AC_CPDReqEnergyTransferMode;
using AC_ModeRes = dt::AC_CPDResEnergyTransferMode;
using BPT_AC_ModeRes = dt::BPT_AC_CPDResEnergyTransferMode;
template <typename Out>
void convert(Out& out, const d20::AcTransferLimits& limits, const d20::AcPresentPower& present_power);
template <>
void convert(AC_ModeRes& out, const d20::AcTransferLimits& limits, const d20::AcPresentPower& present_power) {
out.min_charge_power = limits.charge_power.min;
out.max_charge_power = limits.charge_power.max;
if (limits.charge_power_L2.has_value()) {
out.min_charge_power_L2 = limits.charge_power_L2.value().min;
out.max_charge_power_L2 = limits.charge_power_L2.value().max;
}
if (limits.charge_power_L3.has_value()) {
out.min_charge_power_L3 = limits.charge_power_L3.value().min;
out.max_charge_power_L3 = limits.charge_power_L3.value().max;
}
out.nominal_frequency = limits.nominal_frequency;
out.max_power_asymmetry = limits.max_power_asymmetry;
out.power_ramp_limitation = limits.power_ramp_limitation;
out.present_active_power = present_power.present_active_power;
out.present_active_power_L2 = present_power.present_active_power_L2;
out.present_active_power_L3 = present_power.present_active_power_L3;
}
template <>
void convert(BPT_AC_ModeRes& out, const d20::AcTransferLimits& limits, const d20::AcPresentPower& present_power) {
convert(static_cast<AC_ModeRes&>(out), limits, present_power);
if (limits.discharge_power.has_value()) {
out.min_discharge_power = limits.discharge_power.value().min;
out.max_discharge_power = limits.discharge_power.value().max;
}
if (limits.discharge_power_L2.has_value()) {
out.min_discharge_power_L2 = limits.discharge_power_L2.value().min;
out.max_discharge_power_L2 = limits.discharge_power_L2.value().max;
}
if (limits.discharge_power_L3.has_value()) {
out.min_discharge_power_L3 = limits.discharge_power_L3.value().min;
out.max_discharge_power_L3 = limits.discharge_power_L3.value().max;
}
}
message_20::AC_ChargeParameterDiscoveryResponse
handle_request(const message_20::AC_ChargeParameterDiscoveryRequest& req, const d20::Session& session,
const d20::AcTransferLimits& limits, const d20::AcPresentPower& powers) {
message_20::AC_ChargeParameterDiscoveryResponse res;
if (validate_and_setup_header(res.header, session, req.header.session_id) == false) {
return response_with_code(res, message_20::datatypes::ResponseCode::FAILED_UnknownSession);
}
const auto selected_energy_service = session.get_selected_services().selected_energy_service;
if (std::holds_alternative<AC_ModeReq>(req.transfer_mode)) {
if (selected_energy_service != message_20::datatypes::ServiceCategory::AC) {
return response_with_code(res, message_20::datatypes::ResponseCode::FAILED_WrongChargeParameter);
}
auto& mode = res.transfer_mode.emplace<AC_ModeRes>();
convert(mode, limits, powers);
} else if (std::holds_alternative<BPT_AC_ModeReq>(req.transfer_mode)) {
if (selected_energy_service != message_20::datatypes::ServiceCategory::AC_BPT) {
return response_with_code(res, message_20::datatypes::ResponseCode::FAILED_WrongChargeParameter);
}
auto& mode = res.transfer_mode.emplace<BPT_AC_ModeRes>();
convert(mode, limits, powers);
} else {
return response_with_code(res, message_20::datatypes::ResponseCode::FAILED_WrongChargeParameter);
}
return response_with_code(res, message_20::datatypes::ResponseCode::OK);
}
void AC_ChargeParameterDiscovery::enter() {
m_ctx.log.enter_state("AC_ChargeParameterDiscovery");
present_powers = m_ctx.cache_ac_present_power.value_or(AcPresentPower{});
}
Result AC_ChargeParameterDiscovery::feed(Event ev) {
if (ev == Event::CONTROL_MESSAGE) {
if (const auto* control_data = m_ctx.get_control_event<AcPresentPower>()) {
present_powers = *control_data;
}
return {};
}
if (ev != Event::V2GTP_MESSAGE) {
return {};
}
const auto variant = m_ctx.pull_request();
if (const auto req = variant->get_if<message_20::AC_ChargeParameterDiscoveryRequest>()) {
if (const auto* mode = std::get_if<AC_ModeReq>(&req->transfer_mode)) {
// Set EV transfer limits
m_ctx.session_ev_info.ev_transfer_limits.emplace<AC_ModeReq>(*mode);
} else if (const auto* mode = std::get_if<BPT_AC_ModeReq>(&req->transfer_mode)) {
// Set EV transfer limits
m_ctx.session_ev_info.ev_transfer_limits.emplace<BPT_AC_ModeReq>(*mode);
}
const auto res = handle_request(*req, m_ctx.session, m_ctx.session_config.ac_limits, present_powers);
m_ctx.respond(res);
if (res.response_code >= message_20::datatypes::ResponseCode::FAILED) {
m_ctx.session_stopped = true;
return {};
}
m_ctx.feedback.ac_limits(req->transfer_mode);
return m_ctx.create_state<ScheduleExchange>();
} else if (const auto req = variant->get_if<message_20::SessionStopRequest>()) {
const auto res = handle_request(*req, m_ctx.session);
m_ctx.respond(res);
m_ctx.session_stopped = true;
return {};
} else {
m_ctx.log("expected AC_ChargeParameterDiscovery! But code type id: %d", variant->get_type());
m_ctx.session_stopped = true;
// Sequence Error
const message_20::Type req_type = variant->get_type();
send_sequence_error(req_type, m_ctx);
return {};
}
}
} // namespace iso15118::d20::state

View File

@@ -0,0 +1,157 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <algorithm>
#include <iso15118/d20/state/authorization.hpp>
#include <iso15118/d20/state/service_discovery.hpp>
#include <iso15118/d20/timeout.hpp>
#include <iso15118/detail/d20/context_helper.hpp>
#include <iso15118/detail/d20/state/authorization.hpp>
#include <iso15118/detail/d20/state/session_stop.hpp>
namespace iso15118::d20::state {
namespace dt = message_20::datatypes;
using AuthStatus = dt::AuthStatus;
static bool find_auth_service_in_offered_services(const dt::Authorization& req_selected_auth_service,
const d20::Session& session) {
auto& offered_auth_services = session.offered_services.auth_services;
return std::find(offered_auth_services.begin(), offered_auth_services.end(), req_selected_auth_service) !=
offered_auth_services.end();
}
message_20::AuthorizationResponse handle_request(const message_20::AuthorizationRequest& req,
const d20::Session& session,
const dt::AuthStatus& authorization_status, bool timeout_reached) {
message_20::AuthorizationResponse res = message_20::AuthorizationResponse();
if (validate_and_setup_header(res.header, session, req.header.session_id) == false) {
return response_with_code(res, dt::ResponseCode::FAILED_UnknownSession);
}
if (timeout_reached) {
return response_with_code(res, dt::ResponseCode::FAILED);
}
// [V2G20-2209] Check if authorization service was offered in authorization_setup res
if (not find_auth_service_in_offered_services(req.selected_authorization_service, session)) {
return response_with_code(
res, dt::ResponseCode::WARNING_AuthorizationSelectionInvalid); // [V2G20-2226] Handling if warning
}
auto response_code = dt::ResponseCode::OK;
switch (req.selected_authorization_service) {
case dt::Authorization::EIM:
switch (authorization_status) {
case AuthStatus::Accepted:
res.evse_processing = dt::Processing::Finished;
response_code = dt::ResponseCode::OK;
break;
case AuthStatus::Rejected: // Failure [V2G20-2230]
res.evse_processing = dt::Processing::Finished;
response_code = dt::ResponseCode::WARNING_EIMAuthorizationFailure;
break;
case AuthStatus::Pending:
default:
res.evse_processing = dt::Processing::Ongoing;
response_code = dt::ResponseCode::OK;
break;
}
break;
case dt::Authorization::PnC:
// TODO(SL): Handle PnC
break;
default:
// TODO(SL): Fill
break;
}
return response_with_code(res, response_code);
}
void Authorization::enter() {
m_ctx.log.enter_state("Authorization");
}
Result Authorization::feed(Event ev) {
if (ev == Event::CONTROL_MESSAGE) {
const auto control_data = m_ctx.get_control_event<AuthorizationResponse>();
if (not control_data) {
// Ignore control message
return {};
}
if (*control_data) {
authorization_status = AuthStatus::Accepted;
} else {
authorization_status = AuthStatus::Rejected;
}
return {};
}
if (ev == Event::TIMEOUT) {
const auto timeout = m_ctx.get_active_timeout();
if (timeout and *timeout == d20::TimeoutType::ONGOING) {
timeout_ongoing_reached = true;
}
return {};
}
if (ev != Event::V2GTP_MESSAGE) {
return {};
}
const auto variant = m_ctx.pull_request();
if (const auto req = variant->get_if<message_20::AuthorizationRequest>()) {
if (first_req_msg) {
// TODO(SL): Check if ExternalPayment or Contract is active
m_ctx.start_timeout(d20::TimeoutType::ONGOING, TIMEOUT_EIM_ONGOING);
first_req_msg = false;
}
const auto res = handle_request(*req, m_ctx.session, authorization_status, timeout_ongoing_reached);
m_ctx.respond(res);
if (res.response_code >= dt::ResponseCode::FAILED) {
m_ctx.session_stopped = true;
return {};
}
if (authorization_status == AuthStatus::Accepted) {
authorization_status = AuthStatus::Pending; // reset
m_ctx.stop_timeout(d20::TimeoutType::ONGOING);
return m_ctx.create_state<ServiceDiscovery>();
} else {
return {};
}
} else if (const auto req = variant->get_if<message_20::SessionStopRequest>()) {
const auto res = handle_request(*req, m_ctx.session);
m_ctx.respond(res);
m_ctx.session_stopped = true;
return {};
} else {
m_ctx.log("expected AuthorizationReq! But code type id: %d", variant->get_type());
// Sequence Error
const message_20::Type req_type = variant->get_type();
send_sequence_error(req_type, m_ctx);
m_ctx.session_stopped = true;
return {};
}
}
} // namespace iso15118::d20::state

View File

@@ -0,0 +1,104 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <random>
#include <iso15118/d20/state/authorization.hpp>
#include <iso15118/d20/state/authorization_setup.hpp>
#include <iso15118/detail/d20/context_helper.hpp>
#include <iso15118/detail/helper.hpp>
#include <iso15118/detail/d20/state/authorization_setup.hpp>
#include <iso15118/detail/d20/state/session_stop.hpp>
namespace iso15118::d20::state {
namespace dt = message_20::datatypes;
message_20::AuthorizationSetupResponse handle_request(const message_20::AuthorizationSetupRequest& req,
d20::Session& session, bool cert_install_service,
const std::vector<dt::Authorization>& authorization_services) {
auto res = message_20::AuthorizationSetupResponse(); // default mandatory values [V2G20-736]
if (not validate_and_setup_header(res.header, session, req.header.session_id)) {
return response_with_code(res, dt::ResponseCode::FAILED_UnknownSession);
}
res.certificate_installation_service = cert_install_service;
if (authorization_services.empty()) {
logf_warning("authorization_services was not set. Setting EIM as auth_mode");
res.authorization_services = {dt::Authorization::EIM};
} else {
res.authorization_services = everest::lib::util::fixed_vector<dt::Authorization, 2>(authorization_services);
}
session.offered_services.auth_services = res.authorization_services;
if (res.authorization_services.size() == 1 && res.authorization_services[0] == dt::Authorization::EIM) {
res.authorization_mode.emplace<dt::EIM_ASResAuthorizationMode>();
} else {
auto& pnc_auth_mode = res.authorization_mode.emplace<dt::PnC_ASResAuthorizationMode>();
std::random_device rd;
std::mt19937 generator(rd());
std::uniform_int_distribution<uint8_t> distribution(0x00, 0xff);
for (auto& item : pnc_auth_mode.gen_challenge) {
item = distribution(generator);
}
}
return response_with_code(res, dt::ResponseCode::OK);
}
void AuthorizationSetup::enter() {
m_ctx.log.enter_state("AuthorizationSetup");
}
Result AuthorizationSetup::feed(Event ev) {
if (ev != Event::V2GTP_MESSAGE) {
return {};
}
const auto variant = m_ctx.pull_request();
if (const auto req = variant->get_if<message_20::AuthorizationSetupRequest>()) {
const auto res = handle_request(*req, m_ctx.session, m_ctx.session_config.cert_install_service,
m_ctx.session_config.authorization_services);
logf_info("Timestamp: %d", req->header.timestamp);
m_ctx.respond(res);
if (res.response_code >= dt::ResponseCode::FAILED) {
m_ctx.session_stopped = true;
return {};
}
// Todo(sl): PnC is currently not supported
m_ctx.feedback.signal(session::feedback::Signal::REQUIRE_AUTH_EIM);
return m_ctx.create_state<Authorization>();
} else if (const auto req = variant->get_if<message_20::SessionStopRequest>()) {
const auto res = handle_request(*req, m_ctx.session);
m_ctx.respond(res);
m_ctx.session_stopped = true;
return {};
} else {
m_ctx.log("expected AuthorizationSetupReq! But code type id: %d", variant->get_type());
// Sequence Error
const message_20::Type req_type = variant->get_type();
send_sequence_error(req_type, m_ctx);
m_ctx.session_stopped = true;
return {};
}
}
} // namespace iso15118::d20::state

View File

@@ -0,0 +1,96 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/d20/state/dc_cable_check.hpp>
#include <iso15118/d20/state/dc_pre_charge.hpp>
#include <iso15118/detail/d20/context_helper.hpp>
#include <iso15118/detail/d20/state/dc_cable_check.hpp>
#include <iso15118/detail/d20/state/session_stop.hpp>
#include <iso15118/detail/helper.hpp>
namespace iso15118::d20::state {
namespace dt = message_20::datatypes;
message_20::DC_CableCheckResponse handle_request(const message_20::DC_CableCheckRequest& req,
const d20::Session& session, bool cable_check_done) {
message_20::DC_CableCheckResponse res;
if (validate_and_setup_header(res.header, session, req.header.session_id) == false) {
return response_with_code(res, dt::ResponseCode::FAILED_UnknownSession);
}
if (not cable_check_done) {
res.processing = dt::Processing::Ongoing;
} else {
res.processing = dt::Processing::Finished;
}
return response_with_code(res, dt::ResponseCode::OK);
}
void DC_CableCheck::enter() {
m_ctx.log.enter_state("DC_CableCheck");
}
Result DC_CableCheck::feed(Event ev) {
if (ev == Event::CONTROL_MESSAGE) {
const auto control_data = m_ctx.get_control_event<CableCheckFinished>();
if (not control_data) {
// Ignore control message
return {};
}
cable_check_done = *control_data;
return {};
}
if (ev != Event::V2GTP_MESSAGE) {
return {};
}
const auto variant = m_ctx.pull_request();
if (const auto req = variant->get_if<message_20::DC_CableCheckRequest>()) {
if (not cable_check_initiated) {
m_ctx.feedback.signal(session::feedback::Signal::START_CABLE_CHECK);
cable_check_initiated = true;
}
const auto res = handle_request(*req, m_ctx.session, cable_check_done);
m_ctx.respond(res);
if (res.response_code >= dt::ResponseCode::FAILED) {
m_ctx.session_stopped = true;
return {};
}
if (cable_check_done) {
return m_ctx.create_state<DC_PreCharge>();
} else {
return {};
}
} else if (const auto req = variant->get_if<message_20::SessionStopRequest>()) {
const auto res = handle_request(*req, m_ctx.session);
m_ctx.respond(res);
m_ctx.session_stopped = true;
return {};
} else {
m_ctx.log("expected DC_CableCheckReq! But code type id: %d", variant->get_type());
// Sequence Error
const message_20::Type req_type = variant->get_type();
send_sequence_error(req_type, m_ctx);
m_ctx.session_stopped = true;
return {};
}
}
} // namespace iso15118::d20::state

View File

@@ -0,0 +1,279 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/d20/state/dc_charge_loop.hpp>
#include <iso15118/d20/state/dc_welding_detection.hpp>
#include <iso15118/detail/d20/context_helper.hpp>
#include <iso15118/detail/d20/state/dc_charge_loop.hpp>
#include <iso15118/detail/d20/state/power_delivery.hpp>
#include <iso15118/detail/helper.hpp>
namespace iso15118::d20::state {
namespace dt = message_20::datatypes;
using Scheduled_DC_Req = dt::Scheduled_DC_CLReqControlMode;
using Scheduled_BPT_DC_Req = dt::BPT_Scheduled_DC_CLReqControlMode;
using Dynamic_DC_Req = dt::Dynamic_DC_CLReqControlMode;
using Dynamic_BPT_DC_Req = dt::BPT_Dynamic_DC_CLReqControlMode;
using Scheduled_DC_Res = dt::Scheduled_DC_CLResControlMode;
using Scheduled_BPT_DC_Res = dt::BPT_Scheduled_DC_CLResControlMode;
using Dynamic_DC_Res = dt::Dynamic_DC_CLResControlMode;
using Dynamic_BPT_DC_Res = dt::BPT_Dynamic_DC_CLResControlMode;
template <typename In, typename Out> void convert(Out& out, const In& in);
template <> void convert(Scheduled_DC_Res& out, const d20::DcTransferLimits& in) {
out.max_charge_power = in.charge_limits.power.max;
out.min_charge_power = in.charge_limits.power.min;
out.max_charge_current = in.charge_limits.current.max;
out.max_voltage = in.voltage.max;
}
template <> void convert(Scheduled_BPT_DC_Res& out, const d20::DcTransferLimits& in) {
out.max_charge_power = in.charge_limits.power.max;
out.min_charge_power = in.charge_limits.power.min;
out.max_charge_current = in.charge_limits.current.max;
out.max_voltage = in.voltage.max;
out.min_voltage = in.voltage.min;
if (in.discharge_limits.has_value()) {
auto& discharge_limits = in.discharge_limits.value();
out.max_discharge_power = discharge_limits.power.max;
out.min_discharge_power = discharge_limits.power.min;
out.max_discharge_current = discharge_limits.current.max;
}
}
template <> void convert(Dynamic_DC_Res& out, const d20::DcTransferLimits& in) {
out.max_charge_power = in.charge_limits.power.max;
out.min_charge_power = in.charge_limits.power.min;
out.max_charge_current = in.charge_limits.current.max;
out.max_voltage = in.voltage.max;
}
template <> void convert(Dynamic_BPT_DC_Res& out, const d20::DcTransferLimits& in) {
out.max_charge_power = in.charge_limits.power.max;
out.min_charge_power = in.charge_limits.power.min;
out.max_charge_current = in.charge_limits.current.max;
out.max_voltage = in.voltage.max;
out.min_voltage = in.voltage.min;
if (in.discharge_limits.has_value()) {
auto& discharge_limits = in.discharge_limits.value();
out.max_discharge_power = discharge_limits.power.max;
out.min_discharge_power = discharge_limits.power.min;
out.max_discharge_current = discharge_limits.current.max;
}
}
namespace {
template <typename T>
void set_dynamic_parameters_in_res(T& res_mode, const UpdateDynamicModeParameters& parameters,
uint64_t header_timestamp) {
if (parameters.departure_time) {
const auto departure_time = static_cast<uint64_t>(parameters.departure_time.value());
if (departure_time > header_timestamp) {
res_mode.departure_time = static_cast<uint32_t>(departure_time - header_timestamp);
}
}
res_mode.target_soc = parameters.target_soc;
res_mode.minimum_soc = parameters.min_soc;
res_mode.ack_max_delay = 30; // TODO(sl) what to send here and define 30 seconds as const
}
} // namespace
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 bool pause,
const DcTransferLimits& dc_limits,
const UpdateDynamicModeParameters& dynamic_parameters) {
message_20::DC_ChargeLoopResponse res;
if (validate_and_setup_header(res.header, session, req.header.session_id) == false) {
return response_with_code(res, dt::ResponseCode::FAILED_UnknownSession);
}
const auto& selected_services = session.get_selected_services();
const auto selected_control_mode = selected_services.selected_control_mode;
const auto selected_energy_service = selected_services.selected_energy_service;
const auto selected_mobility_needs_mode = selected_services.selected_mobility_needs_mode;
if (std::holds_alternative<Scheduled_DC_Req>(req.control_mode)) {
// If the ev sends a false control mode or a false energy service other than the previous selected ones, then
// the charger should terminate the session
if (selected_control_mode != dt::ControlMode::Scheduled or
not(selected_energy_service == dt::ServiceCategory::DC or
selected_energy_service == dt::ServiceCategory::MCS)) {
return response_with_code(res, dt::ResponseCode::FAILED);
}
auto& res_mode = res.control_mode.emplace<Scheduled_DC_Res>();
convert(res_mode, dc_limits);
} else if (std::holds_alternative<Scheduled_BPT_DC_Req>(req.control_mode)) {
// If the ev sends a false control mode or a false energy service other than the previous selected ones, then
// the charger should terminate the session
if (selected_control_mode != dt::ControlMode::Scheduled or
not(selected_energy_service == dt::ServiceCategory::DC_BPT or
selected_energy_service == dt::ServiceCategory::MCS_BPT)) {
return response_with_code(res, dt::ResponseCode::FAILED);
}
if (not dc_limits.discharge_limits.has_value()) {
logf_error("Transfer mode is BPT, but only dc limits without discharge limits are provided!");
return response_with_code(res, dt::ResponseCode::FAILED);
}
auto& res_mode = res.control_mode.emplace<Scheduled_BPT_DC_Res>();
convert(res_mode, dc_limits);
} else if (std::holds_alternative<Dynamic_DC_Req>(req.control_mode)) {
// If the ev sends a false control mode or a false energy service other than the previous selected ones, then
// the charger should terminate the session
if (selected_control_mode != dt::ControlMode::Dynamic or
not(selected_energy_service == dt::ServiceCategory::DC or
selected_energy_service == dt::ServiceCategory::MCS)) {
return response_with_code(res, dt::ResponseCode::FAILED);
}
auto& res_mode = res.control_mode.emplace<Dynamic_DC_Res>();
convert(res_mode, dc_limits);
if (selected_mobility_needs_mode == dt::MobilityNeedsMode::ProvidedBySecc) {
set_dynamic_parameters_in_res(res_mode, dynamic_parameters, res.header.timestamp);
}
} else if (std::holds_alternative<Dynamic_BPT_DC_Req>(req.control_mode)) {
// If the ev sends a false control mode or a false energy service other than the previous selected ones, then
// the charger should terminate the session
if (selected_control_mode != dt::ControlMode::Dynamic or
not(selected_energy_service == dt::ServiceCategory::DC_BPT or
selected_energy_service == dt::ServiceCategory::MCS_BPT)) {
return response_with_code(res, dt::ResponseCode::FAILED);
}
if (not dc_limits.discharge_limits.has_value()) {
logf_error("Transfer mode is BPT, but only dc limits without discharge limits are provided!");
return response_with_code(res, dt::ResponseCode::FAILED);
}
auto& res_mode = res.control_mode.emplace<Dynamic_BPT_DC_Res>();
convert(res_mode, dc_limits);
if (selected_mobility_needs_mode == dt::MobilityNeedsMode::ProvidedBySecc) {
set_dynamic_parameters_in_res(res_mode, dynamic_parameters, res.header.timestamp);
}
}
res.present_voltage = dt::from_float(present_voltage);
res.present_current = dt::from_float(present_current);
// TODO(sl): Setting EvseStatus, MeterInfo, Receipt, *_limit_achieved
if (stop) {
res.status = {0, dt::EvseNotification::Terminate};
} else 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);
}
void DC_ChargeLoop::enter() {
m_ctx.log.enter_state("DC_ChargeLoop");
dynamic_parameters = m_ctx.cache_dynamic_mode_parameters.value_or(UpdateDynamicModeParameters{});
}
Result DC_ChargeLoop::feed(Event ev) {
if (ev == Event::CONTROL_MESSAGE) {
if (const auto* control_data = m_ctx.get_control_event<PresentVoltageCurrent>()) {
present_voltage = control_data->voltage;
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;
}
// Ignore control message
return {};
}
if (ev != Event::V2GTP_MESSAGE) {
return {};
}
const auto variant = m_ctx.pull_request();
if (const auto req = variant->get_if<message_20::PowerDeliveryRequest>()) {
const auto res = handle_request(*req, m_ctx.session, false);
m_ctx.respond(res);
if (res.response_code >= dt::ResponseCode::FAILED) {
m_ctx.session_stopped = true;
return {};
}
// Reset
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);
return m_ctx.create_state<DC_WeldingDetection>();
}
return {};
} else if (const auto req = variant->get_if<message_20::DC_ChargeLoopRequest>()) {
if (first_entry_in_charge_loop) {
m_ctx.feedback.signal(session::feedback::Signal::CHARGE_LOOP_STARTED);
first_entry_in_charge_loop = false;
}
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);
if (res.response_code >= dt::ResponseCode::FAILED) {
m_ctx.session_stopped = true;
return {};
}
m_ctx.feedback.dc_charge_loop_req(req->control_mode);
m_ctx.feedback.dc_charge_loop_req(req->present_voltage);
m_ctx.feedback.dc_charge_loop_req(req->meter_info_requested);
if (req->display_parameters) {
m_ctx.feedback.dc_charge_loop_req(*req->display_parameters);
}
return {};
} else {
m_ctx.log("Expected PowerDeliveryReq or DC_ChargeLoopReq! But code type id: %d", variant->get_type());
// Sequence Error
const message_20::Type req_type = variant->get_type();
send_sequence_error(req_type, m_ctx);
m_ctx.session_stopped = true;
return {};
}
}
} // namespace iso15118::d20::state

View File

@@ -0,0 +1,156 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/d20/state/dc_charge_parameter_discovery.hpp>
#include <iso15118/d20/state/schedule_exchange.hpp>
#include <iso15118/detail/d20/context_helper.hpp>
#include <iso15118/detail/d20/state/dc_charge_parameter_discovery.hpp>
#include <iso15118/detail/d20/state/session_stop.hpp>
#include <iso15118/detail/helper.hpp>
namespace iso15118::d20::state {
namespace dt = message_20::datatypes;
using DC_ModeReq = dt::DC_CPDReqEnergyTransferMode;
using BPT_DC_ModeReq = dt::BPT_DC_CPDReqEnergyTransferMode;
using DC_ModeRes = dt::DC_CPDResEnergyTransferMode;
using BPT_DC_ModeRes = dt::BPT_DC_CPDResEnergyTransferMode;
template <typename In, typename Out> void convert(Out& out, const In& in);
template <> void convert(DC_ModeRes& out, const d20::DcTransferLimits& in) {
out.max_charge_power = in.charge_limits.power.max;
out.min_charge_power = in.charge_limits.power.min;
out.max_charge_current = in.charge_limits.current.max;
out.min_charge_current = in.charge_limits.current.min;
out.max_voltage = in.voltage.max;
out.min_voltage = in.voltage.min;
out.power_ramp_limit = in.power_ramp_limit;
}
template <> void convert(BPT_DC_ModeRes& out, const d20::DcTransferLimits& in) {
convert(static_cast<DC_ModeRes&>(out), in);
if (in.discharge_limits.has_value()) {
auto& discharge_limits = in.discharge_limits.value();
out.max_discharge_power = discharge_limits.power.max;
out.min_discharge_power = discharge_limits.power.min;
out.max_discharge_current = discharge_limits.current.max;
out.min_discharge_current = discharge_limits.current.min;
}
}
message_20::DC_ChargeParameterDiscoveryResponse
handle_request(const message_20::DC_ChargeParameterDiscoveryRequest& req, const d20::Session& session,
const d20::DcTransferLimits& dc_limits) {
message_20::DC_ChargeParameterDiscoveryResponse res;
if (validate_and_setup_header(res.header, session, req.header.session_id) == false) {
return response_with_code(res, dt::ResponseCode::FAILED_UnknownSession);
}
const auto selected_energy_service = session.get_selected_services().selected_energy_service;
if (std::holds_alternative<DC_ModeReq>(req.transfer_mode)) {
if (not(selected_energy_service == dt::ServiceCategory::DC or
selected_energy_service == dt::ServiceCategory::MCS)) {
return response_with_code(res, dt::ResponseCode::FAILED_WrongChargeParameter);
}
auto& mode = res.transfer_mode.emplace<DC_ModeRes>();
convert(mode, dc_limits);
} else if (std::holds_alternative<BPT_DC_ModeReq>(req.transfer_mode)) {
if (not(selected_energy_service == dt::ServiceCategory::DC_BPT or
selected_energy_service == dt::ServiceCategory::MCS_BPT)) {
return response_with_code(res, dt::ResponseCode::FAILED_WrongChargeParameter);
}
if (not dc_limits.discharge_limits.has_value()) {
logf_error("Transfer mode is BPT, but only dc limits without discharge limits are provided!");
return response_with_code(res, dt::ResponseCode::FAILED);
}
auto& mode = res.transfer_mode.emplace<BPT_DC_ModeRes>();
convert(mode, dc_limits);
} else {
// Not supported transfer_mode
return response_with_code(res, dt::ResponseCode::FAILED_WrongChargeParameter);
}
return response_with_code(res, dt::ResponseCode::OK);
}
void DC_ChargeParameterDiscovery::enter() {
m_ctx.log.enter_state("DC_ChargeParameterDiscovery");
}
Result DC_ChargeParameterDiscovery::feed(Event ev) {
if (ev != Event::V2GTP_MESSAGE) {
return {};
}
const auto variant = m_ctx.pull_request();
if (const auto req = variant->get_if<message_20::DC_ChargeParameterDiscoveryRequest>()) {
auto dc_max_limits = session::feedback::DcMaximumLimits{};
if (const auto* mode = std::get_if<DC_ModeReq>(&req->transfer_mode)) {
dc_max_limits.current = dt::from_RationalNumber(mode->max_charge_current);
dc_max_limits.voltage = dt::from_RationalNumber(mode->max_voltage);
dc_max_limits.power = dt::from_RationalNumber(mode->max_charge_power);
logf_info("Max charge current %fA", dt::from_RationalNumber(mode->max_charge_current));
// Set EV transfer limits
m_ctx.session_ev_info.ev_transfer_limits.emplace<DC_ModeReq>(*mode);
} else if (const auto* mode = std::get_if<BPT_DC_ModeReq>(&req->transfer_mode)) {
dc_max_limits.current = dt::from_RationalNumber(mode->max_charge_current);
dc_max_limits.voltage = dt::from_RationalNumber(mode->max_voltage);
dc_max_limits.power = dt::from_RationalNumber(mode->max_charge_power);
logf_info("Max charge current %fA", dt::from_RationalNumber(mode->max_charge_current));
logf_info("Max discharge current %fA", dt::from_RationalNumber(mode->max_discharge_current));
// Set EV transfer limits
m_ctx.session_ev_info.ev_transfer_limits.emplace<BPT_DC_ModeReq>(*mode);
}
const auto res = handle_request(*req, m_ctx.session, m_ctx.session_config.powersupply_limits);
m_ctx.respond(res);
m_ctx.feedback.dc_max_limits(dc_max_limits);
if (res.response_code >= dt::ResponseCode::FAILED) {
m_ctx.session_stopped = true;
return {};
}
return m_ctx.create_state<ScheduleExchange>();
} else if (const auto req = variant->get_if<message_20::SessionStopRequest>()) {
const auto res = handle_request(*req, m_ctx.session);
m_ctx.respond(res);
m_ctx.session_stopped = true;
return {};
} else {
m_ctx.log("expected DC_ChargeParameterDiscovery! But code type id: %d", variant->get_type());
m_ctx.session_stopped = true;
// Sequence Error
const message_20::Type req_type = variant->get_type();
send_sequence_error(req_type, m_ctx);
return {};
}
}
} // namespace iso15118::d20::state

View File

@@ -0,0 +1,90 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/d20/state/dc_pre_charge.hpp>
#include <iso15118/d20/state/power_delivery.hpp>
#include <iso15118/detail/d20/context_helper.hpp>
#include <iso15118/detail/d20/state/dc_pre_charge.hpp>
#include <iso15118/detail/d20/state/session_stop.hpp>
#include <iso15118/detail/helper.hpp>
namespace iso15118::d20::state {
namespace dt = message_20::datatypes;
message_20::DC_PreChargeResponse handle_request(const message_20::DC_PreChargeRequest& req, const d20::Session& session,
const float present_voltage) {
message_20::DC_PreChargeResponse res;
if (validate_and_setup_header(res.header, session, req.header.session_id) == false) {
return response_with_code(res, dt::ResponseCode::FAILED_UnknownSession);
}
res.present_voltage = dt::from_float(present_voltage);
return response_with_code(res, dt::ResponseCode::OK);
}
void DC_PreCharge::enter() {
m_ctx.log.enter_state("DC_PreCharge");
}
Result DC_PreCharge::feed(Event ev) {
if (ev == Event::CONTROL_MESSAGE) {
const auto control_data = m_ctx.get_control_event<PresentVoltageCurrent>();
if (not control_data) {
// Ignore control message
return {};
}
present_voltage = control_data->voltage;
return {};
}
if (ev != Event::V2GTP_MESSAGE) {
return {};
}
const auto variant = m_ctx.pull_request();
if (const auto req = variant->get_if<message_20::DC_PreChargeRequest>()) {
if (not pre_charge_initiated) {
m_ctx.feedback.signal(session::feedback::Signal::PRE_CHARGE_STARTED);
pre_charge_initiated = true;
}
const auto res = handle_request(*req, m_ctx.session, present_voltage);
m_ctx.feedback.dc_pre_charge_target_voltage(message_20::datatypes::from_RationalNumber(req->target_voltage));
m_ctx.respond(res);
if (res.response_code >= dt::ResponseCode::FAILED) {
m_ctx.session_stopped = true;
return {};
}
return m_ctx.create_state<PowerDelivery>();
} else if (const auto req = variant->get_if<message_20::SessionStopRequest>()) {
const auto res = handle_request(*req, m_ctx.session);
m_ctx.respond(res);
m_ctx.session_stopped = true;
return {};
} else {
m_ctx.log("expected DC_PreChargeReq! But code type id: %d", variant->get_type());
// Sequence Error
const message_20::Type req_type = variant->get_type();
send_sequence_error(req_type, m_ctx);
m_ctx.session_stopped = true;
return {};
}
}
} // namespace iso15118::d20::state

View File

@@ -0,0 +1,106 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/d20/state/dc_welding_detection.hpp>
#include <iso15118/d20/state/session_stop.hpp>
#include <iso15118/detail/d20/context_helper.hpp>
#include <iso15118/detail/d20/state/dc_welding_detection.hpp>
#include <iso15118/detail/d20/state/session_stop.hpp>
#include <iso15118/detail/helper.hpp>
namespace iso15118::d20::state {
namespace dt = message_20::datatypes;
message_20::DC_WeldingDetectionResponse handle_request(const message_20::DC_WeldingDetectionRequest& req,
const d20::Session& session, const float present_voltage) {
message_20::DC_WeldingDetectionResponse res;
if (validate_and_setup_header(res.header, session, req.header.session_id) == false) {
return response_with_code(res, dt::ResponseCode::FAILED_UnknownSession);
}
res.present_voltage = dt::from_float(present_voltage);
return response_with_code(res, dt::ResponseCode::OK);
}
void DC_WeldingDetection::enter() {
m_ctx.log.enter_state("DC_WeldingDetection");
}
Result DC_WeldingDetection::feed(Event ev) {
if (ev == Event::CONTROL_MESSAGE) {
const auto control_data = m_ctx.get_control_event<PresentVoltageCurrent>();
if (not control_data) {
// Ignore control message
return {};
}
present_voltage = control_data->voltage;
return {};
}
if (ev != Event::V2GTP_MESSAGE) {
return {};
}
const auto variant = m_ctx.pull_request();
if (const auto req = variant->get_if<message_20::DC_WeldingDetectionRequest>()) {
const auto res = handle_request(*req, m_ctx.session, present_voltage);
m_ctx.respond(res);
if (res.response_code >= dt::ResponseCode::FAILED) {
m_ctx.session_stopped = true;
return {};
}
if (req->processing == dt::Processing::Ongoing) {
return {};
}
return m_ctx.create_state<SessionStop>();
} else if (const auto req = variant->get_if<message_20::SessionStopRequest>()) {
const auto res = handle_request(*req, m_ctx.session);
if (req->ev_termination_code.has_value()) {
logf_info("EV termination code: %s", req->ev_termination_code.value().c_str());
}
if (req->ev_termination_explanation.has_value()) {
logf_info("EV Termination explanation: %s", req->ev_termination_explanation.value().c_str());
}
m_ctx.respond(res);
// Todo(sl): Tell the reason why the charger is stopping. Shutdown, Error, etc.
if (req->charging_session == message_20::datatypes::ChargingSession::Pause) {
m_ctx.session_paused = true;
if (not m_ctx.pause_ctx.has_value()) {
logf_error("Pause the session but pause_ctx has no value");
return {};
}
m_ctx.pause_ctx->selected_service_parameters = m_ctx.session.get_selected_services();
} else if (req->charging_session == message_20::datatypes::ChargingSession::Terminate) {
m_ctx.session_stopped = true;
m_ctx.pause_ctx.reset();
}
return {};
} else {
m_ctx.log("expected DC_WeldingDetection! But code type id: %d", variant->get_type());
// Sequence Error
const message_20::Type req_type = variant->get_type();
send_sequence_error(req_type, m_ctx);
m_ctx.session_stopped = true;
return {};
}
}
} // namespace iso15118::d20::state

View File

@@ -0,0 +1,162 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/d20/state/ac_charge_loop.hpp>
#include <iso15118/d20/state/dc_charge_loop.hpp>
#include <iso15118/d20/state/power_delivery.hpp>
#include <iso15118/d20/state/session_stop.hpp>
#include <iso15118/d20/timeout.hpp>
#include <iso15118/detail/d20/context_helper.hpp>
#include <iso15118/detail/d20/state/dc_pre_charge.hpp>
#include <iso15118/detail/d20/state/power_delivery.hpp>
#include <iso15118/detail/helper.hpp>
namespace iso15118::d20::state {
namespace dt = message_20::datatypes;
message_20::PowerDeliveryResponse handle_request(const message_20::PowerDeliveryRequest& req,
const d20::Session& session, bool contactor_error) {
message_20::PowerDeliveryResponse res;
if (validate_and_setup_header(res.header, session, req.header.session_id) == false) {
return response_with_code(res, dt::ResponseCode::FAILED_UnknownSession);
}
if (contactor_error) {
return response_with_code(res, dt::ResponseCode::FAILED_ContactorError);
}
// TODO(sl): Check Req PowerProfile & ChannelSelection
// Todo(sl): Add standby feature and define as everest module config
if (req.charge_progress == dt::Progress::Standby) {
return response_with_code(res, dt::ResponseCode::WARNING_StandbyNotAllowed);
}
return response_with_code(res, dt::ResponseCode::OK);
}
void PowerDelivery::enter() {
m_ctx.log.enter_state("PowerDelivery");
}
Result PowerDelivery::feed(Event ev) {
const auto selected_energy_service = m_ctx.session.get_selected_services().selected_energy_service;
if (ev == Event::CONTROL_MESSAGE) {
if (const auto* control_data = m_ctx.get_control_event<PresentVoltageCurrent>()) {
present_voltage = control_data->voltage;
} else if (const auto* control_data = m_ctx.get_control_event<ClosedContactor>()) {
ac_connector_closed = control_data;
if (not ac_connector_closed) {
logf_warning(
"Got ClosedContactor event, but contactor is not closed. Waiting until the contactor is closed");
return {};
}
m_ctx.stop_timeout(d20::TimeoutType::CONTACTOR);
if (not previous_req.has_value()) {
logf_warning("There is no power_delivery_req messages saved!");
return {};
}
const auto& res = handle_request(previous_req.value(), m_ctx.session, false);
m_ctx.respond(res);
if (res.response_code >= dt::ResponseCode::FAILED) {
m_ctx.session_stopped = true;
return {};
}
return m_ctx.create_state<AC_ChargeLoop>();
}
return {};
}
if (ev == Event::TIMEOUT) {
const auto timeout = m_ctx.get_active_timeout();
if (timeout and *timeout == d20::TimeoutType::CONTACTOR) {
// TODO(SL): Check if value_or is the correct way
const auto& res =
handle_request(previous_req.value_or(message_20::PowerDeliveryRequest{}), m_ctx.session, true);
m_ctx.respond(res);
m_ctx.session_stopped = true;
}
return {};
}
if (ev != Event::V2GTP_MESSAGE) {
return {};
}
const auto variant = m_ctx.pull_request();
if (const auto req = variant->get_if<message_20::DC_PreChargeRequest>()) {
const auto res = handle_request(*req, m_ctx.session, present_voltage);
m_ctx.feedback.dc_pre_charge_target_voltage(dt::from_RationalNumber(req->target_voltage));
m_ctx.respond(res);
if (res.response_code >= dt::ResponseCode::FAILED) {
m_ctx.session_stopped = true;
return {};
}
return {};
} else if (const auto req = variant->get_if<message_20::PowerDeliveryRequest>()) {
if (req->charge_progress == dt::Progress::Start) {
m_ctx.feedback.signal(session::feedback::Signal::SETUP_FINISHED);
}
if (m_ctx.session.is_ac_charger() and ac_connector_closed == false and
req->charge_progress == dt::Progress::Start) {
// Save req
previous_req = *req;
// Close the AC contactor so that charging can start
m_ctx.feedback.signal(session::feedback::Signal::AC_CLOSE_CONTACTOR);
m_ctx.start_timeout(d20::TimeoutType::CONTACTOR, 3000);
logf_info("Waiting for contactor is closed");
return {};
}
const auto& res = handle_request(*req, m_ctx.session, false);
m_ctx.respond(res);
if (res.response_code >= dt::ResponseCode::FAILED) {
m_ctx.session_stopped = true;
return {};
}
if (m_ctx.session.is_ac_charger()) {
return m_ctx.create_state<AC_ChargeLoop>();
}
if (m_ctx.session.is_dc_charger()) {
return m_ctx.create_state<DC_ChargeLoop>();
}
m_ctx.log("expected selected_energy_service AC, AC_BPT, DC, DC_BPT! But code type id: %d",
static_cast<int>(selected_energy_service));
m_ctx.session_stopped = true;
return {};
} else {
m_ctx.log("Expected DC_PreChargeReq or PowerDeliveryReq! But code type id: %d", variant->get_type());
// Sequence Error
const message_20::Type req_type = variant->get_type();
send_sequence_error(req_type, m_ctx);
m_ctx.session_stopped = true;
return {};
}
}
} // namespace iso15118::d20::state

View File

@@ -0,0 +1,227 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <ctime>
#include <iso15118/d20/state/dc_cable_check.hpp>
#include <iso15118/d20/state/power_delivery.hpp>
#include <iso15118/d20/state/schedule_exchange.hpp>
#include <iso15118/detail/d20/context_helper.hpp>
#include <iso15118/detail/d20/state/schedule_exchange.hpp>
#include <iso15118/detail/d20/state/session_stop.hpp>
#include <iso15118/detail/helper.hpp>
namespace iso15118::d20::state {
namespace dt = message_20::datatypes;
using ScheduledReqControlMode = message_20::datatypes::Scheduled_SEReqControlMode;
using ScheduledResControlMode = message_20::datatypes::Scheduled_SEResControlMode;
using DynamicReqControlMode = message_20::datatypes::Dynamic_SEReqControlMode;
using DynamicResControlMode = message_20::datatypes::Dynamic_SEResControlMode;
namespace {
auto create_default_scheduled_control_mode(const dt::RationalNumber& max_power) {
dt::ScheduleTuple schedule;
schedule.schedule_tuple_id = 1;
schedule.charging_schedule.power_schedule.time_anchor =
static_cast<uint64_t>(std::time(nullptr)); // PowerSchedule is now active
dt::PowerScheduleEntry power_schedule;
power_schedule.power = max_power;
power_schedule.duration = dt::SCHEDULED_POWER_DURATION_S;
schedule.charging_schedule.power_schedule.entries.push_back(power_schedule);
ScheduledResControlMode scheduled_mode{};
// Providing no price schedule!
// NOTE: Agreement on iso15118.elaad.io: [V2G20-2176] is not required and should be ignored.
scheduled_mode.schedule_tuple = {schedule};
return scheduled_mode;
}
namespace {
void set_dynamic_parameters_in_res(DynamicResControlMode& res_mode, const UpdateDynamicModeParameters& parameters,
uint64_t header_timestamp) {
if (parameters.departure_time) {
const auto departure_time = static_cast<uint64_t>(parameters.departure_time.value());
if (departure_time > header_timestamp) {
res_mode.departure_time = static_cast<uint32_t>(departure_time - header_timestamp);
}
}
res_mode.target_soc = parameters.target_soc;
res_mode.minimum_soc = parameters.min_soc;
}
} // namespace
} // namespace
namespace dt = message_20::datatypes;
message_20::ScheduleExchangeResponse handle_request(const message_20::ScheduleExchangeRequest& req,
const d20::Session& session, const dt::RationalNumber& max_power,
const UpdateDynamicModeParameters& dynamic_parameters,
bool timeout_reached) {
message_20::ScheduleExchangeResponse res;
if (validate_and_setup_header(res.header, session, req.header.session_id) == false) {
return response_with_code(res, dt::ResponseCode::FAILED_UnknownSession);
}
if (timeout_reached) {
return response_with_code(res, dt::ResponseCode::FAILED);
}
const auto selected_services = session.get_selected_services();
const auto selected_control_mode = selected_services.selected_control_mode;
const auto selected_mobility_needs_mode = selected_services.selected_mobility_needs_mode;
// Todo(SL): Publish data from request?
if (selected_control_mode == dt::ControlMode::Scheduled &&
std::holds_alternative<dt::Scheduled_SEReqControlMode>(req.control_mode)) {
res.control_mode.emplace<ScheduledResControlMode>(create_default_scheduled_control_mode(max_power));
// TODO(sl): Adding price schedule
// TODO(sl): Adding discharging schedule
} else if (selected_control_mode == dt::ControlMode::Dynamic &&
std::holds_alternative<DynamicReqControlMode>(req.control_mode)) {
// TODO(sl): Publish req dynamic mode parameters
auto& mode = res.control_mode.emplace<DynamicResControlMode>();
if (selected_mobility_needs_mode == dt::MobilityNeedsMode::ProvidedBySecc) {
set_dynamic_parameters_in_res(mode, dynamic_parameters, res.header.timestamp);
}
} else {
logf_error("The control mode of the req message does not match the previously agreed contol mode.");
return response_with_code(res, dt::ResponseCode::FAILED);
}
res.processing = dt::Processing::Finished;
return response_with_code(res, dt::ResponseCode::OK);
}
void ScheduleExchange::enter() {
m_ctx.log.enter_state("ScheduleExchange");
}
Result ScheduleExchange::feed(Event ev) {
if (ev == Event::CONTROL_MESSAGE) {
// TODO(sl): Not sure if the data comes here just in time?
if (const auto* control_data = m_ctx.get_control_event<UpdateDynamicModeParameters>()) {
dynamic_parameters = *control_data;
}
// Ignore control message
return {};
}
if (ev == Event::TIMEOUT) {
const auto timeout = m_ctx.get_active_timeout();
if (timeout and *timeout == d20::TimeoutType::ONGOING) {
timeout_ongoing_reached = true;
}
return {};
}
if (ev != Event::V2GTP_MESSAGE) {
return {};
}
const auto variant = m_ctx.pull_request();
if (const auto req = variant->get_if<message_20::ScheduleExchangeRequest>()) {
if (first_req_msg) {
m_ctx.start_timeout(d20::TimeoutType::ONGOING, TIMEOUT_ONGOING);
first_req_msg = false;
}
dt::RationalNumber max_charge_power = {0, 0};
const auto& selected_services = m_ctx.session.get_selected_services();
const auto selected_energy_service = selected_services.selected_energy_service;
if (m_ctx.session.is_dc_charger()) {
max_charge_power = m_ctx.session_config.dc_limits.charge_limits.power.max;
}
std::optional<dt::AcConnector> ac_connector{};
if (std::holds_alternative<dt::AcConnector>(selected_services.selected_connector)) {
ac_connector = std::get<dt::AcConnector>(selected_services.selected_connector);
}
session::feedback::EvseTransferLimits evse_limits;
if (m_ctx.session.is_ac_charger()) {
evse_limits = m_ctx.session_config.ac_limits;
} else if (m_ctx.session.is_dc_charger()) {
evse_limits = m_ctx.session_config.dc_limits;
}
const session::feedback::EvTransferLimits& ev_limits = m_ctx.session_ev_info.ev_transfer_limits;
const auto& control_mode = req->control_mode;
// Send the charging feedback
m_ctx.feedback.notify_ev_charging_needs(selected_energy_service, ac_connector,
selected_services.selected_control_mode,
selected_services.selected_mobility_needs_mode, evse_limits, ev_limits,
control_mode, m_ctx.session_ev_info.ev_energy_services);
const auto res =
handle_request(*req, m_ctx.session, max_charge_power, dynamic_parameters, timeout_ongoing_reached);
m_ctx.respond(res);
if (res.response_code >= dt::ResponseCode::FAILED) {
m_ctx.session_stopped = true;
return {};
}
if (res.processing == dt::Processing::Ongoing) {
return {};
}
m_ctx.stop_timeout(d20::TimeoutType::ONGOING);
if (m_ctx.session.is_ac_charger()) {
// For AC move directly to power delivery
return m_ctx.create_state<PowerDelivery>();
}
if (m_ctx.session.is_dc_charger()) {
return m_ctx.create_state<DC_CableCheck>();
}
m_ctx.log("expected selected_energy_service AC, AC_BPT, DC, DC_BPT! But code type id: %d",
static_cast<int>(selected_energy_service));
m_ctx.session_stopped = true;
return {};
} else if (const auto req = variant->get_if<message_20::SessionStopRequest>()) {
const auto res = handle_request(*req, m_ctx.session);
m_ctx.respond(res);
m_ctx.session_stopped = true;
return {};
} else {
m_ctx.log("expected ScheduleExchangeReq! But code type id: %d", variant->get_type());
// Sequence Error
const message_20::Type req_type = variant->get_type();
send_sequence_error(req_type, m_ctx);
m_ctx.session_stopped = true;
return {};
}
}
} // namespace iso15118::d20::state

View File

@@ -0,0 +1,257 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/d20/state/service_detail.hpp>
#include <algorithm>
#include <iso15118/d20/state/service_selection.hpp>
#include <iso15118/detail/d20/context_helper.hpp>
#include <iso15118/detail/d20/state/service_detail.hpp>
#include <iso15118/detail/d20/state/session_stop.hpp>
#include <iso15118/detail/helper.hpp>
namespace iso15118::d20::state {
namespace dt = message_20::datatypes;
namespace {
bool find_energy_services(const std::vector<uint16_t>& services, const uint16_t service) {
return std::find(services.begin(), services.end(), service) != services.end();
}
void fill_internet_parameter_list(std::vector<dt::InternetParameterList>& internet_parameter_list,
const dt::ServiceParameterList& custom_vas_parameters) {
for (const auto& parameter_set : custom_vas_parameters) {
auto& internet_parameter = internet_parameter_list.emplace_back();
if (parameter_set.id == 1) {
internet_parameter.port = dt::Port::Port20;
internet_parameter.protocol = dt::Protocol::Ftp;
} else if (parameter_set.id == 2) {
internet_parameter.port = dt::Port::Port21;
internet_parameter.protocol = dt::Protocol::Ftp;
} else if (parameter_set.id == 3) {
internet_parameter.port = dt::Port::Port80;
internet_parameter.protocol = dt::Protocol::Http;
} else if (parameter_set.id == 4) {
internet_parameter.port = dt::Port::Port443;
internet_parameter.protocol = dt::Protocol::Https;
}
}
}
void fill_parking_parameter_list(std::vector<message_20::datatypes::ParkingParameterList>& parking_parameter_list,
const dt::ServiceParameterList& custom_vas_parameters) {
for (const auto& parameter_set : custom_vas_parameters) {
auto& parking_parameter = parking_parameter_list.emplace_back();
for (const auto& parameter : parameter_set.parameter) {
const auto value = std::get<int32_t>(parameter.value);
if (parameter.name == "IntendedService") {
parking_parameter.intended_service = static_cast<dt::IntendedService>(value);
} else if (parameter.name == "ParkingStatusType") {
parking_parameter.parking_status = static_cast<dt::ParkingStatus>(value);
}
}
}
}
} // namespace
message_20::ServiceDetailResponse handle_request(const message_20::ServiceDetailRequest& req, d20::Session& session,
const d20::SessionConfig& config,
const std::optional<dt::ServiceParameterList>& custom_vas_parameters) {
message_20::ServiceDetailResponse res;
if (not validate_and_setup_header(res.header, session, req.header.session_id)) {
return response_with_code(res, dt::ResponseCode::FAILED_UnknownSession);
}
bool service_found = false;
for (auto& energy_service : session.offered_services.energy_services) {
if (message_20::to_underlying_value(energy_service) == req.service) {
service_found = true;
break;
}
}
for (auto& vas_service : session.offered_services.vas_services) {
if (vas_service == req.service) {
service_found = true;
break;
}
}
if (!service_found) {
return response_with_code(res, dt::ResponseCode::FAILED_ServiceIDInvalid);
}
res.service_parameter_list.clear(); // reset default values
if (custom_vas_parameters.has_value()) {
logf_info("Sending custom vas parameters");
const auto& vas_services = custom_vas_parameters.value();
std::vector<uint16_t> parameter_set_ids{};
for (auto& vas : vas_services) {
parameter_set_ids.push_back(vas.id);
}
session.offered_services.custom_vas_list[req.service] = parameter_set_ids;
res.service = req.service;
res.service_parameter_list = vas_services;
return response_with_code(res, dt::ResponseCode::OK);
}
uint8_t id = 0;
if (req.service == message_20::to_underlying_value(dt::ServiceCategory::AC)) {
res.service = message_20::to_underlying_value(dt::ServiceCategory::AC);
for (auto& parameter_set : config.ac_parameter_list) {
session.offered_services.ac_parameter_list[id] = parameter_set;
res.service_parameter_list.push_back(dt::ParameterSet(id++, parameter_set));
}
} else if (req.service == message_20::to_underlying_value(dt::ServiceCategory::AC_BPT)) {
res.service = message_20::to_underlying_value(dt::ServiceCategory::AC_BPT);
for (auto& parameter_set : config.ac_bpt_parameter_list) {
session.offered_services.ac_bpt_parameter_list[id] = parameter_set;
res.service_parameter_list.push_back(dt::ParameterSet(id++, parameter_set));
}
} else if (req.service == message_20::to_underlying_value(dt::ServiceCategory::DC)) {
res.service = message_20::to_underlying_value(dt::ServiceCategory::DC);
for (auto& parameter_set : config.dc_parameter_list) {
session.offered_services.dc_parameter_list[id] = parameter_set;
res.service_parameter_list.push_back(dt::ParameterSet(id++, parameter_set));
}
} else if (req.service == message_20::to_underlying_value(dt::ServiceCategory::DC_BPT)) {
res.service = message_20::to_underlying_value(dt::ServiceCategory::DC_BPT);
for (auto& parameter_set : config.dc_bpt_parameter_list) {
session.offered_services.dc_bpt_parameter_list[id] = parameter_set;
res.service_parameter_list.push_back(dt::ParameterSet(id++, parameter_set));
}
} else if (req.service == message_20::to_underlying_value(dt::ServiceCategory::MCS)) {
res.service = message_20::to_underlying_value(dt::ServiceCategory::MCS);
for (auto& parameter_set : config.mcs_parameter_list) {
session.offered_services.mcs_parameter_list[id] = parameter_set;
res.service_parameter_list.push_back(dt::ParameterSet(id++, parameter_set));
}
} else if (req.service == message_20::to_underlying_value(dt::ServiceCategory::MCS_BPT)) {
res.service = message_20::to_underlying_value(dt::ServiceCategory::MCS_BPT);
for (auto& parameter_set : config.mcs_bpt_parameter_list) {
session.offered_services.mcs_bpt_parameter_list[id] = parameter_set;
res.service_parameter_list.push_back(dt::ParameterSet(id++, parameter_set));
}
} else if (req.service == message_20::to_underlying_value(dt::ServiceCategory::Internet)) {
res.service = message_20::to_underlying_value(dt::ServiceCategory::Internet);
for (auto& parameter_set : config.internet_parameter_list) {
// TODO(sl): Possibly refactor, define const
if (parameter_set.port == dt::Port::Port20) {
id = 1;
} else if (parameter_set.port == dt::Port::Port21) {
id = 2;
} else if (parameter_set.port == dt::Port::Port80) {
id = 3;
} else if (parameter_set.port == dt::Port::Port443) {
id = 4;
}
session.offered_services.internet_parameter_list[id] = parameter_set;
res.service_parameter_list.push_back(dt::ParameterSet(id, parameter_set));
}
} else if (req.service == message_20::to_underlying_value(dt::ServiceCategory::ParkingStatus)) {
res.service = message_20::to_underlying_value(dt::ServiceCategory::ParkingStatus);
for (auto& parameter_set : config.parking_parameter_list) {
session.offered_services.parking_parameter_list[id] = parameter_set;
res.service_parameter_list.push_back(dt::ParameterSet(id++, parameter_set));
}
} else {
logf_warning("There is no parameters for this service %u available. Sending an \"empty\" response.",
req.service);
res.service = req.service;
res.service_parameter_list = {dt::ParameterSet(0)};
}
return response_with_code(res, dt::ResponseCode::OK);
}
void ServiceDetail::enter() {
m_ctx.log.enter_state("ServiceDetail");
}
Result ServiceDetail::feed(Event ev) {
if (ev != Event::V2GTP_MESSAGE) {
return {};
}
const auto variant = m_ctx.pull_request();
if (const auto req = variant->get_if<message_20::ServiceDetailRequest>()) {
logf_info("Requested info about ServiceID: %d", req->service);
using Service = dt::ServiceCategory;
const std::vector<uint16_t> energy_services{
message_20::to_underlying_value(Service::AC), message_20::to_underlying_value(Service::DC),
message_20::to_underlying_value(Service::WPT), message_20::to_underlying_value(Service::DC_ACDP),
message_20::to_underlying_value(Service::AC_BPT), message_20::to_underlying_value(Service::DC_BPT),
message_20::to_underlying_value(Service::DC_ACDP_BPT), message_20::to_underlying_value(Service::MCS),
message_20::to_underlying_value(Service::MCS_BPT)};
std::optional<dt::ServiceParameterList> custom_vas_parameters{std::nullopt};
if (not find_energy_services(energy_services, req->service)) {
logf_info("Getting vas (id: %u) parameters", req->service);
custom_vas_parameters = m_ctx.feedback.get_vas_parameters(req->service);
if (custom_vas_parameters.has_value() and
req->service == message_20::to_underlying_value(dt::ServiceCategory::Internet)) {
m_ctx.session_config.internet_parameter_list.clear();
fill_internet_parameter_list(m_ctx.session_config.internet_parameter_list,
custom_vas_parameters.value());
custom_vas_parameters.reset();
} else if (custom_vas_parameters.has_value() and
req->service == message_20::to_underlying_value(dt::ServiceCategory::ParkingStatus)) {
m_ctx.session_config.parking_parameter_list.clear();
fill_parking_parameter_list(m_ctx.session_config.parking_parameter_list, custom_vas_parameters.value());
custom_vas_parameters.reset();
}
}
const auto res = handle_request(*req, m_ctx.session, m_ctx.session_config, custom_vas_parameters);
m_ctx.respond(res);
if (res.response_code >= dt::ResponseCode::FAILED) {
m_ctx.session_stopped = true;
return {};
}
return m_ctx.create_state<ServiceSelection>();
} else if (const auto req = variant->get_if<message_20::SessionStopRequest>()) {
const auto res = handle_request(*req, m_ctx.session);
m_ctx.respond(res);
m_ctx.session_stopped = true;
return {};
} else {
m_ctx.log("expected ServiceDetailReq! But code type id: %d", variant->get_type());
// Sequence Error
const message_20::Type req_type = variant->get_type();
send_sequence_error(req_type, m_ctx);
m_ctx.session_stopped = true;
return {};
}
}
} // namespace iso15118::d20::state

View File

@@ -0,0 +1,174 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <algorithm>
#include <iso15118/d20/state/service_detail.hpp>
#include <iso15118/d20/state/service_discovery.hpp>
#include <iso15118/detail/helper.hpp>
#include <iso15118/detail/d20/context_helper.hpp>
#include <iso15118/detail/d20/state/service_discovery.hpp>
#include <iso15118/detail/d20/state/session_stop.hpp>
#include <everest/util/vector/fixed_vector.hpp>
namespace {
iso15118::message_20::datatypes::ServiceCategory
convert_service_id_to_service_category(const std::uint16_t service_id) {
switch (service_id) {
case 1:
return iso15118::message_20::datatypes::ServiceCategory::AC;
case 2:
return iso15118::message_20::datatypes::ServiceCategory::DC;
case 3:
return iso15118::message_20::datatypes::ServiceCategory::WPT;
case 4:
return iso15118::message_20::datatypes::ServiceCategory::DC_ACDP;
case 5:
return iso15118::message_20::datatypes::ServiceCategory::AC_BPT;
case 6:
return iso15118::message_20::datatypes::ServiceCategory::DC_BPT;
case 7:
return iso15118::message_20::datatypes::ServiceCategory::DC_ACDP_BPT;
case 8:
return iso15118::message_20::datatypes::ServiceCategory::MCS;
case 9:
return iso15118::message_20::datatypes::ServiceCategory::MCS_BPT;
case 10:
return iso15118::message_20::datatypes::ServiceCategory::AC_DER;
default:
// returning ParkingStatus as default to show nonsense
return iso15118::message_20::datatypes::ServiceCategory::ParkingStatus;
}
}
} // namespace
namespace iso15118::d20::state {
namespace dt = message_20::datatypes;
static bool find_service_id(const everest::lib::util::fixed_vector<uint16_t, 16>& req_service_ids,
const uint16_t service) {
return std::find(req_service_ids.begin(), req_service_ids.end(), service) != req_service_ids.end();
}
message_20::ServiceDiscoveryResponse
handle_request(const message_20::ServiceDiscoveryRequest& req, d20::Session& session,
const std::vector<dt::ServiceCategory>& energy_services, const std::vector<uint16_t>& vas_services,
std::vector<message_20::datatypes::ServiceCategory>& ev_energy_services) {
message_20::ServiceDiscoveryResponse res;
if (validate_and_setup_header(res.header, session, req.header.session_id) == false) {
return response_with_code(res, dt::ResponseCode::FAILED_UnknownSession);
}
// Service renegotiation is not yet supported
res.service_renegotiation_supported = false;
session.service_renegotiation_supported = false;
// Reset default value
res.energy_transfer_service_list.clear();
std::vector<dt::Service> energy_services_list;
std::vector<dt::VasService> vas_services_list;
// EV supported service ID's
if (req.supported_service_ids.has_value() == true) {
for (auto& energy_service : energy_services) {
if (find_service_id(req.supported_service_ids.value(), message_20::to_underlying_value(energy_service))) {
energy_services_list.push_back({energy_service, false});
}
}
for (auto& vas_service : vas_services) {
if (find_service_id(req.supported_service_ids.value(), vas_service)) {
vas_services_list.push_back({vas_service, false});
}
}
ev_energy_services.reserve(req.supported_service_ids->size());
for (auto service : req.supported_service_ids.value()) {
const auto energy_service = convert_service_id_to_service_category(service);
// ParkingStatus is used as a nonsense value
if (energy_service != message_20::datatypes::ServiceCategory::ParkingStatus) {
ev_energy_services.emplace_back(energy_service);
}
}
} else {
for (auto& energy_service : energy_services) {
energy_services_list.push_back({energy_service, false});
}
for (auto& vas_service : vas_services) {
vas_services_list.push_back({vas_service, false});
}
}
for (auto& conf_energy_service : energy_services_list) {
auto& energy_service = res.energy_transfer_service_list.emplace_back();
energy_service = conf_energy_service;
session.offered_services.energy_services.push_back(conf_energy_service.service_id);
}
if (not vas_services_list.empty()) {
auto& vas_service_list = res.vas_list.emplace();
for (auto& conf_vas_service : vas_services_list) {
auto& vas_service = vas_service_list.emplace_back();
vas_service = conf_vas_service;
session.offered_services.vas_services.push_back(conf_vas_service.service_id);
}
}
return response_with_code(res, dt::ResponseCode::OK);
}
void ServiceDiscovery::enter() {
m_ctx.log.enter_state("ServiceDiscovery");
}
Result ServiceDiscovery::feed(Event ev) {
if (ev != Event::V2GTP_MESSAGE) {
return {};
}
const auto variant = m_ctx.pull_request();
if (const auto req = variant->get_if<message_20::ServiceDiscoveryRequest>()) {
if (req->supported_service_ids) {
logf_info("Possible ids");
for (auto id : req->supported_service_ids.value()) {
logf_info(" %d", id);
}
}
const auto res =
handle_request(*req, m_ctx.session, m_ctx.session_config.supported_energy_transfer_services,
m_ctx.session_config.supported_vas_services, m_ctx.session_ev_info.ev_energy_services);
m_ctx.respond(res);
if (res.response_code >= dt::ResponseCode::FAILED) {
m_ctx.session_stopped = true;
return {};
}
return m_ctx.create_state<ServiceDetail>();
} else if (const auto req = variant->get_if<message_20::SessionStopRequest>()) {
const auto res = handle_request(*req, m_ctx.session);
m_ctx.respond(res);
m_ctx.session_stopped = true;
return {};
} else {
m_ctx.log("expected ServiceDiscoveryReq! But code type id: %d", variant->get_type());
// Sequence Error
const message_20::Type req_type = variant->get_type();
send_sequence_error(req_type, m_ctx);
m_ctx.session_stopped = true;
return {};
}
}
} // namespace iso15118::d20::state

View File

@@ -0,0 +1,232 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/d20/state/ac_charge_parameter_discovery.hpp>
#include <iso15118/d20/state/dc_charge_parameter_discovery.hpp>
#include <iso15118/d20/state/service_selection.hpp>
#include <algorithm>
#include <iso15118/detail/d20/context_helper.hpp>
#include <iso15118/detail/d20/state/service_detail.hpp>
#include <iso15118/detail/d20/state/service_selection.hpp>
#include <iso15118/detail/d20/state/session_stop.hpp>
#include <iso15118/detail/helper.hpp>
namespace iso15118::d20::state {
namespace dt = message_20::datatypes;
namespace {
bool find_energy_services(const std::vector<uint16_t>& services, const uint16_t service) {
return std::find(services.begin(), services.end(), service) != services.end();
}
void fill_internet_parameter_list(std::vector<dt::InternetParameterList>& internet_parameter_list,
const dt::ServiceParameterList& custom_vas_parameters) {
for (const auto& parameter_set : custom_vas_parameters) {
auto& internet_parameter = internet_parameter_list.emplace_back();
if (parameter_set.id == 1) {
internet_parameter.port = dt::Port::Port20;
internet_parameter.protocol = dt::Protocol::Ftp;
} else if (parameter_set.id == 2) {
internet_parameter.port = dt::Port::Port21;
internet_parameter.protocol = dt::Protocol::Ftp;
} else if (parameter_set.id == 3) {
internet_parameter.port = dt::Port::Port80;
internet_parameter.protocol = dt::Protocol::Http;
} else if (parameter_set.id == 4) {
internet_parameter.port = dt::Port::Port443;
internet_parameter.protocol = dt::Protocol::Https;
}
}
}
void fill_parking_parameter_list(std::vector<message_20::datatypes::ParkingParameterList>& parking_parameter_list,
const dt::ServiceParameterList& custom_vas_parameters) {
for (const auto& parameter_set : custom_vas_parameters) {
auto& parking_parameter = parking_parameter_list.emplace_back();
for (const auto& parameter : parameter_set.parameter) {
const auto value = std::get<int32_t>(parameter.value);
if (parameter.name == "IntendedService") {
parking_parameter.intended_service = static_cast<dt::IntendedService>(value);
} else if (parameter.name == "ParkingStatusType") {
parking_parameter.parking_status = static_cast<dt::ParkingStatus>(value);
}
}
}
}
} // namespace
message_20::ServiceSelectionResponse handle_request(const message_20::ServiceSelectionRequest& req,
d20::Session& session) {
message_20::ServiceSelectionResponse res;
if (validate_and_setup_header(res.header, session, req.header.session_id) == false) {
return response_with_code(res, dt::ResponseCode::FAILED_UnknownSession);
}
bool energy_service_found = false;
bool vas_services_found = false;
for (auto& energy_service : session.offered_services.energy_services) {
if (energy_service == req.selected_energy_transfer_service.service_id) {
energy_service_found = true;
break;
}
}
if (!energy_service_found) {
return response_with_code(res, dt::ResponseCode::FAILED_NoEnergyTransferServiceSelected);
}
if (req.selected_vas_list.has_value()) {
auto& selected_vas_list = req.selected_vas_list.value();
for (auto& vas_service : selected_vas_list) {
if (std::find(session.offered_services.vas_services.begin(), session.offered_services.vas_services.end(),
vas_service.service_id) == session.offered_services.vas_services.end()) {
vas_services_found = false;
break;
}
vas_services_found = true;
}
if (not vas_services_found) {
return response_with_code(res, dt::ResponseCode::FAILED_ServiceSelectionInvalid);
}
}
if (not session.find_energy_parameter_set_id(req.selected_energy_transfer_service.service_id,
req.selected_energy_transfer_service.parameter_set_id)) {
return response_with_code(res, dt::ResponseCode::FAILED_ServiceSelectionInvalid);
}
session.selected_service_parameters(req.selected_energy_transfer_service.service_id,
req.selected_energy_transfer_service.parameter_set_id);
if (req.selected_vas_list.has_value()) {
auto& selected_vas_list = req.selected_vas_list.value();
for (auto& vas_service : selected_vas_list) {
if (not session.find_vas_parameter_set_id(vas_service.service_id, vas_service.parameter_set_id)) {
return response_with_code(res, dt::ResponseCode::FAILED_ServiceSelectionInvalid);
}
session.selected_service_parameters(vas_service.service_id, vas_service.parameter_set_id);
}
}
return response_with_code(res, dt::ResponseCode::OK);
}
void ServiceSelection::enter() {
m_ctx.log.enter_state("ServiceSelection");
}
Result ServiceSelection::feed(Event ev) {
if (ev != Event::V2GTP_MESSAGE) {
return {};
}
const auto variant = m_ctx.pull_request();
if (const auto req = variant->get_if<message_20::ServiceDetailRequest>()) {
logf_info("Requested info about ServiceID: %d", req->service);
using Service = dt::ServiceCategory;
const std::vector<uint16_t> energy_services{
message_20::to_underlying_value(Service::AC), message_20::to_underlying_value(Service::DC),
message_20::to_underlying_value(Service::WPT), message_20::to_underlying_value(Service::DC_ACDP),
message_20::to_underlying_value(Service::AC_BPT), message_20::to_underlying_value(Service::DC_BPT),
message_20::to_underlying_value(Service::DC_ACDP_BPT), message_20::to_underlying_value(Service::MCS),
message_20::to_underlying_value(Service::MCS_BPT)};
std::optional<dt::ServiceParameterList> custom_vas_parameters{std::nullopt};
if (not find_energy_services(energy_services, req->service)) {
logf_info("Getting vas (id: %u) parameters", req->service);
custom_vas_parameters = m_ctx.feedback.get_vas_parameters(req->service);
if (custom_vas_parameters.has_value() and
req->service == message_20::to_underlying_value(dt::ServiceCategory::Internet)) {
m_ctx.session_config.internet_parameter_list.clear();
fill_internet_parameter_list(m_ctx.session_config.internet_parameter_list,
custom_vas_parameters.value());
custom_vas_parameters.reset();
} else if (custom_vas_parameters.has_value() and
req->service == message_20::to_underlying_value(dt::ServiceCategory::ParkingStatus)) {
m_ctx.session_config.parking_parameter_list.clear();
fill_parking_parameter_list(m_ctx.session_config.parking_parameter_list, custom_vas_parameters.value());
custom_vas_parameters.reset();
}
}
const auto res = handle_request(*req, m_ctx.session, m_ctx.session_config, custom_vas_parameters);
m_ctx.respond(res);
if (res.response_code >= dt::ResponseCode::FAILED) {
m_ctx.session_stopped = true;
return {};
}
return {};
} else if (const auto req = variant->get_if<message_20::ServiceSelectionRequest>()) {
const auto res = handle_request(*req, m_ctx.session);
if (res.response_code == message_20::datatypes::ResponseCode::OK) {
const auto selected_services = m_ctx.session.get_selected_services();
m_ctx.feedback.selected_service_parameters(selected_services);
}
m_ctx.respond(res);
if (res.response_code >= dt::ResponseCode::FAILED) {
m_ctx.session_stopped = true;
return {};
}
if (req->selected_vas_list.has_value()) {
m_ctx.feedback.selected_vas_services(req->selected_vas_list.value());
}
const auto selected_energy_service = m_ctx.session.get_selected_services().selected_energy_service;
if (m_ctx.session.is_ac_charger()) {
return m_ctx.create_state<AC_ChargeParameterDiscovery>();
}
if (m_ctx.session.is_dc_charger()) {
return m_ctx.create_state<DC_ChargeParameterDiscovery>();
}
m_ctx.log("expected selected_energy_service AC, AC_BPT, DC, DC_BPT, MCS, MCS_BPT! But code type id: %d",
static_cast<int>(selected_energy_service));
m_ctx.session_stopped = true;
return {};
} else if (const auto req = variant->get_if<message_20::SessionStopRequest>()) {
const auto res = handle_request(*req, m_ctx.session);
m_ctx.respond(res);
m_ctx.session_stopped = true;
return {};
} else {
m_ctx.log("expected ServiceDetailReq! But code type id: %d", variant->get_type());
// Sequence Error
const message_20::Type req_type = variant->get_type();
send_sequence_error(req_type, m_ctx);
m_ctx.session_stopped = true;
return {};
}
}
} // namespace iso15118::d20::state

View File

@@ -0,0 +1,162 @@
// 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/ac_charge_parameter_discovery.hpp>
#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>
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();
}
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> concatenated_session_id_vehicle{};
std::copy(session_id.begin(), session_id.end(), concatenated_session_id_vehicle.begin());
std::copy(vehicle_cert_hash.begin(), vehicle_cert_hash.end(),
concatenated_session_id_vehicle.begin() + session_id.size());
unsigned int digestlen{0};
const auto result = EVP_Digest(concatenated_session_id_vehicle.data(), concatenated_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;
message_20::SessionSetupResponse handle_request([[maybe_unused]] const message_20::SessionSetupRequest& req,
const d20::Session& session, const std::string& evse_id,
bool new_session) {
message_20::SessionSetupResponse res;
setup_header(res.header, session);
res.evseid = evse_id;
if (new_session) {
return response_with_code(res, dt::ResponseCode::OK_NewSessionEstablished);
} else {
return response_with_code(res, dt::ResponseCode::OK_OldSessionJoined);
}
}
void SessionSetup::enter() {
m_ctx.log.enter_state("SessionSetup");
}
Result SessionSetup::feed(Event ev) {
if (ev != Event::V2GTP_MESSAGE) {
return {};
}
const auto variant = m_ctx.pull_request();
if (const auto req = variant->get_if<message_20::SessionSetupRequest>()) {
logf_info("Received session setup with evccid: %s", req->evccid.c_str());
m_ctx.feedback.evcc_id(req->evccid);
m_ctx.ev_info.evcc_id = req->evccid;
m_ctx.feedback.ev_information(m_ctx.ev_info);
bool new_session{false};
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();
new_session = true;
} else {
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 = 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;
const auto res = handle_request(*req, m_ctx.session, evse_id, new_session);
m_ctx.respond(res);
if (not new_session) {
const auto& selected_services = m_ctx.session.get_selected_services();
m_ctx.feedback.selected_service_parameters(selected_services);
if (m_ctx.session.is_ac_charger()) {
return m_ctx.create_state<AC_ChargeParameterDiscovery>();
}
if (m_ctx.session.is_dc_charger()) {
return m_ctx.create_state<DC_ChargeParameterDiscovery>();
}
// TODO(sl): Error handling
return {};
}
return m_ctx.create_state<AuthorizationSetup>();
} else {
m_ctx.log("expected SessionSetupReq! But code type id: %d", variant->get_type());
// Sequence Error
const message_20::Type req_type = variant->get_type();
send_sequence_error(req_type, m_ctx);
m_ctx.session_stopped = true;
return {};
}
}
} // namespace iso15118::d20::state

View File

@@ -0,0 +1,89 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/d20/state/session_stop.hpp>
#include <iso15118/detail/d20/context_helper.hpp>
#include <iso15118/detail/d20/state/session_stop.hpp>
#include <iso15118/detail/helper.hpp>
namespace iso15118::d20::state {
namespace dt = message_20::datatypes;
message_20::SessionStopResponse handle_request(const message_20::SessionStopRequest& req, const d20::Session& session) {
message_20::SessionStopResponse res;
if (validate_and_setup_header(res.header, session, req.header.session_id) == false) {
return response_with_code(res, dt::ResponseCode::FAILED_UnknownSession);
}
if (req.charging_session == dt::ChargingSession::ServiceRenegotiation &&
session.service_renegotiation_supported == false) {
return response_with_code(res, dt::ResponseCode::FAILED_NoServiceRenegotiationSupported);
}
// Todo(sl): Check req.charging_session
return response_with_code(res, dt::ResponseCode::OK);
}
void SessionStop::enter() {
m_ctx.log.enter_state("SessionStop");
}
Result SessionStop::feed(Event ev) {
if (ev != Event::V2GTP_MESSAGE) {
return {};
}
const auto variant = m_ctx.pull_request();
if (const auto req = variant->get_if<message_20::SessionStopRequest>()) {
const auto res = handle_request(*req, m_ctx.session);
std::string ev_termination_code;
std::string ev_termination_explanation;
if (req->ev_termination_code.has_value()) {
logf_info("EV termination code: %s", req->ev_termination_code.value().c_str());
ev_termination_code = req->ev_termination_code.value();
}
if (req->ev_termination_explanation.has_value()) {
logf_info("EV Termination explanation: %s", req->ev_termination_explanation.value().c_str());
ev_termination_explanation = req->ev_termination_explanation.value();
}
if (req->ev_termination_code.has_value() or req->ev_termination_explanation.has_value()) {
m_ctx.feedback.ev_termination(ev_termination_code, ev_termination_explanation);
}
m_ctx.respond(res);
// Todo(sl): Tell the reason why the charger is stopping. Shutdown, Error, etc.
if (req->charging_session == message_20::datatypes::ChargingSession::Pause) {
m_ctx.session_paused = true;
if (not m_ctx.pause_ctx.has_value()) {
logf_error("Pause the session but pause_ctx has no value");
return {};
}
m_ctx.pause_ctx->selected_service_parameters = m_ctx.session.get_selected_services();
} else if (req->charging_session == message_20::datatypes::ChargingSession::Terminate) {
m_ctx.session_stopped = true;
m_ctx.pause_ctx.reset();
}
return {};
} else {
m_ctx.log("expected SessionStop! But code type id: %d", variant->get_type());
// Sequence Error
const message_20::Type req_type = variant->get_type();
send_sequence_error(req_type, m_ctx);
m_ctx.session_stopped = true;
return {};
}
}
} // namespace iso15118::d20::state

View File

@@ -0,0 +1,96 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/d20/state/supported_app_protocol.hpp>
#include <map>
#include <optional>
#include <iso15118/d20/state/session_setup.hpp>
#include <iso15118/message/supported_app_protocol.hpp>
#include <iso15118/detail/d20/context_helper.hpp>
#include <iso15118/detail/helper.hpp>
namespace iso15118::d20::state {
constexpr auto ISO20_DC_NAMESPACE = "urn:iso:std:iso:15118:-20:DC";
constexpr auto ISO20_AC_NAMESPACE = "urn:iso:std:iso:15118:-20:AC";
using ResponseCode = message_20::SupportedAppProtocolResponse::ResponseCode;
message_20::SupportedAppProtocolResponse handle_request(const message_20::SupportedAppProtocolRequest& req,
const std::optional<std::string>& custom_protocol_namespace) {
message_20::SupportedAppProtocolResponse res;
std::map<uint8_t, uint8_t> ev_supported_protocols{}; // key: priority, value: schema_id
for (const auto& protocol : req.app_protocol) {
if (protocol.protocol_namespace.compare(ISO20_DC_NAMESPACE) == 0) {
ev_supported_protocols[protocol.priority] = protocol.schema_id;
} else if (protocol.protocol_namespace.compare(ISO20_AC_NAMESPACE) == 0) {
ev_supported_protocols[protocol.priority] = protocol.schema_id;
} else if (protocol.protocol_namespace.compare(custom_protocol_namespace.value_or("")) == 0) {
ev_supported_protocols[protocol.priority] = protocol.schema_id;
}
}
if (ev_supported_protocols.empty()) {
return response_with_code(res, ResponseCode::Failed_NoNegotiation);
}
res.schema_id = ev_supported_protocols.begin()->second; // [V2G20-167] Highest Prio: 1, Lowest Prio: 20
return response_with_code(res, ResponseCode::OK_SuccessfulNegotiation);
}
void SupportedAppProtocol::enter() {
m_ctx.log.enter_state("SupportedAppProtocol");
}
Result SupportedAppProtocol::feed(Event ev) {
if (ev != Event::V2GTP_MESSAGE) {
return {};
}
auto variant = m_ctx.pull_request();
if (const auto req = variant->get_if<message_20::SupportedAppProtocolRequest>()) {
const auto res = handle_request(*req, m_ctx.session_config.custom_protocol);
m_ctx.respond(res);
m_ctx.ev_info.ev_supported_app_protocols = req->app_protocol;
if (res.response_code == ResponseCode::Failed_NoNegotiation) {
m_ctx.log("unsupported app protocol: [%s]",
req->app_protocol.size() ? req->app_protocol[0].protocol_namespace.c_str() : "unknown");
return {};
}
for (const auto& protocol : req->app_protocol) {
if (protocol.schema_id == res.schema_id) {
m_ctx.ev_info.selected_app_protocol = protocol;
if (protocol.protocol_namespace.compare(ISO20_DC_NAMESPACE) == 0) {
m_ctx.feedback.selected_protocol("ISO15118-20:DC");
} else if (protocol.protocol_namespace.compare(ISO20_AC_NAMESPACE) == 0) {
m_ctx.feedback.selected_protocol("ISO15118-20:AC");
} else if (protocol.protocol_namespace.compare(m_ctx.session_config.custom_protocol.value_or("")) ==
0) {
m_ctx.feedback.selected_protocol(m_ctx.session_config.custom_protocol.value());
logf_warning(
"EV and EVSE have agreed on a custom protocol namespace. Problems or aborts can occur in the "
"following states!");
}
}
}
return m_ctx.create_state<SessionSetup>();
} else {
m_ctx.log("expected SupportedAppProtocolReq! But code type id: %d", variant->get_type());
m_ctx.session_stopped = true;
return {};
}
}
} // namespace iso15118::d20::state

View File

@@ -0,0 +1,57 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2025 Pionix GmbH and Contributors to EVerest
#include <iso15118/d20/timeout.hpp>
#include <map>
#include <iso15118/detail/helper.hpp>
namespace iso15118::d20 {
void Timeouts::start_timeout(TimeoutType type, uint32_t timeout_ms) {
const auto type_u8 = to_underlying_value(type);
if (timeouts.at(type_u8).has_value()) {
logf_warning("Timeout %u already started", type_u8);
return;
}
timeouts.at(type_u8).emplace(Timeout(timeout_ms));
}
void Timeouts::stop_timeout(TimeoutType type) {
const auto type_u8 = to_underlying_value(type);
if (not timeouts.at(type_u8).has_value()) {
logf_warning("Timeout %u is not started", type_u8);
}
timeouts.at(type_u8).reset();
}
void Timeouts::reset_timeout(TimeoutType type) {
const auto type_u8 = to_underlying_value(type);
timeouts.at(type_u8).reset();
}
std::optional<std::vector<TimeoutType>> Timeouts::check() {
bool reached{false};
std::map<TimePoint, TimeoutType> active_timeouts_map;
for (uint8_t i = 0; i < TIMEOUT_TYPE_SIZE; i++) {
auto timeout = timeouts.at(i);
if (timeout.has_value() and timeout.value().is_reached()) {
active_timeouts_map.insert({timeout.value().get_timeout_point(), static_cast<TimeoutType>(i)});
reached = true;
}
}
if (reached) {
std::vector<TimeoutType> active_timeouts{};
for (const auto& [_, value] : active_timeouts_map) {
active_timeouts.push_back(value);
}
return active_timeouts;
}
return std::nullopt;
}
} // namespace iso15118::d20

View File

@@ -0,0 +1,171 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/io/connection_plain.hpp>
#include <cassert>
#include <chrono>
#include <cinttypes>
#include <cstring>
#include <thread>
#include <arpa/inet.h>
#include <unistd.h>
#include <iso15118/detail/helper.hpp>
#include <iso15118/detail/io/socket_helper.hpp>
namespace iso15118::io {
static constexpr auto DEFAULT_SOCKET_BACKLOG = 4;
ConnectionPlain::ConnectionPlain(PollManager& poll_manager_, const std::string& interface_name) :
poll_manager(poll_manager_) {
sockaddr_in6 address;
if (not get_first_sockaddr_in6_for_interface(interface_name, address)) {
const auto msg = "Failed to get ipv6 socket address for interface " + interface_name;
log_and_throw(msg.c_str());
}
// setup end point information
end_point.port = 50000;
memcpy(&end_point.address, &address.sin6_addr, sizeof(address.sin6_addr));
fd = socket(AF_INET6, SOCK_STREAM, 0);
if (fd == -1) {
log_and_throw("Failed to create an ipv6 socket");
}
// before bind, set the port
address.sin6_port = htons(end_point.port);
int optval_tmp{1};
const auto set_reuseaddr = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval_tmp, sizeof(optval_tmp));
if (set_reuseaddr == -1) {
log_and_throw("setsockopt(SO_REUSEADDR) failed");
}
const auto set_reuseport = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &optval_tmp, sizeof(optval_tmp));
if (set_reuseport == -1) {
log_and_throw("setsockopt(SO_REUSEPORT) failed");
}
const auto bind_result = bind(fd, reinterpret_cast<const struct sockaddr*>(&address), sizeof(address));
if (bind_result == -1) {
const auto error = "Failed to bind ipv6 socket to interface " + interface_name;
log_and_throw(error.c_str());
}
const auto listen_result = listen(fd, DEFAULT_SOCKET_BACKLOG);
if (listen_result == -1) {
log_and_throw("Listen on socket failed");
}
poll_manager.register_fd(fd, [this]() { this->handle_connect(); });
}
ConnectionPlain::~ConnectionPlain() = default;
void ConnectionPlain::set_event_callback(const ConnectionEventCallback& callback) {
this->event_callback = callback;
}
Ipv6EndPoint ConnectionPlain::get_public_endpoint() const {
return end_point;
}
void ConnectionPlain::write(const uint8_t* buf, size_t len) {
assert(connection_open);
const auto write_result = ::write(fd, buf, len);
if (write_result == -1) {
log_and_throw("Failed to write()");
} else if (not cmp_equal(write_result, len)) {
log_and_throw("Could not complete write");
}
}
ReadResult ConnectionPlain::read(uint8_t* buf, size_t len) {
assert(connection_open);
const auto read_result = ::read(fd, buf, len);
const auto did_block = (len > 0) and (not cmp_equal(read_result, len));
if (read_result >= 0) {
return {did_block, static_cast<size_t>(read_result)};
}
// should be an error
if (errno != EAGAIN) {
// in case the error is not due to blocking, log it
logf_error("ConnectionPlain::read failed with error code: %d", errno);
}
return {did_block, 0};
}
void ConnectionPlain::handle_connect() {
sockaddr_in6 address;
socklen_t address_len = sizeof(address);
const auto accept_fd = accept4(fd, reinterpret_cast<struct sockaddr*>(&address), &address_len, SOCK_NONBLOCK);
if (accept_fd == -1) {
log_and_throw("Failed to accept4");
}
const auto address_name = sockaddr_in6_to_name(address);
if (not address_name) {
log_and_throw("Failed to determine string representation of ipv6 socket address");
}
logf_info("Incoming connection from [%s]:%" PRIu16, address_name.get(), ntohs(address.sin6_port));
poll_manager.unregister_fd(fd);
::close(fd);
call_if_available(event_callback, ConnectionEvent::ACCEPTED);
connection_open = true;
call_if_available(event_callback, ConnectionEvent::OPEN);
fd = accept_fd;
poll_manager.register_fd(fd, [this]() { this->handle_data(); });
}
void ConnectionPlain::handle_data() {
assert(connection_open);
call_if_available(event_callback, ConnectionEvent::NEW_DATA);
}
void ConnectionPlain::close() {
/* tear down TCP connection gracefully */
logf_info("Closing TCP connection");
const auto shutdown_result = shutdown(fd, SHUT_RDWR);
if (shutdown_result == -1) {
logf_error("shutdown() failed");
}
// Waiting for client closing the connection
std::this_thread::sleep_for(std::chrono::seconds(2));
poll_manager.unregister_fd(fd);
const auto close_shutdown = ::close(fd);
if (close_shutdown == -1) {
logf_error("close() failed");
}
logf_info("TCP connection closed gracefully");
connection_open = false;
call_if_available(event_callback, ConnectionEvent::CLOSED);
}
} // namespace iso15118::io

View File

@@ -0,0 +1,560 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Pionix GmbH and Contributors to EVerest
#include <iso15118/io/connection_ssl.hpp>
#include <algorithm>
#include <array>
#include <cassert>
#include <cinttypes>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <unistd.h>
#include <vector>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/ssl.h>
#include <iso15118/detail/helper.hpp>
#include <iso15118/detail/io/helper_ssl.hpp>
#include <iso15118/detail/io/socket_helper.hpp>
#include <iso15118/io/sdp_server.hpp>
namespace std {
template <> class default_delete<SSL> {
public:
void operator()(SSL* ptr) const {
::SSL_free(ptr);
}
};
template <> class default_delete<SSL_CTX> {
public:
void operator()(SSL_CTX* ptr) const {
::SSL_CTX_free(ptr);
}
};
} // namespace std
namespace iso15118::io {
struct SSLContext {
std::unique_ptr<SSL_CTX> ssl_ctx;
std::unique_ptr<SSL> ssl;
int fd{-1};
int accept_fd{-1};
std::string interface_name;
bool enable_key_logging{false};
std::filesystem::path tls_key_log_file_path{};
std::unique_ptr<io::TlsKeyLoggingServer> key_server;
bool enforce_tls_1_3{false};
std::optional<sha512_hash_t> vehicle_cert_hash{std::nullopt};
};
namespace {
constexpr auto DEFAULT_SOCKET_BACKLOG = 4;
constexpr auto TLS_PORT = 50000;
constexpr auto NAME_LENGTH = 256;
int ssl_keylog_server_index{-1};
int ssl_keylog_file_index{-1};
std::string convert_ssl_tls_versions_to_string(uint16_t version) {
switch (version) {
case SSL3_VERSION:
return "SSL3";
case TLS1_VERSION:
return "TLS1";
case TLS1_1_VERSION:
return "TLS1_1";
case TLS1_2_VERSION:
return "TLS1_2";
case TLS1_3_VERSION:
return "TLS1_3";
default:
return "Unknown";
}
}
// The use of X509_NAME_oneline() function is strongly discouraged and could be deprecated in a future release. This is
// the reason for this wrapper.
auto x509_name_oneline(const X509_NAME* a, char* buf, int size) {
return X509_NAME_oneline(a, buf, size);
}
// I found no get or callback function for the supported_versions extension, so I wrote my own parser.
bool is_tls_1_3(const uint8_t* data, std::size_t remaining) {
if (data == nullptr) {
return false;
}
uint8_t length_supported_versions = *(data++);
remaining -= 1;
if (length_supported_versions != remaining) {
logf_error("length_supported_versions is not remaining");
return false;
}
if (length_supported_versions % 2 != 0) {
logf_error("length_supported_versions is not divisible by 2");
return false;
}
// First Byte: length
// Byte 2+3 -> First Version (03, 04)
// Byte 4+5 -> Second Version (03, 03)
// ....
bool result{false};
for (auto i = 0; i < length_supported_versions; i += 2) {
const uint8_t first_byte = *(data++);
const uint8_t second_byte = *(data++);
const auto tls_version = first_byte << 8 | second_byte;
if (tls_version == TLS1_3_VERSION) {
result = true;
}
logf_debug("Client supported tls version: %s", convert_ssl_tls_versions_to_string(tls_version).c_str());
}
return result;
}
int client_hello_cb(SSL* ssl, int* /* alert */, void* /* object */) {
const unsigned char* data;
std::size_t datalen{0};
if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_supported_versions, &data, &datalen)) {
const auto tls_1_3_found = is_tls_1_3(data, datalen);
if (tls_1_3_found) {
logf_info("Client supports TLS1.3: Change verify mode to SSL_VERIFY_PEER and "
"SSL_VERIFY_FAIL_IF_NO_PEER_CERT");
int mode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
SSL_set_verify(ssl, mode, nullptr);
}
}
if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_certificate_authorities, &data, &datalen)) {
logf_info("Extension certificate_authorities found!");
// TODO(SL): Setting var for handle_certificate_cb
}
return SSL_CLIENT_HELLO_SUCCESS;
}
int handle_certificate_cb(SSL* ssl, void* /* arg */) {
// TODO(sl): Check only after names if the extension is there
const STACK_OF(X509_NAME)* names = SSL_get0_peer_CA_list(ssl);
if (names == NULL || sk_X509_NAME_num(names) == 0) {
logf_error("No certificate CA names sent");
} else {
logf_info("Found certificate CA names!");
for (auto i = 0; i < sk_X509_NAME_num(names); i++) {
char name[NAME_LENGTH]{};
x509_name_oneline(sk_X509_NAME_value(names, i), name, sizeof(name));
logf_info("Name: %s", name);
}
// TODO(sl): What to do with the CA Names? Check if the charger has the secc_leaf from this CA Name?
}
// TODO(sl): Change leaf and chain certificate based on ca names
return 1;
}
void keylog_callback(const SSL* ssl, const char* line) {
auto key_logging_server = static_cast<io::TlsKeyLoggingServer*>(SSL_get_ex_data(ssl, ssl_keylog_server_index));
std::string key_log_msg = "TLS Handshake keys on port ";
key_log_msg += std::to_string(key_logging_server->get_port()) + ": ";
key_log_msg += std::string(line);
logf_info(key_log_msg.c_str());
if (key_logging_server->get_fd() != -1) {
const auto result = key_logging_server->send(line);
if (not cmp_equal(result, strlen(line))) {
const auto error_msg = adding_err_msg("key_logging_server send() failed");
logf_error(error_msg.c_str());
}
}
const auto keylog_file_path =
static_cast<std::filesystem::path*>(SSL_CTX_get_ex_data(SSL_get_SSL_CTX(ssl), ssl_keylog_file_index));
if (not keylog_file_path->empty()) {
std::ofstream ofs;
ofs.open(keylog_file_path->string(), std::ofstream::out | std::ofstream::app);
ofs << line << std::endl;
ofs.close();
}
}
int private_key_callback(char* buf, int size, [[maybe_unused]] int rwflag, void* userdata) {
const auto* password = static_cast<const std::string*>(userdata);
const std::size_t max_pass_len = (size - 1); // we exclude the endline
const std::size_t max_copy_chars =
std::min(max_pass_len, password->length()); // truncate if pass is too large and buffer too small
std::memset(buf, 0, size);
std::memcpy(buf, password->c_str(), max_copy_chars);
return max_copy_chars;
}
SSL_CTX* init_ssl(const config::SSLConfig& ssl_config) {
// Note: openssl does not provide support for ECDH-ECDSA-AES128-SHA256 anymore
static constexpr auto TLS1_2_CIPHERSUITES = "ECDHE-ECDSA-AES128-SHA256";
static constexpr auto TLS1_3_CIPHERSUITES = "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256";
const SSL_METHOD* method = TLS_server_method();
const auto ctx = SSL_CTX_new(method);
if (ctx == nullptr) {
log_and_raise_openssl_error("Failed in SSL_CTX_new()");
}
const int result_set_min_proto_version = (ssl_config.enforce_tls_1_3)
? SSL_CTX_set_min_proto_version(ctx, TLS1_3_VERSION)
: SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
if (ssl_config.enforce_tls_1_3) {
SSL_CTX_clear_options(ctx, SSL_OP_ENABLE_MIDDLEBOX_COMPAT);
}
if (result_set_min_proto_version == 0) {
log_and_raise_openssl_error("Failed in SSL_CTX_set_min_proto_version()");
}
if (not ssl_config.enforce_tls_1_3 and SSL_CTX_set_cipher_list(ctx, TLS1_2_CIPHERSUITES) == 0) {
log_and_raise_openssl_error("Failed in SSL_CTX_set_cipher_list()");
}
if (SSL_CTX_set_ciphersuites(ctx, TLS1_3_CIPHERSUITES) == 0) {
log_and_raise_openssl_error("Failed in SSL_CTX_set_ciphersuites()");
}
if (SSL_CTX_use_certificate_chain_file(ctx, ssl_config.path_certificate_chain.c_str()) != 1) {
log_and_raise_openssl_error("Failed in SSL_CTX_use_certificate_chain_file()");
}
// INFO: the password callback uses a non-const argument
if (ssl_config.private_key_password.has_value()) {
// Lifetime of the password is important because using a callback we'll require a valid ref
SSL_CTX_set_default_passwd_cb_userdata(
ctx, &const_cast<config::SSLConfig&>(ssl_config).private_key_password.value());
SSL_CTX_set_default_passwd_cb(ctx, private_key_callback);
}
if (SSL_CTX_use_PrivateKey_file(ctx, ssl_config.path_certificate_key.c_str(), SSL_FILETYPE_PEM) != 1) {
log_and_raise_openssl_error("Failed in SSL_CTX_use_PrivateKey_file()");
}
// Loading root certificates to verify client (only for tls 1.3)
if (SSL_CTX_load_verify_file(ctx, ssl_config.path_certificate_v2g_root.c_str()) == 0) {
logf_error("Verify V2G root not found!");
}
if (SSL_CTX_load_verify_file(ctx, ssl_config.path_certificate_mo_root.c_str()) == 0) {
logf_error("Verify OEM root not found!");
}
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, nullptr);
SSL_CTX_set_client_hello_cb(ctx, &client_hello_cb, nullptr);
// TODO(SL): Adding multi root support with certificate_authorities extension
// SSL_CTX_set_cert_cb(ctx, &handle_certificate_cb, nullptr);
if (ssl_config.enable_tls_key_logging) {
ssl_keylog_file_index = SSL_CTX_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
ssl_keylog_server_index = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
if (ssl_keylog_file_index == -1 or ssl_keylog_server_index == -1) {
auto error_msg = std::string("_get_ex_new_index failed: ssl_keylog_file_index: ");
error_msg += std::to_string(ssl_keylog_file_index);
error_msg += ", ssl_keylog_server_index: " + std::to_string(ssl_keylog_server_index);
logf_error(error_msg.c_str());
} else {
SSL_CTX_set_keylog_callback(ctx, keylog_callback);
}
}
return ctx;
}
} // namespace
ConnectionSSL::ConnectionSSL(PollManager& poll_manager_, const std::string& interface_name_,
const config::SSLConfig& ssl_config) :
poll_manager(poll_manager_), ssl(std::make_unique<SSLContext>()) {
ssl->interface_name = interface_name_;
ssl->enable_key_logging = ssl_config.enable_tls_key_logging;
ssl->enforce_tls_1_3 = ssl_config.enforce_tls_1_3;
// Openssl stuff missing!
const auto ssl_ctx = init_ssl(ssl_config);
ssl->ssl_ctx = std::unique_ptr<SSL_CTX>(ssl_ctx);
if (ssl_keylog_file_index != -1) {
ssl->tls_key_log_file_path = ssl_config.tls_key_logging_path / "tls_session_keys.log";
SSL_CTX_set_ex_data(ssl->ssl_ctx.get(), ssl_keylog_file_index, &ssl->tls_key_log_file_path);
}
sockaddr_in6 address;
if (not get_first_sockaddr_in6_for_interface(interface_name_, address)) {
const auto msg = "Failed to get ipv6 socket address for interface " + interface_name_;
log_and_throw(msg.c_str());
}
end_point.port = TLS_PORT;
memcpy(&end_point.address, &address.sin6_addr, sizeof(address.sin6_addr));
const auto address_name = sockaddr_in6_to_name(address);
if (not address_name) {
const auto msg =
"Failed to determine string representation of ipv6 socket address for interface " + interface_name_;
log_and_throw(msg.c_str());
}
logf_info("Start TLS server [%s]:%" PRIu16, address_name.get(), end_point.port);
ssl->fd = socket(AF_INET6, SOCK_STREAM, 0);
if (ssl->fd == -1) {
log_and_throw("Failed to create an ipv6 socket");
}
// before bind, set the port
address.sin6_port = htons(end_point.port);
int optval_tmp{1};
const auto set_reuseaddr = setsockopt(ssl->fd, SOL_SOCKET, SO_REUSEADDR, &optval_tmp, sizeof(optval_tmp));
if (set_reuseaddr == -1) {
log_and_throw("setsockopt(SO_REUSEADDR) failed");
}
const auto set_reuseport = setsockopt(ssl->fd, SOL_SOCKET, SO_REUSEPORT, &optval_tmp, sizeof(optval_tmp));
if (set_reuseport == -1) {
log_and_throw("setsockopt(SO_REUSEPORT) failed");
}
const auto bind_result = bind(ssl->fd, reinterpret_cast<const struct sockaddr*>(&address), sizeof(address));
if (bind_result == -1) {
const auto error = "Failed to bind ipv6 socket to interface " + interface_name_;
log_and_throw(error.c_str());
}
const auto listen_result = listen(ssl->fd, DEFAULT_SOCKET_BACKLOG);
if (listen_result == -1) {
log_and_throw("Listen on socket failed");
}
poll_manager.register_fd(ssl->fd, [this]() { this->handle_connect(); });
}
ConnectionSSL::~ConnectionSSL() = default;
void ConnectionSSL::set_event_callback(const ConnectionEventCallback& callback) {
event_callback = callback;
}
Ipv6EndPoint ConnectionSSL::get_public_endpoint() const {
return end_point;
}
std::optional<sha512_hash_t> ConnectionSSL::get_vehicle_cert_hash() const {
return ssl->vehicle_cert_hash;
}
void ConnectionSSL::write(const uint8_t* buf, size_t len) {
assert(handshake_complete); // TODO(sl): Adding states?
size_t writebytes = 0;
const auto ssl_ptr = ssl->ssl.get();
const auto ssl_write_result = SSL_write_ex(ssl_ptr, buf, len, &writebytes);
if (ssl_write_result <= 0) {
const auto ssl_err_raw = SSL_get_error(ssl_ptr, ssl_write_result);
log_and_raise_openssl_error("Failed to SSL_write_ex(): " + std::to_string(ssl_err_raw));
} else if (writebytes != len) {
log_and_throw("Didn't complete to write");
}
}
ReadResult ConnectionSSL::read(uint8_t* buf, size_t len) {
assert(handshake_complete); // TODO(sl): Adding states?
size_t readbytes = 0;
const auto ssl_ptr = ssl->ssl.get();
const auto ssl_read_result = SSL_read_ex(ssl_ptr, buf, len, &readbytes);
if (ssl_read_result > 0) {
const auto would_block = (readbytes < len);
return {would_block, readbytes};
}
const auto ssl_error = SSL_get_error(ssl_ptr, ssl_read_result);
if ((ssl_error == SSL_ERROR_WANT_READ) or (ssl_error == SSL_ERROR_WANT_WRITE)) {
return {true, 0};
}
log_and_raise_openssl_error("Failed to SSL_read_ex(): " + std::to_string(ssl_error));
return {false, 0};
}
void ConnectionSSL::handle_connect() {
const auto peer = BIO_ADDR_new();
ssl->accept_fd = BIO_accept_ex(ssl->fd, peer, BIO_SOCK_NONBLOCK);
if (ssl->accept_fd < 0) {
log_and_raise_openssl_error("Failed to BIO_accept_ex");
}
const auto ip = BIO_ADDR_hostname_string(peer, 1);
const auto service = BIO_ADDR_service_string(peer, 1);
logf_info("Incoming connection from [%s]:%s", ip, service);
poll_manager.unregister_fd(ssl->fd);
::close(ssl->fd);
call_if_available(event_callback, ConnectionEvent::ACCEPTED);
ssl->ssl = std::unique_ptr<SSL>(SSL_new(ssl->ssl_ctx.get()));
const auto socket_bio = BIO_new_socket(ssl->accept_fd, BIO_CLOSE);
const auto ssl_ptr = ssl->ssl.get();
SSL_set_bio(ssl_ptr, socket_bio, socket_bio);
SSL_set_accept_state(ssl_ptr);
SSL_set_app_data(ssl_ptr, this);
if (ssl->enable_key_logging) {
const auto port = std::stoul(service);
ssl->key_server = std::make_unique<io::TlsKeyLoggingServer>(ssl->interface_name, port);
SSL_set_ex_data(ssl_ptr, ssl_keylog_server_index, ssl->key_server.get());
}
poll_manager.register_fd(ssl->accept_fd, [this]() { this->handle_data(); });
OPENSSL_free(ip);
OPENSSL_free(service);
BIO_ADDR_free(peer);
}
void ConnectionSSL::handle_data() {
if (not handshake_complete) {
const auto ssl_ptr = ssl->ssl.get();
const auto ssl_handshake_result = SSL_accept(ssl_ptr);
if (ssl_handshake_result <= 0) {
const auto ssl_error = SSL_get_error(ssl_ptr, ssl_handshake_result);
if ((ssl_error == SSL_ERROR_WANT_READ) or (ssl_error == SSL_ERROR_WANT_WRITE)) {
return;
}
log_and_raise_openssl_error("Failed to SSL_accept(): " + std::to_string(ssl_error));
} else {
logf_info("Handshake complete!");
const auto peer = SSL_get0_peer_certificate(ssl_ptr);
if (SSL_get_verify_mode(ssl_ptr) != SSL_VERIFY_NONE and peer) {
const auto verify_result = SSL_get_verify_result(ssl_ptr);
if (verify_result == X509_V_OK) {
logf_info("Verify certificate result is okay");
char name[NAME_LENGTH]{};
x509_name_oneline(X509_get_subject_name(peer), name, sizeof(name));
logf_debug("Peer subject name: %s", name);
name[0] = '\0';
x509_name_oneline(X509_get_issuer_name(peer), name, sizeof(name));
logf_debug("Peer issuer name: %s", name);
unsigned int length = 0;
auto& vehicle_hash = ssl->vehicle_cert_hash.emplace();
const auto result_digest = X509_digest(peer, EVP_sha512(), vehicle_hash.data(), &length);
if (result_digest) {
std::stringstream ss;
ss << std::uppercase << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<int>(vehicle_hash[0]);
for (unsigned int i = 1; i < length; ++i) {
ss << ":" << std::uppercase << std::hex << std::setw(2) << std::setfill('0')
<< (int)static_cast<int>(vehicle_hash[i]);
}
logf_debug("sha512 fingerprint: %s", ss.str().c_str());
// openssl command: openssl x509 -in *.pem -noout -fingerprint -sha512
} else {
logf_error("X509_digest failed");
}
} else {
logf_error("Verify certificate result is not okay");
}
}
handshake_complete = true;
if (ssl->enable_key_logging) {
ssl->key_server.reset();
}
call_if_available(event_callback, ConnectionEvent::OPEN);
return;
}
}
call_if_available(event_callback, ConnectionEvent::NEW_DATA);
}
void ConnectionSSL::close() {
/* tear down TLS connection gracefully */
logf_info("Closing TLS connection");
const auto ssl_ptr = ssl->ssl.get();
const auto ssl_close_result = SSL_shutdown(ssl_ptr); // TODO(sl): Correct shutdown handling
if (ssl_close_result < 0) {
const auto ssl_error = SSL_get_error(ssl_ptr, ssl_close_result);
if ((ssl_error != SSL_ERROR_WANT_READ) and (ssl_error != SSL_ERROR_WANT_WRITE)) {
logf_error("%s", log_openssl_error("Failed to SSL_shutdown(): " + std::to_string(ssl_error)).c_str());
}
}
// TODO(sl): Test if correct
::close(ssl->accept_fd);
poll_manager.unregister_fd(ssl->accept_fd);
logf_info("TLS connection closed gracefully");
call_if_available(event_callback, ConnectionEvent::CLOSED);
}
} // namespace iso15118::io

View File

@@ -0,0 +1,94 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/io/logging.hpp>
#include <cstdarg>
#include <cstdio>
#include <iostream>
static std::function<void(iso15118::LogLevel, std::string)> logging_callback = [](const iso15118::LogLevel& level,
const std::string& msg) {
std::cout << msg << ", level: " << static_cast<int>(level);
};
namespace iso15118 {
void log(const LogLevel& level, const std::string& msg) {
logging_callback(level, msg);
}
void vlogf(const char* fmt, va_list ap) {
static constexpr auto MAX_FMT_LOG_BUFSIZE = 1024;
char msg_buf[MAX_FMT_LOG_BUFSIZE];
vsnprintf(msg_buf, MAX_FMT_LOG_BUFSIZE, fmt, ap);
log(LogLevel::Info, msg_buf);
}
void vlogf(const LogLevel& level, const char* fmt, va_list ap) {
static constexpr auto MAX_FMT_LOG_BUFSIZE = 1024;
char msg_buf[MAX_FMT_LOG_BUFSIZE];
vsnprintf(msg_buf, MAX_FMT_LOG_BUFSIZE, fmt, ap);
log(level, msg_buf);
}
void logf(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
vlogf(fmt, args);
va_end(args);
}
void logf(const LogLevel& level, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
vlogf(level, fmt, args);
va_end(args);
}
void logf_error(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
vlogf(LogLevel::Error, fmt, args);
va_end(args);
}
void logf_warning(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
vlogf(LogLevel::Warning, fmt, args);
va_end(args);
}
void logf_info(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
vlogf(LogLevel::Info, fmt, args);
va_end(args);
}
void logf_debug(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
vlogf(LogLevel::Debug, fmt, args);
va_end(args);
}
void logf_trace(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
vlogf(LogLevel::Trace, fmt, args);
va_end(args);
}
namespace io {
void set_logging_callback(const std::function<void(LogLevel, std::string)>& callback) {
logging_callback = callback;
}
} // namespace io
} // namespace iso15118

View File

@@ -0,0 +1,87 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/io/poll_manager.hpp>
#include <type_traits>
#include <vector>
#include <poll.h>
#include <sys/eventfd.h>
#include <unistd.h>
#include <iso15118/detail/helper.hpp>
namespace iso15118::io {
static PollSet create_poll_set(const std::map<int, PollCallback>& map, int event_fd) {
const auto total_size = map.size() + 1; // including event_fd
decltype(PollSet::fds) fds(total_size);
decltype(PollSet::callbacks) callbacks(total_size);
auto index = 0;
for (auto it = map.begin(); it != map.end(); ++it, ++index) {
fds[index].fd = it->first;
fds[index].events = POLLIN;
callbacks[index] = &it->second;
}
fds[index].fd = event_fd;
fds[index].events = POLLIN;
return {std::move(fds), std::move(callbacks)};
}
PollManager::PollManager() {
event_fd = eventfd(0, 0);
if (event_fd == -1) {
log_and_throw("Failed to create eventfd");
}
}
void PollManager::register_fd(int fd, PollCallback& poll_callback) {
registered_fds.emplace(fd, poll_callback);
poll_set = create_poll_set(registered_fds, event_fd);
}
void PollManager::unregister_fd(int fd) {
registered_fds.erase(fd);
poll_set = create_poll_set(registered_fds, event_fd);
}
void PollManager::poll(int timeout_ms) {
auto& pollfds = poll_set.fds;
const auto ret = ::poll(pollfds.data(), pollfds.size(), timeout_ms);
if (ret == -1) {
log_and_throw("Poll failed\n");
}
if (ret == 0) {
// timeout
return;
}
// first check for event_fd
if (pollfds[pollfds.size() - 1].revents & POLLIN) {
eventfd_t tmp;
eventfd_read(event_fd, &tmp);
// just break;
return;
}
// check fds
for (std::size_t i = 0; i < pollfds.size() - 1; ++i) {
if (pollfds[i].revents & POLLIN) {
(*poll_set.callbacks[i])();
}
}
}
void PollManager::abort() {
eventfd_write(event_fd, 1);
}
} // namespace iso15118::io

View File

@@ -0,0 +1,74 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023-2025 Pionix GmbH and Contributors to EVerest
#include <iso15118/io/sdp_packet.hpp>
#include <cstdio>
#include <cstring>
#include <limits>
#include <arpa/inet.h>
namespace iso15118::io {
v2gtp::PayloadType SdpPacket::get_payload_type() const {
uint16_t tmp;
std::memcpy(&tmp, buffer + 2, sizeof(tmp));
return static_cast<v2gtp::PayloadType>(ntohs(tmp));
}
size_t SdpPacket::get_remaining_bytes_to_read() const {
switch (state) {
case State::BUFFER_EMPTY:
return V2GTP_HEADER_SIZE - bytes_read;
case State::HEADER_READ:
return length - bytes_read;
default:
return 0;
}
}
void SdpPacket::update_read_bytes(size_t len) {
if ((state == State::COMPLETE) or (state == State::INVALID_HEADER) or (state == State::PAYLOAD_TOO_LONG)) {
// nothing to do here - should also not happen, right?
return;
}
bytes_read += len;
if ((state == State::BUFFER_EMPTY) and (bytes_read == V2GTP_HEADER_SIZE)) {
parse_header();
}
if ((state == State::HEADER_READ) and (bytes_read == length)) {
state = State::COMPLETE;
}
}
void SdpPacket::parse_header() {
if ((buffer[0] != SDP_PROTOCOL_VERSION) or (buffer[1] != SDP_INVERSE_PROTOCOL_VERSION)) {
state = State::INVALID_HEADER;
return;
}
uint32_t tmp;
std::memcpy(&tmp, buffer + 4, sizeof(tmp));
// check if length would overflow
const auto len_in_buffer = ntohl(tmp);
if (len_in_buffer > std::numeric_limits<uint32_t>::max() - V2GTP_HEADER_SIZE) {
state = State::INVALID_HEADER;
return;
}
// FIXME (aw): check for ill-formed header!
length = len_in_buffer + V2GTP_HEADER_SIZE;
if (length > sizeof(buffer)) {
state = State::PAYLOAD_TOO_LONG;
return;
}
state = State::HEADER_READ;
}
} // namespace iso15118::io

View File

@@ -0,0 +1,274 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/io/sdp_server.hpp>
#include <cstring>
#include <netdb.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <cbv2g/exi_v2gtp.h>
#include <iso15118/detail/helper.hpp>
// FIXME(Sl): Not sure with define
/* link-local multicast address ff02::1 aka ip6-allnodes */
#define IN6ADDR_ALLNODES \
{ 0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01 }
namespace iso15118 {
static void log_peer_hostname(const struct sockaddr_in6& address) {
char hostname[128];
socklen_t hostname_len = sizeof(hostname);
const auto get_if_name_result = getnameinfo(reinterpret_cast<const struct sockaddr*>(&address), sizeof(address),
hostname, hostname_len, nullptr, 0, NI_NUMERICHOST);
if (0 == get_if_name_result) {
logf_info("Got SDP request from %s", hostname);
} else {
logf_warning("Got SDP request, but failed to get the address");
}
}
namespace io {
SdpServer::SdpServer(const std::string& interface_name) {
fd = socket(AF_INET6, SOCK_DGRAM, 0);
if (fd == -1) {
log_and_throw("Failed to open socket");
}
// initialize socket address, leave scope_id and flowinfo at 0
struct sockaddr_in6 socket_address;
bzero(&socket_address, sizeof(socket_address));
socket_address.sin6_family = AF_INET6;
socket_address.sin6_port = htons(v2gtp::SDP_SERVER_PORT);
memcpy(&socket_address.sin6_addr, &in6addr_any, sizeof(socket_address.sin6_addr));
char addr_res[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, &socket_address.sin6_addr, addr_res, INET6_ADDRSTRLEN);
logf_info("Starting SDP server with address: %s", addr_res);
int enable = 1;
auto result = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(enable));
if (result == -1) {
const auto error_msg = adding_err_msg("Setsockopt(SO_REUSEPORT) failed");
log_and_throw(error_msg.c_str());
}
const auto bind_result =
bind(fd, reinterpret_cast<const struct sockaddr*>(&socket_address), sizeof(socket_address));
if (bind_result == -1) {
log_and_throw("Failed to bind to socket");
}
// Bind only to specified device
result = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, interface_name.c_str(), interface_name.length());
if (result == -1) {
const auto error_msg = adding_err_msg("Setsockopt(SO_BINDTODEVICE) failed");
log_and_throw(error_msg.c_str());
}
// Join multicast group
struct ipv6_mreq mreq {};
mreq.ipv6mr_multiaddr = {{IN6ADDR_ALLNODES}};
mreq.ipv6mr_interface = if_nametoindex(interface_name.c_str());
result = setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq));
if (result == -1) {
const auto error_msg = adding_err_msg("Setsockopt(IPV6_JOIN_GROUP) failed");
log_and_throw(error_msg.c_str());
}
}
void SdpServer::set_dlink_ready(bool ready) {
dlink_ready_.store(ready);
}
bool SdpServer::is_dlink_ready() const {
return dlink_ready_.load();
}
SdpServer::~SdpServer() {
// FIXME (aw): rather use some RAII class for this!
logf_info("Shutting down SDP server!");
if (fd != -1) {
close(fd);
}
}
PeerRequestContext SdpServer::get_peer_request() {
decltype(PeerRequestContext::address) peer_address;
socklen_t peer_addr_len = sizeof(peer_address);
const auto read_result = recvfrom(fd, udp_buffer, sizeof(udp_buffer), 0,
reinterpret_cast<struct sockaddr*>(&peer_address), &peer_addr_len);
if (read_result <= 0) {
log_and_throw("Read on sdp server socket failed");
}
if (peer_addr_len > sizeof(peer_address)) {
log_and_throw("Unexpected address length during read on sdp server socket");
}
log_peer_hostname(peer_address);
if (read_result == sizeof(udp_buffer)) {
logf_warning("Read on sdp server socket succeeded, but message is to big for the buffer");
return PeerRequestContext{false};
}
uint32_t sdp_payload_len;
const auto parse_sdp_result = V2GTP20_ReadHeader(udp_buffer, &sdp_payload_len, V2GTP20_SDP_REQUEST_PAYLOAD_ID);
if (parse_sdp_result != V2GTP_ERROR__NO_ERROR) {
// FIXME (aw): we should not die here immediately
logf_warning("Sdp server received an unexpected payload");
return PeerRequestContext{false};
}
PeerRequestContext peer_request{true};
// NOTE (aw): this could be moved into a constructor
const uint8_t sdp_request_byte1 = udp_buffer[8];
const uint8_t sdp_request_byte2 = udp_buffer[9];
peer_request.security = static_cast<v2gtp::Security>(sdp_request_byte1);
peer_request.transport_protocol = static_cast<v2gtp::TransportProtocol>(sdp_request_byte2);
memcpy(&peer_request.address, &peer_address, sizeof(peer_address));
return peer_request;
}
void SdpServer::send_response(const PeerRequestContext& request, const Ipv6EndPoint& ipv6_endpoint) {
// that worked, now response
uint8_t v2g_packet[28];
uint8_t* sdp_response = v2g_packet + 8;
memcpy(sdp_response, ipv6_endpoint.address, sizeof(ipv6_endpoint.address));
uint16_t port = htons(ipv6_endpoint.port);
memcpy(sdp_response + 16, &port, sizeof(port));
// FIXME (aw): which values to take here?
sdp_response[18] = static_cast<std::underlying_type_t<v2gtp::Security>>(request.security);
sdp_response[19] = static_cast<std::underlying_type_t<v2gtp::TransportProtocol>>(request.transport_protocol);
V2GTP20_WriteHeader(v2g_packet, 20, V2GTP20_SDP_RESPONSE_PAYLOAD_ID);
const auto peer_addr_len = sizeof(request.address);
sendto(fd, v2g_packet, sizeof(v2g_packet), 0, reinterpret_cast<const sockaddr*>(&request.address), peer_addr_len);
}
TlsKeyLoggingServer::TlsKeyLoggingServer(const std::string& interface_name, uint16_t port_) : port(port_) {
static constexpr auto LINK_LOCAL_MULTICAST = "ff02::1";
fd = socket(AF_INET6, SOCK_DGRAM, 0);
if (fd < 0) {
const auto error_msg = adding_err_msg("Could not create socket");
log_and_throw(error_msg.c_str()); // FIXME(sl): Find better handling
}
// source setup
// find port between 49152-65535
auto could_bind = false;
auto source_port = 49152;
for (; source_port < 65535; source_port++) {
sockaddr_in6 source_address = {AF_INET6, htons(source_port), 0, {}, 0};
if (bind(fd, reinterpret_cast<sockaddr*>(&source_address), sizeof(sockaddr_in6)) == 0) {
could_bind = true;
break;
}
}
if (!could_bind) {
const auto error_msg = adding_err_msg("Could not bind");
log_and_throw(error_msg.c_str());
}
logf_info("UDP socket bound to source port: %u", source_port);
const auto index = if_nametoindex(interface_name.c_str());
auto mreq = ipv6_mreq{};
mreq.ipv6mr_interface = index;
if (inet_pton(AF_INET6, LINK_LOCAL_MULTICAST, &mreq.ipv6mr_multiaddr) <= 0) {
const auto error_msg = adding_err_msg("Failed to setup multicast address");
log_and_throw(error_msg.c_str());
}
if (setsockopt(fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
const auto error_msg = adding_err_msg("Could not add multicast group membership");
log_and_throw(error_msg.c_str());
}
if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &index, sizeof(index)) < 0) {
const auto error_msg = adding_err_msg("Could not set interface name:" + std::string(interface_name));
log_and_throw(error_msg.c_str());
}
destination_address = {AF_INET6, htons(port), 0, {}, 0};
if (inet_pton(AF_INET6, LINK_LOCAL_MULTICAST, &destination_address.sin6_addr) <= 0) {
const auto error_msg = adding_err_msg("Failed to setup server address, reset key_log_fd");
log_and_throw(error_msg.c_str());
}
}
TlsKeyLoggingServer::~TlsKeyLoggingServer() {
logf_info("Shutting down TlsKeyLoggingServer server!");
if (fd != -1) {
close(fd);
}
}
ssize_t TlsKeyLoggingServer::send(const char* line) {
return sendto(fd, line, strlen(line), 0, reinterpret_cast<const sockaddr*>(&destination_address),
sizeof(destination_address));
}
#if 0
void parse_sdp_request(uint8_t* packet) {
// check sdp header
uint32_t sdp_payload_len;
const auto parse_sdp_result = V2GTP20_ReadHeader(packet, &sdp_payload_len, V2GTP20_SDP_REQUEST_PAYLOAD_ID);
if (parse_sdp_result != V2GTP_ERROR__NO_ERROR) {
log_and_throw("Failed to parse sdp header");
}
logf_info("Got sdp payload of %d bytes", sdp_payload_len);
const uint8_t sdp_request_byte1 = packet[8];
switch (static_cast<v2gtp::Security>(sdp_request_byte1)) {
case v2gtp::Security::TLS:
logf_info(" -> TLS requested");
break;
case v2gtp::Security::NO_TRANSPORT_SECURITY:
logf_info(" -> no security");
break;
default:
logf_info(" -> EXCEPTION: reserved value");
break;
}
const uint8_t sdp_request_byte2 = packet[9];
switch (static_cast<v2gtp::TransportProtocol>(sdp_request_byte2)) {
case v2gtp::TransportProtocol::TCP:
logf_info(" -> TCP requested");
break;
case v2gtp::TransportProtocol::RESERVED_FOR_UDP:
logf_info(" -> reserved for UDP");
break;
default:
logf_info(" -> EXCEPTION: reserved value");
break;
}
}
#endif
} // namespace io
} // namespace iso15118

View File

@@ -0,0 +1,130 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/detail/io/socket_helper.hpp>
#include <cstring>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <netdb.h>
#include <netinet/in.h>
#include <iso15118/detail/helper.hpp>
namespace iso15118::io {
namespace {
auto choose_first_ipv6_interface() {
std::string interface_name{};
struct ifaddrs* if_list_head;
const auto get_if_addrs_result = getifaddrs(&if_list_head);
if (get_if_addrs_result == -1) {
logf_error("Failed to call getifaddrs");
return std::string("");
}
for (auto current_if = if_list_head; current_if != nullptr; current_if = current_if->ifa_next) {
if (current_if->ifa_addr == nullptr or current_if->ifa_addr->sa_family != AF_INET6) {
continue;
}
// NOTE (aw): because we did the check for AF_INET6, we can assume that ifa_addr is indeed an sockaddr_in6
const auto current_addr = reinterpret_cast<const sockaddr_in6*>(current_if->ifa_addr);
if (not IN6_IS_ADDR_LINKLOCAL(&(current_addr->sin6_addr))) {
continue;
}
interface_name = current_if->ifa_name;
break; // Stop the loop if a interface is found
}
freeifaddrs(if_list_head);
return interface_name;
}
} // namespace
bool check_and_update_interface(std::string& interface_name) {
if (interface_name == "auto") {
logf_info("Search for the first available ipv6 interface");
interface_name = choose_first_ipv6_interface();
}
struct ipv6_mreq mreq {};
mreq.ipv6mr_interface = if_nametoindex(interface_name.c_str());
if (!mreq.ipv6mr_interface) {
logf_error("No such interface: %s", interface_name.c_str());
return false;
}
return not interface_name.empty();
}
bool get_first_sockaddr_in6_for_interface(const std::string& interface_name, sockaddr_in6& address) {
struct ifaddrs* if_list_head;
const auto get_if_addrs_result = getifaddrs(&if_list_head);
if (get_if_addrs_result == -1) {
log_and_throw("Failed to call getifaddrs");
}
bool found_interface = false;
for (auto current_if = if_list_head; current_if != nullptr; current_if = current_if->ifa_next) {
if (current_if->ifa_addr == nullptr) {
continue;
}
if (current_if->ifa_addr->sa_family != AF_INET6) {
continue;
}
if (interface_name.compare("auto") != 0 && interface_name.compare(current_if->ifa_name) != 0) {
continue;
}
// NOTE (aw): because we did the check for AF_INET6, we can assume that ifa_addr is indeed an sockaddr_in6
const auto current_addr = reinterpret_cast<const sockaddr_in6*>(current_if->ifa_addr);
// NOTE (sl): If using loopback device, accept any address. Loopback usually does not have a link local address
if (interface_name.compare("lo") != 0 and not IN6_IS_ADDR_LINKLOCAL(&(current_addr->sin6_addr))) {
continue;
}
if (interface_name == "auto") {
logf_info("Found an ipv6 link local address for interface: %s", current_if->ifa_name);
}
memcpy(&address, current_addr, sizeof(address));
found_interface = true;
break; // Stop the loop if a interface is found
}
freeifaddrs(if_list_head);
// Todo(sl): What to do if interface was not found?
return found_interface;
}
std::unique_ptr<char[]> sockaddr_in6_to_name(const sockaddr_in6& address) {
// account for ipv6 address string length plus possible scope/zone
// identifier which seems to be an interface name, as both constants
// (INET6_ADDRSTRLEN and IFNAMSIZ) include the terminating NULL, we
// have one extra character that can account for the separating '%'
// between the ipv6 address and the scope/zone identifier
static constexpr auto MAX_NUMERIC_NAME_LENGTH = INET6_ADDRSTRLEN + IFNAMSIZ;
auto name = std::make_unique<char[]>(MAX_NUMERIC_NAME_LENGTH);
// FIXME (aw): what about alignment issues here between casting from sockaddr_in6 to sockaddr?
const auto result = getnameinfo(reinterpret_cast<const sockaddr*>(&address), sizeof(address), name.get(),
MAX_NUMERIC_NAME_LENGTH, nullptr, 0, NI_NUMERICHOST);
if (result == 0) {
return name;
} else {
return nullptr;
}
}
} // namespace iso15118::io

View File

@@ -0,0 +1,545 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Pionix GmbH and Contributors to EVerest
#include <iso15118/message/ac_charge_loop.hpp>
#include <type_traits>
#include <iso15118/detail/variant_access.hpp>
#include <cbv2g/iso_20/iso20_AC_Decoder.h>
#include <cbv2g/iso_20/iso20_AC_Encoder.h>
namespace iso15118::message_20 {
// Begin conversion for deserializing an ACChargeLoopRequest (EVSEside)
template <> void convert(const struct iso20_ac_DisplayParametersType& in, datatypes::DisplayParameters& out) {
CB2CPP_ASSIGN_IF_USED(in.PresentSOC, out.present_soc);
CB2CPP_ASSIGN_IF_USED(in.MinimumSOC, out.min_soc);
CB2CPP_ASSIGN_IF_USED(in.TargetSOC, out.target_soc);
CB2CPP_ASSIGN_IF_USED(in.MaximumSOC, out.max_soc);
CB2CPP_ASSIGN_IF_USED(in.RemainingTimeToMinimumSOC, out.remaining_time_to_min_soc);
CB2CPP_ASSIGN_IF_USED(in.RemainingTimeToTargetSOC, out.remaining_time_to_target_soc);
CB2CPP_ASSIGN_IF_USED(in.RemainingTimeToMaximumSOC, out.remaining_time_to_max_soc);
CB2CPP_ASSIGN_IF_USED(in.ChargingComplete, out.charging_complete);
CB2CPP_CONVERT_IF_USED(in.BatteryEnergyCapacity, out.battery_energy_capacity);
CB2CPP_ASSIGN_IF_USED(in.InletHot, out.inlet_hot);
}
// Todo(sl): this should go to common.cpp
template <typename InType> void convert(const InType& in, datatypes::Scheduled_CLReqControlMode& out) {
CB2CPP_CONVERT_IF_USED(in.EVTargetEnergyRequest, out.target_energy_request);
CB2CPP_CONVERT_IF_USED(in.EVMaximumEnergyRequest, out.max_energy_request);
CB2CPP_CONVERT_IF_USED(in.EVMinimumEnergyRequest, out.min_energy_request);
}
template <typename InType> void convert(const InType& in, datatypes::Scheduled_AC_CLReqControlMode& out) {
static_assert(std::is_same_v<InType, iso20_ac_Scheduled_AC_CLReqControlModeType> or
std::is_same_v<InType, iso20_ac_BPT_Scheduled_AC_CLReqControlModeType>);
convert(in, static_cast<datatypes::Scheduled_CLReqControlMode&>(out));
CB2CPP_CONVERT_IF_USED(in.EVMaximumChargePower, out.max_charge_power);
CB2CPP_CONVERT_IF_USED(in.EVMaximumChargePower_L2, out.max_charge_power_L2);
CB2CPP_CONVERT_IF_USED(in.EVMaximumChargePower_L3, out.max_charge_power_L3);
CB2CPP_CONVERT_IF_USED(in.EVMinimumChargePower, out.min_charge_power);
CB2CPP_CONVERT_IF_USED(in.EVMinimumChargePower_L2, out.min_charge_power_L2);
CB2CPP_CONVERT_IF_USED(in.EVMinimumChargePower_L3, out.min_charge_power_L3);
convert(in.EVPresentActivePower, out.present_active_power);
CB2CPP_CONVERT_IF_USED(in.EVPresentActivePower_L2, out.present_active_power_L2);
CB2CPP_CONVERT_IF_USED(in.EVPresentActivePower_L3, out.present_active_power_L3);
CB2CPP_CONVERT_IF_USED(in.EVPresentReactivePower, out.present_reactive_power);
CB2CPP_CONVERT_IF_USED(in.EVPresentReactivePower_L2, out.present_reactive_power_L2);
CB2CPP_CONVERT_IF_USED(in.EVPresentReactivePower_L3, out.present_reactive_power_L3);
}
template <>
void convert(const struct iso20_ac_BPT_Scheduled_AC_CLReqControlModeType& in,
datatypes::BPT_Scheduled_AC_CLReqControlMode& out) {
convert(in, static_cast<datatypes::Scheduled_AC_CLReqControlMode&>(out));
CB2CPP_CONVERT_IF_USED(in.EVMaximumDischargePower, out.max_discharge_power);
CB2CPP_CONVERT_IF_USED(in.EVMaximumDischargePower_L2, out.max_discharge_power_L2);
CB2CPP_CONVERT_IF_USED(in.EVMaximumDischargePower_L3, out.max_discharge_power_L3);
CB2CPP_CONVERT_IF_USED(in.EVMinimumDischargePower, out.min_discharge_power);
CB2CPP_CONVERT_IF_USED(in.EVMinimumDischargePower_L2, out.min_discharge_power_L2);
CB2CPP_CONVERT_IF_USED(in.EVMinimumDischargePower_L3, out.min_discharge_power_L3);
}
// Todo(sl): this should go to common.cpp
template <typename InType> void convert(const InType& in, datatypes::Dynamic_CLReqControlMode& out) {
CB2CPP_ASSIGN_IF_USED(in.DepartureTime, out.departure_time);
convert(in.EVTargetEnergyRequest, out.target_energy_request);
convert(in.EVMaximumEnergyRequest, out.max_energy_request);
convert(in.EVMinimumEnergyRequest, out.min_energy_request);
}
template <typename InType> void convert(const InType& in, datatypes::Dynamic_AC_CLReqControlMode& out) {
static_assert(std::is_same_v<InType, iso20_ac_Dynamic_AC_CLReqControlModeType> or
std::is_same_v<InType, iso20_ac_BPT_Dynamic_AC_CLReqControlModeType>);
convert(in, static_cast<datatypes::Dynamic_CLReqControlMode&>(out));
convert(in.EVMaximumChargePower, out.max_charge_power);
CB2CPP_CONVERT_IF_USED(in.EVMaximumChargePower_L2, out.max_charge_power_L2);
CB2CPP_CONVERT_IF_USED(in.EVMaximumChargePower_L3, out.max_charge_power_L3);
convert(in.EVMinimumChargePower, out.min_charge_power);
CB2CPP_CONVERT_IF_USED(in.EVMinimumChargePower_L2, out.min_charge_power_L2);
CB2CPP_CONVERT_IF_USED(in.EVMinimumChargePower_L3, out.min_charge_power_L3);
convert(in.EVPresentActivePower, out.present_active_power);
CB2CPP_CONVERT_IF_USED(in.EVPresentActivePower_L2, out.present_active_power_L2);
CB2CPP_CONVERT_IF_USED(in.EVPresentActivePower_L3, out.present_active_power_L3);
convert(in.EVPresentReactivePower, out.present_reactive_power);
CB2CPP_CONVERT_IF_USED(in.EVPresentReactivePower_L2, out.present_reactive_power_L2);
CB2CPP_CONVERT_IF_USED(in.EVPresentReactivePower_L3, out.present_reactive_power_L3);
}
template <>
void convert(const struct iso20_ac_BPT_Dynamic_AC_CLReqControlModeType& in,
datatypes::BPT_Dynamic_AC_CLReqControlMode& out) {
convert(in, static_cast<datatypes::Dynamic_AC_CLReqControlMode&>(out));
convert(in.EVMaximumDischargePower, out.max_discharge_power);
CB2CPP_CONVERT_IF_USED(in.EVMaximumDischargePower_L2, out.max_discharge_power_L2);
CB2CPP_CONVERT_IF_USED(in.EVMaximumDischargePower_L3, out.max_discharge_power_L3);
convert(in.EVMinimumDischargePower, out.min_discharge_power);
CB2CPP_CONVERT_IF_USED(in.EVMinimumDischargePower_L2, out.min_discharge_power_L2);
CB2CPP_CONVERT_IF_USED(in.EVMinimumDischargePower_L3, out.min_discharge_power_L3);
CB2CPP_CONVERT_IF_USED(in.EVMaximumV2XEnergyRequest, out.max_v2x_energy_request);
CB2CPP_CONVERT_IF_USED(in.EVMinimumV2XEnergyRequest, out.min_v2x_energy_request);
}
template <> void convert(const struct iso20_ac_AC_ChargeLoopReqType& in, AC_ChargeLoopRequest& out) {
convert(in.Header, out.header);
CB2CPP_CONVERT_IF_USED(in.DisplayParameters, out.display_parameters);
out.meter_info_requested = in.MeterInfoRequested;
if (in.Scheduled_AC_CLReqControlMode_isUsed) {
convert(in.Scheduled_AC_CLReqControlMode, out.control_mode.emplace<datatypes::Scheduled_AC_CLReqControlMode>());
} else if (in.BPT_Scheduled_AC_CLReqControlMode_isUsed) {
convert(in.BPT_Scheduled_AC_CLReqControlMode,
out.control_mode.emplace<datatypes::BPT_Scheduled_AC_CLReqControlMode>());
} else if (in.Dynamic_AC_CLReqControlMode_isUsed) {
convert(in.Dynamic_AC_CLReqControlMode, out.control_mode.emplace<datatypes::Dynamic_AC_CLReqControlMode>());
} else if (in.BPT_Dynamic_AC_CLReqControlMode_isUsed) {
convert(in.BPT_Dynamic_AC_CLReqControlMode,
out.control_mode.emplace<datatypes::BPT_Dynamic_AC_CLReqControlMode>());
} else {
// should not happen
assert(false);
}
}
template <> void insert_type(VariantAccess& va, const struct iso20_ac_AC_ChargeLoopReqType& in) {
va.insert_type<AC_ChargeLoopRequest>(in);
}
// End conversion for deserializing an ACChargeLoopRequest (EVSEside)
// Begin conversion for deserializing an ACChargeLoopResponse (EVside)
template <> void convert(const struct iso20_ac_DetailedCostType& in, datatypes::DetailedCost& out) {
convert(in.Amount, out.amount);
convert(in.CostPerUnit, out.cost_per_unit);
}
template <> void convert(const struct iso20_ac_DetailedTaxType& in, datatypes::DetailedTax& out) {
out.tax_rule_id = in.TaxRuleID;
convert(in.Amount, out.amount);
}
template <> void convert(const struct iso20_ac_ReceiptType& in, datatypes::Receipt& out) {
out.time_anchor = in.TimeAnchor;
CB2CPP_CONVERT_IF_USED(in.EnergyCosts, out.energy_costs);
CB2CPP_CONVERT_IF_USED(in.OccupancyCosts, out.occupancy_costs);
CB2CPP_CONVERT_IF_USED(in.AdditionalServicesCosts, out.additional_service_costs);
CB2CPP_CONVERT_IF_USED(in.OverstayCosts, out.overstay_costs);
if (in.TaxCosts.arrayLen > 10) {
throw std::runtime_error("tax costs array is too large");
}
out.tax_costs.clear();
for (std::size_t i = 0; i < in.TaxCosts.arrayLen; ++i) {
convert(in.TaxCosts.array[i], out.tax_costs.emplace_back());
}
}
template <typename InType> void convert(const InType& in, datatypes::Scheduled_AC_CLResControlMode& out) {
CB2CPP_CONVERT_IF_USED(in.EVSETargetActivePower, out.target_active_power);
CB2CPP_CONVERT_IF_USED(in.EVSETargetActivePower_L2, out.target_active_power_L2);
CB2CPP_CONVERT_IF_USED(in.EVSETargetActivePower_L3, out.target_active_power_L3);
CB2CPP_CONVERT_IF_USED(in.EVSETargetReactivePower, out.target_reactive_power);
CB2CPP_CONVERT_IF_USED(in.EVSETargetReactivePower_L2, out.target_reactive_power_L2);
CB2CPP_CONVERT_IF_USED(in.EVSETargetReactivePower_L3, out.target_reactive_power_L3);
CB2CPP_CONVERT_IF_USED(in.EVSEPresentActivePower, out.present_active_power);
CB2CPP_CONVERT_IF_USED(in.EVSEPresentActivePower_L2, out.present_active_power_L2);
CB2CPP_CONVERT_IF_USED(in.EVSEPresentActivePower_L3, out.present_active_power_L3);
}
template <>
void convert(const iso20_ac_BPT_Scheduled_AC_CLResControlModeType& in,
struct datatypes::BPT_Scheduled_AC_CLResControlMode& out) {
convert(in, static_cast<datatypes::Scheduled_AC_CLResControlMode&>(out));
}
template <typename cb_Type> void convert(const cb_Type& in, datatypes::Dynamic_CLResControlMode& out) {
static_assert(std::is_same_v<cb_Type, iso20_ac_Dynamic_AC_CLResControlModeType> or
std::is_same_v<cb_Type, iso20_ac_BPT_Dynamic_AC_CLResControlModeType>);
CB2CPP_ASSIGN_IF_USED(in.DepartureTime, out.departure_time);
CB2CPP_ASSIGN_IF_USED(in.MinimumSOC, out.minimum_soc);
CB2CPP_ASSIGN_IF_USED(in.TargetSOC, out.target_soc);
CB2CPP_ASSIGN_IF_USED(in.AckMaxDelay, out.ack_max_delay);
}
template <typename cb_Type> void convert(const cb_Type& in, datatypes::Dynamic_AC_CLResControlMode& out) {
convert(in, static_cast<datatypes::Dynamic_CLResControlMode&>(out));
convert(in.EVSETargetActivePower, out.target_active_power);
CB2CPP_CONVERT_IF_USED(in.EVSETargetActivePower_L2, out.target_active_power_L2);
CB2CPP_CONVERT_IF_USED(in.EVSETargetActivePower_L3, out.target_active_power_L3);
CB2CPP_CONVERT_IF_USED(in.EVSETargetReactivePower, out.target_reactive_power);
CB2CPP_CONVERT_IF_USED(in.EVSETargetReactivePower_L2, out.target_reactive_power_L2);
CB2CPP_CONVERT_IF_USED(in.EVSETargetReactivePower_L3, out.target_reactive_power_L3);
CB2CPP_CONVERT_IF_USED(in.EVSEPresentActivePower, out.present_active_power);
CB2CPP_CONVERT_IF_USED(in.EVSEPresentActivePower_L2, out.present_active_power_L2);
CB2CPP_CONVERT_IF_USED(in.EVSEPresentActivePower_L3, out.present_active_power_L3);
}
template <>
void convert(const struct iso20_ac_BPT_Dynamic_AC_CLResControlModeType& in,
datatypes::BPT_Dynamic_AC_CLResControlMode& out) {
convert(in, static_cast<datatypes::Dynamic_AC_CLResControlMode&>(out));
}
template <> void convert(const struct iso20_ac_AC_ChargeLoopResType& in, AC_ChargeLoopResponse& out) {
convert(in.Header, out.header);
cb_convert_enum(in.ResponseCode, out.response_code);
CB2CPP_CONVERT_IF_USED(in.MeterInfo, out.meter_info);
CB2CPP_CONVERT_IF_USED(in.Receipt, out.receipt);
CB2CPP_CONVERT_IF_USED(in.EVSEStatus, out.status);
CB2CPP_CONVERT_IF_USED(in.EVSETargetFrequency, out.target_frequency);
if (in.Scheduled_AC_CLResControlMode_isUsed) {
convert(in.Scheduled_AC_CLResControlMode, out.control_mode.emplace<datatypes::Scheduled_AC_CLResControlMode>());
} else if (in.BPT_Scheduled_AC_CLResControlMode_isUsed) {
convert(in.BPT_Scheduled_AC_CLResControlMode,
out.control_mode.emplace<datatypes::BPT_Scheduled_AC_CLResControlMode>());
} else if (in.Dynamic_AC_CLResControlMode_isUsed) {
convert(in.Dynamic_AC_CLResControlMode, out.control_mode.emplace<datatypes::Dynamic_AC_CLResControlMode>());
} else if (in.BPT_Dynamic_AC_CLResControlMode_isUsed) {
convert(in.BPT_Dynamic_AC_CLResControlMode,
out.control_mode.emplace<datatypes::BPT_Dynamic_AC_CLResControlMode>());
} else {
// should not happen
assert(false);
}
}
template <> void insert_type(VariantAccess& va, const struct iso20_ac_AC_ChargeLoopResType& in) {
va.insert_type<AC_ChargeLoopResponse>(in);
}
// End conversion for deserializing an ACChargeLoopResponse (EVside)
// Begin conversion for serializing an ACChargeLoopResponse (EVSEside)
template <> void convert(const datatypes::DetailedCost& in, struct iso20_ac_DetailedCostType& out) {
init_iso20_ac_DetailedCostType(&out);
convert(in.amount, out.Amount);
convert(in.cost_per_unit, out.CostPerUnit);
}
template <> void convert(const datatypes::DetailedTax& in, struct iso20_ac_DetailedTaxType& out) {
init_iso20_ac_DetailedTaxType(&out);
out.TaxRuleID = in.tax_rule_id;
convert(in.amount, out.Amount);
}
template <> void convert(const datatypes::Receipt& in, struct iso20_ac_ReceiptType& out) {
init_iso20_ac_ReceiptType(&out);
out.TimeAnchor = in.time_anchor;
CPP2CB_CONVERT_IF_USED(in.energy_costs, out.EnergyCosts);
CPP2CB_CONVERT_IF_USED(in.occupancy_costs, out.OccupancyCosts);
CPP2CB_CONVERT_IF_USED(in.additional_service_costs, out.AdditionalServicesCosts);
CPP2CB_CONVERT_IF_USED(in.overstay_costs, out.OverstayCosts);
if (sizeof(out.TaxCosts.array) < in.tax_costs.size()) {
throw std::runtime_error("tax costs array is too large");
}
for (std::size_t i = 0; i < in.tax_costs.size(); ++i) {
convert(in.tax_costs[i], out.TaxCosts.array[i]);
}
out.TaxCosts.arrayLen = in.tax_costs.size();
}
template <typename cb_Type> void convert(const datatypes::Scheduled_AC_CLResControlMode& in, cb_Type& out) {
CPP2CB_CONVERT_IF_USED(in.target_active_power, out.EVSETargetActivePower);
CPP2CB_CONVERT_IF_USED(in.target_active_power_L2, out.EVSETargetActivePower_L2);
CPP2CB_CONVERT_IF_USED(in.target_active_power_L2, out.EVSETargetActivePower_L3);
CPP2CB_CONVERT_IF_USED(in.target_reactive_power, out.EVSETargetReactivePower);
CPP2CB_CONVERT_IF_USED(in.target_reactive_power_L2, out.EVSETargetReactivePower_L2);
CPP2CB_CONVERT_IF_USED(in.target_reactive_power_L2, out.EVSETargetReactivePower_L3);
CPP2CB_CONVERT_IF_USED(in.present_active_power, out.EVSEPresentActivePower);
CPP2CB_CONVERT_IF_USED(in.present_active_power_L2, out.EVSEPresentActivePower_L2);
CPP2CB_CONVERT_IF_USED(in.present_active_power_L3, out.EVSEPresentActivePower_L3);
}
template <>
void convert(const datatypes::BPT_Scheduled_AC_CLResControlMode& in,
struct iso20_ac_BPT_Scheduled_AC_CLResControlModeType& out) {
convert(static_cast<const datatypes::Scheduled_AC_CLResControlMode&>(in), out);
}
template <typename cb_Type> void convert(const datatypes::Dynamic_CLResControlMode& in, cb_Type& out) {
CPP2CB_ASSIGN_IF_USED(in.departure_time, out.DepartureTime);
CPP2CB_ASSIGN_IF_USED(in.minimum_soc, out.MinimumSOC);
CPP2CB_ASSIGN_IF_USED(in.target_soc, out.TargetSOC);
CPP2CB_ASSIGN_IF_USED(in.ack_max_delay, out.AckMaxDelay);
}
template <typename cb_Type> void convert(const datatypes::Dynamic_AC_CLResControlMode& in, cb_Type& out) {
convert(static_cast<const datatypes::Dynamic_CLResControlMode&>(in), out);
convert(in.target_active_power, out.EVSETargetActivePower);
CPP2CB_CONVERT_IF_USED(in.target_active_power_L2, out.EVSETargetActivePower_L2);
CPP2CB_CONVERT_IF_USED(in.target_active_power_L3, out.EVSETargetActivePower_L3);
CPP2CB_CONVERT_IF_USED(in.target_reactive_power, out.EVSETargetReactivePower);
CPP2CB_CONVERT_IF_USED(in.target_reactive_power_L2, out.EVSETargetReactivePower_L2);
CPP2CB_CONVERT_IF_USED(in.target_reactive_power_L3, out.EVSETargetReactivePower_L3);
CPP2CB_CONVERT_IF_USED(in.present_active_power, out.EVSEPresentActivePower);
CPP2CB_CONVERT_IF_USED(in.present_active_power_L2, out.EVSEPresentActivePower_L2);
CPP2CB_CONVERT_IF_USED(in.present_active_power_L3, out.EVSEPresentActivePower_L3);
}
template <>
void convert(const datatypes::BPT_Dynamic_AC_CLResControlMode& in,
struct iso20_ac_BPT_Dynamic_AC_CLResControlModeType& out) {
convert(static_cast<const datatypes::Dynamic_AC_CLResControlMode&>(in), out);
}
struct ControlModeVisitor {
using ScheduledCM = datatypes::Scheduled_AC_CLResControlMode;
using BPT_ScheduledCM = datatypes::BPT_Scheduled_AC_CLResControlMode;
using DynamicCM = datatypes::Dynamic_AC_CLResControlMode;
using BPT_DynamicCM = datatypes::BPT_Dynamic_AC_CLResControlMode;
ControlModeVisitor(iso20_ac_AC_ChargeLoopResType& res_) : res(res_){};
void operator()(const ScheduledCM& in) {
auto& out = res.Scheduled_AC_CLResControlMode;
init_iso20_ac_Scheduled_AC_CLResControlModeType(&out);
convert(in, out);
CB_SET_USED(res.Scheduled_AC_CLResControlMode);
}
void operator()(const BPT_ScheduledCM& in) {
auto& out = res.BPT_Scheduled_AC_CLResControlMode;
init_iso20_ac_BPT_Scheduled_AC_CLResControlModeType(&out);
convert(in, out);
CB_SET_USED(res.BPT_Scheduled_AC_CLResControlMode);
}
void operator()(const DynamicCM& in) {
auto& out = res.Dynamic_AC_CLResControlMode;
init_iso20_ac_Dynamic_AC_CLResControlModeType(&out);
convert(in, out);
CB_SET_USED(res.Dynamic_AC_CLResControlMode);
}
void operator()(const BPT_DynamicCM& in) {
auto& out = res.BPT_Dynamic_AC_CLResControlMode;
init_iso20_ac_BPT_Dynamic_AC_CLResControlModeType(&out);
convert(in, out);
CB_SET_USED(res.BPT_Dynamic_AC_CLResControlMode);
}
private:
iso20_ac_AC_ChargeLoopResType& res;
};
template <> void convert(const AC_ChargeLoopResponse& in, struct iso20_ac_AC_ChargeLoopResType& out) {
init_iso20_ac_AC_ChargeLoopResType(&out);
convert(in.header, out.Header);
cb_convert_enum(in.response_code, out.ResponseCode);
CPP2CB_CONVERT_IF_USED(in.status, out.EVSEStatus);
CPP2CB_CONVERT_IF_USED(in.meter_info, out.MeterInfo);
CPP2CB_CONVERT_IF_USED(in.receipt, out.Receipt);
CPP2CB_CONVERT_IF_USED(in.target_frequency, out.EVSETargetFrequency);
std::visit(ControlModeVisitor(out), in.control_mode);
}
template <> int serialize_to_exi(const AC_ChargeLoopResponse& in, exi_bitstream_t& out) {
iso20_ac_exiDocument doc;
init_iso20_ac_exiDocument(&doc);
CB_SET_USED(doc.AC_ChargeLoopRes);
convert(in, doc.AC_ChargeLoopRes);
return encode_iso20_ac_exiDocument(&out, &doc);
}
template <> size_t serialize(const AC_ChargeLoopResponse& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
// End conversion for serializing an ACChargeLoopResponse (EVSEside)
// Begin conversion for serializing an ACChargeLoopRequest (EVside)
template <> void convert(const datatypes::DisplayParameters& in, struct iso20_ac_DisplayParametersType& out) {
init_iso20_ac_DisplayParametersType(&out);
CPP2CB_ASSIGN_IF_USED(in.present_soc, out.PresentSOC);
CPP2CB_ASSIGN_IF_USED(in.min_soc, out.MinimumSOC);
CPP2CB_ASSIGN_IF_USED(in.target_soc, out.TargetSOC);
CPP2CB_ASSIGN_IF_USED(in.max_soc, out.MaximumSOC);
CPP2CB_ASSIGN_IF_USED(in.remaining_time_to_min_soc, out.RemainingTimeToMinimumSOC);
CPP2CB_ASSIGN_IF_USED(in.remaining_time_to_target_soc, out.RemainingTimeToTargetSOC);
CPP2CB_ASSIGN_IF_USED(in.remaining_time_to_max_soc, out.RemainingTimeToMaximumSOC);
CPP2CB_ASSIGN_IF_USED(in.charging_complete, out.ChargingComplete);
CPP2CB_CONVERT_IF_USED(in.battery_energy_capacity, out.BatteryEnergyCapacity);
CPP2CB_ASSIGN_IF_USED(in.inlet_hot, out.InletHot);
}
struct ModeRequestVisitor {
using ScheduledCM = datatypes::Scheduled_AC_CLReqControlMode;
using BPT_ScheduledCM = datatypes::BPT_Scheduled_AC_CLReqControlMode;
using DynamicCM = datatypes::Dynamic_AC_CLReqControlMode;
using BPT_DynamicCM = datatypes::BPT_Dynamic_AC_CLReqControlMode;
public:
ModeRequestVisitor(iso20_ac_AC_ChargeLoopReqType& req_) : req(req_){};
void operator()(const ScheduledCM in) {
init_iso20_ac_Scheduled_AC_CLReqControlModeType(&req.Scheduled_AC_CLReqControlMode);
CB_SET_USED(req.Scheduled_AC_CLReqControlMode);
convert_scheduled_common(in, req.Scheduled_AC_CLReqControlMode);
}
void operator()(const BPT_ScheduledCM& in) {
init_iso20_ac_BPT_Scheduled_AC_CLReqControlModeType(&req.BPT_Scheduled_AC_CLReqControlMode);
CB_SET_USED(req.BPT_Scheduled_AC_CLReqControlMode);
auto& out = req.BPT_Scheduled_AC_CLReqControlMode;
convert_scheduled_common(in, out);
CPP2CB_CONVERT_IF_USED(in.max_discharge_power, out.EVMaximumDischargePower);
CPP2CB_CONVERT_IF_USED(in.max_discharge_power_L2, out.EVMaximumDischargePower_L2);
CPP2CB_CONVERT_IF_USED(in.max_discharge_power_L3, out.EVMaximumDischargePower_L3);
CPP2CB_CONVERT_IF_USED(in.min_discharge_power, out.EVMinimumDischargePower);
CPP2CB_CONVERT_IF_USED(in.min_discharge_power_L2, out.EVMinimumDischargePower_L2);
CPP2CB_CONVERT_IF_USED(in.min_discharge_power_L3, out.EVMinimumDischargePower_L3);
}
template <typename ModeReqTypeIn, typename ModeReqTypeOut>
static void convert_scheduled_common(const ModeReqTypeIn& in, ModeReqTypeOut& out) {
CPP2CB_CONVERT_IF_USED(in.target_energy_request, out.EVTargetEnergyRequest);
CPP2CB_CONVERT_IF_USED(in.max_energy_request, out.EVMaximumEnergyRequest);
CPP2CB_CONVERT_IF_USED(in.min_energy_request, out.EVMinimumEnergyRequest);
CPP2CB_CONVERT_IF_USED(in.max_charge_power, out.EVMaximumChargePower);
CPP2CB_CONVERT_IF_USED(in.max_charge_power_L2, out.EVMaximumChargePower_L2);
CPP2CB_CONVERT_IF_USED(in.max_charge_power_L3, out.EVMaximumChargePower_L3);
CPP2CB_CONVERT_IF_USED(in.min_charge_power, out.EVMinimumChargePower);
CPP2CB_CONVERT_IF_USED(in.min_charge_power_L2, out.EVMinimumChargePower_L2);
CPP2CB_CONVERT_IF_USED(in.min_charge_power_L3, out.EVMinimumChargePower_L3);
convert(in.present_active_power, out.EVPresentActivePower);
CPP2CB_CONVERT_IF_USED(in.present_active_power_L2, out.EVPresentActivePower_L2);
CPP2CB_CONVERT_IF_USED(in.present_active_power_L3, out.EVPresentActivePower_L3);
CPP2CB_CONVERT_IF_USED(in.present_reactive_power, out.EVPresentReactivePower);
CPP2CB_CONVERT_IF_USED(in.present_reactive_power_L2, out.EVPresentReactivePower_L2);
CPP2CB_CONVERT_IF_USED(in.present_reactive_power_L3, out.EVPresentReactivePower_L3);
}
void operator()(const DynamicCM& in) {
init_iso20_ac_Dynamic_AC_CLReqControlModeType(&req.Dynamic_AC_CLReqControlMode);
CB_SET_USED(req.Dynamic_AC_CLReqControlMode);
convert_dynamic_common(in, req.Dynamic_AC_CLReqControlMode);
}
void operator()(const BPT_DynamicCM& in) {
init_iso20_ac_BPT_Dynamic_AC_CLReqControlModeType(&req.BPT_Dynamic_AC_CLReqControlMode);
CB_SET_USED(req.BPT_Dynamic_AC_CLReqControlMode);
auto& out = req.BPT_Dynamic_AC_CLReqControlMode;
convert_dynamic_common(in, out);
convert(in.max_discharge_power, out.EVMaximumDischargePower);
CPP2CB_CONVERT_IF_USED(in.max_discharge_power_L2, out.EVMaximumDischargePower_L2);
CPP2CB_CONVERT_IF_USED(in.max_discharge_power_L3, out.EVMaximumDischargePower_L3);
convert(in.min_discharge_power, out.EVMinimumDischargePower);
CPP2CB_CONVERT_IF_USED(in.min_discharge_power_L2, out.EVMinimumDischargePower_L2);
CPP2CB_CONVERT_IF_USED(in.min_discharge_power_L3, out.EVMinimumDischargePower_L3);
CPP2CB_CONVERT_IF_USED(in.max_v2x_energy_request, out.EVMaximumV2XEnergyRequest);
CPP2CB_CONVERT_IF_USED(in.min_v2x_energy_request, out.EVMinimumV2XEnergyRequest);
}
template <typename ModeReqTypeIn, typename ModeReqTypeOut>
static void convert_dynamic_common(const ModeReqTypeIn& in, ModeReqTypeOut& out) {
CPP2CB_ASSIGN_IF_USED(in.departure_time, out.DepartureTime);
convert(in.target_energy_request, out.EVTargetEnergyRequest);
convert(in.max_energy_request, out.EVMaximumEnergyRequest);
convert(in.min_energy_request, out.EVMinimumEnergyRequest);
convert(in.max_charge_power, out.EVMaximumChargePower);
CPP2CB_CONVERT_IF_USED(in.max_charge_power_L2, out.EVMaximumChargePower_L2);
CPP2CB_CONVERT_IF_USED(in.max_charge_power_L3, out.EVMaximumChargePower_L3);
convert(in.min_charge_power, out.EVMinimumChargePower);
CPP2CB_CONVERT_IF_USED(in.min_charge_power_L2, out.EVMinimumChargePower_L2);
CPP2CB_CONVERT_IF_USED(in.min_charge_power_L3, out.EVMinimumChargePower_L3);
convert(in.present_active_power, out.EVPresentActivePower);
CPP2CB_CONVERT_IF_USED(in.present_active_power_L2, out.EVPresentActivePower_L2);
CPP2CB_CONVERT_IF_USED(in.present_active_power_L3, out.EVPresentActivePower_L3);
convert(in.present_reactive_power, out.EVPresentReactivePower);
CPP2CB_CONVERT_IF_USED(in.present_reactive_power_L2, out.EVPresentReactivePower_L2);
CPP2CB_CONVERT_IF_USED(in.present_reactive_power_L3, out.EVPresentReactivePower_L3);
}
private:
iso20_ac_AC_ChargeLoopReqType& req;
};
template <> void convert(const AC_ChargeLoopRequest& in, struct iso20_ac_AC_ChargeLoopReqType& out) {
init_iso20_ac_AC_ChargeLoopReqType(&out);
convert(in.header, out.Header);
CPP2CB_CONVERT_IF_USED(in.display_parameters, out.DisplayParameters);
out.MeterInfoRequested = in.meter_info_requested;
std::visit(ModeRequestVisitor(out), in.control_mode);
}
template <> int serialize_to_exi(const AC_ChargeLoopRequest& in, exi_bitstream_t& out) {
iso20_ac_exiDocument doc;
init_iso20_ac_exiDocument(&doc);
CB_SET_USED(doc.AC_ChargeLoopReq);
convert(in, doc.AC_ChargeLoopReq);
return encode_iso20_ac_exiDocument(&out, &doc);
}
template <> size_t serialize(const AC_ChargeLoopRequest& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
// End conversion for serializing an ACChargeLoopRequest (EVside)
} // namespace iso15118::message_20

View File

@@ -0,0 +1,242 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Pionix GmbH and Contributors to EVerest
#include <iso15118/message/ac_charge_parameter_discovery.hpp>
#include <type_traits>
#include <iso15118/detail/variant_access.hpp>
#include <cbv2g/iso_20/iso20_AC_Decoder.h>
#include <cbv2g/iso_20/iso20_AC_Encoder.h>
namespace iso15118::message_20 {
using AC_ModeReq = datatypes::AC_CPDReqEnergyTransferMode;
using BPT_AC_ModeReq = datatypes::BPT_AC_CPDReqEnergyTransferMode;
using AC_ModeRes = datatypes::AC_CPDResEnergyTransferMode;
using BPT_AC_ModeRes = datatypes::BPT_AC_CPDResEnergyTransferMode;
// Begin conversion for deserializing an ACChargeParameterRequest (EVSEside)
template <typename InType> void convert(const InType& in, AC_ModeReq& out) {
convert(in.EVMaximumChargePower, out.max_charge_power);
CB2CPP_CONVERT_IF_USED(in.EVMaximumChargePower_L2, out.max_charge_power_L2);
CB2CPP_CONVERT_IF_USED(in.EVMaximumChargePower_L3, out.max_charge_power_L3);
convert(in.EVMinimumChargePower, out.min_charge_power);
CB2CPP_CONVERT_IF_USED(in.EVMinimumChargePower_L2, out.min_charge_power_L2);
CB2CPP_CONVERT_IF_USED(in.EVMinimumChargePower_L3, out.min_charge_power_L3);
}
template <> void convert(const struct iso20_ac_BPT_AC_CPDReqEnergyTransferModeType& in, BPT_AC_ModeReq& out) {
convert(in, static_cast<AC_ModeReq&>(out));
convert(in.EVMaximumDischargePower, out.max_discharge_power);
CB2CPP_CONVERT_IF_USED(in.EVMaximumDischargePower_L2, out.max_discharge_power_L2);
CB2CPP_CONVERT_IF_USED(in.EVMaximumDischargePower_L3, out.max_discharge_power_L3);
convert(in.EVMinimumDischargePower, out.min_discharge_power);
CB2CPP_CONVERT_IF_USED(in.EVMinimumDischargePower_L2, out.min_discharge_power_L2);
CB2CPP_CONVERT_IF_USED(in.EVMinimumDischargePower_L3, out.min_discharge_power_L3);
}
template <>
void convert(const struct iso20_ac_AC_ChargeParameterDiscoveryReqType& in, AC_ChargeParameterDiscoveryRequest& out) {
convert(in.Header, out.header);
if (in.AC_CPDReqEnergyTransferMode_isUsed) {
auto& mode_out = out.transfer_mode.emplace<AC_ModeReq>();
convert(in.AC_CPDReqEnergyTransferMode, mode_out);
} else if (in.BPT_AC_CPDReqEnergyTransferMode_isUsed) {
auto& mode_out = out.transfer_mode.emplace<BPT_AC_ModeReq>();
convert(in.BPT_AC_CPDReqEnergyTransferMode, mode_out);
} else {
// FIXME (aw): fail, should not happen!
}
}
// End conversion for deserializing an ACChargeParameterRequest (EVSEside)
// Begin conversion for deserializing an ACChargeParameterResponse (EVside)
template <typename InType> void convert(const InType& in, AC_ModeRes& out) {
convert(in.EVSEMaximumChargePower, out.max_charge_power);
CB2CPP_CONVERT_IF_USED(in.EVSEMaximumChargePower_L2, out.max_charge_power_L2);
CB2CPP_CONVERT_IF_USED(in.EVSEMaximumChargePower_L3, out.max_charge_power_L3);
convert(in.EVSEMinimumChargePower, out.min_charge_power);
CB2CPP_CONVERT_IF_USED(in.EVSEMinimumChargePower_L2, out.min_charge_power_L2);
CB2CPP_CONVERT_IF_USED(in.EVSEMinimumChargePower_L3, out.min_charge_power_L3);
convert(in.EVSENominalFrequency, out.nominal_frequency);
CB2CPP_CONVERT_IF_USED(in.MaximumPowerAsymmetry, out.max_power_asymmetry);
CB2CPP_CONVERT_IF_USED(in.EVSEPowerRampLimitation, out.power_ramp_limitation);
CB2CPP_CONVERT_IF_USED(in.EVSEPresentActivePower, out.present_active_power);
CB2CPP_CONVERT_IF_USED(in.EVSEPresentActivePower_L2, out.present_active_power_L2);
CB2CPP_CONVERT_IF_USED(in.EVSEPresentActivePower_L3, out.present_active_power_L3);
}
template <> void convert(const iso20_ac_BPT_AC_CPDResEnergyTransferModeType& in, BPT_AC_ModeRes& out) {
convert(in, static_cast<AC_ModeRes&>(out));
convert(in.EVSEMaximumDischargePower, out.max_discharge_power);
CB2CPP_CONVERT_IF_USED(in.EVSEMaximumDischargePower_L2, out.max_discharge_power_L2);
CB2CPP_CONVERT_IF_USED(in.EVSEMaximumDischargePower_L3, out.max_discharge_power_L3);
convert(in.EVSEMinimumDischargePower, out.min_discharge_power);
CB2CPP_CONVERT_IF_USED(in.EVSEMinimumDischargePower_L2, out.min_discharge_power_L2);
CB2CPP_CONVERT_IF_USED(in.EVSEMinimumDischargePower_L3, out.min_discharge_power_L3);
}
template <>
void convert(const struct iso20_ac_AC_ChargeParameterDiscoveryResType& in, AC_ChargeParameterDiscoveryResponse& out) {
convert(in.Header, out.header);
cb_convert_enum(in.ResponseCode, out.response_code);
if (in.AC_CPDResEnergyTransferMode_isUsed) {
auto& mode_out = out.transfer_mode.emplace<AC_ModeRes>();
convert(in.AC_CPDResEnergyTransferMode, mode_out);
} else if (in.BPT_AC_CPDResEnergyTransferMode_isUsed) {
auto& mode_out = out.transfer_mode.emplace<BPT_AC_ModeRes>();
convert(in.BPT_AC_CPDResEnergyTransferMode, mode_out);
} else {
// FIXME (RB): fail, should not happen!
}
}
// End conversion for deserializing an ACChargeParameterResponse (EVside)
// Begin conversion for serializing an ACChargeParameterResponse (EVSEside)
template <> void insert_type(VariantAccess& va, const struct iso20_ac_AC_ChargeParameterDiscoveryResType& in) {
va.insert_type<AC_ChargeParameterDiscoveryResponse>(in);
}
struct ModeResponseVisitor {
public:
ModeResponseVisitor(iso20_ac_AC_ChargeParameterDiscoveryResType& res_) : res(res_){};
void operator()(const AC_ModeRes& in) {
init_iso20_ac_AC_CPDResEnergyTransferModeType(&res.AC_CPDResEnergyTransferMode);
CB_SET_USED(res.AC_CPDResEnergyTransferMode);
convert_common(in, res.AC_CPDResEnergyTransferMode);
}
void operator()(const BPT_AC_ModeRes& in) {
init_iso20_ac_BPT_AC_CPDResEnergyTransferModeType(&res.BPT_AC_CPDResEnergyTransferMode);
CB_SET_USED(res.BPT_AC_CPDResEnergyTransferMode);
auto& out = res.BPT_AC_CPDResEnergyTransferMode;
convert_common(in, out);
convert(in.max_discharge_power, out.EVSEMaximumDischargePower);
CPP2CB_CONVERT_IF_USED(in.max_charge_power_L2, out.EVSEMaximumDischargePower_L2);
CPP2CB_CONVERT_IF_USED(in.max_charge_power_L3, out.EVSEMaximumDischargePower_L3);
convert(in.min_discharge_power, out.EVSEMinimumDischargePower);
CPP2CB_CONVERT_IF_USED(in.min_charge_power_L2, out.EVSEMinimumDischargePower_L2);
CPP2CB_CONVERT_IF_USED(in.min_charge_power_L3, out.EVSEMinimumDischargePower_L3);
}
template <typename ModeResTypeIn, typename ModeResTypeOut>
static void convert_common(const ModeResTypeIn& in, ModeResTypeOut& out) {
convert(in.max_charge_power, out.EVSEMaximumChargePower);
CPP2CB_CONVERT_IF_USED(in.max_charge_power_L2, out.EVSEMaximumChargePower_L2);
CPP2CB_CONVERT_IF_USED(in.max_charge_power_L3, out.EVSEMaximumChargePower_L3);
convert(in.min_charge_power, out.EVSEMinimumChargePower);
CPP2CB_CONVERT_IF_USED(in.min_charge_power_L2, out.EVSEMinimumChargePower_L2);
CPP2CB_CONVERT_IF_USED(in.min_charge_power_L3, out.EVSEMinimumChargePower_L3);
convert(in.nominal_frequency, out.EVSENominalFrequency);
CPP2CB_CONVERT_IF_USED(in.max_power_asymmetry, out.MaximumPowerAsymmetry);
CPP2CB_CONVERT_IF_USED(in.power_ramp_limitation, out.EVSEPowerRampLimitation);
CPP2CB_CONVERT_IF_USED(in.present_active_power, out.EVSEPresentActivePower);
CPP2CB_CONVERT_IF_USED(in.present_active_power_L2, out.EVSEPresentActivePower_L2);
CPP2CB_CONVERT_IF_USED(in.present_active_power_L3, out.EVSEPresentActivePower_L3);
}
private:
iso20_ac_AC_ChargeParameterDiscoveryResType& res;
};
template <>
void convert(const AC_ChargeParameterDiscoveryResponse& in, struct iso20_ac_AC_ChargeParameterDiscoveryResType& out) {
init_iso20_ac_AC_ChargeParameterDiscoveryResType(&out);
convert(in.header, out.Header);
cb_convert_enum(in.response_code, out.ResponseCode);
std::visit(ModeResponseVisitor(out), in.transfer_mode);
}
template <> int serialize_to_exi(const AC_ChargeParameterDiscoveryResponse& in, exi_bitstream_t& out) {
iso20_ac_exiDocument doc;
init_iso20_ac_exiDocument(&doc);
CB_SET_USED(doc.AC_ChargeParameterDiscoveryRes);
convert(in, doc.AC_ChargeParameterDiscoveryRes);
return encode_iso20_ac_exiDocument(&out, &doc);
}
template <> size_t serialize(const AC_ChargeParameterDiscoveryResponse& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
// End conversion for serializing an ACChargeParameterResponse (EVSEside)
// Begin conversion for serializing an ACChargeParameterRequest (EVside)
template <> void insert_type(VariantAccess& va, const struct iso20_ac_AC_ChargeParameterDiscoveryReqType& in) {
va.insert_type<AC_ChargeParameterDiscoveryRequest>(in);
}
struct ModeRequestVisitor {
public:
ModeRequestVisitor(iso20_ac_AC_ChargeParameterDiscoveryReqType& req_) : req(req_){};
void operator()(const AC_ModeReq& in) {
init_iso20_ac_AC_CPDReqEnergyTransferModeType(&req.AC_CPDReqEnergyTransferMode);
CB_SET_USED(req.AC_CPDReqEnergyTransferMode);
convert_common(in, req.AC_CPDReqEnergyTransferMode);
}
void operator()(const BPT_AC_ModeReq& in) {
init_iso20_ac_BPT_AC_CPDReqEnergyTransferModeType(&req.BPT_AC_CPDReqEnergyTransferMode);
CB_SET_USED(req.BPT_AC_CPDReqEnergyTransferMode);
auto& out = req.BPT_AC_CPDReqEnergyTransferMode;
convert_common(in, out);
convert(in.max_discharge_power, out.EVMaximumDischargePower);
CPP2CB_CONVERT_IF_USED(in.max_discharge_power_L2, out.EVMaximumDischargePower_L2);
CPP2CB_CONVERT_IF_USED(in.max_discharge_power_L3, out.EVMaximumDischargePower_L3);
convert(in.min_discharge_power, out.EVMinimumDischargePower);
CPP2CB_CONVERT_IF_USED(in.min_discharge_power_L2, out.EVMinimumDischargePower_L2);
CPP2CB_CONVERT_IF_USED(in.min_discharge_power_L3, out.EVMinimumDischargePower_L3);
}
template <typename ModeReqTypeIn, typename ModeReqTypeOut>
static void convert_common(const ModeReqTypeIn& in, ModeReqTypeOut& out) {
convert(in.max_charge_power, out.EVMaximumChargePower);
CPP2CB_CONVERT_IF_USED(in.max_charge_power_L2, out.EVMaximumChargePower_L2);
CPP2CB_CONVERT_IF_USED(in.max_charge_power_L3, out.EVMaximumChargePower_L3);
convert(in.min_charge_power, out.EVMinimumChargePower);
CPP2CB_CONVERT_IF_USED(in.min_charge_power_L2, out.EVMinimumChargePower_L2);
CPP2CB_CONVERT_IF_USED(in.min_charge_power_L3, out.EVMinimumChargePower_L3);
}
private:
iso20_ac_AC_ChargeParameterDiscoveryReqType& req;
};
template <>
void convert(const AC_ChargeParameterDiscoveryRequest& in, struct iso20_ac_AC_ChargeParameterDiscoveryReqType& out) {
init_iso20_ac_AC_ChargeParameterDiscoveryReqType(&out);
convert(in.header, out.Header);
std::visit(ModeRequestVisitor(out), in.transfer_mode);
}
template <> int serialize_to_exi(const AC_ChargeParameterDiscoveryRequest& in, exi_bitstream_t& out) {
iso20_ac_exiDocument doc;
init_iso20_ac_exiDocument(&doc);
CB_SET_USED(doc.AC_ChargeParameterDiscoveryReq);
convert(in, doc.AC_ChargeParameterDiscoveryReq);
return encode_iso20_ac_exiDocument(&out, &doc);
}
template <> size_t serialize(const AC_ChargeParameterDiscoveryRequest& in, const io::StreamOutputView& out) {
auto rv = serialize_helper(in, out);
return rv;
}
// End conversion for serializing an ACChargeParameterRequest (EVside)
} // namespace iso15118::message_20

View File

@@ -0,0 +1,96 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/message/authorization.hpp>
#include <type_traits>
#include <iso15118/detail/variant_access.hpp>
#include <cbv2g/iso_20/iso20_CommonMessages_Encoder.h>
namespace iso15118::message_20 {
template <> void convert(const struct iso20_AuthorizationReqType& in, AuthorizationRequest& out) {
convert(in.Header, out.header);
out.selected_authorization_service = static_cast<datatypes::Authorization>(in.SelectedAuthorizationService);
if (in.EIM_AReqAuthorizationMode_isUsed) {
out.authorization_mode.emplace<datatypes::EIM_ASReqAuthorizationMode>();
} else if (in.PnC_AReqAuthorizationMode_isUsed) {
auto& pnc_out = out.authorization_mode.emplace<datatypes::PnC_ASReqAuthorizationMode>();
pnc_out.id = CB2CPP_STRING(in.PnC_AReqAuthorizationMode.Id);
CB2CPP_BYTES(in.PnC_AReqAuthorizationMode.GenChallenge, pnc_out.gen_challenge);
// Todo(sl): Adding certificate
}
}
template <> void convert(const struct iso20_AuthorizationResType& in, AuthorizationResponse& out) {
cb_convert_enum(in.ResponseCode, out.response_code);
cb_convert_enum(in.EVSEProcessing, out.evse_processing);
convert(in.Header, out.header);
}
template <> void convert(const AuthorizationRequest& in, iso20_AuthorizationReqType& out) {
init_iso20_AuthorizationReqType(&out);
convert(in.header, out.Header);
cb_convert_enum(in.selected_authorization_service, out.SelectedAuthorizationService);
// Todo(rb): add pnc
out.EIM_AReqAuthorizationMode_isUsed = true;
}
template <> void convert(const AuthorizationResponse& in, iso20_AuthorizationResType& out) {
init_iso20_AuthorizationResType(&out);
out.ResponseCode = static_cast<iso20_responseCodeType>(in.response_code);
out.EVSEProcessing = static_cast<iso20_processingType>(in.evse_processing);
convert(in.header, out.Header);
}
template <> void insert_type(VariantAccess& va, const struct iso20_AuthorizationReqType& in) {
va.insert_type<AuthorizationRequest>(in);
};
template <> void insert_type(VariantAccess& va, const struct iso20_AuthorizationResType& in) {
va.insert_type<AuthorizationResponse>(in);
};
template <> int serialize_to_exi(const AuthorizationResponse& in, exi_bitstream_t& out) {
iso20_exiDocument doc;
init_iso20_exiDocument(&doc);
CB_SET_USED(doc.AuthorizationRes);
convert(in, doc.AuthorizationRes);
return encode_iso20_exiDocument(&out, &doc);
}
template <> int serialize_to_exi(const AuthorizationRequest& in, exi_bitstream_t& out) {
iso20_exiDocument doc;
init_iso20_exiDocument(&doc);
CB_SET_USED(doc.AuthorizationReq);
convert(in, doc.AuthorizationReq);
return encode_iso20_exiDocument(&out, &doc);
}
template <> size_t serialize(const AuthorizationResponse& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
template <> size_t serialize(const AuthorizationRequest& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
} // namespace iso15118::message_20

View File

@@ -0,0 +1,122 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/message/authorization_setup.hpp>
#include <type_traits>
#include <iso15118/detail/variant_access.hpp>
#include <cbv2g/iso_20/iso20_CommonMessages_Encoder.h>
#include <iso15118/detail/helper.hpp>
namespace iso15118::message_20 {
template <> void convert(const struct iso20_AuthorizationSetupReqType& in, AuthorizationSetupRequest& out) {
convert(in.Header, out.header);
}
template <> void convert(const struct iso20_AuthorizationSetupResType& in, AuthorizationSetupResponse& out) {
cb_convert_enum(in.ResponseCode, out.response_code);
out.authorization_services.clear();
for (uint8_t element = 0; element < in.AuthorizationServices.arrayLen; element++) {
cb_convert_enum(in.AuthorizationServices.array[element], out.authorization_services.emplace_back());
}
out.certificate_installation_service = in.CertificateInstallationService;
if (in.EIM_ASResAuthorizationMode_isUsed) {
out.authorization_mode = datatypes::EIM_ASResAuthorizationMode{};
} else if (in.PnC_ASResAuthorizationMode_isUsed) {
auto& pnc_out = out.authorization_mode.emplace<datatypes::PnC_ASResAuthorizationMode>();
CB2CPP_BYTES(in.PnC_ASResAuthorizationMode.GenChallenge, pnc_out.gen_challenge);
// TODO(sl): supported_providers missing
}
convert(in.Header, out.header);
}
template <> void convert(const AuthorizationSetupRequest& in, iso20_AuthorizationSetupReqType& out) {
init_iso20_AuthorizationSetupReqType(&out);
convert(in.header, out.Header);
}
struct AuthorizationModeVisitor {
AuthorizationModeVisitor(iso20_AuthorizationSetupResType& out_) : out(out_){};
void operator()([[maybe_unused]] const datatypes::EIM_ASResAuthorizationMode& in) {
CB_SET_USED(out.EIM_ASResAuthorizationMode);
init_iso20_EIM_ASResAuthorizationModeType(&out.EIM_ASResAuthorizationMode);
}
void operator()(const datatypes::PnC_ASResAuthorizationMode& in) {
CB_SET_USED(out.PnC_ASResAuthorizationMode);
init_iso20_PnC_ASResAuthorizationModeType(&out.PnC_ASResAuthorizationMode);
CPP2CB_BYTES(in.gen_challenge, out.PnC_ASResAuthorizationMode.GenChallenge);
// TODO(sl): supported_providers missing
}
private:
iso20_AuthorizationSetupResType& out;
};
template <> void convert(const AuthorizationSetupResponse& in, iso20_AuthorizationSetupResType& out) {
init_iso20_AuthorizationSetupResType(&out);
cb_convert_enum(in.response_code, out.ResponseCode);
out.AuthorizationServices.arrayLen = in.authorization_services.size();
uint8_t element = 0;
for (auto const& service : in.authorization_services) {
cb_convert_enum(service, out.AuthorizationServices.array[element++]);
}
out.CertificateInstallationService = in.certificate_installation_service;
std::visit(AuthorizationModeVisitor(out), in.authorization_mode);
convert(in.header, out.Header);
}
template <> void insert_type(VariantAccess& va, const struct iso20_AuthorizationSetupReqType& in) {
va.insert_type<AuthorizationSetupRequest>(in);
};
template <> void insert_type(VariantAccess& va, const struct iso20_AuthorizationSetupResType& in) {
va.insert_type<AuthorizationSetupResponse>(in);
};
template <> int serialize_to_exi(const AuthorizationSetupResponse& in, exi_bitstream_t& out) {
iso20_exiDocument doc;
init_iso20_exiDocument(&doc);
CB_SET_USED(doc.AuthorizationSetupRes);
convert(in, doc.AuthorizationSetupRes);
return encode_iso20_exiDocument(&out, &doc);
}
template <> int serialize_to_exi(const AuthorizationSetupRequest& in, exi_bitstream_t& out) {
iso20_exiDocument doc;
init_iso20_exiDocument(&doc);
CB_SET_USED(doc.AuthorizationSetupReq);
convert(in, doc.AuthorizationSetupReq);
return encode_iso20_exiDocument(&out, &doc);
}
template <> size_t serialize(const AuthorizationSetupResponse& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
template <> size_t serialize(const AuthorizationSetupRequest& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
} // namespace iso15118::message_20

View File

@@ -0,0 +1,210 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <cmath>
#include <iso15118/message/common_types.hpp>
#include <iso15118/detail/cb_exi.hpp>
#include <iso15118/message/variant.hpp>
#include <cbv2g/iso_20/iso20_AC_Datatypes.h>
#include <cbv2g/iso_20/iso20_CommonMessages_Datatypes.h>
#include <cbv2g/iso_20/iso20_DC_Datatypes.h>
namespace iso15118::message_20 {
template <typename cb_HeaderType> void convert(const cb_HeaderType& in, Header& out) {
std::copy(in.SessionID.bytes, in.SessionID.bytes + in.SessionID.bytesLen, out.session_id.begin());
out.timestamp = in.TimeStamp;
// Todo(sl): missing signature
}
template void convert(const struct iso20_MessageHeaderType& in, Header& out);
template void convert(const struct iso20_dc_MessageHeaderType& in, Header& out);
template void convert(const struct iso20_ac_MessageHeaderType& in, Header& out);
template <typename cb_HeaderType> void convert_header(const Header& in, cb_HeaderType& out) {
out.TimeStamp = in.timestamp;
std::copy(in.session_id.begin(), in.session_id.end(), out.SessionID.bytes);
// FIXME (aw): this should be fixed 8
out.SessionID.bytesLen = 8;
}
template <> void convert(const Header& in, iso20_MessageHeaderType& out) {
init_iso20_MessageHeaderType(&out);
convert_header(in, out);
}
template <> void convert(const Header& in, iso20_dc_MessageHeaderType& out) {
init_iso20_dc_MessageHeaderType(&out);
convert_header(in, out);
}
template <> void convert(const Header& in, iso20_ac_MessageHeaderType& out) {
init_iso20_ac_MessageHeaderType(&out);
convert_header(in, out);
}
template <typename cb_RationalNumberType>
void convert(const cb_RationalNumberType& in, datatypes::RationalNumber& out) {
out.exponent = in.Exponent;
out.value = in.Value;
}
template void convert(const struct iso20_ac_RationalNumberType& in, datatypes::RationalNumber& out);
template void convert(const struct iso20_dc_RationalNumberType& in, datatypes::RationalNumber& out);
template void convert(const struct iso20_RationalNumberType& in, datatypes::RationalNumber& out);
template <typename cb_RationalNumberType>
void convert(const datatypes::RationalNumber& in, cb_RationalNumberType& out) {
out.Exponent = in.exponent;
out.Value = in.value;
}
template void convert(const datatypes::RationalNumber& in, struct iso20_ac_RationalNumberType& out);
template void convert(const datatypes::RationalNumber& in, struct iso20_dc_RationalNumberType& out);
template void convert(const datatypes::RationalNumber& in, struct iso20_RationalNumberType& out);
template <> void convert(const datatypes::EvseStatus& in, struct iso20_dc_EVSEStatusType& out) {
out.NotificationMaxDelay = in.notification_max_delay;
cb_convert_enum(in.notification, out.EVSENotification);
}
template <> void convert(const struct iso20_dc_EVSEStatusType& in, datatypes::EvseStatus& out) {
cb_convert_enum(in.EVSENotification, out.notification);
out.notification_max_delay = in.NotificationMaxDelay;
}
template <> void convert(const datatypes::EvseStatus& in, struct iso20_ac_EVSEStatusType& out) {
out.NotificationMaxDelay = in.notification_max_delay;
cb_convert_enum(in.notification, out.EVSENotification);
}
template <> void convert(const struct iso20_ac_EVSEStatusType& in, datatypes::EvseStatus& out) {
cb_convert_enum(in.EVSENotification, out.notification);
out.notification_max_delay = in.NotificationMaxDelay;
}
template <typename cb_MeterInfoType> void convert_meterinfo(const datatypes::MeterInfo& in, cb_MeterInfoType& out) {
CPP2CB_STRING(in.meter_id, out.MeterID);
out.ChargedEnergyReadingWh = in.charged_energy_reading_wh;
CPP2CB_ASSIGN_IF_USED(in.bpt_discharged_energy_reading_wh, out.BPT_DischargedEnergyReadingWh);
CPP2CB_ASSIGN_IF_USED(in.capacitive_energy_reading_varh, out.CapacitiveEnergyReadingVARh);
CPP2CB_ASSIGN_IF_USED(in.bpt_inductive_energy_reading_varh, out.BPT_InductiveEnergyReadingVARh);
if (in.meter_signature) {
CPP2CB_BYTES(in.meter_signature.value(), out.MeterSignature);
CB_SET_USED(out.MeterSignature);
}
CPP2CB_ASSIGN_IF_USED(in.meter_status, out.MeterStatus);
CPP2CB_ASSIGN_IF_USED(in.meter_timestamp, out.MeterTimestamp);
}
template <> void convert(const datatypes::MeterInfo& in, iso20_dc_MeterInfoType& out) {
init_iso20_dc_MeterInfoType(&out);
convert_meterinfo(in, out);
}
template <> void convert(const datatypes::MeterInfo& in, iso20_ac_MeterInfoType& out) {
init_iso20_ac_MeterInfoType(&out);
convert_meterinfo(in, out);
}
template <typename cb_MeterInfoType>
void convert_meterinfo_inverse(const cb_MeterInfoType& in, datatypes::MeterInfo& out) {
out.meter_id = CB2CPP_STRING(in.MeterID);
out.charged_energy_reading_wh = in.ChargedEnergyReadingWh;
CB2CPP_ASSIGN_IF_USED(in.BPT_DischargedEnergyReadingWh, out.bpt_discharged_energy_reading_wh);
CB2CPP_ASSIGN_IF_USED(in.BPT_InductiveEnergyReadingVARh, out.bpt_inductive_energy_reading_varh);
CB2CPP_ASSIGN_IF_USED(in.CapacitiveEnergyReadingVARh, out.capacitive_energy_reading_varh);
CB2CPP_BYTES_IF_USED(in.MeterSignature, out.meter_signature);
CB2CPP_ASSIGN_IF_USED(in.MeterStatus, out.meter_status);
CB2CPP_ASSIGN_IF_USED(in.MeterTimestamp, out.meter_timestamp);
}
template <> void convert(const iso20_dc_MeterInfoType& in, datatypes::MeterInfo& out) {
convert_meterinfo_inverse(in, out);
}
template <> void convert(const iso20_ac_MeterInfoType& in, datatypes::MeterInfo& out) {
convert_meterinfo_inverse(in, out);
}
template <> void convert(const datatypes::EvseStatus& in, iso20_EVSEStatusType& out) {
out.NotificationMaxDelay = in.notification_max_delay;
cb_convert_enum(in.notification, out.EVSENotification);
}
template <> void convert(const struct iso20_EVSEStatusType& in, datatypes::EvseStatus& out) {
cb_convert_enum(in.EVSENotification, out.notification);
out.notification_max_delay = in.NotificationMaxDelay;
}
template <> void convert(const struct iso20_PowerScheduleEntryType& in, datatypes::PowerScheduleEntry& out) {
out.duration = in.Duration;
convert(in.Power, out.power);
CB2CPP_CONVERT_IF_USED(in.Power_L2, out.power_l2);
CB2CPP_CONVERT_IF_USED(in.Power_L3, out.power_l3);
}
namespace datatypes {
float from_RationalNumber(const RationalNumber& in) {
return in.value * pow(10, in.exponent);
}
RationalNumber from_float(float in) {
RationalNumber out;
if (in == 0.0) {
out.exponent = 0;
out.value = 0;
return out;
}
out.exponent = static_cast<int8_t>(floor(log10(fabs(in))));
out.exponent -= 3; // add 3 digits of precision
out.value = static_cast<int16_t>(in * pow(10, -out.exponent));
return out;
}
std::string from_Protocol(const Protocol& in) {
switch (in) {
case Protocol::Ftp:
return std::string("ftp");
case Protocol::Http:
return std::string("http");
case Protocol::Https:
return std::string("https");
}
return "";
}
std::string from_control_mode(const ControlMode& in) {
switch (in) {
case ControlMode::Scheduled:
return "Scheduled";
case ControlMode::Dynamic:
return "Dynamic";
}
return "";
}
std::string from_mobility_needs_mode(const MobilityNeedsMode& in) {
switch (in) {
case MobilityNeedsMode::ProvidedByEvcc:
return "ProvidedByEvcc";
case MobilityNeedsMode::ProvidedBySecc:
return "ProvidedBySecc";
}
return "";
}
} // namespace datatypes
} // namespace iso15118::message_20

View File

@@ -0,0 +1,74 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/message/dc_cable_check.hpp>
#include <type_traits>
#include <iso15118/detail/variant_access.hpp>
#include <cbv2g/iso_20/iso20_DC_Decoder.h>
#include <cbv2g/iso_20/iso20_DC_Encoder.h>
namespace iso15118::message_20 {
template <> void convert(const struct iso20_dc_DC_CableCheckReqType& in, DC_CableCheckRequest& out) {
convert(in.Header, out.header);
}
template <> void convert(const struct iso20_dc_DC_CableCheckResType& in, DC_CableCheckResponse& out) {
cb_convert_enum(in.ResponseCode, out.response_code);
cb_convert_enum(in.EVSEProcessing, out.processing);
convert(in.Header, out.header);
}
template <> void insert_type(VariantAccess& va, const struct iso20_dc_DC_CableCheckResType& in) {
va.insert_type<DC_CableCheckResponse>(in);
};
template <> void convert(const DC_CableCheckResponse& in, struct iso20_dc_DC_CableCheckResType& out) {
init_iso20_dc_DC_CableCheckResType(&out);
convert(in.header, out.Header);
cb_convert_enum(in.response_code, out.ResponseCode);
cb_convert_enum(in.processing, out.EVSEProcessing);
}
template <> int serialize_to_exi(const DC_CableCheckResponse& in, exi_bitstream_t& out) {
iso20_dc_exiDocument doc;
init_iso20_dc_exiDocument(&doc);
CB_SET_USED(doc.DC_CableCheckRes);
convert(in, doc.DC_CableCheckRes);
return encode_iso20_dc_exiDocument(&out, &doc);
}
template <> size_t serialize(const DC_CableCheckResponse& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
template <> void insert_type(VariantAccess& va, const struct iso20_dc_DC_CableCheckReqType& in) {
va.insert_type<DC_CableCheckRequest>(in);
}
template <> void convert(const DC_CableCheckRequest& in, iso20_dc_DC_CableCheckReqType& out) {
init_iso20_dc_DC_CableCheckReqType(&out);
convert(in.header, out.Header);
}
template <> int serialize_to_exi(const DC_CableCheckRequest& in, exi_bitstream_t& out) {
iso20_dc_exiDocument doc;
init_iso20_dc_exiDocument(&doc);
CB_SET_USED(doc.DC_CableCheckReq);
convert(in, doc.DC_CableCheckReq);
return encode_iso20_dc_exiDocument(&out, &doc);
}
template <> size_t serialize(const DC_CableCheckRequest& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
} // namespace iso15118::message_20

View File

@@ -0,0 +1,515 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/message/dc_charge_loop.hpp>
#include <type_traits>
#include <iso15118/detail/variant_access.hpp>
#include <cbv2g/iso_20/iso20_CommonMessages_Decoder.h>
#include <cbv2g/iso_20/iso20_CommonMessages_Encoder.h>
#include <cbv2g/iso_20/iso20_DC_Decoder.h>
#include <cbv2g/iso_20/iso20_DC_Encoder.h>
namespace iso15118::message_20 {
// Begin DC_ChargeLoopRequest Deserialization (EVSEside)
template <> void convert(const struct iso20_dc_DisplayParametersType& in, datatypes::DisplayParameters& out) {
CB2CPP_ASSIGN_IF_USED(in.PresentSOC, out.present_soc);
CB2CPP_ASSIGN_IF_USED(in.MinimumSOC, out.min_soc);
CB2CPP_ASSIGN_IF_USED(in.TargetSOC, out.target_soc);
CB2CPP_ASSIGN_IF_USED(in.MaximumSOC, out.max_soc);
CB2CPP_ASSIGN_IF_USED(in.RemainingTimeToMinimumSOC, out.remaining_time_to_min_soc);
CB2CPP_ASSIGN_IF_USED(in.RemainingTimeToTargetSOC, out.remaining_time_to_target_soc);
CB2CPP_ASSIGN_IF_USED(in.RemainingTimeToMaximumSOC, out.remaining_time_to_max_soc);
CB2CPP_ASSIGN_IF_USED(in.ChargingComplete, out.charging_complete);
CB2CPP_CONVERT_IF_USED(in.BatteryEnergyCapacity, out.battery_energy_capacity);
CB2CPP_ASSIGN_IF_USED(in.InletHot, out.inlet_hot);
}
// FIXME (aw): this should go to common.cpp
template <typename InType> void convert(const InType& in, datatypes::Scheduled_CLReqControlMode& out) {
CB2CPP_CONVERT_IF_USED(in.EVTargetEnergyRequest, out.target_energy_request);
CB2CPP_CONVERT_IF_USED(in.EVMaximumEnergyRequest, out.max_energy_request);
CB2CPP_CONVERT_IF_USED(in.EVMinimumEnergyRequest, out.min_energy_request);
}
template <typename InType> void convert(const InType& in, datatypes::Scheduled_DC_CLReqControlMode& out) {
static_assert(std::is_same_v<InType, iso20_dc_Scheduled_DC_CLReqControlModeType> or
std::is_same_v<InType, iso20_dc_BPT_Scheduled_DC_CLReqControlModeType>);
convert(in, static_cast<datatypes::Scheduled_CLReqControlMode&>(out));
convert(in.EVTargetCurrent, out.target_current);
convert(in.EVTargetVoltage, out.target_voltage);
CB2CPP_CONVERT_IF_USED(in.EVMaximumChargePower, out.max_charge_power);
CB2CPP_CONVERT_IF_USED(in.EVMinimumChargePower, out.min_charge_power);
CB2CPP_CONVERT_IF_USED(in.EVMaximumChargeCurrent, out.max_charge_current);
CB2CPP_CONVERT_IF_USED(in.EVMaximumVoltage, out.max_voltage);
CB2CPP_CONVERT_IF_USED(in.EVMinimumVoltage, out.min_voltage);
}
template <>
void convert(const struct iso20_dc_BPT_Scheduled_DC_CLReqControlModeType& in,
datatypes::BPT_Scheduled_DC_CLReqControlMode& out) {
convert(in, static_cast<datatypes::Scheduled_DC_CLReqControlMode&>(out));
CB2CPP_CONVERT_IF_USED(in.EVMaximumDischargePower, out.max_discharge_power);
CB2CPP_CONVERT_IF_USED(in.EVMinimumDischargePower, out.min_discharge_power);
CB2CPP_CONVERT_IF_USED(in.EVMaximumDischargeCurrent, out.max_discharge_current);
}
// FIXME (aw): this should go to common.cpp
template <typename InType> void convert(const InType& in, datatypes::Dynamic_CLReqControlMode& out) {
CB2CPP_ASSIGN_IF_USED(in.DepartureTime, out.departure_time);
convert(in.EVTargetEnergyRequest, out.target_energy_request);
convert(in.EVMaximumEnergyRequest, out.max_energy_request);
convert(in.EVMinimumEnergyRequest, out.min_energy_request);
}
template <typename InType> void convert(const InType& in, datatypes::Dynamic_DC_CLReqControlMode& out) {
static_assert(std::is_same_v<InType, iso20_dc_Dynamic_DC_CLReqControlModeType> or
std::is_same_v<InType, iso20_dc_BPT_Dynamic_DC_CLReqControlModeType>);
convert(in, static_cast<datatypes::Dynamic_CLReqControlMode&>(out));
convert(in.EVMaximumChargePower, out.max_charge_power);
convert(in.EVMinimumChargePower, out.min_charge_power);
convert(in.EVMaximumChargeCurrent, out.max_charge_current);
convert(in.EVMaximumVoltage, out.max_voltage);
convert(in.EVMinimumVoltage, out.min_voltage);
}
template <>
void convert(const struct iso20_dc_BPT_Dynamic_DC_CLReqControlModeType& in,
datatypes::BPT_Dynamic_DC_CLReqControlMode& out) {
convert(in, static_cast<datatypes::Dynamic_DC_CLReqControlMode&>(out));
convert(in.EVMaximumDischargePower, out.max_discharge_power);
convert(in.EVMinimumDischargePower, out.min_discharge_power);
convert(in.EVMaximumDischargeCurrent, out.max_discharge_current);
CB2CPP_CONVERT_IF_USED(in.EVMaximumV2XEnergyRequest, out.max_v2x_energy_request);
CB2CPP_CONVERT_IF_USED(in.EVMinimumV2XEnergyRequest, out.min_v2x_energy_request);
}
template <> void convert(const struct iso20_dc_DC_ChargeLoopReqType& in, DC_ChargeLoopRequest& out) {
convert(in.Header, out.header);
CB2CPP_CONVERT_IF_USED(in.DisplayParameters, out.display_parameters);
out.meter_info_requested = in.MeterInfoRequested;
convert(in.EVPresentVoltage, out.present_voltage);
if (in.Scheduled_DC_CLReqControlMode_isUsed) {
convert(in.Scheduled_DC_CLReqControlMode, out.control_mode.emplace<datatypes::Scheduled_DC_CLReqControlMode>());
} else if (in.BPT_Scheduled_DC_CLReqControlMode_isUsed) {
convert(in.BPT_Scheduled_DC_CLReqControlMode,
out.control_mode.emplace<datatypes::BPT_Scheduled_DC_CLReqControlMode>());
} else if (in.Dynamic_DC_CLReqControlMode_isUsed) {
convert(in.Dynamic_DC_CLReqControlMode, out.control_mode.emplace<datatypes::Dynamic_DC_CLReqControlMode>());
} else if (in.BPT_Dynamic_DC_CLReqControlMode_isUsed) {
convert(in.BPT_Dynamic_DC_CLReqControlMode,
out.control_mode.emplace<datatypes::BPT_Dynamic_DC_CLReqControlMode>());
} else {
// should not happen
assert(false);
}
}
// End DC_ChargeLoopRequest Deserialization (EVSEside)
// Begin DC_ChargeLoopResponse Deserialization (EVside)
template <typename InType> void convert(const InType& in, datatypes::Scheduled_DC_CLResControlMode& out) {
CB2CPP_CONVERT_IF_USED(in.EVSEMaximumChargePower, out.max_charge_power);
CB2CPP_CONVERT_IF_USED(in.EVSEMinimumChargePower, out.min_charge_power);
CB2CPP_CONVERT_IF_USED(in.EVSEMaximumChargeCurrent, out.max_charge_current);
CB2CPP_CONVERT_IF_USED(in.EVSEMaximumVoltage, out.max_voltage);
}
template <>
void convert(const struct iso20_dc_BPT_Scheduled_DC_CLResControlModeType& in,
datatypes::BPT_Scheduled_DC_CLResControlMode& out) {
convert(in, static_cast<datatypes::Scheduled_DC_CLResControlMode&>(out));
CB2CPP_CONVERT_IF_USED(in.EVSEMaximumDischargePower, out.max_discharge_power);
CB2CPP_CONVERT_IF_USED(in.EVSEMinimumDischargePower, out.min_discharge_power);
CB2CPP_CONVERT_IF_USED(in.EVSEMaximumDischargeCurrent, out.max_discharge_current);
CB2CPP_CONVERT_IF_USED(in.EVSEMinimumVoltage, out.min_voltage);
}
// FIXME (rb): this should go to common.cpp
template <typename InType> void convert(const InType& in, datatypes::Dynamic_CLResControlMode& out) {
CB2CPP_ASSIGN_IF_USED(in.DepartureTime, out.departure_time);
CB2CPP_ASSIGN_IF_USED(in.MinimumSOC, out.minimum_soc);
CB2CPP_ASSIGN_IF_USED(in.TargetSOC, out.target_soc);
CB2CPP_ASSIGN_IF_USED(in.AckMaxDelay, out.ack_max_delay);
}
template <typename InType> void convert(const InType& in, datatypes::Dynamic_DC_CLResControlMode& out) {
static_assert(std::is_same_v<InType, iso20_dc_Dynamic_DC_CLResControlModeType> or
std::is_same_v<InType, iso20_dc_BPT_Dynamic_DC_CLResControlModeType>);
convert(in, static_cast<datatypes::Dynamic_CLResControlMode&>(out));
convert(in.EVSEMaximumChargePower, out.max_charge_power);
convert(in.EVSEMinimumChargePower, out.min_charge_power);
convert(in.EVSEMaximumChargeCurrent, out.max_charge_current);
convert(in.EVSEMaximumVoltage, out.max_voltage);
}
template <>
void convert(const struct iso20_dc_BPT_Dynamic_DC_CLResControlModeType& in,
datatypes::BPT_Dynamic_DC_CLResControlMode& out) {
convert(in, static_cast<datatypes::Dynamic_DC_CLResControlMode&>(out));
convert(in.EVSEMaximumDischargePower, out.max_discharge_power);
convert(in.EVSEMinimumDischargePower, out.min_discharge_power);
convert(in.EVSEMaximumDischargeCurrent, out.max_discharge_current);
convert(in.EVSEMinimumVoltage, out.min_voltage);
}
template <> void convert(const struct iso20_dc_DetailedCostType& in, datatypes::DetailedCost& out) {
convert(in.Amount, out.amount);
convert(in.CostPerUnit, out.cost_per_unit);
}
template <> void convert(const struct iso20_dc_DetailedTaxType& in, datatypes::DetailedTax& out) {
out.tax_rule_id = in.TaxRuleID;
convert(in.Amount, out.amount);
}
template <> void convert(const struct iso20_dc_ReceiptType& in, datatypes::Receipt& out) {
out.time_anchor = in.TimeAnchor;
CB2CPP_CONVERT_IF_USED(in.EnergyCosts, out.energy_costs);
CB2CPP_CONVERT_IF_USED(in.OccupancyCosts, out.occupancy_costs);
CB2CPP_CONVERT_IF_USED(in.AdditionalServicesCosts, out.additional_service_costs);
CB2CPP_CONVERT_IF_USED(in.OverstayCosts, out.overstay_costs);
// todo (rb): TaxCosts should be optional as far as I can tell.
if (in.TaxCosts.arrayLen > 10) {
throw std::runtime_error("tax costs array is too large");
}
out.tax_costs.clear();
for (std::size_t i = 0; i < in.TaxCosts.arrayLen; ++i) {
convert(in.TaxCosts.array[i], out.tax_costs.emplace_back());
}
}
template <> void convert(const struct iso20_dc_DC_ChargeLoopResType& in, DC_ChargeLoopResponse& out) {
cb_convert_enum(in.ResponseCode, out.response_code);
convert(in.EVSEPresentVoltage, out.present_voltage);
convert(in.EVSEPresentCurrent, out.present_current);
out.current_limit_achieved = in.EVSECurrentLimitAchieved;
out.power_limit_achieved = in.EVSEPowerLimitAchieved;
out.voltage_limit_achieved = in.EVSEVoltageLimitAchieved;
CB2CPP_CONVERT_IF_USED(in.EVSEStatus, out.status);
CB2CPP_CONVERT_IF_USED(in.MeterInfo, out.meter_info);
CB2CPP_CONVERT_IF_USED(in.Receipt, out.receipt);
convert(in.Header, out.header);
if (in.Scheduled_DC_CLResControlMode_isUsed) {
convert(in.Scheduled_DC_CLResControlMode, out.control_mode.emplace<datatypes::Scheduled_DC_CLResControlMode>());
} else if (in.BPT_Scheduled_DC_CLResControlMode_isUsed) {
convert(in.BPT_Scheduled_DC_CLResControlMode,
out.control_mode.emplace<datatypes::BPT_Scheduled_DC_CLResControlMode>());
} else if (in.Dynamic_DC_CLResControlMode_isUsed) {
convert(in.Dynamic_DC_CLResControlMode, out.control_mode.emplace<datatypes::Dynamic_DC_CLResControlMode>());
} else if (in.BPT_Dynamic_DC_CLResControlMode_isUsed) {
convert(in.BPT_Dynamic_DC_CLResControlMode,
out.control_mode.emplace<datatypes::BPT_Dynamic_DC_CLResControlMode>());
} else {
// should not happen
assert(false);
}
}
// End DC_ChargeLoopResponse Deserialization (EVside)
// Begin DC_ChargeLoopResponse Serialization (EVSEside)
template <> void insert_type(VariantAccess& va, const struct iso20_dc_DC_ChargeLoopResType& in) {
va.insert_type<DC_ChargeLoopResponse>(in);
};
template <> void convert(const datatypes::DetailedCost& in, struct iso20_dc_DetailedCostType& out) {
init_iso20_dc_DetailedCostType(&out);
convert(in.amount, out.Amount);
convert(in.cost_per_unit, out.CostPerUnit);
}
template <> void convert(const datatypes::DetailedTax& in, struct iso20_dc_DetailedTaxType& out) {
init_iso20_dc_DetailedTaxType(&out);
out.TaxRuleID = in.tax_rule_id;
convert(in.amount, out.Amount);
}
template <> void convert(const datatypes::Receipt& in, struct iso20_dc_ReceiptType& out) {
init_iso20_dc_ReceiptType(&out);
out.TimeAnchor = in.time_anchor;
CPP2CB_CONVERT_IF_USED(in.energy_costs, out.EnergyCosts);
CPP2CB_CONVERT_IF_USED(in.occupancy_costs, out.OccupancyCosts);
CPP2CB_CONVERT_IF_USED(in.additional_service_costs, out.AdditionalServicesCosts);
CPP2CB_CONVERT_IF_USED(in.overstay_costs, out.OverstayCosts);
if (sizeof(out.TaxCosts.array) < in.tax_costs.size()) {
throw std::runtime_error("tax costs array is too large");
}
for (std::size_t i = 0; i < in.tax_costs.size(); ++i) {
convert(in.tax_costs[i], out.TaxCosts.array[i]);
}
out.TaxCosts.arrayLen = in.tax_costs.size();
}
template <typename OutType> void convert(const datatypes::Scheduled_DC_CLResControlMode& in, OutType& out) {
CPP2CB_CONVERT_IF_USED(in.max_charge_power, out.EVSEMaximumChargePower);
CPP2CB_CONVERT_IF_USED(in.min_charge_power, out.EVSEMinimumChargePower);
CPP2CB_CONVERT_IF_USED(in.max_charge_current, out.EVSEMaximumChargeCurrent);
CPP2CB_CONVERT_IF_USED(in.max_voltage, out.EVSEMaximumVoltage);
}
template <typename OutType> void convert(const datatypes::Dynamic_CLResControlMode& in, OutType& out) {
CPP2CB_ASSIGN_IF_USED(in.departure_time, out.DepartureTime);
CPP2CB_ASSIGN_IF_USED(in.minimum_soc, out.MinimumSOC);
CPP2CB_ASSIGN_IF_USED(in.target_soc, out.TargetSOC);
CPP2CB_ASSIGN_IF_USED(in.ack_max_delay, out.AckMaxDelay);
}
template <>
void convert(const datatypes::BPT_Scheduled_DC_CLResControlMode& in,
struct iso20_dc_BPT_Scheduled_DC_CLResControlModeType& out) {
convert(static_cast<const datatypes::Scheduled_DC_CLResControlMode&>(in), out);
CPP2CB_CONVERT_IF_USED(in.max_discharge_power, out.EVSEMaximumDischargePower);
CPP2CB_CONVERT_IF_USED(in.min_discharge_power, out.EVSEMinimumDischargePower);
CPP2CB_CONVERT_IF_USED(in.max_discharge_current, out.EVSEMaximumDischargePower);
CPP2CB_CONVERT_IF_USED(in.min_voltage, out.EVSEMinimumVoltage);
}
template <typename OutType> void convert(const datatypes::Dynamic_DC_CLResControlMode& in, OutType& out) {
convert(static_cast<const datatypes::Dynamic_CLResControlMode&>(in), out);
convert(in.max_charge_power, out.EVSEMaximumChargePower);
convert(in.min_charge_power, out.EVSEMinimumChargePower);
convert(in.max_charge_current, out.EVSEMaximumChargeCurrent);
convert(in.max_voltage, out.EVSEMaximumVoltage);
}
template <>
void convert(const datatypes::BPT_Dynamic_DC_CLResControlMode& in,
struct iso20_dc_BPT_Dynamic_DC_CLResControlModeType& out) {
convert(static_cast<const datatypes::Dynamic_DC_CLResControlMode&>(in), out);
convert(in.max_discharge_power, out.EVSEMaximumDischargePower);
convert(in.min_discharge_power, out.EVSEMinimumDischargePower);
convert(in.max_discharge_current, out.EVSEMaximumDischargeCurrent);
convert(in.min_voltage, out.EVSEMinimumVoltage);
}
struct ControlModeVisitor {
using ScheduledCM = datatypes::Scheduled_DC_CLResControlMode;
using BPT_ScheduledCM = datatypes::BPT_Scheduled_DC_CLResControlMode;
using DynamicCM = datatypes::Dynamic_DC_CLResControlMode;
using BPT_DynamicCM = datatypes::BPT_Dynamic_DC_CLResControlMode;
ControlModeVisitor(iso20_dc_DC_ChargeLoopResType& res_) : res(res_){};
void operator()(const ScheduledCM& in) {
auto& out = res.Scheduled_DC_CLResControlMode;
init_iso20_dc_Scheduled_DC_CLResControlModeType(&out);
convert(in, out);
CB_SET_USED(res.Scheduled_DC_CLResControlMode);
}
void operator()(const BPT_ScheduledCM& in) {
auto& out = res.BPT_Scheduled_DC_CLResControlMode;
init_iso20_dc_BPT_Scheduled_DC_CLResControlModeType(&out);
convert(in, out);
CB_SET_USED(res.BPT_Scheduled_DC_CLResControlMode);
}
void operator()(const DynamicCM& in) {
auto& out = res.Dynamic_DC_CLResControlMode;
init_iso20_dc_Dynamic_DC_CLResControlModeType(&out);
convert(in, out);
CB_SET_USED(res.Dynamic_DC_CLResControlMode);
}
void operator()(const BPT_DynamicCM& in) {
auto& out = res.BPT_Dynamic_DC_CLResControlMode;
init_iso20_dc_BPT_Dynamic_DC_CLResControlModeType(&out);
convert(in, out);
CB_SET_USED(res.BPT_Dynamic_DC_CLResControlMode);
}
private:
iso20_dc_DC_ChargeLoopResType& res;
};
template <> void convert(const DC_ChargeLoopResponse& in, struct iso20_dc_DC_ChargeLoopResType& out) {
init_iso20_dc_DC_ChargeLoopResType(&out);
convert(in.header, out.Header);
cb_convert_enum(in.response_code, out.ResponseCode);
CPP2CB_CONVERT_IF_USED(in.status, out.EVSEStatus);
CPP2CB_CONVERT_IF_USED(in.meter_info, out.MeterInfo);
CPP2CB_CONVERT_IF_USED(in.receipt, out.Receipt);
convert(in.present_current, out.EVSEPresentCurrent);
convert(in.present_voltage, out.EVSEPresentVoltage);
out.EVSEPowerLimitAchieved = in.power_limit_achieved;
out.EVSECurrentLimitAchieved = in.current_limit_achieved;
out.EVSEVoltageLimitAchieved = in.voltage_limit_achieved;
std::visit(ControlModeVisitor(out), in.control_mode);
}
template <> int serialize_to_exi(const DC_ChargeLoopResponse& in, exi_bitstream_t& out) {
iso20_dc_exiDocument doc;
init_iso20_dc_exiDocument(&doc);
CB_SET_USED(doc.DC_ChargeLoopRes);
convert(in, doc.DC_ChargeLoopRes);
return encode_iso20_dc_exiDocument(&out, &doc);
}
template <> size_t serialize(const DC_ChargeLoopResponse& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
// End DC_ChargeLoopResponse Serialization (EVSEside)
// Begin DC_ChargeLoopRequest Serialization (EVside)
template <> void insert_type(VariantAccess& va, const struct iso20_dc_DC_ChargeLoopReqType& in) {
va.insert_type<DC_ChargeLoopRequest>(in);
}
template <> void convert(const datatypes::DisplayParameters& in, struct iso20_dc_DisplayParametersType& out) {
init_iso20_dc_DisplayParametersType(&out);
CPP2CB_ASSIGN_IF_USED(in.present_soc, out.PresentSOC);
CPP2CB_ASSIGN_IF_USED(in.min_soc, out.MinimumSOC);
CPP2CB_ASSIGN_IF_USED(in.target_soc, out.TargetSOC);
CPP2CB_ASSIGN_IF_USED(in.max_soc, out.MaximumSOC);
CPP2CB_ASSIGN_IF_USED(in.remaining_time_to_min_soc, out.RemainingTimeToMinimumSOC);
CPP2CB_ASSIGN_IF_USED(in.remaining_time_to_target_soc, out.RemainingTimeToTargetSOC);
CPP2CB_ASSIGN_IF_USED(in.remaining_time_to_max_soc, out.RemainingTimeToMaximumSOC);
CPP2CB_ASSIGN_IF_USED(in.charging_complete, out.ChargingComplete);
CPP2CB_CONVERT_IF_USED(in.battery_energy_capacity, out.BatteryEnergyCapacity);
CPP2CB_ASSIGN_IF_USED(in.inlet_hot, out.InletHot);
}
template <typename OutType> void convert(const datatypes::Scheduled_CLReqControlMode& in, OutType& out) {
CPP2CB_CONVERT_IF_USED(in.target_energy_request, out.EVTargetEnergyRequest);
CPP2CB_CONVERT_IF_USED(in.max_energy_request, out.EVMaximumEnergyRequest);
CPP2CB_CONVERT_IF_USED(in.min_energy_request, out.EVMinimumEnergyRequest);
}
template <typename OutType> void convert(const datatypes::Scheduled_DC_CLReqControlMode& in, OutType& out) {
static_assert(std::is_same_v<OutType, iso20_dc_Scheduled_DC_CLReqControlModeType> or
std::is_same_v<OutType, iso20_dc_BPT_Scheduled_DC_CLReqControlModeType>);
convert(static_cast<const datatypes::Scheduled_CLReqControlMode&>(in), out);
CPP2CB_CONVERT_IF_USED(in.max_charge_current, out.EVMaximumChargeCurrent);
CPP2CB_CONVERT_IF_USED(in.max_charge_power, out.EVMaximumChargePower);
CPP2CB_CONVERT_IF_USED(in.min_charge_power, out.EVMinimumChargePower);
convert(in.target_current, out.EVTargetCurrent);
convert(in.target_voltage, out.EVTargetVoltage);
CPP2CB_CONVERT_IF_USED(in.max_voltage, out.EVMaximumVoltage);
CPP2CB_CONVERT_IF_USED(in.min_voltage, out.EVMinimumVoltage);
}
template <>
void convert(const datatypes::BPT_Scheduled_DC_CLReqControlMode& in,
struct iso20_dc_BPT_Scheduled_DC_CLReqControlModeType& out) {
convert(static_cast<const datatypes::Scheduled_DC_CLReqControlMode&>(in), out);
CPP2CB_CONVERT_IF_USED(in.max_discharge_power, out.EVMaximumDischargePower);
CPP2CB_CONVERT_IF_USED(in.min_discharge_power, out.EVMinimumDischargePower);
CPP2CB_CONVERT_IF_USED(in.max_discharge_current, out.EVMaximumDischargeCurrent);
}
template <typename OutType> void convert(const datatypes::Dynamic_CLReqControlMode& in, OutType& out) {
convert(in.target_energy_request, out.EVTargetEnergyRequest);
convert(in.max_energy_request, out.EVMaximumEnergyRequest);
convert(in.min_energy_request, out.EVMinimumEnergyRequest);
CPP2CB_ASSIGN_IF_USED(in.departure_time, out.DepartureTime);
}
template <typename OutType> void convert(const datatypes::Dynamic_DC_CLReqControlMode& in, OutType& out) {
static_assert(std::is_same_v<OutType, iso20_dc_Dynamic_DC_CLReqControlModeType> or
std::is_same_v<OutType, iso20_dc_BPT_Dynamic_DC_CLReqControlModeType>);
convert(static_cast<const datatypes::Dynamic_CLReqControlMode&>(in), out);
convert(in.max_charge_power, out.EVMaximumChargePower);
convert(in.min_charge_power, out.EVMinimumChargePower);
convert(in.max_charge_current, out.EVMaximumChargeCurrent);
convert(in.max_voltage, out.EVMaximumVoltage);
convert(in.min_voltage, out.EVMinimumVoltage);
}
template <>
void convert(const datatypes::BPT_Dynamic_DC_CLReqControlMode& in,
struct iso20_dc_BPT_Dynamic_DC_CLReqControlModeType& out) {
convert(static_cast<const datatypes::Dynamic_DC_CLReqControlMode&>(in), out);
convert(in.max_discharge_power, out.EVMaximumDischargePower);
convert(in.min_discharge_power, out.EVMinimumDischargePower);
convert(in.max_discharge_current, out.EVMaximumDischargeCurrent);
CPP2CB_CONVERT_IF_USED(in.max_v2x_energy_request, out.EVMaximumV2XEnergyRequest);
CPP2CB_CONVERT_IF_USED(in.min_v2x_energy_request, out.EVMinimumV2XEnergyRequest);
}
struct RequestControlModeVisitor {
using ScheduledCM = datatypes::Scheduled_DC_CLReqControlMode;
using BPT_ScheduledCM = datatypes::BPT_Scheduled_DC_CLReqControlMode;
using DynamicCM = datatypes::Dynamic_DC_CLReqControlMode;
using BPT_DynamicCM = datatypes::BPT_Dynamic_DC_CLReqControlMode;
RequestControlModeVisitor(iso20_dc_DC_ChargeLoopReqType& req_) : req(req_){};
void operator()(const ScheduledCM& in) {
auto& out = req.Scheduled_DC_CLReqControlMode;
init_iso20_dc_Scheduled_DC_CLReqControlModeType(&out);
convert(in, out);
CB_SET_USED(req.Scheduled_DC_CLReqControlMode);
}
void operator()(const BPT_ScheduledCM& in) {
auto& out = req.BPT_Scheduled_DC_CLReqControlMode;
init_iso20_dc_BPT_Scheduled_DC_CLReqControlModeType(&out);
convert(in, out);
CB_SET_USED(req.BPT_Scheduled_DC_CLReqControlMode);
}
void operator()(const DynamicCM& in) {
auto& out = req.Dynamic_DC_CLReqControlMode;
init_iso20_dc_Dynamic_DC_CLReqControlModeType(&out);
convert(in, out);
CB_SET_USED(req.Dynamic_DC_CLReqControlMode);
}
void operator()(const BPT_DynamicCM& in) {
auto& out = req.BPT_Dynamic_DC_CLReqControlMode;
init_iso20_dc_BPT_Dynamic_DC_CLReqControlModeType(&out);
convert(in, out);
CB_SET_USED(req.BPT_Dynamic_DC_CLReqControlMode);
}
private:
iso20_dc_DC_ChargeLoopReqType& req;
};
template <> void convert(const DC_ChargeLoopRequest& in, iso20_dc_DC_ChargeLoopReqType& out) {
init_iso20_dc_DC_ChargeLoopReqType(&out);
out.MeterInfoRequested = in.meter_info_requested;
convert(in.present_voltage, out.EVPresentVoltage);
CPP2CB_CONVERT_IF_USED(in.display_parameters, out.DisplayParameters);
std::visit(RequestControlModeVisitor{out}, in.control_mode);
convert(in.header, out.Header);
}
template <> int serialize_to_exi(const DC_ChargeLoopRequest& in, exi_bitstream_t& out) {
iso20_dc_exiDocument doc;
init_iso20_dc_exiDocument(&doc);
CB_SET_USED(doc.DC_ChargeLoopReq);
convert(in, doc.DC_ChargeLoopReq);
return encode_iso20_dc_exiDocument(&out, &doc);
}
template <> size_t serialize(const DC_ChargeLoopRequest& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
// End DC_ChargeLoopRequest Serialization (EVside)
} // namespace iso15118::message_20

View File

@@ -0,0 +1,220 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/message/dc_charge_parameter_discovery.hpp>
#include <type_traits>
#include <iso15118/detail/variant_access.hpp>
#include <cbv2g/iso_20/iso20_DC_Decoder.h>
#include <cbv2g/iso_20/iso20_DC_Encoder.h>
namespace iso15118::message_20 {
using DC_ModeReq = datatypes::DC_CPDReqEnergyTransferMode;
using BPT_DC_ModeReq = datatypes::BPT_DC_CPDReqEnergyTransferMode;
using DC_ModeRes = datatypes::DC_CPDResEnergyTransferMode;
using BPT_DC_ModeRes = datatypes::BPT_DC_CPDResEnergyTransferMode;
// Begin conversion for deserializing a DCChargeParameterRequest (EVSEside)
template <typename InType> void convert(const InType& in, DC_ModeReq& out) {
convert(in.EVMaximumChargePower, out.max_charge_power);
convert(in.EVMinimumChargePower, out.min_charge_power);
convert(in.EVMaximumChargeCurrent, out.max_charge_current);
convert(in.EVMinimumChargeCurrent, out.min_charge_current);
convert(in.EVMaximumVoltage, out.max_voltage);
convert(in.EVMinimumVoltage, out.min_voltage);
CB2CPP_ASSIGN_IF_USED(in.TargetSOC, out.target_soc);
}
template <> void convert(const struct iso20_dc_BPT_DC_CPDReqEnergyTransferModeType& in, BPT_DC_ModeReq& out) {
convert(in, static_cast<DC_ModeReq&>(out));
convert(in.EVMaximumDischargePower, out.max_discharge_power);
convert(in.EVMinimumDischargePower, out.min_discharge_power);
convert(in.EVMaximumDischargeCurrent, out.max_discharge_current);
convert(in.EVMinimumDischargeCurrent, out.min_discharge_current);
}
template <>
void convert(const struct iso20_dc_DC_ChargeParameterDiscoveryReqType& in, DC_ChargeParameterDiscoveryRequest& out) {
convert(in.Header, out.header);
if (in.DC_CPDReqEnergyTransferMode_isUsed) {
auto& mode_out = out.transfer_mode.emplace<DC_ModeReq>();
convert(in.DC_CPDReqEnergyTransferMode, mode_out);
} else if (in.BPT_DC_CPDReqEnergyTransferMode_isUsed) {
auto& mode_out = out.transfer_mode.emplace<BPT_DC_ModeReq>();
convert(in.BPT_DC_CPDReqEnergyTransferMode, mode_out);
} else {
// FIXME (aw): fail, should not happen!
}
}
// End conversion for deserializing a DCChargeParameterRequest (EVSEside)
// Begin conversion for deserializing a DCChargeParameterResponse (EVside)
template <typename InType> void convert(const InType& in, DC_ModeRes& out) {
convert(in.EVSEMaximumChargePower, out.max_charge_power);
convert(in.EVSEMinimumChargePower, out.min_charge_power);
convert(in.EVSEMaximumChargeCurrent, out.max_charge_current);
convert(in.EVSEMinimumChargeCurrent, out.min_charge_current);
convert(in.EVSEMaximumVoltage, out.max_voltage);
convert(in.EVSEMinimumVoltage, out.min_voltage);
CB2CPP_CONVERT_IF_USED(in.EVSEPowerRampLimitation, out.power_ramp_limit);
}
template <> void convert(const struct iso20_dc_BPT_DC_CPDResEnergyTransferModeType& in, BPT_DC_ModeRes& out) {
convert(in, static_cast<DC_ModeRes&>(out));
convert(in.EVSEMaximumDischargePower, out.max_discharge_power);
convert(in.EVSEMinimumDischargePower, out.min_discharge_power);
convert(in.EVSEMaximumDischargeCurrent, out.max_discharge_current);
convert(in.EVSEMinimumDischargeCurrent, out.min_discharge_current);
}
template <>
void convert(const struct iso20_dc_DC_ChargeParameterDiscoveryResType& in, DC_ChargeParameterDiscoveryResponse& out) {
cb_convert_enum(in.ResponseCode, out.response_code);
if (in.DC_CPDResEnergyTransferMode_isUsed) {
auto& mode_out = out.transfer_mode.emplace<DC_ModeRes>();
convert(in.DC_CPDResEnergyTransferMode, mode_out);
} else if (in.BPT_DC_CPDResEnergyTransferMode_isUsed) {
auto& mode_out = out.transfer_mode.emplace<BPT_DC_ModeRes>();
convert(in.BPT_DC_CPDResEnergyTransferMode, mode_out);
} else {
// FIXME (SL): fail, should not happen!
}
convert(in.Header, out.header);
}
// End conversion for deserializing a DCChargeParameterResponse (EVside)
// Begin conversion for serializing a DCChargeParameterResponse (EVSEside)
struct ModeResponseVisitor {
ModeResponseVisitor(iso20_dc_DC_ChargeParameterDiscoveryResType& res_) : res(res_){};
void operator()(const DC_ModeRes& in) {
init_iso20_dc_DC_CPDResEnergyTransferModeType(&res.DC_CPDResEnergyTransferMode);
CB_SET_USED(res.DC_CPDResEnergyTransferMode);
convert_common(in, res.DC_CPDResEnergyTransferMode);
}
void operator()(const BPT_DC_ModeRes& in) {
init_iso20_dc_BPT_DC_CPDResEnergyTransferModeType(&res.BPT_DC_CPDResEnergyTransferMode);
CB_SET_USED(res.BPT_DC_CPDResEnergyTransferMode);
auto& out = res.BPT_DC_CPDResEnergyTransferMode;
convert_common(in, out);
convert(in.max_discharge_power, out.EVSEMaximumDischargePower);
convert(in.min_discharge_power, out.EVSEMinimumDischargePower);
convert(in.max_discharge_current, out.EVSEMaximumDischargeCurrent);
convert(in.min_discharge_current, out.EVSEMinimumDischargeCurrent);
}
template <typename ModeResTypeIn, typename ModeResTypeOut>
static void convert_common(const ModeResTypeIn& in, ModeResTypeOut& out) {
convert(in.max_charge_power, out.EVSEMaximumChargePower);
convert(in.min_charge_power, out.EVSEMinimumChargePower);
convert(in.max_charge_current, out.EVSEMaximumChargeCurrent);
convert(in.min_charge_current, out.EVSEMinimumChargeCurrent);
convert(in.max_voltage, out.EVSEMaximumVoltage);
convert(in.min_voltage, out.EVSEMinimumVoltage);
CPP2CB_CONVERT_IF_USED(in.power_ramp_limit, out.EVSEPowerRampLimitation);
}
private:
iso20_dc_DC_ChargeParameterDiscoveryResType& res;
};
template <>
void convert(const DC_ChargeParameterDiscoveryResponse& in, struct iso20_dc_DC_ChargeParameterDiscoveryResType& out) {
init_iso20_dc_DC_ChargeParameterDiscoveryResType(&out);
convert(in.header, out.Header);
cb_convert_enum(in.response_code, out.ResponseCode);
std::visit(ModeResponseVisitor(out), in.transfer_mode);
}
template <> int serialize_to_exi(const DC_ChargeParameterDiscoveryResponse& in, exi_bitstream_t& out) {
iso20_dc_exiDocument doc;
init_iso20_dc_exiDocument(&doc);
CB_SET_USED(doc.DC_ChargeParameterDiscoveryRes);
convert(in, doc.DC_ChargeParameterDiscoveryRes);
return encode_iso20_dc_exiDocument(&out, &doc);
}
template <> void insert_type(VariantAccess& va, const struct iso20_dc_DC_ChargeParameterDiscoveryResType& in) {
va.insert_type<DC_ChargeParameterDiscoveryResponse>(in);
};
template <> size_t serialize(const DC_ChargeParameterDiscoveryResponse& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
// End conversion for serializing a DCChargeParameterResponse (EVSEside)
// Begin conversion for serializing a DCChargeParameterRequest (EVside)
struct ModeRequestVisitor {
ModeRequestVisitor(iso20_dc_DC_ChargeParameterDiscoveryReqType& req_) : req(req_){};
void operator()(const DC_ModeReq& in) {
init_iso20_dc_DC_CPDReqEnergyTransferModeType(&req.DC_CPDReqEnergyTransferMode);
CB_SET_USED(req.DC_CPDReqEnergyTransferMode);
convert_common(in, req.DC_CPDReqEnergyTransferMode);
}
void operator()(const BPT_DC_ModeReq& in) {
init_iso20_dc_BPT_DC_CPDReqEnergyTransferModeType(&req.BPT_DC_CPDReqEnergyTransferMode);
CB_SET_USED(req.BPT_DC_CPDReqEnergyTransferMode);
auto& out = req.BPT_DC_CPDReqEnergyTransferMode;
convert_common(in, out);
convert(in.max_discharge_power, out.EVMaximumDischargePower);
convert(in.min_discharge_power, out.EVMinimumDischargePower);
convert(in.max_discharge_current, out.EVMaximumDischargeCurrent);
convert(in.min_discharge_current, out.EVMinimumDischargeCurrent);
}
template <typename ModeReqTypeIn, typename ModeReqTypeOut>
static void convert_common(const ModeReqTypeIn& in, ModeReqTypeOut& out) {
convert(in.max_charge_power, out.EVMaximumChargePower);
convert(in.min_charge_power, out.EVMinimumChargePower);
convert(in.max_charge_current, out.EVMaximumChargeCurrent);
convert(in.min_charge_current, out.EVMinimumChargeCurrent);
convert(in.max_voltage, out.EVMaximumVoltage);
convert(in.min_voltage, out.EVMinimumVoltage);
CPP2CB_ASSIGN_IF_USED(in.target_soc, out.TargetSOC);
}
private:
iso20_dc_DC_ChargeParameterDiscoveryReqType& req;
};
template <>
void convert(const DC_ChargeParameterDiscoveryRequest& in, iso20_dc_DC_ChargeParameterDiscoveryReqType& out) {
init_iso20_dc_DC_ChargeParameterDiscoveryReqType(&out);
std::visit(ModeRequestVisitor(out), in.transfer_mode);
convert(in.header, out.Header);
}
template <> void insert_type(VariantAccess& va, const struct iso20_dc_DC_ChargeParameterDiscoveryReqType& in) {
va.insert_type<DC_ChargeParameterDiscoveryRequest>(in);
}
template <> int serialize_to_exi(const DC_ChargeParameterDiscoveryRequest& in, exi_bitstream_t& out) {
iso20_dc_exiDocument doc;
init_iso20_dc_exiDocument(&doc);
CB_SET_USED(doc.DC_ChargeParameterDiscoveryReq);
convert(in, doc.DC_ChargeParameterDiscoveryReq);
return encode_iso20_dc_exiDocument(&out, &doc);
}
template <> size_t serialize(const DC_ChargeParameterDiscoveryRequest& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
// End conversion for serializing a DCChargeParameterRequest (EVside)
} // namespace iso15118::message_20

View File

@@ -0,0 +1,84 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/message/dc_pre_charge.hpp>
#include <type_traits>
#include <iso15118/detail/variant_access.hpp>
#include <cbv2g/iso_20/iso20_DC_Decoder.h>
#include <cbv2g/iso_20/iso20_DC_Encoder.h>
namespace iso15118::message_20 {
template <> void convert(const struct iso20_dc_DC_PreChargeReqType& in, DC_PreChargeRequest& out) {
convert(in.Header, out.header);
cb_convert_enum(in.EVProcessing, out.processing);
convert(in.EVPresentVoltage, out.present_voltage);
convert(in.EVTargetVoltage, out.target_voltage);
}
template <> void convert(const struct iso20_dc_DC_PreChargeResType& in, DC_PreChargeResponse& out) {
cb_convert_enum(in.ResponseCode, out.response_code);
convert(in.EVSEPresentVoltage, out.present_voltage);
convert(in.Header, out.header);
}
template <> void insert_type(VariantAccess& va, const struct iso20_dc_DC_PreChargeResType& in) {
va.insert_type<DC_PreChargeResponse>(in);
};
template <> void convert(const DC_PreChargeResponse& in, struct iso20_dc_DC_PreChargeResType& out) {
init_iso20_dc_DC_PreChargeResType(&out);
convert(in.header, out.Header);
cb_convert_enum(in.response_code, out.ResponseCode);
convert(in.present_voltage, out.EVSEPresentVoltage);
}
template <> int serialize_to_exi(const DC_PreChargeResponse& in, exi_bitstream_t& out) {
iso20_dc_exiDocument doc;
init_iso20_dc_exiDocument(&doc);
CB_SET_USED(doc.DC_PreChargeRes);
convert(in, doc.DC_PreChargeRes);
return encode_iso20_dc_exiDocument(&out, &doc);
}
template <> size_t serialize(const DC_PreChargeResponse& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
template <> void insert_type(VariantAccess& va, const struct iso20_dc_DC_PreChargeReqType& in) {
va.insert_type<DC_PreChargeRequest>(in);
}
template <> void convert(const DC_PreChargeRequest& in, iso20_dc_DC_PreChargeReqType& out) {
init_iso20_dc_DC_PreChargeReqType(&out);
convert(in.present_voltage, out.EVPresentVoltage);
convert(in.target_voltage, out.EVTargetVoltage);
cb_convert_enum(in.processing, out.EVProcessing);
convert(in.header, out.Header);
}
template <> int serialize_to_exi(const DC_PreChargeRequest& in, exi_bitstream_t& out) {
iso20_dc_exiDocument doc;
init_iso20_dc_exiDocument(&doc);
CB_SET_USED(doc.DC_PreChargeReq);
convert(in, doc.DC_PreChargeReq);
return encode_iso20_dc_exiDocument(&out, &doc);
}
template <> size_t serialize(const DC_PreChargeRequest& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
} // namespace iso15118::message_20

View File

@@ -0,0 +1,82 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/message/dc_welding_detection.hpp>
#include <type_traits>
#include <iso15118/detail/variant_access.hpp>
#include <cbv2g/iso_20/iso20_DC_Decoder.h>
#include <cbv2g/iso_20/iso20_DC_Encoder.h>
namespace iso15118::message_20 {
template <> void convert(const struct iso20_dc_DC_WeldingDetectionReqType& in, DC_WeldingDetectionRequest& out) {
convert(in.Header, out.header);
cb_convert_enum(in.EVProcessing, out.processing);
}
template <> void insert_type(VariantAccess& va, const struct iso20_dc_DC_WeldingDetectionReqType& in) {
va.insert_type<DC_WeldingDetectionRequest>(in);
}
template <> void convert(const struct iso20_dc_DC_WeldingDetectionResType& in, DC_WeldingDetectionResponse& out) {
cb_convert_enum(in.ResponseCode, out.response_code);
convert(in.EVSEPresentVoltage, out.present_voltage);
convert(in.Header, out.header);
}
template <> void insert_type(VariantAccess& va, const struct iso20_dc_DC_WeldingDetectionResType& in) {
va.insert_type<DC_WeldingDetectionResponse>(in);
};
template <> void convert(const DC_WeldingDetectionResponse& in, struct iso20_dc_DC_WeldingDetectionResType& out) {
init_iso20_dc_DC_WeldingDetectionResType(&out);
convert(in.header, out.Header);
cb_convert_enum(in.response_code, out.ResponseCode);
convert(in.present_voltage, out.EVSEPresentVoltage);
}
template <> int serialize_to_exi(const DC_WeldingDetectionResponse& in, exi_bitstream_t& out) {
iso20_dc_exiDocument doc;
init_iso20_dc_exiDocument(&doc);
CB_SET_USED(doc.DC_WeldingDetectionRes);
convert(in, doc.DC_WeldingDetectionRes);
return encode_iso20_dc_exiDocument(&out, &doc);
}
template <> size_t serialize(const DC_WeldingDetectionResponse& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
template <> void convert(const DC_WeldingDetectionRequest& in, iso20_dc_DC_WeldingDetectionReqType& out) {
init_iso20_dc_DC_WeldingDetectionReqType(&out);
cb_convert_enum(in.processing, out.EVProcessing);
convert(in.header, out.Header);
}
template <> int serialize_to_exi(const DC_WeldingDetectionRequest& in, exi_bitstream_t& out) {
iso20_dc_exiDocument doc;
init_iso20_dc_exiDocument(&doc);
CB_SET_USED(doc.DC_WeldingDetectionReq);
convert(in, doc.DC_WeldingDetectionReq);
return encode_iso20_dc_exiDocument(&out, &doc);
}
template <> size_t serialize(const DC_WeldingDetectionRequest& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
} // namespace iso15118::message_20

View File

@@ -0,0 +1,177 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/message/power_delivery.hpp>
#include <type_traits>
#include <iso15118/detail/variant_access.hpp>
#include <cbv2g/iso_20/iso20_CommonMessages_Encoder.h>
namespace iso15118::message_20 {
// Begin conversion for deserializing a PowerDeliveryRequest (EVSEside)
template <>
void convert(const struct iso20_Scheduled_EVPPTControlModeType& in, datatypes::Scheduled_EVPPTControlMode& out) {
if (in.PowerToleranceAcceptance_isUsed) {
out.power_tolerance_acceptance = static_cast<datatypes::PowerToleranceAcceptance>(in.PowerToleranceAcceptance);
}
out.selected_schedule = in.SelectedScheduleTupleID;
}
template <> void convert(const struct iso20_EVPowerProfileType& in, datatypes::PowerProfile& out) {
out.time_anchor = in.TimeAnchor;
if (in.Dynamic_EVPPTControlMode_isUsed) {
out.control_mode.emplace<datatypes::Dynamic_EVPPTControlMode>();
// NOTE (aw): nothing more to do here because Dynamic_EVPPTControlMode is empty
} else if (in.Scheduled_EVPPTControlMode_isUsed) {
auto& cm = out.control_mode.emplace<datatypes::Scheduled_EVPPTControlMode>();
convert(in.Scheduled_EVPPTControlMode, cm);
} else {
throw std::runtime_error("PowerProfile control mode not defined");
}
auto& entries_in = in.EVPowerProfileEntries.EVPowerProfileEntry;
out.entries.clear();
for (auto i = 0; i < entries_in.arrayLen; ++i) {
auto& entry_out = out.entries.emplace_back();
const auto& entry_in = entries_in.array[i];
convert(entry_in, entry_out);
}
}
void convert(const iso20_channelSelectionType in, datatypes::ChannelSelection& out) {
cb_convert_enum(in, out);
}
template <> void convert(const struct iso20_PowerDeliveryReqType& in, PowerDeliveryRequest& out) {
convert(in.Header, out.header);
cb_convert_enum(in.EVProcessing, out.processing);
cb_convert_enum(in.ChargeProgress, out.charge_progress);
CB2CPP_CONVERT_IF_USED(in.EVPowerProfile, out.power_profile);
CB2CPP_CONVERT_IF_USED(in.BPT_ChannelSelection, out.channel_selection);
}
// End conversion for deserializing a PowerDeliveryRequest (EVSEside)
// Begin conversion for deserializing a PowerDeliveryResponse (EVside)
template <> void convert(const struct iso20_PowerDeliveryResType& in, PowerDeliveryResponse& out) {
cb_convert_enum(in.ResponseCode, out.response_code);
CB2CPP_CONVERT_IF_USED(in.EVSEStatus, out.status);
convert(in.Header, out.header);
}
// End conversion for deserializing a PowerDeliveryResponse (EVside)
// Begin conversion for serializing a PowerDeliveryResponse (EVSEside)
template <> void convert(const datatypes::PowerToleranceAcceptance& in, iso20_powerToleranceAcceptanceType& out) {
cb_convert_enum(in, out);
}
template <> void convert(const datatypes::Scheduled_EVPPTControlMode& in, iso20_Scheduled_EVPPTControlModeType& out) {
init_iso20_Scheduled_EVPPTControlModeType(&out);
CPP2CB_CONVERT_IF_USED(in.power_tolerance_acceptance, out.PowerToleranceAcceptance);
out.SelectedScheduleTupleID = in.selected_schedule;
}
template <> void convert(const datatypes::PowerScheduleEntry& in, iso20_PowerScheduleEntryType& out) {
init_iso20_PowerScheduleEntryType(&out);
out.Duration = in.duration;
convert(in.power, out.Power);
CPP2CB_CONVERT_IF_USED(in.power_l2, out.Power_L2);
CPP2CB_CONVERT_IF_USED(in.power_l3, out.Power_L3);
}
template <> void convert(const datatypes::PowerProfile& in, iso20_EVPowerProfileType& out) {
init_iso20_EVPowerProfileType(&out);
out.TimeAnchor = in.time_anchor;
if (std::holds_alternative<datatypes::Dynamic_EVPPTControlMode>(in.control_mode)) {
out.Dynamic_EVPPTControlMode_isUsed = true;
} else if (std::holds_alternative<datatypes::Scheduled_EVPPTControlMode>(in.control_mode)) {
out.Scheduled_EVPPTControlMode_isUsed = true;
convert(std::get<datatypes::Scheduled_EVPPTControlMode>(in.control_mode), out.Scheduled_EVPPTControlMode);
} else {
throw std::runtime_error("PowerProfile control mode not defined");
}
auto& entries_out = out.EVPowerProfileEntries.EVPowerProfileEntry;
entries_out.arrayLen = static_cast<uint32_t>(in.entries.size());
for (size_t i = 0; i < in.entries.size(); ++i) {
convert(in.entries[i], entries_out.array[i]);
}
}
template <> void convert(const PowerDeliveryResponse& in, iso20_PowerDeliveryResType& out) {
init_iso20_PowerDeliveryResType(&out);
convert(in.header, out.Header);
cb_convert_enum(in.response_code, out.ResponseCode);
CPP2CB_CONVERT_IF_USED(in.status, out.EVSEStatus);
}
template <> void insert_type(VariantAccess& va, const struct iso20_PowerDeliveryResType& in) {
va.insert_type<PowerDeliveryResponse>(in);
};
template <> int serialize_to_exi(const PowerDeliveryResponse& in, exi_bitstream_t& out) {
iso20_exiDocument doc;
init_iso20_exiDocument(&doc);
CB_SET_USED(doc.PowerDeliveryRes);
convert(in, doc.PowerDeliveryRes);
return encode_iso20_exiDocument(&out, &doc);
}
template <> size_t serialize(const PowerDeliveryResponse& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
// End conversion for serializing a PowerDeliveryResponse (EVSEside)
// Begin conversion for serializing a PowerDeliveryRequest (EVside)
template <> void convert(const datatypes::ChannelSelection& in, iso20_channelSelectionType& out) {
cb_convert_enum(in, out);
}
template <> void convert(const PowerDeliveryRequest& in, iso20_PowerDeliveryReqType& out) {
init_iso20_PowerDeliveryReqType(&out);
cb_convert_enum(in.charge_progress, out.ChargeProgress);
cb_convert_enum(in.processing, out.EVProcessing);
CPP2CB_CONVERT_IF_USED(in.power_profile, out.EVPowerProfile);
CPP2CB_CONVERT_IF_USED(in.channel_selection, out.BPT_ChannelSelection);
convert(in.header, out.Header);
}
template <> void insert_type(VariantAccess& va, const struct iso20_PowerDeliveryReqType& in) {
va.insert_type<PowerDeliveryRequest>(in);
};
template <> int serialize_to_exi(const PowerDeliveryRequest& in, exi_bitstream_t& out) {
iso20_exiDocument doc;
init_iso20_exiDocument(&doc);
CB_SET_USED(doc.PowerDeliveryReq);
convert(in, doc.PowerDeliveryReq);
return encode_iso20_exiDocument(&out, &doc);
}
template <> size_t serialize(const PowerDeliveryRequest& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
// End conversion for serializing a PowerDeliveryRequest (EVside)
} // namespace iso15118::message_20

View File

@@ -0,0 +1,687 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/message/schedule_exchange.hpp>
#include <type_traits>
#include <iso15118/detail/cb_exi.hpp>
#include <iso15118/detail/variant_access.hpp>
#include <cbv2g/iso_20/iso20_CommonMessages_Decoder.h>
#include <cbv2g/iso_20/iso20_CommonMessages_Encoder.h>
namespace iso15118::message_20 {
// Begin conversion for deserializing a ScheduleExchangeRequest (EVSEside)
template <> void convert(const struct iso20_EVPowerScheduleEntryType& in, datatypes::EVPowerScheduleEntry& out) {
out.duration = in.Duration;
convert(in.Power, out.power);
}
template <> void convert(const struct iso20_EVPowerScheduleType& in, datatypes::EVPowerSchedule& out) {
out.time_anchor = in.TimeAnchor;
const auto& entries_in = in.EVPowerScheduleEntries.EVPowerScheduleEntry;
out.entries.clear();
for (auto i = 0; i < entries_in.arrayLen; ++i) {
const auto& entry_in = entries_in.array[i];
auto& entry_out = out.entries.emplace_back();
convert(entry_in, entry_out);
}
}
template <> void convert(const struct iso20_EVPriceRuleType& in, datatypes::EVPriceRule& out) {
convert(in.EnergyFee, out.energy_fee);
convert(in.PowerRangeStart, out.power_range_start);
}
template <> void convert(const struct iso20_EVPriceRuleStackType& in, datatypes::EVPriceRuleStack& out) {
out.duration = in.Duration;
const auto& rules_in = in.EVPriceRule;
out.price_rules.clear();
for (auto i = 0; i < rules_in.arrayLen; ++i) {
const auto& rule_in = rules_in.array[i];
auto& rule_out = out.price_rules.emplace_back();
convert(rule_in, rule_out);
}
}
template <> void convert(const struct iso20_EVAbsolutePriceScheduleType& in, datatypes::EVAbsolutePriceSchedule& out) {
out.time_anchor = in.TimeAnchor;
out.currency = CB2CPP_STRING(in.Currency);
out.price_algorithm = CB2CPP_STRING(in.PriceAlgorithm);
const auto& stacks_in = in.EVPriceRuleStacks.EVPriceRuleStack;
out.price_rule_stacks.clear();
for (auto i = 0; i < stacks_in.arrayLen; ++i) {
const auto& stack_in = stacks_in.array[i];
auto& stack_out = out.price_rule_stacks.emplace_back();
convert(stack_in, stack_out);
}
}
template <> void convert(const struct iso20_EVEnergyOfferType& in, datatypes::EVEnergyOffer& out) {
convert(in.EVPowerSchedule, out.power_schedule);
convert(in.EVAbsolutePriceSchedule, out.absolute_price_schedule);
}
template <>
void convert(const struct iso20_Scheduled_SEReqControlModeType& in, datatypes::Scheduled_SEReqControlMode& out) {
CB2CPP_ASSIGN_IF_USED(in.DepartureTime, out.departure_time);
CB2CPP_CONVERT_IF_USED(in.EVTargetEnergyRequest, out.target_energy);
CB2CPP_CONVERT_IF_USED(in.EVMaximumEnergyRequest, out.max_energy);
CB2CPP_CONVERT_IF_USED(in.EVMinimumEnergyRequest, out.min_energy);
CB2CPP_CONVERT_IF_USED(in.EVEnergyOffer, out.energy_offer);
}
template <>
void convert(const struct iso20_Dynamic_SEReqControlModeType& in, datatypes::Dynamic_SEReqControlMode& out) {
out.departure_time = in.DepartureTime;
CB2CPP_ASSIGN_IF_USED(in.MinimumSOC, out.minimum_soc);
CB2CPP_ASSIGN_IF_USED(in.TargetSOC, out.target_soc);
convert(in.EVTargetEnergyRequest, out.target_energy);
convert(in.EVMaximumEnergyRequest, out.max_energy);
convert(in.EVMinimumEnergyRequest, out.min_energy);
CB2CPP_CONVERT_IF_USED(in.EVMaximumV2XEnergyRequest, out.max_v2x_energy);
CB2CPP_CONVERT_IF_USED(in.EVMinimumV2XEnergyRequest, out.min_v2x_energy);
}
template <> void convert(const struct iso20_ScheduleExchangeReqType& in, ScheduleExchangeRequest& out) {
convert(in.Header, out.header);
out.max_supporting_points = in.MaximumSupportingPoints;
if (in.Dynamic_SEReqControlMode_isUsed) {
auto& mode_out = out.control_mode.emplace<datatypes::Dynamic_SEReqControlMode>();
convert(in.Dynamic_SEReqControlMode, mode_out);
} else if (in.Scheduled_SEReqControlMode_isUsed) {
auto& mode_out = out.control_mode.emplace<datatypes::Scheduled_SEReqControlMode>();
convert(in.Scheduled_SEReqControlMode, mode_out);
} else {
throw std::runtime_error("No control mode selected in iso20_ScheduleExchangeReqType");
}
}
// End of conversion for deserializing a ScheduleExchangeRequest (EVSEside)
// Begin conversion for deserializing a ScheduleExchangeResponse (EVCCSide)
template <> void convert(const struct iso20_PriceRuleStackType& in, datatypes::PriceRuleStack& out) {
out.duration = in.Duration;
for (auto i = 0; i < in.PriceRule.arrayLen; ++i) {
const auto& rule_in = in.PriceRule.array[i];
auto& rule_out = out.price_rule[i];
convert(rule_in.EnergyFee, rule_out.energy_fee);
CB2CPP_CONVERT_IF_USED(rule_in.ParkingFee, rule_out.parking_fee);
CB2CPP_ASSIGN_IF_USED(rule_in.ParkingFeePeriod, rule_out.parking_fee_period);
CB2CPP_ASSIGN_IF_USED(rule_in.RenewableGenerationPercentage, rule_out.renewable_generation_percentage);
CB2CPP_ASSIGN_IF_USED(rule_in.CarbonDioxideEmission, rule_out.carbon_dioxide_emission);
convert(rule_in.PowerRangeStart, rule_out.power_range_start);
}
}
template <> void convert(const struct iso20_PriceLevelScheduleType& in, datatypes::PriceLevelSchedule& out) {
out.time_anchor = in.TimeAnchor;
out.price_schedule_id = in.PriceScheduleID;
out.number_of_price_levels = in.NumberOfPriceLevels;
CB2CPP_STRING_IF_USED(in.Id, out.id);
CB2CPP_STRING_IF_USED(in.PriceScheduleDescription, out.price_schedule_description);
out.price_level_schedule_entries.clear();
for (auto i = 0; i < in.PriceLevelScheduleEntries.PriceLevelScheduleEntry.arrayLen; ++i) {
const auto& entry_in = in.PriceLevelScheduleEntries.PriceLevelScheduleEntry.array[i];
auto& entry_out = out.price_level_schedule_entries.emplace_back();
entry_out.duration = entry_in.Duration;
entry_out.price_level = entry_in.PriceLevel;
}
}
template <> void convert(const struct iso20_PriceRuleType& in, datatypes::PriceRule& out) {
convert(in.EnergyFee, out.energy_fee);
CB2CPP_CONVERT_IF_USED(in.ParkingFee, out.parking_fee);
CB2CPP_ASSIGN_IF_USED(in.ParkingFeePeriod, out.parking_fee_period);
CB2CPP_ASSIGN_IF_USED(in.RenewableGenerationPercentage, out.renewable_generation_percentage);
CB2CPP_ASSIGN_IF_USED(in.CarbonDioxideEmission, out.carbon_dioxide_emission);
convert(in.PowerRangeStart, out.power_range_start);
}
template <> void convert(const struct iso20_TaxRuleType& in, datatypes::TaxRule& out) {
out.tax_rule_id = in.TaxRuleID;
CB2CPP_STRING_IF_USED(in.TaxRuleName, out.tax_rule_name);
convert(in.TaxRate, out.tax_rate);
CB2CPP_ASSIGN_IF_USED(in.TaxIncludedInPrice, out.tax_included_in_price);
out.applies_to_energy_fee = in.AppliesToEnergyFee;
out.applies_to_parking_fee = in.AppliesToParkingFee;
out.applies_to_overstay_fee = in.AppliesToOverstayFee;
out.applies_to_minimum_maximum_cost = in.AppliesMinimumMaximumCost;
}
template <> void convert(const struct iso20_OverstayRuleType& in, datatypes::OverstayRule& out) {
out.start_time = in.StartTime;
CB2CPP_STRING_IF_USED(in.OverstayRuleDescription, out.overstay_rule_description);
convert(in.OverstayFee, out.overstay_fee);
out.overstay_fee_period = in.OverstayFeePeriod;
convert(in.OverstayFee, out.overstay_fee);
}
template <> void convert(const struct iso20_AbsolutePriceScheduleType& in, datatypes::AbsolutePriceSchedule& out) {
out.time_anchor = in.TimeAnchor;
CB2CPP_STRING_IF_USED(in.Id, out.id);
out.price_schedule_id = in.PriceScheduleID;
CB2CPP_STRING_IF_USED(in.PriceScheduleDescription, out.price_schedule_description);
out.currency = CB2CPP_STRING(in.Currency);
out.language = CB2CPP_STRING(in.Language);
out.price_algorithm = CB2CPP_STRING(in.PriceAlgorithm);
CB2CPP_CONVERT_IF_USED(in.MaximumCost, out.maximum_cost);
CB2CPP_CONVERT_IF_USED(in.MinimumCost, out.minimum_cost);
const auto& stacks_in = in.PriceRuleStacks.PriceRuleStack;
auto& stacks_out = out.price_rule_stacks;
for (auto i = 0; i < stacks_in.arrayLen; ++i) {
const auto& stack_in = stacks_in.array[i];
auto& stack_out = stacks_out[i];
convert(stack_in, stack_out);
}
if (in.TaxRules_isUsed) {
const auto& tax_rules_in = in.TaxRules.TaxRule;
out.tax_rules.emplace();
for (auto i = 0; i < tax_rules_in.arrayLen; ++i) {
const auto& tax_rule_in = tax_rules_in.array[i];
auto& tax_rule_out = out.tax_rules->at(i);
convert(tax_rule_in, tax_rule_out);
}
}
if (in.OverstayRules_isUsed) {
const auto& in_overstay_rules = in.OverstayRules;
out.overstay_rules.emplace();
out.overstay_rules->overstay_time_threshold = in_overstay_rules.OverstayTimeThreshold;
CB2CPP_CONVERT_IF_USED(in_overstay_rules.OverstayPowerThreshold, out.overstay_rules->overstay_power_threshold);
const auto& overstay_rules_in = in_overstay_rules.OverstayRule;
out.overstay_rules->overstay_rule.clear();
for (auto i = 0; i < overstay_rules_in.arrayLen; ++i) {
const auto& overstay_rule_in = overstay_rules_in.array[i];
auto& overstay_rule_out = out.overstay_rules->overstay_rule.emplace_back();
convert(overstay_rule_in, overstay_rule_out);
}
}
if (in.AdditionalSelectedServices_isUsed) {
const auto& in_add_services = in.AdditionalSelectedServices.AdditionalService;
out.additional_selected_services.emplace();
for (auto i = 0; i < in_add_services.arrayLen; ++i) {
const auto& add_service_in = in_add_services.array[i];
auto& add_service_out = out.additional_selected_services->at(i);
add_service_out.service_name = CB2CPP_STRING(add_service_in.ServiceName);
convert(add_service_in.ServiceFee, add_service_out.service_fee);
}
}
}
template <> void convert(const struct iso20_PowerScheduleType& in, datatypes::PowerSchedule& out) {
out.time_anchor = in.TimeAnchor;
CB2CPP_CONVERT_IF_USED(in.AvailableEnergy, out.available_energy);
CB2CPP_CONVERT_IF_USED(in.PowerTolerance, out.power_tolerance);
out.entries.clear();
for (auto i = 0; i < in.PowerScheduleEntries.PowerScheduleEntry.arrayLen; ++i) {
const auto& entry_in = in.PowerScheduleEntries.PowerScheduleEntry.array[i];
auto& entry_out = out.entries.emplace_back();
convert(entry_in, entry_out);
}
}
template <> void convert(const struct iso20_ChargingScheduleType& in, datatypes::ChargingSchedule& out) {
if (in.AbsolutePriceSchedule_isUsed) {
auto& out_absolute_price_schedule = out.price_schedule.emplace<datatypes::AbsolutePriceSchedule>();
convert(in.AbsolutePriceSchedule, out_absolute_price_schedule);
} else if (in.PriceLevelSchedule_isUsed) {
auto& out_price_level_schedule = out.price_schedule.emplace<datatypes::PriceLevelSchedule>();
convert(in.PriceLevelSchedule, out_price_level_schedule);
} else {
out.price_schedule.emplace<std::monostate>();
}
convert(in.PowerSchedule, out.power_schedule);
}
template <> void convert(const struct iso20_ScheduleTupleType& in, datatypes::ScheduleTuple& out) {
out.schedule_tuple_id = in.ScheduleTupleID;
convert(in.ChargingSchedule, out.charging_schedule);
if (in.DischargingSchedule_isUsed) {
auto& out_discharge_schedule = out.discharging_schedule.emplace();
convert(in.DischargingSchedule, out_discharge_schedule);
}
}
template <>
void convert(const struct iso20_Scheduled_SEResControlModeType& in, datatypes::Scheduled_SEResControlMode& out) {
out.schedule_tuple.clear();
for (auto i = 0; i < in.ScheduleTuple.arrayLen; ++i) {
const auto& tuple_in = in.ScheduleTuple.array[i];
auto& tuple_out = out.schedule_tuple.emplace_back();
convert(tuple_in, tuple_out);
}
}
template <>
void convert(const struct iso20_Dynamic_SEResControlModeType& in, datatypes::Dynamic_SEResControlMode& out) {
CB2CPP_ASSIGN_IF_USED(in.DepartureTime, out.departure_time);
CB2CPP_ASSIGN_IF_USED(in.MinimumSOC, out.minimum_soc);
CB2CPP_ASSIGN_IF_USED(in.TargetSOC, out.target_soc);
if (in.AbsolutePriceSchedule_isUsed) {
auto& out_absolute_price_schedule = out.price_schedule.emplace<datatypes::AbsolutePriceSchedule>();
convert(in.AbsolutePriceSchedule, out_absolute_price_schedule);
} else if (in.PriceLevelSchedule_isUsed) {
auto& out_price_level_schedule = out.price_schedule.emplace<datatypes::PriceLevelSchedule>();
convert(in.PriceLevelSchedule, out_price_level_schedule);
} else {
out.price_schedule.emplace<std::monostate>();
}
}
template <> void convert(const struct iso20_ScheduleExchangeResType& in, ScheduleExchangeResponse& out) {
convert(in.Header, out.header);
cb_convert_enum(in.ResponseCode, out.response_code);
cb_convert_enum(in.EVSEProcessing, out.processing);
CB2CPP_ASSIGN_IF_USED(in.GoToPause, out.go_to_pause);
if (in.Dynamic_SEResControlMode_isUsed) {
auto& mode_out = out.control_mode.emplace<datatypes::Dynamic_SEResControlMode>();
convert(in.Dynamic_SEResControlMode, mode_out);
} else if (in.Scheduled_SEResControlMode_isUsed) {
auto& mode_out = out.control_mode.emplace<datatypes::Scheduled_SEResControlMode>();
convert(in.Scheduled_SEResControlMode, mode_out);
} else {
throw std::runtime_error("No control mode selected in iso20_ScheduleExchangeResType");
}
}
template <> void insert_type(VariantAccess& va, const struct iso20_ScheduleExchangeResType& in) {
va.insert_type<ScheduleExchangeResponse>(in);
};
// End of conversion for deserializing a ScheduleExchangeResponse (EVCCSide)
// Begin conversion for serializing a ScheduleExchangeResponse (EVSESide)
template <> void convert(const datatypes::PowerSchedule& in, struct iso20_PowerScheduleType& out) {
init_iso20_PowerScheduleType(&out);
out.TimeAnchor = in.time_anchor;
CPP2CB_CONVERT_IF_USED(in.available_energy, out.AvailableEnergy);
CPP2CB_CONVERT_IF_USED(in.power_tolerance, out.PowerTolerance);
if ((sizeof(out.PowerScheduleEntries.PowerScheduleEntry.array) /
sizeof(out.PowerScheduleEntries.PowerScheduleEntry.array[0])) < in.entries.size()) {
throw std::runtime_error("array is too large"); // FIXME(SL): Change error message
}
for (std::size_t i = 0; i < in.entries.size(); i++) {
auto& out_entry = out.PowerScheduleEntries.PowerScheduleEntry.array[i];
const auto& in_entry = in.entries[i];
out_entry.Duration = in_entry.duration;
convert(in_entry.power, out_entry.Power);
CPP2CB_CONVERT_IF_USED(in_entry.power_l2, out_entry.Power_L2);
CPP2CB_CONVERT_IF_USED(in_entry.power_l3, out_entry.Power_L3);
}
out.PowerScheduleEntries.PowerScheduleEntry.arrayLen = in.entries.size();
}
template <> void convert(const datatypes::TaxRule& in, struct iso20_TaxRuleType& out) {
out.TaxRuleID = in.tax_rule_id;
CPP2CB_STRING_IF_USED(in.tax_rule_name, out.TaxRuleName);
convert(in.tax_rate, out.TaxRate);
CPP2CB_ASSIGN_IF_USED(in.tax_included_in_price, out.TaxIncludedInPrice);
out.AppliesToEnergyFee = in.applies_to_energy_fee;
out.AppliesToParkingFee = in.applies_to_parking_fee;
out.AppliesToOverstayFee = in.applies_to_overstay_fee;
out.AppliesMinimumMaximumCost = in.applies_to_minimum_maximum_cost;
}
template <> void convert(const datatypes::PriceRule& in, struct iso20_PriceRuleType& out) {
convert(in.energy_fee, out.EnergyFee);
CPP2CB_CONVERT_IF_USED(in.parking_fee, out.ParkingFee);
CPP2CB_ASSIGN_IF_USED(in.parking_fee_period, out.ParkingFeePeriod);
CPP2CB_ASSIGN_IF_USED(in.carbon_dioxide_emission, out.CarbonDioxideEmission);
CPP2CB_ASSIGN_IF_USED(in.renewable_generation_percentage, out.RenewableGenerationPercentage);
convert(in.power_range_start, out.PowerRangeStart);
}
template <> void convert(const datatypes::PriceRuleStack& in, struct iso20_PriceRuleStackType& out) {
out.Duration = in.duration;
if ((sizeof(out.PriceRule.array) / sizeof(out.PriceRule.array[0])) < in.price_rule.size()) {
throw std::runtime_error("array is too large"); // FIXME(SL): Change error message
}
for (std::size_t i = 0; i < in.price_rule.size(); i++) {
convert(in.price_rule.at(i), out.PriceRule.array[i]);
}
out.PriceRule.arrayLen = in.price_rule.size();
}
template <> void convert(const datatypes::OverstayRule& in, struct iso20_OverstayRuleType& out) {
CPP2CB_STRING_IF_USED(in.overstay_rule_description, out.OverstayRuleDescription);
out.StartTime = in.start_time;
convert(in.overstay_fee, out.OverstayFee);
out.OverstayFeePeriod = in.overstay_fee_period;
}
template <> void convert(const datatypes::AbsolutePriceSchedule& in, struct iso20_AbsolutePriceScheduleType& out) {
CPP2CB_STRING_IF_USED(in.id, out.Id);
out.TimeAnchor = in.time_anchor;
out.PriceScheduleID = in.price_schedule_id;
CPP2CB_STRING_IF_USED(in.price_schedule_description, out.PriceScheduleDescription);
CPP2CB_STRING(in.currency, out.Currency);
CPP2CB_STRING(in.language, out.Language);
CPP2CB_STRING(in.price_algorithm, out.PriceAlgorithm);
CPP2CB_CONVERT_IF_USED(in.minimum_cost, out.MaximumCost);
CPP2CB_CONVERT_IF_USED(in.maximum_cost, out.MaximumCost);
if (in.tax_rules.has_value()) {
out.TaxRules_isUsed = true;
const auto& in_tax_rules = in.tax_rules.value();
if ((sizeof(out.TaxRules.TaxRule.array) / sizeof(out.TaxRules.TaxRule.array[0])) < in_tax_rules.size()) {
throw std::runtime_error("array is too large"); // FIXME(SL): Change error message
}
for (std::size_t i = 0; i < in_tax_rules.size(); i++) {
convert(in_tax_rules.at(i), out.TaxRules.TaxRule.array[i]);
}
out.TaxRules.TaxRule.arrayLen = in_tax_rules.size();
}
if ((sizeof(out.PriceRuleStacks.PriceRuleStack.array) / sizeof(out.PriceRuleStacks.PriceRuleStack.array[0])) <
in.price_rule_stacks.size()) {
throw std::runtime_error("array is too large"); // FIXME(SL): Change error message
}
for (std::size_t i = 0; i < in.price_rule_stacks.size(); i++) {
convert(in.price_rule_stacks.at(i), out.PriceRuleStacks.PriceRuleStack.array[i]);
}
out.PriceRuleStacks.PriceRuleStack.arrayLen = in.price_rule_stacks.size();
if (in.overstay_rules.has_value()) {
out.OverstayRules_isUsed = true;
const auto& in_overstay_rules = in.overstay_rules.value();
CPP2CB_ASSIGN_IF_USED(in_overstay_rules.overstay_time_threshold, out.OverstayRules.OverstayTimeThreshold);
CPP2CB_CONVERT_IF_USED(in_overstay_rules.overstay_power_threshold, out.OverstayRules.OverstayPowerThreshold);
if ((sizeof(out.OverstayRules.OverstayRule.array) / sizeof(out.OverstayRules.OverstayRule.array[0])) <
in_overstay_rules.overstay_rule.size()) {
throw std::runtime_error("array is too large"); // FIXME(SL): Change error message
}
for (std::size_t i = 0; i < in_overstay_rules.overstay_rule.size(); i++) {
convert(in_overstay_rules.overstay_rule.at(i), out.OverstayRules.OverstayRule.array[i]);
}
out.OverstayRules.OverstayRule.arrayLen = in_overstay_rules.overstay_rule.size();
}
if (in.additional_selected_services.has_value()) {
out.AdditionalSelectedServices_isUsed = true;
const auto& in_add_services = in.additional_selected_services.value();
if ((sizeof(out.AdditionalSelectedServices.AdditionalService.array) /
sizeof(out.AdditionalSelectedServices.AdditionalService.array[0])) < in_add_services.size()) {
throw std::runtime_error("array is too large"); // FIXME(SL): Change error message
}
for (std::size_t i = 0; i < in_add_services.size(); i++) {
CPP2CB_STRING(in_add_services.at(i).service_name,
out.AdditionalSelectedServices.AdditionalService.array[i].ServiceName);
convert(in_add_services.at(i).service_fee,
out.AdditionalSelectedServices.AdditionalService.array[i].ServiceFee);
}
out.AdditionalSelectedServices.AdditionalService.arrayLen = in_add_services.size();
}
}
template <> void convert(const datatypes::PriceLevelSchedule& in, struct iso20_PriceLevelScheduleType& out) {
CPP2CB_STRING_IF_USED(in.id, out.Id);
out.TimeAnchor = in.time_anchor;
out.PriceScheduleID = in.price_schedule_id;
CPP2CB_STRING_IF_USED(in.price_schedule_description, out.PriceScheduleDescription);
out.NumberOfPriceLevels = in.number_of_price_levels;
if ((sizeof(out.PriceLevelScheduleEntries.PriceLevelScheduleEntry.array) /
sizeof(out.PriceLevelScheduleEntries.PriceLevelScheduleEntry.array[0])) <
in.price_level_schedule_entries.size()) {
throw std::runtime_error("array is too large"); // FIXME(SL): Change error message
}
for (std::size_t i = 0; i < in.price_level_schedule_entries.size(); i++) {
out.PriceLevelScheduleEntries.PriceLevelScheduleEntry.array[i].Duration =
in.price_level_schedule_entries.at(i).duration;
out.PriceLevelScheduleEntries.PriceLevelScheduleEntry.array[i].PriceLevel =
in.price_level_schedule_entries.at(i).price_level;
}
out.PriceLevelScheduleEntries.PriceLevelScheduleEntry.arrayLen = in.price_level_schedule_entries.size();
}
using PriceSchedule = std::variant<std::monostate, datatypes::AbsolutePriceSchedule, datatypes::PriceLevelSchedule>;
template <typename CbMessageType> void convert_price_schedule(const PriceSchedule& in, CbMessageType& out) {
if (const auto* absolute_price = std::get_if<datatypes::AbsolutePriceSchedule>(&in)) {
convert(*absolute_price, out.AbsolutePriceSchedule);
out.AbsolutePriceSchedule_isUsed = true;
} else if (const auto* price_level_schedule = std::get_if<datatypes::PriceLevelSchedule>(&in)) {
convert(*price_level_schedule, out.PriceLevelSchedule);
out.PriceLevelSchedule_isUsed = true;
} else {
out.AbsolutePriceSchedule_isUsed = false;
out.PriceLevelSchedule_isUsed = false;
}
}
template <> void convert(const PriceSchedule& in, struct iso20_ChargingScheduleType& out) {
convert_price_schedule(in, out);
}
template <> void convert(const PriceSchedule& in, struct iso20_Dynamic_SEResControlModeType& out) {
convert_price_schedule(in, out);
}
template <> void convert(const datatypes::ChargingSchedule& in, struct iso20_ChargingScheduleType& out) {
init_iso20_ChargingScheduleType(&out);
convert(in.power_schedule, out.PowerSchedule);
convert(in.price_schedule, out);
}
template <> void convert(const datatypes::ScheduleTuple& in, struct iso20_ScheduleTupleType& out) {
init_iso20_ScheduleTupleType(&out);
out.ScheduleTupleID = in.schedule_tuple_id;
convert(in.charging_schedule, out.ChargingSchedule);
CPP2CB_CONVERT_IF_USED(in.discharging_schedule, out.DischargingSchedule);
}
struct ModeResponseVisitor {
ModeResponseVisitor(iso20_ScheduleExchangeResType& res_) : res(res_){};
void operator()(const datatypes::Dynamic_SEResControlMode& in) {
init_iso20_Dynamic_SEResControlModeType(&res.Dynamic_SEResControlMode);
CB_SET_USED(res.Dynamic_SEResControlMode);
auto& out = res.Dynamic_SEResControlMode;
CPP2CB_ASSIGN_IF_USED(in.departure_time, out.DepartureTime)
CPP2CB_ASSIGN_IF_USED(in.minimum_soc, out.MinimumSOC);
CPP2CB_ASSIGN_IF_USED(in.target_soc, out.TargetSOC);
convert(in.price_schedule, out);
}
void operator()(const datatypes::Scheduled_SEResControlMode& in) {
init_iso20_Scheduled_SEResControlModeType(&res.Scheduled_SEResControlMode);
CB_SET_USED(res.Scheduled_SEResControlMode);
auto& out = res.Scheduled_SEResControlMode;
if ((sizeof(out.ScheduleTuple.array) / sizeof(out.ScheduleTuple.array[0])) < in.schedule_tuple.size()) {
throw std::runtime_error("array is too large"); // FIXME(SL): Change error message
}
for (std::size_t i = 0; i < in.schedule_tuple.size(); i++) {
convert(in.schedule_tuple[i], out.ScheduleTuple.array[i]);
}
out.ScheduleTuple.arrayLen = in.schedule_tuple.size();
}
private:
iso20_ScheduleExchangeResType& res;
};
template <> void convert(const ScheduleExchangeResponse& in, struct iso20_ScheduleExchangeResType& out) {
init_iso20_ScheduleExchangeResType(&out);
convert(in.header, out.Header);
cb_convert_enum(in.response_code, out.ResponseCode);
cb_convert_enum(in.processing, out.EVSEProcessing);
CPP2CB_ASSIGN_IF_USED(in.go_to_pause, out.GoToPause);
std::visit(ModeResponseVisitor(out), in.control_mode);
}
template <> void insert_type(VariantAccess& va, const struct iso20_ScheduleExchangeReqType& in) {
va.insert_type<ScheduleExchangeRequest>(in);
};
template <> int serialize_to_exi(const ScheduleExchangeResponse& in, exi_bitstream_t& out) {
iso20_exiDocument doc;
init_iso20_exiDocument(&doc);
CB_SET_USED(doc.ScheduleExchangeRes);
convert(in, doc.ScheduleExchangeRes);
return encode_iso20_exiDocument(&out, &doc);
}
template <> size_t serialize(const ScheduleExchangeResponse& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
// End conversion for serializing a ScheduleExchangeResponse (EVSESide)
// Begin conversion for serializing a ScheduleExchangeRequest (EVCCSide)
template <> void convert(const datatypes::EVPriceRule& in, struct iso20_EVPriceRuleType& out) {
convert(in.energy_fee, out.EnergyFee);
convert(in.power_range_start, out.PowerRangeStart);
}
template <> void convert(const datatypes::EVPriceRuleStack& in, struct iso20_EVPriceRuleStackType& out) {
out.Duration = in.duration;
if ((sizeof(out.EVPriceRule.array) / sizeof(out.EVPriceRule.array[0])) < in.price_rules.size()) {
throw std::runtime_error("array is too large"); // FIXME(SL): Change error message
}
for (std::size_t i = 0; i < in.price_rules.size(); i++) {
convert(in.price_rules.at(i), out.EVPriceRule.array[i]);
}
out.EVPriceRule.arrayLen = in.price_rules.size();
}
template <> void convert(const datatypes::EVAbsolutePriceSchedule& in, struct iso20_EVAbsolutePriceScheduleType& out) {
out.TimeAnchor = in.time_anchor;
CPP2CB_STRING(in.currency, out.Currency);
CPP2CB_STRING(in.price_algorithm, out.PriceAlgorithm);
if ((sizeof(out.EVPriceRuleStacks.EVPriceRuleStack.array) /
sizeof(out.EVPriceRuleStacks.EVPriceRuleStack.array[0])) < in.price_rule_stacks.size()) {
throw std::runtime_error("array is too large"); // FIXME(SL): Change error message
}
for (std::size_t i = 0; i < in.price_rule_stacks.size(); i++) {
convert(in.price_rule_stacks.at(i), out.EVPriceRuleStacks.EVPriceRuleStack.array[i]);
}
out.EVPriceRuleStacks.EVPriceRuleStack.arrayLen = in.price_rule_stacks.size();
}
template <> void convert(const datatypes::EVPowerScheduleEntry& in, struct iso20_EVPowerScheduleEntryType& out) {
out.Duration = in.duration;
convert(in.power, out.Power);
}
template <> void convert(const datatypes::EVPowerSchedule& in, struct iso20_EVPowerScheduleType& out) {
out.TimeAnchor = in.time_anchor;
if ((sizeof(out.EVPowerScheduleEntries.EVPowerScheduleEntry.array) /
sizeof(out.EVPowerScheduleEntries.EVPowerScheduleEntry.array[0])) < in.entries.size()) {
throw std::runtime_error("array is too large"); // FIXME(SL): Change error message
}
for (std::size_t i = 0; i < in.entries.size(); i++) {
convert(in.entries.at(i), out.EVPowerScheduleEntries.EVPowerScheduleEntry.array[i]);
}
out.EVPowerScheduleEntries.EVPowerScheduleEntry.arrayLen = in.entries.size();
}
template <> void convert(const datatypes::EVEnergyOffer& in, struct iso20_EVEnergyOfferType& out) {
convert(in.power_schedule, out.EVPowerSchedule);
convert(in.absolute_price_schedule, out.EVAbsolutePriceSchedule);
}
struct ModeRequestVisitor {
ModeRequestVisitor(iso20_ScheduleExchangeReqType& req_) : req(req_){};
void operator()(const datatypes::Dynamic_SEReqControlMode& in) {
init_iso20_Dynamic_SEReqControlModeType(&req.Dynamic_SEReqControlMode);
CB_SET_USED(req.Dynamic_SEReqControlMode);
auto& out = req.Dynamic_SEReqControlMode;
out.DepartureTime = in.departure_time;
convert(in.max_energy, out.EVMaximumEnergyRequest);
convert(in.min_energy, out.EVMinimumEnergyRequest);
convert(in.target_energy, out.EVTargetEnergyRequest);
CPP2CB_CONVERT_IF_USED(in.max_v2x_energy, out.EVMaximumV2XEnergyRequest);
CPP2CB_CONVERT_IF_USED(in.min_v2x_energy, out.EVMinimumV2XEnergyRequest);
CPP2CB_ASSIGN_IF_USED(in.minimum_soc, out.MinimumSOC);
CPP2CB_ASSIGN_IF_USED(in.target_soc, out.TargetSOC);
}
void operator()(const datatypes::Scheduled_SEReqControlMode& in) {
init_iso20_Scheduled_SEReqControlModeType(&req.Scheduled_SEReqControlMode);
CB_SET_USED(req.Scheduled_SEReqControlMode);
auto& out = req.Scheduled_SEReqControlMode;
CPP2CB_ASSIGN_IF_USED(in.departure_time, out.DepartureTime);
CPP2CB_CONVERT_IF_USED(in.target_energy, out.EVTargetEnergyRequest);
CPP2CB_CONVERT_IF_USED(in.max_energy, out.EVMaximumEnergyRequest);
CPP2CB_CONVERT_IF_USED(in.min_energy, out.EVMinimumEnergyRequest);
CPP2CB_CONVERT_IF_USED(in.energy_offer, out.EVEnergyOffer);
}
private:
iso20_ScheduleExchangeReqType& req;
};
template <> void convert(const ScheduleExchangeRequest& in, iso20_ScheduleExchangeReqType& out) {
init_iso20_ScheduleExchangeReqType(&out);
out.MaximumSupportingPoints = in.max_supporting_points;
std::visit(ModeRequestVisitor(out), in.control_mode);
convert(in.header, out.Header);
}
template <> int serialize_to_exi(const ScheduleExchangeRequest& in, exi_bitstream_t& out) {
iso20_exiDocument doc;
init_iso20_exiDocument(&doc);
CB_SET_USED(doc.ScheduleExchangeReq);
convert(in, doc.ScheduleExchangeReq);
return encode_iso20_exiDocument(&out, &doc);
}
template <> size_t serialize(const ScheduleExchangeRequest& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
// End conversion for serializing a ScheduleExchangeRequest (EVCCSide)
} // namespace iso15118::message_20

View File

@@ -0,0 +1,333 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/message/service_detail.hpp>
#include <type_traits>
#include <iso15118/detail/variant_access.hpp>
#include <cbv2g/iso_20/iso20_CommonMessages_Encoder.h>
namespace iso15118::message_20 {
namespace datatypes {
ParameterSet::ParameterSet(uint16_t _id) : id(_id), parameter({datatypes::Parameter{"", false}}) {
}
ParameterSet::ParameterSet(uint16_t _id, const AcParameterList& list) : id(_id) {
parameter.push_back({"Connector", static_cast<int32_t>(list.connector)});
parameter.push_back({"ControlMode", static_cast<int32_t>(list.control_mode)});
parameter.push_back({"EVSENominalVoltage", static_cast<int32_t>(list.evse_nominal_voltage)});
if (list.control_mode == ControlMode::Scheduled) {
parameter.push_back({"MobilityNeedsMode", static_cast<int32_t>(MobilityNeedsMode::ProvidedByEvcc)});
} else {
parameter.push_back({"MobilityNeedsMode", static_cast<int32_t>(list.mobility_needs_mode)});
}
parameter.push_back({"Pricing", static_cast<int32_t>(list.pricing)});
}
ParameterSet::ParameterSet(uint16_t _id, const AcBptParameterList& list) : id(_id) {
parameter.push_back({"Connector", static_cast<int32_t>(list.connector)});
parameter.push_back({"ControlMode", static_cast<int32_t>(list.control_mode)});
parameter.push_back({"EVSENominalVoltage", static_cast<int32_t>(list.evse_nominal_voltage)});
if (list.control_mode == ControlMode::Scheduled) {
parameter.push_back({"MobilityNeedsMode", static_cast<int32_t>(MobilityNeedsMode::ProvidedByEvcc)});
} else {
parameter.push_back({"MobilityNeedsMode", static_cast<int32_t>(list.mobility_needs_mode)});
}
parameter.push_back({"Pricing", static_cast<int32_t>(list.pricing)});
parameter.push_back({"BPTChannel", static_cast<int32_t>(list.bpt_channel)});
parameter.push_back({"GeneratorMode", static_cast<int32_t>(list.generator_mode)});
parameter.push_back({"DetectionMethodGridCodeIslanding", static_cast<int32_t>(list.grid_code_detection_method)});
}
ParameterSet::ParameterSet(uint16_t _id, const DcParameterList& list) {
id = _id;
// Connector
auto& connector = parameter.emplace_back();
connector.name = "Connector";
connector.value = static_cast<int32_t>(list.connector);
// ControlMode
auto& control_mode = parameter.emplace_back();
control_mode.name = "ControlMode";
control_mode.value = static_cast<int32_t>(list.control_mode);
// MobilityNeedsMode
auto& mobility = parameter.emplace_back();
mobility.name = "MobilityNeedsMode";
if (list.control_mode == ControlMode::Scheduled) {
mobility.value = static_cast<int32_t>(MobilityNeedsMode::ProvidedByEvcc);
} else {
mobility.value = static_cast<int32_t>(list.mobility_needs_mode);
}
// Pricing
auto& pricing = parameter.emplace_back();
pricing.name = "Pricing";
pricing.value = static_cast<int32_t>(list.pricing);
}
ParameterSet::ParameterSet(uint16_t _id, const DcBptParameterList& list) {
id = _id;
// Todo(sl): Refactor because of duplicate code
// Connector
auto& connector = parameter.emplace_back();
connector.name = "Connector";
connector.value = static_cast<int32_t>(list.connector);
// ControlMode
auto& control_mode = parameter.emplace_back();
control_mode.name = "ControlMode";
control_mode.value = static_cast<int32_t>(list.control_mode);
// MobilityNeedsMode
auto& mobility = parameter.emplace_back();
mobility.name = "MobilityNeedsMode";
if (list.control_mode == ControlMode::Scheduled) {
mobility.value = static_cast<int32_t>(MobilityNeedsMode::ProvidedByEvcc);
} else {
mobility.value = static_cast<int32_t>(list.mobility_needs_mode);
}
// Pricing
auto& pricing = parameter.emplace_back();
pricing.name = "Pricing";
pricing.value = static_cast<int32_t>(list.pricing);
// BPTChannel
auto& channel = parameter.emplace_back();
channel.name = "BPTChannel";
channel.value = static_cast<int32_t>(list.bpt_channel);
// GeneratorMode
auto& generator_mode = parameter.emplace_back();
generator_mode.name = "GeneratorMode";
generator_mode.value = static_cast<int32_t>(list.generator_mode);
}
ParameterSet::ParameterSet(uint16_t _id, const McsParameterList& list) {
id = _id;
// Connector
auto& connector = parameter.emplace_back();
connector.name = "Connector";
connector.value = static_cast<int32_t>(list.connector);
// ControlMode
auto& control_mode = parameter.emplace_back();
control_mode.name = "ControlMode";
control_mode.value = static_cast<int32_t>(list.control_mode);
// MobilityNeedsMode
auto& mobility = parameter.emplace_back();
mobility.name = "MobilityNeedsMode";
if (list.control_mode == ControlMode::Scheduled) {
mobility.value = static_cast<int32_t>(MobilityNeedsMode::ProvidedByEvcc);
} else {
mobility.value = static_cast<int32_t>(list.mobility_needs_mode);
}
// Pricing
auto& pricing = parameter.emplace_back();
pricing.name = "Pricing";
pricing.value = static_cast<int32_t>(list.pricing);
}
ParameterSet::ParameterSet(uint16_t _id, const McsBptParameterList& list) {
id = _id;
// Todo(sl): Refactor because of duplicate code
// Connector
auto& connector = parameter.emplace_back();
connector.name = "Connector";
connector.value = static_cast<int32_t>(list.connector);
// ControlMode
auto& control_mode = parameter.emplace_back();
control_mode.name = "ControlMode";
control_mode.value = static_cast<int32_t>(list.control_mode);
// MobilityNeedsMode
auto& mobility = parameter.emplace_back();
mobility.name = "MobilityNeedsMode";
if (list.control_mode == ControlMode::Scheduled) {
mobility.value = static_cast<int32_t>(MobilityNeedsMode::ProvidedByEvcc);
} else {
mobility.value = static_cast<int32_t>(list.mobility_needs_mode);
}
// Pricing
auto& pricing = parameter.emplace_back();
pricing.name = "Pricing";
pricing.value = static_cast<int32_t>(list.pricing);
// BPTChannel
auto& channel = parameter.emplace_back();
channel.name = "BPTChannel";
channel.value = static_cast<int32_t>(list.bpt_channel);
// GeneratorMode
auto& generator_mode = parameter.emplace_back();
generator_mode.name = "GeneratorMode";
generator_mode.value = static_cast<int32_t>(list.generator_mode);
}
ParameterSet::ParameterSet(uint16_t _id, const InternetParameterList& list) {
id = _id;
auto& protocol = parameter.emplace_back();
protocol.name = "Protocol";
protocol.value = from_Protocol(list.protocol);
auto& port = parameter.emplace_back();
port.name = "Port";
port.value = static_cast<int32_t>(list.port);
}
ParameterSet::ParameterSet(uint16_t _id, const ParkingParameterList& list) {
id = _id;
auto& intended_service = parameter.emplace_back();
intended_service.name = "IntendedService";
intended_service.value = static_cast<int32_t>(list.intended_service);
auto& parking_status = parameter.emplace_back();
parking_status.name = "ParkingStatusType";
parking_status.value = static_cast<int32_t>(list.parking_status);
}
} // namespace datatypes
template <> void convert(const struct iso20_ServiceDetailReqType& in, ServiceDetailRequest& out) {
convert(in.Header, out.header);
out.service = in.ServiceID;
}
template <> void convert(const struct iso20_ServiceDetailResType& in, ServiceDetailResponse& out) {
cb_convert_enum(in.ResponseCode, out.response_code);
out.service = in.ServiceID;
out.service_parameter_list.clear();
for (uint8_t i = 0; i < in.ServiceParameterList.ParameterSet.arrayLen; i++) {
const auto& in_parameter_set = in.ServiceParameterList.ParameterSet.array[i];
datatypes::ParameterSet out_parameter_set;
out_parameter_set.id = in_parameter_set.ParameterSetID;
out_parameter_set.parameter.clear();
for (uint8_t t = 0; t < in_parameter_set.Parameter.arrayLen; t++) {
const auto& in_parameter = in_parameter_set.Parameter.array[t];
datatypes::Parameter out_parameter;
out_parameter.name = CB2CPP_STRING(in_parameter.Name);
if (in_parameter.boolValue_isUsed) {
out_parameter.value = in_parameter.boolValue;
} else if (in_parameter.byteValue_isUsed) {
out_parameter.value = in_parameter.byteValue;
} else if (in_parameter.shortValue_isUsed) {
out_parameter.value = in_parameter.shortValue;
} else if (in_parameter.intValue_isUsed) {
out_parameter.value = in_parameter.intValue;
} else if (in_parameter.finiteString_isUsed) {
out_parameter.value = CB2CPP_STRING(in_parameter.finiteString);
} else if (in_parameter.rationalNumber_isUsed) {
out_parameter.value =
datatypes::RationalNumber{in_parameter.rationalNumber.Value, in_parameter.rationalNumber.Exponent};
}
out_parameter_set.parameter.push_back(out_parameter);
}
out.service_parameter_list.push_back(out_parameter_set);
}
convert(in.Header, out.header);
}
template <> void convert(const ServiceDetailRequest& in, iso20_ServiceDetailReqType& out) {
init_iso20_ServiceDetailReqType(&out);
out.ServiceID = in.service;
convert(in.header, out.Header);
}
struct ParameterValueVisitor {
ParameterValueVisitor(iso20_ParameterType& parameter_) : parameter(parameter_){};
void operator()(const bool& in) {
CB_SET_USED(parameter.boolValue);
parameter.boolValue = in;
}
void operator()(const int8_t& in) {
CB_SET_USED(parameter.byteValue);
parameter.byteValue = in;
}
void operator()(const int16_t& in) {
CB_SET_USED(parameter.shortValue);
parameter.shortValue = in;
}
void operator()(const int32_t& in) {
CB_SET_USED(parameter.intValue);
parameter.intValue = in;
}
void operator()(const std::string& in) {
CB_SET_USED(parameter.finiteString);
CPP2CB_STRING(in, parameter.finiteString);
}
void operator()(const message_20::datatypes::RationalNumber& in) {
CB_SET_USED(parameter.rationalNumber);
parameter.rationalNumber.Exponent = in.exponent;
parameter.rationalNumber.Value = in.value;
}
private:
iso20_ParameterType& parameter;
};
template <> void convert(const ServiceDetailResponse& in, iso20_ServiceDetailResType& out) {
init_iso20_ServiceDetailResType(&out);
convert(in.header, out.Header);
cb_convert_enum(in.response_code, out.ResponseCode);
out.ServiceID = in.service;
uint8_t index = 0;
for (auto const& in_parameter_set : in.service_parameter_list) {
auto& out_parameter_set = out.ServiceParameterList.ParameterSet.array[index++];
out_parameter_set.ParameterSetID = in_parameter_set.id;
uint8_t t = 0;
for (auto const& in_parameter : in_parameter_set.parameter) {
auto& out_parameter = out_parameter_set.Parameter.array[t++];
init_iso20_ParameterType(&out_parameter);
CPP2CB_STRING(in_parameter.name, out_parameter.Name);
std::visit(ParameterValueVisitor(out_parameter), in_parameter.value);
}
out_parameter_set.Parameter.arrayLen = in_parameter_set.parameter.size();
}
out.ServiceParameterList.ParameterSet.arrayLen = in.service_parameter_list.size();
}
template <> void insert_type(VariantAccess& va, const struct iso20_ServiceDetailReqType& in) {
va.insert_type<ServiceDetailRequest>(in);
}
template <> void insert_type(VariantAccess& va, const struct iso20_ServiceDetailResType& in) {
va.insert_type<ServiceDetailResponse>(in);
};
template <> int serialize_to_exi(const ServiceDetailResponse& in, exi_bitstream_t& out) {
iso20_exiDocument doc;
init_iso20_exiDocument(&doc);
CB_SET_USED(doc.ServiceDetailRes);
convert(in, doc.ServiceDetailRes);
return encode_iso20_exiDocument(&out, &doc);
}
template <> int serialize_to_exi(const ServiceDetailRequest& in, exi_bitstream_t& out) {
iso20_exiDocument doc;
init_iso20_exiDocument(&doc);
CB_SET_USED(doc.ServiceDetailReq);
convert(in, doc.ServiceDetailReq);
return encode_iso20_exiDocument(&out, &doc);
}
template <> size_t serialize(const ServiceDetailResponse& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
template <> size_t serialize(const ServiceDetailRequest& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
} // namespace iso15118::message_20

View File

@@ -0,0 +1,134 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/message/service_discovery.hpp>
#include <type_traits>
#include <iso15118/detail/variant_access.hpp>
#include <cbv2g/iso_20/iso20_CommonMessages_Encoder.h>
namespace iso15118::message_20 {
template <> void convert(const struct iso20_ServiceDiscoveryReqType& in, ServiceDiscoveryRequest& out) {
convert(in.Header, out.header);
if (in.SupportedServiceIDs_isUsed) {
auto& temp = out.supported_service_ids.emplace();
for (size_t i = 0; i < in.SupportedServiceIDs.ServiceID.arrayLen; i++) {
temp.emplace_back(in.SupportedServiceIDs.ServiceID.array[i]);
}
}
}
template <> void convert(const struct iso20_ServiceDiscoveryResType& in, ServiceDiscoveryResponse& out) {
cb_convert_enum(in.ResponseCode, out.response_code);
out.service_renegotiation_supported = in.ServiceRenegotiationSupported;
// remove the default AC service
out.energy_transfer_service_list.clear();
for (auto i = 0; i < in.EnergyTransferServiceList.Service.arrayLen; i++) {
const auto& service = in.EnergyTransferServiceList.Service.array[i];
auto& out_service = out.energy_transfer_service_list.emplace_back();
cb_convert_enum(service.ServiceID, out_service.service_id);
out_service.free_service = service.FreeService;
}
if (in.VASList_isUsed) {
out.vas_list.emplace();
out.vas_list->clear();
for (auto i = 0; i < in.VASList.Service.arrayLen; i++) {
const auto& service = in.VASList.Service.array[i];
auto& out_service = out.vas_list->emplace_back();
out_service.service_id = service.ServiceID;
out_service.free_service = service.FreeService;
}
}
convert(in.Header, out.header);
}
template <> void convert(const ServiceDiscoveryRequest& in, iso20_ServiceDiscoveryReqType& out) {
init_iso20_ServiceDiscoveryReqType(&out);
if (in.supported_service_ids) {
auto& out_service_ids = out.SupportedServiceIDs.ServiceID.array;
const auto& supported_service_ids = in.supported_service_ids.value();
std::copy(supported_service_ids.begin(), supported_service_ids.end(), out_service_ids);
out.SupportedServiceIDs.ServiceID.arrayLen = supported_service_ids.size();
CB_SET_USED(out.SupportedServiceIDs);
}
convert(in.header, out.Header);
}
template <> void convert(const ServiceDiscoveryResponse& in, iso20_ServiceDiscoveryResType& out) {
init_iso20_ServiceDiscoveryResType(&out);
cb_convert_enum(in.response_code, out.ResponseCode);
out.ServiceRenegotiationSupported = in.service_renegotiation_supported;
uint8_t index = 0;
for (const auto& service : in.energy_transfer_service_list) {
auto& out_service = out.EnergyTransferServiceList.Service.array[index++];
cb_convert_enum(service.service_id, out_service.ServiceID);
out_service.FreeService = service.free_service;
}
out.EnergyTransferServiceList.Service.arrayLen = in.energy_transfer_service_list.size();
if (in.vas_list) {
index = 0;
for (const auto& service : *in.vas_list) {
auto& out_service = out.VASList.Service.array[index++];
out_service.ServiceID = service.service_id;
out_service.FreeService = service.free_service;
}
out.VASList.Service.arrayLen = in.vas_list.value().size();
CB_SET_USED(out.VASList);
}
convert(in.header, out.Header);
}
template <> void insert_type(VariantAccess& va, const struct iso20_ServiceDiscoveryReqType& in) {
va.insert_type<ServiceDiscoveryRequest>(in);
};
template <> void insert_type(VariantAccess& va, const struct iso20_ServiceDiscoveryResType& in) {
va.insert_type<ServiceDiscoveryResponse>(in);
};
template <> int serialize_to_exi(const ServiceDiscoveryResponse& in, exi_bitstream_t& out) {
iso20_exiDocument doc;
init_iso20_exiDocument(&doc);
CB_SET_USED(doc.ServiceDiscoveryRes);
convert(in, doc.ServiceDiscoveryRes);
return encode_iso20_exiDocument(&out, &doc);
}
template <> int serialize_to_exi(const ServiceDiscoveryRequest& in, exi_bitstream_t& out) {
iso20_exiDocument doc;
init_iso20_exiDocument(&doc);
CB_SET_USED(doc.ServiceDiscoveryReq);
convert(in, doc.ServiceDiscoveryReq);
return encode_iso20_exiDocument(&out, &doc);
}
template <> size_t serialize(const ServiceDiscoveryResponse& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
template <> size_t serialize(const ServiceDiscoveryRequest& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
} // namespace iso15118::message_20

View File

@@ -0,0 +1,107 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/message/service_selection.hpp>
#include <type_traits>
#include <iso15118/detail/variant_access.hpp>
#include <cbv2g/iso_20/iso20_CommonMessages_Encoder.h>
namespace iso15118::message_20 {
template <> void convert(const struct iso20_ServiceSelectionReqType& in, ServiceSelectionRequest& out) {
convert(in.Header, out.header);
cb_convert_enum(in.SelectedEnergyTransferService.ServiceID, out.selected_energy_transfer_service.service_id);
out.selected_energy_transfer_service.parameter_set_id = in.SelectedEnergyTransferService.ParameterSetID;
if (in.SelectedVASList_isUsed == true) {
auto& vas_list_out = out.selected_vas_list.emplace();
vas_list_out.clear();
for (size_t i = 0; i < in.SelectedVASList.SelectedService.arrayLen; i++) {
const auto& item_in = in.SelectedVASList.SelectedService.array[i];
auto& item_out = vas_list_out.emplace_back();
item_out.service_id = item_in.ServiceID;
item_out.parameter_set_id = item_in.ParameterSetID;
}
}
}
template <> void convert(const struct iso20_ServiceSelectionResType& in, ServiceSelectionResponse& out) {
cb_convert_enum(in.ResponseCode, out.response_code);
convert(in.Header, out.header);
}
template <> void convert(const ServiceSelectionRequest& in, iso20_ServiceSelectionReqType& out) {
init_iso20_ServiceSelectionReqType(&out);
cb_convert_enum(in.selected_energy_transfer_service.service_id, out.SelectedEnergyTransferService.ServiceID);
out.SelectedEnergyTransferService.ParameterSetID = in.selected_energy_transfer_service.parameter_set_id;
if (in.selected_vas_list.has_value()) {
out.SelectedVASList_isUsed = true;
out.SelectedVASList.SelectedService.arrayLen = in.selected_vas_list->size();
for (size_t i = 0; i < in.selected_vas_list->size(); i++) {
const auto& item_in = in.selected_vas_list->at(i);
auto& item_out = out.SelectedVASList.SelectedService.array[i];
item_out.ServiceID = item_in.service_id;
item_out.ParameterSetID = item_in.parameter_set_id;
}
} else {
out.SelectedVASList_isUsed = false;
}
convert(in.header, out.Header);
}
template <> void convert(const ServiceSelectionResponse& in, iso20_ServiceSelectionResType& out) {
init_iso20_ServiceSelectionResType(&out);
cb_convert_enum(in.response_code, out.ResponseCode);
convert(in.header, out.Header);
}
template <> void insert_type(VariantAccess& va, const struct iso20_ServiceSelectionReqType& in) {
va.insert_type<ServiceSelectionRequest>(in);
}
template <> void insert_type(VariantAccess& va, const struct iso20_ServiceSelectionResType& in) {
va.insert_type<ServiceSelectionResponse>(in);
};
template <> int serialize_to_exi(const ServiceSelectionResponse& in, exi_bitstream_t& out) {
iso20_exiDocument doc;
init_iso20_exiDocument(&doc);
CB_SET_USED(doc.ServiceSelectionRes);
convert(in, doc.ServiceSelectionRes);
return encode_iso20_exiDocument(&out, &doc);
}
template <> int serialize_to_exi(const ServiceSelectionRequest& in, exi_bitstream_t& out) {
iso20_exiDocument doc;
init_iso20_exiDocument(&doc);
CB_SET_USED(doc.ServiceSelectionReq);
convert(in, doc.ServiceSelectionReq);
return encode_iso20_exiDocument(&out, &doc);
}
template <> size_t serialize(const ServiceSelectionResponse& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
template <> size_t serialize(const ServiceSelectionRequest& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
} // namespace iso15118::message_20

View File

@@ -0,0 +1,81 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/message/session_setup.hpp>
#include <type_traits>
#include <iso15118/detail/variant_access.hpp>
#include <cbv2g/iso_20/iso20_CommonMessages_Datatypes.h>
#include <cbv2g/iso_20/iso20_CommonMessages_Encoder.h>
namespace iso15118::message_20 {
//
// conversions
//
template <> void convert(const struct iso20_SessionSetupReqType& in, SessionSetupRequest& out) {
convert(in.Header, out.header);
out.evccid = CB2CPP_STRING(in.EVCCID);
}
template <> void convert(const struct iso20_SessionSetupResType& in, SessionSetupResponse& out) {
convert(in.Header, out.header);
out.evseid = CB2CPP_STRING(in.EVSEID);
}
template <> void convert(const SessionSetupResponse& in, iso20_SessionSetupResType& out) {
init_iso20_SessionSetupResType(&out);
cb_convert_enum(in.response_code, out.ResponseCode);
CPP2CB_STRING(in.evseid, out.EVSEID);
convert(in.header, out.Header);
}
template <> void convert(const SessionSetupRequest& in, iso20_SessionSetupReqType& out) {
init_iso20_SessionSetupReqType(&out);
CPP2CB_STRING(in.evccid, out.EVCCID);
convert(in.header, out.Header);
}
template <> void insert_type(VariantAccess& va, const struct iso20_SessionSetupReqType& in) {
va.insert_type<SessionSetupRequest>(in);
};
template <> void insert_type(VariantAccess& va, const struct iso20_SessionSetupResType& in) {
va.insert_type<SessionSetupResponse>(in);
};
template <> int serialize_to_exi(const SessionSetupResponse& in, exi_bitstream_t& out) {
iso20_exiDocument doc;
init_iso20_exiDocument(&doc);
CB_SET_USED(doc.SessionSetupRes);
convert(in, doc.SessionSetupRes);
return encode_iso20_exiDocument(&out, &doc);
}
template <> int serialize_to_exi(const SessionSetupRequest& in, exi_bitstream_t& out) {
iso20_exiDocument doc;
init_iso20_exiDocument(&doc);
CB_SET_USED(doc.SessionSetupReq);
convert(in, doc.SessionSetupReq);
return encode_iso20_exiDocument(&out, &doc);
}
template <> size_t serialize(const SessionSetupResponse& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
template <> size_t serialize(const SessionSetupRequest& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
} // namespace iso15118::message_20

View File

@@ -0,0 +1,86 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/message/session_stop.hpp>
#include <type_traits>
#include <iso15118/detail/variant_access.hpp>
#include <cbv2g/iso_20/iso20_CommonMessages_Encoder.h>
namespace iso15118::message_20 {
template <> void convert(const struct iso20_SessionStopReqType& in, SessionStopRequest& out) {
convert(in.Header, out.header);
cb_convert_enum(in.ChargingSession, out.charging_session);
if (in.EVTerminationCode_isUsed) {
out.ev_termination_code = CB2CPP_STRING(in.EVTerminationCode);
}
if (in.EVTerminationExplanation_isUsed) {
out.ev_termination_explanation = CB2CPP_STRING(in.EVTerminationExplanation);
}
}
template <> void convert(const struct iso20_SessionStopResType& in, SessionStopResponse& out) {
cb_convert_enum(in.ResponseCode, out.response_code);
convert(in.Header, out.header);
}
template <> void convert(const SessionStopRequest& in, iso20_SessionStopReqType& out) {
init_iso20_SessionStopReqType(&out);
cb_convert_enum(in.charging_session, out.ChargingSession);
CPP2CB_STRING_IF_USED(in.ev_termination_code, out.EVTerminationCode);
CPP2CB_STRING_IF_USED(in.ev_termination_explanation, out.EVTerminationExplanation);
convert(in.header, out.Header);
}
template <> void insert_type(VariantAccess& va, const struct iso20_SessionStopReqType& in) {
va.insert_type<SessionStopRequest>(in);
}
template <> void insert_type(VariantAccess& va, const struct iso20_SessionStopResType& in) {
va.insert_type<SessionStopResponse>(in);
};
template <> void convert(const SessionStopResponse& in, struct iso20_SessionStopResType& out) {
init_iso20_SessionStopResType(&out);
convert(in.header, out.Header);
cb_convert_enum(in.response_code, out.ResponseCode);
}
template <> int serialize_to_exi(const SessionStopResponse& in, exi_bitstream_t& out) {
iso20_exiDocument doc;
init_iso20_exiDocument(&doc);
CB_SET_USED(doc.SessionStopRes);
convert(in, doc.SessionStopRes);
return encode_iso20_exiDocument(&out, &doc);
}
template <> int serialize_to_exi(const SessionStopRequest& in, exi_bitstream_t& out) {
iso20_exiDocument doc;
init_iso20_exiDocument(&doc);
CB_SET_USED(doc.SessionStopReq);
convert(in, doc.SessionStopReq);
return encode_iso20_exiDocument(&out, &doc);
}
template <> size_t serialize(const SessionStopResponse& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
template <> size_t serialize(const SessionStopRequest& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
} // namespace iso15118::message_20

View File

@@ -0,0 +1,94 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/message/supported_app_protocol.hpp>
#include <type_traits>
#include <iso15118/detail/cb_exi.hpp>
#include <iso15118/detail/variant_access.hpp>
#include <cbv2g/app_handshake/appHand_Encoder.h>
namespace iso15118::message_20 {
template <> void convert(const struct appHand_supportedAppProtocolReq& in, SupportedAppProtocolRequest& out) {
const auto& ap_in = in.AppProtocol;
out.app_protocol.clear();
for (size_t i = 0; i < ap_in.arrayLen; ++i) {
const auto& item_in = ap_in.array[i];
auto& item_out = out.app_protocol.emplace_back();
item_out.protocol_namespace = CB2CPP_STRING(item_in.ProtocolNamespace);
item_out.version_number_major = item_in.VersionNumberMajor;
item_out.version_number_minor = item_in.VersionNumberMinor;
item_out.schema_id = item_in.SchemaID;
item_out.priority = item_in.Priority;
}
}
template <> void convert(const SupportedAppProtocolRequest& in, struct appHand_supportedAppProtocolReq& out) {
init_appHand_supportedAppProtocolReq(&out);
auto& ap_out = out.AppProtocol;
// FIXME (aw): check size constraints
ap_out.arrayLen = in.app_protocol.size();
for (size_t i = 0; i < in.app_protocol.size(); ++i) {
const auto& item_in = in.app_protocol[i];
auto& item_out = ap_out.array[i];
CPP2CB_STRING(item_in.protocol_namespace, item_out.ProtocolNamespace);
item_out.VersionNumberMajor = item_in.version_number_major;
item_out.VersionNumberMinor = item_in.version_number_minor;
item_out.SchemaID = item_in.schema_id;
item_out.Priority = item_in.priority;
}
}
template <> void convert(const SupportedAppProtocolResponse& in, struct appHand_supportedAppProtocolRes& out) {
init_appHand_supportedAppProtocolRes(&out);
cb_convert_enum(in.response_code, out.ResponseCode);
if (in.schema_id) {
out.SchemaID = *in.schema_id;
CB_SET_USED(out.SchemaID);
}
}
template <> void insert_type(VariantAccess& va, const struct appHand_supportedAppProtocolReq& in) {
va.insert_type<SupportedAppProtocolRequest>(in);
};
template <> int serialize_to_exi(const SupportedAppProtocolResponse& in, exi_bitstream_t& out) {
appHand_exiDocument doc;
init_appHand_exiDocument(&doc);
convert(in, doc.supportedAppProtocolRes);
CB_SET_USED(doc.supportedAppProtocolRes);
return encode_appHand_exiDocument(&out, &doc);
}
template <> int serialize_to_exi(const SupportedAppProtocolRequest& in, exi_bitstream_t& out) {
appHand_exiDocument doc;
init_appHand_exiDocument(&doc);
convert(in, doc.supportedAppProtocolReq);
CB_SET_USED(doc.supportedAppProtocolReq);
return encode_appHand_exiDocument(&out, &doc);
}
template <> size_t serialize(const SupportedAppProtocolResponse& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
template <> size_t serialize(const SupportedAppProtocolRequest& in, const io::StreamOutputView& out) {
return serialize_helper(in, out);
}
} // namespace iso15118::message_20

View File

@@ -0,0 +1,187 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/message/variant.hpp>
#include <cassert>
#include <string>
#include <iso15118/detail/helper.hpp>
#include <iso15118/detail/variant_access.hpp>
#include <cbv2g/app_handshake/appHand_Decoder.h>
#include <cbv2g/iso_20/iso20_AC_Decoder.h>
#include <cbv2g/iso_20/iso20_CommonMessages_Decoder.h>
#include <cbv2g/iso_20/iso20_DC_Decoder.h>
using PayloadType = iso15118::io::v2gtp::PayloadType;
namespace iso15118::message_20 {
static void handle_sap(VariantAccess& va) {
appHand_exiDocument doc;
const auto decode_status = decode_appHand_exiDocument(&va.input_stream, &doc);
if (decode_status != 0) {
va.error = "decode_appHand_exiDocument failed with " + std::to_string(decode_status);
return;
}
if (doc.supportedAppProtocolReq_isUsed) {
insert_type(va, doc.supportedAppProtocolReq);
} else {
va.error = "chosen message type unhandled";
}
}
static void handle_main(VariantAccess& va) {
iso20_exiDocument doc;
const auto decode_status = decode_iso20_exiDocument(&va.input_stream, &doc);
if (decode_status != 0) {
va.error = "decode_iso20_exiDocument failed with " + std::to_string(decode_status);
return;
}
if (doc.SessionSetupReq_isUsed) {
insert_type(va, doc.SessionSetupReq);
} else if (doc.SessionSetupRes_isUsed) {
insert_type(va, doc.SessionSetupRes);
} else if (doc.AuthorizationSetupReq_isUsed) {
insert_type(va, doc.AuthorizationSetupReq);
} else if (doc.AuthorizationSetupRes_isUsed) {
insert_type(va, doc.AuthorizationSetupRes);
} else if (doc.AuthorizationReq_isUsed) {
insert_type(va, doc.AuthorizationReq);
} else if (doc.AuthorizationRes_isUsed) {
insert_type(va, doc.AuthorizationRes);
} else if (doc.ServiceDiscoveryReq_isUsed) {
insert_type(va, doc.ServiceDiscoveryReq);
} else if (doc.ServiceDiscoveryRes_isUsed) {
insert_type(va, doc.ServiceDiscoveryRes);
} else if (doc.ServiceDetailReq_isUsed) {
insert_type(va, doc.ServiceDetailReq);
} else if (doc.ServiceDetailRes_isUsed) {
insert_type(va, doc.ServiceDetailRes);
} else if (doc.ServiceSelectionReq_isUsed) {
insert_type(va, doc.ServiceSelectionReq);
} else if (doc.ServiceSelectionRes_isUsed) {
insert_type(va, doc.ServiceSelectionRes);
} else if (doc.ScheduleExchangeReq_isUsed) {
insert_type(va, doc.ScheduleExchangeReq);
} else if (doc.ScheduleExchangeRes_isUsed) {
insert_type(va, doc.ScheduleExchangeRes);
} else if (doc.PowerDeliveryReq_isUsed) {
insert_type(va, doc.PowerDeliveryReq);
} else if (doc.PowerDeliveryRes_isUsed) {
insert_type(va, doc.PowerDeliveryRes);
} else if (doc.SessionStopReq_isUsed) {
insert_type(va, doc.SessionStopReq);
} else if (doc.SessionStopRes_isUsed) {
insert_type(va, doc.SessionStopRes);
} else {
va.error = "chosen message type unhandled";
}
}
static void handle_dc(VariantAccess& va) {
iso20_dc_exiDocument doc;
const auto decode_status = decode_iso20_dc_exiDocument(&va.input_stream, &doc);
if (decode_status != 0) {
va.error = "decode_iso20_dc_exiDocument failed with " + std::to_string(decode_status);
return;
}
if (doc.DC_ChargeParameterDiscoveryReq_isUsed) {
insert_type(va, doc.DC_ChargeParameterDiscoveryReq);
} else if (doc.DC_ChargeParameterDiscoveryRes_isUsed) {
insert_type(va, doc.DC_ChargeParameterDiscoveryRes);
} else if (doc.DC_CableCheckReq_isUsed) {
insert_type(va, doc.DC_CableCheckReq);
} else if (doc.DC_CableCheckRes_isUsed) {
insert_type(va, doc.DC_CableCheckRes);
} else if (doc.DC_PreChargeReq_isUsed) {
insert_type(va, doc.DC_PreChargeReq);
} else if (doc.DC_PreChargeRes_isUsed) {
insert_type(va, doc.DC_PreChargeRes);
} else if (doc.DC_ChargeLoopReq_isUsed) {
insert_type(va, doc.DC_ChargeLoopReq);
} else if (doc.DC_ChargeLoopRes_isUsed) {
insert_type(va, doc.DC_ChargeLoopRes);
} else if (doc.DC_WeldingDetectionReq_isUsed) {
insert_type(va, doc.DC_WeldingDetectionReq);
} else if (doc.DC_WeldingDetectionRes_isUsed) {
insert_type(va, doc.DC_WeldingDetectionRes);
} else {
va.error = "chosen message type unhandled";
}
}
static void handle_ac(VariantAccess& va) {
iso20_ac_exiDocument doc;
const auto decode_status = decode_iso20_ac_exiDocument(&va.input_stream, &doc);
if (decode_status != 0) {
va.error = "decode_iso20_dc_exiDocument failed with " + std::to_string(decode_status);
return;
}
if (doc.AC_ChargeParameterDiscoveryReq_isUsed) {
insert_type(va, doc.AC_ChargeParameterDiscoveryReq);
} else if (doc.AC_ChargeParameterDiscoveryRes_isUsed) {
insert_type(va, doc.AC_ChargeParameterDiscoveryRes);
} else if (doc.AC_ChargeLoopReq_isUsed) {
insert_type(va, doc.AC_ChargeLoopReq);
} else if (doc.AC_ChargeLoopRes_isUsed) {
insert_type(va, doc.AC_ChargeLoopRes);
} else {
va.error = "chosen message type unhandled";
}
}
Variant::Variant(io::v2gtp::PayloadType payload_type, const io::StreamInputView& buffer_view) {
VariantAccess va{
get_exi_input_stream(buffer_view), this->data, this->type, this->custom_deleter, this->error,
};
if (payload_type == PayloadType::SAP) {
handle_sap(va);
} else if (payload_type == PayloadType::Part20Main) {
handle_main(va);
} else if (payload_type == PayloadType::Part20DC) {
handle_dc(va);
} else if (payload_type == PayloadType::Part20AC) {
handle_ac(va);
} else {
logf_warning("Unknown type");
}
if (data) {
// in case data was set, make sure the custom deleter and the type were set!
assert(custom_deleter != nullptr);
assert(type != Type::None);
} else {
logf_error("Failed due to: %s\n", error.c_str());
}
}
Variant::~Variant() {
if (data) {
custom_deleter(data);
}
}
Type Variant::get_type() const {
return type;
}
const std::string& Variant::get_error() const {
return error;
}
} // namespace iso15118::message_20

View File

@@ -0,0 +1,17 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/detail/cb_exi.hpp>
exi_bitstream_t get_exi_input_stream(const iso15118::io::StreamInputView& buffer_view) {
exi_bitstream_t exi_stream_in;
exi_bitstream_init(&exi_stream_in, const_cast<uint8_t*>(buffer_view.payload), buffer_view.payload_len, 0, nullptr);
return exi_stream_in;
}
exi_bitstream_t get_exi_output_stream(const iso15118::io::StreamOutputView& buffer_view) {
exi_bitstream_t exi_stream_out;
exi_bitstream_init(&exi_stream_out, buffer_view.payload, buffer_view.payload_len, 0, nullptr);
return exi_stream_out;
}

View File

@@ -0,0 +1,20 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/detail/helper.hpp>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <stdexcept>
namespace iso15118 {
std::string adding_err_msg(const std::string& msg) {
return (msg + " (reason: " + strerror(errno) + ")");
}
void log_and_throw(const char* msg) {
throw std::runtime_error(std::string(msg) + " (reason: " + strerror(errno) + ")");
}
} // namespace iso15118

View File

@@ -0,0 +1,33 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Pionix GmbH and Contributors to EVerest
#include <iso15118/detail/io/helper_ssl.hpp>
#include <cassert>
#include <stdexcept>
#include <openssl/err.h>
namespace iso15118::io {
static int add_error_str(const char* str, std::size_t len, void* u) {
assert(u);
auto& text = *reinterpret_cast<std::string*>(u);
text += ": " + std::string(str, len);
return 0;
}
static void log_and_raise(const std::string& error_msg) {
throw std::runtime_error(error_msg);
}
std::string log_openssl_error(const std::string& error_msg) {
std::string error_message = {error_msg};
ERR_print_errors_cb(&add_error_str, &error_message);
return error_message;
}
void log_and_raise_openssl_error(const std::string& error_msg) {
log_and_raise(log_openssl_error(error_msg));
}
} // namespace iso15118::io

View File

@@ -0,0 +1,85 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/session/feedback.hpp>
#include <iso15118/detail/helper.hpp>
namespace iso15118::session {
Feedback::Feedback(feedback::Callbacks callbacks_) : callbacks(std::move(callbacks_)) {
}
void Feedback::signal(feedback::Signal signal) const {
call_if_available(callbacks.signal, signal);
}
void Feedback::dc_pre_charge_target_voltage(float voltage) const {
call_if_available(callbacks.dc_pre_charge_target_voltage, voltage);
}
void Feedback::dc_charge_loop_req(const feedback::DcChargeLoopReq& req_values) const {
call_if_available(callbacks.dc_charge_loop_req, req_values);
}
void Feedback::dc_max_limits(const feedback::DcMaximumLimits& max_limits) const {
call_if_available(callbacks.dc_max_limits, max_limits);
}
void Feedback::ac_charge_loop_req(const feedback::AcChargeLoopReq& req_values) const {
call_if_available(callbacks.ac_charge_loop_req, req_values);
}
void Feedback::v2g_message(const message_20::Type& v2g_message) const {
call_if_available(callbacks.v2g_message, v2g_message);
}
void Feedback::evcc_id(const std::string& evccid) const {
call_if_available(callbacks.evccid, evccid);
}
void Feedback::selected_protocol(const std::string& selected_protocol) const {
call_if_available(callbacks.selected_protocol, selected_protocol);
}
void Feedback::notify_ev_charging_needs(
const dt::ServiceCategory& service_category, const std::optional<dt::AcConnector>& ac_connector,
const dt::ControlMode& control_mode, const dt::MobilityNeedsMode& mobility_needs_mode,
const feedback::EvseTransferLimits& evse_limits, const feedback::EvTransferLimits& ev_limits,
const feedback::EvSEControlMode& ev_control_mode,
const std::vector<message_20::datatypes::ServiceCategory>& ev_energy_services) const {
call_if_available(callbacks.notify_ev_charging_needs, service_category, ac_connector, control_mode,
mobility_needs_mode, evse_limits, ev_limits, ev_control_mode, ev_energy_services);
}
void Feedback::selected_service_parameters(const d20::SelectedServiceParameters& services) const {
call_if_available(callbacks.selected_service_parameters, services);
}
void Feedback::ev_information(const d20::EVInformation& ev_information) const {
call_if_available(callbacks.ev_information, ev_information);
}
void Feedback::ev_termination(const std::string& ev_termination_code,
const std::string& ev_termination_explanation) const {
call_if_available(callbacks.ev_termination, ev_termination_code, ev_termination_explanation);
}
std::optional<dt::ServiceParameterList> Feedback::get_vas_parameters(uint16_t vas_id) const {
logf_warning("Caution: This feedback call can block the entire state machine");
if (not callbacks.get_vas_parameters) {
return std::nullopt;
}
return std::invoke(callbacks.get_vas_parameters, vas_id);
}
void Feedback::selected_vas_services(const dt::VasSelectedServiceList& vas_services) const {
call_if_available(callbacks.selected_vas_services, vas_services);
}
void Feedback::ac_limits(const feedback::AcLimits& limits) const {
call_if_available(callbacks.ac_limits, limits);
}
} // namespace iso15118::session

View File

@@ -0,0 +1,328 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/session/iso.hpp>
#include <cassert>
#include <chrono>
#include <cstring>
#include <thread>
#include <arpa/inet.h>
#include <iso15118/d20/state/supported_app_protocol.hpp>
#include <iso15118/detail/helper.hpp>
namespace iso15118 {
static constexpr auto SESSION_IDLE_TIMEOUT_MS = 5000;
static constexpr auto MIN_RESPONSE_INTERVAL_MS = 100; // minimum time between two response messages
static void log_sdp_packet(const iso15118::io::SdpPacket& sdp) {
static constexpr auto ESCAPED_BYTE_CHAR_COUNT = 4;
auto payload_string_buffer = std::make_unique<char[]>(sdp.get_payload_length() * ESCAPED_BYTE_CHAR_COUNT + 1);
for (std::size_t i = 0; i < sdp.get_payload_length(); ++i) {
snprintf(payload_string_buffer.get() + i * ESCAPED_BYTE_CHAR_COUNT, ESCAPED_BYTE_CHAR_COUNT + 1, "\\x%02hx",
sdp.get_payload_buffer()[i]);
}
iso15118::logf_info("[SDP Packet in]: Header: %04hx, Payload: %s", sdp.get_payload_type(),
payload_string_buffer.get());
}
static void log_packet_from_car(const iso15118::io::SdpPacket& packet, session::SessionLogger& logger) {
logger.exi(static_cast<uint16_t>(packet.get_payload_type()), packet.get_payload_buffer(),
packet.get_payload_length(), session::logging::ExiMessageDirection::FROM_EV);
}
static std::unique_ptr<message_20::Variant> make_variant_from_packet(const iso15118::io::SdpPacket& packet) {
return std::make_unique<message_20::Variant>(
packet.get_payload_type(), io::StreamInputView{packet.get_payload_buffer(), packet.get_payload_length()});
}
void raise_invalid_packet_state(const io::SdpPacket& sdp_packet) {
using PacketState = io::SdpPacket::State;
auto error = std::string("Error while reading sdp packet: ");
switch (sdp_packet.get_state()) {
case PacketState::INVALID_HEADER:
error += "invalid sdp packet header";
break;
case PacketState::PAYLOAD_TOO_LONG:
error += "packet too large for buffer";
break;
default:
assert(false);
}
log_and_throw(error.c_str());
}
// NOTE (aw): this function return true, if it would block to read a complete packet
// if it returns false, the packet is complete
bool read_single_sdp_packet(io::IConnection& connection, io::SdpPacket& sdp_packet) {
// NOTE (aw): not happy with this function
// main problem is, that it combines too much logic of the sdp packet and io related stuff
using PacketState = io::SdpPacket::State;
assert(sdp_packet.get_state() == PacketState::BUFFER_EMPTY || sdp_packet.get_state() == PacketState::HEADER_READ);
const auto first_try =
connection.read(sdp_packet.get_current_buffer_pos(), sdp_packet.get_remaining_bytes_to_read());
sdp_packet.update_read_bytes(first_try.bytes_read);
if (first_try.would_block) {
// need more data for at least the header
return true;
}
if (sdp_packet.get_state() == PacketState::COMPLETE) {
// done
return false;
}
// packet not finished
if (sdp_packet.get_state() != PacketState::HEADER_READ) {
raise_invalid_packet_state(sdp_packet);
}
// header read successfully, try to read the rest
const auto second_try =
connection.read(sdp_packet.get_current_buffer_pos(), sdp_packet.get_remaining_bytes_to_read());
sdp_packet.update_read_bytes(second_try.bytes_read);
if (second_try.would_block) {
// need more data for the rest of the packet!
return true;
}
// assert finished packet
if (sdp_packet.get_state() != PacketState::COMPLETE) {
raise_invalid_packet_state(sdp_packet);
}
return false;
}
static size_t setup_response_header(uint8_t* buffer, iso15118::io::v2gtp::PayloadType payload_type, size_t size) {
buffer[0] = iso15118::io::SDP_PROTOCOL_VERSION;
buffer[1] = iso15118::io::SDP_INVERSE_PROTOCOL_VERSION;
const uint16_t response_payload_type =
htons(static_cast<std::underlying_type_t<iso15118::io::v2gtp::PayloadType>>(payload_type));
std::memcpy(buffer + 2, &response_payload_type, sizeof(response_payload_type));
const uint32_t tmp32 = htonl(size);
std::memcpy(buffer + 4, &tmp32, sizeof(tmp32));
return size + iso15118::io::SdpPacket::V2GTP_HEADER_SIZE;
}
Session::Session(std::unique_ptr<io::IConnection> connection_, d20::SessionConfig session_config,
const session::feedback::Callbacks& callbacks, std::optional<d20::PauseContext>& pause_ctx) :
connection(std::move(connection_)),
log(this),
ctx(callbacks, log, std::move(session_config), pause_ctx, active_control_event, message_exchange, timeouts),
fsm(ctx.create_state<d20::state::SupportedAppProtocol>()) {
next_session_event = offset_time_point_by_ms(get_current_time_point(), SESSION_IDLE_TIMEOUT_MS);
connection->set_event_callback([this](io::ConnectionEvent event) { this->handle_connection_event(event); });
}
Session::~Session() = default;
void Session::push_control_event(const d20::ControlEvent& event) {
control_event_queue.push(event);
}
TimePoint const& Session::poll() {
const auto now = get_current_time_point();
// This is the default next session event, which is used when nothing else happens.
next_session_event = offset_time_point_by_ms(now, SESSION_IDLE_TIMEOUT_MS);
if (not state.connected) {
// nothing happened so far, just return
return next_session_event;
}
// check for new data to read
if (state.new_data) {
const bool would_block = read_single_sdp_packet(*connection, packet);
if (would_block) {
state.new_data = false;
}
}
// send all of our queued control events
while ((active_control_event = control_event_queue.pop()) != std::nullopt) {
if (const auto control_data = ctx.get_control_event<d20::DcTransferLimits>()) {
ctx.session_config.dc_limits = *control_data;
} else if (const auto control_data = ctx.get_control_event<d20::EnergyServices>()) {
ctx.session_config.supported_energy_transfer_services = *control_data;
} else if (const auto control_data = ctx.get_control_event<d20::SupportedVASs>()) {
ctx.session_config.supported_vas_services = *control_data;
} else if (const auto control_data = ctx.get_control_event<d20::AcTransferLimits>()) {
ctx.session_config.ac_limits = *control_data;
} else if (const auto control_data = ctx.get_control_event<d20::UpdateDynamicModeParameters>()) {
ctx.cache_dynamic_mode_parameters.emplace(*control_data);
} else if (const auto control_data = ctx.get_control_event<d20::AcTargetPower>()) {
ctx.cache_ac_target_power.emplace(*control_data);
} else if (const auto control_data = ctx.get_control_event<d20::AcPresentPower>()) {
ctx.cache_ac_present_power.emplace(*control_data);
}
// Save some control events. It can happen that these events are sent before the corresponding state. They are
// stored temporarily here.
// TODO(sl): Construct ControlEventCache Struct
[[maybe_unused]] const auto res = fsm.feed(d20::Event::CONTROL_MESSAGE);
// FIXME (aw): check result!
}
const auto timeouts_reached = timeouts.check();
if (timeouts_reached.has_value()) {
const auto& reached = timeouts_reached.value();
for (const auto& timeout : reached) {
if (timeout == d20::TimeoutType::SEQUENCE) {
logf_error("Sequence Timeout 40secs is reached. Stopping the session");
ctx.session_stopped = true;
break;
} else {
ctx.set_active_timeout(timeout);
[[maybe_unused]] const auto res = fsm.feed(d20::Event::TIMEOUT);
timeouts.reset_timeout(timeout);
}
}
}
// check for complete sdp packet
if (packet.is_complete()) {
// FIXME (aw): this event loop only acts on new packets, seems to be enough for now ...
log_packet_from_car(packet, log);
message_exchange.set_request(make_variant_from_packet(packet));
packet = {}; // reset the packet
const auto request_msg_type = ctx.peek_request_type();
// There is no sequence timer before SupportedAppProtocol
if (request_msg_type != message_20::Type::SupportedAppProtocolReq) {
timeouts.stop_timeout(d20::TimeoutType::SEQUENCE);
}
ctx.feedback.v2g_message(request_msg_type);
[[maybe_unused]] const auto res = fsm.feed(d20::Event::V2GTP_MESSAGE);
// FIXME(sl): check result!
}
if (message_exchange.has_response()) {
// Before we send back the response message, we check the time between two response messages
// sent out. If this is less than MIN_RESPONSE_INTERVAL_MS, we delay the response message to
// avoid potential performance issues.
if (not response_send_after.has_value()) {
response_send_after = now;
if (last_response_tx_time.has_value()) {
const auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
get_current_time_point() - last_response_tx_time.value());
if (elapsed < std::chrono::milliseconds(MIN_RESPONSE_INTERVAL_MS)) {
response_send_after =
offset_time_point_by_ms(last_response_tx_time.value(), MIN_RESPONSE_INTERVAL_MS);
}
}
}
// Send the response as soon as the response interval is reached
if (response_send_after.has_value() && now < response_send_after.value()) {
next_session_event = response_send_after.value();
} else {
response_send_after.reset();
// FIXME(fh): Currently, the response is still generated when the request is received.
// This may have changed after the delay. Ideally, the processing of the request message
// and the generated of the response message should be decoupled from each other.
send_response();
}
} else {
response_send_after.reset();
}
if (is_finished()) {
// TODO(SL): Does this also apply when a timeout is triggered? Or should the TCP/TLS connection be terminated
// directly?
// Wait for 5 seconds [V2G20-1643]
std::this_thread::sleep_for(std::chrono::seconds(5));
connection->close();
const auto signal =
(ctx.session_paused) ? session::feedback::Signal::DLINK_PAUSE : session::feedback::Signal::DLINK_TERMINATE;
ctx.feedback.signal(signal);
}
return next_session_event;
}
void Session::send_response() {
const auto [got_response, payload_size, stored_payload_type, stored_response_type] =
message_exchange.check_and_clear_response();
if (not got_response) {
return;
}
const auto response_size = setup_response_header(response_buffer, stored_payload_type, payload_size);
connection->write(response_buffer, response_size);
last_response_tx_time = get_current_time_point();
timeouts.start_timeout(d20::TimeoutType::SEQUENCE, d20::TIMEOUT_SEQUENCE);
// FIXME (aw): this is hacky ...
log.exi(static_cast<uint16_t>(stored_payload_type), response_buffer + io::SdpPacket::V2GTP_HEADER_SIZE,
payload_size, session::logging::ExiMessageDirection::TO_EV);
ctx.feedback.v2g_message(stored_response_type);
}
void Session::handle_connection_event(io::ConnectionEvent event) {
using Event = io::ConnectionEvent;
switch (event) {
case Event::ACCEPTED:
assert(state.connected == false);
state.connected = true;
log("Accepted connection on port %d", connection->get_public_endpoint().port);
return;
case Event::NEW_DATA:
assert(state.connected);
state.new_data = true;
return;
case Event::OPEN:
assert(state.connected);
if (const auto new_vehicle_cert_hash = connection->get_vehicle_cert_hash()) {
logf_info("Vehicle Cert is available");
ctx.set_new_vehicle_cert_hash(new_vehicle_cert_hash);
}
// NOTE (aw): for now, we don't really need this information ...
return;
case Event::CLOSED:
state.connected = false;
logf_info("Connection is closed");
return;
}
}
void Session::close() {
connection->close();
ctx.feedback.signal(session::feedback::Signal::DLINK_TERMINATE);
ctx.session_stopped = true;
}
} // namespace iso15118

View File

@@ -0,0 +1,61 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/session/logger.hpp>
#include <iso15118/detail/helper.hpp>
static iso15118::session::logging::Callback session_log_callback{nullptr};
namespace iso15118::session {
SessionLogger::SessionLogger(void* id_) : id(reinterpret_cast<std::uintptr_t>(id_)){};
void SessionLogger::event(const std::string& info) const {
logging::SimpleEvent event{std::chrono::system_clock::now(), info};
session_log_callback(this->id, std::move(event));
}
void SessionLogger::exi(uint16_t payload_type, uint8_t const* data, size_t len,
logging::ExiMessageDirection direction) const {
logging::ExiMessageEvent event{
std::chrono::system_clock::now(), payload_type, data, len, direction,
};
session_log_callback(this->id, std::move(event));
}
void SessionLogger::enter_state(const std::string& new_state) {
if (last_state_name.size()) {
this->operator()("Transition (%s -> %s)", last_state_name.c_str(), new_state.c_str());
} else {
this->operator()("Transition (entered %s)", new_state.c_str());
}
last_state_name = std::move(new_state);
}
void SessionLogger::operator()(const std::string& info) const {
event(info);
}
void SessionLogger::operator()(const char* format, ...) const {
static constexpr auto MAX_FMT_LOG_BUFSIZE = 1024;
char msg_buf[MAX_FMT_LOG_BUFSIZE];
va_list args;
va_start(args, format);
vsnprintf(msg_buf, MAX_FMT_LOG_BUFSIZE, format, args);
va_end(args);
event(msg_buf);
}
namespace logging {
void set_session_log_callback(const logging::Callback& callback) {
session_log_callback = callback;
}
} // namespace logging
} // namespace iso15118::session

View File

@@ -0,0 +1,216 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
#include <iso15118/tbd_controller.hpp>
#include <algorithm>
#include <chrono>
#include <cstdio>
#include <iso15118/io/connection_plain.hpp>
#include <iso15118/io/connection_ssl.hpp>
#include <iso15118/session/iso.hpp>
#include <iso15118/detail/helper.hpp>
#include <iso15118/detail/io/socket_helper.hpp>
namespace iso15118 {
TbdController::TbdController(TbdConfig config_, session::feedback::Callbacks callbacks_, d20::EvseSetupConfig setup_) :
config(std::move(config_)),
callbacks(std::move(callbacks_)),
evse_setup(std::move(setup_)),
interface_name(config.interface_name) {
const auto result_interface_check = io::check_and_update_interface(interface_name);
if (result_interface_check) {
logf_info("Using ethernet interface: %s", interface_name.c_str());
} else {
throw std::runtime_error("Ethernet interface was not found!");
}
if (config.enable_sdp_server) {
sdp_server = std::make_unique<io::SdpServer>(interface_name);
poll_manager.register_fd(sdp_server->get_fd(), [this]() { handle_sdp_server_input(); });
}
}
void TbdController::loop() {
static constexpr auto POLL_MANAGER_TIMEOUT_MS = 50;
if (not config.enable_sdp_server) {
auto connection = std::make_unique<io::ConnectionPlain>(poll_manager, interface_name);
session =
std::make_unique<Session>(std::move(connection), d20::SessionConfig(evse_setup), callbacks, pause_ctx);
}
auto next_event = get_current_time_point();
while (true) {
const auto poll_timeout_ms = get_timeout_ms_until(next_event, POLL_MANAGER_TIMEOUT_MS);
try {
poll_manager.poll(poll_timeout_ms);
} catch (const std::runtime_error& e) {
logf_error("Shutdown loop() because of: %s", e.what());
break;
}
next_event = offset_time_point_by_ms(get_current_time_point(), POLL_MANAGER_TIMEOUT_MS);
if (communication_setup_timeout && communication_setup_timeout->is_reached()) {
logf_warning("V2G communication setup timeout (18s) expired before session was established");
communication_setup_timeout.reset();
if (sdp_server) {
sdp_server->set_dlink_ready(false);
}
callbacks.signal(session::feedback::Signal::DLINK_ERROR);
}
if (session) {
try {
const auto next_session_event = session->poll();
next_event = std::min(next_event, next_session_event);
} catch (const std::runtime_error& e) {
logf_error("Shutting down session because of: %s", e.what());
logf_info("Restarting session ...");
session->close();
}
if (session->is_finished()) {
session.reset();
if (not config.enable_sdp_server) {
auto connection = std::make_unique<io::ConnectionPlain>(poll_manager, interface_name);
session = std::make_unique<Session>(std::move(connection), d20::SessionConfig(evse_setup),
callbacks, pause_ctx);
}
}
}
}
}
void TbdController::send_control_event(const d20::ControlEvent& event) {
if (session) {
session->push_control_event(event);
}
}
void TbdController::update_authorization_services(const std::vector<message_20::datatypes::Authorization>& services,
bool cert_install_service) {
evse_setup.enable_certificate_install_service = cert_install_service;
if (services.empty()) {
logf_warning("The authorization services are not updated because services are empty!");
return;
}
evse_setup.authorization_services = services;
}
void TbdController::update_dc_limits(const d20::DcTransferLimits& limits) {
evse_setup.dc_limits = limits;
if (session) {
session->push_control_event(limits);
}
}
void TbdController::update_powersupply_limits(const d20::DcTransferLimits& limits) {
evse_setup.powersupply_limits = limits;
}
void TbdController::update_energy_modes(const std::vector<message_20::datatypes::ServiceCategory>& modes) {
evse_setup.supported_energy_services = modes;
if (session) {
session->push_control_event(modes);
}
}
void TbdController::update_supported_vas_services(const d20::SupportedVASs& vas_services) {
evse_setup.supported_vas_services = vas_services;
if (session) {
session->push_control_event(vas_services);
}
}
void TbdController::update_ac_limits(const d20::AcTransferLimits& limits) {
evse_setup.ac_limits = limits;
if (session) {
session->push_control_event(limits);
}
}
void TbdController::set_dlink_ready(bool ready) {
if (sdp_server) {
sdp_server->set_dlink_ready(ready);
}
if (ready) {
communication_setup_timeout.emplace(V2G_COMMUNICATION_SETUP_TIMEOUT_MS);
logf_info("V2G communication setup timeout started (%u ms)", V2G_COMMUNICATION_SETUP_TIMEOUT_MS);
} else {
communication_setup_timeout.reset();
}
}
void TbdController::handle_sdp_server_input() {
auto request = sdp_server->get_peer_request();
if (not sdp_server->is_dlink_ready()) {
logf_info("Ignoring SDP request because dlink is not ready");
return;
}
if (session) {
logf_warning("Ignoring sdp request message because a session is already created and running");
return;
}
if (not request) {
return;
}
switch (config.tls_negotiation_strategy) {
case config::TlsNegotiationStrategy::ACCEPT_CLIENT_OFFER:
// nothing to change
break;
case config::TlsNegotiationStrategy::ENFORCE_TLS:
request.security = io::v2gtp::Security::TLS;
break;
case config::TlsNegotiationStrategy::ENFORCE_NO_TLS:
request.security = io::v2gtp::Security::NO_TRANSPORT_SECURITY;
break;
}
auto connection = [this](bool secure_connection) -> std::unique_ptr<io::IConnection> {
try {
if (secure_connection) {
return std::make_unique<io::ConnectionSSL>(poll_manager, interface_name, config.ssl);
}
return std::make_unique<io::ConnectionPlain>(poll_manager, interface_name);
} catch (const std::runtime_error& e) {
logf_error("%s", e.what());
return nullptr;
}
}(request.security == io::v2gtp::Security::TLS);
if (not connection) {
logf_error("A TCP/TLS connection could not be established. Ignoring this SDP request for now");
return;
}
const auto ipv6_endpoint = connection->get_public_endpoint();
session = std::make_unique<Session>(std::move(connection), d20::SessionConfig(evse_setup), callbacks, pause_ctx);
communication_setup_timeout.reset();
sdp_server->send_response(request, ipv6_endpoint);
}
} // namespace iso15118