From 066f26e6e20cd2cd54ad63197ba36981692c308e Mon Sep 17 00:00:00 2001 From: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> Date: Mon, 8 Jul 2024 19:18:01 +0000 Subject: [PATCH 01/18] smart_charging: Make profile and evse_id order consistent Signed-off-by: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> --- include/ocpp/v201/smart_charging.hpp | 4 +- lib/ocpp/v201/smart_charging.cpp | 10 ++-- .../ocpp/v201/test_smart_charging_handler.cpp | 46 +++++++++---------- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/include/ocpp/v201/smart_charging.hpp b/include/ocpp/v201/smart_charging.hpp index 63967fb74..a9ef11bcd 100644 --- a/include/ocpp/v201/smart_charging.hpp +++ b/include/ocpp/v201/smart_charging.hpp @@ -77,7 +77,7 @@ class SmartChargingHandler { /// /// \brief Adds a given \p profile and associated \p evse_id to our stored list of profiles /// - SetChargingProfileResponse add_profile(int32_t evse_id, ChargingProfile& profile); + SetChargingProfileResponse add_profile(ChargingProfile& profile, int32_t evse_id); /// /// \brief Retrieves existing profiles on system. @@ -117,7 +117,7 @@ class SmartChargingHandler { /// \brief Checks a given \p profile and associated \p evse_id validFrom and validTo range /// This method assumes that the existing profile will have dates set for validFrom and validTo /// - bool is_overlapping_validity_period(int evse_id, const ChargingProfile& profile) const; + bool is_overlapping_validity_period(const ChargingProfile& profile, int32_t evse_id) const; /// /// \brief Checks a given \p profile does not have an id that conflicts with an existing profile diff --git a/lib/ocpp/v201/smart_charging.cpp b/lib/ocpp/v201/smart_charging.cpp index 7156c289e..731ef4d3e 100644 --- a/lib/ocpp/v201/smart_charging.cpp +++ b/lib/ocpp/v201/smart_charging.cpp @@ -161,7 +161,7 @@ ProfileValidationResultEnum SmartChargingHandler::validate_charging_station_max_ return ProfileValidationResultEnum::InvalidProfileType; } - if (is_overlapping_validity_period(evse_id, profile)) { + if (is_overlapping_validity_period(profile, evse_id)) { return ProfileValidationResultEnum::DuplicateProfileValidityPeriod; } @@ -180,7 +180,7 @@ ProfileValidationResultEnum SmartChargingHandler::validate_tx_default_profile(Ch int32_t evse_id) const { auto profiles = evse_id == 0 ? get_evse_specific_tx_default_profiles() : get_station_wide_tx_default_profiles(); - if (is_overlapping_validity_period(evse_id, profile)) { + if (is_overlapping_validity_period(profile, evse_id)) { return ProfileValidationResultEnum::DuplicateProfileValidityPeriod; } @@ -322,7 +322,7 @@ SmartChargingHandler::validate_profile_schedules(ChargingProfile& profile, return ProfileValidationResultEnum::Valid; } -SetChargingProfileResponse SmartChargingHandler::add_profile(int32_t evse_id, ChargingProfile& profile) { +SetChargingProfileResponse SmartChargingHandler::add_profile(ChargingProfile& profile, int32_t evse_id) { SetChargingProfileResponse response; response.status = ChargingProfileStatusEnum::Accepted; auto found_profile = false; @@ -391,8 +391,8 @@ std::vector SmartChargingHandler::get_station_wide_tx_default_p return station_wide_tx_default_profiles; } -bool SmartChargingHandler::is_overlapping_validity_period(int candidate_evse_id, - const ChargingProfile& candidate_profile) const { +bool SmartChargingHandler::is_overlapping_validity_period(const ChargingProfile& candidate_profile, + int candidate_evse_id) const { if (candidate_profile.chargingProfilePurpose == ChargingProfilePurposeEnum::TxProfile) { // This only applies to non TxProfile types. diff --git a/tests/lib/ocpp/v201/test_smart_charging_handler.cpp b/tests/lib/ocpp/v201/test_smart_charging_handler.cpp index ee15fbb67..fa0ef65ef 100644 --- a/tests/lib/ocpp/v201/test_smart_charging_handler.cpp +++ b/tests/lib/ocpp/v201/test_smart_charging_handler.cpp @@ -204,7 +204,7 @@ class ChargepointTestFixtureV201 : public DatabaseTestingUtils { auto existing_profile = create_charging_profile( profile_id, ChargingProfilePurposeEnum::TxDefaultProfile, create_charge_schedule(ChargingRateUnitEnum::A), {}, ChargingProfileKindEnum::Absolute, DEFAULT_STACK_LEVEL, validFrom, validTo); - handler.add_profile(evse_id, existing_profile); + handler.add_profile(existing_profile, evse_id); } // Default values used within the tests @@ -231,7 +231,7 @@ TEST_F(ChargepointTestFixtureV201, auto external_constraints = create_charging_profile(DEFAULT_PROFILE_ID, ChargingProfilePurposeEnum::ChargingStationExternalConstraints, create_charge_schedule(ChargingRateUnitEnum::A), {}); - handler.add_profile(STATION_WIDE_ID, external_constraints); + handler.add_profile(external_constraints, STATION_WIDE_ID); auto profile = create_charging_profile(DEFAULT_PROFILE_ID, ChargingProfilePurposeEnum::TxProfile, create_charge_schedule(ChargingRateUnitEnum::A), {}); @@ -449,7 +449,7 @@ TEST_F(ChargepointTestFixtureV201, auto profile_2 = create_charging_profile(DEFAULT_PROFILE_ID + 1, ChargingProfilePurposeEnum::TxProfile, create_charge_schedule(ChargingRateUnitEnum::A), DEFAULT_TX_ID, ChargingProfileKindEnum::Absolute, same_stack_level); - handler.add_profile(DEFAULT_EVSE_ID, profile_2); + handler.add_profile(profile_2, DEFAULT_EVSE_ID); auto sut = handler.validate_tx_profile(profile_1, DEFAULT_EVSE_ID); EXPECT_THAT(sut, testing::Eq(ProfileValidationResultEnum::TxProfileConflictingStackLevel)); @@ -467,7 +467,7 @@ TEST_F(ChargepointTestFixtureV201, auto profile_2 = create_charging_profile(DEFAULT_PROFILE_ID + 1, ChargingProfilePurposeEnum::TxProfile, create_charge_schedule(ChargingRateUnitEnum::A), different_transaction_id, ChargingProfileKindEnum::Absolute, same_stack_level); - handler.add_profile(DEFAULT_EVSE_ID, profile_2); + handler.add_profile(profile_2, DEFAULT_EVSE_ID); auto sut = handler.validate_tx_profile(profile_1, DEFAULT_EVSE_ID); EXPECT_THAT(sut, testing::Eq(ProfileValidationResultEnum::Valid)); @@ -487,7 +487,7 @@ TEST_F(ChargepointTestFixtureV201, create_charge_schedule(ChargingRateUnitEnum::A), DEFAULT_TX_ID, ChargingProfileKindEnum::Absolute, stack_level_2); - handler.add_profile(DEFAULT_EVSE_ID, profile_2); + handler.add_profile(profile_2, DEFAULT_EVSE_ID); auto sut = handler.validate_tx_profile(profile_1, DEFAULT_EVSE_ID); EXPECT_THAT(sut, testing::Eq(ProfileValidationResultEnum::Valid)); @@ -944,7 +944,7 @@ TEST_F( auto external_constraints = create_charging_profile(DEFAULT_PROFILE_ID, ChargingProfilePurposeEnum::ChargingStationExternalConstraints, create_charge_schedule(ChargingRateUnitEnum::A), {}); - handler.add_profile(STATION_WIDE_ID, external_constraints); + handler.add_profile(external_constraints, STATION_WIDE_ID); auto profile = create_charging_profile( DEFAULT_PROFILE_ID, ChargingProfilePurposeEnum::TxDefaultProfile, @@ -961,7 +961,7 @@ TEST_F(ChargepointTestFixtureV201, create_charge_schedule(ChargingRateUnitEnum::A), uuid(), ChargingProfileKindEnum::Absolute, DEFAULT_STACK_LEVEL); - auto sut = handler.add_profile(STATION_WIDE_ID, profile); + auto sut = handler.add_profile(profile, STATION_WIDE_ID); EXPECT_THAT(sut.status, testing::Eq(ChargingProfileStatusEnum::Accepted)); EXPECT_THAT(handler.get_profiles(), testing::Contains(profile)); @@ -974,7 +974,7 @@ TEST_F(ChargepointTestFixtureV201, create_charge_schedule(ChargingRateUnitEnum::A), uuid(), ChargingProfileKindEnum::Absolute, DEFAULT_STACK_LEVEL); - auto sut = handler.add_profile(DEFAULT_EVSE_ID, profile); + auto sut = handler.add_profile(profile, DEFAULT_EVSE_ID); EXPECT_THAT(sut.status, testing::Eq(ChargingProfileStatusEnum::Accepted)); EXPECT_THAT(handler.get_profiles(), testing::Contains(profile)); @@ -990,8 +990,8 @@ TEST_F(ChargepointTestFixtureV201, create_charge_schedule(ChargingRateUnitEnum::A), uuid(), ChargingProfileKindEnum::Absolute, DEFAULT_STACK_LEVEL); - auto sut1 = handler.add_profile(DEFAULT_EVSE_ID, profile1); - auto sut2 = handler.add_profile(DEFAULT_EVSE_ID, profile2); + auto sut1 = handler.add_profile(profile1, DEFAULT_EVSE_ID); + auto sut2 = handler.add_profile(profile2, DEFAULT_EVSE_ID); auto profiles = handler.get_profiles(); @@ -1010,8 +1010,8 @@ TEST_F(ChargepointTestFixtureV201, create_charge_schedule(ChargingRateUnitEnum::A), uuid(), ChargingProfileKindEnum::Absolute, DEFAULT_STACK_LEVEL); - auto sut1 = handler.add_profile(STATION_WIDE_ID, profile1); - auto sut2 = handler.add_profile(DEFAULT_EVSE_ID, profile2); + auto sut1 = handler.add_profile(profile1, STATION_WIDE_ID); + auto sut2 = handler.add_profile(profile2, DEFAULT_EVSE_ID); auto profiles = handler.get_profiles(); @@ -1030,8 +1030,8 @@ TEST_F(ChargepointTestFixtureV201, create_charge_schedule(ChargingRateUnitEnum::A), uuid(), ChargingProfileKindEnum::Absolute, DEFAULT_STACK_LEVEL); - auto sut1 = handler.add_profile(DEFAULT_EVSE_ID, profile1); - auto sut2 = handler.add_profile(DEFAULT_EVSE_ID + 1, profile2); + auto sut1 = handler.add_profile(profile1, DEFAULT_EVSE_ID); + auto sut2 = handler.add_profile(profile2, DEFAULT_EVSE_ID + 1); auto profiles = handler.get_profiles(); @@ -1050,8 +1050,8 @@ TEST_F(ChargepointTestFixtureV201, create_charge_schedule(ChargingRateUnitEnum::A), uuid(), ChargingProfileKindEnum::Absolute, DEFAULT_STACK_LEVEL); - auto sut1 = handler.add_profile(DEFAULT_EVSE_ID + 1, profile1); - auto sut2 = handler.add_profile(STATION_WIDE_ID, profile2); + auto sut1 = handler.add_profile(profile1, DEFAULT_EVSE_ID + 1); + auto sut2 = handler.add_profile(profile2, STATION_WIDE_ID); auto profiles = handler.get_profiles(); @@ -1070,8 +1070,8 @@ TEST_F(ChargepointTestFixtureV201, create_charge_schedule(ChargingRateUnitEnum::A), uuid(), ChargingProfileKindEnum::Relative, DEFAULT_STACK_LEVEL); - auto sut1 = handler.add_profile(STATION_WIDE_ID, profile1); - auto sut2 = handler.add_profile(STATION_WIDE_ID, profile2); + auto sut1 = handler.add_profile(profile1, STATION_WIDE_ID); + auto sut2 = handler.add_profile(profile2, STATION_WIDE_ID); auto profiles = handler.get_profiles(); @@ -1093,9 +1093,9 @@ TEST_F(ChargepointTestFixtureV201, create_charge_schedule(ChargingRateUnitEnum::A), uuid(), ChargingProfileKindEnum::Recurring, DEFAULT_STACK_LEVEL); - auto sut1 = handler.add_profile(DEFAULT_EVSE_ID, profile1); - auto sut2 = handler.add_profile(DEFAULT_EVSE_ID, profile2); - auto sut3 = handler.add_profile(STATION_WIDE_ID, profile3); + auto sut1 = handler.add_profile(profile1, DEFAULT_EVSE_ID); + auto sut2 = handler.add_profile(profile2, DEFAULT_EVSE_ID); + auto sut3 = handler.add_profile(profile3, STATION_WIDE_ID); auto profiles = handler.get_profiles(); @@ -1115,8 +1115,8 @@ TEST_F(ChargepointTestFixtureV201, create_charge_schedule(ChargingRateUnitEnum::A), uuid(), ChargingProfileKindEnum::Recurring, DEFAULT_STACK_LEVEL); - auto sut4 = handler.add_profile(STATION_WIDE_ID, profile4); - auto sut5 = handler.add_profile(DEFAULT_EVSE_ID, profile5); + auto sut4 = handler.add_profile(profile4, STATION_WIDE_ID); + auto sut5 = handler.add_profile(profile5, DEFAULT_EVSE_ID); profiles = handler.get_profiles(); From 9be6660c682c9396e7d79870ad541b42bff6cb36 Mon Sep 17 00:00:00 2001 From: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> Date: Mon, 1 Jul 2024 15:03:23 +0000 Subject: [PATCH 02/18] charge_point: Make `handle_message` protected This makes it possible to use from our unit tests. Signed-off-by: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> --- include/ocpp/v201/charge_point.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index 030bb8bff..947606092 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -520,8 +520,6 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa /// device model void remove_network_connection_profiles_below_actual_security_profile(); - void handle_message(const EnhancedMessage& message); - void message_callback(const std::string& message); void update_aligned_data_interval(); @@ -774,6 +772,9 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa /// If \param persist is set to true, the change will be persisted across a reboot void execute_change_availability_request(ChangeAvailabilityRequest request, bool persist); +protected: + void handle_message(const EnhancedMessage& message); + public: /// \brief Construct a new ChargePoint object /// \param evse_connector_structure Map that defines the structure of EVSE and connectors of the chargepoint. The From bd312b635cac0583bfbc0c80d6524aec1e9b447b Mon Sep 17 00:00:00 2001 From: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> Date: Mon, 1 Jul 2024 19:24:31 +0000 Subject: [PATCH 03/18] charge_point: Add SmartChargingHandler Signed-off-by: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> --- include/ocpp/v201/charge_point.hpp | 9 +++++++++ lib/ocpp/v201/charge_point.cpp | 14 ++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index 947606092..16f59c314 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -774,6 +775,14 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa protected: void handle_message(const EnhancedMessage& message); + std::shared_ptr smart_charging_handler; + + ChargePoint(const std::map& evse_connector_structure, + std::unique_ptr device_model_storage, const std::string& ocpp_main_path, + const std::string& core_database_path, const std::string& sql_init_path, + const std::string& message_log_path, const std::shared_ptr evse_security, + const Callbacks& callbacks, + std::shared_ptr smart_charging_handler); public: /// \brief Construct a new ChargePoint object diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index c0bda44af..6a945107b 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -85,6 +86,17 @@ ChargePoint::ChargePoint(const std::map& evse_connector_struct ocpp_main_path, core_database_path, sql_init_path, message_log_path, evse_security, callbacks) { } +ChargePoint::ChargePoint(const std::map& evse_connector_structure, + std::unique_ptr device_model_storage, const std::string& ocpp_main_path, + const std::string& core_database_path, const std::string& sql_init_path, + const std::string& message_log_path, const std::shared_ptr evse_security, + const Callbacks& callbacks, + std::shared_ptr smart_charging_handler) : + ChargePoint(evse_connector_structure, std::move(device_model_storage), + ocpp_main_path, core_database_path, sql_init_path, message_log_path, evse_security, callbacks) { + this->smart_charging_handler = smart_charging_handler; +} + ChargePoint::ChargePoint(const std::map& evse_connector_structure, std::unique_ptr device_model_storage, const std::string& ocpp_main_path, const std::string& core_database_path, const std::string& sql_init_path, @@ -178,6 +190,8 @@ ChargePoint::ChargePoint(const std::map& evse_connector_struct evse_connector_structure, *this->device_model, this->database_handler, component_state_manager, transaction_meter_value_callback, this->callbacks.pause_charging_callback); + this->smart_charging_handler = std::make_shared(*this->evse_manager, this->device_model); + // configure logging this->configure_message_logging_format(message_log_path); From 006dcb58efd1d2bdf765d465fad685ef3ab84bc5 Mon Sep 17 00:00:00 2001 From: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> Date: Mon, 1 Jul 2024 20:08:01 +0000 Subject: [PATCH 04/18] smart_charging: Make SmartChargingHandler mockable Also add the mock class for it. Signed-off-by: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> --- include/ocpp/v201/charge_point.hpp | 4 ++-- include/ocpp/v201/smart_charging.hpp | 15 ++++++++++++--- lib/ocpp/v201/charge_point.cpp | 2 +- .../v201/mocks/smart_charging_handler_mock.hpp | 17 +++++++++++++++++ 4 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 tests/lib/ocpp/v201/mocks/smart_charging_handler_mock.hpp diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index 16f59c314..98acd531f 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -775,14 +775,14 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa protected: void handle_message(const EnhancedMessage& message); - std::shared_ptr smart_charging_handler; + std::shared_ptr smart_charging_handler; ChargePoint(const std::map& evse_connector_structure, std::unique_ptr device_model_storage, const std::string& ocpp_main_path, const std::string& core_database_path, const std::string& sql_init_path, const std::string& message_log_path, const std::shared_ptr evse_security, const Callbacks& callbacks, - std::shared_ptr smart_charging_handler); + std::shared_ptr smart_charging_handler); public: /// \brief Construct a new ChargePoint object diff --git a/include/ocpp/v201/smart_charging.hpp b/include/ocpp/v201/smart_charging.hpp index a9ef11bcd..d5433bc62 100644 --- a/include/ocpp/v201/smart_charging.hpp +++ b/include/ocpp/v201/smart_charging.hpp @@ -53,9 +53,18 @@ std::string profile_validation_result_to_string(ProfileValidationResultEnum e); std::ostream& operator<<(std::ostream& os, const ProfileValidationResultEnum validation_result); +class SmartChargingHandlerInterface { +public: + virtual ~SmartChargingHandlerInterface() = default; + + virtual ProfileValidationResultEnum validate_profile(ChargingProfile& profile, int32_t evse_id) = 0; + + virtual SetChargingProfileResponse add_profile(ChargingProfile& profile, int32_t evse_id) = 0; +}; + /// \brief This class handles and maintains incoming ChargingProfiles and contains the logic /// to calculate the composite schedules -class SmartChargingHandler { +class SmartChargingHandler : public SmartChargingHandlerInterface { private: EvseManagerInterface& evse_manager; std::shared_ptr& device_model; @@ -72,12 +81,12 @@ class SmartChargingHandler { /// If a profile does not have validFrom or validTo set, we conform the values /// to a representation that fits the spec. /// - ProfileValidationResultEnum validate_profile(ChargingProfile& profile, int32_t evse_id); + ProfileValidationResultEnum validate_profile(ChargingProfile& profile, int32_t evse_id) override; /// /// \brief Adds a given \p profile and associated \p evse_id to our stored list of profiles /// - SetChargingProfileResponse add_profile(ChargingProfile& profile, int32_t evse_id); + SetChargingProfileResponse add_profile(ChargingProfile& profile, int32_t evse_id) override; /// /// \brief Retrieves existing profiles on system. diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 6a945107b..339e1bf07 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -91,7 +91,7 @@ ChargePoint::ChargePoint(const std::map& evse_connector_struct const std::string& core_database_path, const std::string& sql_init_path, const std::string& message_log_path, const std::shared_ptr evse_security, const Callbacks& callbacks, - std::shared_ptr smart_charging_handler) : + std::shared_ptr smart_charging_handler) : ChargePoint(evse_connector_structure, std::move(device_model_storage), ocpp_main_path, core_database_path, sql_init_path, message_log_path, evse_security, callbacks) { this->smart_charging_handler = smart_charging_handler; diff --git a/tests/lib/ocpp/v201/mocks/smart_charging_handler_mock.hpp b/tests/lib/ocpp/v201/mocks/smart_charging_handler_mock.hpp new file mode 100644 index 000000000..25eea67f3 --- /dev/null +++ b/tests/lib/ocpp/v201/mocks/smart_charging_handler_mock.hpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2020 - 2024 Pionix GmbH and Contributors to EVerest + +#include "gmock/gmock.h" +#include +#include + +#include "ocpp/v201/messages/SetChargingProfile.hpp" +#include "ocpp/v201/smart_charging.hpp" + +namespace ocpp::v201 { +class SmartChargingHandlerMock : public SmartChargingHandlerInterface { +public: + MOCK_METHOD(ProfileValidationResultEnum, validate_profile, (ChargingProfile & profile, int32_t evse_id)); + MOCK_METHOD(SetChargingProfileResponse, add_profile, (ChargingProfile & profile, int32_t evse_id)); +}; +} // namespace ocpp::v201 From 5ebeede38583b447c680eb96acb7af9858b1af74 Mon Sep 17 00:00:00 2001 From: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> Date: Mon, 1 Jul 2024 17:52:47 +0000 Subject: [PATCH 05/18] charge_point: Handle SetChargingProfileRequest Add a handler for SetChargingProfileRequest with unit tests that ensure that we add valid profiles - and only valid profiles. Signed-off-by: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> --- include/ocpp/v201/charge_point.hpp | 12 +- include/ocpp/v201/smart_charging.hpp | 4 + lib/ocpp/v201/charge_point.cpp | 38 ++- lib/ocpp/v201/smart_charging.cpp | 39 +++ tests/lib/ocpp/v201/test_charge_point.cpp | 281 +++++++++++++++++++++- 5 files changed, 354 insertions(+), 20 deletions(-) diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index 98acd531f..4484d7450 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -53,6 +53,7 @@ #include #include #include +#include #include #include #include @@ -718,6 +719,9 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa void handle_change_availability_req(Call call); void handle_heartbeat_response(CallResult call); + // Functional Block K: Smart Charging + void handle_set_charging_profile_req(Call call); + // Functional Block L: Firmware management void handle_firmware_update_req(Call call); @@ -774,15 +778,9 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa void execute_change_availability_request(ChangeAvailabilityRequest request, bool persist); protected: - void handle_message(const EnhancedMessage& message); std::shared_ptr smart_charging_handler; - ChargePoint(const std::map& evse_connector_structure, - std::unique_ptr device_model_storage, const std::string& ocpp_main_path, - const std::string& core_database_path, const std::string& sql_init_path, - const std::string& message_log_path, const std::shared_ptr evse_security, - const Callbacks& callbacks, - std::shared_ptr smart_charging_handler); + void handle_message(const EnhancedMessage& message); public: /// \brief Construct a new ChargePoint object diff --git a/include/ocpp/v201/smart_charging.hpp b/include/ocpp/v201/smart_charging.hpp index d5433bc62..e029e6cd0 100644 --- a/include/ocpp/v201/smart_charging.hpp +++ b/include/ocpp/v201/smart_charging.hpp @@ -49,6 +49,10 @@ namespace conversions { /// \brief Converts the given ProfileValidationResultEnum \p e to human readable string /// \returns a string representation of the ProfileValidationResultEnum std::string profile_validation_result_to_string(ProfileValidationResultEnum e); + +/// \brief Converts the given ProfileValidationResultEnum \p e to a OCPP reasonCode. +/// \returns a reasonCode +std::string profile_validation_result_to_reason_code(ProfileValidationResultEnum e); } // namespace conversions std::ostream& operator<<(std::ostream& os, const ProfileValidationResultEnum validation_result); diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 339e1bf07..e3e6b47c3 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include @@ -86,17 +85,6 @@ ChargePoint::ChargePoint(const std::map& evse_connector_struct ocpp_main_path, core_database_path, sql_init_path, message_log_path, evse_security, callbacks) { } -ChargePoint::ChargePoint(const std::map& evse_connector_structure, - std::unique_ptr device_model_storage, const std::string& ocpp_main_path, - const std::string& core_database_path, const std::string& sql_init_path, - const std::string& message_log_path, const std::shared_ptr evse_security, - const Callbacks& callbacks, - std::shared_ptr smart_charging_handler) : - ChargePoint(evse_connector_structure, std::move(device_model_storage), - ocpp_main_path, core_database_path, sql_init_path, message_log_path, evse_security, callbacks) { - this->smart_charging_handler = smart_charging_handler; -} - ChargePoint::ChargePoint(const std::map& evse_connector_structure, std::unique_ptr device_model_storage, const std::string& ocpp_main_path, const std::string& core_database_path, const std::string& sql_init_path, @@ -1321,6 +1309,9 @@ void ChargePoint::handle_message(const EnhancedMessage& messa case MessageType::CustomerInformation: this->handle_customer_information_req(json_message); break; + case MessageType::SetChargingProfile: + this->handle_set_charging_profile_req(json_message); + break; case MessageType::SetMonitoringBase: this->handle_set_monitoring_base_req(json_message); break; @@ -3161,6 +3152,29 @@ void ChargePoint::handle_heartbeat_response(CallResult call) } } +void ChargePoint::handle_set_charging_profile_req(Call call) { + EVLOG_debug << "Received SetChargingProfileRequest: " << call.msg << "\nwith messageId: " << call.uniqueId; + auto msg = call.msg; + SetChargingProfileResponse response; + response.status = ChargingProfileStatusEnum::Rejected; + + auto res = this->smart_charging_handler->validate_profile(msg.chargingProfile, msg.evseId); + if (res == ProfileValidationResultEnum::Valid) { + EVLOG_debug << "Accepting SetChargingProfileRequest"; + response = this->smart_charging_handler->add_profile(msg.chargingProfile, msg.evseId); + this->callbacks.set_charging_profiles_callback(); + } else { + response.statusInfo = StatusInfo(); + response.statusInfo->reasonCode = conversions::profile_validation_result_to_reason_code(res); + response.statusInfo->additionalInfo = conversions::profile_validation_result_to_string(res); + EVLOG_debug << "Rejecting SetChargingProfileRequest:\n reasonCode: " << response.statusInfo->reasonCode.get() + << "\nadditionalInfo: " << response.statusInfo->additionalInfo->get(); + } + + ocpp::CallResult call_result(response, call.uniqueId); + this->send(call_result); +} + void ChargePoint::handle_firmware_update_req(Call call) { EVLOG_debug << "Received UpdateFirmwareRequest: " << call.msg << "\nwith messageId: " << call.uniqueId; if (call.msg.firmware.signingCertificate.has_value() or call.msg.firmware.signature.has_value()) { diff --git a/lib/ocpp/v201/smart_charging.cpp b/lib/ocpp/v201/smart_charging.cpp index 731ef4d3e..c2ff02394 100644 --- a/lib/ocpp/v201/smart_charging.cpp +++ b/lib/ocpp/v201/smart_charging.cpp @@ -73,6 +73,45 @@ std::string profile_validation_result_to_string(ProfileValidationResultEnum e) { throw std::out_of_range("No known string conversion for provided enum of type ProfileValidationResultEnum"); } + +std::string profile_validation_result_to_reason_code(ProfileValidationResultEnum e) { + switch (e) { + case ProfileValidationResultEnum::Valid: + return "NoError"; + case ProfileValidationResultEnum::DuplicateProfileValidityPeriod: + case ProfileValidationResultEnum::DuplicateTxDefaultProfileFound: + case ProfileValidationResultEnum::ExistingChargingStationExternalConstraints: + return "DuplicateProfile"; + case ProfileValidationResultEnum::TxProfileTransactionNotOnEvse: + case ProfileValidationResultEnum::TxProfileEvseHasNoActiveTransaction: + return "TxNotFound"; + case ProfileValidationResultEnum::TxProfileConflictingStackLevel: + return "InvalidStackLevel"; + case ProfileValidationResultEnum::ChargingScheduleChargingRateUnitUnsupported: + return "UnsupportedRateUnit"; + case ProfileValidationResultEnum::ChargingProfileNoChargingSchedulePeriods: + case ProfileValidationResultEnum::ChargingProfileFirstStartScheduleIsNotZero: + case ProfileValidationResultEnum::ChargingProfileMissingRequiredStartSchedule: + case ProfileValidationResultEnum::ChargingProfileExtraneousStartSchedule: + case ProfileValidationResultEnum::ChargingSchedulePeriodsOutOfOrder: + case ProfileValidationResultEnum::ChargingSchedulePeriodInvalidPhaseToUse: + case ProfileValidationResultEnum::ChargingSchedulePeriodUnsupportedNumberPhases: + case ProfileValidationResultEnum::ChargingSchedulePeriodExtraneousPhaseValues: + case ProfileValidationResultEnum::ChargingSchedulePeriodPhaseToUseACPhaseSwitchingUnsupported: + return "InvalidSchedule"; + case ProfileValidationResultEnum::TxProfileMissingTransactionId: + return "MissingParam"; + case ProfileValidationResultEnum::EvseDoesNotExist: + case ProfileValidationResultEnum::TxProfileEvseIdNotGreaterThanZero: + case ProfileValidationResultEnum::ChargingStationMaxProfileCannotBeRelative: + case ProfileValidationResultEnum::ChargingStationMaxProfileEvseIdGreaterThanZero: + return "InvalidValue"; + case ProfileValidationResultEnum::InvalidProfileType: + return "InternalError"; + } + + throw std::out_of_range("No applicable reason code for provided enum of type ProfileValidationResultEnum"); +} } // namespace conversions std::ostream& operator<<(std::ostream& os, const ProfileValidationResultEnum validation_result) { diff --git a/tests/lib/ocpp/v201/test_charge_point.cpp b/tests/lib/ocpp/v201/test_charge_point.cpp index 8b6f1836c..bf4452c64 100644 --- a/tests/lib/ocpp/v201/test_charge_point.cpp +++ b/tests/lib/ocpp/v201/test_charge_point.cpp @@ -1,16 +1,179 @@ +#include "everest/logging.hpp" +#include "evse_security_mock.hpp" +#include "lib/ocpp/common/database_testing_utils.hpp" +#include "ocpp/common/call_types.hpp" +#include "ocpp/common/message_queue.hpp" #include "ocpp/v201/charge_point.hpp" +#include "ocpp/v201/device_model_storage_sqlite.hpp" +#include "ocpp/v201/init_device_model_db.hpp" +#include "ocpp/v201/messages/SetChargingProfile.hpp" +#include "ocpp/v201/smart_charging.hpp" +#include "ocpp/v201/types.hpp" +#include "smart_charging_handler_mock.hpp" #include "gmock/gmock.h" +#include +#include #include +#include +#include + +static const int DEFAULT_EVSE_ID = 1; +static const int DEFAULT_PROFILE_ID = 1; +static const int DEFAULT_STACK_LEVEL = 1; +static const std::string TEMP_OUTPUT_PATH = "/tmp/ocpp201"; +const static std::string MIGRATION_FILES_PATH = "./resources/v201/device_model_migration_files"; +const static std::string SCHEMAS_PATH = "./resources/example_config/v201/component_schemas"; +const static std::string CONFIG_PATH = "./resources/example_config/v201/config.json"; +const static std::string DEVICE_MODEL_DB_IN_MEMORY_PATH = "file::memory:?cache=shared"; +static const std::string DEFAULT_TX_ID = "10c75ff7-74f5-44f5-9d01-f649f3ac7b78"; namespace ocpp::v201 { -class ChargePointFixture : public testing::Test { +class TestChargePoint : public ChargePoint { +public: + using ChargePoint::handle_message; + using ChargePoint::smart_charging_handler; + + TestChargePoint(std::map& evse_connector_structure, + std::unique_ptr device_model_storage, const std::string& ocpp_main_path, + const std::string& core_database_path, const std::string& sql_init_path, + const std::string& message_log_path, const std::shared_ptr evse_security, + const Callbacks& callbacks, std::shared_ptr smart_charging_handler) : + ChargePoint(evse_connector_structure, std::move(device_model_storage), ocpp_main_path, core_database_path, + sql_init_path, message_log_path, evse_security, callbacks) { + this->smart_charging_handler = smart_charging_handler; + } +}; + +class ChargePointFixture : public DatabaseTestingUtils { public: ChargePointFixture() { } ~ChargePointFixture() { } + void SetUp() override { + charge_point->start(); + } + + void TearDown() override { + charge_point->stop(); + } + + void create_device_model_db(const std::string& path) { + InitDeviceModelDb db(path, MIGRATION_FILES_PATH); + db.initialize_database(SCHEMAS_PATH, true); + db.insert_config_and_default_values(SCHEMAS_PATH, CONFIG_PATH); + } + + std::shared_ptr + create_device_model(const std::optional ac_phase_switching_supported = "true") { + create_device_model_db(DEVICE_MODEL_DB_IN_MEMORY_PATH); + auto device_model_storage = std::make_unique(DEVICE_MODEL_DB_IN_MEMORY_PATH); + auto device_model = std::make_shared(std::move(device_model_storage)); + + // Defaults + const auto& charging_rate_unit_cv = ControllerComponentVariables::ChargingScheduleChargingRateUnit; + device_model->set_value(charging_rate_unit_cv.component, charging_rate_unit_cv.variable.value(), + AttributeEnum::Actual, "A,W", "test", true); + + const auto& ac_phase_switching_cv = ControllerComponentVariables::ACPhaseSwitchingSupported; + device_model->set_value(ac_phase_switching_cv.component, ac_phase_switching_cv.variable.value(), + AttributeEnum::Actual, ac_phase_switching_supported.value_or(""), "test", true); + + return device_model; + } + + std::unique_ptr create_charge_point() { + std::map evse_connector_structure = {{1, 1}, {2, 1}}; + std::unique_ptr device_model_storage = + std::make_unique(DEVICE_MODEL_DB_IN_MEMORY_PATH); + auto charge_point = std::make_unique(evse_connector_structure, std::move(device_model_storage), + "", TEMP_OUTPUT_PATH, MIGRATION_FILES_LOCATION_V201, + TEMP_OUTPUT_PATH, std::make_shared(), + create_callbacks_with_mocks(), smart_charging_handler); + return charge_point; + } + + std::vector create_charging_schedule_periods(std::vector start_periods) { + auto charging_schedule_periods = std::vector(); + for (auto start_period : start_periods) { + auto charging_schedule_period = ChargingSchedulePeriod{ + .startPeriod = start_period, + }; + charging_schedule_periods.push_back(charging_schedule_period); + } + + return charging_schedule_periods; + } + + ChargingSchedule create_charge_schedule(ChargingRateUnitEnum charging_rate_unit, + std::vector charging_schedule_period, + std::optional start_schedule = std::nullopt) { + int32_t id; + std::optional custom_data; + std::optional duration; + std::optional min_charging_rate; + std::optional sales_tariff; + + return ChargingSchedule{ + id, + charging_rate_unit, + charging_schedule_period, + custom_data, + start_schedule, + duration, + min_charging_rate, + sales_tariff, + }; + } + + ChargingProfile + create_charging_profile(int32_t charging_profile_id, ChargingProfilePurposeEnum charging_profile_purpose, + ChargingSchedule charging_schedule, std::optional transaction_id = {}, + ChargingProfileKindEnum charging_profile_kind = ChargingProfileKindEnum::Absolute, + int stack_level = DEFAULT_STACK_LEVEL, std::optional validFrom = {}, + std::optional validTo = {}) { + auto recurrency_kind = RecurrencyKindEnum::Daily; + std::vector charging_schedules = {charging_schedule}; + return ChargingProfile{.id = charging_profile_id, + .stackLevel = stack_level, + .chargingProfilePurpose = charging_profile_purpose, + .chargingProfileKind = charging_profile_kind, + .chargingSchedule = charging_schedules, + .customData = {}, + .recurrencyKind = recurrency_kind, + .validFrom = validFrom, + .validTo = validTo, + .transactionId = transaction_id}; + } + + ocpp::v201::Callbacks create_callbacks_with_mocks() { + ocpp::v201::Callbacks callbacks; + + callbacks.is_reset_allowed_callback = is_reset_allowed_callback_mock.AsStdFunction(); + callbacks.reset_callback = reset_callback_mock.AsStdFunction(); + callbacks.stop_transaction_callback = stop_transaction_callback_mock.AsStdFunction(); + callbacks.pause_charging_callback = pause_charging_callback_mock.AsStdFunction(); + callbacks.connector_effective_operative_status_changed_callback = + connector_effective_operative_status_changed_callback_mock.AsStdFunction(); + callbacks.get_log_request_callback = get_log_request_callback_mock.AsStdFunction(); + callbacks.unlock_connector_callback = unlock_connector_callback_mock.AsStdFunction(); + callbacks.remote_start_transaction_callback = remote_start_transaction_callback_mock.AsStdFunction(); + callbacks.is_reservation_for_token_callback = is_reservation_for_token_callback_mock.AsStdFunction(); + callbacks.update_firmware_request_callback = update_firmware_request_callback_mock.AsStdFunction(); + callbacks.security_event_callback = security_event_callback_mock.AsStdFunction(); + callbacks.set_charging_profiles_callback = set_charging_profiles_callback_mock.AsStdFunction(); + + return callbacks; + } + + sqlite3* db_handle; + std::shared_ptr device_model = create_device_model(); + std::shared_ptr smart_charging_handler = std::make_shared(); + std::unique_ptr charge_point = create_charge_point(); + boost::uuids::random_generator uuid_generator = boost::uuids::random_generator(); + void configure_callbacks_with_mocks() { callbacks.is_reset_allowed_callback = is_reset_allowed_callback_mock.AsStdFunction(); callbacks.reset_callback = reset_callback_mock.AsStdFunction(); @@ -27,6 +190,34 @@ class ChargePointFixture : public testing::Test { callbacks.set_charging_profiles_callback = set_charging_profiles_callback_mock.AsStdFunction(); } + std::string uuid() { + std::stringstream s; + s << uuid_generator(); + return s.str(); + } + + template void call_to_json(json& j, const ocpp::Call& call) { + j = json::array(); + j.push_back(MessageTypeId::CALL); + j.push_back(call.uniqueId.get()); + j.push_back(call.msg.get_type()); + j.push_back(json(call.msg)); + } + + template EnhancedMessage request_to_enhanced_message(const T& req) { + auto message_id = uuid(); + ocpp::Call call(req, message_id); + EnhancedMessage enhanced_message{ + .uniqueId = message_id, + .messageType = M, + .messageTypeId = MessageTypeId::CALL, + }; + + call_to_json(enhanced_message.message, call); + + return enhanced_message; + } + testing::MockFunction evse_id, const ResetEnum& reset_type)> is_reset_allowed_callback_mock; testing::MockFunction evse_id, const ResetEnum& reset_type)> @@ -340,4 +531,92 @@ TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfOptionalTransactionE EXPECT_TRUE(callbacks.all_callbacks_valid()); } +TEST_F(ChargePointFixture, K01_SetChargingProfileRequest_ValidatesProfile) { + auto periods = create_charging_schedule_periods({0, 1, 2}); + + auto profile = create_charging_profile( + DEFAULT_PROFILE_ID, ChargingProfilePurposeEnum::TxProfile, + create_charge_schedule(ChargingRateUnitEnum::A, periods, ocpp::DateTime("2024-01-17T17:00:00")), DEFAULT_TX_ID); + + SetChargingProfileRequest req; + req.evseId = DEFAULT_EVSE_ID; + req.chargingProfile = profile; + + auto set_charging_profile_req = + request_to_enhanced_message(req); + + EXPECT_CALL(*smart_charging_handler, validate_profile(testing::_, testing::_)); + + charge_point->handle_message(set_charging_profile_req); +} + +TEST_F(ChargePointFixture, K01_SetChargingProfileRequest_AddsValidProfile) { + auto periods = create_charging_schedule_periods({0, 1, 2}); + + auto profile = create_charging_profile( + DEFAULT_PROFILE_ID, ChargingProfilePurposeEnum::TxProfile, + create_charge_schedule(ChargingRateUnitEnum::A, periods, ocpp::DateTime("2024-01-17T17:00:00")), DEFAULT_TX_ID); + + SetChargingProfileRequest req; + req.evseId = DEFAULT_EVSE_ID; + req.chargingProfile = profile; + + auto set_charging_profile_req = + request_to_enhanced_message(req); + + EXPECT_CALL(*smart_charging_handler, validate_profile(testing::_, testing::_)); + ON_CALL(*smart_charging_handler, validate_profile) + .WillByDefault(testing::Return(ProfileValidationResultEnum::Valid)); + EXPECT_CALL(*smart_charging_handler, add_profile(testing::_, testing::_)); + + charge_point->handle_message(set_charging_profile_req); +} + +class ChargePointFixture_InvalidProfiles : public ChargePointFixture, + public ::testing::WithParamInterface {}; + +INSTANTIATE_TEST_SUITE_P( + ChargePointOnlyAddValidProfilesTests, ChargePointFixture_InvalidProfiles, + testing::Values(ProfileValidationResultEnum::EvseDoesNotExist, ProfileValidationResultEnum::InvalidProfileType, + ProfileValidationResultEnum::TxProfileMissingTransactionId, + ProfileValidationResultEnum::TxProfileEvseIdNotGreaterThanZero, + ProfileValidationResultEnum::TxProfileTransactionNotOnEvse, + ProfileValidationResultEnum::TxProfileEvseHasNoActiveTransaction, + ProfileValidationResultEnum::TxProfileConflictingStackLevel, + ProfileValidationResultEnum::ChargingProfileNoChargingSchedulePeriods, + ProfileValidationResultEnum::ChargingProfileFirstStartScheduleIsNotZero, + ProfileValidationResultEnum::ChargingProfileMissingRequiredStartSchedule, + ProfileValidationResultEnum::ChargingProfileExtraneousStartSchedule, + ProfileValidationResultEnum::ChargingScheduleChargingRateUnitUnsupported, + ProfileValidationResultEnum::ChargingSchedulePeriodsOutOfOrder, + ProfileValidationResultEnum::ChargingSchedulePeriodInvalidPhaseToUse, + ProfileValidationResultEnum::ChargingSchedulePeriodUnsupportedNumberPhases, + ProfileValidationResultEnum::ChargingSchedulePeriodExtraneousPhaseValues, + ProfileValidationResultEnum::ChargingSchedulePeriodPhaseToUseACPhaseSwitchingUnsupported, + ProfileValidationResultEnum::ChargingStationMaxProfileCannotBeRelative, + ProfileValidationResultEnum::ChargingStationMaxProfileEvseIdGreaterThanZero, + ProfileValidationResultEnum::DuplicateTxDefaultProfileFound, + ProfileValidationResultEnum::DuplicateProfileValidityPeriod)); + +TEST_P(ChargePointFixture_InvalidProfiles, K01_SetChargingProfileRequest_DoesnNotAddInvalidProfiles) { + auto periods = create_charging_schedule_periods({0, 1, 2}); + + auto profile = create_charging_profile( + DEFAULT_PROFILE_ID, ChargingProfilePurposeEnum::TxProfile, + create_charge_schedule(ChargingRateUnitEnum::A, periods, ocpp::DateTime("2024-01-17T17:00:00")), DEFAULT_TX_ID); + + SetChargingProfileRequest req; + req.evseId = DEFAULT_EVSE_ID; + req.chargingProfile = profile; + + auto set_charging_profile_req = + request_to_enhanced_message(req); + + EXPECT_CALL(*smart_charging_handler, validate_profile(testing::_, testing::_)); + ON_CALL(*smart_charging_handler, validate_profile).WillByDefault(testing::Return(GetParam())); + EXPECT_CALL(*smart_charging_handler, add_profile(testing::_, testing::_)).Times(0); + + charge_point->handle_message(set_charging_profile_req); +} + } // namespace ocpp::v201 From 52759a2ff105eb71830bfdab696daa9fb51142da Mon Sep 17 00:00:00 2001 From: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> Date: Wed, 3 Jul 2024 18:14:10 +0000 Subject: [PATCH 06/18] doc: Update SmartCharging status Add functional requirements handled by the implementation of `handle_set_charging_profile_req()` and a few missed FRs covered by `validate_profile()`. Signed-off-by: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> --- doc/ocpp_201_status.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/ocpp_201_status.md b/doc/ocpp_201_status.md index f0c2d3257..5695c593e 100644 --- a/doc/ocpp_201_status.md +++ b/doc/ocpp_201_status.md @@ -1238,29 +1238,29 @@ This document contains the status of which OCPP 2.0.1 numbered functional requir | K01.FR.06 | 🌐 | | | K01.FR.07 | ⛽️ | Notified through the `signal_set_charging_profiles` callback. | | K01.FR.08 | 🌐 | `TxDefaultProfile`s are supported. | -| K01.FR.09 | | | +| K01.FR.09 | ✅ | | | K01.FR.10 | ✅ | | | K01.FR.11 | | | | K01.FR.12 | | | | K01.FR.13 | | | | K01.FR.14 | ✅ | | | K01.FR.15 | ✅ | | -| K01.FR.16 | | | +| K01.FR.16 | ✅ | | | K01.FR.17 | | | | K01.FR.19 | | | | K01.FR.20 | ✅ | Suggests `ACPhaseSwitchingSupported` should be per EVSE, conflicting with the rest of the spec. | | K01.FR.21 | | | | K01.FR.22 | | | -| K01.FR.26 | | | +| K01.FR.26 | ✅ | | | K01.FR.27 | | | -| K01.FR.28 | | | +| K01.FR.28 | ✅ | | | K01.FR.29 | | | | K01.FR.30 | | | | K01.FR.31 | | | | K01.FR.32 | ✅ | | -| K01.FR.33 | | | +| K01.FR.33 | ✅ | | | K01.FR.34 | ✅ | | -| K01.FR.35 | | | +| K01.FR.35 | ✅ | | | K01.FR.36 | ✅ | | | K01.FR.37 | | | | K01.FR.38 | 🌐 💂 | `ChargingStationMaxProfile`s with `Relative` for `chargingProfileKind` are rejected. | From 957b0e5c898762a38d1c30f7485ecc8fdff138f7 Mon Sep 17 00:00:00 2001 From: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> Date: Tue, 16 Jul 2024 18:16:07 +0000 Subject: [PATCH 07/18] charge_point: Test that set_charging_profile callback is called Signed-off-by: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> --- tests/lib/ocpp/v201/test_charge_point.cpp | 41 +++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/lib/ocpp/v201/test_charge_point.cpp b/tests/lib/ocpp/v201/test_charge_point.cpp index bf4452c64..e9bfa2a87 100644 --- a/tests/lib/ocpp/v201/test_charge_point.cpp +++ b/tests/lib/ocpp/v201/test_charge_point.cpp @@ -619,4 +619,45 @@ TEST_P(ChargePointFixture_InvalidProfiles, K01_SetChargingProfileRequest_DoesnNo charge_point->handle_message(set_charging_profile_req); } +TEST_F(ChargePointFixture, K01FR07_SetChargingProfileRequest_TriggersCallbackWhenValid) { + auto periods = create_charging_schedule_periods({0, 1, 2}); + + auto profile = create_charging_profile( + DEFAULT_PROFILE_ID, ChargingProfilePurposeEnum::TxProfile, + create_charge_schedule(ChargingRateUnitEnum::A, periods, ocpp::DateTime("2024-01-17T17:00:00")), DEFAULT_TX_ID); + + SetChargingProfileRequest req; + req.evseId = DEFAULT_EVSE_ID; + req.chargingProfile = profile; + + auto set_charging_profile_req = + request_to_enhanced_message(req); + + ON_CALL(*smart_charging_handler, validate_profile) + .WillByDefault(testing::Return(ProfileValidationResultEnum::Valid)); + EXPECT_CALL(set_charging_profiles_callback_mock, Call); + + charge_point->handle_message(set_charging_profile_req); +} + +TEST_P(ChargePointFixture_InvalidProfiles, K01FR07_SetChargingProfileRequest_DoesNotTriggerCallbackWhenInvalid) { + auto periods = create_charging_schedule_periods({0, 1, 2}); + + auto profile = create_charging_profile( + DEFAULT_PROFILE_ID, ChargingProfilePurposeEnum::TxProfile, + create_charge_schedule(ChargingRateUnitEnum::A, periods, ocpp::DateTime("2024-01-17T17:00:00")), DEFAULT_TX_ID); + + SetChargingProfileRequest req; + req.evseId = DEFAULT_EVSE_ID; + req.chargingProfile = profile; + + auto set_charging_profile_req = + request_to_enhanced_message(req); + + ON_CALL(*smart_charging_handler, validate_profile).WillByDefault(testing::Return(GetParam())); + EXPECT_CALL(set_charging_profiles_callback_mock, Call).Times(0); + + charge_point->handle_message(set_charging_profile_req); +} + } // namespace ocpp::v201 From c4538f8f095c971d193873c48866a354f5a5c3a5 Mon Sep 17 00:00:00 2001 From: Coury Richards <146002925+couryrr-afs@users.noreply.github.com> Date: Mon, 17 Jun 2024 17:52:20 +0000 Subject: [PATCH 08/18] added ability to load profile from database Signed-off-by: Coury Richards <146002925+couryrr-afs@users.noreply.github.com> Signed-off-by: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> --- .../5_down-charging_profiles_db.sql | 1 + .../5_up-charging_profiles_db.sql | 5 + doc/ocpp_201_status.md | 2 +- include/ocpp/v201/charge_point.hpp | 2 + include/ocpp/v201/database_handler.hpp | 14 ++ include/ocpp/v201/smart_charging.hpp | 3 +- lib/ocpp/v201/charge_point.cpp | 35 +++- lib/ocpp/v201/database_handler.cpp | 52 +++++ lib/ocpp/v201/smart_charging.cpp | 6 +- tests/lib/ocpp/v201/CMakeLists.txt | 1 + tests/lib/ocpp/v201/test_database_handler.cpp | 182 +++++++++++++++++- .../ocpp/v201/test_smart_charging_handler.cpp | 7 +- 12 files changed, 303 insertions(+), 7 deletions(-) create mode 100644 config/v201/core_migrations/5_down-charging_profiles_db.sql create mode 100644 config/v201/core_migrations/5_up-charging_profiles_db.sql diff --git a/config/v201/core_migrations/5_down-charging_profiles_db.sql b/config/v201/core_migrations/5_down-charging_profiles_db.sql new file mode 100644 index 000000000..4adf87ac0 --- /dev/null +++ b/config/v201/core_migrations/5_down-charging_profiles_db.sql @@ -0,0 +1 @@ +DROP TABLE CHARGING_PROFILES; diff --git a/config/v201/core_migrations/5_up-charging_profiles_db.sql b/config/v201/core_migrations/5_up-charging_profiles_db.sql new file mode 100644 index 000000000..39dda1bb2 --- /dev/null +++ b/config/v201/core_migrations/5_up-charging_profiles_db.sql @@ -0,0 +1,5 @@ +CREATE TABLE CHARGING_PROFILES ( + ID INT PRIMARY KEY NOT NULL, + EVSE_ID INT NOT NULL, + PROFILE TEXT NOT NULL +); diff --git a/doc/ocpp_201_status.md b/doc/ocpp_201_status.md index 5695c593e..ec4aad037 100644 --- a/doc/ocpp_201_status.md +++ b/doc/ocpp_201_status.md @@ -1252,7 +1252,7 @@ This document contains the status of which OCPP 2.0.1 numbered functional requir | K01.FR.21 | | | | K01.FR.22 | | | | K01.FR.26 | ✅ | | -| K01.FR.27 | | | +| K01.FR.27 | ✅ | | | K01.FR.28 | ✅ | | | K01.FR.29 | | | | K01.FR.30 | | | diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index 4484d7450..b6c4f76b6 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -67,6 +67,7 @@ #include #include "component_state_manager.hpp" +#include "ocpp/v201/smart_charging.hpp" namespace ocpp { namespace v201 { @@ -781,6 +782,7 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa std::shared_ptr smart_charging_handler; void handle_message(const EnhancedMessage& message); + void load_charging_profiles(); public: /// \brief Construct a new ChargePoint object diff --git a/include/ocpp/v201/database_handler.hpp b/include/ocpp/v201/database_handler.hpp index ae7117b78..99f294913 100644 --- a/include/ocpp/v201/database_handler.hpp +++ b/include/ocpp/v201/database_handler.hpp @@ -168,6 +168,20 @@ class DatabaseHandler : public common::DatabaseHandlerCommon { /// \param transaction_id transaction id of the transaction to clear from. /// \return true if succeeded void transaction_delete(const std::string& transaction_id); + + /// charging profiles + + /// \brief Inserts or updates the given \p profile to CHARGING_PROFILES table + void insert_or_update_charging_profile(const int evse_id, const v201::ChargingProfile& profile); + + /// \brief Deletes the profile with the given \p profile_id + void delete_charging_profile(const int profile_id); + + /// \brief Deletes all profiles from table CHARGING_PROFILES + void clear_charging_profiles(); + + /// \brief Retrieves all ChargingProfiles + virtual std::map> get_all_charging_profiles_by_evse(); }; } // namespace v201 diff --git a/include/ocpp/v201/smart_charging.hpp b/include/ocpp/v201/smart_charging.hpp index e029e6cd0..0b41d3011 100644 --- a/include/ocpp/v201/smart_charging.hpp +++ b/include/ocpp/v201/smart_charging.hpp @@ -78,7 +78,8 @@ class SmartChargingHandler : public SmartChargingHandlerInterface { std::map> charging_profiles; public: - SmartChargingHandler(EvseManagerInterface& evse_manager, std::shared_ptr& device_model); + SmartChargingHandler(EvseManagerInterface& evse_manager, std::shared_ptr& device_model, + std::shared_ptr database_handler); /// /// \brief validates the given \p profile according to the specification. diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index e3e6b47c3..fb12d47a7 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -178,7 +178,8 @@ ChargePoint::ChargePoint(const std::map& evse_connector_struct evse_connector_structure, *this->device_model, this->database_handler, component_state_manager, transaction_meter_value_callback, this->callbacks.pause_charging_callback); - this->smart_charging_handler = std::make_shared(*this->evse_manager, this->device_model); + this->smart_charging_handler = + std::make_shared(*this->evse_manager, this->device_model, this->database_handler); // configure logging this->configure_message_logging_format(message_log_path); @@ -220,6 +221,8 @@ void ChargePoint::start(BootReasonEnum bootreason) { // get transaction messages from db (if there are any) so they can be sent again. this->message_queue->get_persisted_messages_from_db(); this->boot_notification_req(bootreason); + // K01.27 - call load_charging_profiles when system boots + this->load_charging_profiles(); this->start_websocket(); if (this->bootreason == BootReasonEnum::RemoteReset) { @@ -3846,6 +3849,36 @@ void ChargePoint::execute_change_availability_request(ChangeAvailabilityRequest } } +// K01.27 - load profiles from database +void ChargePoint::load_charging_profiles() { + try { + auto evses = this->database_handler->get_all_charging_profiles_by_evse(); + EVLOG_info << "Found " << evses.size() << " evse in the database"; + for (auto& profiles : evses) { + try { + auto evse_id = profiles.first; + for (auto profile : profiles.second) { + if (this->smart_charging_handler->validate_profile(profile, evse_id) == + ProfileValidationResultEnum::Valid) { + this->smart_charging_handler->add_profile(profile, evse_id); + } else { + // delete if not valid anymore + this->database_handler->delete_charging_profile(profile.id); + } + } + } catch (common::RequiredEntryNotFoundException& e) { + EVLOG_warning << "Could not get connector id from database: " << e.what(); + } catch (const QueryExecutionException& e) { + EVLOG_warning << "Could not get connector id from database: " << e.what(); + } + } + } catch (const QueryExecutionException& e) { + EVLOG_warning << "Could not load charging profiles from database: " << e.what(); + } catch (const std::exception& e) { + EVLOG_warning << "Unknown error while loading charging profiles from database: " << e.what(); + } +} + std::vector ChargePoint::get_variables(const std::vector& get_variable_data_vector) { std::vector response; diff --git a/lib/ocpp/v201/database_handler.cpp b/lib/ocpp/v201/database_handler.cpp index bb83a652f..4c20d6814 100644 --- a/lib/ocpp/v201/database_handler.cpp +++ b/lib/ocpp/v201/database_handler.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest +#include "everest/logging.hpp" #include #include #include @@ -719,5 +720,56 @@ void DatabaseHandler::transaction_delete(const std::string& transaction_id) { } } +void DatabaseHandler::insert_or_update_charging_profile(const int evse_id, const v201::ChargingProfile& profile) { + // add or replace + std::string sql = "INSERT OR REPLACE INTO CHARGING_PROFILES (ID, EVSE_ID, PROFILE) VALUES " + "(@id, @evse_id, @profile)"; + auto stmt = this->database->new_statement(sql); + + json json_profile(profile); + + stmt->bind_int("@id", profile.id); + stmt->bind_int("@evse_id", evse_id); + stmt->bind_text("@profile", json_profile.dump(), SQLiteString::Transient); + + if (stmt->step() != SQLITE_DONE) { + throw QueryExecutionException(this->database->get_error_message()); + } +} + +void DatabaseHandler::delete_charging_profile(const int profile_id) { + std::string sql = "DELETE FROM CHARGING_PROFILES WHERE ID = @profile_id;"; + auto stmt = this->database->new_statement(sql); + + stmt->bind_int("@profile_id", profile_id); + if (stmt->step() != SQLITE_DONE) { + throw QueryExecutionException(this->database->get_error_message()); + } +} + +void DatabaseHandler::clear_charging_profiles() { + this->database->clear_table("CHARGING_PROFILES"); +} + +std::map> DatabaseHandler::get_all_charging_profiles_by_evse() { + std::map> map; + + std::string sql = "SELECT EVSE_ID, PROFILE FROM CHARGING_PROFILES"; + + auto stmt = this->database->new_statement(sql); + + while (stmt->step() != SQLITE_DONE) { + auto evse_id = stmt->column_int(0); + auto profile = json::parse(stmt->column_text(1)); + + auto profiles = map[evse_id]; + profiles.emplace_back(profile); + + map[evse_id] = profiles; + } + + return map; +} + } // namespace v201 } // namespace ocpp diff --git a/lib/ocpp/v201/smart_charging.cpp b/lib/ocpp/v201/smart_charging.cpp index c2ff02394..51dc9c2c0 100644 --- a/lib/ocpp/v201/smart_charging.cpp +++ b/lib/ocpp/v201/smart_charging.cpp @@ -3,6 +3,7 @@ #include "date/tz.h" #include "everest/logging.hpp" +#include "ocpp/common/message_queue.hpp" #include "ocpp/common/types.hpp" #include "ocpp/v201/ctrlr_component_variables.hpp" #include "ocpp/v201/device_model.hpp" @@ -138,8 +139,9 @@ CurrentPhaseType SmartChargingHandler::get_current_phase_type(const std::optiona } SmartChargingHandler::SmartChargingHandler(EvseManagerInterface& evse_manager, - std::shared_ptr& device_model) : - evse_manager(evse_manager), device_model(device_model) { + std::shared_ptr& device_model, + std::shared_ptr database_handler) : + evse_manager(evse_manager), device_model(device_model), database_handler(database_handler) { } ProfileValidationResultEnum SmartChargingHandler::validate_profile(ChargingProfile& profile, int32_t evse_id) { diff --git a/tests/lib/ocpp/v201/CMakeLists.txt b/tests/lib/ocpp/v201/CMakeLists.txt index 8ab50a805..97c2714f4 100644 --- a/tests/lib/ocpp/v201/CMakeLists.txt +++ b/tests/lib/ocpp/v201/CMakeLists.txt @@ -4,6 +4,7 @@ target_include_directories(libocpp_unit_tests PUBLIC target_sources(libocpp_unit_tests PRIVATE test_charge_point.cpp + test_database_handler.cpp test_database_migration_files.cpp test_device_model_storage_sqlite.cpp test_notify_report_requests_splitter.cpp diff --git a/tests/lib/ocpp/v201/test_database_handler.cpp b/tests/lib/ocpp/v201/test_database_handler.cpp index 17844462f..e1ec4f031 100644 --- a/tests/lib/ocpp/v201/test_database_handler.cpp +++ b/tests/lib/ocpp/v201/test_database_handler.cpp @@ -144,4 +144,184 @@ TEST_F(DatabaseHandlerTest, TransactionDelete) { TEST_F(DatabaseHandlerTest, TransactionDeleteNotFound) { EXPECT_NO_THROW(this->database_handler.transaction_delete("txIdNotFound")); -} \ No newline at end of file +} + +TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithNoData_InsertProfile) { + this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 1, .stackLevel = 1}); + std::string sql = "SELECT COUNT(*) FROM CHARGING_PROFILES"; + auto select_stmt = this->database->new_statement(sql); + + EXPECT_EQ(select_stmt->step(), SQLITE_ROW); + auto count = select_stmt->column_int(0); + EXPECT_EQ(count, 1); +} + +TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithProfileData_UpdateProfile) { + this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 2, .stackLevel = 1}); + this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 2, .stackLevel = 2}); + + std::string sql = "SELECT COUNT(*) FROM CHARGING_PROFILES"; + auto select_stmt = this->database->new_statement(sql); + + EXPECT_EQ(select_stmt->step(), SQLITE_ROW); + + auto count = select_stmt->column_int(0); + EXPECT_EQ(count, 1); +} + +TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithProfileData_InsertNewProfile) { + this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 1, .stackLevel = 1}); + this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 2, .stackLevel = 1}); + + std::string sql = "SELECT COUNT(*) FROM CHARGING_PROFILES"; + auto select_stmt = this->database->new_statement(sql); + + EXPECT_EQ(select_stmt->step(), SQLITE_ROW); + + auto count = select_stmt->column_int(0); + EXPECT_EQ(count, 2); +} + +TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithProfileData_DeleteRemovesSpecifiedProfiles) { + this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 1, .stackLevel = 1}); + this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 2, .stackLevel = 1}); + + auto sql = "SELECT COUNT(*) FROM CHARGING_PROFILES"; + + auto select_stmt = this->database->new_statement(sql); + + do { + EXPECT_EQ(select_stmt->step(), SQLITE_ROW); + auto count = select_stmt->column_int(0); + EXPECT_EQ(count, 2); + } while (select_stmt->step() != SQLITE_DONE); + + this->database_handler.delete_charging_profile(1); + + do { + EXPECT_EQ(select_stmt->step(), SQLITE_ROW); + auto count = select_stmt->column_int(0); + EXPECT_EQ(count, 1); + } while (select_stmt->step() != SQLITE_DONE); +} + +TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithProfileData_DeleteAllRemovesAllProfiles) { + this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 1, .stackLevel = 1}); + this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 2, .stackLevel = 1}); + + auto sql = "SELECT COUNT(*) FROM CHARGING_PROFILES"; + + auto select_stmt = this->database->new_statement(sql); + + do { + EXPECT_EQ(select_stmt->step(), SQLITE_ROW); + auto count = select_stmt->column_int(0); + EXPECT_EQ(count, 2); + } while (select_stmt->step() != SQLITE_DONE); + + this->database_handler.clear_charging_profiles(); + + do { + EXPECT_EQ(select_stmt->step(), SQLITE_ROW); + auto count = select_stmt->column_int(0); + EXPECT_EQ(count, 0); + } while (select_stmt->step() != SQLITE_DONE); +} + +TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithNoProfileData_DeleteAllDoesNotFail) { + + auto sql = "SELECT COUNT(*) FROM CHARGING_PROFILES"; + + auto select_stmt = this->database->new_statement(sql); + + do { + EXPECT_EQ(select_stmt->step(), SQLITE_ROW); + auto count = select_stmt->column_int(0); + EXPECT_EQ(count, 0); + } while (select_stmt->step() != SQLITE_DONE); + + this->database_handler.clear_charging_profiles(); + + do { + EXPECT_EQ(select_stmt->step(), SQLITE_ROW); + auto count = select_stmt->column_int(0); + EXPECT_EQ(count, 0); + } while (select_stmt->step() != SQLITE_DONE); +} + +TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseNoProfileData_DeleteAllDoesNotFail) { + auto sql = "SELECT COUNT(*) FROM CHARGING_PROFILES"; + + auto select_stmt = this->database->new_statement(sql); + + do { + EXPECT_EQ(select_stmt->step(), SQLITE_ROW); + auto count = select_stmt->column_int(0); + EXPECT_EQ(count, 0); + } while (select_stmt->step() != SQLITE_DONE); + + this->database_handler.clear_charging_profiles(); + + do { + EXPECT_EQ(select_stmt->step(), SQLITE_ROW); + auto count = select_stmt->column_int(0); + EXPECT_EQ(count, 0); + } while (select_stmt->step() != SQLITE_DONE); +} + +TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithSingleProfileData_LoadsCharingProfile) { + this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 1, .stackLevel = 1}); + + auto sut = this->database_handler.get_all_charging_profiles_by_evse(); + + EXPECT_EQ(sut.size(), 1); + + // The evse id is found + EXPECT_NE(sut.find(1), sut.end()); + + auto profiles = sut[1]; + + EXPECT_EQ(profiles.size(), 1); +} + +TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithMultipleProfileSameEvse_LoadsCharingProfile) { + this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 1, .stackLevel = 1}); + this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 2, .stackLevel = 2}); + this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 3, .stackLevel = 3}); + + auto sut = this->database_handler.get_all_charging_profiles_by_evse(); + + EXPECT_EQ(sut.size(), 1); + + // The evse id is found + EXPECT_NE(sut.find(1), sut.end()); + + auto profiles = sut[1]; + + EXPECT_EQ(profiles.size(), 3); +} + +TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithMultipleProfileDiffEvse_LoadsCharingProfile) { + this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 1, .stackLevel = 1}); + this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 2, .stackLevel = 2}); + this->database_handler.insert_or_update_charging_profile(2, ChargingProfile{.id = 3, .stackLevel = 3}); + this->database_handler.insert_or_update_charging_profile(2, ChargingProfile{.id = 4, .stackLevel = 4}); + this->database_handler.insert_or_update_charging_profile(3, ChargingProfile{.id = 5, .stackLevel = 5}); + this->database_handler.insert_or_update_charging_profile(3, ChargingProfile{.id = 6, .stackLevel = 6}); + + auto sut = this->database_handler.get_all_charging_profiles_by_evse(); + + EXPECT_EQ(sut.size(), 3); + + EXPECT_NE(sut.find(1), sut.end()); + EXPECT_NE(sut.find(2), sut.end()); + EXPECT_NE(sut.find(3), sut.end()); + + auto profiles1 = sut[1]; + auto profiles2 = sut[2]; + auto profiles3 = sut[3]; + + EXPECT_EQ(profiles1.size(), 2); + EXPECT_EQ(profiles2.size(), 2); + EXPECT_EQ(profiles3.size(), 2); +} diff --git a/tests/lib/ocpp/v201/test_smart_charging_handler.cpp b/tests/lib/ocpp/v201/test_smart_charging_handler.cpp index fa0ef65ef..b22d160ce 100644 --- a/tests/lib/ocpp/v201/test_smart_charging_handler.cpp +++ b/tests/lib/ocpp/v201/test_smart_charging_handler.cpp @@ -189,7 +189,12 @@ class ChargepointTestFixtureV201 : public DatabaseTestingUtils { } TestSmartChargingHandler create_smart_charging_handler() { - return TestSmartChargingHandler(*this->evse_manager, device_model); + std::unique_ptr database_connection = + std::make_unique(fs::path("/tmp/ocpp201") / "cp.db"); + std::shared_ptr database_handler = + std::make_shared(std::move(database_connection), MIGRATION_FILES_LOCATION_V201); + database_handler->open_connection(); + return TestSmartChargingHandler(*this->evse_manager, device_model, database_handler); } std::string uuid() { From dc23c5277ffcbb6f3745de18ebcde22f34342b10 Mon Sep 17 00:00:00 2001 From: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> Date: Wed, 24 Jul 2024 18:24:28 +0000 Subject: [PATCH 09/18] charge_point: Reject ChargingStationExternalConstraints profiles in SetChargingProfileRequest Implements K01.FR.22 Signed-off-by: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> --- lib/ocpp/v201/charge_point.cpp | 19 ++++++++++++++++--- tests/lib/ocpp/v201/test_charge_point.cpp | 20 ++++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index fb12d47a7..32ec9349d 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -3161,6 +3161,20 @@ void ChargePoint::handle_set_charging_profile_req(CallreasonCode = "InvalidValue"; + response.statusInfo->additionalInfo = "ChargingStationExternalConstraintsInSetChargingProfileRequest"; + EVLOG_debug << "Rejecting SetChargingProfileRequest:\n reasonCode: " << response.statusInfo->reasonCode.get() + << "\nadditionalInfo: " << response.statusInfo->additionalInfo->get(); + + ocpp::CallResult call_result(response, call.uniqueId); + this->send(call_result); + + return; + } + auto res = this->smart_charging_handler->validate_profile(msg.chargingProfile, msg.evseId); if (res == ProfileValidationResultEnum::Valid) { EVLOG_debug << "Accepting SetChargingProfileRequest"; @@ -3854,10 +3868,9 @@ void ChargePoint::load_charging_profiles() { try { auto evses = this->database_handler->get_all_charging_profiles_by_evse(); EVLOG_info << "Found " << evses.size() << " evse in the database"; - for (auto& profiles : evses) { + for (const auto& [evse_id, profiles] : evses) { try { - auto evse_id = profiles.first; - for (auto profile : profiles.second) { + for (auto profile : profiles) { if (this->smart_charging_handler->validate_profile(profile, evse_id) == ProfileValidationResultEnum::Valid) { this->smart_charging_handler->add_profile(profile, evse_id); diff --git a/tests/lib/ocpp/v201/test_charge_point.cpp b/tests/lib/ocpp/v201/test_charge_point.cpp index e9bfa2a87..53bf18f5c 100644 --- a/tests/lib/ocpp/v201/test_charge_point.cpp +++ b/tests/lib/ocpp/v201/test_charge_point.cpp @@ -660,4 +660,24 @@ TEST_P(ChargePointFixture_InvalidProfiles, K01FR07_SetChargingProfileRequest_Doe charge_point->handle_message(set_charging_profile_req); } +TEST_F(ChargePointFixture, K01FR22_SetChargingProfileRequest_RejectsChargingStationExternalConstraints) { + auto periods = create_charging_schedule_periods({0, 1, 2}); + + auto profile = create_charging_profile( + DEFAULT_PROFILE_ID, ChargingProfilePurposeEnum::ChargingStationExternalConstraints, + create_charge_schedule(ChargingRateUnitEnum::A, periods, ocpp::DateTime("2024-01-17T17:00:00")), DEFAULT_TX_ID); + + SetChargingProfileRequest req; + req.evseId = DEFAULT_EVSE_ID; + req.chargingProfile = profile; + + auto set_charging_profile_req = + request_to_enhanced_message(req); + + EXPECT_CALL(*smart_charging_handler, validate_profile).Times(0); + EXPECT_CALL(*smart_charging_handler, add_profile).Times(0); + + charge_point->handle_message(set_charging_profile_req); +} + } // namespace ocpp::v201 From f42910ebfa399ca25b93ce4438d5c07cf9b0a603 Mon Sep 17 00:00:00 2001 From: Coury Richards <146002925+couryrr-afs@users.noreply.github.com> Date: Mon, 29 Jul 2024 20:31:28 +0000 Subject: [PATCH 10/18] updated database statements for tests Signed-off-by: Coury Richards <146002925+couryrr-afs@users.noreply.github.com> --- tests/lib/ocpp/v201/test_database_handler.cpp | 104 +++++++++--------- 1 file changed, 49 insertions(+), 55 deletions(-) diff --git a/tests/lib/ocpp/v201/test_database_handler.cpp b/tests/lib/ocpp/v201/test_database_handler.cpp index e1ec4f031..30af05753 100644 --- a/tests/lib/ocpp/v201/test_database_handler.cpp +++ b/tests/lib/ocpp/v201/test_database_handler.cpp @@ -148,12 +148,13 @@ TEST_F(DatabaseHandlerTest, TransactionDeleteNotFound) { TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithNoData_InsertProfile) { this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 1, .stackLevel = 1}); - std::string sql = "SELECT COUNT(*) FROM CHARGING_PROFILES"; - auto select_stmt = this->database->new_statement(sql); - EXPECT_EQ(select_stmt->step(), SQLITE_ROW); - auto count = select_stmt->column_int(0); - EXPECT_EQ(count, 1); + auto sut = this->database_handler.get_all_charging_profiles_by_evse(); + + EXPECT_EQ(sut.size(), 1); + EXPECT_EQ(sut[1].size(), 1); // Access the profiles at EVSE_ID 1 + EXPECT_EQ(sut[1][0].id, 1); // Access the profiles at EVSE_ID 1 and get the first profile + EXPECT_EQ(sut[1][0].stackLevel, 1); // Access the profiles at EVSE_ID 1 and get the first profile } TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithProfileData_UpdateProfile) { @@ -190,19 +191,20 @@ TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithProfileData_DeleteRemovesSpecif auto select_stmt = this->database->new_statement(sql); - do { - EXPECT_EQ(select_stmt->step(), SQLITE_ROW); - auto count = select_stmt->column_int(0); - EXPECT_EQ(count, 2); - } while (select_stmt->step() != SQLITE_DONE); + EXPECT_NE(select_stmt->step(), SQLITE_DONE); + auto count = select_stmt->column_int(0); + EXPECT_EQ(count, 2); + + select_stmt->step(); this->database_handler.delete_charging_profile(1); - do { - EXPECT_EQ(select_stmt->step(), SQLITE_ROW); - auto count = select_stmt->column_int(0); - EXPECT_EQ(count, 1); - } while (select_stmt->step() != SQLITE_DONE); + select_stmt->reset(); + + EXPECT_NE(select_stmt->step(), SQLITE_DONE); + count = select_stmt->column_int(0); + EXPECT_EQ(count, 1); + select_stmt->step(); } TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithProfileData_DeleteAllRemovesAllProfiles) { @@ -213,19 +215,18 @@ TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithProfileData_DeleteAllRemovesAll auto select_stmt = this->database->new_statement(sql); - do { - EXPECT_EQ(select_stmt->step(), SQLITE_ROW); - auto count = select_stmt->column_int(0); - EXPECT_EQ(count, 2); - } while (select_stmt->step() != SQLITE_DONE); + EXPECT_NE(select_stmt->step(), SQLITE_DONE); + auto count = select_stmt->column_int(0); + EXPECT_EQ(count, 2); + select_stmt->step(); this->database_handler.clear_charging_profiles(); + select_stmt->reset(); - do { - EXPECT_EQ(select_stmt->step(), SQLITE_ROW); - auto count = select_stmt->column_int(0); - EXPECT_EQ(count, 0); - } while (select_stmt->step() != SQLITE_DONE); + EXPECT_NE(select_stmt->step(), SQLITE_DONE); + count = select_stmt->column_int(0); + EXPECT_EQ(count, 0); + select_stmt->step(); } TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithNoProfileData_DeleteAllDoesNotFail) { @@ -234,39 +235,18 @@ TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithNoProfileData_DeleteAllDoesNotF auto select_stmt = this->database->new_statement(sql); - do { - EXPECT_EQ(select_stmt->step(), SQLITE_ROW); - auto count = select_stmt->column_int(0); - EXPECT_EQ(count, 0); - } while (select_stmt->step() != SQLITE_DONE); - - this->database_handler.clear_charging_profiles(); - - do { - EXPECT_EQ(select_stmt->step(), SQLITE_ROW); - auto count = select_stmt->column_int(0); - EXPECT_EQ(count, 0); - } while (select_stmt->step() != SQLITE_DONE); -} - -TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseNoProfileData_DeleteAllDoesNotFail) { - auto sql = "SELECT COUNT(*) FROM CHARGING_PROFILES"; - - auto select_stmt = this->database->new_statement(sql); - - do { - EXPECT_EQ(select_stmt->step(), SQLITE_ROW); - auto count = select_stmt->column_int(0); - EXPECT_EQ(count, 0); - } while (select_stmt->step() != SQLITE_DONE); + EXPECT_NE(select_stmt->step(), SQLITE_DONE); + auto count = select_stmt->column_int(0); + EXPECT_EQ(count, 0); + select_stmt->step(); this->database_handler.clear_charging_profiles(); + select_stmt->reset(); - do { - EXPECT_EQ(select_stmt->step(), SQLITE_ROW); - auto count = select_stmt->column_int(0); - EXPECT_EQ(count, 0); - } while (select_stmt->step() != SQLITE_DONE); + EXPECT_NE(select_stmt->step(), SQLITE_DONE); + count = select_stmt->column_int(0); + EXPECT_EQ(count, 0); + select_stmt->step(); } TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithSingleProfileData_LoadsCharingProfile) { @@ -322,6 +302,20 @@ TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithMultipleProfileDiffEvse_LoadsCh auto profiles3 = sut[3]; EXPECT_EQ(profiles1.size(), 2); + EXPECT_EQ(profiles1[0].id, 1); + EXPECT_EQ(profiles1[0].stackLevel, 1); + EXPECT_EQ(profiles1[1].id, 2); + EXPECT_EQ(profiles1[1].stackLevel, 2); + EXPECT_EQ(profiles2.size(), 2); + EXPECT_EQ(profiles2[0].id, 3); + EXPECT_EQ(profiles2[0].stackLevel, 3); + EXPECT_EQ(profiles2[1].id, 4); + EXPECT_EQ(profiles2[1].stackLevel, 4); + EXPECT_EQ(profiles3.size(), 2); + EXPECT_EQ(profiles3[0].id, 5); + EXPECT_EQ(profiles3[0].stackLevel, 5); + EXPECT_EQ(profiles3[1].id, 6); + EXPECT_EQ(profiles3[1].stackLevel, 6); } From ef5c5cfb670eb948ca75630400c797d3f0bf9cf4 Mon Sep 17 00:00:00 2001 From: Coury Richards <146002925+couryrr-afs@users.noreply.github.com> Date: Tue, 30 Jul 2024 18:03:21 +0000 Subject: [PATCH 11/18] added columns to database and updated equality checks Signed-off-by: Coury Richards <146002925+couryrr-afs@users.noreply.github.com> --- .../5_up-charging_profiles_db.sql | 2 + include/ocpp/v201/profile_utils.hpp | 6 + lib/CMakeLists.txt | 1 + lib/ocpp/v201/charge_point.cpp | 12 +- lib/ocpp/v201/database_handler.cpp | 8 +- lib/ocpp/v201/profile_utils.cpp | 11 ++ lib/ocpp/v201/smart_charging.cpp | 34 ++++-- tests/lib/ocpp/v201/test_database_handler.cpp | 106 +++++++++++++----- .../ocpp/v201/test_smart_charging_handler.cpp | 7 +- 9 files changed, 129 insertions(+), 58 deletions(-) create mode 100644 include/ocpp/v201/profile_utils.hpp create mode 100644 lib/ocpp/v201/profile_utils.cpp diff --git a/config/v201/core_migrations/5_up-charging_profiles_db.sql b/config/v201/core_migrations/5_up-charging_profiles_db.sql index 39dda1bb2..3f5849288 100644 --- a/config/v201/core_migrations/5_up-charging_profiles_db.sql +++ b/config/v201/core_migrations/5_up-charging_profiles_db.sql @@ -1,5 +1,7 @@ CREATE TABLE CHARGING_PROFILES ( ID INT PRIMARY KEY NOT NULL, EVSE_ID INT NOT NULL, + STACK_LEVEL INT NOT NULL, + CHARGING_PROFILE_PURPOSE TEXT NOT NULL, PROFILE TEXT NOT NULL ); diff --git a/include/ocpp/v201/profile_utils.hpp b/include/ocpp/v201/profile_utils.hpp new file mode 100644 index 000000000..de75ecc8a --- /dev/null +++ b/include/ocpp/v201/profile_utils.hpp @@ -0,0 +1,6 @@ +#include "ocpp/v201/ocpp_types.hpp" +namespace ocpp::v201 { + +bool operator==(const ChargingProfile& lhs, const ChargingProfile& rhs); + +} // namespace ocpp::v201 \ No newline at end of file diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 3e6932d29..cf0967d3f 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -74,6 +74,7 @@ if(LIBOCPP_ENABLE_V201) ocpp/v201/types.cpp ocpp/v201/utils.cpp ocpp/v201/component_state_manager.cpp + ocpp/v201/profile_utils.cpp ) add_subdirectory(ocpp/v201/messages) endif() diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 32ec9349d..6550cac05 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -3869,8 +3869,8 @@ void ChargePoint::load_charging_profiles() { auto evses = this->database_handler->get_all_charging_profiles_by_evse(); EVLOG_info << "Found " << evses.size() << " evse in the database"; for (const auto& [evse_id, profiles] : evses) { - try { - for (auto profile : profiles) { + for (auto profile : profiles) { + try { if (this->smart_charging_handler->validate_profile(profile, evse_id) == ProfileValidationResultEnum::Valid) { this->smart_charging_handler->add_profile(profile, evse_id); @@ -3878,15 +3878,11 @@ void ChargePoint::load_charging_profiles() { // delete if not valid anymore this->database_handler->delete_charging_profile(profile.id); } + } catch (const QueryExecutionException& e) { + EVLOG_warning << "Failed database operation for ChargingProfiles: " << e.what(); } - } catch (common::RequiredEntryNotFoundException& e) { - EVLOG_warning << "Could not get connector id from database: " << e.what(); - } catch (const QueryExecutionException& e) { - EVLOG_warning << "Could not get connector id from database: " << e.what(); } } - } catch (const QueryExecutionException& e) { - EVLOG_warning << "Could not load charging profiles from database: " << e.what(); } catch (const std::exception& e) { EVLOG_warning << "Unknown error while loading charging profiles from database: " << e.what(); } diff --git a/lib/ocpp/v201/database_handler.cpp b/lib/ocpp/v201/database_handler.cpp index 4c20d6814..5b4907cd1 100644 --- a/lib/ocpp/v201/database_handler.cpp +++ b/lib/ocpp/v201/database_handler.cpp @@ -722,14 +722,18 @@ void DatabaseHandler::transaction_delete(const std::string& transaction_id) { void DatabaseHandler::insert_or_update_charging_profile(const int evse_id, const v201::ChargingProfile& profile) { // add or replace - std::string sql = "INSERT OR REPLACE INTO CHARGING_PROFILES (ID, EVSE_ID, PROFILE) VALUES " - "(@id, @evse_id, @profile)"; + std::string sql = + "INSERT OR REPLACE INTO CHARGING_PROFILES (ID, EVSE_ID, STACK_LEVEL, CHARGING_PROFILE_PURPOSE, PROFILE) VALUES " + "(@id, @evse_id, @stack_level, @charging_profile_purpose, @profile)"; auto stmt = this->database->new_statement(sql); json json_profile(profile); stmt->bind_int("@id", profile.id); stmt->bind_int("@evse_id", evse_id); + stmt->bind_int("@stack_level", profile.stackLevel); + stmt->bind_text("@charging_profile_purpose", + conversions::charging_profile_purpose_enum_to_string(profile.chargingProfilePurpose)); stmt->bind_text("@profile", json_profile.dump(), SQLiteString::Transient); if (stmt->step() != SQLITE_DONE) { diff --git a/lib/ocpp/v201/profile_utils.cpp b/lib/ocpp/v201/profile_utils.cpp new file mode 100644 index 000000000..21910c8de --- /dev/null +++ b/lib/ocpp/v201/profile_utils.cpp @@ -0,0 +1,11 @@ +#include "ocpp/v201/profile_utils.hpp" +#include "ocpp/v201/ocpp_types.hpp" + +namespace ocpp::v201 { + +bool operator==(const ChargingProfile& lhs, const ChargingProfile& rhs) { + return lhs.chargingProfileKind == rhs.chargingProfileKind && + lhs.chargingProfilePurpose == rhs.chargingProfilePurpose && lhs.id == rhs.id && + lhs.stackLevel == rhs.stackLevel; +} +} // namespace ocpp::v201 \ No newline at end of file diff --git a/lib/ocpp/v201/smart_charging.cpp b/lib/ocpp/v201/smart_charging.cpp index 51dc9c2c0..5f5d9ae42 100644 --- a/lib/ocpp/v201/smart_charging.cpp +++ b/lib/ocpp/v201/smart_charging.cpp @@ -366,23 +366,35 @@ SmartChargingHandler::validate_profile_schedules(ChargingProfile& profile, SetChargingProfileResponse SmartChargingHandler::add_profile(ChargingProfile& profile, int32_t evse_id) { SetChargingProfileResponse response; response.status = ChargingProfileStatusEnum::Accepted; - auto found_profile = false; - for (auto& [existing_evse_id, evse_profiles] : charging_profiles) { - for (auto it = evse_profiles.begin(); it != evse_profiles.end(); it++) { - if (profile.id == it->id) { - evse_profiles.erase(it); - found_profile = true; + + // K01.FR05 - replace non-ChargingStationExternalConstraints profiles if id exists. + try { + // K01.FR27 - add profiles to database when valid + this->database_handler->insert_or_update_charging_profile(evse_id, profile); + + auto found_profile = false; + for (auto& [existing_evse_id, evse_profiles] : charging_profiles) { + for (auto it = evse_profiles.begin(); it != evse_profiles.end(); it++) { + if (profile.id == it->id) { + evse_profiles.erase(it); + found_profile = true; + break; + } + } + + if (found_profile) { break; } } + charging_profiles[evse_id].push_back(profile); - if (found_profile) { - break; - } + } catch (const QueryExecutionException& e) { + EVLOG_error << "Could not store ChargingProfile in the database: " << e.what(); + response.status = ChargingProfileStatusEnum::Rejected; + response.statusInfo = StatusInfo(); + response.statusInfo->reasonCode = "InternalError"; } - charging_profiles[evse_id].push_back(profile); - return response; } diff --git a/tests/lib/ocpp/v201/test_database_handler.cpp b/tests/lib/ocpp/v201/test_database_handler.cpp index 30af05753..9b8ab0ea8 100644 --- a/tests/lib/ocpp/v201/test_database_handler.cpp +++ b/tests/lib/ocpp/v201/test_database_handler.cpp @@ -2,6 +2,8 @@ // Copyright 2020 - 2024 Pionix GmbH and Contributors to EVerest #include "database_testing_utils.hpp" +#include "ocpp/v201/enums.hpp" +#include "ocpp/v201/profile_utils.hpp" #include #include #include @@ -147,7 +149,9 @@ TEST_F(DatabaseHandlerTest, TransactionDeleteNotFound) { } TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithNoData_InsertProfile) { - this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 1, .stackLevel = 1}); + this->database_handler.insert_or_update_charging_profile( + 1, ChargingProfile{ + .id = 1, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}); auto sut = this->database_handler.get_all_charging_profiles_by_evse(); @@ -158,8 +162,12 @@ TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithNoData_InsertProfile) { } TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithProfileData_UpdateProfile) { - this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 2, .stackLevel = 1}); - this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 2, .stackLevel = 2}); + this->database_handler.insert_or_update_charging_profile( + 1, ChargingProfile{ + .id = 2, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}); + this->database_handler.insert_or_update_charging_profile( + 1, ChargingProfile{ + .id = 2, .stackLevel = 2, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}); std::string sql = "SELECT COUNT(*) FROM CHARGING_PROFILES"; auto select_stmt = this->database->new_statement(sql); @@ -171,8 +179,12 @@ TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithProfileData_UpdateProfile) { } TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithProfileData_InsertNewProfile) { - this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 1, .stackLevel = 1}); - this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 2, .stackLevel = 1}); + this->database_handler.insert_or_update_charging_profile( + 1, ChargingProfile{ + .id = 1, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}); + this->database_handler.insert_or_update_charging_profile( + 1, ChargingProfile{ + .id = 2, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}); std::string sql = "SELECT COUNT(*) FROM CHARGING_PROFILES"; auto select_stmt = this->database->new_statement(sql); @@ -184,8 +196,12 @@ TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithProfileData_InsertNewProfile) { } TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithProfileData_DeleteRemovesSpecifiedProfiles) { - this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 1, .stackLevel = 1}); - this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 2, .stackLevel = 1}); + this->database_handler.insert_or_update_charging_profile( + 1, ChargingProfile{ + .id = 1, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}); + this->database_handler.insert_or_update_charging_profile( + 1, ChargingProfile{ + .id = 2, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}); auto sql = "SELECT COUNT(*) FROM CHARGING_PROFILES"; @@ -208,8 +224,12 @@ TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithProfileData_DeleteRemovesSpecif } TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithProfileData_DeleteAllRemovesAllProfiles) { - this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 1, .stackLevel = 1}); - this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 2, .stackLevel = 1}); + this->database_handler.insert_or_update_charging_profile( + 1, ChargingProfile{ + .id = 1, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}); + this->database_handler.insert_or_update_charging_profile( + 1, ChargingProfile{ + .id = 2, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}); auto sql = "SELECT COUNT(*) FROM CHARGING_PROFILES"; @@ -250,7 +270,9 @@ TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithNoProfileData_DeleteAllDoesNotF } TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithSingleProfileData_LoadsCharingProfile) { - this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 1, .stackLevel = 1}); + this->database_handler.insert_or_update_charging_profile( + 1, ChargingProfile{ + .id = 1, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}); auto sut = this->database_handler.get_all_charging_profiles_by_evse(); @@ -265,9 +287,18 @@ TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithSingleProfileData_LoadsCharingP } TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithMultipleProfileSameEvse_LoadsCharingProfile) { - this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 1, .stackLevel = 1}); - this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 2, .stackLevel = 2}); - this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 3, .stackLevel = 3}); + auto p1 = ChargingProfile{ + .id = 1, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + + this->database_handler.insert_or_update_charging_profile(1, p1); + + auto p2 = ChargingProfile{ + .id = 2, .stackLevel = 2, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(1, p2); + + auto p3 = ChargingProfile{ + .id = 3, .stackLevel = 3, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(1, p3); auto sut = this->database_handler.get_all_charging_profiles_by_evse(); @@ -279,15 +310,34 @@ TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithMultipleProfileSameEvse_LoadsCh auto profiles = sut[1]; EXPECT_EQ(profiles.size(), 3); + EXPECT_EQ(profiles[0], p1); + EXPECT_EQ(profiles[1], p2); + EXPECT_EQ(profiles[2], p3); } TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithMultipleProfileDiffEvse_LoadsCharingProfile) { - this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 1, .stackLevel = 1}); - this->database_handler.insert_or_update_charging_profile(1, ChargingProfile{.id = 2, .stackLevel = 2}); - this->database_handler.insert_or_update_charging_profile(2, ChargingProfile{.id = 3, .stackLevel = 3}); - this->database_handler.insert_or_update_charging_profile(2, ChargingProfile{.id = 4, .stackLevel = 4}); - this->database_handler.insert_or_update_charging_profile(3, ChargingProfile{.id = 5, .stackLevel = 5}); - this->database_handler.insert_or_update_charging_profile(3, ChargingProfile{.id = 6, .stackLevel = 6}); + auto p1 = ChargingProfile{ + .id = 1, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(1, p1); + + auto p2 = + ChargingProfile{.id = 2, .stackLevel = 2, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxProfile}; + this->database_handler.insert_or_update_charging_profile(1, p2); + + auto p3 = ChargingProfile{ + .id = 3, .stackLevel = 3, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(2, p3); + auto p4 = + ChargingProfile{.id = 4, .stackLevel = 4, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxProfile}; + this->database_handler.insert_or_update_charging_profile(2, p4); + + auto p5 = ChargingProfile{ + .id = 5, .stackLevel = 5, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(3, p5); + + auto p6 = + ChargingProfile{.id = 6, .stackLevel = 6, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxProfile}; + this->database_handler.insert_or_update_charging_profile(3, p6); auto sut = this->database_handler.get_all_charging_profiles_by_evse(); @@ -302,20 +352,14 @@ TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithMultipleProfileDiffEvse_LoadsCh auto profiles3 = sut[3]; EXPECT_EQ(profiles1.size(), 2); - EXPECT_EQ(profiles1[0].id, 1); - EXPECT_EQ(profiles1[0].stackLevel, 1); - EXPECT_EQ(profiles1[1].id, 2); - EXPECT_EQ(profiles1[1].stackLevel, 2); + EXPECT_EQ(profiles1[0], p1); + EXPECT_EQ(profiles1[1], p2); EXPECT_EQ(profiles2.size(), 2); - EXPECT_EQ(profiles2[0].id, 3); - EXPECT_EQ(profiles2[0].stackLevel, 3); - EXPECT_EQ(profiles2[1].id, 4); - EXPECT_EQ(profiles2[1].stackLevel, 4); + EXPECT_EQ(profiles2[0], p3); + EXPECT_EQ(profiles2[1], p4); EXPECT_EQ(profiles3.size(), 2); - EXPECT_EQ(profiles3[0].id, 5); - EXPECT_EQ(profiles3[0].stackLevel, 5); - EXPECT_EQ(profiles3[1].id, 6); - EXPECT_EQ(profiles3[1].stackLevel, 6); + EXPECT_EQ(profiles3[0], p5); + EXPECT_EQ(profiles3[1], p6); } diff --git a/tests/lib/ocpp/v201/test_smart_charging_handler.cpp b/tests/lib/ocpp/v201/test_smart_charging_handler.cpp index b22d160ce..76491b8c9 100644 --- a/tests/lib/ocpp/v201/test_smart_charging_handler.cpp +++ b/tests/lib/ocpp/v201/test_smart_charging_handler.cpp @@ -28,17 +28,12 @@ #include #include +#include "ocpp/v201/profile_utils.hpp" #include #include namespace ocpp::v201 { -bool operator==(const ChargingProfile& lhs, const ChargingProfile& rhs) { - return lhs.chargingProfileKind == rhs.chargingProfileKind && - lhs.chargingProfilePurpose == rhs.chargingProfilePurpose && lhs.id == rhs.id && - lhs.stackLevel == rhs.stackLevel; -} - static const int NR_OF_EVSES = 1; static const int STATION_WIDE_ID = 0; static const int DEFAULT_EVSE_ID = 1; From 47863179a9bf3e3453fe682c304da28c0c3bb57e Mon Sep 17 00:00:00 2001 From: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> Date: Wed, 31 Jul 2024 15:07:39 +0000 Subject: [PATCH 12/18] smart_charging: Add combined validation and add method Signed-off-by: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> --- include/ocpp/v201/smart_charging.hpp | 8 ++ lib/ocpp/v201/charge_point.cpp | 8 +- lib/ocpp/v201/smart_charging.cpp | 16 ++++ .../mocks/smart_charging_handler_mock.hpp | 1 + tests/lib/ocpp/v201/test_charge_point.cpp | 96 ++++--------------- .../ocpp/v201/test_smart_charging_handler.cpp | 37 +++++++ 6 files changed, 83 insertions(+), 83 deletions(-) diff --git a/include/ocpp/v201/smart_charging.hpp b/include/ocpp/v201/smart_charging.hpp index 0b41d3011..9b5763c0c 100644 --- a/include/ocpp/v201/smart_charging.hpp +++ b/include/ocpp/v201/smart_charging.hpp @@ -61,6 +61,8 @@ class SmartChargingHandlerInterface { public: virtual ~SmartChargingHandlerInterface() = default; + virtual SetChargingProfileResponse validate_and_add_profile(ChargingProfile& profile, int32_t evse_id) = 0; + virtual ProfileValidationResultEnum validate_profile(ChargingProfile& profile, int32_t evse_id) = 0; virtual SetChargingProfileResponse add_profile(ChargingProfile& profile, int32_t evse_id) = 0; @@ -81,6 +83,12 @@ class SmartChargingHandler : public SmartChargingHandlerInterface { SmartChargingHandler(EvseManagerInterface& evse_manager, std::shared_ptr& device_model, std::shared_ptr database_handler); + /// + /// \brief validates the given \p profile according to the specification, + /// adding it to our stored list of profiles if valid. + /// + SetChargingProfileResponse validate_and_add_profile(ChargingProfile& profile, int32_t evse_id) override; + /// /// \brief validates the given \p profile according to the specification. /// If a profile does not have validFrom or validTo set, we conform the values diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 6550cac05..fde7e8e5e 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -3175,15 +3175,11 @@ void ChargePoint::handle_set_charging_profile_req(Callsmart_charging_handler->validate_profile(msg.chargingProfile, msg.evseId); - if (res == ProfileValidationResultEnum::Valid) { + response = this->smart_charging_handler->validate_and_add_profile(msg.chargingProfile, msg.evseId); + if (response.status == ChargingProfileStatusEnum::Accepted) { EVLOG_debug << "Accepting SetChargingProfileRequest"; - response = this->smart_charging_handler->add_profile(msg.chargingProfile, msg.evseId); this->callbacks.set_charging_profiles_callback(); } else { - response.statusInfo = StatusInfo(); - response.statusInfo->reasonCode = conversions::profile_validation_result_to_reason_code(res); - response.statusInfo->additionalInfo = conversions::profile_validation_result_to_string(res); EVLOG_debug << "Rejecting SetChargingProfileRequest:\n reasonCode: " << response.statusInfo->reasonCode.get() << "\nadditionalInfo: " << response.statusInfo->additionalInfo->get(); } diff --git a/lib/ocpp/v201/smart_charging.cpp b/lib/ocpp/v201/smart_charging.cpp index 5f5d9ae42..d07a0a76a 100644 --- a/lib/ocpp/v201/smart_charging.cpp +++ b/lib/ocpp/v201/smart_charging.cpp @@ -144,6 +144,22 @@ SmartChargingHandler::SmartChargingHandler(EvseManagerInterface& evse_manager, evse_manager(evse_manager), device_model(device_model), database_handler(database_handler) { } +SetChargingProfileResponse SmartChargingHandler::validate_and_add_profile(ChargingProfile& profile, int32_t evse_id) { + SetChargingProfileResponse response; + response.status = ChargingProfileStatusEnum::Rejected; + + auto result = this->validate_profile(profile, evse_id); + if (result == ProfileValidationResultEnum::Valid) { + response = this->add_profile(profile, evse_id); + } else { + response.statusInfo = StatusInfo(); + response.statusInfo->reasonCode = conversions::profile_validation_result_to_reason_code(result); + response.statusInfo->additionalInfo = conversions::profile_validation_result_to_string(result); + } + + return response; +} + ProfileValidationResultEnum SmartChargingHandler::validate_profile(ChargingProfile& profile, int32_t evse_id) { conform_validity_periods(profile); diff --git a/tests/lib/ocpp/v201/mocks/smart_charging_handler_mock.hpp b/tests/lib/ocpp/v201/mocks/smart_charging_handler_mock.hpp index 25eea67f3..b352c34b2 100644 --- a/tests/lib/ocpp/v201/mocks/smart_charging_handler_mock.hpp +++ b/tests/lib/ocpp/v201/mocks/smart_charging_handler_mock.hpp @@ -11,6 +11,7 @@ namespace ocpp::v201 { class SmartChargingHandlerMock : public SmartChargingHandlerInterface { public: + MOCK_METHOD(SetChargingProfileResponse, validate_and_add_profile, (ChargingProfile & profile, int32_t evse_id)); MOCK_METHOD(ProfileValidationResultEnum, validate_profile, (ChargingProfile & profile, int32_t evse_id)); MOCK_METHOD(SetChargingProfileResponse, add_profile, (ChargingProfile & profile, int32_t evse_id)); }; diff --git a/tests/lib/ocpp/v201/test_charge_point.cpp b/tests/lib/ocpp/v201/test_charge_point.cpp index 53bf18f5c..a02fecddb 100644 --- a/tests/lib/ocpp/v201/test_charge_point.cpp +++ b/tests/lib/ocpp/v201/test_charge_point.cpp @@ -7,6 +7,7 @@ #include "ocpp/v201/device_model_storage_sqlite.hpp" #include "ocpp/v201/init_device_model_db.hpp" #include "ocpp/v201/messages/SetChargingProfile.hpp" +#include "ocpp/v201/profile_utils.hpp" #include "ocpp/v201/smart_charging.hpp" #include "ocpp/v201/types.hpp" #include "smart_charging_handler_mock.hpp" @@ -531,7 +532,7 @@ TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfOptionalTransactionE EXPECT_TRUE(callbacks.all_callbacks_valid()); } -TEST_F(ChargePointFixture, K01_SetChargingProfileRequest_ValidatesProfile) { +TEST_F(ChargePointFixture, K01_SetChargingProfileRequest_ValidatesAndAddsProfile) { auto periods = create_charging_schedule_periods({0, 1, 2}); auto profile = create_charging_profile( @@ -545,76 +546,7 @@ TEST_F(ChargePointFixture, K01_SetChargingProfileRequest_ValidatesProfile) { auto set_charging_profile_req = request_to_enhanced_message(req); - EXPECT_CALL(*smart_charging_handler, validate_profile(testing::_, testing::_)); - - charge_point->handle_message(set_charging_profile_req); -} - -TEST_F(ChargePointFixture, K01_SetChargingProfileRequest_AddsValidProfile) { - auto periods = create_charging_schedule_periods({0, 1, 2}); - - auto profile = create_charging_profile( - DEFAULT_PROFILE_ID, ChargingProfilePurposeEnum::TxProfile, - create_charge_schedule(ChargingRateUnitEnum::A, periods, ocpp::DateTime("2024-01-17T17:00:00")), DEFAULT_TX_ID); - - SetChargingProfileRequest req; - req.evseId = DEFAULT_EVSE_ID; - req.chargingProfile = profile; - - auto set_charging_profile_req = - request_to_enhanced_message(req); - - EXPECT_CALL(*smart_charging_handler, validate_profile(testing::_, testing::_)); - ON_CALL(*smart_charging_handler, validate_profile) - .WillByDefault(testing::Return(ProfileValidationResultEnum::Valid)); - EXPECT_CALL(*smart_charging_handler, add_profile(testing::_, testing::_)); - - charge_point->handle_message(set_charging_profile_req); -} - -class ChargePointFixture_InvalidProfiles : public ChargePointFixture, - public ::testing::WithParamInterface {}; - -INSTANTIATE_TEST_SUITE_P( - ChargePointOnlyAddValidProfilesTests, ChargePointFixture_InvalidProfiles, - testing::Values(ProfileValidationResultEnum::EvseDoesNotExist, ProfileValidationResultEnum::InvalidProfileType, - ProfileValidationResultEnum::TxProfileMissingTransactionId, - ProfileValidationResultEnum::TxProfileEvseIdNotGreaterThanZero, - ProfileValidationResultEnum::TxProfileTransactionNotOnEvse, - ProfileValidationResultEnum::TxProfileEvseHasNoActiveTransaction, - ProfileValidationResultEnum::TxProfileConflictingStackLevel, - ProfileValidationResultEnum::ChargingProfileNoChargingSchedulePeriods, - ProfileValidationResultEnum::ChargingProfileFirstStartScheduleIsNotZero, - ProfileValidationResultEnum::ChargingProfileMissingRequiredStartSchedule, - ProfileValidationResultEnum::ChargingProfileExtraneousStartSchedule, - ProfileValidationResultEnum::ChargingScheduleChargingRateUnitUnsupported, - ProfileValidationResultEnum::ChargingSchedulePeriodsOutOfOrder, - ProfileValidationResultEnum::ChargingSchedulePeriodInvalidPhaseToUse, - ProfileValidationResultEnum::ChargingSchedulePeriodUnsupportedNumberPhases, - ProfileValidationResultEnum::ChargingSchedulePeriodExtraneousPhaseValues, - ProfileValidationResultEnum::ChargingSchedulePeriodPhaseToUseACPhaseSwitchingUnsupported, - ProfileValidationResultEnum::ChargingStationMaxProfileCannotBeRelative, - ProfileValidationResultEnum::ChargingStationMaxProfileEvseIdGreaterThanZero, - ProfileValidationResultEnum::DuplicateTxDefaultProfileFound, - ProfileValidationResultEnum::DuplicateProfileValidityPeriod)); - -TEST_P(ChargePointFixture_InvalidProfiles, K01_SetChargingProfileRequest_DoesnNotAddInvalidProfiles) { - auto periods = create_charging_schedule_periods({0, 1, 2}); - - auto profile = create_charging_profile( - DEFAULT_PROFILE_ID, ChargingProfilePurposeEnum::TxProfile, - create_charge_schedule(ChargingRateUnitEnum::A, periods, ocpp::DateTime("2024-01-17T17:00:00")), DEFAULT_TX_ID); - - SetChargingProfileRequest req; - req.evseId = DEFAULT_EVSE_ID; - req.chargingProfile = profile; - - auto set_charging_profile_req = - request_to_enhanced_message(req); - - EXPECT_CALL(*smart_charging_handler, validate_profile(testing::_, testing::_)); - ON_CALL(*smart_charging_handler, validate_profile).WillByDefault(testing::Return(GetParam())); - EXPECT_CALL(*smart_charging_handler, add_profile(testing::_, testing::_)).Times(0); + EXPECT_CALL(*smart_charging_handler, validate_and_add_profile(profile, DEFAULT_EVSE_ID)); charge_point->handle_message(set_charging_profile_req); } @@ -633,14 +565,16 @@ TEST_F(ChargePointFixture, K01FR07_SetChargingProfileRequest_TriggersCallbackWhe auto set_charging_profile_req = request_to_enhanced_message(req); - ON_CALL(*smart_charging_handler, validate_profile) - .WillByDefault(testing::Return(ProfileValidationResultEnum::Valid)); + SetChargingProfileResponse accept_response; + accept_response.status = ChargingProfileStatusEnum::Accepted; + + ON_CALL(*smart_charging_handler, validate_and_add_profile).WillByDefault(testing::Return(accept_response)); EXPECT_CALL(set_charging_profiles_callback_mock, Call); charge_point->handle_message(set_charging_profile_req); } -TEST_P(ChargePointFixture_InvalidProfiles, K01FR07_SetChargingProfileRequest_DoesNotTriggerCallbackWhenInvalid) { +TEST_F(ChargePointFixture, K01FR07_SetChargingProfileRequest_DoesNotTriggerCallbackWhenInvalid) { auto periods = create_charging_schedule_periods({0, 1, 2}); auto profile = create_charging_profile( @@ -654,7 +588,15 @@ TEST_P(ChargePointFixture_InvalidProfiles, K01FR07_SetChargingProfileRequest_Doe auto set_charging_profile_req = request_to_enhanced_message(req); - ON_CALL(*smart_charging_handler, validate_profile).WillByDefault(testing::Return(GetParam())); + SetChargingProfileResponse reject_response; + reject_response.status = ChargingProfileStatusEnum::Rejected; + reject_response.statusInfo = StatusInfo(); + reject_response.statusInfo->reasonCode = conversions::profile_validation_result_to_reason_code( + ProfileValidationResultEnum::TxProfileEvseHasNoActiveTransaction); + reject_response.statusInfo->additionalInfo = conversions::profile_validation_result_to_string( + ProfileValidationResultEnum::TxProfileEvseHasNoActiveTransaction); + + ON_CALL(*smart_charging_handler, validate_and_add_profile).WillByDefault(testing::Return(reject_response)); EXPECT_CALL(set_charging_profiles_callback_mock, Call).Times(0); charge_point->handle_message(set_charging_profile_req); @@ -674,8 +616,8 @@ TEST_F(ChargePointFixture, K01FR22_SetChargingProfileRequest_RejectsChargingStat auto set_charging_profile_req = request_to_enhanced_message(req); - EXPECT_CALL(*smart_charging_handler, validate_profile).Times(0); - EXPECT_CALL(*smart_charging_handler, add_profile).Times(0); + EXPECT_CALL(*smart_charging_handler, validate_and_add_profile).Times(0); + EXPECT_CALL(set_charging_profiles_callback_mock, Call).Times(0); charge_point->handle_message(set_charging_profile_req); } diff --git a/tests/lib/ocpp/v201/test_smart_charging_handler.cpp b/tests/lib/ocpp/v201/test_smart_charging_handler.cpp index 76491b8c9..b298b9fc3 100644 --- a/tests/lib/ocpp/v201/test_smart_charging_handler.cpp +++ b/tests/lib/ocpp/v201/test_smart_charging_handler.cpp @@ -1127,4 +1127,41 @@ TEST_F(ChargepointTestFixtureV201, EXPECT_THAT(profiles, testing::Contains(profile5)); } +TEST_F(ChargepointTestFixtureV201, K01_ValidateAndAdd_RejectsInvalidProfiles) { + auto periods = create_charging_schedule_periods(0); + auto profile = create_charging_profile( + DEFAULT_PROFILE_ID, ChargingProfilePurposeEnum::TxProfile, + create_charge_schedule(ChargingRateUnitEnum::A, periods, ocpp::DateTime("2024-01-17T17:00:00"))); + + auto sut = handler.validate_and_add_profile(profile, DEFAULT_EVSE_ID); + auto status_info = sut.statusInfo; + EXPECT_THAT(sut.status, testing::Eq(ChargingProfileStatusEnum::Rejected)); + EXPECT_THAT(status_info->reasonCode.get(), testing::Eq(conversions::profile_validation_result_to_reason_code( + ProfileValidationResultEnum::TxProfileMissingTransactionId))); + + EXPECT_THAT(status_info->additionalInfo.has_value(), testing::IsTrue()); + EXPECT_THAT(status_info->additionalInfo->get(), testing::Eq(conversions::profile_validation_result_to_string( + ProfileValidationResultEnum::TxProfileMissingTransactionId))); + + auto profiles = handler.get_profiles(); + EXPECT_THAT(profiles, testing::Not(testing::Contains(profile))); +} + +TEST_F(ChargepointTestFixtureV201, K01_ValidateAndAdd_AddsValidProfiles) { + auto periods = create_charging_schedule_periods({0, 1, 2}); + + this->evse_manager->open_transaction(DEFAULT_EVSE_ID, DEFAULT_TX_ID); + + auto profile = create_charging_profile( + DEFAULT_PROFILE_ID, ChargingProfilePurposeEnum::TxProfile, + create_charge_schedule(ChargingRateUnitEnum::A, periods, ocpp::DateTime("2024-01-17T17:00:00")), DEFAULT_TX_ID); + + auto sut = handler.validate_and_add_profile(profile, DEFAULT_EVSE_ID); + EXPECT_THAT(sut.status, testing::Eq(ChargingProfileStatusEnum::Accepted)); + EXPECT_THAT(sut.statusInfo.has_value(), testing::IsFalse()); + + auto profiles = handler.get_profiles(); + EXPECT_THAT(profiles, testing::Contains(profile)); +} + } // namespace ocpp::v201 From 550b9a814d7a39f18b3dd0c43a9fca64ef47feb1 Mon Sep 17 00:00:00 2001 From: Peter Giavotto <146003699+Giavotto@users.noreply.github.com> Date: Thu, 1 Aug 2024 12:31:40 -0400 Subject: [PATCH 13/18] Updated FR45 to use supplyPhases Signed-off-by: Peter Giavotto <146003699+Giavotto@users.noreply.github.com> --- lib/ocpp/v201/smart_charging.cpp | 5 ++++- tests/lib/ocpp/v201/test_smart_charging_handler.cpp | 8 ++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/ocpp/v201/smart_charging.cpp b/lib/ocpp/v201/smart_charging.cpp index d07a0a76a..e1ae46250 100644 --- a/lib/ocpp/v201/smart_charging.cpp +++ b/lib/ocpp/v201/smart_charging.cpp @@ -302,6 +302,9 @@ ProfileValidationResultEnum SmartChargingHandler::validate_tx_profile(const Char ProfileValidationResultEnum SmartChargingHandler::validate_profile_schedules(ChargingProfile& profile, std::optional evse_opt) const { + auto charging_station_supply_phases = + this->device_model->get_value(ControllerComponentVariables::ChargingStationSupplyPhases); + for (auto& schedule : profile.chargingSchedule) { // K01.FR.26; We currently need to do string conversions for this manually because our DeviceModel class does // not let us get a vector of ChargingScheduleChargingRateUnits. @@ -355,7 +358,7 @@ SmartChargingHandler::validate_profile_schedules(ChargingProfile& profile, if (phase_type == CurrentPhaseType::AC) { // K01.FR.45; Once again rejecting invalid values if (charging_schedule_period.numberPhases.has_value() && - charging_schedule_period.numberPhases > DEFAULT_AND_MAX_NUMBER_PHASES) { + charging_schedule_period.numberPhases > charging_station_supply_phases) { return ProfileValidationResultEnum::ChargingSchedulePeriodUnsupportedNumberPhases; } diff --git a/tests/lib/ocpp/v201/test_smart_charging_handler.cpp b/tests/lib/ocpp/v201/test_smart_charging_handler.cpp index b298b9fc3..7197f560d 100644 --- a/tests/lib/ocpp/v201/test_smart_charging_handler.cpp +++ b/tests/lib/ocpp/v201/test_smart_charging_handler.cpp @@ -686,7 +686,11 @@ TEST_F(ChargepointTestFixtureV201, K01FR44_IfPhaseToUseProvidedForDCChargingStat EXPECT_THAT(sut, testing::Eq(ProfileValidationResultEnum::ChargingSchedulePeriodExtraneousPhaseValues)); } -TEST_F(ChargepointTestFixtureV201, K01FR45_IfNumberPhasesGreaterThanMaxNumberPhasesForACEVSE_ThenProfileIsInvalid) { +TEST_F(ChargepointTestFixtureV201, + K01FR45_IfNumberPhasesGreaterThanChargingStationSupplyPhasesForACEVSE_ThenProfileIsInvalid) { + device_model->set_value(ControllerComponentVariables::ChargingStationSupplyPhases.component, + ControllerComponentVariables::ChargingStationSupplyPhases.variable.value(), + AttributeEnum::Actual, std::to_string(0), "test", true); auto mock_evse = testing::NiceMock(); ON_CALL(mock_evse, get_current_phase_type).WillByDefault(testing::Return(CurrentPhaseType::AC)); @@ -701,7 +705,7 @@ TEST_F(ChargepointTestFixtureV201, K01FR45_IfNumberPhasesGreaterThanMaxNumberPha } TEST_F(ChargepointTestFixtureV201, - K01FR45_IfNumberPhasesGreaterThanMaxNumberPhasesForACChargingStation_ThenProfileIsInvalid) { + K01FR45_IfNumberPhasesGreaterThanChargingStationSupplyPhasesForACChargingStation_ThenProfileIsInvalid) { device_model->set_value(ControllerComponentVariables::ChargingStationSupplyPhases.component, ControllerComponentVariables::ChargingStationSupplyPhases.variable.value(), AttributeEnum::Actual, std::to_string(1), "test", true); From 48ab57f72208c7f280f8bd47da23064951f70f46 Mon Sep 17 00:00:00 2001 From: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> Date: Mon, 5 Aug 2024 19:06:41 +0000 Subject: [PATCH 14/18] database_handler: Correct spelling in database tests Signed-off-by: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> --- tests/lib/ocpp/v201/test_database_handler.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/lib/ocpp/v201/test_database_handler.cpp b/tests/lib/ocpp/v201/test_database_handler.cpp index 9b8ab0ea8..8265e5dd4 100644 --- a/tests/lib/ocpp/v201/test_database_handler.cpp +++ b/tests/lib/ocpp/v201/test_database_handler.cpp @@ -269,7 +269,7 @@ TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithNoProfileData_DeleteAllDoesNotF select_stmt->step(); } -TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithSingleProfileData_LoadsCharingProfile) { +TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithSingleProfileData_LoadsChargingProfile) { this->database_handler.insert_or_update_charging_profile( 1, ChargingProfile{ .id = 1, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}); @@ -286,7 +286,7 @@ TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithSingleProfileData_LoadsCharingP EXPECT_EQ(profiles.size(), 1); } -TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithMultipleProfileSameEvse_LoadsCharingProfile) { +TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithMultipleProfileSameEvse_LoadsChargingProfile) { auto p1 = ChargingProfile{ .id = 1, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; @@ -315,7 +315,7 @@ TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithMultipleProfileSameEvse_LoadsCh EXPECT_EQ(profiles[2], p3); } -TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithMultipleProfileDiffEvse_LoadsCharingProfile) { +TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithMultipleProfileDiffEvse_LoadsChargingProfile) { auto p1 = ChargingProfile{ .id = 1, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; this->database_handler.insert_or_update_charging_profile(1, p1); From ac7cdf5d2a86d613c3e18a1304df0fda7ed541a4 Mon Sep 17 00:00:00 2001 From: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> Date: Mon, 5 Aug 2024 19:10:32 +0000 Subject: [PATCH 15/18] database_handler: Test profile equality Signed-off-by: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> --- tests/lib/ocpp/v201/test_database_handler.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/lib/ocpp/v201/test_database_handler.cpp b/tests/lib/ocpp/v201/test_database_handler.cpp index 8265e5dd4..6d4c8848f 100644 --- a/tests/lib/ocpp/v201/test_database_handler.cpp +++ b/tests/lib/ocpp/v201/test_database_handler.cpp @@ -270,9 +270,9 @@ TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithNoProfileData_DeleteAllDoesNotF } TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithSingleProfileData_LoadsChargingProfile) { - this->database_handler.insert_or_update_charging_profile( - 1, ChargingProfile{ - .id = 1, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}); + auto profile = ChargingProfile{ + .id = 1, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(1, profile); auto sut = this->database_handler.get_all_charging_profiles_by_evse(); @@ -284,6 +284,7 @@ TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithSingleProfileData_LoadsCharging auto profiles = sut[1]; EXPECT_EQ(profiles.size(), 1); + EXPECT_EQ(profile, profiles[0]); } TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithMultipleProfileSameEvse_LoadsChargingProfile) { From c10087ff9886d1d47403612d6923b76c064d2ca8 Mon Sep 17 00:00:00 2001 From: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> Date: Mon, 5 Aug 2024 19:21:23 +0000 Subject: [PATCH 16/18] database_handler: Rename function to get all profiles grouped by evse id Signed-off-by: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> --- include/ocpp/v201/database_handler.hpp | 2 +- lib/ocpp/v201/charge_point.cpp | 2 +- lib/ocpp/v201/database_handler.cpp | 2 +- tests/lib/ocpp/v201/test_database_handler.cpp | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/ocpp/v201/database_handler.hpp b/include/ocpp/v201/database_handler.hpp index 99f294913..94ddd3c91 100644 --- a/include/ocpp/v201/database_handler.hpp +++ b/include/ocpp/v201/database_handler.hpp @@ -181,7 +181,7 @@ class DatabaseHandler : public common::DatabaseHandlerCommon { void clear_charging_profiles(); /// \brief Retrieves all ChargingProfiles - virtual std::map> get_all_charging_profiles_by_evse(); + virtual std::map> get_all_charging_profiles_group_by_evse(); }; } // namespace v201 diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index fde7e8e5e..9cd21c3e1 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -3862,7 +3862,7 @@ void ChargePoint::execute_change_availability_request(ChangeAvailabilityRequest // K01.27 - load profiles from database void ChargePoint::load_charging_profiles() { try { - auto evses = this->database_handler->get_all_charging_profiles_by_evse(); + auto evses = this->database_handler->get_all_charging_profiles_group_by_evse(); EVLOG_info << "Found " << evses.size() << " evse in the database"; for (const auto& [evse_id, profiles] : evses) { for (auto profile : profiles) { diff --git a/lib/ocpp/v201/database_handler.cpp b/lib/ocpp/v201/database_handler.cpp index 5b4907cd1..ff3c0b17e 100644 --- a/lib/ocpp/v201/database_handler.cpp +++ b/lib/ocpp/v201/database_handler.cpp @@ -755,7 +755,7 @@ void DatabaseHandler::clear_charging_profiles() { this->database->clear_table("CHARGING_PROFILES"); } -std::map> DatabaseHandler::get_all_charging_profiles_by_evse() { +std::map> DatabaseHandler::get_all_charging_profiles_group_by_evse() { std::map> map; std::string sql = "SELECT EVSE_ID, PROFILE FROM CHARGING_PROFILES"; diff --git a/tests/lib/ocpp/v201/test_database_handler.cpp b/tests/lib/ocpp/v201/test_database_handler.cpp index 6d4c8848f..8dd4f654f 100644 --- a/tests/lib/ocpp/v201/test_database_handler.cpp +++ b/tests/lib/ocpp/v201/test_database_handler.cpp @@ -153,7 +153,7 @@ TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithNoData_InsertProfile) { 1, ChargingProfile{ .id = 1, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}); - auto sut = this->database_handler.get_all_charging_profiles_by_evse(); + auto sut = this->database_handler.get_all_charging_profiles_group_by_evse(); EXPECT_EQ(sut.size(), 1); EXPECT_EQ(sut[1].size(), 1); // Access the profiles at EVSE_ID 1 @@ -274,7 +274,7 @@ TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithSingleProfileData_LoadsCharging .id = 1, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; this->database_handler.insert_or_update_charging_profile(1, profile); - auto sut = this->database_handler.get_all_charging_profiles_by_evse(); + auto sut = this->database_handler.get_all_charging_profiles_group_by_evse(); EXPECT_EQ(sut.size(), 1); @@ -301,7 +301,7 @@ TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithMultipleProfileSameEvse_LoadsCh .id = 3, .stackLevel = 3, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; this->database_handler.insert_or_update_charging_profile(1, p3); - auto sut = this->database_handler.get_all_charging_profiles_by_evse(); + auto sut = this->database_handler.get_all_charging_profiles_group_by_evse(); EXPECT_EQ(sut.size(), 1); @@ -340,7 +340,7 @@ TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithMultipleProfileDiffEvse_LoadsCh ChargingProfile{.id = 6, .stackLevel = 6, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxProfile}; this->database_handler.insert_or_update_charging_profile(3, p6); - auto sut = this->database_handler.get_all_charging_profiles_by_evse(); + auto sut = this->database_handler.get_all_charging_profiles_group_by_evse(); EXPECT_EQ(sut.size(), 3); From 44e41f00f614b045071fa2738ec941c3f7d2d628 Mon Sep 17 00:00:00 2001 From: Coury Richards <146002925+couryrr-afs@users.noreply.github.com> Date: Tue, 6 Aug 2024 17:21:20 +0000 Subject: [PATCH 17/18] moved charing profile equality to test since it is not a full comparison Signed-off-by: Coury Richards <146002925+couryrr-afs@users.noreply.github.com> --- include/ocpp/v201/profile_utils.hpp | 6 ------ lib/CMakeLists.txt | 1 - lib/ocpp/v201/profile_utils.cpp | 11 ----------- tests/lib/ocpp/v201/comparators.cpp | 10 +++++++++- tests/lib/ocpp/v201/comparators.hpp | 6 ++++++ tests/lib/ocpp/v201/test_charge_point.cpp | 2 +- tests/lib/ocpp/v201/test_database_handler.cpp | 2 +- tests/lib/ocpp/v201/test_smart_charging_handler.cpp | 2 +- 8 files changed, 18 insertions(+), 22 deletions(-) delete mode 100644 include/ocpp/v201/profile_utils.hpp delete mode 100644 lib/ocpp/v201/profile_utils.cpp diff --git a/include/ocpp/v201/profile_utils.hpp b/include/ocpp/v201/profile_utils.hpp deleted file mode 100644 index de75ecc8a..000000000 --- a/include/ocpp/v201/profile_utils.hpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "ocpp/v201/ocpp_types.hpp" -namespace ocpp::v201 { - -bool operator==(const ChargingProfile& lhs, const ChargingProfile& rhs); - -} // namespace ocpp::v201 \ No newline at end of file diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index cf0967d3f..3e6932d29 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -74,7 +74,6 @@ if(LIBOCPP_ENABLE_V201) ocpp/v201/types.cpp ocpp/v201/utils.cpp ocpp/v201/component_state_manager.cpp - ocpp/v201/profile_utils.cpp ) add_subdirectory(ocpp/v201/messages) endif() diff --git a/lib/ocpp/v201/profile_utils.cpp b/lib/ocpp/v201/profile_utils.cpp deleted file mode 100644 index 21910c8de..000000000 --- a/lib/ocpp/v201/profile_utils.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "ocpp/v201/profile_utils.hpp" -#include "ocpp/v201/ocpp_types.hpp" - -namespace ocpp::v201 { - -bool operator==(const ChargingProfile& lhs, const ChargingProfile& rhs) { - return lhs.chargingProfileKind == rhs.chargingProfileKind && - lhs.chargingProfilePurpose == rhs.chargingProfilePurpose && lhs.id == rhs.id && - lhs.stackLevel == rhs.stackLevel; -} -} // namespace ocpp::v201 \ No newline at end of file diff --git a/tests/lib/ocpp/v201/comparators.cpp b/tests/lib/ocpp/v201/comparators.cpp index b7f28dca7..9940bfb62 100644 --- a/tests/lib/ocpp/v201/comparators.cpp +++ b/tests/lib/ocpp/v201/comparators.cpp @@ -18,4 +18,12 @@ bool operator==(const ::ocpp::v201::GetCertificateStatusRequest& a, a.ocspRequestData.responderURL == b.ocspRequestData.responderURL; } -} // namespace testing::internal \ No newline at end of file +} // namespace testing::internal + +namespace ocpp::v201 { + +bool operator==(const ChargingProfile& a, const ChargingProfile& b) { + return a.chargingProfileKind == b.chargingProfileKind && a.chargingProfilePurpose == b.chargingProfilePurpose && + a.id == b.id && a.stackLevel == b.stackLevel; +} +} // namespace ocpp::v201 \ No newline at end of file diff --git a/tests/lib/ocpp/v201/comparators.hpp b/tests/lib/ocpp/v201/comparators.hpp index 504ec9fe1..50ca639c8 100644 --- a/tests/lib/ocpp/v201/comparators.hpp +++ b/tests/lib/ocpp/v201/comparators.hpp @@ -15,4 +15,10 @@ bool operator==(const ::ocpp::v201::GetCertificateStatusRequest& a, const ::ocpp } // namespace testing::internal +namespace ocpp::v201 { + +bool operator==(const ChargingProfile& a, const ChargingProfile& b); + +} // namespace ocpp::v201 + #endif // TESTS_OCPP_COMPARATORS_H diff --git a/tests/lib/ocpp/v201/test_charge_point.cpp b/tests/lib/ocpp/v201/test_charge_point.cpp index a02fecddb..1ae57a57d 100644 --- a/tests/lib/ocpp/v201/test_charge_point.cpp +++ b/tests/lib/ocpp/v201/test_charge_point.cpp @@ -1,3 +1,4 @@ +#include "comparators.hpp" #include "everest/logging.hpp" #include "evse_security_mock.hpp" #include "lib/ocpp/common/database_testing_utils.hpp" @@ -7,7 +8,6 @@ #include "ocpp/v201/device_model_storage_sqlite.hpp" #include "ocpp/v201/init_device_model_db.hpp" #include "ocpp/v201/messages/SetChargingProfile.hpp" -#include "ocpp/v201/profile_utils.hpp" #include "ocpp/v201/smart_charging.hpp" #include "ocpp/v201/types.hpp" #include "smart_charging_handler_mock.hpp" diff --git a/tests/lib/ocpp/v201/test_database_handler.cpp b/tests/lib/ocpp/v201/test_database_handler.cpp index 8dd4f654f..340dd42b1 100644 --- a/tests/lib/ocpp/v201/test_database_handler.cpp +++ b/tests/lib/ocpp/v201/test_database_handler.cpp @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2020 - 2024 Pionix GmbH and Contributors to EVerest +#include "comparators.hpp" #include "database_testing_utils.hpp" #include "ocpp/v201/enums.hpp" -#include "ocpp/v201/profile_utils.hpp" #include #include #include diff --git a/tests/lib/ocpp/v201/test_smart_charging_handler.cpp b/tests/lib/ocpp/v201/test_smart_charging_handler.cpp index 7197f560d..3cbf7cb57 100644 --- a/tests/lib/ocpp/v201/test_smart_charging_handler.cpp +++ b/tests/lib/ocpp/v201/test_smart_charging_handler.cpp @@ -28,7 +28,7 @@ #include #include -#include "ocpp/v201/profile_utils.hpp" +#include "comparators.hpp" #include #include From de40df06174bd3d140a5c2c6f6c9fe130f8b2b6b Mon Sep 17 00:00:00 2001 From: Coury Richards <146002925+couryrr-afs@users.noreply.github.com> Date: Thu, 8 Aug 2024 12:56:02 +0000 Subject: [PATCH 18/18] removed duplicate include Signed-off-by: Coury Richards <146002925+couryrr-afs@users.noreply.github.com> --- include/ocpp/v201/charge_point.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index b6c4f76b6..b564d00c9 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -67,7 +67,6 @@ #include #include "component_state_manager.hpp" -#include "ocpp/v201/smart_charging.hpp" namespace ocpp { namespace v201 {