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:
@@ -0,0 +1 @@
|
||||
add_subdirectory(iso15118)
|
||||
@@ -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})
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user