From 107fbe69d5b7bf86ca2b6e2d3ba6c38c980f45e8 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Tue, 23 Jul 2024 12:48:55 +0200 Subject: [PATCH 01/22] Store some callbacks and other todo's in new branch for implementing pricing for 2.0.1 Signed-off-by: Maaike Zijderveld, iolar --- include/ocpp/v201/charge_point.hpp | 10 +++ lib/ocpp/common/types.cpp | 105 +++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index 5f9d3687d..e5259bab4 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -28,11 +28,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -52,6 +54,7 @@ #include #include #include +#include #include #include #include @@ -186,6 +189,13 @@ struct Callbacks { /// \brief Callback function is called when the websocket connection status changes std::optional> connection_state_changed_callback; + + // TODO mz if one of the three is defined, they must all be defined + std::optional(const GetDisplayMessagesRequest& request /* TODO */)>> get_display_message_callback; + std::optional& display_messages)>> set_display_message_callback; + std::optional> clear_display_message_callback; + + std::optional> set_running_cost; }; /// \brief Combines ChangeAvailabilityRequest with persist flag for scheduled Availability changes diff --git a/lib/ocpp/common/types.cpp b/lib/ocpp/common/types.cpp index 8342b613c..6570ae899 100644 --- a/lib/ocpp/common/types.cpp +++ b/lib/ocpp/common/types.cpp @@ -568,6 +568,111 @@ std::ostream& operator<<(std::ostream& os, const Measurement& k) { return os; } +void from_json(const json& j, RunningCost& c) { + // TODO for 2.0.1, the cost and transactionid are set in CostUpdatedRequest, the remaining is set in the + // 'customData'. Don't forget to add `cost` and `transactionId` to the class as well for 2.0.1!! + + if (j.contains("transactionId")) { + if (j.at("transactionId").is_number()) { + uint32_t transaction_id = j.at("transactionId"); + c.transaction_id = std::to_string(transaction_id); + } else if (j.at("transactionId").is_string()) { + c.transaction_id = j.at("transactionId"); + } else { + j.dump(j.at("transactionId")); + } + } + + if (j.contains("timestamp")) { + c.timestamp = j.at("timestamp"); + } + + if (j.contains("meterValue")) { + c.meter_value = j.at("meterValue"); + } + + if (j.contains("cost")) { + c.cost = j.at("cost"); + } + + if (j.contains("state")) { + c.state = conversions::string_to_running_cost_state(j.at("state")); + } + + if (j.contains("chargingPrice")) { + c.charging_price = j.at("chargingPrice"); + } + + if (j.contains("idlePrice")) { + c.idle_price = j.at("idlePrice"); + } + + if (j.contains("nextPeriod")) { + const json& nextPeriod = j.at("nextPeriod"); + if (nextPeriod.is_object()) { + if (nextPeriod.contains("atTime")) { + c.next_period_at_time = nextPeriod.at("atTime"); + } + + if (nextPeriod.contains("chargingPrice")) { + c.next_period_charging_price = nextPeriod.at("chargingPrice"); + } + + if (nextPeriod.contains("idlePrice")) { + c.next_period_idle_price = nextPeriod.at("idlePrice"); + } + } + } + + if (j.contains("priceText")) { + DisplayMessageContent display_message; + display_message.message = j.at("priceText"); + c.cost_messages = std::vector(); + c.cost_messages->push_back(display_message); + } + + if (j.contains("priceTextExtra")) { + const json& price_text = j.at("priceTextExtra"); + if (!price_text.is_array()) { + EVLOG_warning << "priceTextExtra should be an array, but is not. Content: " << price_text; + } else { + if (!c.cost_messages.has_value()) { + c.cost_messages = std::vector(); + } + for (const json& p : price_text) { + DisplayMessageContent display_message = p; + c.cost_messages->push_back(display_message); + } + } + } + + if (j.contains("qrCodeText")) { + c.qr_code_text = j.at("qrCodeText"); + } + + // TODO mz this is done by OCPP (implement in 2.0.1!!!) + // if (j.contains("triggerMeterValue")) { + // const json& triggerMeterValue = j.at("triggerMeterValue"); + // if (triggerMeterValue.is_object()) { + // if (triggerMeterValue.contains("atTime")) { + // c.trigger_meter_value_at_time = triggerMeterValue.at("atTime"); + // } + + // if (triggerMeterValue.contains("atEnergykWh")) { + // c.trigger_meter_value_at_energy_kwh = triggerMeterValue.at("atEnergykWh"); + // } + + // if (triggerMeterValue.contains("atPowerkW")) { + // c.trigger_meter_value_at_power_kw = triggerMeterValue.at("atPowerkW"); + // } + + // if (triggerMeterValue.contains("atCPStatus")) { + // v16::conversions::string_to_charge_point_status(triggerMeterValue.at("atCPStatus")); + // } + // } + // } +} + namespace conversions { std::string ca_certificate_type_to_string(CaCertificateType e) { switch (e) { From 9b4c513062d87b00984e34b7cf969dd8afcddea5 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Tue, 13 Aug 2024 19:17:03 +0200 Subject: [PATCH 02/22] Start implementing display messages and tariff and cost for ocpp 2.0.1 Signed-off-by: Maaike Zijderveld, iolar --- include/ocpp/v201/charge_point.hpp | 24 ++++++++++--- lib/ocpp/v201/charge_point.cpp | 58 +++++++++++++++++++++++++++++- 2 files changed, 76 insertions(+), 6 deletions(-) diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index f45a48084..e77eb9141 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -196,11 +197,16 @@ struct Callbacks { std::optional> connection_state_changed_callback; // TODO mz if one of the three is defined, they must all be defined - std::optional(const GetDisplayMessagesRequest& request /* TODO */)>> get_display_message_callback; - std::optional& display_messages)>> set_display_message_callback; - std::optional> clear_display_message_callback; - - std::optional> set_running_cost; + /// \brief Callback functions called for get / set / clear display messages + std::optional(const GetDisplayMessagesRequest& request /* TODO */)>> + get_display_message_callback; + std::optional& display_messages)>> + set_display_message_callback; + std::optional> + clear_display_message_callback; + + /// \brief Callback function is called when running cost is set. + std::optional> set_running_cost_callback; }; /// \brief Combines ChangeAvailabilityRequest with persist flag for scheduled Availability changes @@ -734,6 +740,9 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa void handle_change_availability_req(Call call); void handle_heartbeat_response(CallResult call); + // Functional Block I: TariffAndCost + void handle_costupdated_req(Call call); + // Functional Block K: Smart Charging void handle_set_charging_profile_req(Call call); @@ -755,6 +764,11 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa void handle_get_monitoring_report_req(Call call); void handle_clear_variable_monitoring_req(Call call); + // Functional Block O: DisplayMessage + void handle_get_display_message(Call call); + void handle_set_display_message(Call call); + void handle_clear_display_message(Call call); + // Functional Block P: DataTransfer void handle_data_transfer_req(Call call); diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index c534ff48d..0f5588866 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -59,7 +59,19 @@ bool Callbacks::all_callbacks_valid() const { (!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); + this->transaction_event_response_callback.value() != nullptr) and + (!this->clear_display_message_callback.has_value() or + this->clear_display_message_callback.value() != nullptr) and + (!this->get_display_message_callback.has_value() or + this->get_display_message_callback.value() != nullptr) and + (!this->set_display_message_callback.has_value() or this->set_display_message_callback.value()) and + (!this->set_running_cost_callback.has_value() or this->set_running_cost_callback.value() == nullptr) and + // If one of the display message callbacks is implemented, all should be implemented (we do not support half + // implementations) + ((this->set_display_message_callback.has_value() and this->get_display_message_callback.has_value() and + this->clear_display_message_callback.has_value()) or + (!this->set_display_message_callback.has_value() and !this->get_display_message_callback.has_value() and + !this->clear_display_message_callback.has_value())); } ChargePoint::ChargePoint(const std::map& evse_connector_structure, @@ -1353,6 +1365,15 @@ void ChargePoint::handle_message(const EnhancedMessage& messa case MessageType::ClearVariableMonitoring: this->handle_clear_variable_monitoring_req(json_message); break; + case MessageType::GetDisplayMessages: + this->handle_get_display_message(json_message); + break; + case MessageType::SetDisplayMessage: + this->handle_set_display_message(json_message); + break; + case MessageType::ClearDisplayMessage: + this->handle_clear_display_message(json_message); + break; default: if (message.messageTypeId == MessageTypeId::CALL) { const auto call_error = CallError(message.uniqueId, "NotImplemented", "", json({})); @@ -3174,6 +3195,10 @@ void ChargePoint::handle_heartbeat_response(CallResult call) } } +void ChargePoint::handle_costupdated_req(Call call) { + // TODO mz implement +} + void ChargePoint::handle_set_charging_profile_req(Call call) { EVLOG_debug << "Received SetChargingProfileRequest: " << call.msg << "\nwith messageId: " << call.uniqueId; auto msg = call.msg; @@ -3534,6 +3559,37 @@ void ChargePoint::handle_clear_variable_monitoring_req(Callsend(call_result); } +void ChargePoint::handle_get_display_message(Call call) { + GetDisplayMessagesResponse response; + if (!this->callbacks.get_display_message_callback.has_value()) { + response.status = GetDisplayMessagesStatusEnum::Unknown; + ocpp::CallResult call_result(response, call.uniqueId); + this->send(call_result); + } + // TODO mz implement +} + +void ChargePoint::handle_set_display_message(Call call) { + SetDisplayMessageResponse response; + if (!this->callbacks.set_display_message_callback.has_value()) { + response.status = DisplayMessageStatusEnum::Rejected; + ocpp::CallResult call_result(response, call.uniqueId); + this->send(call_result); + } + // TODO mz implement +} + +void ChargePoint::handle_clear_display_message(Call call) { + ClearDisplayMessageResponse response; + if (!this->callbacks.clear_display_message_callback.has_value()) { + EVLOG_error << "Received a clear display message request, but callback is not implemented."; + response.status = ClearMessageStatusEnum::Unknown; + ocpp::CallResult call_result(response, call.uniqueId); + this->send(call_result); + } + // TODO mz implement +} + void ChargePoint::handle_data_transfer_req(Call call) { const auto msg = call.msg; DataTransferResponse response; From 7a0379fc9705d6c255d39305b662ee8404b50f18 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Wed, 14 Aug 2024 18:14:49 +0200 Subject: [PATCH 03/22] Further implement DisplayMessage and TariffAndCost for OCPP 2.0.1 Signed-off-by: Maaike Zijderveld, iolar --- include/ocpp/common/utils.hpp | 3 +- include/ocpp/v201/charge_point.hpp | 2 +- lib/ocpp/common/utils.cpp | 5 +- lib/ocpp/v201/charge_point.cpp | 182 ++++++++++++++++++++++++-- tests/lib/ocpp/common/utils_tests.cpp | 14 ++ 5 files changed, 195 insertions(+), 11 deletions(-) diff --git a/include/ocpp/common/utils.hpp b/include/ocpp/common/utils.hpp index 6dbb18886..e95202b90 100644 --- a/include/ocpp/common/utils.hpp +++ b/include/ocpp/common/utils.hpp @@ -24,9 +24,10 @@ bool is_rfc3339_datetime(const std::string& value); /// \brief Split string on a given character. /// \param string_to_split The string to split. /// \param c The character to split the string on. +/// \param trim True if all strings must be trimmed as well. Defaults to 'false'. /// \return A vector with the string 'segments'. /// -std::vector split_string(const std::string& string_to_split, const char c); +std::vector split_string(const std::string& string_to_split, const char c, const bool trim = false); /// /// \brief Trim string, removing leading and trailing white spaces. diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index e77eb9141..ba6272c83 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -741,7 +741,7 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa void handle_heartbeat_response(CallResult call); // Functional Block I: TariffAndCost - void handle_costupdated_req(Call call); + void handle_costupdated_req(const Call call); // Functional Block K: Smart Charging void handle_set_charging_profile_req(Call call); diff --git a/lib/ocpp/common/utils.cpp b/lib/ocpp/common/utils.cpp index 665abfa85..62d193301 100644 --- a/lib/ocpp/common/utils.cpp +++ b/lib/ocpp/common/utils.cpp @@ -86,12 +86,15 @@ bool is_rfc3339_datetime(const std::string& value) { return std::regex_match(value, datetime_pattern); } -std::vector split_string(const std::string& string_to_split, const char c) { +std::vector 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 result; while (std::getline(input, temp, c)) { + if (trim) { + temp = trim_string(temp); + } result.push_back(temp); } diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 0f5588866..a93a01174 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -2717,6 +2718,35 @@ void ChargePoint::handle_transaction_event_response(const EnhancedMessagecallbacks.transaction_event_response_callback.value()(original_msg, call_result.msg); } + // TODO mz check if tariff and cost is enabled + if (call_result.msg.totalCost.has_value() && this->callbacks.set_running_cost_callback.has_value()) { + RunningCost running_cost; + running_cost.cost = static_cast(call_result.msg.totalCost.value()); + if (original_msg.eventType == TransactionEventEnum::Ended) { + running_cost.state = RunningCostState::Finished; + } else { + running_cost.state = RunningCostState::Charging; + } + + running_cost.transaction_id = original_msg.transactionInfo.transactionId; + // TODO mz add metervalue + // running_cost.meter_value = original_msg.meterValue; + running_cost.timestamp = original_msg.timestamp; + + // TODO mz make conversion function of this?? + if (call_result.msg.updatedPersonalMessage.has_value()) { + MessageContent personal_message = call_result.msg.updatedPersonalMessage.value(); + running_cost.cost_messages = std::vector(); + DisplayMessageContent message; + message.message = personal_message.content; + message.language = personal_message.language; + message.message_format = personal_message.format; + running_cost.cost_messages->push_back(message); + } + + this->callbacks.set_running_cost_callback.value()(running_cost); + } + if (original_msg.eventType == TransactionEventEnum::Ended) { // nothing to do for TransactionEventEnum::Ended return; @@ -3195,8 +3225,23 @@ void ChargePoint::handle_heartbeat_response(CallResult call) } } -void ChargePoint::handle_costupdated_req(Call call) { - // TODO mz implement +void ChargePoint::handle_costupdated_req(const Call call) { + CostUpdatedResponse response; + ocpp::CallResult call_result(response, call.uniqueId); + + // TODO check if this is enabled in settings (device model)??? + if (!this->callbacks.set_running_cost_callback.has_value()) { + this->send(call_result); + return; + } + + RunningCost running_cost; + running_cost.cost = static_cast(call.msg.totalCost); + running_cost.transaction_id = call.msg.transactionId; + running_cost.state = RunningCostState::Charging; + this->callbacks.set_running_cost_callback.value()(running_cost); + + this->send(call_result); } void ChargePoint::handle_set_charging_profile_req(Call call) { @@ -3559,27 +3604,145 @@ void ChargePoint::handle_clear_variable_monitoring_req(Callsend(call_result); } -void ChargePoint::handle_get_display_message(Call call) { +// TODO mz move somewhere else?? +std::optional 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.transaction_id; + + // Note: component is (not yet?) supported for display messages in libocpp. + + return info; +} + +// TODO mz move somewhere else? +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.transaction_id = message_info.transactionId; + display_message.message.message = message_info.message.content; + display_message.message.message_format = message_info.message.format; + display_message.message.language = message_info.message.language; + + return display_message; +} + +void ChargePoint::handle_get_display_message(const Call call) { GetDisplayMessagesResponse response; if (!this->callbacks.get_display_message_callback.has_value()) { response.status = GetDisplayMessagesStatusEnum::Unknown; ocpp::CallResult call_result(response, call.uniqueId); this->send(call_result); + return; + } + + // Call 'get display message callback' to get all display messages from the charging station. + const std::vector display_messages = this->callbacks.get_display_message_callback.value()(call.msg); + + NotifyDisplayMessagesRequest messages_request; + messages_request.requestId = call.msg.requestId; + messages_request.messageInfo = std::vector(); + // 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 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. + if (messages_request.messageInfo.value().empty()) { + response.status = GetDisplayMessagesStatusEnum::Unknown; + ocpp::CallResult call_result(response, call.uniqueId); + this->send(call_result); + return; } - // TODO mz implement + + // 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). + ocpp::Call request(messages_request, this->message_queue->createMessageId()); + this->send(request); } -void ChargePoint::handle_set_display_message(Call call) { +void ChargePoint::handle_set_display_message(const Call call) { SetDisplayMessageResponse response; if (!this->callbacks.set_display_message_callback.has_value()) { response.status = DisplayMessageStatusEnum::Rejected; ocpp::CallResult call_result(response, call.uniqueId); this->send(call_result); + return; + } + + // 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. + // Can not check if state is supported here, as libocpp does not have that information (TODO mz toevoegen aan device + // model?). + const std::optional display_message_available = + this->device_model->get_optional_value(ControllerComponentVariables::DisplayMessageCtrlrAvailable); + const std::string supported_priorities = + this->device_model->get_value(ControllerComponentVariables::DisplayMessageSupportedPriorities); + const std::string supported_message_formats = + this->device_model->get_value(ControllerComponentVariables::DisplayMessageSupportedFormats); + const std::vector priorities = split_string(supported_priorities, ',', true); + const std::vector 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() || + get_transaction_evseid(call.msg.message.transactionId.value()) != std::nullopt); + + if ( // Check if display messages are available + !display_message_available.has_value() || !display_message_available.value() || + // Check if priority is supported + supported_priority_it == priorities.end() || + // Check if format is supported + supported_format_it == formats.end() || + // Check if transaction is running + !transaction_valid) { + response.status = DisplayMessageStatusEnum::Rejected; + ocpp::CallResult call_result(response, call.uniqueId); + this->send(call_result); + return; } - // TODO mz implement + + const DisplayMessage message = message_info_to_display_message(call.msg.message); + response = this->callbacks.set_display_message_callback.value()({message}); + ocpp::CallResult call_result(response, call.uniqueId); + this->send(call_result); } -void ChargePoint::handle_clear_display_message(Call call) { +void ChargePoint::handle_clear_display_message(const Call call) { ClearDisplayMessageResponse response; if (!this->callbacks.clear_display_message_callback.has_value()) { EVLOG_error << "Received a clear display message request, but callback is not implemented."; @@ -3587,7 +3750,10 @@ void ChargePoint::handle_clear_display_message(Call ocpp::CallResult call_result(response, call.uniqueId); this->send(call_result); } - // TODO mz implement + + response = this->callbacks.clear_display_message_callback.value()(call.msg); + ocpp::CallResult call_result(response, call.uniqueId); + this->send(call_result); } void ChargePoint::handle_data_transfer_req(Call call) { diff --git a/tests/lib/ocpp/common/utils_tests.cpp b/tests/lib/ocpp/common/utils_tests.cpp index 09410196d..057d4b9ab 100644 --- a/tests/lib/ocpp/common/utils_tests.cpp +++ b/tests/lib/ocpp/common/utils_tests.cpp @@ -64,6 +64,20 @@ TEST(Utils, test_split_string) { ASSERT_EQ(result.size(), 2); EXPECT_EQ(result.at(0), "This is a test"); EXPECT_EQ(result.at(1), " It is performed using google test"); + + result = split_string("Aa, Bb, Cc, Dd", ',', false); + ASSERT_EQ(result.size(), 4); + EXPECT_EQ(result.at(0), "Aa"); + EXPECT_EQ(result.at(1), " Bb"); + EXPECT_EQ(result.at(2), " Cc"); + EXPECT_EQ(result.at(3), " Dd"); + + result = split_string("Aa, Bb, Cc, Dd", ',', true); + ASSERT_EQ(result.size(), 4); + EXPECT_EQ(result.at(0), "Aa"); + EXPECT_EQ(result.at(1), "Bb"); + EXPECT_EQ(result.at(2), "Cc"); + EXPECT_EQ(result.at(3), "Dd"); } TEST(Utils, test_trim_string) { From 8943e980c83f84305b52c86420fcfa9f15cd43a1 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Thu, 15 Aug 2024 18:06:49 +0200 Subject: [PATCH 04/22] Add SupportedState to DisplayMessageCtrlr. Move callbacks to new files and add more logging in the callback validate function. Implement cost and tariff from transactioneventresponse. Signed-off-by: Maaike Zijderveld, iolar --- .../standardized/DisplayMessageCtrlr.json | 17 + include/ocpp/v201/charge_point.hpp | 139 +------- include/ocpp/v201/charge_point_callbacks.hpp | 152 +++++++++ .../ocpp/v201/ctrlr_component_variables.hpp | 1 + include/ocpp/v201/device_model.hpp | 6 +- lib/CMakeLists.txt | 1 + lib/ocpp/v201/charge_point.cpp | 305 ++++++++++-------- lib/ocpp/v201/charge_point_callbacks.cpp | 82 +++++ lib/ocpp/v201/ctrlr_component_variables.cpp | 3 + lib/ocpp/v201/device_model.cpp | 2 +- tests/lib/ocpp/v201/test_charge_point.cpp | 84 ++--- 11 files changed, 487 insertions(+), 305 deletions(-) create mode 100644 include/ocpp/v201/charge_point_callbacks.hpp create mode 100644 lib/ocpp/v201/charge_point_callbacks.cpp diff --git a/config/v201/component_schemas/standardized/DisplayMessageCtrlr.json b/config/v201/component_schemas/standardized/DisplayMessageCtrlr.json index 77546946d..1451ba922 100644 --- a/config/v201/component_schemas/standardized/DisplayMessageCtrlr.json +++ b/config/v201/component_schemas/standardized/DisplayMessageCtrlr.json @@ -94,6 +94,23 @@ ], "description": "List of the priorities supported by this Charging Station.", "type": "string" + }, + "DisplayMessageSupportedStates": { + "variable_name": "SupportedStates", + "characteristics": { + "valuesList": "Charging,Faulted,Idle,Unavailable", + "supportsMonitoring": true, + "dataType": "MemberList" + }, + "attributes": [ + { + "type": "Actual", + "mutability": "ReadOnly" + } + ], + "description": "List of the priorities supported by this Charging Station.", + "type": "string", + "default": "Charging,Faulted,Idle,Unavailable" } }, "required": [ diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index ba6272c83..bd446d7c1 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -79,135 +80,7 @@ class UnexpectedMessageTypeFromCSMS : public std::runtime_error { using std::runtime_error::runtime_error; }; -struct Callbacks { - ///\brief Function to check if the callback struct is completely filled. All std::functions should hold a function, - /// all std::optional should either be empty or hold a function. - /// - ///\retval false if any of the normal callbacks are nullptr or any of the optional ones are filled with a nullptr - /// true otherwise - bool all_callbacks_valid() const; - - /// - /// \brief Callback if reset is allowed. If evse_id has a value, reset only applies to the given evse id. If it has - /// no value, applies to complete charging station. - /// - std::function evse_id, const ResetEnum& reset_type)> - is_reset_allowed_callback; - std::function evse_id, const ResetEnum& reset_type)> reset_callback; - std::function stop_transaction_callback; - std::function pause_charging_callback; - - /// \brief Used to notify the user of libocpp that the Operative/Inoperative state of the charging station changed - /// If as a result the state of EVSEs or connectors changed as well, libocpp will additionally call the - /// evse_effective_operative_status_changed_callback once for each EVSE whose status changed, and - /// connector_effective_operative_status_changed_callback once for each connector whose status changed. - /// If left empty, the callback is ignored. - /// \param new_status The operational status the CS switched to - std::optional> - cs_effective_operative_status_changed_callback; - - /// \brief Used to notify the user of libocpp that the Operative/Inoperative state of an EVSE changed - /// If as a result the state of connectors changed as well, libocpp will additionally call the - /// connector_effective_operative_status_changed_callback once for each connector whose status changed. - /// If left empty, the callback is ignored. - /// \param evse_id The id of the EVSE - /// \param new_status The operational status the EVSE switched to - std::optional> - evse_effective_operative_status_changed_callback; - - /// \brief Used to notify the user of libocpp that the Operative/Inoperative state of a connector changed. - /// \param evse_id The id of the EVSE - /// \param connector_id The ID of the connector within the EVSE - /// \param new_status The operational status the connector switched to - std::function - connector_effective_operative_status_changed_callback; - - std::function get_log_request_callback; - std::function unlock_connector_callback; - // callback to be called when the request can be accepted. authorize_remote_start indicates if Authorize.req needs - // to follow or not - std::function - remote_start_transaction_callback; - /// - /// \brief Check if the current reservation for the given evse id is made for the id token / group id token. - /// \return True if evse is reserved for the given id token / group id token, false if it is reserved for another - /// one. - /// - std::function idToken, - const std::optional> groupIdToken)> - is_reservation_for_token_callback; - std::function update_firmware_request_callback; - // callback to be called when a variable has been changed by the CSMS - std::optional> variable_changed_callback; - // callback is called when receiving a SetNetworkProfile.req from the CSMS - std::optional> - validate_network_profile_callback; - std::optional> - configure_network_connection_profile_callback; - std::optional> time_sync_callback; - - /// \brief callback to be called to congfigure ocpp message logging - std::optional> ocpp_messages_callback; - /// - /// \brief callback function that can be used to react to a security event callback. This callback is - /// called only if the SecurityEvent occured internally within libocpp - /// Typically this callback is used to log security events in the security log - /// - std::function& event_type, const std::optional>& tech_info)> - security_event_callback; - - /// \brief Callback for indicating when a charging profile is received and was accepted. - std::function set_charging_profiles_callback; - - /// \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 Callback function that can be called when all connectors are unavailable - std::optional> all_connectors_unavailable_callback; - - /// \brief Callback function that can be used to handle arbitrary data transfers for all vendorId and - /// messageId - std::optional> data_transfer_callback; - - /// \brief Callback function that is called when a transaction_event was sent to the CSMS - std::optional> transaction_event_callback; - - /// \brief Callback function that is called when a transaction_event_response was received from the CSMS - std::optional> - transaction_event_response_callback; - - /// \brief Callback function is called when the websocket connection status changes - std::optional> connection_state_changed_callback; - - // TODO mz if one of the three is defined, they must all be defined - /// \brief Callback functions called for get / set / clear display messages - std::optional(const GetDisplayMessagesRequest& request /* TODO */)>> - get_display_message_callback; - std::optional& display_messages)>> - set_display_message_callback; - std::optional> - clear_display_message_callback; - - /// \brief Callback function is called when running cost is set. - std::optional> set_running_cost_callback; -}; /// \brief Combines ChangeAvailabilityRequest with persist flag for scheduled Availability changes struct AvailabilityChange { @@ -662,6 +535,16 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa /// @param message_log_path path to file logging void configure_message_logging_format(const std::string& message_log_path); + /// + /// \brief Create cost and / or tariff message and call the callbacks to send it, if tariff and / or cost is + /// enabled. + /// \param response The TransactionEventResponse where the tariff and cost information is added to. + /// \param original_message The original TransactionEventRequest, which contains some information we need as + /// well. + /// + void handle_cost_and_tariff(const TransactionEventResponse& response, + const TransactionEventRequest& original_message); + /* OCPP message requests */ // Functional Block A: Security diff --git a/include/ocpp/v201/charge_point_callbacks.hpp b/include/ocpp/v201/charge_point_callbacks.hpp new file mode 100644 index 000000000..5ad71a1ce --- /dev/null +++ b/include/ocpp/v201/charge_point_callbacks.hpp @@ -0,0 +1,152 @@ +#pragma once + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace ocpp::v201 +{ +struct Callbacks { + /// \brief Function to check if the callback struct is completely filled. All std::functions should hold a function, + /// all std::optional should either be empty or hold a function. + /// \param device_model The device model, to check if certain modules are enabled / available. + /// + /// \retval false if any of the normal callbacks are nullptr or any of the optional ones are filled with a nullptr + /// true otherwise + bool all_callbacks_valid(std::shared_ptr device_model) const; + + /// + /// \brief Callback if reset is allowed. If evse_id has a value, reset only applies to the given evse id. If it has + /// no value, applies to complete charging station. + /// + std::function evse_id, const ResetEnum& reset_type)> + is_reset_allowed_callback; + std::function evse_id, const ResetEnum& reset_type)> reset_callback; + std::function stop_transaction_callback; + std::function pause_charging_callback; + + /// \brief Used to notify the user of libocpp that the Operative/Inoperative state of the charging station changed + /// If as a result the state of EVSEs or connectors changed as well, libocpp will additionally call the + /// evse_effective_operative_status_changed_callback once for each EVSE whose status changed, and + /// connector_effective_operative_status_changed_callback once for each connector whose status changed. + /// If left empty, the callback is ignored. + /// \param new_status The operational status the CS switched to + std::optional> + cs_effective_operative_status_changed_callback; + + /// \brief Used to notify the user of libocpp that the Operative/Inoperative state of an EVSE changed + /// If as a result the state of connectors changed as well, libocpp will additionally call the + /// connector_effective_operative_status_changed_callback once for each connector whose status changed. + /// If left empty, the callback is ignored. + /// \param evse_id The id of the EVSE + /// \param new_status The operational status the EVSE switched to + std::optional> + evse_effective_operative_status_changed_callback; + + /// \brief Used to notify the user of libocpp that the Operative/Inoperative state of a connector changed. + /// \param evse_id The id of the EVSE + /// \param connector_id The ID of the connector within the EVSE + /// \param new_status The operational status the connector switched to + std::function + connector_effective_operative_status_changed_callback; + + std::function get_log_request_callback; + std::function unlock_connector_callback; + // callback to be called when the request can be accepted. authorize_remote_start indicates if Authorize.req needs + // to follow or not + std::function + remote_start_transaction_callback; + /// + /// \brief Check if the current reservation for the given evse id is made for the id token / group id token. + /// \return True if evse is reserved for the given id token / group id token, false if it is reserved for another + /// one. + /// + std::function idToken, + const std::optional> groupIdToken)> + is_reservation_for_token_callback; + std::function update_firmware_request_callback; + // callback to be called when a variable has been changed by the CSMS + std::optional> variable_changed_callback; + // callback is called when receiving a SetNetworkProfile.req from the CSMS + std::optional> + validate_network_profile_callback; + std::optional> + configure_network_connection_profile_callback; + std::optional> time_sync_callback; + + /// \brief callback to be called to congfigure ocpp message logging + std::optional> ocpp_messages_callback; + + /// + /// \brief callback function that can be used to react to a security event callback. This callback is + /// called only if the SecurityEvent occured internally within libocpp + /// Typically this callback is used to log security events in the security log + /// + std::function& event_type, const std::optional>& tech_info)> + security_event_callback; + + /// \brief Callback for indicating when a charging profile is received and was accepted. + std::function set_charging_profiles_callback; + + /// \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 Callback function that can be called when all connectors are unavailable + std::optional> all_connectors_unavailable_callback; + + /// \brief Callback function that can be used to handle arbitrary data transfers for all vendorId and + /// messageId + std::optional> data_transfer_callback; + + /// \brief Callback function that is called when a transaction_event was sent to the CSMS + std::optional> transaction_event_callback; + + /// \brief Callback function that is called when a transaction_event_response was received from the CSMS + std::optional> + transaction_event_response_callback; + + /// \brief Callback function is called when the websocket connection status changes + std::optional> connection_state_changed_callback; + + // TODO mz if one of the three is defined, they must all be defined + /// \brief Callback functions called for get / set / clear display messages + std::optional(const GetDisplayMessagesRequest& request /* TODO */)>> + get_display_message_callback; + std::optional& display_messages)>> + set_display_message_callback; + std::optional> + clear_display_message_callback; + + /// \brief Callback function is called when running cost is set. + std::optional> set_running_cost_callback; +}; +} diff --git a/include/ocpp/v201/ctrlr_component_variables.hpp b/include/ocpp/v201/ctrlr_component_variables.hpp index 677b5c015..43b7f3ffe 100644 --- a/include/ocpp/v201/ctrlr_component_variables.hpp +++ b/include/ocpp/v201/ctrlr_component_variables.hpp @@ -131,6 +131,7 @@ extern const ComponentVariable& DisplayMessageCtrlrAvailable; extern const RequiredComponentVariable& NumberOfDisplayMessages; extern const RequiredComponentVariable& DisplayMessageSupportedFormats; extern const RequiredComponentVariable& DisplayMessageSupportedPriorities; +extern const ComponentVariable& DisplayMessageSupportedStates; extern const ComponentVariable& CentralContractValidationAllowed; extern const RequiredComponentVariable& ContractValidationOffline; extern const ComponentVariable& RequestMeteringReceipt; diff --git a/include/ocpp/v201/device_model.hpp b/include/ocpp/v201/device_model.hpp index 8c8468956..586f57f68 100644 --- a/include/ocpp/v201/device_model.hpp +++ b/include/ocpp/v201/device_model.hpp @@ -105,7 +105,7 @@ class DeviceModel { /// \return GetVariableStatusEnum that indicates the result of the request GetVariableStatusEnum request_value_internal(const Component& component_id, const Variable& variable_id, const AttributeEnum& attribute_enum, std::string& value, - bool allow_write_only); + bool allow_write_only) const; /// \brief Iterates over the given \p component_criteria and converts this to the variable names /// (Active,Available,Enabled,Problem). If any of the variables can not be found as part of a component this @@ -139,7 +139,7 @@ class DeviceModel { /// \return the requested value from the device model storage template T get_value(const RequiredComponentVariable& component_variable, - const AttributeEnum& attribute_enum = AttributeEnum::Actual) { + const AttributeEnum& attribute_enum = AttributeEnum::Actual) const { std::string value; auto response = GetVariableStatusEnum::UnknownVariable; if (component_variable.variable.has_value()) { @@ -165,7 +165,7 @@ class DeviceModel { /// requested from the storage and a value is present for this combination, else std::nullopt . template std::optional get_optional_value(const ComponentVariable& component_variable, - const AttributeEnum& attribute_enum = AttributeEnum::Actual) { + const AttributeEnum& attribute_enum = AttributeEnum::Actual) const { std::string value; auto response = GetVariableStatusEnum::UnknownVariable; if (component_variable.variable.has_value()) { diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 61e2153d8..88e6d83e9 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -55,6 +55,7 @@ if(LIBOCPP_ENABLE_V201) PRIVATE ocpp/v201/average_meter_values.cpp ocpp/v201/charge_point.cpp + ocpp/v201/charge_point_callbacks.cpp ocpp/v201/smart_charging.cpp ocpp/v201/connector.cpp ocpp/v201/ctrlr_component_variables.cpp diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index a93a01174..761227d94 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -31,49 +31,9 @@ const auto WEBSOCKET_INIT_DELAY = std::chrono::seconds(2); const auto DEFAULT_MESSAGE_QUEUE_SIZE_THRESHOLD = 2E5; const auto DEFAULT_MAX_MESSAGE_SIZE = 65000; -bool Callbacks::all_callbacks_valid() const { - return 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) and - (!this->clear_display_message_callback.has_value() or - this->clear_display_message_callback.value() != nullptr) and - (!this->get_display_message_callback.has_value() or - this->get_display_message_callback.value() != nullptr) and - (!this->set_display_message_callback.has_value() or this->set_display_message_callback.value()) and - (!this->set_running_cost_callback.has_value() or this->set_running_cost_callback.value() == nullptr) and - // If one of the display message callbacks is implemented, all should be implemented (we do not support half - // implementations) - ((this->set_display_message_callback.has_value() and this->get_display_message_callback.has_value() and - this->clear_display_message_callback.has_value()) or - (!this->set_display_message_callback.has_value() and !this->get_display_message_callback.has_value() and - !this->clear_display_message_callback.has_value())); -} +static DisplayMessageContent message_content_to_display_message_content(const MessageContent& message_content); +static std::optional display_message_to_message_info_type(const DisplayMessage& display_message); +static DisplayMessage message_info_to_display_message(const MessageInfo& message_info); ChargePoint::ChargePoint(const std::map& evse_connector_structure, const std::string& device_model_storage_address, const bool initialize_device_model, @@ -125,7 +85,7 @@ ChargePoint::ChargePoint(const std::map& evse_connector_struct callbacks(callbacks), stop_auth_cache_cleanup_handler(false) { // Make sure the received callback struct is completely filled early before we actually start running - if (!this->callbacks.all_callbacks_valid()) { + if (!this->callbacks.all_callbacks_valid(this->device_model)) { EVLOG_AND_THROW(std::invalid_argument("All non-optional callbacks must be supplied")); } @@ -658,6 +618,66 @@ void ChargePoint::configure_message_logging_format(const std::string& message_lo } } +void ChargePoint::handle_cost_and_tariff(const TransactionEventResponse& response, + const TransactionEventRequest& original_message) { + const bool tariff_enabled = + device_model->get_optional_value(ControllerComponentVariables::TariffCostCtrlrAvailableTariff) + .value_or(false) && + device_model->get_optional_value(ControllerComponentVariables::TariffCostCtrlrEnabledTariff) + .value_or(false); + + const bool cost_enabled = + device_model->get_optional_value(ControllerComponentVariables::TariffCostCtrlrAvailableCost) + .value_or(false) && + device_model->get_optional_value(ControllerComponentVariables::TariffCostCtrlrEnabledCost) + .value_or(false); + + std::vector cost_messages; + + // Check if there is a tariff message and if 'Tariff' is available and enabled + if (response.updatedPersonalMessage.has_value() && tariff_enabled) { + MessageContent personal_message = response.updatedPersonalMessage.value(); + DisplayMessageContent message = message_content_to_display_message_content(personal_message); + cost_messages.push_back(message); + + // If cost is enabled, the message will be sent to the running cost callback. But if it is not enabled, the + // tariff message will be sent using the display message callback. + if (!cost_enabled && this->callbacks.set_display_message_callback.has_value() && + this->callbacks.set_display_message_callback != nullptr) { + DisplayMessage display_message; + display_message.message = message; + display_message.transaction_id = original_message.transactionInfo.transactionId; + this->callbacks.set_display_message_callback.value()({display_message}); + } + } + + // Check if cost is available and enabled, and if there is a totalcost message. + if (cost_enabled && response.totalCost.has_value() && this->callbacks.set_running_cost_callback.has_value()) { + RunningCost running_cost; + running_cost.cost = static_cast(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()) { + // running_cost.meter_value = original_message.meterValue; + // TODO mz add metervalue + } + + running_cost.timestamp = original_message.timestamp; + + if (tariff_enabled && !cost_messages.empty()) { + running_cost.cost_messages = cost_messages; + } + + this->callbacks.set_running_cost_callback.value()(running_cost); + } +} + void ChargePoint::on_unavailable(const int32_t evse_id, const int32_t connector_id) { this->evse_manager->get_evse(evse_id).submit_event(connector_id, ConnectorEvent::Unavailable); } @@ -2718,34 +2738,7 @@ void ChargePoint::handle_transaction_event_response(const EnhancedMessagecallbacks.transaction_event_response_callback.value()(original_msg, call_result.msg); } - // TODO mz check if tariff and cost is enabled - if (call_result.msg.totalCost.has_value() && this->callbacks.set_running_cost_callback.has_value()) { - RunningCost running_cost; - running_cost.cost = static_cast(call_result.msg.totalCost.value()); - if (original_msg.eventType == TransactionEventEnum::Ended) { - running_cost.state = RunningCostState::Finished; - } else { - running_cost.state = RunningCostState::Charging; - } - - running_cost.transaction_id = original_msg.transactionInfo.transactionId; - // TODO mz add metervalue - // running_cost.meter_value = original_msg.meterValue; - running_cost.timestamp = original_msg.timestamp; - - // TODO mz make conversion function of this?? - if (call_result.msg.updatedPersonalMessage.has_value()) { - MessageContent personal_message = call_result.msg.updatedPersonalMessage.value(); - running_cost.cost_messages = std::vector(); - DisplayMessageContent message; - message.message = personal_message.content; - message.language = personal_message.language; - message.message_format = personal_message.format; - running_cost.cost_messages->push_back(message); - } - - this->callbacks.set_running_cost_callback.value()(running_cost); - } + this->handle_cost_and_tariff(call_result.msg, original_msg); if (original_msg.eventType == TransactionEventEnum::Ended) { // nothing to do for TransactionEventEnum::Ended @@ -3604,55 +3597,6 @@ void ChargePoint::handle_clear_variable_monitoring_req(Callsend(call_result); } -// TODO mz move somewhere else?? -std::optional 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.transaction_id; - - // Note: component is (not yet?) supported for display messages in libocpp. - - return info; -} - -// TODO mz move somewhere else? -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.transaction_id = message_info.transactionId; - display_message.message.message = message_info.message.content; - display_message.message.message_format = message_info.message.format; - display_message.message.language = message_info.message.language; - - return display_message; -} - void ChargePoint::handle_get_display_message(const Call call) { GetDisplayMessagesResponse response; if (!this->callbacks.get_display_message_callback.has_value()) { @@ -3703,34 +3647,63 @@ void ChargePoint::handle_set_display_message(const Call display_message_available = this->device_model->get_optional_value(ControllerComponentVariables::DisplayMessageCtrlrAvailable); const std::string supported_priorities = this->device_model->get_value(ControllerComponentVariables::DisplayMessageSupportedPriorities); const std::string supported_message_formats = this->device_model->get_value(ControllerComponentVariables::DisplayMessageSupportedFormats); + const std::vector priorities = split_string(supported_priorities, ',', true); const std::vector 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() || get_transaction_evseid(call.msg.message.transactionId.value()) != std::nullopt); - if ( // Check if display messages are available - !display_message_available.has_value() || !display_message_available.value() || - // Check if priority is supported - supported_priority_it == priorities.end() || - // Check if format is supported - supported_format_it == formats.end() || - // Check if transaction is running - !transaction_valid) { + // Check if display messages are available. + if (!display_message_available.has_value() || !display_message_available.value()) { + 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 supported_states = this->device_model->get_optional_value( + ControllerComponentVariables::DisplayMessageSupportedStates); + if (supported_states.has_value()) { + const std::vector 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) { ocpp::CallResult call_result(response, call.uniqueId); this->send(call_result); return; @@ -4154,5 +4127,75 @@ ChargePoint::set_variables(const std::vector& set_variable_data return response; } +// Static functions + +/// +/// \brief Convert message content from OCPP spec to DisplayMessageContent. +/// \param message_content The struct to convert. +/// \return The converted struct. +/// +static 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. +/// +static std::optional 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.transaction_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. +/// +static 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.transaction_id = message_info.transactionId; + display_message.message = message_content_to_display_message_content(message_info.message); + + return display_message; +} + } // namespace v201 } // namespace ocpp diff --git a/lib/ocpp/v201/charge_point_callbacks.cpp b/lib/ocpp/v201/charge_point_callbacks.cpp new file mode 100644 index 000000000..a3d27e735 --- /dev/null +++ b/lib/ocpp/v201/charge_point_callbacks.cpp @@ -0,0 +1,82 @@ +#include + +#include + +namespace ocpp::v201 { + +bool Callbacks::all_callbacks_valid(std::shared_ptr device_model) 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(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(ControllerComponentVariables::TariffCostCtrlrAvailableCost) + .value_or(false) && + device_model->get_optional_value(ControllerComponentVariables::TariffCostCtrlrEnabledCost) + .value_or(false)) { + if (!this->set_running_cost_callback.has_value() || this->set_running_cost_callback.value() == nullptr) { + EVLOG_error << "TariffAndCost controller 'Cost' is set to 'Available' and 'Enabled' in device model, " + "but callback is not implemented"; + valid = false; + } + } + + if (device_model->get_optional_value(ControllerComponentVariables::TariffCostCtrlrAvailableTariff) + .value_or(false) && + device_model->get_optional_value(ControllerComponentVariables::TariffCostCtrlrEnabledTariff) + .value_or(false)) { + if (!this->set_display_message_callback.has_value() || + this->set_display_message_callback.value() == nullptr) { + EVLOG_error + << "TariffAndCost controller 'Tariff' is set to 'Available' and 'Enabled'. In this case, the " + "set_display_message_callback must be implemented to send the tariff, but it is not"; + valid = false; + } + } + } + + return valid; +} +} // namespace ocpp::v201 diff --git a/lib/ocpp/v201/ctrlr_component_variables.cpp b/lib/ocpp/v201/ctrlr_component_variables.cpp index 074f11aaf..4f007ef6b 100644 --- a/lib/ocpp/v201/ctrlr_component_variables.cpp +++ b/lib/ocpp/v201/ctrlr_component_variables.cpp @@ -658,6 +658,9 @@ const RequiredComponentVariable& DisplayMessageSupportedPriorities = { "SupportedPriorities", }), }; +const ComponentVariable& DisplayMessageSupportedStates = {ControllerComponents::DisplayMessageCtrlr, std::nullopt, + std::optional({"SupportedStates"})}; + const ComponentVariable& CentralContractValidationAllowed = { ControllerComponents::ISO15118Ctrlr, std::nullopt, diff --git a/lib/ocpp/v201/device_model.cpp b/lib/ocpp/v201/device_model.cpp index 86d43b527..d22b7505b 100644 --- a/lib/ocpp/v201/device_model.cpp +++ b/lib/ocpp/v201/device_model.cpp @@ -218,7 +218,7 @@ bool include_in_summary_inventory(const ComponentVariable& cv, const VariableAtt GetVariableStatusEnum DeviceModel::request_value_internal(const Component& component_id, const Variable& variable_id, const AttributeEnum& attribute_enum, std::string& value, - bool allow_write_only) { + bool allow_write_only) const { const auto component_it = this->device_model.find(component_id); if (component_it == this->device_model.end()) { EVLOG_debug << "unknown component in " << component_id.name << "." << variable_id.name; diff --git a/tests/lib/ocpp/v201/test_charge_point.cpp b/tests/lib/ocpp/v201/test_charge_point.cpp index 1ae57a57d..f94dcd119 100644 --- a/tests/lib/ocpp/v201/test_charge_point.cpp +++ b/tests/lib/ocpp/v201/test_charge_point.cpp @@ -265,7 +265,7 @@ TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfSetChargingProfilesC configure_callbacks_with_mocks(); callbacks.set_charging_profiles_callback = nullptr; - EXPECT_FALSE(callbacks.all_callbacks_valid()); + EXPECT_FALSE(callbacks.all_callbacks_valid(device_model)); } /* @@ -274,113 +274,113 @@ TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfSetChargingProfilesC */ TEST_F(ChargePointFixture, K01FR02_CallbacksAreInvalidWhenNotProvided) { - EXPECT_FALSE(callbacks.all_callbacks_valid()); + EXPECT_FALSE(callbacks.all_callbacks_valid(device_model)); } TEST_F(ChargePointFixture, K01FR02_CallbacksAreValidWhenAllRequiredCallbacksProvided) { configure_callbacks_with_mocks(); - EXPECT_TRUE(callbacks.all_callbacks_valid()); + EXPECT_TRUE(callbacks.all_callbacks_valid(device_model)); } TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfResetIsAllowedCallbackExists) { configure_callbacks_with_mocks(); callbacks.is_reset_allowed_callback = nullptr; - EXPECT_FALSE(callbacks.all_callbacks_valid()); + EXPECT_FALSE(callbacks.all_callbacks_valid(device_model)); } TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfResetCallbackExists) { configure_callbacks_with_mocks(); callbacks.reset_callback = nullptr; - EXPECT_FALSE(callbacks.all_callbacks_valid()); + EXPECT_FALSE(callbacks.all_callbacks_valid(device_model)); } TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfStopTransactionCallbackExists) { configure_callbacks_with_mocks(); callbacks.stop_transaction_callback = nullptr; - EXPECT_FALSE(callbacks.all_callbacks_valid()); + EXPECT_FALSE(callbacks.all_callbacks_valid(device_model)); } TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfPauseChargingCallbackExists) { configure_callbacks_with_mocks(); callbacks.pause_charging_callback = nullptr; - EXPECT_FALSE(callbacks.all_callbacks_valid()); + EXPECT_FALSE(callbacks.all_callbacks_valid(device_model)); } TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfConnectorEffectiveOperativeStatusChangedCallbackExists) { configure_callbacks_with_mocks(); callbacks.connector_effective_operative_status_changed_callback = nullptr; - EXPECT_FALSE(callbacks.all_callbacks_valid()); + EXPECT_FALSE(callbacks.all_callbacks_valid(device_model)); } TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfGetLogRequestCallbackExists) { configure_callbacks_with_mocks(); callbacks.get_log_request_callback = nullptr; - EXPECT_FALSE(callbacks.all_callbacks_valid()); + EXPECT_FALSE(callbacks.all_callbacks_valid(device_model)); } TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfUnlockConnectorCallbackExists) { configure_callbacks_with_mocks(); callbacks.unlock_connector_callback = nullptr; - EXPECT_FALSE(callbacks.all_callbacks_valid()); + EXPECT_FALSE(callbacks.all_callbacks_valid(device_model)); } TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfRemoteStartTransactionCallbackExists) { configure_callbacks_with_mocks(); callbacks.remote_start_transaction_callback = nullptr; - EXPECT_FALSE(callbacks.all_callbacks_valid()); + EXPECT_FALSE(callbacks.all_callbacks_valid(device_model)); } TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfIsReservationForTokenCallbackExists) { configure_callbacks_with_mocks(); callbacks.is_reservation_for_token_callback = nullptr; - EXPECT_FALSE(callbacks.all_callbacks_valid()); + EXPECT_FALSE(callbacks.all_callbacks_valid(device_model)); } TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfUpdateFirmwareRequestCallbackExists) { configure_callbacks_with_mocks(); callbacks.update_firmware_request_callback = nullptr; - EXPECT_FALSE(callbacks.all_callbacks_valid()); + EXPECT_FALSE(callbacks.all_callbacks_valid(device_model)); } TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfSecurityEventCallbackExists) { configure_callbacks_with_mocks(); callbacks.security_event_callback = nullptr; - EXPECT_FALSE(callbacks.all_callbacks_valid()); + EXPECT_FALSE(callbacks.all_callbacks_valid(device_model)); } TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfOptionalVariableChangedCallbackIsNotSetOrNotNull) { configure_callbacks_with_mocks(); callbacks.variable_changed_callback = nullptr; - EXPECT_FALSE(callbacks.all_callbacks_valid()); + EXPECT_FALSE(callbacks.all_callbacks_valid(device_model)); testing::MockFunction variable_changed_callback_mock; callbacks.variable_changed_callback = variable_changed_callback_mock.AsStdFunction(); - EXPECT_TRUE(callbacks.all_callbacks_valid()); + EXPECT_TRUE(callbacks.all_callbacks_valid(device_model)); } TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfOptionalVariableNetworkProfileCallbackIsNotSetOrNotNull) { configure_callbacks_with_mocks(); callbacks.validate_network_profile_callback = nullptr; - EXPECT_FALSE(callbacks.all_callbacks_valid()); + EXPECT_FALSE(callbacks.all_callbacks_valid(device_model)); testing::MockFunction validate_network_profile_callback_mock; callbacks.validate_network_profile_callback = validate_network_profile_callback_mock.AsStdFunction(); - EXPECT_TRUE(callbacks.all_callbacks_valid()); + EXPECT_TRUE(callbacks.all_callbacks_valid(device_model)); } TEST_F(ChargePointFixture, @@ -388,46 +388,46 @@ TEST_F(ChargePointFixture, configure_callbacks_with_mocks(); callbacks.configure_network_connection_profile_callback = nullptr; - EXPECT_FALSE(callbacks.all_callbacks_valid()); + EXPECT_FALSE(callbacks.all_callbacks_valid(device_model)); testing::MockFunction configure_network_connection_profile_callback_mock; callbacks.configure_network_connection_profile_callback = configure_network_connection_profile_callback_mock.AsStdFunction(); - EXPECT_TRUE(callbacks.all_callbacks_valid()); + EXPECT_TRUE(callbacks.all_callbacks_valid(device_model)); } TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfOptionalTimeSyncCallbackIsNotSetOrNotNull) { configure_callbacks_with_mocks(); callbacks.time_sync_callback = nullptr; - EXPECT_FALSE(callbacks.all_callbacks_valid()); + EXPECT_FALSE(callbacks.all_callbacks_valid(device_model)); testing::MockFunction time_sync_callback_mock; callbacks.time_sync_callback = time_sync_callback_mock.AsStdFunction(); - EXPECT_TRUE(callbacks.all_callbacks_valid()); + EXPECT_TRUE(callbacks.all_callbacks_valid(device_model)); } TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfOptionalBootNotificationCallbackIsNotSetOrNotNull) { configure_callbacks_with_mocks(); callbacks.boot_notification_callback = nullptr; - EXPECT_FALSE(callbacks.all_callbacks_valid()); + EXPECT_FALSE(callbacks.all_callbacks_valid(device_model)); testing::MockFunction boot_notification_callback_mock; callbacks.boot_notification_callback = boot_notification_callback_mock.AsStdFunction(); - EXPECT_TRUE(callbacks.all_callbacks_valid()); + EXPECT_TRUE(callbacks.all_callbacks_valid(device_model)); } TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfOptionalOCPPMessagesCallbackIsNotSetOrNotNull) { configure_callbacks_with_mocks(); callbacks.ocpp_messages_callback = nullptr; - EXPECT_FALSE(callbacks.all_callbacks_valid()); + EXPECT_FALSE(callbacks.all_callbacks_valid(device_model)); testing::MockFunction ocpp_messages_callback_mock; callbacks.ocpp_messages_callback = ocpp_messages_callback_mock.AsStdFunction(); - EXPECT_TRUE(callbacks.all_callbacks_valid()); + EXPECT_TRUE(callbacks.all_callbacks_valid(device_model)); } TEST_F(ChargePointFixture, @@ -435,13 +435,13 @@ TEST_F(ChargePointFixture, configure_callbacks_with_mocks(); callbacks.cs_effective_operative_status_changed_callback = nullptr; - EXPECT_FALSE(callbacks.all_callbacks_valid()); + EXPECT_FALSE(callbacks.all_callbacks_valid(device_model)); testing::MockFunction cs_effective_operative_status_changed_callback_mock; callbacks.cs_effective_operative_status_changed_callback = cs_effective_operative_status_changed_callback_mock.AsStdFunction(); - EXPECT_TRUE(callbacks.all_callbacks_valid()); + EXPECT_TRUE(callbacks.all_callbacks_valid(device_model)); } TEST_F(ChargePointFixture, @@ -449,87 +449,87 @@ TEST_F(ChargePointFixture, configure_callbacks_with_mocks(); callbacks.evse_effective_operative_status_changed_callback = nullptr; - EXPECT_FALSE(callbacks.all_callbacks_valid()); + EXPECT_FALSE(callbacks.all_callbacks_valid(device_model)); testing::MockFunction evse_effective_operative_status_changed_callback_mock; callbacks.evse_effective_operative_status_changed_callback = evse_effective_operative_status_changed_callback_mock.AsStdFunction(); - EXPECT_TRUE(callbacks.all_callbacks_valid()); + EXPECT_TRUE(callbacks.all_callbacks_valid(device_model)); } TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfOptionalGetCustomerInformationCallbackIsNotSetOrNotNull) { configure_callbacks_with_mocks(); callbacks.get_customer_information_callback = nullptr; - EXPECT_FALSE(callbacks.all_callbacks_valid()); + EXPECT_FALSE(callbacks.all_callbacks_valid(device_model)); testing::MockFunction customer_certificate, const std::optional id_token, const std::optional> customer_identifier)> get_customer_information_callback_mock; callbacks.get_customer_information_callback = get_customer_information_callback_mock.AsStdFunction(); - EXPECT_TRUE(callbacks.all_callbacks_valid()); + EXPECT_TRUE(callbacks.all_callbacks_valid(device_model)); } TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfOptionalClearCustomerInformationCallbackIsNotSetOrNotNull) { configure_callbacks_with_mocks(); callbacks.clear_customer_information_callback = nullptr; - EXPECT_FALSE(callbacks.all_callbacks_valid()); + EXPECT_FALSE(callbacks.all_callbacks_valid(device_model)); testing::MockFunction customer_certificate, const std::optional id_token, const std::optional> customer_identifier)> clear_customer_information_callback_mock; callbacks.clear_customer_information_callback = clear_customer_information_callback_mock.AsStdFunction(); - EXPECT_TRUE(callbacks.all_callbacks_valid()); + EXPECT_TRUE(callbacks.all_callbacks_valid(device_model)); } TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfOptionalAllConnectorsUnavailableCallbackIsNotSetOrNotNull) { configure_callbacks_with_mocks(); callbacks.all_connectors_unavailable_callback = nullptr; - EXPECT_FALSE(callbacks.all_callbacks_valid()); + EXPECT_FALSE(callbacks.all_callbacks_valid(device_model)); testing::MockFunction all_connectors_unavailable_callback_mock; callbacks.all_connectors_unavailable_callback = all_connectors_unavailable_callback_mock.AsStdFunction(); - EXPECT_TRUE(callbacks.all_callbacks_valid()); + EXPECT_TRUE(callbacks.all_callbacks_valid(device_model)); } TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfOptionalDataTransferCallbackIsNotSetOrNotNull) { configure_callbacks_with_mocks(); callbacks.data_transfer_callback = nullptr; - EXPECT_FALSE(callbacks.all_callbacks_valid()); + EXPECT_FALSE(callbacks.all_callbacks_valid(device_model)); testing::MockFunction data_transfer_callback_mock; callbacks.data_transfer_callback = data_transfer_callback_mock.AsStdFunction(); - EXPECT_TRUE(callbacks.all_callbacks_valid()); + EXPECT_TRUE(callbacks.all_callbacks_valid(device_model)); } TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfOptionalTransactionEventCallbackIsNotSetOrNotNull) { configure_callbacks_with_mocks(); callbacks.transaction_event_callback = nullptr; - EXPECT_FALSE(callbacks.all_callbacks_valid()); + EXPECT_FALSE(callbacks.all_callbacks_valid(device_model)); testing::MockFunction transaction_event_callback_mock; callbacks.transaction_event_callback = transaction_event_callback_mock.AsStdFunction(); - EXPECT_TRUE(callbacks.all_callbacks_valid()); + EXPECT_TRUE(callbacks.all_callbacks_valid(device_model)); } TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfOptionalTransactionEventResponseCallbackIsNotSetOrNotNull) { configure_callbacks_with_mocks(); callbacks.transaction_event_response_callback = nullptr; - EXPECT_FALSE(callbacks.all_callbacks_valid()); + EXPECT_FALSE(callbacks.all_callbacks_valid(device_model)); testing::MockFunction transaction_event_response_callback_mock; callbacks.transaction_event_response_callback = transaction_event_response_callback_mock.AsStdFunction(); - EXPECT_TRUE(callbacks.all_callbacks_valid()); + EXPECT_TRUE(callbacks.all_callbacks_valid(device_model)); } TEST_F(ChargePointFixture, K01_SetChargingProfileRequest_ValidatesAndAddsProfile) { From 774c99f3909ab697106a0c7ba2ff01f069088e3e Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Thu, 15 Aug 2024 18:55:26 +0200 Subject: [PATCH 05/22] Start adding California Pricing and multilanguage support for 2.0.1. Signed-off-by: Maaike Zijderveld, iolar --- .../standardized/CustomizationCtrlr.json | 32 ++++++++ .../standardized/TariffCostCtrlr.json | 80 +++++++++++++++++++ .../ocpp/v201/ctrlr_component_variables.hpp | 2 + lib/ocpp/v201/charge_point.cpp | 27 ++++++- lib/ocpp/v201/ctrlr_component_variables.cpp | 10 +++ 5 files changed, 148 insertions(+), 3 deletions(-) diff --git a/config/v201/component_schemas/standardized/CustomizationCtrlr.json b/config/v201/component_schemas/standardized/CustomizationCtrlr.json index e8cefa606..33b295d45 100644 --- a/config/v201/component_schemas/standardized/CustomizationCtrlr.json +++ b/config/v201/component_schemas/standardized/CustomizationCtrlr.json @@ -32,6 +32,38 @@ ], "description": "Custom implementation has been enabled.", "type": "boolean" + }, + "CustomImplementationCaliforniaPricingEnabled": { + "variable_name": "CustomImplementationEnabled", + "instance": "org.openchargealliance.costmsg", + "characteristics": { + "supportsMonitoring": true, + "dataType": "boolean" + }, + "attributes": [ + { + "type": "Actual", + "mutability": "ReadWrite" + } + ], + "description": "Custom implementation org.openchargealliance.costmsg (California Pricing) has been enabled.", + "type": "boolean" + }, + "CustomImplementationMultiLanguageEnabled": { + "variable_name": "CustomImplementationEnabled", + "instance": "org.openchargealliance.multilanguage", + "characteristics": { + "supportsMonitoring": true, + "dataType": "boolean" + }, + "attributes": [ + { + "type": "Actual", + "mutability": "ReadWrite" + } + ], + "description": "Custom implementation org.openchargealliance.multilanguage has been enabled.", + "type": "boolean" } }, "required": [] diff --git a/config/v201/component_schemas/standardized/TariffCostCtrlr.json b/config/v201/component_schemas/standardized/TariffCostCtrlr.json index fc5e917f9..feb38ff17 100644 --- a/config/v201/component_schemas/standardized/TariffCostCtrlr.json +++ b/config/v201/component_schemas/standardized/TariffCostCtrlr.json @@ -115,6 +115,86 @@ ], "description": "Message to be shown to an EV Driver when the Charging Station cannot retrieve the cost for a transaction at the end of the transaction.", "type": "string" + }, + "OfflineChargingPricekWhPrice": { + "variable_name": "OfflineChargingPrice", + "instance": "kWhPrice", + "characteristics": { + "supportsMonitoring": true, + "dataType": "decimal" + }, + "attributes": [ + { + "type": "Actual", + "mutability": "ReadWrite" + } + ], + "description": "Charging kWh price in the default currency when charging station is offline.", + "type": "number" + }, + "OfflineChargingPriceHourPrice": { + "variable_name": "OfflineChargingPrice", + "instance": "hourPrice", + "characteristics": { + "supportsMonitoring": true, + "dataType": "decimal" + }, + "attributes": [ + { + "type": "Actual", + "mutability": "ReadWrite" + } + ], + "description": "Charging kWh price in the default currency when charging station is offline.", + "type": "number" + }, + "TariffFallbackMessageEn": { + "variable_name": "TariffFallbackMessage", + "instance": "en-US", + "characteristics": { + "supportsMonitoring": true, + "dataType": "string" + }, + "attributes": [ + { + "type": "Actual", + "mutability": "ReadWrite" + } + ], + "description": "Message (and / or tariff information) to be shown to an EV Driver when there is no driver specific tariff information available. Note: Add a TariffFallbackMessage with correct instance for every supported language!!", + "type": "string" + }, + "OfflineTariffFallbackMessageEn": { + "variable_name": "OfflineTariffFallbackMessage", + "instance": "en", + "characteristics": { + "supportsMonitoring": true, + "dataType": "string" + }, + "attributes": [ + { + "type": "Actual", + "mutability": "ReadWrite" + } + ], + "description": "Message (and/or tariff information) to be shown to an EV Driver when Charging Station is offline. Note: Add a OfflineTariffFallbackMessage with correct instance for every supported language!!", + "type": "string" + }, + "TotalCostFallbackMessageEn": { + "variable_name": "TotalCostFallbackMessage", + "instance": "en-US", + "characteristics": { + "supportsMonitoring": true, + "dataType": "string" + }, + "attributes": [ + { + "type": "Actual", + "mutability": "ReadWrite" + } + ], + "description": "Message to be shown to an EV Driver when the Charging Station cannot retrieve the cost for a transaction at the end of the transaction. Note: Add a TotalCostFallbackMessage with correct instance for every supported language!!", + "type": "string" } }, "required": [ diff --git a/include/ocpp/v201/ctrlr_component_variables.hpp b/include/ocpp/v201/ctrlr_component_variables.hpp index 43b7f3ffe..d1fa53df9 100644 --- a/include/ocpp/v201/ctrlr_component_variables.hpp +++ b/include/ocpp/v201/ctrlr_component_variables.hpp @@ -119,6 +119,8 @@ extern const ComponentVariable& TimeOffsetNextTransition; extern const RequiredComponentVariable& TimeSource; extern const ComponentVariable& TimeZone; extern const ComponentVariable& CustomImplementationEnabled; +extern const ComponentVariable& CustomImplementationCaliforniaPricingEnabled; +extern const ComponentVariable& CustomImplementationMultiLanguageEnabled; extern const RequiredComponentVariable& BytesPerMessageGetReport; extern const RequiredComponentVariable& BytesPerMessageGetVariables; extern const RequiredComponentVariable& BytesPerMessageSetVariables; diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 761227d94..9c9fc2ca8 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -3222,19 +3222,40 @@ void ChargePoint::handle_costupdated_req(const Call call) { CostUpdatedResponse response; ocpp::CallResult call_result(response, call.uniqueId); - // TODO check if this is enabled in settings (device model)??? + // TODO mz check if this is enabled in settings (device model)??? if (!this->callbacks.set_running_cost_callback.has_value()) { this->send(call_result); return; } RunningCost running_cost; + TriggerMeterValue triggers; + + if (device_model + ->get_optional_value(ControllerComponentVariables::CustomImplementationCaliforniaPricingEnabled) + .value_or(false) && + call.msg.customData.has_value()) { + json running_cost_json = json::parse(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(call.msg.totalCost); running_cost.transaction_id = call.msg.transactionId; - running_cost.state = RunningCostState::Charging; - this->callbacks.set_running_cost_callback.value()(running_cost); + this->callbacks.set_running_cost_callback.value()(running_cost); this->send(call_result); + + // TODO mz implement the triggers!!! } void ChargePoint::handle_set_charging_profile_req(Call call) { diff --git a/lib/ocpp/v201/ctrlr_component_variables.cpp b/lib/ocpp/v201/ctrlr_component_variables.cpp index 4f007ef6b..c4ba87d7f 100644 --- a/lib/ocpp/v201/ctrlr_component_variables.cpp +++ b/lib/ocpp/v201/ctrlr_component_variables.cpp @@ -586,6 +586,16 @@ const ComponentVariable& CustomImplementationEnabled = { "CustomImplementationEnabled", }), }; +const ComponentVariable& CustomImplementationCaliforniaPricingEnabled = { + ControllerComponents::CustomizationCtrlr, + std::nullopt, + std::optional({"CustomImplementationCaliforniaPricingEnabled", std::nullopt, std::nullopt}), +}; +const ComponentVariable& CustomImplementationMultiLanguageEnabled = { + ControllerComponents::CustomizationCtrlr, + std::nullopt, + std::optional({"CustomImplementationMultiLanguageEnabled", std::nullopt, std::nullopt}), +}; const RequiredComponentVariable& BytesPerMessageGetReport = { ControllerComponents::DeviceDataCtrlr, std::nullopt, From f675fb680b076818799caa00e4242015ab25485b Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Fri, 16 Aug 2024 18:02:34 +0200 Subject: [PATCH 06/22] Add some extra California Pricing variables to DisplayMessageCtrlr. Add multilanguage for california pricing. Signed-off-by: Maaike Zijderveld, iolar --- .../standardized/DisplayMessageCtrlr.json | 31 ++++++++ include/ocpp/v201/charge_point.hpp | 3 + .../ocpp/v201/ctrlr_component_variables.hpp | 2 + lib/ocpp/v201/charge_point.cpp | 76 ++++++++++++++++--- lib/ocpp/v201/ctrlr_component_variables.cpp | 12 ++- 5 files changed, 111 insertions(+), 13 deletions(-) diff --git a/config/v201/component_schemas/standardized/DisplayMessageCtrlr.json b/config/v201/component_schemas/standardized/DisplayMessageCtrlr.json index 1451ba922..98e225005 100644 --- a/config/v201/component_schemas/standardized/DisplayMessageCtrlr.json +++ b/config/v201/component_schemas/standardized/DisplayMessageCtrlr.json @@ -111,6 +111,37 @@ "description": "List of the priorities supported by this Charging Station.", "type": "string", "default": "Charging,Faulted,Idle,Unavailable" + }, + "QRCodeDisplayCapable": { + "variable_name": "QRCodeDisplayCapable", + "characteristics": { + "dataType": "boolean", + "supportsMonitoring": true + }, + "attributes": [ + { + "type": "Actual", + "mutability": "ReadOnly" + } + ], + "description": "Whether the station can display QR codes or not.", + "type": "boolean" + }, + "DisplayMessageLanguage": { + "variable_name": "Language", + "characteristics": { + "valuesList": "en_US,de,nl", + "supportesMonitoring": true, + "dataType": "OptionList" + }, + "attributes": [ + { + "type": "Actual", + "mutability": "ReadWrite" + } + ], + "description": "Default language of the charging station. Note: set all supported languages by this charging station in 'valuesList' of this Variable.", + "type": "string" } }, "required": [ diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index bd446d7c1..e8a1e837c 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -544,6 +544,9 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa /// void handle_cost_and_tariff(const TransactionEventResponse& response, const TransactionEventRequest& original_message); + bool is_multilanguage_enabled() const; + bool is_tariff_enabled() const; + bool is_cost_enabled() const; /* OCPP message requests */ diff --git a/include/ocpp/v201/ctrlr_component_variables.hpp b/include/ocpp/v201/ctrlr_component_variables.hpp index d1fa53df9..d0f3c78c2 100644 --- a/include/ocpp/v201/ctrlr_component_variables.hpp +++ b/include/ocpp/v201/ctrlr_component_variables.hpp @@ -134,6 +134,8 @@ extern const RequiredComponentVariable& NumberOfDisplayMessages; extern const RequiredComponentVariable& DisplayMessageSupportedFormats; extern const RequiredComponentVariable& DisplayMessageSupportedPriorities; extern const ComponentVariable& DisplayMessageSupportedStates; +extern const ComponentVariable& DisplayMessageQRCodeDisplayCapable; +extern const ComponentVariable& DisplayMessageLanguage; extern const ComponentVariable& CentralContractValidationAllowed; extern const RequiredComponentVariable& ContractValidationOffline; extern const ComponentVariable& RequestMeteringReceipt; diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 9c9fc2ca8..86f4c1f00 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -620,17 +620,9 @@ void ChargePoint::configure_message_logging_format(const std::string& message_lo void ChargePoint::handle_cost_and_tariff(const TransactionEventResponse& response, const TransactionEventRequest& original_message) { - const bool tariff_enabled = - device_model->get_optional_value(ControllerComponentVariables::TariffCostCtrlrAvailableTariff) - .value_or(false) && - device_model->get_optional_value(ControllerComponentVariables::TariffCostCtrlrEnabledTariff) - .value_or(false); + const bool tariff_enabled = this->is_tariff_enabled(); - const bool cost_enabled = - device_model->get_optional_value(ControllerComponentVariables::TariffCostCtrlrAvailableCost) - .value_or(false) && - device_model->get_optional_value(ControllerComponentVariables::TariffCostCtrlrEnabledCost) - .value_or(false); + const bool cost_enabled = this->is_cost_enabled(); std::vector cost_messages; @@ -670,6 +662,48 @@ void ChargePoint::handle_cost_and_tariff(const TransactionEventResponse& respons 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") && + (custom_data.at("vendorId").get() == "org.openchargealliance.org.qrcode") &&*/ + custom_data.contains("qrCodeText") && + device_model->get_optional_value(ControllerComponentVariables::DisplayMessageQRCodeDisplayCapable) + .value_or(false)) { + running_cost.qr_code_text = custom_data.at("qrCodeText"); + } + + // Add multilanguage messages + if (custom_data.contains("updatedPersonalMessageExtra") && is_multilanguage_enabled()) { + // Get supported languages, which is stored in the values list of "Language" of "DisplayMessageCtrlr" + std::optional metadata = device_model->get_variable_meta_data( + ControllerComponentVariables::DisplayMessageLanguage.component, + ControllerComponentVariables::DisplayMessageLanguage.variable.value()); + + std::vector supported_languages; + + if (metadata.has_value() && 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() && !supported_languages.empty() && + (std::find(supported_languages.begin(), supported_languages.end(), c.language.value()) != + supported_languages.end())) { + // TODO mz some scenarios where the languages are not found in settings + add logging + cost_messages.push_back(c); + } + } + } + } + if (tariff_enabled && !cost_messages.empty()) { running_cost.cost_messages = cost_messages; } @@ -678,6 +712,26 @@ void ChargePoint::handle_cost_and_tariff(const TransactionEventResponse& respons } } +bool ChargePoint::is_multilanguage_enabled() const { + return this->device_model + ->get_optional_value(ControllerComponentVariables::CustomImplementationMultiLanguageEnabled) + .value_or(false); +} + +bool ChargePoint::is_tariff_enabled() const { + return this->device_model->get_optional_value(ControllerComponentVariables::TariffCostCtrlrAvailableTariff) + .value_or(false) && + this->device_model->get_optional_value(ControllerComponentVariables::TariffCostCtrlrEnabledTariff) + .value_or(false); +} + +bool ChargePoint::is_cost_enabled() const { + return this->device_model->get_optional_value(ControllerComponentVariables::TariffCostCtrlrAvailableCost) + .value_or(false) && + this->device_model->get_optional_value(ControllerComponentVariables::TariffCostCtrlrEnabledCost) + .value_or(false); +} + void ChargePoint::on_unavailable(const int32_t evse_id, const int32_t connector_id) { this->evse_manager->get_evse(evse_id).submit_event(connector_id, ConnectorEvent::Unavailable); } @@ -3235,7 +3289,7 @@ void ChargePoint::handle_costupdated_req(const Call call) { ->get_optional_value(ControllerComponentVariables::CustomImplementationCaliforniaPricingEnabled) .value_or(false) && call.msg.customData.has_value()) { - json running_cost_json = json::parse(call.msg.customData.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; diff --git a/lib/ocpp/v201/ctrlr_component_variables.cpp b/lib/ocpp/v201/ctrlr_component_variables.cpp index c4ba87d7f..396e928dc 100644 --- a/lib/ocpp/v201/ctrlr_component_variables.cpp +++ b/lib/ocpp/v201/ctrlr_component_variables.cpp @@ -668,8 +668,16 @@ const RequiredComponentVariable& DisplayMessageSupportedPriorities = { "SupportedPriorities", }), }; -const ComponentVariable& DisplayMessageSupportedStates = {ControllerComponents::DisplayMessageCtrlr, std::nullopt, - std::optional({"SupportedStates"})}; +const ComponentVariable& DisplayMessageSupportedStates = { + ControllerComponents::DisplayMessageCtrlr, std::nullopt, + std::optional({"SupportedStates", std::nullopt, std::nullopt})}; + +const ComponentVariable& DisplayMessageQRCodeDisplayCapable = { + ControllerComponents::DisplayMessageCtrlr, std::nullopt, + std::optional({"QRCodeDisplayCapable", std::nullopt, std::nullopt})}; + +const ComponentVariable& DisplayMessageLanguage = {ControllerComponents::DisplayMessageCtrlr, std::nullopt, + std::optional({"Language", std::nullopt, std::nullopt})}; const ComponentVariable& CentralContractValidationAllowed = { ControllerComponents::ISO15118Ctrlr, From 26d3df9a4b2e3067946361e083f91d237be5db59 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Tue, 20 Aug 2024 17:47:38 +0200 Subject: [PATCH 07/22] Add display messages and tariff and cost callbacks to ocpp module. Move conversions to everest core. Change transaction id to identifier type. Start with metervalue triggers. Signed-off-by: Maaike Zijderveld, iolar --- .../standardized/TariffCostCtrlr.json | 15 ++++ include/ocpp/common/types.hpp | 9 +- include/ocpp/v201/charge_point_callbacks.hpp | 12 +-- .../ocpp/v201/ctrlr_component_variables.hpp | 1 + include/ocpp/v201/evse.hpp | 6 ++ lib/ocpp/v16/charge_point_impl.cpp | 16 ++-- lib/ocpp/v201/charge_point.cpp | 86 ++++++++++++++----- lib/ocpp/v201/ctrlr_component_variables.cpp | 5 ++ lib/ocpp/v201/evse.cpp | 18 ++++ 9 files changed, 133 insertions(+), 35 deletions(-) diff --git a/config/v201/component_schemas/standardized/TariffCostCtrlr.json b/config/v201/component_schemas/standardized/TariffCostCtrlr.json index feb38ff17..30d2ce64d 100644 --- a/config/v201/component_schemas/standardized/TariffCostCtrlr.json +++ b/config/v201/component_schemas/standardized/TariffCostCtrlr.json @@ -195,6 +195,21 @@ ], "description": "Message to be shown to an EV Driver when the Charging Station cannot retrieve the cost for a transaction at the end of the transaction. Note: Add a TotalCostFallbackMessage with correct instance for every supported language!!", "type": "string" + }, + "NumberOfDecimalsForCostValues": { + "variable_name": "NumberOfDecimalsForCostValues", + "characteristics": { + "supportsMonitoring": true, + "dataType": "integer" + }, + "attributes": [ + { + "type": "Actual", + "mutability": "ReadWrite" + } + ], + "description": "Number of decimals for the cost values. Value will be ", + "type": "integer" } }, "required": [ diff --git a/include/ocpp/common/types.hpp b/include/ocpp/common/types.hpp index 3f9b88478..8c2a206b8 100644 --- a/include/ocpp/common/types.hpp +++ b/include/ocpp/common/types.hpp @@ -321,13 +321,20 @@ struct DisplayMessageContent { friend void to_json(json& j, const DisplayMessageContent& m); }; +enum class IdentifierType { + SessionId, + IdToken, + TransactionId +}; + struct DisplayMessage { std::optional id; std::optional priority; std::optional state; std::optional timestamp_from; std::optional timestamp_to; - std::optional transaction_id; + std::optional identifier_id; + std::optional identifier_type; DisplayMessageContent message; std::optional qr_code; }; diff --git a/include/ocpp/v201/charge_point_callbacks.hpp b/include/ocpp/v201/charge_point_callbacks.hpp index 5ad71a1ce..74b0198e7 100644 --- a/include/ocpp/v201/charge_point_callbacks.hpp +++ b/include/ocpp/v201/charge_point_callbacks.hpp @@ -1,7 +1,7 @@ #pragma once -#include #include +#include #include @@ -16,9 +16,7 @@ #include #include - -namespace ocpp::v201 -{ +namespace ocpp::v201 { struct Callbacks { /// \brief Function to check if the callback struct is completely filled. All std::functions should hold a function, /// all std::optional should either be empty or hold a function. @@ -147,6 +145,8 @@ struct Callbacks { clear_display_message_callback; /// \brief Callback function is called when running cost is set. - std::optional> set_running_cost_callback; + std::optional currency_code)>> + set_running_cost_callback; }; -} +} // namespace ocpp::v201 diff --git a/include/ocpp/v201/ctrlr_component_variables.hpp b/include/ocpp/v201/ctrlr_component_variables.hpp index d0f3c78c2..8c961c596 100644 --- a/include/ocpp/v201/ctrlr_component_variables.hpp +++ b/include/ocpp/v201/ctrlr_component_variables.hpp @@ -215,6 +215,7 @@ extern const ComponentVariable& TariffCostCtrlrEnabledTariff; extern const ComponentVariable& TariffCostCtrlrEnabledCost; extern const RequiredComponentVariable& TariffFallbackMessage; extern const RequiredComponentVariable& TotalCostFallbackMessage; +extern const ComponentVariable& NumberOfDecimalsForCostValues; extern const RequiredComponentVariable& EVConnectionTimeOut; extern const ComponentVariable& MaxEnergyOnInvalidId; extern const RequiredComponentVariable& StopTxOnEVSideDisconnect; diff --git a/include/ocpp/v201/evse.hpp b/include/ocpp/v201/evse.hpp index af68dc8d5..9ce63168e 100644 --- a/include/ocpp/v201/evse.hpp +++ b/include/ocpp/v201/evse.hpp @@ -142,6 +142,12 @@ class Evse : public EvseInterface { Everest::SteadyTimer sampled_meter_values_timer; std::shared_ptr database_handler; + std::optional& meter_values, + const bool initiated_by_trigger_message)>>> trigger_metervalue_on_power_kw; + std::optional& meter_values, + const bool initiated_by_trigger_message)>>> trigger_metervalue_on_energy_kwh; + std::unique_ptr trigger_metervalue_at_time_timer; + /// \brief gets the active import energy meter value from meter_value, normalized to Wh. std::optional get_active_import_register_meter_value(); diff --git a/lib/ocpp/v16/charge_point_impl.cpp b/lib/ocpp/v16/charge_point_impl.cpp index 9fc1048ac..66ee75832 100644 --- a/lib/ocpp/v16/charge_point_impl.cpp +++ b/lib/ocpp/v16/charge_point_impl.cpp @@ -2949,14 +2949,19 @@ DataTransferResponse ChargePointImpl::handle_set_user_price(const std::optional< std::vector messages; DisplayMessage message; const auto t = this->transaction_handler->get_transaction_from_id_tag(id_token.value()); + std::string identifier_id; + IdentifierType identifier_type; if (t == nullptr) { - EVLOG_error << "Set user price failed: could not get session id from transaction."; - return response; + identifier_id = id_token.value(); + identifier_type = IdentifierType::IdToken; + } else { + identifier_id = t->get_session_id(); + identifier_type = IdentifierType::SessionId; } - std::string session_id = t->get_session_id(); - message.transaction_id = session_id; + message.identifier_id = identifier_id; + message.identifier_type = identifier_type; if (data.contains("priceText")) { message.message.message = data.at("priceText"); @@ -2971,7 +2976,8 @@ DataTransferResponse ChargePointImpl::handle_set_user_price(const std::optional< data.at("priceTextExtra").is_array()) { for (const json& j : data.at("priceTextExtra")) { DisplayMessage display_message; - display_message.transaction_id = session_id; + display_message.identifier_id = identifier_id; + display_message.identifier_type = identifier_type; display_message.message = j; messages.push_back(display_message); diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 86f4c1f00..6beb46c75 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -20,6 +20,7 @@ const auto DEFAULT_MAX_CUSTOMER_INFORMATION_DATA_LENGTH = 51200; const std::string VARIABLE_ATTRIBUTE_VALUE_SOURCE_INTERNAL = "internal"; const std::string VARIABLE_ATTRIBUTE_VALUE_SOURCE_CSMS = "csms"; const auto DEFAULT_WAIT_FOR_FUTURE_TIMEOUT = std::chrono::seconds(60); +const auto DEFAULT_PRICE_NUMBER_OF_DECIMALS = 3; using DatabaseException = ocpp::common::DatabaseException; @@ -638,7 +639,8 @@ void ChargePoint::handle_cost_and_tariff(const TransactionEventResponse& respons this->callbacks.set_display_message_callback != nullptr) { DisplayMessage display_message; display_message.message = message; - display_message.transaction_id = original_message.transactionInfo.transactionId; + display_message.identifier_id = original_message.transactionInfo.transactionId; + display_message.identifier_type = IdentifierType::TransactionId; this->callbacks.set_display_message_callback.value()({display_message}); } } @@ -656,16 +658,30 @@ void ChargePoint::handle_cost_and_tariff(const TransactionEventResponse& respons running_cost.transaction_id = original_message.transactionInfo.transactionId; if (original_message.meterValue.has_value()) { - // running_cost.meter_value = original_message.meterValue; - // TODO mz add metervalue + const auto& meter_value = original_message.meterValue.value(); + std::optional 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() || max_meter_value.value() < it->value) { + max_meter_value = it->value; + } + } + } + if (max_meter_value.has_value()) { + running_cost.meter_value = static_cast(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. + // 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") && (custom_data.at("vendorId").get() == "org.openchargealliance.org.qrcode") &&*/ @@ -677,7 +693,8 @@ void ChargePoint::handle_cost_and_tariff(const TransactionEventResponse& respons // Add multilanguage messages if (custom_data.contains("updatedPersonalMessageExtra") && is_multilanguage_enabled()) { - // Get supported languages, which is stored in the values list of "Language" of "DisplayMessageCtrlr" + // Get supported languages, which is stored in the values list of "Language" of + // "DisplayMessageCtrlr" std::optional metadata = device_model->get_variable_meta_data( ControllerComponentVariables::DisplayMessageLanguage.component, ControllerComponentVariables::DisplayMessageLanguage.variable.value()); @@ -688,8 +705,8 @@ void ChargePoint::handle_cost_and_tariff(const TransactionEventResponse& respons supported_languages = ocpp::split_string(metadata.value().characteristics.valuesList.value(), ',', true); } else { - EVLOG_error - << "DisplayMessageCtrlr variable Language should have a valuesList with supported languages"; + EVLOG_error << "DisplayMessageCtrlr variable Language should have a valuesList with supported " + "languages"; } for (const auto& m : custom_data.at("updatedPersonalMessageExtra").items()) { @@ -708,7 +725,14 @@ void ChargePoint::handle_cost_and_tariff(const TransactionEventResponse& respons running_cost.cost_messages = cost_messages; } - this->callbacks.set_running_cost_callback.value()(running_cost); + const int number_of_decimals = + this->device_model->get_optional_value(ControllerComponentVariables::NumberOfDecimalsForCostValues) + .value_or(DEFAULT_PRICE_NUMBER_OF_DECIMALS); + uint32_t decimals = + (number_of_decimals < 0 ? DEFAULT_PRICE_NUMBER_OF_DECIMALS : static_cast(number_of_decimals)); + const std::optional currency = + this->device_model->get_value(ControllerComponentVariables::TariffCostCtrlrCurrency); + this->callbacks.set_running_cost_callback.value()(running_cost, decimals, currency); } } @@ -3276,8 +3300,7 @@ void ChargePoint::handle_costupdated_req(const Call call) { CostUpdatedResponse response; ocpp::CallResult call_result(response, call.uniqueId); - // TODO mz check if this is enabled in settings (device model)??? - if (!this->callbacks.set_running_cost_callback.has_value()) { + if (!is_cost_enabled() || !this->callbacks.set_running_cost_callback.has_value()) { this->send(call_result); return; } @@ -3306,9 +3329,24 @@ void ChargePoint::handle_costupdated_req(const Call call) { running_cost.cost = static_cast(call.msg.totalCost); running_cost.transaction_id = call.msg.transactionId; - this->callbacks.set_running_cost_callback.value()(running_cost); + const int number_of_decimals = + this->device_model->get_optional_value(ControllerComponentVariables::NumberOfDecimalsForCostValues) + .value_or(DEFAULT_PRICE_NUMBER_OF_DECIMALS); + uint32_t decimals = + (number_of_decimals < 0 ? DEFAULT_PRICE_NUMBER_OF_DECIMALS : static_cast(number_of_decimals)); + const std::optional currency = + this->device_model->get_value(ControllerComponentVariables::TariffCostCtrlrCurrency); + this->callbacks.set_running_cost_callback.value()(running_cost, decimals, currency); + this->send(call_result); + const std::optional evse_id = get_transaction_evseid(running_cost.transaction_id); + if (!evse_id.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; + } + // TODO mz implement the triggers!!! } @@ -3687,9 +3725,9 @@ void ChargePoint::handle_get_display_message(const Call(); - // 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. + // 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 message_info = display_message_to_message_info_type(display_message); if (message_info.has_value()) { @@ -3697,7 +3735,8 @@ void ChargePoint::handle_get_display_message(const Call call_result(response, call.uniqueId); @@ -3720,8 +3759,8 @@ void ChargePoint::handle_set_display_message(const Call display_message_available = this->device_model->get_optional_value(ControllerComponentVariables::DisplayMessageCtrlrAvailable); @@ -3737,8 +3776,8 @@ void ChargePoint::handle_set_display_message(const Call display_message_to_message_info_type(const Dis info.id = display_message.id.value(); info.priority = display_message.priority.value(); info.state = display_message.state; - info.transactionId = display_message.transaction_id; + info.transactionId = display_message.identifier_id; // Note: component is (not yet?) supported for display messages in libocpp. @@ -4266,7 +4305,8 @@ static DisplayMessage message_info_to_display_message(const MessageInfo& message display_message.state = message_info.state; display_message.timestamp_from = message_info.startDateTime; display_message.timestamp_to = message_info.endDateTime; - display_message.transaction_id = message_info.transactionId; + 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; diff --git a/lib/ocpp/v201/ctrlr_component_variables.cpp b/lib/ocpp/v201/ctrlr_component_variables.cpp index 396e928dc..a4fbda06f 100644 --- a/lib/ocpp/v201/ctrlr_component_variables.cpp +++ b/lib/ocpp/v201/ctrlr_component_variables.cpp @@ -1204,6 +1204,11 @@ const RequiredComponentVariable& TotalCostFallbackMessage = { "TotalCostFallbackMessage", }), }; + +const ComponentVariable& NumberOfDecimalsForCostValues = { + ControllerComponents::TariffCostCtrlr, std::nullopt, + std::optional({"NumberOfDecimalsForCostValues", std::nullopt, std::nullopt})}; + const RequiredComponentVariable& EVConnectionTimeOut = { ControllerComponents::TxCtrlr, std::nullopt, diff --git a/lib/ocpp/v201/evse.cpp b/lib/ocpp/v201/evse.cpp index b43ad66bd..e8774d1d6 100644 --- a/lib/ocpp/v201/evse.cpp +++ b/lib/ocpp/v201/evse.cpp @@ -218,6 +218,9 @@ void Evse::release_transaction() { EVLOG_error << "Could not clear transaction meter values: " << e.what(); } this->transaction = nullptr; + + // TODO mz clear metervalue triggers + // TODO mz should metervalue triggers be stored to the database as well??? } std::unique_ptr& Evse::get_transaction() { @@ -234,6 +237,21 @@ void Evse::on_meter_value(const MeterValue& 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(); + + // TODO mz put in separate function + if (this->trigger_metervalue_on_power_kw.has_value()) { + auto& [power_kw, send_meter_value_function] = this->trigger_metervalue_on_power_kw.value(); + if (send_meter_value_function == nullptr) { + // TODO mz logging + this->trigger_metervalue_on_power_kw.reset(); + return; + } + + // TODO mz / or * 1000? + if (get_active_import_register_meter_value().has_value() && + get_active_import_register_meter_value().value() == power_kw) { + } + } } MeterValue Evse::get_meter_value() { From 907c18652f8b4f5cef6f8dffde49133ae2101d55 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Wed, 21 Aug 2024 11:24:06 +0200 Subject: [PATCH 08/22] Implement energy and power triggers. Signed-off-by: Maaike Zijderveld, iolar --- include/ocpp/v201/evse.hpp | 1 + lib/ocpp/v201/evse.cpp | 61 +++++++++++++++++++++++++++++++------- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/include/ocpp/v201/evse.hpp b/include/ocpp/v201/evse.hpp index 9ce63168e..4e439bf23 100644 --- a/include/ocpp/v201/evse.hpp +++ b/include/ocpp/v201/evse.hpp @@ -147,6 +147,7 @@ class Evse : public EvseInterface { std::optional& meter_values, const bool initiated_by_trigger_message)>>> trigger_metervalue_on_energy_kwh; std::unique_ptr trigger_metervalue_at_time_timer; + std::optional last_triggered_metervalue_power_kw; /// \brief gets the active import energy meter value from meter_value, normalized to Wh. std::optional get_active_import_register_meter_value(); diff --git a/lib/ocpp/v201/evse.cpp b/lib/ocpp/v201/evse.cpp index e8774d1d6..17066cea5 100644 --- a/lib/ocpp/v201/evse.cpp +++ b/lib/ocpp/v201/evse.cpp @@ -239,18 +239,57 @@ void Evse::on_meter_value(const MeterValue& meter_value) { this->check_max_energy_on_invalid_id(); // TODO mz put in separate function - if (this->trigger_metervalue_on_power_kw.has_value()) { - auto& [power_kw, send_meter_value_function] = this->trigger_metervalue_on_power_kw.value(); + bool meter_value_sent = false; + if (this->trigger_metervalue_on_energy_kwh.has_value()) { + auto& [energy_wh, send_meter_value_function] = this->trigger_metervalue_on_energy_kwh.value(); if (send_meter_value_function == nullptr) { // TODO mz logging - this->trigger_metervalue_on_power_kw.reset(); - return; + this->trigger_metervalue_on_energy_kwh.reset(); + } else { + const std::optional active_import_register_meter_value_wh = get_active_import_register_meter_value(); + if (active_import_register_meter_value_wh.has_value() && + static_cast(active_import_register_meter_value_wh.value()) >= energy_wh) { + send_meter_value_function(this->get_id(), {meter_value}, false); + this->trigger_metervalue_on_energy_kwh.reset(); + return; + } } + } + + const std::optional active_power_meter_value = utils::get_total_power_active_import(meter_value); + + if (!this->trigger_metervalue_on_power_kw.has_value() || !active_power_meter_value.has_value()) { + return; + } - // TODO mz / or * 1000? - if (get_active_import_register_meter_value().has_value() && - get_active_import_register_meter_value().value() == power_kw) { + auto& [power_w, send_meter_value_function] = this->trigger_metervalue_on_power_kw.value(); + if (send_meter_value_function == nullptr) { + // TODO mz logging + 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_w = power_w * 0.05; + const double triggered_power_w = this->last_triggered_metervalue_power_kw.value(); + const double trigger_metervalue_w = power_w; + const double current_metervalue_w = active_power_meter_value.value(); + + if ( // Check if trigger value is crossed in upward direction. + (triggered_power_w < trigger_metervalue_w && + current_metervalue_w >= (trigger_metervalue_w + hysterisis_w)) || + // Check if trigger value is crossed in downward direction. + (triggered_power_w > trigger_metervalue_w && + current_metervalue_w <= (trigger_metervalue_w - hysterisis_w))) { + + // Power threshold is crossed, send metervalues. + send_meter_value_function(this->get_id(), {meter_value}, false); + this->last_triggered_metervalue_power_kw = active_power_meter_value.value(); } + } else { + // Send metervalue anyway since we have no previous metervalue stored and don't know if we should send any + send_meter_value_function(this->get_id(), {meter_value}, false); + this->last_triggered_metervalue_power_kw = active_power_meter_value.value(); } } @@ -398,10 +437,10 @@ void Evse::start_metering_timers(const DateTime& timestamp) { store_aligned_metervalue, aligned_data_tx_ended_interval, std::chrono::floor(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. + // 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(); } From 07b53cf4f71e7d1580da230def2533b639bb4d87 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Wed, 21 Aug 2024 17:37:44 +0200 Subject: [PATCH 09/22] Implement metervalue triggers. Signed-off-by: Maaike Zijderveld, iolar --- include/ocpp/v201/charge_point_callbacks.hpp | 1 - include/ocpp/v201/evse.hpp | 25 ++- lib/ocpp/v201/charge_point.cpp | 41 ++++- lib/ocpp/v201/evse.cpp | 173 +++++++++++++------ tests/lib/ocpp/v201/mocks/evse_mock.hpp | 4 + 5 files changed, 177 insertions(+), 67 deletions(-) diff --git a/include/ocpp/v201/charge_point_callbacks.hpp b/include/ocpp/v201/charge_point_callbacks.hpp index 74b0198e7..800948521 100644 --- a/include/ocpp/v201/charge_point_callbacks.hpp +++ b/include/ocpp/v201/charge_point_callbacks.hpp @@ -135,7 +135,6 @@ struct Callbacks { /// \brief Callback function is called when the websocket connection status changes std::optional> connection_state_changed_callback; - // TODO mz if one of the three is defined, they must all be defined /// \brief Callback functions called for get / set / clear display messages std::optional(const GetDisplayMessagesRequest& request /* TODO */)>> get_display_message_callback; diff --git a/include/ocpp/v201/evse.hpp b/include/ocpp/v201/evse.hpp index 4e439bf23..2fe8c706e 100644 --- a/include/ocpp/v201/evse.hpp +++ b/include/ocpp/v201/evse.hpp @@ -124,6 +124,12 @@ class EvseInterface { /// \brief Returns the phase type for the EVSE based on its SupplyPhases. It can be AC, DC, or Unknown. virtual CurrentPhaseType get_current_phase_type() = 0; + + virtual void set_meter_value_pricing_triggers( + std::optional trigger_metervalue_on_power_kw, std::optional trigger_metervalue_on_energy_kwh, + std::optional trigger_metervalue_at_time, + std::function& meter_values)> send_metervalue_function, + boost::asio::io_service& io_service) = 0; }; /// \brief Represents an EVSE. An EVSE can contain multiple Connector objects, but can only supply energy to one of @@ -142,12 +148,12 @@ class Evse : public EvseInterface { Everest::SteadyTimer sampled_meter_values_timer; std::shared_ptr database_handler; - std::optional& meter_values, - const bool initiated_by_trigger_message)>>> trigger_metervalue_on_power_kw; - std::optional& meter_values, - const bool initiated_by_trigger_message)>>> trigger_metervalue_on_energy_kwh; + std::optional trigger_metervalue_on_power_kw; + std::optional trigger_metervalue_on_energy_kwh; std::unique_ptr trigger_metervalue_at_time_timer; std::optional last_triggered_metervalue_power_kw; + std::function& meter_values)> send_metervalue_function; + boost::asio::io_service io_service; /// \brief gets the active import energy meter value from meter_value, normalized to Wh. std::optional get_active_import_register_meter_value(); @@ -159,6 +165,9 @@ class Evse : public EvseInterface { /// \param timestamp void start_metering_timers(const DateTime& timestamp); + void send_meter_value_on_pricing_trigger(const MeterValue& meter_value); + void reset_pricing_triggers(void); + AverageMeterValues aligned_data_updated; AverageMeterValues aligned_data_tx_end; @@ -188,6 +197,8 @@ class Evse : public EvseInterface { transaction_meter_value_req, const std::function& pause_charging_callback); + virtual ~Evse(); + int32_t get_id() const; uint32_t get_number_of_connectors() const; @@ -223,6 +234,12 @@ class Evse : public EvseInterface { void restore_connector_operative_status(int32_t connector_id); CurrentPhaseType get_current_phase_type(); + + void set_meter_value_pricing_triggers( + std::optional trigger_metervalue_on_power_kw, std::optional trigger_metervalue_on_energy_kwh, + std::optional trigger_metervalue_at_time, + std::function& meter_values)> send_metervalue_function, + boost::asio::io_service& io_service); }; } // namespace v201 diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index fab261392..06b1e73c0 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -716,11 +716,26 @@ void ChargePoint::handle_cost_and_tariff(const TransactionEventResponse& respons for (const auto& m : custom_data.at("updatedPersonalMessageExtra").items()) { DisplayMessageContent c = message_content_to_display_message_content(m.value()); - if (c.language.has_value() && !supported_languages.empty() && - (std::find(supported_languages.begin(), supported_languages.end(), c.language.value()) != - supported_languages.end())) { - // TODO mz some scenarios where the languages are not found in settings + add logging + 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."; } } } @@ -3368,14 +3383,26 @@ void ChargePoint::handle_costupdated_req(const Call call) { this->send(call_result); - const std::optional evse_id = get_transaction_evseid(running_cost.transaction_id); - if (!evse_id.has_value()) { + // In OCPP 2.0.1, the chargepoint status trigger is not used. + if (!triggers.at_energy_kwh.has_value() && !triggers.at_power_kw.has_value() && !triggers.at_time.has_value()) { + return; + } + + const std::optional evse_id_opt = 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; } - // TODO mz implement the triggers!!! + const int32_t evse_id = evse_id_opt.value(); + auto& evse = this->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& meter_values) { + this->meter_values_req(evse_id, meter_values, false); + }, + this->io_service); } void ChargePoint::handle_set_charging_profile_req(Call call) { diff --git a/lib/ocpp/v201/evse.cpp b/lib/ocpp/v201/evse.cpp index 17066cea5..21a900f64 100644 --- a/lib/ocpp/v201/evse.cpp +++ b/lib/ocpp/v201/evse.cpp @@ -71,6 +71,13 @@ Evse::Evse(const int32_t evse_id, const int32_t number_of_connectors, DeviceMode } } +Evse::~Evse() { + if (this->trigger_metervalue_at_time_timer != nullptr) { + this->trigger_metervalue_at_time_timer->stop(); + this->trigger_metervalue_at_time_timer = nullptr; + } +} + int32_t Evse::get_id() const { return this->evse_id; } @@ -219,7 +226,8 @@ void Evse::release_transaction() { } this->transaction = nullptr; - // TODO mz clear metervalue triggers + this->reset_pricing_triggers(); + // TODO mz should metervalue triggers be stored to the database as well??? } @@ -237,60 +245,7 @@ void Evse::on_meter_value(const MeterValue& 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(); - - // TODO mz put in separate function - bool meter_value_sent = false; - if (this->trigger_metervalue_on_energy_kwh.has_value()) { - auto& [energy_wh, send_meter_value_function] = this->trigger_metervalue_on_energy_kwh.value(); - if (send_meter_value_function == nullptr) { - // TODO mz logging - this->trigger_metervalue_on_energy_kwh.reset(); - } else { - const std::optional active_import_register_meter_value_wh = get_active_import_register_meter_value(); - if (active_import_register_meter_value_wh.has_value() && - static_cast(active_import_register_meter_value_wh.value()) >= energy_wh) { - send_meter_value_function(this->get_id(), {meter_value}, false); - this->trigger_metervalue_on_energy_kwh.reset(); - return; - } - } - } - - const std::optional active_power_meter_value = utils::get_total_power_active_import(meter_value); - - if (!this->trigger_metervalue_on_power_kw.has_value() || !active_power_meter_value.has_value()) { - return; - } - - auto& [power_w, send_meter_value_function] = this->trigger_metervalue_on_power_kw.value(); - if (send_meter_value_function == nullptr) { - // TODO mz logging - 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_w = power_w * 0.05; - const double triggered_power_w = this->last_triggered_metervalue_power_kw.value(); - const double trigger_metervalue_w = power_w; - const double current_metervalue_w = active_power_meter_value.value(); - - if ( // Check if trigger value is crossed in upward direction. - (triggered_power_w < trigger_metervalue_w && - current_metervalue_w >= (trigger_metervalue_w + hysterisis_w)) || - // Check if trigger value is crossed in downward direction. - (triggered_power_w > trigger_metervalue_w && - current_metervalue_w <= (trigger_metervalue_w - hysterisis_w))) { - - // Power threshold is crossed, send metervalues. - send_meter_value_function(this->get_id(), {meter_value}, false); - this->last_triggered_metervalue_power_kw = active_power_meter_value.value(); - } - } else { - // Send metervalue anyway since we have no previous metervalue stored and don't know if we should send any - send_meter_value_function(this->get_id(), {meter_value}, false); - this->last_triggered_metervalue_power_kw = active_power_meter_value.value(); - } + this->send_meter_value_on_pricing_trigger(meter_value); } MeterValue Evse::get_meter_value() { @@ -447,6 +402,114 @@ void Evse::start_metering_timers(const DateTime& timestamp) { } } +void Evse::set_meter_value_pricing_triggers( + std::optional trigger_metervalue_on_power_kw, std::optional trigger_metervalue_on_energy_kwh, + std::optional trigger_metervalue_at_time, + std::function& meter_values)> send_metervalue_function, + boost::asio::io_service& io_service) { + 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 && trigger_metervalue_at_time.has_value()) { + this->trigger_metervalue_at_time_timer->stop(); + this->trigger_metervalue_at_time_timer = nullptr; + + std::chrono::time_point trigger_timepoint = trigger_metervalue_at_time.value().to_time_point(); + const std::chrono::time_point now = date::utc_clock::now(); + + if (trigger_timepoint < now) { + EVLOG_error << "Could not set trigger metervalue because trigger time is in the past."; + return; + } + + this->trigger_metervalue_at_time_timer = std::make_unique(&io_service, [this]() { + const std::optional& meter_value = this->get_meter_value(); + if (!meter_value.has_value()) { + EVLOG_error << "Send latest meter value because of chargepoint time trigger failed"; + } else { + this->send_metervalue_function({meter_value.value()}); + } + }); + 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; + 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 active_import_register_meter_value_wh = get_active_import_register_meter_value(); + if (active_import_register_meter_value_wh.has_value() && + static_cast(active_import_register_meter_value_wh.value()) >= trigger_energy_kwh * 1000) { + this->send_metervalue_function({meter_value}); + this->trigger_metervalue_on_energy_kwh.reset(); + return; + } + } + } + + const std::optional active_power_meter_value = utils::get_total_power_active_import(meter_value); + + if (!this->trigger_metervalue_on_power_kw.has_value() || !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 double current_metervalue_w = static_cast(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 && current_metervalue_kw >= (trigger_power_kw + hysterisis_kw)) || + // Check if trigger value is crossed in downward direction. + (triggered_power_kw > trigger_power_kw && 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. + this->send_metervalue_function({meter_value}); + } + 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. + this->send_metervalue_function({meter_value}); + } + 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); } diff --git a/tests/lib/ocpp/v201/mocks/evse_mock.hpp b/tests/lib/ocpp/v201/mocks/evse_mock.hpp index 4e7b0b50c..5cc709bff 100644 --- a/tests/lib/ocpp/v201/mocks/evse_mock.hpp +++ b/tests/lib/ocpp/v201/mocks/evse_mock.hpp @@ -36,5 +36,9 @@ class EvseMock : public EvseInterface { (int32_t connector_id, OperationalStatusEnum new_status, bool persist)); MOCK_METHOD(void, restore_connector_operative_status, (int32_t connector_id)); MOCK_METHOD(CurrentPhaseType, get_current_phase_type, ()); + MOCK_METHOD(void, set_meter_value_pricing_triggers, (std::optional trigger_metervalue_on_power_kw, std::optional trigger_metervalue_on_energy_kwh, + std::optional trigger_metervalue_at_time, + std::function& meter_values)> send_metervalue_function, + boost::asio::io_service& io_service)); }; } // namespace ocpp::v201 From 5a7cf6cd02e3d9be1d051819472ec38545c5f414 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Thu, 22 Aug 2024 18:04:07 +0200 Subject: [PATCH 10/22] typo Signed-off-by: Maaike Zijderveld, iolar --- .../v201/component_config/standardized/DisplayMessageCtrlr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/v201/component_config/standardized/DisplayMessageCtrlr.json b/config/v201/component_config/standardized/DisplayMessageCtrlr.json index 057673e2d..288db73d4 100644 --- a/config/v201/component_config/standardized/DisplayMessageCtrlr.json +++ b/config/v201/component_config/standardized/DisplayMessageCtrlr.json @@ -135,7 +135,7 @@ "variable_name": "Language", "characteristics": { "valuesList": "en_US,de,nl", - "supportesMonitoring": true, + "supportsMonitoring": true, "dataType": "OptionList" }, "attributes": [ From 14e22f8b24a808e660d20222e9c6f740c839b806 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Mon, 2 Sep 2024 17:16:14 +0200 Subject: [PATCH 11/22] Add documentation for california pricing. Signed-off-by: Maaike Zijderveld, iolar --- doc/california_pricing_requirements.md | 84 ++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 doc/california_pricing_requirements.md diff --git a/doc/california_pricing_requirements.md b/doc/california_pricing_requirements.md new file mode 100644 index 000000000..9438325a1 --- /dev/null +++ b/doc/california_pricing_requirements.md @@ -0,0 +1,84 @@ +# California Pricing Requirements + +OCPP has several whitepapers, which can be found here: https://openchargealliance.org/whitepapers/ + +One of them is OCPP & California Pricing Requirements. This can be optionally enabled in libocpp, for OCPP 1.6 as well +as OCPP 2.0.1. + +## Callbacks in libocpp + +To be kind of compatible with eachother, the callbacks for OCPP 1.6 and 2.0.1 use the same structs with the pricing +information. + +### User-specific price / SetUserPrice + +The User-specific price is used for display purposes only and can be sent as soon as the user identifies itself with an +id token. It should not be used to calculate prices. +Internally, the DataTransfer json is converted to a `DisplayMessage`, defined in `common/types.hpp`. In case of multi +language messages, they are all added to the DisplayMessage vector. +If the message is sent when a transaction has already started, the session id will be included in the display message +and the `IdentifierType` will be set to `SessionId`. If it has not started yet, the id token is sent with +`IdentifierType` set to `IdToken`. + + +### Running cost and Final / total cost + +The running cost and final cost messages are converted to a `RunningCost` struct, also defined in `common/types.hpp`. +The triggers in the message (running cost) are handled in libocpp itself. +The prices are converted to integers, because floating point numbers are not precise enough for pricing calculations. +To set the number of decimals to calculate with, you should set NumberOfDecimalsForCostValues (1.6, in CostAndPrice / 2.0.1, TariffCostCtrlr). Default is 3. + + +## OCPP 1.6 + +OCPP 1.6 mostly uses DataTransfer to send the pricing messages, and also has some extra configuration items. In libocpp, +the DataTransfer message is converted to internally used structs as described above. + +### Configuration Items + +| Name | Description | +| ---- | ----------- | +| `CustomDisplayCostAndPrice` | Set to `true` to enable California Pricing (readonly) | +| `DefaultPrice` | Holds the default price and default price text in a json object. Can be updated by the CSMS. Not used by libocpp. See the specification for the specific fields. | +| `NumberOfDecimalsForCostValues` | Holds the number of decimals the cost / price values are converted with. | +| `CustomIdleFeeAfterStop` | Set to `true` to extend the transaction until `ConnectorUnplugged` is sent (readonly). The chargepoint implementation should send this DataTransfer message, this is not part of libocpp (yet, 2024-08) | +| `CustomMultiLanguageMessages` | Set to `true` to enable multi language support (readonly). | +| `Language` | Default language code for the stations UI (IETF RFC5646) (readwrite). | +| `SupportedLanguages` | Comma separated list of supported languages, specified as IETF RFC5646 (readonly). | +| `DefaultPriceText` | Holds an array (`priceTexts`) of default price texts in several languages. Each item has the `priceText` (string) in the given `language` (string) and a `priceTextOffline` (string) in the given language. The CSMS sends the DefaultPriceText per language: `"DefaultPriceText,\"`, but libocpp will convert it to the above described json object. (readwrite) | +| `TimeOffset` | As OCPP 1.6 does not have built-in support for timezones, you can set a timezone when displaying time related pricing information. This timezone is also used for the `atTime` trigger. (readwrite) | +| `NextTimeOffsetTransitionDateTime` | When to change to summer or winter time, to the offset `TimeOffsetNextTransition` | +| `TimeOffsetNextTransition` | What the new offset should be at the given `NextTimeOffsetTransationDateTime` (readwrite) | + + +## OCPP 2.0.1 + +OCPP 2.0.1 uses different mechanisms to send pricing information. The messages are converted to internally used structs +as descripbed above. For California Pricing Requirements to work, DisplayMessage and TariffAndCost must be implemented +as well. + +### Device Model Variables + +| Variable name | Instance | Component | Description | +| ------------- | -------- | --------- | ----------- | +| `CustomImplementationEnabled` | `org.openchargealliance.costmsg` | `CustomizationCtrlr` | Set to 'true' to support California Pricing (actually to indicate `customData` fields for California Pricing are supported). | +| `Enabled` | `Tariff` | `TariffCostCtrlr` | Enable showing tariffs. | +| `Enabled` | `Cost` | `TariffCostCtrlr` | Enable showing of cost. | +| `TariffFallbackMessage` | | `TariffCostCtrlr` | Fallback message to show to EV Driver when there is no driver specific tariff information. Not used by libocpp. | +| `TotalCostFallbackMessage` | | `TariffCostCtrlr` | Fallback message to sho to EV Driver when CS can not retrieve the cost for a transaction at the end of the transaction. Not used by libocpp. | +| `Currency` | | `TariffCostCtrlr` | Currency used for tariff and cost information. | +| `NumberOfDecimalsForCostValues` | | `TariffCostCtrlr` | Holds the number of decimals the cost / price values are converted with. | +| `TariffFallbackMessage` | `Offline` | `TariffCostCtrlr` | Fallback message to be shown to an EV Driver when CS is offline. Not used by libocpp. | +| `OfflineChargingPrice` | `kWhPrice` | `TariffCostCtrlr` | The energy (kWh) price for transactions started while offline. Not used by libocpp. | +| `OfflineChargingPrice` | `hourPrice` | `TariffCostCtrlr` | The time (hour) price for transactions started while offline. Not used by libocpp. | +| `TariffFallbackMessage` | `` | `TariffCostCtrlr` | TariffFallbackMessage in a specific language. There must be a variable with the language as instance for every supported language. | +| `OfflineTariffFallbackMessage` | `` | `TariffCostCtrlr` | TariffFallbackMessage when charging station is offline, in a specific language. There must be a variable with the language as instance for every supported language. | +| `TotalCostFallbackMessage` | `` | `TariffCostCtrlr` | Multi language TotalCostFallbackMessage. There must be a variable with the language as instance for every supported language. | + + + + + +# TODO tariff enabled but cost not etc. +# TODO tariff and cost information in Authorizeresponse? + From 22bc734f3ae14a806315bd61bb97de9095b9076b Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Tue, 3 Sep 2024 14:43:22 +0200 Subject: [PATCH 12/22] Add documentation Signed-off-by: Maaike Zijderveld, iolar --- doc/california_pricing_requirements.md | 43 +++++++++++++++++++++++--- include/ocpp/common/types.hpp | 9 ++++-- include/ocpp/v201/charge_point.hpp | 15 +++++++++ include/ocpp/v201/evse.hpp | 26 ++++++++++++++++ 4 files changed, 85 insertions(+), 8 deletions(-) diff --git a/doc/california_pricing_requirements.md b/doc/california_pricing_requirements.md index 9438325a1..78ad57fa5 100644 --- a/doc/california_pricing_requirements.md +++ b/doc/california_pricing_requirements.md @@ -14,8 +14,8 @@ information. The User-specific price is used for display purposes only and can be sent as soon as the user identifies itself with an id token. It should not be used to calculate prices. -Internally, the DataTransfer json is converted to a `DisplayMessage`, defined in `common/types.hpp`. In case of multi -language messages, they are all added to the DisplayMessage vector. +Internally, the messages in the DataTransfer json (for 1.6) is converted to a `DisplayMessage`, defined in +`common/types.hpp`. In case of multi language messages, they are all added to the DisplayMessage vector. If the message is sent when a transaction has already started, the session id will be included in the display message and the `IdentifierType` will be set to `SessionId`. If it has not started yet, the id token is sent with `IdentifierType` set to `IdToken`. @@ -26,7 +26,9 @@ and the `IdentifierType` will be set to `SessionId`. If it has not started yet, The running cost and final cost messages are converted to a `RunningCost` struct, also defined in `common/types.hpp`. The triggers in the message (running cost) are handled in libocpp itself. The prices are converted to integers, because floating point numbers are not precise enough for pricing calculations. -To set the number of decimals to calculate with, you should set NumberOfDecimalsForCostValues (1.6, in CostAndPrice / 2.0.1, TariffCostCtrlr). Default is 3. +To set the number of decimals to calculate with, you should set NumberOfDecimalsForCostValues (1.6, in CostAndPrice / +2.0.1, TariffCostCtrlr). Default is 3. There might be messages in multiple languages, they are all added to the messages +vector. ## OCPP 1.6 @@ -51,6 +53,13 @@ the DataTransfer message is converted to internally used structs as described ab | `TimeOffsetNextTransition` | What the new offset should be at the given `NextTimeOffsetTransationDateTime` (readwrite) | +### Callbacks + +For California Pricing to work, the following callbacks must be enabled: +- `session_cost_callback`, used for running cost and final cost +- `set_display_message_callback`, used to show a user specific price + + ## OCPP 2.0.1 OCPP 2.0.1 uses different mechanisms to send pricing information. The messages are converted to internally used structs @@ -71,14 +80,38 @@ as well. | `TariffFallbackMessage` | `Offline` | `TariffCostCtrlr` | Fallback message to be shown to an EV Driver when CS is offline. Not used by libocpp. | | `OfflineChargingPrice` | `kWhPrice` | `TariffCostCtrlr` | The energy (kWh) price for transactions started while offline. Not used by libocpp. | | `OfflineChargingPrice` | `hourPrice` | `TariffCostCtrlr` | The time (hour) price for transactions started while offline. Not used by libocpp. | +| `QRCodeDisplayCapable` | | `DisplayMessageCtrlr` | Set to 'true' if station can display QR codes | +| `CustomImplementationEnabled` | `org.openchargealliance.multilanguage` | `CustomizationCtrlr` | Enable multilanguage | | `TariffFallbackMessage` | `` | `TariffCostCtrlr` | TariffFallbackMessage in a specific language. There must be a variable with the language as instance for every supported language. | | `OfflineTariffFallbackMessage` | `` | `TariffCostCtrlr` | TariffFallbackMessage when charging station is offline, in a specific language. There must be a variable with the language as instance for every supported language. | | `TotalCostFallbackMessage` | `` | `TariffCostCtrlr` | Multi language TotalCostFallbackMessage. There must be a variable with the language as instance for every supported language. | +| `Language` | | `DisplayMessageCtrlr` | Default language code (RFC 5646). The `valuesList` holds the supported languages of the charging station. The value must be one of `valuesList`. | + + +> **_NOTE:_** Tariff and cost can be enabled separately. To be able to use all functionality, it is recommended to +enable both. If cost is enabled and tariff is not enabled, the total cost message will not contain the personal message +(`set_running_cost_callback`). +If tariff is enabled and cost is not enabled, the total cost message will only be a DisplayMessage +(`set_display_message_callback`) containing the personal message(s). +### Callbacks +For California Pricing to work, the following callbacks must be enabled: +- `set_running_cost_callback` +- `set_display_message_callback` +For the tariff information (the personal messages), the `set_display_message_callback` is used. The same callback is +also used for the SetDisplayMessageRequest in OCPP. The latter does require an id, the former will not have an id. So +when `GetDisplayMessageRequest` is called from the CSMS, the Tariff display messages (that do not have an id) should not +be returned. They should also be removed as soon as the transaction has ended. -# TODO tariff enabled but cost not etc. -# TODO tariff and cost information in Authorizeresponse? +Driver specific tariffs / pricing information can be returned by the CSMS in the `AuthorizeResponse` message. In +libocpp, the whole message is just forwared (pricing information is not extracted from it), because the pricing +information is coupled to the authorize response. So when Tariff and Cost are enabled, the `idTokenInfo` field must be +read for pricing information. +Cost information is also sent by the CSMS in the TransactionEventResponse. In that case, the pricing / cost information +is extracted from the message and a RunningCost message is sent containing the current cost and extra messages +(optional). If only Tariff is enabled and there is a personal message in the TransationEventResponse, a DisplayMessage +is sent. diff --git a/include/ocpp/common/types.hpp b/include/ocpp/common/types.hpp index fd4d0b3ae..d7ba1e178 100644 --- a/include/ocpp/common/types.hpp +++ b/include/ocpp/common/types.hpp @@ -352,10 +352,13 @@ struct DisplayMessageContent { friend void to_json(json& j, const DisplayMessageContent& m); }; +/// +/// \brief Type of an identifier string. +/// enum class IdentifierType { - SessionId, - IdToken, - TransactionId + SessionId, ///< \brief Identifier is the session id. + IdToken, ///< \brief Identifier is the id token. + TransactionId ///< \brief Identifier is the transaction id. }; struct DisplayMessage { diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index 379e429d0..ab0b2a972 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -539,8 +539,23 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa /// void handle_cost_and_tariff(const TransactionEventResponse& response, const TransactionEventRequest& original_message); + + /// + /// \brief Check if multilanguage setting (variable) is enabled. + /// \return True if enabled. + /// bool is_multilanguage_enabled() const; + + /// + /// \brief Check if tariff setting (variable) is enabled. + /// \return True if enabled. + /// bool is_tariff_enabled() const; + + /// + /// \brief Check if cost setting (variable) is enabled. + /// \return True if enabled. + /// bool is_cost_enabled() const; /* OCPP message requests */ diff --git a/include/ocpp/v201/evse.hpp b/include/ocpp/v201/evse.hpp index 2fe8c706e..f53cf7d19 100644 --- a/include/ocpp/v201/evse.hpp +++ b/include/ocpp/v201/evse.hpp @@ -125,6 +125,14 @@ class EvseInterface { /// \brief Returns the phase type for the EVSE based on its SupplyPhases. It can be AC, DC, or Unknown. virtual CurrentPhaseType get_current_phase_type() = 0; + /// + /// \brief Set metervalue triggers for California Pricing. + /// \param trigger_metervalue_on_power_kw Send metervalues on this amount of kw (with hysteresis). + /// \param trigger_metervalue_on_energy_kwh Send metervalues when this kwh is reached. + /// \param trigger_metervalue_at_time Send metervalues at a specific time. + /// \param send_metervalue_function Function used to send the metervalues. + /// \param io_service io service for the timers. + /// virtual void set_meter_value_pricing_triggers( std::optional trigger_metervalue_on_power_kw, std::optional trigger_metervalue_on_energy_kwh, std::optional trigger_metervalue_at_time, @@ -165,7 +173,17 @@ class Evse : public EvseInterface { /// \param timestamp void start_metering_timers(const DateTime& timestamp); + /// + /// \brief Send metervalue to CSMS after a pricing trigger occured. + /// \param meter_value The metervalue to send. + /// void send_meter_value_on_pricing_trigger(const MeterValue& meter_value); + + /// + /// \brief Reset pricing triggers. + /// + /// Resets timer, set all pricing trigger related members to std::nullopt and / or nullptr. + /// void reset_pricing_triggers(void); AverageMeterValues aligned_data_updated; @@ -235,6 +253,14 @@ class Evse : public EvseInterface { CurrentPhaseType get_current_phase_type(); + /// + /// \brief Set pricing triggers to send the meter value. + /// \param trigger_metervalue_on_power_kw Trigger for this amount of kw + /// \param trigger_metervalue_on_energy_kwh Trigger when amount of kwh is reached + /// \param trigger_metervalue_at_time Trigger for a specific time + /// \param send_metervalue_function Function to send metervalues when trigger 'fires' + /// \param io_service Io service needed for the timer + /// void set_meter_value_pricing_triggers( std::optional trigger_metervalue_on_power_kw, std::optional trigger_metervalue_on_energy_kwh, std::optional trigger_metervalue_at_time, From 50212c0d25796dbb3f1aeae17551881ea56f4a91 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Tue, 3 Sep 2024 14:51:52 +0200 Subject: [PATCH 13/22] Corrected configuration item that was not according to spec. Signed-off-by: Maaike Zijderveld, iolar --- config/v16/profile_schemas/CostAndPrice.json | 2 +- lib/ocpp/v16/charge_point_configuration.cpp | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/config/v16/profile_schemas/CostAndPrice.json b/config/v16/profile_schemas/CostAndPrice.json index d4d1d40c3..f76d9527f 100644 --- a/config/v16/profile_schemas/CostAndPrice.json +++ b/config/v16/profile_schemas/CostAndPrice.json @@ -113,7 +113,7 @@ "type": "boolean", "readOnly": false }, - "MultiLanguageSupportedLanguages": { + "SupportedLanguages": { "description": "Comma separated list of supported language codes, per RFC5646.", "type": "string", "readOnly": true, diff --git a/lib/ocpp/v16/charge_point_configuration.cpp b/lib/ocpp/v16/charge_point_configuration.cpp index 1af9014cd..5fa63fdf0 100644 --- a/lib/ocpp/v16/charge_point_configuration.cpp +++ b/lib/ocpp/v16/charge_point_configuration.cpp @@ -2651,9 +2651,8 @@ std::optional ChargePointConfiguration::getCustomMultiLanguageMessages } std::optional ChargePointConfiguration::getMultiLanguageSupportedLanguages() { - if (this->config.contains("CostAndPrice") && - this->config["CostAndPrice"].contains("MultiLanguageSupportedLanguages")) { - return this->config["CostAndPrice"]["MultiLanguageSupportedLanguages"]; + if (this->config.contains("CostAndPrice") && this->config["CostAndPrice"].contains("SupportedLanguages")) { + return this->config["CostAndPrice"]["SupportedLanguages"]; } return std::nullopt; @@ -2664,7 +2663,7 @@ std::optional ChargePointConfiguration::getMultiLanguageSupportedLangu std::optional languages = getMultiLanguageSupportedLanguages(); if (languages.has_value()) { result = KeyValue(); - result->key = "MultiLanguageSupportedLanguages"; + result->key = "SupportedLanguages"; result->value = languages.value(); result->readonly = true; } @@ -3068,7 +3067,7 @@ std::optional ChargePointConfiguration::get(CiString<50> key) { if (key == "CustomIdleFeeAfterStop") { return this->getCustomIdleFeeAfterStopKeyValue(); } - if (key == "MultiLanguageSupportedLanguages") { + if (key == "SupportedLanguages") { return this->getMultiLanguageSupportedLanguagesKeyValue(); } if (key == "CustomMultiLanguageMessages") { From b919295eac907955252888b738caae8ec228265f Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Thu, 5 Sep 2024 14:09:12 +0200 Subject: [PATCH 14/22] Add device_model to callbacks valid call Signed-off-by: Maaike Zijderveld, iolar --- 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 4ac03f457..da6c0911f 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -62,7 +62,7 @@ ChargePoint::ChargePoint(const std::map& evse_connector_struct callbacks(callbacks) { // Make sure the received callback struct is completely filled early before we actually start running - if (!this->callbacks.all_callbacks_valid()) { + if (!this->callbacks.all_callbacks_valid(this->device_model)) { EVLOG_AND_THROW(std::invalid_argument("All non-optional callbacks must be supplied")); } From 53448eab4eb7777f177d413ebba420cc3d5d053a Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Mon, 9 Sep 2024 17:48:18 +0200 Subject: [PATCH 15/22] Add call to handle_costupdated_req if CostUpdatedRequest is received. Some changes in variables with a wrong name. Signed-off-by: Maaike Zijderveld, iolar --- lib/ocpp/v201/charge_point.cpp | 10 ++++++++++ lib/ocpp/v201/ctrlr_component_variables.cpp | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index da6c0911f..1f9789232 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -1327,6 +1327,9 @@ void ChargePoint::handle_message(const EnhancedMessage& messa case MessageType::ClearDisplayMessage: this->handle_clear_display_message(json_message); break; + case MessageType::CostUpdated: + this->handle_costupdated_req(json_message); + break; default: if (message.messageTypeId == MessageTypeId::CALL) { const auto call_error = CallError(message.uniqueId, "NotImplemented", "", json({})); @@ -3217,6 +3220,13 @@ void ChargePoint::handle_costupdated_req(const Call call) { running_cost.cost = static_cast(call.msg.totalCost); running_cost.transaction_id = call.msg.transactionId; + std::optional transaction_evse_id = 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->device_model->get_optional_value(ControllerComponentVariables::NumberOfDecimalsForCostValues) .value_or(DEFAULT_PRICE_NUMBER_OF_DECIMALS); diff --git a/lib/ocpp/v201/ctrlr_component_variables.cpp b/lib/ocpp/v201/ctrlr_component_variables.cpp index 707e33345..18235d5b7 100644 --- a/lib/ocpp/v201/ctrlr_component_variables.cpp +++ b/lib/ocpp/v201/ctrlr_component_variables.cpp @@ -589,12 +589,12 @@ const ComponentVariable& CustomImplementationEnabled = { const ComponentVariable& CustomImplementationCaliforniaPricingEnabled = { ControllerComponents::CustomizationCtrlr, std::nullopt, - std::optional({"CustomImplementationCaliforniaPricingEnabled", std::nullopt, std::nullopt}), + std::optional({"CustomImplementationEnabled", std::nullopt, "org.openchargealliance.costmsg"}), }; const ComponentVariable& CustomImplementationMultiLanguageEnabled = { ControllerComponents::CustomizationCtrlr, std::nullopt, - std::optional({"CustomImplementationMultiLanguageEnabled", std::nullopt, std::nullopt}), + std::optional({"CustomImplementationEnabled", std::nullopt, "org.openchargealliance.multilanguage"}), }; const RequiredComponentVariable& BytesPerMessageGetReport = { ControllerComponents::DeviceDataCtrlr, From 6f4994d88ce79f6e38d64b9ae6a73c094ac3f9e4 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Tue, 10 Sep 2024 18:10:14 +0200 Subject: [PATCH 16/22] Fix rounding issue. Fix trigger issues. Signed-off-by: Maaike Zijderveld, iolar --- include/ocpp/v201/charge_point.hpp | 4 +- include/ocpp/v201/evse.hpp | 2 + lib/ocpp/v201/charge_point.cpp | 17 +++++++-- lib/ocpp/v201/evse.cpp | 61 ++++++++++++++++++++++-------- 4 files changed, 64 insertions(+), 20 deletions(-) diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index ab0b2a972..b7285a981 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -536,9 +536,11 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa /// \param response The TransactionEventResponse where the tariff and cost information is added to. /// \param original_message The original TransactionEventRequest, which contains some information we need as /// well. + /// \param original_transaction_event_response The original json from the response. /// void handle_cost_and_tariff(const TransactionEventResponse& response, - const TransactionEventRequest& original_message); + const TransactionEventRequest& original_message, + const json& original_transaction_event_response); /// /// \brief Check if multilanguage setting (variable) is enabled. diff --git a/include/ocpp/v201/evse.hpp b/include/ocpp/v201/evse.hpp index f53cf7d19..1bd98d523 100644 --- a/include/ocpp/v201/evse.hpp +++ b/include/ocpp/v201/evse.hpp @@ -163,6 +163,8 @@ class Evse : public EvseInterface { std::function& meter_values)> send_metervalue_function; boost::asio::io_service io_service; + std::optional get_current_active_import_register_meter_value(const ReadingContextEnum context); + /// \brief gets the active import energy meter value from meter_value, normalized to Wh. std::optional get_active_import_register_meter_value(); diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 1f9789232..7232b376b 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -597,7 +597,8 @@ void ChargePoint::configure_message_logging_format(const std::string& message_lo } void ChargePoint::handle_cost_and_tariff(const TransactionEventResponse& response, - const TransactionEventRequest& original_message) { + 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(); @@ -625,7 +626,17 @@ void ChargePoint::handle_cost_and_tariff(const TransactionEventResponse& respons // Check if cost is available and enabled, and if there is a totalcost message. if (cost_enabled && response.totalCost.has_value() && this->callbacks.set_running_cost_callback.has_value()) { RunningCost running_cost; - running_cost.cost = static_cast(response.totalCost.value()); + std::string total_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")) { + total_cost = original_transaction_event_response.at("totalCost").dump(); + running_cost.cost = stod(total_cost); + } else { + running_cost.cost = static_cast(response.totalCost.value()); + } + if (original_message.eventType == TransactionEventEnum::Ended) { running_cost.state = RunningCostState::Finished; } else { @@ -2707,7 +2718,7 @@ void ChargePoint::handle_transaction_event_response(const EnhancedMessagecallbacks.transaction_event_response_callback.value()(original_msg, call_result.msg); } - this->handle_cost_and_tariff(call_result.msg, original_msg); + this->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 diff --git a/lib/ocpp/v201/evse.cpp b/lib/ocpp/v201/evse.cpp index 21a900f64..a8838a3b2 100644 --- a/lib/ocpp/v201/evse.cpp +++ b/lib/ocpp/v201/evse.cpp @@ -245,7 +245,11 @@ void Evse::on_meter_value(const MeterValue& 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); + std::optional import_register_meter_value = + get_current_active_import_register_meter_value(ReadingContextEnum::Other); + if (import_register_meter_value.has_value()) { + this->send_meter_value_on_pricing_trigger(import_register_meter_value.value()); + } } MeterValue Evse::get_meter_value() { @@ -261,6 +265,27 @@ void Evse::clear_idle_meter_values() { this->aligned_data_updated.clear_values(); } +std::optional Evse::get_current_active_import_register_meter_value(const ReadingContextEnum context) { + std::unique_lock lk(this->meter_value_mutex); + MeterValue meter_value = this->meter_value; + lk.unlock(); + + meter_value.sampledValue.erase( + std::remove_if(meter_value.sampledValue.begin(), meter_value.sampledValue.end(), [](const SampledValue& value) { + return value.measurand != MeasurandEnum::Energy_Active_Import_Register or value.phase.has_value(); + })); + + if (meter_value.sampledValue.empty()) { + return std::nullopt; + } + + for (SampledValue& v : meter_value.sampledValue) { + v.context = context; + } + + return meter_value; +} + std::optional Evse::get_active_import_register_meter_value() { std::lock_guard lk(this->meter_value_mutex); auto it = std::find_if( @@ -413,25 +438,29 @@ void Evse::set_meter_value_pricing_triggers( if (this->trigger_metervalue_at_time_timer != nullptr && trigger_metervalue_at_time.has_value()) { this->trigger_metervalue_at_time_timer->stop(); this->trigger_metervalue_at_time_timer = nullptr; + } - std::chrono::time_point trigger_timepoint = trigger_metervalue_at_time.value().to_time_point(); - const std::chrono::time_point now = date::utc_clock::now(); + std::chrono::time_point trigger_timepoint = trigger_metervalue_at_time.value().to_time_point(); + const std::chrono::time_point now = date::utc_clock::now(); - if (trigger_timepoint < now) { - EVLOG_error << "Could not set trigger metervalue because trigger time is in the past."; - return; + if (trigger_timepoint < now) { + EVLOG_error << "Could not set trigger metervalue because trigger time is in the past."; + return; + } + + this->trigger_metervalue_at_time_timer = std::make_unique(&io_service, [this]() { + EVLOG_error << "Sending metervalue in timer"; + const std::optional& meter_value = + this->get_current_active_import_register_meter_value(ReadingContextEnum::Other); + if (!meter_value.has_value()) { + EVLOG_error << "Send latest meter value because of chargepoint time trigger failed"; + } else { + this->send_metervalue_function({meter_value.value()}); } + }); + EVLOG_error << "Set trigger metervalue at time " << trigger_timepoint; - this->trigger_metervalue_at_time_timer = std::make_unique(&io_service, [this]() { - const std::optional& meter_value = this->get_meter_value(); - if (!meter_value.has_value()) { - EVLOG_error << "Send latest meter value because of chargepoint time trigger failed"; - } else { - this->send_metervalue_function({meter_value.value()}); - } - }); - this->trigger_metervalue_at_time_timer->at(trigger_timepoint); - } + this->trigger_metervalue_at_time_timer->at(trigger_timepoint); } void Evse::reset_pricing_triggers() { From cb4cfc9b1253966ae8bf9d8da651c34b57ae799c Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Wed, 11 Sep 2024 18:19:01 +0200 Subject: [PATCH 17/22] Update status document for display message and california pricing. Fix some bugs, mainly with triggers. Signed-off-by: Maaike Zijderveld, iolar --- doc/ocpp_201_status.md | 125 +++++++++++++++++---------------- include/ocpp/v201/evse.hpp | 3 +- lib/ocpp/v201/charge_point.cpp | 4 ++ lib/ocpp/v201/evse.cpp | 57 +++++++++++---- 4 files changed, 111 insertions(+), 78 deletions(-) diff --git a/doc/ocpp_201_status.md b/doc/ocpp_201_status.md index c99c62319..99bdec8bc 100644 --- a/doc/ocpp_201_status.md +++ b/doc/ocpp_201_status.md @@ -1125,50 +1125,50 @@ This document contains the status of which OCPP 2.0.1 numbered functional requir | ID | Status | Remark | |-----------|--------|--------| -| I01.FR.01 | | | -| I01.FR.02 | | | -| I01.FR.03 | | | +| I01.FR.01 | 🌐 | | +| I01.FR.02 | 🌐 | | +| I01.FR.03 | ⛽️ | | ## TariffAndCost - Show EV Driver Running Total Cost During Charging | ID | Status | Remark | |-----------|--------|--------| -| I02.FR.01 | | | -| I02.FR.02 | | | -| I02.FR.03 | | | -| I02.FR.04 | | | +| I02.FR.01 | 🌐 | | +| I02.FR.02 | ✅ | | +| I02.FR.03 | ⛽️ | | +| I02.FR.04 | ⛽️ | | ## TariffAndCost - Show EV Driver Final Total Cost After Charging | ID | Status | Remark | |-----------|--------|--------| -| I03.FR.01 | | | -| I03.FR.02 | | | -| I03.FR.03 | | | -| I03.FR.04 | | | -| I03.FR.05 | | | +| I03.FR.01 | ✅ | | +| I03.FR.02 | 🌐 | | +| I03.FR.03 | ⛽️ | | +| I03.FR.04 | 🌐 | | +| I03.FR.05 | ⛽️ | | ## TariffAndCost - Show Fallback Tariff Information | ID | Status | Remark | |-----------|--------|--------| -| I04.FR.01 | | | -| I04.FR.02 | | | +| I04.FR.01 | ⛽️ | | +| I04.FR.02 | 🌐 | | ## TariffAndCost - Show Fallback Total Cost Message | ID | Status | Remark | |-----------|--------|--------| -| I05.FR.01 | | | -| I05.FR.02 | | | +| I05.FR.01 | 🌐 | | +| I05.FR.02 | ⛽️ | | ## TariffAndCost - Update Tariff Information During Transaction | ID | Status | Remark | |-----------|--------|--------| -| I06.FR.01 | | | -| I06.FR.02 | | | -| I06.FR.03 | | | +| I06.FR.01 | 🌐 | | +| I06.FR.02 | 🌐 | | +| I06.FR.03 | ⛽️ | | ## MeterValues - Sending Meter Values not related to a transaction @@ -1795,80 +1795,81 @@ This document contains the status of which OCPP 2.0.1 numbered functional requir | ID | Status | Remark | |-----------|--------|--------| -| O01.FR.01 | | | -| O01.FR.02 | | | -| O01.FR.03 | | | -| O01.FR.04 | | | -| O01.FR.05 | | | -| O01.FR.06 | | | -| O01.FR.07 | | | -| O01.FR.08 | | | -| O01.FR.09 | | | -| O01.FR.10 | | | -| O01.FR.11 | | | -| O01.FR.12 | | | -| O01.FR.13 | | | -| O01.FR.14 | | | -| O01.FR.15 | | | -| O01.FR.16 | | | -| O01.FR.17 | | | +| O01.FR.01 | ✅ | | +| O01.FR.02 | ✅ | | +| O01.FR.03 | ✅ | | +| O01.FR.04 | 🌐 | | +| O01.FR.05 | 🌐 | | +| O01.FR.06 | ⛽️ | | +| O01.FR.07 | ⛽️ | | +| O01.FR.08 | ⛽️ | | +| O01.FR.09 | ⛽️ | | +| O01.FR.10 | ⛽️ | | +| O01.FR.11 | ⛽️ | | +| O01.FR.12 | ⛽️ | | +| O01.FR.13 | ⛽️ | | +| O01.FR.14 | ⛽️ | | +| O01.FR.15 | ⛽️ | | +| O01.FR.16 | ⛽️ | | +| O01.FR.17 | ⛽️ / 🌐 | | ## DisplayMessage - Set DisplayMessage for Transaction | ID | Status | Remark | |-----------|--------|--------| -| O02.FR.01 | | | -| O02.FR.02 | | | -| O02.FR.03 | | | -| O02.FR.04 | | | -| O02.FR.05 | | | -| O02.FR.06 | | | -| O02.FR.07 | | | -| O02.FR.08 | | | -| O02.FR.09 | | | -| O02.FR.10 | | | -| O02.FR.11 | | | -| O02.FR.12 | | | -| O02.FR.14 | | | -| O02.FR.15 | | | -| O02.FR.16 | | | -| O02.FR.17 | | | -| O02.FR.18 | | | +| O02.FR.01 | ✅ | | +| O02.FR.02 | ⛽️ | | +| O02.FR.03 | ✅ | | +| O02.FR.04 | ✅ | | +| O02.FR.05 | ✅ | | +| O02.FR.06 | ⛽️ | | +| O02.FR.07 | ⛽️ | | +| O02.FR.08 | ⛽️ | | +| O02.FR.09 | ⛽️ | | +| O02.FR.10 | ⛽️ | | +| O02.FR.11 | ⛽️ | | +| O02.FR.12 | ⛽️ / 🌐 | | +| O02.FR.14 | ⛽️ | | +| O02.FR.15 | ⛽️ | | +| O02.FR.16 | ⛽️ | | +| O02.FR.17 | ⛽️ | | +| O02.FR.18 | ⛽️ | | ## DisplayMessage - Get All DisplayMessages | ID | Status | Remark | |-----------|--------|--------| -| O03.FR.02 | | | +| O03.FR.01 | ✅ | | +| O03.FR.02 | ✅ | | | O03.FR.03 | | | | O03.FR.04 | | | | O03.FR.05 | | | -| O03.FR.06 | | | +| O03.FR.06 | ✅ | | ## DisplayMessage - Get Specific DisplayMessages | ID | Status | Remark | |-----------|--------|--------| -| O04.FR.01 | | | -| O04.FR.02 | | | -| O04.FR.03 | | | +| O04.FR.01 | ✅ | | +| O04.FR.02 | ✅ | | +| O04.FR.03 | ✅ | | | O04.FR.04 | | | | O04.FR.05 | | | | O04.FR.06 | | | -| O04.FR.07 | | | +| O04.FR.07 | ✅ | | ## DisplayMessage - Clear a DisplayMessage | ID | Status | Remark | |-----------|--------|--------| -| O05.FR.01 | | | -| O05.FR.02 | | | +| O05.FR.01 | ⛽️ | | +| O05.FR.02 | ⛽️ | | ## DisplayMessage - Replace DisplayMessage | ID | Status | Remark | |-----------|--------|--------| -| O06.FR.01 | | | +| O06.FR.01 | ⛽️ | | ## DataTransfer - Data Transfer to the Charging Station diff --git a/include/ocpp/v201/evse.hpp b/include/ocpp/v201/evse.hpp index 1bd98d523..0b3510392 100644 --- a/include/ocpp/v201/evse.hpp +++ b/include/ocpp/v201/evse.hpp @@ -163,7 +163,8 @@ class Evse : public EvseInterface { std::function& meter_values)> send_metervalue_function; boost::asio::io_service io_service; - std::optional get_current_active_import_register_meter_value(const ReadingContextEnum context); + std::optional get_meter_value_with_measurand(const MeasurandEnum measurand, + const ReadingContextEnum context); /// \brief gets the active import energy meter value from meter_value, normalized to Wh. std::optional get_active_import_register_meter_value(); diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 7232b376b..7a1e97124 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -3763,6 +3763,10 @@ void ChargePoint::handle_get_display_message(const Call call_result(response, call.uniqueId); this->send(call_result); return; + } else { + response.status = GetDisplayMessagesStatusEnum::Accepted; + ocpp::CallResult call_result(response, call.uniqueId); + this->send(call_result); } // Send display messages. The response is empty, so we don't have to get that back. diff --git a/lib/ocpp/v201/evse.cpp b/lib/ocpp/v201/evse.cpp index a8838a3b2..bfdc0ba72 100644 --- a/lib/ocpp/v201/evse.cpp +++ b/lib/ocpp/v201/evse.cpp @@ -245,11 +245,7 @@ void Evse::on_meter_value(const MeterValue& 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(); - std::optional import_register_meter_value = - get_current_active_import_register_meter_value(ReadingContextEnum::Other); - if (import_register_meter_value.has_value()) { - this->send_meter_value_on_pricing_trigger(import_register_meter_value.value()); - } + this->send_meter_value_on_pricing_trigger(meter_value); } MeterValue Evse::get_meter_value() { @@ -265,15 +261,23 @@ void Evse::clear_idle_meter_values() { this->aligned_data_updated.clear_values(); } -std::optional Evse::get_current_active_import_register_meter_value(const ReadingContextEnum context) { +std::optional Evse::get_meter_value_with_measurand(const MeasurandEnum measurand, + const ReadingContextEnum context) { std::unique_lock lk(this->meter_value_mutex); MeterValue meter_value = this->meter_value; lk.unlock(); meter_value.sampledValue.erase( - std::remove_if(meter_value.sampledValue.begin(), meter_value.sampledValue.end(), [](const SampledValue& value) { - return value.measurand != MeasurandEnum::Energy_Active_Import_Register or value.phase.has_value(); - })); + std::remove_if(meter_value.sampledValue.begin(), meter_value.sampledValue.end(), + [](const SampledValue& value) { + if (!value.measurand.has_value() || + (value.measurand.value() != MeasurandEnum::Energy_Active_Import_Register)) { + return true; + } + + return false; + }), + meter_value.sampledValue.end()); if (meter_value.sampledValue.empty()) { return std::nullopt; @@ -432,6 +436,18 @@ void Evse::set_meter_value_pricing_triggers( std::optional trigger_metervalue_at_time, std::function& meter_values)> send_metervalue_function, boost::asio::io_service& io_service) { + + 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; @@ -450,8 +466,8 @@ void Evse::set_meter_value_pricing_triggers( this->trigger_metervalue_at_time_timer = std::make_unique(&io_service, [this]() { EVLOG_error << "Sending metervalue in timer"; - const std::optional& meter_value = - this->get_current_active_import_register_meter_value(ReadingContextEnum::Other); + const std::optional& meter_value = this->get_meter_value_with_measurand( + MeasurandEnum::Energy_Active_Import_Register, ReadingContextEnum::Other); if (!meter_value.has_value()) { EVLOG_error << "Send latest meter value because of chargepoint time trigger failed"; } else { @@ -487,9 +503,16 @@ void Evse::send_meter_value_on_pricing_trigger(const MeterValue& meter_value) { const std::optional active_import_register_meter_value_wh = get_active_import_register_meter_value(); if (active_import_register_meter_value_wh.has_value() && static_cast(active_import_register_meter_value_wh.value()) >= trigger_energy_kwh * 1000) { - this->send_metervalue_function({meter_value}); - this->trigger_metervalue_on_energy_kwh.reset(); - return; + const std::optional active_import_meter_value = get_meter_value_with_measurand( + MeasurandEnum::Energy_Active_Import_Register, ReadingContextEnum::Other); + if (!active_import_meter_value.has_value()) { + EVLOG_error + << "No current active import register metervalue found. Can not send trigger metervalue."; + } else { + this->send_metervalue_function({active_import_meter_value.value()}); + this->trigger_metervalue_on_energy_kwh.reset(); + return; + } } } } @@ -525,7 +548,11 @@ void Evse::send_meter_value_on_pricing_trigger(const MeterValue& meter_value) { // 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. - this->send_metervalue_function({meter_value}); + MeterValue mv = meter_value; + for (auto& sampled_value : mv.sampledValue) { + sampled_value.context = ReadingContextEnum::Other; + } + this->send_metervalue_function({mv}); } this->last_triggered_metervalue_power_kw = current_metervalue_kw; } From 1b706636ec64f828924af6945a8d6822ea8db6b5 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Thu, 12 Sep 2024 18:26:31 +0200 Subject: [PATCH 18/22] Remove 'duplicate' function (a same kind of function was already there in utils). Some comments and also send power active measurand to be sure (if it is there anyway) Signed-off-by: Maaike Zijderveld, iolar --- include/ocpp/v201/evse.hpp | 3 -- include/ocpp/v201/utils.hpp | 8 +++++ lib/ocpp/v201/evse.cpp | 62 ++++++++++++------------------------- lib/ocpp/v201/utils.cpp | 9 ++++++ 4 files changed, 37 insertions(+), 45 deletions(-) diff --git a/include/ocpp/v201/evse.hpp b/include/ocpp/v201/evse.hpp index 0b3510392..f53cf7d19 100644 --- a/include/ocpp/v201/evse.hpp +++ b/include/ocpp/v201/evse.hpp @@ -163,9 +163,6 @@ class Evse : public EvseInterface { std::function& meter_values)> send_metervalue_function; boost::asio::io_service io_service; - std::optional get_meter_value_with_measurand(const MeasurandEnum measurand, - const ReadingContextEnum context); - /// \brief gets the active import energy meter value from meter_value, normalized to Wh. std::optional get_active_import_register_meter_value(); diff --git a/include/ocpp/v201/utils.hpp b/include/ocpp/v201/utils.hpp index 3d7e20038..ef23aa24a 100644 --- a/include/ocpp/v201/utils.hpp +++ b/include/ocpp/v201/utils.hpp @@ -41,6 +41,14 @@ std::vector get_meter_values_with_measurands_applied( const std::vector& aligned_tx_ended_measurands, ocpp::DateTime max_timestamp, bool include_sampled_signed = true, bool include_aligned_signed = true); +/// +/// \brief Set reading context of metervalue sampled values. +/// \param meter_value The meter value to set context on +/// \param reading_context Reading context to set. +/// \return The metervalue with the reading context +/// +MeterValue set_meter_value_reading_context(const MeterValue& meter_value, const ReadingContextEnum reading_context); + /// \brief Returns the given \p str hashed using SHA256 /// \param str /// \return diff --git a/lib/ocpp/v201/evse.cpp b/lib/ocpp/v201/evse.cpp index bfdc0ba72..1fcd3a44a 100644 --- a/lib/ocpp/v201/evse.cpp +++ b/lib/ocpp/v201/evse.cpp @@ -261,35 +261,6 @@ void Evse::clear_idle_meter_values() { this->aligned_data_updated.clear_values(); } -std::optional Evse::get_meter_value_with_measurand(const MeasurandEnum measurand, - const ReadingContextEnum context) { - std::unique_lock lk(this->meter_value_mutex); - MeterValue meter_value = this->meter_value; - lk.unlock(); - - meter_value.sampledValue.erase( - std::remove_if(meter_value.sampledValue.begin(), meter_value.sampledValue.end(), - [](const SampledValue& value) { - if (!value.measurand.has_value() || - (value.measurand.value() != MeasurandEnum::Energy_Active_Import_Register)) { - return true; - } - - return false; - }), - meter_value.sampledValue.end()); - - if (meter_value.sampledValue.empty()) { - return std::nullopt; - } - - for (SampledValue& v : meter_value.sampledValue) { - v.context = context; - } - - return meter_value; -} - std::optional Evse::get_active_import_register_meter_value() { std::lock_guard lk(this->meter_value_mutex); auto it = std::find_if( @@ -464,14 +435,17 @@ void Evse::set_meter_value_pricing_triggers( return; } + // Start a timer for the trigger 'atTime'. this->trigger_metervalue_at_time_timer = std::make_unique(&io_service, [this]() { EVLOG_error << "Sending metervalue in timer"; - const std::optional& meter_value = this->get_meter_value_with_measurand( - MeasurandEnum::Energy_Active_Import_Register, ReadingContextEnum::Other); - if (!meter_value.has_value()) { + + 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 { - this->send_metervalue_function({meter_value.value()}); + 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; @@ -493,6 +467,7 @@ void Evse::reset_pricing_triggers() { 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) { @@ -503,20 +478,24 @@ void Evse::send_meter_value_on_pricing_trigger(const MeterValue& meter_value) { const std::optional active_import_register_meter_value_wh = get_active_import_register_meter_value(); if (active_import_register_meter_value_wh.has_value() && static_cast(active_import_register_meter_value_wh.value()) >= trigger_energy_kwh * 1000) { - const std::optional active_import_meter_value = get_meter_value_with_measurand( - MeasurandEnum::Energy_Active_Import_Register, ReadingContextEnum::Other); - if (!active_import_meter_value.has_value()) { + 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 { - this->send_metervalue_function({active_import_meter_value.value()}); + 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(); - return; + 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 active_power_meter_value = utils::get_total_power_active_import(meter_value); if (!this->trigger_metervalue_on_power_kw.has_value() || !active_power_meter_value.has_value()) { @@ -548,12 +527,11 @@ void Evse::send_meter_value_on_pricing_trigger(const MeterValue& meter_value) { // 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. - MeterValue mv = meter_value; - for (auto& sampled_value : mv.sampledValue) { - sampled_value.context = ReadingContextEnum::Other; - } + 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 { diff --git a/lib/ocpp/v201/utils.cpp b/lib/ocpp/v201/utils.cpp index da2ae3809..80b7f94be 100644 --- a/lib/ocpp/v201/utils.cpp +++ b/lib/ocpp/v201/utils.cpp @@ -102,6 +102,15 @@ std::vector get_meter_values_with_measurands_applied( return meter_values_result; } +MeterValue set_meter_value_reading_context(const MeterValue& meter_value, const ReadingContextEnum reading_context) { + MeterValue return_value = meter_value; + for (auto& sampled_value : return_value.sampledValue) { + sampled_value.context = reading_context; + } + + return return_value; +} + std::string sha256(const std::string& str) { unsigned char hash[SHA256_DIGEST_LENGTH]; EVP_Digest(str.c_str(), str.size(), hash, NULL, EVP_sha256(), NULL); From 9ee204dea17a539f6f096c4391d65c5fbdbff531 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Thu, 12 Sep 2024 18:30:07 +0200 Subject: [PATCH 19/22] Remove todo Signed-off-by: Maaike Zijderveld, iolar --- lib/ocpp/v201/evse.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/ocpp/v201/evse.cpp b/lib/ocpp/v201/evse.cpp index 1fcd3a44a..27169e954 100644 --- a/lib/ocpp/v201/evse.cpp +++ b/lib/ocpp/v201/evse.cpp @@ -227,8 +227,6 @@ void Evse::release_transaction() { this->transaction = nullptr; this->reset_pricing_triggers(); - - // TODO mz should metervalue triggers be stored to the database as well??? } std::unique_ptr& Evse::get_transaction() { From a47c67757ea34dc85e7ae39b1d6c7429a35f47a1 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Mon, 23 Sep 2024 15:22:52 +0200 Subject: [PATCH 20/22] Formatting Signed-off-by: Maaike Zijderveld, iolar --- include/ocpp/common/types.hpp | 6 +++--- include/ocpp/v201/charge_point.hpp | 2 -- tests/lib/ocpp/v201/mocks/evse_mock.hpp | 10 ++++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/include/ocpp/common/types.hpp b/include/ocpp/common/types.hpp index 899a4b4e0..6a4813329 100644 --- a/include/ocpp/common/types.hpp +++ b/include/ocpp/common/types.hpp @@ -357,9 +357,9 @@ struct DisplayMessageContent { /// \brief Type of an identifier string. /// enum class IdentifierType { - SessionId, ///< \brief Identifier is the session id. - IdToken, ///< \brief Identifier is the id token. - TransactionId ///< \brief Identifier is the transaction id. + SessionId, ///< \brief Identifier is the session id. + IdToken, ///< \brief Identifier is the id token. + TransactionId ///< \brief Identifier is the transaction id. }; struct DisplayMessage { diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index 582b62154..b34095350 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -85,8 +85,6 @@ class UnexpectedMessageTypeFromCSMS : public std::runtime_error { using std::runtime_error::runtime_error; }; - - /// \brief Combines ChangeAvailabilityRequest with persist flag for scheduled Availability changes struct AvailabilityChange { ChangeAvailabilityRequest request; diff --git a/tests/lib/ocpp/v201/mocks/evse_mock.hpp b/tests/lib/ocpp/v201/mocks/evse_mock.hpp index 5cc709bff..da05da0ac 100644 --- a/tests/lib/ocpp/v201/mocks/evse_mock.hpp +++ b/tests/lib/ocpp/v201/mocks/evse_mock.hpp @@ -36,9 +36,11 @@ class EvseMock : public EvseInterface { (int32_t connector_id, OperationalStatusEnum new_status, bool persist)); MOCK_METHOD(void, restore_connector_operative_status, (int32_t connector_id)); MOCK_METHOD(CurrentPhaseType, get_current_phase_type, ()); - MOCK_METHOD(void, set_meter_value_pricing_triggers, (std::optional trigger_metervalue_on_power_kw, std::optional trigger_metervalue_on_energy_kwh, - std::optional trigger_metervalue_at_time, - std::function& meter_values)> send_metervalue_function, - boost::asio::io_service& io_service)); + MOCK_METHOD(void, set_meter_value_pricing_triggers, + (std::optional trigger_metervalue_on_power_kw, + std::optional trigger_metervalue_on_energy_kwh, + std::optional trigger_metervalue_at_time, + std::function& meter_values)> send_metervalue_function, + boost::asio::io_service& io_service)); }; } // namespace ocpp::v201 From 3060c4bd9e26f4eb78626dfa0d57324e26da3e11 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Mon, 23 Sep 2024 15:53:20 +0200 Subject: [PATCH 21/22] Fix order of checks because of failing test after merge. Signed-off-by: Maaike Zijderveld, iolar --- lib/ocpp/v201/charge_point.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 862e55649..61466aad1 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -61,15 +61,15 @@ ChargePoint::ChargePoint(const std::map& evse_connector_struct v2g_certificate_expiration_check_timer([this]() { this->scheduled_check_v2g_certificate_expiration(); }), callbacks(callbacks) { + if (!this->device_model) { + EVLOG_AND_THROW(std::invalid_argument("Device model should not be null")); + } + // Make sure the received callback struct is completely filled early before we actually start running if (!this->callbacks.all_callbacks_valid(this->device_model)) { EVLOG_AND_THROW(std::invalid_argument("All non-optional callbacks must be supplied")); } - if (!this->device_model) { - EVLOG_AND_THROW(std::invalid_argument("Device model should not be null")); - } - if (!this->database_handler) { EVLOG_AND_THROW(std::invalid_argument("Database handler should not be null")); } From 2aa95a1b3a295a0c95672f80157bdd5b9d63a05a Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Tue, 24 Sep 2024 11:08:14 +0200 Subject: [PATCH 22/22] Remove unnecessary TODO's. Change '&&' to 'and' and '||' to 'or' Signed-off-by: Maaike Zijderveld, iolar --- include/ocpp/v201/charge_point_callbacks.hpp | 4 +- lib/ocpp/v16/charge_point_configuration.cpp | 48 +++++----- lib/ocpp/v201/charge_point.cpp | 94 ++++++++++---------- lib/ocpp/v201/charge_point_callbacks.cpp | 8 +- lib/ocpp/v201/evse.cpp | 14 +-- 5 files changed, 85 insertions(+), 83 deletions(-) diff --git a/include/ocpp/v201/charge_point_callbacks.hpp b/include/ocpp/v201/charge_point_callbacks.hpp index 800948521..dc70dc338 100644 --- a/include/ocpp/v201/charge_point_callbacks.hpp +++ b/include/ocpp/v201/charge_point_callbacks.hpp @@ -136,11 +136,11 @@ struct Callbacks { std::optional> connection_state_changed_callback; /// \brief Callback functions called for get / set / clear display messages - std::optional(const GetDisplayMessagesRequest& request /* TODO */)>> + std::optional(const GetDisplayMessagesRequest& request)>> get_display_message_callback; std::optional& display_messages)>> set_display_message_callback; - std::optional> + std::optional> clear_display_message_callback; /// \brief Callback function is called when running cost is set. diff --git a/lib/ocpp/v16/charge_point_configuration.cpp b/lib/ocpp/v16/charge_point_configuration.cpp index ffd8c2b84..6e2357154 100644 --- a/lib/ocpp/v16/charge_point_configuration.cpp +++ b/lib/ocpp/v16/charge_point_configuration.cpp @@ -1323,7 +1323,8 @@ KeyValue ChargePointConfiguration::getNumberOfConnectorsKeyValue() { // Reservation Profile std::optional ChargePointConfiguration::getReserveConnectorZeroSupported() { std::optional reserve_connector_zero_supported = std::nullopt; - if (this->config.contains("Reservation") && this->config["Reservation"].contains("ReserveConnectorZeroSupported")) { + if (this->config.contains("Reservation") and + this->config["Reservation"].contains("ReserveConnectorZeroSupported")) { reserve_connector_zero_supported.emplace(this->config["Reservation"]["ReserveConnectorZeroSupported"]); } return reserve_connector_zero_supported; @@ -1735,14 +1736,14 @@ std::string hexToString(std::string const& s) { } bool isHexNotation(std::string const& s) { - bool is_hex = s.size() > 2 && s.find_first_not_of("0123456789abcdefABCDEF", 2) == std::string::npos; + bool is_hex = s.size() > 2 and s.find_first_not_of("0123456789abcdefABCDEF", 2) == std::string::npos; if (is_hex) { // 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); - if ((chr < 0x20 || chr > 0x7e) && chr != 0xa) { + if ((chr < 0x20 or chr > 0x7e) and chr != 0xa) { return false; } } @@ -1792,15 +1793,15 @@ bool ChargePointConfiguration::isConnectorPhaseRotationValid(std::string str) { } try { auto connector = std::stoi(e.substr(0, 1)); - if (connector < 0 || connector > this->getNumberOfConnectors()) { + if (connector < 0 or connector > this->getNumberOfConnectors()) { return false; } } catch (const std::invalid_argument&) { return false; } std::string phase_rotation = e.substr(2, 5); - if (phase_rotation != "RST" && phase_rotation != "RTS" && phase_rotation != "SRT" && phase_rotation != "STR" && - phase_rotation != "TRS" && phase_rotation != "TSR") { + if (phase_rotation != "RST" and phase_rotation != "RTS" and phase_rotation != "SRT" and + phase_rotation != "STR" and phase_rotation != "TRS" and phase_rotation != "TSR") { return false; } } @@ -1821,13 +1822,13 @@ bool ChargePointConfiguration::checkTimeOffset(const std::string& offset) { const int32_t minutes = std::stoi(times.at(1)); // And check if numbers are valid. - if (hours < -24 || hours > 24) { + 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); return false; } - if (minutes < 0 || minutes > 59) { + 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); return false; @@ -1844,7 +1845,7 @@ bool ChargePointConfiguration::checkTimeOffset(const std::string& offset) { } bool isBool(const std::string& str) { - return str == "true" || str == "false"; + return str == "true" or str == "false"; } std::optional ChargePointConfiguration::getAuthorizationKeyKeyValue() { @@ -2312,7 +2313,7 @@ KeyValue ChargePointConfiguration::getWaitForStopTransactionsOnResetTimeoutKeyVa // California Pricing Requirements bool ChargePointConfiguration::getCustomDisplayCostAndPriceEnabled() { - if (this->config.contains("CostAndPrice") && + if (this->config.contains("CostAndPrice") and this->config.at("CostAndPrice").contains("CustomDisplayCostAndPrice")) { return this->config["CostAndPrice"]["CustomDisplayCostAndPrice"]; } @@ -2330,7 +2331,7 @@ KeyValue ChargePointConfiguration::getCustomDisplayCostAndPriceEnabledKeyValue() } std::optional ChargePointConfiguration::getPriceNumberOfDecimalsForCostValues() { - if (this->config.contains("CostAndPrice") && + if (this->config.contains("CostAndPrice") and this->config.at("CostAndPrice").contains("NumberOfDecimalsForCostValues")) { return this->config["CostAndPrice"]["NumberOfDecimalsForCostValues"]; } @@ -2352,7 +2353,7 @@ std::optional ChargePointConfiguration::getPriceNumberOfDecimalsForCos } std::optional ChargePointConfiguration::getDefaultPriceText(const std::string& language) { - if (this->config.contains("CostAndPrice") && this->config.at("CostAndPrice").contains("DefaultPriceText")) { + if (this->config.contains("CostAndPrice") and this->config.at("CostAndPrice").contains("DefaultPriceText")) { bool found = false; json result = json::object(); json& default_price = this->config["CostAndPrice"]["DefaultPriceText"]; @@ -2415,7 +2416,7 @@ ConfigurationStatus ChargePointConfiguration::setDefaultPriceText(const CiString } json default_price = json::object(); - if (this->config.contains("CostAndPrice") && this->config.at("CostAndPrice").contains("DefaultPriceText")) { + if (this->config.contains("CostAndPrice") and this->config.at("CostAndPrice").contains("DefaultPriceText")) { json result = json::object(); default_price = this->config["CostAndPrice"]["DefaultPriceText"]; } @@ -2460,7 +2461,7 @@ KeyValue ChargePointConfiguration::getDefaultPriceTextKeyValue(const std::string } std::optional> ChargePointConfiguration::getAllDefaultPriceTextKeyValues() { - if (this->config.contains("CostAndPrice") && this->config.at("CostAndPrice").contains("DefaultPriceText")) { + if (this->config.contains("CostAndPrice") and this->config.at("CostAndPrice").contains("DefaultPriceText")) { std::vector key_values; const json& default_price = this->config["CostAndPrice"]["DefaultPriceText"]; if (!default_price.contains("priceTexts")) { @@ -2493,7 +2494,7 @@ std::optional> ChargePointConfiguration::getAllDefaultPric } std::optional ChargePointConfiguration::getDefaultPrice() { - if (this->config.contains("CostAndPrice") && this->config.at("CostAndPrice").contains("DefaultPrice")) { + if (this->config.contains("CostAndPrice") and this->config.at("CostAndPrice").contains("DefaultPrice")) { return this->config["CostAndPrice"]["DefaultPrice"].dump(2); } @@ -2530,7 +2531,7 @@ std::optional ChargePointConfiguration::getDefaultPriceKeyValue() { } std::optional ChargePointConfiguration::getDisplayTimeOffset() { - if (this->config.contains("CostAndPrice") && this->config["CostAndPrice"].contains("TimeOffset")) { + if (this->config.contains("CostAndPrice") and this->config["CostAndPrice"].contains("TimeOffset")) { return this->config["CostAndPrice"]["TimeOffset"]; } @@ -2560,7 +2561,7 @@ std::optional ChargePointConfiguration::getDisplayTimeOffsetKeyValue() } std::optional ChargePointConfiguration::getNextTimeOffsetTransitionDateTime() { - if (this->config.contains("CostAndPrice") && + if (this->config.contains("CostAndPrice") and this->config["CostAndPrice"].contains("NextTimeOffsetTransitionDateTime")) { return this->config["CostAndPrice"]["NextTimeOffsetTransitionDateTime"]; } @@ -2594,7 +2595,7 @@ std::optional ChargePointConfiguration::getNextTimeOffsetTransitionDat } std::optional ChargePointConfiguration::getTimeOffsetNextTransition() { - if (this->config.contains("CostAndPrice") && this->config["CostAndPrice"].contains("TimeOffsetNextTransition")) { + if (this->config.contains("CostAndPrice") and this->config["CostAndPrice"].contains("TimeOffsetNextTransition")) { return this->config["CostAndPrice"]["TimeOffsetNextTransition"]; } @@ -2624,7 +2625,7 @@ std::optional ChargePointConfiguration::getTimeOffsetNextTransitionKey } std::optional ChargePointConfiguration::getCustomIdleFeeAfterStop() { - if (this->config.contains("CostAndPrice") && this->config["CostAndPrice"].contains("CustomIdleFeeAfterStop")) { + if (this->config.contains("CostAndPrice") and this->config["CostAndPrice"].contains("CustomIdleFeeAfterStop")) { return this->config["CostAndPrice"]["CustomIdleFeeAfterStop"]; } @@ -2650,7 +2651,8 @@ std::optional ChargePointConfiguration::getCustomIdleFeeAfterStopKeyVa } std::optional ChargePointConfiguration::getCustomMultiLanguageMessagesEnabled() { - if (this->config.contains("CostAndPrice") && this->config["CostAndPrice"].contains("CustomMultiLanguageMessages")) { + if (this->config.contains("CostAndPrice") and + this->config["CostAndPrice"].contains("CustomMultiLanguageMessages")) { return this->config["CostAndPrice"]["CustomMultiLanguageMessages"]; } @@ -2671,7 +2673,7 @@ std::optional ChargePointConfiguration::getCustomMultiLanguageMessages } std::optional ChargePointConfiguration::getMultiLanguageSupportedLanguages() { - if (this->config.contains("CostAndPrice") && this->config["CostAndPrice"].contains("SupportedLanguages")) { + if (this->config.contains("CostAndPrice") and this->config["CostAndPrice"].contains("SupportedLanguages")) { return this->config["CostAndPrice"]["SupportedLanguages"]; } @@ -2692,7 +2694,7 @@ std::optional ChargePointConfiguration::getMultiLanguageSupportedLangu } std::optional ChargePointConfiguration::getLanguage() { - if (this->config.contains("CostAndPrice") && this->config["CostAndPrice"].contains("Language")) { + if (this->config.contains("CostAndPrice") and this->config["CostAndPrice"].contains("Language")) { return this->config["CostAndPrice"]["Language"]; } @@ -3071,7 +3073,7 @@ std::optional ChargePointConfiguration::get(CiString<50> key) { if (key == "DefaultPrice") { return this->getDefaultPriceKeyValue(); } - if (key.get().find("DefaultPriceText") == 0 && this->getCustomMultiLanguageMessagesEnabled().has_value() && + if (key.get().find("DefaultPriceText") == 0 and this->getCustomMultiLanguageMessagesEnabled().has_value() and this->getCustomMultiLanguageMessagesEnabled().value()) { const std::vector message_language = split_string(key, ','); if (message_language.size() > 1) { diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 61466aad1..c9920692d 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -268,7 +268,7 @@ void ChargePoint::on_firmware_update_status_notification(int32_t request_id, CiString<50>(ocpp::security_events::INVALIDFIRMWARESIGNATURE), std::optional>("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 - } else if (req.status == FirmwareStatusEnum::InstallVerificationFailed || + } else if (req.status == FirmwareStatusEnum::InstallVerificationFailed or req.status == FirmwareStatusEnum::InstallationFailed) { this->restore_all_connector_states(); } @@ -615,14 +615,14 @@ void ChargePoint::handle_cost_and_tariff(const TransactionEventResponse& respons std::vector cost_messages; // Check if there is a tariff message and if 'Tariff' is available and enabled - if (response.updatedPersonalMessage.has_value() && tariff_enabled) { + if (response.updatedPersonalMessage.has_value() and tariff_enabled) { MessageContent personal_message = response.updatedPersonalMessage.value(); DisplayMessageContent message = message_content_to_display_message_content(personal_message); cost_messages.push_back(message); // If cost is enabled, the message will be sent to the running cost callback. But if it is not enabled, the // tariff message will be sent using the display message callback. - if (!cost_enabled && this->callbacks.set_display_message_callback.has_value() && + if (!cost_enabled and this->callbacks.set_display_message_callback.has_value() and this->callbacks.set_display_message_callback != nullptr) { DisplayMessage display_message; display_message.message = message; @@ -633,7 +633,7 @@ void ChargePoint::handle_cost_and_tariff(const TransactionEventResponse& respons } // Check if cost is available and enabled, and if there is a totalcost message. - if (cost_enabled && response.totalCost.has_value() && this->callbacks.set_running_cost_callback.has_value()) { + if (cost_enabled and response.totalCost.has_value() and this->callbacks.set_running_cost_callback.has_value()) { RunningCost running_cost; std::string total_cost; // We use the original string and convert it to a double ourselves, as the nlohmann library converts it to a @@ -663,7 +663,7 @@ void ChargePoint::handle_cost_and_tariff(const TransactionEventResponse& respons }); if (it != mv.sampledValue.end()) { // Found a sampled metervalue we are searching for! - if (!max_meter_value.has_value() || max_meter_value.value() < it->value) { + if (!max_meter_value.has_value() or max_meter_value.value() < it->value) { max_meter_value = it->value; } } @@ -680,16 +680,16 @@ void ChargePoint::handle_cost_and_tariff(const TransactionEventResponse& respons // 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") && - (custom_data.at("vendorId").get() == "org.openchargealliance.org.qrcode") &&*/ - custom_data.contains("qrCodeText") && + if (/*custom_data.contains("vendorId") and + (custom_data.at("vendorId").get() == "org.openchargealliance.org.qrcode") and */ + custom_data.contains("qrCodeText") and device_model->get_optional_value(ControllerComponentVariables::DisplayMessageQRCodeDisplayCapable) .value_or(false)) { running_cost.qr_code_text = custom_data.at("qrCodeText"); } // Add multilanguage messages - if (custom_data.contains("updatedPersonalMessageExtra") && is_multilanguage_enabled()) { + 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 metadata = device_model->get_variable_meta_data( @@ -698,7 +698,7 @@ void ChargePoint::handle_cost_and_tariff(const TransactionEventResponse& respons std::vector supported_languages; - if (metadata.has_value() && metadata.value().characteristics.valuesList.has_value()) { + if (metadata.has_value() and metadata.value().characteristics.valuesList.has_value()) { supported_languages = ocpp::split_string(metadata.value().characteristics.valuesList.value(), ',', true); } else { @@ -733,7 +733,7 @@ void ChargePoint::handle_cost_and_tariff(const TransactionEventResponse& respons } } - if (tariff_enabled && !cost_messages.empty()) { + if (tariff_enabled and !cost_messages.empty()) { running_cost.cost_messages = cost_messages; } @@ -756,14 +756,14 @@ bool ChargePoint::is_multilanguage_enabled() const { bool ChargePoint::is_tariff_enabled() const { return this->device_model->get_optional_value(ControllerComponentVariables::TariffCostCtrlrAvailableTariff) - .value_or(false) && + .value_or(false) and this->device_model->get_optional_value(ControllerComponentVariables::TariffCostCtrlrEnabledTariff) .value_or(false); } bool ChargePoint::is_cost_enabled() const { return this->device_model->get_optional_value(ControllerComponentVariables::TariffCostCtrlrAvailableCost) - .value_or(false) && + .value_or(false) and this->device_model->get_optional_value(ControllerComponentVariables::TariffCostCtrlrEnabledCost) .value_or(false); } @@ -999,12 +999,12 @@ AuthorizeResponse ChargePoint::validate_token(const IdToken id_token, const std: const auto lifetime = this->device_model->get_optional_value(ControllerComponentVariables::AuthCacheLifeTime); const bool lifetime_expired = - lifetime.has_value() && ((cache_entry->last_used.to_time_point() + - std::chrono::seconds(lifetime.value())) < now.to_time_point()); + 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 || cache_expiry_passed) { + 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"; @@ -1113,7 +1113,7 @@ void ChargePoint::initialize(const std::map& evse_connector_st evse_connector_structure, database_handler, [this](auto evse_id, auto connector_id, auto status, bool initiated_by_trigger_message) { this->update_dm_availability_state(evse_id, connector_id, status); - if (this->connectivity_manager == nullptr || !this->connectivity_manager->is_websocket_connected() || + if (this->connectivity_manager == nullptr or !this->connectivity_manager->is_websocket_connected() or this->registration_status != RegistrationStatusEnum::Accepted) { return false; } else { @@ -1482,7 +1482,7 @@ void ChargePoint::message_callback(const std::string& message) { this->send(call_error); } catch (json::exception& e) { EVLOG_error << "JSON exception during handling of message: " << e.what(); - if (json_message.is_array() && json_message.size() > MESSAGE_ID) { + if (json_message.is_array() and json_message.size() > MESSAGE_ID) { auto call_error = CallError(enhanced_message.uniqueId, "FormationViolation", e.what(), json({})); this->send(call_error); } @@ -1516,7 +1516,7 @@ void ChargePoint::change_all_connectors_to_unavailable_for_firmware_update() { } } // Check succeeded, trigger the callback if needed - if (this->callbacks.all_connectors_unavailable_callback.has_value() && + if (this->callbacks.all_connectors_unavailable_callback.has_value() and this->are_all_connectors_effectively_inoperative()) { this->callbacks.all_connectors_unavailable_callback.value()(); } @@ -1735,7 +1735,7 @@ void ChargePoint::handle_scheduled_change_availability_requests(const int32_t ev this->execute_change_availability_request(req, persist); this->scheduled_change_availability_requests.erase(evse_id); // Check succeeded, trigger the callback if needed - if (this->callbacks.all_connectors_unavailable_callback.has_value() && + if (this->callbacks.all_connectors_unavailable_callback.has_value() and this->are_all_connectors_effectively_inoperative()) { this->callbacks.all_connectors_unavailable_callback.value()(); } @@ -1755,10 +1755,10 @@ void ChargePoint::handle_scheduled_change_availability_requests(const int32_t ev static bool component_variable_change_requires_websocket_option_update_without_reconnect( const ComponentVariable& component_variable) { - return component_variable == ControllerComponentVariables::RetryBackOffRandomRange || - component_variable == ControllerComponentVariables::RetryBackOffRepeatTimes || - component_variable == ControllerComponentVariables::RetryBackOffWaitMinimum || - component_variable == ControllerComponentVariables::NetworkProfileConnectionAttempts || + 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; } @@ -1957,7 +1957,7 @@ bool ChargePoint::is_evse_connector_available(EvseInterface& evse) const { evse.get_connector(static_cast(i))->get_effective_connector_status(); // At least one of the connectors is available / not faulted. - if (status != ConnectorStatusEnum::Faulted && status != ConnectorStatusEnum::Unavailable) { + if (status != ConnectorStatusEnum::Faulted and status != ConnectorStatusEnum::Unavailable) { return true; } } @@ -2054,7 +2054,7 @@ void ChargePoint::sign_certificate_req(const ocpp::CertificateSigningUseEnum& ce const auto result = this->evse_security->generate_certificate_signing_request( certificate_signing_use, country.value(), organization.value(), common.value(), should_use_tpm); - if (result.status != GetCertificateSignRequestStatus::Accepted || !result.csr.has_value()) { + 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); @@ -2208,13 +2208,13 @@ void ChargePoint::transaction_event_req(const TransactionEventEnum& event_type, auto it = std::find_if( remote_start_id_per_evse.begin(), remote_start_id_per_evse.end(), [&id_token, &evse](const std::pair>& remote_start_per_evse) { - if (id_token.has_value() && remote_start_per_evse.second.first.idToken == id_token.value().idToken) { + 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() && evse.value().id == remote_start_per_evse.first) { + if (evse.has_value() and evse.value().id == remote_start_per_evse.first) { return true; } } @@ -2278,7 +2278,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() or pos == 0 && data.empty()) { + while (pos < data.length() or pos == 0 and data.empty()) { const auto req = [&]() { NotifyCustomerInformationRequest req; req.data = CiString<512>(data.substr(pos, 512)); @@ -2417,7 +2417,7 @@ void ChargePoint::handle_boot_notification_response(CallResultregistration_status == RegistrationStatusEnum::Accepted) { this->message_queue->set_registration_status_accepted(); // B01.FR.06 Only use boot timestamp if TimeSource contains Heartbeat - if (this->callbacks.time_sync_callback.has_value() && + if (this->callbacks.time_sync_callback.has_value() and this->device_model->get_value(ControllerComponentVariables::TimeSource).find("Heartbeat") != std::string::npos) { this->callbacks.time_sync_callback.value()(msg.currentTime); @@ -2667,7 +2667,7 @@ void ChargePoint::handle_reset_req(Call call) { bool transaction_active = false; std::set evse_active_transactions; std::set evse_no_transactions; - if (msg.evseId.has_value() && this->evse_manager->get_evse(msg.evseId.value()).has_active_transaction()) { + if (msg.evseId.has_value() and this->evse_manager->get_evse(msg.evseId.value()).has_active_transaction()) { transaction_active = true; evse_active_transactions.emplace(msg.evseId.value()); } else { @@ -2705,7 +2705,7 @@ void ChargePoint::handle_reset_req(Call call) { response.status = ResetStatusEnum::Rejected; } - if (response.status == ResetStatusEnum::Accepted && transaction_active && msg.type == ResetEnum::OnIdle) { + if (response.status == ResetStatusEnum::Accepted and transaction_active and msg.type == ResetEnum::OnIdle) { if (msg.evseId.has_value()) { // B12.FR.07 this->reset_scheduled_evseids.insert(msg.evseId.value()); @@ -2722,13 +2722,13 @@ void ChargePoint::handle_reset_req(Call call) { // Reset response is sent, now set evse connectors to unavailable and / or // stop transaction (depending on reset type) - if (response.status != ResetStatusEnum::Rejected && transaction_active) { + if (response.status != ResetStatusEnum::Rejected and transaction_active) { if (msg.type == ResetEnum::Immediate) { // B12.FR.08 and B12.FR.04 for (const int32_t evse_id : evse_active_transactions) { callbacks.stop_transaction_callback(evse_id, ReasonEnum::ImmediateReset); } - } else if (msg.type == ResetEnum::OnIdle && !evse_no_transactions.empty()) { + } else if (msg.type == ResetEnum::OnIdle and !evse_no_transactions.empty()) { for (const int32_t evse_id : evse_no_transactions) { auto& evse = this->evse_manager->get_evse(evse_id); this->set_evse_connectors_unavailable(evse, false); @@ -3109,7 +3109,7 @@ void ChargePoint::handle_remote_start_transaction_request(Call } void ChargePoint::handle_heartbeat_response(CallResult call) { - if (this->callbacks.time_sync_callback.has_value() && + if (this->callbacks.time_sync_callback.has_value() and this->device_model->get_value(ControllerComponentVariables::TimeSource).find("Heartbeat") != std::string::npos) { // the received currentTime was the time the CSMS received the heartbeat request @@ -3281,7 +3281,7 @@ void ChargePoint::handle_costupdated_req(const Call call) { CostUpdatedResponse response; ocpp::CallResult call_result(response, call.uniqueId); - if (!is_cost_enabled() || !this->callbacks.set_running_cost_callback.has_value()) { + if (!is_cost_enabled() or !this->callbacks.set_running_cost_callback.has_value()) { this->send(call_result); return; } @@ -3291,7 +3291,7 @@ void ChargePoint::handle_costupdated_req(const Call call) { if (device_model ->get_optional_value(ControllerComponentVariables::CustomImplementationCaliforniaPricingEnabled) - .value_or(false) && + .value_or(false) and call.msg.customData.has_value()) { const json running_cost_json = call.msg.customData.value(); @@ -3329,7 +3329,7 @@ void ChargePoint::handle_costupdated_req(const Call call) { this->send(call_result); // In OCPP 2.0.1, the chargepoint status trigger is not used. - if (!triggers.at_energy_kwh.has_value() && !triggers.at_power_kw.has_value() && !triggers.at_time.has_value()) { + if (!triggers.at_energy_kwh.has_value() and !triggers.at_power_kw.has_value() and !triggers.at_time.has_value()) { return; } @@ -3520,7 +3520,7 @@ void ChargePoint::handle_firmware_update_req(Call call) { ocpp::CallResult call_result(response, call.uniqueId); this->send(call_result); - if ((response.status == UpdateFirmwareStatusEnum::InvalidCertificate) || + if ((response.status == UpdateFirmwareStatusEnum::InvalidCertificate) or (response.status == UpdateFirmwareStatusEnum::RevokedCertificate)) { // L01.FR.02 this->security_event_notification_req( @@ -3663,7 +3663,7 @@ void ChargePoint::handle_set_monitoring_base_req(Call } else { response.status = GenericDeviceModelStatusEnum::Accepted; - if (msg.monitoringBase == MonitoringBaseEnum::HardWiredOnly || + if (msg.monitoringBase == MonitoringBaseEnum::HardWiredOnly or msg.monitoringBase == MonitoringBaseEnum::FactoryDefault) { try { this->device_model->clear_custom_monitors(); @@ -3682,7 +3682,7 @@ void ChargePoint::handle_set_monitoring_level_req(Call MonitoringLevelSeverity::MAX) { + if (msg.severity < MonitoringLevelSeverity::MIN or msg.severity > MonitoringLevelSeverity::MAX) { response.status = GenericStatusEnum::Rejected; } else { auto result = this->device_model->set_value( @@ -3767,7 +3767,7 @@ void ChargePoint::notify_monitoring_report_req(const int request_id, // Construct sub-message part std::vector sub_data; - for (int32_t j = i; j < MAXIMUM_VARIABLE_SEND && j < montoring_data.size(); ++j) { + for (int32_t j = i; j < MAXIMUM_VARIABLE_SEND and j < montoring_data.size(); ++j) { sub_data.push_back(std::move(montoring_data[i + j])); } @@ -3907,11 +3907,11 @@ void ChargePoint::handle_set_display_message(const Callauth_cache_cleanup_mutex); if (this->auth_cache_cleanup_cv.wait_for(lk, std::chrono::minutes(15), [&]() { - return this->stop_auth_cache_cleanup_handler || this->auth_cache_cleanup_required; + return this->stop_auth_cache_cleanup_handler or this->auth_cache_cleanup_required; })) { EVLOG_debug << "Triggered authorization cache cleanup"; } else { @@ -4320,7 +4320,7 @@ GetCompositeScheduleResponse ChargePoint::get_composite_schedule_internal(const request.chargingRateUnit.value())) != supported_charging_rate_units.npos; // K01.FR.05 & K01.FR.07 - if (this->evse_manager->does_evse_exist(request.evseId) && unit_supported) { + if (this->evse_manager->does_evse_exist(request.evseId) and unit_supported) { auto start_time = ocpp::DateTime(); auto end_time = ocpp::DateTime(start_time.to_time_point() + std::chrono::seconds(request.duration)); diff --git a/lib/ocpp/v201/charge_point_callbacks.cpp b/lib/ocpp/v201/charge_point_callbacks.cpp index a3d27e735..93063c6ab 100644 --- a/lib/ocpp/v201/charge_point_callbacks.cpp +++ b/lib/ocpp/v201/charge_point_callbacks.cpp @@ -53,10 +53,10 @@ bool Callbacks::all_callbacks_valid(std::shared_ptr device_model) c // If cost is available and enabled, the running cost callback must be enabled as well. if (device_model->get_optional_value(ControllerComponentVariables::TariffCostCtrlrAvailableCost) - .value_or(false) && + .value_or(false) and device_model->get_optional_value(ControllerComponentVariables::TariffCostCtrlrEnabledCost) .value_or(false)) { - if (!this->set_running_cost_callback.has_value() || this->set_running_cost_callback.value() == nullptr) { + 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' and 'Enabled' in device model, " "but callback is not implemented"; valid = false; @@ -64,10 +64,10 @@ bool Callbacks::all_callbacks_valid(std::shared_ptr device_model) c } if (device_model->get_optional_value(ControllerComponentVariables::TariffCostCtrlrAvailableTariff) - .value_or(false) && + .value_or(false) and device_model->get_optional_value(ControllerComponentVariables::TariffCostCtrlrEnabledTariff) .value_or(false)) { - if (!this->set_display_message_callback.has_value() || + 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' and 'Enabled'. In this case, the " diff --git a/lib/ocpp/v201/evse.cpp b/lib/ocpp/v201/evse.cpp index 27169e954..6260131dd 100644 --- a/lib/ocpp/v201/evse.cpp +++ b/lib/ocpp/v201/evse.cpp @@ -420,7 +420,7 @@ void Evse::set_meter_value_pricing_triggers( 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 && trigger_metervalue_at_time.has_value()) { + 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; } @@ -474,7 +474,7 @@ void Evse::send_meter_value_on_pricing_trigger(const MeterValue& meter_value) { this->trigger_metervalue_on_energy_kwh.reset(); } else { const std::optional active_import_register_meter_value_wh = get_active_import_register_meter_value(); - if (active_import_register_meter_value_wh.has_value() && + if (active_import_register_meter_value_wh.has_value() and static_cast(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}); @@ -496,7 +496,7 @@ void Evse::send_meter_value_on_pricing_trigger(const MeterValue& meter_value) { // prevent constant triggering. const std::optional active_power_meter_value = utils::get_total_power_active_import(meter_value); - if (!this->trigger_metervalue_on_power_kw.has_value() || !active_power_meter_value.has_value()) { + if (!this->trigger_metervalue_on_power_kw.has_value() or !active_power_meter_value.has_value()) { return; } @@ -518,9 +518,9 @@ void Evse::send_meter_value_on_pricing_trigger(const MeterValue& meter_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 && current_metervalue_kw >= (trigger_power_kw + hysterisis_kw)) || + (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 && current_metervalue_kw <= (trigger_power_kw - hysterisis_kw))) { + (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) { @@ -559,7 +559,7 @@ OperationalStatusEnum Evse::get_effective_operational_status() { } Connector* Evse::get_connector(int32_t connector_id) { - if (connector_id <= 0 || connector_id > this->get_number_of_connectors()) { + 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()); @@ -573,7 +573,7 @@ CurrentPhaseType Evse::get_current_phase_type() { auto supply_phases = this->device_model.get_optional_value(evse_variable); if (supply_phases == std::nullopt) { return CurrentPhaseType::Unknown; - } else if (*supply_phases == 1 || *supply_phases == 3) { + } else if (*supply_phases == 1 or *supply_phases == 3) { return CurrentPhaseType::AC; } else if (*supply_phases == 0) { return CurrentPhaseType::DC;