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:
233
tools/EVerest-main/lib/everest/ocpp/lib/CMakeLists.txt
Normal file
233
tools/EVerest-main/lib/everest/ocpp/lib/CMakeLists.txt
Normal file
@@ -0,0 +1,233 @@
|
||||
# OCPP library
|
||||
add_library(ocpp)
|
||||
add_library(everest::ocpp ALIAS ocpp)
|
||||
|
||||
target_compile_options(ocpp
|
||||
PRIVATE
|
||||
#-Werror # turn warnings into errors
|
||||
-Wimplicit-fallthrough # avoid unintended fallthroughs
|
||||
-pedantic-errors
|
||||
-Werror=switch-enum
|
||||
)
|
||||
|
||||
target_compile_definitions(ocpp
|
||||
PRIVATE
|
||||
MIGRATION_FILE_VERSION_V16=${MIGRATION_FILE_VERSION_V16}
|
||||
MIGRATION_FILE_VERSION_V2=${MIGRATION_FILE_VERSION_V2}
|
||||
MIGRATION_DEVICE_MODEL_FILE_VERSION_V2=${MIGRATION_DEVICE_MODEL_FILE_VERSION_V2}
|
||||
)
|
||||
|
||||
target_sources(ocpp
|
||||
PRIVATE
|
||||
ocpp/common/call_types.cpp
|
||||
ocpp/common/charging_station_base.cpp
|
||||
ocpp/common/connectivity_manager.cpp
|
||||
ocpp/common/ocpp_logging.cpp
|
||||
ocpp/common/schemas.cpp
|
||||
ocpp/common/types.cpp
|
||||
ocpp/common/utils.cpp
|
||||
ocpp/common/evse_security_impl.cpp
|
||||
ocpp/common/evse_security.cpp
|
||||
ocpp/common/database/database_handler_common.cpp
|
||||
)
|
||||
|
||||
if(LIBOCPP_ENABLE_V16)
|
||||
target_sources(ocpp
|
||||
PRIVATE
|
||||
ocpp/v16/charge_point.cpp
|
||||
ocpp/v16/database_handler.cpp
|
||||
ocpp/v16/charge_point_impl.cpp
|
||||
ocpp/v16/message_dispatcher.cpp
|
||||
ocpp/v16/smart_charging.cpp
|
||||
ocpp/v16/known_keys.cpp
|
||||
ocpp/v16/charge_point_configuration_base.cpp
|
||||
ocpp/v16/charge_point_configuration_devicemodel.cpp
|
||||
ocpp/v16/charge_point_configuration.cpp
|
||||
ocpp/v16/charge_point_state_machine.cpp
|
||||
ocpp/v16/message_queue.cpp
|
||||
ocpp/v16/profile.cpp
|
||||
ocpp/v16/transaction.cpp
|
||||
ocpp/v16/ocpp_enums.cpp
|
||||
ocpp/v16/ocpp_types.cpp
|
||||
ocpp/v16/types.cpp
|
||||
ocpp/v16/utils.cpp
|
||||
ocpp/v2/messages/InstallCertificate.cpp
|
||||
ocpp/v2/messages/CertificateSigned.cpp
|
||||
ocpp/v2/messages/Authorize.cpp
|
||||
ocpp/v2/messages/GetCertificateStatus.cpp
|
||||
ocpp/v2/messages/Get15118EVCertificate.cpp
|
||||
ocpp/v2/messages/DeleteCertificate.cpp
|
||||
ocpp/v2/messages/TriggerMessage.cpp
|
||||
ocpp/v2/ocpp_enums.cpp
|
||||
ocpp/v2/ocpp_types.cpp
|
||||
ocpp/v2/types.cpp
|
||||
ocpp/v2/messages/SignCertificate.cpp
|
||||
ocpp/v2/messages/GetInstalledCertificateIds.cpp
|
||||
)
|
||||
add_subdirectory(ocpp/v16/messages)
|
||||
endif()
|
||||
|
||||
if(LIBOCPP_ENABLE_V2)
|
||||
target_sources(ocpp
|
||||
PRIVATE
|
||||
ocpp/v2/average_meter_values.cpp
|
||||
ocpp/v2/charge_point.cpp
|
||||
ocpp/v2/charge_point_callbacks.cpp
|
||||
ocpp/v2/connector.cpp
|
||||
ocpp/v2/ctrlr_component_variables.cpp
|
||||
ocpp/v2/database_handler.cpp
|
||||
ocpp/v2/device_model.cpp
|
||||
ocpp/v2/device_model_storage_sqlite.cpp
|
||||
ocpp/v2/enums.cpp
|
||||
ocpp/v2/evse.cpp
|
||||
ocpp/v2/evse_manager.cpp
|
||||
ocpp/v2/init_device_model_db.cpp
|
||||
ocpp/v2/notify_report_requests_splitter.cpp
|
||||
ocpp/v2/message_queue.cpp
|
||||
ocpp/v2/ocpp_enums.cpp
|
||||
ocpp/v2/profile.cpp
|
||||
ocpp/v2/ocpp_types.cpp
|
||||
ocpp/v2/ocsp_updater.cpp
|
||||
ocpp/v2/monitoring_updater.cpp
|
||||
ocpp/v2/transaction.cpp
|
||||
ocpp/v2/types.cpp
|
||||
ocpp/v2/utils.cpp
|
||||
ocpp/v2/component_state_manager.cpp
|
||||
ocpp/v2/message_dispatcher.cpp
|
||||
ocpp/v2/functional_blocks/authorization.cpp
|
||||
ocpp/v2/functional_blocks/availability.cpp
|
||||
ocpp/v2/functional_blocks/security.cpp
|
||||
ocpp/v2/functional_blocks/smart_charging.cpp
|
||||
ocpp/v2/functional_blocks/data_transfer.cpp
|
||||
ocpp/v2/functional_blocks/firmware_update.cpp
|
||||
ocpp/v2/functional_blocks/reservation.cpp
|
||||
ocpp/v2/functional_blocks/diagnostics.cpp
|
||||
ocpp/v2/functional_blocks/display_message.cpp
|
||||
ocpp/v2/functional_blocks/meter_values.cpp
|
||||
ocpp/v2/functional_blocks/provisioning.cpp
|
||||
ocpp/v2/functional_blocks/remote_transaction_control.cpp
|
||||
ocpp/v2/functional_blocks/tariff_and_cost.cpp
|
||||
ocpp/v2/functional_blocks/transaction.cpp
|
||||
ocpp/v21/functional_blocks/bidirectional.cpp
|
||||
ocpp/v21/functional_blocks/der_control.cpp
|
||||
)
|
||||
add_subdirectory(ocpp/v2/messages)
|
||||
add_subdirectory(ocpp/v21/messages)
|
||||
|
||||
# Embed the canonical NetworkConfiguration_1 schema as a build-time C++ string so
|
||||
# blob migration can bootstrap a per-slot component without a filesystem dependency.
|
||||
set(OCPP_NETWORK_CONFIGURATION_DEFAULT_SCHEMA_SOURCE
|
||||
"${PROJECT_SOURCE_DIR}/config/common/component_config/standardized/NetworkConfiguration_1.json")
|
||||
file(READ "${OCPP_NETWORK_CONFIGURATION_DEFAULT_SCHEMA_SOURCE}"
|
||||
OCPP_NETWORK_CONFIGURATION_DEFAULT_SCHEMA_JSON)
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
|
||||
"${OCPP_NETWORK_CONFIGURATION_DEFAULT_SCHEMA_SOURCE}")
|
||||
set(OCPP_NETWORK_CONFIGURATION_DEFAULT_SCHEMA_GENERATED
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/generated/network_configuration_default_schema.cpp")
|
||||
configure_file(
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/ocpp/v2/network_configuration_default_schema.cpp.in"
|
||||
"${OCPP_NETWORK_CONFIGURATION_DEFAULT_SCHEMA_GENERATED}"
|
||||
@ONLY)
|
||||
target_sources(ocpp PRIVATE
|
||||
"${OCPP_NETWORK_CONFIGURATION_DEFAULT_SCHEMA_GENERATED}")
|
||||
set(OCPP_NETWORK_CONFIGURATION_DEFAULT_SCHEMA_GENERATED
|
||||
"${OCPP_NETWORK_CONFIGURATION_DEFAULT_SCHEMA_GENERATED}" PARENT_SCOPE)
|
||||
endif()
|
||||
|
||||
add_subdirectory(ocpp/common/websocket)
|
||||
|
||||
option(LIBOCPP_USE_BOOST_FILESYSTEM "Usage of boost/filesystem.hpp instead of std::filesystem" OFF)
|
||||
|
||||
target_include_directories(ocpp
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
||||
)
|
||||
|
||||
target_include_directories(ocpp
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/3rd_party>
|
||||
)
|
||||
|
||||
#############
|
||||
# Logging configuration
|
||||
#
|
||||
# Set EVEREST_CUSTOM_LOGGING_LIBRARY to use a custom library.
|
||||
# Alternatively set EVEREST_CUSTOM_LOGGING_INCLUDE_PATH to use a custom header file.
|
||||
#
|
||||
# Both options need to make a header file available on path "everest/logging.hpp"
|
||||
#
|
||||
# To use the normal library, don't set any of these options.
|
||||
#
|
||||
#############
|
||||
if (EVEREST_CUSTOM_LOGGING_LIBRARY)
|
||||
if(NOT TARGET ${EVEREST_CUSTOM_LOGGING_LIBRARY})
|
||||
message(FATAL_ERROR "${EVEREST_CUSTOM_LOGGING_LIBRARY} is not a valid library")
|
||||
else()
|
||||
target_link_libraries(ocpp
|
||||
PUBLIC
|
||||
${EVEREST_CUSTOM_LOGGING_LIBRARY}
|
||||
)
|
||||
message(STATUS "Using custom logging library: ${EVEREST_CUSTOM_LOGGING_LIBRARY}")
|
||||
endif()
|
||||
elseif(EVEREST_CUSTOM_LOGGING_INCLUDE_PATH)
|
||||
if (NOT EXISTS "${EVEREST_CUSTOM_LOGGING_INCLUDE_PATH}/everest/logging.hpp")
|
||||
message(FATAL_ERROR "everest/logging.hpp not found in directory ${EVEREST_CUSTOM_LOGGING_INCLUDE_PATH}")
|
||||
else()
|
||||
target_include_directories(ocpp
|
||||
PUBLIC
|
||||
include
|
||||
${EVEREST_CUSTOM_LOGGING_INCLUDE_PATH}
|
||||
)
|
||||
message(STATUS "Using the following logging header: ${EVEREST_CUSTOM_LOGGING_INCLUDE_PATH}/everest/logging.hpp")
|
||||
endif()
|
||||
else()
|
||||
target_link_libraries(ocpp
|
||||
PUBLIC
|
||||
everest::log
|
||||
)
|
||||
message(STATUS "Using the default logging header")
|
||||
endif()
|
||||
|
||||
#############
|
||||
# End logging configuration
|
||||
#############
|
||||
|
||||
target_link_libraries(ocpp
|
||||
PUBLIC
|
||||
everest::timer
|
||||
everest::util
|
||||
nlohmann_json_schema_validator
|
||||
everest::evse_security
|
||||
websockets_shared
|
||||
everest::sqlite
|
||||
PRIVATE
|
||||
OpenSSL::SSL
|
||||
OpenSSL::Crypto
|
||||
SQLite::SQLite3
|
||||
Threads::Threads
|
||||
nlohmann_json::nlohmann_json
|
||||
date::date-tz
|
||||
)
|
||||
|
||||
if(LIBOCPP_USE_BOOST_FILESYSTEM)
|
||||
find_package(Boost REQUIRED COMPONENTS filesystem)
|
||||
target_link_libraries(ocpp
|
||||
PRIVATE
|
||||
Boost::filesystem
|
||||
)
|
||||
target_compile_definitions(ocpp
|
||||
PRIVATE
|
||||
LIBOCPP_USE_BOOST_FILESYSTEM
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(ocpp
|
||||
PUBLIC
|
||||
${ATOMIC_LIBS}
|
||||
)
|
||||
|
||||
# FIXME (aw): right now nlohmann_json and boost::optional don't compile
|
||||
# with gcc 10.x and C++11/14, so we need to publish the
|
||||
# C++17 standard
|
||||
target_compile_features(ocpp PUBLIC cxx_std_17)
|
||||
@@ -0,0 +1,32 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <ocpp/common/call_types.hpp>
|
||||
|
||||
#include <boost/uuid/uuid.hpp>
|
||||
#include <boost/uuid/uuid_generators.hpp>
|
||||
#include <boost/uuid/uuid_io.hpp>
|
||||
|
||||
namespace ocpp {
|
||||
|
||||
MessageId create_message_id() {
|
||||
static boost::uuids::random_generator uuid_generator;
|
||||
const boost::uuids::uuid uuid = uuid_generator();
|
||||
std::stringstream s;
|
||||
s << uuid;
|
||||
return MessageId(s.str());
|
||||
}
|
||||
|
||||
bool operator<(const MessageId& lhs, const MessageId& rhs) {
|
||||
return lhs.get() < rhs.get();
|
||||
}
|
||||
|
||||
void to_json(json& j, const MessageId& k) {
|
||||
j = json(k.get());
|
||||
}
|
||||
|
||||
void from_json(const json& j, MessageId& k) {
|
||||
k.set(j);
|
||||
}
|
||||
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,31 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <boost/make_shared.hpp>
|
||||
#include <ocpp/common/charging_station_base.hpp>
|
||||
|
||||
namespace ocpp {
|
||||
ChargingStationBase::ChargingStationBase(const std::shared_ptr<EvseSecurity>& evse_security,
|
||||
const std::optional<SecurityConfiguration>& security_configuration) {
|
||||
|
||||
if (evse_security != nullptr) {
|
||||
this->evse_security = evse_security;
|
||||
} else {
|
||||
if (!security_configuration.has_value()) {
|
||||
throw std::runtime_error("No implementation of EvseSecurity interface and no SecurityConfiguration "
|
||||
"provided to chargepoint constructor. One of options must be set");
|
||||
}
|
||||
this->evse_security = std::make_shared<EvseSecurityImpl>(security_configuration.value());
|
||||
}
|
||||
this->work = boost::make_shared<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>>(
|
||||
boost::asio::make_work_guard(this->io_context));
|
||||
this->io_context_thread = std::thread([this]() { this->io_context.run(); });
|
||||
}
|
||||
|
||||
ChargingStationBase::~ChargingStationBase() {
|
||||
work->get_executor().context().stop();
|
||||
io_context.stop();
|
||||
io_context_thread.join();
|
||||
}
|
||||
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,853 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2024 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <ocpp/common/connectivity_manager.hpp>
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include <everest/logging.hpp>
|
||||
#include <ocpp/v2/ctrlr_component_variables.hpp>
|
||||
#include <ocpp/v2/device_model.hpp>
|
||||
#include <ocpp/v2/utils.hpp>
|
||||
|
||||
namespace {
|
||||
const auto WEBSOCKET_INIT_DELAY = std::chrono::seconds(2);
|
||||
const std::string VARIABLE_ATTRIBUTE_VALUE_SOURCE_INTERNAL = "internal";
|
||||
/// \brief Default timeout for the return value (future) of the `configure_network_connection_profile_callback`
|
||||
/// function.
|
||||
constexpr std::int32_t default_network_config_timeout_seconds = 60;
|
||||
} // namespace
|
||||
|
||||
namespace ocpp {
|
||||
|
||||
using NetworkConnectionProfile = ocpp::v2::NetworkConnectionProfile;
|
||||
using AttributeEnum = ocpp::v2::AttributeEnum;
|
||||
using DeviceModel = ocpp::v2::DeviceModel;
|
||||
using DeviceModelAbstract = ocpp::v2::DeviceModelAbstract;
|
||||
using OCPPInterfaceEnum = ocpp::v2::OCPPInterfaceEnum;
|
||||
using SetNetworkProfileRequest = ocpp::v2::SetNetworkProfileRequest;
|
||||
namespace ControllerComponentVariables = ocpp::v2::ControllerComponentVariables;
|
||||
namespace ControllerComponents = ocpp::v2::ControllerComponents;
|
||||
namespace NetworkConfigurationComponentVariables = ocpp::v2::NetworkConfigurationComponentVariables;
|
||||
namespace utils = ocpp::v2::utils;
|
||||
|
||||
ConnectivityManager::ConnectivityManager(ocpp::v2::DeviceModelAbstract& device_model,
|
||||
std::shared_ptr<EvseSecurity> evse_security, const fs::path& share_path) :
|
||||
device_model{device_model},
|
||||
evse_security{evse_security},
|
||||
share_path{share_path},
|
||||
websocket{nullptr},
|
||||
message_callback([](const std::string& message) {
|
||||
EVLOG_warning << "No message callback set in ConnectivityManager. Dropping message: " << message;
|
||||
}),
|
||||
wants_to_be_connected{false},
|
||||
connected_ocpp_version{OcppProtocolVersion::Unknown} {
|
||||
cache_network_connection_profiles();
|
||||
}
|
||||
|
||||
void ConnectivityManager::set_message_callback(const std::function<void(const std::string& message)>& callback) {
|
||||
this->message_callback = callback;
|
||||
}
|
||||
|
||||
void ConnectivityManager::set_logging(std::shared_ptr<MessageLogging> logging) {
|
||||
this->logging = logging;
|
||||
}
|
||||
|
||||
void ConnectivityManager::set_websocket_authorization_key(const std::string& authorization_key) {
|
||||
if (this->websocket != nullptr) {
|
||||
this->websocket->set_authorization_key(authorization_key);
|
||||
this->websocket->disconnect(WebsocketCloseReason::ServiceRestart);
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectivityManager::set_websocket_connection_options(const WebsocketConnectionOptions& connection_options) {
|
||||
if (this->websocket != nullptr) {
|
||||
this->websocket->set_connection_options(connection_options);
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectivityManager::set_websocket_connection_options_without_reconnect() {
|
||||
const auto configuration_slot = get_active_network_configuration_slot();
|
||||
if (!configuration_slot.has_value()) {
|
||||
return;
|
||||
}
|
||||
const auto connection_options = this->get_ws_connection_options(configuration_slot.value());
|
||||
if (connection_options.has_value()) {
|
||||
this->set_websocket_connection_options(connection_options.value());
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectivityManager::set_websocket_connected_callback(WebsocketConnectionCallback callback) {
|
||||
this->websocket_connected_callback = callback;
|
||||
}
|
||||
|
||||
void ConnectivityManager::set_websocket_disconnected_callback(WebsocketConnectionCallback callback) {
|
||||
this->websocket_disconnected_callback = callback;
|
||||
}
|
||||
|
||||
void ConnectivityManager::set_websocket_connection_failed_callback(WebsocketConnectionFailedCallback callback) {
|
||||
this->websocket_connection_failed_callback = callback;
|
||||
}
|
||||
|
||||
void ConnectivityManager::set_configure_network_connection_profile_callback(
|
||||
ConfigureNetworkConnectionProfileCallback callback) {
|
||||
this->configure_network_connection_profile_callback = callback;
|
||||
}
|
||||
|
||||
std::optional<NetworkConnectionProfile>
|
||||
ConnectivityManager::get_network_connection_profile(const std::int32_t configuration_slot) const {
|
||||
auto state = this->m_state.handle();
|
||||
for (const auto& network_profile : state->cached_profiles) {
|
||||
if (network_profile.configurationSlot == configuration_slot) {
|
||||
if (!this->device_model
|
||||
.get_optional_value<bool>(ControllerComponentVariables::AllowSecurityLevelZeroConnections)
|
||||
.value_or(false) &&
|
||||
network_profile.connectionData.securityProfile ==
|
||||
security::OCPP_1_6_ONLY_UNSECURED_TRANSPORT_WITHOUT_BASIC_AUTHENTICATION) {
|
||||
EVLOG_error << "security_profile 0 not officially allowed in OCPP 2.0.1, skipping profile";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return network_profile.connectionData;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::int32_t>
|
||||
ConnectivityManager::get_priority_from_configuration_slot(const int configuration_slot) const {
|
||||
auto state = this->m_state.handle();
|
||||
auto it = std::find(state->slots.begin(), state->slots.end(), configuration_slot);
|
||||
if (it != state->slots.end()) {
|
||||
// Index is iterator - begin iterator
|
||||
return it - state->slots.begin();
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<int> ConnectivityManager::get_active_network_configuration_slot() const {
|
||||
auto state = this->m_state.handle();
|
||||
if (state->slots.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto idx = static_cast<std::size_t>(std::max<std::int32_t>(state->active_priority, 0));
|
||||
if (idx >= state->slots.size()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return state->slots[idx];
|
||||
}
|
||||
|
||||
std::optional<int> ConnectivityManager::get_configuration_slot_from_priority(const int priority) {
|
||||
auto state = this->m_state.handle();
|
||||
if (priority < 0 || static_cast<std::size_t>(priority) >= state->slots.size()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return state->slots[static_cast<std::size_t>(priority)];
|
||||
}
|
||||
|
||||
std::vector<int> ConnectivityManager::get_network_connection_slots() const {
|
||||
auto state = this->m_state.handle();
|
||||
return state->slots;
|
||||
}
|
||||
|
||||
bool ConnectivityManager::is_websocket_connected() {
|
||||
return this->websocket != nullptr && this->websocket->is_connected();
|
||||
}
|
||||
|
||||
std::chrono::time_point<std::chrono::steady_clock> ConnectivityManager::get_time_disconnected() const {
|
||||
return this->time_disconnected;
|
||||
}
|
||||
|
||||
void ConnectivityManager::connect(std::optional<std::int32_t> network_profile_slot) {
|
||||
std::int32_t configuration_slot{};
|
||||
{
|
||||
auto state = this->m_state.handle();
|
||||
if (state->slots.empty()) {
|
||||
EVLOG_warning << "No network connection profiles configured, aborting websocket connection.";
|
||||
return;
|
||||
}
|
||||
if (network_profile_slot.has_value()) {
|
||||
configuration_slot = network_profile_slot.value();
|
||||
} else {
|
||||
const auto idx = static_cast<std::size_t>(std::max<std::int32_t>(state->active_priority, 0));
|
||||
if (idx >= state->slots.size()) {
|
||||
EVLOG_warning << "Active priority index out of range, aborting websocket connection.";
|
||||
return;
|
||||
}
|
||||
configuration_slot = state->slots[idx];
|
||||
}
|
||||
state->pending_configuration_slot = configuration_slot;
|
||||
}
|
||||
|
||||
if (!this->get_network_connection_profile(configuration_slot).has_value()) {
|
||||
EVLOG_warning << "Could not find network connection profile belonging to configuration slot "
|
||||
<< configuration_slot;
|
||||
return;
|
||||
}
|
||||
|
||||
this->wants_to_be_connected = true;
|
||||
if (this->websocket != nullptr && this->is_websocket_connected()) {
|
||||
// After the websocket gets closed a reconnect will be triggered
|
||||
this->websocket->disconnect(WebsocketCloseReason::ServiceRestart);
|
||||
} else {
|
||||
this->try_connect_websocket();
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectivityManager::disconnect() {
|
||||
this->wants_to_be_connected = false;
|
||||
this->websocket_timer.stop();
|
||||
if (this->websocket != nullptr) {
|
||||
this->websocket->disconnect(WebsocketCloseReason::Normal);
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectivityManager::confirm_successful_connection() {
|
||||
const auto config_slot = this->get_active_network_configuration_slot();
|
||||
if (!config_slot.has_value()) {
|
||||
EVLOG_warning << "confirm_successful_connection: no active configuration slot";
|
||||
return;
|
||||
}
|
||||
|
||||
const auto network_connection_profile = this->get_network_connection_profile(config_slot.value());
|
||||
|
||||
if (const auto& security_profile_cv = ControllerComponentVariables::SecurityProfile;
|
||||
security_profile_cv.variable.has_value() and network_connection_profile.has_value()) {
|
||||
this->device_model.set_read_only_value(security_profile_cv.component, security_profile_cv.variable.value(),
|
||||
AttributeEnum::Actual,
|
||||
std::to_string(network_connection_profile.value().securityProfile),
|
||||
VARIABLE_ATTRIBUTE_VALUE_SOURCE_INTERNAL);
|
||||
}
|
||||
|
||||
this->remove_network_connection_profiles_below_actual_security_profile();
|
||||
this->check_cache_for_invalid_security_profiles();
|
||||
}
|
||||
|
||||
void ConnectivityManager::try_connect_websocket() {
|
||||
if (this->device_model.get_value<std::string>(ControllerComponentVariables::ChargePointId).find(':') !=
|
||||
std::string::npos) {
|
||||
EVLOG_AND_THROW(std::runtime_error("ChargePointId must not contain \':\'"));
|
||||
}
|
||||
|
||||
// Check the cache runtime since security profile might change async
|
||||
this->check_cache_for_invalid_security_profiles();
|
||||
|
||||
std::int32_t configuration_slot_to_set{};
|
||||
{
|
||||
auto state = this->m_state.handle();
|
||||
if (state->pending_configuration_slot.has_value()) {
|
||||
configuration_slot_to_set = state->pending_configuration_slot.value();
|
||||
} else if (!state->slots.empty()) {
|
||||
const auto idx = static_cast<std::size_t>(std::max<std::int32_t>(state->active_priority, 0));
|
||||
if (idx >= state->slots.size()) {
|
||||
EVLOG_warning << "try_connect_websocket: active_priority index out of range";
|
||||
return;
|
||||
}
|
||||
configuration_slot_to_set = state->slots[idx];
|
||||
} else {
|
||||
EVLOG_warning << "try_connect_websocket: no configuration slots available";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const std::optional<int> priority_to_set = this->get_priority_from_configuration_slot(configuration_slot_to_set);
|
||||
const auto network_connection_profile = this->get_network_connection_profile(configuration_slot_to_set);
|
||||
// Not const as the iface member can be set by the configure network connection profile callback
|
||||
auto connection_options = this->get_ws_connection_options(configuration_slot_to_set);
|
||||
bool can_use_connection_profile = true;
|
||||
|
||||
if (!network_connection_profile.has_value() || !priority_to_set.has_value()) {
|
||||
EVLOG_warning << "No network connection profile configured for " << configuration_slot_to_set;
|
||||
can_use_connection_profile = false;
|
||||
} else if (!connection_options.has_value()) {
|
||||
EVLOG_warning << "Connection profile configured for " << configuration_slot_to_set << " failed: not valid URL";
|
||||
can_use_connection_profile = false;
|
||||
} else if (this->configure_network_connection_profile_callback.has_value()) {
|
||||
std::optional<ConfigNetworkResult> config = handle_configure_network_connection_profile_callback(
|
||||
configuration_slot_to_set, network_connection_profile.value());
|
||||
if (config.has_value() && config->success) {
|
||||
connection_options->iface = config->interface_address;
|
||||
} else {
|
||||
EVLOG_debug << "Could not use config slot " << configuration_slot_to_set;
|
||||
can_use_connection_profile = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!can_use_connection_profile) {
|
||||
if (this->wants_to_be_connected) {
|
||||
this->websocket_timer.timeout(
|
||||
[this, configuration_slot_to_set] {
|
||||
const auto next_slot = this->get_next_configuration_slot(configuration_slot_to_set);
|
||||
if (next_slot.has_value()) {
|
||||
auto state = this->m_state.handle();
|
||||
state->pending_configuration_slot = next_slot.value();
|
||||
}
|
||||
this->try_connect_websocket();
|
||||
},
|
||||
WEBSOCKET_INIT_DELAY);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
auto state = this->m_state.handle();
|
||||
state->pending_configuration_slot.reset();
|
||||
state->active_priority = priority_to_set.value();
|
||||
}
|
||||
|
||||
if (connection_options->security_profile ==
|
||||
security::OCPP_1_6_ONLY_UNSECURED_TRANSPORT_WITHOUT_BASIC_AUTHENTICATION) {
|
||||
EVLOG_warning << "Using insecure security profile 0 without authentication";
|
||||
}
|
||||
|
||||
// priority_to_set is the 0-indexed position into NetworkConfigurationPriority; log it as 1-indexed
|
||||
// for human-readable parity with how the priority list is described in the OCPP spec and configs.
|
||||
EVLOG_info << "Open websocket with NetworkConfigurationPriority: " << priority_to_set.value() + 1
|
||||
<< " which is configurationSlot " << configuration_slot_to_set;
|
||||
|
||||
if (const auto& active_network_profile_cv = ControllerComponentVariables::ActiveNetworkProfile;
|
||||
active_network_profile_cv.variable.has_value()) {
|
||||
this->device_model.set_read_only_value(
|
||||
active_network_profile_cv.component, active_network_profile_cv.variable.value(), AttributeEnum::Actual,
|
||||
std::to_string(configuration_slot_to_set), VARIABLE_ATTRIBUTE_VALUE_SOURCE_INTERNAL);
|
||||
}
|
||||
|
||||
if (this->websocket == nullptr) {
|
||||
this->websocket = std::make_unique<Websocket>(connection_options.value(), this->evse_security, this->logging);
|
||||
|
||||
this->websocket->register_connected_callback(
|
||||
[this](OcppProtocolVersion protocol) { this->on_websocket_connected(protocol); });
|
||||
this->websocket->register_disconnected_callback([this]() { this->on_websocket_disconnected(); });
|
||||
this->websocket->register_stopped_connecting_callback(
|
||||
[this](ocpp::WebsocketCloseReason reason) { this->on_websocket_stopped_connecting(reason); });
|
||||
} else {
|
||||
this->websocket->set_connection_options(connection_options.value());
|
||||
}
|
||||
|
||||
// Attach external callbacks everytime since they might have changed
|
||||
if (websocket_connection_failed_callback.has_value()) {
|
||||
this->websocket->register_connection_failed_callback(websocket_connection_failed_callback.value());
|
||||
}
|
||||
|
||||
this->websocket->register_message_callback([this](const std::string& message) { this->message_callback(message); });
|
||||
|
||||
this->websocket->start_connecting();
|
||||
}
|
||||
|
||||
std::optional<ConfigNetworkResult>
|
||||
ConnectivityManager::handle_configure_network_connection_profile_callback(int slot,
|
||||
const NetworkConnectionProfile& profile) {
|
||||
if (!this->configure_network_connection_profile_callback.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::future<ConfigNetworkResult> config_status =
|
||||
this->configure_network_connection_profile_callback.value()(slot, profile);
|
||||
const std::int32_t config_timeout =
|
||||
this->device_model.get_optional_value<int>(ControllerComponentVariables::NetworkConfigTimeout)
|
||||
.value_or(default_network_config_timeout_seconds);
|
||||
|
||||
if (config_status.wait_for(std::chrono::seconds(config_timeout)) == std::future_status::ready) {
|
||||
return config_status.get();
|
||||
}
|
||||
|
||||
EVLOG_warning << "Timeout configuring config slot: " << slot;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<int> ConnectivityManager::get_next_configuration_slot(std::int32_t configuration_slot) {
|
||||
auto state = this->m_state.handle();
|
||||
if (state->slots.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (state->slots.size() > 1) {
|
||||
EVLOG_info << "Switching to next network configuration priority";
|
||||
}
|
||||
const auto it = std::find(state->slots.begin(), state->slots.end(), configuration_slot);
|
||||
std::int32_t next_priority = 0;
|
||||
if (it != state->slots.end()) {
|
||||
const auto current_priority = static_cast<std::int32_t>(it - state->slots.begin());
|
||||
next_priority = (current_priority + 1) % clamp_to<std::int32_t>(state->slots.size());
|
||||
}
|
||||
return state->slots[static_cast<std::size_t>(next_priority)];
|
||||
}
|
||||
|
||||
bool ConnectivityManager::send_to_websocket(const std::string& message) {
|
||||
if (this->websocket == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this->websocket->send(message);
|
||||
}
|
||||
|
||||
void ConnectivityManager::on_network_disconnected(OCPPInterfaceEnum ocpp_interface) {
|
||||
const auto actual_configuration_slot = get_active_network_configuration_slot();
|
||||
if (!actual_configuration_slot.has_value()) {
|
||||
EVLOG_warning << "Network disconnected. No active configuration slot";
|
||||
return;
|
||||
}
|
||||
std::optional<NetworkConnectionProfile> network_connection_profile =
|
||||
this->get_network_connection_profile(actual_configuration_slot.value());
|
||||
|
||||
if (!network_connection_profile.has_value()) {
|
||||
EVLOG_warning << "Network disconnected. No network connection profile configured";
|
||||
} else if (ocpp_interface == network_connection_profile.value().ocppInterface && this->websocket != nullptr) {
|
||||
// Since there is no connection anymore: disconnect the websocket, the manager will try to connect with the next
|
||||
// available network connection profile as we enable reconnects.
|
||||
this->websocket->disconnect(ocpp::WebsocketCloseReason::GoingAway);
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectivityManager::on_charging_station_certificate_changed() {
|
||||
if (this->websocket != nullptr) {
|
||||
// After the websocket gets closed a reconnect will be triggered
|
||||
this->websocket->disconnect(WebsocketCloseReason::ServiceRestart);
|
||||
}
|
||||
}
|
||||
|
||||
std::string ConnectivityManager::resolve_identity(const std::int32_t configuration_slot) const {
|
||||
std::string identity_to_use =
|
||||
this->device_model.get_value<std::string>(ControllerComponentVariables::SecurityCtrlrIdentity);
|
||||
const auto slot_identity_cv = NetworkConfigurationComponentVariables::get_component_variable(
|
||||
configuration_slot, NetworkConfigurationComponentVariables::Identity);
|
||||
if (const auto slot_identity = this->device_model.get_optional_value<std::string>(slot_identity_cv);
|
||||
slot_identity.has_value() && !slot_identity->empty()) {
|
||||
identity_to_use = *slot_identity;
|
||||
EVLOG_debug << "Using per-slot Identity for slot " << configuration_slot;
|
||||
}
|
||||
return identity_to_use;
|
||||
}
|
||||
|
||||
std::optional<std::string>
|
||||
ConnectivityManager::resolve_basic_auth_password(const std::int32_t configuration_slot) const {
|
||||
const auto slot_pwd_cv = NetworkConfigurationComponentVariables::get_component_variable(
|
||||
configuration_slot, NetworkConfigurationComponentVariables::BasicAuthPassword);
|
||||
if (const auto slot_pwd = this->device_model.get_optional_value<std::string>(slot_pwd_cv);
|
||||
slot_pwd.has_value() && !slot_pwd->empty()) {
|
||||
EVLOG_debug << "Using per-slot BasicAuthPassword for slot " << configuration_slot;
|
||||
return slot_pwd.value();
|
||||
}
|
||||
return this->device_model.get_optional_value<std::string>(ControllerComponentVariables::BasicAuthPassword);
|
||||
}
|
||||
|
||||
std::optional<std::string> ConnectivityManager::read_everest_version() const {
|
||||
const fs::path version_file_path = this->share_path.parent_path().parent_path() / "version_information.txt";
|
||||
if (!fs::exists(version_file_path)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::ifstream ifs(version_file_path);
|
||||
std::string version;
|
||||
std::getline(ifs, version);
|
||||
std::string trimmed_version = ocpp::trim_string(version);
|
||||
trimmed_version.erase(std::remove(trimmed_version.begin(), trimmed_version.end(), '\n'), trimmed_version.end());
|
||||
if (trimmed_version.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return trimmed_version;
|
||||
}
|
||||
|
||||
std::optional<WebsocketConnectionOptions>
|
||||
ConnectivityManager::get_ws_connection_options(const std::int32_t configuration_slot) {
|
||||
const auto network_connection_profile_opt = this->get_network_connection_profile(configuration_slot);
|
||||
if (!network_connection_profile_opt.has_value()) {
|
||||
EVLOG_critical << "Could not retrieve NetworkProfile of configurationSlot: " << configuration_slot;
|
||||
throw std::runtime_error("Could not retrieve NetworkProfile");
|
||||
}
|
||||
const auto& network_connection_profile = network_connection_profile_opt.value();
|
||||
|
||||
try {
|
||||
const auto identity_to_use = this->resolve_identity(configuration_slot);
|
||||
auto uri = Uri::parse_and_validate(network_connection_profile.ocppCsmsUrl.get(), identity_to_use,
|
||||
network_connection_profile.securityProfile);
|
||||
const auto ocpp_versions = utils::get_ocpp_protocol_versions(
|
||||
this->device_model.get_value<std::string>(ControllerComponentVariables::SupportedOcppVersions));
|
||||
const auto basic_auth_password = this->resolve_basic_auth_password(configuration_slot);
|
||||
|
||||
WebsocketConnectionOptions connection_options{
|
||||
ocpp_versions, uri, network_connection_profile.securityProfile, basic_auth_password,
|
||||
// Always use a minimum of 1 second otherwise each message would timeout immediately
|
||||
std::chrono::seconds(std::max(network_connection_profile.messageTimeout, 1)),
|
||||
this->device_model.get_value<int>(ControllerComponentVariables::RetryBackOffRandomRange),
|
||||
this->device_model.get_value<int>(ControllerComponentVariables::RetryBackOffRepeatTimes),
|
||||
this->device_model.get_value<int>(ControllerComponentVariables::RetryBackOffWaitMinimum),
|
||||
this->device_model.get_value<int>(ControllerComponentVariables::NetworkProfileConnectionAttempts),
|
||||
this->device_model.get_value<std::string>(ControllerComponentVariables::SupportedCiphers12),
|
||||
this->device_model.get_value<std::string>(ControllerComponentVariables::SupportedCiphers13),
|
||||
this->device_model.get_value<int>(ControllerComponentVariables::WebSocketPingInterval),
|
||||
this->device_model.get_optional_value<std::string>(ControllerComponentVariables::WebsocketPingPayload)
|
||||
.value_or("payload"),
|
||||
this->device_model.get_optional_value<int>(ControllerComponentVariables::WebsocketPongTimeout).value_or(5),
|
||||
this->device_model.get_optional_value<bool>(ControllerComponentVariables::UseSslDefaultVerifyPaths)
|
||||
.value_or(true),
|
||||
this->device_model.get_optional_value<bool>(ControllerComponentVariables::AdditionalRootCertificateCheck)
|
||||
.value_or(false),
|
||||
std::nullopt, // hostName
|
||||
this->device_model.get_optional_value<bool>(ControllerComponentVariables::VerifyCsmsCommonName)
|
||||
.value_or(true),
|
||||
this->device_model.get_optional_value<bool>(ControllerComponentVariables::UseTPM).value_or(false),
|
||||
this->device_model.get_optional_value<bool>(ControllerComponentVariables::VerifyCsmsAllowWildcards)
|
||||
.value_or(false),
|
||||
this->device_model.get_optional_value<std::string>(ControllerComponentVariables::IFace),
|
||||
this->device_model.get_optional_value<bool>(ControllerComponentVariables::EnableTLSKeylog).value_or(false),
|
||||
this->device_model.get_optional_value<std::string>(ControllerComponentVariables::TLSKeylogFile)};
|
||||
|
||||
if (auto version = this->read_everest_version(); version.has_value()) {
|
||||
connection_options.everest_version = std::move(*version);
|
||||
}
|
||||
|
||||
return connection_options;
|
||||
|
||||
} catch (const std::invalid_argument& e) {
|
||||
EVLOG_error << "Could not configure the connection options: " << e.what();
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void ConnectivityManager::on_websocket_connected(OcppProtocolVersion protocol) {
|
||||
this->connected_ocpp_version = protocol;
|
||||
const auto actual_configuration_slot = get_active_network_configuration_slot();
|
||||
if (!actual_configuration_slot.has_value()) {
|
||||
EVLOG_warning << "on_websocket_connected: no active configuration slot";
|
||||
return;
|
||||
}
|
||||
std::optional<NetworkConnectionProfile> network_connection_profile =
|
||||
this->get_network_connection_profile(actual_configuration_slot.value());
|
||||
|
||||
// Write negotiated OcppVersion to the per-slot NetworkConfiguration DM variable (B09)
|
||||
if (protocol != OcppProtocolVersion::Unknown) {
|
||||
std::string version_str;
|
||||
switch (protocol) {
|
||||
case OcppProtocolVersion::v201:
|
||||
version_str = "OCPP201";
|
||||
break;
|
||||
case OcppProtocolVersion::v21:
|
||||
version_str = "OCPP21";
|
||||
break;
|
||||
case OcppProtocolVersion::v16:
|
||||
case OcppProtocolVersion::Unknown:
|
||||
break;
|
||||
}
|
||||
if (!version_str.empty()) {
|
||||
const auto nc_cv = NetworkConfigurationComponentVariables::get_component_variable(
|
||||
actual_configuration_slot.value(), NetworkConfigurationComponentVariables::OcppVersion);
|
||||
if (nc_cv.variable.has_value()) {
|
||||
this->device_model.set_value(nc_cv.component, nc_cv.variable.value(), AttributeEnum::Actual,
|
||||
version_str, VARIABLE_ATTRIBUTE_VALUE_SOURCE_INTERNAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (network_connection_profile.has_value()) {
|
||||
this->device_model.set_value(ControllerComponents::SecurityCtrlr,
|
||||
NetworkConfigurationComponentVariables::SecurityProfile, AttributeEnum::Actual,
|
||||
std::to_string(network_connection_profile->securityProfile),
|
||||
VARIABLE_ATTRIBUTE_VALUE_SOURCE_INTERNAL);
|
||||
if (network_connection_profile->identity.has_value()) {
|
||||
this->device_model.set_value(ControllerComponents::SecurityCtrlr,
|
||||
NetworkConfigurationComponentVariables::Identity, AttributeEnum::Actual,
|
||||
network_connection_profile->identity.value(),
|
||||
VARIABLE_ATTRIBUTE_VALUE_SOURCE_INTERNAL);
|
||||
}
|
||||
}
|
||||
|
||||
if (this->websocket_connected_callback.has_value() and network_connection_profile.has_value()) {
|
||||
this->websocket_connected_callback.value()(actual_configuration_slot.value(),
|
||||
network_connection_profile.value(), this->connected_ocpp_version);
|
||||
}
|
||||
this->time_disconnected = std::chrono::time_point<std::chrono::steady_clock>();
|
||||
}
|
||||
|
||||
void ConnectivityManager::mark_disconnected_at_now() {
|
||||
auto expected = std::chrono::time_point<std::chrono::steady_clock>{};
|
||||
this->time_disconnected.compare_exchange_strong(expected, std::chrono::steady_clock::now());
|
||||
}
|
||||
|
||||
void ConnectivityManager::on_websocket_disconnected() {
|
||||
const auto actual_configuration_slot = get_active_network_configuration_slot();
|
||||
if (!actual_configuration_slot.has_value()) {
|
||||
this->mark_disconnected_at_now();
|
||||
return;
|
||||
}
|
||||
std::optional<NetworkConnectionProfile> network_connection_profile =
|
||||
this->get_network_connection_profile(actual_configuration_slot.value());
|
||||
this->mark_disconnected_at_now();
|
||||
|
||||
if (this->websocket_disconnected_callback.has_value() and network_connection_profile.has_value()) {
|
||||
this->websocket_disconnected_callback.value()(actual_configuration_slot.value(),
|
||||
network_connection_profile.value(), this->connected_ocpp_version);
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectivityManager::on_websocket_stopped_connecting(ocpp::WebsocketCloseReason reason) {
|
||||
const auto active_slot = this->get_active_network_configuration_slot();
|
||||
if (active_slot.has_value()) {
|
||||
auto state = this->m_state.handle();
|
||||
EVLOG_warning << "Closed websocket of NetworkConfigurationPriority: " << state->active_priority + 1
|
||||
<< " which is configurationSlot " << active_slot.value();
|
||||
} else {
|
||||
EVLOG_warning << "Closed websocket (no active configuration slot)";
|
||||
}
|
||||
|
||||
if (this->wants_to_be_connected) {
|
||||
this->websocket_timer.timeout(
|
||||
[this, reason] {
|
||||
if (reason != WebsocketCloseReason::ServiceRestart) {
|
||||
const auto current = get_active_network_configuration_slot();
|
||||
if (current.has_value()) {
|
||||
const auto next_slot = get_next_configuration_slot(current.value());
|
||||
if (next_slot.has_value()) {
|
||||
auto state = this->m_state.handle();
|
||||
state->pending_configuration_slot = next_slot.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
this->try_connect_websocket();
|
||||
},
|
||||
WEBSOCKET_INIT_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectivityManager::append_slot_to_network_configuration_priority_if_absent(const int32_t slot,
|
||||
const std::string& source) {
|
||||
if (!ControllerComponentVariables::NetworkConfigurationPriority.variable.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto priority_str =
|
||||
this->device_model.get_optional_value<std::string>(ControllerComponentVariables::NetworkConfigurationPriority);
|
||||
const auto slot_str = std::to_string(slot);
|
||||
bool slot_found = false;
|
||||
if (priority_str.has_value()) {
|
||||
for (const auto& s : ocpp::split_string(priority_str.value(), ',')) {
|
||||
if (s == slot_str) {
|
||||
slot_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!slot_found) {
|
||||
std::string new_priority = priority_str.value_or("");
|
||||
if (!new_priority.empty()) {
|
||||
new_priority += ',';
|
||||
}
|
||||
new_priority += slot_str;
|
||||
this->device_model.set_value(ControllerComponentVariables::NetworkConfigurationPriority.component,
|
||||
ControllerComponentVariables::NetworkConfigurationPriority.variable.value(),
|
||||
AttributeEnum::Actual, new_priority, source);
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectivityManager::cache_network_connection_profiles() {
|
||||
auto state = this->m_state.handle();
|
||||
state->cached_profiles.clear();
|
||||
state->slots.clear();
|
||||
|
||||
// Build profiles and priority-ordered slot list from NetworkConfiguration DM components
|
||||
for (const std::string& str : ocpp::split_string(
|
||||
this->device_model.get_value<std::string>(ControllerComponentVariables::NetworkConfigurationPriority),
|
||||
',')) {
|
||||
int slot = 0;
|
||||
try {
|
||||
slot = std::stoi(str);
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_warning << "Skipping non-integer token '" << str << "' in NetworkConfigurationPriority: " << e.what();
|
||||
continue;
|
||||
}
|
||||
state->slots.push_back(slot);
|
||||
if (const auto profile =
|
||||
NetworkConfigurationComponentVariables::read_profile_from_device_model(this->device_model, slot)) {
|
||||
SetNetworkProfileRequest req;
|
||||
req.configurationSlot = slot;
|
||||
req.connectionData = *profile;
|
||||
state->cached_profiles.push_back(req);
|
||||
}
|
||||
}
|
||||
|
||||
// Re-clamp active_priority to remain a valid index after rebuild
|
||||
if (state->slots.empty()) {
|
||||
state->active_priority = 0;
|
||||
} else if (static_cast<std::size_t>(state->active_priority) >= state->slots.size()) {
|
||||
state->active_priority = static_cast<std::int32_t>(state->slots.size() - 1);
|
||||
}
|
||||
|
||||
this->warn_if_all_security_level_zero_locked(*state);
|
||||
}
|
||||
|
||||
void ConnectivityManager::warn_if_all_security_level_zero_locked(const NetworkProfileCacheState& state) const {
|
||||
if (state.cached_profiles.empty()) {
|
||||
return;
|
||||
}
|
||||
if (this->device_model.get_optional_value<bool>(ControllerComponentVariables::AllowSecurityLevelZeroConnections)
|
||||
.value_or(false)) {
|
||||
return;
|
||||
}
|
||||
if (std::none_of(state.cached_profiles.begin(), state.cached_profiles.end(),
|
||||
[](const SetNetworkProfileRequest& profile) {
|
||||
return profile.connectionData.securityProfile !=
|
||||
security::OCPP_1_6_ONLY_UNSECURED_TRANSPORT_WITHOUT_BASIC_AUTHENTICATION;
|
||||
})) {
|
||||
EVLOG_error << "All profiles configured have security_profile 0 which is not officially allowed in OCPP 2.0.1";
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectivityManager::reload_network_profiles() {
|
||||
try {
|
||||
cache_network_connection_profiles();
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Failed to reload network profiles: " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
bool ConnectivityManager::set_network_profile(const int32_t slot, const NetworkConnectionProfile& profile,
|
||||
const std::string& source) {
|
||||
// B09.FR.18: each slot's per-slot Identity / BasicAuthPassword is independent. Do not inherit
|
||||
// from the currently-active slot — when the incoming profile omits a per-slot value, reads on
|
||||
// the new slot fall back to SecurityCtrlr globals per B09.FR.16.
|
||||
if (!NetworkConfigurationComponentVariables::write_profile_to_device_model(this->device_model, slot, profile,
|
||||
source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->append_slot_to_network_configuration_priority_if_absent(slot, source);
|
||||
|
||||
try {
|
||||
cache_network_connection_profiles();
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Failed to refresh network profiles after set_network_profile: " << e.what();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConnectivityManager::check_cache_for_invalid_security_profiles() {
|
||||
const auto security_level = this->device_model.get_value<int>(ControllerComponentVariables::SecurityProfile);
|
||||
|
||||
// Single critical section over read-modify-write of state->slots and state->pending_configuration_slot.
|
||||
// Splitting the lock between the prune step and the pending-slot reassignment can let another writer
|
||||
// mutate state->slots in between, which would invalidate the locally captured before_slot.
|
||||
auto state = this->m_state.handle();
|
||||
if (state->last_known_security_level == security_level) {
|
||||
return;
|
||||
}
|
||||
state->last_known_security_level = security_level;
|
||||
|
||||
std::optional<int> before_slot;
|
||||
if (state->pending_configuration_slot.has_value()) {
|
||||
before_slot = state->pending_configuration_slot;
|
||||
} else if (!state->slots.empty()) {
|
||||
const auto idx = static_cast<std::size_t>(std::max<std::int32_t>(state->active_priority, 0));
|
||||
if (idx < state->slots.size()) {
|
||||
before_slot = state->slots[idx];
|
||||
}
|
||||
}
|
||||
|
||||
auto is_lower_security_level = [this, security_level](const int slot) {
|
||||
const auto opt_profile = this->get_network_connection_profile(slot);
|
||||
return !opt_profile.has_value() || opt_profile->securityProfile < security_level;
|
||||
};
|
||||
|
||||
state->slots.erase(std::remove_if(state->slots.begin(), state->slots.end(), is_lower_security_level),
|
||||
state->slots.end());
|
||||
|
||||
// Mirror the prune onto cached_profiles so get_network_connection_profile() does not
|
||||
// return stale data for slots that were just removed from the priority list.
|
||||
state->cached_profiles.erase(std::remove_if(state->cached_profiles.begin(), state->cached_profiles.end(),
|
||||
[security_level](const SetNetworkProfileRequest& p) {
|
||||
return p.connectionData.securityProfile < security_level;
|
||||
}),
|
||||
state->cached_profiles.end());
|
||||
|
||||
// Cap active_priority after shrink so later reads don't index past the end
|
||||
if (state->slots.empty()) {
|
||||
state->active_priority = 0;
|
||||
state->pending_configuration_slot.reset();
|
||||
EVLOG_warning << "All network connection slots were removed due to insufficient security profile";
|
||||
return;
|
||||
}
|
||||
if (static_cast<std::size_t>(state->active_priority) >= state->slots.size()) {
|
||||
state->active_priority = static_cast<std::int32_t>(state->slots.size() - 1);
|
||||
}
|
||||
|
||||
if (!before_slot.has_value()) {
|
||||
return;
|
||||
}
|
||||
// Use the active slot and if not valid any longer use the next available one. The recursive
|
||||
// mutex allows the helper calls below to re-acquire the same handle.
|
||||
const auto opt_priority = this->get_priority_from_configuration_slot(before_slot.value());
|
||||
if (opt_priority.has_value()) {
|
||||
state->pending_configuration_slot = before_slot.value();
|
||||
} else {
|
||||
const auto next = this->get_next_configuration_slot(before_slot.value());
|
||||
if (next.has_value()) {
|
||||
state->pending_configuration_slot = next.value();
|
||||
} else {
|
||||
state->pending_configuration_slot.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectivityManager::remove_network_connection_profiles_below_actual_security_profile() {
|
||||
const auto security_level = this->device_model.get_value<int>(ControllerComponentVariables::SecurityProfile);
|
||||
|
||||
std::vector<int32_t> pruned_slots;
|
||||
{
|
||||
auto state = this->m_state.handle();
|
||||
// Collect slots to prune from the in-memory cache
|
||||
for (const auto& profile : state->cached_profiles) {
|
||||
if (profile.connectionData.securityProfile < security_level) {
|
||||
pruned_slots.push_back(profile.configurationSlot);
|
||||
}
|
||||
}
|
||||
|
||||
if (pruned_slots.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove pruned slots from in-memory caches
|
||||
auto is_pruned = [&pruned_slots](int32_t slot) {
|
||||
return std::find(pruned_slots.begin(), pruned_slots.end(), slot) != pruned_slots.end();
|
||||
};
|
||||
|
||||
state->cached_profiles.erase(
|
||||
std::remove_if(state->cached_profiles.begin(), state->cached_profiles.end(),
|
||||
[&is_pruned](const SetNetworkProfileRequest& p) { return is_pruned(p.configurationSlot); }),
|
||||
state->cached_profiles.end());
|
||||
|
||||
state->slots.erase(std::remove_if(state->slots.begin(), state->slots.end(), is_pruned), state->slots.end());
|
||||
|
||||
// Cap active_priority after shrink
|
||||
if (state->slots.empty()) {
|
||||
state->active_priority = 0;
|
||||
} else if (static_cast<std::size_t>(state->active_priority) >= state->slots.size()) {
|
||||
state->active_priority = static_cast<std::int32_t>(state->slots.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear per-slot DM variables to prevent re-injection via SetVariables; done outside the state
|
||||
// lock because DeviceModel has its own synchronization.
|
||||
for (const int32_t slot : pruned_slots) {
|
||||
NetworkConfigurationComponentVariables::clear_slot_in_device_model(this->device_model, slot);
|
||||
}
|
||||
|
||||
// Rebuild and persist NetworkConfigurationPriority from remaining slots
|
||||
if (!ControllerComponentVariables::NetworkConfigurationPriority.variable.has_value()) {
|
||||
EVLOG_warning << "NetworkConfigurationPriority variable is not set, cannot update network priority";
|
||||
return;
|
||||
}
|
||||
|
||||
std::string new_priority;
|
||||
{
|
||||
auto state = this->m_state.handle();
|
||||
for (const int32_t slot : state->slots) {
|
||||
if (!new_priority.empty()) {
|
||||
new_priority += ',';
|
||||
}
|
||||
new_priority += std::to_string(slot);
|
||||
}
|
||||
}
|
||||
|
||||
this->device_model.set_value(ControllerComponentVariables::NetworkConfigurationPriority.component,
|
||||
ControllerComponentVariables::NetworkConfigurationPriority.variable.value(),
|
||||
AttributeEnum::Actual, new_priority, VARIABLE_ATTRIBUTE_VALUE_SOURCE_INTERNAL);
|
||||
}
|
||||
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,128 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <ocpp/common/database/database_handler_common.hpp>
|
||||
|
||||
#include <everest/database/sqlite/schema_updater.hpp>
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
using namespace everest::db;
|
||||
using namespace everest::db::sqlite;
|
||||
|
||||
namespace ocpp::common {
|
||||
|
||||
DatabaseHandlerCommon::DatabaseHandlerCommon(std::unique_ptr<ConnectionInterface> database,
|
||||
const fs::path& sql_migration_files_path,
|
||||
std::uint32_t target_schema_version) noexcept :
|
||||
database(std::move(database)),
|
||||
sql_migration_files_path(sql_migration_files_path),
|
||||
target_schema_version(target_schema_version) {
|
||||
}
|
||||
|
||||
void DatabaseHandlerCommon::open_connection() {
|
||||
SchemaUpdater updater{this->database.get()};
|
||||
|
||||
if (!updater.apply_migration_files(this->sql_migration_files_path, target_schema_version)) {
|
||||
throw MigrationException("SQL migration failed");
|
||||
}
|
||||
|
||||
if (!this->database->open_connection()) {
|
||||
throw ConnectionException("Could not open database at provided path.");
|
||||
}
|
||||
|
||||
this->init_sql();
|
||||
}
|
||||
|
||||
void DatabaseHandlerCommon::close_connection() {
|
||||
this->database->close_connection();
|
||||
}
|
||||
|
||||
std::vector<DBTransactionMessage> DatabaseHandlerCommon::get_message_queue_messages(const QueueType queue_type) {
|
||||
std::vector<DBTransactionMessage> messages;
|
||||
|
||||
const std::string table_name = queue_type == QueueType::Normal ? "NORMAL_QUEUE" : "TRANSACTION_QUEUE";
|
||||
|
||||
const std::string sql =
|
||||
"SELECT UNIQUE_ID, MESSAGE, MESSAGE_TYPE, MESSAGE_ATTEMPTS, MESSAGE_TIMESTAMP FROM " + table_name;
|
||||
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
int status = SQLITE_ERROR;
|
||||
while ((status = stmt->step()) == SQLITE_ROW) {
|
||||
try {
|
||||
const std::string message = stmt->column_text(1);
|
||||
const std::string unique_id = stmt->column_text(0);
|
||||
const std::string message_type = stmt->column_text(2);
|
||||
const std::string message_timestamp = stmt->column_text(4);
|
||||
const int message_attempts = stmt->column_int(3);
|
||||
|
||||
const json json_message = json::parse(message);
|
||||
|
||||
DBTransactionMessage control_message;
|
||||
control_message.message_attempts = message_attempts;
|
||||
control_message.timestamp = ocpp::DateTime(message_timestamp);
|
||||
control_message.message_type = message_type;
|
||||
control_message.unique_id = unique_id;
|
||||
control_message.json_message = json_message;
|
||||
messages.push_back(std::move(control_message));
|
||||
} catch (const json::exception& e) {
|
||||
EVLOG_error << "json parse failed because: "
|
||||
<< "(" << e.what() << ")";
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "can not get queued transaction message from database: "
|
||||
<< "(" << e.what() << ")";
|
||||
}
|
||||
}
|
||||
|
||||
if (status != SQLITE_DONE) {
|
||||
EVLOG_error << "Could not get (all) queued transaction messages from database";
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
void DatabaseHandlerCommon::insert_message_queue_message(const DBTransactionMessage& db_message,
|
||||
const QueueType queue_type) {
|
||||
const std::string table_name = queue_type == QueueType::Normal ? "NORMAL_QUEUE" : "TRANSACTION_QUEUE";
|
||||
|
||||
const std::string sql = "INSERT INTO " + table_name +
|
||||
" (UNIQUE_ID, MESSAGE, MESSAGE_TYPE, MESSAGE_ATTEMPTS, MESSAGE_TIMESTAMP) VALUES "
|
||||
"(@unique_id, @message, @message_type, @message_attempts, @message_timestamp)";
|
||||
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
const std::string message = db_message.json_message.dump();
|
||||
stmt->bind_text("@unique_id", db_message.unique_id);
|
||||
stmt->bind_text("@message", message);
|
||||
stmt->bind_text("@message_type", db_message.message_type);
|
||||
stmt->bind_int("@message_attempts", db_message.message_attempts);
|
||||
stmt->bind_text("@message_timestamp", db_message.timestamp.to_rfc3339(), SQLiteString::Transient);
|
||||
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseHandlerCommon::remove_message_queue_message(const std::string& unique_id, const QueueType queue_type) {
|
||||
const std::string table_name = queue_type == QueueType::Normal ? "NORMAL_QUEUE" : "TRANSACTION_QUEUE";
|
||||
const std::string sql = "DELETE FROM " + table_name + " WHERE UNIQUE_ID = @unique_id";
|
||||
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
stmt->bind_text("@unique_id", unique_id);
|
||||
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseHandlerCommon::clear_message_queue(const QueueType queue_type) {
|
||||
const std::string table_name = queue_type == QueueType::Normal ? "NORMAL_QUEUE" : "TRANSACTION_QUEUE";
|
||||
const auto retval = this->database->clear_table(table_name);
|
||||
if (retval == false) {
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ocpp::common
|
||||
@@ -0,0 +1,266 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <ocpp/common/evse_security.hpp>
|
||||
|
||||
namespace ocpp {
|
||||
|
||||
namespace evse_security_conversions {
|
||||
|
||||
ocpp::v2::GetCertificateIdUseEnum to_ocpp_v2(ocpp::CertificateType other) {
|
||||
switch (other) {
|
||||
case ocpp::CertificateType::V2GRootCertificate:
|
||||
return ocpp::v2::GetCertificateIdUseEnum::V2GRootCertificate;
|
||||
case ocpp::CertificateType::MORootCertificate:
|
||||
return ocpp::v2::GetCertificateIdUseEnum::MORootCertificate;
|
||||
case ocpp::CertificateType::CSMSRootCertificate:
|
||||
return ocpp::v2::GetCertificateIdUseEnum::CSMSRootCertificate;
|
||||
case ocpp::CertificateType::V2GCertificateChain:
|
||||
return ocpp::v2::GetCertificateIdUseEnum::V2GCertificateChain;
|
||||
case ocpp::CertificateType::MFRootCertificate:
|
||||
return ocpp::v2::GetCertificateIdUseEnum::ManufacturerRootCertificate;
|
||||
case ocpp::CertificateType::OEMRootCertificate:
|
||||
return ocpp::v2::GetCertificateIdUseEnum::OEMRootCertificate;
|
||||
}
|
||||
|
||||
throw EnumConversionException("Could not convert CertificateType to GetCertificateIdUseEnum");
|
||||
}
|
||||
|
||||
ocpp::v2::InstallCertificateUseEnum to_ocpp_v2(ocpp::CaCertificateType other) {
|
||||
switch (other) {
|
||||
case ocpp::CaCertificateType::V2G:
|
||||
return ocpp::v2::InstallCertificateUseEnum::V2GRootCertificate;
|
||||
case ocpp::CaCertificateType::MO:
|
||||
return ocpp::v2::InstallCertificateUseEnum::MORootCertificate;
|
||||
case ocpp::CaCertificateType::CSMS:
|
||||
return ocpp::v2::InstallCertificateUseEnum::CSMSRootCertificate;
|
||||
case ocpp::CaCertificateType::MF:
|
||||
return ocpp::v2::InstallCertificateUseEnum::ManufacturerRootCertificate;
|
||||
case ocpp::CaCertificateType::OEM:
|
||||
return ocpp::v2::InstallCertificateUseEnum::OEMRootCertificate;
|
||||
}
|
||||
|
||||
throw EnumConversionException("Could not convert CaCertificateType to InstallCertificateUseEnum");
|
||||
}
|
||||
|
||||
ocpp::v2::HashAlgorithmEnum to_ocpp_v2(ocpp::HashAlgorithmEnumType other) {
|
||||
switch (other) {
|
||||
case ocpp::HashAlgorithmEnumType::SHA256:
|
||||
return ocpp::v2::HashAlgorithmEnum::SHA256;
|
||||
case ocpp::HashAlgorithmEnumType::SHA384:
|
||||
return ocpp::v2::HashAlgorithmEnum::SHA384;
|
||||
case ocpp::HashAlgorithmEnumType::SHA512:
|
||||
return ocpp::v2::HashAlgorithmEnum::SHA512;
|
||||
}
|
||||
|
||||
throw EnumConversionException("Could not convert HashAlgorithmEnumType to HashAlgorithmEnum");
|
||||
}
|
||||
|
||||
ocpp::v2::InstallCertificateStatusEnum to_ocpp_v2(ocpp::InstallCertificateResult other) {
|
||||
switch (other) {
|
||||
case ocpp::InstallCertificateResult::InvalidSignature:
|
||||
case ocpp::InstallCertificateResult::InvalidCertificateChain:
|
||||
case ocpp::InstallCertificateResult::InvalidFormat:
|
||||
case ocpp::InstallCertificateResult::InvalidCommonName:
|
||||
case ocpp::InstallCertificateResult::NoRootCertificateInstalled:
|
||||
case ocpp::InstallCertificateResult::Expired:
|
||||
case ocpp::InstallCertificateResult::CertificateStoreMaxLengthExceeded:
|
||||
return ocpp::v2::InstallCertificateStatusEnum::Rejected;
|
||||
case ocpp::InstallCertificateResult::WriteError:
|
||||
return ocpp::v2::InstallCertificateStatusEnum::Failed;
|
||||
case ocpp::InstallCertificateResult::Accepted:
|
||||
return ocpp::v2::InstallCertificateStatusEnum::Accepted;
|
||||
}
|
||||
|
||||
throw EnumConversionException("Could not convert InstallCertificateResult to InstallCertificateStatusEnum");
|
||||
}
|
||||
|
||||
ocpp::v2::DeleteCertificateStatusEnum to_ocpp_v2(ocpp::DeleteCertificateResult other) {
|
||||
switch (other) {
|
||||
case ocpp::DeleteCertificateResult::Accepted:
|
||||
return ocpp::v2::DeleteCertificateStatusEnum ::Accepted;
|
||||
case ocpp::DeleteCertificateResult::Failed:
|
||||
return ocpp::v2::DeleteCertificateStatusEnum ::Failed;
|
||||
case ocpp::DeleteCertificateResult::NotFound:
|
||||
return ocpp::v2::DeleteCertificateStatusEnum ::NotFound;
|
||||
}
|
||||
|
||||
throw EnumConversionException("Could not convert DeleteCertificateResult to DeleteCertificateResult");
|
||||
}
|
||||
|
||||
ocpp::v2::CertificateHashDataType to_ocpp_v2(ocpp::CertificateHashDataType other) {
|
||||
ocpp::v2::CertificateHashDataType lhs;
|
||||
lhs.hashAlgorithm = to_ocpp_v2(other.hashAlgorithm);
|
||||
lhs.issuerNameHash = other.issuerNameHash;
|
||||
lhs.issuerKeyHash = other.issuerKeyHash;
|
||||
lhs.serialNumber = other.serialNumber;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
ocpp::v2::CertificateHashDataChain to_ocpp_v2(ocpp::CertificateHashDataChain other) {
|
||||
ocpp::v2::CertificateHashDataChain lhs;
|
||||
lhs.certificateType = to_ocpp_v2(other.certificateType);
|
||||
lhs.certificateHashData = to_ocpp_v2(other.certificateHashData);
|
||||
if (other.childCertificateHashData.has_value() && !other.childCertificateHashData.value().empty()) {
|
||||
std::vector<ocpp::v2::CertificateHashDataType> v;
|
||||
for (const auto& certificate_hash_data : other.childCertificateHashData.value()) {
|
||||
v.push_back(to_ocpp_v2(certificate_hash_data));
|
||||
}
|
||||
lhs.childCertificateHashData = v;
|
||||
}
|
||||
return lhs;
|
||||
}
|
||||
|
||||
ocpp::v2::OCSPRequestData to_ocpp_v2(ocpp::OCSPRequestData other) {
|
||||
ocpp::v2::OCSPRequestData lhs;
|
||||
lhs.issuerNameHash = other.issuerNameHash;
|
||||
lhs.issuerKeyHash = other.issuerKeyHash;
|
||||
lhs.serialNumber = other.serialNumber;
|
||||
lhs.responderURL = other.responderUrl;
|
||||
lhs.hashAlgorithm = to_ocpp_v2(other.hashAlgorithm);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
std::vector<ocpp::v2::OCSPRequestData> to_ocpp_v2(const std::vector<ocpp::OCSPRequestData>& ocsp_request_data) {
|
||||
std::vector<ocpp::v2::OCSPRequestData> ocsp_request_data_list;
|
||||
for (const auto& ocsp_data : ocsp_request_data) {
|
||||
const ocpp::v2::OCSPRequestData request = to_ocpp_v2(ocsp_data);
|
||||
ocsp_request_data_list.push_back(request);
|
||||
}
|
||||
return ocsp_request_data_list;
|
||||
}
|
||||
|
||||
ocpp::CertificateType from_ocpp_v2(ocpp::v2::GetCertificateIdUseEnum other) {
|
||||
switch (other) {
|
||||
case ocpp::v2::GetCertificateIdUseEnum::V2GRootCertificate:
|
||||
return ocpp::CertificateType::V2GRootCertificate;
|
||||
case ocpp::v2::GetCertificateIdUseEnum::V2GCertificateChain:
|
||||
return ocpp::CertificateType::V2GCertificateChain;
|
||||
case ocpp::v2::GetCertificateIdUseEnum::MORootCertificate:
|
||||
return ocpp::CertificateType::MORootCertificate;
|
||||
case ocpp::v2::GetCertificateIdUseEnum::CSMSRootCertificate:
|
||||
return ocpp::CertificateType::CSMSRootCertificate;
|
||||
case ocpp::v2::GetCertificateIdUseEnum::ManufacturerRootCertificate:
|
||||
return ocpp::CertificateType::MFRootCertificate;
|
||||
case ocpp::v2::GetCertificateIdUseEnum::OEMRootCertificate:
|
||||
return ocpp::CertificateType::OEMRootCertificate;
|
||||
}
|
||||
|
||||
throw EnumConversionException("Could not convert GetCertificateIdUseEnum to CertificateType");
|
||||
}
|
||||
|
||||
std::vector<ocpp::CertificateType> from_ocpp_v2(const std::vector<ocpp::v2::GetCertificateIdUseEnum>& other) {
|
||||
std::vector<ocpp::CertificateType> certificate_types;
|
||||
certificate_types.reserve(other.size());
|
||||
for (const auto& certificate_id_use_enum : other) {
|
||||
certificate_types.push_back(from_ocpp_v2(certificate_id_use_enum));
|
||||
}
|
||||
return certificate_types;
|
||||
}
|
||||
|
||||
ocpp::CaCertificateType from_ocpp_v2(ocpp::v2::InstallCertificateUseEnum other) {
|
||||
switch (other) {
|
||||
case ocpp::v2::InstallCertificateUseEnum::V2GRootCertificate:
|
||||
return ocpp::CaCertificateType::V2G;
|
||||
case ocpp::v2::InstallCertificateUseEnum::MORootCertificate:
|
||||
return ocpp::CaCertificateType::MO;
|
||||
case ocpp::v2::InstallCertificateUseEnum::CSMSRootCertificate:
|
||||
return ocpp::CaCertificateType::CSMS;
|
||||
case ocpp::v2::InstallCertificateUseEnum::ManufacturerRootCertificate:
|
||||
return ocpp::CaCertificateType::MF;
|
||||
case ocpp::v2::InstallCertificateUseEnum::OEMRootCertificate:
|
||||
return ocpp::CaCertificateType::OEM;
|
||||
}
|
||||
|
||||
throw EnumConversionException("Could not convert CaCertificateType to InstallCertificateUseEnum");
|
||||
}
|
||||
|
||||
ocpp::CertificateSigningUseEnum from_ocpp_v2(ocpp::v2::CertificateSigningUseEnum other) {
|
||||
switch (other) {
|
||||
case ocpp::v2::CertificateSigningUseEnum::ChargingStationCertificate:
|
||||
return ocpp::CertificateSigningUseEnum::ChargingStationCertificate;
|
||||
case ocpp::v2::CertificateSigningUseEnum::V2GCertificate:
|
||||
return ocpp::CertificateSigningUseEnum::V2GCertificate;
|
||||
case ocpp::v2::CertificateSigningUseEnum::V2G20Certificate:
|
||||
return ocpp::CertificateSigningUseEnum::V2G20Certificate;
|
||||
}
|
||||
|
||||
throw EnumConversionException("Could not convert CertificateSigningUseEnum to CertificateSigningUseEnum");
|
||||
}
|
||||
|
||||
ocpp::HashAlgorithmEnumType from_ocpp_v2(ocpp::v2::HashAlgorithmEnum other) {
|
||||
switch (other) {
|
||||
case ocpp::v2::HashAlgorithmEnum::SHA256:
|
||||
return ocpp::HashAlgorithmEnumType::SHA256;
|
||||
case ocpp::v2::HashAlgorithmEnum::SHA384:
|
||||
return ocpp::HashAlgorithmEnumType::SHA384;
|
||||
case ocpp::v2::HashAlgorithmEnum::SHA512:
|
||||
return ocpp::HashAlgorithmEnumType::SHA512;
|
||||
}
|
||||
|
||||
throw EnumConversionException("Could not convert HashAlgorithmEnum to HashAlgorithmEnumType");
|
||||
}
|
||||
|
||||
ocpp::InstallCertificateResult from_ocpp_v2(ocpp::v2::InstallCertificateStatusEnum other) {
|
||||
switch (other) {
|
||||
case ocpp::v2::InstallCertificateStatusEnum::Rejected:
|
||||
return ocpp::InstallCertificateResult::InvalidCertificateChain;
|
||||
case ocpp::v2::InstallCertificateStatusEnum::Failed:
|
||||
return ocpp::InstallCertificateResult::WriteError;
|
||||
case ocpp::v2::InstallCertificateStatusEnum::Accepted:
|
||||
return ocpp::InstallCertificateResult::Accepted;
|
||||
}
|
||||
|
||||
throw EnumConversionException(
|
||||
"Could not convert InstallCertificateResult to evse_security::InstallCertificateResult");
|
||||
}
|
||||
|
||||
ocpp::DeleteCertificateResult from_ocpp_v2(ocpp::v2::DeleteCertificateStatusEnum other) {
|
||||
switch (other) {
|
||||
case ocpp::v2::DeleteCertificateStatusEnum::Accepted:
|
||||
return ocpp::DeleteCertificateResult::Accepted;
|
||||
case ocpp::v2::DeleteCertificateStatusEnum::Failed:
|
||||
return ocpp::DeleteCertificateResult::Failed;
|
||||
case ocpp::v2::DeleteCertificateStatusEnum::NotFound:
|
||||
return ocpp::DeleteCertificateResult::NotFound;
|
||||
}
|
||||
|
||||
throw EnumConversionException(
|
||||
"Could not convert DeleteCertificateResult to evse_security::DeleteCertificateResult");
|
||||
}
|
||||
|
||||
ocpp::CertificateHashDataType from_ocpp_v2(ocpp::v2::CertificateHashDataType other) {
|
||||
ocpp::CertificateHashDataType lhs;
|
||||
lhs.hashAlgorithm = from_ocpp_v2(other.hashAlgorithm);
|
||||
lhs.issuerNameHash = other.issuerNameHash;
|
||||
lhs.issuerKeyHash = other.issuerKeyHash;
|
||||
lhs.serialNumber = other.serialNumber;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
ocpp::CertificateHashDataChain from_ocpp_v2(ocpp::v2::CertificateHashDataChain other) {
|
||||
ocpp::CertificateHashDataChain lhs;
|
||||
lhs.certificateType = from_ocpp_v2(other.certificateType);
|
||||
lhs.certificateHashData = from_ocpp_v2(other.certificateHashData);
|
||||
if (other.childCertificateHashData.has_value()) {
|
||||
std::vector<ocpp::CertificateHashDataType> v;
|
||||
for (const auto& certificate_hash_data : other.childCertificateHashData.value()) {
|
||||
v.push_back(from_ocpp_v2(certificate_hash_data));
|
||||
}
|
||||
lhs.childCertificateHashData = v;
|
||||
}
|
||||
return lhs;
|
||||
}
|
||||
|
||||
ocpp::OCSPRequestData from_ocpp_v2(ocpp::v2::OCSPRequestData other) {
|
||||
ocpp::OCSPRequestData lhs;
|
||||
lhs.issuerNameHash = other.issuerNameHash;
|
||||
lhs.issuerKeyHash = other.issuerKeyHash;
|
||||
lhs.serialNumber = other.serialNumber;
|
||||
lhs.responderUrl = other.responderURL;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
} // namespace evse_security_conversions
|
||||
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,538 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <ocpp/common/evse_security_impl.hpp>
|
||||
|
||||
namespace ocpp {
|
||||
EvseSecurityImpl::EvseSecurityImpl(const SecurityConfiguration& security_configuration) {
|
||||
evse_security::FilePaths file_paths;
|
||||
file_paths.csms_ca_bundle = security_configuration.csms_ca_bundle;
|
||||
file_paths.mf_ca_bundle = security_configuration.mf_ca_bundle;
|
||||
file_paths.mo_ca_bundle = security_configuration.mo_ca_bundle;
|
||||
file_paths.v2g_ca_bundle = security_configuration.v2g_ca_bundle;
|
||||
|
||||
file_paths.directories.csms_leaf_cert_directory = security_configuration.csms_leaf_cert_directory;
|
||||
file_paths.directories.csms_leaf_key_directory = security_configuration.csms_leaf_key_directory;
|
||||
file_paths.directories.secc_leaf_cert_directory = security_configuration.secc_leaf_cert_directory;
|
||||
file_paths.directories.secc_leaf_key_directory = security_configuration.secc_leaf_key_directory;
|
||||
|
||||
file_paths.links.secc_leaf_cert_link = security_configuration.secc_leaf_cert_link;
|
||||
file_paths.links.secc_leaf_key_link = security_configuration.secc_leaf_key_link;
|
||||
file_paths.links.cpo_cert_chain_link = security_configuration.cpo_cert_chain_link;
|
||||
|
||||
this->evse_security =
|
||||
std::make_unique<evse_security::EvseSecurity>(file_paths, security_configuration.private_key_password);
|
||||
}
|
||||
|
||||
InstallCertificateResult EvseSecurityImpl::install_ca_certificate(const std::string& certificate,
|
||||
const CaCertificateType& certificate_type) {
|
||||
return conversions::to_ocpp(
|
||||
this->evse_security->install_ca_certificate(certificate, conversions::from_ocpp(certificate_type)));
|
||||
}
|
||||
|
||||
DeleteCertificateResult EvseSecurityImpl::delete_certificate(const CertificateHashDataType& certificate_hash_data) {
|
||||
return conversions::to_ocpp(
|
||||
this->evse_security->delete_certificate(conversions::from_ocpp(certificate_hash_data)).result);
|
||||
}
|
||||
|
||||
InstallCertificateResult EvseSecurityImpl::update_leaf_certificate(const std::string& certificate_chain,
|
||||
const CertificateSigningUseEnum& certificate_type) {
|
||||
return conversions::to_ocpp(
|
||||
this->evse_security->update_leaf_certificate(certificate_chain, conversions::from_ocpp(certificate_type)));
|
||||
}
|
||||
|
||||
CertificateValidationResult EvseSecurityImpl::verify_certificate(const std::string& certificate_chain,
|
||||
const LeafCertificateType& certificate_type) {
|
||||
|
||||
return conversions::to_ocpp(
|
||||
this->evse_security->verify_certificate(certificate_chain, conversions::from_ocpp(certificate_type)));
|
||||
}
|
||||
|
||||
CertificateValidationResult
|
||||
EvseSecurityImpl::verify_certificate(const std::string& certificate_chain,
|
||||
const std::vector<LeafCertificateType>& certificate_types) {
|
||||
std::vector<evse_security::LeafCertificateType> _certificate_types;
|
||||
|
||||
_certificate_types.reserve(certificate_types.size());
|
||||
for (const auto& certificate_type : certificate_types) {
|
||||
_certificate_types.push_back(conversions::from_ocpp(certificate_type));
|
||||
}
|
||||
|
||||
return conversions::to_ocpp(this->evse_security->verify_certificate(certificate_chain, _certificate_types));
|
||||
}
|
||||
|
||||
std::vector<CertificateHashDataChain>
|
||||
EvseSecurityImpl::get_installed_certificates(const std::vector<CertificateType>& certificate_types) {
|
||||
std::vector<CertificateHashDataChain> result;
|
||||
|
||||
std::vector<evse_security::CertificateType> _certificate_types;
|
||||
|
||||
_certificate_types.reserve(certificate_types.size());
|
||||
for (const auto& certificate_type : certificate_types) {
|
||||
_certificate_types.push_back(conversions::from_ocpp(certificate_type));
|
||||
}
|
||||
|
||||
const auto installed_certificates = this->evse_security->get_installed_certificates(_certificate_types);
|
||||
|
||||
result.reserve(installed_certificates.certificate_hash_data_chain.size());
|
||||
for (const auto& certificate_hash_data : installed_certificates.certificate_hash_data_chain) {
|
||||
result.push_back(conversions::to_ocpp(certificate_hash_data));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<OCSPRequestData> EvseSecurityImpl::get_v2g_ocsp_request_data() {
|
||||
std::vector<OCSPRequestData> result;
|
||||
|
||||
const auto ocsp_request_data = this->evse_security->get_v2g_ocsp_request_data();
|
||||
result.reserve(ocsp_request_data.ocsp_request_data_list.size());
|
||||
for (const auto& ocsp_request_entry : ocsp_request_data.ocsp_request_data_list) {
|
||||
result.push_back(conversions::to_ocpp(ocsp_request_entry));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<OCSPRequestData> EvseSecurityImpl::get_mo_ocsp_request_data(const std::string& certificate_chain) {
|
||||
std::vector<OCSPRequestData> result;
|
||||
|
||||
const auto ocsp_request_data = this->evse_security->get_mo_ocsp_request_data(certificate_chain);
|
||||
result.reserve(ocsp_request_data.ocsp_request_data_list.size());
|
||||
for (const auto& ocsp_request_entry : ocsp_request_data.ocsp_request_data_list) {
|
||||
result.push_back(conversions::to_ocpp(ocsp_request_entry));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void EvseSecurityImpl::update_ocsp_cache(const CertificateHashDataType& certificate_hash_data,
|
||||
const std::string& ocsp_response) {
|
||||
this->evse_security->update_ocsp_cache(conversions::from_ocpp(certificate_hash_data), ocsp_response);
|
||||
}
|
||||
|
||||
bool EvseSecurityImpl::is_ca_certificate_installed(const CaCertificateType& certificate_type) {
|
||||
return this->evse_security->is_ca_certificate_installed(conversions::from_ocpp(certificate_type));
|
||||
}
|
||||
|
||||
GetCertificateSignRequestResult
|
||||
EvseSecurityImpl::generate_certificate_signing_request(const CertificateSigningUseEnum& certificate_type,
|
||||
const std::string& country, const std::string& organization,
|
||||
const std::string& common, bool use_tpm) {
|
||||
auto csr_response = this->evse_security->generate_certificate_signing_request(
|
||||
conversions::from_ocpp(certificate_type), country, organization, common, use_tpm);
|
||||
|
||||
GetCertificateSignRequestResult result;
|
||||
|
||||
result.status = conversions::to_ocpp(csr_response.status);
|
||||
result.csr = csr_response.csr;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
GetCertificateInfoResult EvseSecurityImpl::get_leaf_certificate_info(const CertificateSigningUseEnum& certificate_type,
|
||||
bool include_ocsp) {
|
||||
const auto info_response = this->evse_security->get_leaf_certificate_info(
|
||||
conversions::from_ocpp(certificate_type), evse_security::EncodingFormat::PEM, include_ocsp);
|
||||
|
||||
GetCertificateInfoResult result;
|
||||
|
||||
result.status = conversions::to_ocpp(info_response.status);
|
||||
if (info_response.info.has_value()) {
|
||||
result.info = conversions::to_ocpp(info_response.info.value());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool EvseSecurityImpl::update_certificate_links(const CertificateSigningUseEnum& certificate_type) {
|
||||
return this->evse_security->update_certificate_links(conversions::from_ocpp(certificate_type));
|
||||
}
|
||||
|
||||
std::string EvseSecurityImpl::get_verify_file(const CaCertificateType& certificate_type) {
|
||||
return this->evse_security->get_verify_file(conversions::from_ocpp(certificate_type));
|
||||
}
|
||||
|
||||
std::string EvseSecurityImpl::get_verify_location(const CaCertificateType& certificate_type) {
|
||||
return this->evse_security->get_verify_location(conversions::from_ocpp(certificate_type));
|
||||
}
|
||||
|
||||
int EvseSecurityImpl::get_leaf_expiry_days_count(const CertificateSigningUseEnum& certificate_type) {
|
||||
return this->evse_security->get_leaf_expiry_days_count(conversions::from_ocpp(certificate_type));
|
||||
}
|
||||
|
||||
namespace conversions {
|
||||
|
||||
GetCertificateSignRequestStatus to_ocpp(evse_security::GetCertificateSignRequestStatus other) {
|
||||
switch (other) {
|
||||
case evse_security::GetCertificateSignRequestStatus::Accepted:
|
||||
return GetCertificateSignRequestStatus::Accepted;
|
||||
case evse_security::GetCertificateSignRequestStatus::InvalidRequestedType:
|
||||
return GetCertificateSignRequestStatus::InvalidRequestedType;
|
||||
case evse_security::GetCertificateSignRequestStatus::KeyGenError:
|
||||
return GetCertificateSignRequestStatus::KeyGenError;
|
||||
case evse_security::GetCertificateSignRequestStatus::GenerationError:
|
||||
return GetCertificateSignRequestStatus::GenerationError;
|
||||
}
|
||||
throw EnumConversionException(
|
||||
"Could not convert evse_security::GetCertificateSignRequestStatus to GetCertificateSignRequestStatus");
|
||||
}
|
||||
|
||||
CaCertificateType to_ocpp(evse_security::CaCertificateType other) {
|
||||
switch (other) {
|
||||
case evse_security::CaCertificateType::V2G:
|
||||
return CaCertificateType::V2G;
|
||||
case evse_security::CaCertificateType::MO:
|
||||
return CaCertificateType::MO;
|
||||
case evse_security::CaCertificateType::CSMS:
|
||||
return CaCertificateType::CSMS;
|
||||
case evse_security::CaCertificateType::MF:
|
||||
return CaCertificateType::MF;
|
||||
}
|
||||
|
||||
throw EnumConversionException("Could not convert evse_security::CaCertificateType to CaCertificateType");
|
||||
}
|
||||
|
||||
CertificateType to_ocpp(evse_security::CertificateType other) {
|
||||
switch (other) {
|
||||
case evse_security::CertificateType::V2GRootCertificate:
|
||||
return CertificateType::V2GRootCertificate;
|
||||
case evse_security::CertificateType::MORootCertificate:
|
||||
return CertificateType::MORootCertificate;
|
||||
case evse_security::CertificateType::CSMSRootCertificate:
|
||||
return CertificateType::CSMSRootCertificate;
|
||||
case evse_security::CertificateType::V2GCertificateChain:
|
||||
return CertificateType::V2GCertificateChain;
|
||||
case evse_security::CertificateType::MFRootCertificate:
|
||||
return CertificateType::MFRootCertificate;
|
||||
}
|
||||
throw EnumConversionException("Could not convert evse_security::CertificateType to CertificateType");
|
||||
}
|
||||
|
||||
HashAlgorithmEnumType to_ocpp(evse_security::HashAlgorithm other) {
|
||||
switch (other) {
|
||||
case evse_security::HashAlgorithm::SHA256:
|
||||
return HashAlgorithmEnumType::SHA256;
|
||||
case evse_security::HashAlgorithm::SHA384:
|
||||
return HashAlgorithmEnumType::SHA384;
|
||||
case evse_security::HashAlgorithm::SHA512:
|
||||
return HashAlgorithmEnumType::SHA512;
|
||||
}
|
||||
throw EnumConversionException("Could not convert evse_security::HashAlgorithm to HashAlgorithmEnumType");
|
||||
}
|
||||
|
||||
GetCertificateInfoStatus to_ocpp(evse_security::GetCertificateInfoStatus other) {
|
||||
switch (other) {
|
||||
case evse_security::GetCertificateInfoStatus::Accepted:
|
||||
return GetCertificateInfoStatus::Accepted;
|
||||
case evse_security::GetCertificateInfoStatus::Rejected:
|
||||
return GetCertificateInfoStatus::Rejected;
|
||||
case evse_security::GetCertificateInfoStatus::NotFound:
|
||||
return GetCertificateInfoStatus::NotFound;
|
||||
case evse_security::GetCertificateInfoStatus::NotFoundValid:
|
||||
return GetCertificateInfoStatus::NotFoundValid;
|
||||
case evse_security::GetCertificateInfoStatus::PrivateKeyNotFound:
|
||||
return GetCertificateInfoStatus::PrivateKeyNotFound;
|
||||
}
|
||||
throw EnumConversionException(
|
||||
"Could not convert evse_security::GetCertificateInfoStatus to GetCertificateInfoStatus");
|
||||
}
|
||||
|
||||
InstallCertificateResult to_ocpp(evse_security::InstallCertificateResult other) {
|
||||
switch (other) {
|
||||
case evse_security::InstallCertificateResult::InvalidSignature:
|
||||
return InstallCertificateResult::InvalidSignature;
|
||||
case evse_security::InstallCertificateResult::InvalidCertificateChain:
|
||||
return InstallCertificateResult::InvalidCertificateChain;
|
||||
case evse_security::InstallCertificateResult::InvalidFormat:
|
||||
return InstallCertificateResult::InvalidFormat;
|
||||
case evse_security::InstallCertificateResult::InvalidCommonName:
|
||||
return InstallCertificateResult::InvalidCommonName;
|
||||
case evse_security::InstallCertificateResult::NoRootCertificateInstalled:
|
||||
return InstallCertificateResult::NoRootCertificateInstalled;
|
||||
case evse_security::InstallCertificateResult::Expired:
|
||||
return InstallCertificateResult::Expired;
|
||||
case evse_security::InstallCertificateResult::CertificateStoreMaxLengthExceeded:
|
||||
return InstallCertificateResult::CertificateStoreMaxLengthExceeded;
|
||||
case evse_security::InstallCertificateResult::WriteError:
|
||||
return InstallCertificateResult::WriteError;
|
||||
case evse_security::InstallCertificateResult::Accepted:
|
||||
return InstallCertificateResult::Accepted;
|
||||
}
|
||||
throw EnumConversionException(
|
||||
"Could not convert evse_security::InstallCertificateResult to InstallCertificateResult");
|
||||
}
|
||||
|
||||
CertificateValidationResult to_ocpp(evse_security::CertificateValidationResult other) {
|
||||
switch (other) {
|
||||
case evse_security::CertificateValidationResult::Valid:
|
||||
return CertificateValidationResult::Valid;
|
||||
case evse_security::CertificateValidationResult::InvalidSignature:
|
||||
return CertificateValidationResult::InvalidSignature;
|
||||
case evse_security::CertificateValidationResult::IssuerNotFound:
|
||||
return CertificateValidationResult::IssuerNotFound;
|
||||
case evse_security::CertificateValidationResult::InvalidLeafSignature:
|
||||
return CertificateValidationResult::InvalidLeafSignature;
|
||||
case evse_security::CertificateValidationResult::InvalidChain:
|
||||
return CertificateValidationResult::InvalidChain;
|
||||
case evse_security::CertificateValidationResult::Unknown:
|
||||
return CertificateValidationResult::Unknown;
|
||||
case evse_security::CertificateValidationResult::Expired:
|
||||
return CertificateValidationResult::Expired;
|
||||
}
|
||||
throw EnumConversionException(
|
||||
"Could not convert evse_security::CertificateValidationResult to CertificateValidationResult");
|
||||
}
|
||||
|
||||
DeleteCertificateResult to_ocpp(evse_security::DeleteCertificateResult other) {
|
||||
switch (other) {
|
||||
case evse_security::DeleteCertificateResult::Accepted:
|
||||
return DeleteCertificateResult::Accepted;
|
||||
case evse_security::DeleteCertificateResult::Failed:
|
||||
return DeleteCertificateResult::Failed;
|
||||
case evse_security::DeleteCertificateResult::NotFound:
|
||||
return DeleteCertificateResult::NotFound;
|
||||
}
|
||||
throw EnumConversionException(
|
||||
"Could not convert evse_security::DeleteCertificateResult to DeleteCertificateResult");
|
||||
}
|
||||
|
||||
CertificateHashDataType to_ocpp(evse_security::CertificateHashData other) {
|
||||
CertificateHashDataType lhs;
|
||||
lhs.hashAlgorithm = to_ocpp(other.hash_algorithm);
|
||||
lhs.issuerNameHash = other.issuer_name_hash;
|
||||
lhs.issuerKeyHash = other.issuer_key_hash;
|
||||
lhs.serialNumber = other.serial_number;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
CertificateHashDataChain to_ocpp(evse_security::CertificateHashDataChain other) {
|
||||
CertificateHashDataChain lhs;
|
||||
lhs.certificateType = to_ocpp(other.certificate_type);
|
||||
lhs.certificateHashData = to_ocpp(other.certificate_hash_data);
|
||||
|
||||
std::vector<CertificateHashDataType> v;
|
||||
v.reserve(other.child_certificate_hash_data.size());
|
||||
for (const auto& certificate_hash_data : other.child_certificate_hash_data) {
|
||||
v.push_back(to_ocpp(certificate_hash_data));
|
||||
}
|
||||
lhs.childCertificateHashData = v;
|
||||
|
||||
return lhs;
|
||||
}
|
||||
|
||||
OCSPRequestData to_ocpp(evse_security::OCSPRequestData other) {
|
||||
OCSPRequestData lhs;
|
||||
if (other.certificate_hash_data.has_value()) {
|
||||
lhs.issuerNameHash = other.certificate_hash_data.value().issuer_name_hash;
|
||||
lhs.issuerKeyHash = other.certificate_hash_data.value().issuer_key_hash;
|
||||
lhs.serialNumber = other.certificate_hash_data.value().serial_number;
|
||||
lhs.hashAlgorithm = to_ocpp(other.certificate_hash_data.value().hash_algorithm);
|
||||
}
|
||||
if (other.responder_url.has_value()) {
|
||||
lhs.responderUrl = other.responder_url.value();
|
||||
}
|
||||
return lhs;
|
||||
}
|
||||
|
||||
CertificateOCSP to_ocpp(evse_security::CertificateOCSP other) {
|
||||
CertificateOCSP lhs;
|
||||
lhs.hash = to_ocpp(other.hash);
|
||||
lhs.ocsp_path = other.ocsp_path;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
CertificateInfo to_ocpp(evse_security::CertificateInfo other) {
|
||||
CertificateInfo lhs;
|
||||
lhs.certificate_path = other.certificate;
|
||||
lhs.certificate_single_path = other.certificate_single;
|
||||
lhs.certificate_count = other.certificate_count;
|
||||
lhs.key_path = other.key;
|
||||
lhs.password = other.password;
|
||||
|
||||
if (other.ocsp.empty() == false) {
|
||||
for (auto& ocsp_data : other.ocsp) {
|
||||
lhs.ocsp.push_back(to_ocpp(ocsp_data));
|
||||
}
|
||||
}
|
||||
|
||||
return lhs;
|
||||
}
|
||||
|
||||
evse_security::CaCertificateType from_ocpp(CaCertificateType other) {
|
||||
switch (other) {
|
||||
case CaCertificateType::V2G:
|
||||
return evse_security::CaCertificateType::V2G;
|
||||
case CaCertificateType::MO:
|
||||
return evse_security::CaCertificateType::MO;
|
||||
case CaCertificateType::CSMS:
|
||||
return evse_security::CaCertificateType::CSMS;
|
||||
case CaCertificateType::MF:
|
||||
return evse_security::CaCertificateType::MF;
|
||||
case CaCertificateType::OEM:
|
||||
// FIXME: Add OEM to evse_security::CaCertificateType
|
||||
throw EnumConversionException("Could not convert CaCertificateType::OEM to evse_security::CaCertificateType");
|
||||
}
|
||||
throw EnumConversionException("Could not convert CaCertificateType to evse_security::CaCertificateType");
|
||||
}
|
||||
|
||||
evse_security::LeafCertificateType from_ocpp(LeafCertificateType other) {
|
||||
switch (other) {
|
||||
case LeafCertificateType::V2G:
|
||||
return evse_security::LeafCertificateType::V2G;
|
||||
case LeafCertificateType::MO:
|
||||
return evse_security::LeafCertificateType::MO;
|
||||
case LeafCertificateType::CSMS:
|
||||
return evse_security::LeafCertificateType::CSMS;
|
||||
case LeafCertificateType::MF:
|
||||
return evse_security::LeafCertificateType::MF;
|
||||
}
|
||||
throw EnumConversionException("Could not convert evse_security::CaCertificateType to CaCertificateType");
|
||||
}
|
||||
|
||||
evse_security::LeafCertificateType from_ocpp(CertificateSigningUseEnum other) {
|
||||
switch (other) {
|
||||
case CertificateSigningUseEnum::ChargingStationCertificate:
|
||||
return evse_security::LeafCertificateType::CSMS;
|
||||
case CertificateSigningUseEnum::V2GCertificate:
|
||||
return evse_security::LeafCertificateType::V2G;
|
||||
case CertificateSigningUseEnum::ManufacturerCertificate:
|
||||
return evse_security::LeafCertificateType::MF;
|
||||
case CertificateSigningUseEnum::V2G20Certificate:
|
||||
// FIXME: Add V2G20Certificate to evse_security::LeafCertificateType
|
||||
throw EnumConversionException(
|
||||
"Could not convert CertificateSigningUseEnum::V2G20Certificate to evse_security::LeafCertificateType");
|
||||
}
|
||||
throw EnumConversionException("Could not convert CertificateSigningUseEnum to evse_security::LeafCertificateType");
|
||||
}
|
||||
|
||||
evse_security::CertificateType from_ocpp(CertificateType other) {
|
||||
switch (other) {
|
||||
case CertificateType::V2GRootCertificate:
|
||||
return evse_security::CertificateType::V2GRootCertificate;
|
||||
case CertificateType::MORootCertificate:
|
||||
return evse_security::CertificateType::MORootCertificate;
|
||||
case CertificateType::CSMSRootCertificate:
|
||||
return evse_security::CertificateType::CSMSRootCertificate;
|
||||
case CertificateType::V2GCertificateChain:
|
||||
return evse_security::CertificateType::V2GCertificateChain;
|
||||
case CertificateType::MFRootCertificate:
|
||||
return evse_security::CertificateType::MFRootCertificate;
|
||||
case CertificateType::OEMRootCertificate:
|
||||
throw EnumConversionException(
|
||||
"Could not convert CertificateType::OEMRootCertificate to evse_security::CertificateType");
|
||||
}
|
||||
throw EnumConversionException("Could not convert CertificateType to evse_security::CertificateType");
|
||||
}
|
||||
|
||||
evse_security::HashAlgorithm from_ocpp(HashAlgorithmEnumType other) {
|
||||
switch (other) {
|
||||
case HashAlgorithmEnumType::SHA256:
|
||||
return evse_security::HashAlgorithm::SHA256;
|
||||
case HashAlgorithmEnumType::SHA384:
|
||||
return evse_security::HashAlgorithm::SHA384;
|
||||
case HashAlgorithmEnumType::SHA512:
|
||||
return evse_security::HashAlgorithm::SHA512;
|
||||
}
|
||||
throw EnumConversionException("Could not convert HashAlgorithmEnumType to evse_security::HashAlgorithm");
|
||||
}
|
||||
|
||||
evse_security::InstallCertificateResult from_ocpp(InstallCertificateResult other) {
|
||||
switch (other) {
|
||||
case InstallCertificateResult::InvalidSignature:
|
||||
return evse_security::InstallCertificateResult::InvalidSignature;
|
||||
case InstallCertificateResult::InvalidCertificateChain:
|
||||
return evse_security::InstallCertificateResult::InvalidCertificateChain;
|
||||
case InstallCertificateResult::InvalidFormat:
|
||||
return evse_security::InstallCertificateResult::InvalidFormat;
|
||||
case InstallCertificateResult::InvalidCommonName:
|
||||
return evse_security::InstallCertificateResult::InvalidCommonName;
|
||||
case InstallCertificateResult::NoRootCertificateInstalled:
|
||||
return evse_security::InstallCertificateResult::NoRootCertificateInstalled;
|
||||
case InstallCertificateResult::Expired:
|
||||
return evse_security::InstallCertificateResult::Expired;
|
||||
case InstallCertificateResult::CertificateStoreMaxLengthExceeded:
|
||||
return evse_security::InstallCertificateResult::CertificateStoreMaxLengthExceeded;
|
||||
case InstallCertificateResult::WriteError:
|
||||
return evse_security::InstallCertificateResult::WriteError;
|
||||
case InstallCertificateResult::Accepted:
|
||||
return evse_security::InstallCertificateResult::Accepted;
|
||||
}
|
||||
throw EnumConversionException(
|
||||
"Could not convert InstallCertificateResult to evse_security::InstallCertificateResult");
|
||||
}
|
||||
|
||||
evse_security::DeleteCertificateResult from_ocpp(DeleteCertificateResult other) {
|
||||
switch (other) {
|
||||
case DeleteCertificateResult::Accepted:
|
||||
return evse_security::DeleteCertificateResult::Accepted;
|
||||
case DeleteCertificateResult::Failed:
|
||||
return evse_security::DeleteCertificateResult::Failed;
|
||||
case DeleteCertificateResult::NotFound:
|
||||
return evse_security::DeleteCertificateResult::NotFound;
|
||||
}
|
||||
throw EnumConversionException(
|
||||
"Could not convert DeleteCertificateResult to evse_security::DeleteCertificateResult");
|
||||
}
|
||||
|
||||
evse_security::CertificateHashData from_ocpp(CertificateHashDataType other) {
|
||||
evse_security::CertificateHashData lhs;
|
||||
lhs.hash_algorithm = from_ocpp(other.hashAlgorithm);
|
||||
lhs.issuer_name_hash = other.issuerNameHash;
|
||||
lhs.issuer_key_hash = other.issuerKeyHash;
|
||||
lhs.serial_number = other.serialNumber;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
evse_security::CertificateHashDataChain from_ocpp(CertificateHashDataChain other) {
|
||||
evse_security::CertificateHashDataChain lhs;
|
||||
lhs.certificate_type = from_ocpp(other.certificateType);
|
||||
lhs.certificate_hash_data = from_ocpp(other.certificateHashData);
|
||||
if (other.childCertificateHashData.has_value()) {
|
||||
std::vector<evse_security::CertificateHashData> v;
|
||||
for (const auto& certificate_hash_data : other.childCertificateHashData.value()) {
|
||||
v.push_back(from_ocpp(certificate_hash_data));
|
||||
}
|
||||
lhs.child_certificate_hash_data = v;
|
||||
}
|
||||
return lhs;
|
||||
}
|
||||
|
||||
evse_security::OCSPRequestData from_ocpp(OCSPRequestData other) {
|
||||
evse_security::OCSPRequestData lhs;
|
||||
evse_security::CertificateHashData certificate_hash_data;
|
||||
certificate_hash_data.issuer_name_hash = other.issuerNameHash;
|
||||
certificate_hash_data.issuer_key_hash = other.issuerKeyHash;
|
||||
certificate_hash_data.serial_number = other.serialNumber;
|
||||
certificate_hash_data.hash_algorithm = from_ocpp(other.hashAlgorithm);
|
||||
lhs.certificate_hash_data = certificate_hash_data;
|
||||
lhs.responder_url = other.responderUrl;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
evse_security::CertificateOCSP from_ocpp(CertificateOCSP other) {
|
||||
evse_security::CertificateOCSP lhs;
|
||||
lhs.hash = from_ocpp(other.hash);
|
||||
lhs.ocsp_path = other.ocsp_path;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
evse_security::CertificateInfo from_ocpp(CertificateInfo other) {
|
||||
evse_security::CertificateInfo lhs;
|
||||
lhs.certificate = other.certificate_path;
|
||||
lhs.certificate_single = other.certificate_single_path;
|
||||
lhs.certificate_count = other.certificate_count;
|
||||
lhs.key = other.key_path;
|
||||
lhs.password = other.password;
|
||||
|
||||
if (other.ocsp.empty() == false) {
|
||||
for (auto& ocsp_data : other.ocsp) {
|
||||
lhs.ocsp.push_back(from_ocpp(ocsp_data));
|
||||
}
|
||||
}
|
||||
|
||||
return lhs;
|
||||
}
|
||||
|
||||
} // namespace conversions
|
||||
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,514 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include <algorithm>
|
||||
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
#include <ocpp/common/call_types.hpp>
|
||||
#include <ocpp/common/ocpp_logging.hpp>
|
||||
#include <ocpp/common/types.hpp>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace {
|
||||
/// \brief Add opening html tags to the given stream \p os
|
||||
void open_html_tags(std::ofstream& os);
|
||||
|
||||
/// \brief Add closing html tags to the given stream \p os
|
||||
void close_html_tags(std::ofstream& os);
|
||||
|
||||
/// \returns a datetime string in YearMonthDayHourMinuteSecond format
|
||||
std::string get_datetime_string();
|
||||
|
||||
/// \returns file size of the given path or 0 if the file does not exist
|
||||
std::uintmax_t safe_file_size(const std::filesystem::path& path);
|
||||
} // namespace
|
||||
|
||||
MessageLogging::MessageLogging(
|
||||
bool log_messages, const std::string& message_log_path, const std::string& output_file_name, bool log_to_console,
|
||||
bool detailed_log_to_console, bool log_to_file, bool log_to_html, bool log_raw, bool log_security,
|
||||
bool session_logging,
|
||||
std::function<void(const std::string& message, MessageDirection direction)> message_callback) :
|
||||
log_messages(log_messages),
|
||||
message_log_path(message_log_path),
|
||||
output_file_name(output_file_name),
|
||||
log_to_console(log_to_console),
|
||||
detailed_log_to_console(detailed_log_to_console),
|
||||
log_to_file(log_to_file),
|
||||
log_to_html(log_to_html),
|
||||
log_raw(log_raw),
|
||||
log_security(log_security),
|
||||
session_logging(session_logging),
|
||||
message_callback(message_callback),
|
||||
rotate_logs(false),
|
||||
date_suffix(false),
|
||||
maximum_file_size_bytes(0),
|
||||
maximum_file_count(0) {
|
||||
this->initialize();
|
||||
}
|
||||
|
||||
MessageLogging::MessageLogging(
|
||||
bool log_messages, const std::string& message_log_path, const std::string& output_file_name, bool log_to_console,
|
||||
bool detailed_log_to_console, bool log_to_file, bool log_to_html, bool log_raw, bool log_security,
|
||||
bool session_logging, std::function<void(const std::string& message, MessageDirection direction)> message_callback,
|
||||
LogRotationConfig log_rotation_config, std::function<void(LogRotationStatus status)> status_callback) :
|
||||
log_messages(log_messages),
|
||||
message_log_path(message_log_path),
|
||||
output_file_name(output_file_name),
|
||||
log_to_console(log_to_console),
|
||||
detailed_log_to_console(detailed_log_to_console),
|
||||
log_to_file(log_to_file),
|
||||
log_to_html(log_to_html),
|
||||
log_raw(log_raw),
|
||||
log_security(log_security),
|
||||
session_logging(session_logging),
|
||||
message_callback(message_callback),
|
||||
rotate_logs(true),
|
||||
date_suffix(log_rotation_config.date_suffix),
|
||||
maximum_file_size_bytes(log_rotation_config.maximum_file_size_bytes),
|
||||
maximum_file_count(log_rotation_config.maximum_file_count),
|
||||
status_callback(status_callback) {
|
||||
this->initialize();
|
||||
}
|
||||
|
||||
void MessageLogging::initialize() {
|
||||
if (this->log_messages) {
|
||||
if (this->rotate_logs) {
|
||||
EVLOG_debug << "Log rotation enabled";
|
||||
}
|
||||
if (this->log_to_console) {
|
||||
EVLOG_debug << "Logging OCPP messages to console";
|
||||
}
|
||||
if (this->message_callback != nullptr) {
|
||||
EVLOG_debug << "Logging OCPP messages to callback";
|
||||
}
|
||||
if (this->log_to_file) {
|
||||
auto output_file_path = message_log_path + "/";
|
||||
output_file_path += output_file_name;
|
||||
if (this->log_raw) {
|
||||
auto raw_output_file_path = output_file_path + "_raw.log";
|
||||
EVLOG_debug << "Logging raw OCPP messages to log file: " << raw_output_file_path;
|
||||
this->log_raw_file = std::filesystem::path(raw_output_file_path);
|
||||
this->log_raw_os.open(raw_output_file_path, std::ofstream::app);
|
||||
this->rotate_log_if_needed(this->log_raw_file, this->log_raw_os);
|
||||
}
|
||||
output_file_path += +".log";
|
||||
EVLOG_debug << "Logging OCPP messages to log file: " << output_file_path;
|
||||
this->log_file = std::filesystem::path(output_file_path);
|
||||
this->log_os.open(output_file_path, std::ofstream::app);
|
||||
this->rotate_log_if_needed(this->log_file, this->log_os);
|
||||
}
|
||||
|
||||
if (this->log_to_html) {
|
||||
auto html_file_path = message_log_path + "/";
|
||||
html_file_path += output_file_name;
|
||||
if (this->log_raw) {
|
||||
auto raw_html_file_path = html_file_path + "_raw.html";
|
||||
EVLOG_debug << "Logging raw OCPP messages to html file: " << raw_html_file_path;
|
||||
this->html_raw_log_file = std::filesystem::path(raw_html_file_path);
|
||||
this->html_raw_log_os.open(html_raw_log_file, std::ofstream::app);
|
||||
this->rotate_log_if_needed(
|
||||
this->html_raw_log_file, this->html_raw_log_os, [this](std::ofstream& os) { close_html_tags(os); },
|
||||
[this](std::ofstream& os) { open_html_tags(os); });
|
||||
|
||||
if (safe_file_size(this->html_raw_log_file) > 0) {
|
||||
// TODO: try to remove the end tags in the HTML if present
|
||||
} else {
|
||||
open_html_tags(this->html_raw_log_os);
|
||||
}
|
||||
}
|
||||
html_file_path += ".html";
|
||||
EVLOG_debug << "Logging OCPP messages to html file: " << html_file_path;
|
||||
this->html_log_file = std::filesystem::path(html_file_path);
|
||||
this->html_log_os.open(html_log_file, std::ofstream::app);
|
||||
this->rotate_log_if_needed(
|
||||
this->html_log_file, this->html_log_os, [this](std::ofstream& os) { close_html_tags(os); },
|
||||
[this](std::ofstream& os) { open_html_tags(os); });
|
||||
|
||||
if (safe_file_size(this->html_log_file) > 0) {
|
||||
// TODO: try to remove the end tags in the HTML if present
|
||||
} else {
|
||||
open_html_tags(this->html_log_os);
|
||||
}
|
||||
}
|
||||
if (this->log_security) {
|
||||
auto security_file_path = message_log_path + "/";
|
||||
security_file_path += output_file_name;
|
||||
security_file_path += ".security.log";
|
||||
EVLOG_debug << "Logging SecurityEvents to file: " << security_file_path;
|
||||
this->security_log_file = std::filesystem::path(security_file_path);
|
||||
this->security_log_os.open(security_log_file, std::ofstream::app);
|
||||
this->rotate_log_if_needed(this->security_log_file, this->security_log_os);
|
||||
}
|
||||
sys("Session logging started.");
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
void open_html_tags(std::ofstream& os) {
|
||||
os << "<html><head><title>EVerest OCPP log session</title>\n";
|
||||
os << "<style>"
|
||||
".log {"
|
||||
" font-family: Arial, Helvetica, sans-serif;"
|
||||
" border-collapse: collapse;"
|
||||
" width: 100%;"
|
||||
"}"
|
||||
".log td, .log th {"
|
||||
" border: 1px solid #ddd;"
|
||||
" padding: 8px;"
|
||||
" vertical-align: top;"
|
||||
"}"
|
||||
".log td:nth-child(1) { white-space: nowrap; }"
|
||||
".log td:nth-child(5) pre {"
|
||||
" white-space: pre-wrap;"
|
||||
" word-wrap: break-word;"
|
||||
" word-break: break-word;"
|
||||
"}"
|
||||
".log tr.CentralSystem{background-color: #E4E6F2;}"
|
||||
".log tr.ChargePoint{background-color: #F2F0E4;}"
|
||||
".log tr.SYS{background-color: white;}"
|
||||
".log th {"
|
||||
" padding-top: 12px;"
|
||||
" padding-bottom: 12px;"
|
||||
" text-align: left;"
|
||||
" vertical-align: top;"
|
||||
" background-color: #04AA6D;"
|
||||
" color: white;"
|
||||
"}"
|
||||
"</style>";
|
||||
os << "</head><body><table class=\"log\">\n";
|
||||
os.flush();
|
||||
}
|
||||
|
||||
void close_html_tags(std::ofstream& os) {
|
||||
os << "</table></body></html>\n";
|
||||
os.flush();
|
||||
}
|
||||
|
||||
std::string get_datetime_string() {
|
||||
return date::format("%Y%m%d%H%M%S", std::chrono::time_point_cast<std::chrono::seconds>(date::utc_clock::now()));
|
||||
}
|
||||
|
||||
std::uintmax_t safe_file_size(const std::filesystem::path& path) {
|
||||
try {
|
||||
return std::filesystem::file_size(path);
|
||||
} catch (...) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
LogRotationStatus MessageLogging::rotate_log(const std::string& file_basename) {
|
||||
LogRotationStatus status = LogRotationStatus::NotRotated;
|
||||
auto path = std::filesystem::path(this->message_log_path);
|
||||
std::vector<std::filesystem::path> files;
|
||||
for (const auto& entry : std::filesystem::directory_iterator(path)) {
|
||||
const auto& file_path = entry.path();
|
||||
if (std::filesystem::is_regular_file(entry)) {
|
||||
// check file
|
||||
if (file_path.filename() == file_basename or file_path.stem() == file_basename) {
|
||||
files.push_back(file_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
std::sort(files.begin(), files.end(), std::greater<std::filesystem::path>());
|
||||
|
||||
if (this->maximum_file_count > 0 and files.size() >= this->maximum_file_count) {
|
||||
// drop the oldest file
|
||||
EVLOG_info << "Removing oldest log file: " << files.front();
|
||||
std::filesystem::remove(files.front());
|
||||
files.erase(files.begin());
|
||||
status = LogRotationStatus::RotatedWithDeletion;
|
||||
}
|
||||
|
||||
// rename the oldest file first
|
||||
for (auto& file : files) {
|
||||
if (file.filename() == file_basename) {
|
||||
std::filesystem::path new_file_name;
|
||||
if (this->date_suffix) {
|
||||
new_file_name = std::filesystem::path(file.string() + "." + get_datetime_string());
|
||||
} else {
|
||||
// traditional .0 .1 ... suffix
|
||||
// does not have a .0 or .1, so needs a new one
|
||||
new_file_name = std::filesystem::path(file.string() + ".0");
|
||||
}
|
||||
|
||||
std::filesystem::rename(file, new_file_name);
|
||||
EVLOG_info << "Renaming: " << file.string() << " -> " << new_file_name.string();
|
||||
if (status == LogRotationStatus::NotRotated) {
|
||||
status = LogRotationStatus::Rotated;
|
||||
}
|
||||
} else {
|
||||
// try parsing the .x extension and log an error if this was not possible
|
||||
try {
|
||||
if (not this->date_suffix) {
|
||||
auto extension_str = file.extension().string();
|
||||
boost::replace_all(extension_str, ".", ""); // .extension() comes with the "."
|
||||
auto extension = std::stoi(extension_str);
|
||||
extension += 1;
|
||||
auto new_extension = std::to_string(extension);
|
||||
|
||||
std::filesystem::path new_file_name = file;
|
||||
new_file_name.replace_extension(new_extension);
|
||||
EVLOG_info << "Renaming: " << file.string() << " -> " << new_file_name.string();
|
||||
std::filesystem::rename(file, new_file_name);
|
||||
if (status == LogRotationStatus::NotRotated) {
|
||||
status = LogRotationStatus::Rotated;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (...) {
|
||||
EVLOG_warning << "Could not rename logfile: " << file.string();
|
||||
}
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
LogRotationStatus MessageLogging::rotate_log_if_needed(const std::filesystem::path& path, std::ofstream& os) {
|
||||
return rotate_log_if_needed(path, os, nullptr, nullptr);
|
||||
}
|
||||
|
||||
LogRotationStatus MessageLogging::rotate_log_if_needed(const std::filesystem::path& path, std::ofstream& os,
|
||||
std::function<void(std::ofstream& os)> before_close_of_os,
|
||||
std::function<void(std::ofstream& os)> after_open_of_os) {
|
||||
LogRotationStatus status = LogRotationStatus::NotRotated;
|
||||
if (not this->rotate_logs) {
|
||||
// do nothing if log rotation is turned off
|
||||
return LogRotationStatus::NotRotated;
|
||||
}
|
||||
if (maximum_file_size_bytes <= 0) {
|
||||
// do nothing if no maximum file size is set
|
||||
return LogRotationStatus::NotRotated;
|
||||
}
|
||||
auto log_file_size = safe_file_size(path);
|
||||
if (log_file_size >= maximum_file_size_bytes) {
|
||||
EVLOG_info << "Logfile: " << path.filename().string() << " file size (" << log_file_size << " bytes) >= ("
|
||||
<< maximum_file_size_bytes << " bytes) rotating log.";
|
||||
if (before_close_of_os != nullptr) {
|
||||
before_close_of_os(os);
|
||||
}
|
||||
os.close();
|
||||
os.clear();
|
||||
status = rotate_log(path.filename().string());
|
||||
os.open(path.string(), std::ofstream::app);
|
||||
if (after_open_of_os != nullptr) {
|
||||
after_open_of_os(os);
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
MessageLogging::~MessageLogging() {
|
||||
if (this->log_messages) {
|
||||
if (this->log_to_file) {
|
||||
this->log_os.close();
|
||||
}
|
||||
|
||||
if (this->log_to_html) {
|
||||
close_html_tags(this->html_log_os);
|
||||
this->html_log_os.close();
|
||||
}
|
||||
|
||||
if (this->log_security) {
|
||||
this->security_log_os.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MessageLogging::charge_point(const std::string& message_type, const std::string& json_str) {
|
||||
if (this->message_callback != nullptr) {
|
||||
this->message_callback(json_str, MessageDirection::ChargingStationToCSMS);
|
||||
}
|
||||
auto formatted = format_message(message_type, json_str);
|
||||
log_output(LogType::ChargePoint, formatted.message_type, formatted.message);
|
||||
if (this->session_logging) {
|
||||
const std::scoped_lock lock(this->session_id_logging_mutex);
|
||||
for (const auto& [session_id, logging] : this->session_id_logging) {
|
||||
logging->charge_point(message_type, json_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MessageLogging::central_system(const std::string& message_type, const std::string& json_str) {
|
||||
if (this->message_callback != nullptr) {
|
||||
this->message_callback(json_str, MessageDirection::CSMSToChargingStation);
|
||||
}
|
||||
auto formatted = format_message(message_type, json_str);
|
||||
log_output(LogType::CentralSystem, formatted.message_type, formatted.message);
|
||||
if (this->session_logging) {
|
||||
const std::scoped_lock lock(this->session_id_logging_mutex);
|
||||
for (const auto& [session_id, logging] : this->session_id_logging) {
|
||||
logging->central_system(message_type, json_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MessageLogging::sys(const std::string& msg) {
|
||||
log_output(LogType::System, msg, "");
|
||||
if (this->session_logging) {
|
||||
const std::scoped_lock lock(this->session_id_logging_mutex);
|
||||
for (const auto& [session_id, logging] : this->session_id_logging) {
|
||||
log_output(LogType::System, msg, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MessageLogging::security(const std::string& msg) {
|
||||
const std::lock_guard<std::mutex> lock(this->output_file_mutex);
|
||||
auto status = this->rotate_log_if_needed(this->security_log_file, this->security_log_os);
|
||||
if (status_callback != nullptr) {
|
||||
status_callback(status);
|
||||
}
|
||||
this->security_log_os << msg << "\n";
|
||||
this->security_log_os.flush();
|
||||
}
|
||||
|
||||
void MessageLogging::raw(const std::string& msg, LogType log_type) {
|
||||
if (this->log_raw) {
|
||||
log_output(log_type, msg, "", true);
|
||||
if (this->session_logging) {
|
||||
const std::scoped_lock lock(this->session_id_logging_mutex);
|
||||
for (const auto& [session_id, logging] : this->session_id_logging) {
|
||||
log_output(log_type, "", msg, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
std::string html_encode(const std::string& msg) {
|
||||
std::string out = msg;
|
||||
boost::replace_all(out, "<", "<");
|
||||
boost::replace_all(out, ">", ">");
|
||||
return out;
|
||||
}
|
||||
|
||||
void write_log_to_file(std::ofstream& log_os, LogType typ, const std::string& ts, const std::string& origin,
|
||||
const std::string& target, const std::string& message_type, const std::string& json_str) {
|
||||
log_os << ts << ": " << origin + ">" + target << " "
|
||||
<< (typ == LogType::ChargePoint || typ == LogType::System ? message_type : "") << " "
|
||||
<< (typ == LogType::CentralSystem ? message_type : "") << "\n"
|
||||
<< json_str << "\n\n";
|
||||
log_os.flush();
|
||||
}
|
||||
|
||||
void write_html_log_to_file(std::ofstream& html_log_os, LogType typ, const std::string& ts, const std::string& origin,
|
||||
const std::string& target, const std::string& message_type, const std::string& json_str) {
|
||||
html_log_os << "<tr class=\"" << origin << "\"> <td>" << ts << "</td> <td>" << origin + "-><br>" + target
|
||||
<< "</td> <td><b>" << (typ == LogType::ChargePoint || typ == LogType::System ? message_type : "")
|
||||
<< "</b></td><td><b>" << (typ == LogType::CentralSystem ? message_type : "")
|
||||
<< "</b></td> <td><pre lang=\"json\">" << html_encode(json_str) << "</pre></td> </tr>\n";
|
||||
html_log_os.flush();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void MessageLogging::log_output(LogType typ, const std::string& message_type, const std::string& json_str, bool raw) {
|
||||
if (this->log_messages) {
|
||||
const std::lock_guard<std::mutex> lock(this->output_file_mutex);
|
||||
|
||||
const std::string ts = DateTime().to_rfc3339();
|
||||
|
||||
std::string origin;
|
||||
std::string target;
|
||||
|
||||
if (typ == LogType::ChargePoint) {
|
||||
origin = "ChargePoint";
|
||||
target = "CentralSystem";
|
||||
if (this->detailed_log_to_console) {
|
||||
EVLOG_info << "\033[1;35mChargePoint: " << json_str << "\033[1;0m";
|
||||
} else if (this->log_to_console) {
|
||||
EVLOG_info << "\033[1;35mChargePoint: " << message_type << "\033[1;0m";
|
||||
}
|
||||
} else if (typ == LogType::CentralSystem) {
|
||||
origin = "CentralSystem";
|
||||
target = "ChargePoint";
|
||||
if (this->detailed_log_to_console) {
|
||||
EVLOG_info << "\033[1;36mCentralSystem: " << json_str << "\033[1;0m";
|
||||
} else if (this->log_to_console) {
|
||||
EVLOG_info << " \033[1;36mCentralSystem: " << message_type
|
||||
<< "\033[1;0m";
|
||||
}
|
||||
} else if (typ == LogType::System) {
|
||||
origin = "SYS";
|
||||
target = "";
|
||||
if (this->detailed_log_to_console || this->log_to_console) {
|
||||
EVLOG_info << "\033[1;32mSYS: " << message_type << "\033[1;0m";
|
||||
}
|
||||
}
|
||||
|
||||
if (this->log_to_file) {
|
||||
if (raw and this->log_raw) {
|
||||
this->rotate_log_if_needed(this->log_raw_file, this->log_raw_os);
|
||||
write_log_to_file(this->log_raw_os, typ, ts, origin, target, message_type, json_str);
|
||||
} else {
|
||||
this->rotate_log_if_needed(this->log_file, this->log_os);
|
||||
write_log_to_file(this->log_os, typ, ts, origin, target, message_type, json_str);
|
||||
}
|
||||
}
|
||||
if (this->log_to_html) {
|
||||
if (raw and this->log_raw) {
|
||||
this->rotate_log_if_needed(
|
||||
this->html_raw_log_file, this->html_raw_log_os, [this](std::ofstream& os) { close_html_tags(os); },
|
||||
[this](std::ofstream& os) { open_html_tags(os); });
|
||||
write_html_log_to_file(this->html_raw_log_os, typ, ts, origin, target, message_type, json_str);
|
||||
} else {
|
||||
this->rotate_log_if_needed(
|
||||
this->html_log_file, this->html_log_os, [this](std::ofstream& os) { close_html_tags(os); },
|
||||
[this](std::ofstream& os) { open_html_tags(os); });
|
||||
write_html_log_to_file(this->html_log_os, typ, ts, origin, target, message_type, json_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FormattedMessageWithType MessageLogging::format_message(const std::string& message_type, const std::string& json_str) {
|
||||
auto extracted_message_type = message_type;
|
||||
auto formatted_message = json_str;
|
||||
|
||||
try {
|
||||
auto json_object = json::parse(json_str);
|
||||
if (json_object.at(MESSAGE_TYPE_ID) == MessageTypeId::CALL) {
|
||||
extracted_message_type = json_object.at(CALL_ACTION);
|
||||
this->lookup_map[json_object.at(MESSAGE_ID)] = extracted_message_type + "Response";
|
||||
} else if (json_object.at(MESSAGE_TYPE_ID) == MessageTypeId::CALLRESULT) {
|
||||
extracted_message_type = this->lookup_map[json_object.at(MESSAGE_ID)];
|
||||
this->lookup_map[json_object.at(MESSAGE_ID)].erase();
|
||||
}
|
||||
formatted_message = json_object.dump(2);
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_warning << "Error parsing OCPP message " << message_type << ": " << e.what();
|
||||
}
|
||||
|
||||
return {extracted_message_type, formatted_message};
|
||||
}
|
||||
|
||||
void MessageLogging::start_session_logging(const std::string& session_id, const std::string& log_path) {
|
||||
const std::scoped_lock lock(this->session_id_logging_mutex);
|
||||
this->session_id_logging[session_id] = std::make_shared<ocpp::MessageLogging>(
|
||||
true, log_path, "incomplete-ocpp", false, false, false, true, true, false, false, nullptr);
|
||||
}
|
||||
|
||||
void MessageLogging::stop_session_logging(const std::string& session_id) {
|
||||
const std::scoped_lock lock(this->session_id_logging_mutex);
|
||||
if (this->session_id_logging.count(session_id) != 0) {
|
||||
auto old_file_path =
|
||||
this->session_id_logging.at(session_id)->get_message_log_path() + "/" + "incomplete-ocpp.html";
|
||||
auto new_file_path = this->session_id_logging.at(session_id)->get_message_log_path() + "/" + "ocpp.html";
|
||||
std::rename(old_file_path.c_str(), new_file_path.c_str());
|
||||
this->session_id_logging.erase(session_id);
|
||||
}
|
||||
}
|
||||
|
||||
std::string MessageLogging::get_message_log_path() {
|
||||
return this->message_log_path;
|
||||
}
|
||||
|
||||
bool MessageLogging::session_logging_active() const {
|
||||
return this->session_logging;
|
||||
}
|
||||
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,99 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <ocpp/common/schemas.hpp>
|
||||
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
namespace ocpp {
|
||||
|
||||
Schemas::Schemas(fs::path schemas_path) : schemas_path(schemas_path) {
|
||||
if (!fs::exists(this->schemas_path) || !fs::is_directory(this->schemas_path)) {
|
||||
EVLOG_error << this->schemas_path << " does not exist";
|
||||
// FIXME(kai): exception?
|
||||
} else {
|
||||
for (const auto& file : fs::directory_iterator(this->schemas_path)) {
|
||||
available_schemas_paths.insert(file.path());
|
||||
}
|
||||
this->load_root_schema();
|
||||
}
|
||||
}
|
||||
|
||||
Schemas::Schemas(const json& schema_in) : schema(schema_in) {
|
||||
validator = std::make_shared<json_validator>(
|
||||
[this](const json_uri& uri, json& schema) { this->loader(uri, schema); }, Schemas::format_checker);
|
||||
validator->set_root_schema(this->schema);
|
||||
}
|
||||
|
||||
Schemas::Schemas(json&& schema_in) : schema(std::move(schema_in)) {
|
||||
validator = std::make_shared<json_validator>(
|
||||
[this](const json_uri& uri, json& schema) { this->loader(uri, schema); }, Schemas::format_checker);
|
||||
validator->set_root_schema(this->schema);
|
||||
}
|
||||
|
||||
void Schemas::load_root_schema() {
|
||||
const fs::path config_schema_path = this->schemas_path / "Config.json";
|
||||
|
||||
EVLOG_debug << "parsing root schema file: " << fs::canonical(config_schema_path);
|
||||
|
||||
std::ifstream ifs(config_schema_path.c_str());
|
||||
const std::string schema_file((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
|
||||
this->schema = json::parse(schema_file);
|
||||
|
||||
const auto custom_schema_path = schemas_path / "Custom.json";
|
||||
if (fs::exists(custom_schema_path)) {
|
||||
const json custom_object = {{"type", "object"}, {"$ref", "Custom.json"}};
|
||||
this->schema["properties"]["Custom"] = custom_object;
|
||||
}
|
||||
|
||||
this->validator = std::make_shared<json_validator>(
|
||||
[this](const json_uri& uri, json& schema) { this->loader(uri, schema); }, Schemas::format_checker);
|
||||
this->validator->set_root_schema(this->schema);
|
||||
}
|
||||
|
||||
json Schemas::get_schema() {
|
||||
return this->schema;
|
||||
}
|
||||
|
||||
std::shared_ptr<json_validator> Schemas::get_validator() {
|
||||
return this->validator;
|
||||
}
|
||||
|
||||
void Schemas::loader(const json_uri& uri, json& schema) {
|
||||
std::string location = uri.location();
|
||||
if (location == "http://json-schema.org/draft-07/schema") {
|
||||
schema = nlohmann::json_schema::draft7_schema_builtin;
|
||||
return;
|
||||
}
|
||||
if (location.rfind('/', 0) == 0) {
|
||||
// remove leading /
|
||||
location.erase(0, 1);
|
||||
}
|
||||
|
||||
const fs::path schema_path = this->schemas_path / fs::path(location);
|
||||
if (available_schemas_paths.count(schema_path) != 0) {
|
||||
std::ifstream ifs(schema_path.string().c_str());
|
||||
const std::string schema_file((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
|
||||
schema = json::parse(schema_file);
|
||||
return;
|
||||
}
|
||||
|
||||
throw std::runtime_error(uri.url() + " is not supported for schema loading at the moment");
|
||||
}
|
||||
|
||||
void Schemas::format_checker(const std::string& format, const std::string& value) {
|
||||
if (format == "date-time") {
|
||||
if (!std::regex_match(value, Schemas::date_time_regex)) {
|
||||
throw std::runtime_error("No format checker available for date-time");
|
||||
}
|
||||
} else {
|
||||
nlohmann::json_schema::default_string_format_check(format, value);
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(cert-err58-cpp)
|
||||
const std::regex Schemas::date_time_regex =
|
||||
std::regex(R"(^((?:(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?))(Z|[\+-]\d{2}:\d{2})?)$)");
|
||||
} // namespace ocpp
|
||||
1316
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/common/types.cpp
Normal file
1316
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/common/types.cpp
Normal file
File diff suppressed because it is too large
Load Diff
128
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/common/utils.cpp
Normal file
128
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/common/utils.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <ocpp/common/utils.hpp>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
|
||||
namespace ocpp {
|
||||
|
||||
bool iequals(const std::string& lhs, const std::string rhs) {
|
||||
return boost::algorithm::iequals(lhs, rhs);
|
||||
}
|
||||
|
||||
bool is_finite_or_unset(const std::optional<float>& v) {
|
||||
return !v.has_value() || std::isfinite(v.value());
|
||||
}
|
||||
|
||||
bool is_integer(const std::string& value) {
|
||||
if (value.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for + or - in the beginning
|
||||
auto value_it = value.begin();
|
||||
if (value[0] == '+' or value[0] == '-') {
|
||||
value_it += 1;
|
||||
}
|
||||
|
||||
return std::all_of(value_it, value.end(), ::isdigit);
|
||||
}
|
||||
|
||||
std::tuple<bool, int> is_positive_integer(const std::string& value) {
|
||||
bool valid = is_integer(value);
|
||||
auto value_int = 0;
|
||||
if (valid) {
|
||||
value_int = std::stoi(value);
|
||||
if (value_int < 0) {
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
return {valid, value_int};
|
||||
}
|
||||
|
||||
bool is_decimal_number(const std::string& value) {
|
||||
if (value.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for + or - in the beginning
|
||||
size_t start_pos = 0;
|
||||
if (value[0] == '+' || value[0] == '-') {
|
||||
start_pos = 1;
|
||||
}
|
||||
int decimal_point_count = 0;
|
||||
|
||||
for (size_t i = start_pos; i < value.length(); ++i) {
|
||||
if (value[i] == '.') {
|
||||
// Allow at most one decimal point
|
||||
if (++decimal_point_count > 1) {
|
||||
return false;
|
||||
}
|
||||
} else if (std::isdigit(value[i]) == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_rfc3339_datetime(const std::string& value) {
|
||||
static std::regex datetime_pattern{};
|
||||
static std::once_flag datetime_regex_once;
|
||||
std::call_once(datetime_regex_once, []() {
|
||||
datetime_pattern =
|
||||
std::regex{"\\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2]\\d|3[0-1])T(?:[0-1]\\d|2[0-3]):[0-5]\\d:["
|
||||
"0-5]\\d(?:\\.\\d{0,3}|)(?:Z|(?:\\+|\\-)(?:\\d{2}):?(?:\\d{2}))"};
|
||||
});
|
||||
return std::regex_match(value, datetime_pattern);
|
||||
}
|
||||
|
||||
std::vector<std::string> split_string(const std::string& string_to_split, const char c, const bool trim) {
|
||||
std::stringstream input(string_to_split);
|
||||
std::string temp;
|
||||
std::vector<std::string> result;
|
||||
|
||||
while (std::getline(input, temp, c)) {
|
||||
if (trim) {
|
||||
temp = trim_string(temp);
|
||||
}
|
||||
result.push_back(temp);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string trim_string(const std::string& string_to_trim) {
|
||||
const size_t first = string_to_trim.find_first_not_of(' ');
|
||||
if (std::string::npos == first) {
|
||||
return string_to_trim;
|
||||
}
|
||||
const size_t last = string_to_trim.find_last_not_of(' ');
|
||||
return string_to_trim.substr(first, (last - first + 1));
|
||||
}
|
||||
|
||||
bool is_boolean(const std::string& value) {
|
||||
return iequals(value, "true") || iequals(value, "false");
|
||||
}
|
||||
|
||||
bool is_equal(const float& value1, const float& value2, const double& epsilon) {
|
||||
return is_equal(static_cast<double>(value1), static_cast<double>(value2), epsilon);
|
||||
}
|
||||
|
||||
bool is_equal(const double& value1, const double& value2, const double& epsilon) {
|
||||
return fabs(value1 - value2) < epsilon;
|
||||
}
|
||||
|
||||
std::size_t convert_to_positive_size_t(float value) {
|
||||
if (value < 0) {
|
||||
return 0;
|
||||
}
|
||||
return clamp_to<std::size_t>(std::llround(std::ceil(value)));
|
||||
}
|
||||
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
target_sources(ocpp
|
||||
PRIVATE
|
||||
websocket_base.cpp
|
||||
websocket_uri.cpp
|
||||
websocket.cpp
|
||||
websocket_libwebsockets.cpp
|
||||
)
|
||||
@@ -0,0 +1,100 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
#include <ocpp/common/websocket/websocket.hpp>
|
||||
#include <ocpp/v16/types.hpp>
|
||||
|
||||
#include <ocpp/common/websocket/websocket_libwebsockets.hpp>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
|
||||
Websocket::Websocket(const WebsocketConnectionOptions& connection_options, std::shared_ptr<EvseSecurity> evse_security,
|
||||
std::shared_ptr<MessageLogging> logging) :
|
||||
logging(logging) {
|
||||
|
||||
if (logging == nullptr) {
|
||||
throw std::runtime_error("Websocket requires a valid MessageLogging instance");
|
||||
}
|
||||
|
||||
this->websocket = std::make_unique<WebsocketLibwebsockets>(connection_options, evse_security);
|
||||
}
|
||||
|
||||
bool Websocket::start_connecting() {
|
||||
this->logging->sys("Connecting");
|
||||
return this->websocket->start_connecting();
|
||||
}
|
||||
|
||||
void Websocket::set_connection_options(const WebsocketConnectionOptions& connection_options) {
|
||||
this->websocket->set_connection_options(connection_options);
|
||||
}
|
||||
|
||||
void Websocket::disconnect(const WebsocketCloseReason code) {
|
||||
this->logging->sys("Disconnecting");
|
||||
this->websocket->disconnect(code);
|
||||
}
|
||||
|
||||
void Websocket::reconnect(long delay) {
|
||||
this->logging->sys("Reconnecting");
|
||||
this->websocket->reconnect(delay);
|
||||
}
|
||||
|
||||
bool Websocket::is_connected() {
|
||||
return this->websocket->is_connected();
|
||||
}
|
||||
|
||||
void Websocket::register_connected_callback(const std::function<void(OcppProtocolVersion protocol)>& callback) {
|
||||
this->connected_callback = callback;
|
||||
|
||||
this->websocket->register_connected_callback([this](OcppProtocolVersion protocol) {
|
||||
this->logging->sys("Connected");
|
||||
this->connected_callback(protocol);
|
||||
});
|
||||
}
|
||||
|
||||
void Websocket::register_disconnected_callback(const std::function<void()>& callback) {
|
||||
this->disconnected_callback = callback;
|
||||
|
||||
this->websocket->register_disconnected_callback([this]() {
|
||||
this->logging->sys("Disconnected");
|
||||
this->disconnected_callback();
|
||||
});
|
||||
}
|
||||
|
||||
void Websocket::register_stopped_connecting_callback(
|
||||
const std::function<void(const WebsocketCloseReason reason)>& callback) {
|
||||
this->stopped_connecting_callback = callback;
|
||||
this->websocket->register_stopped_connecting_callback(
|
||||
[this](const WebsocketCloseReason reason) { this->stopped_connecting_callback(reason); });
|
||||
}
|
||||
|
||||
void Websocket::register_message_callback(const std::function<void(const std::string& message)>& callback) {
|
||||
this->message_callback = callback;
|
||||
|
||||
this->websocket->register_message_callback([this](const std::string& message) { this->message_callback(message); });
|
||||
}
|
||||
|
||||
void Websocket::register_connection_failed_callback(const std::function<void(ConnectionFailedReason)>& callback) {
|
||||
this->websocket->register_connection_failed_callback(callback);
|
||||
}
|
||||
|
||||
bool Websocket::send(const std::string& message) {
|
||||
this->logging->raw(message, LogType::ChargePoint);
|
||||
this->logging->charge_point("Unknown", message);
|
||||
return this->websocket->send(message);
|
||||
}
|
||||
|
||||
void Websocket::set_websocket_ping_interval(std::int32_t ping_interval_s, std::int32_t pong_interval_s) {
|
||||
this->logging->sys("WebSocketPingInterval changed");
|
||||
this->websocket->set_websocket_ping_interval(ping_interval_s, pong_interval_s);
|
||||
}
|
||||
|
||||
void Websocket::set_authorization_key(const std::string& authorization_key) {
|
||||
this->websocket->set_authorization_key(authorization_key);
|
||||
}
|
||||
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,231 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <random>
|
||||
|
||||
#include <everest/logging.hpp>
|
||||
#include <ocpp/common/websocket/websocket_base.hpp>
|
||||
#include <websocketpp_utils/base64.hpp>
|
||||
namespace ocpp {
|
||||
|
||||
WebsocketBase::WebsocketBase() :
|
||||
m_is_connected(false),
|
||||
connected_callback(nullptr),
|
||||
stopped_connecting_callback(nullptr),
|
||||
message_callback(nullptr),
|
||||
reconnect_timer(nullptr),
|
||||
connection_attempts(1),
|
||||
ping_cleared(true),
|
||||
ping_elapsed_s(0),
|
||||
pong_elapsed_s(0),
|
||||
reconnect_backoff_ms(0),
|
||||
shutting_down(false) {
|
||||
|
||||
set_connection_options_base(connection_options);
|
||||
|
||||
this->ping_timer = std::make_unique<Everest::SteadyTimer>();
|
||||
const auto auth_key = connection_options.authorization_key;
|
||||
if (auth_key.has_value() and auth_key.value().length() < 16) {
|
||||
EVLOG_warning << "AuthorizationKey with only " << auth_key.value().length()
|
||||
<< " characters has been configured";
|
||||
}
|
||||
}
|
||||
|
||||
WebsocketBase::~WebsocketBase() {
|
||||
try {
|
||||
this->cancel_reconnect_timer();
|
||||
} catch (...) {
|
||||
EVLOG_error << "Exception during dtor call of reconnect timer cancellation";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void WebsocketBase::set_connection_options_base(const WebsocketConnectionOptions& connection_options) {
|
||||
this->connection_options = connection_options;
|
||||
}
|
||||
|
||||
void WebsocketBase::register_connected_callback(const std::function<void(OcppProtocolVersion protocol)>& callback) {
|
||||
this->connected_callback = callback;
|
||||
}
|
||||
|
||||
void WebsocketBase::register_disconnected_callback(const std::function<void()>& callback) {
|
||||
this->disconnected_callback = callback;
|
||||
}
|
||||
|
||||
void WebsocketBase::register_stopped_connecting_callback(
|
||||
const std::function<void(const WebsocketCloseReason reason)>& callback) {
|
||||
this->stopped_connecting_callback = callback;
|
||||
}
|
||||
|
||||
void WebsocketBase::register_message_callback(const std::function<void(const std::string& message)>& callback) {
|
||||
this->message_callback = callback;
|
||||
}
|
||||
|
||||
void WebsocketBase::register_connection_failed_callback(const std::function<void(ConnectionFailedReason)>& callback) {
|
||||
this->connection_failed_callback = callback;
|
||||
}
|
||||
|
||||
bool WebsocketBase::initialized() {
|
||||
if (this->connected_callback == nullptr) {
|
||||
EVLOG_error << "Not properly initialized: please register connected callback.";
|
||||
return false;
|
||||
}
|
||||
if (this->stopped_connecting_callback == nullptr) {
|
||||
EVLOG_error << "Not properly initialized: please closed_callback.";
|
||||
return false;
|
||||
}
|
||||
if (this->message_callback == nullptr) {
|
||||
EVLOG_error << "Not properly initialized: please register message callback.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WebsocketBase::disconnect(const WebsocketCloseReason code) {
|
||||
if (!this->initialized()) {
|
||||
EVLOG_error << "Cannot disconnect a websocket that was not initialized";
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
const std::lock_guard<std::mutex> lk(this->reconnect_mutex);
|
||||
if (code == WebsocketCloseReason::Normal) {
|
||||
this->shutting_down = true;
|
||||
}
|
||||
|
||||
if (this->reconnect_timer) {
|
||||
this->reconnect_timer.get()->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
if (this->ping_timer) {
|
||||
this->ping_timer->stop();
|
||||
}
|
||||
|
||||
EVLOG_info << "Disconnecting websocket...";
|
||||
this->close(code, "");
|
||||
}
|
||||
|
||||
bool WebsocketBase::is_connected() {
|
||||
return this->m_is_connected;
|
||||
}
|
||||
|
||||
std::optional<std::string> WebsocketBase::getAuthorizationHeader() {
|
||||
std::optional<std::string> auth_header = std::nullopt;
|
||||
const auto authorization_key = this->connection_options.authorization_key;
|
||||
if (authorization_key.has_value()) {
|
||||
EVLOG_debug << "AuthorizationKey present, encoding authentication header";
|
||||
const std::string plain_auth_header =
|
||||
this->connection_options.csms_uri.get_chargepoint_id() + ":" + authorization_key.value();
|
||||
|
||||
// TODO (ioan): replace with libevse-security usage
|
||||
auth_header.emplace(std::string("Basic ") + ocpp::base64_encode(plain_auth_header));
|
||||
|
||||
EVLOG_debug << "Basic Auth header: " << auth_header.value();
|
||||
}
|
||||
|
||||
return auth_header;
|
||||
}
|
||||
|
||||
void WebsocketBase::log_on_fail(const std::error_code& ec, const boost::system::error_code& transport_ec,
|
||||
const int http_status) {
|
||||
EVLOG_error << "Failed to connect to websocket server"
|
||||
<< ", error_code: " << ec.value() << ", reason: " << ec.message()
|
||||
<< ", HTTP response code: " << http_status << ", category: " << ec.category().name()
|
||||
<< ", transport error code: " << transport_ec.value()
|
||||
<< ", Transport error category: " << transport_ec.category().name();
|
||||
}
|
||||
|
||||
long WebsocketBase::get_reconnect_interval() {
|
||||
|
||||
// We need to add 1 to the repeat times since the first try is already connection_attempt 1
|
||||
if (this->connection_attempts > (this->connection_options.retry_backoff_repeat_times + 1)) {
|
||||
return this->reconnect_backoff_ms;
|
||||
}
|
||||
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
std::uniform_int_distribution<> distr(0, this->connection_options.retry_backoff_random_range_s);
|
||||
|
||||
const int random_number = distr(gen);
|
||||
|
||||
if (this->connection_attempts == 1) {
|
||||
this->reconnect_backoff_ms = (this->connection_options.retry_backoff_wait_minimum_s + random_number) * 1000;
|
||||
return this->reconnect_backoff_ms;
|
||||
}
|
||||
|
||||
this->reconnect_backoff_ms = (this->reconnect_backoff_ms * 2) + (random_number * 1000);
|
||||
return this->reconnect_backoff_ms;
|
||||
}
|
||||
|
||||
void WebsocketBase::cancel_reconnect_timer() {
|
||||
const std::lock_guard<std::mutex> lk(this->reconnect_mutex);
|
||||
if (this->reconnect_timer) {
|
||||
this->reconnect_timer.get()->cancel();
|
||||
}
|
||||
this->reconnect_timer = nullptr;
|
||||
}
|
||||
|
||||
void WebsocketBase::set_websocket_ping_interval(std::int32_t ping_interval_s, std::int32_t pong_timeout_s) {
|
||||
static constexpr std::int32_t PING_TIMER_INTERVAL = 1;
|
||||
|
||||
if (this->ping_timer) {
|
||||
this->ping_timer->stop();
|
||||
}
|
||||
|
||||
if (ping_interval_s > 0) {
|
||||
EVLOG_debug << "Started a ping interval of: " << ping_interval_s
|
||||
<< " s with a pong timeout of: " << pong_timeout_s << " s";
|
||||
|
||||
this->ping_timer->interval(
|
||||
[this]() {
|
||||
if (false == ping_cleared.load()) { // We have a ping to which we require a response
|
||||
pong_elapsed_s += PING_TIMER_INTERVAL;
|
||||
|
||||
// We have waited more than we should for the pong response
|
||||
if (pong_elapsed_s > this->connection_options.pong_timeout_s) {
|
||||
on_pong_timeout("Pong not received from server!");
|
||||
|
||||
// Always clear ping value on a fail
|
||||
ping_cleared.store(true);
|
||||
|
||||
// Reset the elapsed time
|
||||
ping_elapsed_s = pong_elapsed_s = 0;
|
||||
|
||||
// Clear the ping timer
|
||||
if (this->ping_timer) {
|
||||
this->ping_timer->stop();
|
||||
}
|
||||
}
|
||||
} else { // We need to see if we have to send the ping
|
||||
ping_elapsed_s += PING_TIMER_INTERVAL;
|
||||
|
||||
if (ping_elapsed_s > this->connection_options.ping_interval_s) {
|
||||
// Set the ping flag before sending it out
|
||||
ping_cleared.store(false);
|
||||
this->ping();
|
||||
|
||||
// Reset the elapsed time
|
||||
ping_elapsed_s = pong_elapsed_s = 0;
|
||||
}
|
||||
}
|
||||
},
|
||||
// Tick each second to see if we reached a timeout
|
||||
std::chrono::seconds(PING_TIMER_INTERVAL));
|
||||
}
|
||||
|
||||
this->connection_options.ping_interval_s = ping_interval_s;
|
||||
this->connection_options.pong_timeout_s = pong_timeout_s;
|
||||
}
|
||||
|
||||
void WebsocketBase::set_authorization_key(const std::string& authorization_key) {
|
||||
this->connection_options.authorization_key = authorization_key;
|
||||
}
|
||||
|
||||
void WebsocketBase::on_pong_timeout(std::string msg) {
|
||||
EVLOG_info << "Reconnecting because of a pong timeout after " << this->connection_options.pong_timeout_s << "s"
|
||||
<< " and with reason: " << msg;
|
||||
this->close(WebsocketCloseReason::ServiceRestart, "Pong timeout"); // application code will handle the reconnect
|
||||
}
|
||||
|
||||
} // namespace ocpp
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,102 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <ocpp/common/types.hpp>
|
||||
#include <ocpp/common/websocket/websocket_uri.hpp>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace ocpp {
|
||||
|
||||
namespace {
|
||||
auto path_split_last_segment(std::string path) {
|
||||
struct result {
|
||||
std::string path_without_last_segment;
|
||||
std::string last_segment;
|
||||
};
|
||||
|
||||
if (path == "/") {
|
||||
return result{"/", ""};
|
||||
}
|
||||
auto pos_last_slash = path.rfind('/');
|
||||
|
||||
if (pos_last_slash == path.length() - 1) {
|
||||
path.pop_back();
|
||||
pos_last_slash -= 1;
|
||||
}
|
||||
|
||||
auto path_without_last_segment = std::string(path.substr(0, pos_last_slash + 1));
|
||||
auto last_segment = std::string(path.substr(pos_last_slash + 1));
|
||||
|
||||
return result{path_without_last_segment, last_segment};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Uri Uri::parse_and_validate(std::string uri, std::string chargepoint_id, int security_profile) {
|
||||
if (uri.empty()) {
|
||||
throw std::invalid_argument("`uri`-parameter must not be empty");
|
||||
}
|
||||
if (chargepoint_id.empty()) {
|
||||
throw std::invalid_argument("`chargepoint_id`-parameter must not be empty");
|
||||
}
|
||||
|
||||
// workaround for required schema in `websocketpp::uri()`
|
||||
bool scheme_added_workaround = false;
|
||||
if (uri.find("://") == std::string::npos) {
|
||||
scheme_added_workaround = true;
|
||||
if (security_profile >= security::SecurityProfile::TLS_WITH_BASIC_AUTHENTICATION) {
|
||||
uri = "wss://" + uri;
|
||||
} else {
|
||||
uri = "ws://" + uri;
|
||||
}
|
||||
}
|
||||
|
||||
auto uri_temp = ev_uri(uri);
|
||||
if (!uri_temp.get_valid()) {
|
||||
throw std::invalid_argument("given `uri` is invalid");
|
||||
}
|
||||
|
||||
if (!scheme_added_workaround) {
|
||||
switch (security_profile) { // `switch` to lint for unused enum-values
|
||||
case security::SecurityProfile::OCPP_1_6_ONLY_UNSECURED_TRANSPORT_WITHOUT_BASIC_AUTHENTICATION:
|
||||
case security::SecurityProfile::UNSECURED_TRANSPORT_WITH_BASIC_AUTHENTICATION:
|
||||
if (uri_temp.get_secure()) {
|
||||
throw std::invalid_argument(
|
||||
"secure schema '" + uri_temp.get_scheme() +
|
||||
"://' in URI does not fit with insecure security-profile = " + std::to_string(security_profile));
|
||||
}
|
||||
break;
|
||||
case security::SecurityProfile::TLS_WITH_BASIC_AUTHENTICATION:
|
||||
case security::SecurityProfile::TLS_WITH_CLIENT_SIDE_CERTIFICATES:
|
||||
if (!uri_temp.get_secure()) {
|
||||
throw std::invalid_argument(
|
||||
"insecure schema '" + uri_temp.get_scheme() +
|
||||
"://' in URI does not fit with secure security-profile = " + std::to_string(security_profile));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw std::invalid_argument("`security_profile`-parameter has unknown value = " +
|
||||
std::to_string(security_profile));
|
||||
}
|
||||
}
|
||||
|
||||
auto path = uri_temp.get_resource();
|
||||
|
||||
// backwards-compatibility: remove chargepoint-ID from URI, if given as last path-segment
|
||||
const auto [path_without_base, base] = path_split_last_segment(path);
|
||||
if (base == chargepoint_id) {
|
||||
path = path_without_base;
|
||||
}
|
||||
|
||||
if (path.back() != '/') {
|
||||
path.push_back('/');
|
||||
}
|
||||
|
||||
return Uri(uri_temp.get_secure(), uri_temp.get_host(), uri_temp.get_port(), path, chargepoint_id);
|
||||
}
|
||||
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,398 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <everest/logging.hpp>
|
||||
#include <ocpp/v16/charge_point.hpp>
|
||||
#include <ocpp/v16/charge_point_configuration_interface.hpp>
|
||||
#include <ocpp/v16/charge_point_impl.hpp>
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
ChargePoint::ChargePoint(
|
||||
ChargePointConfigurationInterface& cfg, const fs::path& share_path, const fs::path& database_path,
|
||||
const fs::path& sql_init_path, const fs::path& message_log_path, const std::shared_ptr<EvseSecurity> evse_security,
|
||||
const std::optional<SecurityConfiguration> security_configuration,
|
||||
const std::function<void(const std::string& message, MessageDirection direction)>& message_callback) {
|
||||
this->charge_point =
|
||||
std::make_unique<ChargePointImpl>(cfg, share_path, database_path, sql_init_path, message_log_path,
|
||||
evse_security, security_configuration, message_callback);
|
||||
}
|
||||
|
||||
ChargePoint::~ChargePoint() = default;
|
||||
|
||||
void ChargePoint::update_chargepoint_information(const std::string& vendor, const std::string& model,
|
||||
const std::optional<std::string>& serialnumber,
|
||||
const std::optional<std::string>& chargebox_serialnumber,
|
||||
const std::optional<std::string>& firmware_version) {
|
||||
this->charge_point->update_chargepoint_information(vendor, model, serialnumber, chargebox_serialnumber,
|
||||
firmware_version);
|
||||
}
|
||||
|
||||
void ChargePoint::update_modem_information(const std::optional<std::string>& iccid,
|
||||
const std::optional<std::string>& imsi) {
|
||||
this->charge_point->update_modem_information(iccid, imsi);
|
||||
}
|
||||
|
||||
void ChargePoint::update_meter_information(const std::optional<std::string>& meter_serialnumber,
|
||||
const std::optional<std::string>& meter_type) {
|
||||
this->charge_point->update_meter_information(meter_serialnumber, meter_type);
|
||||
}
|
||||
|
||||
bool ChargePoint::init(const std::map<int, ChargePointStatus>& connector_status_map,
|
||||
const std::set<std::string>& resuming_session_ids) {
|
||||
return this->charge_point->init(connector_status_map, resuming_session_ids);
|
||||
}
|
||||
|
||||
bool ChargePoint::start(const std::map<int, ChargePointStatus>& connector_status_map, BootReasonEnum bootreason,
|
||||
const std::set<std::string>& resuming_session_ids) {
|
||||
return this->charge_point->start(connector_status_map, bootreason, resuming_session_ids);
|
||||
}
|
||||
|
||||
bool ChargePoint::restart(const std::map<int, ChargePointStatus>& connector_status_map, BootReasonEnum bootreason) {
|
||||
return this->charge_point->restart(connector_status_map, bootreason);
|
||||
}
|
||||
|
||||
bool ChargePoint::stop() {
|
||||
return this->charge_point->stop();
|
||||
}
|
||||
|
||||
void ChargePoint::connect_websocket() {
|
||||
this->charge_point->connect_websocket();
|
||||
}
|
||||
|
||||
void ChargePoint::disconnect_websocket() {
|
||||
this->charge_point->disconnect_websocket();
|
||||
}
|
||||
|
||||
void ChargePoint::call_set_connection_timeout() {
|
||||
this->charge_point->call_set_connection_timeout();
|
||||
}
|
||||
|
||||
EnhancedIdTagInfo ChargePoint::authorize_id_token(CiString<20> id_token) {
|
||||
return this->charge_point->authorize_id_token(id_token);
|
||||
}
|
||||
|
||||
ocpp::v2::AuthorizeResponse ChargePoint::data_transfer_pnc_authorize(
|
||||
|
||||
const std::string& emaid, const std::optional<std::string>& certificate,
|
||||
const std::optional<std::vector<ocpp::v2::OCSPRequestData>>& iso15118_certificate_hash_data) {
|
||||
return this->charge_point->data_transfer_pnc_authorize(emaid, certificate, iso15118_certificate_hash_data);
|
||||
}
|
||||
|
||||
void ChargePoint::data_transfer_pnc_get_15118_ev_certificate(
|
||||
const std::int32_t connector_id, const std::string& exi_request, const std::string& iso15118_schema_version,
|
||||
const ocpp::v2::CertificateActionEnum& certificate_action) {
|
||||
|
||||
this->charge_point->data_transfer_pnc_get_15118_ev_certificate(connector_id, exi_request, iso15118_schema_version,
|
||||
certificate_action);
|
||||
}
|
||||
|
||||
std::optional<DataTransferResponse> ChargePoint::data_transfer(const CiString<255>& vendorId,
|
||||
const std::optional<CiString<50>>& messageId,
|
||||
const std::optional<std::string>& data) {
|
||||
return this->charge_point->data_transfer(vendorId, messageId, data);
|
||||
}
|
||||
|
||||
std::map<std::int32_t, ChargingSchedule>
|
||||
ChargePoint::get_all_composite_charging_schedules(const std::int32_t duration_s, const ChargingRateUnit unit) {
|
||||
return this->charge_point->get_all_composite_charging_schedules(duration_s, unit);
|
||||
}
|
||||
|
||||
std::map<std::int32_t, EnhancedChargingSchedule>
|
||||
ChargePoint::get_all_enhanced_composite_charging_schedules(const std::int32_t duration_s, const ChargingRateUnit unit) {
|
||||
return this->charge_point->get_all_enhanced_composite_charging_schedules(duration_s, unit);
|
||||
}
|
||||
|
||||
void ChargePoint::on_meter_values(std::int32_t connector, const Measurement& measurement) {
|
||||
this->charge_point->on_meter_values(connector, measurement);
|
||||
}
|
||||
|
||||
void ChargePoint::on_max_current_offered(std::int32_t connector, std::int32_t max_current) {
|
||||
this->charge_point->on_max_current_offered(connector, max_current);
|
||||
}
|
||||
|
||||
void ChargePoint::on_max_power_offered(std::int32_t connector, std::int32_t max_power) {
|
||||
this->charge_point->on_max_power_offered(connector, max_power);
|
||||
}
|
||||
|
||||
void ChargePoint::on_session_started(std::int32_t connector, const std::string& session_id,
|
||||
const ocpp::SessionStartedReason reason,
|
||||
const std::optional<std::string>& session_logging_path) {
|
||||
|
||||
this->charge_point->on_session_started(connector, session_id, reason, session_logging_path);
|
||||
}
|
||||
|
||||
void ChargePoint::on_session_stopped(const std::int32_t connector, const std::string& session_id) {
|
||||
this->charge_point->on_session_stopped(connector, session_id);
|
||||
}
|
||||
|
||||
void ChargePoint::on_transaction_started(const std::int32_t& connector, const std::string& session_id,
|
||||
const std::string& id_token, const double meter_start,
|
||||
std::optional<std::int32_t> reservation_id, const ocpp::DateTime& timestamp,
|
||||
std::optional<std::string> signed_meter_value) {
|
||||
this->charge_point->on_transaction_started(connector, session_id, id_token, meter_start, reservation_id, timestamp,
|
||||
signed_meter_value);
|
||||
}
|
||||
|
||||
void ChargePoint::on_transaction_stopped(const std::int32_t connector, const std::string& session_id,
|
||||
const Reason& reason, ocpp::DateTime timestamp, float energy_wh_import,
|
||||
std::optional<CiString<20>> id_tag_end,
|
||||
std::optional<std::string> signed_meter_value) {
|
||||
this->charge_point->on_transaction_stopped(connector, session_id, reason, timestamp, energy_wh_import, id_tag_end,
|
||||
signed_meter_value);
|
||||
}
|
||||
|
||||
void ChargePoint::on_suspend_charging_ev(std::int32_t connector, const std::optional<CiString<50>> info) {
|
||||
this->charge_point->on_suspend_charging_ev(connector, info);
|
||||
}
|
||||
|
||||
void ChargePoint::on_suspend_charging_evse(std::int32_t connector, const std::optional<CiString<50>> info) {
|
||||
this->charge_point->on_suspend_charging_evse(connector, info);
|
||||
}
|
||||
|
||||
void ChargePoint::on_resume_charging(std::int32_t connector) {
|
||||
this->charge_point->on_resume_charging(connector);
|
||||
}
|
||||
|
||||
void ChargePoint::on_error(std::int32_t connector, const ErrorInfo& error_info) {
|
||||
this->charge_point->on_error(connector, error_info);
|
||||
}
|
||||
|
||||
void ChargePoint::on_error_cleared(std::int32_t connector, const std::string uuid) {
|
||||
this->charge_point->on_error_cleared(connector, uuid);
|
||||
}
|
||||
|
||||
void ChargePoint::on_all_errors_cleared(std::int32_t connector) {
|
||||
this->charge_point->on_all_errors_cleared(connector);
|
||||
}
|
||||
|
||||
void ChargePoint::on_log_status_notification(std::int32_t request_id, std::string log_status) {
|
||||
this->charge_point->on_log_status_notification(request_id, log_status);
|
||||
}
|
||||
|
||||
void ChargePoint::on_firmware_update_status_notification(std::int32_t request_id,
|
||||
const FirmwareStatusNotification firmware_update_status) {
|
||||
this->charge_point->on_firmware_update_status_notification(request_id, firmware_update_status);
|
||||
}
|
||||
|
||||
void ChargePoint::on_reservation_start(std::int32_t connector) {
|
||||
this->charge_point->on_reservation_start(connector);
|
||||
}
|
||||
|
||||
void ChargePoint::on_reservation_end(std::int32_t connector) {
|
||||
this->charge_point->on_reservation_end(connector);
|
||||
}
|
||||
|
||||
void ChargePoint::on_enabled(std::int32_t connector) {
|
||||
this->charge_point->on_enabled(connector);
|
||||
}
|
||||
|
||||
void ChargePoint::on_disabled(std::int32_t connector) {
|
||||
this->charge_point->on_disabled(connector);
|
||||
}
|
||||
|
||||
void ChargePoint::on_plugin_timeout(std::int32_t connector) {
|
||||
this->charge_point->on_plugin_timeout(connector);
|
||||
}
|
||||
|
||||
void ChargePoint::on_security_event(const CiString<50>& event_type, const std::optional<CiString<255>>& tech_info,
|
||||
const std::optional<bool>& critical, const std::optional<DateTime>& timestamp) {
|
||||
this->charge_point->on_security_event(event_type, tech_info, critical, timestamp);
|
||||
}
|
||||
|
||||
ChangeAvailabilityResponse ChargePoint::on_change_availability(const ChangeAvailabilityRequest& request) {
|
||||
return this->charge_point->on_change_availability(request);
|
||||
}
|
||||
|
||||
void ChargePoint::register_data_transfer_callback(
|
||||
const CiString<255>& vendorId, const CiString<50>& messageId,
|
||||
const std::function<DataTransferResponse(const std::optional<std::string>& msg)>& callback) {
|
||||
this->charge_point->register_data_transfer_callback(vendorId, messageId, callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_data_transfer_callback(
|
||||
const std::function<DataTransferResponse(const DataTransferRequest& request)>& callback) {
|
||||
this->charge_point->register_data_transfer_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_enable_evse_callback(const std::function<bool(std::int32_t connector)>& callback) {
|
||||
this->charge_point->register_enable_evse_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_disable_evse_callback(const std::function<bool(std::int32_t connector)>& callback) {
|
||||
this->charge_point->register_disable_evse_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_pause_charging_callback(const std::function<bool(std::int32_t connector)>& callback) {
|
||||
this->charge_point->register_pause_charging_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_resume_charging_callback(const std::function<bool(std::int32_t connector)>& callback) {
|
||||
this->charge_point->register_resume_charging_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_provide_token_callback(
|
||||
const std::function<void(const std::string& id_token, std::vector<std::int32_t> referenced_connectors,
|
||||
bool prevalidated)>& callback) {
|
||||
this->charge_point->register_provide_token_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_stop_transaction_callback(
|
||||
const std::function<bool(std::int32_t connector, Reason reason)>& callback) {
|
||||
this->charge_point->register_stop_transaction_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_reserve_now_callback(
|
||||
const std::function<ReservationStatus(std::int32_t reservation_id, std::int32_t connector,
|
||||
ocpp::DateTime expiryDate, CiString<20> idTag,
|
||||
std::optional<CiString<20>> parent_id)>& callback) {
|
||||
this->charge_point->register_reserve_now_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_cancel_reservation_callback(const std::function<bool(std::int32_t connector)>& callback) {
|
||||
this->charge_point->register_cancel_reservation_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_unlock_connector_callback(
|
||||
const std::function<UnlockStatus(std::int32_t connector)>& callback) {
|
||||
this->charge_point->register_unlock_connector_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_upload_diagnostics_callback(
|
||||
const std::function<GetLogResponse(const GetDiagnosticsRequest& request)>& callback) {
|
||||
this->charge_point->register_upload_diagnostics_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_update_firmware_callback(
|
||||
const std::function<void(const UpdateFirmwareRequest msg)>& callback) {
|
||||
this->charge_point->register_update_firmware_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_signed_update_firmware_callback(
|
||||
const std::function<UpdateFirmwareStatusEnumType(const SignedUpdateFirmwareRequest msg)>& callback) {
|
||||
this->charge_point->register_signed_update_firmware_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_all_connectors_unavailable_callback(const std::function<void()>& callback) {
|
||||
this->charge_point->register_all_connectors_unavailable_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_upload_logs_callback(const std::function<GetLogResponse(GetLogRequest req)>& callback) {
|
||||
this->charge_point->register_upload_logs_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_set_connection_timeout_callback(
|
||||
const std::function<void(std::int32_t connection_timeout)>& callback) {
|
||||
this->charge_point->register_set_connection_timeout_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_is_reset_allowed_callback(const std::function<bool(const ResetType& reset_type)>& callback) {
|
||||
this->charge_point->register_is_reset_allowed_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_reset_callback(const std::function<void(const ResetType& reset_type)>& callback) {
|
||||
this->charge_point->register_reset_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_set_system_time_callback(
|
||||
const std::function<void(const std::string& system_time)>& callback) {
|
||||
this->charge_point->register_set_system_time_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_boot_notification_response_callback(
|
||||
const std::function<void(const BootNotificationResponse& boot_notification_response)>& callback) {
|
||||
this->charge_point->register_boot_notification_response_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_signal_set_charging_profiles_callback(const std::function<void()>& callback) {
|
||||
this->charge_point->register_signal_set_charging_profiles_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_connection_state_changed_callback(const std::function<void(bool is_connected)>& callback) {
|
||||
this->charge_point->register_connection_state_changed_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_get_15118_ev_certificate_response_callback(
|
||||
const std::function<void(const std::int32_t connector,
|
||||
const ocpp::v2::Get15118EVCertificateResponse& certificate_response,
|
||||
const ocpp::v2::CertificateActionEnum& certificate_action)>& callback) {
|
||||
this->charge_point->register_get_15118_ev_certificate_response_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_transaction_started_callback(
|
||||
const std::function<void(const std::int32_t connector, const std::string& session_id)>& callback) {
|
||||
this->charge_point->register_transaction_started_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_transaction_stopped_callback(
|
||||
const std::function<void(const std::int32_t connector, const std::string& session_id,
|
||||
const std::int32_t transaction_id)>& callback) {
|
||||
this->charge_point->register_transaction_stopped_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_transaction_updated_callback(
|
||||
const std::function<void(const std::int32_t connector, const std::string& session_id,
|
||||
const std::int32_t transaction_id, const IdTagInfo& id_tag_info)>& callback) {
|
||||
this->charge_point->register_transaction_updated_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_configuration_key_changed_callback(
|
||||
const CiString<50>& key, const std::function<void(const KeyValue& key_value)>& callback) {
|
||||
this->charge_point->register_configuration_key_changed_callback(key, callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_generic_configuration_key_changed_callback(
|
||||
const std::function<void(const KeyValue& key_value)>& callback) {
|
||||
this->charge_point->register_generic_configuration_key_changed_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_security_event_callback(
|
||||
const std::function<void(const std::string& type, const std::string& tech_info)>& callback) {
|
||||
this->charge_point->register_security_event_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_is_token_reserved_for_connector_callback(
|
||||
const std::function<ocpp::ReservationCheckStatus(const std::int32_t connector, const std::string& id_token)>&
|
||||
callback) {
|
||||
this->charge_point->register_is_token_reserved_for_connector_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_session_cost_callback(
|
||||
const std::function<DataTransferResponse(const RunningCost& running_cost, const std::uint32_t number_of_decimals)>&
|
||||
session_cost_callback) {
|
||||
this->charge_point->register_session_cost_callback(session_cost_callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_tariff_message_callback(
|
||||
const std::function<DataTransferResponse(const TariffMessage& message)>& tariff_message_callback) {
|
||||
this->charge_point->register_tariff_message_callback(tariff_message_callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_default_price_callback(const std::function<void(const TariffMessage& message)>& callback) {
|
||||
this->charge_point->register_default_price_callback(callback);
|
||||
}
|
||||
|
||||
void ChargePoint::register_set_display_message_callback(
|
||||
const std::function<DataTransferResponse(const std::vector<DisplayMessage>&)> set_display_message_callback) {
|
||||
this->charge_point->register_set_display_message_callback(set_display_message_callback);
|
||||
}
|
||||
|
||||
GetConfigurationResponse ChargePoint::get_configuration_key(const GetConfigurationRequest& request) {
|
||||
return this->charge_point->get_configuration_key(request);
|
||||
}
|
||||
|
||||
ConfigurationStatus ChargePoint::set_configuration_key(CiString<50> key, CiString<500> value) {
|
||||
return this->charge_point->set_configuration_key(key, value);
|
||||
}
|
||||
|
||||
void ChargePoint::set_message_queue_resume_delay(std::chrono::seconds delay) {
|
||||
this->charge_point->set_message_queue_resume_delay(delay);
|
||||
}
|
||||
|
||||
bool ChargePoint::set_powermeter_public_key(const int32_t connector, const std::string& public_key_pem) {
|
||||
return this->charge_point->set_powermeter_public_key(connector, public_key_pem);
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,445 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <cstddef>
|
||||
#include <ocpp/common/schemas.hpp>
|
||||
#include <ocpp/v16/charge_point_configuration_base.hpp>
|
||||
#include <ocpp/v16/utils.hpp>
|
||||
|
||||
#include <exception>
|
||||
#include <fstream>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
|
||||
namespace ocpp::v16 {
|
||||
|
||||
const ChargePointConfigurationBase::FeaturesMap
|
||||
ChargePointConfigurationBase::supported_message_types_from_charge_point = {
|
||||
{SupportedFeatureProfiles::Core,
|
||||
{MessageType::Authorize, MessageType::BootNotification, MessageType::ChangeAvailabilityResponse,
|
||||
MessageType::ChangeConfigurationResponse, MessageType::ClearCacheResponse, MessageType::DataTransfer,
|
||||
MessageType::DataTransferResponse, MessageType::GetConfigurationResponse, MessageType::Heartbeat,
|
||||
MessageType::MeterValues, MessageType::RemoteStartTransactionResponse,
|
||||
MessageType::RemoteStopTransactionResponse, MessageType::ResetResponse, MessageType::StartTransaction,
|
||||
MessageType::StatusNotification, MessageType::StopTransaction, MessageType::UnlockConnectorResponse}},
|
||||
{SupportedFeatureProfiles::FirmwareManagement,
|
||||
{MessageType::GetDiagnosticsResponse, MessageType::DiagnosticsStatusNotification,
|
||||
MessageType::FirmwareStatusNotification, MessageType::UpdateFirmwareResponse}},
|
||||
{SupportedFeatureProfiles::LocalAuthListManagement,
|
||||
{MessageType::GetLocalListVersionResponse, MessageType::SendLocalListResponse}},
|
||||
{SupportedFeatureProfiles::RemoteTrigger, {MessageType::TriggerMessageResponse}},
|
||||
{SupportedFeatureProfiles::Reservation,
|
||||
{MessageType::CancelReservationResponse, MessageType::ReserveNowResponse}},
|
||||
{SupportedFeatureProfiles::SmartCharging,
|
||||
{MessageType::ClearChargingProfileResponse, MessageType::GetCompositeScheduleResponse,
|
||||
MessageType::SetChargingProfileResponse}},
|
||||
{SupportedFeatureProfiles::Security,
|
||||
{MessageType::CertificateSignedResponse, MessageType::DeleteCertificateResponse,
|
||||
MessageType::ExtendedTriggerMessageResponse, MessageType::GetInstalledCertificateIdsResponse,
|
||||
MessageType::GetLogResponse, MessageType::InstallCertificateResponse, MessageType::LogStatusNotification,
|
||||
MessageType::SecurityEventNotification, MessageType::SignCertificate,
|
||||
MessageType::SignedFirmwareStatusNotification, MessageType::SignedUpdateFirmwareResponse}}};
|
||||
|
||||
const ChargePointConfigurationBase::FeaturesMap
|
||||
ChargePointConfigurationBase::supported_message_types_from_central_system = {
|
||||
{SupportedFeatureProfiles::Core,
|
||||
{MessageType::AuthorizeResponse, MessageType::BootNotificationResponse, MessageType::ChangeAvailability,
|
||||
MessageType::ChangeConfiguration, MessageType::ClearCache, MessageType::DataTransfer,
|
||||
MessageType::DataTransferResponse, MessageType::GetConfiguration, MessageType::HeartbeatResponse,
|
||||
MessageType::MeterValuesResponse, MessageType::RemoteStartTransaction, MessageType::RemoteStopTransaction,
|
||||
MessageType::Reset, MessageType::StartTransactionResponse, MessageType::StatusNotificationResponse,
|
||||
MessageType::StopTransactionResponse, MessageType::UnlockConnector}},
|
||||
{SupportedFeatureProfiles::FirmwareManagement,
|
||||
{MessageType::GetDiagnostics, MessageType::DiagnosticsStatusNotificationResponse,
|
||||
MessageType::FirmwareStatusNotificationResponse, MessageType::UpdateFirmware}},
|
||||
{SupportedFeatureProfiles::LocalAuthListManagement,
|
||||
{MessageType::GetLocalListVersion, MessageType::SendLocalList}},
|
||||
{SupportedFeatureProfiles::RemoteTrigger, {MessageType::TriggerMessage}},
|
||||
{SupportedFeatureProfiles::Reservation, {MessageType::CancelReservation, MessageType::ReserveNow}},
|
||||
{SupportedFeatureProfiles::SmartCharging,
|
||||
{MessageType::ClearChargingProfile, MessageType::GetCompositeSchedule, MessageType::SetChargingProfile}},
|
||||
{SupportedFeatureProfiles::Security,
|
||||
{MessageType::CertificateSigned, MessageType::DeleteCertificate, MessageType::ExtendedTriggerMessage,
|
||||
MessageType::GetInstalledCertificateIds, MessageType::GetLog, MessageType::InstallCertificate,
|
||||
MessageType::LogStatusNotificationResponse, MessageType::SecurityEventNotificationResponse,
|
||||
MessageType::SignCertificateResponse, MessageType::SignedFirmwareStatusNotificationResponse,
|
||||
MessageType::SignedUpdateFirmware}}};
|
||||
|
||||
void ChargePointConfigurationBase::initialise(const ProfilesSet& initial_set,
|
||||
const std::optional<std::string>& supported_profiles_csl,
|
||||
const std::optional<std::string>& supported_measurands_csl) {
|
||||
supported_feature_profiles = initial_set;
|
||||
|
||||
if (supported_profiles_csl) {
|
||||
const auto supported_profiles = utils::from_csl(supported_profiles_csl.value());
|
||||
for (const auto& component : supported_profiles) {
|
||||
try {
|
||||
supported_feature_profiles.insert(conversions::string_to_supported_feature_profiles(component));
|
||||
} catch (const StringToEnumException& e) {
|
||||
EVLOG_error << "Feature profile: \"" << component << "\" not recognized";
|
||||
throw std::runtime_error("Unknown component in SupportedFeatureProfiles config option.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add Security behind the scenes as supported feature profile
|
||||
supported_feature_profiles.insert(conversions::string_to_supported_feature_profiles("Security"));
|
||||
|
||||
// add Internal behind the scenes as supported feature profile
|
||||
supported_feature_profiles.insert(conversions::string_to_supported_feature_profiles("Internal"));
|
||||
|
||||
// check Core profile is included
|
||||
if (const auto it = supported_feature_profiles.find(SupportedFeatureProfiles::Core);
|
||||
it == supported_feature_profiles.end()) {
|
||||
throw std::runtime_error("Core profile not listed in SupportedFeatureProfiles. This is required.");
|
||||
}
|
||||
|
||||
// add supported messages based on supported features
|
||||
for (const auto& feature_profile : supported_feature_profiles) {
|
||||
if (const auto it = supported_message_types_from_charge_point.find(feature_profile);
|
||||
it != supported_message_types_from_charge_point.end()) {
|
||||
supported_message_types_sending.insert(it->second.begin(), it->second.end());
|
||||
}
|
||||
|
||||
if (const auto it = supported_message_types_from_central_system.find(feature_profile);
|
||||
it != supported_message_types_from_central_system.end()) {
|
||||
supported_message_types_receiving.insert(it->second.begin(), it->second.end());
|
||||
}
|
||||
}
|
||||
|
||||
// those MessageTypes should still be accepted and implement their individual handling in case the feature profile
|
||||
// is not supported
|
||||
supported_message_types_receiving.insert(MessageType::GetLocalListVersion);
|
||||
supported_message_types_receiving.insert(MessageType::SendLocalList);
|
||||
supported_message_types_receiving.insert(MessageType::ReserveNow);
|
||||
|
||||
// populate supported_measurands
|
||||
|
||||
if (supported_measurands_csl) {
|
||||
const auto supported = utils::from_csl(supported_measurands_csl.value());
|
||||
for (const auto& measurand : supported) {
|
||||
try {
|
||||
const auto measurand_type = conversions::string_to_measurand(measurand);
|
||||
switch (measurand_type) {
|
||||
case Measurand::Energy_Active_Export_Register:
|
||||
case Measurand::Energy_Active_Import_Register:
|
||||
case Measurand::Energy_Reactive_Export_Register:
|
||||
case Measurand::Energy_Reactive_Import_Register:
|
||||
case Measurand::Energy_Active_Export_Interval:
|
||||
case Measurand::Energy_Active_Import_Interval:
|
||||
case Measurand::Energy_Reactive_Export_Interval:
|
||||
case Measurand::Energy_Reactive_Import_Interval:
|
||||
case Measurand::Power_Active_Export:
|
||||
case Measurand::Power_Active_Import:
|
||||
case Measurand::Voltage:
|
||||
case Measurand::Frequency:
|
||||
case Measurand::Power_Reactive_Export:
|
||||
case Measurand::Power_Reactive_Import:
|
||||
supported_measurands[measurand_type] = {Phase::L1, Phase::L2, Phase::L3};
|
||||
break;
|
||||
case Measurand::Current_Import:
|
||||
case Measurand::Current_Export:
|
||||
supported_measurands[measurand_type] = {Phase::L1, Phase::L2, Phase::L3, Phase::N};
|
||||
break;
|
||||
case Measurand::Power_Factor:
|
||||
case Measurand::Current_Offered:
|
||||
case Measurand::Power_Offered:
|
||||
case Measurand::Temperature:
|
||||
case Measurand::SoC:
|
||||
case Measurand::RPM:
|
||||
supported_measurands[measurand_type] = {};
|
||||
break;
|
||||
default:
|
||||
EVLOG_AND_THROW(std::runtime_error("Given SupportedMeasurands are invalid"));
|
||||
}
|
||||
} catch (const StringToEnumException& o) {
|
||||
EVLOG_AND_THROW(std::runtime_error("Given SupportedMeasurands are invalid"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ChargePointConfigurationBase::validate(const std::string_view& schema_file, const json& object) const {
|
||||
bool result{false};
|
||||
try {
|
||||
fs::path schema_path = ocpp_main_path / "profile_schemas" / schema_file;
|
||||
std::ifstream ifs(schema_path);
|
||||
auto schema_json = json::parse(ifs);
|
||||
ocpp::Schemas schema(std::move(schema_json));
|
||||
auto validator = schema.get_validator();
|
||||
if (validator) {
|
||||
validator->validate(object);
|
||||
result = true;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Error validating against schema " << schema_file << ": " << e.what();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ChargePointConfigurationBase::isValidSupportedMeasurands(const std::string& csl) const {
|
||||
const auto elements = utils::from_csl(csl);
|
||||
bool result{true};
|
||||
|
||||
for (const auto& element : elements) {
|
||||
try {
|
||||
auto measurand = conversions::string_to_measurand(element);
|
||||
if (const auto it = supported_measurands.find(measurand); it == supported_measurands.end()) {
|
||||
// this measurand is not supported
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
} catch (const StringToEnumException& o) {
|
||||
EVLOG_warning << "Measurand: " << element << " is not supported!";
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<ChargePointConfigurationBase::MeasurandWithPhaseList>
|
||||
ChargePointConfigurationBase::csvToMeasurandWithPhaseVector(const std::string& csl) const {
|
||||
const auto elements = utils::from_csl(csl);
|
||||
bool element_error{false};
|
||||
|
||||
// collect results in a vector (ensures no duplicates)
|
||||
std::set<Measurand> result_set; // capture measurands already in the vector
|
||||
std::vector<MeasurandWithPhase> result_vector;
|
||||
std::optional<std::vector<MeasurandWithPhase>> result;
|
||||
|
||||
for (const auto& element : elements) {
|
||||
try {
|
||||
MeasurandWithPhase measurand_with_phase;
|
||||
measurand_with_phase.measurand = conversions::string_to_measurand(element);
|
||||
|
||||
if (const auto it = supported_measurands.find(measurand_with_phase.measurand);
|
||||
it != supported_measurands.end()) {
|
||||
// this measurand is supported
|
||||
|
||||
if (const auto included = result_set.find(measurand_with_phase.measurand);
|
||||
included == result_set.end()) {
|
||||
// this measurand hasn't been added to the result vector
|
||||
result_set.insert(measurand_with_phase.measurand);
|
||||
|
||||
// the measurand without a phase as a total value
|
||||
result_vector.push_back(measurand_with_phase);
|
||||
|
||||
if (it->second.size() > 0) {
|
||||
// measurand can be provided on multiple phases
|
||||
for (const auto& phase : it->second) {
|
||||
measurand_with_phase.phase = phase;
|
||||
result_vector.push_back(measurand_with_phase);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const StringToEnumException& o) {
|
||||
EVLOG_warning << "Measurand: " << element << " is not supported!";
|
||||
element_error = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!element_error) {
|
||||
for (auto m : result_vector) {
|
||||
if (!m.phase) {
|
||||
EVLOG_debug << "measurand without phase: " << m.measurand;
|
||||
} else {
|
||||
EVLOG_debug << "measurand: " << m.measurand
|
||||
<< " with phase: " << conversions::phase_to_string(m.phase.value());
|
||||
}
|
||||
}
|
||||
|
||||
result = std::move(result_vector);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<std::uint32_t>
|
||||
ChargePointConfigurationBase::extractConnectorIdFromMeterPublicKey(const std::string& str) {
|
||||
// example string "MeterPublicKey1"
|
||||
|
||||
std::optional<std::uint32_t> result;
|
||||
const std::regex id_regex(R"(^MeterPublicKey(\d+)$)");
|
||||
std::smatch id_match;
|
||||
|
||||
if (std::regex_match(str, id_match, id_regex)) {
|
||||
if (id_match.size() == 2) {
|
||||
try {
|
||||
result = std::stoul(id_match[1]);
|
||||
} catch (const std::exception& ex) {
|
||||
EVLOG_error << "Unable to extract connector ID from '" << str << "': " << ex.what();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
EVLOG_error << "Connector ID not found: '" << str << '\'';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string ChargePointConfigurationBase::meterPublicKeyString(std::uint32_t connector_id) {
|
||||
return std::string{"MeterPublicKey"} + std::to_string(connector_id);
|
||||
}
|
||||
|
||||
bool ChargePointConfigurationBase::toBool(const std::string& value) {
|
||||
std::string out = value;
|
||||
std::transform(out.begin(), out.end(), out.begin(), ::tolower);
|
||||
if ((out == "true") || out == "1") {
|
||||
return true;
|
||||
} else if ((out == "false") || out == "0") {
|
||||
return false;
|
||||
}
|
||||
throw std::invalid_argument("Not a boolean value");
|
||||
}
|
||||
|
||||
bool ChargePointConfigurationBase::isBool(const std::string& str) {
|
||||
std::string out = str;
|
||||
std::transform(out.begin(), out.end(), out.begin(), ::tolower);
|
||||
return out == "true" || out == "false";
|
||||
}
|
||||
|
||||
bool ChargePointConfigurationBase::isPositiveInteger(const std::string& str) {
|
||||
bool result{false};
|
||||
try {
|
||||
result = std::get<bool>(is_positive_integer(str));
|
||||
} catch (const std::invalid_argument& e) {
|
||||
} catch (const std::out_of_range& e) {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ChargePointConfigurationBase::isConnectorPhaseRotationValid(std::int32_t num_connectors,
|
||||
const std::string& value) {
|
||||
std::string str{value};
|
||||
bool result{true};
|
||||
|
||||
str.erase(std::remove_if(str.begin(), str.end(), isspace), str.end());
|
||||
auto elements = v16::utils::from_csl(str);
|
||||
|
||||
for (const auto& e : elements) {
|
||||
const auto parts = utils::split_string('.', e);
|
||||
result = false;
|
||||
if (parts.size() == 2) {
|
||||
try {
|
||||
const std::string& connector_str = parts[0];
|
||||
const std::string& rotation_str = parts[1];
|
||||
std::size_t len;
|
||||
auto connector = std::stoi(connector_str, &len);
|
||||
if ((len == connector_str.size()) && (connector >= 0) && (connector <= num_connectors)) {
|
||||
if (rotation_str == "NotApplicable" || rotation_str == "Unknown" || rotation_str == "RST" ||
|
||||
rotation_str == "RTS" || rotation_str == "SRT" || rotation_str == "STR" ||
|
||||
rotation_str == "TRS" || rotation_str == "TSR") {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
} catch (const std::invalid_argument&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ChargePointConfigurationBase::isTimeOffset(const std::string& offset) {
|
||||
bool result{false};
|
||||
|
||||
const auto times = v16::utils::split_string(':', offset);
|
||||
if (times.size() != 2) {
|
||||
EVLOG_error
|
||||
<< R"(Could not set display time offset: format not correct (should be something like "-05:00", but is )"
|
||||
<< offset << ')';
|
||||
} else {
|
||||
try {
|
||||
result = true;
|
||||
// Check if strings are numbers.
|
||||
const int32_t hours = std::stoi(times.at(0));
|
||||
const int32_t minutes = std::stoi(times.at(1));
|
||||
|
||||
// And check if numbers are valid.
|
||||
if (hours < -24 or hours > 24) {
|
||||
EVLOG_error << "Could not set display time offset: hours should be between -24 and +24, but is "
|
||||
<< times.at(0);
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (minutes < 0 or minutes > 59) {
|
||||
EVLOG_error << "Could not set display time offset: minutes should be between 0 and 59, but is "
|
||||
<< times.at(1);
|
||||
result = false;
|
||||
}
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error
|
||||
<< R"(Could not set display time offset: format not correct (should be something like "-19:15", but is )"
|
||||
<< offset << "): " << e.what();
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ChargePointConfigurationBase::areValidEvseIds(const std::string& value) {
|
||||
bool result{true};
|
||||
|
||||
if (value.length() <= CONNECTOR_EVSE_IDS_MAX_LENGTH) {
|
||||
// this fullfills parts of HUB-24-003 of Requirements EVSE Check PnC with ISO15118-2 v4
|
||||
const auto evse_ids = v16::utils::from_csl(value);
|
||||
for (const auto& evse_id : evse_ids) {
|
||||
if (evse_id.size() < 7 or evse_id.size() > 37) {
|
||||
EVLOG_warning << "Attempting to set ConnectorEvseIds to invalid value: " << evse_id;
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string ChargePointConfigurationBase::hexToString(const std::string& s) {
|
||||
std::string str;
|
||||
for (size_t i = 0; i < s.length(); i += 2) {
|
||||
std::string byte = s.substr(i, 2);
|
||||
char chr = (char)(int)strtol(byte.c_str(), NULL, 16);
|
||||
str.push_back(chr);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
bool ChargePointConfigurationBase::isHexNotation(const std::string& s) {
|
||||
// TODO(james-ctc): this check is problematic
|
||||
// need to identify what is considered valid
|
||||
// e.g. what can the first two characters be?
|
||||
// why are they not ignored in hexToString()?
|
||||
// the size should be a multiple of 2 …
|
||||
//
|
||||
// consider combining isHexNotation and hexToString to avoid reparsing
|
||||
// the string
|
||||
|
||||
bool result = s.size() > 2 and s.find_first_not_of("0123456789abcdefABCDEF", 2) == std::string::npos;
|
||||
if (result) {
|
||||
// check if every char is printable
|
||||
for (size_t i = 0; i < s.length(); i += 2) {
|
||||
std::string byte = s.substr(i, 2);
|
||||
char chr = (char)(int)strtol(byte.c_str(), NULL, 16);
|
||||
// ignoring check for \n (0x0a) because find_first_not_of() has not
|
||||
// found one
|
||||
if (!std::isprint(chr)) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace ocpp::v16
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,401 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <ocpp/v16/charge_point_state_machine.hpp>
|
||||
#include <ocpp/v16/ocpp_enums.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <everest/logging.hpp>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
static const FSMDefinition FSM_DEF = {
|
||||
{FSMState::Available,
|
||||
{
|
||||
{FSMEvent::UsageInitiated, FSMState::Preparing},
|
||||
{FSMEvent::StartCharging, FSMState::Charging},
|
||||
{FSMEvent::PauseChargingEV, FSMState::SuspendedEV},
|
||||
{FSMEvent::PauseChargingEVSE, FSMState::SuspendedEVSE},
|
||||
{FSMEvent::ReserveConnector, FSMState::Reserved},
|
||||
{FSMEvent::ChangeAvailabilityToUnavailable, FSMState::Unavailable},
|
||||
}},
|
||||
{FSMState::Preparing,
|
||||
{
|
||||
{FSMEvent::BecomeAvailable, FSMState::Available},
|
||||
{FSMEvent::StartCharging, FSMState::Charging},
|
||||
{FSMEvent::PauseChargingEV, FSMState::SuspendedEV},
|
||||
{FSMEvent::PauseChargingEVSE, FSMState::SuspendedEVSE},
|
||||
{FSMEvent::TransactionStoppedAndUserActionRequired, FSMState::Finishing},
|
||||
}},
|
||||
{FSMState::Charging,
|
||||
{
|
||||
{FSMEvent::BecomeAvailable, FSMState::Available},
|
||||
{FSMEvent::PauseChargingEV, FSMState::SuspendedEV},
|
||||
{FSMEvent::PauseChargingEVSE, FSMState::SuspendedEVSE},
|
||||
{FSMEvent::TransactionStoppedAndUserActionRequired, FSMState::Finishing},
|
||||
{FSMEvent::ChangeAvailabilityToUnavailable, FSMState::Unavailable},
|
||||
}},
|
||||
{FSMState::SuspendedEV,
|
||||
{
|
||||
{FSMEvent::BecomeAvailable, FSMState::Available},
|
||||
{FSMEvent::StartCharging, FSMState::Charging},
|
||||
{FSMEvent::PauseChargingEVSE, FSMState::SuspendedEVSE},
|
||||
{FSMEvent::TransactionStoppedAndUserActionRequired, FSMState::Finishing},
|
||||
{FSMEvent::ChangeAvailabilityToUnavailable, FSMState::Unavailable},
|
||||
}},
|
||||
{FSMState::SuspendedEVSE,
|
||||
{
|
||||
{FSMEvent::BecomeAvailable, FSMState::Available},
|
||||
{FSMEvent::StartCharging, FSMState::Charging},
|
||||
{FSMEvent::PauseChargingEV, FSMState::SuspendedEV},
|
||||
{FSMEvent::TransactionStoppedAndUserActionRequired, FSMState::Finishing},
|
||||
{FSMEvent::ChangeAvailabilityToUnavailable, FSMState::Unavailable},
|
||||
}},
|
||||
{FSMState::Finishing,
|
||||
{
|
||||
{FSMEvent::BecomeAvailable, FSMState::Available},
|
||||
{FSMEvent::UsageInitiated, FSMState::Preparing},
|
||||
{FSMEvent::ChangeAvailabilityToUnavailable, FSMState::Unavailable},
|
||||
}},
|
||||
{FSMState::Reserved,
|
||||
{{FSMEvent::BecomeAvailable, FSMState::Available},
|
||||
{FSMEvent::UsageInitiated, FSMState::Preparing},
|
||||
{FSMEvent::ChangeAvailabilityToUnavailable, FSMState::Unavailable},
|
||||
{FSMEvent::ReservationEnd, FSMState::Available}}},
|
||||
{FSMState::Unavailable,
|
||||
{
|
||||
{FSMEvent::BecomeAvailable, FSMState::Available},
|
||||
{FSMEvent::UsageInitiated, FSMState::Preparing},
|
||||
{FSMEvent::StartCharging, FSMState::Charging},
|
||||
{FSMEvent::PauseChargingEV, FSMState::SuspendedEV},
|
||||
{FSMEvent::PauseChargingEVSE, FSMState::SuspendedEVSE},
|
||||
}},
|
||||
{FSMState::Faulted,
|
||||
{
|
||||
{FSMEvent::I1_ReturnToAvailable, FSMState::Available},
|
||||
{FSMEvent::I2_ReturnToPreparing, FSMState::Preparing},
|
||||
{FSMEvent::I3_ReturnToCharging, FSMState::Charging},
|
||||
{FSMEvent::I4_ReturnToSuspendedEV, FSMState::SuspendedEV},
|
||||
{FSMEvent::I5_ReturnToSuspendedEVSE, FSMState::SuspendedEVSE},
|
||||
{FSMEvent::I6_ReturnToFinishing, FSMState::Finishing},
|
||||
{FSMEvent::I7_ReturnToReserved, FSMState::Reserved},
|
||||
{FSMEvent::I8_ReturnToUnavailable, FSMState::Unavailable},
|
||||
}},
|
||||
};
|
||||
|
||||
// special fsm definition for connector 0 wih reduced states
|
||||
static const FSMDefinition FSM_DEF_CONNECTOR_ZERO = {
|
||||
{FSMState::Available,
|
||||
{
|
||||
{FSMEvent::ChangeAvailabilityToUnavailable, FSMState::Unavailable},
|
||||
}},
|
||||
{FSMState::Unavailable,
|
||||
{
|
||||
{FSMEvent::BecomeAvailable, FSMState::Available},
|
||||
}},
|
||||
{FSMState::Faulted,
|
||||
{
|
||||
{FSMEvent::I1_ReturnToAvailable, FSMState::Available},
|
||||
{FSMEvent::I8_ReturnToUnavailable, FSMState::Unavailable},
|
||||
}},
|
||||
};
|
||||
|
||||
ErrorInfo::ErrorInfo(const std::string uuid, const ChargePointErrorCode error_code, const bool is_fault) :
|
||||
uuid(uuid), error_code(error_code), is_fault(is_fault) {
|
||||
}
|
||||
|
||||
ErrorInfo::ErrorInfo(const std::string uuid, const ChargePointErrorCode error_code, const bool is_fault,
|
||||
const std::optional<std::string> info) :
|
||||
ErrorInfo(uuid, error_code, is_fault) {
|
||||
if (!info.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// CiString for info is allowed to have max of 50 characters
|
||||
if (info.value().size() > 50) {
|
||||
this->info = info.value().substr(0, 50);
|
||||
} else {
|
||||
this->info = info;
|
||||
}
|
||||
}
|
||||
|
||||
ErrorInfo::ErrorInfo(const std::string uuid, const ChargePointErrorCode error_code, const bool is_fault,
|
||||
const std::optional<std::string> info, const std::optional<std::string> vendor_id) :
|
||||
ErrorInfo(uuid, error_code, is_fault, info) {
|
||||
if (!vendor_id.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// CiString for vendor_id is allowed to have max of 50 characters
|
||||
if (vendor_id.value().size() > 255) {
|
||||
this->vendor_id = vendor_id.value().substr(0, 255);
|
||||
} else {
|
||||
this->vendor_id = vendor_id;
|
||||
}
|
||||
}
|
||||
|
||||
ErrorInfo::ErrorInfo(const std::string uuid, const ChargePointErrorCode error_code, const bool is_fault,
|
||||
const std::optional<std::string> info, const std::optional<std::string> vendor_id,
|
||||
const std::optional<std::string> vendor_error_code) :
|
||||
ErrorInfo(uuid, error_code, is_fault, info, vendor_id) {
|
||||
if (!vendor_error_code.has_value()) {
|
||||
return;
|
||||
}
|
||||
// CiString for vendor_error_code is allowed to have max of 50 characters
|
||||
if (vendor_error_code.value().size() > 50) {
|
||||
this->vendor_error_code = vendor_error_code.value().substr(0, 50);
|
||||
} else {
|
||||
this->vendor_error_code = vendor_error_code;
|
||||
}
|
||||
}
|
||||
|
||||
ChargePointFSM::ChargePointFSM(const StatusNotificationCallback& status_notification_callback_,
|
||||
FSMState initial_state) :
|
||||
status_notification_callback(status_notification_callback_), state(initial_state) {
|
||||
}
|
||||
|
||||
FSMState ChargePointFSM::get_state() {
|
||||
if (this->is_faulted()) {
|
||||
return FSMState::Faulted;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
bool ChargePointFSM::is_faulted() {
|
||||
// check if there is any active fault
|
||||
return std::find_if(this->active_errors.begin(), this->active_errors.end(),
|
||||
[](const std::pair<std::string, ErrorInfo>& entry) { return entry.second.is_fault; }) !=
|
||||
this->active_errors.end();
|
||||
}
|
||||
|
||||
std::optional<ErrorInfo> ChargePointFSM::get_latest_error() {
|
||||
if (this->active_errors.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto latest_error = (*this->active_errors.begin()).second;
|
||||
for (const auto& [uuid, error_info] : this->active_errors) {
|
||||
if (error_info.timestamp > latest_error.timestamp) {
|
||||
latest_error = error_info;
|
||||
}
|
||||
}
|
||||
return latest_error;
|
||||
}
|
||||
|
||||
bool ChargePointFSM::handle_event(FSMEvent event, const ocpp::DateTime timestamp,
|
||||
const std::optional<CiString<50>>& info) {
|
||||
const auto& transitions = FSM_DEF.at(state);
|
||||
const auto dest_state_it = transitions.find(event);
|
||||
|
||||
if (dest_state_it == transitions.end()) {
|
||||
// no transition defined for this event / should this be logged?
|
||||
return false;
|
||||
}
|
||||
|
||||
// fall through: transition found
|
||||
state = dest_state_it->second;
|
||||
|
||||
const auto error_info = this->get_latest_error().value_or(ErrorInfo("", ChargePointErrorCode::NoError, false));
|
||||
|
||||
// only send a StatusNotification.req with the updated state if not in faulted
|
||||
if (!this->is_faulted()) {
|
||||
status_notification_callback(state, error_info.error_code, timestamp, info, error_info.vendor_id,
|
||||
error_info.vendor_error_code);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChargePointFSM::handle_error(const ErrorInfo& error_info) {
|
||||
if (this->active_errors.count(error_info.uuid) != 0) {
|
||||
// has already been handled and reported
|
||||
return false;
|
||||
}
|
||||
|
||||
this->active_errors.insert({error_info.uuid, error_info});
|
||||
|
||||
if (!this->is_faulted()) {
|
||||
status_notification_callback(this->state, error_info.error_code, error_info.timestamp, error_info.info,
|
||||
error_info.vendor_id, error_info.vendor_error_code);
|
||||
} else {
|
||||
status_notification_callback(FSMState::Faulted, error_info.error_code, error_info.timestamp, error_info.info,
|
||||
error_info.vendor_id, error_info.vendor_error_code);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChargePointFSM::handle_error_cleared(const std::string uuid) {
|
||||
// dont do anything if the error is unknown
|
||||
if (this->active_errors.find(uuid) == this->active_errors.end()) {
|
||||
EVLOG_warning << "Attempt to clear error with unknown id: " << uuid;
|
||||
return false;
|
||||
}
|
||||
|
||||
this->active_errors.erase(uuid);
|
||||
|
||||
// dont report StatusNotification if still "Faulted"
|
||||
if (this->is_faulted()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// defaults if no errors are active anymore
|
||||
ChargePointErrorCode error_code = ChargePointErrorCode::NoError;
|
||||
std::optional<CiString<50>> info;
|
||||
std::optional<CiString<255>> vendor_id;
|
||||
std::optional<CiString<50>> vendor_error_code;
|
||||
|
||||
// report the latest error if there are still errors active
|
||||
if (not this->active_errors.empty()) {
|
||||
const auto latest_error_opt = this->get_latest_error();
|
||||
if (latest_error_opt.has_value()) {
|
||||
const auto& latest_error = latest_error_opt.value();
|
||||
error_code = latest_error.error_code;
|
||||
info = latest_error.info;
|
||||
vendor_id = latest_error.vendor_id;
|
||||
vendor_error_code = latest_error.vendor_error_code;
|
||||
}
|
||||
}
|
||||
|
||||
// Send a StatusNotification.req
|
||||
status_notification_callback(this->state, error_code, DateTime(), info, vendor_id, vendor_error_code);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChargePointFSM::handle_all_errors_cleared() {
|
||||
this->active_errors.clear();
|
||||
status_notification_callback(this->state, ChargePointErrorCode::NoError, DateTime(), std::nullopt, std::nullopt,
|
||||
std::nullopt);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ChargePointFSM::trigger_status_notification() {
|
||||
// get latest error or report NoError
|
||||
const auto error_info = this->get_latest_error().value_or(ErrorInfo("", ChargePointErrorCode::NoError, false));
|
||||
if (!this->is_faulted()) {
|
||||
status_notification_callback(this->state, error_info.error_code, error_info.timestamp, error_info.info,
|
||||
error_info.vendor_id, error_info.vendor_error_code);
|
||||
} else {
|
||||
status_notification_callback(FSMState::Faulted, error_info.error_code, error_info.timestamp, error_info.info,
|
||||
error_info.vendor_id, error_info.vendor_error_code);
|
||||
}
|
||||
}
|
||||
|
||||
ChargePointStates::ChargePointStates(const ConnectorStatusCallback& callback) : connector_status_callback(callback) {
|
||||
}
|
||||
|
||||
void ChargePointStates::reset(std::map<int, ChargePointStatus> connector_status_map) {
|
||||
const std::lock_guard<std::mutex> lck(state_machines_mutex);
|
||||
state_machines.clear();
|
||||
|
||||
for (size_t connector_id = 0; connector_id < connector_status_map.size(); ++connector_id) {
|
||||
const auto initial_state = connector_status_map.at(clamp_to<int>(connector_id));
|
||||
|
||||
if (connector_id == 0 and initial_state != ChargePointStatus::Available and
|
||||
initial_state != ChargePointStatus::Unavailable and initial_state != ChargePointStatus::Faulted) {
|
||||
throw std::runtime_error("Invalid initial status for connector 0: " +
|
||||
conversions::charge_point_status_to_string(initial_state));
|
||||
}
|
||||
if (connector_id == 0) {
|
||||
state_machine_connector_zero = std::make_unique<ChargePointFSM>(
|
||||
[this](const ChargePointStatus status, const ChargePointErrorCode error_code,
|
||||
const ocpp::DateTime& timestamp, const std::optional<CiString<50>>& info,
|
||||
const std::optional<CiString<255>>& vendor_id,
|
||||
const std::optional<CiString<50>>& vendor_error_code) {
|
||||
this->connector_status_callback(0, error_code, status, timestamp, info, vendor_id,
|
||||
vendor_error_code);
|
||||
},
|
||||
initial_state);
|
||||
} else {
|
||||
state_machines.emplace_back(
|
||||
[this, connector_id](ChargePointStatus status, ChargePointErrorCode error_code,
|
||||
ocpp::DateTime timestamp, std::optional<CiString<50>> info,
|
||||
std::optional<CiString<255>> vendor_id,
|
||||
std::optional<CiString<50>> vendor_error_code) {
|
||||
this->connector_status_callback(clamp_to<int>(connector_id), error_code, status, timestamp, info,
|
||||
vendor_id, vendor_error_code);
|
||||
},
|
||||
initial_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChargePointStates::submit_event(const int connector_id, FSMEvent event, const ocpp::DateTime& timestamp,
|
||||
const std::optional<CiString<50>>& info) {
|
||||
const std::lock_guard<std::mutex> lck(state_machines_mutex);
|
||||
if (connector_id == 0) {
|
||||
this->state_machine_connector_zero->handle_event(event, timestamp, info);
|
||||
} else if (connector_id > 0 && static_cast<size_t>(connector_id) <= this->state_machines.size()) {
|
||||
this->state_machines.at(connector_id - 1).handle_event(event, timestamp, info);
|
||||
}
|
||||
}
|
||||
|
||||
void ChargePointStates::submit_error(const int connector_id, const ErrorInfo& error_info) {
|
||||
const std::lock_guard<std::mutex> lck(state_machines_mutex);
|
||||
if (connector_id == 0) {
|
||||
this->state_machine_connector_zero->handle_error(error_info);
|
||||
} else if (connector_id > 0 && static_cast<size_t>(connector_id) <= state_machines.size()) {
|
||||
state_machines.at(connector_id - 1).handle_error(error_info);
|
||||
}
|
||||
}
|
||||
|
||||
void ChargePointStates::submit_error_cleared(const int connector_id, const std::string uuid) {
|
||||
const std::lock_guard<std::mutex> lck(state_machines_mutex);
|
||||
if (connector_id == 0) {
|
||||
this->state_machine_connector_zero->handle_error_cleared(uuid);
|
||||
} else if (connector_id > 0 && static_cast<size_t>(connector_id) <= state_machines.size()) {
|
||||
state_machines.at(connector_id - 1).handle_error_cleared(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
void ChargePointStates::submit_all_errors_cleared(const std::int32_t connector_id) {
|
||||
const std::lock_guard<std::mutex> lck(state_machines_mutex);
|
||||
if (connector_id == 0) {
|
||||
this->state_machine_connector_zero->handle_all_errors_cleared();
|
||||
} else if (connector_id > 0 && static_cast<size_t>(connector_id) <= state_machines.size()) {
|
||||
state_machines.at(connector_id - 1).handle_all_errors_cleared();
|
||||
}
|
||||
}
|
||||
|
||||
void ChargePointStates::trigger_status_notification(const int connector_id) {
|
||||
const std::lock_guard<std::mutex> lck(state_machines_mutex);
|
||||
if (connector_id == 0) {
|
||||
this->state_machine_connector_zero->trigger_status_notification();
|
||||
} else {
|
||||
this->state_machines.at(connector_id - 1).trigger_status_notification();
|
||||
}
|
||||
}
|
||||
|
||||
void ChargePointStates::trigger_status_notifications() {
|
||||
const std::lock_guard<std::mutex> lck(state_machines_mutex);
|
||||
this->state_machine_connector_zero->trigger_status_notification();
|
||||
for (size_t connector_id = 0; connector_id < state_machines.size(); ++connector_id) {
|
||||
this->state_machines.at(connector_id).trigger_status_notification();
|
||||
}
|
||||
}
|
||||
|
||||
ChargePointStatus ChargePointStates::get_state(int connector_id) {
|
||||
const std::lock_guard<std::mutex> lck(state_machines_mutex);
|
||||
if (connector_id > 0 && static_cast<size_t>(connector_id) <= this->state_machines.size()) {
|
||||
return state_machines.at(connector_id - 1).get_state();
|
||||
}
|
||||
if (connector_id == 0) {
|
||||
return state_machine_connector_zero->get_state();
|
||||
}
|
||||
|
||||
// fall through on invalid id
|
||||
return ChargePointStatus::Unavailable;
|
||||
}
|
||||
|
||||
std::optional<ErrorInfo> ChargePointStates::get_latest_error(int connector_id) {
|
||||
const std::lock_guard<std::mutex> lck(state_machines_mutex);
|
||||
if (connector_id > 0 && static_cast<size_t>(connector_id) <= this->state_machines.size()) {
|
||||
return state_machines.at(connector_id - 1).get_latest_error();
|
||||
}
|
||||
return state_machine_connector_zero->get_latest_error();
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,683 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
#include <ocpp/v16/database_handler.hpp>
|
||||
|
||||
using namespace everest::db;
|
||||
using namespace everest::db::sqlite;
|
||||
|
||||
namespace ocpp {
|
||||
|
||||
using namespace common;
|
||||
|
||||
namespace v16 {
|
||||
|
||||
DatabaseHandler::DatabaseHandler(std::unique_ptr<ConnectionInterface> database,
|
||||
const fs::path& sql_migration_files_path, std::int32_t number_of_connectors) :
|
||||
DatabaseHandlerCommon(std::move(database), sql_migration_files_path, MIGRATION_FILE_VERSION_V16),
|
||||
number_of_connectors(number_of_connectors) {
|
||||
}
|
||||
|
||||
void DatabaseHandler::init_sql() {
|
||||
this->init_connector_table();
|
||||
try {
|
||||
this->insert_or_ignore_local_list_version(0);
|
||||
} catch (const QueryExecutionException& e) {
|
||||
EVLOG_warning << "Could not insert or ignore version into AUTH_LIST_VERSION table: " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseHandler::init_connector_table() {
|
||||
for (std::int32_t connector = 0; connector <= this->number_of_connectors; connector++) {
|
||||
const std::string sql =
|
||||
"INSERT OR IGNORE INTO CONNECTORS (ID, AVAILABILITY) VALUES (@connector, @availability_type)";
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
stmt->bind_int("@connector", connector);
|
||||
stmt->bind_text("@availability_type", "Operative", SQLiteString::Transient);
|
||||
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
EVLOG_error << "Could not insert into Connector table";
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// transactions
|
||||
void DatabaseHandler::insert_transaction(const std::string& session_id, const std::int32_t transaction_id,
|
||||
const std::int32_t connector, const std::string& id_tag_start,
|
||||
const std::string& time_start, const std::int32_t meter_start,
|
||||
const bool csms_ack, const std::optional<std::int32_t> reservation_id,
|
||||
const std::string& start_transaction_message_id) {
|
||||
const std::string sql =
|
||||
"INSERT INTO TRANSACTIONS (ID, TRANSACTION_ID, CONNECTOR, ID_TAG_START, TIME_START, METER_START, "
|
||||
"CSMS_ACK, METER_LAST, METER_LAST_TIME, LAST_UPDATE, RESERVATION_ID, START_TRANSACTION_MESSAGE_ID) VALUES "
|
||||
"(@session_id, @transaction_id, @connector, @id_tag_start, @time_start, @meter_start, @csms_ack, "
|
||||
"@meter_last, @meter_last_time, @last_update, @reservation_id, @start_transaction_message_id)";
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
stmt->bind_text("@session_id", session_id);
|
||||
stmt->bind_int("@transaction_id", transaction_id);
|
||||
stmt->bind_int("@connector", connector);
|
||||
stmt->bind_text("@id_tag_start", id_tag_start);
|
||||
stmt->bind_text("@time_start", time_start);
|
||||
stmt->bind_int("@meter_start", meter_start);
|
||||
stmt->bind_int("@csms_ack", int(csms_ack));
|
||||
stmt->bind_int("@meter_last", meter_start);
|
||||
stmt->bind_text("@meter_last_time", time_start);
|
||||
stmt->bind_text("@last_update", ocpp::DateTime().to_rfc3339(), SQLiteString::Transient);
|
||||
stmt->bind_text("@start_transaction_message_id", start_transaction_message_id, SQLiteString::Transient);
|
||||
|
||||
if (reservation_id.has_value()) {
|
||||
stmt->bind_int("@reservation_id", reservation_id.value());
|
||||
} else {
|
||||
stmt->bind_null("@reservation_id");
|
||||
}
|
||||
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseHandler::update_transaction(const std::string& session_id, std::int32_t transaction_id,
|
||||
std::optional<CiString<20>> parent_id_tag) {
|
||||
|
||||
const std::string sql = "UPDATE TRANSACTIONS SET TRANSACTION_ID=@transaction_id, PARENT_ID_TAG=@parent_id_tag, "
|
||||
"LAST_UPDATE=@last_update WHERE ID==@session_id";
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
// bindings
|
||||
stmt->bind_int("@transaction_id", transaction_id);
|
||||
if (parent_id_tag.has_value()) {
|
||||
stmt->bind_text("@parent_id_tag", parent_id_tag.value().get(), SQLiteString::Transient);
|
||||
}
|
||||
stmt->bind_text("@last_update", ocpp::DateTime().to_rfc3339(), SQLiteString::Transient);
|
||||
stmt->bind_text("@session_id", session_id);
|
||||
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseHandler::update_transaction(const std::string& session_id, std::int32_t meter_stop,
|
||||
const std::string& time_end, std::optional<CiString<20>> id_tag_end,
|
||||
std::optional<v16::Reason> stop_reason,
|
||||
const std::string& stop_transaction_message_id) {
|
||||
const std::string sql = "UPDATE TRANSACTIONS SET METER_STOP=@meter_stop, TIME_END=@time_end, "
|
||||
"ID_TAG_END=@id_tag_end, STOP_REASON=@stop_reason, LAST_UPDATE=@last_update, "
|
||||
"STOP_TRANSACTION_MESSAGE_ID=@stop_transaction_message_id WHERE ID==@session_id";
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
stmt->bind_int("@meter_stop", meter_stop);
|
||||
stmt->bind_text("@time_end", time_end);
|
||||
if (id_tag_end.has_value()) {
|
||||
stmt->bind_text("@id_tag_end", id_tag_end.value().get(), SQLiteString::Transient);
|
||||
}
|
||||
if (stop_reason.has_value()) {
|
||||
stmt->bind_text("@stop_reason", v16::conversions::reason_to_string(stop_reason.value()),
|
||||
SQLiteString::Transient);
|
||||
}
|
||||
stmt->bind_text("@last_update", ocpp::DateTime().to_rfc3339(), SQLiteString::Transient);
|
||||
stmt->bind_text("@session_id", session_id);
|
||||
stmt->bind_text("@stop_transaction_message_id", stop_transaction_message_id, SQLiteString::Transient);
|
||||
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseHandler::update_transaction_csms_ack(const std::int32_t transaction_id) {
|
||||
const std::string sql =
|
||||
"UPDATE TRANSACTIONS SET CSMS_ACK=1, LAST_UPDATE=@last_update WHERE TRANSACTION_ID==@transaction_id";
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
stmt->bind_text("@last_update", ocpp::DateTime().to_rfc3339(), SQLiteString::Transient);
|
||||
stmt->bind_int("@transaction_id", transaction_id);
|
||||
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<TransactionEntry> DatabaseHandler::get_transaction(const std::int32_t transaction_id) {
|
||||
const std::string sql = "SELECT * FROM TRANSACTIONS WHERE TRANSACTION_ID==@transaction_id";
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
stmt->bind_int("@transaction_id", transaction_id);
|
||||
if (stmt->step() != SQLITE_ROW) {
|
||||
return std::nullopt;
|
||||
}
|
||||
TransactionEntry entry;
|
||||
entry.session_id = stmt->column_text(0);
|
||||
entry.transaction_id = stmt->column_int(1);
|
||||
entry.connector = stmt->column_int(2);
|
||||
entry.id_tag_start = stmt->column_text(3);
|
||||
entry.time_start = stmt->column_text(4);
|
||||
entry.meter_start = stmt->column_int(5);
|
||||
entry.csms_ack = bool(stmt->column_int(6));
|
||||
entry.meter_last = stmt->column_int(7);
|
||||
entry.meter_last_time = stmt->column_text(8);
|
||||
entry.last_update = stmt->column_text(9);
|
||||
if (stmt->column_type(10) != SQLITE_NULL) {
|
||||
entry.reservation_id.emplace(stmt->column_int(10));
|
||||
}
|
||||
if (stmt->column_type(11) != SQLITE_NULL) {
|
||||
entry.parent_id_tag.emplace(stmt->column_text(11));
|
||||
}
|
||||
if (stmt->column_type(12) != SQLITE_NULL) {
|
||||
entry.id_tag_end.emplace(stmt->column_text(12));
|
||||
}
|
||||
if (stmt->column_type(13) != SQLITE_NULL) {
|
||||
entry.time_end.emplace(stmt->column_text(13));
|
||||
}
|
||||
if (stmt->column_type(14) != SQLITE_NULL) {
|
||||
entry.meter_stop.emplace(stmt->column_int(14));
|
||||
}
|
||||
if (stmt->column_type(15) != SQLITE_NULL) {
|
||||
entry.stop_reason.emplace(stmt->column_text(15));
|
||||
}
|
||||
entry.start_transaction_message_id = stmt->column_text(16);
|
||||
if (stmt->column_type(17) != SQLITE_NULL) {
|
||||
entry.stop_transaction_message_id = stmt->column_text(17);
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
void DatabaseHandler::update_start_transaction_message_id(const std::string& /*session_id*/,
|
||||
const std::string& start_transaction_message_id) {
|
||||
const std::string sql = "UPDATE TRANSACTIONS SET START_TRANSACTION_MESSAGE_ID=@start_transaction_message_id, "
|
||||
"LAST_UPDATE=@last_update WHERE ID==@session_id";
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
stmt->bind_text("@last_update", ocpp::DateTime().to_rfc3339(), SQLiteString::Transient);
|
||||
stmt->bind_text("@start_transaction_message_id", start_transaction_message_id);
|
||||
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseHandler::update_transaction_meter_value(const std::string& session_id, const std::int32_t value,
|
||||
const std::string& last_meter_time) {
|
||||
const std::string sql = "UPDATE TRANSACTIONS SET METER_LAST=@meter_last, METER_LAST_TIME=@meter_last_time, "
|
||||
"LAST_UPDATE=@last_update WHERE ID==@session_id";
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
stmt->bind_int("@meter_last", value);
|
||||
stmt->bind_text("@meter_last_time", last_meter_time);
|
||||
stmt->bind_text("@last_update", ocpp::DateTime().to_rfc3339(), SQLiteString::Transient);
|
||||
stmt->bind_text("@session_id", session_id);
|
||||
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<TransactionEntry> DatabaseHandler::get_transactions(bool filter_incomplete) {
|
||||
std::vector<TransactionEntry> transactions;
|
||||
|
||||
std::string sql = "SELECT * FROM TRANSACTIONS";
|
||||
|
||||
if (filter_incomplete) {
|
||||
sql += " WHERE CSMS_ACK==0";
|
||||
}
|
||||
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
int status = SQLITE_ERROR;
|
||||
while ((status = stmt->step()) == SQLITE_ROW) {
|
||||
TransactionEntry transaction_entry;
|
||||
transaction_entry.session_id = stmt->column_text(0);
|
||||
transaction_entry.transaction_id = stmt->column_int(1);
|
||||
transaction_entry.connector = stmt->column_int(2);
|
||||
transaction_entry.id_tag_start = stmt->column_text(3);
|
||||
transaction_entry.time_start = stmt->column_text(4);
|
||||
transaction_entry.meter_start = stmt->column_int(5);
|
||||
transaction_entry.csms_ack = bool(stmt->column_int(6));
|
||||
transaction_entry.meter_last = stmt->column_int(7);
|
||||
transaction_entry.meter_last_time = stmt->column_text(8);
|
||||
transaction_entry.last_update = stmt->column_text(9);
|
||||
|
||||
if (stmt->column_type(10) != SQLITE_NULL) {
|
||||
transaction_entry.reservation_id.emplace(stmt->column_int(10));
|
||||
}
|
||||
if (stmt->column_type(11) != SQLITE_NULL) {
|
||||
transaction_entry.parent_id_tag.emplace(stmt->column_text(11));
|
||||
}
|
||||
if (stmt->column_type(12) != SQLITE_NULL) {
|
||||
transaction_entry.id_tag_end.emplace(stmt->column_text(12));
|
||||
}
|
||||
if (stmt->column_type(13) != SQLITE_NULL) {
|
||||
transaction_entry.time_end.emplace(stmt->column_text(13));
|
||||
}
|
||||
if (stmt->column_type(14) != SQLITE_NULL) {
|
||||
transaction_entry.meter_stop.emplace(stmt->column_int(14));
|
||||
}
|
||||
if (stmt->column_type(15) != SQLITE_NULL) {
|
||||
transaction_entry.stop_reason.emplace(stmt->column_text(15));
|
||||
}
|
||||
transaction_entry.start_transaction_message_id = stmt->column_text(16);
|
||||
if (stmt->column_type(17) != SQLITE_NULL) {
|
||||
transaction_entry.stop_transaction_message_id = stmt->column_text(17);
|
||||
}
|
||||
transactions.push_back(transaction_entry);
|
||||
}
|
||||
|
||||
if (status != SQLITE_DONE) {
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
|
||||
return transactions;
|
||||
}
|
||||
|
||||
// authorization cache
|
||||
void DatabaseHandler::insert_or_update_authorization_cache_entry(const CiString<20>& id_tag,
|
||||
const v16::IdTagInfo& id_tag_info) {
|
||||
|
||||
// TODO(piet): Only call this when authorization cache is enabled!
|
||||
|
||||
const std::string sql =
|
||||
"INSERT OR REPLACE INTO AUTH_CACHE (ID_TAG, AUTH_STATUS, EXPIRY_DATE, PARENT_ID_TAG) VALUES "
|
||||
"(@id_tag, @auth_status, @expiry_date, @parent_id_tag)";
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
stmt->bind_text("@id_tag", id_tag.get(), SQLiteString::Transient);
|
||||
stmt->bind_text("@auth_status", v16::conversions::authorization_status_to_string(id_tag_info.status),
|
||||
SQLiteString::Transient);
|
||||
if (id_tag_info.expiryDate.has_value()) {
|
||||
stmt->bind_text("@expiry_date", id_tag_info.expiryDate.value().to_rfc3339(), SQLiteString::Transient);
|
||||
}
|
||||
if (id_tag_info.parentIdTag.has_value()) {
|
||||
stmt->bind_text("@parent_id_tag", id_tag_info.parentIdTag.value().get(), SQLiteString::Transient);
|
||||
}
|
||||
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<v16::IdTagInfo> DatabaseHandler::get_authorization_cache_entry(const CiString<20>& id_tag) {
|
||||
|
||||
const std::string sql =
|
||||
"SELECT ID_TAG, AUTH_STATUS, EXPIRY_DATE, PARENT_ID_TAG FROM AUTH_CACHE WHERE ID_TAG = @id_tag";
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
stmt->bind_text("@id_tag", id_tag.get(), SQLiteString::Transient);
|
||||
|
||||
const int status = stmt->step();
|
||||
|
||||
// entry was not found
|
||||
if (status == SQLITE_DONE) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (status == SQLITE_ROW) {
|
||||
v16::IdTagInfo id_tag_info;
|
||||
id_tag_info.status = v16::conversions::string_to_authorization_status(stmt->column_text(1));
|
||||
|
||||
if (stmt->column_type(2) != SQLITE_NULL) {
|
||||
id_tag_info.expiryDate.emplace(stmt->column_text(2));
|
||||
}
|
||||
|
||||
if (stmt->column_type(3) != SQLITE_NULL) {
|
||||
id_tag_info.parentIdTag.emplace(stmt->column_text(3));
|
||||
}
|
||||
|
||||
// check if expiry date is set and the entry should be set to Expired
|
||||
if (id_tag_info.status != v16::AuthorizationStatus::Expired) {
|
||||
if (id_tag_info.expiryDate) {
|
||||
auto now = DateTime();
|
||||
if (id_tag_info.expiryDate.value() <= now) {
|
||||
EVLOG_debug << "IdTag " << id_tag
|
||||
<< " in auth cache has expiry date in the past, setting entry to expired.";
|
||||
id_tag_info.status = v16::AuthorizationStatus::Expired;
|
||||
this->insert_or_update_authorization_cache_entry(id_tag, id_tag_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
return id_tag_info;
|
||||
}
|
||||
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
|
||||
void DatabaseHandler::clear_authorization_cache() {
|
||||
const auto retval = this->database->clear_table("AUTH_CACHE");
|
||||
if (retval == false) {
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseHandler::insert_or_update_connector_availability(std::int32_t connector,
|
||||
const v16::AvailabilityType& availability_type) {
|
||||
const std::string sql = "INSERT OR REPLACE INTO CONNECTORS (ID, AVAILABILITY) VALUES (@id, @availability)";
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
stmt->bind_int("@id", connector);
|
||||
stmt->bind_text("@availability", v16::conversions::availability_type_to_string(availability_type),
|
||||
SQLiteString::Transient);
|
||||
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
EVLOG_error << "Could not insert availability into CONNECTORS table";
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
}
|
||||
|
||||
// connector availability
|
||||
void DatabaseHandler::insert_or_update_connector_availability(const std::vector<std::int32_t>& connectors,
|
||||
const v16::AvailabilityType& availability_type) {
|
||||
for (const auto connector : connectors) {
|
||||
this->insert_or_update_connector_availability(connector, availability_type);
|
||||
}
|
||||
}
|
||||
|
||||
v16::AvailabilityType DatabaseHandler::get_connector_availability(std::int32_t connector) {
|
||||
const std::string sql = "SELECT AVAILABILITY FROM CONNECTORS WHERE ID = @connector";
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
stmt->bind_int("@connector", connector);
|
||||
|
||||
const int status = stmt->step();
|
||||
|
||||
if (status == SQLITE_DONE) {
|
||||
throw RequiredEntryNotFoundException("Connector not found");
|
||||
}
|
||||
|
||||
if (status != SQLITE_ROW) {
|
||||
EVLOG_error << "Error while selecting availability of CONNECTORS table";
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
|
||||
return v16::conversions::string_to_availability_type(stmt->column_text(0));
|
||||
}
|
||||
|
||||
std::map<std::int32_t, v16::AvailabilityType> DatabaseHandler::get_connector_availability() {
|
||||
std::map<std::int32_t, v16::AvailabilityType> availability_map;
|
||||
const std::string sql = "SELECT ID, AVAILABILITY FROM CONNECTORS";
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
int status = SQLITE_ERROR;
|
||||
while ((status = stmt->step()) == SQLITE_ROW) {
|
||||
auto connector = stmt->column_int(0);
|
||||
availability_map[connector] = v16::conversions::string_to_availability_type(stmt->column_text(1));
|
||||
}
|
||||
|
||||
if (status != SQLITE_DONE) {
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
|
||||
return availability_map;
|
||||
}
|
||||
|
||||
void DatabaseHandler::insert_or_ignore_local_list_version(std::int32_t version) {
|
||||
const std::string sql = "INSERT OR IGNORE INTO AUTH_LIST_VERSION (ID, VERSION) VALUES (0, @version)";
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
stmt->bind_int("@version", version);
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
}
|
||||
|
||||
// local auth list management
|
||||
void DatabaseHandler::insert_or_update_local_list_version(std::int32_t version) {
|
||||
const std::string sql = "INSERT OR REPLACE INTO AUTH_LIST_VERSION (ID, VERSION) VALUES (0, @version)";
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
stmt->bind_int("@version", version);
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
}
|
||||
|
||||
std::int32_t DatabaseHandler::get_local_list_version() {
|
||||
const std::string sql = "SELECT VERSION FROM AUTH_LIST_VERSION WHERE ID = 0";
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
const int status = stmt->step();
|
||||
if (status == SQLITE_DONE) {
|
||||
throw RequiredEntryNotFoundException("Local list version not found");
|
||||
}
|
||||
|
||||
if (status != SQLITE_ROW) {
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
|
||||
return stmt->column_int(0);
|
||||
}
|
||||
|
||||
void DatabaseHandler::insert_or_update_local_authorization_list_entry(const CiString<20>& id_tag,
|
||||
const v16::IdTagInfo& id_tag_info) {
|
||||
// add or replace
|
||||
const std::string sql = "INSERT OR REPLACE INTO AUTH_LIST (ID_TAG, AUTH_STATUS, EXPIRY_DATE, PARENT_ID_TAG) VALUES "
|
||||
"(@id_tag, @auth_status, @expiry_date, @parent_id_tag)";
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
stmt->bind_text("@id_tag", id_tag.get(), SQLiteString::Transient);
|
||||
stmt->bind_text("@auth_status", v16::conversions::authorization_status_to_string(id_tag_info.status),
|
||||
SQLiteString::Transient);
|
||||
if (id_tag_info.expiryDate.has_value()) {
|
||||
stmt->bind_text("@expiry_date", id_tag_info.expiryDate.value().to_rfc3339(), SQLiteString::Transient);
|
||||
}
|
||||
if (id_tag_info.parentIdTag.has_value()) {
|
||||
stmt->bind_text("@parent_id_tag", id_tag_info.parentIdTag.value().get(), SQLiteString::Transient);
|
||||
}
|
||||
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
EVLOG_error << "Could not insert or update local authorization list entry into the database";
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseHandler::insert_or_update_local_authorization_list(
|
||||
std::vector<v16::LocalAuthorizationList> local_authorization_list) {
|
||||
bool success = true; // indicates if all database operations succeeded
|
||||
for (const auto& authorization_data : local_authorization_list) {
|
||||
try {
|
||||
if (authorization_data.idTagInfo) {
|
||||
this->insert_or_update_local_authorization_list_entry(authorization_data.idTag,
|
||||
authorization_data.idTagInfo.value());
|
||||
} else {
|
||||
this->delete_local_authorization_list_entry(authorization_data.idTag.get());
|
||||
}
|
||||
} catch (const QueryExecutionException& e) {
|
||||
// catch but continue with remaining entries
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
throw QueryExecutionException("At least one insertion or deletion of local authorization list entries failed");
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseHandler::delete_local_authorization_list_entry(const std::string& id_tag) {
|
||||
const std::string sql = "DELETE FROM AUTH_LIST WHERE ID_TAG = @id_tag;";
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
stmt->bind_text("@id_tag", id_tag);
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<v16::IdTagInfo> DatabaseHandler::get_local_authorization_list_entry(const CiString<20>& id_tag) {
|
||||
const std::string sql =
|
||||
"SELECT ID_TAG, AUTH_STATUS, EXPIRY_DATE, PARENT_ID_TAG FROM AUTH_LIST WHERE ID_TAG = @id_tag";
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
stmt->bind_text("@id_tag", id_tag.get(), SQLiteString::Transient);
|
||||
|
||||
const int status = stmt->step();
|
||||
|
||||
// entry was not found
|
||||
if (status == SQLITE_DONE) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// entry was found
|
||||
if (status == SQLITE_ROW) {
|
||||
v16::IdTagInfo id_tag_info;
|
||||
id_tag_info.status = v16::conversions::string_to_authorization_status(stmt->column_text(1));
|
||||
|
||||
if (stmt->column_type(2) != SQLITE_NULL) {
|
||||
id_tag_info.expiryDate.emplace(stmt->column_text(2));
|
||||
}
|
||||
|
||||
if (stmt->column_type(3) != SQLITE_NULL) {
|
||||
id_tag_info.parentIdTag.emplace(stmt->column_text(3));
|
||||
}
|
||||
|
||||
// check if expiry date is set and the entry should be set to Expired
|
||||
if (id_tag_info.status != v16::AuthorizationStatus::Expired) {
|
||||
if (id_tag_info.expiryDate) {
|
||||
auto now = DateTime();
|
||||
if (id_tag_info.expiryDate.value() <= now) {
|
||||
EVLOG_debug << "IdTag " << id_tag
|
||||
<< " in auth list has expiry date in the past, setting entry to expired.";
|
||||
id_tag_info.status = v16::AuthorizationStatus::Expired;
|
||||
this->insert_or_update_local_authorization_list_entry(id_tag, id_tag_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
return id_tag_info;
|
||||
}
|
||||
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
|
||||
void DatabaseHandler::clear_local_authorization_list() {
|
||||
const auto retval = this->database->clear_table("AUTH_LIST");
|
||||
if (retval == false) {
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
}
|
||||
|
||||
std::int32_t DatabaseHandler::get_local_authorization_list_number_of_entries() {
|
||||
const std::string sql = "SELECT COUNT(*) FROM AUTH_LIST;";
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
if (stmt->step() != SQLITE_ROW) {
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
|
||||
return stmt->column_int(0);
|
||||
}
|
||||
|
||||
void DatabaseHandler::insert_or_update_charging_profile(const int connector_id, const v16::ChargingProfile& profile) {
|
||||
|
||||
// Ensure compliance with OCPP 1.6 3.13.2. Stacking charging profiles:
|
||||
// To avoid conflicts, the existence of multiple Charging Profiles with
|
||||
// the same stackLevel and Purposes in a Charge Point is not allowed.
|
||||
// Whenever a Charge Point receives a Charging Profile with a stackLevel
|
||||
// and Purpose that already exists in the Charge Point, the Charge Point
|
||||
// SHALL replace the existing profile.
|
||||
|
||||
std::string sql = "DELETE FROM CHARGING_PROFILES WHERE "
|
||||
"Json_extract(PROFILE, '$.stackLevel') = @level AND "
|
||||
"Json_extract(PROFILE, '$.chargingProfilePurpose') = @purpose";
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
const std::string purpose =
|
||||
ocpp::v16::conversions::charging_profile_purpose_type_to_string(profile.chargingProfilePurpose);
|
||||
|
||||
stmt->bind_int("@level", profile.stackLevel);
|
||||
stmt->bind_text("@purpose", purpose, SQLiteString::Transient);
|
||||
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
|
||||
// add or replace
|
||||
sql = "INSERT OR REPLACE INTO CHARGING_PROFILES (ID, CONNECTOR_ID, PROFILE) VALUES "
|
||||
"(@id, @connector_id, @profile)";
|
||||
stmt = this->database->new_statement(sql);
|
||||
|
||||
const json json_profile(profile);
|
||||
|
||||
stmt->bind_int("@id", profile.chargingProfileId);
|
||||
stmt->bind_int("@connector_id", connector_id);
|
||||
stmt->bind_text("@profile", json_profile.dump(), SQLiteString::Transient);
|
||||
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseHandler::delete_charging_profile(const int profile_id) {
|
||||
const std::string sql = "DELETE FROM CHARGING_PROFILES WHERE ID = @id;";
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
stmt->bind_int("@id", profile_id);
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseHandler::delete_charging_profiles() {
|
||||
const auto retval = this->database->clear_table("CHARGING_PROFILES");
|
||||
if (retval == false) {
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<v16::ChargingProfile> DatabaseHandler::get_charging_profiles() {
|
||||
|
||||
std::vector<v16::ChargingProfile> profiles;
|
||||
const std::string sql = "SELECT * FROM CHARGING_PROFILES";
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
int status = SQLITE_ERROR;
|
||||
while ((status = stmt->step()) == SQLITE_ROW) {
|
||||
profiles.emplace_back(json::parse(stmt->column_text(2)));
|
||||
}
|
||||
|
||||
if (status != SQLITE_DONE) {
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
|
||||
return profiles;
|
||||
}
|
||||
|
||||
int DatabaseHandler::get_connector_id(const int profile_id) {
|
||||
const std::string sql = "SELECT CONNECTOR_ID FROM CHARGING_PROFILES WHERE ID = @profile_id";
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
stmt->bind_int("@profile_id", profile_id);
|
||||
|
||||
const int status = stmt->step();
|
||||
|
||||
if (status == SQLITE_DONE) {
|
||||
throw RequiredEntryNotFoundException("Connector id not found based on charging profile id");
|
||||
}
|
||||
|
||||
if (status != SQLITE_ROW) {
|
||||
throw QueryExecutionException(this->database->get_error_message());
|
||||
}
|
||||
|
||||
return stmt->column_int(0);
|
||||
}
|
||||
|
||||
std::vector<int32_t> DatabaseHandler::get_charging_profile_ids_by_connector_id(const int connector_id) {
|
||||
std::vector<int32_t> ids;
|
||||
const std::string sql = "SELECT ID FROM CHARGING_PROFILES WHERE CONNECTOR_ID = @connector_id";
|
||||
auto stmt = this->database->new_statement(sql);
|
||||
|
||||
stmt->bind_int("@connector_id", connector_id);
|
||||
|
||||
int status = SQLITE_ERROR;
|
||||
while ((status = stmt->step()) == SQLITE_ROW) {
|
||||
ids.push_back(stmt->column_int(0));
|
||||
}
|
||||
|
||||
if (status != SQLITE_DONE) {
|
||||
EVLOG_warning << "Could not enumerate charging profiles for connector " << connector_id << ": "
|
||||
<< this->database->get_error_message();
|
||||
}
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
334
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v16/known_keys.cpp
Normal file
334
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v16/known_keys.cpp
Normal file
@@ -0,0 +1,334 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "ocpp/v2/ocpp_enums.hpp"
|
||||
#include <everest/logging.hpp>
|
||||
#include <ocpp/v16/known_keys.hpp>
|
||||
#include <ocpp/v2/ctrlr_component_variables.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
namespace {
|
||||
using ocpp::v16::keys::valid_keys;
|
||||
|
||||
// clang-format off
|
||||
#define FOR_ALL_READONLY(key) \
|
||||
key(CentralSystemURI) \
|
||||
key(ChargePointId) \
|
||||
key(ChargeBoxSerialNumber) \
|
||||
key(ChargePointModel) \
|
||||
key(ChargePointSerialNumber) \
|
||||
key(ChargePointVendor) \
|
||||
key(FirmwareVersion) \
|
||||
key(ICCID) \
|
||||
key(IMSI) \
|
||||
key(MeterSerialNumber) \
|
||||
key(MeterType) \
|
||||
key(AuthorizeConnectorZeroOnConnectorOne) \
|
||||
key(LogMessages) \
|
||||
key(LogMessagesRaw) \
|
||||
key(LogMessagesFormat) \
|
||||
key(LogRotation) \
|
||||
key(LogRotationDateSuffix) \
|
||||
key(LogRotationMaximumFileCount) \
|
||||
key(LogRotationMaximumFileSize) \
|
||||
key(SupportedChargingProfilePurposeTypes) \
|
||||
key(MaxCompositeScheduleDuration) \
|
||||
key(SupportedCiphers12) \
|
||||
key(SupportedCiphers13) \
|
||||
key(UseSslDefaultVerifyPaths) \
|
||||
key(VerifyCsmsCommonName) \
|
||||
key(VerifyCsmsAllowWildcards) \
|
||||
key(SupportedMeasurands) \
|
||||
key(MaxMessageSize) \
|
||||
key(WebsocketPingPayload) \
|
||||
key(WebsocketPongTimeout) \
|
||||
key(QueueAllMessages) \
|
||||
key(MessageTypesDiscardForQueueing) \
|
||||
key(MessageQueueSizeThreshold) \
|
||||
key(ConnectorPhaseRotationMaxLength) \
|
||||
key(GetConfigurationMaxKeys) \
|
||||
key(MeterValuesAlignedDataMaxLength) \
|
||||
key(MeterValuesSampledDataMaxLength) \
|
||||
key(NumberOfConnectors) \
|
||||
key(ReserveConnectorZeroSupported) \
|
||||
key(StopTransactionOnEVSideDisconnect) \
|
||||
key(StopTxnAlignedDataMaxLength) \
|
||||
key(StopTxnSampledDataMaxLength) \
|
||||
key(SupportedFeatureProfiles) \
|
||||
key(SupportedFeatureProfilesMaxLength) \
|
||||
key(HostName) \
|
||||
key(IFace) \
|
||||
key(SupportedFileTransferProtocols) \
|
||||
key(ChargeProfileMaxStackLevel) \
|
||||
key(ChargingScheduleAllowedChargingRateUnit) \
|
||||
key(ChargingScheduleMaxPeriods) \
|
||||
key(ConnectorSwitch3to1PhaseSupported) \
|
||||
key(MaxChargingProfilesInstalled) \
|
||||
key(AdditionalRootCertificateCheck) \
|
||||
key(CertificateSignedMaxChainSize) \
|
||||
key(CertificateStoreMaxLength) \
|
||||
key(LocalAuthListMaxLength) \
|
||||
key(SendLocalListMaxLength) \
|
||||
key(CustomDisplayCostAndPrice) \
|
||||
key(NumberOfDecimalsForCostValues) \
|
||||
key(CustomMultiLanguageMessages) \
|
||||
key(SupportedLanguages) \
|
||||
key(Language) \
|
||||
key(EnableTLSKeylog) \
|
||||
key(TLSKeylogFile) \
|
||||
key(UseTPM) \
|
||||
key(UseTPMSeccLeafCertificate)
|
||||
|
||||
// Hidden keys are ones that are not made available over OCPP
|
||||
// AuthorizationKey because it contains the connection secret
|
||||
|
||||
#define FOR_ALL_HIDDEN(key) \
|
||||
key(AuthorizationKey)
|
||||
|
||||
// clang-format on
|
||||
|
||||
#define VALUE(a) valid_keys::a,
|
||||
|
||||
constexpr valid_keys read_only[] = {FOR_ALL_READONLY(VALUE)};
|
||||
constexpr valid_keys hidden[] = {FOR_ALL_HIDDEN(VALUE)};
|
||||
|
||||
#undef VALUE
|
||||
|
||||
constexpr char convert(ocpp::v2::AttributeEnum attribute) {
|
||||
char result{'.'};
|
||||
switch (attribute) {
|
||||
case ocpp::v2::AttributeEnum::Actual:
|
||||
result = 'A';
|
||||
break;
|
||||
case ocpp::v2::AttributeEnum::MaxSet:
|
||||
result = '+';
|
||||
break;
|
||||
case ocpp::v2::AttributeEnum::MinSet:
|
||||
result = '-';
|
||||
break;
|
||||
case ocpp::v2::AttributeEnum::Target:
|
||||
result = '=';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::string mapping_name(const ocpp::v2::Component& component, const ocpp::v2::Variable& variable,
|
||||
ocpp::v2::AttributeEnum attribute) {
|
||||
return std::string{component.name} + std::string{variable.name} + std::string{variable.instance.value_or("")} +
|
||||
convert(attribute);
|
||||
}
|
||||
|
||||
class V2ConfigMap {
|
||||
private:
|
||||
bool configured{false};
|
||||
using v2details = std::pair<const ocpp::v2::ComponentVariable*, ocpp::v2::AttributeEnum>;
|
||||
|
||||
std::map<std::string_view, v2details> map;
|
||||
std::map<std::string, std::string_view> reverse_map;
|
||||
|
||||
void configure(const std::string_view& v16, const ocpp::v2::ComponentVariable& cv,
|
||||
ocpp::v2::AttributeEnum attribute);
|
||||
void configure();
|
||||
void warn_no_mapping(const std::string_view& v16);
|
||||
void check();
|
||||
|
||||
public:
|
||||
ocpp::v16::keys::DeviceModel_CV convert_v2(const std::string_view& v16_key);
|
||||
std::optional<std::string> convert_v2(const ocpp::v2::Component& component, const ocpp::v2::Variable& variable,
|
||||
ocpp::v2::AttributeEnum attribute);
|
||||
};
|
||||
|
||||
void V2ConfigMap::configure(const std::string_view& v16, const ocpp::v2::ComponentVariable& cv,
|
||||
ocpp::v2::AttributeEnum attribute) {
|
||||
if (cv.variable) {
|
||||
if (const auto it = map.find(v16); it != map.end()) {
|
||||
const auto& tmp_cv = std::get<const ocpp::v2::ComponentVariable*>(it->second);
|
||||
if (tmp_cv->variable) {
|
||||
EVLOG_warning << "V16 " << v16 << ": '" << tmp_cv->variable->name << "' replaced with '"
|
||||
<< cv.variable->name << '\'';
|
||||
}
|
||||
}
|
||||
map.insert_or_assign(v16, v2details{&cv, attribute});
|
||||
std::string name = mapping_name(cv.component, cv.variable.value(), attribute);
|
||||
if (const auto it = reverse_map.find(name); it != reverse_map.end()) {
|
||||
EVLOG_error << "V2 " << name << ": '" << it->second << "' replaced with '" << v16 << '\'';
|
||||
}
|
||||
reverse_map.insert_or_assign(std::move(name), v16);
|
||||
}
|
||||
}
|
||||
|
||||
#define VALUE(a, b, c) configure(#a, ocpp::v2::ControllerComponentVariables::b, ocpp::v2::AttributeEnum::c);
|
||||
void V2ConfigMap::configure() {
|
||||
if (!configured) {
|
||||
configured = true;
|
||||
MAPPING_ALL(VALUE)
|
||||
check();
|
||||
}
|
||||
}
|
||||
#undef VALUE
|
||||
|
||||
void V2ConfigMap::warn_no_mapping(const std::string_view& v16) {
|
||||
// Keys in MAPPING_MAX_LIMIT map to VariableCharacteristics.maxLimit, not to a VariableAttribute,
|
||||
// so they intentionally have no entry in the attribute map.
|
||||
#define VALUE(a, b) \
|
||||
if (v16 == #a) \
|
||||
return;
|
||||
MAPPING_MAX_LIMIT(VALUE)
|
||||
#undef VALUE
|
||||
const auto it = map.find(v16);
|
||||
if (it == map.end()) {
|
||||
EVLOG_error << "No V2 mapping for " << v16;
|
||||
}
|
||||
}
|
||||
|
||||
#define VALUE(a, b) warn_no_mapping(#b);
|
||||
void V2ConfigMap::check() {
|
||||
if (configured) {
|
||||
FOR_ALL_MAPPED_KEYS(VALUE);
|
||||
}
|
||||
}
|
||||
#undef VALUE
|
||||
|
||||
ocpp::v16::keys::DeviceModel_CV V2ConfigMap::convert_v2(const std::string_view& v16_key) {
|
||||
configure();
|
||||
ocpp::v16::keys::DeviceModel_CV result;
|
||||
std::string name{v16_key};
|
||||
if (const auto it = map.find(name); it != map.end()) {
|
||||
const auto& cv = std::get<const ocpp::v2::ComponentVariable*>(it->second);
|
||||
const auto attribute = std::get<ocpp::v2::AttributeEnum>(it->second);
|
||||
if (cv->variable) {
|
||||
result = std::make_tuple(cv->component, cv->variable.value(), attribute);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<std::string> V2ConfigMap::convert_v2(const ocpp::v2::Component& component,
|
||||
const ocpp::v2::Variable& variable,
|
||||
ocpp::v2::AttributeEnum attribute) {
|
||||
configure();
|
||||
std::optional<std::string> result;
|
||||
std::string name = mapping_name(component, variable, attribute);
|
||||
if (const auto it = reverse_map.find(name); it != reverse_map.end()) {
|
||||
result = it->second;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
V2ConfigMap v2_map;
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace ocpp::v16::keys {
|
||||
|
||||
#define VALUE(a, b) \
|
||||
if (str == #b) { \
|
||||
return valid_keys::b; \
|
||||
}
|
||||
|
||||
std::optional<valid_keys> convert(const std::string_view& str) {
|
||||
FOR_ALL_KEYS(VALUE)
|
||||
return {};
|
||||
}
|
||||
|
||||
#undef VALUE
|
||||
#define VALUE(a, b) \
|
||||
case valid_keys::b: \
|
||||
return #b;
|
||||
|
||||
std::string_view convert(valid_keys key) {
|
||||
switch (key) {
|
||||
FOR_ALL_KEYS(VALUE)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
#undef VALUE
|
||||
#define VALUE(a, b) \
|
||||
case valid_keys::b: \
|
||||
return sections::a;
|
||||
|
||||
sections to_section(valid_keys key) {
|
||||
switch (key) {
|
||||
FOR_ALL_KEYS(VALUE)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return sections::Custom;
|
||||
}
|
||||
|
||||
#undef VALUE
|
||||
#define VALUE(a, b) \
|
||||
case valid_keys::b: \
|
||||
return #a;
|
||||
|
||||
std::string_view to_section_string_view(valid_keys key) {
|
||||
switch (key) {
|
||||
FOR_ALL_KEYS(VALUE)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
#undef VALUE
|
||||
|
||||
bool is_readonly(valid_keys key) {
|
||||
return std::find(std::cbegin(read_only), std::cend(read_only), key) != std::cend(read_only);
|
||||
}
|
||||
|
||||
bool is_hidden(valid_keys key) {
|
||||
return std::find(std::cbegin(hidden), std::cend(hidden), key) != std::cend(hidden);
|
||||
}
|
||||
|
||||
#undef VALUE
|
||||
#define VALUE(a, b) \
|
||||
case valid_keys::b: \
|
||||
return ocpp::v16::SupportedFeatureProfiles::a;
|
||||
|
||||
std::optional<ocpp::v16::SupportedFeatureProfiles> get_profile(valid_keys key) {
|
||||
switch (key) {
|
||||
FOR_ALL_KEYS(VALUE)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
#undef VALUE
|
||||
|
||||
DeviceModel_CV convert_v2(const std::string_view& str) {
|
||||
return v2_map.convert_v2(str);
|
||||
}
|
||||
|
||||
DeviceModel_CV convert_v2(valid_keys key) {
|
||||
return convert_v2(convert(key));
|
||||
}
|
||||
|
||||
std::optional<std::string> convert_v2(const ocpp::v2::Component& component, const ocpp::v2::Variable& variable,
|
||||
ocpp::v2::AttributeEnum attribute) {
|
||||
return v2_map.convert_v2(component, variable, attribute);
|
||||
}
|
||||
|
||||
DeviceModel_MaxLimitCV convert_v2_max_limit(valid_keys key) {
|
||||
for (const auto& [k, cv] : max_limit_entries) {
|
||||
if (k == key && cv->variable) {
|
||||
return std::make_pair(cv->component, cv->variable.value());
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace ocpp::v16::keys
|
||||
@@ -0,0 +1,59 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <ocpp/v16/message_dispatcher.hpp>
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
void MessageDispatcher::dispatch_call(const json& call, bool triggered) {
|
||||
const auto message_type = conversions::string_to_messagetype(call.at(CALL_ACTION));
|
||||
const auto message_transmission_priority = get_message_transmission_priority(
|
||||
is_boot_notification_message(message_type), triggered,
|
||||
(this->registration_status == RegistrationStatus::Accepted), is_transaction_message(message_type),
|
||||
this->configuration.getQueueAllMessages().value_or(false));
|
||||
switch (message_transmission_priority) {
|
||||
case MessageTransmissionPriority::SendImmediately:
|
||||
this->message_queue.push_call(call);
|
||||
return;
|
||||
case MessageTransmissionPriority::SendAfterRegistrationStatusAccepted:
|
||||
this->message_queue.push_call(call, true);
|
||||
return;
|
||||
case MessageTransmissionPriority::Discard:
|
||||
return;
|
||||
}
|
||||
throw std::runtime_error("Missing handling for MessageTransmissionPriority");
|
||||
}
|
||||
|
||||
std::future<ocpp::EnhancedMessage<MessageType>> MessageDispatcher::dispatch_call_async(const json& call,
|
||||
bool triggered) {
|
||||
const auto message_type = conversions::string_to_messagetype(call.at(CALL_ACTION));
|
||||
const auto message_transmission_priority = get_message_transmission_priority(
|
||||
is_boot_notification_message(message_type), triggered,
|
||||
(this->registration_status == RegistrationStatus::Accepted), is_transaction_message(message_type),
|
||||
this->configuration.getQueueAllMessages().value_or(false));
|
||||
|
||||
switch (message_transmission_priority) {
|
||||
case MessageTransmissionPriority::SendImmediately:
|
||||
return this->message_queue.push_call_async(call);
|
||||
case MessageTransmissionPriority::SendAfterRegistrationStatusAccepted:
|
||||
case MessageTransmissionPriority::Discard:
|
||||
auto promise = std::promise<EnhancedMessage<MessageType>>();
|
||||
auto enhanced_message = EnhancedMessage<MessageType>();
|
||||
enhanced_message.offline = true;
|
||||
promise.set_value(enhanced_message);
|
||||
return promise.get_future();
|
||||
}
|
||||
throw std::runtime_error("Missing handling for MessageTransmissionPriority");
|
||||
}
|
||||
|
||||
void MessageDispatcher::dispatch_call_result(const json& call_result) {
|
||||
this->message_queue.push_call_result(call_result);
|
||||
}
|
||||
|
||||
void MessageDispatcher::dispatch_call_error(const json& call_error) {
|
||||
this->message_queue.push_call_error(call_error);
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <ocpp/common/message_queue.hpp>
|
||||
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
namespace ocpp {
|
||||
|
||||
template <>
|
||||
ControlMessage<v16::MessageType>::ControlMessage(const json& message, const bool stall_until_accepted) :
|
||||
message(message.get<json::array_t>()),
|
||||
messageType(v16::conversions::string_to_messagetype(message.at(CALL_ACTION))),
|
||||
message_attempts(0),
|
||||
initial_unique_id(message[MESSAGE_ID]),
|
||||
stall_until_accepted(stall_until_accepted) {
|
||||
}
|
||||
|
||||
bool is_transaction_message(const ocpp::v16::MessageType message_type) {
|
||||
return (message_type == v16::MessageType::StartTransaction) ||
|
||||
(message_type == v16::MessageType::StopTransaction) || (message_type == v16::MessageType::MeterValues) ||
|
||||
(message_type == v16::MessageType::SecurityEventNotification);
|
||||
}
|
||||
|
||||
bool is_start_transaction_message(const ocpp::v16::MessageType message_type) {
|
||||
return message_type == v16::MessageType::StartTransaction;
|
||||
}
|
||||
|
||||
bool is_boot_notification_message(const ocpp::v16::MessageType message_type) {
|
||||
return message_type == ocpp::v16::MessageType::BootNotification;
|
||||
}
|
||||
|
||||
template <> bool ControlMessage<v16::MessageType>::is_transaction_update_message() const {
|
||||
return (this->messageType == v16::MessageType::MeterValues);
|
||||
}
|
||||
|
||||
template <> v16::MessageType MessageQueue<v16::MessageType>::string_to_messagetype(const std::string& s) {
|
||||
return v16::conversions::string_to_messagetype(s);
|
||||
}
|
||||
|
||||
template <> std::string MessageQueue<v16::MessageType>::messagetype_to_string(v16::MessageType m) {
|
||||
return v16::conversions::messagetype_to_string(m);
|
||||
}
|
||||
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,69 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/Authorize.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string AuthorizeRequest::get_type() const {
|
||||
return "Authorize";
|
||||
}
|
||||
|
||||
void to_json(json& j, const AuthorizeRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"idTag", k.idTag},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, AuthorizeRequest& k) {
|
||||
// the required parts of the message
|
||||
k.idTag = j.at("idTag");
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given AuthorizeRequest \p k to the given output stream \p os
|
||||
/// \returns an output stream with the AuthorizeRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const AuthorizeRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string AuthorizeResponse::get_type() const {
|
||||
return "AuthorizeResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const AuthorizeResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"idTagInfo", k.idTagInfo},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, AuthorizeResponse& k) {
|
||||
// the required parts of the message
|
||||
k.idTagInfo = j.at("idTagInfo");
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given AuthorizeResponse \p k to the given output stream \p os
|
||||
/// \returns an output stream with the AuthorizeResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const AuthorizeResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,117 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/BootNotification.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string BootNotificationRequest::get_type() const {
|
||||
return "BootNotification";
|
||||
}
|
||||
|
||||
void to_json(json& j, const BootNotificationRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"chargePointVendor", k.chargePointVendor},
|
||||
{"chargePointModel", k.chargePointModel},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.chargePointSerialNumber) {
|
||||
j["chargePointSerialNumber"] = k.chargePointSerialNumber.value();
|
||||
}
|
||||
if (k.chargeBoxSerialNumber) {
|
||||
j["chargeBoxSerialNumber"] = k.chargeBoxSerialNumber.value();
|
||||
}
|
||||
if (k.firmwareVersion) {
|
||||
j["firmwareVersion"] = k.firmwareVersion.value();
|
||||
}
|
||||
if (k.iccid) {
|
||||
j["iccid"] = k.iccid.value();
|
||||
}
|
||||
if (k.imsi) {
|
||||
j["imsi"] = k.imsi.value();
|
||||
}
|
||||
if (k.meterType) {
|
||||
j["meterType"] = k.meterType.value();
|
||||
}
|
||||
if (k.meterSerialNumber) {
|
||||
j["meterSerialNumber"] = k.meterSerialNumber.value();
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, BootNotificationRequest& k) {
|
||||
// the required parts of the message
|
||||
k.chargePointVendor = j.at("chargePointVendor");
|
||||
k.chargePointModel = j.at("chargePointModel");
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("chargePointSerialNumber")) {
|
||||
k.chargePointSerialNumber.emplace(j.at("chargePointSerialNumber"));
|
||||
}
|
||||
if (j.contains("chargeBoxSerialNumber")) {
|
||||
k.chargeBoxSerialNumber.emplace(j.at("chargeBoxSerialNumber"));
|
||||
}
|
||||
if (j.contains("firmwareVersion")) {
|
||||
k.firmwareVersion.emplace(j.at("firmwareVersion"));
|
||||
}
|
||||
if (j.contains("iccid")) {
|
||||
k.iccid.emplace(j.at("iccid"));
|
||||
}
|
||||
if (j.contains("imsi")) {
|
||||
k.imsi.emplace(j.at("imsi"));
|
||||
}
|
||||
if (j.contains("meterType")) {
|
||||
k.meterType.emplace(j.at("meterType"));
|
||||
}
|
||||
if (j.contains("meterSerialNumber")) {
|
||||
k.meterSerialNumber.emplace(j.at("meterSerialNumber"));
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given BootNotificationRequest \p k to the given output stream \p os
|
||||
/// \returns an output stream with the BootNotificationRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const BootNotificationRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string BootNotificationResponse::get_type() const {
|
||||
return "BootNotificationResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const BootNotificationResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::registration_status_to_string(k.status)},
|
||||
{"currentTime", k.currentTime.to_rfc3339()},
|
||||
{"interval", k.interval},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, BootNotificationResponse& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_registration_status(j.at("status"));
|
||||
k.currentTime = ocpp::DateTime(std::string(j.at("currentTime")));
|
||||
k.interval = j.at("interval");
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given BootNotificationResponse \p k to the given output stream \p os
|
||||
/// \returns an output stream with the BootNotificationResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const BootNotificationResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,43 @@
|
||||
|
||||
target_sources(ocpp
|
||||
PRIVATE
|
||||
Authorize.cpp
|
||||
BootNotification.cpp
|
||||
CancelReservation.cpp
|
||||
CertificateSigned.cpp
|
||||
ChangeAvailability.cpp
|
||||
ChangeConfiguration.cpp
|
||||
ClearCache.cpp
|
||||
ClearChargingProfile.cpp
|
||||
DataTransfer.cpp
|
||||
DeleteCertificate.cpp
|
||||
DiagnosticsStatusNotification.cpp
|
||||
ExtendedTriggerMessage.cpp
|
||||
FirmwareStatusNotification.cpp
|
||||
GetCompositeSchedule.cpp
|
||||
GetConfiguration.cpp
|
||||
GetDiagnostics.cpp
|
||||
GetInstalledCertificateIds.cpp
|
||||
GetLocalListVersion.cpp
|
||||
GetLog.cpp
|
||||
Heartbeat.cpp
|
||||
InstallCertificate.cpp
|
||||
LogStatusNotification.cpp
|
||||
MeterValues.cpp
|
||||
RemoteStartTransaction.cpp
|
||||
RemoteStopTransaction.cpp
|
||||
ReserveNow.cpp
|
||||
Reset.cpp
|
||||
SecurityEventNotification.cpp
|
||||
SendLocalList.cpp
|
||||
SetChargingProfile.cpp
|
||||
SignCertificate.cpp
|
||||
SignedFirmwareStatusNotification.cpp
|
||||
SignedUpdateFirmware.cpp
|
||||
StartTransaction.cpp
|
||||
StatusNotification.cpp
|
||||
StopTransaction.cpp
|
||||
TriggerMessage.cpp
|
||||
UnlockConnector.cpp
|
||||
UpdateFirmware.cpp
|
||||
)
|
||||
@@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/CancelReservation.hpp>
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string CancelReservationRequest::get_type() const {
|
||||
return "CancelReservation";
|
||||
}
|
||||
|
||||
void to_json(json& j, const CancelReservationRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"reservationId", k.reservationId},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, CancelReservationRequest& k) {
|
||||
// the required parts of the message
|
||||
k.reservationId = j.at("reservationId");
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given CancelReservationRequest \p k to the given output stream \p os
|
||||
/// \returns an output stream with the CancelReservationRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const CancelReservationRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string CancelReservationResponse::get_type() const {
|
||||
return "CancelReservationResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const CancelReservationResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::cancel_reservation_status_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, CancelReservationResponse& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_cancel_reservation_status(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given CancelReservationResponse \p k to the given output stream \p os
|
||||
/// \returns an output stream with the CancelReservationResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const CancelReservationResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/CertificateSigned.hpp>
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string CertificateSignedRequest::get_type() const {
|
||||
return "CertificateSigned";
|
||||
}
|
||||
|
||||
void to_json(json& j, const CertificateSignedRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"certificateChain", k.certificateChain},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, CertificateSignedRequest& k) {
|
||||
// the required parts of the message
|
||||
k.certificateChain = j.at("certificateChain");
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given CertificateSignedRequest \p k to the given output stream \p os
|
||||
/// \returns an output stream with the CertificateSignedRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const CertificateSignedRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string CertificateSignedResponse::get_type() const {
|
||||
return "CertificateSignedResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const CertificateSignedResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::certificate_signed_status_enum_type_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, CertificateSignedResponse& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_certificate_signed_status_enum_type(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given CertificateSignedResponse \p k to the given output stream \p os
|
||||
/// \returns an output stream with the CertificateSignedResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const CertificateSignedResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,71 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/ChangeAvailability.hpp>
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string ChangeAvailabilityRequest::get_type() const {
|
||||
return "ChangeAvailability";
|
||||
}
|
||||
|
||||
void to_json(json& j, const ChangeAvailabilityRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"connectorId", k.connectorId},
|
||||
{"type", conversions::availability_type_to_string(k.type)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, ChangeAvailabilityRequest& k) {
|
||||
// the required parts of the message
|
||||
k.connectorId = j.at("connectorId");
|
||||
k.type = conversions::string_to_availability_type(j.at("type"));
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given ChangeAvailabilityRequest \p k to the given output stream \p os
|
||||
/// \returns an output stream with the ChangeAvailabilityRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const ChangeAvailabilityRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string ChangeAvailabilityResponse::get_type() const {
|
||||
return "ChangeAvailabilityResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const ChangeAvailabilityResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::availability_status_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, ChangeAvailabilityResponse& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_availability_status(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given ChangeAvailabilityResponse \p k to the given output stream \p
|
||||
/// os
|
||||
/// \returns an output stream with the ChangeAvailabilityResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const ChangeAvailabilityResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,72 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/ChangeConfiguration.hpp>
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string ChangeConfigurationRequest::get_type() const {
|
||||
return "ChangeConfiguration";
|
||||
}
|
||||
|
||||
void to_json(json& j, const ChangeConfigurationRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"key", k.key},
|
||||
{"value", k.value},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, ChangeConfigurationRequest& k) {
|
||||
// the required parts of the message
|
||||
k.key = j.at("key");
|
||||
k.value = j.at("value");
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given ChangeConfigurationRequest \p k to the given output stream \p
|
||||
/// os
|
||||
/// \returns an output stream with the ChangeConfigurationRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const ChangeConfigurationRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string ChangeConfigurationResponse::get_type() const {
|
||||
return "ChangeConfigurationResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const ChangeConfigurationResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::configuration_status_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, ChangeConfigurationResponse& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_configuration_status(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given ChangeConfigurationResponse \p k to the given output stream \p
|
||||
/// os
|
||||
/// \returns an output stream with the ChangeConfigurationResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const ChangeConfigurationResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,69 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/ClearCache.hpp>
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string ClearCacheRequest::get_type() const {
|
||||
return "ClearCache";
|
||||
}
|
||||
|
||||
void to_json(json& j, const ClearCacheRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json({}, true);
|
||||
// the optional parts of the message
|
||||
(void)k; // no elements to unpack, silence unused parameter warning
|
||||
}
|
||||
|
||||
void from_json(const json& j, ClearCacheRequest& k) {
|
||||
// the required parts of the message
|
||||
|
||||
// the optional parts of the message
|
||||
// no elements to unpack, silence unused parameter warning
|
||||
(void)j;
|
||||
(void)k;
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given ClearCacheRequest \p k to the given output stream \p os
|
||||
/// \returns an output stream with the ClearCacheRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const ClearCacheRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string ClearCacheResponse::get_type() const {
|
||||
return "ClearCacheResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const ClearCacheResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::clear_cache_status_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, ClearCacheResponse& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_clear_cache_status(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given ClearCacheResponse \p k to the given output stream \p os
|
||||
/// \returns an output stream with the ClearCacheResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const ClearCacheResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,94 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/ClearChargingProfile.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string ClearChargingProfileRequest::get_type() const {
|
||||
return "ClearChargingProfile";
|
||||
}
|
||||
|
||||
void to_json(json& j, const ClearChargingProfileRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json({}, true);
|
||||
// the optional parts of the message
|
||||
if (k.id) {
|
||||
j["id"] = k.id.value();
|
||||
}
|
||||
if (k.connectorId) {
|
||||
j["connectorId"] = k.connectorId.value();
|
||||
}
|
||||
if (k.chargingProfilePurpose) {
|
||||
j["chargingProfilePurpose"] =
|
||||
conversions::charging_profile_purpose_type_to_string(k.chargingProfilePurpose.value());
|
||||
}
|
||||
if (k.stackLevel) {
|
||||
j["stackLevel"] = k.stackLevel.value();
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, ClearChargingProfileRequest& k) {
|
||||
// the required parts of the message
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("id")) {
|
||||
k.id.emplace(j.at("id"));
|
||||
}
|
||||
if (j.contains("connectorId")) {
|
||||
k.connectorId.emplace(j.at("connectorId"));
|
||||
}
|
||||
if (j.contains("chargingProfilePurpose")) {
|
||||
k.chargingProfilePurpose.emplace(
|
||||
conversions::string_to_charging_profile_purpose_type(j.at("chargingProfilePurpose")));
|
||||
}
|
||||
if (j.contains("stackLevel")) {
|
||||
k.stackLevel.emplace(j.at("stackLevel"));
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given ClearChargingProfileRequest \p k to the given output stream \p
|
||||
/// os
|
||||
/// \returns an output stream with the ClearChargingProfileRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const ClearChargingProfileRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string ClearChargingProfileResponse::get_type() const {
|
||||
return "ClearChargingProfileResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const ClearChargingProfileResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::clear_charging_profile_status_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, ClearChargingProfileResponse& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_clear_charging_profile_status(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given ClearChargingProfileResponse \p k to the given output stream \p
|
||||
/// os
|
||||
/// \returns an output stream with the ClearChargingProfileResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const ClearChargingProfileResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,87 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/DataTransfer.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string DataTransferRequest::get_type() const {
|
||||
return "DataTransfer";
|
||||
}
|
||||
|
||||
void to_json(json& j, const DataTransferRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"vendorId", k.vendorId},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.messageId) {
|
||||
j["messageId"] = k.messageId.value();
|
||||
}
|
||||
if (k.data) {
|
||||
j["data"] = k.data.value();
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, DataTransferRequest& k) {
|
||||
// the required parts of the message
|
||||
k.vendorId = j.at("vendorId");
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("messageId")) {
|
||||
k.messageId.emplace(j.at("messageId"));
|
||||
}
|
||||
if (j.contains("data")) {
|
||||
k.data.emplace(j.at("data"));
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given DataTransferRequest \p k to the given output stream \p os
|
||||
/// \returns an output stream with the DataTransferRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const DataTransferRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string DataTransferResponse::get_type() const {
|
||||
return "DataTransferResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const DataTransferResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::data_transfer_status_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.data) {
|
||||
j["data"] = k.data.value();
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, DataTransferResponse& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_data_transfer_status(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("data")) {
|
||||
k.data.emplace(j.at("data"));
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given DataTransferResponse \p k to the given output stream \p os
|
||||
/// \returns an output stream with the DataTransferResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const DataTransferResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/DeleteCertificate.hpp>
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string DeleteCertificateRequest::get_type() const {
|
||||
return "DeleteCertificate";
|
||||
}
|
||||
|
||||
void to_json(json& j, const DeleteCertificateRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"certificateHashData", k.certificateHashData},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, DeleteCertificateRequest& k) {
|
||||
// the required parts of the message
|
||||
k.certificateHashData = j.at("certificateHashData");
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given DeleteCertificateRequest \p k to the given output stream \p os
|
||||
/// \returns an output stream with the DeleteCertificateRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const DeleteCertificateRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string DeleteCertificateResponse::get_type() const {
|
||||
return "DeleteCertificateResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const DeleteCertificateResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::delete_certificate_status_enum_type_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, DeleteCertificateResponse& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_delete_certificate_status_enum_type(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given DeleteCertificateResponse \p k to the given output stream \p os
|
||||
/// \returns an output stream with the DeleteCertificateResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const DeleteCertificateResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,71 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/DiagnosticsStatusNotification.hpp>
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string DiagnosticsStatusNotificationRequest::get_type() const {
|
||||
return "DiagnosticsStatusNotification";
|
||||
}
|
||||
|
||||
void to_json(json& j, const DiagnosticsStatusNotificationRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::diagnostics_status_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, DiagnosticsStatusNotificationRequest& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_diagnostics_status(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given DiagnosticsStatusNotificationRequest \p k to the given output
|
||||
/// stream \p os
|
||||
/// \returns an output stream with the DiagnosticsStatusNotificationRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const DiagnosticsStatusNotificationRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string DiagnosticsStatusNotificationResponse::get_type() const {
|
||||
return "DiagnosticsStatusNotificationResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const DiagnosticsStatusNotificationResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json({}, true);
|
||||
// the optional parts of the message
|
||||
(void)k; // no elements to unpack, silence unused parameter warning
|
||||
}
|
||||
|
||||
void from_json(const json& j, DiagnosticsStatusNotificationResponse& k) {
|
||||
// the required parts of the message
|
||||
|
||||
// the optional parts of the message
|
||||
// no elements to unpack, silence unused parameter warning
|
||||
(void)j;
|
||||
(void)k;
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given DiagnosticsStatusNotificationResponse \p k to the given output
|
||||
/// stream \p os
|
||||
/// \returns an output stream with the DiagnosticsStatusNotificationResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const DiagnosticsStatusNotificationResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,77 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/ExtendedTriggerMessage.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string ExtendedTriggerMessageRequest::get_type() const {
|
||||
return "ExtendedTriggerMessage";
|
||||
}
|
||||
|
||||
void to_json(json& j, const ExtendedTriggerMessageRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"requestedMessage", conversions::message_trigger_enum_type_to_string(k.requestedMessage)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.connectorId) {
|
||||
j["connectorId"] = k.connectorId.value();
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, ExtendedTriggerMessageRequest& k) {
|
||||
// the required parts of the message
|
||||
k.requestedMessage = conversions::string_to_message_trigger_enum_type(j.at("requestedMessage"));
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("connectorId")) {
|
||||
k.connectorId.emplace(j.at("connectorId"));
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given ExtendedTriggerMessageRequest \p k to the given output stream
|
||||
/// \p os
|
||||
/// \returns an output stream with the ExtendedTriggerMessageRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const ExtendedTriggerMessageRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string ExtendedTriggerMessageResponse::get_type() const {
|
||||
return "ExtendedTriggerMessageResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const ExtendedTriggerMessageResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::trigger_message_status_enum_type_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, ExtendedTriggerMessageResponse& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_trigger_message_status_enum_type(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given ExtendedTriggerMessageResponse \p k to the given output stream
|
||||
/// \p os
|
||||
/// \returns an output stream with the ExtendedTriggerMessageResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const ExtendedTriggerMessageResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,71 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/FirmwareStatusNotification.hpp>
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string FirmwareStatusNotificationRequest::get_type() const {
|
||||
return "FirmwareStatusNotification";
|
||||
}
|
||||
|
||||
void to_json(json& j, const FirmwareStatusNotificationRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::firmware_status_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, FirmwareStatusNotificationRequest& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_firmware_status(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given FirmwareStatusNotificationRequest \p k to the given output
|
||||
/// stream \p os
|
||||
/// \returns an output stream with the FirmwareStatusNotificationRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const FirmwareStatusNotificationRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string FirmwareStatusNotificationResponse::get_type() const {
|
||||
return "FirmwareStatusNotificationResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const FirmwareStatusNotificationResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json({}, true);
|
||||
// the optional parts of the message
|
||||
(void)k; // no elements to unpack, silence unused parameter warning
|
||||
}
|
||||
|
||||
void from_json(const json& j, FirmwareStatusNotificationResponse& k) {
|
||||
// the required parts of the message
|
||||
|
||||
// the optional parts of the message
|
||||
// no elements to unpack, silence unused parameter warning
|
||||
(void)j;
|
||||
(void)k;
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given FirmwareStatusNotificationResponse \p k to the given output
|
||||
/// stream \p os
|
||||
/// \returns an output stream with the FirmwareStatusNotificationResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const FirmwareStatusNotificationResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,97 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/GetCompositeSchedule.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string GetCompositeScheduleRequest::get_type() const {
|
||||
return "GetCompositeSchedule";
|
||||
}
|
||||
|
||||
void to_json(json& j, const GetCompositeScheduleRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"connectorId", k.connectorId},
|
||||
{"duration", k.duration},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.chargingRateUnit) {
|
||||
j["chargingRateUnit"] = conversions::charging_rate_unit_to_string(k.chargingRateUnit.value());
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, GetCompositeScheduleRequest& k) {
|
||||
// the required parts of the message
|
||||
k.connectorId = j.at("connectorId");
|
||||
k.duration = j.at("duration");
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("chargingRateUnit")) {
|
||||
k.chargingRateUnit.emplace(conversions::string_to_charging_rate_unit(j.at("chargingRateUnit")));
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given GetCompositeScheduleRequest \p k to the given output stream \p
|
||||
/// os
|
||||
/// \returns an output stream with the GetCompositeScheduleRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const GetCompositeScheduleRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string GetCompositeScheduleResponse::get_type() const {
|
||||
return "GetCompositeScheduleResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const GetCompositeScheduleResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::get_composite_schedule_status_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.connectorId) {
|
||||
j["connectorId"] = k.connectorId.value();
|
||||
}
|
||||
if (k.scheduleStart) {
|
||||
j["scheduleStart"] = k.scheduleStart.value().to_rfc3339();
|
||||
}
|
||||
if (k.chargingSchedule) {
|
||||
j["chargingSchedule"] = k.chargingSchedule.value();
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, GetCompositeScheduleResponse& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_get_composite_schedule_status(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("connectorId")) {
|
||||
k.connectorId.emplace(j.at("connectorId"));
|
||||
}
|
||||
if (j.contains("scheduleStart")) {
|
||||
k.scheduleStart.emplace(ocpp::DateTime(std::string(j.at("scheduleStart"))));
|
||||
}
|
||||
if (j.contains("chargingSchedule")) {
|
||||
k.chargingSchedule.emplace(j.at("chargingSchedule"));
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given GetCompositeScheduleResponse \p k to the given output stream \p
|
||||
/// os
|
||||
/// \returns an output stream with the GetCompositeScheduleResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const GetCompositeScheduleResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,117 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/GetConfiguration.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string GetConfigurationRequest::get_type() const {
|
||||
return "GetConfiguration";
|
||||
}
|
||||
|
||||
void to_json(json& j, const GetConfigurationRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json({}, true);
|
||||
// the optional parts of the message
|
||||
if (k.key) {
|
||||
if (j.empty()) {
|
||||
j = json{{"key", json::array()}};
|
||||
} else {
|
||||
j["key"] = json::array();
|
||||
}
|
||||
for (const auto& val : k.key.value()) {
|
||||
j["key"].push_back(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, GetConfigurationRequest& k) {
|
||||
// the required parts of the message
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("key")) {
|
||||
const json& arr = j.at("key");
|
||||
std::vector<CiString<50>> vec;
|
||||
for (const auto& val : arr) {
|
||||
vec.push_back(val);
|
||||
}
|
||||
k.key.emplace(vec);
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given GetConfigurationRequest \p k to the given output stream \p os
|
||||
/// \returns an output stream with the GetConfigurationRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const GetConfigurationRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string GetConfigurationResponse::get_type() const {
|
||||
return "GetConfigurationResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const GetConfigurationResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json({}, true);
|
||||
// the optional parts of the message
|
||||
if (k.configurationKey) {
|
||||
if (j.empty()) {
|
||||
j = json{{"configurationKey", json::array()}};
|
||||
} else {
|
||||
j["configurationKey"] = json::array();
|
||||
}
|
||||
for (const auto& val : k.configurationKey.value()) {
|
||||
j["configurationKey"].push_back(val);
|
||||
}
|
||||
}
|
||||
if (k.unknownKey) {
|
||||
if (j.empty()) {
|
||||
j = json{{"unknownKey", json::array()}};
|
||||
} else {
|
||||
j["unknownKey"] = json::array();
|
||||
}
|
||||
for (const auto& val : k.unknownKey.value()) {
|
||||
j["unknownKey"].push_back(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, GetConfigurationResponse& k) {
|
||||
// the required parts of the message
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("configurationKey")) {
|
||||
const json& arr = j.at("configurationKey");
|
||||
std::vector<KeyValue> vec;
|
||||
for (const auto& val : arr) {
|
||||
vec.push_back(val);
|
||||
}
|
||||
k.configurationKey.emplace(vec);
|
||||
}
|
||||
if (j.contains("unknownKey")) {
|
||||
const json& arr = j.at("unknownKey");
|
||||
std::vector<CiString<50>> vec;
|
||||
for (const auto& val : arr) {
|
||||
vec.push_back(val);
|
||||
}
|
||||
k.unknownKey.emplace(vec);
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given GetConfigurationResponse \p k to the given output stream \p os
|
||||
/// \returns an output stream with the GetConfigurationResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const GetConfigurationResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,96 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/GetDiagnostics.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string GetDiagnosticsRequest::get_type() const {
|
||||
return "GetDiagnostics";
|
||||
}
|
||||
|
||||
void to_json(json& j, const GetDiagnosticsRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"location", k.location},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.retries) {
|
||||
j["retries"] = k.retries.value();
|
||||
}
|
||||
if (k.retryInterval) {
|
||||
j["retryInterval"] = k.retryInterval.value();
|
||||
}
|
||||
if (k.startTime) {
|
||||
j["startTime"] = k.startTime.value().to_rfc3339();
|
||||
}
|
||||
if (k.stopTime) {
|
||||
j["stopTime"] = k.stopTime.value().to_rfc3339();
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, GetDiagnosticsRequest& k) {
|
||||
// the required parts of the message
|
||||
k.location = j.at("location");
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("retries")) {
|
||||
k.retries.emplace(j.at("retries"));
|
||||
}
|
||||
if (j.contains("retryInterval")) {
|
||||
k.retryInterval.emplace(j.at("retryInterval"));
|
||||
}
|
||||
if (j.contains("startTime")) {
|
||||
k.startTime.emplace(ocpp::DateTime(std::string(j.at("startTime"))));
|
||||
}
|
||||
if (j.contains("stopTime")) {
|
||||
k.stopTime.emplace(ocpp::DateTime(std::string(j.at("stopTime"))));
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given GetDiagnosticsRequest \p k to the given output stream \p os
|
||||
/// \returns an output stream with the GetDiagnosticsRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const GetDiagnosticsRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string GetDiagnosticsResponse::get_type() const {
|
||||
return "GetDiagnosticsResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const GetDiagnosticsResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json({}, true);
|
||||
// the optional parts of the message
|
||||
if (k.fileName) {
|
||||
j["fileName"] = k.fileName.value();
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, GetDiagnosticsResponse& k) {
|
||||
// the required parts of the message
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("fileName")) {
|
||||
k.fileName.emplace(j.at("fileName"));
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given GetDiagnosticsResponse \p k to the given output stream \p os
|
||||
/// \returns an output stream with the GetDiagnosticsResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const GetDiagnosticsResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,85 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/GetInstalledCertificateIds.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string GetInstalledCertificateIdsRequest::get_type() const {
|
||||
return "GetInstalledCertificateIds";
|
||||
}
|
||||
|
||||
void to_json(json& j, const GetInstalledCertificateIdsRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"certificateType", conversions::certificate_use_enum_type_to_string(k.certificateType)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, GetInstalledCertificateIdsRequest& k) {
|
||||
// the required parts of the message
|
||||
k.certificateType = conversions::string_to_certificate_use_enum_type(j.at("certificateType"));
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given GetInstalledCertificateIdsRequest \p k to the given output
|
||||
/// stream \p os
|
||||
/// \returns an output stream with the GetInstalledCertificateIdsRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const GetInstalledCertificateIdsRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string GetInstalledCertificateIdsResponse::get_type() const {
|
||||
return "GetInstalledCertificateIdsResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const GetInstalledCertificateIdsResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::get_installed_certificate_status_enum_type_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.certificateHashData) {
|
||||
j["certificateHashData"] = json::array();
|
||||
for (const auto& val : k.certificateHashData.value()) {
|
||||
j["certificateHashData"].push_back(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, GetInstalledCertificateIdsResponse& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_get_installed_certificate_status_enum_type(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("certificateHashData")) {
|
||||
const json& arr = j.at("certificateHashData");
|
||||
std::vector<CertificateHashDataType> vec;
|
||||
for (const auto& val : arr) {
|
||||
vec.push_back(val);
|
||||
}
|
||||
k.certificateHashData.emplace(vec);
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given GetInstalledCertificateIdsResponse \p k to the given output
|
||||
/// stream \p os
|
||||
/// \returns an output stream with the GetInstalledCertificateIdsResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const GetInstalledCertificateIdsResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,71 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/GetLocalListVersion.hpp>
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string GetLocalListVersionRequest::get_type() const {
|
||||
return "GetLocalListVersion";
|
||||
}
|
||||
|
||||
void to_json(json& j, const GetLocalListVersionRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json({}, true);
|
||||
// the optional parts of the message
|
||||
(void)k; // no elements to unpack, silence unused parameter warning
|
||||
}
|
||||
|
||||
void from_json(const json& j, GetLocalListVersionRequest& k) {
|
||||
// the required parts of the message
|
||||
|
||||
// the optional parts of the message
|
||||
// no elements to unpack, silence unused parameter warning
|
||||
(void)j;
|
||||
(void)k;
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given GetLocalListVersionRequest \p k to the given output stream \p
|
||||
/// os
|
||||
/// \returns an output stream with the GetLocalListVersionRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const GetLocalListVersionRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string GetLocalListVersionResponse::get_type() const {
|
||||
return "GetLocalListVersionResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const GetLocalListVersionResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"listVersion", k.listVersion},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, GetLocalListVersionResponse& k) {
|
||||
// the required parts of the message
|
||||
k.listVersion = j.at("listVersion");
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given GetLocalListVersionResponse \p k to the given output stream \p
|
||||
/// os
|
||||
/// \returns an output stream with the GetLocalListVersionResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const GetLocalListVersionResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,91 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/GetLog.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string GetLogRequest::get_type() const {
|
||||
return "GetLog";
|
||||
}
|
||||
|
||||
void to_json(json& j, const GetLogRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"log", k.log},
|
||||
{"logType", conversions::log_enum_type_to_string(k.logType)},
|
||||
{"requestId", k.requestId},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.retries) {
|
||||
j["retries"] = k.retries.value();
|
||||
}
|
||||
if (k.retryInterval) {
|
||||
j["retryInterval"] = k.retryInterval.value();
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, GetLogRequest& k) {
|
||||
// the required parts of the message
|
||||
k.log = j.at("log");
|
||||
k.logType = conversions::string_to_log_enum_type(j.at("logType"));
|
||||
k.requestId = j.at("requestId");
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("retries")) {
|
||||
k.retries.emplace(j.at("retries"));
|
||||
}
|
||||
if (j.contains("retryInterval")) {
|
||||
k.retryInterval.emplace(j.at("retryInterval"));
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given GetLogRequest \p k to the given output stream \p os
|
||||
/// \returns an output stream with the GetLogRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const GetLogRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string GetLogResponse::get_type() const {
|
||||
return "GetLogResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const GetLogResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::log_status_enum_type_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.filename) {
|
||||
j["filename"] = k.filename.value();
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, GetLogResponse& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_log_status_enum_type(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("filename")) {
|
||||
k.filename.emplace(j.at("filename"));
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given GetLogResponse \p k to the given output stream \p os
|
||||
/// \returns an output stream with the GetLogResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const GetLogResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,69 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/Heartbeat.hpp>
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string HeartbeatRequest::get_type() const {
|
||||
return "Heartbeat";
|
||||
}
|
||||
|
||||
void to_json(json& j, const HeartbeatRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json({}, true);
|
||||
// the optional parts of the message
|
||||
(void)k; // no elements to unpack, silence unused parameter warning
|
||||
}
|
||||
|
||||
void from_json(const json& j, HeartbeatRequest& k) {
|
||||
// the required parts of the message
|
||||
|
||||
// the optional parts of the message
|
||||
// no elements to unpack, silence unused parameter warning
|
||||
(void)j;
|
||||
(void)k;
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given HeartbeatRequest \p k to the given output stream \p os
|
||||
/// \returns an output stream with the HeartbeatRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const HeartbeatRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string HeartbeatResponse::get_type() const {
|
||||
return "HeartbeatResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const HeartbeatResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"currentTime", k.currentTime.to_rfc3339()},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, HeartbeatResponse& k) {
|
||||
// the required parts of the message
|
||||
k.currentTime = ocpp::DateTime(std::string(j.at("currentTime")));
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given HeartbeatResponse \p k to the given output stream \p os
|
||||
/// \returns an output stream with the HeartbeatResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const HeartbeatResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,71 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/InstallCertificate.hpp>
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string InstallCertificateRequest::get_type() const {
|
||||
return "InstallCertificate";
|
||||
}
|
||||
|
||||
void to_json(json& j, const InstallCertificateRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"certificateType", conversions::certificate_use_enum_type_to_string(k.certificateType)},
|
||||
{"certificate", k.certificate},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, InstallCertificateRequest& k) {
|
||||
// the required parts of the message
|
||||
k.certificateType = conversions::string_to_certificate_use_enum_type(j.at("certificateType"));
|
||||
k.certificate = j.at("certificate");
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given InstallCertificateRequest \p k to the given output stream \p os
|
||||
/// \returns an output stream with the InstallCertificateRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const InstallCertificateRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string InstallCertificateResponse::get_type() const {
|
||||
return "InstallCertificateResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const InstallCertificateResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::install_certificate_status_enum_type_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, InstallCertificateResponse& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_install_certificate_status_enum_type(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given InstallCertificateResponse \p k to the given output stream \p
|
||||
/// os
|
||||
/// \returns an output stream with the InstallCertificateResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const InstallCertificateResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,78 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/LogStatusNotification.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string LogStatusNotificationRequest::get_type() const {
|
||||
return "LogStatusNotification";
|
||||
}
|
||||
|
||||
void to_json(json& j, const LogStatusNotificationRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::upload_log_status_enum_type_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.requestId) {
|
||||
j["requestId"] = k.requestId.value();
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, LogStatusNotificationRequest& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_upload_log_status_enum_type(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("requestId")) {
|
||||
k.requestId.emplace(j.at("requestId"));
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given LogStatusNotificationRequest \p k to the given output stream \p
|
||||
/// os
|
||||
/// \returns an output stream with the LogStatusNotificationRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const LogStatusNotificationRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string LogStatusNotificationResponse::get_type() const {
|
||||
return "LogStatusNotificationResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const LogStatusNotificationResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json({}, true);
|
||||
// the optional parts of the message
|
||||
(void)k; // no elements to unpack, silence unused parameter warning
|
||||
}
|
||||
|
||||
void from_json(const json& j, LogStatusNotificationResponse& k) {
|
||||
// the required parts of the message
|
||||
|
||||
// the optional parts of the message
|
||||
// no elements to unpack, silence unused parameter warning
|
||||
(void)j;
|
||||
(void)k;
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given LogStatusNotificationResponse \p k to the given output stream
|
||||
/// \p os
|
||||
/// \returns an output stream with the LogStatusNotificationResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const LogStatusNotificationResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,80 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/MeterValues.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string MeterValuesRequest::get_type() const {
|
||||
return "MeterValues";
|
||||
}
|
||||
|
||||
void to_json(json& j, const MeterValuesRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"connectorId", k.connectorId},
|
||||
{"meterValue", k.meterValue},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.transactionId) {
|
||||
j["transactionId"] = k.transactionId.value();
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, MeterValuesRequest& k) {
|
||||
// the required parts of the message
|
||||
k.connectorId = j.at("connectorId");
|
||||
for (const auto& val : j.at("meterValue")) {
|
||||
k.meterValue.push_back(val);
|
||||
}
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("transactionId")) {
|
||||
k.transactionId.emplace(j.at("transactionId"));
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given MeterValuesRequest \p k to the given output stream \p os
|
||||
/// \returns an output stream with the MeterValuesRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const MeterValuesRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string MeterValuesResponse::get_type() const {
|
||||
return "MeterValuesResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const MeterValuesResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json({}, true);
|
||||
// the optional parts of the message
|
||||
(void)k; // no elements to unpack, silence unused parameter warning
|
||||
}
|
||||
|
||||
void from_json(const json& j, MeterValuesResponse& k) {
|
||||
// the required parts of the message
|
||||
|
||||
// the optional parts of the message
|
||||
// no elements to unpack, silence unused parameter warning
|
||||
(void)j;
|
||||
(void)k;
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given MeterValuesResponse \p k to the given output stream \p os
|
||||
/// \returns an output stream with the MeterValuesResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const MeterValuesResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,83 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/RemoteStartTransaction.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string RemoteStartTransactionRequest::get_type() const {
|
||||
return "RemoteStartTransaction";
|
||||
}
|
||||
|
||||
void to_json(json& j, const RemoteStartTransactionRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"idTag", k.idTag},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.connectorId) {
|
||||
j["connectorId"] = k.connectorId.value();
|
||||
}
|
||||
if (k.chargingProfile) {
|
||||
j["chargingProfile"] = k.chargingProfile.value();
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, RemoteStartTransactionRequest& k) {
|
||||
// the required parts of the message
|
||||
k.idTag = j.at("idTag");
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("connectorId")) {
|
||||
k.connectorId.emplace(j.at("connectorId"));
|
||||
}
|
||||
if (j.contains("chargingProfile")) {
|
||||
k.chargingProfile.emplace(j.at("chargingProfile"));
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given RemoteStartTransactionRequest \p k to the given output stream
|
||||
/// \p os
|
||||
/// \returns an output stream with the RemoteStartTransactionRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const RemoteStartTransactionRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string RemoteStartTransactionResponse::get_type() const {
|
||||
return "RemoteStartTransactionResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const RemoteStartTransactionResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::remote_start_stop_status_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, RemoteStartTransactionResponse& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_remote_start_stop_status(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given RemoteStartTransactionResponse \p k to the given output stream
|
||||
/// \p os
|
||||
/// \returns an output stream with the RemoteStartTransactionResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const RemoteStartTransactionResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,70 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/RemoteStopTransaction.hpp>
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string RemoteStopTransactionRequest::get_type() const {
|
||||
return "RemoteStopTransaction";
|
||||
}
|
||||
|
||||
void to_json(json& j, const RemoteStopTransactionRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"transactionId", k.transactionId},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, RemoteStopTransactionRequest& k) {
|
||||
// the required parts of the message
|
||||
k.transactionId = j.at("transactionId");
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given RemoteStopTransactionRequest \p k to the given output stream \p
|
||||
/// os
|
||||
/// \returns an output stream with the RemoteStopTransactionRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const RemoteStopTransactionRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string RemoteStopTransactionResponse::get_type() const {
|
||||
return "RemoteStopTransactionResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const RemoteStopTransactionResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::remote_start_stop_status_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, RemoteStopTransactionResponse& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_remote_start_stop_status(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given RemoteStopTransactionResponse \p k to the given output stream
|
||||
/// \p os
|
||||
/// \returns an output stream with the RemoteStopTransactionResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const RemoteStopTransactionResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,81 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/ReserveNow.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string ReserveNowRequest::get_type() const {
|
||||
return "ReserveNow";
|
||||
}
|
||||
|
||||
void to_json(json& j, const ReserveNowRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"connectorId", k.connectorId},
|
||||
{"expiryDate", k.expiryDate.to_rfc3339()},
|
||||
{"idTag", k.idTag},
|
||||
{"reservationId", k.reservationId},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.parentIdTag) {
|
||||
j["parentIdTag"] = k.parentIdTag.value();
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, ReserveNowRequest& k) {
|
||||
// the required parts of the message
|
||||
k.connectorId = j.at("connectorId");
|
||||
k.expiryDate = ocpp::DateTime(std::string(j.at("expiryDate")));
|
||||
k.idTag = j.at("idTag");
|
||||
k.reservationId = j.at("reservationId");
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("parentIdTag")) {
|
||||
k.parentIdTag.emplace(j.at("parentIdTag"));
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given ReserveNowRequest \p k to the given output stream \p os
|
||||
/// \returns an output stream with the ReserveNowRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const ReserveNowRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string ReserveNowResponse::get_type() const {
|
||||
return "ReserveNowResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const ReserveNowResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::reservation_status_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, ReserveNowResponse& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_reservation_status(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given ReserveNowResponse \p k to the given output stream \p os
|
||||
/// \returns an output stream with the ReserveNowResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const ReserveNowResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/Reset.hpp>
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string ResetRequest::get_type() const {
|
||||
return "Reset";
|
||||
}
|
||||
|
||||
void to_json(json& j, const ResetRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"type", conversions::reset_type_to_string(k.type)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, ResetRequest& k) {
|
||||
// the required parts of the message
|
||||
k.type = conversions::string_to_reset_type(j.at("type"));
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given ResetRequest \p k to the given output stream \p os
|
||||
/// \returns an output stream with the ResetRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const ResetRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string ResetResponse::get_type() const {
|
||||
return "ResetResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const ResetResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::reset_status_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, ResetResponse& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_reset_status(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given ResetResponse \p k to the given output stream \p os
|
||||
/// \returns an output stream with the ResetResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const ResetResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,80 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/SecurityEventNotification.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string SecurityEventNotificationRequest::get_type() const {
|
||||
return "SecurityEventNotification";
|
||||
}
|
||||
|
||||
void to_json(json& j, const SecurityEventNotificationRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"type", k.type},
|
||||
{"timestamp", k.timestamp.to_rfc3339()},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.techInfo) {
|
||||
j["techInfo"] = k.techInfo.value();
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, SecurityEventNotificationRequest& k) {
|
||||
// the required parts of the message
|
||||
k.type = j.at("type");
|
||||
k.timestamp = ocpp::DateTime(std::string(j.at("timestamp")));
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("techInfo")) {
|
||||
k.techInfo.emplace(j.at("techInfo"));
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given SecurityEventNotificationRequest \p k to the given output
|
||||
/// stream \p os
|
||||
/// \returns an output stream with the SecurityEventNotificationRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const SecurityEventNotificationRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string SecurityEventNotificationResponse::get_type() const {
|
||||
return "SecurityEventNotificationResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const SecurityEventNotificationResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json({}, true);
|
||||
// the optional parts of the message
|
||||
(void)k; // no elements to unpack, silence unused parameter warning
|
||||
}
|
||||
|
||||
void from_json(const json& j, SecurityEventNotificationResponse& k) {
|
||||
// the required parts of the message
|
||||
|
||||
// the optional parts of the message
|
||||
// no elements to unpack, silence unused parameter warning
|
||||
(void)j;
|
||||
(void)k;
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given SecurityEventNotificationResponse \p k to the given output
|
||||
/// stream \p os
|
||||
/// \returns an output stream with the SecurityEventNotificationResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const SecurityEventNotificationResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,85 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/SendLocalList.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string SendLocalListRequest::get_type() const {
|
||||
return "SendLocalList";
|
||||
}
|
||||
|
||||
void to_json(json& j, const SendLocalListRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"listVersion", k.listVersion},
|
||||
{"updateType", conversions::update_type_to_string(k.updateType)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.localAuthorizationList) {
|
||||
j["localAuthorizationList"] = json::array();
|
||||
for (const auto& val : k.localAuthorizationList.value()) {
|
||||
j["localAuthorizationList"].push_back(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, SendLocalListRequest& k) {
|
||||
// the required parts of the message
|
||||
k.listVersion = j.at("listVersion");
|
||||
k.updateType = conversions::string_to_update_type(j.at("updateType"));
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("localAuthorizationList")) {
|
||||
const json& arr = j.at("localAuthorizationList");
|
||||
std::vector<LocalAuthorizationList> vec;
|
||||
for (const auto& val : arr) {
|
||||
vec.push_back(val);
|
||||
}
|
||||
k.localAuthorizationList.emplace(vec);
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given SendLocalListRequest \p k to the given output stream \p os
|
||||
/// \returns an output stream with the SendLocalListRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const SendLocalListRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string SendLocalListResponse::get_type() const {
|
||||
return "SendLocalListResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const SendLocalListResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::update_status_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, SendLocalListResponse& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_update_status(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given SendLocalListResponse \p k to the given output stream \p os
|
||||
/// \returns an output stream with the SendLocalListResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const SendLocalListResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,72 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/SetChargingProfile.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string SetChargingProfileRequest::get_type() const {
|
||||
return "SetChargingProfile";
|
||||
}
|
||||
|
||||
void to_json(json& j, const SetChargingProfileRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"connectorId", k.connectorId},
|
||||
{"csChargingProfiles", k.csChargingProfiles},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, SetChargingProfileRequest& k) {
|
||||
// the required parts of the message
|
||||
k.connectorId = j.at("connectorId");
|
||||
k.csChargingProfiles = j.at("csChargingProfiles");
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given SetChargingProfileRequest \p k to the given output stream \p os
|
||||
/// \returns an output stream with the SetChargingProfileRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const SetChargingProfileRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string SetChargingProfileResponse::get_type() const {
|
||||
return "SetChargingProfileResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const SetChargingProfileResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::charging_profile_status_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, SetChargingProfileResponse& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_charging_profile_status(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given SetChargingProfileResponse \p k to the given output stream \p
|
||||
/// os
|
||||
/// \returns an output stream with the SetChargingProfileResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const SetChargingProfileResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/SignCertificate.hpp>
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string SignCertificateRequest::get_type() const {
|
||||
return "SignCertificate";
|
||||
}
|
||||
|
||||
void to_json(json& j, const SignCertificateRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"csr", k.csr},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, SignCertificateRequest& k) {
|
||||
// the required parts of the message
|
||||
k.csr = j.at("csr");
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given SignCertificateRequest \p k to the given output stream \p os
|
||||
/// \returns an output stream with the SignCertificateRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const SignCertificateRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string SignCertificateResponse::get_type() const {
|
||||
return "SignCertificateResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const SignCertificateResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::generic_status_enum_type_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, SignCertificateResponse& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_generic_status_enum_type(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given SignCertificateResponse \p k to the given output stream \p os
|
||||
/// \returns an output stream with the SignCertificateResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const SignCertificateResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,78 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/SignedFirmwareStatusNotification.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string SignedFirmwareStatusNotificationRequest::get_type() const {
|
||||
return "SignedFirmwareStatusNotification";
|
||||
}
|
||||
|
||||
void to_json(json& j, const SignedFirmwareStatusNotificationRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::firmware_status_enum_type_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.requestId) {
|
||||
j["requestId"] = k.requestId.value();
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, SignedFirmwareStatusNotificationRequest& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_firmware_status_enum_type(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("requestId")) {
|
||||
k.requestId.emplace(j.at("requestId"));
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given SignedFirmwareStatusNotificationRequest \p k to the given
|
||||
/// output stream \p os
|
||||
/// \returns an output stream with the SignedFirmwareStatusNotificationRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const SignedFirmwareStatusNotificationRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string SignedFirmwareStatusNotificationResponse::get_type() const {
|
||||
return "SignedFirmwareStatusNotificationResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const SignedFirmwareStatusNotificationResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json({}, true);
|
||||
// the optional parts of the message
|
||||
(void)k; // no elements to unpack, silence unused parameter warning
|
||||
}
|
||||
|
||||
void from_json(const json& j, SignedFirmwareStatusNotificationResponse& k) {
|
||||
// the required parts of the message
|
||||
|
||||
// the optional parts of the message
|
||||
// no elements to unpack, silence unused parameter warning
|
||||
(void)j;
|
||||
(void)k;
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given SignedFirmwareStatusNotificationResponse \p k to the given
|
||||
/// output stream \p os
|
||||
/// \returns an output stream with the SignedFirmwareStatusNotificationResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const SignedFirmwareStatusNotificationResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,85 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/SignedUpdateFirmware.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string SignedUpdateFirmwareRequest::get_type() const {
|
||||
return "SignedUpdateFirmware";
|
||||
}
|
||||
|
||||
void to_json(json& j, const SignedUpdateFirmwareRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"requestId", k.requestId},
|
||||
{"firmware", k.firmware},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.retries) {
|
||||
j["retries"] = k.retries.value();
|
||||
}
|
||||
if (k.retryInterval) {
|
||||
j["retryInterval"] = k.retryInterval.value();
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, SignedUpdateFirmwareRequest& k) {
|
||||
// the required parts of the message
|
||||
k.requestId = j.at("requestId");
|
||||
k.firmware = j.at("firmware");
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("retries")) {
|
||||
k.retries.emplace(j.at("retries"));
|
||||
}
|
||||
if (j.contains("retryInterval")) {
|
||||
k.retryInterval.emplace(j.at("retryInterval"));
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given SignedUpdateFirmwareRequest \p k to the given output stream \p
|
||||
/// os
|
||||
/// \returns an output stream with the SignedUpdateFirmwareRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const SignedUpdateFirmwareRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string SignedUpdateFirmwareResponse::get_type() const {
|
||||
return "SignedUpdateFirmwareResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const SignedUpdateFirmwareResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::update_firmware_status_enum_type_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, SignedUpdateFirmwareResponse& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_update_firmware_status_enum_type(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given SignedUpdateFirmwareResponse \p k to the given output stream \p
|
||||
/// os
|
||||
/// \returns an output stream with the SignedUpdateFirmwareResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const SignedUpdateFirmwareResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,83 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/StartTransaction.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string StartTransactionRequest::get_type() const {
|
||||
return "StartTransaction";
|
||||
}
|
||||
|
||||
void to_json(json& j, const StartTransactionRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"connectorId", k.connectorId},
|
||||
{"idTag", k.idTag},
|
||||
{"meterStart", k.meterStart},
|
||||
{"timestamp", k.timestamp.to_rfc3339()},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.reservationId) {
|
||||
j["reservationId"] = k.reservationId.value();
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, StartTransactionRequest& k) {
|
||||
// the required parts of the message
|
||||
k.connectorId = j.at("connectorId");
|
||||
k.idTag = j.at("idTag");
|
||||
k.meterStart = j.at("meterStart");
|
||||
k.timestamp = ocpp::DateTime(std::string(j.at("timestamp")));
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("reservationId")) {
|
||||
k.reservationId.emplace(j.at("reservationId"));
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given StartTransactionRequest \p k to the given output stream \p os
|
||||
/// \returns an output stream with the StartTransactionRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const StartTransactionRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string StartTransactionResponse::get_type() const {
|
||||
return "StartTransactionResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const StartTransactionResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"idTagInfo", k.idTagInfo},
|
||||
{"transactionId", k.transactionId},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, StartTransactionResponse& k) {
|
||||
// the required parts of the message
|
||||
k.idTagInfo = j.at("idTagInfo");
|
||||
k.transactionId = j.at("transactionId");
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given StartTransactionResponse \p k to the given output stream \p os
|
||||
/// \returns an output stream with the StartTransactionResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const StartTransactionResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,99 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/StatusNotification.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string StatusNotificationRequest::get_type() const {
|
||||
return "StatusNotification";
|
||||
}
|
||||
|
||||
void to_json(json& j, const StatusNotificationRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"connectorId", k.connectorId},
|
||||
{"errorCode", conversions::charge_point_error_code_to_string(k.errorCode)},
|
||||
{"status", conversions::charge_point_status_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.info) {
|
||||
j["info"] = k.info.value();
|
||||
}
|
||||
if (k.timestamp) {
|
||||
j["timestamp"] = k.timestamp.value().to_rfc3339();
|
||||
}
|
||||
if (k.vendorId) {
|
||||
j["vendorId"] = k.vendorId.value();
|
||||
}
|
||||
if (k.vendorErrorCode) {
|
||||
j["vendorErrorCode"] = k.vendorErrorCode.value();
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, StatusNotificationRequest& k) {
|
||||
// the required parts of the message
|
||||
k.connectorId = j.at("connectorId");
|
||||
k.errorCode = conversions::string_to_charge_point_error_code(j.at("errorCode"));
|
||||
k.status = conversions::string_to_charge_point_status(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("info")) {
|
||||
k.info.emplace(j.at("info"));
|
||||
}
|
||||
if (j.contains("timestamp")) {
|
||||
k.timestamp.emplace(ocpp::DateTime(std::string(j.at("timestamp"))));
|
||||
}
|
||||
if (j.contains("vendorId")) {
|
||||
k.vendorId.emplace(j.at("vendorId"));
|
||||
}
|
||||
if (j.contains("vendorErrorCode")) {
|
||||
k.vendorErrorCode.emplace(j.at("vendorErrorCode"));
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given StatusNotificationRequest \p k to the given output stream \p os
|
||||
/// \returns an output stream with the StatusNotificationRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const StatusNotificationRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string StatusNotificationResponse::get_type() const {
|
||||
return "StatusNotificationResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const StatusNotificationResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json({}, true);
|
||||
// the optional parts of the message
|
||||
(void)k; // no elements to unpack, silence unused parameter warning
|
||||
}
|
||||
|
||||
void from_json(const json& j, StatusNotificationResponse& k) {
|
||||
// the required parts of the message
|
||||
|
||||
// the optional parts of the message
|
||||
// no elements to unpack, silence unused parameter warning
|
||||
(void)j;
|
||||
(void)k;
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given StatusNotificationResponse \p k to the given output stream \p
|
||||
/// os
|
||||
/// \returns an output stream with the StatusNotificationResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const StatusNotificationResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,102 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/StopTransaction.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string StopTransactionRequest::get_type() const {
|
||||
return "StopTransaction";
|
||||
}
|
||||
|
||||
void to_json(json& j, const StopTransactionRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"meterStop", k.meterStop},
|
||||
{"timestamp", k.timestamp.to_rfc3339()},
|
||||
{"transactionId", k.transactionId},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.idTag) {
|
||||
j["idTag"] = k.idTag.value();
|
||||
}
|
||||
if (k.reason) {
|
||||
j["reason"] = conversions::reason_to_string(k.reason.value());
|
||||
}
|
||||
if (k.transactionData) {
|
||||
j["transactionData"] = json::array();
|
||||
for (const auto& val : k.transactionData.value()) {
|
||||
j["transactionData"].push_back(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, StopTransactionRequest& k) {
|
||||
// the required parts of the message
|
||||
k.meterStop = j.at("meterStop");
|
||||
k.timestamp = ocpp::DateTime(std::string(j.at("timestamp")));
|
||||
k.transactionId = j.at("transactionId");
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("idTag")) {
|
||||
k.idTag.emplace(j.at("idTag"));
|
||||
}
|
||||
if (j.contains("reason")) {
|
||||
k.reason.emplace(conversions::string_to_reason(j.at("reason")));
|
||||
}
|
||||
if (j.contains("transactionData")) {
|
||||
const json& arr = j.at("transactionData");
|
||||
std::vector<TransactionData> vec;
|
||||
for (const auto& val : arr) {
|
||||
vec.push_back(val);
|
||||
}
|
||||
k.transactionData.emplace(vec);
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given StopTransactionRequest \p k to the given output stream \p os
|
||||
/// \returns an output stream with the StopTransactionRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const StopTransactionRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string StopTransactionResponse::get_type() const {
|
||||
return "StopTransactionResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const StopTransactionResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json({}, true);
|
||||
// the optional parts of the message
|
||||
if (k.idTagInfo) {
|
||||
j["idTagInfo"] = k.idTagInfo.value();
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, StopTransactionResponse& k) {
|
||||
// the required parts of the message
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("idTagInfo")) {
|
||||
k.idTagInfo.emplace(j.at("idTagInfo"));
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given StopTransactionResponse \p k to the given output stream \p os
|
||||
/// \returns an output stream with the StopTransactionResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const StopTransactionResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,75 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/TriggerMessage.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string TriggerMessageRequest::get_type() const {
|
||||
return "TriggerMessage";
|
||||
}
|
||||
|
||||
void to_json(json& j, const TriggerMessageRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"requestedMessage", conversions::message_trigger_to_string(k.requestedMessage)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.connectorId) {
|
||||
j["connectorId"] = k.connectorId.value();
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, TriggerMessageRequest& k) {
|
||||
// the required parts of the message
|
||||
k.requestedMessage = conversions::string_to_message_trigger(j.at("requestedMessage"));
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("connectorId")) {
|
||||
k.connectorId.emplace(j.at("connectorId"));
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given TriggerMessageRequest \p k to the given output stream \p os
|
||||
/// \returns an output stream with the TriggerMessageRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const TriggerMessageRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string TriggerMessageResponse::get_type() const {
|
||||
return "TriggerMessageResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const TriggerMessageResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::trigger_message_status_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, TriggerMessageResponse& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_trigger_message_status(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given TriggerMessageResponse \p k to the given output stream \p os
|
||||
/// \returns an output stream with the TriggerMessageResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const TriggerMessageResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/UnlockConnector.hpp>
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string UnlockConnectorRequest::get_type() const {
|
||||
return "UnlockConnector";
|
||||
}
|
||||
|
||||
void to_json(json& j, const UnlockConnectorRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"connectorId", k.connectorId},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, UnlockConnectorRequest& k) {
|
||||
// the required parts of the message
|
||||
k.connectorId = j.at("connectorId");
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given UnlockConnectorRequest \p k to the given output stream \p os
|
||||
/// \returns an output stream with the UnlockConnectorRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const UnlockConnectorRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string UnlockConnectorResponse::get_type() const {
|
||||
return "UnlockConnectorResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const UnlockConnectorResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::unlock_status_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
void from_json(const json& j, UnlockConnectorResponse& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_unlock_status(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given UnlockConnectorResponse \p k to the given output stream \p os
|
||||
/// \returns an output stream with the UnlockConnectorResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const UnlockConnectorResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,84 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/messages/UpdateFirmware.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
std::string UpdateFirmwareRequest::get_type() const {
|
||||
return "UpdateFirmware";
|
||||
}
|
||||
|
||||
void to_json(json& j, const UpdateFirmwareRequest& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"location", k.location},
|
||||
{"retrieveDate", k.retrieveDate.to_rfc3339()},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.retries) {
|
||||
j["retries"] = k.retries.value();
|
||||
}
|
||||
if (k.retryInterval) {
|
||||
j["retryInterval"] = k.retryInterval.value();
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, UpdateFirmwareRequest& k) {
|
||||
// the required parts of the message
|
||||
k.location = j.at("location");
|
||||
k.retrieveDate = ocpp::DateTime(std::string(j.at("retrieveDate")));
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("retries")) {
|
||||
k.retries.emplace(j.at("retries"));
|
||||
}
|
||||
if (j.contains("retryInterval")) {
|
||||
k.retryInterval.emplace(j.at("retryInterval"));
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given UpdateFirmwareRequest \p k to the given output stream \p os
|
||||
/// \returns an output stream with the UpdateFirmwareRequest written to
|
||||
std::ostream& operator<<(std::ostream& os, const UpdateFirmwareRequest& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string UpdateFirmwareResponse::get_type() const {
|
||||
return "UpdateFirmwareResponse";
|
||||
}
|
||||
|
||||
void to_json(json& j, const UpdateFirmwareResponse& k) {
|
||||
// the required parts of the message
|
||||
j = json({}, true);
|
||||
// the optional parts of the message
|
||||
(void)k; // no elements to unpack, silence unused parameter warning
|
||||
}
|
||||
|
||||
void from_json(const json& j, UpdateFirmwareResponse& k) {
|
||||
// the required parts of the message
|
||||
|
||||
// the optional parts of the message
|
||||
// no elements to unpack, silence unused parameter warning
|
||||
(void)j;
|
||||
(void)k;
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given UpdateFirmwareResponse \p k to the given output stream \p os
|
||||
/// \returns an output stream with the UpdateFirmwareResponse written to
|
||||
std::ostream& operator<<(std::ostream& os, const UpdateFirmwareResponse& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
2197
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v16/ocpp_enums.cpp
Normal file
2197
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v16/ocpp_enums.cpp
Normal file
File diff suppressed because it is too large
Load Diff
469
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v16/ocpp_types.cpp
Normal file
469
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v16/ocpp_types.cpp
Normal file
@@ -0,0 +1,469 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
// This code is generated using the generator in 'src/code_generator/common`, please do not edit manually
|
||||
|
||||
#include <ocpp/v16/ocpp_types.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <ocpp/common/types.hpp>
|
||||
#include <ocpp/v16/ocpp_enums.hpp>
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
/// \brief Conversion from a given IdTagInfo \p k to a given json object \p j
|
||||
void to_json(json& j, const IdTagInfo& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"status", conversions::authorization_status_to_string(k.status)},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.expiryDate) {
|
||||
j["expiryDate"] = k.expiryDate.value().to_rfc3339();
|
||||
}
|
||||
if (k.parentIdTag) {
|
||||
j["parentIdTag"] = k.parentIdTag.value();
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given json object \p j to a given IdTagInfo \p k
|
||||
void from_json(const json& j, IdTagInfo& k) {
|
||||
// the required parts of the message
|
||||
k.status = conversions::string_to_authorization_status(j.at("status"));
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("expiryDate")) {
|
||||
k.expiryDate.emplace(j.at("expiryDate").get<std::string>());
|
||||
}
|
||||
if (j.contains("parentIdTag")) {
|
||||
k.parentIdTag.emplace(j.at("parentIdTag"));
|
||||
}
|
||||
}
|
||||
|
||||
// \brief Writes the string representation of the given IdTagInfo \p k to the given output stream \p os
|
||||
/// \returns an output stream with the IdTagInfo written to
|
||||
std::ostream& operator<<(std::ostream& os, const IdTagInfo& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given CertificateHashDataType \p k to a given json object \p j
|
||||
void to_json(json& j, const CertificateHashDataType& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"hashAlgorithm", conversions::hash_algorithm_enum_type_to_string(k.hashAlgorithm)},
|
||||
{"issuerNameHash", k.issuerNameHash},
|
||||
{"issuerKeyHash", k.issuerKeyHash},
|
||||
{"serialNumber", k.serialNumber},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given json object \p j to a given CertificateHashDataType \p k
|
||||
void from_json(const json& j, CertificateHashDataType& k) {
|
||||
// the required parts of the message
|
||||
k.hashAlgorithm = conversions::string_to_hash_algorithm_enum_type(j.at("hashAlgorithm"));
|
||||
k.issuerNameHash = j.at("issuerNameHash");
|
||||
k.issuerKeyHash = j.at("issuerKeyHash");
|
||||
k.serialNumber = j.at("serialNumber");
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
// \brief Writes the string representation of the given CertificateHashDataType \p k to the given output stream \p os
|
||||
/// \returns an output stream with the CertificateHashDataType written to
|
||||
std::ostream& operator<<(std::ostream& os, const CertificateHashDataType& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given ChargingSchedulePeriod \p k to a given json object \p j
|
||||
void to_json(json& j, const ChargingSchedulePeriod& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"startPeriod", k.startPeriod},
|
||||
{"limit", k.limit},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.numberPhases) {
|
||||
j["numberPhases"] = k.numberPhases.value();
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given json object \p j to a given ChargingSchedulePeriod \p k
|
||||
void from_json(const json& j, ChargingSchedulePeriod& k) {
|
||||
// the required parts of the message
|
||||
k.startPeriod = j.at("startPeriod");
|
||||
k.limit = j.at("limit");
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("numberPhases")) {
|
||||
k.numberPhases.emplace(j.at("numberPhases"));
|
||||
}
|
||||
}
|
||||
|
||||
// \brief Writes the string representation of the given ChargingSchedulePeriod \p k to the given output stream \p os
|
||||
/// \returns an output stream with the ChargingSchedulePeriod written to
|
||||
std::ostream& operator<<(std::ostream& os, const ChargingSchedulePeriod& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given ChargingSchedule \p k to a given json object \p j
|
||||
void to_json(json& j, const ChargingSchedule& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"chargingRateUnit", conversions::charging_rate_unit_to_string(k.chargingRateUnit)},
|
||||
{"chargingSchedulePeriod", k.chargingSchedulePeriod},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.duration) {
|
||||
j["duration"] = k.duration.value();
|
||||
}
|
||||
if (k.startSchedule) {
|
||||
j["startSchedule"] = k.startSchedule.value().to_rfc3339();
|
||||
}
|
||||
if (k.minChargingRate) {
|
||||
j["minChargingRate"] = k.minChargingRate.value();
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given json object \p j to a given ChargingSchedule \p k
|
||||
void from_json(const json& j, ChargingSchedule& k) {
|
||||
// the required parts of the message
|
||||
k.chargingRateUnit = conversions::string_to_charging_rate_unit(j.at("chargingRateUnit"));
|
||||
for (const auto& val : j.at("chargingSchedulePeriod")) {
|
||||
k.chargingSchedulePeriod.push_back(val);
|
||||
}
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("duration")) {
|
||||
k.duration.emplace(j.at("duration"));
|
||||
}
|
||||
if (j.contains("startSchedule")) {
|
||||
k.startSchedule.emplace(j.at("startSchedule").get<std::string>());
|
||||
}
|
||||
if (j.contains("minChargingRate")) {
|
||||
k.minChargingRate.emplace(j.at("minChargingRate"));
|
||||
}
|
||||
}
|
||||
|
||||
// \brief Writes the string representation of the given ChargingSchedule \p k to the given output stream \p os
|
||||
/// \returns an output stream with the ChargingSchedule written to
|
||||
std::ostream& operator<<(std::ostream& os, const ChargingSchedule& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given KeyValue \p k to a given json object \p j
|
||||
void to_json(json& j, const KeyValue& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"key", k.key},
|
||||
{"readonly", k.readonly},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.value) {
|
||||
j["value"] = k.value.value();
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given json object \p j to a given KeyValue \p k
|
||||
void from_json(const json& j, KeyValue& k) {
|
||||
// the required parts of the message
|
||||
k.key = j.at("key");
|
||||
k.readonly = j.at("readonly");
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("value")) {
|
||||
k.value.emplace(j.at("value"));
|
||||
}
|
||||
}
|
||||
|
||||
// \brief Writes the string representation of the given KeyValue \p k to the given output stream \p os
|
||||
/// \returns an output stream with the KeyValue written to
|
||||
std::ostream& operator<<(std::ostream& os, const KeyValue& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given LogParametersType \p k to a given json object \p j
|
||||
void to_json(json& j, const LogParametersType& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"remoteLocation", k.remoteLocation},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.oldestTimestamp) {
|
||||
j["oldestTimestamp"] = k.oldestTimestamp.value().to_rfc3339();
|
||||
}
|
||||
if (k.latestTimestamp) {
|
||||
j["latestTimestamp"] = k.latestTimestamp.value().to_rfc3339();
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given json object \p j to a given LogParametersType \p k
|
||||
void from_json(const json& j, LogParametersType& k) {
|
||||
// the required parts of the message
|
||||
k.remoteLocation = j.at("remoteLocation");
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("oldestTimestamp")) {
|
||||
k.oldestTimestamp.emplace(j.at("oldestTimestamp").get<std::string>());
|
||||
}
|
||||
if (j.contains("latestTimestamp")) {
|
||||
k.latestTimestamp.emplace(j.at("latestTimestamp").get<std::string>());
|
||||
}
|
||||
}
|
||||
|
||||
// \brief Writes the string representation of the given LogParametersType \p k to the given output stream \p os
|
||||
/// \returns an output stream with the LogParametersType written to
|
||||
std::ostream& operator<<(std::ostream& os, const LogParametersType& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given SampledValue \p k to a given json object \p j
|
||||
void to_json(json& j, const SampledValue& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"value", k.value},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.context) {
|
||||
j["context"] = conversions::reading_context_to_string(k.context.value());
|
||||
}
|
||||
if (k.format) {
|
||||
j["format"] = conversions::value_format_to_string(k.format.value());
|
||||
}
|
||||
if (k.measurand) {
|
||||
j["measurand"] = conversions::measurand_to_string(k.measurand.value());
|
||||
}
|
||||
if (k.phase) {
|
||||
j["phase"] = conversions::phase_to_string(k.phase.value());
|
||||
}
|
||||
if (k.location) {
|
||||
j["location"] = conversions::location_to_string(k.location.value());
|
||||
}
|
||||
if (k.unit) {
|
||||
j["unit"] = conversions::unit_of_measure_to_string(k.unit.value());
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given json object \p j to a given SampledValue \p k
|
||||
void from_json(const json& j, SampledValue& k) {
|
||||
// the required parts of the message
|
||||
k.value = j.at("value");
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("context")) {
|
||||
k.context.emplace(conversions::string_to_reading_context(j.at("context")));
|
||||
}
|
||||
if (j.contains("format")) {
|
||||
k.format.emplace(conversions::string_to_value_format(j.at("format")));
|
||||
}
|
||||
if (j.contains("measurand")) {
|
||||
k.measurand.emplace(conversions::string_to_measurand(j.at("measurand")));
|
||||
}
|
||||
if (j.contains("phase")) {
|
||||
k.phase.emplace(conversions::string_to_phase(j.at("phase")));
|
||||
}
|
||||
if (j.contains("location")) {
|
||||
k.location.emplace(conversions::string_to_location(j.at("location")));
|
||||
}
|
||||
if (j.contains("unit")) {
|
||||
k.unit.emplace(conversions::string_to_unit_of_measure(j.at("unit")));
|
||||
}
|
||||
}
|
||||
|
||||
// \brief Writes the string representation of the given SampledValue \p k to the given output stream \p os
|
||||
/// \returns an output stream with the SampledValue written to
|
||||
std::ostream& operator<<(std::ostream& os, const SampledValue& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given MeterValue \p k to a given json object \p j
|
||||
void to_json(json& j, const MeterValue& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"timestamp", k.timestamp.to_rfc3339()},
|
||||
{"sampledValue", k.sampledValue},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given json object \p j to a given MeterValue \p k
|
||||
void from_json(const json& j, MeterValue& k) {
|
||||
// the required parts of the message
|
||||
k.timestamp = ocpp::DateTime(std::string(j.at("timestamp")));
|
||||
for (const auto& val : j.at("sampledValue")) {
|
||||
k.sampledValue.push_back(val);
|
||||
}
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
// \brief Writes the string representation of the given MeterValue \p k to the given output stream \p os
|
||||
/// \returns an output stream with the MeterValue written to
|
||||
std::ostream& operator<<(std::ostream& os, const MeterValue& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given ChargingProfile \p k to a given json object \p j
|
||||
void to_json(json& j, const ChargingProfile& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"chargingProfileId", k.chargingProfileId},
|
||||
{"stackLevel", k.stackLevel},
|
||||
{"chargingProfilePurpose", conversions::charging_profile_purpose_type_to_string(k.chargingProfilePurpose)},
|
||||
{"chargingProfileKind", conversions::charging_profile_kind_type_to_string(k.chargingProfileKind)},
|
||||
{"chargingSchedule", k.chargingSchedule},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.transactionId) {
|
||||
j["transactionId"] = k.transactionId.value();
|
||||
}
|
||||
if (k.recurrencyKind) {
|
||||
j["recurrencyKind"] = conversions::recurrency_kind_type_to_string(k.recurrencyKind.value());
|
||||
}
|
||||
if (k.validFrom) {
|
||||
j["validFrom"] = k.validFrom.value().to_rfc3339();
|
||||
}
|
||||
if (k.validTo) {
|
||||
j["validTo"] = k.validTo.value().to_rfc3339();
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given json object \p j to a given ChargingProfile \p k
|
||||
void from_json(const json& j, ChargingProfile& k) {
|
||||
// the required parts of the message
|
||||
k.chargingProfileId = j.at("chargingProfileId");
|
||||
k.stackLevel = j.at("stackLevel");
|
||||
k.chargingProfilePurpose = conversions::string_to_charging_profile_purpose_type(j.at("chargingProfilePurpose"));
|
||||
k.chargingProfileKind = conversions::string_to_charging_profile_kind_type(j.at("chargingProfileKind"));
|
||||
k.chargingSchedule = j.at("chargingSchedule");
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("transactionId")) {
|
||||
k.transactionId.emplace(j.at("transactionId"));
|
||||
}
|
||||
if (j.contains("recurrencyKind")) {
|
||||
k.recurrencyKind.emplace(conversions::string_to_recurrency_kind_type(j.at("recurrencyKind")));
|
||||
}
|
||||
if (j.contains("validFrom")) {
|
||||
k.validFrom.emplace(j.at("validFrom").get<std::string>());
|
||||
}
|
||||
if (j.contains("validTo")) {
|
||||
k.validTo.emplace(j.at("validTo").get<std::string>());
|
||||
}
|
||||
}
|
||||
|
||||
// \brief Writes the string representation of the given ChargingProfile \p k to the given output stream \p os
|
||||
/// \returns an output stream with the ChargingProfile written to
|
||||
std::ostream& operator<<(std::ostream& os, const ChargingProfile& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given LocalAuthorizationList \p k to a given json object \p j
|
||||
void to_json(json& j, const LocalAuthorizationList& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"idTag", k.idTag},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.idTagInfo) {
|
||||
j["idTagInfo"] = k.idTagInfo.value();
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given json object \p j to a given LocalAuthorizationList \p k
|
||||
void from_json(const json& j, LocalAuthorizationList& k) {
|
||||
// the required parts of the message
|
||||
k.idTag = j.at("idTag");
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("idTagInfo")) {
|
||||
k.idTagInfo.emplace(j.at("idTagInfo"));
|
||||
}
|
||||
}
|
||||
|
||||
// \brief Writes the string representation of the given LocalAuthorizationList \p k to the given output stream \p os
|
||||
/// \returns an output stream with the LocalAuthorizationList written to
|
||||
std::ostream& operator<<(std::ostream& os, const LocalAuthorizationList& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given FirmwareType \p k to a given json object \p j
|
||||
void to_json(json& j, const FirmwareType& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"location", k.location},
|
||||
{"retrieveDateTime", k.retrieveDateTime.to_rfc3339()},
|
||||
{"signingCertificate", k.signingCertificate},
|
||||
{"signature", k.signature},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.installDateTime) {
|
||||
j["installDateTime"] = k.installDateTime.value().to_rfc3339();
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given json object \p j to a given FirmwareType \p k
|
||||
void from_json(const json& j, FirmwareType& k) {
|
||||
// the required parts of the message
|
||||
k.location = j.at("location");
|
||||
k.retrieveDateTime = ocpp::DateTime(std::string(j.at("retrieveDateTime")));
|
||||
k.signingCertificate = j.at("signingCertificate");
|
||||
k.signature = j.at("signature");
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("installDateTime")) {
|
||||
k.installDateTime.emplace(j.at("installDateTime").get<std::string>());
|
||||
}
|
||||
}
|
||||
|
||||
// \brief Writes the string representation of the given FirmwareType \p k to the given output stream \p os
|
||||
/// \returns an output stream with the FirmwareType written to
|
||||
std::ostream& operator<<(std::ostream& os, const FirmwareType& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given TransactionData \p k to a given json object \p j
|
||||
void to_json(json& j, const TransactionData& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"timestamp", k.timestamp.to_rfc3339()},
|
||||
{"sampledValue", k.sampledValue},
|
||||
};
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given json object \p j to a given TransactionData \p k
|
||||
void from_json(const json& j, TransactionData& k) {
|
||||
// the required parts of the message
|
||||
k.timestamp = ocpp::DateTime(std::string(j.at("timestamp")));
|
||||
for (const auto& val : j.at("sampledValue")) {
|
||||
k.sampledValue.push_back(val);
|
||||
}
|
||||
|
||||
// the optional parts of the message
|
||||
}
|
||||
|
||||
// \brief Writes the string representation of the given TransactionData \p k to the given output stream \p os
|
||||
/// \returns an output stream with the TransactionData written to
|
||||
std::ostream& operator<<(std::ostream& os, const TransactionData& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
719
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v16/profile.cpp
Normal file
719
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v16/profile.cpp
Normal file
@@ -0,0 +1,719 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <chrono>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
|
||||
#include <ocpp/common/constants.hpp>
|
||||
#include <ocpp/common/types.hpp>
|
||||
#include <ocpp/v16/ocpp_enums.hpp>
|
||||
#include <ocpp/v16/ocpp_types.hpp>
|
||||
#include <ocpp/v16/profile.hpp>
|
||||
#include <ocpp/v16/smart_charging.hpp>
|
||||
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
using std::chrono::duration_cast;
|
||||
using std::chrono::seconds;
|
||||
|
||||
namespace {
|
||||
|
||||
using ocpp::v16::EnhancedChargingSchedulePeriod;
|
||||
|
||||
/// \brief update the iterator when the current period has elapsed
|
||||
/// \param[in] schedule_duration the time in seconds from the start of the composite schedule
|
||||
/// \param[inout] itt the iterator for the periods in the schedule
|
||||
/// \param[in] end the item beyond the last period in the schedule
|
||||
/// \param[out] period the details of the current period in the schedule
|
||||
/// \param[out] period_duration how long this period lasts
|
||||
///
|
||||
/// \note period_duration is defined by the startPeriod of the next period or forever when
|
||||
/// there is no next period.
|
||||
void update_itt(const int schedule_duration, std::vector<EnhancedChargingSchedulePeriod>::const_iterator& itt,
|
||||
const std::vector<EnhancedChargingSchedulePeriod>::const_iterator& end,
|
||||
EnhancedChargingSchedulePeriod& period, int& period_duration) {
|
||||
if (itt != end) {
|
||||
// default is to remain in the current period
|
||||
period = *itt;
|
||||
|
||||
/*
|
||||
* calculate the duration of this period:
|
||||
* - the startPeriod of the next period in the vector, or
|
||||
* - forever where there is no next period
|
||||
*/
|
||||
auto next = std::next(itt);
|
||||
period_duration = (next != end) ? next->startPeriod : std::numeric_limits<int>::max();
|
||||
|
||||
if (schedule_duration >= period_duration) {
|
||||
/*
|
||||
* when the current duration is beyond the duration of this period
|
||||
* move to the next period in the vector and recalculate the period duration
|
||||
* (the handling for being at the last element is below)
|
||||
*/
|
||||
itt++;
|
||||
if (itt != end) {
|
||||
period = *itt;
|
||||
next = std::next(itt);
|
||||
period_duration = (next != end) ? next->startPeriod : std::numeric_limits<int>::max();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* all periods in the schedule have been used
|
||||
* i.e. there are no future periods to consider in this schedule
|
||||
*/
|
||||
if (itt == end) {
|
||||
period.startPeriod = -1;
|
||||
period_duration = std::numeric_limits<int>::max();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace ocpp::v16 {
|
||||
|
||||
std::int32_t elapsed_seconds(const ocpp::DateTime& to, const ocpp::DateTime& from) {
|
||||
return clamp_to<std::int32_t>(duration_cast<seconds>(to.to_time_point() - from.to_time_point()).count());
|
||||
}
|
||||
|
||||
ocpp::DateTime floor_seconds(const ocpp::DateTime& dt) {
|
||||
return ocpp::DateTime(std::chrono::floor<seconds>(dt.to_time_point()));
|
||||
}
|
||||
|
||||
namespace {
|
||||
constexpr bool operator==(const ChargingSchedulePeriod& lhs, const ChargingSchedulePeriod& rhs) {
|
||||
return (lhs.startPeriod == rhs.startPeriod) && (lhs.limit == rhs.limit);
|
||||
}
|
||||
|
||||
constexpr bool operator!=(const ChargingSchedulePeriod& lhs, const ChargingSchedulePeriod& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& os, const period_entry_t& entry) {
|
||||
os << entry.start << " " << entry.end << " S:" << entry.stack_level << " " << entry.limit
|
||||
<< ((entry.charging_rate_unit == ChargingRateUnit::A) ? "A" : "W");
|
||||
return os;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
/// \brief populate a schedule period
|
||||
/// \param in_start the start time of the profile
|
||||
/// \param in_duration the time in seconds from the start of the profile to the end of this period
|
||||
/// \param in_period the details of this period
|
||||
void period_entry_t::init(const DateTime& in_start, int in_duration, const ChargingSchedulePeriod& in_period,
|
||||
const ChargingProfile& in_profile) {
|
||||
// note duration can be negative and hence end time is before start time
|
||||
// see period_entry_t::validate()
|
||||
const auto start_tp = std::chrono::floor<seconds>(in_start.to_time_point());
|
||||
start = DateTime(start_tp + seconds(in_period.startPeriod));
|
||||
end = DateTime(start_tp + seconds(in_duration));
|
||||
limit = in_period.limit;
|
||||
number_phases = in_period.numberPhases;
|
||||
stack_level = in_profile.stackLevel;
|
||||
charging_rate_unit = in_profile.chargingSchedule.chargingRateUnit;
|
||||
min_charging_rate = in_profile.chargingSchedule.minChargingRate;
|
||||
}
|
||||
|
||||
/// \brief validate and update entry based on profile
|
||||
/// \param profile the profile this entry is part of
|
||||
/// \param now the current date and time
|
||||
/// \return true when the entry is valid
|
||||
bool period_entry_t::validate(const ChargingProfile& profile, const DateTime& now) {
|
||||
bool b_valid{true};
|
||||
|
||||
if (profile.validFrom) {
|
||||
const auto valid_from = floor_seconds(profile.validFrom.value());
|
||||
if (valid_from > start) {
|
||||
// the calculated start is before the profile is valid
|
||||
if (valid_from >= end) {
|
||||
// the whole period isn't valid
|
||||
b_valid = false;
|
||||
} else {
|
||||
// adjust start to match validFrom
|
||||
start = valid_from;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b_valid = b_valid && end > start; // check end time is after the start time
|
||||
b_valid = b_valid && end > now; // ignore expired periods
|
||||
return b_valid;
|
||||
}
|
||||
|
||||
/// \brief calculate the start times for the profile
|
||||
/// \param in_now the current date and time
|
||||
/// \param in_end the end of the composite schedule
|
||||
/// \param in_session_start optional when the charging session started
|
||||
/// \param in_profile the charging profile
|
||||
/// \return a list of the start times of the profile
|
||||
std::vector<DateTime> calculate_start(const DateTime& in_now, const DateTime& in_end,
|
||||
const std::optional<DateTime>& in_session_start,
|
||||
const ChargingProfile& in_profile) {
|
||||
/*
|
||||
* Absolute schedules start at the defined startSchedule
|
||||
* Relative schedules start at session start
|
||||
* Recurring schedules start based on startSchedule and the current date/time
|
||||
* start can be affected by the profile validFrom. See period_entry_t::validate()
|
||||
*/
|
||||
|
||||
std::vector<DateTime> start_times;
|
||||
DateTime start = floor_seconds(in_now); // fallback when a better value can't be found
|
||||
|
||||
switch (in_profile.chargingProfileKind) {
|
||||
case ChargingProfileKindType::Absolute:
|
||||
if (in_profile.chargingSchedule.startSchedule) {
|
||||
start = in_profile.chargingSchedule.startSchedule.value();
|
||||
} else {
|
||||
// Absolute should have a startSchedule
|
||||
EVLOG_warning << "Absolute charging profile (" << in_profile.chargingProfileId << ") without startSchedule";
|
||||
|
||||
// use validFrom where available
|
||||
if (in_profile.validFrom) {
|
||||
start = in_profile.validFrom.value();
|
||||
}
|
||||
}
|
||||
start_times.push_back(floor_seconds(start));
|
||||
break;
|
||||
case ChargingProfileKindType::Recurring:
|
||||
if (in_profile.recurrencyKind && in_profile.chargingSchedule.startSchedule) {
|
||||
const auto start_schedule = floor_seconds(in_profile.chargingSchedule.startSchedule.value());
|
||||
const auto end = floor_seconds(in_end);
|
||||
const auto now_tp = start.to_time_point();
|
||||
long seconds_to_go_back{0};
|
||||
long seconds_to_go_forward{0};
|
||||
|
||||
/*
|
||||
example problem case:
|
||||
- allow daily charging 08:00 to 18:00
|
||||
at 07:00 and 19:00 what should the start time be?
|
||||
|
||||
a) profile could have 1 period (32A) at 0s with a duration of 36000s (10 hours)
|
||||
relying on a lower stack level to deny charging
|
||||
b) profile could have 2 periods (32A) at 0s and (0A) at 36000s (10 hours)
|
||||
i.e. the profile covers the full 24 hours
|
||||
|
||||
at 07:00 is the start time in 1 hour, or 23 hours ago?
|
||||
|
||||
23 hours ago is the chosen result - however the profile code needs to consider that
|
||||
a new daily profile is about to start hence the next start time is provided.
|
||||
|
||||
Weekly has a similar problem
|
||||
*/
|
||||
|
||||
switch (in_profile.recurrencyKind.value()) {
|
||||
case RecurrencyKindType::Daily:
|
||||
seconds_to_go_back =
|
||||
duration_cast<seconds>(now_tp - start_schedule.to_time_point()).count() %
|
||||
// NOLINTNEXTLINE(bugprone-implicit-widening-of-multiplication-result): well below int::max()
|
||||
(HOURS_PER_DAY * SECONDS_PER_HOUR);
|
||||
if (seconds_to_go_back < 0) {
|
||||
// NOLINTNEXTLINE(bugprone-implicit-widening-of-multiplication-result): well below int::max()
|
||||
seconds_to_go_back += HOURS_PER_DAY * SECONDS_PER_HOUR;
|
||||
}
|
||||
// NOLINTNEXTLINE(bugprone-implicit-widening-of-multiplication-result): well below int::max()
|
||||
seconds_to_go_forward = HOURS_PER_DAY * SECONDS_PER_HOUR;
|
||||
break;
|
||||
case RecurrencyKindType::Weekly:
|
||||
seconds_to_go_back =
|
||||
duration_cast<seconds>(now_tp - start_schedule.to_time_point()).count() %
|
||||
// NOLINTNEXTLINE(bugprone-implicit-widening-of-multiplication-result): well below int::max()
|
||||
(SECONDS_PER_DAY * DAYS_PER_WEEK);
|
||||
if (seconds_to_go_back < 0) {
|
||||
// NOLINTNEXTLINE(bugprone-implicit-widening-of-multiplication-result): well below int::max()
|
||||
seconds_to_go_back += SECONDS_PER_DAY * DAYS_PER_WEEK;
|
||||
}
|
||||
// NOLINTNEXTLINE(bugprone-implicit-widening-of-multiplication-result): well below int::max()
|
||||
seconds_to_go_forward = SECONDS_PER_DAY * DAYS_PER_WEEK;
|
||||
break;
|
||||
}
|
||||
|
||||
start = DateTime(now_tp - seconds(seconds_to_go_back));
|
||||
|
||||
while (start <= end) {
|
||||
start_times.push_back(start);
|
||||
start = DateTime(start.to_time_point() + seconds(seconds_to_go_forward));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ChargingProfileKindType::Relative:
|
||||
// if there isn't a session start then assume the session starts now
|
||||
if (in_session_start) {
|
||||
start = floor_seconds(in_session_start.value());
|
||||
}
|
||||
start_times.push_back(start);
|
||||
break;
|
||||
default:
|
||||
EVLOG_error << "Invalid ChargingProfileKindType: " << static_cast<int>(in_profile.chargingProfileKind);
|
||||
break;
|
||||
}
|
||||
|
||||
return start_times;
|
||||
}
|
||||
|
||||
/// \brief calculate the start times for the schedule period
|
||||
/// \param in_now the current date and time
|
||||
/// \param in_end the end of the composite schedule
|
||||
/// \param in_session_start optional when the charging session started
|
||||
/// \param in_profile the charging profile
|
||||
/// \param in_period_index the schedule period index
|
||||
/// \return the list of start times
|
||||
std::vector<period_entry_t> calculate_profile_entry(const DateTime& in_now, const DateTime& in_end,
|
||||
const std::optional<DateTime>& in_session_start,
|
||||
const ChargingProfile& in_profile, std::size_t in_period_index) {
|
||||
std::vector<period_entry_t> entries;
|
||||
|
||||
if (in_period_index >= in_profile.chargingSchedule.chargingSchedulePeriod.size()) {
|
||||
EVLOG_error << "Invalid schedule period index [" << static_cast<int>(in_period_index)
|
||||
<< "] (too large) for profile " << in_profile.chargingProfileId;
|
||||
} else {
|
||||
const auto& this_period = in_profile.chargingSchedule.chargingSchedulePeriod[in_period_index];
|
||||
|
||||
if ((in_period_index == 0) && (this_period.startPeriod != 0)) {
|
||||
// invalid profile - first period must be 0
|
||||
EVLOG_error << "Invalid schedule period index [0] startPeriod " << this_period.startPeriod
|
||||
<< " for profile " << in_profile.chargingProfileId;
|
||||
} else if ((in_period_index > 0) &&
|
||||
(in_profile.chargingSchedule.chargingSchedulePeriod[in_period_index - 1].startPeriod >=
|
||||
this_period.startPeriod)) {
|
||||
// invalid profile - periods must be in order and with increasing startPeriod values
|
||||
EVLOG_error << "Invalid schedule period index [" << static_cast<int>(in_period_index) << "] startPeriod "
|
||||
<< this_period.startPeriod << " for profile " << in_profile.chargingProfileId;
|
||||
} else {
|
||||
const bool has_next_period =
|
||||
(in_period_index + 1) < in_profile.chargingSchedule.chargingSchedulePeriod.size();
|
||||
|
||||
// start time(s) of the schedule
|
||||
// the start time of this period is calculated in period_entry_t::init()
|
||||
const auto schedule_start = calculate_start(in_now, in_end, in_session_start, in_profile);
|
||||
|
||||
for (std::size_t i = 0; i < schedule_start.size(); i++) {
|
||||
const bool has_next_occurrance = (i + 1) < schedule_start.size();
|
||||
const auto& entry_start = schedule_start[i];
|
||||
|
||||
/*
|
||||
* The duration of this period (from the start of the schedule) is the sooner of
|
||||
* - forever
|
||||
* - next period start time
|
||||
* - optional duration
|
||||
* - the start of the next recurrence
|
||||
* - optional validTo
|
||||
*/
|
||||
|
||||
int duration = std::numeric_limits<int>::max(); // forever
|
||||
|
||||
if (has_next_period) {
|
||||
duration = in_profile.chargingSchedule.chargingSchedulePeriod[in_period_index + 1].startPeriod;
|
||||
}
|
||||
|
||||
// check optional chargingSchedule duration field
|
||||
if (in_profile.chargingSchedule.duration && (in_profile.chargingSchedule.duration.value() < duration)) {
|
||||
duration = in_profile.chargingSchedule.duration.value();
|
||||
}
|
||||
|
||||
// check duration doesn't extend into the next recurrence
|
||||
if (has_next_occurrance) {
|
||||
const auto next_start =
|
||||
duration_cast<seconds>(schedule_start[i + 1].to_time_point() - entry_start.to_time_point())
|
||||
.count();
|
||||
if (next_start < duration) {
|
||||
duration = clamp_to<int>(next_start);
|
||||
}
|
||||
}
|
||||
|
||||
// check duration doesn't extend beyond profile validity
|
||||
if (in_profile.validTo) {
|
||||
// note can be negative
|
||||
const auto valid_to = floor_seconds(in_profile.validTo.value());
|
||||
const auto valid_to_seconds =
|
||||
duration_cast<seconds>(valid_to.to_time_point() - entry_start.to_time_point()).count();
|
||||
if (valid_to_seconds < duration) {
|
||||
duration = clamp_to<int>(valid_to_seconds);
|
||||
}
|
||||
}
|
||||
|
||||
period_entry_t entry;
|
||||
entry.init(entry_start, duration, this_period, in_profile);
|
||||
const auto now = floor_seconds(in_now);
|
||||
|
||||
if (entry.validate(in_profile, now)) {
|
||||
entries.push_back(std::move(entry));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
namespace {
|
||||
std::vector<period_entry_t> calculate_profile_unsorted(const DateTime& now, const DateTime& end,
|
||||
const std::optional<DateTime>& session_start,
|
||||
const ChargingProfile& profile) {
|
||||
std::vector<period_entry_t> entries;
|
||||
|
||||
const auto nr_of_entries = profile.chargingSchedule.chargingSchedulePeriod.size();
|
||||
for (std::size_t i = 0; i < nr_of_entries; i++) {
|
||||
const auto results = calculate_profile_entry(now, end, session_start, profile, i);
|
||||
for (const auto& entry : results) {
|
||||
if (entry.start <= end) {
|
||||
entries.push_back(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
void sort_periods_into_date_order(std::vector<period_entry_t>& periods) {
|
||||
// sort into date order
|
||||
const struct {
|
||||
bool operator()(const period_entry_t& a, const period_entry_t& b) const {
|
||||
// earliest first
|
||||
return a.start < b.start;
|
||||
}
|
||||
} less_than;
|
||||
std::sort(periods.begin(), periods.end(), less_than);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::vector<period_entry_t> calculate_profile(const DateTime& now, const DateTime& end,
|
||||
const std::optional<DateTime>& session_start,
|
||||
const ChargingProfile& profile) {
|
||||
std::vector<period_entry_t> entries = calculate_profile_unsorted(now, end, session_start, profile);
|
||||
|
||||
sort_periods_into_date_order(entries);
|
||||
return entries;
|
||||
}
|
||||
|
||||
std::vector<period_entry_t> calculate_all_profiles(const DateTime& now, const DateTime& end,
|
||||
const std::optional<DateTime>& session_start,
|
||||
const std::vector<ChargingProfile>& profiles,
|
||||
ChargingProfilePurposeType purpose) {
|
||||
std::vector<period_entry_t> output;
|
||||
for (const auto& profile : profiles) {
|
||||
if (profile.chargingProfilePurpose == purpose) {
|
||||
std::vector<period_entry_t> periods = calculate_profile_unsorted(now, end, session_start, profile);
|
||||
output.insert(output.end(), periods.begin(), periods.end());
|
||||
}
|
||||
}
|
||||
|
||||
sort_periods_into_date_order(output);
|
||||
return output;
|
||||
}
|
||||
|
||||
IntermediateProfile generate_profile_from_periods(std::vector<period_entry_t>& periods, const DateTime& in_now,
|
||||
const DateTime& in_end) {
|
||||
|
||||
const auto now = floor_seconds(in_now);
|
||||
const auto end = floor_seconds(in_end);
|
||||
|
||||
if (periods.empty()) {
|
||||
return {{0, NO_LIMIT_SPECIFIED, NO_LIMIT_SPECIFIED, 0, 0, std::nullopt, std::nullopt}};
|
||||
}
|
||||
|
||||
// sort the combined_schedules in stack priority order
|
||||
const struct {
|
||||
bool operator()(const period_entry_t& a, const period_entry_t& b) const {
|
||||
// highest stack level first
|
||||
return a.stack_level > b.stack_level;
|
||||
}
|
||||
} less_than;
|
||||
std::sort(periods.begin(), periods.end(), less_than);
|
||||
|
||||
IntermediateProfile combined{};
|
||||
DateTime current = now;
|
||||
|
||||
// run calculation at least once especially where current >= end
|
||||
do {
|
||||
// find schedule to use for time: current
|
||||
DateTime earliest = end;
|
||||
DateTime next_earliest = end;
|
||||
const period_entry_t* chosen{nullptr};
|
||||
|
||||
for (const auto& schedule : periods) {
|
||||
if (schedule.start <= earliest) {
|
||||
// ensure the earlier schedule is valid at the current time
|
||||
if (schedule.end > current) {
|
||||
next_earliest = earliest;
|
||||
earliest = schedule.start;
|
||||
chosen = &schedule;
|
||||
if (earliest <= current) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (earliest > current) {
|
||||
// there is a gap to fill
|
||||
combined.push_back({elapsed_seconds(current, now), NO_LIMIT_SPECIFIED, NO_LIMIT_SPECIFIED, 0, 0,
|
||||
std::nullopt, std::nullopt});
|
||||
current = earliest;
|
||||
} else {
|
||||
// there is a schedule to use
|
||||
float current_limit = NO_LIMIT_SPECIFIED;
|
||||
float power_limit = NO_LIMIT_SPECIFIED;
|
||||
std::int32_t stack_level_current = 0;
|
||||
std::int32_t stack_level_power = 0;
|
||||
|
||||
if (chosen->charging_rate_unit == ChargingRateUnit::A) {
|
||||
current_limit = chosen->limit;
|
||||
stack_level_current = chosen->stack_level;
|
||||
} else {
|
||||
power_limit = chosen->limit;
|
||||
stack_level_power = chosen->stack_level;
|
||||
}
|
||||
|
||||
const IntermediatePeriod charging_schedule_period{
|
||||
elapsed_seconds(current, now), current_limit, power_limit, stack_level_current, stack_level_power,
|
||||
chosen->number_phases, std::nullopt};
|
||||
|
||||
combined.push_back(charging_schedule_period);
|
||||
if (chosen->end < next_earliest) {
|
||||
current = chosen->end;
|
||||
} else {
|
||||
current = next_earliest;
|
||||
}
|
||||
}
|
||||
} while (current < end);
|
||||
|
||||
return combined;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
using period_iterator = IntermediateProfile::const_iterator;
|
||||
using period_pair_vector = std::vector<std::pair<period_iterator, period_iterator>>;
|
||||
using IntermediateProfileRef = std::reference_wrapper<const IntermediateProfile>;
|
||||
|
||||
inline std::vector<IntermediateProfileRef> convert_to_ref_vector(const std::vector<IntermediateProfile>& profiles) {
|
||||
std::vector<IntermediateProfileRef> references{};
|
||||
references.reserve(profiles.size());
|
||||
for (auto& profile : profiles) {
|
||||
references.emplace_back(profile);
|
||||
}
|
||||
return references;
|
||||
}
|
||||
|
||||
IntermediateProfile combine_list_of_profiles(const std::vector<IntermediateProfileRef>& profiles,
|
||||
std::function<IntermediatePeriod(const period_pair_vector&)> combinator) {
|
||||
if (profiles.empty()) {
|
||||
// We should never get here as there are always profiles, otherwise there is a mistake in the calling function
|
||||
// Return an empty profile to be safe
|
||||
return {{0, NO_LIMIT_SPECIFIED, NO_LIMIT_SPECIFIED, 0, 0, std::nullopt, std::nullopt}};
|
||||
}
|
||||
|
||||
IntermediateProfile combined{};
|
||||
|
||||
period_pair_vector profile_iterators{};
|
||||
for (const auto& wrapped_profile : profiles) {
|
||||
auto& profile = wrapped_profile.get();
|
||||
if (!profile.empty()) {
|
||||
profile_iterators.emplace_back(profile.begin(), profile.end());
|
||||
}
|
||||
}
|
||||
|
||||
std::int32_t current_period = 0;
|
||||
while (std::any_of(profile_iterators.begin(), profile_iterators.end(),
|
||||
[](const std::pair<period_iterator, period_iterator>& it) { return it.first != it.second; })) {
|
||||
|
||||
IntermediatePeriod period = combinator(profile_iterators);
|
||||
period.startPeriod = current_period;
|
||||
|
||||
if (combined.empty() || (period.current_limit != combined.back().current_limit) ||
|
||||
(period.power_limit != combined.back().power_limit) ||
|
||||
(period.numberPhases != combined.back().numberPhases) ||
|
||||
(period.stack_level_current != combined.back().stack_level_current) ||
|
||||
(period.stack_level_power != combined.back().stack_level_power)) {
|
||||
combined.push_back(period);
|
||||
}
|
||||
|
||||
// Determine the next earliest period
|
||||
std::int32_t next_lowest_period = std::numeric_limits<std::int32_t>::max();
|
||||
|
||||
for (const auto& [it, end] : profile_iterators) {
|
||||
auto next = it + 1;
|
||||
if (next != end && next->startPeriod > current_period && next->startPeriod < next_lowest_period) {
|
||||
next_lowest_period = next->startPeriod;
|
||||
}
|
||||
}
|
||||
|
||||
// If there is none, we are done
|
||||
if (next_lowest_period == std::numeric_limits<std::int32_t>::max()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Otherwise update to next earliest period
|
||||
for (auto& [it, end] : profile_iterators) {
|
||||
auto next = it + 1;
|
||||
if (next != end && next->startPeriod == next_lowest_period) {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
current_period = next_lowest_period;
|
||||
}
|
||||
|
||||
if (combined.empty()) {
|
||||
combined.push_back({0, NO_LIMIT_SPECIFIED, NO_LIMIT_SPECIFIED, 0, 0, std::nullopt, std::nullopt});
|
||||
}
|
||||
|
||||
return combined;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
IntermediateProfile merge_tx_profile_with_tx_default_profile(const IntermediateProfile& tx_profile,
|
||||
const IntermediateProfile& tx_default_profile) {
|
||||
|
||||
auto combinator = [](const period_pair_vector& periods) {
|
||||
IntermediatePeriod period{};
|
||||
period.current_limit = NO_LIMIT_SPECIFIED;
|
||||
period.power_limit = NO_LIMIT_SPECIFIED;
|
||||
period.stack_level_current = 0;
|
||||
period.stack_level_power = 0;
|
||||
|
||||
for (const auto& [it, end] : periods) {
|
||||
if (it->current_limit != NO_LIMIT_SPECIFIED || it->power_limit != NO_LIMIT_SPECIFIED) {
|
||||
period.current_limit = it->current_limit;
|
||||
period.power_limit = it->power_limit;
|
||||
period.numberPhases = it->numberPhases;
|
||||
period.stack_level_current = it->stack_level_current;
|
||||
period.stack_level_power = it->stack_level_power;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return period;
|
||||
};
|
||||
|
||||
// This ordering together with the combinator will prefer the tx_profile above the default profile
|
||||
const std::vector<IntermediateProfileRef> profiles{tx_profile, tx_default_profile};
|
||||
|
||||
return combine_list_of_profiles(profiles, combinator);
|
||||
}
|
||||
|
||||
IntermediateProfile merge_profiles_by_lowest_limit(const std::vector<IntermediateProfile>& profiles) {
|
||||
auto combinator = [](const period_pair_vector& periods) {
|
||||
IntermediatePeriod period{};
|
||||
period.current_limit = std::numeric_limits<float>::max();
|
||||
period.power_limit = std::numeric_limits<float>::max();
|
||||
|
||||
for (const auto& [it, end] : periods) {
|
||||
if (it->current_limit >= 0.0F && it->current_limit < period.current_limit) {
|
||||
period.current_limit = it->current_limit;
|
||||
period.stack_level_current = it->stack_level_current;
|
||||
}
|
||||
if (it->power_limit >= 0.0F && it->power_limit < period.power_limit) {
|
||||
period.power_limit = it->power_limit;
|
||||
period.stack_level_power = it->stack_level_power;
|
||||
}
|
||||
|
||||
// Copy number of phases if lower
|
||||
if (!period.numberPhases.has_value()) { // NOLINT(bugprone-branch-clone): readability
|
||||
// Don't care if this copies a nullopt, thats what it was already
|
||||
period.numberPhases = it->numberPhases;
|
||||
} else if (it->numberPhases.has_value() && it->numberPhases.value() < period.numberPhases.value()) {
|
||||
period.numberPhases = it->numberPhases;
|
||||
}
|
||||
}
|
||||
|
||||
if (period.current_limit == std::numeric_limits<float>::max()) {
|
||||
period.current_limit = NO_LIMIT_SPECIFIED;
|
||||
period.stack_level_current = 0;
|
||||
}
|
||||
if (period.power_limit == std::numeric_limits<float>::max()) {
|
||||
period.power_limit = NO_LIMIT_SPECIFIED;
|
||||
period.stack_level_power = 0;
|
||||
}
|
||||
|
||||
return period;
|
||||
};
|
||||
|
||||
return combine_list_of_profiles(convert_to_ref_vector(profiles), combinator);
|
||||
}
|
||||
|
||||
IntermediateProfile merge_profiles_by_summing_limits(const std::vector<IntermediateProfile>& profiles,
|
||||
float current_default, float power_default) {
|
||||
auto combinator = [current_default, power_default](const period_pair_vector& periods) {
|
||||
IntermediatePeriod period{};
|
||||
for (const auto& [it, end] : periods) {
|
||||
period.current_limit += it->current_limit >= 0.0F ? it->current_limit : current_default;
|
||||
period.power_limit += it->power_limit >= 0.0F ? it->power_limit : power_default;
|
||||
period.stack_level_current = 0; // Stack level cant be determined when summing intermediate profiles
|
||||
period.stack_level_power = 0; // Stack level cant be determined when summing intermediate profiles
|
||||
|
||||
// Copy number of phases if higher
|
||||
if (!period.numberPhases.has_value()) { // NOLINT(bugprone-branch-clone): readability
|
||||
// Don't care if this copies a nullopt, thats what it was already
|
||||
period.numberPhases = it->numberPhases;
|
||||
} else if (it->numberPhases.has_value() && it->numberPhases.value() > period.numberPhases.value()) {
|
||||
period.numberPhases = it->numberPhases;
|
||||
}
|
||||
}
|
||||
return period;
|
||||
};
|
||||
|
||||
return combine_list_of_profiles(convert_to_ref_vector(profiles), combinator);
|
||||
}
|
||||
|
||||
std::vector<EnhancedChargingSchedulePeriod>
|
||||
convert_intermediate_into_schedule(const IntermediateProfile& profile, ChargingRateUnit charging_rate_unit,
|
||||
float default_limit, std::int32_t default_number_phases, float supply_voltage) {
|
||||
|
||||
std::vector<EnhancedChargingSchedulePeriod> output{};
|
||||
|
||||
for (const auto& period : profile) {
|
||||
EnhancedChargingSchedulePeriod period_out{};
|
||||
period_out.startPeriod = period.startPeriod;
|
||||
period_out.numberPhases = period.numberPhases;
|
||||
|
||||
if (period.current_limit == NO_LIMIT_SPECIFIED && period.power_limit == NO_LIMIT_SPECIFIED) {
|
||||
period_out.limit = default_limit;
|
||||
} else {
|
||||
const float transform_value =
|
||||
supply_voltage * static_cast<float>(period_out.numberPhases.value_or(default_number_phases));
|
||||
period_out.limit = std::numeric_limits<float>::max();
|
||||
if (charging_rate_unit == ChargingRateUnit::A) {
|
||||
if (period.current_limit != NO_LIMIT_SPECIFIED) {
|
||||
period_out.limit = period.current_limit;
|
||||
period_out.stackLevel = period.stack_level_current;
|
||||
period_out.periodTransformed = false;
|
||||
}
|
||||
if (period.power_limit != NO_LIMIT_SPECIFIED) {
|
||||
const auto converted_power_limit = period.power_limit / transform_value;
|
||||
if (converted_power_limit < period_out.limit) {
|
||||
period_out.limit = converted_power_limit;
|
||||
period_out.stackLevel = period.stack_level_power;
|
||||
period_out.periodTransformed = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (period.power_limit != NO_LIMIT_SPECIFIED) {
|
||||
period_out.limit = period.power_limit;
|
||||
period_out.stackLevel = period.stack_level_power;
|
||||
period_out.periodTransformed = false;
|
||||
}
|
||||
if (period.current_limit != NO_LIMIT_SPECIFIED) {
|
||||
const auto converted_current_limit = period.current_limit * transform_value;
|
||||
if (converted_current_limit < period_out.limit) {
|
||||
period_out.limit = converted_current_limit;
|
||||
period_out.stackLevel = period.stack_level_current;
|
||||
period_out.periodTransformed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (output.empty() || (period_out.limit != output.back().limit) ||
|
||||
(period_out.numberPhases != output.back().numberPhases) ||
|
||||
period_out.stackLevel != output.back().stackLevel) {
|
||||
output.push_back(period_out);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
} // namespace ocpp::v16
|
||||
@@ -0,0 +1,664 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "ocpp/v16/charge_point_configuration_interface.hpp"
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <ocpp/common/constants.hpp>
|
||||
#include <ocpp/common/types.hpp>
|
||||
#include <ocpp/common/utils.hpp>
|
||||
#include <ocpp/v16/ocpp_enums.hpp>
|
||||
#include <ocpp/v16/profile.hpp>
|
||||
#include <ocpp/v16/smart_charging.hpp>
|
||||
|
||||
using namespace std::chrono;
|
||||
|
||||
using QueryExecutionException = everest::db::QueryExecutionException;
|
||||
|
||||
const std::int32_t STATION_WIDE_ID = 0;
|
||||
|
||||
namespace {
|
||||
/**
|
||||
* \brief remove expired profiles from the memory map structure and database
|
||||
* \param[in] now - profiles with a validTo time earlier that this are removed
|
||||
* \param[in] db - a reference to the database handler
|
||||
* \param[in,out] map - the map containing charging profiles
|
||||
*/
|
||||
template <class PROFILES>
|
||||
void clear_expired_profiles(const date::utc_clock::time_point& now, ocpp::v16::DatabaseHandler& db, PROFILES& map) {
|
||||
using ocpp::v16::ChargingProfileKindType;
|
||||
|
||||
// check all profiles in the map
|
||||
for (auto it = map.cbegin(); it != map.cend();) {
|
||||
bool remove = false;
|
||||
const auto& profile = it->second;
|
||||
const auto& schedule = it->second.chargingSchedule;
|
||||
|
||||
// check if the profile has expired based on validTo
|
||||
if (profile.validTo) {
|
||||
const auto validTo = profile.validTo.value().to_time_point();
|
||||
if (validTo < now) {
|
||||
remove = true;
|
||||
}
|
||||
}
|
||||
|
||||
// check if the absolute profile has expired based on
|
||||
// startTime + duration
|
||||
if ((profile.chargingProfileKind == ChargingProfileKindType::Absolute) && schedule.startSchedule &&
|
||||
schedule.duration) {
|
||||
const auto duration = std::chrono::seconds(schedule.duration.value());
|
||||
const auto validTo = schedule.startSchedule.value().to_time_point() + duration;
|
||||
if (validTo < now) {
|
||||
remove = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (remove) {
|
||||
// remove expired profile from the database and map
|
||||
// the order of these matters!
|
||||
db.delete_charging_profile(it->second.chargingProfileId);
|
||||
it = map.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
bool validate_schedule(const ChargingSchedule& schedule, const int charging_schedule_max_periods,
|
||||
const std::vector<ChargingRateUnit>& charging_schedule_allowed_charging_rate_units) {
|
||||
|
||||
if (schedule.chargingSchedulePeriod.size() > static_cast<size_t>(charging_schedule_max_periods)) {
|
||||
EVLOG_warning << "INVALID SCHEDULE - Number of chargingSchedulePeriod(s) is greater than configured "
|
||||
"ChargingScheduleMaxPeriods of "
|
||||
<< charging_schedule_max_periods;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (std::find(charging_schedule_allowed_charging_rate_units.begin(),
|
||||
charging_schedule_allowed_charging_rate_units.end(),
|
||||
schedule.chargingRateUnit) == charging_schedule_allowed_charging_rate_units.end()) {
|
||||
EVLOG_warning << "INVALID SCHEDULE - ChargingRateUnit not supported: " << schedule.chargingRateUnit;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!std::isfinite(schedule.minChargingRate.value_or(0.0))) {
|
||||
EVLOG_warning << "INVALID SCHEDULE - Non-finite minChargingRate: " << schedule.minChargingRate.value_or(0.0);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& period : schedule.chargingSchedulePeriod) {
|
||||
if (!std::isfinite(period.limit)) {
|
||||
EVLOG_warning << "INVALID SCHEDULE - Non-finite limit: " << period.limit;
|
||||
return false;
|
||||
}
|
||||
if (period.numberPhases.has_value()) {
|
||||
const auto& number_phases = period.numberPhases.value();
|
||||
if (number_phases <= 0 or number_phases > DEFAULT_AND_MAX_NUMBER_PHASES) {
|
||||
EVLOG_warning << "INVALID SCHEDULE - Invalid number of phases: " << number_phases;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (period.limit < 0) {
|
||||
EVLOG_warning << "INVALID SCHEDULE - Invalid limit: " << period.limit;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
SmartChargingHandler::SmartChargingHandler(std::map<std::int32_t, std::shared_ptr<Connector>>& connectors,
|
||||
std::shared_ptr<DatabaseHandler> database_handler,
|
||||
ChargePointConfigurationInterface& configuration) :
|
||||
connectors(connectors), database_handler(database_handler), configuration(configuration) {
|
||||
this->clear_profiles_timer = std::make_unique<Everest::SteadyTimer>();
|
||||
this->clear_profiles_timer->interval([this]() { this->clear_expired_profiles(date::utc_clock::now()); },
|
||||
hours(HOURS_PER_DAY));
|
||||
}
|
||||
|
||||
void SmartChargingHandler::clear_expired_profiles(const date::utc_clock::time_point& now) {
|
||||
EVLOG_debug << "Scanning all installed profiles and clearing expired profiles";
|
||||
|
||||
// obtain locks - note the order needs to be consistent with other uses
|
||||
const std::lock_guard<std::mutex> lk_cp(charge_point_max_profiles_map_mutex);
|
||||
const std::lock_guard<std::mutex> lk_txd(tx_default_profiles_map_mutex);
|
||||
const std::lock_guard<std::mutex> lk_tx(tx_profiles_map_mutex);
|
||||
|
||||
// check all profile types for expired entries
|
||||
::clear_expired_profiles(now, *database_handler, stack_level_charge_point_max_profiles_map);
|
||||
for (auto& [connector_id, connector] : connectors) {
|
||||
::clear_expired_profiles(now, *database_handler, connector->stack_level_tx_default_profiles_map);
|
||||
::clear_expired_profiles(now, *database_handler, connector->stack_level_tx_profiles_map);
|
||||
}
|
||||
}
|
||||
|
||||
int SmartChargingHandler::get_number_installed_profiles() {
|
||||
int number = 0;
|
||||
|
||||
const std::lock_guard<std::mutex> lk_cp(this->charge_point_max_profiles_map_mutex);
|
||||
const std::lock_guard<std::mutex> lk_txd(this->tx_default_profiles_map_mutex);
|
||||
const std::lock_guard<std::mutex> lk_tx(this->tx_profiles_map_mutex);
|
||||
|
||||
number += clamp_to<int>(this->stack_level_charge_point_max_profiles_map.size());
|
||||
for (const auto& [connector_id, connector] : this->connectors) {
|
||||
number += clamp_to<int>(connector->stack_level_tx_default_profiles_map.size());
|
||||
number += clamp_to<int>(connector->stack_level_tx_profiles_map.size());
|
||||
}
|
||||
|
||||
return number;
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct CompositeScheduleConfig {
|
||||
std::set<ChargingProfilePurposeType> purposes_to_ignore;
|
||||
float current_limit{};
|
||||
float power_limit{};
|
||||
std::int32_t default_number_phases{};
|
||||
float supply_voltage{};
|
||||
|
||||
CompositeScheduleConfig(ChargePointConfigurationInterface& configuration, bool is_offline) {
|
||||
|
||||
if (is_offline) {
|
||||
const auto _purposes_to_ignore = configuration.getIgnoredProfilePurposesOffline();
|
||||
for (const auto purpose : _purposes_to_ignore) {
|
||||
purposes_to_ignore.insert(purpose);
|
||||
}
|
||||
}
|
||||
|
||||
this->current_limit =
|
||||
static_cast<float>(configuration.getCompositeScheduleDefaultLimitAmps().value_or(DEFAULT_LIMIT_AMPS));
|
||||
|
||||
this->power_limit =
|
||||
static_cast<float>(configuration.getCompositeScheduleDefaultLimitWatts().value_or(DEFAULT_LIMIT_WATTS));
|
||||
|
||||
this->default_number_phases =
|
||||
configuration.getCompositeScheduleDefaultNumberPhases().value_or(DEFAULT_AND_MAX_NUMBER_PHASES);
|
||||
|
||||
this->supply_voltage = static_cast<float>(configuration.getSupplyVoltage().value_or(LOW_VOLTAGE));
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<IntermediateProfile> generate_evse_intermediates(std::vector<ChargingProfile>&& evse_profiles,
|
||||
const std::vector<ChargingProfile>& station_wide_profiles,
|
||||
const ocpp::DateTime& start_time,
|
||||
const ocpp::DateTime& end_time,
|
||||
std::optional<ocpp::DateTime> session_start,
|
||||
bool simulate_transaction_active
|
||||
|
||||
) {
|
||||
|
||||
// Combine the profiles with those from the station
|
||||
evse_profiles.insert(evse_profiles.end(), station_wide_profiles.begin(), station_wide_profiles.end());
|
||||
|
||||
std::vector<IntermediateProfile> output;
|
||||
|
||||
// If there is a session active or we want to simulate, add the combined tx and tx_default to the output
|
||||
if (session_start.has_value() || simulate_transaction_active) {
|
||||
auto tx_default_periods = calculate_all_profiles(start_time, end_time, session_start, evse_profiles,
|
||||
ChargingProfilePurposeType::TxDefaultProfile);
|
||||
auto tx_periods = calculate_all_profiles(start_time, end_time, session_start, evse_profiles,
|
||||
ChargingProfilePurposeType::TxProfile);
|
||||
|
||||
auto tx_default = generate_profile_from_periods(tx_default_periods, start_time, end_time);
|
||||
auto tx = generate_profile_from_periods(tx_periods, start_time, end_time);
|
||||
|
||||
// Merges the TxProfile with the TxDefaultProfile, for every period preferring a tx period over a tx_default
|
||||
// period
|
||||
output.push_back(merge_tx_profile_with_tx_default_profile(tx, tx_default));
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ChargingSchedule SmartChargingHandler::calculate_composite_schedule(const ocpp::DateTime& start_time,
|
||||
const ocpp::DateTime& end_time,
|
||||
const std::int32_t evse_id,
|
||||
ChargingRateUnit charging_rate_unit,
|
||||
bool is_offline, bool simulate_transaction_active) {
|
||||
// handle edge case where start_time > end_time
|
||||
auto start_time_w = start_time;
|
||||
if (start_time_w > end_time) {
|
||||
start_time_w = end_time;
|
||||
}
|
||||
|
||||
const auto enhanced_composite_schedule = this->calculate_enhanced_composite_schedule(
|
||||
start_time_w, end_time, evse_id, charging_rate_unit, is_offline, simulate_transaction_active);
|
||||
ChargingSchedule composite_schedule;
|
||||
composite_schedule.chargingRateUnit = enhanced_composite_schedule.chargingRateUnit;
|
||||
composite_schedule.duration = enhanced_composite_schedule.duration;
|
||||
composite_schedule.startSchedule = enhanced_composite_schedule.startSchedule;
|
||||
composite_schedule.minChargingRate = enhanced_composite_schedule.minChargingRate;
|
||||
for (const auto enhanced_period : enhanced_composite_schedule.chargingSchedulePeriod) {
|
||||
ChargingSchedulePeriod period;
|
||||
period.startPeriod = enhanced_period.startPeriod;
|
||||
period.limit = enhanced_period.limit;
|
||||
period.numberPhases = enhanced_period.numberPhases;
|
||||
composite_schedule.chargingSchedulePeriod.push_back(period);
|
||||
}
|
||||
return composite_schedule;
|
||||
}
|
||||
|
||||
EnhancedChargingSchedule SmartChargingHandler::calculate_enhanced_composite_schedule(
|
||||
const ocpp::DateTime& start_time, const ocpp::DateTime& end_time, const std::int32_t evse_id,
|
||||
ChargingRateUnit charging_rate_unit, bool is_offline, bool simulate_transaction_active) {
|
||||
|
||||
const CompositeScheduleConfig config{this->configuration, is_offline};
|
||||
|
||||
std::optional<ocpp::DateTime> session_start{};
|
||||
|
||||
if (const auto& itt = connectors.find(evse_id); itt != connectors.end()) {
|
||||
// connector exists!
|
||||
if (itt->second->transaction) {
|
||||
session_start.emplace(ocpp::DateTime(
|
||||
floor<seconds>(itt->second->transaction->get_start_energy_wh()->timestamp.to_time_point())));
|
||||
}
|
||||
}
|
||||
|
||||
const auto station_wide_profiles =
|
||||
get_valid_profiles(start_time, end_time, STATION_WIDE_ID, config.purposes_to_ignore);
|
||||
|
||||
std::vector<IntermediateProfile> combined_profiles{};
|
||||
|
||||
if (evse_id == STATION_WIDE_ID) {
|
||||
auto nr_of_evses = this->connectors.size() - 1;
|
||||
|
||||
// Get the ChargingStationExternalConstraints and Combined Tx(Default)Profiles per evse
|
||||
std::vector<IntermediateProfile> evse_schedules{};
|
||||
for (int evse = 1; evse <= nr_of_evses; evse++) {
|
||||
session_start.reset();
|
||||
auto transaction = this->connectors.at(evse)->transaction;
|
||||
if (transaction != nullptr) {
|
||||
session_start = transaction->get_start_energy_wh()->timestamp;
|
||||
}
|
||||
auto intermediates = generate_evse_intermediates(
|
||||
get_valid_profiles(start_time, end_time, evse, config.purposes_to_ignore), station_wide_profiles,
|
||||
start_time, end_time, session_start, simulate_transaction_active);
|
||||
|
||||
// Determine the lowest limits per evse
|
||||
evse_schedules.push_back(merge_profiles_by_lowest_limit(intermediates));
|
||||
}
|
||||
|
||||
// Add all the limits of all the evse's together since that will be the max the whole charging station can
|
||||
// consume at any point in time
|
||||
combined_profiles.push_back(
|
||||
merge_profiles_by_summing_limits(evse_schedules, config.current_limit, config.power_limit));
|
||||
|
||||
} else {
|
||||
combined_profiles = generate_evse_intermediates(
|
||||
get_valid_profiles(start_time, end_time, evse_id, config.purposes_to_ignore), station_wide_profiles,
|
||||
start_time, end_time, session_start, simulate_transaction_active);
|
||||
}
|
||||
|
||||
// ChargingStationMaxProfile is always station wide
|
||||
auto charge_point_max_periods = calculate_all_profiles(start_time, end_time, session_start, station_wide_profiles,
|
||||
ChargingProfilePurposeType::ChargePointMaxProfile);
|
||||
auto charge_point_max = generate_profile_from_periods(charge_point_max_periods, start_time, end_time);
|
||||
|
||||
// Add the ChargingStationMaxProfile limits to the other profiles
|
||||
combined_profiles.push_back(std::move(charge_point_max));
|
||||
|
||||
// Calculate the final limit of all the combined profiles
|
||||
auto retval = merge_profiles_by_lowest_limit(combined_profiles);
|
||||
|
||||
EnhancedChargingSchedule composite{};
|
||||
composite.startSchedule = floor_seconds(start_time);
|
||||
composite.duration = elapsed_seconds(floor_seconds(end_time), floor_seconds(start_time));
|
||||
composite.chargingRateUnit = charging_rate_unit;
|
||||
|
||||
// Convert the intermediate result into a proper schedule. Will fill in the periods with no limits with the
|
||||
// default one
|
||||
const auto limit = charging_rate_unit == ChargingRateUnit::A ? config.current_limit : config.power_limit;
|
||||
composite.chargingSchedulePeriod = convert_intermediate_into_schedule(
|
||||
retval, charging_rate_unit, limit, config.default_number_phases, config.supply_voltage);
|
||||
|
||||
return composite;
|
||||
}
|
||||
|
||||
bool SmartChargingHandler::validate_profile(
|
||||
ChargingProfile& profile, const int connector_id, bool ignore_no_transaction, const int profile_max_stack_level,
|
||||
const int max_charging_profiles_installed, const int charging_schedule_max_periods,
|
||||
const std::vector<ChargingRateUnit>& charging_schedule_allowed_charging_rate_units) {
|
||||
if (static_cast<size_t>(connector_id) >= this->connectors.size() or connector_id < 0 or profile.stackLevel < 0 or
|
||||
profile.stackLevel > profile_max_stack_level) {
|
||||
EVLOG_warning << "INVALID PROFILE - connector_id invalid or invalid stack level";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (profile.chargingProfileKind == ChargingProfileKindType::Absolute && !profile.chargingSchedule.startSchedule) {
|
||||
EVLOG_warning << "INVALID PROFILE - Absolute Profile Kind with no given startSchedule";
|
||||
if (this->configuration.getAllowChargingProfileWithoutStartSchedule().value_or(false)) {
|
||||
EVLOG_warning << "Allowing profile because AllowChargingProfileWithoutStartSchedule is configured";
|
||||
profile.chargingSchedule.startSchedule = ocpp::DateTime();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->get_number_installed_profiles() >= max_charging_profiles_installed) {
|
||||
EVLOG_warning << "Maximum amount of profiles are installed at the charging point";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!validate_schedule(profile.chargingSchedule, charging_schedule_max_periods,
|
||||
charging_schedule_allowed_charging_rate_units)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (profile.chargingProfileKind == ChargingProfileKindType::Recurring) {
|
||||
if (!profile.recurrencyKind) {
|
||||
EVLOG_warning << "INVALID PROFILE - Recurring Profile Kind with no given RecurrencyKind";
|
||||
return false;
|
||||
}
|
||||
if (!profile.chargingSchedule.startSchedule) {
|
||||
EVLOG_warning << "INVALID PROFILE - Recurring Profile Kind with no startSchedule";
|
||||
if (this->configuration.getAllowChargingProfileWithoutStartSchedule().value_or(false)) {
|
||||
EVLOG_warning << "Allowing profile because AllowChargingProfileWithoutStartSchedule is configured";
|
||||
profile.chargingSchedule.startSchedule = ocpp::DateTime();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (profile.chargingSchedule.duration) {
|
||||
const int max_recurrency_duration =
|
||||
profile.recurrencyKind == RecurrencyKindType::Daily ? SECONDS_PER_DAY : SECONDS_PER_DAY * DAYS_PER_WEEK;
|
||||
|
||||
if (profile.chargingSchedule.duration > max_recurrency_duration) {
|
||||
EVLOG_warning << "Given duration of Recurring profile was > than max_recurrency_duration. Setting "
|
||||
"duration of "
|
||||
"schedule to max_currency_duration";
|
||||
profile.chargingSchedule.duration = max_recurrency_duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (profile.chargingProfilePurpose == ChargingProfilePurposeType::ChargePointMaxProfile) {
|
||||
if (connector_id == 0 and profile.chargingProfileKind != ChargingProfileKindType::Relative) {
|
||||
return true;
|
||||
}
|
||||
EVLOG_warning << "INVALID PROFILE - connector_id != 0 with purpose ChargePointMaxProfile or kind is Relative";
|
||||
return false;
|
||||
}
|
||||
if (profile.chargingProfilePurpose == ChargingProfilePurposeType::TxDefaultProfile) {
|
||||
return true;
|
||||
}
|
||||
if (profile.chargingProfilePurpose == ChargingProfilePurposeType::TxProfile) {
|
||||
if (connector_id == 0) {
|
||||
EVLOG_warning << "INVALID PROFILE - connector_id is 0";
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& connector = this->connectors.at(connector_id);
|
||||
|
||||
if (connector->transaction == nullptr && !ignore_no_transaction) {
|
||||
EVLOG_warning << "INVALID PROFILE - No active transaction at this connector";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (profile.transactionId.has_value()) {
|
||||
if (connector->transaction == nullptr) {
|
||||
EVLOG_warning << "INVALID PROFILE - profile.transaction_id is present but no transaction is active at "
|
||||
"this connector";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (connector->transaction->get_transaction_id() != profile.transactionId) {
|
||||
EVLOG_warning << "INVALID PROFILE - transaction_id doesn't match for purpose TxProfile";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SmartChargingHandler::add_charge_point_max_profile(const ChargingProfile& profile) {
|
||||
const std::lock_guard<std::mutex> lk(this->charge_point_max_profiles_map_mutex);
|
||||
this->stack_level_charge_point_max_profiles_map[profile.stackLevel] = profile;
|
||||
try {
|
||||
this->database_handler->insert_or_update_charging_profile(0, profile);
|
||||
} catch (const QueryExecutionException& e) {
|
||||
EVLOG_warning << "Could not store ChargePointMaxProfile in the database: " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
void SmartChargingHandler::add_tx_default_profile(const ChargingProfile& profile, const int connector_id) {
|
||||
const std::lock_guard<std::mutex> lk(this->tx_default_profiles_map_mutex);
|
||||
if (connector_id == 0) {
|
||||
for (size_t id = 1; id <= this->connectors.size() - 1; id++) {
|
||||
this->connectors.at(clamp_to<int>(id))->stack_level_tx_default_profiles_map[profile.stackLevel] = profile;
|
||||
}
|
||||
} else {
|
||||
this->connectors.at(connector_id)->stack_level_tx_default_profiles_map[profile.stackLevel] = profile;
|
||||
}
|
||||
try {
|
||||
this->database_handler->insert_or_update_charging_profile(connector_id, profile);
|
||||
} catch (const QueryExecutionException& e) {
|
||||
EVLOG_warning << "Could not store TxDefaultProfile for connector id " << connector_id
|
||||
<< " in the database: " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
void SmartChargingHandler::add_tx_profile(const ChargingProfile& profile, const int connector_id) {
|
||||
const std::lock_guard<std::mutex> lk(this->tx_profiles_map_mutex);
|
||||
this->connectors.at(connector_id)->stack_level_tx_profiles_map[profile.stackLevel] = profile;
|
||||
try {
|
||||
this->database_handler->insert_or_update_charging_profile(connector_id, profile);
|
||||
} catch (const QueryExecutionException& e) {
|
||||
EVLOG_warning << "Could not store TxProfile in the database: " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
bool SmartChargingHandler::clear_profiles(std::map<std::int32_t, ChargingProfile>& stack_level_profiles_map,
|
||||
std::optional<int> profile_id_opt, std::optional<int> connector_id_opt,
|
||||
const int connector_id, std::optional<int> stack_level_opt,
|
||||
std::optional<ChargingProfilePurposeType> charging_profile_purpose_opt,
|
||||
bool check_id_only) {
|
||||
bool erased_at_least_one = false;
|
||||
|
||||
for (auto it = stack_level_profiles_map.cbegin(); it != stack_level_profiles_map.cend();) {
|
||||
if (profile_id_opt && it->second.chargingProfileId == profile_id_opt.value()) {
|
||||
EVLOG_info << "Clearing ChargingProfile with id: " << it->second.chargingProfileId;
|
||||
try {
|
||||
this->database_handler->delete_charging_profile(it->second.chargingProfileId);
|
||||
} catch (const QueryExecutionException& e) {
|
||||
EVLOG_warning << "Could not delete ChargingProfile from the database: " << e.what();
|
||||
}
|
||||
stack_level_profiles_map.erase(it++);
|
||||
erased_at_least_one = true;
|
||||
} else if (!check_id_only and (!connector_id_opt or connector_id_opt.value() == connector_id) and
|
||||
(!stack_level_opt or stack_level_opt.value() == it->first) and
|
||||
(!charging_profile_purpose_opt or
|
||||
charging_profile_purpose_opt.value() == it->second.chargingProfilePurpose)) {
|
||||
EVLOG_info << "Clearing ChargingProfile with id: " << it->second.chargingProfileId;
|
||||
try {
|
||||
this->database_handler->delete_charging_profile(it->second.chargingProfileId);
|
||||
} catch (const QueryExecutionException& e) {
|
||||
EVLOG_warning << "Could not delete ChargingProfile from the database: " << e.what();
|
||||
}
|
||||
stack_level_profiles_map.erase(it++);
|
||||
erased_at_least_one = true;
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
return erased_at_least_one;
|
||||
}
|
||||
|
||||
bool SmartChargingHandler::clear_all_profiles_with_filter(
|
||||
std::optional<int> profile_id_opt, std::optional<int> connector_id_opt, std::optional<int> stack_level_opt,
|
||||
std::optional<ChargingProfilePurposeType> charging_profile_purpose_opt, bool check_id_only) {
|
||||
|
||||
// for ChargePointMaxProfile
|
||||
auto erased_charge_point_max_profile =
|
||||
this->clear_profiles(this->stack_level_charge_point_max_profiles_map, profile_id_opt, connector_id_opt, 0,
|
||||
stack_level_opt, charging_profile_purpose_opt, check_id_only);
|
||||
|
||||
bool erased_at_least_one_tx_profile = false;
|
||||
|
||||
// TxDefaultProfiles installed via SetChargingProfile(connectorId=0) are fanned out into
|
||||
// every physical connector's map at install time (see add_tx_default_profile). A
|
||||
// ClearChargingProfile(connectorId=0) therefore cannot match them via the per-connector
|
||||
// loop below. connector_id_opt=0 never equals any physical connector id. Use the DB,
|
||||
// which keeps the install-time connector id, to enumerate those profile ids and erase
|
||||
// every fan-out copy along with the DB row.
|
||||
if (not profile_id_opt.has_value() and connector_id_opt.value_or(1) == 0) {
|
||||
const auto ids_at_charge_point = this->database_handler->get_charging_profile_ids_by_connector_id(0);
|
||||
|
||||
// An absent optional means "no filter"; a present optional requires an exact match.
|
||||
// ChargePointMaxProfile rows are handled by the earlier per-map clear; ids absent from
|
||||
// every TxDefault map either fail these filters or belong to another purpose and are
|
||||
// skipped here intentionally.
|
||||
const auto stack_matches = [&](const ChargingProfile& profile) {
|
||||
return not stack_level_opt.has_value() or stack_level_opt.value() == profile.stackLevel;
|
||||
};
|
||||
const auto purpose_matches = [&](const ChargingProfile& profile) {
|
||||
return not charging_profile_purpose_opt.has_value() or
|
||||
charging_profile_purpose_opt.value() == profile.chargingProfilePurpose;
|
||||
};
|
||||
const auto id_has_matching_tx_default = [&](int id) {
|
||||
for (const auto& [_, connector] : this->connectors) {
|
||||
for (const auto& [_, profile] : connector->stack_level_tx_default_profiles_map) {
|
||||
if (profile.chargingProfileId == id and stack_matches(profile) and purpose_matches(profile)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
for (const auto id : ids_at_charge_point) {
|
||||
if (not id_has_matching_tx_default(id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Erase every fan-out copy across connectors and drop the DB row.
|
||||
bool erased_any_copy = false;
|
||||
for (auto& [_, connector] : this->connectors) {
|
||||
auto& map = connector->stack_level_tx_default_profiles_map;
|
||||
for (auto it = map.begin(); it != map.end();) {
|
||||
if (it->second.chargingProfileId == id) {
|
||||
it = map.erase(it);
|
||||
erased_any_copy = true;
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (erased_any_copy) {
|
||||
erased_at_least_one_tx_profile = true;
|
||||
EVLOG_info << "Clearing ChargingProfile with id: " << id;
|
||||
try {
|
||||
this->database_handler->delete_charging_profile(id);
|
||||
} catch (const QueryExecutionException& e) {
|
||||
EVLOG_warning << "Could not delete ChargingProfile from the database: " << e.what();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& [connector_id, connector] : this->connectors) {
|
||||
// for TxDefaultProfiles
|
||||
auto tmp_erased_tx_default_profile =
|
||||
this->clear_profiles(connector->stack_level_tx_default_profiles_map, profile_id_opt, connector_id_opt,
|
||||
connector_id, stack_level_opt, charging_profile_purpose_opt, check_id_only);
|
||||
// for TxProfiles
|
||||
auto tmp_erased_tx_profile =
|
||||
this->clear_profiles(connector->stack_level_tx_profiles_map, profile_id_opt, connector_id_opt, connector_id,
|
||||
stack_level_opt, charging_profile_purpose_opt, check_id_only);
|
||||
|
||||
if (!erased_at_least_one_tx_profile and (tmp_erased_tx_profile or tmp_erased_tx_default_profile)) {
|
||||
erased_at_least_one_tx_profile = true;
|
||||
}
|
||||
}
|
||||
return erased_charge_point_max_profile or erased_at_least_one_tx_profile;
|
||||
}
|
||||
|
||||
void SmartChargingHandler::clear_all_profiles() {
|
||||
EVLOG_info << "Clearing all charging profiles";
|
||||
const std::lock_guard<std::mutex> lk_cp(this->charge_point_max_profiles_map_mutex);
|
||||
const std::lock_guard<std::mutex> lk_txd(this->tx_default_profiles_map_mutex);
|
||||
const std::lock_guard<std::mutex> lk_tx(this->tx_profiles_map_mutex);
|
||||
this->stack_level_charge_point_max_profiles_map.clear();
|
||||
|
||||
for (auto& [connector_id, connector] : this->connectors) {
|
||||
connector->stack_level_tx_default_profiles_map.clear();
|
||||
connector->stack_level_tx_profiles_map.clear();
|
||||
}
|
||||
|
||||
try {
|
||||
this->database_handler->delete_charging_profiles();
|
||||
} catch (const QueryExecutionException& e) {
|
||||
EVLOG_warning << "Could not delete ChargingProfile from the database: " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ChargingProfile>
|
||||
SmartChargingHandler::get_valid_profiles(const ocpp::DateTime& /*start_time*/, const ocpp::DateTime& /*end_time*/,
|
||||
const int connector_id,
|
||||
const std::set<ChargingProfilePurposeType>& purposes_to_ignore) {
|
||||
std::vector<ChargingProfile> valid_profiles;
|
||||
|
||||
{
|
||||
const std::lock_guard<std::mutex> lk(charge_point_max_profiles_map_mutex);
|
||||
|
||||
if (std::find(std::begin(purposes_to_ignore), std::end(purposes_to_ignore),
|
||||
ChargingProfilePurposeType::ChargePointMaxProfile) == std::end(purposes_to_ignore)) {
|
||||
for (const auto& [stack_level, profile] : stack_level_charge_point_max_profiles_map) {
|
||||
valid_profiles.push_back(profile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (connector_id > 0) {
|
||||
// tx_default profiles added to connector 0 are copied to every connector
|
||||
|
||||
std::optional<std::int32_t> transactionId;
|
||||
const auto& itt = connectors.find(connector_id);
|
||||
if (itt != connectors.end()) {
|
||||
// connector exists!
|
||||
|
||||
if (itt->second->transaction) {
|
||||
transactionId = itt->second->transaction->get_transaction_id();
|
||||
}
|
||||
|
||||
const std::lock_guard<std::mutex> lk_txd(tx_default_profiles_map_mutex);
|
||||
const std::lock_guard<std::mutex> lk_tx(tx_profiles_map_mutex);
|
||||
|
||||
if (std::find(std::begin(purposes_to_ignore), std::end(purposes_to_ignore),
|
||||
ChargingProfilePurposeType::TxProfile) == std::end(purposes_to_ignore)) {
|
||||
for (const auto& [stack_level, profile] : itt->second->stack_level_tx_profiles_map) {
|
||||
// only include profiles that match the transactionId (when there is one)
|
||||
bool b_add{false};
|
||||
|
||||
if (profile.transactionId) {
|
||||
if ((transactionId) && (transactionId.value() == profile.transactionId.value())) {
|
||||
// there is a session/transaction in progress and the ID matches the profile
|
||||
b_add = true;
|
||||
}
|
||||
} else {
|
||||
// profile doesn't have a transaction ID specified
|
||||
b_add = true;
|
||||
}
|
||||
|
||||
if (b_add) {
|
||||
valid_profiles.push_back(profile);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (std::find(std::begin(purposes_to_ignore), std::end(purposes_to_ignore),
|
||||
ChargingProfilePurposeType::TxDefaultProfile) == std::end(purposes_to_ignore)) {
|
||||
for (const auto& [stack_level, profile] : itt->second->stack_level_tx_default_profiles_map) {
|
||||
valid_profiles.push_back(profile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return valid_profiles;
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
276
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v16/transaction.cpp
Normal file
276
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v16/transaction.cpp
Normal file
@@ -0,0 +1,276 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <mutex>
|
||||
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
#include <ocpp/v16/transaction.hpp>
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
Transaction::Transaction(const std::int32_t internal_transaction_id, const std::int32_t& connector,
|
||||
const std::string& session_id, const CiString<20>& id_token, const double meter_start,
|
||||
std::optional<std::int32_t> reservation_id, const ocpp::DateTime& timestamp,
|
||||
std::unique_ptr<Everest::SteadyTimer> meter_values_sample_timer) :
|
||||
internal_transaction_id(internal_transaction_id),
|
||||
connector(connector),
|
||||
session_id(session_id),
|
||||
id_token(id_token),
|
||||
start_energy_wh(std::make_shared<StampedEnergyWh>(timestamp, meter_start)),
|
||||
reservation_id(reservation_id),
|
||||
active(true),
|
||||
finished(false),
|
||||
has_signed_meter_values(false),
|
||||
meter_values_sample_timer(std::move(meter_values_sample_timer)) {
|
||||
}
|
||||
|
||||
std::int32_t Transaction::get_connector() const {
|
||||
return this->connector;
|
||||
}
|
||||
|
||||
CiString<20> Transaction::get_id_tag() {
|
||||
return this->id_token;
|
||||
}
|
||||
|
||||
void Transaction::add_meter_value(MeterValue meter_value) {
|
||||
if (this->active) {
|
||||
const std::lock_guard<std::mutex> lock(this->meter_values_mutex);
|
||||
this->meter_values.push_back(meter_value);
|
||||
|
||||
if (std::find_if(meter_value.sampledValue.begin(), meter_value.sampledValue.end(),
|
||||
[](const SampledValue& SampledValueItem) {
|
||||
return SampledValueItem.format == ValueFormat::SignedData;
|
||||
}) != meter_value.sampledValue.end()) {
|
||||
this->set_has_signed_meter_values();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<MeterValue> Transaction::get_meter_values() {
|
||||
const std::lock_guard<std::mutex> lock(this->meter_values_mutex);
|
||||
return this->meter_values;
|
||||
}
|
||||
|
||||
bool Transaction::change_meter_values_sample_interval(std::int32_t interval) {
|
||||
this->meter_values_sample_timer->interval(std::chrono::seconds(interval));
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<std::int32_t> Transaction::get_transaction_id() {
|
||||
return this->transaction_id;
|
||||
}
|
||||
|
||||
std::int32_t Transaction::get_internal_transaction_id() const {
|
||||
return this->internal_transaction_id;
|
||||
}
|
||||
|
||||
std::string Transaction::get_session_id() {
|
||||
return this->session_id;
|
||||
}
|
||||
|
||||
void Transaction::set_start_transaction_message_id(const std::string& message_id) {
|
||||
this->start_transaction_message_id = message_id;
|
||||
}
|
||||
|
||||
std::string Transaction::get_start_transaction_message_id() {
|
||||
return this->start_transaction_message_id;
|
||||
}
|
||||
|
||||
void Transaction::set_stop_transaction_message_id(const std::string& message_id) {
|
||||
this->stop_transaction_message_id = message_id;
|
||||
}
|
||||
|
||||
std::string Transaction::get_stop_transaction_message_id() {
|
||||
return this->stop_transaction_message_id;
|
||||
}
|
||||
|
||||
void Transaction::set_transaction_id(std::int32_t transaction_id) {
|
||||
this->transaction_id = transaction_id;
|
||||
}
|
||||
|
||||
std::vector<TransactionData> Transaction::get_transaction_data() {
|
||||
std::vector<TransactionData> transaction_data_vec;
|
||||
for (const auto& meter_value : this->get_meter_values()) {
|
||||
TransactionData transaction_data;
|
||||
transaction_data.timestamp = meter_value.timestamp;
|
||||
transaction_data.sampledValue = meter_value.sampledValue;
|
||||
transaction_data_vec.push_back(transaction_data);
|
||||
}
|
||||
return transaction_data_vec;
|
||||
}
|
||||
|
||||
void Transaction::stop() {
|
||||
if (this->meter_values_sample_timer != nullptr) {
|
||||
this->meter_values_sample_timer->stop();
|
||||
}
|
||||
this->active = false;
|
||||
}
|
||||
|
||||
bool Transaction::is_active() const {
|
||||
return this->active;
|
||||
}
|
||||
|
||||
bool Transaction::is_finished() const {
|
||||
return this->finished;
|
||||
}
|
||||
|
||||
void Transaction::set_finished() {
|
||||
this->finished = true;
|
||||
}
|
||||
|
||||
std::shared_ptr<StampedEnergyWh> Transaction::get_start_energy_wh() {
|
||||
return this->start_energy_wh;
|
||||
}
|
||||
|
||||
void Transaction::add_stop_energy_wh(std::shared_ptr<StampedEnergyWh> stop_energy_wh) {
|
||||
this->stop_energy_wh = stop_energy_wh;
|
||||
this->stop();
|
||||
}
|
||||
|
||||
void Transaction::set_has_signed_meter_values() {
|
||||
this->has_signed_meter_values = true;
|
||||
}
|
||||
|
||||
bool Transaction::get_has_signed_meter_values() {
|
||||
const std::lock_guard<std::mutex> lock(this->meter_values_mutex);
|
||||
return this->has_signed_meter_values;
|
||||
}
|
||||
|
||||
std::shared_ptr<StampedEnergyWh> Transaction::get_stop_energy_wh() {
|
||||
return this->stop_energy_wh;
|
||||
}
|
||||
|
||||
std::optional<std::int32_t> Transaction::get_reservation_id() {
|
||||
return this->reservation_id;
|
||||
}
|
||||
|
||||
TransactionHandler::TransactionHandler(std::int32_t number_of_connectors) :
|
||||
number_of_connectors(number_of_connectors),
|
||||
gen(std::random_device{}()),
|
||||
distr(std::numeric_limits<int>::min(), -1) {
|
||||
for (std::int32_t i = 0; i < number_of_connectors + 1; i++) {
|
||||
this->active_transactions.push_back(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
std::int32_t TransactionHandler::get_negative_random_transaction_id() {
|
||||
return distr(this->gen);
|
||||
}
|
||||
|
||||
void TransactionHandler::add_transaction(std::shared_ptr<Transaction> transaction) {
|
||||
const std::lock_guard<std::mutex> lk(this->active_transactions_mutex);
|
||||
this->active_transactions.at(transaction->get_connector()) = std::move(transaction);
|
||||
}
|
||||
|
||||
void TransactionHandler::add_stopped_transaction(std::int32_t connector) {
|
||||
this->stopped_transactions.push_back(std::move(this->active_transactions.at(connector)));
|
||||
}
|
||||
|
||||
bool TransactionHandler::remove_active_transaction(std::int32_t connector) {
|
||||
if (connector == 0) {
|
||||
EVLOG_warning << "Attempting to remove a transaction on connector 0, this is not supported.";
|
||||
return false;
|
||||
}
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(this->active_transactions_mutex);
|
||||
this->active_transactions.at(connector) = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void TransactionHandler::erase_stopped_transaction(std::string stop_transaction_message_id) {
|
||||
this->stopped_transactions.erase(
|
||||
std::remove_if(this->stopped_transactions.begin(), this->stopped_transactions.end(),
|
||||
[stop_transaction_message_id](std::shared_ptr<Transaction>& transaction) {
|
||||
return transaction->get_stop_transaction_message_id() == stop_transaction_message_id;
|
||||
}),
|
||||
this->stopped_transactions.end());
|
||||
}
|
||||
|
||||
std::shared_ptr<Transaction> TransactionHandler::get_transaction(std::int32_t connector) {
|
||||
const std::lock_guard<std::mutex> lock(this->active_transactions_mutex);
|
||||
if (this->active_transactions.at(connector) == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return this->active_transactions.at(connector);
|
||||
}
|
||||
|
||||
std::shared_ptr<Transaction> TransactionHandler::get_transaction(const std::string& transaction_message_id) {
|
||||
const std::lock_guard<std::mutex> lock(this->active_transactions_mutex);
|
||||
for (const auto& transaction : this->active_transactions) {
|
||||
if (transaction != nullptr && (transaction->get_start_transaction_message_id() == transaction_message_id or
|
||||
transaction->get_stop_transaction_message_id() == transaction_message_id)) {
|
||||
return transaction;
|
||||
}
|
||||
}
|
||||
for (auto transaction : this->stopped_transactions) {
|
||||
if (transaction->get_start_transaction_message_id() == transaction_message_id or
|
||||
transaction->get_stop_transaction_message_id() == transaction_message_id) {
|
||||
return transaction;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<Transaction> TransactionHandler::get_transaction_from_id_tag(const std::string& id_tag) {
|
||||
const std::lock_guard<std::mutex> lock(this->active_transactions_mutex);
|
||||
for (const auto& transaction : this->active_transactions) {
|
||||
if (transaction != nullptr && (transaction->get_id_tag().get() == id_tag)) {
|
||||
return transaction;
|
||||
}
|
||||
}
|
||||
for (auto transaction : this->stopped_transactions) {
|
||||
if (transaction->get_id_tag().get() == id_tag) {
|
||||
return transaction;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::int32_t TransactionHandler::get_connector_from_transaction_id(std::int32_t transaction_id) {
|
||||
const std::lock_guard<std::mutex> lock(this->active_transactions_mutex);
|
||||
std::int32_t index = 0;
|
||||
for (auto& transaction : this->active_transactions) {
|
||||
if (transaction != nullptr) {
|
||||
if (transaction->get_transaction_id() == transaction_id) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void TransactionHandler::add_meter_value(std::int32_t connector, const MeterValue& meter_value) {
|
||||
const std::lock_guard<std::mutex> lock(this->active_transactions_mutex);
|
||||
if (this->active_transactions.at(connector) == nullptr) {
|
||||
return;
|
||||
}
|
||||
this->active_transactions.at(connector)->add_meter_value(meter_value);
|
||||
}
|
||||
|
||||
void TransactionHandler::change_meter_values_sample_intervals(std::int32_t interval) {
|
||||
const std::lock_guard<std::mutex> lock(this->active_transactions_mutex);
|
||||
for (auto& transaction : this->active_transactions) {
|
||||
if (transaction != nullptr && transaction->is_active()) {
|
||||
transaction->change_meter_values_sample_interval(interval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<CiString<20>> TransactionHandler::get_authorized_id_tag(const std::string& stop_transaction_message_id) {
|
||||
for (const auto& transaction : this->stopped_transactions) {
|
||||
if (transaction->get_stop_transaction_message_id() == stop_transaction_message_id) {
|
||||
transaction->get_id_tag();
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool TransactionHandler::transaction_active(std::int32_t connector) {
|
||||
return this->get_transaction(connector) != nullptr && this->get_transaction(connector)->is_active();
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
631
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v16/types.cpp
Normal file
631
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v16/types.cpp
Normal file
@@ -0,0 +1,631 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <ocpp/v16/types.hpp>
|
||||
|
||||
namespace ocpp {
|
||||
namespace v16 {
|
||||
|
||||
namespace conversions {
|
||||
std::string messagetype_to_string(MessageType m) {
|
||||
switch (m) {
|
||||
case MessageType::Authorize:
|
||||
return "Authorize";
|
||||
case MessageType::AuthorizeResponse:
|
||||
return "AuthorizeResponse";
|
||||
case MessageType::BootNotification:
|
||||
return "BootNotification";
|
||||
case MessageType::BootNotificationResponse:
|
||||
return "BootNotificationResponse";
|
||||
case MessageType::CancelReservation:
|
||||
return "CancelReservation";
|
||||
case MessageType::CancelReservationResponse:
|
||||
return "CancelReservationResponse";
|
||||
case MessageType::CertificateSigned:
|
||||
return "CertificateSigned";
|
||||
case MessageType::CertificateSignedResponse:
|
||||
return "CertificateSignedResponse";
|
||||
case MessageType::ChangeAvailability:
|
||||
return "ChangeAvailability";
|
||||
case MessageType::ChangeAvailabilityResponse:
|
||||
return "ChangeAvailabilityResponse";
|
||||
case MessageType::ChangeConfiguration:
|
||||
return "ChangeConfiguration";
|
||||
case MessageType::ChangeConfigurationResponse:
|
||||
return "ChangeConfigurationResponse";
|
||||
case MessageType::ClearCache:
|
||||
return "ClearCache";
|
||||
case MessageType::ClearCacheResponse:
|
||||
return "ClearCacheResponse";
|
||||
case MessageType::ClearChargingProfile:
|
||||
return "ClearChargingProfile";
|
||||
case MessageType::ClearChargingProfileResponse:
|
||||
return "ClearChargingProfileResponse";
|
||||
case MessageType::DataTransfer:
|
||||
return "DataTransfer";
|
||||
case MessageType::DataTransferResponse:
|
||||
return "DataTransferResponse";
|
||||
case MessageType::DeleteCertificate:
|
||||
return "DeleteCertificate";
|
||||
case MessageType::DeleteCertificateResponse:
|
||||
return "DeleteCertificateResponse";
|
||||
case MessageType::DiagnosticsStatusNotification:
|
||||
return "DiagnosticsStatusNotification";
|
||||
case MessageType::DiagnosticsStatusNotificationResponse:
|
||||
return "DiagnosticsStatusNotificationResponse";
|
||||
case MessageType::ExtendedTriggerMessage:
|
||||
return "ExtendedTriggerMessage";
|
||||
case MessageType::ExtendedTriggerMessageResponse:
|
||||
return "ExtendedTriggerMessageResponse";
|
||||
case MessageType::FirmwareStatusNotification:
|
||||
return "FirmwareStatusNotification";
|
||||
case MessageType::FirmwareStatusNotificationResponse:
|
||||
return "FirmwareStatusNotificationResponse";
|
||||
case MessageType::GetCompositeSchedule:
|
||||
return "GetCompositeSchedule";
|
||||
case MessageType::GetCompositeScheduleResponse:
|
||||
return "GetCompositeScheduleResponse";
|
||||
case MessageType::GetConfiguration:
|
||||
return "GetConfiguration";
|
||||
case MessageType::GetConfigurationResponse:
|
||||
return "GetConfigurationResponse";
|
||||
case MessageType::GetDiagnostics:
|
||||
return "GetDiagnostics";
|
||||
case MessageType::GetDiagnosticsResponse:
|
||||
return "GetDiagnosticsResponse";
|
||||
case MessageType::GetInstalledCertificateIds:
|
||||
return "GetInstalledCertificateIds";
|
||||
case MessageType::GetInstalledCertificateIdsResponse:
|
||||
return "GetInstalledCertificateIdsResponse";
|
||||
case MessageType::GetLocalListVersion:
|
||||
return "GetLocalListVersion";
|
||||
case MessageType::GetLocalListVersionResponse:
|
||||
return "GetLocalListVersionResponse";
|
||||
case MessageType::GetLog:
|
||||
return "GetLog";
|
||||
case MessageType::GetLogResponse:
|
||||
return "GetLogResponse";
|
||||
case MessageType::Heartbeat:
|
||||
return "Heartbeat";
|
||||
case MessageType::HeartbeatResponse:
|
||||
return "HeartbeatResponse";
|
||||
case MessageType::InstallCertificate:
|
||||
return "InstallCertificate";
|
||||
case MessageType::InstallCertificateResponse:
|
||||
return "InstallCertificateResponse";
|
||||
case MessageType::LogStatusNotification:
|
||||
return "LogStatusNotification";
|
||||
case MessageType::LogStatusNotificationResponse:
|
||||
return "LogStatusNotificationResponse";
|
||||
case MessageType::MeterValues:
|
||||
return "MeterValues";
|
||||
case MessageType::MeterValuesResponse:
|
||||
return "MeterValuesResponse";
|
||||
case MessageType::RemoteStartTransaction:
|
||||
return "RemoteStartTransaction";
|
||||
case MessageType::RemoteStartTransactionResponse:
|
||||
return "RemoteStartTransactionResponse";
|
||||
case MessageType::RemoteStopTransaction:
|
||||
return "RemoteStopTransaction";
|
||||
case MessageType::RemoteStopTransactionResponse:
|
||||
return "RemoteStopTransactionResponse";
|
||||
case MessageType::ReserveNow:
|
||||
return "ReserveNow";
|
||||
case MessageType::ReserveNowResponse:
|
||||
return "ReserveNowResponse";
|
||||
case MessageType::Reset:
|
||||
return "Reset";
|
||||
case MessageType::ResetResponse:
|
||||
return "ResetResponse";
|
||||
case MessageType::SecurityEventNotification:
|
||||
return "SecurityEventNotification";
|
||||
case MessageType::SecurityEventNotificationResponse:
|
||||
return "SecurityEventNotificationResponse";
|
||||
case MessageType::SendLocalList:
|
||||
return "SendLocalList";
|
||||
case MessageType::SendLocalListResponse:
|
||||
return "SendLocalListResponse";
|
||||
case MessageType::SetChargingProfile:
|
||||
return "SetChargingProfile";
|
||||
case MessageType::SetChargingProfileResponse:
|
||||
return "SetChargingProfileResponse";
|
||||
case MessageType::SignCertificate:
|
||||
return "SignCertificate";
|
||||
case MessageType::SignCertificateResponse:
|
||||
return "SignCertificateResponse";
|
||||
case MessageType::SignedFirmwareStatusNotification:
|
||||
return "SignedFirmwareStatusNotification";
|
||||
case MessageType::SignedFirmwareStatusNotificationResponse:
|
||||
return "SignedFirmwareStatusNotificationResponse";
|
||||
case MessageType::SignedUpdateFirmware:
|
||||
return "SignedUpdateFirmware";
|
||||
case MessageType::SignedUpdateFirmwareResponse:
|
||||
return "SignedUpdateFirmwareResponse";
|
||||
case MessageType::StartTransaction:
|
||||
return "StartTransaction";
|
||||
case MessageType::StartTransactionResponse:
|
||||
return "StartTransactionResponse";
|
||||
case MessageType::StatusNotification:
|
||||
return "StatusNotification";
|
||||
case MessageType::StatusNotificationResponse:
|
||||
return "StatusNotificationResponse";
|
||||
case MessageType::StopTransaction:
|
||||
return "StopTransaction";
|
||||
case MessageType::StopTransactionResponse:
|
||||
return "StopTransactionResponse";
|
||||
case MessageType::TriggerMessage:
|
||||
return "TriggerMessage";
|
||||
case MessageType::TriggerMessageResponse:
|
||||
return "TriggerMessageResponse";
|
||||
case MessageType::UnlockConnector:
|
||||
return "UnlockConnector";
|
||||
case MessageType::UnlockConnectorResponse:
|
||||
return "UnlockConnectorResponse";
|
||||
case MessageType::UpdateFirmware:
|
||||
return "UpdateFirmware";
|
||||
case MessageType::UpdateFirmwareResponse:
|
||||
return "UpdateFirmwareResponse";
|
||||
case MessageType::InternalError:
|
||||
return "InternalError";
|
||||
}
|
||||
|
||||
throw EnumToStringException{m, "MessageType"};
|
||||
}
|
||||
|
||||
MessageType string_to_messagetype(const std::string& s) {
|
||||
if (s == "Authorize") {
|
||||
return MessageType::Authorize;
|
||||
}
|
||||
if (s == "AuthorizeResponse") {
|
||||
return MessageType::AuthorizeResponse;
|
||||
}
|
||||
if (s == "BootNotification") {
|
||||
return MessageType::BootNotification;
|
||||
}
|
||||
if (s == "BootNotificationResponse") {
|
||||
return MessageType::BootNotificationResponse;
|
||||
}
|
||||
if (s == "CancelReservation") {
|
||||
return MessageType::CancelReservation;
|
||||
}
|
||||
if (s == "CancelReservationResponse") {
|
||||
return MessageType::CancelReservationResponse;
|
||||
}
|
||||
if (s == "CertificateSigned") {
|
||||
return MessageType::CertificateSigned;
|
||||
}
|
||||
if (s == "CertificateSignedResponse") {
|
||||
return MessageType::CertificateSignedResponse;
|
||||
}
|
||||
if (s == "ChangeAvailability") {
|
||||
return MessageType::ChangeAvailability;
|
||||
}
|
||||
if (s == "ChangeAvailabilityResponse") {
|
||||
return MessageType::ChangeAvailabilityResponse;
|
||||
}
|
||||
if (s == "ChangeConfiguration") {
|
||||
return MessageType::ChangeConfiguration;
|
||||
}
|
||||
if (s == "ChangeConfigurationResponse") {
|
||||
return MessageType::ChangeConfigurationResponse;
|
||||
}
|
||||
if (s == "ClearCache") {
|
||||
return MessageType::ClearCache;
|
||||
}
|
||||
if (s == "ClearCacheResponse") {
|
||||
return MessageType::ClearCacheResponse;
|
||||
}
|
||||
if (s == "ClearChargingProfile") {
|
||||
return MessageType::ClearChargingProfile;
|
||||
}
|
||||
if (s == "ClearChargingProfileResponse") {
|
||||
return MessageType::ClearChargingProfileResponse;
|
||||
}
|
||||
if (s == "DataTransfer") {
|
||||
return MessageType::DataTransfer;
|
||||
}
|
||||
if (s == "DataTransferResponse") {
|
||||
return MessageType::DataTransferResponse;
|
||||
}
|
||||
if (s == "DeleteCertificate") {
|
||||
return MessageType::DeleteCertificate;
|
||||
}
|
||||
if (s == "DeleteCertificateResponse") {
|
||||
return MessageType::DeleteCertificateResponse;
|
||||
}
|
||||
if (s == "DiagnosticsStatusNotification") {
|
||||
return MessageType::DiagnosticsStatusNotification;
|
||||
}
|
||||
if (s == "DiagnosticsStatusNotificationResponse") {
|
||||
return MessageType::DiagnosticsStatusNotificationResponse;
|
||||
}
|
||||
if (s == "ExtendedTriggerMessage") {
|
||||
return MessageType::ExtendedTriggerMessage;
|
||||
}
|
||||
if (s == "ExtendedTriggerMessageResponse") {
|
||||
return MessageType::ExtendedTriggerMessageResponse;
|
||||
}
|
||||
if (s == "FirmwareStatusNotification") {
|
||||
return MessageType::FirmwareStatusNotification;
|
||||
}
|
||||
if (s == "FirmwareStatusNotificationResponse") {
|
||||
return MessageType::FirmwareStatusNotificationResponse;
|
||||
}
|
||||
if (s == "GetCompositeSchedule") {
|
||||
return MessageType::GetCompositeSchedule;
|
||||
}
|
||||
if (s == "GetCompositeScheduleResponse") {
|
||||
return MessageType::GetCompositeScheduleResponse;
|
||||
}
|
||||
if (s == "GetConfiguration") {
|
||||
return MessageType::GetConfiguration;
|
||||
}
|
||||
if (s == "GetConfigurationResponse") {
|
||||
return MessageType::GetConfigurationResponse;
|
||||
}
|
||||
if (s == "GetDiagnostics") {
|
||||
return MessageType::GetDiagnostics;
|
||||
}
|
||||
if (s == "GetDiagnosticsResponse") {
|
||||
return MessageType::GetDiagnosticsResponse;
|
||||
}
|
||||
if (s == "GetInstalledCertificateIds") {
|
||||
return MessageType::GetInstalledCertificateIds;
|
||||
}
|
||||
if (s == "GetInstalledCertificateIdsResponse") {
|
||||
return MessageType::GetInstalledCertificateIdsResponse;
|
||||
}
|
||||
if (s == "GetLocalListVersion") {
|
||||
return MessageType::GetLocalListVersion;
|
||||
}
|
||||
if (s == "GetLocalListVersionResponse") {
|
||||
return MessageType::GetLocalListVersionResponse;
|
||||
}
|
||||
if (s == "GetLog") {
|
||||
return MessageType::GetLog;
|
||||
}
|
||||
if (s == "GetLogResponse") {
|
||||
return MessageType::GetLogResponse;
|
||||
}
|
||||
if (s == "Heartbeat") {
|
||||
return MessageType::Heartbeat;
|
||||
}
|
||||
if (s == "HeartbeatResponse") {
|
||||
return MessageType::HeartbeatResponse;
|
||||
}
|
||||
if (s == "InstallCertificate") {
|
||||
return MessageType::InstallCertificate;
|
||||
}
|
||||
if (s == "InstallCertificateResponse") {
|
||||
return MessageType::InstallCertificateResponse;
|
||||
}
|
||||
if (s == "LogStatusNotification") {
|
||||
return MessageType::LogStatusNotification;
|
||||
}
|
||||
if (s == "LogStatusNotificationResponse") {
|
||||
return MessageType::LogStatusNotificationResponse;
|
||||
}
|
||||
if (s == "MeterValues") {
|
||||
return MessageType::MeterValues;
|
||||
}
|
||||
if (s == "MeterValuesResponse") {
|
||||
return MessageType::MeterValuesResponse;
|
||||
}
|
||||
if (s == "RemoteStartTransaction") {
|
||||
return MessageType::RemoteStartTransaction;
|
||||
}
|
||||
if (s == "RemoteStartTransactionResponse") {
|
||||
return MessageType::RemoteStartTransactionResponse;
|
||||
}
|
||||
if (s == "RemoteStopTransaction") {
|
||||
return MessageType::RemoteStopTransaction;
|
||||
}
|
||||
if (s == "RemoteStopTransactionResponse") {
|
||||
return MessageType::RemoteStopTransactionResponse;
|
||||
}
|
||||
if (s == "ReserveNow") {
|
||||
return MessageType::ReserveNow;
|
||||
}
|
||||
if (s == "ReserveNowResponse") {
|
||||
return MessageType::ReserveNowResponse;
|
||||
}
|
||||
if (s == "Reset") {
|
||||
return MessageType::Reset;
|
||||
}
|
||||
if (s == "ResetResponse") {
|
||||
return MessageType::ResetResponse;
|
||||
}
|
||||
if (s == "SecurityEventNotification") {
|
||||
return MessageType::SecurityEventNotification;
|
||||
}
|
||||
if (s == "SecurityEventNotificationResponse") {
|
||||
return MessageType::SecurityEventNotificationResponse;
|
||||
}
|
||||
if (s == "SendLocalList") {
|
||||
return MessageType::SendLocalList;
|
||||
}
|
||||
if (s == "SendLocalListResponse") {
|
||||
return MessageType::SendLocalListResponse;
|
||||
}
|
||||
if (s == "SetChargingProfile") {
|
||||
return MessageType::SetChargingProfile;
|
||||
}
|
||||
if (s == "SetChargingProfileResponse") {
|
||||
return MessageType::SetChargingProfileResponse;
|
||||
}
|
||||
if (s == "SignCertificate") {
|
||||
return MessageType::SignCertificate;
|
||||
}
|
||||
if (s == "SignCertificateResponse") {
|
||||
return MessageType::SignCertificateResponse;
|
||||
}
|
||||
if (s == "SignedFirmwareStatusNotification") {
|
||||
return MessageType::SignedFirmwareStatusNotification;
|
||||
}
|
||||
if (s == "SignedFirmwareStatusNotificationResponse") {
|
||||
return MessageType::SignedFirmwareStatusNotificationResponse;
|
||||
}
|
||||
if (s == "SignedUpdateFirmware") {
|
||||
return MessageType::SignedUpdateFirmware;
|
||||
}
|
||||
if (s == "SignedUpdateFirmwareResponse") {
|
||||
return MessageType::SignedUpdateFirmwareResponse;
|
||||
}
|
||||
if (s == "StartTransaction") {
|
||||
return MessageType::StartTransaction;
|
||||
}
|
||||
if (s == "StartTransactionResponse") {
|
||||
return MessageType::StartTransactionResponse;
|
||||
}
|
||||
if (s == "StatusNotification") {
|
||||
return MessageType::StatusNotification;
|
||||
}
|
||||
if (s == "StatusNotificationResponse") {
|
||||
return MessageType::StatusNotificationResponse;
|
||||
}
|
||||
if (s == "StopTransaction") {
|
||||
return MessageType::StopTransaction;
|
||||
}
|
||||
if (s == "StopTransactionResponse") {
|
||||
return MessageType::StopTransactionResponse;
|
||||
}
|
||||
if (s == "TriggerMessage") {
|
||||
return MessageType::TriggerMessage;
|
||||
}
|
||||
if (s == "TriggerMessageResponse") {
|
||||
return MessageType::TriggerMessageResponse;
|
||||
}
|
||||
if (s == "UnlockConnector") {
|
||||
return MessageType::UnlockConnector;
|
||||
}
|
||||
if (s == "UnlockConnectorResponse") {
|
||||
return MessageType::UnlockConnectorResponse;
|
||||
}
|
||||
if (s == "UpdateFirmware") {
|
||||
return MessageType::UpdateFirmware;
|
||||
}
|
||||
if (s == "UpdateFirmwareResponse") {
|
||||
return MessageType::UpdateFirmwareResponse;
|
||||
}
|
||||
|
||||
throw StringToEnumException{s, "MessageType"};
|
||||
}
|
||||
|
||||
} // namespace conversions
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const MessageType& message_type) {
|
||||
os << conversions::messagetype_to_string(message_type);
|
||||
return os;
|
||||
}
|
||||
|
||||
namespace conversions {
|
||||
/// \brief Converts the given SupportedFeatureProfiles \p e to std::string
|
||||
/// \returns a string representation of the SupportedFeatureProfiles
|
||||
std::string supported_feature_profiles_to_string(SupportedFeatureProfiles e) {
|
||||
switch (e) {
|
||||
case SupportedFeatureProfiles::Internal:
|
||||
return "Internal";
|
||||
case SupportedFeatureProfiles::Core:
|
||||
return "Core";
|
||||
case SupportedFeatureProfiles::FirmwareManagement:
|
||||
return "FirmwareManagement";
|
||||
case SupportedFeatureProfiles::LocalAuthListManagement:
|
||||
return "LocalAuthListManagement";
|
||||
case SupportedFeatureProfiles::Reservation:
|
||||
return "Reservation";
|
||||
case SupportedFeatureProfiles::SmartCharging:
|
||||
return "SmartCharging";
|
||||
case SupportedFeatureProfiles::RemoteTrigger:
|
||||
return "RemoteTrigger";
|
||||
case SupportedFeatureProfiles::Security:
|
||||
return "Security";
|
||||
case SupportedFeatureProfiles::PnC:
|
||||
return "PnC";
|
||||
case SupportedFeatureProfiles::CostAndPrice:
|
||||
return "CostAndPrice";
|
||||
case SupportedFeatureProfiles::Custom:
|
||||
return "Custom";
|
||||
}
|
||||
|
||||
throw EnumToStringException{e, "SupportedFeatureProfiles"};
|
||||
}
|
||||
|
||||
/// \brief Converts the given std::string \p s to SupportedFeatureProfiles
|
||||
/// \returns a SupportedFeatureProfiles from a string representation
|
||||
SupportedFeatureProfiles string_to_supported_feature_profiles(const std::string_view& s) {
|
||||
if (s == "Internal") {
|
||||
return SupportedFeatureProfiles::Internal;
|
||||
}
|
||||
if (s == "Core") {
|
||||
return SupportedFeatureProfiles::Core;
|
||||
}
|
||||
if (s == "FirmwareManagement") {
|
||||
return SupportedFeatureProfiles::FirmwareManagement;
|
||||
}
|
||||
if (s == "LocalAuthListManagement") {
|
||||
return SupportedFeatureProfiles::LocalAuthListManagement;
|
||||
}
|
||||
if (s == "Reservation") {
|
||||
return SupportedFeatureProfiles::Reservation;
|
||||
}
|
||||
if (s == "SmartCharging") {
|
||||
return SupportedFeatureProfiles::SmartCharging;
|
||||
}
|
||||
if (s == "RemoteTrigger") {
|
||||
return SupportedFeatureProfiles::RemoteTrigger;
|
||||
}
|
||||
if (s == "Security") {
|
||||
return SupportedFeatureProfiles::Security;
|
||||
}
|
||||
if (s == "PnC") {
|
||||
return SupportedFeatureProfiles::PnC;
|
||||
}
|
||||
if (s == "CostAndPrice") {
|
||||
return SupportedFeatureProfiles::CostAndPrice;
|
||||
}
|
||||
if (s == "Custom") {
|
||||
return SupportedFeatureProfiles::Custom;
|
||||
}
|
||||
|
||||
throw StringToEnumException{s, "SupportedFeatureProfiles"};
|
||||
}
|
||||
} // namespace conversions
|
||||
|
||||
/// \brief Writes the string representation of the given \p supported_feature_profiles to the given output stream \p os
|
||||
/// \returns an output stream with the SupportedFeatureProfiles written to
|
||||
std::ostream& operator<<(std::ostream& os, const SupportedFeatureProfiles& supported_feature_profiles) {
|
||||
os << conversions::supported_feature_profiles_to_string(supported_feature_profiles);
|
||||
return os;
|
||||
}
|
||||
|
||||
namespace conversions {
|
||||
|
||||
std::string charge_point_connection_state_to_string(ChargePointConnectionState e) {
|
||||
switch (e) {
|
||||
case ChargePointConnectionState::Disconnected:
|
||||
return "Disconnected";
|
||||
case ChargePointConnectionState::Connected:
|
||||
return "Connected";
|
||||
case ChargePointConnectionState::Booted:
|
||||
return "Booted";
|
||||
case ChargePointConnectionState::Pending:
|
||||
return "Pending";
|
||||
case ChargePointConnectionState::Rejected:
|
||||
return "Rejected";
|
||||
}
|
||||
|
||||
throw EnumToStringException{e, "ChargePointConnectionState"};
|
||||
}
|
||||
|
||||
ChargePointConnectionState string_to_charge_point_connection_state(const std::string& s) {
|
||||
if (s == "Disconnected") {
|
||||
return ChargePointConnectionState::Disconnected;
|
||||
}
|
||||
if (s == "Connected") {
|
||||
return ChargePointConnectionState::Connected;
|
||||
}
|
||||
if (s == "Booted") {
|
||||
return ChargePointConnectionState::Booted;
|
||||
}
|
||||
if (s == "Pending") {
|
||||
return ChargePointConnectionState::Pending;
|
||||
}
|
||||
if (s == "Rejected") {
|
||||
return ChargePointConnectionState::Rejected;
|
||||
}
|
||||
|
||||
throw StringToEnumException{s, "ChargePointConnectionState"};
|
||||
}
|
||||
} // namespace conversions
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const ChargePointConnectionState& charge_point_connection_state) {
|
||||
os << conversions::charge_point_connection_state_to_string(charge_point_connection_state);
|
||||
return os;
|
||||
}
|
||||
|
||||
bool MeasurandWithPhase::operator==(MeasurandWithPhase measurand_with_phase) {
|
||||
if (this->measurand == measurand_with_phase.measurand) {
|
||||
if (this->phase || measurand_with_phase.phase) {
|
||||
if (this->phase && measurand_with_phase.phase) {
|
||||
if (this->phase.value() == measurand_with_phase.phase.value()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given ChargingSchedulePeriod \p k to a given json object \p j
|
||||
void to_json(json& j, const EnhancedChargingSchedulePeriod& k) {
|
||||
// the required parts of the message
|
||||
j = json{{"startPeriod", k.startPeriod}, {"limit", k.limit}, {"stackLevel", k.stackLevel}};
|
||||
// the optional parts of the message
|
||||
if (k.numberPhases) {
|
||||
j["numberPhases"] = k.numberPhases.value();
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given json object \p j to a given ChargingSchedulePeriod \p k
|
||||
void from_json(const json& j, EnhancedChargingSchedulePeriod& k) {
|
||||
// the required parts of the message
|
||||
k.startPeriod = j.at("startPeriod");
|
||||
k.limit = j.at("limit");
|
||||
k.stackLevel = j.at("stackLevel");
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("numberPhases")) {
|
||||
k.numberPhases.emplace(j.at("numberPhases"));
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given ChargingSchedule \p k to a given json object \p j
|
||||
void to_json(json& j, const EnhancedChargingSchedule& k) {
|
||||
// the required parts of the message
|
||||
j = json{
|
||||
{"chargingRateUnit", conversions::charging_rate_unit_to_string(k.chargingRateUnit)},
|
||||
{"chargingSchedulePeriod", k.chargingSchedulePeriod},
|
||||
};
|
||||
// the optional parts of the message
|
||||
if (k.duration) {
|
||||
j["duration"] = k.duration.value();
|
||||
}
|
||||
if (k.startSchedule) {
|
||||
j["startSchedule"] = k.startSchedule.value().to_rfc3339();
|
||||
}
|
||||
if (k.minChargingRate) {
|
||||
j["minChargingRate"] = k.minChargingRate.value();
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given json object \p j to a given ChargingSchedule \p k
|
||||
void from_json(const json& j, EnhancedChargingSchedule& k) {
|
||||
// the required parts of the message
|
||||
k.chargingRateUnit = conversions::string_to_charging_rate_unit(j.at("chargingRateUnit"));
|
||||
for (const auto& val : j.at("chargingSchedulePeriod")) {
|
||||
k.chargingSchedulePeriod.push_back(val);
|
||||
}
|
||||
|
||||
// the optional parts of the message
|
||||
if (j.contains("duration")) {
|
||||
k.duration.emplace(j.at("duration"));
|
||||
}
|
||||
if (j.contains("startSchedule")) {
|
||||
k.startSchedule.emplace(j.at("startSchedule").get<std::string>());
|
||||
}
|
||||
if (j.contains("minChargingRate")) {
|
||||
k.minChargingRate.emplace(j.at("minChargingRate"));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace v16
|
||||
} // namespace ocpp
|
||||
115
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v16/utils.cpp
Normal file
115
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v16/utils.cpp
Normal file
@@ -0,0 +1,115 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <ocpp/common/utils.hpp>
|
||||
#include <ocpp/v16/utils.hpp>
|
||||
|
||||
namespace ocpp::v16::utils {
|
||||
|
||||
size_t get_message_size(const ocpp::Call<StopTransactionRequest>& call) {
|
||||
return json(call).at(CALL_PAYLOAD).dump().length();
|
||||
}
|
||||
|
||||
void drop_transaction_data(size_t max_message_size, ocpp::Call<StopTransactionRequest>& call) {
|
||||
auto& transaction_data = call.msg.transactionData.value();
|
||||
while (get_message_size(call) > max_message_size && transaction_data.size() > 2) {
|
||||
// Drop every second message, keeping the first and last.
|
||||
// Iterate backwards so that erase() does not shift the indices
|
||||
// of elements we still need to remove.
|
||||
int last = static_cast<int>(transaction_data.size()) - 2;
|
||||
int start = (last % 2 == 0) ? last - 1 : last;
|
||||
for (int i = start; i >= 1; i -= 2) {
|
||||
transaction_data.erase(transaction_data.begin() + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool is_critical(const std::string& security_event) {
|
||||
if (security_event == ocpp::security_events::FIRMWARE_UPDATED) {
|
||||
return true;
|
||||
}
|
||||
if (security_event == ocpp::security_events::SETTINGSYSTEMTIME) {
|
||||
return true;
|
||||
}
|
||||
if (security_event == ocpp::security_events::STARTUP_OF_THE_DEVICE) {
|
||||
return true;
|
||||
}
|
||||
if (security_event == ocpp::security_events::RESET_OR_REBOOT) {
|
||||
return true;
|
||||
}
|
||||
if (security_event == ocpp::security_events::SECURITYLOGWASCLEARED) {
|
||||
return true;
|
||||
}
|
||||
if (security_event == ocpp::security_events::MEMORYEXHAUSTION) {
|
||||
return true;
|
||||
}
|
||||
if (security_event == ocpp::security_events::TAMPERDETECTIONACTIVATED) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string to_csl(const std::vector<std::string>& vec) {
|
||||
std::string csl;
|
||||
for (const auto& i : vec) {
|
||||
csl += i;
|
||||
csl += ",";
|
||||
}
|
||||
if (!csl.empty()) {
|
||||
csl.pop_back();
|
||||
}
|
||||
return csl;
|
||||
}
|
||||
|
||||
std::vector<std::string> from_csl(const std::string& csl) {
|
||||
std::vector<std::string> vec;
|
||||
auto start = csl.find_first_not_of(',');
|
||||
while (start != std::string::npos) {
|
||||
auto end = csl.find_first_of(',', start);
|
||||
if (end == std::string::npos) {
|
||||
vec.push_back(std::move(csl.substr(start)));
|
||||
start = std::string::npos;
|
||||
} else {
|
||||
vec.push_back(std::move(csl.substr(start, end - start)));
|
||||
start = csl.find_first_not_of(',', end);
|
||||
}
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
std::vector<std::string> split_string(char separator, const std::string& csl) {
|
||||
std::vector<std::string> vec;
|
||||
std::size_t start{0};
|
||||
if (!csl.empty()) {
|
||||
while (start != std::string::npos) {
|
||||
auto end = csl.find_first_of(separator, start);
|
||||
if (end == std::string::npos) {
|
||||
vec.push_back(std::move(csl.substr(start)));
|
||||
start = std::string::npos;
|
||||
} else if (start == end) {
|
||||
vec.emplace_back();
|
||||
start++;
|
||||
} else {
|
||||
vec.push_back(std::move(csl.substr(start, end - start)));
|
||||
start = end + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
void OrderedUniqueStringList::do_insert(std::string&& s) {
|
||||
if (const auto it = list.find(s); it == list.end()) {
|
||||
list.insert({std::move(s), count++});
|
||||
}
|
||||
}
|
||||
std::vector<std::string> OrderedUniqueStringList::get() const {
|
||||
std::vector<std::string> result(list.size());
|
||||
for (const auto& i : list) {
|
||||
result[i.second] = i.first;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace ocpp::v16::utils
|
||||
@@ -0,0 +1,71 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <everest/logging.hpp>
|
||||
#include <everest/timer.hpp>
|
||||
#include <ocpp/v2/average_meter_values.hpp>
|
||||
|
||||
namespace ocpp {
|
||||
namespace v2 {
|
||||
namespace {
|
||||
bool is_avg_meas(const SampledValue& sample) {
|
||||
if (sample.measurand.has_value() and (sample.measurand == MeasurandEnum::Current_Import) or
|
||||
(sample.measurand == MeasurandEnum::Voltage) or (sample.measurand == MeasurandEnum::Power_Active_Import) or
|
||||
(sample.measurand == MeasurandEnum::Frequency)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void AverageMeterValues::clear_values() {
|
||||
const std::lock_guard<std::mutex> lk(this->avg_meter_value_mutex);
|
||||
this->aligned_meter_values.clear();
|
||||
this->averaged_meter_values.sampledValue.clear();
|
||||
}
|
||||
|
||||
void AverageMeterValues::set_values(const MeterValue& meter_value) {
|
||||
const std::lock_guard<std::mutex> lk(this->avg_meter_value_mutex);
|
||||
// store all the meter values in the struct
|
||||
this->averaged_meter_values = meter_value;
|
||||
|
||||
// avg all the possible measurerands
|
||||
for (auto& element : meter_value.sampledValue) {
|
||||
if (is_avg_meas(element)) {
|
||||
if (element.measurand.has_value()) {
|
||||
const MeterValueMeasurands key{element.measurand.value(), element.phase, element.location};
|
||||
this->aligned_meter_values.try_emplace(key,
|
||||
MeterValueCalc{0.0, 0}); // If not exists yet, set to default
|
||||
|
||||
MeterValueCalc& temp = this->aligned_meter_values.at(key);
|
||||
temp.sum += element.value;
|
||||
temp.num_elements++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MeterValue AverageMeterValues::retrieve_processed_values() {
|
||||
const std::lock_guard<std::mutex> lk(this->avg_meter_value_mutex);
|
||||
this->average_meter_value();
|
||||
return this->averaged_meter_values;
|
||||
}
|
||||
|
||||
void AverageMeterValues::average_meter_value() {
|
||||
for (auto& element : this->averaged_meter_values.sampledValue) {
|
||||
if (is_avg_meas(element)) {
|
||||
if (element.measurand.has_value()) {
|
||||
const auto measurand = element.measurand.value();
|
||||
const MeterValueMeasurands key{measurand, element.phase, element.location};
|
||||
if (this->aligned_meter_values.find(key) == this->aligned_meter_values.end()) {
|
||||
EVLOG_warning << "Measurand: " << measurand << " not present in map";
|
||||
} else {
|
||||
const MeterValueCalc& temp = this->aligned_meter_values.at(key);
|
||||
element.value = static_cast<float>(temp.sum / temp.num_elements);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace v2
|
||||
} // namespace ocpp
|
||||
1349
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v2/charge_point.cpp
Normal file
1349
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v2/charge_point.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,114 @@
|
||||
#include <ocpp/v2/charge_point_callbacks.hpp>
|
||||
|
||||
#include <ocpp/v2/ctrlr_component_variables.hpp>
|
||||
|
||||
namespace ocpp::v2 {
|
||||
|
||||
bool Callbacks::all_callbacks_valid(std::shared_ptr<DeviceModelAbstract> device_model,
|
||||
const std::map<std::int32_t, std::int32_t>& evse_connector_structure) const {
|
||||
bool valid =
|
||||
this->is_reset_allowed_callback != nullptr and this->reset_callback != nullptr and
|
||||
this->stop_transaction_callback != nullptr and this->pause_charging_callback != nullptr and
|
||||
this->connector_effective_operative_status_changed_callback != nullptr and
|
||||
this->get_log_request_callback != nullptr and this->unlock_connector_callback != nullptr and
|
||||
this->remote_start_transaction_callback != nullptr and this->is_reservation_for_token_callback != nullptr and
|
||||
this->update_firmware_request_callback != nullptr and this->security_event_callback != nullptr and
|
||||
this->set_charging_profiles_callback != nullptr and
|
||||
(!this->variable_changed_callback.has_value() or this->variable_changed_callback.value() != nullptr) and
|
||||
(!this->validate_network_profile_callback.has_value() or
|
||||
this->validate_network_profile_callback.value() != nullptr) and
|
||||
(!this->configure_network_connection_profile_callback.has_value() or
|
||||
this->configure_network_connection_profile_callback.value() != nullptr) and
|
||||
(!this->time_sync_callback.has_value() or this->time_sync_callback.value() != nullptr) and
|
||||
(!this->boot_notification_callback.has_value() or this->boot_notification_callback.value() != nullptr) and
|
||||
(!this->ocpp_messages_callback.has_value() or this->ocpp_messages_callback.value() != nullptr) and
|
||||
(!this->cs_effective_operative_status_changed_callback.has_value() or
|
||||
this->cs_effective_operative_status_changed_callback.value() != nullptr) and
|
||||
(!this->evse_effective_operative_status_changed_callback.has_value() or
|
||||
this->evse_effective_operative_status_changed_callback.value() != nullptr) and
|
||||
(!this->get_customer_information_callback.has_value() or
|
||||
this->get_customer_information_callback.value() != nullptr) and
|
||||
(!this->clear_customer_information_callback.has_value() or
|
||||
this->clear_customer_information_callback.value() != nullptr) and
|
||||
(!this->all_connectors_unavailable_callback.has_value() or
|
||||
this->all_connectors_unavailable_callback.value() != nullptr) and
|
||||
(!this->data_transfer_callback.has_value() or this->data_transfer_callback.value() != nullptr) and
|
||||
(!this->transaction_event_callback.has_value() or this->transaction_event_callback.value() != nullptr) and
|
||||
(!this->transaction_event_response_callback.has_value() or
|
||||
this->transaction_event_response_callback.value() != nullptr);
|
||||
|
||||
if (valid) {
|
||||
if (device_model->get_optional_value<bool>(ControllerComponentVariables::DisplayMessageCtrlrAvailable)
|
||||
.value_or(false)) {
|
||||
if ((!this->clear_display_message_callback.has_value() or
|
||||
this->clear_display_message_callback.value() == nullptr) or
|
||||
(!this->get_display_message_callback.has_value() or
|
||||
this->get_display_message_callback.value() == nullptr) or
|
||||
(!this->set_display_message_callback.has_value() or
|
||||
this->set_display_message_callback.value() == nullptr)) {
|
||||
EVLOG_error << "Display message controller is set to 'Available' in device model, but callbacks are "
|
||||
"not (all) implemented";
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If cost is available and enabled, the running cost callback must be enabled as well.
|
||||
if (device_model->get_optional_value<bool>(ControllerComponentVariables::TariffCostCtrlrAvailableCost)
|
||||
.value_or(false)) {
|
||||
if (!this->set_running_cost_callback.has_value() or this->set_running_cost_callback.value() == nullptr) {
|
||||
EVLOG_error << "TariffAndCost controller 'Cost' is set to 'Available' in device model, "
|
||||
"but callback is not implemented";
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (device_model->get_optional_value<bool>(ControllerComponentVariables::TariffCostCtrlrAvailableTariff)
|
||||
.value_or(false)) {
|
||||
if (!this->set_display_message_callback.has_value() or
|
||||
this->set_display_message_callback.value() == nullptr) {
|
||||
EVLOG_error << "TariffAndCost controller 'Tariff' is set to 'Available'. In this case, the "
|
||||
"set_display_message_callback must be implemented to send the tariff, but it is not";
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (device_model->get_optional_value<bool>(ControllerComponentVariables::ReservationCtrlrAvailable)
|
||||
.value_or(false)) {
|
||||
if (!this->reserve_now_callback.has_value() or this->reserve_now_callback == nullptr) {
|
||||
EVLOG_error << "Reservation is set to 'Available' and 'Enabled' in device model, but "
|
||||
"reserve_now_callback is not implemented.";
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (!this->cancel_reservation_callback.has_value() or this->cancel_reservation_callback == nullptr) {
|
||||
EVLOG_error
|
||||
<< "Reservation is set to 'Available' and 'Enabled' in device model, but cancel_reservation "
|
||||
"callback is not implemented";
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
const bool v2x_available = std::any_of(
|
||||
evse_connector_structure.begin(), evse_connector_structure.end(), [device_model](const auto& entry) {
|
||||
const auto& [evse, connectors] = entry;
|
||||
return device_model
|
||||
->get_optional_value<bool>(
|
||||
V2xComponentVariables::get_component_variable(evse, V2xComponentVariables::Available))
|
||||
.value_or(false);
|
||||
});
|
||||
|
||||
if (v2x_available and
|
||||
device_model->get_optional_value<bool>(ControllerComponentVariables::ISO15118CtrlrAvailable)
|
||||
.value_or(false)) {
|
||||
if (!this->update_allowed_energy_transfer_modes_callback.has_value() or
|
||||
this->update_allowed_energy_transfer_modes_callback == nullptr) {
|
||||
EVLOG_error << "V2XCharging and ISO15118 are both marked as 'Available', but "
|
||||
"update_allowed_energy_transfer_modes_callback is not implemented";
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
} // namespace ocpp::v2
|
||||
@@ -0,0 +1,347 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <ocpp/v2/component_state_manager.hpp>
|
||||
#include <utility>
|
||||
|
||||
namespace ocpp::v2 {
|
||||
|
||||
ComponentStateManagerInterface::~ComponentStateManagerInterface() = default;
|
||||
|
||||
void ComponentStateManager::read_all_states_from_database_or_set_defaults(
|
||||
const std::map<std::int32_t, std::int32_t>& evse_connector_structure) {
|
||||
|
||||
this->database->insert_cs_availability(OperationalStatusEnum::Operative, false);
|
||||
this->cs_individual_status = this->database->get_cs_availability();
|
||||
|
||||
const auto num_evses = clamp_to<std::int32_t>(evse_connector_structure.size());
|
||||
for (std::int32_t evse_id = 1; evse_id <= num_evses; evse_id++) {
|
||||
if (evse_connector_structure.count(evse_id) == 0) {
|
||||
throw std::invalid_argument("evse_connector_structure should contain EVSE ids counting from 1 upwards.");
|
||||
}
|
||||
const int num_connectors = evse_connector_structure.at(evse_id);
|
||||
std::vector<FullConnectorStatus> connector_statuses;
|
||||
|
||||
this->database->insert_evse_availability(evse_id, OperationalStatusEnum::Operative, false);
|
||||
const OperationalStatusEnum evse_operational = this->database->get_evse_availability(evse_id);
|
||||
|
||||
for (int connector_id = 1; connector_id <= num_connectors; connector_id++) {
|
||||
this->database->insert_connector_availability(evse_id, connector_id, OperationalStatusEnum::Operative,
|
||||
false);
|
||||
const OperationalStatusEnum connector_operational =
|
||||
this->database->get_connector_availability(evse_id, connector_id);
|
||||
const FullConnectorStatus full_connector_status{connector_operational, false, false, false, false};
|
||||
connector_statuses.push_back(full_connector_status);
|
||||
}
|
||||
|
||||
this->evse_and_connector_individual_statuses.emplace_back(evse_operational, connector_statuses);
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentStateManager::initialize_reported_state_cache() {
|
||||
// Initialize the cached statuses (after everything else is done)
|
||||
this->last_cs_effective_operational_status = this->get_cs_individual_operational_status();
|
||||
for (int evse_id = 1; evse_id <= this->num_evses(); evse_id++) {
|
||||
const int num_connectors = this->num_connectors(evse_id);
|
||||
std::vector<ConnectorStatusEnum> connector_statuses;
|
||||
std::vector<OperationalStatusEnum> connector_op_statuses;
|
||||
|
||||
const OperationalStatusEnum evse_effective = this->get_evse_effective_operational_status(evse_id);
|
||||
for (int connector_id = 1; connector_id <= num_connectors; connector_id++) {
|
||||
const ConnectorStatusEnum connector_status =
|
||||
this->individual_connector_status(evse_id, connector_id).to_connector_status();
|
||||
connector_statuses.push_back(connector_status);
|
||||
connector_op_statuses.push_back(this->get_connector_effective_operational_status(evse_id, connector_id));
|
||||
}
|
||||
|
||||
this->last_evse_and_connector_effective_operational_statuses.emplace_back(evse_effective,
|
||||
connector_op_statuses);
|
||||
this->last_connector_reported_statuses.push_back(connector_statuses);
|
||||
}
|
||||
}
|
||||
|
||||
ComponentStateManager::ComponentStateManager(
|
||||
const std::map<std::int32_t, std::int32_t>& evse_connector_structure, std::shared_ptr<DatabaseHandler> db_handler,
|
||||
std::function<bool(const std::int32_t evse_id, const std::int32_t connector_id,
|
||||
const ConnectorStatusEnum new_status, const bool initiated_by_trigger_message)>
|
||||
send_connector_status_notification_callback) :
|
||||
database(std::move(db_handler)),
|
||||
send_connector_status_notification_callback(std::move(send_connector_status_notification_callback)) {
|
||||
this->read_all_states_from_database_or_set_defaults(evse_connector_structure);
|
||||
this->initialize_reported_state_cache();
|
||||
}
|
||||
|
||||
std::int32_t ComponentStateManager::num_evses() {
|
||||
return clamp_to<std::int32_t>(this->evse_and_connector_individual_statuses.size());
|
||||
}
|
||||
|
||||
void ComponentStateManager::check_evse_id(std::int32_t evse_id) {
|
||||
if (evse_id <= 0 || evse_id > this->num_evses()) {
|
||||
throw EvseOutOfRangeException(evse_id);
|
||||
}
|
||||
}
|
||||
|
||||
std::int32_t ComponentStateManager::num_connectors(std::int32_t evse_id) {
|
||||
check_evse_id(evse_id);
|
||||
return clamp_to<std::int32_t>(this->evse_and_connector_individual_statuses[evse_id - 1].second.size());
|
||||
}
|
||||
|
||||
void ComponentStateManager::check_evse_and_connector_id(std::int32_t evse_id, std::int32_t connector_id) {
|
||||
this->check_evse_id(evse_id);
|
||||
if (connector_id <= 0 || connector_id > this->num_connectors(evse_id)) {
|
||||
throw ConnectorOutOfRangeException(connector_id, evse_id);
|
||||
}
|
||||
}
|
||||
|
||||
OperationalStatusEnum& ComponentStateManager::individual_evse_status(std::int32_t evse_id) {
|
||||
this->check_evse_id(evse_id);
|
||||
return this->evse_and_connector_individual_statuses[evse_id - 1].first;
|
||||
}
|
||||
|
||||
FullConnectorStatus& ComponentStateManager::individual_connector_status(std::int32_t evse_id,
|
||||
std::int32_t connector_id) {
|
||||
this->check_evse_and_connector_id(evse_id, connector_id);
|
||||
return this->evse_and_connector_individual_statuses[evse_id - 1].second[connector_id - 1];
|
||||
}
|
||||
|
||||
OperationalStatusEnum& ComponentStateManager::last_evse_effective_status(std::int32_t evse_id) {
|
||||
this->check_evse_id(evse_id);
|
||||
return this->last_evse_and_connector_effective_operational_statuses[evse_id - 1].first;
|
||||
}
|
||||
|
||||
OperationalStatusEnum& ComponentStateManager::last_connector_effective_status(std::int32_t evse_id,
|
||||
std::int32_t connector_id) {
|
||||
this->check_evse_and_connector_id(evse_id, connector_id);
|
||||
return this->last_evse_and_connector_effective_operational_statuses[evse_id - 1].second[connector_id - 1];
|
||||
}
|
||||
|
||||
ConnectorStatusEnum& ComponentStateManager::last_connector_reported_status(std::int32_t evse_id,
|
||||
std::int32_t connector_id) {
|
||||
this->check_evse_and_connector_id(evse_id, connector_id);
|
||||
return this->last_connector_reported_statuses[evse_id - 1][connector_id - 1];
|
||||
}
|
||||
|
||||
void ComponentStateManager::trigger_callbacks_cs(bool only_if_state_changed) {
|
||||
const OperationalStatusEnum current_effective_status = this->get_cs_individual_operational_status();
|
||||
if (!only_if_state_changed || this->last_cs_effective_operational_status != current_effective_status) {
|
||||
if (this->cs_effective_availability_changed_callback.has_value()) {
|
||||
this->cs_effective_availability_changed_callback.value()(current_effective_status);
|
||||
}
|
||||
this->last_cs_effective_operational_status = current_effective_status;
|
||||
for (std::int32_t evse_id = 1; evse_id <= this->num_evses(); evse_id++) {
|
||||
this->trigger_callbacks_evse(evse_id, only_if_state_changed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentStateManager::trigger_callbacks_evse(std::int32_t evse_id, bool only_if_state_changed) {
|
||||
const OperationalStatusEnum current_effective_status = this->get_evse_effective_operational_status(evse_id);
|
||||
if (!only_if_state_changed || this->last_evse_effective_status(evse_id) != current_effective_status) {
|
||||
if (this->evse_effective_availability_changed_callback.has_value()) {
|
||||
this->evse_effective_availability_changed_callback.value()(evse_id, current_effective_status);
|
||||
}
|
||||
this->last_evse_effective_status(evse_id) = current_effective_status;
|
||||
for (std::int32_t connector_id = 1; connector_id <= this->num_connectors(evse_id); connector_id++) {
|
||||
this->trigger_callbacks_connector(evse_id, connector_id, only_if_state_changed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentStateManager::trigger_callbacks_connector(std::int32_t evse_id, std::int32_t connector_id,
|
||||
bool only_if_state_changed) {
|
||||
// Operational status callbacks
|
||||
const OperationalStatusEnum current_effective_operational_status =
|
||||
this->get_connector_effective_operational_status(evse_id, connector_id);
|
||||
OperationalStatusEnum& last_effective_status = this->last_connector_effective_status(evse_id, connector_id);
|
||||
if (!only_if_state_changed || last_effective_status != current_effective_operational_status) {
|
||||
if (this->connector_effective_availability_changed_callback.has_value()) {
|
||||
this->connector_effective_availability_changed_callback.value()(evse_id, connector_id,
|
||||
current_effective_operational_status);
|
||||
}
|
||||
last_effective_status = current_effective_operational_status;
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentStateManager::set_cs_effective_availability_changed_callback(
|
||||
const std::function<void(const OperationalStatusEnum new_status)>& callback) {
|
||||
this->cs_effective_availability_changed_callback = callback;
|
||||
}
|
||||
|
||||
void ComponentStateManager::set_evse_effective_availability_changed_callback(
|
||||
const std::function<void(const std::int32_t evse_id, const OperationalStatusEnum new_status)>& callback) {
|
||||
this->evse_effective_availability_changed_callback = callback;
|
||||
}
|
||||
|
||||
void ComponentStateManager::set_connector_effective_availability_changed_callback(
|
||||
const std::function<void(const std::int32_t evse_id, const std::int32_t connector_id,
|
||||
const OperationalStatusEnum new_status)>& callback) {
|
||||
this->connector_effective_availability_changed_callback = callback;
|
||||
}
|
||||
|
||||
OperationalStatusEnum ComponentStateManager::get_cs_individual_operational_status() {
|
||||
return this->cs_individual_status;
|
||||
}
|
||||
OperationalStatusEnum ComponentStateManager::get_evse_individual_operational_status(std::int32_t evse_id) {
|
||||
return this->individual_evse_status(evse_id);
|
||||
}
|
||||
OperationalStatusEnum ComponentStateManager::get_connector_individual_operational_status(std::int32_t evse_id,
|
||||
std::int32_t connector_id) {
|
||||
return this->individual_connector_status(evse_id, connector_id).individual_operational_status;
|
||||
}
|
||||
|
||||
void ComponentStateManager::set_cs_individual_operational_status(OperationalStatusEnum new_status, bool persist) {
|
||||
this->cs_individual_status = new_status;
|
||||
if (persist) {
|
||||
try {
|
||||
this->database->insert_cs_availability(new_status, true);
|
||||
} catch (const everest::db::QueryExecutionException& e) {
|
||||
EVLOG_warning << "Could not insert charging station availability of id into database: " << e.what();
|
||||
}
|
||||
}
|
||||
this->trigger_callbacks_cs(true);
|
||||
}
|
||||
void ComponentStateManager::set_evse_individual_operational_status(std::int32_t evse_id,
|
||||
OperationalStatusEnum new_status, bool persist) {
|
||||
this->individual_evse_status(evse_id) = new_status;
|
||||
if (persist) {
|
||||
try {
|
||||
this->database->insert_evse_availability(evse_id, new_status, true);
|
||||
} catch (const everest::db::QueryExecutionException& e) {
|
||||
EVLOG_warning << "Could not insert evse availability of id " << evse_id << " into database: " << e.what();
|
||||
}
|
||||
this->database->insert_evse_availability(evse_id, new_status, true);
|
||||
}
|
||||
this->trigger_callbacks_evse(evse_id, true);
|
||||
}
|
||||
void ComponentStateManager::set_connector_individual_operational_status(std::int32_t evse_id, std::int32_t connector_id,
|
||||
OperationalStatusEnum new_status,
|
||||
bool persist) {
|
||||
this->individual_connector_status(evse_id, connector_id).individual_operational_status = new_status;
|
||||
if (persist) {
|
||||
try {
|
||||
this->database->insert_connector_availability(evse_id, connector_id, new_status, true);
|
||||
} catch (const everest::db::QueryExecutionException& e) {
|
||||
EVLOG_warning << "Could not insert connector availability of id " << connector_id
|
||||
<< " into database: " << e.what();
|
||||
}
|
||||
}
|
||||
this->trigger_callbacks_connector(evse_id, connector_id, true);
|
||||
}
|
||||
|
||||
ConnectorStatusEnum FullConnectorStatus::to_connector_status() const {
|
||||
// faulted has precedence over unavailable
|
||||
if (this->faulted) {
|
||||
return ConnectorStatusEnum::Faulted;
|
||||
}
|
||||
if (this->unavailable) {
|
||||
return ConnectorStatusEnum::Unavailable;
|
||||
}
|
||||
if (this->occupied) {
|
||||
return ConnectorStatusEnum::Occupied;
|
||||
}
|
||||
if (this->reserved) {
|
||||
return ConnectorStatusEnum::Reserved;
|
||||
}
|
||||
return ConnectorStatusEnum::Available;
|
||||
}
|
||||
|
||||
OperationalStatusEnum ComponentStateManager::get_evse_effective_operational_status(std::int32_t evse_id) {
|
||||
this->check_evse_id(evse_id);
|
||||
if (this->cs_individual_status == OperationalStatusEnum::Inoperative) {
|
||||
return OperationalStatusEnum::Inoperative;
|
||||
}
|
||||
return this->individual_evse_status(evse_id);
|
||||
}
|
||||
ConnectorStatusEnum ComponentStateManager::get_connector_effective_status(std::int32_t evse_id,
|
||||
std::int32_t connector_id) {
|
||||
this->check_evse_and_connector_id(evse_id, connector_id);
|
||||
if (this->get_evse_effective_operational_status(evse_id) == OperationalStatusEnum::Inoperative) {
|
||||
return ConnectorStatusEnum::Unavailable;
|
||||
}
|
||||
|
||||
return this->individual_connector_status(evse_id, connector_id).to_connector_status();
|
||||
}
|
||||
OperationalStatusEnum ComponentStateManager::get_connector_effective_operational_status(std::int32_t evse_id,
|
||||
std::int32_t connector_id) {
|
||||
if (this->get_evse_effective_operational_status(evse_id) == OperationalStatusEnum::Inoperative) {
|
||||
return OperationalStatusEnum::Inoperative;
|
||||
}
|
||||
return this->individual_connector_status(evse_id, connector_id).individual_operational_status;
|
||||
}
|
||||
|
||||
OperationalStatusEnum ComponentStateManager::get_cs_persisted_operational_status() {
|
||||
return this->database->get_cs_availability();
|
||||
}
|
||||
OperationalStatusEnum ComponentStateManager::get_evse_persisted_operational_status(std::int32_t evse_id) {
|
||||
this->check_evse_id(evse_id);
|
||||
return this->database->get_evse_availability(evse_id);
|
||||
}
|
||||
OperationalStatusEnum ComponentStateManager::get_connector_persisted_operational_status(std::int32_t evse_id,
|
||||
std::int32_t connector_id) {
|
||||
this->check_evse_and_connector_id(evse_id, connector_id);
|
||||
return this->database->get_connector_availability(evse_id, connector_id);
|
||||
}
|
||||
|
||||
void ComponentStateManager::set_connector_occupied(std::int32_t evse_id, std::int32_t connector_id, bool is_occupied) {
|
||||
this->individual_connector_status(evse_id, connector_id).occupied = is_occupied;
|
||||
// Check if the connector is set to reserved. Because if it is, it should not go to occupied but stay reserved.
|
||||
// If the connector is reserved and there is a plug in, the internal state should be 'occupied' and 'reserved',
|
||||
// but a status notification with 'occupied' should not be sent yet. Only when the transaction is started, this
|
||||
// should be sent.
|
||||
if (!this->individual_connector_status(evse_id, connector_id).reserved) {
|
||||
this->send_status_notification_single_connector_internal(evse_id, connector_id, true);
|
||||
}
|
||||
}
|
||||
void ComponentStateManager::set_connector_reserved(std::int32_t evse_id, std::int32_t connector_id, bool is_reserved) {
|
||||
this->individual_connector_status(evse_id, connector_id).reserved = is_reserved;
|
||||
this->send_status_notification_single_connector_internal(evse_id, connector_id, true);
|
||||
}
|
||||
void ComponentStateManager::set_connector_faulted(std::int32_t evse_id, std::int32_t connector_id, bool is_faulted) {
|
||||
this->individual_connector_status(evse_id, connector_id).faulted = is_faulted;
|
||||
this->send_status_notification_single_connector_internal(evse_id, connector_id, true);
|
||||
}
|
||||
|
||||
void ComponentStateManager::set_connector_unavailable(std::int32_t evse_id, std::int32_t connector_id,
|
||||
bool is_unavailable) {
|
||||
this->individual_connector_status(evse_id, connector_id).unavailable = is_unavailable;
|
||||
this->send_status_notification_single_connector_internal(evse_id, connector_id, true);
|
||||
}
|
||||
|
||||
void ComponentStateManager::trigger_all_effective_availability_changed_callbacks() {
|
||||
this->trigger_callbacks_cs(false);
|
||||
}
|
||||
|
||||
// TODO(Piet): Move to connector file
|
||||
void ComponentStateManager::send_status_notification_single_connector_internal(std::int32_t evse_id,
|
||||
std::int32_t connector_id,
|
||||
bool only_if_changed,
|
||||
bool intiated_by_trigger_message) {
|
||||
const ConnectorStatusEnum connector_status =
|
||||
this->individual_connector_status(evse_id, connector_id).to_connector_status();
|
||||
ConnectorStatusEnum& last_reported_status = this->last_connector_reported_status(evse_id, connector_id);
|
||||
if (!only_if_changed || last_reported_status != connector_status) {
|
||||
if (this->send_connector_status_notification_callback(evse_id, connector_id, connector_status,
|
||||
intiated_by_trigger_message)) {
|
||||
last_reported_status = connector_status;
|
||||
}
|
||||
}
|
||||
}
|
||||
void ComponentStateManager::send_status_notification_all_connectors() {
|
||||
for (int evse_id = 1; evse_id <= this->num_evses(); evse_id++) {
|
||||
for (int connector_id = 1; connector_id <= this->num_connectors(evse_id); connector_id++) {
|
||||
this->send_status_notification_single_connector_internal(evse_id, connector_id, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
void ComponentStateManager::send_status_notification_changed_connectors() {
|
||||
for (int evse_id = 1; evse_id <= this->num_evses(); evse_id++) {
|
||||
for (int connector_id = 1; connector_id <= this->num_connectors(evse_id); connector_id++) {
|
||||
this->send_status_notification_single_connector_internal(evse_id, connector_id, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
void ComponentStateManager::send_status_notification_single_connector(std::int32_t evse_id, std::int32_t connector_id) {
|
||||
this->send_status_notification_single_connector_internal(evse_id, connector_id, false, true);
|
||||
}
|
||||
|
||||
} // namespace ocpp::v2
|
||||
103
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v2/connector.cpp
Normal file
103
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v2/connector.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <everest/logging.hpp>
|
||||
#include <ocpp/v2/connector.hpp>
|
||||
|
||||
namespace ocpp {
|
||||
namespace v2 {
|
||||
|
||||
namespace conversions {
|
||||
|
||||
std::string connector_event_to_string(ConnectorEvent e) {
|
||||
switch (e) {
|
||||
case ConnectorEvent::PlugIn:
|
||||
return "PlugIn";
|
||||
case ConnectorEvent::PlugOut:
|
||||
return "PlugOut";
|
||||
case ConnectorEvent::Reserve:
|
||||
return "Reserve";
|
||||
case ConnectorEvent::ReservationCleared:
|
||||
return "ReservationCleared";
|
||||
case ConnectorEvent::Error:
|
||||
return "Error";
|
||||
case ConnectorEvent::ErrorCleared:
|
||||
return "ErrorCleared";
|
||||
case ConnectorEvent::Unavailable:
|
||||
return "Unavailable";
|
||||
case ConnectorEvent::UnavailableCleared:
|
||||
return "UnavailableCleared";
|
||||
}
|
||||
|
||||
throw EnumToStringException{e, "ConnectorEvent"};
|
||||
}
|
||||
|
||||
} // namespace conversions
|
||||
|
||||
Connector::Connector(const std::int32_t evse_id, const std::int32_t connector_id,
|
||||
std::shared_ptr<ComponentStateManagerInterface> component_state_manager) :
|
||||
evse_id(evse_id), connector_id(connector_id), component_state_manager(component_state_manager) {
|
||||
}
|
||||
|
||||
void Connector::submit_event(ConnectorEvent event) {
|
||||
const std::lock_guard lk(this->status_mutex);
|
||||
switch (event) {
|
||||
case ConnectorEvent::PlugIn:
|
||||
this->component_state_manager->set_connector_occupied(this->evse_id, this->connector_id, true);
|
||||
break;
|
||||
case ConnectorEvent::PlugOut:
|
||||
this->component_state_manager->set_connector_occupied(this->evse_id, this->connector_id, false);
|
||||
break;
|
||||
case ConnectorEvent::Reserve:
|
||||
this->component_state_manager->set_connector_reserved(this->evse_id, this->connector_id, true);
|
||||
break;
|
||||
case ConnectorEvent::ReservationCleared:
|
||||
this->component_state_manager->set_connector_reserved(this->evse_id, this->connector_id, false);
|
||||
break;
|
||||
case ConnectorEvent::Error:
|
||||
this->component_state_manager->set_connector_faulted(this->evse_id, this->connector_id, true);
|
||||
break;
|
||||
case ConnectorEvent::ErrorCleared:
|
||||
this->component_state_manager->set_connector_faulted(this->evse_id, this->connector_id, false);
|
||||
break;
|
||||
case ConnectorEvent::Unavailable:
|
||||
this->component_state_manager->set_connector_unavailable(this->evse_id, this->connector_id, true);
|
||||
break;
|
||||
case ConnectorEvent::UnavailableCleared:
|
||||
this->component_state_manager->set_connector_unavailable(this->evse_id, this->connector_id, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Connector::set_connector_operative_status(OperationalStatusEnum new_status, bool persist) {
|
||||
this->component_state_manager->set_connector_individual_operational_status(this->evse_id, this->connector_id,
|
||||
new_status, persist);
|
||||
}
|
||||
|
||||
void Connector::restore_connector_operative_status() {
|
||||
try {
|
||||
auto persisted_status = this->component_state_manager->get_connector_persisted_operational_status(
|
||||
this->evse_id, this->connector_id);
|
||||
this->component_state_manager->set_connector_individual_operational_status(this->evse_id, this->connector_id,
|
||||
persisted_status, false);
|
||||
} catch (const everest::db::QueryExecutionException& e) {
|
||||
EVLOG_error << "QueryExecutionException while restoring connector status of evse_id: " << this->evse_id
|
||||
<< "; connector_id: " << this->connector_id << ": " << e.what();
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Internal error while restoring connector status of evse_id: " << this->evse_id
|
||||
<< "; connector_id: " << this->connector_id << ": " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
OperationalStatusEnum Connector::get_effective_operational_status() {
|
||||
return this->component_state_manager->get_connector_effective_operational_status(this->evse_id, this->connector_id);
|
||||
}
|
||||
|
||||
ConnectorStatusEnum Connector::get_effective_connector_status() {
|
||||
return this->component_state_manager->get_connector_effective_status(this->evse_id, this->connector_id);
|
||||
}
|
||||
|
||||
} // namespace v2
|
||||
} // namespace ocpp
|
||||
File diff suppressed because it is too large
Load Diff
1358
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v2/database_handler.cpp
Normal file
1358
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v2/database_handler.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1050
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v2/device_model.cpp
Normal file
1050
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v2/device_model.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,600 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <everest/database/sqlite/statement.hpp>
|
||||
#include <everest/logging.hpp>
|
||||
#include <limits>
|
||||
#include <ocpp/v2/device_model_storage_sqlite.hpp>
|
||||
#include <ocpp/v2/init_device_model_db.hpp>
|
||||
#include <ocpp/v2/network_configuration_default_schema.hpp>
|
||||
#include <ocpp/v2/utils.hpp>
|
||||
|
||||
using namespace everest::db;
|
||||
using namespace everest::db::sqlite;
|
||||
|
||||
namespace ocpp {
|
||||
|
||||
using namespace common;
|
||||
|
||||
namespace v2 {
|
||||
|
||||
DeviceModelStorageSqlite::DeviceModelStorageSqlite(const fs::path& db_path, const fs::path& migration_files_path,
|
||||
const fs::path& config_path) {
|
||||
if (db_path.empty() || migration_files_path.empty() || config_path.empty()) {
|
||||
EVLOG_AND_THROW(DeviceModelError("Can not initialize device model storage: one of the paths is empty."));
|
||||
}
|
||||
const auto component_configs = get_all_component_configs(config_path);
|
||||
InitDeviceModelDb init_device_model_db(db_path, migration_files_path);
|
||||
init_device_model_db.initialize_database(component_configs, false);
|
||||
|
||||
initialize_connection(db_path);
|
||||
}
|
||||
|
||||
DeviceModelStorageSqlite::DeviceModelStorageSqlite(const fs::path& db_path, const fs::path& migration_files_path) {
|
||||
const InitDeviceModelDb init_device_model_db(db_path, migration_files_path);
|
||||
initialize_connection(db_path);
|
||||
}
|
||||
|
||||
DeviceModelStorageSqlite::DeviceModelStorageSqlite(const fs::path& db_path) {
|
||||
initialize_connection(db_path);
|
||||
}
|
||||
|
||||
void DeviceModelStorageSqlite::initialize_connection(const fs::path& db_path) {
|
||||
db = std::make_unique<Connection>(db_path);
|
||||
if (!db->open_connection()) {
|
||||
EVLOG_AND_THROW(std::runtime_error("Could not open device model database at: " + db_path.string()));
|
||||
}
|
||||
EVLOG_info << "Established connection to device model database: " << db_path;
|
||||
}
|
||||
|
||||
int DeviceModelStorageSqlite::get_component_id(const Component& component_id) {
|
||||
const std::string select_query =
|
||||
"SELECT ID FROM COMPONENT WHERE NAME = ? AND INSTANCE IS ? AND EVSE_ID IS ? AND CONNECTOR_ID IS ?";
|
||||
|
||||
auto select_stmt = this->db->new_statement(select_query);
|
||||
|
||||
select_stmt->bind_text(1, component_id.name.get(), SQLiteString::Transient);
|
||||
if (component_id.instance.has_value()) {
|
||||
select_stmt->bind_text(2, component_id.instance.value().get(), SQLiteString::Transient);
|
||||
} else {
|
||||
select_stmt->bind_null(2);
|
||||
}
|
||||
if (component_id.evse.has_value()) {
|
||||
select_stmt->bind_int(3, component_id.evse.value().id);
|
||||
if (component_id.evse.value().connectorId.has_value()) {
|
||||
select_stmt->bind_int(4, component_id.evse.value().connectorId.value());
|
||||
} else {
|
||||
select_stmt->bind_null(4);
|
||||
}
|
||||
} else {
|
||||
select_stmt->bind_null(3);
|
||||
}
|
||||
|
||||
if (select_stmt->step() == SQLITE_ROW) {
|
||||
return select_stmt->column_int(0);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int DeviceModelStorageSqlite::get_variable_id(const Component& component_id, const Variable& variable_id) {
|
||||
const auto _component_id = this->get_component_id(component_id);
|
||||
if (_component_id == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const std::string select_query = "SELECT ID FROM VARIABLE WHERE COMPONENT_ID = ? AND NAME = ? AND INSTANCE IS ?";
|
||||
auto select_stmt = this->db->new_statement(select_query);
|
||||
|
||||
select_stmt->bind_int(1, _component_id);
|
||||
select_stmt->bind_text(2, variable_id.name.get(), SQLiteString::Transient);
|
||||
if (variable_id.instance.has_value()) {
|
||||
select_stmt->bind_text(3, variable_id.instance.value().get(), SQLiteString::Transient);
|
||||
} else {
|
||||
select_stmt->bind_null(3);
|
||||
}
|
||||
if (select_stmt->step() == SQLITE_ROW) {
|
||||
return select_stmt->column_int(0);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
DeviceModelMap DeviceModelStorageSqlite::get_device_model() {
|
||||
std::map<Component, std::map<Variable, VariableMetaData>> device_model;
|
||||
|
||||
const std::string select_query =
|
||||
"SELECT c.NAME, c.EVSE_ID, c.CONNECTOR_ID, c.INSTANCE, v.NAME, v.INSTANCE, vc.DATATYPE_ID, "
|
||||
"vc.SUPPORTS_MONITORING, vc.UNIT, vc.MIN_LIMIT, vc.MAX_LIMIT, vc.VALUES_LIST, v.SOURCE "
|
||||
"FROM COMPONENT c "
|
||||
"JOIN VARIABLE v ON c.ID = v.COMPONENT_ID "
|
||||
"JOIN VARIABLE_CHARACTERISTICS vc ON vc.VARIABLE_ID = v.ID";
|
||||
|
||||
auto select_stmt = this->db->new_statement(select_query);
|
||||
|
||||
while (select_stmt->step() == SQLITE_ROW) {
|
||||
Component component;
|
||||
component.name = select_stmt->column_text(0);
|
||||
|
||||
if (select_stmt->column_type(1) != SQLITE_NULL) {
|
||||
auto evse_id = select_stmt->column_int(1);
|
||||
EVSE evse;
|
||||
evse.id = evse_id;
|
||||
if (select_stmt->column_type(2) != SQLITE_NULL) {
|
||||
evse.connectorId = select_stmt->column_int(2);
|
||||
}
|
||||
component.evse = evse;
|
||||
}
|
||||
|
||||
if (select_stmt->column_type(3) != SQLITE_NULL) {
|
||||
component.instance = select_stmt->column_text(3);
|
||||
}
|
||||
|
||||
Variable variable;
|
||||
variable.name = select_stmt->column_text(4);
|
||||
|
||||
if (select_stmt->column_type(5) != SQLITE_NULL) {
|
||||
variable.instance = select_stmt->column_text(5);
|
||||
}
|
||||
|
||||
VariableCharacteristics characteristics;
|
||||
VariableMetaData meta_data;
|
||||
characteristics.dataType = static_cast<DataEnum>(select_stmt->column_int(6));
|
||||
characteristics.supportsMonitoring = select_stmt->column_int(7) != 0;
|
||||
|
||||
if (select_stmt->column_type(8) != SQLITE_NULL) {
|
||||
characteristics.unit = select_stmt->column_text(8);
|
||||
}
|
||||
|
||||
if (select_stmt->column_type(9) != SQLITE_NULL) {
|
||||
characteristics.minLimit = select_stmt->column_double(9);
|
||||
}
|
||||
|
||||
if (select_stmt->column_type(10) != SQLITE_NULL) {
|
||||
characteristics.maxLimit = select_stmt->column_double(10);
|
||||
}
|
||||
|
||||
if (select_stmt->column_type(11) != SQLITE_NULL) {
|
||||
characteristics.valuesList = select_stmt->column_text(11);
|
||||
}
|
||||
|
||||
if (select_stmt->column_type(12) != SQLITE_NULL) {
|
||||
meta_data.source = select_stmt->column_text(12);
|
||||
}
|
||||
|
||||
meta_data.characteristics = characteristics;
|
||||
|
||||
// Query all monitors for this variable
|
||||
auto monitoring = get_monitoring_data({}, component, variable);
|
||||
if (!monitoring.empty()) {
|
||||
for (auto& monitor_meta : monitoring) {
|
||||
meta_data.monitors.insert(std::pair{monitor_meta.monitor.id, std::move(monitor_meta)});
|
||||
}
|
||||
}
|
||||
|
||||
device_model[component][variable] = meta_data;
|
||||
}
|
||||
|
||||
EVLOG_info << "Successfully retrieved Device Model from DeviceModelStorage";
|
||||
return device_model;
|
||||
}
|
||||
|
||||
std::optional<VariableAttribute> DeviceModelStorageSqlite::get_variable_attribute(const Component& component_id,
|
||||
const Variable& variable_id,
|
||||
const AttributeEnum& attribute_enum) {
|
||||
const auto attributes = this->get_variable_attributes(component_id, variable_id, attribute_enum);
|
||||
if (!attributes.empty()) {
|
||||
return attributes.at(0);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<VariableAttribute>
|
||||
DeviceModelStorageSqlite::get_variable_attributes(const Component& component_id, const Variable& variable_id,
|
||||
const std::optional<AttributeEnum>& attribute_enum) {
|
||||
std::vector<VariableAttribute> attributes;
|
||||
const auto _variable_id = this->get_variable_id(component_id, variable_id);
|
||||
if (_variable_id == -1) {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
std::string select_query = "SELECT va.VALUE, va.MUTABILITY_ID, va.PERSISTENT, va.CONSTANT, va.TYPE_ID "
|
||||
"FROM VARIABLE_ATTRIBUTE va "
|
||||
"WHERE va.VARIABLE_ID = @variable_id";
|
||||
|
||||
if (attribute_enum.has_value()) {
|
||||
std::stringstream ss;
|
||||
ss << select_query << " AND va.TYPE_ID = " << static_cast<int>(attribute_enum.value());
|
||||
select_query = ss.str();
|
||||
}
|
||||
|
||||
auto select_stmt = this->db->new_statement(select_query);
|
||||
|
||||
select_stmt->bind_int(1, _variable_id);
|
||||
|
||||
while (select_stmt->step() == SQLITE_ROW) {
|
||||
VariableAttribute attribute;
|
||||
|
||||
if (select_stmt->column_type(0) != SQLITE_NULL) {
|
||||
attribute.value = select_stmt->column_text(0);
|
||||
}
|
||||
attribute.mutability = static_cast<MutabilityEnum>(select_stmt->column_int(1));
|
||||
attribute.persistent = static_cast<bool>(select_stmt->column_int(2));
|
||||
attribute.constant = static_cast<bool>(select_stmt->column_int(3));
|
||||
attribute.type = static_cast<AttributeEnum>(select_stmt->column_int(4));
|
||||
attributes.push_back(attribute);
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
SetVariableStatusEnum DeviceModelStorageSqlite::set_variable_attribute_value(const Component& component_id,
|
||||
const Variable& variable_id,
|
||||
const AttributeEnum& attribute_enum,
|
||||
const std::string& value,
|
||||
const std::string& source) {
|
||||
auto transaction = this->db->begin_transaction();
|
||||
|
||||
const std::string insert_query =
|
||||
"UPDATE VARIABLE_ATTRIBUTE SET VALUE = ?, VALUE_SOURCE = ? WHERE VARIABLE_ID = ? AND TYPE_ID = ?";
|
||||
auto insert_stmt = this->db->new_statement(insert_query);
|
||||
|
||||
const auto _variable_id = this->get_variable_id(component_id, variable_id);
|
||||
|
||||
if (_variable_id == -1) {
|
||||
return SetVariableStatusEnum::Rejected;
|
||||
}
|
||||
|
||||
insert_stmt->bind_text(1, value);
|
||||
insert_stmt->bind_text(2, source);
|
||||
insert_stmt->bind_int(3, _variable_id);
|
||||
insert_stmt->bind_int(4, static_cast<int>(attribute_enum));
|
||||
if (insert_stmt->step() != SQLITE_DONE) {
|
||||
EVLOG_error << this->db->get_error_message();
|
||||
return SetVariableStatusEnum::Rejected;
|
||||
}
|
||||
|
||||
transaction->commit();
|
||||
return SetVariableStatusEnum::Accepted;
|
||||
}
|
||||
|
||||
bool DeviceModelStorageSqlite::update_monitoring_reference(const std::int32_t monitor_id,
|
||||
const std::string& reference_value) {
|
||||
auto transaction = this->db->begin_transaction();
|
||||
|
||||
const std::string update_query = "UPDATE VARIABLE_MONITORING SET REFERENCE_VALUE = ? WHERE ID = ?";
|
||||
auto update_stmt = this->db->new_statement(update_query);
|
||||
|
||||
update_stmt->bind_text(1, reference_value, SQLiteString::Transient);
|
||||
update_stmt->bind_int(2, monitor_id);
|
||||
|
||||
if (update_stmt->step() != SQLITE_DONE) {
|
||||
EVLOG_error << this->db->get_error_message();
|
||||
return false;
|
||||
}
|
||||
|
||||
transaction->commit();
|
||||
|
||||
const int changes = update_stmt->changes();
|
||||
|
||||
return (changes == 1);
|
||||
}
|
||||
|
||||
std::optional<VariableMonitoringMeta> DeviceModelStorageSqlite::set_monitoring_data(const SetMonitoringData& data,
|
||||
const VariableMonitorType type) {
|
||||
const auto _variable_id = this->get_variable_id(data.component, data.variable);
|
||||
if (_variable_id == -1) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::string> actual_value;
|
||||
|
||||
// For a delta monitor, the actual value is mandatory,
|
||||
// since it is used as a reference value when triggering
|
||||
if (data.type == MonitorEnum::Delta) {
|
||||
auto attrib = get_variable_attribute(data.component, data.variable, AttributeEnum::Actual);
|
||||
|
||||
if (attrib.has_value() && attrib.value().value.has_value()) {
|
||||
actual_value = attrib.value().value.value();
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO (ioan): see if we already have an existing monitor?
|
||||
auto transaction = this->db->begin_transaction();
|
||||
|
||||
std::string insert_query;
|
||||
|
||||
if (data.id.has_value()) {
|
||||
insert_query = "INSERT OR REPLACE INTO VARIABLE_MONITORING (VARIABLE_ID, SEVERITY, 'TRANSACTION', TYPE_ID, "
|
||||
"CONFIG_TYPE_ID, VALUE, REFERENCE_VALUE, ID) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
} else {
|
||||
insert_query = "INSERT OR REPLACE INTO VARIABLE_MONITORING (VARIABLE_ID, SEVERITY, 'TRANSACTION', TYPE_ID, "
|
||||
"CONFIG_TYPE_ID, VALUE, REFERENCE_VALUE) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||
}
|
||||
|
||||
auto insert_stmt = this->db->new_statement(insert_query);
|
||||
|
||||
insert_stmt->bind_int(1, _variable_id);
|
||||
insert_stmt->bind_int(2, data.severity);
|
||||
insert_stmt->bind_int(3, static_cast<int>(data.transaction.value_or(false)));
|
||||
insert_stmt->bind_int(4, static_cast<int>(data.type));
|
||||
insert_stmt->bind_int(5, static_cast<int>(type));
|
||||
insert_stmt->bind_double(6, data.value);
|
||||
|
||||
if (actual_value.has_value()) {
|
||||
// If the monitor is a delta, we need to insert the reference value
|
||||
insert_stmt->bind_text(7, actual_value.value(), SQLiteString::Transient);
|
||||
} else {
|
||||
insert_stmt->bind_null(7);
|
||||
}
|
||||
|
||||
if (data.id.has_value()) {
|
||||
insert_stmt->bind_int(8, data.id.value());
|
||||
}
|
||||
|
||||
if (insert_stmt->step() != SQLITE_DONE) {
|
||||
EVLOG_error << this->db->get_error_message();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
transaction->commit();
|
||||
|
||||
const std::int64_t last_row_id = this->db->get_last_inserted_rowid();
|
||||
|
||||
VariableMonitoringMeta meta;
|
||||
|
||||
if (last_row_id > std::numeric_limits<std::int32_t>::max()) {
|
||||
EVLOG_warning << "Monitor id exceeds 32 bit integer, clamped at maximum";
|
||||
}
|
||||
|
||||
meta.monitor.id = clamp_to<std::int32_t>(last_row_id);
|
||||
meta.monitor.severity = data.severity;
|
||||
meta.monitor.transaction = data.transaction.value_or(false);
|
||||
meta.monitor.type = data.type;
|
||||
meta.monitor.value = data.value;
|
||||
// this is a workaround to set the eventNotificationType which became a required property for the
|
||||
// VariableMonitoringType in OCPP2.1
|
||||
meta.monitor.eventNotificationType = conversions::variable_monitor_type_to_event_notification_type(type);
|
||||
meta.type = type;
|
||||
meta.reference_value = actual_value;
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
||||
std::vector<VariableMonitoringMeta>
|
||||
DeviceModelStorageSqlite::get_monitoring_data(const std::vector<MonitoringCriterionEnum>& criteria,
|
||||
const Component& component_id, const Variable& variable_id) {
|
||||
const auto _variable_id = this->get_variable_id(component_id, variable_id);
|
||||
if (_variable_id == -1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// TODO (ioan): optimize select based on criterions
|
||||
const std::string select_query =
|
||||
"SELECT vm.TYPE_ID, vm.ID, vm.SEVERITY, vm.'TRANSACTION', vm.VALUE, vm.CONFIG_TYPE_ID, vm.REFERENCE_VALUE "
|
||||
"FROM VARIABLE_MONITORING vm "
|
||||
"WHERE vm.VARIABLE_ID = @variable_id";
|
||||
|
||||
auto select_stmt = this->db->new_statement(select_query);
|
||||
select_stmt->bind_int(1, _variable_id);
|
||||
|
||||
std::vector<VariableMonitoringMeta> monitors;
|
||||
|
||||
while (select_stmt->step() == SQLITE_ROW) {
|
||||
VariableMonitoringMeta monitor_meta;
|
||||
VariableMonitoring monitor;
|
||||
|
||||
// Retrieve monitor data
|
||||
monitor.type = static_cast<MonitorEnum>(select_stmt->column_int(0));
|
||||
monitor.id = select_stmt->column_int(1);
|
||||
monitor.severity = select_stmt->column_int(2);
|
||||
monitor.transaction = static_cast<bool>(select_stmt->column_int(3));
|
||||
monitor.value = static_cast<float>(select_stmt->column_double(4));
|
||||
|
||||
const auto type = static_cast<VariableMonitorType>(select_stmt->column_int(5));
|
||||
auto reference_value = select_stmt->column_text_nullable(6);
|
||||
// this is a workaround to set the eventNotificationType which became a required property for the
|
||||
// VariableMonitoringType in OCPP2.1
|
||||
monitor.eventNotificationType = conversions::variable_monitor_type_to_event_notification_type(type);
|
||||
|
||||
monitor_meta.monitor = monitor;
|
||||
monitor_meta.reference_value = reference_value;
|
||||
monitor_meta.type = type;
|
||||
|
||||
monitors.push_back(monitor_meta);
|
||||
|
||||
// Filter only required monitors
|
||||
ocpp::v2::utils::filter_criteria_monitors(criteria, monitors);
|
||||
}
|
||||
|
||||
return monitors;
|
||||
}
|
||||
|
||||
ClearMonitoringStatusEnum DeviceModelStorageSqlite::clear_variable_monitor(int monitor_id, bool allow_protected) {
|
||||
const std::string select_query = "SELECT COUNT(*) FROM VARIABLE_MONITORING WHERE ID = ?";
|
||||
|
||||
auto select_stmt = this->db->new_statement(select_query);
|
||||
select_stmt->bind_int(1, monitor_id);
|
||||
|
||||
if (select_stmt->step() != SQLITE_ROW) {
|
||||
EVLOG_error << this->db->get_error_message();
|
||||
return ClearMonitoringStatusEnum::Rejected;
|
||||
}
|
||||
// If we couldn't find a monitor in the DB
|
||||
if (select_stmt->column_int(0) != 1) {
|
||||
return ClearMonitoringStatusEnum::NotFound;
|
||||
}
|
||||
|
||||
std::string delete_query;
|
||||
|
||||
if (allow_protected) {
|
||||
delete_query = "DELETE FROM VARIABLE_MONITORING WHERE ID = ?";
|
||||
} else {
|
||||
delete_query = "DELETE FROM VARIABLE_MONITORING WHERE ID = ? AND CONFIG_TYPE_ID = ?";
|
||||
}
|
||||
|
||||
auto transaction = this->db->begin_transaction();
|
||||
auto delete_stmt = this->db->new_statement(delete_query);
|
||||
|
||||
delete_stmt->bind_int(1, monitor_id);
|
||||
|
||||
if (!allow_protected) {
|
||||
delete_stmt->bind_int(2, static_cast<int>(VariableMonitorType::CustomMonitor));
|
||||
}
|
||||
|
||||
if (delete_stmt->step() != SQLITE_DONE) {
|
||||
EVLOG_error << this->db->get_error_message();
|
||||
return ClearMonitoringStatusEnum::Rejected;
|
||||
}
|
||||
|
||||
transaction->commit();
|
||||
|
||||
// Ensure that we deleted 1 row
|
||||
return ((delete_stmt->changes() == 1) ? ClearMonitoringStatusEnum::Accepted : ClearMonitoringStatusEnum::Rejected);
|
||||
}
|
||||
|
||||
std::int32_t DeviceModelStorageSqlite::clear_custom_variable_monitors() {
|
||||
const std::string delete_query = "DELETE FROM VARIABLE_MONITORING WHERE CONFIG_TYPE_ID = ?";
|
||||
|
||||
auto transaction = this->db->begin_transaction();
|
||||
auto delete_stmt = this->db->new_statement(delete_query);
|
||||
|
||||
delete_stmt->bind_int(1, static_cast<int>(VariableMonitorType::CustomMonitor));
|
||||
if (delete_stmt->step() != SQLITE_DONE) {
|
||||
EVLOG_error << this->db->get_error_message();
|
||||
return 0;
|
||||
}
|
||||
|
||||
transaction->commit();
|
||||
|
||||
return delete_stmt->changes();
|
||||
}
|
||||
|
||||
void DeviceModelStorageSqlite::check_integrity() {
|
||||
// Function is now empty because checks are already done elsewhere (for example the check for 'required' variables).
|
||||
}
|
||||
|
||||
bool DeviceModelStorageSqlite::create_network_configuration_slot_from_default_schema(std::int32_t new_slot) {
|
||||
const std::string new_instance = std::to_string(new_slot);
|
||||
|
||||
// Embedded schema parse. Failure means a build defect, not a runtime data issue.
|
||||
std::pair<ComponentKey, std::vector<DeviceModelVariable>> parsed;
|
||||
try {
|
||||
parsed = parse_component_config_from_string(get_default_network_configuration_schema());
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_critical << "create_network_configuration_slot_from_default_schema: embedded default "
|
||||
"schema could not be parsed (build defect): "
|
||||
<< e.what();
|
||||
return false;
|
||||
}
|
||||
const auto& variables = parsed.second;
|
||||
|
||||
try {
|
||||
auto select_existing = this->db->new_statement(
|
||||
"SELECT 1 FROM COMPONENT WHERE NAME = 'NetworkConfiguration' AND INSTANCE = @instance");
|
||||
select_existing->bind_text("@instance", new_instance, SQLiteString::Transient);
|
||||
if (select_existing->step() == SQLITE_ROW) {
|
||||
EVLOG_warning << "create_network_configuration_slot_from_default_schema: slot " << new_slot
|
||||
<< " already exists";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto transaction = this->db->begin_transaction();
|
||||
|
||||
auto bind_opt_text = [](everest::db::sqlite::StatementInterface& stmt, const char* p,
|
||||
const std::optional<std::string>& v) {
|
||||
if (v.has_value() && !v->empty()) {
|
||||
stmt.bind_text(p, *v, SQLiteString::Transient);
|
||||
} else {
|
||||
stmt.bind_null(p);
|
||||
}
|
||||
};
|
||||
auto bind_opt_int = [](everest::db::sqlite::StatementInterface& stmt, const char* p, const auto& v) {
|
||||
if (v.has_value()) {
|
||||
stmt.bind_int(p, static_cast<int>(*v));
|
||||
} else {
|
||||
stmt.bind_null(p);
|
||||
}
|
||||
};
|
||||
auto bind_opt_double = [](everest::db::sqlite::StatementInterface& stmt, const char* p, const auto& v) {
|
||||
if (v.has_value()) {
|
||||
stmt.bind_double(p, static_cast<double>(*v));
|
||||
} else {
|
||||
stmt.bind_null(p);
|
||||
}
|
||||
};
|
||||
|
||||
auto insert_component = this->db->new_statement("INSERT INTO COMPONENT (NAME, INSTANCE, EVSE_ID, CONNECTOR_ID) "
|
||||
"VALUES ('NetworkConfiguration', @instance, NULL, NULL)");
|
||||
insert_component->bind_text("@instance", new_instance, SQLiteString::Transient);
|
||||
if (insert_component->step() != SQLITE_DONE) {
|
||||
EVLOG_error << "create_network_configuration_slot_from_default_schema: insert COMPONENT failed: "
|
||||
<< this->db->get_error_message();
|
||||
return false;
|
||||
}
|
||||
const auto new_component_id = this->db->get_last_inserted_rowid();
|
||||
|
||||
for (const auto& variable : variables) {
|
||||
auto insert_variable = this->db->new_statement(
|
||||
"INSERT INTO VARIABLE (NAME, INSTANCE, COMPONENT_ID, SOURCE) VALUES (@n, @i, @cid, @src)");
|
||||
insert_variable->bind_text("@n", variable.name, SQLiteString::Transient);
|
||||
bind_opt_text(*insert_variable, "@i", variable.instance);
|
||||
insert_variable->bind_int("@cid", static_cast<int>(new_component_id));
|
||||
bind_opt_text(*insert_variable, "@src", variable.source);
|
||||
if (insert_variable->step() != SQLITE_DONE) {
|
||||
EVLOG_error << "create_network_configuration_slot_from_default_schema: insert VARIABLE failed: "
|
||||
<< this->db->get_error_message();
|
||||
return false;
|
||||
}
|
||||
const auto new_variable_id = this->db->get_last_inserted_rowid();
|
||||
|
||||
auto insert_characteristics = this->db->new_statement(
|
||||
"INSERT INTO VARIABLE_CHARACTERISTICS "
|
||||
"(DATATYPE_ID, VARIABLE_ID, MAX_LIMIT, MIN_LIMIT, SUPPORTS_MONITORING, UNIT, VALUES_LIST) "
|
||||
"VALUES (@dtype, @vid, @max, @min, @supp, @unit, @vlist)");
|
||||
insert_characteristics->bind_int("@dtype", static_cast<int>(variable.characteristics.dataType));
|
||||
insert_characteristics->bind_int("@vid", static_cast<int>(new_variable_id));
|
||||
bind_opt_double(*insert_characteristics, "@max", variable.characteristics.maxLimit);
|
||||
bind_opt_double(*insert_characteristics, "@min", variable.characteristics.minLimit);
|
||||
insert_characteristics->bind_int("@supp", variable.characteristics.supportsMonitoring ? 1 : 0);
|
||||
bind_opt_text(*insert_characteristics, "@unit", variable.characteristics.unit);
|
||||
bind_opt_text(*insert_characteristics, "@vlist", variable.characteristics.valuesList);
|
||||
if (insert_characteristics->step() != SQLITE_DONE) {
|
||||
EVLOG_error << "create_network_configuration_slot_from_default_schema: "
|
||||
"insert VARIABLE_CHARACTERISTICS failed: "
|
||||
<< this->db->get_error_message();
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& db_attribute : variable.attributes) {
|
||||
const auto& attribute = db_attribute.variable_attribute;
|
||||
auto insert_attribute = this->db->new_statement(
|
||||
"INSERT INTO VARIABLE_ATTRIBUTE "
|
||||
"(VARIABLE_ID, MUTABILITY_ID, PERSISTENT, CONSTANT, TYPE_ID, VALUE_SOURCE, VALUE) "
|
||||
"VALUES (@vid, @mut, 1, 0, @type, 'internal', NULL)");
|
||||
insert_attribute->bind_int("@vid", static_cast<int>(new_variable_id));
|
||||
bind_opt_int(*insert_attribute, "@mut", attribute.mutability);
|
||||
bind_opt_int(*insert_attribute, "@type", attribute.type);
|
||||
if (insert_attribute->step() != SQLITE_DONE) {
|
||||
EVLOG_error << "create_network_configuration_slot_from_default_schema: "
|
||||
"insert VARIABLE_ATTRIBUTE failed: "
|
||||
<< this->db->get_error_message();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transaction->commit();
|
||||
EVLOG_info << "Created NetworkConfiguration_" << new_slot << " from embedded default schema ("
|
||||
<< variables.size() << " variables)";
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "create_network_configuration_slot_from_default_schema: exception: " << e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace v2
|
||||
} // namespace ocpp
|
||||
42
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v2/enums.cpp
Normal file
42
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v2/enums.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2024 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <ocpp/v2/enums.hpp>
|
||||
|
||||
namespace ocpp::v2 {
|
||||
|
||||
namespace conversions {
|
||||
/// \brief Converts the given std::string \p s to VariableMonitorType
|
||||
/// \returns a VariableMonitorType from a string representation
|
||||
VariableMonitorType string_to_variable_monitor_type(const std::string& s) {
|
||||
if (s == "HardWiredMonitor") {
|
||||
return VariableMonitorType::HardWiredMonitor;
|
||||
}
|
||||
if (s == "PreconfiguredMonitor") {
|
||||
return VariableMonitorType::PreconfiguredMonitor;
|
||||
}
|
||||
if (s == "CustomMonitor") {
|
||||
return VariableMonitorType::CustomMonitor;
|
||||
}
|
||||
|
||||
throw std::out_of_range("Provided string " + s + " could not be converted to enum of type VariableMonitorType");
|
||||
}
|
||||
|
||||
EventNotificationEnum variable_monitor_type_to_event_notification_type(const VariableMonitorType& type) {
|
||||
switch (type) {
|
||||
case VariableMonitorType::HardWiredMonitor:
|
||||
return ocpp::v2::EventNotificationEnum::HardWiredMonitor;
|
||||
case VariableMonitorType::PreconfiguredMonitor:
|
||||
return ocpp::v2::EventNotificationEnum::PreconfiguredMonitor;
|
||||
case VariableMonitorType::CustomMonitor:
|
||||
return ocpp::v2::EventNotificationEnum::CustomMonitor;
|
||||
default:
|
||||
throw std::out_of_range("Provided VariableMonitorType could not be converted to EventNotificationType");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace conversions
|
||||
|
||||
} // namespace ocpp::v2
|
||||
717
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v2/evse.cpp
Normal file
717
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v2/evse.cpp
Normal file
@@ -0,0 +1,717 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include <everest/database/exceptions.hpp>
|
||||
#include <everest/logging.hpp>
|
||||
#include <ocpp/v2/ctrlr_component_variables.hpp>
|
||||
#include <ocpp/v2/evse.hpp>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
using QueryExecutionException = everest::db::QueryExecutionException;
|
||||
|
||||
namespace ocpp {
|
||||
namespace v2 {
|
||||
|
||||
namespace {
|
||||
// Convert an energy value into Wh
|
||||
float get_normalized_energy_value(SampledValue sampled_value) {
|
||||
float value = sampled_value.value;
|
||||
// If no unit of measure is present the unit is in Wh so nothing to do
|
||||
if (sampled_value.unitOfMeasure.has_value()) {
|
||||
const auto& unit_of_measure = sampled_value.unitOfMeasure.value();
|
||||
if (unit_of_measure.unit.has_value()) {
|
||||
if (unit_of_measure.unit.value() == "kWh") {
|
||||
value *= 1000.0F;
|
||||
} else if (unit_of_measure.unit.value() == "Wh") {
|
||||
// do nothing
|
||||
} else {
|
||||
EVLOG_AND_THROW(
|
||||
std::runtime_error("Attempt to convert an energy value which does not have a correct unit"));
|
||||
}
|
||||
}
|
||||
|
||||
if (unit_of_measure.multiplier.has_value()) {
|
||||
if (unit_of_measure.multiplier.value() != 0) {
|
||||
value *= powf(10, static_cast<float>(unit_of_measure.multiplier.value()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Evse::Evse(const std::int32_t evse_id, const std::int32_t number_of_connectors, DeviceModelAbstract& device_model,
|
||||
std::shared_ptr<DatabaseHandler> database_handler,
|
||||
std::shared_ptr<ComponentStateManagerInterface> component_state_manager,
|
||||
const std::function<void(const MeterValue& meter_value, EnhancedTransaction& transaction)>&
|
||||
transaction_meter_value_req,
|
||||
const std::function<void(std::int32_t evse_id)>& pause_charging_callback) :
|
||||
evse_id(evse_id),
|
||||
device_model(device_model),
|
||||
transaction_meter_value_req(transaction_meter_value_req),
|
||||
pause_charging_callback(pause_charging_callback),
|
||||
database_handler(database_handler),
|
||||
component_state_manager(component_state_manager),
|
||||
transaction(nullptr) {
|
||||
for (int connector_id = 1; connector_id <= number_of_connectors; connector_id++) {
|
||||
this->id_connector_map.insert(
|
||||
std::make_pair(connector_id, std::make_unique<Connector>(evse_id, connector_id, component_state_manager)));
|
||||
}
|
||||
|
||||
if (this->device_model.get_optional_value<bool>(ControllerComponentVariables::ResumeTransactionsOnBoot)
|
||||
.value_or(false)) {
|
||||
this->try_resume_transaction();
|
||||
} else {
|
||||
this->delete_database_transaction();
|
||||
}
|
||||
}
|
||||
|
||||
Evse::~Evse() {
|
||||
try {
|
||||
if (this->trigger_metervalue_at_time_timer != nullptr) {
|
||||
this->trigger_metervalue_at_time_timer->stop();
|
||||
this->trigger_metervalue_at_time_timer = nullptr;
|
||||
}
|
||||
} catch (...) {
|
||||
EVLOG_error << "Exception during dtor call trigger metervalue at time timer stop";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::int32_t Evse::get_id() const {
|
||||
return this->evse_id;
|
||||
}
|
||||
|
||||
std::uint32_t Evse::get_number_of_connectors() const {
|
||||
return static_cast<std::uint32_t>(this->id_connector_map.size());
|
||||
}
|
||||
|
||||
bool Evse::does_connector_exist(const CiString<20> connector_type) const {
|
||||
const std::uint32_t number_of_connectors = this->get_number_of_connectors();
|
||||
if (number_of_connectors == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (connector_type == ConnectorEnumStringType::Unknown) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (std::uint32_t i = 1; i <= number_of_connectors; ++i) {
|
||||
const Connector* connector = nullptr;
|
||||
try {
|
||||
connector = this->get_connector(static_cast<std::int32_t>(i));
|
||||
} catch (const std::logic_error&) {
|
||||
EVLOG_error << "Connector with id " << i << " does not exist";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (connector == nullptr) {
|
||||
EVLOG_error << "Connector with id " << i << " does not exist";
|
||||
continue;
|
||||
}
|
||||
|
||||
const CiString<20> type = this->get_evse_connector_type(i).value_or(ConnectorEnumStringType::Unknown);
|
||||
if (type == ConnectorEnumStringType::Unknown || type == connector_type) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<ConnectorStatusEnum> Evse::get_connector_status(std::optional<CiString<20>> connector_type) {
|
||||
bool type_found = false;
|
||||
ConnectorStatusEnum found_status = ConnectorStatusEnum::Unavailable;
|
||||
const std::uint32_t number_of_connectors = this->get_number_of_connectors();
|
||||
if (number_of_connectors == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
for (std::uint32_t i = 1; i <= number_of_connectors; ++i) {
|
||||
Connector* connector = nullptr;
|
||||
try {
|
||||
connector = this->get_connector(static_cast<std::int32_t>(i));
|
||||
} catch (const std::logic_error&) {
|
||||
EVLOG_error << "Connector with id " << i << " does not exist";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (connector == nullptr) {
|
||||
EVLOG_error << "Connector with id " << i << " does not exist";
|
||||
continue;
|
||||
}
|
||||
|
||||
const ConnectorStatusEnum connector_status = connector->get_effective_connector_status();
|
||||
|
||||
const CiString<20> evse_connector_type =
|
||||
this->get_evse_connector_type(i).value_or(ConnectorEnumStringType::Unknown);
|
||||
const CiString<20> input_connector_type = connector_type.value_or(ConnectorEnumStringType::Unknown);
|
||||
const bool connector_type_unknown = evse_connector_type == ConnectorEnumStringType::Unknown ||
|
||||
input_connector_type == ConnectorEnumStringType::Unknown;
|
||||
|
||||
if (connector_type_unknown || evse_connector_type == input_connector_type) {
|
||||
type_found = true;
|
||||
// We found an available connector, also store the status.
|
||||
found_status = connector_status;
|
||||
if (found_status == ConnectorStatusEnum::Available) {
|
||||
// There is an available connector with the correct type and status: we don't have to search
|
||||
// any further.
|
||||
return found_status;
|
||||
}
|
||||
|
||||
// If status is not available, we keep on searching. If no connector is available, the status of
|
||||
// (at least one of) the connectors is stored to return that later if no available connector is found.
|
||||
}
|
||||
}
|
||||
|
||||
if (!type_found) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return found_status;
|
||||
}
|
||||
|
||||
void Evse::try_resume_transaction() {
|
||||
// Get an open transactions from the database and resume it if there is one
|
||||
try {
|
||||
auto transaction = this->database_handler->transaction_get(evse_id);
|
||||
if (transaction == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->id_connector_map.count(transaction->connector_id) != 0) {
|
||||
this->transaction = std::move(transaction);
|
||||
this->start_metering_timers(this->transaction->start_time);
|
||||
} else {
|
||||
EVLOG_error << "Can't resume transaction on evse_id " << evse_id << " for non existent connector "
|
||||
<< transaction->connector_id;
|
||||
|
||||
try {
|
||||
this->database_handler->transaction_delete(transaction->transactionId);
|
||||
} catch (const QueryExecutionException& e) {
|
||||
EVLOG_error << "Can't delete transaction: " << e.what();
|
||||
}
|
||||
|
||||
// Todo: Can we drop the transaction like this or do we still want to transmit an ended message?
|
||||
}
|
||||
} catch (const QueryExecutionException& e) {
|
||||
EVLOG_error << "Can't fetch transaction on evse_id " << evse_id << ": " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
void Evse::delete_database_transaction() {
|
||||
try {
|
||||
auto transaction = this->database_handler->transaction_get(evse_id);
|
||||
if (transaction != nullptr) {
|
||||
this->database_handler->transaction_delete(transaction->transactionId);
|
||||
}
|
||||
} catch (const QueryExecutionException& e) {
|
||||
EVLOG_error << "Can't delete transaction: " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<CiString<20>> Evse::get_evse_connector_type(const std::uint32_t connector_id) const {
|
||||
|
||||
auto connector = this->get_connector(static_cast<std::int32_t>(connector_id));
|
||||
if (connector == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (connector_id > std::numeric_limits<std::int32_t>::max()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const ComponentVariable connector_cv = ConnectorComponentVariables::get_component_variable(
|
||||
this->evse_id, clamp_to<std::int32_t>(connector_id), ConnectorComponentVariables::Type);
|
||||
|
||||
const std::optional<std::string> connector_type =
|
||||
this->device_model.get_optional_value<std::string>(connector_cv, AttributeEnum::Actual);
|
||||
if (!connector_type.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return connector_type.value();
|
||||
}
|
||||
|
||||
void Evse::open_transaction(const std::string& transaction_id, const std::int32_t connector_id,
|
||||
const DateTime& timestamp, const MeterValue& meter_start,
|
||||
const std::optional<IdToken>& id_token, const std::optional<IdToken>& /*group_id_token*/,
|
||||
const std::optional<std::int32_t> /*reservation_id*/,
|
||||
const ChargingStateEnum charging_state) {
|
||||
if (this->id_connector_map.count(connector_id) == 0) {
|
||||
EVLOG_AND_THROW(std::runtime_error("Attempt to start transaction at invalid connector_id"));
|
||||
}
|
||||
|
||||
const bool tx_database_enabled =
|
||||
this->device_model.get_optional_value<bool>(ControllerComponentVariables::ResumeTransactionsOnBoot)
|
||||
.value_or(false);
|
||||
|
||||
this->transaction = std::make_unique<EnhancedTransaction>(*this->database_handler.get(), tx_database_enabled);
|
||||
this->transaction->transactionId = transaction_id;
|
||||
this->transaction->connector_id = connector_id;
|
||||
this->transaction->id_token_sent = id_token.has_value();
|
||||
this->transaction->start_time = timestamp;
|
||||
this->transaction->active_energy_import_start_value = this->get_active_import_register_meter_value();
|
||||
this->transaction->chargingState = charging_state;
|
||||
|
||||
try {
|
||||
this->database_handler->transaction_metervalues_insert(this->transaction->transactionId.get(), meter_start);
|
||||
} catch (const QueryExecutionException& e) {
|
||||
EVLOG_warning << "Could not insert transaction meter values of transaction: "
|
||||
<< this->transaction->transactionId.get() << " into database: " << e.what();
|
||||
} catch (const std::invalid_argument& e) {
|
||||
EVLOG_warning << "Could not insert transaction meter values of transaction: "
|
||||
<< this->transaction->transactionId.get() << " into database: " << e.what();
|
||||
}
|
||||
|
||||
this->start_metering_timers(timestamp);
|
||||
|
||||
if (tx_database_enabled) {
|
||||
try {
|
||||
this->database_handler->transaction_insert(*this->transaction, this->evse_id);
|
||||
} catch (const QueryExecutionException& e) {
|
||||
// Delete previous transactions that should not exist anyway and try again. Otherwise throw to higher
|
||||
// level
|
||||
this->delete_database_transaction();
|
||||
this->database_handler->transaction_insert(*this->transaction, this->evse_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Evse::close_transaction(const DateTime& /*timestamp*/, const MeterValue& meter_stop, const ReasonEnum& reason) {
|
||||
if (this->transaction == nullptr) {
|
||||
EVLOG_warning << "Received attempt to stop a transaction without an active transaction";
|
||||
return;
|
||||
}
|
||||
|
||||
this->transaction->stoppedReason.emplace(reason);
|
||||
|
||||
// First stop all the timers to make sure the meter_stop is the last one in the database
|
||||
this->transaction->sampled_tx_updated_meter_values_timer.stop();
|
||||
this->transaction->sampled_tx_ended_meter_values_timer.stop();
|
||||
this->transaction->aligned_tx_updated_meter_values_timer.stop();
|
||||
this->transaction->aligned_tx_ended_meter_values_timer.stop();
|
||||
|
||||
try {
|
||||
this->database_handler->transaction_metervalues_insert(this->transaction->transactionId.get(), meter_stop);
|
||||
} catch (const QueryExecutionException& e) {
|
||||
EVLOG_warning << "Could not insert transaction meter values of transaction: "
|
||||
<< this->transaction->transactionId.get() << " into database: " << e.what();
|
||||
} catch (const std::invalid_argument& e) {
|
||||
EVLOG_warning << "Could not insert transaction meter values of transaction: "
|
||||
<< this->transaction->transactionId.get() << " into database: " << e.what();
|
||||
}
|
||||
// Clear for non transaction aligned metervalues
|
||||
this->aligned_data_updated.clear_values();
|
||||
}
|
||||
|
||||
void Evse::start_checking_max_energy_on_invalid_id() {
|
||||
if (this->transaction != nullptr) {
|
||||
this->transaction->check_max_active_import_energy = true;
|
||||
this->check_max_energy_on_invalid_id();
|
||||
} else {
|
||||
EVLOG_error << "Trying to start \"MaxEnergyOnInvalidId\" checking without an active transaction";
|
||||
}
|
||||
}
|
||||
|
||||
bool Evse::has_active_transaction() const {
|
||||
return this->transaction != nullptr;
|
||||
}
|
||||
|
||||
bool Evse::has_active_transaction(std::int32_t connector_id) const {
|
||||
if (this->id_connector_map.count(connector_id) == 0) {
|
||||
EVLOG_warning << "has_active_transaction called for invalid connector_id";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->transaction == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this->transaction->connector_id == connector_id;
|
||||
}
|
||||
|
||||
void Evse::release_transaction() {
|
||||
try {
|
||||
this->database_handler->transaction_metervalues_clear(this->transaction->transactionId);
|
||||
this->database_handler->transaction_delete(this->transaction->transactionId);
|
||||
} catch (const everest::db::Exception& e) {
|
||||
EVLOG_error << "Could not clear transaction meter values: " << e.what();
|
||||
}
|
||||
this->transaction = nullptr;
|
||||
|
||||
this->reset_pricing_triggers();
|
||||
}
|
||||
|
||||
std::unique_ptr<EnhancedTransaction>& Evse::get_transaction() {
|
||||
return this->transaction;
|
||||
}
|
||||
|
||||
void Evse::submit_event(const std::int32_t connector_id, ConnectorEvent event) {
|
||||
this->id_connector_map.at(connector_id)->submit_event(event);
|
||||
}
|
||||
|
||||
void Evse::on_meter_value(const MeterValue& meter_value) {
|
||||
const std::lock_guard<std::recursive_mutex> lk(this->meter_value_mutex);
|
||||
this->meter_value = meter_value;
|
||||
this->aligned_data_updated.set_values(meter_value);
|
||||
this->aligned_data_tx_end.set_values(meter_value);
|
||||
this->check_max_energy_on_invalid_id();
|
||||
this->send_meter_value_on_pricing_trigger(meter_value);
|
||||
}
|
||||
|
||||
MeterValue Evse::get_meter_value() {
|
||||
const std::lock_guard<std::recursive_mutex> lk(this->meter_value_mutex);
|
||||
return this->meter_value;
|
||||
}
|
||||
|
||||
MeterValue Evse::get_idle_meter_value() {
|
||||
return this->aligned_data_updated.retrieve_processed_values();
|
||||
}
|
||||
|
||||
void Evse::clear_idle_meter_values() {
|
||||
this->aligned_data_updated.clear_values();
|
||||
}
|
||||
|
||||
std::optional<float> Evse::get_active_import_register_meter_value() {
|
||||
const std::lock_guard<std::recursive_mutex> lk(this->meter_value_mutex);
|
||||
auto it = std::find_if(
|
||||
this->meter_value.sampledValue.begin(), this->meter_value.sampledValue.end(), [](const SampledValue& value) {
|
||||
return value.measurand == MeasurandEnum::Energy_Active_Import_Register and !value.phase.has_value();
|
||||
});
|
||||
if (it != this->meter_value.sampledValue.end()) {
|
||||
return get_normalized_energy_value(*it);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void Evse::check_max_energy_on_invalid_id() {
|
||||
// Handle E05.02
|
||||
auto max_energy_on_invalid_id =
|
||||
this->device_model.get_optional_value<std::int32_t>(ControllerComponentVariables::MaxEnergyOnInvalidId);
|
||||
auto& transaction = this->transaction;
|
||||
if (transaction != nullptr and max_energy_on_invalid_id.has_value() and
|
||||
transaction->check_max_active_import_energy) {
|
||||
const auto opt_energy_value = this->get_active_import_register_meter_value();
|
||||
auto active_energy_import_start_value = transaction->active_energy_import_start_value;
|
||||
if (opt_energy_value.has_value() and active_energy_import_start_value.has_value()) {
|
||||
auto charged_energy = opt_energy_value.value() - active_energy_import_start_value.value();
|
||||
|
||||
if (std::lround(charged_energy) >= max_energy_on_invalid_id.value()) {
|
||||
this->pause_charging_callback(this->evse_id);
|
||||
transaction->check_max_active_import_energy = false; // No need to check anymore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Evse::start_metering_timers(const DateTime& timestamp) {
|
||||
|
||||
this->aligned_data_updated.clear_values();
|
||||
this->aligned_data_tx_end.clear_values();
|
||||
|
||||
const auto sampled_data_tx_updated_interval = std::chrono::seconds(
|
||||
this->device_model.get_value<int>(ControllerComponentVariables::SampledDataTxUpdatedInterval));
|
||||
const auto sampled_data_tx_ended_interval = std::chrono::seconds(
|
||||
this->device_model.get_value<int>(ControllerComponentVariables::SampledDataTxEndedInterval));
|
||||
const auto aligned_data_tx_updated_interval =
|
||||
std::chrono::seconds(this->device_model.get_value<int>(ControllerComponentVariables::AlignedDataInterval));
|
||||
const auto aligned_data_tx_ended_interval = std::chrono::seconds(
|
||||
this->device_model.get_value<int>(ControllerComponentVariables::AlignedDataTxEndedInterval));
|
||||
|
||||
if (sampled_data_tx_updated_interval > 0s) {
|
||||
this->transaction->sampled_tx_updated_meter_values_timer.interval_starting_from(
|
||||
[this] { this->transaction_meter_value_req(this->get_meter_value(), *this->transaction); },
|
||||
sampled_data_tx_updated_interval, date::utc_clock::to_sys(timestamp.to_time_point()));
|
||||
}
|
||||
|
||||
if (sampled_data_tx_ended_interval > 0s) {
|
||||
this->transaction->sampled_tx_ended_meter_values_timer.interval_starting_from(
|
||||
[this] {
|
||||
try {
|
||||
this->database_handler->transaction_metervalues_insert(this->transaction->transactionId.get(),
|
||||
this->get_meter_value());
|
||||
} catch (const QueryExecutionException& e) {
|
||||
EVLOG_warning << "Could not insert transaction meter values of transaction: "
|
||||
<< this->transaction->transactionId.get() << " into database: " << e.what();
|
||||
} catch (const std::invalid_argument& e) {
|
||||
EVLOG_warning << "Could not insert transaction meter values of transaction: "
|
||||
<< this->transaction->transactionId.get() << " into database: " << e.what();
|
||||
}
|
||||
},
|
||||
sampled_data_tx_ended_interval, date::utc_clock::to_sys(timestamp.to_time_point()));
|
||||
}
|
||||
|
||||
if (aligned_data_tx_updated_interval > 0s) {
|
||||
this->transaction->aligned_tx_updated_meter_values_timer.interval_starting_from(
|
||||
[this, aligned_data_tx_updated_interval] {
|
||||
if (this->device_model.get_optional_value<bool>(ControllerComponentVariables::AlignedDataSendDuringIdle)
|
||||
.value_or(false)) {
|
||||
return;
|
||||
}
|
||||
auto meter_value = this->aligned_data_updated.retrieve_processed_values();
|
||||
|
||||
// If empty fallback on last updated metervalue
|
||||
if (meter_value.sampledValue.empty()) {
|
||||
meter_value = this->get_meter_value();
|
||||
}
|
||||
|
||||
for (auto& item : meter_value.sampledValue) {
|
||||
item.context = ReadingContextEnum::Sample_Clock;
|
||||
}
|
||||
if (this->device_model
|
||||
.get_optional_value<bool>(ControllerComponentVariables::RoundClockAlignedTimestamps)
|
||||
.value_or(false)) {
|
||||
meter_value.timestamp = utils::align_timestamp(DateTime{}, aligned_data_tx_updated_interval);
|
||||
}
|
||||
this->transaction_meter_value_req(meter_value, *this->transaction);
|
||||
this->aligned_data_updated.clear_values();
|
||||
},
|
||||
aligned_data_tx_updated_interval,
|
||||
std::chrono::floor<date::days>(date::utc_clock::to_sys(date::utc_clock::now())));
|
||||
}
|
||||
|
||||
if (aligned_data_tx_ended_interval > 0s) {
|
||||
auto store_aligned_metervalue = [this, aligned_data_tx_ended_interval] {
|
||||
auto meter_value = this->aligned_data_tx_end.retrieve_processed_values();
|
||||
|
||||
// If empty fallback on last updated metervalue
|
||||
if (meter_value.sampledValue.empty()) {
|
||||
meter_value = this->get_meter_value();
|
||||
}
|
||||
|
||||
for (auto& item : meter_value.sampledValue) {
|
||||
item.context = ReadingContextEnum::Sample_Clock;
|
||||
}
|
||||
if (this->device_model.get_optional_value<bool>(ControllerComponentVariables::RoundClockAlignedTimestamps)
|
||||
.value_or(false)) {
|
||||
meter_value.timestamp = utils::align_timestamp(DateTime{}, aligned_data_tx_ended_interval);
|
||||
}
|
||||
try {
|
||||
this->database_handler->transaction_metervalues_insert(this->transaction->transactionId.get(),
|
||||
meter_value);
|
||||
} catch (const QueryExecutionException& e) {
|
||||
EVLOG_warning << "Could not insert transaction meter values of transaction: "
|
||||
<< this->transaction->transactionId.get() << " into database: " << e.what();
|
||||
} catch (const std::invalid_argument& e) {
|
||||
EVLOG_warning << "Could not insert transaction meter values of transaction: "
|
||||
<< this->transaction->transactionId.get() << " into database: " << e.what();
|
||||
}
|
||||
this->aligned_data_tx_end.clear_values();
|
||||
};
|
||||
|
||||
auto next_interval = transaction->aligned_tx_ended_meter_values_timer.interval_starting_from(
|
||||
store_aligned_metervalue, aligned_data_tx_ended_interval,
|
||||
std::chrono::floor<date::days>(date::utc_clock::to_sys(date::utc_clock::now())));
|
||||
|
||||
// Store an extra aligned metervalue to fix the edge case where a transaction is started just before an
|
||||
// interval but this code is processed just after the interval. For example, aligned interval = 1 min,
|
||||
// transaction started at 11:59:59.500 and we get here on 12:00:00.100. There is still the expectation for
|
||||
// us to add a metervalue at timepoint 12:00:00.000 which we do with this.
|
||||
if (date::utc_clock::to_sys(timestamp.to_time_point()) <= (next_interval - aligned_data_tx_ended_interval)) {
|
||||
store_aligned_metervalue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Evse::set_meter_value_pricing_triggers(
|
||||
std::optional<double> trigger_metervalue_on_power_kw, std::optional<double> trigger_metervalue_on_energy_kwh,
|
||||
std::optional<DateTime> trigger_metervalue_at_time,
|
||||
std::function<void(const std::vector<MeterValue>& meter_values)> send_metervalue_function,
|
||||
boost::asio::io_context& io_context) {
|
||||
|
||||
EVLOG_debug << "Set metervalue pricing triggers: "
|
||||
<< (trigger_metervalue_at_time.has_value()
|
||||
? "at time: " + trigger_metervalue_at_time.value().to_rfc3339()
|
||||
: "no time pricing trigger")
|
||||
<< (trigger_metervalue_on_energy_kwh.has_value()
|
||||
? ", on energy kWh: " + std::to_string(trigger_metervalue_on_energy_kwh.value())
|
||||
: ", No energy kWh trigger, ")
|
||||
<< (trigger_metervalue_on_power_kw.has_value()
|
||||
? ", on power kW: " + std::to_string(trigger_metervalue_on_power_kw.value())
|
||||
: ", No power kW trigger");
|
||||
|
||||
this->send_metervalue_function = send_metervalue_function;
|
||||
this->trigger_metervalue_on_power_kw = trigger_metervalue_on_power_kw;
|
||||
this->trigger_metervalue_on_energy_kwh = trigger_metervalue_on_energy_kwh;
|
||||
if (this->trigger_metervalue_at_time_timer != nullptr and trigger_metervalue_at_time.has_value()) {
|
||||
this->trigger_metervalue_at_time_timer->stop();
|
||||
this->trigger_metervalue_at_time_timer = nullptr;
|
||||
}
|
||||
|
||||
if (not trigger_metervalue_at_time.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::chrono::time_point<date::utc_clock> trigger_timepoint = trigger_metervalue_at_time.value().to_time_point();
|
||||
const std::chrono::time_point<date::utc_clock> now = date::utc_clock::now();
|
||||
|
||||
if (trigger_timepoint < now) {
|
||||
EVLOG_error << "Could not set trigger metervalue because trigger time is in the past.";
|
||||
return;
|
||||
}
|
||||
|
||||
// Start a timer for the trigger 'atTime'.
|
||||
this->trigger_metervalue_at_time_timer = std::make_unique<Everest::SystemTimer>(&io_context, [this]() {
|
||||
EVLOG_error << "Sending metervalue in timer";
|
||||
|
||||
const MeterValue meter_value = utils::get_meter_value_with_measurands_applied(
|
||||
this->get_meter_value(), {MeasurandEnum::Energy_Active_Import_Register});
|
||||
if (meter_value.sampledValue.empty()) {
|
||||
EVLOG_error << "Send latest meter value because of chargepoint time trigger failed";
|
||||
} else {
|
||||
const MeterValue mv = utils::set_meter_value_reading_context(meter_value, ReadingContextEnum::Other);
|
||||
this->send_metervalue_function({mv});
|
||||
}
|
||||
});
|
||||
EVLOG_error << "Set trigger metervalue at time " << trigger_timepoint;
|
||||
|
||||
this->trigger_metervalue_at_time_timer->at(trigger_timepoint);
|
||||
}
|
||||
|
||||
void Evse::reset_pricing_triggers() {
|
||||
this->last_triggered_metervalue_power_kw = std::nullopt;
|
||||
this->trigger_metervalue_on_power_kw = std::nullopt;
|
||||
this->trigger_metervalue_on_energy_kwh = std::nullopt;
|
||||
this->send_metervalue_function = nullptr;
|
||||
|
||||
if (this->trigger_metervalue_at_time_timer != nullptr) {
|
||||
this->trigger_metervalue_at_time_timer->stop();
|
||||
this->trigger_metervalue_at_time_timer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Evse::send_meter_value_on_pricing_trigger(const MeterValue& meter_value) {
|
||||
bool meter_value_sent = false;
|
||||
// Check if there is a kwh trigger and if the value is exceeded.
|
||||
if (this->trigger_metervalue_on_energy_kwh.has_value()) {
|
||||
const double trigger_energy_kwh = this->trigger_metervalue_on_energy_kwh.value();
|
||||
if (this->send_metervalue_function == nullptr) {
|
||||
EVLOG_error << "Cost and price metervalue kwh trigger: Can not send metervalue because the send metervalue "
|
||||
"function is not set.";
|
||||
this->trigger_metervalue_on_energy_kwh.reset();
|
||||
} else {
|
||||
const std::optional<float> active_import_register_meter_value_wh = get_active_import_register_meter_value();
|
||||
if (active_import_register_meter_value_wh.has_value() and
|
||||
static_cast<double>(active_import_register_meter_value_wh.value()) >= trigger_energy_kwh * 1000) {
|
||||
const MeterValue active_import_meter_value = utils::get_meter_value_with_measurands_applied(
|
||||
meter_value, {MeasurandEnum::Energy_Active_Import_Register, MeasurandEnum::Power_Active_Import});
|
||||
if (active_import_meter_value.sampledValue.empty()) {
|
||||
EVLOG_error
|
||||
<< "No current active import register metervalue found. Can not send trigger metervalue.";
|
||||
} else {
|
||||
const MeterValue to_send =
|
||||
utils::set_meter_value_reading_context(active_import_meter_value, ReadingContextEnum::Other);
|
||||
this->send_metervalue_function({to_send});
|
||||
this->trigger_metervalue_on_energy_kwh.reset();
|
||||
meter_value_sent = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there is a power kw trigger and if that is triggered. For the power kw trigger, we added hysterisis
|
||||
// to prevent constant triggering.
|
||||
const std::optional<float> active_power_meter_value = utils::get_total_power_active_import(meter_value);
|
||||
|
||||
if (!this->trigger_metervalue_on_power_kw.has_value() or !active_power_meter_value.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const double trigger_power_kw = this->trigger_metervalue_on_power_kw.value();
|
||||
if (this->send_metervalue_function == nullptr) {
|
||||
EVLOG_error << "Cost and price metervalue wh trigger: Can not send metervalue because the send metervalue "
|
||||
"function is not set.";
|
||||
// Remove trigger because next time function is not set as well (this is probably a bug because it should be
|
||||
// set in the `set_meter_value_pricing_triggers` function together with the trigger values).
|
||||
this->trigger_metervalue_on_energy_kwh.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->last_triggered_metervalue_power_kw.has_value()) {
|
||||
// Hysteresis of 5% to avoid repetitive triggers when the power fluctuates around the trigger level.
|
||||
const double hysterisis_kw = trigger_power_kw * 0.05;
|
||||
const double triggered_power_kw = this->last_triggered_metervalue_power_kw.value();
|
||||
const auto current_metervalue_w = static_cast<double>(active_power_meter_value.value());
|
||||
const double current_metervalue_kw = current_metervalue_w / 1000;
|
||||
|
||||
if ( // Check if trigger value is crossed in upward direction.
|
||||
(triggered_power_kw < trigger_power_kw and current_metervalue_kw >= (trigger_power_kw + hysterisis_kw)) or
|
||||
// Check if trigger value is crossed in downward direction.
|
||||
(triggered_power_kw > trigger_power_kw and current_metervalue_kw <= (trigger_power_kw - hysterisis_kw))) {
|
||||
|
||||
// Power threshold is crossed, send metervalues.
|
||||
if (!meter_value_sent) {
|
||||
// Only send metervalue if it is not sent yet, otherwise only the last triggered metervalue is set.
|
||||
const MeterValue mv = utils::set_meter_value_reading_context(meter_value, ReadingContextEnum::Other);
|
||||
this->send_metervalue_function({mv});
|
||||
}
|
||||
|
||||
// Also when metervalue is sent, we want to set the last triggered metervalue.
|
||||
this->last_triggered_metervalue_power_kw = current_metervalue_kw;
|
||||
}
|
||||
} else {
|
||||
// Send metervalue anyway since we have no previous metervalue stored and don't know if we should send any
|
||||
if (!meter_value_sent) {
|
||||
// Only send metervalue if it is not sent yet, otherwise only the last triggered metervalue is set.
|
||||
const MeterValue mv = utils::set_meter_value_reading_context(meter_value, ReadingContextEnum::Other);
|
||||
this->send_metervalue_function({mv});
|
||||
}
|
||||
this->last_triggered_metervalue_power_kw = active_power_meter_value.value() / 1000;
|
||||
}
|
||||
}
|
||||
|
||||
void Evse::set_evse_operative_status(OperationalStatusEnum new_status, bool persist) {
|
||||
this->component_state_manager->set_evse_individual_operational_status(this->evse_id, new_status, persist);
|
||||
}
|
||||
|
||||
void Evse::set_connector_operative_status(std::int32_t connector_id, OperationalStatusEnum new_status, bool persist) {
|
||||
this->id_connector_map.at(connector_id)->set_connector_operative_status(new_status, persist);
|
||||
}
|
||||
|
||||
void Evse::restore_connector_operative_status(std::int32_t connector_id) {
|
||||
this->id_connector_map.at(connector_id)->restore_connector_operative_status();
|
||||
}
|
||||
|
||||
OperationalStatusEnum Evse::get_connector_effective_operational_status(const std::int32_t connector_id) {
|
||||
return this->component_state_manager->get_connector_effective_operational_status(this->get_id(), connector_id);
|
||||
}
|
||||
|
||||
OperationalStatusEnum Evse::get_effective_operational_status() {
|
||||
return this->component_state_manager->get_evse_effective_operational_status(this->evse_id);
|
||||
}
|
||||
|
||||
Connector* Evse::get_connector(std::int32_t connector_id) const {
|
||||
if (connector_id <= 0 or connector_id > this->get_number_of_connectors()) {
|
||||
std::stringstream err_msg;
|
||||
err_msg << "ConnectorID " << connector_id << " out of bounds for EVSE " << this->evse_id;
|
||||
throw std::logic_error(err_msg.str());
|
||||
}
|
||||
return this->id_connector_map.at(connector_id).get();
|
||||
}
|
||||
|
||||
CurrentPhaseType Evse::get_current_phase_type() {
|
||||
const ComponentVariable evse_variable =
|
||||
EvseComponentVariables::get_component_variable(this->evse_id, EvseComponentVariables::SupplyPhases);
|
||||
auto supply_phases = this->device_model.get_optional_value<std::int32_t>(evse_variable);
|
||||
if (supply_phases == std::nullopt) {
|
||||
return CurrentPhaseType::Unknown;
|
||||
}
|
||||
if (*supply_phases == 1 or *supply_phases == 3) {
|
||||
return CurrentPhaseType::AC;
|
||||
}
|
||||
if (*supply_phases == 0) {
|
||||
return CurrentPhaseType::DC;
|
||||
}
|
||||
|
||||
// NOTE: SupplyPhases should never be a value that isn't NULL, 1, 3, or 0.
|
||||
return CurrentPhaseType::Unknown;
|
||||
}
|
||||
|
||||
} // namespace v2
|
||||
} // namespace ocpp
|
||||
121
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v2/evse_manager.cpp
Normal file
121
tools/EVerest-main/lib/everest/ocpp/lib/ocpp/v2/evse_manager.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2024 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <ocpp/v2/evse_manager.hpp>
|
||||
|
||||
namespace ocpp {
|
||||
namespace v2 {
|
||||
|
||||
using EvseIteratorImpl = VectorOfUniquePtrIterator<EvseInterface>;
|
||||
|
||||
EvseManager::EvseManager(const std::map<std::int32_t, std::int32_t>& evse_connector_structure,
|
||||
DeviceModelAbstract& device_model, std::shared_ptr<DatabaseHandler> database_handler,
|
||||
std::shared_ptr<ComponentStateManagerInterface> component_state_manager,
|
||||
const std::function<void(const MeterValue& meter_value, EnhancedTransaction& transaction)>&
|
||||
transaction_meter_value_req,
|
||||
const std::function<void(std::int32_t evse_id)>& pause_charging_callback) {
|
||||
evses.reserve(evse_connector_structure.size());
|
||||
for (const auto& [evse_id, connectors] : evse_connector_structure) {
|
||||
evses.push_back(std::make_unique<Evse>(evse_id, connectors, device_model, database_handler,
|
||||
component_state_manager, transaction_meter_value_req,
|
||||
pause_charging_callback));
|
||||
}
|
||||
}
|
||||
|
||||
EvseManager::EvseIterator EvseManager::begin() {
|
||||
return EvseIterator(std::make_unique<EvseIteratorImpl>(this->evses.begin()));
|
||||
}
|
||||
EvseManager::EvseIterator EvseManager::end() {
|
||||
return EvseIterator(std::make_unique<EvseIteratorImpl>(this->evses.end()));
|
||||
}
|
||||
|
||||
EvseInterface& EvseManager::get_evse(std::int32_t id) {
|
||||
if (id <= 0 or id > this->evses.size()) {
|
||||
throw EvseOutOfRangeException(id);
|
||||
}
|
||||
return *this->evses.at(id - 1);
|
||||
}
|
||||
|
||||
const EvseInterface& EvseManager::get_evse(const std::int32_t id) const {
|
||||
if (id <= 0 or id > this->evses.size()) {
|
||||
throw EvseOutOfRangeException(id);
|
||||
}
|
||||
return *this->evses.at(id - 1);
|
||||
}
|
||||
|
||||
bool EvseManager::does_connector_exist(const std::int32_t evse_id, const CiString<20> connector_type) const {
|
||||
const EvseInterface* evse = nullptr;
|
||||
try {
|
||||
evse = &this->get_evse(evse_id);
|
||||
} catch (const EvseOutOfRangeException&) {
|
||||
EVLOG_error << "Evse id " << evse_id << " is not a valid evse id.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return evse->does_connector_exist(connector_type);
|
||||
}
|
||||
|
||||
bool EvseManager::does_evse_exist(const std::int32_t id) const {
|
||||
return id >= 0 && static_cast<std::uint64_t>(id) <= this->evses.size();
|
||||
}
|
||||
|
||||
bool EvseManager::are_all_connectors_effectively_inoperative() const {
|
||||
// Check that all connectors on all EVSEs are inoperative
|
||||
for (const auto& evse : this->evses) {
|
||||
for (int connector_id = 1; connector_id <= evse->get_number_of_connectors(); connector_id++) {
|
||||
const OperationalStatusEnum connector_status =
|
||||
evse->get_connector_effective_operational_status(connector_id);
|
||||
if (connector_status == OperationalStatusEnum::Operative) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t EvseManager::get_number_of_evses() const {
|
||||
return this->evses.size();
|
||||
}
|
||||
|
||||
std::optional<std::int32_t> EvseManager::get_transaction_evseid(const CiString<36>& transaction_id) const {
|
||||
for (const auto& evse : this->evses) {
|
||||
if (evse->has_active_transaction()) {
|
||||
if (transaction_id == evse->get_transaction()->get_transaction().transactionId) {
|
||||
return evse->get_id();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool EvseManager::any_transaction_active(const std::optional<EVSE>& evse) const {
|
||||
if (!evse.has_value()) {
|
||||
for (const auto& evse : this->evses) {
|
||||
if (evse->has_active_transaction()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return this->get_evse(evse.value().id).has_active_transaction();
|
||||
}
|
||||
|
||||
bool EvseManager::is_valid_evse(const EVSE& evse) const {
|
||||
return this->does_evse_exist(evse.id) and
|
||||
(!evse.connectorId.has_value() or
|
||||
this->get_evse(evse.id).get_number_of_connectors() >= evse.connectorId.value());
|
||||
}
|
||||
|
||||
// Free functions
|
||||
|
||||
void set_evse_connectors_unavailable(EvseInterface& evse, bool persist) {
|
||||
const std::uint32_t number_of_connectors = evse.get_number_of_connectors();
|
||||
|
||||
for (std::uint32_t i = 1; i <= number_of_connectors; ++i) {
|
||||
evse.set_connector_operative_status(static_cast<std::int32_t>(i), OperationalStatusEnum::Inoperative, persist);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace v2
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,627 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <ocpp/v2/functional_blocks/authorization.hpp>
|
||||
|
||||
#include <ocpp/common/connectivity_manager.hpp>
|
||||
#include <ocpp/common/constants.hpp>
|
||||
#include <ocpp/common/evse_security.hpp>
|
||||
#include <ocpp/v2/ctrlr_component_variables.hpp>
|
||||
#include <ocpp/v2/database_handler.hpp>
|
||||
#include <ocpp/v2/device_model.hpp>
|
||||
#include <ocpp/v2/functional_blocks/functional_block_context.hpp>
|
||||
#include <ocpp/v2/utils.hpp>
|
||||
|
||||
#include <ocpp/v2/messages/Authorize.hpp>
|
||||
#include <ocpp/v2/messages/ClearCache.hpp>
|
||||
#include <ocpp/v2/messages/GetLocalListVersion.hpp>
|
||||
#include <ocpp/v2/messages/SendLocalList.hpp>
|
||||
|
||||
namespace {
|
||||
///
|
||||
/// \brief Check if vector of authorization data has a duplicate id token.
|
||||
/// \param list List to check.
|
||||
/// \return True if there is a duplicate.
|
||||
///
|
||||
bool has_duplicate_in_list(const std::vector<ocpp::v2::AuthorizationData>& list);
|
||||
bool has_no_token_info(const ocpp::v2::AuthorizationData& item);
|
||||
} // namespace
|
||||
|
||||
ocpp::v2::Authorization::Authorization(const FunctionalBlockContext& context) :
|
||||
context(context), auth_cache_cleanup_handler_running(false) {
|
||||
}
|
||||
|
||||
ocpp::v2::Authorization::~Authorization() {
|
||||
stop_auth_cache_cleanup_thread();
|
||||
}
|
||||
|
||||
void ocpp::v2::Authorization::start_auth_cache_cleanup_thread() {
|
||||
if (!auth_cache_cleanup_handler_running) {
|
||||
auth_cache_cleanup_handler_running = true;
|
||||
this->auth_cache_cleanup_thread = std::thread(&Authorization::cache_cleanup_handler, this);
|
||||
}
|
||||
}
|
||||
|
||||
void ocpp::v2::Authorization::handle_message(const ocpp::EnhancedMessage<MessageType>& message) {
|
||||
const auto& json_message = message.message;
|
||||
|
||||
if (message.messageType == MessageType::ClearCache) {
|
||||
this->handle_clear_cache_req(json_message);
|
||||
} else if (message.messageType == MessageType::SendLocalList) {
|
||||
this->handle_send_local_authorization_list_req(json_message);
|
||||
} else if (message.messageType == MessageType::GetLocalListVersion) {
|
||||
this->handle_get_local_authorization_list_version_req(json_message);
|
||||
} else {
|
||||
throw MessageTypeNotImplementedException(message.messageType);
|
||||
}
|
||||
}
|
||||
|
||||
ocpp::v2::AuthorizeResponse
|
||||
ocpp::v2::Authorization::authorize_req(const IdToken id_token, const std::optional<ocpp::CiString<10000>>& certificate,
|
||||
const std::optional<std::vector<OCSPRequestData>>& ocsp_request_data) {
|
||||
AuthorizeRequest req;
|
||||
req.idToken = id_token;
|
||||
req.certificate = certificate;
|
||||
req.iso15118CertificateHashData = ocsp_request_data;
|
||||
|
||||
AuthorizeResponse response;
|
||||
response.idTokenInfo.status = AuthorizationStatusEnum::Unknown;
|
||||
|
||||
if (!this->context.connectivity_manager.is_websocket_connected()) {
|
||||
return response;
|
||||
}
|
||||
|
||||
const ocpp::Call<AuthorizeRequest> call(req);
|
||||
auto future = this->context.message_dispatcher.dispatch_call_async(call);
|
||||
|
||||
if (future.wait_for(DEFAULT_WAIT_FOR_FUTURE_TIMEOUT) == std::future_status::timeout) {
|
||||
EVLOG_warning << "Waiting for Authorize Response future timed out!";
|
||||
return response;
|
||||
}
|
||||
|
||||
EnhancedMessage<MessageType> enhanced_message;
|
||||
try {
|
||||
enhanced_message = future.get();
|
||||
} catch (const EnumConversionException& e) {
|
||||
EVLOG_error << "EnumConversionException during handling of message: " << e.what();
|
||||
return response;
|
||||
}
|
||||
|
||||
if (enhanced_message.messageType != MessageType::AuthorizeResponse) {
|
||||
return response;
|
||||
}
|
||||
|
||||
try {
|
||||
const ocpp::CallResult<AuthorizeResponse> call_result = enhanced_message.message;
|
||||
return call_result.msg;
|
||||
} catch (const EnumConversionException& e) {
|
||||
EVLOG_error << "EnumConversionException during handling of AuthorizeResponse: " << e.what();
|
||||
auto call_error = CallError(enhanced_message.uniqueId, "FormationViolation", e.what(), json({}));
|
||||
this->context.message_dispatcher.dispatch_call_error(call_error);
|
||||
return response;
|
||||
} catch (const json::exception& e) {
|
||||
EVLOG_error << "json::exception during handling of AuthorizeResponse: " << e.what();
|
||||
auto call_error = CallError(enhanced_message.uniqueId, "FormationViolation", e.what(), json({}));
|
||||
this->context.message_dispatcher.dispatch_call_error(call_error);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
void ocpp::v2::Authorization::trigger_authorization_cache_cleanup() {
|
||||
{
|
||||
const std::scoped_lock lk(this->auth_cache_cleanup_mutex);
|
||||
this->auth_cache_cleanup_required = true;
|
||||
}
|
||||
this->auth_cache_cleanup_cv.notify_one();
|
||||
}
|
||||
|
||||
void ocpp::v2::Authorization::update_authorization_cache_size() {
|
||||
auto& auth_cache_size = ControllerComponentVariables::AuthCacheStorage;
|
||||
if (auth_cache_size.variable.has_value()) {
|
||||
try {
|
||||
auto size = this->context.database_handler.authorization_cache_get_binary_size();
|
||||
this->context.device_model.set_read_only_value(auth_cache_size.component, auth_cache_size.variable.value(),
|
||||
AttributeEnum::Actual, std::to_string(size),
|
||||
VARIABLE_ATTRIBUTE_VALUE_SOURCE_INTERNAL);
|
||||
} catch (const everest::db::Exception& e) {
|
||||
EVLOG_warning << "Could not get authorization cache binary size from database: " << e.what();
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_warning << "Could not get authorization cache binary size from database" << e.what();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ocpp::v2::Authorization::is_auth_cache_ctrlr_enabled() {
|
||||
return this->context.device_model.get_optional_value<bool>(ControllerComponentVariables::AuthCacheCtrlrEnabled)
|
||||
.value_or(false);
|
||||
}
|
||||
|
||||
void ocpp::v2::Authorization::authorization_cache_insert_entry(const std::string& id_token_hash,
|
||||
const IdTokenInfo& id_token_info) {
|
||||
this->context.database_handler.authorization_cache_insert_entry(id_token_hash, id_token_info);
|
||||
}
|
||||
|
||||
std::optional<ocpp::v2::AuthorizationCacheEntry>
|
||||
ocpp::v2::Authorization::authorization_cache_get_entry(const std::string& id_token_hash) {
|
||||
return this->context.database_handler.authorization_cache_get_entry(id_token_hash);
|
||||
}
|
||||
|
||||
void ocpp::v2::Authorization::authorization_cache_delete_entry(const std::string& id_token_hash) {
|
||||
this->context.database_handler.authorization_cache_delete_entry(id_token_hash);
|
||||
}
|
||||
|
||||
ocpp::v2::AuthorizeResponse
|
||||
ocpp::v2::Authorization::validate_token(const IdToken id_token, const std::optional<CiString<10000>>& certificate,
|
||||
const std::optional<std::vector<OCSPRequestData>>& ocsp_request_data) {
|
||||
// TODO(piet): C01.FR.17
|
||||
|
||||
AuthorizeResponse response;
|
||||
const bool is_online = this->context.connectivity_manager.is_websocket_connected();
|
||||
|
||||
// C03.FR.01 && C05.FR.01: We SHALL NOT send an authorize reqeust for IdTokenType Central
|
||||
if (id_token.type == IdTokenEnumStringType::Central or id_token.type == IdTokenEnumStringType::NoAuthorization or
|
||||
!this->context.device_model.get_optional_value<bool>(ControllerComponentVariables::AuthCtrlrEnabled)
|
||||
.value_or(false)) {
|
||||
response.idTokenInfo.status = AuthorizationStatusEnum::Accepted;
|
||||
return response;
|
||||
}
|
||||
|
||||
const bool local_authorize_offline =
|
||||
this->context.device_model.get_optional_value<bool>(ControllerComponentVariables::LocalAuthorizeOffline)
|
||||
.value_or(false);
|
||||
const bool disabled_remote_auth =
|
||||
this->context.device_model.get_optional_value<bool>(ControllerComponentVariables::DisableRemoteAuthorization)
|
||||
.value_or(false);
|
||||
|
||||
// C07: Authorization using contract certificates
|
||||
if (id_token.type == IdTokenEnumStringType::eMAID) {
|
||||
// Temporary variable that is set to true to avoid immediate response to allow the local auth list
|
||||
// or auth cache to be tried
|
||||
bool try_local_auth_list_or_cache = false;
|
||||
bool forwarded_to_csms = false;
|
||||
|
||||
// If OCSP data is provided as argument, use it
|
||||
if (is_online and ocsp_request_data.has_value()) {
|
||||
EVLOG_info << "Online: Pass provided OCSP data to CSMS";
|
||||
response = this->authorize_req(id_token, std::nullopt, ocsp_request_data);
|
||||
forwarded_to_csms = true;
|
||||
} else if (certificate.has_value()) {
|
||||
// First try to validate the contract certificate locally
|
||||
const CertificateValidationResult local_verify_result = this->context.evse_security.verify_certificate(
|
||||
certificate.value().get(), {ocpp::LeafCertificateType::MO, ocpp::LeafCertificateType::V2G});
|
||||
EVLOG_info << "Local contract validation result: " << local_verify_result;
|
||||
|
||||
const bool central_contract_validation_allowed =
|
||||
this->context.device_model
|
||||
.get_optional_value<bool>(ControllerComponentVariables::CentralContractValidationAllowed)
|
||||
.value_or(false);
|
||||
const bool contract_validation_offline =
|
||||
this->context.device_model
|
||||
.get_optional_value<bool>(ControllerComponentVariables::ContractValidationOffline)
|
||||
.value_or(false);
|
||||
|
||||
// C07.FR.01: When CS is online, it shall send an AuthorizeRequest
|
||||
// C07.FR.02: The AuthorizeRequest shall at least contain the OCSP data
|
||||
// TODO: local validation results are ignored if response is based only on OCSP data, is that acceptable?
|
||||
if (is_online) {
|
||||
// If no OCSP data was provided, check for a contract root
|
||||
if (local_verify_result == CertificateValidationResult::IssuerNotFound) {
|
||||
// C07.FR.06: Pass contract validation to CSMS when no contract root is found
|
||||
if (central_contract_validation_allowed) {
|
||||
EVLOG_info << "Online: No local contract root found. Pass contract validation to CSMS";
|
||||
response = this->authorize_req(id_token, certificate, std::nullopt);
|
||||
forwarded_to_csms = true;
|
||||
} else {
|
||||
EVLOG_warning << "Online: Central Contract Validation not allowed";
|
||||
response.idTokenInfo.status = AuthorizationStatusEnum::Invalid;
|
||||
}
|
||||
} else {
|
||||
// Try to generate the OCSP data from the certificate chain and use that
|
||||
const auto generated_ocsp_request_data_list = ocpp::evse_security_conversions::to_ocpp_v2(
|
||||
this->context.evse_security.get_mo_ocsp_request_data(certificate.value()));
|
||||
if (!generated_ocsp_request_data_list.empty()) {
|
||||
EVLOG_info << "Online: Pass generated OCSP data to CSMS";
|
||||
response = this->authorize_req(id_token, std::nullopt, generated_ocsp_request_data_list);
|
||||
forwarded_to_csms = true;
|
||||
} else {
|
||||
if (central_contract_validation_allowed) {
|
||||
EVLOG_info << "Online: OCSP data could not be generated. Pass contract validation to CSMS";
|
||||
response = this->authorize_req(id_token, certificate, std::nullopt);
|
||||
forwarded_to_csms = true;
|
||||
} else {
|
||||
EVLOG_warning
|
||||
<< "Online: OCSP data could not be generated and CentralContractValidation not allowed";
|
||||
response.idTokenInfo.status = AuthorizationStatusEnum::Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // Offline
|
||||
// C07.FR.08: CS shall try to validate the contract locally
|
||||
if (contract_validation_offline) {
|
||||
EVLOG_info << "Offline: contract " << local_verify_result;
|
||||
switch (local_verify_result) {
|
||||
// C07.FR.09: CS shall lookup the eMAID in Local Auth List or Auth Cache when
|
||||
// local validation succeeded
|
||||
case CertificateValidationResult::Valid:
|
||||
// In C07.FR.09 LocalAuthorizeOffline is mentioned, this seems to be a generic config item
|
||||
// that applies to Local Auth List and Auth Cache, but since there are no requirements about
|
||||
// it, lets check it here
|
||||
if (local_authorize_offline) {
|
||||
try_local_auth_list_or_cache = true;
|
||||
} else {
|
||||
// No requirement states what to do when ContractValidationOffline is true
|
||||
// and LocalAuthorizeOffline is false
|
||||
response.idTokenInfo.status = AuthorizationStatusEnum::Unknown;
|
||||
response.certificateStatus = AuthorizeCertificateStatusEnum::Accepted;
|
||||
}
|
||||
break;
|
||||
case CertificateValidationResult::Expired:
|
||||
response.idTokenInfo.status = AuthorizationStatusEnum::Expired;
|
||||
response.certificateStatus = AuthorizeCertificateStatusEnum::CertificateExpired;
|
||||
break;
|
||||
case CertificateValidationResult::InvalidSignature:
|
||||
case CertificateValidationResult::IssuerNotFound:
|
||||
case CertificateValidationResult::InvalidLeafSignature:
|
||||
case CertificateValidationResult::InvalidChain:
|
||||
case CertificateValidationResult::Unknown:
|
||||
response.idTokenInfo.status = AuthorizationStatusEnum::Unknown;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// C07.FR.07: CS shall not allow charging
|
||||
response.idTokenInfo.status = AuthorizationStatusEnum::NotAtThisTime;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
EVLOG_warning << "Can not validate eMAID without certificate chain";
|
||||
response.idTokenInfo.status = AuthorizationStatusEnum::Invalid;
|
||||
}
|
||||
if (forwarded_to_csms) {
|
||||
// AuthorizeRequest sent to CSMS, let's show the results
|
||||
EVLOG_info << "CSMS idToken status: " << response.idTokenInfo.status;
|
||||
if (response.certificateStatus.has_value()) {
|
||||
EVLOG_info << "CSMS certificate status: " << response.certificateStatus.value();
|
||||
}
|
||||
}
|
||||
// For eMAID, we will respond here, unless the local auth list or auth cache is tried
|
||||
if (!try_local_auth_list_or_cache) {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
const bool local_pre_authorize =
|
||||
this->context.device_model.get_value<bool>(ControllerComponentVariables::LocalPreAuthorize);
|
||||
const bool can_locally_check = ((is_online and local_pre_authorize) or (!is_online and local_authorize_offline));
|
||||
if (this->context.device_model.get_optional_value<bool>(ControllerComponentVariables::LocalAuthListCtrlrEnabled)
|
||||
.value_or(false) and
|
||||
can_locally_check) {
|
||||
std::optional<IdTokenInfo> id_token_info = std::nullopt;
|
||||
try {
|
||||
id_token_info = this->context.database_handler.get_local_authorization_list_entry(id_token);
|
||||
} catch (const everest::db::Exception& e) {
|
||||
EVLOG_warning << "Could not request local authorization list entry: " << e.what();
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Unknown Error while requesting IdTokenInfo: " << e.what();
|
||||
}
|
||||
|
||||
if (id_token_info.has_value()) {
|
||||
if (id_token_info.value().status == AuthorizationStatusEnum::Accepted) {
|
||||
// C14.FR.02: If found in local list we shall start charging without an AuthorizeRequest
|
||||
EVLOG_info << "Found valid entry in local authorization list";
|
||||
response.idTokenInfo = id_token_info.value();
|
||||
} else if (disabled_remote_auth) {
|
||||
EVLOG_info << "Found invalid entry in local authorization list but not sending Authorize.req because "
|
||||
"RemoteAuthorization is disabled";
|
||||
response.idTokenInfo.status = AuthorizationStatusEnum::Unknown;
|
||||
} else if (is_online) {
|
||||
// C14.FR.03: If a value found but not valid we shall send an authorize request
|
||||
EVLOG_info << "Found invalid entry in local authorization list: Sending Authorize.req";
|
||||
response = this->authorize_req(id_token, certificate, ocsp_request_data);
|
||||
} else {
|
||||
// errata C13.FR.04: even in the offline state we should not authorize if present (and not accepted)
|
||||
EVLOG_info << "Found invalid entry in local authorization list whilst offline: Not authorized";
|
||||
response.idTokenInfo.status = AuthorizationStatusEnum::Unknown;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
const auto hashed_id_token = utils::generate_token_hash(id_token);
|
||||
const auto auth_cache_enabled = this->is_auth_cache_ctrlr_enabled();
|
||||
|
||||
if (auth_cache_enabled and can_locally_check) {
|
||||
try {
|
||||
const auto cache_entry = this->authorization_cache_get_entry(hashed_id_token);
|
||||
if (cache_entry.has_value()) {
|
||||
const auto now = DateTime();
|
||||
const IdTokenInfo& id_token_info = cache_entry->id_token_info;
|
||||
|
||||
const auto lifetime =
|
||||
this->context.device_model.get_optional_value<int>(ControllerComponentVariables::AuthCacheLifeTime);
|
||||
const bool lifetime_expired =
|
||||
lifetime.has_value() and ((cache_entry->last_used.to_time_point() +
|
||||
std::chrono::seconds(lifetime.value())) < now.to_time_point());
|
||||
const bool cache_expiry_passed =
|
||||
id_token_info.cacheExpiryDateTime.has_value() and (id_token_info.cacheExpiryDateTime.value() < now);
|
||||
|
||||
if (lifetime_expired or cache_expiry_passed) {
|
||||
EVLOG_info << "Found valid entry in AuthCache but "
|
||||
<< (lifetime_expired ? "lifetime expired" : "expiry date passed")
|
||||
<< ": Removing from cache and sending new request";
|
||||
this->authorization_cache_delete_entry(hashed_id_token);
|
||||
this->update_authorization_cache_size();
|
||||
} else if (id_token_info.status == AuthorizationStatusEnum::Accepted) {
|
||||
EVLOG_info << "Found valid entry in AuthCache";
|
||||
this->context.database_handler.authorization_cache_update_last_used(hashed_id_token);
|
||||
response.idTokenInfo = id_token_info;
|
||||
return response;
|
||||
} else if (this->context.device_model
|
||||
.get_optional_value<bool>(ControllerComponentVariables::AuthCacheDisablePostAuthorize)
|
||||
.value_or(false)) {
|
||||
EVLOG_info << "Found invalid entry in AuthCache: Not sending new request because "
|
||||
"AuthCacheDisablePostAuthorize is enabled";
|
||||
response.idTokenInfo = id_token_info;
|
||||
return response;
|
||||
} else {
|
||||
EVLOG_info << "Found invalid entry in AuthCache: Sending new request";
|
||||
}
|
||||
}
|
||||
} catch (const everest::db::Exception& e) {
|
||||
EVLOG_error << "Database Error: " << e.what();
|
||||
} catch (const json::exception& e) {
|
||||
EVLOG_warning << "Could not parse data of IdTokenInfo: " << e.what();
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Unknown Error while parsing IdTokenInfo: " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_online and
|
||||
this->context.device_model.get_optional_value<bool>(ControllerComponentVariables::OfflineTxForUnknownIdEnabled)
|
||||
.value_or(false)) {
|
||||
EVLOG_info << "Offline authorization due to OfflineTxForUnknownIdEnabled being enabled";
|
||||
response.idTokenInfo.status = AuthorizationStatusEnum::Accepted;
|
||||
return response;
|
||||
}
|
||||
|
||||
// When set to true this instructs the Charging Station to not issue any AuthorizationRequests, but only use
|
||||
// Authorization Cache and Local Authorization List to determine validity of idTokens.
|
||||
if (!disabled_remote_auth) {
|
||||
response = this->authorize_req(id_token, certificate, ocsp_request_data);
|
||||
|
||||
if (auth_cache_enabled) {
|
||||
try {
|
||||
this->authorization_cache_insert_entry(hashed_id_token, response.idTokenInfo);
|
||||
} catch (const everest::db::Exception& e) {
|
||||
EVLOG_error << "Could not insert into authorization cache entry: " << e.what();
|
||||
}
|
||||
this->trigger_authorization_cache_cleanup();
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
EVLOG_info << "Not sending Authorize.req because RemoteAuthorization is disabled";
|
||||
|
||||
response.idTokenInfo.status = AuthorizationStatusEnum::Unknown;
|
||||
return response;
|
||||
}
|
||||
|
||||
void ocpp::v2::Authorization::stop_auth_cache_cleanup_thread() {
|
||||
if (this->auth_cache_cleanup_handler_running) {
|
||||
{
|
||||
const std::scoped_lock lk(this->auth_cache_cleanup_mutex);
|
||||
this->auth_cache_cleanup_handler_running = false;
|
||||
}
|
||||
this->auth_cache_cleanup_cv.notify_one();
|
||||
|
||||
if (this->auth_cache_cleanup_thread.joinable()) {
|
||||
this->auth_cache_cleanup_thread.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ocpp::v2::Authorization::handle_clear_cache_req(Call<ClearCacheRequest> call) {
|
||||
ClearCacheResponse response;
|
||||
response.status = ClearCacheStatusEnum::Rejected;
|
||||
|
||||
if (this->is_auth_cache_ctrlr_enabled()) {
|
||||
try {
|
||||
this->context.database_handler.authorization_cache_clear();
|
||||
this->update_authorization_cache_size();
|
||||
response.status = ClearCacheStatusEnum::Accepted;
|
||||
} catch (const everest::db::Exception& e) {
|
||||
auto call_error = CallError(call.uniqueId, "InternalError",
|
||||
"Database error while clearing authorization cache", json({}, true));
|
||||
this->context.message_dispatcher.dispatch_call_error(call_error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const ocpp::CallResult<ClearCacheResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
}
|
||||
|
||||
void ocpp::v2::Authorization::cache_cleanup_handler() {
|
||||
// Run the update once so the ram variable gets initialized
|
||||
this->update_authorization_cache_size();
|
||||
|
||||
while (true) {
|
||||
{
|
||||
// Wait for next wakeup or timeout
|
||||
std::unique_lock lk(this->auth_cache_cleanup_mutex);
|
||||
if (this->auth_cache_cleanup_cv.wait_for(lk, std::chrono::minutes(15), [&]() {
|
||||
return !this->auth_cache_cleanup_handler_running or this->auth_cache_cleanup_required;
|
||||
})) {
|
||||
EVLOG_debug << "Triggered authorization cache cleanup";
|
||||
} else {
|
||||
EVLOG_debug << "Time based authorization cache cleanup";
|
||||
}
|
||||
this->auth_cache_cleanup_required = false;
|
||||
}
|
||||
|
||||
if (!this->auth_cache_cleanup_handler_running) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto lifetime =
|
||||
this->context.device_model.get_optional_value<int>(ControllerComponentVariables::AuthCacheLifeTime);
|
||||
try {
|
||||
this->context.database_handler.authorization_cache_delete_expired_entries(
|
||||
lifetime.has_value() ? std::optional<std::chrono::seconds>(*lifetime) : std::nullopt);
|
||||
|
||||
std::optional<VariableMetaData> meta_data;
|
||||
if (ControllerComponentVariables::AuthCacheStorage.variable.has_value()) {
|
||||
meta_data = this->context.device_model.get_variable_meta_data(
|
||||
ControllerComponentVariables::AuthCacheStorage.component,
|
||||
ControllerComponentVariables::AuthCacheStorage.variable.value());
|
||||
}
|
||||
|
||||
if (meta_data.has_value()) {
|
||||
auto max_storage = meta_data.value().characteristics.maxLimit;
|
||||
if (max_storage.has_value()) {
|
||||
while (this->context.database_handler.authorization_cache_get_binary_size() >
|
||||
convert_to_positive_size_t(max_storage.value())) {
|
||||
this->context.database_handler.authorization_cache_delete_nr_of_oldest_entries(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const everest::db::Exception& e) {
|
||||
EVLOG_warning << "Could not delete expired authorization cache entries from database: " << e.what();
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_warning << "Could not delete expired authorization cache entries from database: " << e.what();
|
||||
}
|
||||
|
||||
this->update_authorization_cache_size();
|
||||
}
|
||||
}
|
||||
|
||||
void ocpp::v2::Authorization::handle_send_local_authorization_list_req(Call<SendLocalListRequest> call) {
|
||||
SendLocalListResponse response;
|
||||
|
||||
if (this->context.device_model.get_optional_value<bool>(ControllerComponentVariables::LocalAuthListCtrlrEnabled)
|
||||
.value_or(false)) {
|
||||
response.status = apply_local_authorization_list(call.msg);
|
||||
} else {
|
||||
response.status = SendLocalListStatusEnum::Failed;
|
||||
}
|
||||
|
||||
// Set nr of entries in device_model
|
||||
if (response.status == SendLocalListStatusEnum::Accepted) {
|
||||
try {
|
||||
this->context.database_handler.insert_or_update_local_authorization_list_version(call.msg.versionNumber);
|
||||
auto& local_entries = ControllerComponentVariables::LocalAuthListCtrlrEntries;
|
||||
if (local_entries.variable.has_value()) {
|
||||
try {
|
||||
auto entries = this->context.database_handler.get_local_authorization_list_number_of_entries();
|
||||
this->context.device_model.set_read_only_value(
|
||||
local_entries.component, local_entries.variable.value(), AttributeEnum::Actual,
|
||||
std::to_string(entries), VARIABLE_ATTRIBUTE_VALUE_SOURCE_INTERNAL);
|
||||
} catch (const DeviceModelError& e) {
|
||||
EVLOG_warning << "Could not set local list count to device model:" << e.what();
|
||||
} catch (const everest::db::Exception& e) {
|
||||
EVLOG_warning << "Could not get local list count from database: " << e.what();
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_warning << "Could not get local list count from database: " << e.what();
|
||||
}
|
||||
}
|
||||
} catch (const everest::db::Exception& e) {
|
||||
EVLOG_warning << "Could not update local authorization list in database: " << e.what();
|
||||
response.status = SendLocalListStatusEnum::Failed;
|
||||
}
|
||||
}
|
||||
|
||||
const ocpp::CallResult<SendLocalListResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
}
|
||||
|
||||
void ocpp::v2::Authorization::handle_get_local_authorization_list_version_req(Call<GetLocalListVersionRequest> call) {
|
||||
GetLocalListVersionResponse response;
|
||||
|
||||
if (this->context.device_model.get_optional_value<bool>(ControllerComponentVariables::LocalAuthListCtrlrEnabled)
|
||||
.value_or(false)) {
|
||||
try {
|
||||
response.versionNumber = this->context.database_handler.get_local_authorization_list_version();
|
||||
} catch (const everest::db::Exception& e) {
|
||||
const auto call_error = CallError(call.uniqueId, "InternalError",
|
||||
"Unable to retrieve LocalListVersion from the database", json({}));
|
||||
this->context.message_dispatcher.dispatch_call_error(call_error);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
response.versionNumber = 0;
|
||||
}
|
||||
|
||||
const ocpp::CallResult<GetLocalListVersionResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
}
|
||||
|
||||
ocpp::v2::SendLocalListStatusEnum
|
||||
ocpp::v2::Authorization::apply_local_authorization_list(const SendLocalListRequest& request) {
|
||||
auto status = SendLocalListStatusEnum::Failed;
|
||||
|
||||
if (request.versionNumber <= 0) {
|
||||
// D01.FR.18: Do nothing, not allowed, respond with failed
|
||||
} else if (request.updateType == UpdateEnum::Full) {
|
||||
if (!request.localAuthorizationList.has_value() or request.localAuthorizationList.value().empty()) {
|
||||
try {
|
||||
this->context.database_handler.clear_local_authorization_list();
|
||||
status = SendLocalListStatusEnum::Accepted;
|
||||
} catch (const everest::db::Exception& e) {
|
||||
status = SendLocalListStatusEnum::Failed;
|
||||
EVLOG_warning << "Clearing of local authorization list failed: " << e.what();
|
||||
}
|
||||
} else {
|
||||
const auto& list = request.localAuthorizationList.value();
|
||||
|
||||
if (!has_duplicate_in_list(list) and
|
||||
std::find_if(list.begin(), list.end(), has_no_token_info) == list.end()) {
|
||||
try {
|
||||
this->context.database_handler.clear_local_authorization_list();
|
||||
this->context.database_handler.insert_or_update_local_authorization_list(list);
|
||||
status = SendLocalListStatusEnum::Accepted;
|
||||
} catch (const everest::db::Exception& e) {
|
||||
status = SendLocalListStatusEnum::Failed;
|
||||
EVLOG_warning << "Full update of local authorization list failed (at least partially): "
|
||||
<< e.what();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (request.updateType == UpdateEnum::Differential) {
|
||||
if (request.versionNumber <= this->context.database_handler.get_local_authorization_list_version()) {
|
||||
// D01.FR.19: Do not allow version numbers smaller than current to update differentially
|
||||
status = SendLocalListStatusEnum::VersionMismatch;
|
||||
} else if (!request.localAuthorizationList.has_value() or request.localAuthorizationList.value().empty()) {
|
||||
// D01.FR.05: Do not update database with empty list, only update version number
|
||||
status = SendLocalListStatusEnum::Accepted;
|
||||
} else if (has_duplicate_in_list(request.localAuthorizationList.value())) {
|
||||
// Do nothing with duplicate in list
|
||||
} else {
|
||||
const auto& list = request.localAuthorizationList.value();
|
||||
try {
|
||||
this->context.database_handler.insert_or_update_local_authorization_list(list);
|
||||
status = SendLocalListStatusEnum::Accepted;
|
||||
} catch (const everest::db::Exception& e) {
|
||||
status = SendLocalListStatusEnum::Failed;
|
||||
EVLOG_warning << "Differential update of authorization list failed (at least partially): " << e.what();
|
||||
}
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
namespace {
|
||||
bool has_duplicate_in_list(const std::vector<ocpp::v2::AuthorizationData>& list) {
|
||||
for (auto it1 = list.begin(); it1 != list.end(); ++it1) {
|
||||
for (auto it2 = it1 + 1; it2 != list.end(); ++it2) {
|
||||
if (it1->idToken.idToken == it2->idToken.idToken and it1->idToken.type == it2->idToken.type) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool has_no_token_info(const ocpp::v2::AuthorizationData& item) {
|
||||
return !item.idTokenInfo.has_value();
|
||||
};
|
||||
} // namespace
|
||||
@@ -0,0 +1,244 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "ocpp/v2/messages/ChangeAvailability.hpp"
|
||||
#include "ocpp/v2/ocpp_enums.hpp"
|
||||
#include <ocpp/v2/functional_blocks/availability.hpp>
|
||||
|
||||
#include <ocpp/v2/ctrlr_component_variables.hpp>
|
||||
#include <ocpp/v2/device_model.hpp>
|
||||
#include <ocpp/v2/evse_manager.hpp>
|
||||
#include <ocpp/v2/functional_blocks/functional_block_context.hpp>
|
||||
|
||||
#include <ocpp/v2/messages/Heartbeat.hpp>
|
||||
#include <ocpp/v2/messages/StatusNotification.hpp>
|
||||
|
||||
namespace ocpp::v2 {
|
||||
Availability::Availability(const FunctionalBlockContext& functional_block_context,
|
||||
std::optional<TimeSyncCallback> time_sync_callback,
|
||||
std::optional<AllConnectorsUnavailableCallback> all_connectors_unavailable_callback) :
|
||||
context(functional_block_context),
|
||||
time_sync_callback(time_sync_callback),
|
||||
all_connectors_unavailable_callback(all_connectors_unavailable_callback) {
|
||||
}
|
||||
|
||||
Availability::~Availability() {
|
||||
try {
|
||||
this->stop_heartbeat_timer();
|
||||
} catch (...) {
|
||||
EVLOG_error << "Exception during dtor call of stop heartbeat timer";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Availability::handle_message(const ocpp::EnhancedMessage<MessageType>& message) {
|
||||
const auto& json_message = message.message;
|
||||
|
||||
if (message.messageType == MessageType::ChangeAvailability) {
|
||||
this->handle_change_availability_req(json_message);
|
||||
} else if (message.messageType == MessageType::HeartbeatResponse) {
|
||||
this->handle_heartbeat_response(json_message);
|
||||
} else {
|
||||
throw MessageTypeNotImplementedException(message.messageType);
|
||||
}
|
||||
}
|
||||
|
||||
void Availability::status_notification_req(const std::int32_t evse_id, const std::int32_t connector_id,
|
||||
const ConnectorStatusEnum status, const bool initiated_by_trigger_message) {
|
||||
StatusNotificationRequest req;
|
||||
req.connectorId = connector_id;
|
||||
req.evseId = evse_id;
|
||||
req.timestamp = DateTime();
|
||||
req.connectorStatus = status;
|
||||
|
||||
const ocpp::Call<StatusNotificationRequest> call(req);
|
||||
this->context.message_dispatcher.dispatch_call(call, initiated_by_trigger_message);
|
||||
}
|
||||
|
||||
void Availability::heartbeat_req(const bool initiated_by_trigger_message) {
|
||||
const HeartbeatRequest req;
|
||||
|
||||
heartbeat_request_time = std::chrono::steady_clock::now();
|
||||
const ocpp::Call<HeartbeatRequest> call(req);
|
||||
this->context.message_dispatcher.dispatch_call(call, initiated_by_trigger_message);
|
||||
}
|
||||
|
||||
void Availability::handle_scheduled_change_availability_requests(const std::int32_t evse_id) {
|
||||
if (this->scheduled_change_availability_requests.count(evse_id) != 0) {
|
||||
EVLOG_info << "Found scheduled ChangeAvailability.req for evse_id:" << evse_id;
|
||||
const auto req = this->scheduled_change_availability_requests[evse_id].request;
|
||||
const auto persist = this->scheduled_change_availability_requests[evse_id].persist;
|
||||
if (!this->context.evse_manager.any_transaction_active(req.evse)) {
|
||||
EVLOG_info << "Changing availability of evse:" << evse_id;
|
||||
this->execute_change_availability_request(req, persist);
|
||||
this->scheduled_change_availability_requests.erase(evse_id);
|
||||
// Check succeeded, trigger the callback if needed
|
||||
if (this->all_connectors_unavailable_callback.has_value() and
|
||||
this->context.evse_manager.are_all_connectors_effectively_inoperative()) {
|
||||
this->all_connectors_unavailable_callback.value()();
|
||||
}
|
||||
} else {
|
||||
EVLOG_info << "Cannot change availability because transaction is still active";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Availability::set_scheduled_change_availability_requests(const std::int32_t evse_id,
|
||||
AvailabilityChange availability_change) {
|
||||
this->scheduled_change_availability_requests[evse_id] = availability_change;
|
||||
}
|
||||
|
||||
void Availability::set_heartbeat_timer_interval(const std::chrono::seconds& interval) {
|
||||
this->heartbeat_timer.interval([this]() { this->heartbeat_req(); }, interval);
|
||||
}
|
||||
|
||||
void Availability::stop_heartbeat_timer() {
|
||||
this->heartbeat_timer.stop();
|
||||
}
|
||||
|
||||
ChangeAvailabilityResponse Availability::change_availability_req(bool& transaction_active,
|
||||
const ChangeAvailabilityRequest& request) {
|
||||
ChangeAvailabilityResponse response;
|
||||
response.status = ChangeAvailabilityStatusEnum::Scheduled;
|
||||
|
||||
// Sanity check: if we're addressing an EVSE or a connector, it must actually exist
|
||||
if (request.evse.has_value() and !context.evse_manager.is_valid_evse(request.evse.value())) {
|
||||
EVLOG_warning << "CSMS requested ChangeAvailability for invalid evse id or connector id";
|
||||
response.status = ChangeAvailabilityStatusEnum::Rejected;
|
||||
transaction_active = false; // set a default value
|
||||
} else {
|
||||
// Check if we have any transaction running on the EVSE (or any EVSE if we're addressing the whole CS)
|
||||
transaction_active = context.evse_manager.any_transaction_active(request.evse);
|
||||
|
||||
// evse_id will be 0 if we're addressing the whole CS, and >=1 otherwise
|
||||
auto evse_id = 0;
|
||||
if (request.evse.has_value()) {
|
||||
evse_id = request.evse.value().id;
|
||||
}
|
||||
|
||||
// Check if we're already in the requested state
|
||||
if (!transaction_active or is_already_in_state(request) or
|
||||
(evse_id == 0 and request.operationalStatus == OperationalStatusEnum::Operative)) {
|
||||
// If the chosen EVSE (or CS) has no transactions, we're already in the desired state,
|
||||
// or we're telling the whole CS to power on, we can accept the request - there's nothing stopping us.
|
||||
response.status = ChangeAvailabilityStatusEnum::Accepted;
|
||||
// Remove any scheduled availability requests for the evse_id.
|
||||
// This is relevant in case some of those requests become activated later - the current one overrides them.
|
||||
scheduled_change_availability_requests.erase(evse_id);
|
||||
} else {
|
||||
// We can't immediately perform the change, because we have a transaction running.
|
||||
// Schedule the request to run when the transaction finishes.
|
||||
scheduled_change_availability_requests[evse_id] = {request, true};
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
void Availability::action_change_availability_req(bool transaction_active, const ChangeAvailabilityRequest& request,
|
||||
const ChangeAvailabilityResponse& response) {
|
||||
if (response.status != ChangeAvailabilityStatusEnum::Rejected) {
|
||||
// evse_id will be 0 if we're addressing the whole CS, and >=1 otherwise
|
||||
auto evse_id = 0;
|
||||
if (request.evse.has_value()) {
|
||||
evse_id = request.evse.value().id;
|
||||
}
|
||||
|
||||
if (!transaction_active) {
|
||||
// No transactions - execute the change now
|
||||
execute_change_availability_request(request, true);
|
||||
} else if (response.status == ChangeAvailabilityStatusEnum::Scheduled) {
|
||||
// We can't execute the change now, but it's scheduled to run after transactions are finished.
|
||||
if (evse_id == 0) {
|
||||
// The whole CS is being addressed - we need to prevent further transactions from starting.
|
||||
// To do that, make all EVSEs without an active transaction Inoperative
|
||||
for (const auto& evse : context.evse_manager) {
|
||||
if (!evse.has_active_transaction()) {
|
||||
// FIXME: This will linger after the update too! We probably need another mechanism...
|
||||
set_evse_operative_status(evse.get_id(), OperationalStatusEnum::Inoperative, false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// A single EVSE is being addressed. We need to prevent further transactions from starting on it.
|
||||
// To do that, make all connectors of the EVSE without an active transaction Inoperative.
|
||||
const auto number_of_connectors = context.evse_manager.get_evse(evse_id).get_number_of_connectors();
|
||||
for (auto connector_id = 1; connector_id <= number_of_connectors; connector_id++) {
|
||||
if (!context.evse_manager.get_evse(evse_id).has_active_transaction(connector_id)) {
|
||||
// FIXME: This will linger after the update too! We probably need another mechanism...
|
||||
set_connector_operative_status(evse_id, connector_id, OperationalStatusEnum::Inoperative,
|
||||
false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Availability::handle_change_availability_req(Call<ChangeAvailabilityRequest> call) {
|
||||
const auto request = call.msg;
|
||||
bool transaction_active;
|
||||
const auto response = change_availability_req(transaction_active, request);
|
||||
|
||||
// Respond to the CSMS before performing any changes to avoid StatusNotification.req being sent before
|
||||
// the ChangeAvailabilityResponse.
|
||||
const ocpp::CallResult<ChangeAvailabilityResponse> call_result(response, call.uniqueId);
|
||||
context.message_dispatcher.dispatch_call_result(call_result);
|
||||
|
||||
action_change_availability_req(transaction_active, request, response);
|
||||
}
|
||||
|
||||
void Availability::handle_heartbeat_response(CallResult<HeartbeatResponse> call) {
|
||||
if (this->time_sync_callback.has_value() and
|
||||
this->context.device_model.get_value<std::string>(ControllerComponentVariables::TimeSource).find("Heartbeat") !=
|
||||
std::string::npos) {
|
||||
// the received currentTime was the time the CSMS received the heartbeat request
|
||||
// to get a system time as accurate as possible keep the time-of-flight into account
|
||||
auto timeOfFlight = (std::chrono::steady_clock::now() - this->heartbeat_request_time) / 2;
|
||||
const ocpp::DateTime currentTimeCompensated(std::chrono::time_point_cast<std::chrono::microseconds>(
|
||||
call.msg.currentTime.to_time_point() + timeOfFlight));
|
||||
this->time_sync_callback.value()(currentTimeCompensated);
|
||||
}
|
||||
}
|
||||
|
||||
bool Availability::is_already_in_state(const ChangeAvailabilityRequest& request) {
|
||||
// TODO: This checks against the individual status setting. What about effective/persisted status?
|
||||
if (!request.evse.has_value()) {
|
||||
// We're addressing the whole charging station
|
||||
return (this->context.component_state_manager.get_cs_individual_operational_status() ==
|
||||
request.operationalStatus);
|
||||
}
|
||||
if (!request.evse.value().connectorId.has_value()) {
|
||||
// An EVSE is addressed
|
||||
return (this->context.component_state_manager.get_evse_individual_operational_status(request.evse.value().id) ==
|
||||
request.operationalStatus);
|
||||
}
|
||||
// A connector is being addressed
|
||||
return (this->context.component_state_manager.get_connector_individual_operational_status(
|
||||
request.evse.value().id, request.evse.value().connectorId.value()) == request.operationalStatus);
|
||||
}
|
||||
|
||||
void Availability::execute_change_availability_request(ChangeAvailabilityRequest request, bool persist) {
|
||||
if (request.evse.has_value()) {
|
||||
if (request.evse.value().connectorId.has_value()) {
|
||||
this->set_connector_operative_status(request.evse.value().id, request.evse.value().connectorId.value(),
|
||||
request.operationalStatus, persist);
|
||||
} else {
|
||||
this->set_evse_operative_status(request.evse.value().id, request.operationalStatus, persist);
|
||||
}
|
||||
} else {
|
||||
this->set_cs_operative_status(request.operationalStatus, persist);
|
||||
}
|
||||
}
|
||||
|
||||
void Availability::set_cs_operative_status(OperationalStatusEnum new_status, bool persist) {
|
||||
this->context.component_state_manager.set_cs_individual_operational_status(new_status, persist);
|
||||
}
|
||||
|
||||
void Availability::set_evse_operative_status(std::int32_t evse_id, OperationalStatusEnum new_status, bool persist) {
|
||||
this->context.evse_manager.get_evse(evse_id).set_evse_operative_status(new_status, persist);
|
||||
}
|
||||
|
||||
void Availability::set_connector_operative_status(std::int32_t evse_id, std::int32_t connector_id,
|
||||
OperationalStatusEnum new_status, bool persist) {
|
||||
this->context.evse_manager.get_evse(evse_id).set_connector_operative_status(connector_id, new_status, persist);
|
||||
}
|
||||
} // namespace ocpp::v2
|
||||
@@ -0,0 +1,84 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <ocpp/v2/functional_blocks/data_transfer.hpp>
|
||||
|
||||
#include <ocpp/common/constants.hpp>
|
||||
#include <ocpp/v2/functional_blocks/functional_block_context.hpp>
|
||||
#include <ocpp/v2/messages/DataTransfer.hpp>
|
||||
|
||||
namespace ocpp {
|
||||
namespace v2 {
|
||||
|
||||
void DataTransfer::handle_message(const EnhancedMessage<MessageType>& message) {
|
||||
if (message.messageType != MessageType::DataTransfer) {
|
||||
throw MessageTypeNotImplementedException(message.messageType);
|
||||
}
|
||||
|
||||
const Call<DataTransferRequest> call = message.message;
|
||||
const auto msg = call.msg;
|
||||
DataTransferResponse response;
|
||||
response.status = DataTransferStatusEnum::UnknownVendorId;
|
||||
|
||||
if (this->data_transfer_callback.has_value()) {
|
||||
response = this->data_transfer_callback.value()(call.msg);
|
||||
} else {
|
||||
response.status = DataTransferStatusEnum::UnknownVendorId;
|
||||
EVLOG_warning << "Received a DataTransferRequest but no data transfer callback was registered";
|
||||
}
|
||||
|
||||
const ocpp::CallResult<DataTransferResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
}
|
||||
|
||||
std::optional<DataTransferResponse> DataTransfer::data_transfer_req(const CiString<255>& vendorId,
|
||||
const std::optional<CiString<50>>& messageId,
|
||||
const std::optional<json>& data) {
|
||||
DataTransferRequest req;
|
||||
req.vendorId = vendorId;
|
||||
req.messageId = messageId;
|
||||
req.data = data;
|
||||
|
||||
return this->data_transfer_req(req);
|
||||
}
|
||||
|
||||
std::optional<DataTransferResponse> DataTransfer::data_transfer_req(const DataTransferRequest& request) {
|
||||
DataTransferResponse response;
|
||||
response.status = DataTransferStatusEnum::Rejected;
|
||||
|
||||
const ocpp::Call<DataTransferRequest> call(request);
|
||||
auto data_transfer_future = this->context.message_dispatcher.dispatch_call_async(call);
|
||||
|
||||
if (data_transfer_future.wait_for(this->response_timeout) == std::future_status::timeout) {
|
||||
EVLOG_warning << "Waiting for DataTransfer.conf future timed out";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto enhanced_message = data_transfer_future.get();
|
||||
|
||||
if (enhanced_message.offline) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (enhanced_message.messageType == MessageType::DataTransferResponse) {
|
||||
try {
|
||||
const ocpp::CallResult<DataTransferResponse> call_result = enhanced_message.message;
|
||||
response = call_result.msg;
|
||||
} catch (const EnumConversionException& e) {
|
||||
EVLOG_error << "EnumConversionException during handling of message: " << e.what();
|
||||
auto call_error = CallError(enhanced_message.uniqueId, "FormationViolation", e.what(), json({}));
|
||||
this->context.message_dispatcher.dispatch_call_error(call_error);
|
||||
return std::nullopt;
|
||||
} catch (const json::exception& e) {
|
||||
EVLOG_error << "Unable to parse DataTransfer.conf from CSMS: " << enhanced_message.message;
|
||||
auto call_error = CallError(enhanced_message.uniqueId, "FormationViolation", e.what(), json({}));
|
||||
this->context.message_dispatcher.dispatch_call_error(call_error);
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
}; // namespace v2
|
||||
} // namespace ocpp
|
||||
@@ -0,0 +1,432 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <ocpp/v2/functional_blocks/diagnostics.hpp>
|
||||
|
||||
#include <ocpp/common/connectivity_manager.hpp>
|
||||
#include <ocpp/common/constants.hpp>
|
||||
#include <ocpp/v2/ctrlr_component_variables.hpp>
|
||||
#include <ocpp/v2/database_handler.hpp>
|
||||
#include <ocpp/v2/device_model.hpp>
|
||||
#include <ocpp/v2/functional_blocks/authorization.hpp>
|
||||
#include <ocpp/v2/functional_blocks/functional_block_context.hpp>
|
||||
#include <ocpp/v2/utils.hpp>
|
||||
|
||||
#include <ocpp/v2/messages/ClearVariableMonitoring.hpp>
|
||||
#include <ocpp/v2/messages/CustomerInformation.hpp>
|
||||
#include <ocpp/v2/messages/GetLog.hpp>
|
||||
#include <ocpp/v2/messages/GetMonitoringReport.hpp>
|
||||
#include <ocpp/v2/messages/NotifyCustomerInformation.hpp>
|
||||
#include <ocpp/v2/messages/NotifyEvent.hpp>
|
||||
#include <ocpp/v2/messages/NotifyMonitoringReport.hpp>
|
||||
#include <ocpp/v2/messages/SetMonitoringBase.hpp>
|
||||
#include <ocpp/v2/messages/SetMonitoringLevel.hpp>
|
||||
#include <ocpp/v2/messages/SetVariableMonitoring.hpp>
|
||||
|
||||
const auto DEFAULT_MAX_CUSTOMER_INFORMATION_DATA_LENGTH = 51200;
|
||||
|
||||
namespace ocpp::v2 {
|
||||
|
||||
Diagnostics::Diagnostics(const FunctionalBlockContext& context, AuthorizationInterface& authorization,
|
||||
GetLogRequestCallback get_log_request_callback,
|
||||
std::optional<GetCustomerInformationCallback> get_customer_information_callback,
|
||||
std::optional<ClearCustomerInformationCallback> clear_customer_information_callback) :
|
||||
context(context),
|
||||
authorization(authorization),
|
||||
monitoring_updater(
|
||||
context.device_model, [this](const std::vector<EventData>& events) { this->notify_event_req(events); },
|
||||
[this]() { return !this->context.connectivity_manager.is_websocket_connected(); }),
|
||||
get_log_request_callback(get_log_request_callback),
|
||||
get_customer_information_callback(get_customer_information_callback),
|
||||
clear_customer_information_callback(clear_customer_information_callback),
|
||||
is_monitoring_available(
|
||||
this->context.device_model.get_optional_value<bool>(ControllerComponentVariables::MonitoringCtrlrAvailable)
|
||||
.value_or(false)) {
|
||||
}
|
||||
|
||||
void Diagnostics::handle_message(const ocpp::EnhancedMessage<MessageType>& message) {
|
||||
const auto& json_message = message.message;
|
||||
|
||||
if (message.messageType == MessageType::GetLog) {
|
||||
this->handle_get_log_req(json_message);
|
||||
} else if (message.messageType == MessageType::CustomerInformation) {
|
||||
this->handle_customer_information_req(json_message);
|
||||
} else if (message.messageType == MessageType::SetMonitoringBase) {
|
||||
this->throw_when_monitoring_not_available(message.messageType);
|
||||
this->handle_set_monitoring_base_req(json_message);
|
||||
} else if (message.messageType == MessageType::SetMonitoringLevel) {
|
||||
this->throw_when_monitoring_not_available(message.messageType);
|
||||
this->handle_set_monitoring_level_req(json_message);
|
||||
} else if (message.messageType == MessageType::SetVariableMonitoring) {
|
||||
this->throw_when_monitoring_not_available(message.messageType);
|
||||
this->handle_set_variable_monitoring_req(message);
|
||||
} else if (message.messageType == MessageType::GetMonitoringReport) {
|
||||
this->throw_when_monitoring_not_available(message.messageType);
|
||||
this->handle_get_monitoring_report_req(json_message);
|
||||
} else if (message.messageType == MessageType::ClearVariableMonitoring) {
|
||||
this->throw_when_monitoring_not_available(message.messageType);
|
||||
this->handle_clear_variable_monitoring_req(json_message);
|
||||
} else {
|
||||
throw MessageTypeNotImplementedException(message.messageType);
|
||||
}
|
||||
}
|
||||
|
||||
void Diagnostics::notify_event_req(const std::vector<EventData>& events) {
|
||||
NotifyEventRequest req;
|
||||
req.eventData = events;
|
||||
req.generatedAt = DateTime();
|
||||
req.seqNo = 0;
|
||||
|
||||
const ocpp::Call<NotifyEventRequest> call(req);
|
||||
this->context.message_dispatcher.dispatch_call(call);
|
||||
}
|
||||
|
||||
void Diagnostics::stop_monitoring() {
|
||||
monitoring_updater.stop_monitoring();
|
||||
}
|
||||
|
||||
void Diagnostics::start_monitoring() {
|
||||
monitoring_updater.start_monitoring();
|
||||
}
|
||||
|
||||
void Diagnostics::process_triggered_monitors() {
|
||||
monitoring_updater.process_triggered_monitors();
|
||||
}
|
||||
|
||||
void Diagnostics::notify_customer_information_req(const std::string& data, const std::int32_t request_id) {
|
||||
size_t pos = 0;
|
||||
std::int32_t seq_no = 0;
|
||||
while (pos < data.length() or (pos == 0 and data.empty())) {
|
||||
const auto req = [&]() {
|
||||
NotifyCustomerInformationRequest req;
|
||||
req.data = CiString<512>(data.substr(pos, 512));
|
||||
req.seqNo = seq_no;
|
||||
req.requestId = request_id;
|
||||
req.generatedAt = DateTime();
|
||||
req.tbc = data.length() - pos > 512;
|
||||
return req;
|
||||
}();
|
||||
|
||||
const ocpp::Call<NotifyCustomerInformationRequest> call(req);
|
||||
this->context.message_dispatcher.dispatch_call(call);
|
||||
|
||||
pos += 512;
|
||||
seq_no++;
|
||||
}
|
||||
}
|
||||
|
||||
void Diagnostics::notify_monitoring_report_req(const int request_id, std::vector<MonitoringData>& montoring_data) {
|
||||
static constexpr std::int32_t MAXIMUM_VARIABLE_SEND = 10;
|
||||
|
||||
for (auto& element : montoring_data) {
|
||||
for (auto& variable_monitoring : element.variableMonitoring) {
|
||||
// in OCPP2.0.1 the eventNotificationType is optional, but in OCPP2.1 it is mandatory. We have to reset it
|
||||
// in case we are on OCPP2.0.1
|
||||
// if (this->context.ocpp_version == OcppProtocolVersion::v201) {
|
||||
variable_monitoring.eventNotificationType.reset();
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
if (montoring_data.size() <= MAXIMUM_VARIABLE_SEND) {
|
||||
NotifyMonitoringReportRequest req;
|
||||
req.requestId = request_id;
|
||||
req.seqNo = 0;
|
||||
req.generatedAt = ocpp::DateTime();
|
||||
req.monitor.emplace(montoring_data);
|
||||
req.tbc = false;
|
||||
|
||||
const ocpp::Call<NotifyMonitoringReportRequest> call(req);
|
||||
this->context.message_dispatcher.dispatch_call(call);
|
||||
} else {
|
||||
// Split for larger message sizes
|
||||
std::int32_t sequence_num = 0;
|
||||
auto generated_at = ocpp::DateTime();
|
||||
|
||||
for (std::int32_t i = 0; i < montoring_data.size(); i += MAXIMUM_VARIABLE_SEND) {
|
||||
// If our next index is >= than the last index then we're finished
|
||||
const bool last_part = ((i + MAXIMUM_VARIABLE_SEND) >= montoring_data.size());
|
||||
|
||||
NotifyMonitoringReportRequest req;
|
||||
req.requestId = request_id;
|
||||
req.seqNo = sequence_num;
|
||||
req.generatedAt = generated_at;
|
||||
req.tbc = (!last_part);
|
||||
|
||||
// Construct sub-message part
|
||||
std::vector<MonitoringData> sub_data;
|
||||
|
||||
for (std::int32_t j = i; j < MAXIMUM_VARIABLE_SEND and j < montoring_data.size(); ++j) {
|
||||
sub_data.push_back(std::move(montoring_data[i + j]));
|
||||
}
|
||||
|
||||
req.monitor = sub_data;
|
||||
|
||||
const ocpp::Call<NotifyMonitoringReportRequest> call(req);
|
||||
this->context.message_dispatcher.dispatch_call(call);
|
||||
|
||||
sequence_num++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Diagnostics::handle_get_log_req(Call<GetLogRequest> call) {
|
||||
const GetLogResponse response = this->get_log_request_callback(call.msg);
|
||||
|
||||
const ocpp::CallResult<GetLogResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
}
|
||||
|
||||
void Diagnostics::handle_customer_information_req(Call<CustomerInformationRequest> call) {
|
||||
CustomerInformationResponse response;
|
||||
const auto& msg = call.msg;
|
||||
response.status = CustomerInformationStatusEnum::Accepted;
|
||||
|
||||
if (!msg.report and !msg.clear) {
|
||||
EVLOG_warning << "CSMS sent CustomerInformation.req with both report and clear flags being false";
|
||||
response.status = CustomerInformationStatusEnum::Rejected;
|
||||
}
|
||||
|
||||
if (!msg.customerCertificate.has_value() and !msg.idToken.has_value() and !msg.customerIdentifier.has_value()) {
|
||||
EVLOG_warning << "CSMS sent CustomerInformation.req without setting one of customerCertificate, idToken, "
|
||||
"customerIdentifier fields";
|
||||
response.status = CustomerInformationStatusEnum::Invalid;
|
||||
}
|
||||
|
||||
const ocpp::CallResult<CustomerInformationResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
|
||||
if (response.status == CustomerInformationStatusEnum::Accepted) {
|
||||
std::string data;
|
||||
if (msg.report) {
|
||||
data += this->get_customer_information(msg.customerCertificate, msg.idToken, msg.customerIdentifier);
|
||||
}
|
||||
if (msg.clear) {
|
||||
this->clear_customer_information(msg.customerCertificate, msg.idToken, msg.customerIdentifier);
|
||||
}
|
||||
|
||||
const auto max_customer_information_data_length =
|
||||
this->context.device_model
|
||||
.get_optional_value<int>(ControllerComponentVariables::MaxCustomerInformationDataLength)
|
||||
.value_or(DEFAULT_MAX_CUSTOMER_INFORMATION_DATA_LENGTH);
|
||||
if (data.length() > max_customer_information_data_length) {
|
||||
EVLOG_warning << "NotifyCustomerInformation.req data field is too large. Cropping it down to: "
|
||||
<< max_customer_information_data_length << "characters";
|
||||
data.erase(max_customer_information_data_length);
|
||||
}
|
||||
|
||||
this->notify_customer_information_req(data, msg.requestId);
|
||||
}
|
||||
}
|
||||
|
||||
void Diagnostics::handle_set_monitoring_base_req(Call<SetMonitoringBaseRequest> call) {
|
||||
SetMonitoringBaseResponse response;
|
||||
const auto& msg = call.msg;
|
||||
|
||||
auto result = SetVariableStatusEnum::Rejected;
|
||||
if (not ControllerComponentVariables::ActiveMonitoringBase.variable.has_value()) {
|
||||
result = SetVariableStatusEnum::UnknownVariable;
|
||||
} else {
|
||||
result = this->context.device_model.set_value(
|
||||
ControllerComponentVariables::ActiveMonitoringBase.component,
|
||||
ControllerComponentVariables::ActiveMonitoringBase.variable.value(), AttributeEnum::Actual,
|
||||
conversions::monitoring_base_enum_to_string(msg.monitoringBase), VARIABLE_ATTRIBUTE_VALUE_SOURCE_CSMS,
|
||||
true);
|
||||
}
|
||||
|
||||
if (result != SetVariableStatusEnum::Accepted) {
|
||||
EVLOG_warning << "Could not persist in device model new monitoring base: "
|
||||
<< conversions::monitoring_base_enum_to_string(msg.monitoringBase);
|
||||
response.status = GenericDeviceModelStatusEnum::Rejected;
|
||||
} else {
|
||||
response.status = GenericDeviceModelStatusEnum::Accepted;
|
||||
|
||||
if (msg.monitoringBase == MonitoringBaseEnum::HardWiredOnly or
|
||||
msg.monitoringBase == MonitoringBaseEnum::FactoryDefault) {
|
||||
try {
|
||||
this->context.device_model.clear_custom_monitors();
|
||||
} catch (const DeviceModelError& e) {
|
||||
EVLOG_warning << "Could not clear custom monitors from DB: " << e.what();
|
||||
response.status = GenericDeviceModelStatusEnum::Rejected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ocpp::CallResult<SetMonitoringBaseResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
}
|
||||
|
||||
void Diagnostics::handle_set_monitoring_level_req(Call<SetMonitoringLevelRequest> call) {
|
||||
SetMonitoringLevelResponse response;
|
||||
const auto& msg = call.msg;
|
||||
|
||||
if (msg.severity < MonitoringLevelSeverity::MIN or msg.severity > MonitoringLevelSeverity::MAX) {
|
||||
response.status = GenericStatusEnum::Rejected;
|
||||
} else {
|
||||
auto result = SetVariableStatusEnum::Rejected;
|
||||
|
||||
if (not ControllerComponentVariables::ActiveMonitoringLevel.variable.has_value()) {
|
||||
result = SetVariableStatusEnum::UnknownVariable;
|
||||
} else {
|
||||
result = this->context.device_model.set_value(
|
||||
ControllerComponentVariables::ActiveMonitoringLevel.component,
|
||||
ControllerComponentVariables::ActiveMonitoringLevel.variable.value(), AttributeEnum::Actual,
|
||||
std::to_string(msg.severity), VARIABLE_ATTRIBUTE_VALUE_SOURCE_CSMS, true);
|
||||
}
|
||||
if (result != SetVariableStatusEnum::Accepted) {
|
||||
EVLOG_warning << "Could not persist in device model new monitoring level: " << msg.severity;
|
||||
response.status = GenericStatusEnum::Rejected;
|
||||
} else {
|
||||
response.status = GenericStatusEnum::Accepted;
|
||||
}
|
||||
}
|
||||
|
||||
const ocpp::CallResult<SetMonitoringLevelResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
}
|
||||
|
||||
void Diagnostics::handle_set_variable_monitoring_req(const EnhancedMessage<MessageType>& message) {
|
||||
const Call<SetVariableMonitoringRequest> call = message.call_message;
|
||||
SetVariableMonitoringResponse response;
|
||||
const auto& msg = call.msg;
|
||||
|
||||
const auto max_items_per_message =
|
||||
this->context.device_model.get_value<int>(ControllerComponentVariables::ItemsPerMessageSetVariableMonitoring);
|
||||
const auto max_bytes_message =
|
||||
this->context.device_model.get_value<int>(ControllerComponentVariables::BytesPerMessageSetVariableMonitoring);
|
||||
|
||||
// N04.FR.09
|
||||
if (msg.setMonitoringData.size() > max_items_per_message) {
|
||||
const auto call_error = CallError(call.uniqueId, "OccurenceConstraintViolation", "", json({}));
|
||||
this->context.message_dispatcher.dispatch_call_error(call_error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.message_size > max_bytes_message) {
|
||||
const auto call_error = CallError(call.uniqueId, "FormatViolation", "", json({}));
|
||||
this->context.message_dispatcher.dispatch_call_error(call_error);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
response.setMonitoringResult = this->context.device_model.set_monitors(msg.setMonitoringData);
|
||||
} catch (const DeviceModelError& e) {
|
||||
EVLOG_error << "Set monitors failed:" << e.what();
|
||||
}
|
||||
|
||||
const ocpp::CallResult<SetVariableMonitoringResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
}
|
||||
|
||||
void Diagnostics::handle_get_monitoring_report_req(Call<GetMonitoringReportRequest> call) {
|
||||
GetMonitoringReportResponse response;
|
||||
const auto& msg = call.msg;
|
||||
|
||||
const auto component_variables = msg.componentVariable.value_or(std::vector<ComponentVariable>());
|
||||
const auto max_variable_components_per_message =
|
||||
this->context.device_model.get_value<int>(ControllerComponentVariables::ItemsPerMessageGetReport);
|
||||
|
||||
// N02.FR.07
|
||||
if (component_variables.size() > max_variable_components_per_message) {
|
||||
const auto call_error = CallError(call.uniqueId, "OccurenceConstraintViolation", "", json({}));
|
||||
this->context.message_dispatcher.dispatch_call_error(call_error);
|
||||
return;
|
||||
}
|
||||
|
||||
auto criteria = msg.monitoringCriteria.value_or(std::vector<MonitoringCriterionEnum>());
|
||||
std::vector<MonitoringData> data{};
|
||||
|
||||
try {
|
||||
data = this->context.device_model.get_monitors(criteria, component_variables);
|
||||
|
||||
if (!data.empty()) {
|
||||
response.status = GenericDeviceModelStatusEnum::Accepted;
|
||||
} else {
|
||||
response.status = GenericDeviceModelStatusEnum::EmptyResultSet;
|
||||
}
|
||||
} catch (const DeviceModelError& e) {
|
||||
EVLOG_error << "Get variable monitoring failed:" << e.what();
|
||||
response.status = GenericDeviceModelStatusEnum::Rejected;
|
||||
}
|
||||
|
||||
const ocpp::CallResult<GetMonitoringReportResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
|
||||
if (response.status == GenericDeviceModelStatusEnum::Accepted) {
|
||||
// Send the result with splits if required
|
||||
notify_monitoring_report_req(msg.requestId, data);
|
||||
}
|
||||
}
|
||||
|
||||
void Diagnostics::handle_clear_variable_monitoring_req(Call<ClearVariableMonitoringRequest> call) {
|
||||
ClearVariableMonitoringResponse response;
|
||||
const auto& msg = call.msg;
|
||||
|
||||
try {
|
||||
response.clearMonitoringResult = this->context.device_model.clear_monitors(msg.id);
|
||||
} catch (const DeviceModelError& e) {
|
||||
EVLOG_error << "Clear variable monitoring failed:" << e.what();
|
||||
}
|
||||
|
||||
const ocpp::CallResult<ClearVariableMonitoringResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
}
|
||||
|
||||
std::string Diagnostics::get_customer_information(const std::optional<CertificateHashDataType> customer_certificate,
|
||||
const std::optional<IdToken> id_token,
|
||||
const std::optional<CiString<64>> customer_identifier) {
|
||||
std::stringstream s;
|
||||
|
||||
// Retrieve possible customer information from application that uses this library
|
||||
if (this->get_customer_information_callback.has_value()) {
|
||||
s << this->get_customer_information_callback.value()(customer_certificate, id_token, customer_identifier);
|
||||
}
|
||||
|
||||
// Retrieve information from auth cache
|
||||
if (id_token.has_value()) {
|
||||
const auto hashed_id_token = utils::generate_token_hash(id_token.value());
|
||||
try {
|
||||
const auto entry = this->authorization.authorization_cache_get_entry(hashed_id_token);
|
||||
if (entry.has_value()) {
|
||||
s << "Hashed id_token stored in cache: " + hashed_id_token + "\n";
|
||||
s << "IdTokenInfo: " << entry->id_token_info;
|
||||
}
|
||||
} catch (const everest::db::Exception& e) {
|
||||
EVLOG_warning << "Could not get authorization cache entry from database";
|
||||
} catch (const json::exception& e) {
|
||||
EVLOG_warning << "Could not parse data of IdTokenInfo: " << e.what();
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Unknown Error while parsing IdTokenInfo: " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
return s.str();
|
||||
}
|
||||
|
||||
void Diagnostics::clear_customer_information(const std::optional<CertificateHashDataType> customer_certificate,
|
||||
const std::optional<IdToken> id_token,
|
||||
const std::optional<CiString<64>> customer_identifier) {
|
||||
if (this->clear_customer_information_callback.has_value()) {
|
||||
this->clear_customer_information_callback.value()(customer_certificate, id_token, customer_identifier);
|
||||
}
|
||||
|
||||
if (id_token.has_value()) {
|
||||
const auto hashed_id_token = utils::generate_token_hash(id_token.value());
|
||||
try {
|
||||
this->authorization.authorization_cache_delete_entry(hashed_id_token);
|
||||
} catch (const everest::db::Exception& e) {
|
||||
EVLOG_error << "Could not delete from table: " << e.what();
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Exception while deleting from auth cache table: " << e.what();
|
||||
}
|
||||
this->authorization.update_authorization_cache_size();
|
||||
}
|
||||
}
|
||||
|
||||
void Diagnostics::throw_when_monitoring_not_available(const MessageType type) const {
|
||||
if (!is_monitoring_available) {
|
||||
throw MessageTypeNotImplementedException(type);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ocpp::v2
|
||||
@@ -0,0 +1,226 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <ocpp/v2/functional_blocks/display_message.hpp>
|
||||
|
||||
#include <ocpp/v2/ctrlr_component_variables.hpp>
|
||||
#include <ocpp/v2/evse_manager.hpp>
|
||||
#include <ocpp/v2/functional_blocks/functional_block_context.hpp>
|
||||
|
||||
#include <ocpp/v2/messages/ClearDisplayMessage.hpp>
|
||||
#include <ocpp/v2/messages/GetDisplayMessages.hpp>
|
||||
#include <ocpp/v2/messages/NotifyDisplayMessages.hpp>
|
||||
|
||||
namespace ocpp::v2 {
|
||||
|
||||
DisplayMessageBlock::DisplayMessageBlock(const FunctionalBlockContext& functional_block_context,
|
||||
GetDisplayMessageCallback get_display_message_callback,
|
||||
SetDisplayMessageCallback set_display_message_callback,
|
||||
ClearDisplayMessageCallback clear_display_message_callback) :
|
||||
context(functional_block_context),
|
||||
get_display_message_callback(get_display_message_callback),
|
||||
set_display_message_callback(set_display_message_callback),
|
||||
clear_display_message_callback(clear_display_message_callback) {
|
||||
}
|
||||
|
||||
void DisplayMessageBlock::handle_message(const ocpp::EnhancedMessage<MessageType>& message) {
|
||||
const auto& json_message = message.message;
|
||||
|
||||
if (message.messageType == MessageType::GetDisplayMessages) {
|
||||
this->handle_get_display_message(json_message);
|
||||
} else if (message.messageType == MessageType::SetDisplayMessage) {
|
||||
this->handle_set_display_message(json_message);
|
||||
} else if (message.messageType == MessageType::ClearDisplayMessage) {
|
||||
this->handle_clear_display_message(json_message);
|
||||
} else {
|
||||
throw MessageTypeNotImplementedException(message.messageType);
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayMessageBlock::handle_get_display_message(const Call<GetDisplayMessagesRequest> call) {
|
||||
// Call 'get display message callback' to get all display messages from the charging station.
|
||||
const std::vector<DisplayMessage> display_messages = this->get_display_message_callback(call.msg);
|
||||
|
||||
NotifyDisplayMessagesRequest messages_request;
|
||||
messages_request.requestId = call.msg.requestId;
|
||||
messages_request.messageInfo = std::vector<MessageInfo>();
|
||||
// Convert all display messages from the charging station to the correct format. They will not be included if
|
||||
// they do not have the required values. That's why we wait with sending the response until we converted all
|
||||
// display messages, because we then know if there are any.
|
||||
for (const auto& display_message : display_messages) {
|
||||
const std::optional<MessageInfo> message_info = display_message_to_message_info_type(display_message);
|
||||
if (message_info.has_value()) {
|
||||
messages_request.messageInfo->push_back(message_info.value());
|
||||
}
|
||||
}
|
||||
|
||||
// Send 'accepted' back to the CSMS if there is at least one message and send all the messages in another
|
||||
// request.
|
||||
GetDisplayMessagesResponse response;
|
||||
if (messages_request.messageInfo.value().empty()) {
|
||||
response.status = GetDisplayMessagesStatusEnum::Unknown;
|
||||
const ocpp::CallResult<GetDisplayMessagesResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
return;
|
||||
}
|
||||
response.status = GetDisplayMessagesStatusEnum::Accepted;
|
||||
const ocpp::CallResult<GetDisplayMessagesResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
|
||||
// Send display messages. The response is empty, so we don't have to get that back.
|
||||
// Sending multiple messages is not supported for now, because there is no need to split them up (yet).
|
||||
const ocpp::Call<NotifyDisplayMessagesRequest> request(messages_request);
|
||||
this->context.message_dispatcher.dispatch_call(request);
|
||||
}
|
||||
|
||||
void DisplayMessageBlock::handle_set_display_message(const Call<SetDisplayMessageRequest> call) {
|
||||
SetDisplayMessageResponse response;
|
||||
|
||||
// Check if display messages are available, priority and message format are supported and if the given
|
||||
// transaction is running, if a transaction id was included in the message.
|
||||
bool error = false;
|
||||
const std::optional<bool> display_message_available =
|
||||
this->context.device_model.get_optional_value<bool>(ControllerComponentVariables::DisplayMessageCtrlrAvailable);
|
||||
const auto supported_priorities = this->context.device_model.get_value<std::string>(
|
||||
ControllerComponentVariables::DisplayMessageSupportedPriorities);
|
||||
const auto supported_message_formats =
|
||||
this->context.device_model.get_value<std::string>(ControllerComponentVariables::DisplayMessageSupportedFormats);
|
||||
|
||||
const std::vector<std::string> priorities = split_string(supported_priorities, ',', true);
|
||||
const std::vector<std::string> formats = split_string(supported_message_formats, ',', true);
|
||||
const auto& supported_priority_it = std::find(
|
||||
priorities.begin(), priorities.end(), conversions::message_priority_enum_to_string(call.msg.message.priority));
|
||||
const auto& supported_format_it = std::find(
|
||||
formats.begin(), formats.end(), conversions::message_format_enum_to_string(call.msg.message.message.format));
|
||||
|
||||
// Check if transaction is valid: this is the case if there is no transaction id, or if the transaction id
|
||||
// belongs to a running transaction.
|
||||
const bool transaction_valid =
|
||||
(!call.msg.message.transactionId.has_value() or
|
||||
this->context.evse_manager.get_transaction_evseid(call.msg.message.transactionId.value()) != std::nullopt);
|
||||
|
||||
// Check if display messages are available.
|
||||
if (!display_message_available.value_or(false)) {
|
||||
error = true;
|
||||
response.status = DisplayMessageStatusEnum::Rejected;
|
||||
}
|
||||
// Check if the priority is supported.
|
||||
else if (supported_priority_it == priorities.end()) {
|
||||
error = true;
|
||||
response.status = DisplayMessageStatusEnum::NotSupportedPriority;
|
||||
}
|
||||
// Check if the message format is supported.
|
||||
else if (supported_format_it == formats.end()) {
|
||||
error = true;
|
||||
response.status = DisplayMessageStatusEnum::NotSupportedMessageFormat;
|
||||
}
|
||||
// Check if transaction is valid.
|
||||
else if (!transaction_valid) {
|
||||
error = true;
|
||||
response.status = DisplayMessageStatusEnum::UnknownTransaction;
|
||||
}
|
||||
// Check if message state is supported.
|
||||
else if (call.msg.message.state.has_value()) {
|
||||
const std::optional<std::string> supported_states = this->context.device_model.get_optional_value<std::string>(
|
||||
ControllerComponentVariables::DisplayMessageSupportedStates);
|
||||
if (supported_states.has_value()) {
|
||||
const std::vector<std::string> states = split_string(supported_states.value(), ',', true);
|
||||
const auto& supported_states_it =
|
||||
std::find(states.begin(), states.end(),
|
||||
conversions::message_state_enum_to_string(call.msg.message.state.value()));
|
||||
if (supported_states_it == states.end()) {
|
||||
error = true;
|
||||
response.status = DisplayMessageStatusEnum::NotSupportedState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
const ocpp::CallResult<SetDisplayMessageResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
return;
|
||||
}
|
||||
|
||||
const DisplayMessage message = message_info_to_display_message(call.msg.message);
|
||||
response = this->set_display_message_callback({message});
|
||||
const ocpp::CallResult<SetDisplayMessageResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
}
|
||||
|
||||
void DisplayMessageBlock::handle_clear_display_message(const Call<ClearDisplayMessageRequest> call) {
|
||||
ClearDisplayMessageResponse response;
|
||||
response = this->clear_display_message_callback(call.msg);
|
||||
const ocpp::CallResult<ClearDisplayMessageResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief Convert message content from OCPP spec to DisplayMessageContent.
|
||||
/// \param message_content The struct to convert.
|
||||
/// \return The converted struct.
|
||||
///
|
||||
DisplayMessageContent message_content_to_display_message_content(const MessageContent& message_content) {
|
||||
DisplayMessageContent result;
|
||||
result.message = message_content.content;
|
||||
result.message_format = message_content.format;
|
||||
result.language = message_content.language;
|
||||
return result;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief Convert display message to MessageInfo from OCPP.
|
||||
/// \param display_message The struct to convert.
|
||||
/// \return The converted struct.
|
||||
///
|
||||
std::optional<MessageInfo> display_message_to_message_info_type(const DisplayMessage& display_message) {
|
||||
// Each display message should have an id and p[riority, this is required for OCPP.
|
||||
if (!display_message.id.has_value()) {
|
||||
EVLOG_error << "Can not convert DisplayMessage to MessageInfo: No id is provided, which is required by OCPP.";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (!display_message.priority.has_value()) {
|
||||
EVLOG_error
|
||||
<< "Can not convert DisplayMessage to MessageInfo: No priority is provided, which is required by OCPP.";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
MessageInfo info;
|
||||
info.message.content = display_message.message.message;
|
||||
info.message.format =
|
||||
(display_message.message.message_format.has_value() ? display_message.message.message_format.value()
|
||||
: MessageFormatEnum::UTF8);
|
||||
info.message.language = display_message.message.language;
|
||||
info.endDateTime = display_message.timestamp_to;
|
||||
info.startDateTime = display_message.timestamp_from;
|
||||
info.id = display_message.id.value();
|
||||
info.priority = display_message.priority.value();
|
||||
info.state = display_message.state;
|
||||
info.transactionId = display_message.identifier_id;
|
||||
|
||||
// Note: component is (not yet?) supported for display messages in libocpp.
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief Convert message info from OCPP to DisplayMessage.
|
||||
/// \param message_info The struct to convert.
|
||||
/// \return The converted struct.
|
||||
///
|
||||
DisplayMessage message_info_to_display_message(const MessageInfo& message_info) {
|
||||
DisplayMessage display_message;
|
||||
|
||||
display_message.id = message_info.id;
|
||||
display_message.priority = message_info.priority;
|
||||
display_message.state = message_info.state;
|
||||
display_message.timestamp_from = message_info.startDateTime;
|
||||
display_message.timestamp_to = message_info.endDateTime;
|
||||
display_message.identifier_id = message_info.transactionId;
|
||||
display_message.identifier_type = IdentifierType::TransactionId;
|
||||
display_message.message = message_content_to_display_message_content(message_info.message);
|
||||
|
||||
return display_message;
|
||||
}
|
||||
|
||||
} // namespace ocpp::v2
|
||||
@@ -0,0 +1,205 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <ocpp/v2/functional_blocks/firmware_update.hpp>
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <ocpp/v2/functional_blocks/availability.hpp>
|
||||
#include <ocpp/v2/functional_blocks/functional_block_context.hpp>
|
||||
#include <ocpp/v2/functional_blocks/security.hpp>
|
||||
#include <ocpp/v2/messages/FirmwareStatusNotification.hpp>
|
||||
#include <ocpp/v2/messages/UpdateFirmware.hpp>
|
||||
|
||||
#include <ocpp/v2/ctrlr_component_variables.hpp>
|
||||
#include <ocpp/v2/device_model.hpp>
|
||||
#include <ocpp/v2/evse_manager.hpp>
|
||||
|
||||
namespace ocpp::v2 {
|
||||
|
||||
// Firmware update end states.
|
||||
const static std::array<FirmwareStatusEnum, 5> firmware_status_end_states = {
|
||||
FirmwareStatusEnum::DownloadFailed, FirmwareStatusEnum::InstallationFailed, FirmwareStatusEnum::Installed,
|
||||
FirmwareStatusEnum::InstallVerificationFailed, FirmwareStatusEnum::InvalidSignature};
|
||||
|
||||
FirmwareUpdate::FirmwareUpdate(const FunctionalBlockContext& functional_block_context,
|
||||
AvailabilityInterface& availability, SecurityInterface& security,
|
||||
UpdateFirmwareRequestCallback update_firmware_request_callback,
|
||||
std::optional<AllConnectorsUnavailableCallback> all_connectors_unavailable_callback) :
|
||||
context(functional_block_context),
|
||||
availability(availability),
|
||||
security(security),
|
||||
update_firmware_request_callback(update_firmware_request_callback),
|
||||
all_connectors_unavailable_callback(all_connectors_unavailable_callback),
|
||||
firmware_status(FirmwareStatusEnum::Idle) {
|
||||
}
|
||||
|
||||
void FirmwareUpdate::handle_message(const ocpp::EnhancedMessage<MessageType>& message) {
|
||||
if (message.messageType == MessageType::UpdateFirmware) {
|
||||
this->handle_firmware_update_req(message.message);
|
||||
} else {
|
||||
throw MessageTypeNotImplementedException(message.messageType);
|
||||
}
|
||||
}
|
||||
|
||||
void FirmwareUpdate::on_firmware_update_status_notification(std::int32_t request_id,
|
||||
const FirmwareStatusEnum& firmware_update_status) {
|
||||
if (this->firmware_status == firmware_update_status) {
|
||||
if (request_id == -1 or
|
||||
(this->firmware_status_id.has_value() and this->firmware_status_id.value() == request_id)) {
|
||||
// already sent, do not send again
|
||||
return;
|
||||
}
|
||||
}
|
||||
FirmwareStatusNotificationRequest req;
|
||||
req.status = firmware_update_status;
|
||||
// Firmware status and id are stored for future trigger message request.
|
||||
this->firmware_status = req.status;
|
||||
|
||||
if (request_id != -1) {
|
||||
req.requestId = request_id; // L01.FR.20
|
||||
this->firmware_status_id = request_id;
|
||||
}
|
||||
|
||||
const ocpp::Call<FirmwareStatusNotificationRequest> call(req);
|
||||
this->context.message_dispatcher.dispatch_call_async(call);
|
||||
|
||||
if (req.status == FirmwareStatusEnum::Installed) {
|
||||
std::string firmwareVersionMessage = "New firmware succesfully installed! Version: ";
|
||||
firmwareVersionMessage.append(
|
||||
this->context.device_model.get_value<std::string>(ControllerComponentVariables::FirmwareVersion));
|
||||
this->security.security_event_notification_req(CiString<50>(ocpp::security_events::FIRMWARE_UPDATED),
|
||||
std::optional<CiString<255>>(firmwareVersionMessage), true,
|
||||
true); // L01.FR.31
|
||||
} else if (req.status == FirmwareStatusEnum::InvalidSignature) {
|
||||
this->security.security_event_notification_req(
|
||||
CiString<50>(ocpp::security_events::INVALIDFIRMWARESIGNATURE),
|
||||
std::optional<CiString<255>>("Signature of the provided firmware is not valid!"), true,
|
||||
true); // L01.FR.03 - critical because TC_L_06_CS requires this message to be sent
|
||||
}
|
||||
|
||||
if (std::find(firmware_status_end_states.begin(), firmware_status_end_states.end(), req.status) !=
|
||||
firmware_status_end_states.end()) {
|
||||
// One of the end states is reached. Restore all connector states.
|
||||
this->restore_all_connector_states();
|
||||
}
|
||||
|
||||
if (this->firmware_status_before_installing == req.status) {
|
||||
// FIXME(Kai): This is a temporary workaround, because the EVerest System module does not keep track of
|
||||
// transactions and can't inquire about their status from the OCPP modules. If the firmware status is expected
|
||||
// to become "Installing", but we still have a transaction running, the update will wait for the transaction to
|
||||
// finish, and so we send an "InstallScheduled" status. This is necessary for OCTT TC_L_15_CS to pass.
|
||||
const auto transaction_active = this->context.evse_manager.any_transaction_active(std::nullopt);
|
||||
if (transaction_active) {
|
||||
this->firmware_status = FirmwareStatusEnum::InstallScheduled;
|
||||
req.status = firmware_status;
|
||||
const ocpp::Call<FirmwareStatusNotificationRequest> call(req);
|
||||
this->context.message_dispatcher.dispatch_call_async(call);
|
||||
}
|
||||
this->change_all_connectors_to_unavailable_for_firmware_update();
|
||||
}
|
||||
}
|
||||
|
||||
void FirmwareUpdate::on_firmware_status_notification_request() {
|
||||
FirmwareStatusNotificationRequest request;
|
||||
|
||||
if (this->firmware_status == FirmwareStatusEnum::Idle or this->firmware_status == FirmwareStatusEnum::Installed) {
|
||||
// L01.FR.25
|
||||
// do not set requestId when idle: L01.FR.20
|
||||
request.status = FirmwareStatusEnum::Idle;
|
||||
} else { // L01.FR.26
|
||||
// So not Idle or Installed
|
||||
request.status = this->firmware_status;
|
||||
request.requestId = this->firmware_status_id;
|
||||
}
|
||||
|
||||
const ocpp::Call<FirmwareStatusNotificationRequest> call(request);
|
||||
this->context.message_dispatcher.dispatch_call(call, true);
|
||||
}
|
||||
|
||||
void FirmwareUpdate::handle_firmware_update_req(Call<UpdateFirmwareRequest> call) {
|
||||
EVLOG_debug << "Received UpdateFirmwareRequest: " << call.msg << "\nwith messageId: " << call.uniqueId;
|
||||
if (call.msg.firmware.signingCertificate.has_value() or call.msg.firmware.signature.has_value()) {
|
||||
this->firmware_status_before_installing = FirmwareStatusEnum::SignatureVerified;
|
||||
} else {
|
||||
this->firmware_status_before_installing = FirmwareStatusEnum::Downloaded;
|
||||
}
|
||||
|
||||
UpdateFirmwareResponse response;
|
||||
const auto msg = call.msg;
|
||||
bool cert_valid_or_not_set = true;
|
||||
|
||||
// L01.FR.22 check if certificate is valid
|
||||
if (msg.firmware.signingCertificate.has_value() and
|
||||
this->context.evse_security.verify_certificate(msg.firmware.signingCertificate.value().get(),
|
||||
ocpp::LeafCertificateType::MF) !=
|
||||
ocpp::CertificateValidationResult::Valid) {
|
||||
response.status = UpdateFirmwareStatusEnum::InvalidCertificate;
|
||||
cert_valid_or_not_set = false;
|
||||
}
|
||||
|
||||
if (cert_valid_or_not_set) {
|
||||
// execute firwmare update callback
|
||||
response = update_firmware_request_callback(msg);
|
||||
}
|
||||
|
||||
const ocpp::CallResult<UpdateFirmwareResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
|
||||
if ((response.status == UpdateFirmwareStatusEnum::InvalidCertificate) or
|
||||
(response.status == UpdateFirmwareStatusEnum::RevokedCertificate)) {
|
||||
// L01.FR.02
|
||||
this->security.security_event_notification_req(
|
||||
CiString<50>(ocpp::security_events::INVALIDFIRMWARESIGNINGCERTIFICATE),
|
||||
std::optional<CiString<255>>("Provided signing certificate is not valid!"), true,
|
||||
true); // critical because TC_L_05_CS requires this message to be sent
|
||||
}
|
||||
}
|
||||
|
||||
void FirmwareUpdate::change_all_connectors_to_unavailable_for_firmware_update() {
|
||||
ChangeAvailabilityResponse response;
|
||||
response.status = ChangeAvailabilityStatusEnum::Scheduled;
|
||||
|
||||
ChangeAvailabilityRequest msg;
|
||||
msg.operationalStatus = OperationalStatusEnum::Inoperative;
|
||||
|
||||
const auto transaction_active = this->context.evse_manager.any_transaction_active(std::nullopt);
|
||||
|
||||
if (!transaction_active) {
|
||||
// execute change availability if possible
|
||||
for (auto& evse : this->context.evse_manager) {
|
||||
if (!evse.has_active_transaction()) {
|
||||
set_evse_connectors_unavailable(evse, false);
|
||||
}
|
||||
}
|
||||
// Check succeeded, trigger the callback if needed
|
||||
if (this->all_connectors_unavailable_callback.has_value() and
|
||||
this->context.evse_manager.are_all_connectors_effectively_inoperative()) {
|
||||
this->all_connectors_unavailable_callback.value()();
|
||||
}
|
||||
} else if (response.status == ChangeAvailabilityStatusEnum::Scheduled) {
|
||||
// put all EVSEs to unavailable that do not have active transaction
|
||||
for (auto& evse : this->context.evse_manager) {
|
||||
if (!evse.has_active_transaction()) {
|
||||
set_evse_connectors_unavailable(evse, false);
|
||||
} else {
|
||||
EVSE e;
|
||||
e.id = evse.get_id();
|
||||
msg.evse = e;
|
||||
this->availability.set_scheduled_change_availability_requests(evse.get_id(), {msg, false});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FirmwareUpdate::restore_all_connector_states() {
|
||||
for (auto& evse : this->context.evse_manager) {
|
||||
const std::uint32_t number_of_connectors = evse.get_number_of_connectors();
|
||||
|
||||
for (std::uint32_t i = 1; i <= number_of_connectors; ++i) {
|
||||
evse.restore_connector_operative_status(static_cast<std::int32_t>(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ocpp::v2
|
||||
@@ -0,0 +1,118 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <ocpp/v2/functional_blocks/meter_values.hpp>
|
||||
|
||||
#include <ocpp/common/constants.hpp>
|
||||
#include <ocpp/v2/ctrlr_component_variables.hpp>
|
||||
#include <ocpp/v2/device_model.hpp>
|
||||
#include <ocpp/v2/evse_manager.hpp>
|
||||
#include <ocpp/v2/functional_blocks/functional_block_context.hpp>
|
||||
#include <ocpp/v2/message_dispatcher.hpp>
|
||||
#include <ocpp/v2/messages/MeterValues.hpp>
|
||||
|
||||
ocpp::v2::MeterValues::MeterValues(const FunctionalBlockContext& functional_block_context) :
|
||||
context(functional_block_context) {
|
||||
}
|
||||
|
||||
void ocpp::v2::MeterValues::handle_message(const ocpp::EnhancedMessage<MessageType>& message) {
|
||||
throw MessageTypeNotImplementedException(message.messageType);
|
||||
}
|
||||
|
||||
void ocpp::v2::MeterValues::update_aligned_data_interval() {
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
auto interval = std::chrono::seconds(
|
||||
this->context.device_model.get_value<int>(ControllerComponentVariables::AlignedDataInterval));
|
||||
if (interval <= 0s) {
|
||||
this->aligned_meter_values_timer.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
this->aligned_meter_values_timer.interval_starting_from(
|
||||
[this, interval]() {
|
||||
// J01.FR.20 if AlignedDataSendDuringIdle is true and any transaction is active, don't send clock aligned
|
||||
// meter values
|
||||
if (this->context.device_model
|
||||
.get_optional_value<bool>(ControllerComponentVariables::AlignedDataSendDuringIdle)
|
||||
.value_or(false)) {
|
||||
for (const auto& evse : this->context.evse_manager) {
|
||||
if (evse.has_active_transaction()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bool align_timestamps =
|
||||
this->context.device_model
|
||||
.get_optional_value<bool>(ControllerComponentVariables::RoundClockAlignedTimestamps)
|
||||
.value_or(false);
|
||||
|
||||
// send evseID = 0 values
|
||||
auto meter_value = get_latest_meter_value_filtered(this->aligned_data_evse0.retrieve_processed_values(),
|
||||
ReadingContextEnum::Sample_Clock,
|
||||
ControllerComponentVariables::AlignedDataMeasurands);
|
||||
|
||||
if (!meter_value.sampledValue.empty()) {
|
||||
if (align_timestamps) {
|
||||
meter_value.timestamp = utils::align_timestamp(DateTime{}, interval);
|
||||
}
|
||||
this->meter_values_req(0, std::vector<ocpp::v2::MeterValue>(1, meter_value));
|
||||
}
|
||||
this->aligned_data_evse0.clear_values();
|
||||
|
||||
for (auto& evse : this->context.evse_manager) {
|
||||
if (evse.has_active_transaction()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// this will apply configured measurands and possibly reduce the entries of sampledValue
|
||||
// according to the configuration
|
||||
auto meter_value =
|
||||
get_latest_meter_value_filtered(evse.get_idle_meter_value(), ReadingContextEnum::Sample_Clock,
|
||||
ControllerComponentVariables::AlignedDataMeasurands);
|
||||
|
||||
if (align_timestamps) {
|
||||
meter_value.timestamp = utils::align_timestamp(DateTime{}, interval);
|
||||
}
|
||||
|
||||
if (!meter_value.sampledValue.empty()) {
|
||||
// J01.FR.14 this is the only case where we send a MeterValue.req
|
||||
this->meter_values_req(evse.get_id(), std::vector<ocpp::v2::MeterValue>(1, meter_value));
|
||||
// clear the values
|
||||
}
|
||||
evse.clear_idle_meter_values();
|
||||
}
|
||||
},
|
||||
interval, std::chrono::floor<date::days>(date::utc_clock::to_sys(date::utc_clock::now())));
|
||||
}
|
||||
|
||||
void ocpp::v2::MeterValues::on_meter_value(const std::int32_t evse_id, const MeterValue& meter_value) {
|
||||
if (evse_id == 0) {
|
||||
// if evseId = 0 then store in the chargepoint metervalues
|
||||
this->aligned_data_evse0.set_values(meter_value);
|
||||
} else {
|
||||
this->context.evse_manager.get_evse(evse_id).on_meter_value(meter_value);
|
||||
}
|
||||
}
|
||||
|
||||
ocpp::v2::MeterValue
|
||||
ocpp::v2::MeterValues::get_latest_meter_value_filtered(const MeterValue& meter_value, ReadingContextEnum context,
|
||||
const RequiredComponentVariable& component_variable) {
|
||||
auto filtered_meter_value = utils::get_meter_value_with_measurands_applied(
|
||||
meter_value, utils::get_measurands_vec(this->context.device_model.get_value<std::string>(component_variable)));
|
||||
for (auto& sampled_value : filtered_meter_value.sampledValue) {
|
||||
sampled_value.context = context;
|
||||
}
|
||||
return filtered_meter_value;
|
||||
}
|
||||
|
||||
void ocpp::v2::MeterValues::meter_values_req(const std::int32_t evse_id, const std::vector<MeterValue>& meter_values,
|
||||
const bool initiated_by_trigger_message) {
|
||||
MeterValuesRequest req;
|
||||
req.evseId = evse_id;
|
||||
req.meterValue = meter_values;
|
||||
|
||||
const ocpp::Call<MeterValuesRequest> call(req);
|
||||
this->context.message_dispatcher.dispatch_call(call, initiated_by_trigger_message);
|
||||
}
|
||||
@@ -0,0 +1,986 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <ocpp/v2/functional_blocks/provisioning.hpp>
|
||||
|
||||
#include <set>
|
||||
|
||||
#include <ocpp/common/connectivity_manager.hpp>
|
||||
#include <ocpp/common/constants.hpp>
|
||||
#include <ocpp/common/evse_security.hpp>
|
||||
#include <ocpp/v2/component_state_manager.hpp>
|
||||
#include <ocpp/v2/ctrlr_component_variables.hpp>
|
||||
#include <ocpp/v2/evse_manager.hpp>
|
||||
#include <ocpp/v2/functional_blocks/functional_block_context.hpp>
|
||||
#include <ocpp/v2/notify_report_requests_splitter.hpp>
|
||||
|
||||
#include <ocpp/v2/functional_blocks/availability.hpp>
|
||||
#include <ocpp/v2/functional_blocks/diagnostics.hpp>
|
||||
#include <ocpp/v2/functional_blocks/meter_values.hpp>
|
||||
#include <ocpp/v2/functional_blocks/security.hpp>
|
||||
#include <ocpp/v2/functional_blocks/tariff_and_cost.hpp>
|
||||
#include <ocpp/v2/functional_blocks/transaction.hpp>
|
||||
|
||||
#include <ocpp/v2/messages/BootNotification.hpp>
|
||||
#include <ocpp/v2/messages/GetBaseReport.hpp>
|
||||
#include <ocpp/v2/messages/GetReport.hpp>
|
||||
#include <ocpp/v2/messages/GetVariables.hpp>
|
||||
#include <ocpp/v2/messages/NotifyReport.hpp>
|
||||
#include <ocpp/v2/messages/Reset.hpp>
|
||||
#include <ocpp/v2/messages/SetNetworkProfile.hpp>
|
||||
#include <ocpp/v2/messages/SetVariables.hpp>
|
||||
|
||||
const auto DEFAULT_MAX_MESSAGE_SIZE = 65000;
|
||||
const auto DEFAULT_BOOT_NOTIFICATION_RETRY_INTERVAL = std::chrono::seconds(30);
|
||||
|
||||
namespace ocpp::v2 {
|
||||
|
||||
namespace {
|
||||
bool component_variable_change_requires_websocket_option_update_without_reconnect(
|
||||
const ComponentVariable& component_variable);
|
||||
}
|
||||
|
||||
Provisioning::Provisioning(const FunctionalBlockContext& functional_block_context,
|
||||
MessageQueue<MessageType>& message_queue, OcspUpdaterInterface& ocsp_updater,
|
||||
AvailabilityInterface& availability, MeterValuesInterface& meter_values,
|
||||
SecurityInterface& security, DiagnosticsInterface& diagnostics,
|
||||
TransactionInterface& transaction, std::optional<TimeSyncCallback> time_sync_callback,
|
||||
std::optional<BootNotificationCallback> boot_notification_callback,
|
||||
std::optional<ValidateNetworkProfileCallback> validate_network_profile_callback,
|
||||
IsResetAllowedCallback is_reset_allowed_callback, ResetCallback reset_callback,
|
||||
StopTransactionCallback stop_transaction_callback,
|
||||
std::optional<VariableChangedCallback> variable_changed_callback,
|
||||
TariffAndCostInterface& tariff_and_cost,
|
||||
std::atomic<RegistrationStatusEnum>& registration_status) :
|
||||
context(functional_block_context),
|
||||
message_queue(message_queue),
|
||||
ocsp_updater(ocsp_updater),
|
||||
availability(availability),
|
||||
meter_values(meter_values),
|
||||
security(security),
|
||||
diagnostics(diagnostics),
|
||||
transaction(transaction),
|
||||
tariff_and_cost(tariff_and_cost),
|
||||
time_sync_callback(time_sync_callback),
|
||||
boot_notification_callback(boot_notification_callback),
|
||||
validate_network_profile_callback(validate_network_profile_callback),
|
||||
is_reset_allowed_callback(is_reset_allowed_callback),
|
||||
reset_callback(reset_callback),
|
||||
stop_transaction_callback(stop_transaction_callback),
|
||||
variable_changed_callback(variable_changed_callback),
|
||||
registration_status(registration_status) {
|
||||
}
|
||||
|
||||
void Provisioning::handle_message(const ocpp::EnhancedMessage<MessageType>& message) {
|
||||
const auto& json_message = message.message;
|
||||
|
||||
if (message.messageType == MessageType::BootNotificationResponse) {
|
||||
this->handle_boot_notification_response(json_message);
|
||||
} else if (message.messageType == MessageType::SetVariables) {
|
||||
this->handle_set_variables_req(json_message);
|
||||
} else if (message.messageType == MessageType::GetVariables) {
|
||||
this->handle_get_variables_req(message);
|
||||
} else if (message.messageType == MessageType::GetBaseReport) {
|
||||
this->handle_get_base_report_req(json_message);
|
||||
} else if (message.messageType == MessageType::GetReport) {
|
||||
this->handle_get_report_req(message);
|
||||
} else if (message.messageType == MessageType::Reset) {
|
||||
this->handle_reset_req(json_message);
|
||||
} else if (message.messageType == MessageType::SetNetworkProfile) {
|
||||
this->handle_set_network_profile_req(json_message);
|
||||
} else {
|
||||
throw MessageTypeNotImplementedException(message.messageType);
|
||||
}
|
||||
}
|
||||
|
||||
void Provisioning::boot_notification_req(const BootReasonEnum& reason, const bool initiated_by_trigger_message) {
|
||||
EVLOG_debug << "Sending BootNotification";
|
||||
BootNotificationRequest req;
|
||||
|
||||
ChargingStation charging_station;
|
||||
charging_station.model =
|
||||
this->context.device_model.get_value<std::string>(ControllerComponentVariables::ChargePointModel);
|
||||
charging_station.vendorName =
|
||||
this->context.device_model.get_value<std::string>(ControllerComponentVariables::ChargePointVendor);
|
||||
charging_station.firmwareVersion.emplace(
|
||||
this->context.device_model.get_value<std::string>(ControllerComponentVariables::FirmwareVersion));
|
||||
charging_station.serialNumber.emplace(
|
||||
this->context.device_model.get_value<std::string>(ControllerComponentVariables::ChargeBoxSerialNumber));
|
||||
|
||||
auto iccid = this->context.device_model.get_optional_value<std::string>(ControllerComponentVariables::ICCID);
|
||||
auto imsi = this->context.device_model.get_optional_value<std::string>(ControllerComponentVariables::IMSI);
|
||||
|
||||
if (iccid.has_value() || imsi.has_value()) {
|
||||
Modem modem;
|
||||
if (iccid.has_value()) {
|
||||
modem.iccid.emplace(iccid.value());
|
||||
}
|
||||
if (imsi.has_value()) {
|
||||
modem.imsi.emplace(imsi.value());
|
||||
}
|
||||
charging_station.modem.emplace(modem);
|
||||
}
|
||||
|
||||
req.reason = reason;
|
||||
req.chargingStation = charging_station;
|
||||
|
||||
const ocpp::Call<BootNotificationRequest> call(req);
|
||||
this->context.message_dispatcher.dispatch_call(call, initiated_by_trigger_message);
|
||||
}
|
||||
|
||||
void Provisioning::stop_bootnotification_timer() {
|
||||
this->boot_notification_timer.stop();
|
||||
}
|
||||
|
||||
void Provisioning::on_variable_changed(const SetVariableData& set_variable_data) {
|
||||
this->handle_variable_changed(set_variable_data);
|
||||
}
|
||||
|
||||
std::vector<GetVariableResult>
|
||||
Provisioning::get_variables(const std::vector<GetVariableData>& get_variable_data_vector) {
|
||||
std::vector<GetVariableResult> response;
|
||||
for (const auto& get_variable_data : get_variable_data_vector) {
|
||||
GetVariableResult get_variable_result;
|
||||
get_variable_result.component = get_variable_data.component;
|
||||
get_variable_result.variable = get_variable_data.variable;
|
||||
get_variable_result.attributeType = get_variable_data.attributeType.value_or(AttributeEnum::Actual);
|
||||
const auto request_value_response = this->context.device_model.request_value<std::string>(
|
||||
get_variable_data.component, get_variable_data.variable,
|
||||
get_variable_data.attributeType.value_or(AttributeEnum::Actual));
|
||||
if (request_value_response.status == GetVariableStatusEnum::Accepted and
|
||||
request_value_response.value.has_value()) {
|
||||
get_variable_result.attributeValue = request_value_response.value.value();
|
||||
}
|
||||
get_variable_result.attributeStatus = request_value_response.status;
|
||||
|
||||
this->try_apply_active_slot_identity_override(get_variable_data, get_variable_result);
|
||||
|
||||
response.push_back(get_variable_result);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
std::map<SetVariableData, SetVariableResult>
|
||||
Provisioning::set_variables(const std::vector<SetVariableData>& set_variable_data_vector, const std::string& source) {
|
||||
// set variables and allow setting of ReadOnly variables
|
||||
const auto response = this->set_variables_internal(set_variable_data_vector, source, true);
|
||||
this->handle_variables_changed(response);
|
||||
return response;
|
||||
}
|
||||
|
||||
void Provisioning::notify_report_req(const int request_id, const std::vector<ReportData>& report_data) {
|
||||
NotifyReportRequest req;
|
||||
req.requestId = request_id;
|
||||
req.seqNo = 0;
|
||||
req.generatedAt = ocpp::DateTime();
|
||||
req.reportData.emplace(report_data);
|
||||
req.tbc = false;
|
||||
|
||||
if (report_data.size() <= 1) {
|
||||
const ocpp::Call<NotifyReportRequest> call(req);
|
||||
this->context.message_dispatcher.dispatch_call(call);
|
||||
} else {
|
||||
NotifyReportRequestsSplitter splitter{
|
||||
req,
|
||||
this->context.device_model.get_optional_value<size_t>(ControllerComponentVariables::MaxMessageSize)
|
||||
.value_or(DEFAULT_MAX_MESSAGE_SIZE),
|
||||
[]() { return ocpp::create_message_id(); }};
|
||||
for (const auto& msg : splitter.create_call_payloads()) {
|
||||
this->message_queue.push_call(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Provisioning::handle_boot_notification_response(CallResult<BootNotificationResponse> call_result) {
|
||||
EVLOG_info << "Received BootNotificationResponse: " << call_result.msg
|
||||
<< "\nwith messageId: " << call_result.uniqueId;
|
||||
|
||||
const auto msg = call_result.msg;
|
||||
|
||||
this->registration_status = msg.status;
|
||||
|
||||
if (this->registration_status == RegistrationStatusEnum::Accepted) {
|
||||
this->message_queue.set_registration_status_accepted();
|
||||
// B01.FR.06 Only use boot timestamp if TimeSource contains Heartbeat
|
||||
if (this->time_sync_callback.has_value() and
|
||||
this->context.device_model.get_value<std::string>(ControllerComponentVariables::TimeSource)
|
||||
.find("Heartbeat") != std::string::npos) {
|
||||
this->time_sync_callback.value()(msg.currentTime);
|
||||
}
|
||||
|
||||
this->context.connectivity_manager.confirm_successful_connection();
|
||||
|
||||
// set timers
|
||||
if (msg.interval > 0) {
|
||||
this->availability.set_heartbeat_timer_interval(std::chrono::seconds(msg.interval));
|
||||
if (ControllerComponentVariables::HeartbeatInterval.variable.has_value()) {
|
||||
this->context.device_model.set_value(ControllerComponentVariables::HeartbeatInterval.component,
|
||||
ControllerComponentVariables::HeartbeatInterval.variable.value(),
|
||||
AttributeEnum::Actual, std::to_string(msg.interval),
|
||||
VARIABLE_ATTRIBUTE_VALUE_SOURCE_CSMS);
|
||||
}
|
||||
}
|
||||
|
||||
// in case the BootNotification.req was triggered by a TriggerMessage.req the timer might still run
|
||||
this->boot_notification_timer.stop();
|
||||
|
||||
this->security.init_certificate_expiration_check_timers();
|
||||
this->meter_values.update_aligned_data_interval();
|
||||
this->context.component_state_manager.send_status_notification_all_connectors();
|
||||
this->ocsp_updater.start();
|
||||
} else {
|
||||
auto retry_interval = DEFAULT_BOOT_NOTIFICATION_RETRY_INTERVAL;
|
||||
if (msg.interval > 0) {
|
||||
retry_interval = std::chrono::seconds(msg.interval);
|
||||
}
|
||||
this->boot_notification_timer.timeout(
|
||||
[this, msg]() {
|
||||
this->boot_notification_req(BootReasonEnum::PowerUp); // FIXME(piet): Choose correct reason here
|
||||
},
|
||||
retry_interval);
|
||||
}
|
||||
|
||||
if (this->boot_notification_callback.has_value()) {
|
||||
// call the registered boot notification callback
|
||||
boot_notification_callback.value()(call_result.msg);
|
||||
}
|
||||
}
|
||||
|
||||
void Provisioning::handle_set_variables_req(Call<SetVariablesRequest> call) {
|
||||
const auto msg = call.msg;
|
||||
|
||||
SetVariablesResponse response;
|
||||
|
||||
// set variables but do not allow setting ReadOnly variables
|
||||
const auto set_variables_response =
|
||||
this->set_variables_internal(msg.setVariableData, VARIABLE_ATTRIBUTE_VALUE_SOURCE_CSMS, false);
|
||||
for (const auto& [single_set_variable_data, single_set_variable_result] : set_variables_response) {
|
||||
response.setVariableResult.push_back(single_set_variable_result);
|
||||
}
|
||||
|
||||
const ocpp::CallResult<SetVariablesResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
|
||||
// post handling of changed variables after the SetVariables.conf has been queued
|
||||
this->handle_variables_changed(set_variables_response);
|
||||
}
|
||||
|
||||
void Provisioning::handle_get_variables_req(const EnhancedMessage<MessageType>& message) {
|
||||
const Call<GetVariablesRequest> call = message.call_message;
|
||||
const auto msg = call.msg;
|
||||
|
||||
const auto max_variables_per_message =
|
||||
this->context.device_model.get_value<int>(ControllerComponentVariables::ItemsPerMessageGetVariables);
|
||||
const auto max_bytes_per_message =
|
||||
this->context.device_model.get_value<int>(ControllerComponentVariables::BytesPerMessageGetVariables);
|
||||
|
||||
// B06.FR.16
|
||||
if (msg.getVariableData.size() > max_variables_per_message) {
|
||||
// send a CALLERROR
|
||||
const auto call_error = CallError(call.uniqueId, "OccurenceConstraintViolation", "", json({}));
|
||||
this->context.message_dispatcher.dispatch_call_error(call_error);
|
||||
return;
|
||||
}
|
||||
|
||||
// B06.FR.17
|
||||
if (message.message_size > max_bytes_per_message) {
|
||||
// send a CALLERROR
|
||||
const auto call_error = CallError(call.uniqueId, "FormatViolation", "", json({}));
|
||||
this->context.message_dispatcher.dispatch_call_error(call_error);
|
||||
return;
|
||||
}
|
||||
|
||||
GetVariablesResponse response;
|
||||
response.getVariableResult = this->get_variables(msg.getVariableData);
|
||||
|
||||
const ocpp::CallResult<GetVariablesResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
}
|
||||
|
||||
void Provisioning::handle_get_base_report_req(Call<GetBaseReportRequest> call) {
|
||||
const auto msg = call.msg;
|
||||
GetBaseReportResponse response;
|
||||
response.status = GenericDeviceModelStatusEnum::Accepted;
|
||||
|
||||
const ocpp::CallResult<GetBaseReportResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
|
||||
if (response.status == GenericDeviceModelStatusEnum::Accepted) {
|
||||
const auto report_data = this->context.device_model.get_base_report_data(msg.reportBase);
|
||||
this->notify_report_req(msg.requestId, report_data);
|
||||
}
|
||||
}
|
||||
|
||||
void Provisioning::handle_get_report_req(const EnhancedMessage<MessageType>& message) {
|
||||
const Call<GetReportRequest> call = message.call_message;
|
||||
const auto msg = call.msg;
|
||||
std::vector<ReportData> report_data;
|
||||
GetReportResponse response;
|
||||
|
||||
const auto max_items_per_message =
|
||||
this->context.device_model.get_value<int>(ControllerComponentVariables::ItemsPerMessageGetReport);
|
||||
const auto max_bytes_per_message =
|
||||
this->context.device_model.get_value<int>(ControllerComponentVariables::BytesPerMessageGetReport);
|
||||
|
||||
// B08.FR.17
|
||||
if (msg.componentVariable.has_value() and msg.componentVariable->size() > max_items_per_message) {
|
||||
// send a CALLERROR
|
||||
const auto call_error = CallError(call.uniqueId, "OccurenceConstraintViolation", "", json({}));
|
||||
this->context.message_dispatcher.dispatch_call_error(call_error);
|
||||
return;
|
||||
}
|
||||
|
||||
// B08.FR.18
|
||||
if (message.message_size > max_bytes_per_message) {
|
||||
// send a CALLERROR
|
||||
const auto call_error = CallError(call.uniqueId, "FormatViolation", "", json({}));
|
||||
this->context.message_dispatcher.dispatch_call_error(call_error);
|
||||
return;
|
||||
}
|
||||
|
||||
// if a criteria is not supported then send a not supported response.
|
||||
auto sup_criteria =
|
||||
this->context.device_model.get_optional_value<std::string>(ControllerComponentVariables::SupportedCriteria);
|
||||
if (sup_criteria.has_value() and msg.componentCriteria.has_value()) {
|
||||
for (const auto& criteria : msg.componentCriteria.value()) {
|
||||
const auto variable_ = conversions::component_criterion_enum_to_string(criteria);
|
||||
if (sup_criteria.value().find(variable_) == std::string::npos) {
|
||||
EVLOG_info << "This criteria is not supported: " << variable_;
|
||||
response.status = GenericDeviceModelStatusEnum::NotSupported;
|
||||
break;
|
||||
// TODO: maybe consider adding the reason why in statusInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (response.status != GenericDeviceModelStatusEnum::NotSupported) {
|
||||
|
||||
// TODO(piet): Propably split this up into several NotifyReport.req depending on ItemsPerMessage /
|
||||
// BytesPerMessage
|
||||
report_data = this->context.device_model.get_custom_report_data(msg.componentVariable, msg.componentCriteria);
|
||||
if (report_data.empty()) {
|
||||
response.status = GenericDeviceModelStatusEnum::EmptyResultSet;
|
||||
} else {
|
||||
response.status = GenericDeviceModelStatusEnum::Accepted;
|
||||
}
|
||||
}
|
||||
|
||||
const ocpp::CallResult<GetReportResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
|
||||
if (response.status == GenericDeviceModelStatusEnum::Accepted) {
|
||||
this->notify_report_req(msg.requestId, report_data);
|
||||
}
|
||||
}
|
||||
|
||||
void Provisioning::handle_set_network_profile_req(Call<SetNetworkProfileRequest> call) {
|
||||
const auto msg = call.msg;
|
||||
|
||||
SetNetworkProfileResponse response;
|
||||
StatusInfo status_info;
|
||||
|
||||
if (!this->validate_network_profile_callback.has_value()) {
|
||||
const auto warning = "No callback registered to validate network profile";
|
||||
EVLOG_warning << warning;
|
||||
status_info.reasonCode = "InternalError";
|
||||
status_info.additionalInfo = warning;
|
||||
response.statusInfo = status_info;
|
||||
response.status = SetNetworkProfileStatusEnum::Rejected;
|
||||
const ocpp::CallResult<SetNetworkProfileResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
return;
|
||||
}
|
||||
|
||||
// B09.FR.05: If configurationSlot is not listed in NetworkConfigurationPriority.valuesList,
|
||||
// reject with reasonCode "InvalidConfSlot" before validating the profile contents.
|
||||
if (!this->is_slot_allowed_by_priority_values_list(msg.configurationSlot)) {
|
||||
const auto warning = "CSMS attempted to set a network profile on a configurationSlot that is not in "
|
||||
"NetworkConfigurationPriority.valuesList";
|
||||
EVLOG_warning << warning << ": slot=" << msg.configurationSlot;
|
||||
status_info.reasonCode = "InvalidConfSlot";
|
||||
status_info.additionalInfo = warning;
|
||||
response.statusInfo = status_info;
|
||||
response.status = SetNetworkProfileStatusEnum::Rejected;
|
||||
const ocpp::CallResult<SetNetworkProfileResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.connectionData.securityProfile <
|
||||
this->context.device_model.get_value<int>(ControllerComponentVariables::SecurityProfile)) {
|
||||
const auto warning = "CSMS attempted to set a network profile with a lower securityProfile";
|
||||
EVLOG_warning << warning;
|
||||
status_info.reasonCode = "NoSecurityDowngrade";
|
||||
status_info.additionalInfo = warning;
|
||||
response.statusInfo = status_info;
|
||||
response.status = SetNetworkProfileStatusEnum::Rejected;
|
||||
const ocpp::CallResult<SetNetworkProfileResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto profile_validation =
|
||||
this->validate_network_connection_profile(msg.configurationSlot, msg.connectionData);
|
||||
if (profile_validation.has_value()) {
|
||||
const auto warning = "CSMS attempted to set a network profile with invalid URL/security configuration";
|
||||
EVLOG_warning << warning;
|
||||
status_info.reasonCode = profile_validation.value();
|
||||
status_info.additionalInfo = warning;
|
||||
response.statusInfo = status_info;
|
||||
response.status = SetNetworkProfileStatusEnum::Rejected;
|
||||
const ocpp::CallResult<SetNetworkProfileResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->validate_network_profile_callback.value()(msg.configurationSlot, msg.connectionData) !=
|
||||
SetNetworkProfileStatusEnum::Accepted) {
|
||||
const auto warning = "CSMS attempted to set a network profile that could not be validated.";
|
||||
EVLOG_warning << warning;
|
||||
status_info.reasonCode = "InvalidNetworkConf";
|
||||
status_info.additionalInfo = warning;
|
||||
response.statusInfo = status_info;
|
||||
response.status = SetNetworkProfileStatusEnum::Rejected;
|
||||
const ocpp::CallResult<SetNetworkProfileResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
return;
|
||||
}
|
||||
|
||||
// Write the profile to NetworkConfiguration device model variables and refresh the cache (B09.FR.09 /
|
||||
// B09.FR.11/12). B09.FR.03: if the profile is semantically valid but the device-model write fails, respond with
|
||||
// status `Failed` (as opposed to `Rejected`, which is reserved for invalid content per B09.FR.02).
|
||||
if (!this->context.connectivity_manager.set_network_profile(msg.configurationSlot, msg.connectionData,
|
||||
VARIABLE_ATTRIBUTE_VALUE_SOURCE_INTERNAL)) {
|
||||
const auto warning = "Network profile could not be written to the device model";
|
||||
EVLOG_warning << warning;
|
||||
status_info.reasonCode = "InternalError";
|
||||
status_info.additionalInfo = warning;
|
||||
response.statusInfo = status_info;
|
||||
response.status = SetNetworkProfileStatusEnum::Failed;
|
||||
const ocpp::CallResult<SetNetworkProfileResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string tech_info = "Received and stored a new network connection profile at configurationSlot: " +
|
||||
std::to_string(msg.configurationSlot);
|
||||
EVLOG_info << tech_info;
|
||||
|
||||
const auto& security_event = ocpp::security_events::RECONFIGURATIONOFSECURITYPARAMETERS;
|
||||
this->security.security_event_notification_req(CiString<50>(security_event), CiString<255>(tech_info), true,
|
||||
utils::is_critical(security_event));
|
||||
|
||||
response.status = SetNetworkProfileStatusEnum::Accepted;
|
||||
const ocpp::CallResult<SetNetworkProfileResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
}
|
||||
|
||||
void Provisioning::handle_reset_req(Call<ResetRequest> call) {
|
||||
EVLOG_debug << "Received ResetRequest: " << call.msg << "\nwith messageId: " << call.uniqueId;
|
||||
const auto msg = call.msg;
|
||||
|
||||
ResetResponse response;
|
||||
|
||||
// Check if there is an active transaction (on the given evse or if not
|
||||
// given, on one of the evse's)
|
||||
bool transaction_active = false;
|
||||
std::set<std::int32_t> evse_active_transactions;
|
||||
std::set<std::int32_t> evse_no_transactions;
|
||||
if (msg.evseId.has_value() and this->context.evse_manager.get_evse(msg.evseId.value()).has_active_transaction()) {
|
||||
transaction_active = true;
|
||||
evse_active_transactions.emplace(msg.evseId.value());
|
||||
} else {
|
||||
for (const auto& evse : this->context.evse_manager) {
|
||||
if (evse.has_active_transaction()) {
|
||||
transaction_active = true;
|
||||
evse_active_transactions.emplace(evse.get_id());
|
||||
} else {
|
||||
evse_no_transactions.emplace(evse.get_id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto is_reset_allowed = [&]() {
|
||||
if (!this->is_reset_allowed_callback(msg.evseId, msg.type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We dont need to check AllowReset if evseId is not set and can directly return true
|
||||
if (!msg.evseId.has_value()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// B11.FR.10
|
||||
const auto allow_reset_cv =
|
||||
EvseComponentVariables::get_component_variable(msg.evseId.value(), EvseComponentVariables::AllowReset);
|
||||
// allow reset if AllowReset is not set or set to true
|
||||
return this->context.device_model.get_optional_value<bool>(allow_reset_cv).value_or(true);
|
||||
};
|
||||
|
||||
if (is_reset_allowed()) {
|
||||
// reset is allowed
|
||||
response.status = ResetStatusEnum::Accepted;
|
||||
} else {
|
||||
response.status = ResetStatusEnum::Rejected;
|
||||
}
|
||||
|
||||
if (response.status == ResetStatusEnum::Accepted and transaction_active and msg.type == ResetEnum::OnIdle) {
|
||||
std::optional<std::int32_t> reset_scheduled_evseid = std::nullopt;
|
||||
// B12.FR.07
|
||||
reset_scheduled_evseid = msg.evseId;
|
||||
|
||||
// B12.FR.01: We have to wait until transactions have ended.
|
||||
// B12.FR.07
|
||||
this->transaction.schedule_reset(reset_scheduled_evseid);
|
||||
response.status = ResetStatusEnum::Scheduled;
|
||||
}
|
||||
|
||||
const ocpp::CallResult<ResetResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
|
||||
// Reset response is sent, now set evse connectors to unavailable and / or
|
||||
// stop transaction (depending on reset type)
|
||||
if (response.status != ResetStatusEnum::Rejected and transaction_active) {
|
||||
if (msg.type == ResetEnum::Immediate) {
|
||||
// B12.FR.08 and B12.FR.04
|
||||
for (const std::int32_t evse_id : evse_active_transactions) {
|
||||
stop_transaction_callback(evse_id, ReasonEnum::ImmediateReset);
|
||||
}
|
||||
} else if (msg.type == ResetEnum::OnIdle and !evse_no_transactions.empty()) {
|
||||
for (const std::int32_t evse_id : evse_no_transactions) {
|
||||
auto& evse = this->context.evse_manager.get_evse(evse_id);
|
||||
set_evse_connectors_unavailable(evse, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (response.status == ResetStatusEnum::Accepted) {
|
||||
this->reset_callback(call.msg.evseId, ResetEnum::Immediate);
|
||||
}
|
||||
}
|
||||
|
||||
void Provisioning::handle_variable_changed(const SetVariableData& set_variable_data) {
|
||||
const ComponentVariable component_variable = {set_variable_data.component, set_variable_data.variable,
|
||||
std::nullopt};
|
||||
|
||||
if (set_variable_data.attributeType.has_value() and
|
||||
set_variable_data.attributeType.value() != AttributeEnum::Actual) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (component_variable == ControllerComponentVariables::BasicAuthPassword) {
|
||||
if (this->context.device_model.get_value<int>(ControllerComponentVariables::SecurityProfile) < 3) {
|
||||
this->context.connectivity_manager.set_websocket_authorization_key(set_variable_data.attributeValue.get());
|
||||
}
|
||||
// A01.FR.11: log the change of BasicAuthPassword via a SecurityEventNotification.
|
||||
// The event payload must not contain the new password value.
|
||||
const auto& security_event = ocpp::security_events::RECONFIGURATIONOFSECURITYPARAMETERS;
|
||||
this->security.security_event_notification_req(CiString<50>(security_event),
|
||||
CiString<255>("BasicAuthPassword changed"), true,
|
||||
utils::is_critical(security_event));
|
||||
// B09.FR.27: Clear the per-slot BasicAuthPassword override on the active NetworkConfiguration
|
||||
// slot so reads fall back to the new SecurityCtrlr global per B09.FR.16.
|
||||
this->clear_active_slot_variable(NetworkConfigurationComponentVariables::BasicAuthPassword, "B09.FR.27");
|
||||
}
|
||||
if (component_variable == ControllerComponentVariables::SecurityCtrlrIdentity) {
|
||||
// B09.FR.26: Clear the per-slot Identity override on the active NetworkConfiguration slot so
|
||||
// reads fall back to the new SecurityCtrlr global per B09.FR.16.
|
||||
this->clear_active_slot_variable(NetworkConfigurationComponentVariables::Identity, "B09.FR.26");
|
||||
}
|
||||
if (component_variable == ControllerComponentVariables::HeartbeatInterval and
|
||||
this->registration_status == RegistrationStatusEnum::Accepted) {
|
||||
try {
|
||||
this->availability.set_heartbeat_timer_interval(
|
||||
std::chrono::seconds(std::stoi(set_variable_data.attributeValue.get())));
|
||||
} catch (const std::invalid_argument& e) {
|
||||
EVLOG_error << "Invalid argument exception while updating the heartbeat interval: " << e.what();
|
||||
} catch (const std::out_of_range& e) {
|
||||
EVLOG_error << "Out of range exception while updating the heartbeat interval: " << e.what();
|
||||
}
|
||||
}
|
||||
if (component_variable == ControllerComponentVariables::AlignedDataInterval) {
|
||||
this->meter_values.update_aligned_data_interval();
|
||||
}
|
||||
|
||||
if (component_variable_change_requires_websocket_option_update_without_reconnect(component_variable)) {
|
||||
EVLOG_debug << "Reconfigure websocket due to relevant change of ControllerComponentVariable";
|
||||
this->context.connectivity_manager.set_websocket_connection_options_without_reconnect();
|
||||
}
|
||||
|
||||
if (component_variable == ControllerComponentVariables::MessageAttemptInterval) {
|
||||
if (component_variable.variable.has_value()) {
|
||||
this->message_queue.update_transaction_message_retry_interval(
|
||||
this->context.device_model.get_value<int>(ControllerComponentVariables::MessageAttemptInterval));
|
||||
}
|
||||
}
|
||||
|
||||
if (component_variable == ControllerComponentVariables::MessageAttempts) {
|
||||
if (component_variable.variable.has_value()) {
|
||||
this->message_queue.update_transaction_message_attempts(
|
||||
this->context.device_model.get_value<int>(ControllerComponentVariables::MessageAttempts));
|
||||
}
|
||||
}
|
||||
|
||||
if (component_variable == ControllerComponentVariables::TariffFallbackMessage ||
|
||||
component_variable == ControllerComponentVariables::OfflineTariffFallbackMessage) {
|
||||
// SetVariables can only be received while connected, so publish the online price.
|
||||
this->tariff_and_cost.publish_default_price(this->context.connectivity_manager.is_websocket_connected());
|
||||
}
|
||||
|
||||
// TODO(piet): other special handling of changed variables can be added here...
|
||||
}
|
||||
|
||||
void Provisioning::handle_variables_changed(const std::map<SetVariableData, SetVariableResult>& set_variable_results) {
|
||||
// Track which NetworkConfiguration slots were modified
|
||||
std::set<int32_t> modified_slots;
|
||||
|
||||
// iterate over set_variable_results
|
||||
for (const auto& [set_variable_data, set_variable_result] : set_variable_results) {
|
||||
if (set_variable_result.attributeStatus == SetVariableStatusEnum::Accepted) {
|
||||
std::optional<MutabilityEnum> mutability = this->context.device_model.get_mutability(
|
||||
set_variable_data.component, set_variable_data.variable,
|
||||
set_variable_data.attributeType.value_or(AttributeEnum::Actual));
|
||||
// If a nullopt is returned for whatever reason, assume it's write-only to prevent leaking secrets
|
||||
if (!mutability.has_value() || (mutability.value() == MutabilityEnum::WriteOnly)) {
|
||||
EVLOG_info << "Write-only " << set_variable_data.component.name << ":"
|
||||
<< set_variable_data.variable.name << " changed";
|
||||
} else {
|
||||
EVLOG_info << set_variable_data.component.name << ":" << set_variable_data.variable.name
|
||||
<< " changed to " << set_variable_data.attributeValue.get();
|
||||
}
|
||||
|
||||
// Track which NetworkConfiguration DM component slots were modified via SetVariables
|
||||
if (set_variable_data.component.name == "NetworkConfiguration" &&
|
||||
set_variable_data.component.instance.has_value()) {
|
||||
try {
|
||||
const int32_t slot = std::stoi(set_variable_data.component.instance.value().get());
|
||||
modified_slots.insert(slot);
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_warning << "Error parsing NetworkConfiguration slot: " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
// handles required behavior specified within OCPP2.0.1 (e.g. reconnect when BasicAuthPassword has changed)
|
||||
this->handle_variable_changed(set_variable_data);
|
||||
// notifies libocpp user application that a variable has changed
|
||||
if (this->variable_changed_callback.has_value()) {
|
||||
this->variable_changed_callback.value()(set_variable_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh the connectivity manager's in-memory profile cache if any NetworkConfiguration slot changed
|
||||
if (!modified_slots.empty()) {
|
||||
this->context.connectivity_manager.reload_network_profiles();
|
||||
}
|
||||
|
||||
// process all triggered monitors, after a possible disconnect
|
||||
this->diagnostics.process_triggered_monitors();
|
||||
}
|
||||
|
||||
std::optional<std::string>
|
||||
Provisioning::validate_set_network_configuration_slot(const SetVariableData& set_variable_data,
|
||||
const ComponentVariable& cv) {
|
||||
try {
|
||||
const int slot = std::stoi(cv.component.instance.value().get());
|
||||
|
||||
// B09.FR.22: Reject SetVariables targeting the currently active slot or any slot listed
|
||||
// in the current NetworkConfigurationPriority. The spec mandates a single reasonCode
|
||||
// `PriorityNetworkConf` for every such slot (no separate "active" reason code).
|
||||
const auto active_slot_opt =
|
||||
this->context.device_model.get_optional_value<int>(ControllerComponentVariables::ActiveNetworkProfile);
|
||||
if (active_slot_opt.has_value() && slot == active_slot_opt.value()) {
|
||||
EVLOG_warning << "Cannot set NetworkConfiguration variable for slot " << slot
|
||||
<< " which is the currently active network profile";
|
||||
return "PriorityNetworkConf";
|
||||
}
|
||||
|
||||
const auto priority_slot_opt = this->context.device_model.get_optional_value<std::string>(
|
||||
ControllerComponentVariables::NetworkConfigurationPriority);
|
||||
if (priority_slot_opt.has_value()) {
|
||||
for (const auto& priority_slot_str : ocpp::split_string(priority_slot_opt.value(), ',')) {
|
||||
int priority_slot = 0;
|
||||
try {
|
||||
priority_slot = std::stoi(priority_slot_str);
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_warning << "NetworkConfigurationPriority contains non-integer token '" << priority_slot_str
|
||||
<< "': " << e.what();
|
||||
continue;
|
||||
}
|
||||
if (priority_slot == slot) {
|
||||
EVLOG_warning << "Cannot set NetworkConfiguration variable for slot " << slot
|
||||
<< " which is a priority network profile";
|
||||
return "PriorityNetworkConf";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// B09.FR.35: Reject security profile downgrades
|
||||
if (cv.variable.value().name == "SecurityProfile") {
|
||||
try {
|
||||
const int new_profile = std::stoi(set_variable_data.attributeValue.get());
|
||||
const int active_profile =
|
||||
this->context.device_model.get_value<int>(ControllerComponentVariables::SecurityProfile);
|
||||
if (new_profile < active_profile) {
|
||||
EVLOG_warning << "Cannot downgrade SecurityProfile from " << active_profile << " to "
|
||||
<< new_profile;
|
||||
return "NoSecurityDowngrade";
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_warning << "Error validating SecurityProfile: " << e.what();
|
||||
return "InvalidNetworkConf";
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that the resulting profile (current state + proposed change) has consistent
|
||||
// URL scheme / security profile / certificate configuration
|
||||
auto profile_opt =
|
||||
NetworkConfigurationComponentVariables::read_profile_from_device_model(this->context.device_model, slot);
|
||||
if (profile_opt.has_value()) {
|
||||
auto profile = *profile_opt;
|
||||
// Apply the proposed change to the profile copy before validation
|
||||
if (cv.variable.value().name == "SecurityProfile") {
|
||||
profile.securityProfile = std::stoi(set_variable_data.attributeValue.get());
|
||||
} else if (cv.variable.value().name == "OcppCsmsUrl") {
|
||||
profile.ocppCsmsUrl = CiString<2000>(set_variable_data.attributeValue.get());
|
||||
}
|
||||
const auto result = this->validate_network_connection_profile(slot, profile);
|
||||
if (result.has_value()) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_warning << "Error validating NetworkConfiguration: " << e.what();
|
||||
return "InvalidNetworkConf";
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::string> Provisioning::validate_network_connection_profile(const int32_t configuration_slot,
|
||||
const NetworkConnectionProfile& profile) {
|
||||
const auto active_security_profile =
|
||||
this->context.device_model.get_value<int>(ControllerComponentVariables::SecurityProfile);
|
||||
|
||||
// Validate URL scheme matches security profile
|
||||
const std::string url = profile.ocppCsmsUrl.get();
|
||||
const bool is_wss = url.find("wss://") == 0;
|
||||
const bool is_ws = url.find("ws://") == 0;
|
||||
if (is_wss && profile.securityProfile < 2) {
|
||||
EVLOG_warning << "configurationSlot " << configuration_slot
|
||||
<< ": wss:// URL requires securityProfile >= 2, got " << profile.securityProfile;
|
||||
return "InvalidNetworkConf";
|
||||
}
|
||||
if (is_ws && profile.securityProfile >= 2) {
|
||||
EVLOG_warning << "configurationSlot " << configuration_slot
|
||||
<< ": ws:// URL is not allowed with securityProfile >= 2, got " << profile.securityProfile;
|
||||
return "InvalidNetworkConf";
|
||||
}
|
||||
|
||||
if (profile.securityProfile <= active_security_profile) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (profile.securityProfile == 3 and
|
||||
this->context.evse_security
|
||||
.get_leaf_certificate_info(ocpp::CertificateSigningUseEnum::ChargingStationCertificate)
|
||||
.status != ocpp::GetCertificateInfoStatus::Accepted) {
|
||||
EVLOG_warning << "SecurityProfile of configurationSlot: " << configuration_slot
|
||||
<< " is 3 but no CSMS Leaf Certificate is installed";
|
||||
return "InvalidNetworkConf";
|
||||
}
|
||||
if (profile.securityProfile >= 2 and
|
||||
!this->context.evse_security.is_ca_certificate_installed(ocpp::CaCertificateType::CSMS)) {
|
||||
EVLOG_warning << "SecurityProfile of configurationSlot: " << configuration_slot
|
||||
<< " is >= 2 but no CSMS Root Certifciate is installed";
|
||||
return "InvalidNetworkConf";
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::string>
|
||||
Provisioning::validate_network_configuration_priority(const SetVariableData& set_variable_data) {
|
||||
const auto network_configuration_priorities = ocpp::split_string(set_variable_data.attributeValue.get(), ',');
|
||||
|
||||
try {
|
||||
for (const auto& configuration_slot_str : network_configuration_priorities) {
|
||||
const int slot = std::stoi(configuration_slot_str);
|
||||
const auto profile_opt = NetworkConfigurationComponentVariables::read_profile_from_device_model(
|
||||
this->context.device_model, slot);
|
||||
|
||||
if (!profile_opt.has_value()) {
|
||||
EVLOG_warning << "Could not find network profile for configurationSlot: " << configuration_slot_str;
|
||||
return "InvalidNetworkConf";
|
||||
}
|
||||
}
|
||||
} catch (const std::invalid_argument& e) {
|
||||
EVLOG_warning << "NetworkConfigurationPriority contains at least one value which is not an integer: "
|
||||
<< set_variable_data.attributeValue.get();
|
||||
return "InvalidNetworkConf";
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::string> Provisioning::validate_set_variable(const SetVariableData& set_variable_data) {
|
||||
const ComponentVariable cv = {set_variable_data.component, set_variable_data.variable, std::nullopt};
|
||||
|
||||
// B09.FR.21/22: Reject changes to the currently active NetworkConfiguration slot or a priority network slot
|
||||
if (cv.component.name == "NetworkConfiguration" && cv.component.instance.has_value() && cv.variable.has_value()) {
|
||||
const auto result = this->validate_set_network_configuration_slot(set_variable_data, cv);
|
||||
if (result.has_value()) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (cv == ControllerComponentVariables::NetworkConfigurationPriority) {
|
||||
return this->validate_network_configuration_priority(set_variable_data);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
// TODO(piet): other special validating of variables requested to change can be added here...
|
||||
}
|
||||
|
||||
std::map<SetVariableData, SetVariableResult>
|
||||
Provisioning::set_variables_internal(const std::vector<SetVariableData>& set_variable_data_vector,
|
||||
const std::string& source, const bool allow_read_only) {
|
||||
std::map<SetVariableData, SetVariableResult> response;
|
||||
|
||||
// iterate over the set_variable_data_vector
|
||||
for (const auto& set_variable_data : set_variable_data_vector) {
|
||||
SetVariableResult set_variable_result;
|
||||
set_variable_result.component = set_variable_data.component;
|
||||
set_variable_result.variable = set_variable_data.variable;
|
||||
set_variable_result.attributeType = set_variable_data.attributeType.value_or(AttributeEnum::Actual);
|
||||
|
||||
// validates variable against business logic of the spec
|
||||
const auto validation_result = this->validate_set_variable(set_variable_data);
|
||||
if (!validation_result.has_value()) {
|
||||
// attempt to set the value includes device model validation
|
||||
set_variable_result.attributeStatus =
|
||||
this->context.device_model.set_value(set_variable_data.component, set_variable_data.variable,
|
||||
set_variable_data.attributeType.value_or(AttributeEnum::Actual),
|
||||
set_variable_data.attributeValue.get(), source, allow_read_only);
|
||||
} else {
|
||||
set_variable_result.attributeStatus = SetVariableStatusEnum::Rejected;
|
||||
StatusInfo status_info;
|
||||
status_info.reasonCode = validation_result.value();
|
||||
set_variable_result.attributeStatusInfo = status_info;
|
||||
}
|
||||
response[set_variable_data] = set_variable_result;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
void Provisioning::clear_active_slot_variable(const Variable& variable, const std::string& reason_tag) {
|
||||
const auto active_slot_opt =
|
||||
this->context.device_model.get_optional_value<int>(ControllerComponentVariables::ActiveNetworkProfile);
|
||||
if (!active_slot_opt.has_value()) {
|
||||
return;
|
||||
}
|
||||
const auto cv = NetworkConfigurationComponentVariables::get_component_variable(active_slot_opt.value(), variable);
|
||||
if (!cv.variable.has_value()) {
|
||||
return;
|
||||
}
|
||||
// Use clear_value which bypasses validate_value; per-slot variables like BasicAuthPassword
|
||||
// declare minLimit=16, which would reject the empty-string sentinel via set_value's normal
|
||||
// path. read_profile_from_device_model then treats the empty per-slot override as unset and
|
||||
// falls back to SecurityCtrlr per B09.FR.16.
|
||||
const auto status = this->context.device_model.clear_value(cv.component, cv.variable.value(), AttributeEnum::Actual,
|
||||
VARIABLE_ATTRIBUTE_VALUE_SOURCE_INTERNAL);
|
||||
if (status == SetVariableStatusEnum::Accepted) {
|
||||
EVLOG_info << "Cleared " << variable.name.get() << " on active NetworkConfiguration slot "
|
||||
<< active_slot_opt.value() << " (" << reason_tag << ")";
|
||||
} else {
|
||||
EVLOG_warning << "Could not clear " << variable.name.get() << " on active NetworkConfiguration slot "
|
||||
<< active_slot_opt.value() << " (" << reason_tag << "): set rejected";
|
||||
}
|
||||
}
|
||||
|
||||
void Provisioning::try_apply_active_slot_identity_override(const GetVariableData& get_variable_data,
|
||||
GetVariableResult& result) const {
|
||||
// B09.FR.28: Return the per-slot Identity for the currently active profile. Gate on that
|
||||
// slot still being listed in NetworkConfigurationPriority; after a security escalation
|
||||
// prunes a slot from the priority list, its per-slot Identity may still be readable from
|
||||
// the device model but no longer represents the active identity and must not leak out.
|
||||
const ComponentVariable cv = {get_variable_data.component, get_variable_data.variable, std::nullopt};
|
||||
if (!(cv == ControllerComponentVariables::SecurityCtrlrIdentity) ||
|
||||
result.attributeStatus != GetVariableStatusEnum::Accepted) {
|
||||
return;
|
||||
}
|
||||
const auto active_slot_opt =
|
||||
this->context.device_model.get_optional_value<int>(ControllerComponentVariables::ActiveNetworkProfile);
|
||||
if (!active_slot_opt.has_value()) {
|
||||
return;
|
||||
}
|
||||
const auto priority_opt = this->context.device_model.get_optional_value<std::string>(
|
||||
ControllerComponentVariables::NetworkConfigurationPriority);
|
||||
bool active_slot_in_priority = false;
|
||||
if (priority_opt.has_value()) {
|
||||
for (const auto& slot_str : ocpp::split_string(priority_opt.value(), ',')) {
|
||||
try {
|
||||
if (std::stoi(slot_str) == active_slot_opt.value()) {
|
||||
active_slot_in_priority = true;
|
||||
break;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_debug << "Skipping non-integer token in NetworkConfigurationPriority '" << slot_str
|
||||
<< "': " << e.what();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!active_slot_in_priority) {
|
||||
return;
|
||||
}
|
||||
const auto slot_cv = NetworkConfigurationComponentVariables::get_component_variable(
|
||||
active_slot_opt.value(), NetworkConfigurationComponentVariables::Identity);
|
||||
if (const auto slot_id = this->context.device_model.get_optional_value<std::string>(slot_cv);
|
||||
slot_id.has_value() && !slot_id->empty()) {
|
||||
result.attributeValue = slot_id.value();
|
||||
}
|
||||
}
|
||||
|
||||
bool Provisioning::is_slot_allowed_by_priority_values_list(int32_t slot) {
|
||||
if (!ControllerComponentVariables::NetworkConfigurationPriority.variable.has_value()) {
|
||||
return true;
|
||||
}
|
||||
const auto meta = this->context.device_model.get_variable_meta_data(
|
||||
ControllerComponentVariables::NetworkConfigurationPriority.component,
|
||||
ControllerComponentVariables::NetworkConfigurationPriority.variable.value());
|
||||
if (meta.has_value() && meta->characteristics.valuesList.has_value() &&
|
||||
!meta->characteristics.valuesList.value().get().empty()) {
|
||||
for (const auto& allowed_str : ocpp::split_string(meta->characteristics.valuesList.value().get(), ',')) {
|
||||
try {
|
||||
if (std::stoi(allowed_str) == slot) {
|
||||
return true;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_debug << "Skipping non-integer token in NetworkConfigurationPriority.valuesList '" << allowed_str
|
||||
<< "': " << e.what();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// If no valuesList is configured, fall back to allowing any slot; the spec only requires
|
||||
// the check when valuesList is defined on the NetworkConfigurationPriority variable.
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
/**
|
||||
* Determine for a component variable whether it affects the Websocket Connection Options (cf.
|
||||
* get_ws_connection_options); return true if it is furthermore writable and does not require a reconnect
|
||||
*
|
||||
* @param component_variable
|
||||
* @return
|
||||
*/
|
||||
bool component_variable_change_requires_websocket_option_update_without_reconnect(
|
||||
const ComponentVariable& component_variable) {
|
||||
|
||||
return component_variable == ControllerComponentVariables::RetryBackOffRandomRange or
|
||||
component_variable == ControllerComponentVariables::RetryBackOffRepeatTimes or
|
||||
component_variable == ControllerComponentVariables::RetryBackOffWaitMinimum or
|
||||
component_variable == ControllerComponentVariables::NetworkProfileConnectionAttempts or
|
||||
component_variable == ControllerComponentVariables::WebSocketPingInterval;
|
||||
}
|
||||
} // namespace
|
||||
} // namespace ocpp::v2
|
||||
@@ -0,0 +1,445 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <ocpp/v2/functional_blocks/remote_transaction_control.hpp>
|
||||
|
||||
#include <ocpp/common/connectivity_manager.hpp>
|
||||
#include <ocpp/v2/ctrlr_component_variables.hpp>
|
||||
#include <ocpp/v2/device_model.hpp>
|
||||
#include <ocpp/v2/evse_manager.hpp>
|
||||
#include <ocpp/v2/functional_blocks/functional_block_context.hpp>
|
||||
|
||||
#include <ocpp/v2/functional_blocks/availability.hpp>
|
||||
#include <ocpp/v2/functional_blocks/firmware_update.hpp>
|
||||
#include <ocpp/v2/functional_blocks/meter_values.hpp>
|
||||
#include <ocpp/v2/functional_blocks/provisioning.hpp>
|
||||
#include <ocpp/v2/functional_blocks/reservation.hpp>
|
||||
#include <ocpp/v2/functional_blocks/security.hpp>
|
||||
#include <ocpp/v2/functional_blocks/smart_charging.hpp>
|
||||
#include <ocpp/v2/functional_blocks/transaction.hpp>
|
||||
|
||||
#include <ocpp/v2/messages/LogStatusNotification.hpp>
|
||||
#include <ocpp/v2/messages/RequestStartTransaction.hpp>
|
||||
#include <ocpp/v2/messages/RequestStopTransaction.hpp>
|
||||
#include <ocpp/v2/messages/SetChargingProfile.hpp>
|
||||
#include <ocpp/v2/messages/TriggerMessage.hpp>
|
||||
#include <ocpp/v2/messages/UnlockConnector.hpp>
|
||||
|
||||
namespace ocpp::v2 {
|
||||
namespace {
|
||||
///
|
||||
/// \brief Check if one of the connectors of the evse is available (both connectors faulted or unavailable or on of
|
||||
/// the connectors occupied).
|
||||
/// \param evse Evse to check.
|
||||
/// \return True if at least one connector is not faulted or unavailable.
|
||||
///
|
||||
bool is_evse_connector_available(EvseInterface& evse) {
|
||||
if (evse.has_active_transaction()) {
|
||||
// If an EV is connected and has no authorization yet then the status is 'Occupied' and the
|
||||
// RemoteStartRequest should still be accepted. So this is the 'occupied' check instead.
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::uint32_t connectors = evse.get_number_of_connectors();
|
||||
for (std::uint32_t i = 1; i <= connectors; ++i) {
|
||||
const ConnectorStatusEnum status =
|
||||
evse.get_connector(static_cast<std::int32_t>(i))->get_effective_connector_status();
|
||||
|
||||
// At least one of the connectors is available / not faulted.
|
||||
if (status != ConnectorStatusEnum::Faulted and status != ConnectorStatusEnum::Unavailable) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Connectors are faulted or unavailable.
|
||||
return false;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
RemoteTransactionControl::RemoteTransactionControl(
|
||||
const FunctionalBlockContext& functional_block_context, TransactionInterface& transaction,
|
||||
SmartChargingInterface& smart_charging, MeterValuesInterface& meter_values, AvailabilityInterface& availability,
|
||||
FirmwareUpdateInterface& firmware_update, SecurityInterface& security, ReservationInterface* reservation,
|
||||
ProvisioningInterface& provisioning, UnlockConnectorCallback unlock_connector_callback,
|
||||
RemoteStartTransactionCallback remote_start_transaction_callback, StopTransactionCallback stop_transaction_callback,
|
||||
std::atomic<RegistrationStatusEnum>& registration_status, std::atomic<UploadLogStatusEnum>& upload_log_status,
|
||||
std::atomic<std::int32_t>& upload_log_status_id) :
|
||||
context(functional_block_context),
|
||||
transaction(transaction),
|
||||
smart_charging(smart_charging),
|
||||
meter_values(meter_values),
|
||||
availability(availability),
|
||||
firmware_update(firmware_update),
|
||||
security(security),
|
||||
reservation(reservation),
|
||||
provisioning(provisioning),
|
||||
unlock_connector_callback(unlock_connector_callback),
|
||||
remote_start_transaction_callback(remote_start_transaction_callback),
|
||||
stop_transaction_callback(stop_transaction_callback),
|
||||
registration_status(registration_status),
|
||||
upload_log_status(upload_log_status),
|
||||
upload_log_status_id(upload_log_status_id) {
|
||||
}
|
||||
|
||||
void RemoteTransactionControl::handle_message(const ocpp::EnhancedMessage<MessageType>& message) {
|
||||
const auto& json_message = message.message;
|
||||
|
||||
if (message.messageType == MessageType::RequestStartTransaction) {
|
||||
this->handle_remote_start_transaction_request(json_message);
|
||||
} else if (message.messageType == MessageType::RequestStopTransaction) {
|
||||
this->handle_remote_stop_transaction_request(json_message);
|
||||
} else if (message.messageType == MessageType::UnlockConnector) {
|
||||
this->handle_unlock_connector(json_message);
|
||||
} else if (message.messageType == MessageType::TriggerMessage) {
|
||||
this->handle_trigger_message(json_message);
|
||||
} else {
|
||||
throw MessageTypeNotImplementedException(message.messageType);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteTransactionControl::handle_unlock_connector(Call<UnlockConnectorRequest> call) {
|
||||
const UnlockConnectorRequest& msg = call.msg;
|
||||
UnlockConnectorResponse unlock_response;
|
||||
|
||||
EVSE evse;
|
||||
evse.id = msg.evseId;
|
||||
evse.connectorId = msg.connectorId;
|
||||
|
||||
if (this->context.evse_manager.is_valid_evse(evse)) {
|
||||
if (!this->context.evse_manager.get_evse(msg.evseId).has_active_transaction()) {
|
||||
unlock_response = unlock_connector_callback(msg.evseId, msg.connectorId);
|
||||
} else {
|
||||
unlock_response.status = UnlockStatusEnum::OngoingAuthorizedTransaction;
|
||||
}
|
||||
} else {
|
||||
unlock_response.status = UnlockStatusEnum::UnknownConnector;
|
||||
}
|
||||
|
||||
const ocpp::CallResult<UnlockConnectorResponse> call_result(unlock_response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
}
|
||||
|
||||
void RemoteTransactionControl::handle_remote_start_transaction_request(Call<RequestStartTransactionRequest> call) {
|
||||
auto msg = call.msg;
|
||||
|
||||
RequestStartTransactionResponse response;
|
||||
response.status = RequestStartStopStatusEnum::Rejected;
|
||||
|
||||
// Check if evse id is given.
|
||||
if (msg.evseId.has_value()) {
|
||||
const std::int32_t evse_id = msg.evseId.value();
|
||||
auto& evse = this->context.evse_manager.get_evse(evse_id);
|
||||
|
||||
// F01.FR.23: Faulted or unavailable. F01.FR.24 / F02.FR.25: Occupied. Send rejected.
|
||||
const bool available = is_evse_connector_available(evse);
|
||||
|
||||
// When available but there was a reservation for another token id or group token id:
|
||||
// send rejected (F01.FR.21 & F01.FR.22)
|
||||
const ocpp::ReservationCheckStatus reservation_status =
|
||||
is_evse_reserved_for_other(evse, call.msg.idToken, call.msg.groupIdToken);
|
||||
|
||||
const bool is_reserved = (reservation_status == ocpp::ReservationCheckStatus::ReservedForOtherToken);
|
||||
|
||||
if (!available or is_reserved) {
|
||||
// Note: we only support TxStartPoint PowerPathClosed, so we did not implement starting a
|
||||
// transaction first (and send TransactionEventRequest (eventType = Started). Only if a transaction
|
||||
// is authorized, a TransactionEventRequest will be sent. Because of this, F01.FR.13 is not
|
||||
// implemented as well, because in the current situation, this is an impossible state. (TODO: when
|
||||
// more TxStartPoints are supported, add implementation for F01.FR.13 as well).
|
||||
EVLOG_info << "Remote start transaction requested, but connector is not available or reserved.";
|
||||
} else {
|
||||
// F02: No active transaction yet and there is an available connector, so just send 'accepted'.
|
||||
response.status = RequestStartStopStatusEnum::Accepted;
|
||||
|
||||
this->transaction.set_remote_start_id_for_evse(evse_id, msg.idToken, msg.remoteStartId);
|
||||
}
|
||||
|
||||
// F01.FR.26 If a Charging Station with support for Smart Charging receives a
|
||||
// RequestStartTransactionRequest with an invalid ChargingProfile: The Charging Station SHALL respond
|
||||
// with RequestStartTransactionResponse with status = Rejected and optionally with reasonCode =
|
||||
// "InvalidProfile" or "InvalidSchedule".
|
||||
|
||||
const bool is_smart_charging_enabled =
|
||||
this->context.device_model.get_optional_value<bool>(ControllerComponentVariables::SmartChargingCtrlrEnabled)
|
||||
.value_or(false);
|
||||
|
||||
if (is_smart_charging_enabled) {
|
||||
if (msg.chargingProfile.has_value()) {
|
||||
|
||||
auto charging_profile = msg.chargingProfile.value();
|
||||
|
||||
if (charging_profile.chargingProfilePurpose == ChargingProfilePurposeEnum::TxProfile) {
|
||||
|
||||
const auto add_profile_response = this->smart_charging.conform_validate_and_add_profile(
|
||||
msg.chargingProfile.value(), evse_id, ChargingLimitSourceEnumStringType::CSO,
|
||||
AddChargingProfileSource::RequestStartTransactionRequest);
|
||||
if (add_profile_response.status == ChargingProfileStatusEnum::Accepted) {
|
||||
EVLOG_debug << "Accepting SetChargingProfileRequest";
|
||||
} else {
|
||||
std::string reason_code = "Unspecified";
|
||||
std::string additional_info = "unknown";
|
||||
if (add_profile_response.statusInfo.has_value()) {
|
||||
const auto status_info = add_profile_response.statusInfo.value();
|
||||
reason_code = status_info.reasonCode;
|
||||
if (status_info.additionalInfo.has_value()) {
|
||||
additional_info = status_info.additionalInfo.value();
|
||||
}
|
||||
}
|
||||
EVLOG_debug << "Rejecting SetChargingProfileRequest:\n reasonCode: " << reason_code
|
||||
<< "\nadditionalInfo: " << additional_info;
|
||||
response.statusInfo = add_profile_response.statusInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// F01.FR.07 RequestStartTransactionRequest does not contain an evseId. The Charging Station MAY reject the
|
||||
// RequestStartTransactionRequest. We do this for now (send rejected) (TODO: eventually support the charging
|
||||
// station to accept no evse id. If so: add token and remote start id for evse id 0 to
|
||||
// remote_start_id_per_evse, so we know for '0' it means 'all evse id's').
|
||||
EVLOG_warning << "No evse id given. Can not remote start transaction.";
|
||||
}
|
||||
|
||||
if (response.status == RequestStartStopStatusEnum::Accepted) {
|
||||
response.status = this->remote_start_transaction_callback(
|
||||
msg, this->context.device_model.get_value<bool>(ControllerComponentVariables::AuthorizeRemoteStart));
|
||||
}
|
||||
|
||||
const ocpp::CallResult<RequestStartTransactionResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
}
|
||||
|
||||
void RemoteTransactionControl::handle_remote_stop_transaction_request(Call<RequestStopTransactionRequest> call) {
|
||||
const auto msg = call.msg;
|
||||
|
||||
RequestStopTransactionResponse response;
|
||||
std::optional<std::int32_t> evseid = this->context.evse_manager.get_transaction_evseid(msg.transactionId);
|
||||
|
||||
if (evseid.has_value()) {
|
||||
// F03.FR.07: send 'accepted' if there was an ongoing transaction with the given transaction id
|
||||
response.status = this->stop_transaction_callback(evseid.value(), ReasonEnum::Remote);
|
||||
} else {
|
||||
// F03.FR.08: send 'rejected' if there was no ongoing transaction with the given transaction id
|
||||
response.status = RequestStartStopStatusEnum::Rejected;
|
||||
}
|
||||
|
||||
const ocpp::CallResult<RequestStopTransactionResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
}
|
||||
|
||||
void RemoteTransactionControl::handle_trigger_message(Call<TriggerMessageRequest> call) {
|
||||
const TriggerMessageRequest& msg = call.msg;
|
||||
TriggerMessageResponse response;
|
||||
EvseInterface* evse_ptr = nullptr;
|
||||
|
||||
response.status = TriggerMessageStatusEnum::Rejected;
|
||||
|
||||
if (msg.evse.has_value()) {
|
||||
const std::int32_t evse_id = msg.evse.value().id;
|
||||
evse_ptr = &this->context.evse_manager.get_evse(evse_id);
|
||||
}
|
||||
|
||||
// F06.FR.04: First send the TriggerMessageResponse before sending the requested message
|
||||
// so we split the functionality to be able to determine if we need to respond first.
|
||||
switch (msg.requestedMessage) {
|
||||
case MessageTriggerEnum::BootNotification:
|
||||
// F06.FR.17: Respond with rejected in case registration status is already accepted
|
||||
if (this->registration_status != RegistrationStatusEnum::Accepted) {
|
||||
response.status = TriggerMessageStatusEnum::Accepted;
|
||||
}
|
||||
break;
|
||||
|
||||
case MessageTriggerEnum::LogStatusNotification:
|
||||
case MessageTriggerEnum::Heartbeat:
|
||||
case MessageTriggerEnum::FirmwareStatusNotification:
|
||||
response.status = TriggerMessageStatusEnum::Accepted;
|
||||
break;
|
||||
|
||||
case MessageTriggerEnum::MeterValues:
|
||||
if (msg.evse.has_value()) {
|
||||
if (evse_ptr != nullptr and utils::meter_value_has_any_measurand(
|
||||
evse_ptr->get_meter_value(),
|
||||
utils::get_measurands_vec(this->context.device_model.get_value<std::string>(
|
||||
ControllerComponentVariables::AlignedDataMeasurands)))) {
|
||||
response.status = TriggerMessageStatusEnum::Accepted;
|
||||
}
|
||||
} else {
|
||||
const auto measurands = utils::get_measurands_vec(
|
||||
this->context.device_model.get_value<std::string>(ControllerComponentVariables::AlignedDataMeasurands));
|
||||
for (auto& evse : this->context.evse_manager) {
|
||||
if (utils::meter_value_has_any_measurand(evse.get_meter_value(), measurands)) {
|
||||
response.status = TriggerMessageStatusEnum::Accepted;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MessageTriggerEnum::TransactionEvent:
|
||||
if (msg.evse.has_value()) {
|
||||
if (evse_ptr != nullptr and evse_ptr->has_active_transaction()) {
|
||||
response.status = TriggerMessageStatusEnum::Accepted;
|
||||
}
|
||||
} else {
|
||||
for (const auto& evse : this->context.evse_manager) {
|
||||
if (evse.has_active_transaction()) {
|
||||
response.status = TriggerMessageStatusEnum::Accepted;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MessageTriggerEnum::StatusNotification:
|
||||
if (msg.evse.has_value() and msg.evse.value().connectorId.has_value()) {
|
||||
const std::int32_t connector_id = msg.evse.value().connectorId.value();
|
||||
if (evse_ptr != nullptr and connector_id > 0 and connector_id <= evse_ptr->get_number_of_connectors()) {
|
||||
response.status = TriggerMessageStatusEnum::Accepted;
|
||||
}
|
||||
} else {
|
||||
// F06.FR.12: Reject if evse or connectorId is ommited
|
||||
}
|
||||
break;
|
||||
|
||||
case MessageTriggerEnum::SignChargingStationCertificate:
|
||||
response.status = TriggerMessageStatusEnum::Accepted;
|
||||
break;
|
||||
case MessageTriggerEnum::SignV2GCertificate:
|
||||
if (this->context.device_model
|
||||
.get_optional_value<bool>(ControllerComponentVariables::V2GCertificateInstallationEnabled)
|
||||
.value_or(false)) {
|
||||
response.status = TriggerMessageStatusEnum::Accepted;
|
||||
} else {
|
||||
EVLOG_warning << "CSMS requested SignV2GCertificate but V2GCertificateInstallationEnabled is configured as "
|
||||
"false, so the TriggerMessage is rejected!";
|
||||
response.status = TriggerMessageStatusEnum::Rejected;
|
||||
}
|
||||
|
||||
break;
|
||||
// TODO:
|
||||
// PublishFirmwareStatusNotification
|
||||
// SignCombinedCertificate
|
||||
|
||||
case MessageTriggerEnum::PublishFirmwareStatusNotification:
|
||||
case MessageTriggerEnum::SignCombinedCertificate:
|
||||
case MessageTriggerEnum::SignV2G20Certificate:
|
||||
case MessageTriggerEnum::CustomTrigger:
|
||||
response.status = TriggerMessageStatusEnum::NotImplemented;
|
||||
break;
|
||||
}
|
||||
|
||||
const ocpp::CallResult<TriggerMessageResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
|
||||
if (response.status != TriggerMessageStatusEnum::Accepted) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto send_evse_message = [&](std::function<void(std::int32_t evse_id, EvseInterface & evse)> send) {
|
||||
if (evse_ptr != nullptr) {
|
||||
send(msg.evse.value().id, *evse_ptr);
|
||||
} else {
|
||||
for (auto& evse : this->context.evse_manager) {
|
||||
send(evse.get_id(), evse);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
switch (msg.requestedMessage) {
|
||||
case MessageTriggerEnum::BootNotification:
|
||||
this->provisioning.boot_notification_req(BootReasonEnum::Triggered);
|
||||
break;
|
||||
|
||||
case MessageTriggerEnum::MeterValues: {
|
||||
auto send_meter_value = [&](std::int32_t evse_id, EvseInterface& evse) {
|
||||
const auto meter_value =
|
||||
this->meter_values.get_latest_meter_value_filtered(evse.get_meter_value(), ReadingContextEnum::Trigger,
|
||||
ControllerComponentVariables::AlignedDataMeasurands);
|
||||
|
||||
if (!meter_value.sampledValue.empty()) {
|
||||
this->meter_values.meter_values_req(evse_id, std::vector<ocpp::v2::MeterValue>(1, meter_value), true);
|
||||
}
|
||||
};
|
||||
send_evse_message(send_meter_value);
|
||||
} break;
|
||||
|
||||
case MessageTriggerEnum::TransactionEvent: {
|
||||
auto send_transaction = [&](std::int32_t /*evse_id*/, EvseInterface& evse) {
|
||||
if (!evse.has_active_transaction()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto meter_value = this->meter_values.get_latest_meter_value_filtered(
|
||||
evse.get_meter_value(), ReadingContextEnum::Trigger,
|
||||
ControllerComponentVariables::SampledDataTxUpdatedMeasurands);
|
||||
|
||||
std::optional<std::vector<MeterValue>> opt_meter_value;
|
||||
if (!meter_value.sampledValue.empty()) {
|
||||
opt_meter_value.emplace(1, meter_value);
|
||||
}
|
||||
const auto& enhanced_transaction = evse.get_transaction();
|
||||
this->transaction.transaction_event_req(
|
||||
TransactionEventEnum::Updated, DateTime(), enhanced_transaction->get_transaction(),
|
||||
TriggerReasonEnum::Trigger, enhanced_transaction->get_seq_no(), std::nullopt, std::nullopt,
|
||||
std::nullopt, opt_meter_value, std::nullopt,
|
||||
!this->context.connectivity_manager.is_websocket_connected(), std::nullopt, true);
|
||||
};
|
||||
send_evse_message(send_transaction);
|
||||
} break;
|
||||
|
||||
case MessageTriggerEnum::StatusNotification:
|
||||
if (evse_ptr != nullptr and msg.evse.value().connectorId.has_value()) {
|
||||
this->context.component_state_manager.send_status_notification_single_connector(
|
||||
msg.evse.value().id, msg.evse.value().connectorId.value());
|
||||
}
|
||||
break;
|
||||
|
||||
case MessageTriggerEnum::Heartbeat:
|
||||
this->availability.heartbeat_req(true);
|
||||
break;
|
||||
|
||||
case MessageTriggerEnum::LogStatusNotification: {
|
||||
LogStatusNotificationRequest request;
|
||||
if (this->upload_log_status == UploadLogStatusEnum::Uploading) {
|
||||
request.status = UploadLogStatusEnum::Uploading;
|
||||
request.requestId = this->upload_log_status_id;
|
||||
} else {
|
||||
request.status = UploadLogStatusEnum::Idle;
|
||||
}
|
||||
|
||||
const ocpp::Call<LogStatusNotificationRequest> call(request);
|
||||
this->context.message_dispatcher.dispatch_call(call, true);
|
||||
} break;
|
||||
|
||||
case MessageTriggerEnum::FirmwareStatusNotification: {
|
||||
this->firmware_update.on_firmware_status_notification_request();
|
||||
} break;
|
||||
|
||||
case MessageTriggerEnum::SignChargingStationCertificate: {
|
||||
this->security.sign_certificate_req(ocpp::CertificateSigningUseEnum::ChargingStationCertificate, true);
|
||||
} break;
|
||||
|
||||
case MessageTriggerEnum::SignV2GCertificate: {
|
||||
this->security.sign_certificate_req(ocpp::CertificateSigningUseEnum::V2GCertificate, true);
|
||||
} break;
|
||||
|
||||
case MessageTriggerEnum::PublishFirmwareStatusNotification:
|
||||
case MessageTriggerEnum::SignCombinedCertificate:
|
||||
case MessageTriggerEnum::SignV2G20Certificate:
|
||||
case MessageTriggerEnum::CustomTrigger:
|
||||
EVLOG_error << "Sent a TriggerMessageResponse::Accepted while not following up with a message";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ReservationCheckStatus
|
||||
RemoteTransactionControl::is_evse_reserved_for_other(EvseInterface& evse, const IdToken& id_token,
|
||||
const std::optional<IdToken>& group_id_token) const {
|
||||
if (this->reservation != nullptr) {
|
||||
return this->reservation->is_evse_reserved_for_other(evse, id_token, group_id_token);
|
||||
}
|
||||
|
||||
return ReservationCheckStatus::NotReserved;
|
||||
}
|
||||
|
||||
} // namespace ocpp::v2
|
||||
@@ -0,0 +1,205 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <ocpp/v2/functional_blocks/reservation.hpp>
|
||||
|
||||
#include <ocpp/common/constants.hpp>
|
||||
#include <ocpp/v2/ctrlr_component_variables.hpp>
|
||||
#include <ocpp/v2/evse.hpp>
|
||||
#include <ocpp/v2/evse_manager.hpp>
|
||||
#include <ocpp/v2/functional_blocks/functional_block_context.hpp>
|
||||
|
||||
#include <ocpp/v2/messages/CancelReservation.hpp>
|
||||
#include <ocpp/v2/messages/ReservationStatusUpdate.hpp>
|
||||
#include <ocpp/v2/messages/ReserveNow.hpp>
|
||||
|
||||
namespace ocpp::v2 {
|
||||
Reservation::Reservation(const FunctionalBlockContext& functional_block_context,
|
||||
ReserveNowCallback reserve_now_callback, CancelReservationCallback cancel_reservation_callback,
|
||||
const IsReservationForTokenCallback is_reservation_for_token_callback) :
|
||||
context(functional_block_context),
|
||||
reserve_now_callback(reserve_now_callback),
|
||||
cancel_reservation_callback(cancel_reservation_callback),
|
||||
is_reservation_for_token_callback(is_reservation_for_token_callback) {
|
||||
}
|
||||
|
||||
void Reservation::handle_message(const ocpp::EnhancedMessage<MessageType>& message) {
|
||||
const auto& json_message = message.message;
|
||||
|
||||
if (message.messageType == MessageType::ReserveNow) {
|
||||
this->handle_reserve_now_request(json_message);
|
||||
} else if (message.messageType == MessageType::CancelReservation) {
|
||||
this->handle_cancel_reservation_callback(json_message);
|
||||
} else {
|
||||
throw MessageTypeNotImplementedException(message.messageType);
|
||||
}
|
||||
}
|
||||
|
||||
void Reservation::on_reservation_status(const std::int32_t reservation_id, const ReservationUpdateStatusEnum status) {
|
||||
ReservationStatusUpdateRequest req;
|
||||
req.reservationId = reservation_id;
|
||||
req.reservationUpdateStatus = status;
|
||||
|
||||
const ocpp::Call<ReservationStatusUpdateRequest> call(req);
|
||||
this->context.message_dispatcher.dispatch_call(call);
|
||||
}
|
||||
|
||||
ocpp::ReservationCheckStatus
|
||||
Reservation::is_evse_reserved_for_other(const EvseInterface& evse, const IdToken& id_token,
|
||||
const std::optional<IdToken>& group_id_token) const {
|
||||
const std::optional<CiString<255>> no = std::nullopt;
|
||||
const std::optional<CiString<255>> groupIdToken = group_id_token.has_value() ? group_id_token.value().idToken : no;
|
||||
|
||||
return this->is_reservation_for_token_callback(evse.get_id(), id_token.idToken, groupIdToken);
|
||||
}
|
||||
|
||||
void Reservation::on_reserved(const std::int32_t evse_id, const std::int32_t connector_id) {
|
||||
this->context.evse_manager.get_evse(evse_id).submit_event(connector_id, ConnectorEvent::Reserve);
|
||||
}
|
||||
|
||||
void Reservation::on_reservation_cleared(const std::int32_t evse_id, const std::int32_t connector_id) {
|
||||
this->context.evse_manager.get_evse(evse_id).submit_event(connector_id, ConnectorEvent::ReservationCleared);
|
||||
}
|
||||
|
||||
void Reservation::handle_reserve_now_request(Call<ReserveNowRequest> call) {
|
||||
ReserveNowResponse response;
|
||||
response.status = ReserveNowStatusEnum::Rejected;
|
||||
bool reservation_available = true;
|
||||
|
||||
std::string status_info;
|
||||
|
||||
if (this->reserve_now_callback == nullptr) {
|
||||
reservation_available = false;
|
||||
status_info = "Reservation is not implemented";
|
||||
} else if (!this->context.device_model
|
||||
.get_optional_value<bool>(ControllerComponentVariables::ReservationCtrlrAvailable)
|
||||
.value_or(false)) {
|
||||
status_info = "Reservation is not available";
|
||||
reservation_available = false;
|
||||
} else if (!this->context.device_model
|
||||
.get_optional_value<bool>(ControllerComponentVariables::ReservationCtrlrEnabled)
|
||||
.value_or(false)) {
|
||||
reservation_available = false;
|
||||
status_info = "Reservation is not enabled";
|
||||
}
|
||||
|
||||
if (!reservation_available) {
|
||||
// Reservation not available / implemented, return 'Rejected'.
|
||||
// H01.FR.01
|
||||
EVLOG_info << "Receiving a reservation request, but reservation is not enabled or implemented.";
|
||||
send_reserve_now_rejected_response(call.uniqueId, status_info);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we need a specific evse id during a reservation and if that is the case, if we recevied an evse id.
|
||||
const ReserveNowRequest request = call.msg;
|
||||
if (!request.evseId.has_value() &&
|
||||
!this->context.device_model
|
||||
.get_optional_value<bool>(ControllerComponentVariables::ReservationCtrlrNonEvseSpecific)
|
||||
.value_or(false)) {
|
||||
// H01.FR.19
|
||||
EVLOG_warning << "Trying to make a reservation, but no evse id was given while it should be sent in the "
|
||||
"request when NonEvseSpecific is disabled.";
|
||||
send_reserve_now_rejected_response(
|
||||
call.uniqueId,
|
||||
"No evse id was given while it should be sent in the request when NonEvseSpecific is disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
const std::optional<std::int32_t> evse_id = request.evseId;
|
||||
|
||||
if (evse_id.has_value()) {
|
||||
if (evse_id <= 0 || !this->context.evse_manager.does_evse_exist(evse_id.value())) {
|
||||
EVLOG_error << "Trying to make a reservation, but evse " << evse_id.value() << " is not a valid evse id.";
|
||||
send_reserve_now_rejected_response(call.uniqueId, "Evse id does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if there is a connector available for this evse id.
|
||||
if (!this->context.evse_manager.does_connector_exist(
|
||||
evse_id.value(), request.connectorType.value_or(ConnectorEnumStringType::Unknown))) {
|
||||
EVLOG_info << "Trying to make a reservation for connector type "
|
||||
<< request.connectorType.value_or(ConnectorEnumStringType::Unknown) << " for evse "
|
||||
<< evse_id.value() << ", but this connector type does not exist.";
|
||||
send_reserve_now_rejected_response(call.uniqueId, "Connector type does not exist");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// No evse id. Just search for all evse's if there is something available for reservation
|
||||
const std::uint64_t number_of_evses = this->context.evse_manager.get_number_of_evses();
|
||||
if (number_of_evses == 0) {
|
||||
send_reserve_now_rejected_response(call.uniqueId, "No evse's found in charging station");
|
||||
EVLOG_error << "Trying to make a reservation, but number of evse's is 0";
|
||||
return;
|
||||
}
|
||||
|
||||
bool connector_exists = false;
|
||||
for (std::uint64_t i = 1; i <= number_of_evses; i++) {
|
||||
if (i > std::numeric_limits<std::int32_t>::max()) {
|
||||
break;
|
||||
}
|
||||
if (this->context.evse_manager.does_connector_exist(
|
||||
clamp_to<std::int32_t>(i), request.connectorType.value_or(ConnectorEnumStringType::Unknown))) {
|
||||
connector_exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!connector_exists) {
|
||||
send_reserve_now_rejected_response(call.uniqueId, "Could not get status info from connector");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Connector exists and might or might not be available, but if the reservation id is already existing, reservation
|
||||
// should be overwritten.
|
||||
|
||||
// Call reserve now callback and wait for the response.
|
||||
const ReserveNowRequest reservation_request = call.msg;
|
||||
response.status = this->reserve_now_callback(reservation_request);
|
||||
|
||||
// Reply with the response from the callback.
|
||||
const ocpp::CallResult<ReserveNowResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
|
||||
if (response.status == ReserveNowStatusEnum::Accepted) {
|
||||
EVLOG_debug << "Reservation with id " << reservation_request.id << " for "
|
||||
<< (reservation_request.evseId.has_value()
|
||||
? " evse_id: " + std::to_string(reservation_request.evseId.value())
|
||||
: "")
|
||||
<< " is accepted";
|
||||
}
|
||||
}
|
||||
|
||||
void Reservation::handle_cancel_reservation_callback(Call<CancelReservationRequest> call) {
|
||||
|
||||
CancelReservationResponse response;
|
||||
if (this->cancel_reservation_callback == nullptr ||
|
||||
!this->context.device_model.get_optional_value<bool>(ControllerComponentVariables::ReservationCtrlrAvailable)
|
||||
.value_or(false) ||
|
||||
!this->context.device_model.get_optional_value<bool>(ControllerComponentVariables::ReservationCtrlrEnabled)
|
||||
.value_or(false)) {
|
||||
// Reservation not available / implemented, return 'Rejected'.
|
||||
// H01.FR.01
|
||||
EVLOG_info << "Receiving a cancel reservation request, but reservation is not implemented.";
|
||||
response.status = CancelReservationStatusEnum::Rejected;
|
||||
} else {
|
||||
response.status =
|
||||
(this->cancel_reservation_callback(call.msg.reservationId) ? CancelReservationStatusEnum::Accepted
|
||||
: CancelReservationStatusEnum::Rejected);
|
||||
}
|
||||
|
||||
const ocpp::CallResult<CancelReservationResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
}
|
||||
|
||||
void Reservation::send_reserve_now_rejected_response(const MessageId& unique_id, const std::string& status_info) {
|
||||
ReserveNowResponse response;
|
||||
response.status = ReserveNowStatusEnum::Rejected;
|
||||
response.statusInfo = StatusInfo();
|
||||
response.statusInfo->additionalInfo = status_info;
|
||||
const ocpp::CallResult<ReserveNowResponse> call_result(response, unique_id);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
}
|
||||
|
||||
} // namespace ocpp::v2
|
||||
@@ -0,0 +1,512 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <ocpp/v2/functional_blocks/security.hpp>
|
||||
|
||||
#include <ocpp/common/connectivity_manager.hpp>
|
||||
#include <ocpp/common/constants.hpp>
|
||||
#include <ocpp/common/ocpp_logging.hpp>
|
||||
#include <ocpp/v2/ctrlr_component_variables.hpp>
|
||||
#include <ocpp/v2/device_model.hpp>
|
||||
#include <ocpp/v2/functional_blocks/functional_block_context.hpp>
|
||||
#include <ocpp/v2/utils.hpp>
|
||||
|
||||
#include <ocpp/v2/messages/CertificateSigned.hpp>
|
||||
#include <ocpp/v2/messages/DeleteCertificate.hpp>
|
||||
#include <ocpp/v2/messages/Get15118EVCertificate.hpp>
|
||||
#include <ocpp/v2/messages/GetInstalledCertificateIds.hpp>
|
||||
#include <ocpp/v2/messages/InstallCertificate.hpp>
|
||||
#include <ocpp/v2/messages/SecurityEventNotification.hpp>
|
||||
#include <ocpp/v2/messages/SignCertificate.hpp>
|
||||
|
||||
constexpr std::int32_t minimum_cert_signing_wait_time_seconds = 10;
|
||||
|
||||
namespace ocpp::v2 {
|
||||
|
||||
Security::Security(const FunctionalBlockContext& functional_block_context, MessageLogging& logging,
|
||||
OcspUpdaterInterface& ocsp_updater, SecurityEventCallback security_event_callback) :
|
||||
context(functional_block_context),
|
||||
logging(logging),
|
||||
ocsp_updater(ocsp_updater),
|
||||
security_event_callback(security_event_callback),
|
||||
csr_attempt(1),
|
||||
client_certificate_expiration_check_timer([this]() { this->scheduled_check_client_certificate_expiration(); }),
|
||||
v2g_certificate_expiration_check_timer([this]() { this->scheduled_check_v2g_certificate_expiration(); }) {
|
||||
}
|
||||
|
||||
Security::~Security() {
|
||||
try {
|
||||
stop_certificate_signed_timer();
|
||||
stop_certificate_expiration_check_timers();
|
||||
} catch (...) {
|
||||
EVLOG_error << "Exception during dtor call of certificate timer stop";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Security::handle_message(const EnhancedMessage<MessageType>& message) {
|
||||
const auto& json_message = message.message;
|
||||
|
||||
if (message.messageType == MessageType::CertificateSigned) {
|
||||
this->handle_certificate_signed_req(json_message);
|
||||
} else if (message.messageType == MessageType::SignCertificateResponse) {
|
||||
this->handle_sign_certificate_response(json_message);
|
||||
} else if (message.messageType == MessageType::GetInstalledCertificateIds) {
|
||||
this->handle_get_installed_certificate_ids_req(json_message);
|
||||
} else if (message.messageType == MessageType::InstallCertificate) {
|
||||
this->handle_install_certificate_req(json_message);
|
||||
} else if (message.messageType == MessageType::DeleteCertificate) {
|
||||
this->handle_delete_certificate_req(json_message);
|
||||
} else {
|
||||
throw MessageTypeNotImplementedException(message.messageType);
|
||||
}
|
||||
}
|
||||
|
||||
void Security::stop_certificate_signed_timer() {
|
||||
this->certificate_signed_timer.stop();
|
||||
}
|
||||
|
||||
Get15118EVCertificateResponse
|
||||
Security::on_get_15118_ev_certificate_request(const Get15118EVCertificateRequest& request) {
|
||||
Get15118EVCertificateResponse response;
|
||||
|
||||
if (!this->context.device_model
|
||||
.get_optional_value<bool>(ControllerComponentVariables::ContractCertificateInstallationEnabled)
|
||||
.value_or(false)) {
|
||||
EVLOG_warning << "Can not fulfill Get15118EVCertificateRequest because ContractCertificateInstallationEnabled "
|
||||
"is configured as false!";
|
||||
response.status = Iso15118EVCertificateStatusEnum::Failed;
|
||||
return response;
|
||||
}
|
||||
|
||||
EVLOG_debug << "Received Get15118EVCertificateRequest " << request;
|
||||
auto future_res =
|
||||
this->context.message_dispatcher.dispatch_call_async(ocpp::Call<Get15118EVCertificateRequest>(request));
|
||||
|
||||
if (future_res.wait_for(DEFAULT_WAIT_FOR_FUTURE_TIMEOUT) == std::future_status::timeout) {
|
||||
EVLOG_warning << "Waiting for Get15118EVCertificateRequest.conf future timed out!";
|
||||
response.status = Iso15118EVCertificateStatusEnum::Failed;
|
||||
return response;
|
||||
}
|
||||
|
||||
const auto response_message = future_res.get();
|
||||
EVLOG_debug << "Received Get15118EVCertificateResponse " << response_message.message;
|
||||
if (response_message.messageType != MessageType::Get15118EVCertificateResponse) {
|
||||
response.status = Iso15118EVCertificateStatusEnum::Failed;
|
||||
return response;
|
||||
}
|
||||
|
||||
try {
|
||||
const ocpp::CallResult<Get15118EVCertificateResponse> call_result = response_message.message;
|
||||
return call_result.msg;
|
||||
} catch (const EnumConversionException& e) {
|
||||
EVLOG_error << "EnumConversionException during handling of Get15118EVCertificateResponse: " << e.what();
|
||||
auto call_error = CallError(response_message.uniqueId, "FormationViolation", e.what(), json({}));
|
||||
this->context.message_dispatcher.dispatch_call_error(call_error);
|
||||
return response;
|
||||
} catch (const json::exception& e) {
|
||||
EVLOG_error << "json::exception during handling of Get15118EVCertificateResponse: " << e.what();
|
||||
auto call_error = CallError(response_message.uniqueId, "FormationViolation", e.what(), json({}));
|
||||
this->context.message_dispatcher.dispatch_call_error(call_error);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
void Security::init_certificate_expiration_check_timers() {
|
||||
// Timers started with initial delays; callback functions are supposed to re-schedule on their own!
|
||||
|
||||
// Client Certificate only needs to be checked for SecurityProfile 3; if SecurityProfile changes, timers get
|
||||
// re-initialized at reconnect
|
||||
if (this->context.device_model.get_value<int>(ControllerComponentVariables::SecurityProfile) == 3) {
|
||||
this->client_certificate_expiration_check_timer.timeout(std::chrono::seconds(
|
||||
this->context.device_model
|
||||
.get_optional_value<int>(ControllerComponentVariables::ClientCertificateExpireCheckInitialDelaySeconds)
|
||||
.value_or(60)));
|
||||
}
|
||||
|
||||
// V2G Certificate timer is started in any case; condition (V2GCertificateInstallationEnabled) is validated in
|
||||
// callback (ChargePoint::scheduled_check_v2g_certificate_expiration)
|
||||
this->v2g_certificate_expiration_check_timer.timeout(std::chrono::seconds(
|
||||
this->context.device_model
|
||||
.get_optional_value<int>(ControllerComponentVariables::V2GCertificateExpireCheckInitialDelaySeconds)
|
||||
.value_or(60)));
|
||||
}
|
||||
|
||||
void Security::stop_certificate_expiration_check_timers() {
|
||||
this->client_certificate_expiration_check_timer.stop();
|
||||
this->v2g_certificate_expiration_check_timer.stop();
|
||||
}
|
||||
|
||||
void Security::security_event_notification_req(const CiString<50>& event_type,
|
||||
const std::optional<CiString<255>>& tech_info,
|
||||
const bool triggered_internally, const bool critical,
|
||||
const std::optional<DateTime>& timestamp) {
|
||||
EVLOG_debug << "Sending SecurityEventNotification";
|
||||
SecurityEventNotificationRequest req;
|
||||
|
||||
req.type = event_type;
|
||||
if (timestamp.has_value()) {
|
||||
req.timestamp = timestamp.value();
|
||||
} else {
|
||||
req.timestamp = DateTime();
|
||||
}
|
||||
req.techInfo = tech_info;
|
||||
this->logging.security(json(req).dump());
|
||||
if (critical) {
|
||||
const ocpp::Call<SecurityEventNotificationRequest> call(req);
|
||||
this->context.message_dispatcher.dispatch_call(call);
|
||||
}
|
||||
if (triggered_internally and this->security_event_callback != nullptr) {
|
||||
this->security_event_callback(event_type, tech_info);
|
||||
}
|
||||
}
|
||||
|
||||
void Security::sign_certificate_req(const ocpp::CertificateSigningUseEnum& certificate_signing_use,
|
||||
const bool initiated_by_trigger_message) {
|
||||
if (this->awaited_certificate_signing_use_enum.has_value()) {
|
||||
EVLOG_warning
|
||||
<< "Not sending new SignCertificate.req because still waiting for CertificateSigned.req from CSMS";
|
||||
return;
|
||||
}
|
||||
|
||||
SignCertificateRequest req;
|
||||
|
||||
std::optional<std::string> common;
|
||||
std::optional<std::string> country;
|
||||
std::optional<std::string> organization;
|
||||
bool should_use_tpm = false;
|
||||
|
||||
if (certificate_signing_use == ocpp::CertificateSigningUseEnum::ChargingStationCertificate) {
|
||||
req.certificateType = ocpp::v2::CertificateSigningUseEnum::ChargingStationCertificate;
|
||||
common = this->context.device_model.get_optional_value<std::string>(
|
||||
ControllerComponentVariables::ChargeBoxSerialNumber);
|
||||
organization =
|
||||
this->context.device_model.get_optional_value<std::string>(ControllerComponentVariables::OrganizationName);
|
||||
country = this->context.device_model.get_optional_value<std::string>(
|
||||
ControllerComponentVariables::ISO15118CtrlrCountryName);
|
||||
should_use_tpm =
|
||||
this->context.device_model.get_optional_value<bool>(ControllerComponentVariables::UseTPM).value_or(false);
|
||||
} else {
|
||||
req.certificateType = ocpp::v2::CertificateSigningUseEnum::V2GCertificate;
|
||||
common = this->context.device_model.get_optional_value<std::string>(
|
||||
ControllerComponentVariables::ISO15118CtrlrSeccId);
|
||||
organization = this->context.device_model.get_optional_value<std::string>(
|
||||
ControllerComponentVariables::ISO15118CtrlrOrganizationName);
|
||||
country = this->context.device_model.get_optional_value<std::string>(
|
||||
ControllerComponentVariables::ISO15118CtrlrCountryName);
|
||||
should_use_tpm =
|
||||
this->context.device_model.get_optional_value<bool>(ControllerComponentVariables::UseTPMSeccLeafCertificate)
|
||||
.value_or(false);
|
||||
}
|
||||
|
||||
if (!common.has_value()) {
|
||||
EVLOG_warning << "Missing configuration of commonName to generate CSR";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!country.has_value()) {
|
||||
EVLOG_warning << "Missing configuration country to generate CSR";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!organization.has_value()) {
|
||||
EVLOG_warning << "Missing configuration of organizationName to generate CSR";
|
||||
return;
|
||||
}
|
||||
|
||||
const auto result = this->context.evse_security.generate_certificate_signing_request(
|
||||
certificate_signing_use, country.value(), organization.value(), common.value(), should_use_tpm);
|
||||
|
||||
if (result.status != GetCertificateSignRequestStatus::Accepted or !result.csr.has_value()) {
|
||||
EVLOG_error << "CSR generation was unsuccessful for sign request: "
|
||||
<< ocpp::conversions::certificate_signing_use_enum_to_string(certificate_signing_use);
|
||||
|
||||
std::string gen_error = "Sign certificate req failed due to:" +
|
||||
ocpp::conversions::generate_certificate_signing_request_status_to_string(result.status);
|
||||
this->security_event_notification_req(ocpp::security_events::CSRGENERATIONFAILED,
|
||||
std::optional<CiString<255>>(gen_error), true, true);
|
||||
return;
|
||||
}
|
||||
|
||||
req.csr = result.csr.value();
|
||||
|
||||
this->awaited_certificate_signing_use_enum = certificate_signing_use;
|
||||
|
||||
const ocpp::Call<SignCertificateRequest> call(req);
|
||||
this->context.message_dispatcher.dispatch_call(call, initiated_by_trigger_message);
|
||||
}
|
||||
|
||||
void Security::handle_certificate_signed_req(Call<CertificateSignedRequest> call) {
|
||||
// reset these parameters
|
||||
this->csr_attempt = 1;
|
||||
this->awaited_certificate_signing_use_enum = std::nullopt;
|
||||
this->certificate_signed_timer.stop();
|
||||
|
||||
CertificateSignedResponse response;
|
||||
response.status = CertificateSignedStatusEnum::Rejected;
|
||||
|
||||
const auto certificate_chain = call.msg.certificateChain.get();
|
||||
ocpp::CertificateSigningUseEnum cert_signing_use; // NOLINT(cppcoreguidelines-init-variables): initialized below
|
||||
|
||||
if (!call.msg.certificateType.has_value() or
|
||||
call.msg.certificateType.value() == CertificateSigningUseEnum::ChargingStationCertificate) {
|
||||
cert_signing_use = ocpp::CertificateSigningUseEnum::ChargingStationCertificate;
|
||||
} else {
|
||||
cert_signing_use = ocpp::CertificateSigningUseEnum::V2GCertificate;
|
||||
}
|
||||
|
||||
const auto result = this->context.evse_security.update_leaf_certificate(certificate_chain, cert_signing_use);
|
||||
|
||||
if (result == ocpp::InstallCertificateResult::Accepted) {
|
||||
response.status = CertificateSignedStatusEnum::Accepted;
|
||||
// For V2G certificates, also trigger an OCSP cache update
|
||||
if (cert_signing_use == ocpp::CertificateSigningUseEnum::V2GCertificate) {
|
||||
this->ocsp_updater.trigger_ocsp_cache_update();
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger a symlink update for V2G certificates
|
||||
if ((cert_signing_use == ocpp::CertificateSigningUseEnum::V2GCertificate) and
|
||||
this->context.device_model.get_optional_value<bool>(ControllerComponentVariables::UpdateCertificateSymlinks)
|
||||
.value_or(false)) {
|
||||
this->context.evse_security.update_certificate_links(cert_signing_use);
|
||||
}
|
||||
|
||||
const ocpp::CallResult<CertificateSignedResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
|
||||
if (result != ocpp::InstallCertificateResult::Accepted) {
|
||||
this->security_event_notification_req("InvalidChargingStationCertificate",
|
||||
ocpp::conversions::install_certificate_result_to_string(result), true,
|
||||
true);
|
||||
}
|
||||
|
||||
// reconnect with new certificate if valid and security profile is 3
|
||||
if (response.status == CertificateSignedStatusEnum::Accepted and
|
||||
cert_signing_use == ocpp::CertificateSigningUseEnum::ChargingStationCertificate and
|
||||
this->context.device_model.get_value<int>(ControllerComponentVariables::SecurityProfile) == 3) {
|
||||
this->context.connectivity_manager.on_charging_station_certificate_changed();
|
||||
|
||||
const auto& security_event = ocpp::security_events::RECONFIGURATIONOFSECURITYPARAMETERS;
|
||||
const std::string tech_info = "Changed charging station certificate";
|
||||
this->security_event_notification_req(CiString<50>(security_event), CiString<255>(tech_info), true,
|
||||
utils::is_critical(security_event));
|
||||
}
|
||||
}
|
||||
|
||||
void Security::handle_sign_certificate_response(CallResult<SignCertificateResponse> call_result) {
|
||||
if (!this->awaited_certificate_signing_use_enum.has_value()) {
|
||||
EVLOG_warning
|
||||
<< "Received SignCertificate.conf while not awaiting a CertificateSigned.req . This should not happen.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (call_result.msg.status == GenericStatusEnum::Accepted) {
|
||||
// set timer waiting for certificate signed
|
||||
const auto cert_signing_wait_minimum =
|
||||
this->context.device_model.get_optional_value<int>(ControllerComponentVariables::CertSigningWaitMinimum);
|
||||
const auto cert_signing_repeat_times =
|
||||
this->context.device_model.get_optional_value<int>(ControllerComponentVariables::CertSigningRepeatTimes);
|
||||
|
||||
if (!cert_signing_wait_minimum.has_value()) {
|
||||
EVLOG_warning << "No CertSigningWaitMinimum is configured, will not attempt to retry SignCertificate.req "
|
||||
"in case CSMS doesn't send CertificateSigned.req";
|
||||
return;
|
||||
}
|
||||
if (!cert_signing_repeat_times.has_value()) {
|
||||
EVLOG_warning << "No CertSigningRepeatTimes is configured, will not attempt to retry SignCertificate.req "
|
||||
"in case CSMS doesn't send CertificateSigned.req";
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->csr_attempt > cert_signing_repeat_times.value()) {
|
||||
this->csr_attempt = 1;
|
||||
this->certificate_signed_timer.stop();
|
||||
this->awaited_certificate_signing_use_enum = std::nullopt;
|
||||
return;
|
||||
}
|
||||
const int retry_backoff_seconds = clamp_to<int>(
|
||||
static_cast<double>(std::max(minimum_cert_signing_wait_time_seconds, cert_signing_wait_minimum.value())) *
|
||||
std::pow(2, std::max(0, this->csr_attempt - 1))); // first wait = CertSigningWaitMinimum * 2^0
|
||||
this->certificate_signed_timer.timeout(
|
||||
[this]() {
|
||||
EVLOG_info << "Did not receive CertificateSigned.req in time. Will retry with SignCertificate.req";
|
||||
this->csr_attempt++;
|
||||
const auto current_awaited_certificate_signing_use_enum =
|
||||
this->awaited_certificate_signing_use_enum.value();
|
||||
this->awaited_certificate_signing_use_enum.reset();
|
||||
this->sign_certificate_req(current_awaited_certificate_signing_use_enum);
|
||||
},
|
||||
std::chrono::seconds(retry_backoff_seconds));
|
||||
} else {
|
||||
this->awaited_certificate_signing_use_enum = std::nullopt;
|
||||
this->csr_attempt = 1;
|
||||
EVLOG_warning << "SignCertificate.req has not been accepted by CSMS";
|
||||
}
|
||||
}
|
||||
|
||||
void Security::handle_get_installed_certificate_ids_req(Call<GetInstalledCertificateIdsRequest> call) {
|
||||
EVLOG_debug << "Received GetInstalledCertificateIdsRequest: " << call.msg << "\nwith messageId: " << call.uniqueId;
|
||||
GetInstalledCertificateIdsResponse response;
|
||||
|
||||
const auto msg = call.msg;
|
||||
|
||||
// prepare argument for getRootCertificate
|
||||
std::vector<ocpp::CertificateType> certificate_types;
|
||||
if (msg.certificateType.has_value()) {
|
||||
certificate_types = ocpp::evse_security_conversions::from_ocpp_v2(msg.certificateType.value());
|
||||
} else {
|
||||
certificate_types.push_back(CertificateType::CSMSRootCertificate);
|
||||
certificate_types.push_back(CertificateType::MFRootCertificate);
|
||||
certificate_types.push_back(CertificateType::MORootCertificate);
|
||||
certificate_types.push_back(CertificateType::V2GCertificateChain);
|
||||
certificate_types.push_back(CertificateType::V2GRootCertificate);
|
||||
}
|
||||
|
||||
// retrieve installed certificates
|
||||
const auto certificate_hash_data_chains = this->context.evse_security.get_installed_certificates(certificate_types);
|
||||
|
||||
// convert the common type back to the v2 type(s) for the response
|
||||
std::vector<CertificateHashDataChain> certificate_hash_data_chain_v2;
|
||||
certificate_hash_data_chain_v2.reserve(certificate_hash_data_chains.size());
|
||||
for (const auto& certificate_hash_data_chain_entry : certificate_hash_data_chains) {
|
||||
certificate_hash_data_chain_v2.push_back(
|
||||
ocpp::evse_security_conversions::to_ocpp_v2(certificate_hash_data_chain_entry));
|
||||
}
|
||||
|
||||
if (certificate_hash_data_chain_v2.empty()) {
|
||||
response.status = GetInstalledCertificateStatusEnum::NotFound;
|
||||
} else {
|
||||
response.certificateHashDataChain = certificate_hash_data_chain_v2;
|
||||
response.status = GetInstalledCertificateStatusEnum::Accepted;
|
||||
}
|
||||
|
||||
const ocpp::CallResult<GetInstalledCertificateIdsResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
}
|
||||
|
||||
void Security::handle_install_certificate_req(Call<InstallCertificateRequest> call) {
|
||||
EVLOG_debug << "Received InstallCertificateRequest: " << call.msg << "\nwith messageId: " << call.uniqueId;
|
||||
|
||||
const auto msg = call.msg;
|
||||
InstallCertificateResponse response;
|
||||
|
||||
if (!should_allow_certificate_install(msg.certificateType)) {
|
||||
response.status = InstallCertificateStatusEnum::Rejected;
|
||||
response.statusInfo = StatusInfo();
|
||||
response.statusInfo->reasonCode = "UnsecureConnection";
|
||||
response.statusInfo->additionalInfo = "CertificateInstallationNotAllowedWithUnsecureConnection";
|
||||
} else {
|
||||
const auto result = this->context.evse_security.install_ca_certificate(
|
||||
msg.certificate.get(), ocpp::evse_security_conversions::from_ocpp_v2(msg.certificateType));
|
||||
response.status = ocpp::evse_security_conversions::to_ocpp_v2(result);
|
||||
if (response.status == InstallCertificateStatusEnum::Accepted) {
|
||||
const auto& security_event = ocpp::security_events::RECONFIGURATIONOFSECURITYPARAMETERS;
|
||||
const std::string tech_info =
|
||||
"Installed certificate: " + conversions::install_certificate_use_enum_to_string(msg.certificateType);
|
||||
this->security_event_notification_req(CiString<50>(security_event), CiString<255>(tech_info), true,
|
||||
utils::is_critical(security_event));
|
||||
}
|
||||
}
|
||||
const ocpp::CallResult<InstallCertificateResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
}
|
||||
|
||||
void Security::handle_delete_certificate_req(Call<DeleteCertificateRequest> call) {
|
||||
EVLOG_debug << "Received DeleteCertificateRequest: " << call.msg << "\nwith messageId: " << call.uniqueId;
|
||||
|
||||
const auto msg = call.msg;
|
||||
DeleteCertificateResponse response;
|
||||
|
||||
const auto certificate_hash_data = ocpp::evse_security_conversions::from_ocpp_v2(msg.certificateHashData);
|
||||
|
||||
const auto status = this->context.evse_security.delete_certificate(certificate_hash_data);
|
||||
|
||||
response.status = ocpp::evse_security_conversions::to_ocpp_v2(status);
|
||||
|
||||
if (response.status == DeleteCertificateStatusEnum::Accepted) {
|
||||
const auto& security_event = ocpp::security_events::RECONFIGURATIONOFSECURITYPARAMETERS;
|
||||
const std::string tech_info =
|
||||
"Deleted certificate with serial number: " + msg.certificateHashData.serialNumber.get();
|
||||
this->security_event_notification_req(CiString<50>(security_event), CiString<255>(tech_info), true,
|
||||
utils::is_critical(security_event));
|
||||
}
|
||||
|
||||
const ocpp::CallResult<DeleteCertificateResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
}
|
||||
|
||||
bool Security::should_allow_certificate_install(InstallCertificateUseEnum cert_type) const {
|
||||
const int security_profile =
|
||||
this->context.device_model.get_value<int>(ControllerComponentVariables::SecurityProfile);
|
||||
|
||||
if (security_profile > 1) {
|
||||
return true;
|
||||
}
|
||||
switch (cert_type) {
|
||||
case InstallCertificateUseEnum::CSMSRootCertificate:
|
||||
return this->context.device_model
|
||||
.get_optional_value<bool>(ControllerComponentVariables::AllowCSMSRootCertInstallWithUnsecureConnection)
|
||||
.value_or(true);
|
||||
|
||||
case InstallCertificateUseEnum::ManufacturerRootCertificate:
|
||||
return this->context.device_model
|
||||
.get_optional_value<bool>(ControllerComponentVariables::AllowMFRootCertInstallWithUnsecureConnection)
|
||||
.value_or(true);
|
||||
case InstallCertificateUseEnum::MORootCertificate:
|
||||
case InstallCertificateUseEnum::V2GRootCertificate:
|
||||
return true;
|
||||
case InstallCertificateUseEnum::OEMRootCertificate:
|
||||
// FIXME: Implement OEMRootCertificate
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Security::scheduled_check_client_certificate_expiration() {
|
||||
EVLOG_info << "Checking if CSMS client certificate has expired";
|
||||
const int expiry_days_count = this->context.evse_security.get_leaf_expiry_days_count(
|
||||
ocpp::CertificateSigningUseEnum::ChargingStationCertificate);
|
||||
if (expiry_days_count < 30) {
|
||||
EVLOG_info << "CSMS client certificate is invalid in " << expiry_days_count
|
||||
<< " days. Requesting new certificate with certificate signing request";
|
||||
this->sign_certificate_req(ocpp::CertificateSigningUseEnum::ChargingStationCertificate);
|
||||
} else {
|
||||
EVLOG_info << "CSMS client certificate is still valid.";
|
||||
}
|
||||
|
||||
this->client_certificate_expiration_check_timer.interval(std::chrono::seconds(
|
||||
this->context.device_model
|
||||
.get_optional_value<int>(ControllerComponentVariables::ClientCertificateExpireCheckIntervalSeconds)
|
||||
.value_or(12 * 60 * 60)));
|
||||
}
|
||||
|
||||
void Security::scheduled_check_v2g_certificate_expiration() {
|
||||
if (this->context.device_model
|
||||
.get_optional_value<bool>(ControllerComponentVariables::V2GCertificateInstallationEnabled)
|
||||
.value_or(false)) {
|
||||
EVLOG_info << "Checking if V2GCertificate has expired";
|
||||
const int expiry_days_count =
|
||||
this->context.evse_security.get_leaf_expiry_days_count(ocpp::CertificateSigningUseEnum::V2GCertificate);
|
||||
if (expiry_days_count < 30) {
|
||||
EVLOG_info << "V2GCertificate is invalid in " << expiry_days_count
|
||||
<< " days. Requesting new certificate with certificate signing request";
|
||||
this->sign_certificate_req(ocpp::CertificateSigningUseEnum::V2GCertificate);
|
||||
} else {
|
||||
EVLOG_info << "V2GCertificate is still valid.";
|
||||
}
|
||||
} else {
|
||||
if (this->context.device_model.get_optional_value<bool>(ControllerComponentVariables::PnCEnabled)
|
||||
.value_or(false)) {
|
||||
EVLOG_warning << "PnC is enabled but V2G certificate installation is not, so no certificate expiration "
|
||||
"check is performed.";
|
||||
}
|
||||
}
|
||||
|
||||
this->v2g_certificate_expiration_check_timer.interval(std::chrono::seconds(
|
||||
this->context.device_model
|
||||
.get_optional_value<int>(ControllerComponentVariables::V2GCertificateExpireCheckIntervalSeconds)
|
||||
.value_or(12 * 60 * 60)));
|
||||
}
|
||||
|
||||
} // namespace ocpp::v2
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,446 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <ocpp/v2/functional_blocks/tariff_and_cost.hpp>
|
||||
|
||||
#include <ocpp/v2/ctrlr_component_variables.hpp>
|
||||
#include <ocpp/v2/device_model.hpp>
|
||||
#include <ocpp/v2/evse_manager.hpp>
|
||||
#include <ocpp/v2/functional_blocks/functional_block_context.hpp>
|
||||
#include <ocpp/v2/functional_blocks/meter_values.hpp>
|
||||
|
||||
#include <ocpp/v2/messages/CostUpdated.hpp>
|
||||
|
||||
const auto DEFAULT_PRICE_NUMBER_OF_DECIMALS = 3;
|
||||
|
||||
namespace ocpp::v2 {
|
||||
TariffAndCost::TariffAndCost(const FunctionalBlockContext& functional_block_context, MeterValuesInterface& meter_values,
|
||||
std::optional<TariffMessageCallback>& tariff_message_callback,
|
||||
std::optional<SetRunningCostCallback>& set_running_cost_callback,
|
||||
std::optional<DefaultPriceCallback>& default_price_callback,
|
||||
boost::asio::io_context& io_context) :
|
||||
context(functional_block_context),
|
||||
meter_values(meter_values),
|
||||
tariff_message_callback(tariff_message_callback),
|
||||
set_running_cost_callback(set_running_cost_callback),
|
||||
default_price_callback(default_price_callback),
|
||||
io_context(io_context) {
|
||||
}
|
||||
|
||||
void TariffAndCost::handle_message(const ocpp::EnhancedMessage<MessageType>& message) {
|
||||
if (message.messageType == MessageType::CostUpdated) {
|
||||
const auto& json_message = message.message;
|
||||
this->handle_costupdated_req(json_message);
|
||||
} else {
|
||||
throw MessageTypeNotImplementedException(message.messageType);
|
||||
}
|
||||
}
|
||||
|
||||
void TariffAndCost::handle_cost_and_tariff(const TransactionEventResponse& response,
|
||||
const TransactionEventRequest& original_message,
|
||||
const json& original_transaction_event_response) {
|
||||
const bool tariff_enabled = this->is_tariff_enabled();
|
||||
|
||||
const bool cost_enabled = this->is_cost_enabled();
|
||||
|
||||
std::vector<DisplayMessageContent> cost_messages;
|
||||
|
||||
// Check if there is a tariff message and if 'Tariff' is available and enabled
|
||||
if (response.updatedPersonalMessage.has_value() and tariff_enabled) {
|
||||
const MessageContent personal_message = response.updatedPersonalMessage.value();
|
||||
const DisplayMessageContent message = message_content_to_display_message_content(personal_message);
|
||||
cost_messages.push_back(message);
|
||||
|
||||
// Always publish the tariff message so the tariff_message variable on the session_cost interface is updated.
|
||||
// When cost is also enabled, the message will additionally be included in the running cost callback below
|
||||
// (if totalCost is present in the same response).
|
||||
if (this->tariff_message_callback.has_value() and this->tariff_message_callback != nullptr) {
|
||||
TariffMessage tariff_message;
|
||||
tariff_message.message = cost_messages;
|
||||
tariff_message.ocpp_transaction_id = original_message.transactionInfo.transactionId;
|
||||
tariff_message.identifier_id = original_message.transactionInfo.transactionId;
|
||||
tariff_message.identifier_type = IdentifierType::TransactionId;
|
||||
this->tariff_message_callback.value()({tariff_message});
|
||||
}
|
||||
}
|
||||
|
||||
// Check if cost is available and enabled, and if there is a totalcost message.
|
||||
if (cost_enabled and response.totalCost.has_value() and this->set_running_cost_callback.has_value()) {
|
||||
RunningCost running_cost;
|
||||
// We use the original string and convert it to a double ourselves, as the nlohmann library converts it to a
|
||||
// float first and then multiply by 10^5 for example (5 decimals) will give some rounding errors. With a initial
|
||||
// double instead of float, we have (a bit) more accuracy.
|
||||
if (original_transaction_event_response.contains("totalCost")) {
|
||||
const std::string total_cost = original_transaction_event_response.at("totalCost").dump();
|
||||
running_cost.cost = stod(total_cost);
|
||||
} else {
|
||||
running_cost.cost = static_cast<double>(response.totalCost.value());
|
||||
}
|
||||
|
||||
if (original_message.eventType == TransactionEventEnum::Ended) {
|
||||
running_cost.state = RunningCostState::Finished;
|
||||
} else {
|
||||
running_cost.state = RunningCostState::Charging;
|
||||
}
|
||||
|
||||
running_cost.transaction_id = original_message.transactionInfo.transactionId;
|
||||
|
||||
if (original_message.meterValue.has_value()) {
|
||||
const auto& meter_value = original_message.meterValue.value();
|
||||
std::optional<float> max_meter_value;
|
||||
for (const MeterValue& mv : meter_value) {
|
||||
auto it = std::find_if(mv.sampledValue.begin(), mv.sampledValue.end(), [](const SampledValue& value) {
|
||||
return value.measurand == MeasurandEnum::Energy_Active_Import_Register and !value.phase.has_value();
|
||||
});
|
||||
if (it != mv.sampledValue.end()) {
|
||||
// Found a sampled metervalue we are searching for!
|
||||
if (!max_meter_value.has_value() or max_meter_value.value() < it->value) {
|
||||
max_meter_value = it->value;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (max_meter_value.has_value()) {
|
||||
running_cost.meter_value = static_cast<std::int32_t>(max_meter_value.value());
|
||||
}
|
||||
}
|
||||
|
||||
running_cost.timestamp = original_message.timestamp;
|
||||
|
||||
if (response.customData.has_value()) {
|
||||
// With the current spec, it is not possible to send a qr code as well as a multi language personal
|
||||
// message, because there can only be one vendor id in custom data. If you not check the vendor id, it
|
||||
// is just possible for a csms to include them both.
|
||||
const json& custom_data = response.customData.value();
|
||||
if (/*custom_data.contains("vendorId") and
|
||||
(custom_data.at("vendorId").get<std::string>() == "org.openchargealliance.org.qrcode") and */
|
||||
custom_data.contains("qrCodeText") and
|
||||
this->context.device_model
|
||||
.get_optional_value<bool>(ControllerComponentVariables::DisplayMessageQRCodeDisplayCapable)
|
||||
.value_or(false)) {
|
||||
running_cost.qr_code_text = custom_data.at("qrCodeText");
|
||||
}
|
||||
|
||||
// Add multilanguage messages
|
||||
if (custom_data.contains("updatedPersonalMessageExtra") and is_multilanguage_enabled()) {
|
||||
// Get supported languages, which is stored in the values list of "Language" of
|
||||
// "DisplayMessageCtrlr"
|
||||
std::optional<VariableMetaData> metadata;
|
||||
if (ControllerComponentVariables::DisplayMessageLanguage.variable.has_value()) {
|
||||
metadata = this->context.device_model.get_variable_meta_data(
|
||||
ControllerComponentVariables::DisplayMessageLanguage.component,
|
||||
ControllerComponentVariables::DisplayMessageLanguage.variable.value());
|
||||
}
|
||||
|
||||
std::vector<std::string> supported_languages;
|
||||
|
||||
if (metadata.has_value() and metadata.value().characteristics.valuesList.has_value()) {
|
||||
supported_languages =
|
||||
ocpp::split_string(metadata.value().characteristics.valuesList.value(), ',', true);
|
||||
} else {
|
||||
EVLOG_error << "DisplayMessageCtrlr variable Language should have a valuesList with supported "
|
||||
"languages";
|
||||
}
|
||||
|
||||
for (const auto& m : custom_data.at("updatedPersonalMessageExtra").items()) {
|
||||
DisplayMessageContent c = message_content_to_display_message_content(m.value());
|
||||
if (!c.language.has_value()) {
|
||||
EVLOG_warning
|
||||
<< "updated personal message extra sent but language unknown: Can not show message.";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (supported_languages.empty()) {
|
||||
EVLOG_warning << "Can not show personal message as the supported languages are unknown "
|
||||
"(please set the `valuesList` of `DisplayMessageCtrlr` variable `Language` to "
|
||||
"set the supported languages)";
|
||||
// Break loop because the next iteration, the supported languages will also not be there.
|
||||
break;
|
||||
}
|
||||
|
||||
if (std::find(supported_languages.begin(), supported_languages.end(), c.language.value()) !=
|
||||
supported_languages.end()) {
|
||||
cost_messages.push_back(c);
|
||||
} else {
|
||||
EVLOG_warning << "Can not send a personal message text in language " << c.language.value()
|
||||
<< " as it is not supported by the charging station.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tariff_enabled and !cost_messages.empty()) {
|
||||
running_cost.cost_messages = cost_messages;
|
||||
}
|
||||
|
||||
const int number_of_decimals =
|
||||
this->context.device_model
|
||||
.get_optional_value<int>(ControllerComponentVariables::NumberOfDecimalsForCostValues)
|
||||
.value_or(DEFAULT_PRICE_NUMBER_OF_DECIMALS);
|
||||
const std::uint32_t decimals = (number_of_decimals < 0 ? DEFAULT_PRICE_NUMBER_OF_DECIMALS
|
||||
: static_cast<std::uint32_t>(number_of_decimals));
|
||||
const std::optional<std::string> currency =
|
||||
this->context.device_model.get_value<std::string>(ControllerComponentVariables::TariffCostCtrlrCurrency);
|
||||
this->set_running_cost_callback.value()(running_cost, decimals, currency);
|
||||
}
|
||||
}
|
||||
|
||||
void TariffAndCost::handle_costupdated_req(const Call<CostUpdatedRequest> call) {
|
||||
const CostUpdatedResponse response;
|
||||
const ocpp::CallResult<CostUpdatedResponse> call_result(response, call.uniqueId);
|
||||
|
||||
if (!is_cost_enabled() or !this->set_running_cost_callback.has_value()) {
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
return;
|
||||
}
|
||||
|
||||
RunningCost running_cost;
|
||||
TriggerMeterValue triggers;
|
||||
|
||||
if (this->context.device_model
|
||||
.get_optional_value<bool>(ControllerComponentVariables::CustomImplementationCaliforniaPricingEnabled)
|
||||
.value_or(false) and
|
||||
call.msg.customData.has_value()) {
|
||||
const json running_cost_json = call.msg.customData.value();
|
||||
|
||||
// California pricing is enabled, which means we have to read the custom data.
|
||||
running_cost = running_cost_json;
|
||||
|
||||
if (running_cost_json.contains("triggerMeterValue")) {
|
||||
triggers = running_cost_json.at("triggerMeterValue");
|
||||
}
|
||||
} else {
|
||||
running_cost.state = RunningCostState::Charging;
|
||||
}
|
||||
|
||||
// In 2.0.1, the cost and transaction id are already part of the CostUpdatedRequest, so they need to be added to
|
||||
// the 'RunningCost' struct.
|
||||
running_cost.cost = static_cast<double>(call.msg.totalCost);
|
||||
running_cost.transaction_id = call.msg.transactionId;
|
||||
|
||||
const std::optional<std::int32_t> transaction_evse_id =
|
||||
this->context.evse_manager.get_transaction_evseid(running_cost.transaction_id);
|
||||
if (!transaction_evse_id.has_value()) {
|
||||
// We just put an error in the log as the spec does not define what to do here. It is not possible to return
|
||||
// a 'Rejected' or something in that manner.
|
||||
EVLOG_error << "Received CostUpdatedRequest, but transaction id is not a valid transaction id.";
|
||||
}
|
||||
|
||||
const int number_of_decimals =
|
||||
this->context.device_model.get_optional_value<int>(ControllerComponentVariables::NumberOfDecimalsForCostValues)
|
||||
.value_or(DEFAULT_PRICE_NUMBER_OF_DECIMALS);
|
||||
const std::uint32_t decimals =
|
||||
(number_of_decimals < 0 ? DEFAULT_PRICE_NUMBER_OF_DECIMALS : static_cast<std::uint32_t>(number_of_decimals));
|
||||
const std::optional<std::string> currency =
|
||||
this->context.device_model.get_value<std::string>(ControllerComponentVariables::TariffCostCtrlrCurrency);
|
||||
this->set_running_cost_callback.value()(running_cost, decimals, currency);
|
||||
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
|
||||
// In OCPP 2.0.1, the chargepoint status trigger is not used.
|
||||
if (!triggers.at_energy_kwh.has_value() and !triggers.at_power_kw.has_value() and !triggers.at_time.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::optional<std::int32_t> evse_id_opt =
|
||||
this->context.evse_manager.get_transaction_evseid(running_cost.transaction_id);
|
||||
if (!evse_id_opt.has_value()) {
|
||||
EVLOG_warning << "Can not set running cost triggers as there is no evse id found with the transaction id from "
|
||||
"the incoming CostUpdatedRequest";
|
||||
return;
|
||||
}
|
||||
|
||||
const std::int32_t evse_id = evse_id_opt.value();
|
||||
auto& evse = this->context.evse_manager.get_evse(evse_id);
|
||||
evse.set_meter_value_pricing_triggers(
|
||||
triggers.at_power_kw, triggers.at_energy_kwh, triggers.at_time,
|
||||
[this, evse_id](const std::vector<MeterValue>& meter_values) {
|
||||
this->meter_values.meter_values_req(evse_id, meter_values, false);
|
||||
},
|
||||
this->io_context);
|
||||
}
|
||||
|
||||
std::vector<DisplayMessageContent>
|
||||
TariffAndCost::get_fallback_messages(const ComponentVariable& component_variable) const {
|
||||
std::vector<DisplayMessageContent> messages;
|
||||
|
||||
// Default (no-instance) entry
|
||||
const auto default_msg = this->context.device_model.get_optional_value<std::string>(component_variable);
|
||||
if (default_msg.has_value() and !default_msg.value().empty()) {
|
||||
messages.push_back({default_msg.value(), std::nullopt, std::nullopt});
|
||||
}
|
||||
|
||||
// Per-language instances — one entry per supported language in DisplayMessageCtrlr.Language
|
||||
const auto metadata = this->context.device_model.get_variable_meta_data(
|
||||
ControllerComponentVariables::DisplayMessageLanguage.component,
|
||||
ControllerComponentVariables::DisplayMessageLanguage.variable.value());
|
||||
if (metadata.has_value() and metadata.value().characteristics.valuesList.has_value()) {
|
||||
for (const auto& language :
|
||||
ocpp::split_string(metadata.value().characteristics.valuesList.value(), ',', true)) {
|
||||
Variable var;
|
||||
var.name = component_variable.variable.value().name;
|
||||
var.instance = language;
|
||||
const auto msg =
|
||||
this->context.device_model.get_optional_value<std::string>(component_variable.component, var);
|
||||
if (msg.has_value() and !msg.value().empty()) {
|
||||
messages.push_back({msg.value(), language, std::nullopt});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
std::optional<TariffMessage> TariffAndCost::get_fallback_tariff_message(bool offline) const {
|
||||
if (!is_tariff_enabled()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// I04.FR.01: prefer the offline-specific variable when disconnected; fall back to the
|
||||
// online TariffFallbackMessage when no offline messages are configured.
|
||||
auto messages = offline ? get_fallback_messages(ControllerComponentVariables::OfflineTariffFallbackMessage)
|
||||
: get_fallback_messages(ControllerComponentVariables::TariffFallbackMessage);
|
||||
|
||||
if (offline and messages.empty()) {
|
||||
EVLOG_debug << "No OfflineTariffFallbackMessage configured, falling back to TariffFallbackMessage";
|
||||
messages = get_fallback_messages(ControllerComponentVariables::TariffFallbackMessage);
|
||||
}
|
||||
|
||||
if (messages.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
TariffMessage tariff_message;
|
||||
tariff_message.message = messages;
|
||||
return tariff_message;
|
||||
}
|
||||
|
||||
void TariffAndCost::send_total_cost_fallback_message(const std::string& transaction_id) {
|
||||
if (!is_cost_enabled() or !this->tariff_message_callback.has_value() or this->tariff_message_callback == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// I05.FR.02: show TotalCostFallbackMessage when the CS is offline at transaction end.
|
||||
const auto messages = get_fallback_messages(ControllerComponentVariables::TotalCostFallbackMessage);
|
||||
if (messages.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
TariffMessage tariff_message;
|
||||
tariff_message.message = messages;
|
||||
tariff_message.ocpp_transaction_id = transaction_id;
|
||||
tariff_message.identifier_id = transaction_id;
|
||||
tariff_message.identifier_type = IdentifierType::TransactionId;
|
||||
this->tariff_message_callback.value()(tariff_message);
|
||||
}
|
||||
|
||||
void TariffAndCost::publish_default_price(bool is_connected) {
|
||||
if (!is_tariff_enabled() or !this->default_price_callback.has_value() or this->default_price_callback == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prefer the offline message when disconnected; fall back to the online message when no offline messages are
|
||||
// configured, mirroring the logic in get_fallback_tariff_message / ensure_personal_message.
|
||||
auto messages = is_connected ? get_fallback_messages(ControllerComponentVariables::TariffFallbackMessage)
|
||||
: get_fallback_messages(ControllerComponentVariables::OfflineTariffFallbackMessage);
|
||||
|
||||
if (!is_connected and messages.empty()) {
|
||||
messages = get_fallback_messages(ControllerComponentVariables::TariffFallbackMessage);
|
||||
}
|
||||
|
||||
if (messages.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->default_price_callback.value()(messages);
|
||||
}
|
||||
|
||||
void TariffAndCost::ensure_personal_message(IdTokenInfo& id_token_info, bool offline) {
|
||||
if (!is_tariff_enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (id_token_info.personalMessage.has_value()) {
|
||||
// Personal message already set by CSMS — assume it contains the tariff information.
|
||||
return;
|
||||
}
|
||||
|
||||
const auto fallback = get_fallback_tariff_message(offline);
|
||||
if (!fallback.has_value() or fallback->message.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Per California Pricing 4.3.4: personalMessage holds the text in the default language
|
||||
// (DisplayMessageCtrlr.Language). All other languages go into customData.personalMessageExtra[] (max 4 additional
|
||||
// entries).
|
||||
const auto default_language =
|
||||
this->context.device_model.get_optional_value<std::string>(ControllerComponentVariables::DisplayMessageLanguage)
|
||||
.value_or(std::string{});
|
||||
|
||||
const auto& messages = fallback->message;
|
||||
|
||||
// Find the entry matching the default language; fall back to index 0 if none matches.
|
||||
std::size_t primary_idx = 0;
|
||||
for (std::size_t i = 0; i < messages.size(); ++i) {
|
||||
if (!default_language.empty() and messages[i].language.has_value() and
|
||||
messages[i].language.value() == default_language) {
|
||||
primary_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Set personalMessage to the default-language entry.
|
||||
const auto& primary = messages[primary_idx];
|
||||
MessageContent personal_msg;
|
||||
personal_msg.format = primary.message_format.value_or(MessageFormatEnum::UTF8);
|
||||
personal_msg.content = CiString<1024>(primary.message);
|
||||
if (primary.language.has_value()) {
|
||||
personal_msg.language = CiString<8>(primary.language.value());
|
||||
}
|
||||
id_token_info.personalMessage = personal_msg;
|
||||
|
||||
// Put remaining language entries into customData.personalMessageExtra.
|
||||
constexpr std::size_t max_extra = 4;
|
||||
json extra = json::array();
|
||||
for (std::size_t i = 0; i < messages.size() and extra.size() < max_extra; ++i) {
|
||||
if (i == primary_idx) {
|
||||
continue;
|
||||
}
|
||||
json entry;
|
||||
entry["content"] = messages[i].message;
|
||||
if (messages[i].language.has_value()) {
|
||||
entry["language"] = messages[i].language.value();
|
||||
}
|
||||
extra.push_back(entry);
|
||||
}
|
||||
|
||||
if (!extra.empty()) {
|
||||
CustomData custom_data = id_token_info.customData.value_or(json{});
|
||||
custom_data["vendorId"] = "org.openchargealliance.multilanguage";
|
||||
custom_data["personalMessageExtra"] = extra;
|
||||
id_token_info.customData = custom_data;
|
||||
}
|
||||
}
|
||||
|
||||
bool TariffAndCost::is_multilanguage_enabled() const {
|
||||
return this->context.device_model
|
||||
.get_optional_value<bool>(ControllerComponentVariables::CustomImplementationMultiLanguageEnabled)
|
||||
.value_or(false);
|
||||
}
|
||||
|
||||
bool TariffAndCost::is_tariff_enabled() const {
|
||||
return this->context.device_model
|
||||
.get_optional_value<bool>(ControllerComponentVariables::TariffCostCtrlrAvailableTariff)
|
||||
.value_or(false) and
|
||||
this->context.device_model
|
||||
.get_optional_value<bool>(ControllerComponentVariables::TariffCostCtrlrEnabledTariff)
|
||||
.value_or(false);
|
||||
}
|
||||
|
||||
bool TariffAndCost::is_cost_enabled() const {
|
||||
return this->context.device_model
|
||||
.get_optional_value<bool>(ControllerComponentVariables::TariffCostCtrlrAvailableCost)
|
||||
.value_or(false) and
|
||||
this->context.device_model.get_optional_value<bool>(ControllerComponentVariables::TariffCostCtrlrEnabledCost)
|
||||
.value_or(false);
|
||||
}
|
||||
} // namespace ocpp::v2
|
||||
@@ -0,0 +1,365 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <ocpp/v2/functional_blocks/transaction.hpp>
|
||||
|
||||
#include <ocpp/common/connectivity_manager.hpp>
|
||||
#include <ocpp/v2/ctrlr_component_variables.hpp>
|
||||
#include <ocpp/v2/device_model.hpp>
|
||||
#include <ocpp/v2/evse_manager.hpp>
|
||||
#include <ocpp/v2/functional_blocks/functional_block_context.hpp>
|
||||
#include <ocpp/v2/utils.hpp>
|
||||
|
||||
#include <ocpp/v2/functional_blocks/authorization.hpp>
|
||||
#include <ocpp/v2/functional_blocks/availability.hpp>
|
||||
#include <ocpp/v2/functional_blocks/smart_charging.hpp>
|
||||
#include <ocpp/v2/functional_blocks/tariff_and_cost.hpp>
|
||||
|
||||
#include <ocpp/v2/messages/GetTransactionStatus.hpp>
|
||||
#include <ocpp/v2/messages/TransactionEvent.hpp>
|
||||
|
||||
namespace ocpp::v2 {
|
||||
TransactionBlock::TransactionBlock(
|
||||
const FunctionalBlockContext& functional_block_context, MessageQueue<v2::MessageType>& message_queue,
|
||||
AuthorizationInterface& authorization, AvailabilityInterface& availability, SmartChargingInterface& smart_charging,
|
||||
TariffAndCostInterface& tariff_and_cost, StopTransactionCallback stop_transaction_callback,
|
||||
PauseChargingCallback pause_charging_callback, std::optional<TransactionEventCallback> transaction_event_callback,
|
||||
std::optional<TransactionEventResponseCallback> transaction_event_response_callback, ResetCallback reset_callback) :
|
||||
context(functional_block_context),
|
||||
message_queue(message_queue),
|
||||
authorization(authorization),
|
||||
availability(availability),
|
||||
smart_charging(smart_charging),
|
||||
tariff_and_cost(tariff_and_cost),
|
||||
stop_transaction_callback(stop_transaction_callback),
|
||||
pause_charging_callback(pause_charging_callback),
|
||||
transaction_event_callback(transaction_event_callback),
|
||||
transaction_event_response_callback(transaction_event_response_callback),
|
||||
reset_callback(reset_callback),
|
||||
reset_scheduled(false) {
|
||||
}
|
||||
|
||||
void TransactionBlock::handle_message(const ocpp::EnhancedMessage<MessageType>& message) {
|
||||
const auto& json_message = message.message;
|
||||
|
||||
if (message.messageType == MessageType::TransactionEventResponse) {
|
||||
this->handle_transaction_event_response(message);
|
||||
} else if (message.messageType == MessageType::GetTransactionStatus) {
|
||||
this->handle_get_transaction_status(json_message);
|
||||
} else {
|
||||
throw MessageTypeNotImplementedException(message.messageType);
|
||||
}
|
||||
}
|
||||
|
||||
void TransactionBlock::on_transaction_started(const std::int32_t evse_id, const std::int32_t connector_id,
|
||||
const std::string& session_id, const DateTime& timestamp,
|
||||
const TriggerReasonEnum trigger_reason, const MeterValue& meter_start,
|
||||
const std::optional<IdToken>& id_token,
|
||||
const std::optional<IdToken>& group_id_token,
|
||||
const std::optional<std::int32_t>& reservation_id,
|
||||
const std::optional<std::int32_t>& remote_start_id,
|
||||
const ChargingStateEnum charging_state) {
|
||||
auto& evse_handle = this->context.evse_manager.get_evse(evse_id);
|
||||
evse_handle.open_transaction(session_id, connector_id, timestamp, meter_start, id_token, group_id_token,
|
||||
reservation_id, charging_state);
|
||||
|
||||
const auto meter_value = utils::get_meter_value_with_measurands_applied(
|
||||
meter_start, utils::get_measurands_vec(this->context.device_model.get_value<std::string>(
|
||||
ControllerComponentVariables::SampledDataTxStartedMeasurands)));
|
||||
|
||||
const auto& enhanced_transaction = evse_handle.get_transaction();
|
||||
Transaction transaction{enhanced_transaction->transactionId};
|
||||
transaction.chargingState = charging_state;
|
||||
transaction.remoteStartId = remote_start_id;
|
||||
enhanced_transaction->remoteStartId = remote_start_id;
|
||||
|
||||
EVSE evse{evse_id};
|
||||
evse.connectorId.emplace(connector_id);
|
||||
|
||||
std::optional<std::vector<MeterValue>> opt_meter_value;
|
||||
if (!meter_value.sampledValue.empty()) {
|
||||
opt_meter_value.emplace(1, meter_value);
|
||||
}
|
||||
|
||||
this->transaction_event_req(TransactionEventEnum::Started, timestamp, transaction, trigger_reason,
|
||||
enhanced_transaction->get_seq_no(), std::nullopt, evse, id_token, opt_meter_value,
|
||||
std::nullopt, !this->context.connectivity_manager.is_websocket_connected(),
|
||||
reservation_id);
|
||||
}
|
||||
|
||||
void TransactionBlock::on_transaction_finished(const std::int32_t evse_id, const DateTime& timestamp,
|
||||
const MeterValue& meter_stop, const ReasonEnum reason,
|
||||
const TriggerReasonEnum trigger_reason,
|
||||
const std::optional<IdToken>& id_token,
|
||||
const std::optional<std::string>& /*signed_meter_value*/,
|
||||
const ChargingStateEnum charging_state) {
|
||||
auto& evse_handle = this->context.evse_manager.get_evse(evse_id);
|
||||
auto& enhanced_transaction = evse_handle.get_transaction();
|
||||
if (enhanced_transaction == nullptr) {
|
||||
EVLOG_warning << "Received notification of finished transaction while no transaction was active";
|
||||
return;
|
||||
}
|
||||
|
||||
enhanced_transaction->chargingState = charging_state;
|
||||
evse_handle.close_transaction(timestamp, meter_stop, reason);
|
||||
const auto transaction = enhanced_transaction->get_transaction();
|
||||
|
||||
std::optional<std::vector<ocpp::v2::MeterValue>> meter_values = std::nullopt;
|
||||
try {
|
||||
meter_values = std::make_optional(utils::get_meter_values_with_measurands_applied(
|
||||
this->context.database_handler.transaction_metervalues_get_all(enhanced_transaction->transactionId.get()),
|
||||
utils::get_measurands_vec(this->context.device_model.get_value<std::string>(
|
||||
ControllerComponentVariables::SampledDataTxEndedMeasurands)),
|
||||
utils::get_measurands_vec(this->context.device_model.get_value<std::string>(
|
||||
ControllerComponentVariables::AlignedDataTxEndedMeasurands)),
|
||||
timestamp,
|
||||
this->context.device_model.get_optional_value<bool>(ControllerComponentVariables::SampledDataSignReadings)
|
||||
.value_or(false),
|
||||
this->context.device_model.get_optional_value<bool>(ControllerComponentVariables::AlignedDataSignReadings)
|
||||
.value_or(false)));
|
||||
|
||||
if (meter_values.value().empty()) {
|
||||
meter_values.reset();
|
||||
}
|
||||
} catch (const everest::db::Exception& e) {
|
||||
EVLOG_warning << "Could not get metervalues of transaction: " << e.what();
|
||||
}
|
||||
|
||||
// E07.FR.02 The field idToken is provided when the authorization of the transaction has been ended
|
||||
const std::optional<IdToken> transaction_id_token =
|
||||
trigger_reason == ocpp::v2::TriggerReasonEnum::StopAuthorized ? id_token : std::nullopt;
|
||||
|
||||
const bool is_offline = !this->context.connectivity_manager.is_websocket_connected();
|
||||
this->transaction_event_req(TransactionEventEnum::Ended, timestamp, enhanced_transaction->get_transaction(),
|
||||
trigger_reason, enhanced_transaction->get_seq_no(), std::nullopt, std::nullopt,
|
||||
transaction_id_token, meter_values, std::nullopt, is_offline, std::nullopt);
|
||||
|
||||
// I05.FR.02: When offline, the CSMS response with totalCost will not arrive, so show the fallback message.
|
||||
if (is_offline) {
|
||||
this->tariff_and_cost.send_total_cost_fallback_message(transaction.transactionId.get());
|
||||
}
|
||||
|
||||
// K02.FR.05 The transaction is over, so delete the TxProfiles associated with the transaction.
|
||||
smart_charging.delete_transaction_tx_profiles(enhanced_transaction->get_transaction().transactionId);
|
||||
evse_handle.release_transaction();
|
||||
|
||||
bool send_reset = false;
|
||||
if (this->reset_scheduled) {
|
||||
// Check if this evse needs to be reset or set to inoperative.
|
||||
if (!this->reset_scheduled_evseids.empty()) {
|
||||
// There is an evse id in the 'reset scheduled' list, it needs to be
|
||||
// reset because it has finished charging.
|
||||
if (this->reset_scheduled_evseids.find(evse_id) != this->reset_scheduled_evseids.end()) {
|
||||
send_reset = true;
|
||||
}
|
||||
} else {
|
||||
// No evse id is given, whole charging station needs a reset. Wait
|
||||
// for last evse id to stop charging.
|
||||
bool is_charging = false;
|
||||
for (const auto& evse : this->context.evse_manager) {
|
||||
if (evse.has_active_transaction()) {
|
||||
is_charging = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_charging) {
|
||||
set_evse_connectors_unavailable(evse_handle, false);
|
||||
} else {
|
||||
send_reset = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (send_reset) {
|
||||
// Reset evse.
|
||||
if (reset_scheduled_evseids.empty()) {
|
||||
// This was the last evse that was charging, whole charging station
|
||||
// should be rest, send reset.
|
||||
this->reset_callback(std::nullopt, ResetEnum::OnIdle);
|
||||
this->reset_scheduled = false;
|
||||
} else {
|
||||
// Reset evse that just stopped the transaction.
|
||||
this->reset_callback(evse_id, ResetEnum::OnIdle);
|
||||
// Remove evse id that is just reset.
|
||||
this->reset_scheduled_evseids.erase(evse_id);
|
||||
|
||||
// Check if there are more evse's that should be reset.
|
||||
if (reset_scheduled_evseids.empty()) {
|
||||
// No other evse's should be reset
|
||||
this->reset_scheduled = false;
|
||||
}
|
||||
}
|
||||
|
||||
this->reset_scheduled_evseids.erase(evse_id);
|
||||
}
|
||||
|
||||
this->availability.handle_scheduled_change_availability_requests(evse_id);
|
||||
this->availability.handle_scheduled_change_availability_requests(0);
|
||||
}
|
||||
|
||||
void TransactionBlock::transaction_event_req(const TransactionEventEnum& event_type, const DateTime& timestamp,
|
||||
const Transaction& transaction, const TriggerReasonEnum& trigger_reason,
|
||||
const std::int32_t seq_no,
|
||||
const std::optional<std::int32_t>& cable_max_current,
|
||||
const std::optional<EVSE>& evse, const std::optional<IdToken>& id_token,
|
||||
const std::optional<std::vector<MeterValue>>& meter_value,
|
||||
const std::optional<std::int32_t>& number_of_phases_used,
|
||||
const bool offline, const std::optional<std::int32_t>& reservation_id,
|
||||
const bool initiated_by_trigger_message) {
|
||||
TransactionEventRequest req;
|
||||
req.eventType = event_type;
|
||||
req.timestamp = timestamp;
|
||||
req.transactionInfo = transaction;
|
||||
req.triggerReason = trigger_reason;
|
||||
req.seqNo = seq_no;
|
||||
req.cableMaxCurrent = cable_max_current;
|
||||
req.evse = evse;
|
||||
req.idToken = id_token;
|
||||
req.meterValue = meter_value;
|
||||
req.numberOfPhasesUsed = number_of_phases_used;
|
||||
req.offline = offline;
|
||||
req.reservationId = reservation_id;
|
||||
|
||||
ocpp::Call<TransactionEventRequest> call(req);
|
||||
|
||||
// Check if id token is in the remote start map, because when a remote
|
||||
// start request is done, the first transaction event request should
|
||||
// always contain trigger reason 'RemoteStart'.
|
||||
auto it = std::find_if(
|
||||
remote_start_id_per_evse.begin(), remote_start_id_per_evse.end(),
|
||||
[&id_token, &evse](const std::pair<std::int32_t, std::pair<IdToken, std::int32_t>>& remote_start_per_evse) {
|
||||
if (id_token.has_value() and remote_start_per_evse.second.first.idToken == id_token.value().idToken) {
|
||||
|
||||
if (remote_start_per_evse.first == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (evse.has_value() and evse.value().id == remote_start_per_evse.first) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (it != remote_start_id_per_evse.end()) {
|
||||
// Found remote start. Set remote start id and the trigger reason.
|
||||
call.msg.triggerReason = TriggerReasonEnum::RemoteStart;
|
||||
call.msg.transactionInfo.remoteStartId = it->second.second;
|
||||
|
||||
remote_start_id_per_evse.erase(it);
|
||||
}
|
||||
|
||||
this->context.message_dispatcher.dispatch_call(call, initiated_by_trigger_message);
|
||||
|
||||
if (this->transaction_event_callback.has_value()) {
|
||||
this->transaction_event_callback.value()(req);
|
||||
}
|
||||
}
|
||||
|
||||
void TransactionBlock::set_remote_start_id_for_evse(const std::int32_t evse_id, const IdToken id_token,
|
||||
const std::int32_t remote_start_id) {
|
||||
remote_start_id_per_evse[evse_id] = {id_token, remote_start_id};
|
||||
}
|
||||
|
||||
void TransactionBlock::schedule_reset(const std::optional<std::int32_t> reset_scheduled_evseid) {
|
||||
reset_scheduled = true;
|
||||
if (reset_scheduled_evseid.has_value()) {
|
||||
this->reset_scheduled_evseids.insert(reset_scheduled_evseid.value());
|
||||
}
|
||||
}
|
||||
|
||||
void TransactionBlock::handle_transaction_event_response(const EnhancedMessage<MessageType>& message) {
|
||||
const CallResult<TransactionEventResponse> call_result = message.message;
|
||||
const Call<TransactionEventRequest>& original_call = message.call_message;
|
||||
const auto& original_msg = original_call.msg;
|
||||
|
||||
if (this->transaction_event_response_callback.has_value()) {
|
||||
this->transaction_event_response_callback.value()(original_msg, call_result.msg);
|
||||
}
|
||||
|
||||
this->tariff_and_cost.handle_cost_and_tariff(call_result.msg, original_msg, message.message[CALLRESULT_PAYLOAD]);
|
||||
|
||||
if (original_msg.eventType == TransactionEventEnum::Ended) {
|
||||
// nothing to do for TransactionEventEnum::Ended
|
||||
return;
|
||||
}
|
||||
|
||||
const auto msg = call_result.msg;
|
||||
|
||||
if (!msg.idTokenInfo.has_value()) {
|
||||
// nothing to do when the response does not contain idTokenInfo
|
||||
return;
|
||||
}
|
||||
|
||||
if (!original_msg.idToken.has_value()) {
|
||||
EVLOG_error
|
||||
<< "TransactionEvent.conf contains idTokenInfo when no idToken was part of the TransactionEvent.req";
|
||||
return;
|
||||
}
|
||||
|
||||
const IdToken& id_token = original_msg.idToken.value();
|
||||
|
||||
// C03.FR.0x and C05.FR.01: We SHALL NOT store central information in the Authorization Cache
|
||||
// C10.FR.05
|
||||
if (id_token.type != IdTokenEnumStringType::Central and this->authorization.is_auth_cache_ctrlr_enabled()) {
|
||||
try {
|
||||
this->authorization.authorization_cache_insert_entry(utils::generate_token_hash(id_token),
|
||||
msg.idTokenInfo.value());
|
||||
} catch (const everest::db::Exception& e) {
|
||||
EVLOG_warning << "Could not insert into authorization cache entry: " << e.what();
|
||||
}
|
||||
this->authorization.trigger_authorization_cache_cleanup();
|
||||
}
|
||||
|
||||
if (msg.idTokenInfo.value().status == AuthorizationStatusEnum::Accepted) {
|
||||
// nothing to do in case status is accepted
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& evse : this->context.evse_manager) {
|
||||
if (auto& transaction = evse.get_transaction();
|
||||
transaction != nullptr and transaction->transactionId == original_msg.transactionInfo.transactionId) {
|
||||
// Deal with invalid token for transaction
|
||||
auto evse_id = evse.get_id();
|
||||
if (this->context.device_model.get_value<bool>(ControllerComponentVariables::StopTxOnInvalidId)) {
|
||||
this->stop_transaction_callback(evse_id, ReasonEnum::DeAuthorized);
|
||||
} else {
|
||||
if (this->context.device_model
|
||||
.get_optional_value<std::int32_t>(ControllerComponentVariables::MaxEnergyOnInvalidId)
|
||||
.has_value()) {
|
||||
// Energy delivery to the EV SHALL be allowed until the amount of energy specified in
|
||||
// MaxEnergyOnInvalidId has been reached.
|
||||
evse.start_checking_max_energy_on_invalid_id();
|
||||
} else {
|
||||
this->pause_charging_callback(evse_id);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TransactionBlock::handle_get_transaction_status(const Call<GetTransactionStatusRequest> call) {
|
||||
const auto msg = call.msg;
|
||||
|
||||
GetTransactionStatusResponse response;
|
||||
response.messagesInQueue = false;
|
||||
|
||||
if (msg.transactionId.has_value()) {
|
||||
if (this->context.evse_manager.get_transaction_evseid(msg.transactionId.value()).has_value()) {
|
||||
response.ongoingIndicator = true;
|
||||
} else {
|
||||
response.ongoingIndicator = false;
|
||||
}
|
||||
if (this->message_queue.contains_transaction_messages(msg.transactionId.value())) {
|
||||
response.messagesInQueue = true;
|
||||
}
|
||||
} else if (!this->message_queue.is_transaction_message_queue_empty()) {
|
||||
response.messagesInQueue = true;
|
||||
}
|
||||
|
||||
const ocpp::CallResult<GetTransactionStatusResponse> call_result(response, call.uniqueId);
|
||||
this->context.message_dispatcher.dispatch_call_result(call_result);
|
||||
}
|
||||
} // namespace ocpp::v2
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user