From 2e7d1618b4ced8e67f33a9a82fbdc3c726e57f54 Mon Sep 17 00:00:00 2001 From: pietfried Date: Mon, 6 Nov 2023 17:23:12 +0100 Subject: [PATCH 1/6] implemented GetCustomerInformation and NotifyCustomerInformation (n09, n10) Signed-off-by: pietfried --- .../standardized/InternalCtrlr.json | 18 +++ include/ocpp/v201/charge_point.hpp | 48 ++++++- .../ocpp/v201/ctrlr_component_variables.hpp | 1 + lib/ocpp/v201/charge_point.cpp | 123 +++++++++++++++++- lib/ocpp/v201/ctrlr_component_variables.cpp | 7 + 5 files changed, 186 insertions(+), 11 deletions(-) diff --git a/config/v201/component_schemas/standardized/InternalCtrlr.json b/config/v201/component_schemas/standardized/InternalCtrlr.json index 3baee5f80..4708be22f 100644 --- a/config/v201/component_schemas/standardized/InternalCtrlr.json +++ b/config/v201/component_schemas/standardized/InternalCtrlr.json @@ -369,6 +369,24 @@ ], "default": "hello there", "type": "string" + }, + "MaxCustomerInformationDataLength": { + "variable_name": "MaxCustomerInformationDataLength", + "characteristics": { + "minLimit": 0, + "supportsMonitoring": true, + "dataType": "integer" + }, + "attributes": [ + { + "type": "Actual", + "mutability": "ReadOnly" + } + ], + "description": "Maximum number of characters of Customer Information data", + "minimum": 0, + "default": "51200", + "type": "integer" } }, "required": [ diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index 42f0b8504..628fde93b 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -114,8 +116,21 @@ struct Callbacks { std::function& event_type, const std::optional>& tech_info)> security_event_callback; - /// @brief Callback for when a bootnotification response is received + /// \brief Callback for when a bootnotification response is received std::optional> boot_notification_callback; + + /// \brief Callback function that can be used to get (human readable) customer information based on the given + /// arguments + std::optional customer_certificate, + const std::optional id_token, + const std::optional> customer_identifier)>> + get_customer_information_callback; + + /// \brief Callback function that can be called to clear customer information based on the given arguments + std::optional customer_certificate, + const std::optional id_token, + const std::optional> customer_identifier)>> + clear_customer_information_callback; }; /// \brief Class implements OCPP2.0.1 Charging Station @@ -273,14 +288,35 @@ class ChargePoint : ocpp::ChargingStationBase { /// void set_evse_connectors_unavailable(const std::unique_ptr& evse, bool persist); - /// @brief Get the value optional offline flag - /// @return true if the charge point is offline. std::nullopt if it is online; + /// \brief Get the value optional offline flag + /// \return true if the charge point is offline. std::nullopt if it is online; bool is_offline(); /// \brief Returns the last present meter value for evseId 0 /// \return MeterValue MeterValue get_meter_value(); + /// \brief Returns customer information based on the given arguments. This function also executes the + /// get_customer_information_callback in case it is present + /// \param customer_certificate Certificate of the customer this request refers to + /// \param id_token IdToken of the customer this request refers to + /// \param customer_identifier A (e.g. vendor specific) identifier of the customer this request refers to. This + /// field contains a custom identifier other than IdToken and Certificate + /// \return customer information + std::string get_customer_information(const std::optional customer_certificate, + const std::optional id_token, + const std::optional> customer_identifier); + + /// \brief Clears customer information based on the given arguments. This function also executes the + /// clear_customer_information_callback in case it is present + /// \param customer_certificate Certificate of the customer this request refers to + /// \param id_token IdToken of the customer this request refers to + /// \param customer_identifier A (e.g. vendor specific) identifier of the customer this request refers to. This + /// field contains a custom identifier other than IdToken and Certificate + void clear_customer_information(const std::optional customer_certificate, + const std::optional id_token, + const std::optional> customer_identifier); + /* OCPP message requests */ // Functional Block A: Security @@ -314,8 +350,8 @@ class ChargePoint : ocpp::ChargingStationBase { void meter_values_req(const int32_t evse_id, const std::vector& meter_values); // Functional Block N: Diagnostics - void handle_get_log_req(Call call); void notify_event_req(const std::vector& events); + void notify_customer_information_req(const std::string& data, const int32_t seq_no, const int32_t request_id); /* OCPP message handlers */ @@ -357,6 +393,10 @@ class ChargePoint : ocpp::ChargingStationBase { void handle_install_certificate_req(Call call); void handle_delete_certificate_req(Call call); + // Functional Block N: Diagnostics + void handle_get_log_req(Call call); + void handle_customer_information_req(Call call); + // Functional Block P: DataTransfer void handle_data_transfer_req(Call call); diff --git a/include/ocpp/v201/ctrlr_component_variables.hpp b/include/ocpp/v201/ctrlr_component_variables.hpp index 0cbb785e3..2670e9e65 100644 --- a/include/ocpp/v201/ctrlr_component_variables.hpp +++ b/include/ocpp/v201/ctrlr_component_variables.hpp @@ -59,6 +59,7 @@ extern const ComponentVariable& UseSslDefaultVerifyPaths; extern const ComponentVariable& OcspRequestInterval; extern const ComponentVariable& WebsocketPingPayload; extern const ComponentVariable& WebsocketPongTimeout; +extern const ComponentVariable& MaxCustomerInformationDataLength; extern const ComponentVariable& AlignedDataCtrlrEnabled; extern const ComponentVariable& AlignedDataCtrlrAvailable; extern const ComponentVariable& AlignedDataInterval; diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 218261f2e..721de3374 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -8,6 +8,8 @@ using namespace std::chrono_literals; +const auto DEFAULT_MAX_CUSTOMER_INFORMATION_DATA_LENGTH = 51200; + namespace ocpp { namespace v201 { @@ -383,6 +385,45 @@ MeterValue ChargePoint::get_meter_value() { return this->meter_value; } +std::string ChargePoint::get_customer_information(const std::optional customer_certificate, + const std::optional id_token, + const std::optional> customer_identifier) { + std::stringstream s; + + // Retrieve possible customer information from application that uses this library + if (this->callbacks.get_customer_information_callback.has_value()) { + s << this->callbacks.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()); + const auto entry = this->database_handler->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.value(); + } + } + + return s.str(); +} + +void ChargePoint::clear_customer_information(const std::optional customer_certificate, + const std::optional id_token, + const std::optional> customer_identifier) { + if (this->callbacks.clear_customer_information_callback.has_value()) { + this->callbacks.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()); + this->database_handler->authorization_cache_delete_entry(hashed_id_token); + this->update_authorization_cache_size(); + } +} + void ChargePoint::on_unavailable(const int32_t evse_id, const int32_t connector_id) { this->evses.at(evse_id)->submit_event(connector_id, ConnectorEvent::Unavailable); } @@ -808,6 +849,9 @@ void ChargePoint::handle_message(const EnhancedMessage& messa case MessageType::DeleteCertificate: this->handle_delete_certificate_req(json_message); break; + case MessageType::CustomerInformation: + this->handle_customer_information_req(json_message); + break; default: if (message.messageTypeId == MessageTypeId::CALL) { const auto call_error = CallError(message.uniqueId, "NotImplemented", "", json({})); @@ -1419,13 +1463,6 @@ void ChargePoint::meter_values_req(const int32_t evse_id, const std::vectorsend(call); } -void ChargePoint::handle_get_log_req(Call call) { - const GetLogResponse response = this->callbacks.get_log_request_callback(call.msg); - - ocpp::CallResult call_result(response, call.uniqueId); - this->send(call_result); -} - void ChargePoint::notify_event_req(const std::vector& events) { NotifyEventRequest req; req.eventData = events; @@ -1436,6 +1473,30 @@ void ChargePoint::notify_event_req(const std::vector& events) { this->send(call); } +void ChargePoint::notify_customer_information_req(const std::string& data, const int32_t seq_no, + const int32_t request_id) { + const auto req = [&]() { + NotifyCustomerInformationRequest req; + req.data = CiString<512>(data.substr(0, 512)); + req.seqNo = seq_no; + req.requestId = request_id; + req.generatedAt = DateTime(); + req.tbc = data.length() > 512; + return req; + }(); + + ocpp::Call call(req, this->message_queue->createMessageId()); + this->send(call); + + if (data.length() > 512) { + // Recursively send next sequence + EVLOG_info << "data field of NotifyCustomerInformation.req contains more than 512 characters - sending another " + "sequence with seqNo" + << seq_no + 1; + this->notify_customer_information_req(data.substr(512), seq_no + 1, request_id); + } +} + void ChargePoint::handle_boot_notification_response(CallResult call_result) { // TODO(piet): B01.FR.06 // TODO(piet): B01.FR.07 @@ -2337,6 +2398,54 @@ void ChargePoint::handle_delete_certificate_req(Call c this->send(call_result); } +void ChargePoint::handle_get_log_req(Call call) { + const GetLogResponse response = this->callbacks.get_log_request_callback(call.msg); + + ocpp::CallResult call_result(response, call.uniqueId); + this->send(call_result); +} + +void ChargePoint::handle_customer_information_req(Call 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; + } + + ocpp::CallResult call_result(response, call.uniqueId); + this->send(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->device_model->get_optional_value(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 = data.substr(0, max_customer_information_data_length); + } + + this->notify_customer_information_req(data, 0, msg.requestId); + } +} + void ChargePoint::handle_data_transfer_req(Call call) { const auto msg = call.msg; DataTransferResponse response; diff --git a/lib/ocpp/v201/ctrlr_component_variables.cpp b/lib/ocpp/v201/ctrlr_component_variables.cpp index eb1a1c7fe..05087535e 100644 --- a/lib/ocpp/v201/ctrlr_component_variables.cpp +++ b/lib/ocpp/v201/ctrlr_component_variables.cpp @@ -200,6 +200,13 @@ const ComponentVariable& WebsocketPongTimeout = { "WebsocketPongTimeout", }), }; +const ComponentVariable& MaxCustomerInformationDataLength = { + ControllerComponents::InternalCtrlr, + std::nullopt, + std::optional({ + "MaxCustomerInformationDataLength", + }), +}; const ComponentVariable& AlignedDataCtrlrEnabled = { ControllerComponents::AlignedDataCtrlr, std::nullopt, From ecc51437f6f62ac5de5426c1f6dcd5fb83957075 Mon Sep 17 00:00:00 2001 From: pietfried Date: Tue, 7 Nov 2023 10:16:02 +0100 Subject: [PATCH 2/6] added minimum of 512 to MaxCustomerInformationDataLength Signed-off-by: pietfried --- config/v201/component_schemas/standardized/InternalCtrlr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/v201/component_schemas/standardized/InternalCtrlr.json b/config/v201/component_schemas/standardized/InternalCtrlr.json index 4708be22f..72bceb646 100644 --- a/config/v201/component_schemas/standardized/InternalCtrlr.json +++ b/config/v201/component_schemas/standardized/InternalCtrlr.json @@ -384,7 +384,7 @@ } ], "description": "Maximum number of characters of Customer Information data", - "minimum": 0, + "minimum": 512, "default": "51200", "type": "integer" } From 64e784915694266792661b7bdb93a69237cde246 Mon Sep 17 00:00:00 2001 From: pietfried Date: Tue, 7 Nov 2023 10:42:39 +0100 Subject: [PATCH 3/6] clang format Signed-off-by: pietfried --- include/ocpp/v201/charge_point.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index 628fde93b..1ed79a5f8 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -128,8 +128,8 @@ struct Callbacks { /// \brief Callback function that can be called to clear customer information based on the given arguments std::optional customer_certificate, - const std::optional id_token, - const std::optional> customer_identifier)>> + const std::optional id_token, + const std::optional> customer_identifier)>> clear_customer_information_callback; }; @@ -304,8 +304,8 @@ class ChargePoint : ocpp::ChargingStationBase { /// field contains a custom identifier other than IdToken and Certificate /// \return customer information std::string get_customer_information(const std::optional customer_certificate, - const std::optional id_token, - const std::optional> customer_identifier); + const std::optional id_token, + const std::optional> customer_identifier); /// \brief Clears customer information based on the given arguments. This function also executes the /// clear_customer_information_callback in case it is present From a6681abf3166a5ef9ad33ef723dc6d5d48ae89e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piet=20G=C3=B6mpel?= <37657534+Pietfried@users.noreply.github.com> Date: Tue, 7 Nov 2023 12:53:41 +0100 Subject: [PATCH 4/6] Update lib/ocpp/v201/charge_point.cpp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: marcemmers <35759328+marcemmers@users.noreply.github.com> Signed-off-by: Piet Gömpel <37657534+Pietfried@users.noreply.github.com> --- lib/ocpp/v201/charge_point.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 721de3374..3c7981aff 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -2439,7 +2439,7 @@ void ChargePoint::handle_customer_information_req(Call 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 = data.substr(0, max_customer_information_data_length); + data.erase(max_customer_information_data_length); } this->notify_customer_information_req(data, 0, msg.requestId); From 930a7a0d442da0aed5788c8b19c66499a9608209 Mon Sep 17 00:00:00 2001 From: pietfried Date: Tue, 7 Nov 2023 13:44:44 +0100 Subject: [PATCH 5/6] changed from recursion to loop Signed-off-by: pietfried --- include/ocpp/v201/charge_point.hpp | 2 +- lib/ocpp/v201/charge_point.cpp | 42 ++++++++++++++---------------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index 1ed79a5f8..ce566ab78 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -351,7 +351,7 @@ class ChargePoint : ocpp::ChargingStationBase { // Functional Block N: Diagnostics void notify_event_req(const std::vector& events); - void notify_customer_information_req(const std::string& data, const int32_t seq_no, const int32_t request_id); + void notify_customer_information_req(const std::string& data, const int32_t request_id); /* OCPP message handlers */ diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 3c7981aff..19e5b26d9 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -1473,27 +1473,25 @@ void ChargePoint::notify_event_req(const std::vector& events) { this->send(call); } -void ChargePoint::notify_customer_information_req(const std::string& data, const int32_t seq_no, - const int32_t request_id) { - const auto req = [&]() { - NotifyCustomerInformationRequest req; - req.data = CiString<512>(data.substr(0, 512)); - req.seqNo = seq_no; - req.requestId = request_id; - req.generatedAt = DateTime(); - req.tbc = data.length() > 512; - return req; - }(); - - ocpp::Call call(req, this->message_queue->createMessageId()); - this->send(call); - - if (data.length() > 512) { - // Recursively send next sequence - EVLOG_info << "data field of NotifyCustomerInformation.req contains more than 512 characters - sending another " - "sequence with seqNo" - << seq_no + 1; - this->notify_customer_information_req(data.substr(512), seq_no + 1, request_id); +void ChargePoint::notify_customer_information_req(const std::string& data, const int32_t request_id) { + size_t pos = 0; + int32_t seq_no = 0; + while (pos < data.length()) { + 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; + }(); + + ocpp::Call call(req, this->message_queue->createMessageId()); + this->send(call); + + pos += 512; + seq_no++; } } @@ -2442,7 +2440,7 @@ void ChargePoint::handle_customer_information_req(Callnotify_customer_information_req(data, 0, msg.requestId); + this->notify_customer_information_req(data, msg.requestId); } } From 0bc72985c54264ccf89a6edaba366621d46e3335 Mon Sep 17 00:00:00 2001 From: pietfried Date: Tue, 7 Nov 2023 16:58:35 +0100 Subject: [PATCH 6/6] changing type from bool to int; allowing pos 0 when data is empty Signed-off-by: pietfried --- lib/ocpp/v201/charge_point.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 19e5b26d9..bdbb5565e 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -1476,7 +1476,7 @@ void ChargePoint::notify_event_req(const std::vector& events) { void ChargePoint::notify_customer_information_req(const std::string& data, const int32_t request_id) { size_t pos = 0; int32_t seq_no = 0; - while (pos < data.length()) { + while (pos < data.length() or pos == 0 && data.empty()) { const auto req = [&]() { NotifyCustomerInformationRequest req; req.data = CiString<512>(data.substr(pos, 512)); @@ -2432,7 +2432,7 @@ void ChargePoint::handle_customer_information_req(Calldevice_model->get_optional_value(ControllerComponentVariables::MaxCustomerInformationDataLength) + this->device_model->get_optional_value(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: "