From 7f6f95148bfa334c3f585787f2503bef548dc724 Mon Sep 17 00:00:00 2001 From: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> Date: Thu, 22 Aug 2024 19:54:16 +0000 Subject: [PATCH 01/15] smart_charging: Store ChargingLimitSource When calling `validate_and_add_profile()`, `add_profile()`, and `insert_or_update_charging_profile()`, we now can be given a source for the profile. This source is stored in our database and can be retrieved with the new method, `get_charging_limit_source_for_profile()`. We use this new method to retrieve the source when retrieving profiles for K09. All new functionality is under test. Co-authored-by: Coury Richards <146002925+couryrr-afs@users.noreply.github.com> Signed-off-by: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> --- .../6_down-charging_limit_source_column.sql | 1 + .../6_up-charging_limit_source_column.sql | 1 + include/ocpp/v201/database_handler.hpp | 6 +- include/ocpp/v201/smart_charging.hpp | 10 +++- lib/ocpp/v201/charge_point.cpp | 3 +- lib/ocpp/v201/database_handler.cpp | 30 ++++++++-- lib/ocpp/v201/smart_charging.cpp | 13 +++-- .../mocks/smart_charging_handler_mock.hpp | 7 ++- tests/lib/ocpp/v201/test_charge_point.cpp | 3 +- tests/lib/ocpp/v201/test_database_handler.cpp | 22 +++++++ .../ocpp/v201/test_smart_charging_handler.cpp | 58 ++++++++++++++++++- 11 files changed, 136 insertions(+), 18 deletions(-) create mode 100644 config/v201/core_migrations/6_down-charging_limit_source_column.sql create mode 100644 config/v201/core_migrations/6_up-charging_limit_source_column.sql diff --git a/config/v201/core_migrations/6_down-charging_limit_source_column.sql b/config/v201/core_migrations/6_down-charging_limit_source_column.sql new file mode 100644 index 000000000..d11bd83bd --- /dev/null +++ b/config/v201/core_migrations/6_down-charging_limit_source_column.sql @@ -0,0 +1 @@ +ALTER TABLE CHARGING_PROFILES DROP COLUMN CHARGING_LIMIT_SOURCE; \ No newline at end of file diff --git a/config/v201/core_migrations/6_up-charging_limit_source_column.sql b/config/v201/core_migrations/6_up-charging_limit_source_column.sql new file mode 100644 index 000000000..36f3cf3d0 --- /dev/null +++ b/config/v201/core_migrations/6_up-charging_limit_source_column.sql @@ -0,0 +1 @@ +ALTER TABLE CHARGING_PROFILES ADD COLUMN CHARGING_LIMIT_SOURCE TEXT NOT NULL DEFAULT 'CSO'; \ No newline at end of file diff --git a/include/ocpp/v201/database_handler.hpp b/include/ocpp/v201/database_handler.hpp index 57d5867cf..139bcec62 100644 --- a/include/ocpp/v201/database_handler.hpp +++ b/include/ocpp/v201/database_handler.hpp @@ -178,7 +178,9 @@ class DatabaseHandler : public common::DatabaseHandlerCommon { /// 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); + void insert_or_update_charging_profile( + const int evse_id, const v201::ChargingProfile& profile, + const ChargingLimitSourceEnum charging_limit_source = ChargingLimitSourceEnum::CSO); /// \brief Deletes the profile with the given \p profile_id void delete_charging_profile(const int profile_id); @@ -188,6 +190,8 @@ class DatabaseHandler : public common::DatabaseHandlerCommon { /// \brief Retrieves all ChargingProfiles virtual std::map> get_all_charging_profiles_group_by_evse(); + + ChargingLimitSourceEnum get_charging_limit_source_for_profile(const int profile_id); }; } // namespace v201 diff --git a/include/ocpp/v201/smart_charging.hpp b/include/ocpp/v201/smart_charging.hpp index de55dec23..93fe8c1c6 100644 --- a/include/ocpp/v201/smart_charging.hpp +++ b/include/ocpp/v201/smart_charging.hpp @@ -86,6 +86,7 @@ class SmartChargingHandlerInterface { virtual SetChargingProfileResponse validate_and_add_profile( ChargingProfile& profile, int32_t evse_id, + ChargingLimitSourceEnum charging_limit_source = ChargingLimitSourceEnum::CSO, AddChargingProfileSource source_of_request = AddChargingProfileSource::SetChargingProfile) = 0; virtual void delete_transaction_tx_profiles(const std::string& transaction_id) = 0; @@ -94,7 +95,9 @@ class SmartChargingHandlerInterface { validate_profile(ChargingProfile& profile, int32_t evse_id, AddChargingProfileSource source_of_request = AddChargingProfileSource::SetChargingProfile) = 0; - virtual SetChargingProfileResponse add_profile(ChargingProfile& profile, int32_t evse_id) = 0; + virtual SetChargingProfileResponse + add_profile(ChargingProfile& profile, int32_t evse_id, + ChargingLimitSourceEnum charging_limit_source = ChargingLimitSourceEnum::CSO) = 0; virtual ClearChargingProfileResponse clear_profiles(const ClearChargingProfileRequest& request) = 0; @@ -134,6 +137,7 @@ class SmartChargingHandler : public SmartChargingHandlerInterface { /// SetChargingProfileResponse validate_and_add_profile( ChargingProfile& profile, int32_t evse_id, + ChargingLimitSourceEnum charging_limit_source = ChargingLimitSourceEnum::CSO, AddChargingProfileSource source_of_request = AddChargingProfileSource::SetChargingProfile) override; /// @@ -148,7 +152,9 @@ class SmartChargingHandler : public SmartChargingHandlerInterface { /// /// \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) override; + SetChargingProfileResponse + add_profile(ChargingProfile& profile, int32_t evse_id, + ChargingLimitSourceEnum charging_limit_source = ChargingLimitSourceEnum::CSO) override; /// /// \brief Retrieves existing profiles on system. diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index b8a2f66ee..cf82fbf5f 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -3144,7 +3144,8 @@ void ChargePoint::handle_remote_start_transaction_request(Callsmart_charging_handler->validate_and_add_profile( - msg.chargingProfile.value(), evse_id, AddChargingProfileSource::RequestStartTransactionRequest); + msg.chargingProfile.value(), evse_id, ChargingLimitSourceEnum::CSO, + AddChargingProfileSource::RequestStartTransactionRequest); if (add_profile_response.status == ChargingProfileStatusEnum::Accepted) { EVLOG_debug << "Accepting SetChargingProfileRequest"; } else { diff --git a/lib/ocpp/v201/database_handler.cpp b/lib/ocpp/v201/database_handler.cpp index 380d6af44..142f88fb6 100644 --- a/lib/ocpp/v201/database_handler.cpp +++ b/lib/ocpp/v201/database_handler.cpp @@ -721,11 +721,12 @@ void DatabaseHandler::transaction_delete(const std::string& transaction_id) { } } -void DatabaseHandler::insert_or_update_charging_profile(const int evse_id, const v201::ChargingProfile& profile) { +void DatabaseHandler::insert_or_update_charging_profile(const int evse_id, const v201::ChargingProfile& profile, + const ChargingLimitSourceEnum charging_limit_source) { // add or replace - 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)"; + std::string sql = "INSERT OR REPLACE INTO CHARGING_PROFILES (ID, EVSE_ID, STACK_LEVEL, CHARGING_PROFILE_PURPOSE, " + "PROFILE, CHARGING_LIMIT_SOURCE) VALUES " + "(@id, @evse_id, @stack_level, @charging_profile_purpose, @profile, @charging_limit_source)"; auto stmt = this->database->new_statement(sql); json json_profile(profile); @@ -736,6 +737,7 @@ void DatabaseHandler::insert_or_update_charging_profile(const int evse_id, const stmt->bind_text("@charging_profile_purpose", conversions::charging_profile_purpose_enum_to_string(profile.chargingProfilePurpose)); stmt->bind_text("@profile", json_profile.dump(), SQLiteString::Transient); + stmt->bind_text("@charging_limit_source", conversions::charging_limit_source_enum_to_string(charging_limit_source)); if (stmt->step() != SQLITE_DONE) { throw QueryExecutionException(this->database->get_error_message()); @@ -776,5 +778,25 @@ std::map> DatabaseHandler::get_all_c return map; } +ChargingLimitSourceEnum DatabaseHandler::get_charging_limit_source_for_profile(const int profile_id) { + std::string sql = "SELECT CHARGING_LIMIT_SOURCE FROM CHARGING_PROFILES WHERE ID = @profile_id;"; + + auto stmnt = this->database->new_statement(sql); + + stmnt->bind_int("@profile_id", profile_id); + + if (stmnt->step() != SQLITE_ROW) { + EVLOG_warning << "No record found for " << profile_id; + } + + auto res = conversions::string_to_charging_limit_source_enum(stmnt->column_text(0)); + + if (stmnt->step() != SQLITE_DONE) { + throw QueryExecutionException(this->database->get_error_message()); + } + + return res; +} + } // namespace v201 } // namespace ocpp diff --git a/lib/ocpp/v201/smart_charging.cpp b/lib/ocpp/v201/smart_charging.cpp index 17fcadab8..1a619a0a4 100644 --- a/lib/ocpp/v201/smart_charging.cpp +++ b/lib/ocpp/v201/smart_charging.cpp @@ -234,13 +234,14 @@ void SmartChargingHandler::delete_transaction_tx_profiles(const std::string& tra } SetChargingProfileResponse SmartChargingHandler::validate_and_add_profile(ChargingProfile& profile, int32_t evse_id, + ChargingLimitSourceEnum charging_limit_source, AddChargingProfileSource source_of_request) { SetChargingProfileResponse response; response.status = ChargingProfileStatusEnum::Rejected; auto result = this->validate_profile(profile, evse_id, source_of_request); if (result == ProfileValidationResultEnum::Valid) { - response = this->add_profile(profile, evse_id); + response = this->add_profile(profile, evse_id, charging_limit_source); } else { response.statusInfo = StatusInfo(); response.statusInfo->reasonCode = conversions::profile_validation_result_to_reason_code(result); @@ -497,14 +498,15 @@ SmartChargingHandler::validate_request_start_transaction_profile(const ChargingP return ProfileValidationResultEnum::Valid; } -SetChargingProfileResponse SmartChargingHandler::add_profile(ChargingProfile& profile, int32_t evse_id) { +SetChargingProfileResponse SmartChargingHandler::add_profile(ChargingProfile& profile, int32_t evse_id, + ChargingLimitSourceEnum charging_limit_source) { SetChargingProfileResponse response; response.status = ChargingProfileStatusEnum::Accepted; // 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); + this->database_handler->insert_or_update_charging_profile(evse_id, profile, charging_limit_source); auto found_profile = false; for (auto& [existing_evse_id, evse_profiles] : charging_profiles) { @@ -575,9 +577,8 @@ SmartChargingHandler::get_reported_profiles(const GetChargingProfilesRequest& re for (auto& [existing_evse_id, evse_profiles] : charging_profiles) { for (auto& profile : evse_profiles) { if (profile_matches_get_criteria(profile, request.chargingProfile, request.evseId, existing_evse_id)) { - profiles.push_back( - ReportedChargingProfile(profile, existing_evse_id, - ChargingLimitSourceEnum::CSO)); // TODO: Add correct source when available + auto source = this->database_handler->get_charging_limit_source_for_profile(profile.id); + profiles.push_back(ReportedChargingProfile(profile, existing_evse_id, source)); } } } 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 d77abcf6e..544d40777 100644 --- a/tests/lib/ocpp/v201/mocks/smart_charging_handler_mock.hpp +++ b/tests/lib/ocpp/v201/mocks/smart_charging_handler_mock.hpp @@ -6,17 +6,20 @@ #include #include "ocpp/v201/messages/SetChargingProfile.hpp" +#include "ocpp/v201/ocpp_enums.hpp" #include "ocpp/v201/smart_charging.hpp" namespace ocpp::v201 { class SmartChargingHandlerMock : public SmartChargingHandlerInterface { public: MOCK_METHOD(SetChargingProfileResponse, validate_and_add_profile, - (ChargingProfile & profile, int32_t evse_id, AddChargingProfileSource source_of_request)); + (ChargingProfile & profile, int32_t evse_id, ChargingLimitSourceEnum charging_limit_source, + AddChargingProfileSource source_of_request)); MOCK_METHOD(ProfileValidationResultEnum, validate_profile, (ChargingProfile & profile, int32_t evse_id, AddChargingProfileSource source_of_request)); MOCK_METHOD(void, delete_transaction_tx_profiles, (const std::string& transaction_id)); - MOCK_METHOD(SetChargingProfileResponse, add_profile, (ChargingProfile & profile, int32_t evse_id)); + MOCK_METHOD(SetChargingProfileResponse, add_profile, + (ChargingProfile & profile, int32_t evse_id, ChargingLimitSourceEnum charging_limit_source)); MOCK_METHOD(ClearChargingProfileResponse, clear_profiles, (const ClearChargingProfileRequest& request), (override)); MOCK_METHOD(std::vector, get_reported_profiles, (const GetChargingProfilesRequest& request), (const, override)); diff --git a/tests/lib/ocpp/v201/test_charge_point.cpp b/tests/lib/ocpp/v201/test_charge_point.cpp index 4d3f2098c..fc1e55019 100644 --- a/tests/lib/ocpp/v201/test_charge_point.cpp +++ b/tests/lib/ocpp/v201/test_charge_point.cpp @@ -675,7 +675,8 @@ TEST_F(ChargepointTestFixtureV201, K01_SetChargingProfileRequest_ValidatesAndAdd request_to_enhanced_message(req); EXPECT_CALL(*smart_charging_handler, - validate_and_add_profile(profile, DEFAULT_EVSE_ID, DEFAULT_REQUEST_TO_ADD_PROFILE_SOURCE)); + validate_and_add_profile(profile, DEFAULT_EVSE_ID, ChargingLimitSourceEnum::CSO, + DEFAULT_REQUEST_TO_ADD_PROFILE_SOURCE)); charge_point->handle_message(set_charging_profile_req); } diff --git a/tests/lib/ocpp/v201/test_database_handler.cpp b/tests/lib/ocpp/v201/test_database_handler.cpp index 340dd42b1..b95250a46 100644 --- a/tests/lib/ocpp/v201/test_database_handler.cpp +++ b/tests/lib/ocpp/v201/test_database_handler.cpp @@ -364,3 +364,25 @@ TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithMultipleProfileDiffEvse_LoadsCh EXPECT_EQ(profiles3[0], p5); EXPECT_EQ(profiles3[1], p6); } + +TEST_F(DatabaseHandlerTest, GetChargingLimitSourceForProfile_RetrievesDefaultSourceForProfile) { + const auto profile_id = 1; + auto p1 = ChargingProfile{ + .id = profile_id, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(1, p1); + + auto sut = this->database_handler.get_charging_limit_source_for_profile(profile_id); + EXPECT_EQ(sut, ChargingLimitSourceEnum::CSO); +} + +TEST_F(DatabaseHandlerTest, GetChargingLimitSourceForProfile_RetrievsSetSourceForProfile) { + const ChargingLimitSourceEnum custom_charging_limit_source = ChargingLimitSourceEnum::EMS; + + const auto profile_id = 1; + auto p1 = ChargingProfile{ + .id = profile_id, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(1, p1, custom_charging_limit_source); + + auto sut = this->database_handler.get_charging_limit_source_for_profile(profile_id); + EXPECT_EQ(sut, ChargingLimitSourceEnum::EMS); +} \ No newline at end of file diff --git a/tests/lib/ocpp/v201/test_smart_charging_handler.cpp b/tests/lib/ocpp/v201/test_smart_charging_handler.cpp index 0699b2daa..493e3e16d 100644 --- a/tests/lib/ocpp/v201/test_smart_charging_handler.cpp +++ b/tests/lib/ocpp/v201/test_smart_charging_handler.cpp @@ -225,7 +225,7 @@ class SmartChargingHandlerTestFixtureV201 : public DatabaseTestingUtils { TestSmartChargingHandler create_smart_charging_handler() { std::unique_ptr database_connection = std::make_unique(fs::path("/tmp/ocpp201") / "cp.db"); - std::shared_ptr database_handler = + 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); @@ -263,6 +263,7 @@ class SmartChargingHandlerTestFixtureV201 : public DatabaseTestingUtils { std::unique_ptr evse_manager = std::make_unique(NR_OF_EVSES); sqlite3* db_handle; + std::shared_ptr database_handler; bool ignore_no_transaction = true; std::shared_ptr device_model = create_device_model(); @@ -1232,6 +1233,39 @@ TEST_F(SmartChargingHandlerTestFixtureV201, K04FR01_AddProfile_OnlyAddsToOneEVSE EXPECT_THAT(sut2, testing::Not(testing::Contains(profile1))); } +TEST_F(SmartChargingHandlerTestFixtureV201, AddProfile_StoresChargingLimitSource) { + auto charging_limit_source = ChargingLimitSourceEnum::SO; + + 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); + + auto response = handler.add_profile(profile, DEFAULT_EVSE_ID, charging_limit_source); + EXPECT_THAT(response.status, testing::Eq(ChargingProfileStatusEnum::Accepted)); + + auto sut = database_handler->get_charging_limit_source_for_profile(DEFAULT_PROFILE_ID); + EXPECT_THAT(sut, ChargingLimitSourceEnum::SO); +} + +TEST_F(SmartChargingHandlerTestFixtureV201, ValidateAndAddProfile_StoresChargingLimitSource) { + auto charging_limit_source = ChargingLimitSourceEnum::SO; + + 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 response = handler.validate_and_add_profile(profile, DEFAULT_EVSE_ID, charging_limit_source); + EXPECT_THAT(response.status, testing::Eq(ChargingProfileStatusEnum::Accepted)); + + auto sut = database_handler->get_charging_limit_source_for_profile(DEFAULT_PROFILE_ID); + EXPECT_THAT(sut, ChargingLimitSourceEnum::SO); +} + TEST_F(SmartChargingHandlerTestFixtureV201, K01_ValidateAndAdd_RejectsInvalidProfilesWithReasonCode) { auto periods = create_charging_schedule_periods(0); auto profile = create_charging_profile( @@ -1432,6 +1466,28 @@ TEST_F(SmartChargingHandlerTestFixtureV201, K09_GetChargingProfiles_EvseIdAndPur EXPECT_THAT(profile2, testing::Eq(reported_profiles.at(0).profile)); } +TEST_F(SmartChargingHandlerTestFixtureV201, K09_GetChargingProfiles_ReportsProfileWithSource) { + auto charging_limit_source = ChargingLimitSourceEnum::SO; + + auto periods = create_charging_schedule_periods({0, 1, 2}); + + auto profile = create_charging_profile( + DEFAULT_PROFILE_ID, ChargingProfilePurposeEnum::TxDefaultProfile, + create_charge_schedule(ChargingRateUnitEnum::A, periods, ocpp::DateTime("2024-01-17T17:00:00"))); + + auto response = handler.validate_and_add_profile(profile, DEFAULT_EVSE_ID, charging_limit_source); + EXPECT_THAT(response.status, testing::Eq(ChargingProfileStatusEnum::Accepted)); + + std::vector requested_profile_ids{1}; + auto reported_profiles = handler.get_reported_profiles(create_get_charging_profile_request( + DEFAULT_REQUEST_ID, create_charging_profile_criteria(std::nullopt, requested_profile_ids))); + EXPECT_THAT(reported_profiles, testing::SizeIs(1)); + + auto reported_profile = reported_profiles.at(0); + EXPECT_THAT(profile, testing::Eq(reported_profile.profile)); + EXPECT_THAT(reported_profile.source, ChargingLimitSourceEnum::SO); +} + TEST_F(SmartChargingHandlerTestFixtureV201, K10_ClearChargingProfile_ClearsId) { auto periods = create_charging_schedule_periods({0, 1, 2}); From b097289f05674d8611ac7e2c7872ae6e2bd61a33 Mon Sep 17 00:00:00 2001 From: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> Date: Tue, 27 Aug 2024 16:27:44 +0000 Subject: [PATCH 02/15] database_handler: Add function to get profiles by EVSE ID Signed-off-by: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> --- include/ocpp/v201/database_handler.hpp | 3 ++ lib/ocpp/v201/database_handler.cpp | 17 ++++++++++ tests/lib/ocpp/v201/test_database_handler.cpp | 34 +++++++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/include/ocpp/v201/database_handler.hpp b/include/ocpp/v201/database_handler.hpp index 139bcec62..1b5954287 100644 --- a/include/ocpp/v201/database_handler.hpp +++ b/include/ocpp/v201/database_handler.hpp @@ -188,6 +188,9 @@ class DatabaseHandler : public common::DatabaseHandlerCommon { /// \brief Deletes all profiles from table CHARGING_PROFILES void clear_charging_profiles(); + /// \brief Retrieves the charging profiles stored on \p evse_id + std::vector get_charging_profiles_for_evse(const int evse_id); + /// \brief Retrieves all ChargingProfiles virtual std::map> get_all_charging_profiles_group_by_evse(); diff --git a/lib/ocpp/v201/database_handler.cpp b/lib/ocpp/v201/database_handler.cpp index 142f88fb6..744a93088 100644 --- a/lib/ocpp/v201/database_handler.cpp +++ b/lib/ocpp/v201/database_handler.cpp @@ -758,6 +758,23 @@ void DatabaseHandler::clear_charging_profiles() { this->database->clear_table("CHARGING_PROFILES"); } +std::vector DatabaseHandler::get_charging_profiles_for_evse(const int evse_id) { + std::vector profiles; + + std::string sql = "SELECT PROFILE FROM CHARGING_PROFILES WHERE EVSE_ID = @evse_id"; + + auto stmt = this->database->new_statement(sql); + + stmt->bind_int("@evse_id", evse_id); + + while (stmt->step() != SQLITE_DONE) { + auto profile = json::parse(stmt->column_text(0)); + profiles.push_back(profile); + } + + return profiles; +} + std::map> DatabaseHandler::get_all_charging_profiles_group_by_evse() { std::map> map; diff --git a/tests/lib/ocpp/v201/test_database_handler.cpp b/tests/lib/ocpp/v201/test_database_handler.cpp index b95250a46..8e46984c5 100644 --- a/tests/lib/ocpp/v201/test_database_handler.cpp +++ b/tests/lib/ocpp/v201/test_database_handler.cpp @@ -11,6 +11,8 @@ using namespace ocpp; using namespace ocpp::v201; +const int DEFAULT_EVSE_ID = 1; + class DatabaseHandlerTest : public DatabaseTestingUtils { public: DatabaseHandler database_handler{std::make_unique("file::memory:?cache=shared"), @@ -365,6 +367,38 @@ TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithMultipleProfileDiffEvse_LoadsCh EXPECT_EQ(profiles3[1], p6); } +TEST_F(DatabaseHandlerTest, GetChargingProfilesForEvse_GetsProfilesForEVSE) { + auto profile1 = ChargingProfile{ + .id = 1, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + auto profile2 = + ChargingProfile{.id = 2, .stackLevel = 2, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxProfile}; + + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile1); + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile2); + + auto profiles = this->database_handler.get_charging_profiles_for_evse(DEFAULT_EVSE_ID); + + EXPECT_EQ(profiles.size(), 2); + EXPECT_THAT(profiles, testing::Contains(profile1)); + EXPECT_THAT(profiles, testing::Contains(profile2)); +} + +TEST_F(DatabaseHandlerTest, GetChargingProfilesForEvse_DoesNotGetProfilesOnOtherEVSE) { + auto profile1 = ChargingProfile{ + .id = 1, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + auto profile2 = + ChargingProfile{.id = 2, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxProfile}; + + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile1); + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID + 1, profile2); + + auto profiles = this->database_handler.get_charging_profiles_for_evse(DEFAULT_EVSE_ID); + + EXPECT_EQ(profiles.size(), 1); + EXPECT_THAT(profiles, testing::Contains(profile1)); + EXPECT_THAT(profiles, testing::Not(testing::Contains(profile2))); +} + TEST_F(DatabaseHandlerTest, GetChargingLimitSourceForProfile_RetrievesDefaultSourceForProfile) { const auto profile_id = 1; auto p1 = ChargingProfile{ From b6858500d6b9878d0b0ec55af3fb97fce3439529 Mon Sep 17 00:00:00 2001 From: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> Date: Tue, 27 Aug 2024 16:30:09 +0000 Subject: [PATCH 03/15] smart_charging: Use new database handler func Signed-off-by: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> --- include/ocpp/v201/smart_charging.hpp | 1 - lib/ocpp/v201/smart_charging.cpp | 44 +++++++------------ .../ocpp/v201/test_smart_charging_handler.cpp | 2 + 3 files changed, 17 insertions(+), 30 deletions(-) diff --git a/include/ocpp/v201/smart_charging.hpp b/include/ocpp/v201/smart_charging.hpp index 93fe8c1c6..8ec52f4be 100644 --- a/include/ocpp/v201/smart_charging.hpp +++ b/include/ocpp/v201/smart_charging.hpp @@ -237,7 +237,6 @@ class SmartChargingHandler : public SmartChargingHandlerInterface { std::vector get_profiles_on_evse(int32_t evse_id) const; private: - std::vector get_station_wide_profiles() const; std::vector get_evse_specific_tx_default_profiles() const; std::vector get_station_wide_tx_default_profiles() const; std::vector get_valid_profiles_for_evse(int32_t evse_id); diff --git a/lib/ocpp/v201/smart_charging.cpp b/lib/ocpp/v201/smart_charging.cpp index 1a619a0a4..61ce9a87d 100644 --- a/lib/ocpp/v201/smart_charging.cpp +++ b/lib/ocpp/v201/smart_charging.cpp @@ -534,10 +534,6 @@ SetChargingProfileResponse SmartChargingHandler::add_profile(ChargingProfile& pr return response; } -std::vector SmartChargingHandler::get_station_wide_profiles() const { - return this->get_profiles_on_evse(STATION_WIDE_ID); -} - std::vector SmartChargingHandler::get_profiles() const { std::vector all_profiles; for (auto evse_profile_pair : charging_profiles) { @@ -596,12 +592,10 @@ std::vector SmartChargingHandler::get_profiles_on_evse(int32_t std::vector SmartChargingHandler::get_valid_profiles_for_evse(int32_t evse_id) { std::vector valid_profiles; - if (charging_profiles.count(evse_id) > 0) { - auto& evse_profiles = this->charging_profiles.at(evse_id); - for (auto profile : evse_profiles) { - if (this->validate_profile(profile, evse_id) == ProfileValidationResultEnum::Valid) { - valid_profiles.push_back(profile); - } + auto evse_profiles = this->database_handler->get_charging_profiles_for_evse(evse_id); + for (auto profile : evse_profiles) { + if (this->validate_profile(profile, evse_id) == ProfileValidationResultEnum::Valid) { + valid_profiles.push_back(profile); } } @@ -637,7 +631,7 @@ std::vector SmartChargingHandler::get_evse_specific_tx_default_ std::vector SmartChargingHandler::get_station_wide_tx_default_profiles() const { std::vector station_wide_tx_default_profiles; - for (auto profile : this->get_station_wide_profiles()) { + for (auto profile : this->database_handler->get_charging_profiles_for_evse(STATION_WIDE_ID)) { if (profile.chargingProfilePurpose == ChargingProfilePurposeEnum::TxDefaultProfile) { station_wide_tx_default_profiles.push_back(profile); } @@ -654,26 +648,18 @@ bool SmartChargingHandler::is_overlapping_validity_period(const ChargingProfile& return false; } - auto conflicts_with = [candidate_evse_id, &candidate_profile]( - const std::pair>& existing_profiles) { - auto existing_evse_id = existing_profiles.first; - if (existing_evse_id == candidate_evse_id) { - return std::any_of(existing_profiles.second.begin(), existing_profiles.second.end(), - [&candidate_profile](const ChargingProfile& existing_profile) { - if (existing_profile.stackLevel == candidate_profile.stackLevel && - existing_profile.chargingProfileKind == candidate_profile.chargingProfileKind && - existing_profile.id != candidate_profile.id) { - - return candidate_profile.validFrom <= existing_profile.validTo && - candidate_profile.validTo >= existing_profile.validFrom; // reject - } - return false; - }); + const std::vector profiles = + this->database_handler->get_charging_profiles_for_evse(candidate_evse_id); + return std::any_of(profiles.begin(), profiles.end(), [&candidate_profile](const ChargingProfile& existing_profile) { + if (existing_profile.stackLevel == candidate_profile.stackLevel && + existing_profile.chargingProfileKind == candidate_profile.chargingProfileKind && + existing_profile.id != candidate_profile.id) { + + return candidate_profile.validFrom <= existing_profile.validTo && + candidate_profile.validTo >= existing_profile.validFrom; // reject } return false; - }; - - return std::any_of(charging_profiles.begin(), charging_profiles.end(), conflicts_with); + }); } void SmartChargingHandler::conform_validity_periods(ChargingProfile& profile) const { diff --git a/tests/lib/ocpp/v201/test_smart_charging_handler.cpp b/tests/lib/ocpp/v201/test_smart_charging_handler.cpp index 493e3e16d..a451628ef 100644 --- a/tests/lib/ocpp/v201/test_smart_charging_handler.cpp +++ b/tests/lib/ocpp/v201/test_smart_charging_handler.cpp @@ -64,6 +64,8 @@ class SmartChargingHandlerTestFixtureV201 : public DatabaseTestingUtils { } void TearDown() override { + // TODO: use in-memory db so we don't need to reset the db between tests + this->database_handler->clear_charging_profiles(); } ChargingSchedule create_charge_schedule(ChargingRateUnitEnum charging_rate_unit) { From 8574a9c8f3bfb07cb239fa93f84b02f271901ae4 Mon Sep 17 00:00:00 2001 From: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> Date: Tue, 27 Aug 2024 18:42:09 +0000 Subject: [PATCH 04/15] database_handler: Add function to get all profiles in one vector Signed-off-by: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> --- include/ocpp/v201/database_handler.hpp | 3 +++ lib/ocpp/v201/database_handler.cpp | 15 +++++++++++++++ tests/lib/ocpp/v201/test_database_handler.cpp | 16 ++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/include/ocpp/v201/database_handler.hpp b/include/ocpp/v201/database_handler.hpp index 1b5954287..92b88b93f 100644 --- a/include/ocpp/v201/database_handler.hpp +++ b/include/ocpp/v201/database_handler.hpp @@ -192,6 +192,9 @@ class DatabaseHandler : public common::DatabaseHandlerCommon { std::vector get_charging_profiles_for_evse(const int evse_id); /// \brief Retrieves all ChargingProfiles + std::vector get_all_charging_profiles(); + + /// \brief Retrieves all ChargingProfiles grouped by EVSE ID virtual std::map> get_all_charging_profiles_group_by_evse(); ChargingLimitSourceEnum get_charging_limit_source_for_profile(const int profile_id); diff --git a/lib/ocpp/v201/database_handler.cpp b/lib/ocpp/v201/database_handler.cpp index 744a93088..a18cdd148 100644 --- a/lib/ocpp/v201/database_handler.cpp +++ b/lib/ocpp/v201/database_handler.cpp @@ -775,6 +775,21 @@ std::vector DatabaseHandler::get_charging_profiles_for_ev return profiles; } +std::vector DatabaseHandler::get_all_charging_profiles() { + std::vector profiles; + + std::string sql = "SELECT PROFILE FROM CHARGING_PROFILES"; + + auto stmt = this->database->new_statement(sql); + + while (stmt->step() != SQLITE_DONE) { + auto profile = json::parse(stmt->column_text(0)); + profiles.push_back(profile); + } + + return profiles; +} + std::map> DatabaseHandler::get_all_charging_profiles_group_by_evse() { std::map> map; diff --git a/tests/lib/ocpp/v201/test_database_handler.cpp b/tests/lib/ocpp/v201/test_database_handler.cpp index 8e46984c5..d55ba315d 100644 --- a/tests/lib/ocpp/v201/test_database_handler.cpp +++ b/tests/lib/ocpp/v201/test_database_handler.cpp @@ -367,6 +367,22 @@ TEST_F(DatabaseHandlerTest, KO1_FR27_DatabaseWithMultipleProfileDiffEvse_LoadsCh EXPECT_EQ(profiles3[1], p6); } +TEST_F(DatabaseHandlerTest, GetAllChargingProfiles_GetsAllProfiles) { + auto profile1 = ChargingProfile{ + .id = 1, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + auto profile2 = + ChargingProfile{.id = 2, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxProfile}; + + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile1); + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID + 1, profile2); + + auto profiles = this->database_handler.get_all_charging_profiles(); + + EXPECT_EQ(profiles.size(), 2); + EXPECT_THAT(profiles, testing::Contains(profile1)); + EXPECT_THAT(profiles, testing::Contains(profile2)); +} + TEST_F(DatabaseHandlerTest, GetChargingProfilesForEvse_GetsProfilesForEVSE) { auto profile1 = ChargingProfile{ .id = 1, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; From 4ec52b400f9a4ee8eca6d238029557a1538e1883 Mon Sep 17 00:00:00 2001 From: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> Date: Tue, 27 Aug 2024 18:42:57 +0000 Subject: [PATCH 05/15] smart_charging: Only store and load profiles from db Remove the map of profiles from the smart charging handler and use the database for storing, loading, and finding profiles. We've removed the function for getting all profiles from the smart charging handler, and in all cases where that was used, including tests, we retrieve the profiles from the database. Signed-off-by: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> --- include/ocpp/v201/smart_charging.hpp | 12 ---- lib/ocpp/v201/smart_charging.cpp | 66 +++++-------------- .../ocpp/v201/test_smart_charging_handler.cpp | 61 ++++++++--------- 3 files changed, 48 insertions(+), 91 deletions(-) diff --git a/include/ocpp/v201/smart_charging.hpp b/include/ocpp/v201/smart_charging.hpp index 8ec52f4be..f0baf80ff 100644 --- a/include/ocpp/v201/smart_charging.hpp +++ b/include/ocpp/v201/smart_charging.hpp @@ -119,8 +119,6 @@ class SmartChargingHandler : public SmartChargingHandlerInterface { std::shared_ptr& device_model; std::shared_ptr database_handler; - // cppcheck-suppress unusedStructMember - std::map> charging_profiles; public: SmartChargingHandler(EvseManagerInterface& evse_manager, std::shared_ptr& device_model, @@ -156,11 +154,6 @@ class SmartChargingHandler : public SmartChargingHandlerInterface { add_profile(ChargingProfile& profile, int32_t evse_id, ChargingLimitSourceEnum charging_limit_source = ChargingLimitSourceEnum::CSO) override; - /// - /// \brief Retrieves existing profiles on system. - /// - std::vector get_profiles() const; - /// /// \brief Clears profiles from the system using the given \p request /// @@ -231,11 +224,6 @@ class SmartChargingHandler : public SmartChargingHandlerInterface { /// ProfileValidationResultEnum verify_no_conflicting_external_constraints_id(const ChargingProfile& profile) const; - /// - /// \brief Retrieves existing profiles on the EVSE \p evse_id - /// - std::vector get_profiles_on_evse(int32_t evse_id) const; - private: std::vector get_evse_specific_tx_default_profiles() const; std::vector get_station_wide_tx_default_profiles() const; diff --git a/lib/ocpp/v201/smart_charging.cpp b/lib/ocpp/v201/smart_charging.cpp index 61ce9a87d..d94d83c61 100644 --- a/lib/ocpp/v201/smart_charging.cpp +++ b/lib/ocpp/v201/smart_charging.cpp @@ -220,14 +220,10 @@ SmartChargingHandler::SmartChargingHandler(EvseManagerInterface& evse_manager, } void SmartChargingHandler::delete_transaction_tx_profiles(const std::string& transaction_id) { - for (auto& [evse_id, profiles] : charging_profiles) { - auto iter = profiles.begin(); - while (iter != profiles.end()) { - if (transaction_id.compare(iter->transactionId.value()) == 0) { - this->database_handler->delete_charging_profile(iter->id); - iter = profiles.erase(iter); - } else { - ++iter; + for (auto& profile : this->database_handler->get_all_charging_profiles()) { + if (profile.transactionId.has_value()) { + if (transaction_id == profile.transactionId.value().get()) { + this->database_handler->delete_charging_profile(profile.id); } } } @@ -386,12 +382,11 @@ SmartChargingHandler::validate_tx_profile(const ChargingProfile& profile, int32_ return ProfileValidationResultEnum::TxProfileTransactionNotOnEvse; } - auto conflicts_with = [&profile](const std::pair>& candidate) { - return std::any_of(candidate.second.begin(), candidate.second.end(), - [&profile](const ChargingProfile& candidateProfile) { - return candidateProfile.transactionId == profile.transactionId && - candidateProfile.stackLevel == profile.stackLevel; - }); + auto charging_profiles = this->database_handler->get_all_charging_profiles(); + + auto conflicts_with = [&profile](const ChargingProfile& candidateProfile) { + return candidateProfile.transactionId == profile.transactionId && + candidateProfile.stackLevel == profile.stackLevel; }; if (std::any_of(charging_profiles.begin(), charging_profiles.end(), conflicts_with)) { @@ -503,27 +498,10 @@ SetChargingProfileResponse SmartChargingHandler::add_profile(ChargingProfile& pr SetChargingProfileResponse response; response.status = ChargingProfileStatusEnum::Accepted; - // K01.FR05 - replace non-ChargingStationExternalConstraints profiles if id exists. try { + // K01.FR05 - replace non-ChargingStationExternalConstraints profiles if id exists. // K01.FR27 - add profiles to database when valid this->database_handler->insert_or_update_charging_profile(evse_id, profile, charging_limit_source); - - 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); - } catch (const QueryExecutionException& e) { EVLOG_error << "Could not store ChargingProfile in the database: " << e.what(); response.status = ChargingProfileStatusEnum::Rejected; @@ -534,14 +512,6 @@ SetChargingProfileResponse SmartChargingHandler::add_profile(ChargingProfile& pr return response; } -std::vector SmartChargingHandler::get_profiles() const { - std::vector all_profiles; - for (auto evse_profile_pair : charging_profiles) { - all_profiles.insert(all_profiles.end(), evse_profile_pair.second.begin(), evse_profile_pair.second.end()); - } - return all_profiles; -} - ClearChargingProfileResponse SmartChargingHandler::clear_profiles(const ClearChargingProfileRequest& request) { ClearChargingProfileResponse response; @@ -550,6 +520,8 @@ ClearChargingProfileResponse SmartChargingHandler::clear_profiles(const ClearCha response.status = ClearChargingProfileStatusEnum::Unknown; + auto charging_profiles = this->database_handler->get_all_charging_profiles_group_by_evse(); + for (auto& [existing_evse_id, evse_profiles] : charging_profiles) { for (auto it = evse_profiles.begin(); it != evse_profiles.end();) { // K10.FR.04: logical AND between the filters, if not set, the filter does not apply @@ -570,6 +542,8 @@ std::vector SmartChargingHandler::get_reported_profiles(const GetChargingProfilesRequest& request) const { std::vector profiles; + auto charging_profiles = this->database_handler->get_all_charging_profiles_group_by_evse(); + for (auto& [existing_evse_id, evse_profiles] : charging_profiles) { for (auto& profile : evse_profiles) { if (profile_matches_get_criteria(profile, request.chargingProfile, request.evseId, existing_evse_id)) { @@ -581,14 +555,6 @@ SmartChargingHandler::get_reported_profiles(const GetChargingProfilesRequest& re return profiles; } -std::vector SmartChargingHandler::get_profiles_on_evse(int32_t evse_id) const { - std::vector profiles; - if (charging_profiles.count(evse_id)) { - profiles = charging_profiles.at(evse_id); - } - return profiles; -} - std::vector SmartChargingHandler::get_valid_profiles_for_evse(int32_t evse_id) { std::vector valid_profiles; @@ -616,6 +582,8 @@ std::vector SmartChargingHandler::get_valid_profiles(int32_t ev std::vector SmartChargingHandler::get_evse_specific_tx_default_profiles() const { std::vector evse_specific_tx_default_profiles; + auto charging_profiles = this->database_handler->get_all_charging_profiles_group_by_evse(); + for (auto& [evse_id, profiles] : charging_profiles) { if (evse_id != STATION_WIDE_ID) { for (auto profile : profiles) { @@ -670,7 +638,7 @@ void SmartChargingHandler::conform_validity_periods(ChargingProfile& profile) co ProfileValidationResultEnum SmartChargingHandler::verify_no_conflicting_external_constraints_id(const ChargingProfile& profile) const { auto result = ProfileValidationResultEnum::Valid; - for (auto existing_profile : this->get_profiles()) { + for (auto existing_profile : this->database_handler->get_all_charging_profiles()) { if (existing_profile.id == profile.id && existing_profile.chargingProfilePurpose == ChargingProfilePurposeEnum::ChargingStationExternalConstraints) { result = ProfileValidationResultEnum::ExistingChargingStationExternalConstraints; diff --git a/tests/lib/ocpp/v201/test_smart_charging_handler.cpp b/tests/lib/ocpp/v201/test_smart_charging_handler.cpp index a451628ef..c75d97623 100644 --- a/tests/lib/ocpp/v201/test_smart_charging_handler.cpp +++ b/tests/lib/ocpp/v201/test_smart_charging_handler.cpp @@ -2,6 +2,7 @@ // Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest #include "date/tz.h" +#include "everest/logging.hpp" #include "lib/ocpp/common/database_testing_utils.hpp" #include "ocpp/common/types.hpp" #include "ocpp/v201/ctrlr_component_variables.hpp" @@ -47,7 +48,6 @@ const static std::string DEVICE_MODEL_DB_IN_MEMORY_PATH = "file::memory:?cache=s class TestSmartChargingHandler : public SmartChargingHandler { public: - using SmartChargingHandler::get_profiles_on_evse; using SmartChargingHandler::validate_charging_station_max_profile; using SmartChargingHandler::validate_evse_exists; using SmartChargingHandler::validate_profile_schedules; @@ -1048,7 +1048,7 @@ TEST_F(SmartChargingHandlerTestFixtureV201, 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)); + EXPECT_THAT(database_handler->get_all_charging_profiles(), testing::Contains(profile)); } TEST_F(SmartChargingHandlerTestFixtureV201, @@ -1061,7 +1061,7 @@ TEST_F(SmartChargingHandlerTestFixtureV201, 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)); + EXPECT_THAT(database_handler->get_all_charging_profiles(), testing::Contains(profile)); } TEST_F(SmartChargingHandlerTestFixtureV201, @@ -1077,7 +1077,7 @@ TEST_F(SmartChargingHandlerTestFixtureV201, auto sut1 = handler.add_profile(profile1, DEFAULT_EVSE_ID); auto sut2 = handler.add_profile(profile2, DEFAULT_EVSE_ID); - auto profiles = handler.get_profiles(); + auto profiles = database_handler->get_all_charging_profiles(); EXPECT_THAT(sut1.status, testing::Eq(ChargingProfileStatusEnum::Accepted)); EXPECT_THAT(sut2.status, testing::Eq(ChargingProfileStatusEnum::Accepted)); @@ -1097,7 +1097,7 @@ TEST_F(SmartChargingHandlerTestFixtureV201, auto sut1 = handler.add_profile(profile1, STATION_WIDE_ID); auto sut2 = handler.add_profile(profile2, DEFAULT_EVSE_ID); - auto profiles = handler.get_profiles(); + auto profiles = database_handler->get_all_charging_profiles(); EXPECT_THAT(sut1.status, testing::Eq(ChargingProfileStatusEnum::Accepted)); EXPECT_THAT(sut2.status, testing::Eq(ChargingProfileStatusEnum::Accepted)); @@ -1117,7 +1117,7 @@ TEST_F(SmartChargingHandlerTestFixtureV201, auto sut1 = handler.add_profile(profile1, DEFAULT_EVSE_ID); auto sut2 = handler.add_profile(profile2, DEFAULT_EVSE_ID + 1); - auto profiles = handler.get_profiles(); + auto profiles = database_handler->get_all_charging_profiles(); EXPECT_THAT(sut1.status, testing::Eq(ChargingProfileStatusEnum::Accepted)); EXPECT_THAT(sut2.status, testing::Eq(ChargingProfileStatusEnum::Accepted)); @@ -1137,7 +1137,7 @@ TEST_F(SmartChargingHandlerTestFixtureV201, auto sut1 = handler.add_profile(profile1, DEFAULT_EVSE_ID + 1); auto sut2 = handler.add_profile(profile2, STATION_WIDE_ID); - auto profiles = handler.get_profiles(); + auto profiles = database_handler->get_all_charging_profiles(); EXPECT_THAT(sut1.status, testing::Eq(ChargingProfileStatusEnum::Accepted)); EXPECT_THAT(sut2.status, testing::Eq(ChargingProfileStatusEnum::Accepted)); @@ -1157,7 +1157,7 @@ TEST_F(SmartChargingHandlerTestFixtureV201, auto sut1 = handler.add_profile(profile1, STATION_WIDE_ID); auto sut2 = handler.add_profile(profile2, STATION_WIDE_ID); - auto profiles = handler.get_profiles(); + auto profiles = database_handler->get_all_charging_profiles(); EXPECT_THAT(sut1.status, testing::Eq(ChargingProfileStatusEnum::Accepted)); EXPECT_THAT(sut2.status, testing::Eq(ChargingProfileStatusEnum::Accepted)); @@ -1181,7 +1181,7 @@ TEST_F(SmartChargingHandlerTestFixtureV201, auto sut2 = handler.add_profile(profile2, DEFAULT_EVSE_ID); auto sut3 = handler.add_profile(profile3, STATION_WIDE_ID); - auto profiles = handler.get_profiles(); + auto profiles = database_handler->get_all_charging_profiles(); EXPECT_THAT(sut1.status, testing::Eq(ChargingProfileStatusEnum::Accepted)); EXPECT_THAT(sut2.status, testing::Eq(ChargingProfileStatusEnum::Accepted)); @@ -1202,7 +1202,7 @@ TEST_F(SmartChargingHandlerTestFixtureV201, auto sut4 = handler.add_profile(profile4, STATION_WIDE_ID); auto sut5 = handler.add_profile(profile5, DEFAULT_EVSE_ID); - profiles = handler.get_profiles(); + profiles = database_handler->get_all_charging_profiles(); EXPECT_THAT(profiles, testing::Not(testing::Contains(profile2))); EXPECT_THAT(profiles, testing::Not(testing::Contains(profile3))); @@ -1225,8 +1225,8 @@ TEST_F(SmartChargingHandlerTestFixtureV201, K04FR01_AddProfile_OnlyAddsToOneEVSE EXPECT_THAT(response.status, testing::Eq(ChargingProfileStatusEnum::Accepted)); EXPECT_THAT(response2.status, testing::Eq(ChargingProfileStatusEnum::Accepted)); - auto sut1 = handler.get_profiles_on_evse(DEFAULT_EVSE_ID); - auto sut2 = handler.get_profiles_on_evse(DEFAULT_EVSE_ID + 1); + auto sut1 = this->database_handler->get_charging_profiles_for_evse(DEFAULT_EVSE_ID); + auto sut2 = this->database_handler->get_charging_profiles_for_evse(DEFAULT_EVSE_ID + 1); EXPECT_THAT(sut1, testing::Contains(profile1)); EXPECT_THAT(sut1, testing::Not(testing::Contains(profile2))); @@ -1284,7 +1284,7 @@ TEST_F(SmartChargingHandlerTestFixtureV201, K01_ValidateAndAdd_RejectsInvalidPro EXPECT_THAT(status_info->additionalInfo->get(), testing::Eq(conversions::profile_validation_result_to_string( ProfileValidationResultEnum::TxProfileMissingTransactionId))); - auto profiles = handler.get_profiles(); + auto profiles = database_handler->get_all_charging_profiles(); EXPECT_THAT(profiles, testing::Not(testing::Contains(profile))); } @@ -1301,7 +1301,7 @@ TEST_F(SmartChargingHandlerTestFixtureV201, K01_ValidateAndAdd_AddsValidProfiles EXPECT_THAT(sut.status, testing::Eq(ChargingProfileStatusEnum::Accepted)); EXPECT_THAT(sut.statusInfo.has_value(), testing::IsFalse()); - auto profiles = handler.get_profiles(); + auto profiles = database_handler->get_all_charging_profiles(); EXPECT_THAT(profiles, testing::Contains(profile)); } @@ -1318,7 +1318,7 @@ TEST_F(SmartChargingHandlerTestFixtureV201, K09_GetChargingProfiles_EvseId) { auto sut1 = handler.validate_and_add_profile(profile1, STATION_WIDE_ID); auto sut2 = handler.validate_and_add_profile(profile2, DEFAULT_EVSE_ID); - auto profiles = handler.get_profiles(); + auto profiles = database_handler->get_all_charging_profiles(); EXPECT_THAT(profiles, testing::SizeIs(2)); auto reported_profiles = handler.get_reported_profiles( @@ -1349,7 +1349,7 @@ TEST_F(SmartChargingHandlerTestFixtureV201, K09_GetChargingProfiles_NoEvseId) { auto sut1 = handler.validate_and_add_profile(profile1, STATION_WIDE_ID); auto sut2 = handler.validate_and_add_profile(profile2, DEFAULT_EVSE_ID); - auto profiles = handler.get_profiles(); + auto profiles = database_handler->get_all_charging_profiles(); EXPECT_THAT(profiles, testing::SizeIs(2)); auto reported_profiles = handler.get_reported_profiles( @@ -1373,7 +1373,7 @@ TEST_F(SmartChargingHandlerTestFixtureV201, K09_GetChargingProfiles_ProfileId) { auto sut1 = handler.validate_and_add_profile(profile1, STATION_WIDE_ID); auto sut2 = handler.validate_and_add_profile(profile2, DEFAULT_EVSE_ID); - auto profiles = handler.get_profiles(); + auto profiles = database_handler->get_all_charging_profiles(); EXPECT_THAT(profiles, testing::SizeIs(2)); std::vector requested_profile_ids{1}; @@ -1399,7 +1399,7 @@ TEST_F(SmartChargingHandlerTestFixtureV201, K09_GetChargingProfiles_EvseIdAndSta auto sut1 = handler.validate_and_add_profile(profile1, STATION_WIDE_ID); auto sut2 = handler.validate_and_add_profile(profile2, DEFAULT_EVSE_ID); - auto profiles = handler.get_profiles(); + auto profiles = database_handler->get_all_charging_profiles(); EXPECT_THAT(profiles, testing::SizeIs(2)); auto reported_profiles = handler.get_reported_profiles(create_get_charging_profile_request( @@ -1419,7 +1419,7 @@ TEST_F(SmartChargingHandlerTestFixtureV201, K09_GetChargingProfiles_EvseIdAndSou auto sut1 = handler.validate_and_add_profile(profile, DEFAULT_EVSE_ID); - auto profiles = handler.get_profiles(); + auto profiles = database_handler->get_all_charging_profiles(); EXPECT_THAT(profiles, testing::SizeIs(1)); std::vector requested_sources_cso{ChargingLimitSourceEnum::CSO}; @@ -1449,7 +1449,7 @@ TEST_F(SmartChargingHandlerTestFixtureV201, K09_GetChargingProfiles_EvseIdAndPur auto sut1 = handler.validate_and_add_profile(profile1, STATION_WIDE_ID); auto sut2 = handler.validate_and_add_profile(profile2, DEFAULT_EVSE_ID); - auto profiles = handler.get_profiles(); + auto profiles = database_handler->get_all_charging_profiles(); EXPECT_THAT(profiles, testing::SizeIs(2)); auto reported_profiles = handler.get_reported_profiles(create_get_charging_profile_request( @@ -1498,20 +1498,20 @@ TEST_F(SmartChargingHandlerTestFixtureV201, K10_ClearChargingProfile_ClearsId) { create_charge_schedule(ChargingRateUnitEnum::A, periods, ocpp::DateTime("2024-01-17T17:00:00"))); handler.validate_and_add_profile(profile, STATION_WIDE_ID); - auto profiles = handler.get_profiles(); + auto profiles = database_handler->get_all_charging_profiles(); EXPECT_THAT(profiles, testing::Contains(profile)); auto sut = handler.clear_profiles(create_clear_charging_profile_request(DEFAULT_PROFILE_ID)); EXPECT_THAT(sut.status, testing::Eq(ClearChargingProfileStatusEnum::Accepted)); - profiles = handler.get_profiles(); + profiles = database_handler->get_all_charging_profiles(); EXPECT_THAT(profiles, testing::Not(testing::Contains(profile))); } TEST_F(SmartChargingHandlerTestFixtureV201, K10_ClearChargingProfile_ClearsStackLevelPurposeCombination) { install_profile_on_evse(DEFAULT_EVSE_ID, DEFAULT_PROFILE_ID); - auto profiles = handler.get_profiles(); + auto profiles = database_handler->get_all_charging_profiles(); EXPECT_THAT(profiles, testing::Not(testing::IsEmpty())); auto sut = handler.clear_profiles(create_clear_charging_profile_request( @@ -1519,14 +1519,14 @@ TEST_F(SmartChargingHandlerTestFixtureV201, K10_ClearChargingProfile_ClearsStack DEFAULT_STACK_LEVEL))); EXPECT_THAT(sut.status, testing::Eq(ClearChargingProfileStatusEnum::Accepted)); - profiles = handler.get_profiles(); + profiles = database_handler->get_all_charging_profiles(); EXPECT_THAT(profiles, testing::IsEmpty()); } TEST_F(SmartChargingHandlerTestFixtureV201, K10_ClearChargingProfile_UnknownStackLevelPurposeCombination) { install_profile_on_evse(DEFAULT_EVSE_ID, DEFAULT_PROFILE_ID); - auto profiles = handler.get_profiles(); + auto profiles = database_handler->get_all_charging_profiles(); EXPECT_THAT(profiles, testing::Not(testing::IsEmpty())); auto sut = handler.clear_profiles(create_clear_charging_profile_request( @@ -1534,7 +1534,7 @@ TEST_F(SmartChargingHandlerTestFixtureV201, K10_ClearChargingProfile_UnknownStac STATION_WIDE_ID))); EXPECT_THAT(sut.status, testing::Eq(ClearChargingProfileStatusEnum::Unknown)); - profiles = handler.get_profiles(); + profiles = database_handler->get_all_charging_profiles(); EXPECT_THAT(profiles, testing::Not(testing::IsEmpty())); } @@ -1546,13 +1546,13 @@ TEST_F(SmartChargingHandlerTestFixtureV201, K10_ClearChargingProfile_UnknownId) create_charge_schedule(ChargingRateUnitEnum::A, periods, ocpp::DateTime("2024-01-17T17:00:00"))); handler.validate_and_add_profile(profile, STATION_WIDE_ID); - auto profiles = handler.get_profiles(); + auto profiles = database_handler->get_all_charging_profiles(); EXPECT_THAT(profiles, testing::Contains(profile)); auto sut = handler.clear_profiles(create_clear_charging_profile_request(178)); EXPECT_THAT(sut.status, testing::Eq(ClearChargingProfileStatusEnum::Unknown)); - profiles = handler.get_profiles(); + profiles = database_handler->get_all_charging_profiles(); EXPECT_THAT(profiles, testing::Contains(profile)); } @@ -1622,6 +1622,7 @@ TEST_F(SmartChargingHandlerTestFixtureV201, K08_GetValidProfiles_IfInvalidProfil TEST_F(SmartChargingHandlerTestFixtureV201, K02FR05_SmartChargingTransactionEnds_DeletesTxProfilesByTransactionId) { auto transaction_id = uuid(); + EVLOG_debug << "TRANSACTION ID: " << transaction_id; this->evse_manager->open_transaction(DEFAULT_EVSE_ID, transaction_id); auto profile = create_charging_profile(DEFAULT_PROFILE_ID, ChargingProfilePurposeEnum::TxProfile, create_charge_schedule(ChargingRateUnitEnum::A, @@ -1634,7 +1635,7 @@ TEST_F(SmartChargingHandlerTestFixtureV201, K02FR05_SmartChargingTransactionEnds handler.delete_transaction_tx_profiles(transaction_id); - EXPECT_THAT(handler.get_profiles(), testing::IsEmpty()); + EXPECT_THAT(this->database_handler->get_all_charging_profiles(), testing::IsEmpty()); } TEST_F(SmartChargingHandlerTestFixtureV201, @@ -1655,7 +1656,7 @@ TEST_F(SmartChargingHandlerTestFixtureV201, handler.delete_transaction_tx_profiles(transaction_id); - EXPECT_THAT(handler.get_profiles().size(), testing::Eq(1)); + EXPECT_THAT(this->database_handler->get_all_charging_profiles().size(), testing::Eq(1)); } TEST_F(SmartChargingHandlerTestFixtureV201, K05FR02_RequestStartTransactionRequest_ChargingProfileMustBeTxProfile) { From 30492dd7ad7d06a02a3bc8adfeefa90b2f48f5e4 Mon Sep 17 00:00:00 2001 From: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> Date: Fri, 6 Sep 2024 14:39:57 +0000 Subject: [PATCH 06/15] database_handler: Fix storage of charging profile purpose SQLite needs to hold onto a copy of the string in order for it to be stored properly. Signed-off-by: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> --- lib/ocpp/v201/database_handler.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/ocpp/v201/database_handler.cpp b/lib/ocpp/v201/database_handler.cpp index a18cdd148..8c661578e 100644 --- a/lib/ocpp/v201/database_handler.cpp +++ b/lib/ocpp/v201/database_handler.cpp @@ -2,6 +2,7 @@ // Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest #include "everest/logging.hpp" +#include "ocpp/common/database/sqlite_statement.hpp" #include #include #include @@ -735,7 +736,8 @@ void DatabaseHandler::insert_or_update_charging_profile(const int evse_id, const 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)); + conversions::charging_profile_purpose_enum_to_string(profile.chargingProfilePurpose), + SQLiteString::Transient); stmt->bind_text("@profile", json_profile.dump(), SQLiteString::Transient); stmt->bind_text("@charging_limit_source", conversions::charging_limit_source_enum_to_string(charging_limit_source)); From 22b541aaed94ad1ddccc3502fba68ddf0e1142ba Mon Sep 17 00:00:00 2001 From: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> Date: Wed, 28 Aug 2024 19:02:59 +0000 Subject: [PATCH 07/15] database_handler: Store transaction ID on profiles Edit the migration file for charging limit sources and remove Signed-off-by: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> --- ...ql => 6_down-charging_profiles_source_tx_id.sql} | 3 ++- .../6_up-charging_limit_source_column.sql | 1 - .../6_up-charging_profiles_source_tx_id.sql | 2 ++ lib/ocpp/v201/database_handler.cpp | 13 ++++++++++--- 4 files changed, 14 insertions(+), 5 deletions(-) rename config/v201/core_migrations/{6_down-charging_limit_source_column.sql => 6_down-charging_profiles_source_tx_id.sql} (52%) delete mode 100644 config/v201/core_migrations/6_up-charging_limit_source_column.sql create mode 100644 config/v201/core_migrations/6_up-charging_profiles_source_tx_id.sql diff --git a/config/v201/core_migrations/6_down-charging_limit_source_column.sql b/config/v201/core_migrations/6_down-charging_profiles_source_tx_id.sql similarity index 52% rename from config/v201/core_migrations/6_down-charging_limit_source_column.sql rename to config/v201/core_migrations/6_down-charging_profiles_source_tx_id.sql index d11bd83bd..2e707b58b 100644 --- a/config/v201/core_migrations/6_down-charging_limit_source_column.sql +++ b/config/v201/core_migrations/6_down-charging_profiles_source_tx_id.sql @@ -1 +1,2 @@ -ALTER TABLE CHARGING_PROFILES DROP COLUMN CHARGING_LIMIT_SOURCE; \ No newline at end of file +ALTER TABLE CHARGING_PROFILES DROP COLUMN CHARGING_LIMIT_SOURCE; +ALTER TABLE CHARGING_PROFILES DROP COLUMN TRANSACTION_ID; \ No newline at end of file diff --git a/config/v201/core_migrations/6_up-charging_limit_source_column.sql b/config/v201/core_migrations/6_up-charging_limit_source_column.sql deleted file mode 100644 index 36f3cf3d0..000000000 --- a/config/v201/core_migrations/6_up-charging_limit_source_column.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE CHARGING_PROFILES ADD COLUMN CHARGING_LIMIT_SOURCE TEXT NOT NULL DEFAULT 'CSO'; \ No newline at end of file diff --git a/config/v201/core_migrations/6_up-charging_profiles_source_tx_id.sql b/config/v201/core_migrations/6_up-charging_profiles_source_tx_id.sql new file mode 100644 index 000000000..7a00bb2fc --- /dev/null +++ b/config/v201/core_migrations/6_up-charging_profiles_source_tx_id.sql @@ -0,0 +1,2 @@ +ALTER TABLE CHARGING_PROFILES ADD COLUMN CHARGING_LIMIT_SOURCE TEXT NOT NULL DEFAULT 'CSO'; +ALTER TABLE CHARGING_PROFILES ADD COLUMN TRANSACTION_ID TEXT; \ No newline at end of file diff --git a/lib/ocpp/v201/database_handler.cpp b/lib/ocpp/v201/database_handler.cpp index 8c661578e..441e058a6 100644 --- a/lib/ocpp/v201/database_handler.cpp +++ b/lib/ocpp/v201/database_handler.cpp @@ -725,9 +725,10 @@ void DatabaseHandler::transaction_delete(const std::string& transaction_id) { void DatabaseHandler::insert_or_update_charging_profile(const int evse_id, const v201::ChargingProfile& profile, const ChargingLimitSourceEnum charging_limit_source) { // add or replace - std::string sql = "INSERT OR REPLACE INTO CHARGING_PROFILES (ID, EVSE_ID, STACK_LEVEL, CHARGING_PROFILE_PURPOSE, " - "PROFILE, CHARGING_LIMIT_SOURCE) VALUES " - "(@id, @evse_id, @stack_level, @charging_profile_purpose, @profile, @charging_limit_source)"; + std::string sql = + "INSERT OR REPLACE INTO CHARGING_PROFILES (ID, EVSE_ID, STACK_LEVEL, CHARGING_PROFILE_PURPOSE, " + "TRANSACTION_ID, PROFILE, CHARGING_LIMIT_SOURCE) VALUES " + "(@id, @evse_id, @stack_level, @charging_profile_purpose, @transaction_id, @profile, @charging_limit_source)"; auto stmt = this->database->new_statement(sql); json json_profile(profile); @@ -738,6 +739,12 @@ void DatabaseHandler::insert_or_update_charging_profile(const int evse_id, const stmt->bind_text("@charging_profile_purpose", conversions::charging_profile_purpose_enum_to_string(profile.chargingProfilePurpose), SQLiteString::Transient); + if (profile.transactionId.has_value()) { + stmt->bind_text("@transaction_id", profile.transactionId.value().get(), SQLiteString::Transient); + } else { + stmt->bind_null("@transaction_id"); + } + stmt->bind_text("@profile", json_profile.dump(), SQLiteString::Transient); stmt->bind_text("@charging_limit_source", conversions::charging_limit_source_enum_to_string(charging_limit_source)); From ff6396caa92dd1d075a2236fa9e7da03f9a70b9a Mon Sep 17 00:00:00 2001 From: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> Date: Wed, 28 Aug 2024 13:55:45 +0000 Subject: [PATCH 08/15] database_handler: Add function for new statements Signed-off-by: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> --- include/ocpp/v201/database_handler.hpp | 2 ++ lib/ocpp/v201/database_handler.cpp | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/include/ocpp/v201/database_handler.hpp b/include/ocpp/v201/database_handler.hpp index 92b88b93f..9ee0bf1c9 100644 --- a/include/ocpp/v201/database_handler.hpp +++ b/include/ocpp/v201/database_handler.hpp @@ -198,6 +198,8 @@ class DatabaseHandler : public common::DatabaseHandlerCommon { virtual std::map> get_all_charging_profiles_group_by_evse(); ChargingLimitSourceEnum get_charging_limit_source_for_profile(const int profile_id); + + std::unique_ptr new_statement(const std::string& sql); }; } // namespace v201 diff --git a/lib/ocpp/v201/database_handler.cpp b/lib/ocpp/v201/database_handler.cpp index 441e058a6..30444ab7f 100644 --- a/lib/ocpp/v201/database_handler.cpp +++ b/lib/ocpp/v201/database_handler.cpp @@ -839,5 +839,9 @@ ChargingLimitSourceEnum DatabaseHandler::get_charging_limit_source_for_profile(c return res; } +std::unique_ptr DatabaseHandler::new_statement(const std::string& sql) { + return this->database->new_statement(sql); +} + } // namespace v201 } // namespace ocpp From a3981bcd22e9f8aabe1abb92b0d2c76a1cfe6cf1 Mon Sep 17 00:00:00 2001 From: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> Date: Wed, 28 Aug 2024 13:56:56 +0000 Subject: [PATCH 09/15] smart_charging: Query database for matches directly Use the database handler to select matching profiles instead of iterating over the entire list of profiles. Signed-off-by: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> --- include/ocpp/v201/database_handler.hpp | 3 + lib/ocpp/v201/database_handler.cpp | 10 +++ lib/ocpp/v201/smart_charging.cpp | 63 +++++++++---------- tests/lib/ocpp/v201/test_database_handler.cpp | 37 +++++++++++ 4 files changed, 80 insertions(+), 33 deletions(-) diff --git a/include/ocpp/v201/database_handler.hpp b/include/ocpp/v201/database_handler.hpp index 9ee0bf1c9..f592057c1 100644 --- a/include/ocpp/v201/database_handler.hpp +++ b/include/ocpp/v201/database_handler.hpp @@ -185,6 +185,9 @@ class DatabaseHandler : public common::DatabaseHandlerCommon { /// \brief Deletes the profile with the given \p profile_id void delete_charging_profile(const int profile_id); + /// \brief Deletes the profiles with the given \p transaction_id + void delete_charging_profile_by_transaction_id(const std::string& transaction_id); + /// \brief Deletes all profiles from table CHARGING_PROFILES void clear_charging_profiles(); diff --git a/lib/ocpp/v201/database_handler.cpp b/lib/ocpp/v201/database_handler.cpp index 30444ab7f..5204d60e0 100644 --- a/lib/ocpp/v201/database_handler.cpp +++ b/lib/ocpp/v201/database_handler.cpp @@ -763,6 +763,16 @@ void DatabaseHandler::delete_charging_profile(const int profile_id) { } } +void DatabaseHandler::delete_charging_profile_by_transaction_id(const std::string& transaction_id) { + std::string sql = "DELETE FROM CHARGING_PROFILES WHERE TRANSACTION_ID = @transaction_id"; + auto stmt = this->database->new_statement(sql); + + stmt->bind_text("@transaction_id", transaction_id); + if (stmt->step() != SQLITE_DONE) { + throw QueryExecutionException(this->database->get_error_message()); + } +} + void DatabaseHandler::clear_charging_profiles() { this->database->clear_table("CHARGING_PROFILES"); } diff --git a/lib/ocpp/v201/smart_charging.cpp b/lib/ocpp/v201/smart_charging.cpp index d94d83c61..e18019cce 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/database/sqlite_statement.hpp" #include "ocpp/common/message_queue.hpp" #include "ocpp/common/types.hpp" #include "ocpp/v201/ctrlr_component_variables.hpp" @@ -220,13 +221,7 @@ SmartChargingHandler::SmartChargingHandler(EvseManagerInterface& evse_manager, } void SmartChargingHandler::delete_transaction_tx_profiles(const std::string& transaction_id) { - for (auto& profile : this->database_handler->get_all_charging_profiles()) { - if (profile.transactionId.has_value()) { - if (transaction_id == profile.transactionId.value().get()) { - this->database_handler->delete_charging_profile(profile.id); - } - } - } + this->database_handler->delete_charging_profile_by_transaction_id(transaction_id); } SetChargingProfileResponse SmartChargingHandler::validate_and_add_profile(ChargingProfile& profile, int32_t evse_id, @@ -382,14 +377,17 @@ SmartChargingHandler::validate_tx_profile(const ChargingProfile& profile, int32_ return ProfileValidationResultEnum::TxProfileTransactionNotOnEvse; } - auto charging_profiles = this->database_handler->get_all_charging_profiles(); - - auto conflicts_with = [&profile](const ChargingProfile& candidateProfile) { - return candidateProfile.transactionId == profile.transactionId && - candidateProfile.stackLevel == profile.stackLevel; - }; + auto conflicts_stmt = this->database_handler->new_statement( + "SELECT PROFILE FROM CHARGING_PROFILES WHERE TRANSACTION_ID = @transaction_id AND STACK_LEVEL = @stack_level"); + conflicts_stmt->bind_int("@stack_level", profile.stackLevel); + if (profile.transactionId.has_value()) { + conflicts_stmt->bind_text("@transaction_id", profile.transactionId.value().get(), + common::SQLiteString::Transient); + } else { + conflicts_stmt->bind_null("@transaction_id"); + } - if (std::any_of(charging_profiles.begin(), charging_profiles.end(), conflicts_with)) { + if (conflicts_stmt->step() == SQLITE_ROW) { return ProfileValidationResultEnum::TxProfileConflictingStackLevel; } @@ -582,16 +580,11 @@ std::vector SmartChargingHandler::get_valid_profiles(int32_t ev std::vector SmartChargingHandler::get_evse_specific_tx_default_profiles() const { std::vector evse_specific_tx_default_profiles; - auto charging_profiles = this->database_handler->get_all_charging_profiles_group_by_evse(); - - for (auto& [evse_id, profiles] : charging_profiles) { - if (evse_id != STATION_WIDE_ID) { - for (auto profile : profiles) { - if (profile.chargingProfilePurpose == ChargingProfilePurposeEnum::TxDefaultProfile) { - evse_specific_tx_default_profiles.push_back(profile); - } - } - } + auto stmt = this->database_handler->new_statement("SELECT PROFILE FROM CHARGING_PROFILES WHERE " + "EVSE_ID != 0 AND CHARGING_PROFILE_PURPOSE = 'TxDefaultProfile'"); + while (stmt->step() != SQLITE_DONE) { + ChargingProfile profile = json::parse(stmt->column_text(0)); + evse_specific_tx_default_profiles.push_back(profile); } return evse_specific_tx_default_profiles; @@ -599,10 +592,12 @@ std::vector SmartChargingHandler::get_evse_specific_tx_default_ std::vector SmartChargingHandler::get_station_wide_tx_default_profiles() const { std::vector station_wide_tx_default_profiles; - for (auto profile : this->database_handler->get_charging_profiles_for_evse(STATION_WIDE_ID)) { - if (profile.chargingProfilePurpose == ChargingProfilePurposeEnum::TxDefaultProfile) { - station_wide_tx_default_profiles.push_back(profile); - } + + auto stmt = this->database_handler->new_statement( + "SELECT PROFILE FROM CHARGING_PROFILES WHERE EVSE_ID = 0 AND CHARGING_PROFILE_PURPOSE = 'TxDefaultProfile'"); + while (stmt->step() != SQLITE_DONE) { + ChargingProfile profile = json::parse(stmt->column_text(0)); + station_wide_tx_default_profiles.push_back(profile); } return station_wide_tx_default_profiles; @@ -638,11 +633,13 @@ void SmartChargingHandler::conform_validity_periods(ChargingProfile& profile) co ProfileValidationResultEnum SmartChargingHandler::verify_no_conflicting_external_constraints_id(const ChargingProfile& profile) const { auto result = ProfileValidationResultEnum::Valid; - for (auto existing_profile : this->database_handler->get_all_charging_profiles()) { - if (existing_profile.id == profile.id && - existing_profile.chargingProfilePurpose == ChargingProfilePurposeEnum::ChargingStationExternalConstraints) { - result = ProfileValidationResultEnum::ExistingChargingStationExternalConstraints; - } + auto conflicts_stmt = + this->database_handler->new_statement("SELECT PROFILE FROM CHARGING_PROFILES WHERE ID = @profile_id AND " + "CHARGING_PROFILE_PURPOSE = 'ChargingStationExternalConstraints'"); + + conflicts_stmt->bind_int("@profile_id", profile.id); + if (conflicts_stmt->step() == SQLITE_ROW) { + result = ProfileValidationResultEnum::ExistingChargingStationExternalConstraints; } return result; diff --git a/tests/lib/ocpp/v201/test_database_handler.cpp b/tests/lib/ocpp/v201/test_database_handler.cpp index d55ba315d..8aeb7c6c5 100644 --- a/tests/lib/ocpp/v201/test_database_handler.cpp +++ b/tests/lib/ocpp/v201/test_database_handler.cpp @@ -4,6 +4,9 @@ #include "comparators.hpp" #include "database_testing_utils.hpp" #include "ocpp/v201/enums.hpp" +#include "gmock/gmock.h" +#include +#include #include #include #include @@ -33,6 +36,14 @@ class DatabaseHandlerTest : public DatabaseTestingUtils { return transaction; } + + std::string uuid() { + std::stringstream s; + s << uuid_generator(); + return s.str(); + } + + boost::uuids::random_generator uuid_generator = boost::uuids::random_generator(); }; TEST_F(DatabaseHandlerTest, TransactionInsertAndGet) { @@ -435,4 +446,30 @@ TEST_F(DatabaseHandlerTest, GetChargingLimitSourceForProfile_RetrievsSetSourceFo auto sut = this->database_handler.get_charging_limit_source_for_profile(profile_id); EXPECT_EQ(sut, ChargingLimitSourceEnum::EMS); +} + +TEST_F(DatabaseHandlerTest, DeleteChargingProfileByTransactionId_DeletesByTransactionId) { + const auto profile_id = 1; + const auto transaction_id = uuid(); + auto profile1 = ChargingProfile{ + .id = profile_id, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + auto profile2 = ChargingProfile{.id = profile_id + 1, + .stackLevel = 1, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxProfile, + .transactionId = transaction_id}; + + this->database_handler.insert_or_update_charging_profile(1, profile1); + this->database_handler.insert_or_update_charging_profile(1, profile2); + + auto profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_THAT(profiles, testing::SizeIs(2)); + EXPECT_THAT(profiles, testing::Contains(profile1)); + EXPECT_THAT(profiles, testing::Contains(profile2)); + + this->database_handler.delete_charging_profile_by_transaction_id(transaction_id); + + auto sut = this->database_handler.get_all_charging_profiles(); + EXPECT_THAT(sut, testing::SizeIs(1)); + EXPECT_THAT(sut, testing::Contains(profile1)); + EXPECT_THAT(sut, testing::Not(testing::Contains(profile2))); } \ No newline at end of file From 6b701901cb8cd0a1d7bcee0b570c66ae9b89deb3 Mon Sep 17 00:00:00 2001 From: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> Date: Thu, 29 Aug 2024 15:13:53 +0000 Subject: [PATCH 10/15] smart_charging, database_handler: Use db for K10 Use new functions on the database handler to reimplement clearing charging profiles by a given set of criteria. This removes the existing implementation within the smart charging handler. Signed-off-by: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> --- include/ocpp/v201/database_handler.hpp | 8 +- lib/ocpp/v201/database_handler.cpp | 72 ++++++- lib/ocpp/v201/smart_charging.cpp | 56 +----- tests/lib/ocpp/v201/test_database_handler.cpp | 181 ++++++++++++++++++ 4 files changed, 260 insertions(+), 57 deletions(-) diff --git a/include/ocpp/v201/database_handler.hpp b/include/ocpp/v201/database_handler.hpp index f592057c1..dd5d1ec9b 100644 --- a/include/ocpp/v201/database_handler.hpp +++ b/include/ocpp/v201/database_handler.hpp @@ -183,13 +183,17 @@ class DatabaseHandler : public common::DatabaseHandlerCommon { const ChargingLimitSourceEnum charging_limit_source = ChargingLimitSourceEnum::CSO); /// \brief Deletes the profile with the given \p profile_id - void delete_charging_profile(const int profile_id); + bool delete_charging_profile(const int profile_id); /// \brief Deletes the profiles with the given \p transaction_id void delete_charging_profile_by_transaction_id(const std::string& transaction_id); /// \brief Deletes all profiles from table CHARGING_PROFILES - void clear_charging_profiles(); + bool clear_charging_profiles(); + + /// \brief Deletes all profiles from table CHARGING_PROFILES matching \p profile_id or \p criteria + bool clear_charging_profiles_matching_criteria(const std::optional profile_id, + const std::optional& criteria); /// \brief Retrieves the charging profiles stored on \p evse_id std::vector get_charging_profiles_for_evse(const int evse_id); diff --git a/lib/ocpp/v201/database_handler.cpp b/lib/ocpp/v201/database_handler.cpp index 5204d60e0..3fa426a31 100644 --- a/lib/ocpp/v201/database_handler.cpp +++ b/lib/ocpp/v201/database_handler.cpp @@ -753,7 +753,7 @@ void DatabaseHandler::insert_or_update_charging_profile(const int evse_id, const } } -void DatabaseHandler::delete_charging_profile(const int profile_id) { +bool 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); @@ -761,6 +761,8 @@ void DatabaseHandler::delete_charging_profile(const int profile_id) { if (stmt->step() != SQLITE_DONE) { throw QueryExecutionException(this->database->get_error_message()); } + + return stmt->changes() > 0; } void DatabaseHandler::delete_charging_profile_by_transaction_id(const std::string& transaction_id) { @@ -773,8 +775,72 @@ void DatabaseHandler::delete_charging_profile_by_transaction_id(const std::strin } } -void DatabaseHandler::clear_charging_profiles() { - this->database->clear_table("CHARGING_PROFILES"); +bool DatabaseHandler::clear_charging_profiles() { + return this->database->clear_table("CHARGING_PROFILES"); +} + +bool DatabaseHandler::clear_charging_profiles_matching_criteria(const std::optional profile_id, + const std::optional& criteria) { + // K10.FR.03, K10.FR.09 + if (profile_id.has_value()) { + return this->delete_charging_profile(profile_id.value()); + } + + // criteria has no value, so clear all + if (!criteria.has_value()) { + return this->clear_charging_profiles(); + } + + if (criteria->chargingProfilePurpose.has_value() || criteria->evseId.has_value() || + criteria->stackLevel.has_value()) { + std::string delete_query = "DELETE FROM CHARGING_PROFILES"; + // Start with K10.FR.04, prevent deleting external constraints + std::vector filters = {"CHARGING_PROFILE_PURPOSE != 'ChargingStationExternalConstraints'"}; + + if (criteria->chargingProfilePurpose.has_value()) { + filters.push_back("CHARGING_PROFILE_PURPOSE = @charging_profile_purpose"); + } + + if (criteria->stackLevel.has_value()) { + filters.push_back("STACK_LEVEL = @stack_level"); + } + + if (criteria->evseId.has_value()) { + filters.push_back("EVSE_ID = @evse_id"); + } + + if (filters.size() > 0) { + delete_query += " WHERE "; + for (int i = 0; i < filters.size() - 1; i++) { + delete_query += " " + filters[i] + " AND"; + } + delete_query += " " + filters[filters.size() - 1]; + } + + auto stmt = this->database->new_statement(delete_query); + if (criteria->chargingProfilePurpose.has_value()) { + stmt->bind_text( + "@charging_profile_purpose", + conversions::charging_profile_purpose_enum_to_string(criteria->chargingProfilePurpose.value()), + SQLiteString::Transient); + } + + if (criteria->stackLevel.has_value()) { + stmt->bind_int("@stack_level", criteria->stackLevel.value()); + } + + if (criteria->evseId.has_value()) { + stmt->bind_int("@evse_id", criteria->evseId.value()); + } + + if (stmt->step() != SQLITE_DONE) { + throw QueryExecutionException(this->database->get_error_message()); + } + + return stmt->changes() > 0; + } + + return false; } std::vector DatabaseHandler::get_charging_profiles_for_evse(const int evse_id) { diff --git a/lib/ocpp/v201/smart_charging.cpp b/lib/ocpp/v201/smart_charging.cpp index e18019cce..8cee68b07 100644 --- a/lib/ocpp/v201/smart_charging.cpp +++ b/lib/ocpp/v201/smart_charging.cpp @@ -138,40 +138,6 @@ bool value_exists_and_is_different(const std::optional& requested_value, cons return requested_value.has_value() and requested_value.value() != actual_value; } -/// \brief Checks if the given filter \p criteria and \p evse_id matches the given \p profile -bool profile_matches_clear_criteria(const ChargingProfile& profile, const std::optional profile_id, - const std::optional& criteria, const int32_t evse_id) { - if (profile.chargingProfilePurpose == ChargingProfilePurposeEnum::ChargingStationExternalConstraints) { - // K10.FR.04; profiles with ChargingStationExternalConstraints shall not be removed - return false; - } - - // // K10.FR.03, K10.FR.09 - if (profile_id.has_value() and profile_id.value() == profile.id) { - return true; - } - - if (profile_id.has_value()) { - // profile_id has value but doesnt match - return false; - } - - if (!criteria.has_value()) { - // criteria has no value, so clear all - return true; - } - - const auto _criteria = criteria.value(); - - if (value_exists_and_is_different(_criteria.chargingProfilePurpose, profile.chargingProfilePurpose) or - value_exists_and_is_different(_criteria.stackLevel, profile.stackLevel) or - value_exists_and_is_different(_criteria.evseId, evse_id)) { - return false; - } - - return true; -} - /// \brief Checks if the given filter \p criteria and \p evse_id matches the given \p profile bool profile_matches_get_criteria(const ChargingProfile& profile, const ChargingProfileCriterion& criteria, const std::optional requested_evse_id, const int32_t profile_evse_id) { @@ -512,27 +478,13 @@ SetChargingProfileResponse SmartChargingHandler::add_profile(ChargingProfile& pr ClearChargingProfileResponse SmartChargingHandler::clear_profiles(const ClearChargingProfileRequest& request) { ClearChargingProfileResponse response; - - const auto profile_id = request.chargingProfileId; - const auto criteria = request.chargingProfileCriteria; - response.status = ClearChargingProfileStatusEnum::Unknown; - auto charging_profiles = this->database_handler->get_all_charging_profiles_group_by_evse(); - - for (auto& [existing_evse_id, evse_profiles] : charging_profiles) { - for (auto it = evse_profiles.begin(); it != evse_profiles.end();) { - // K10.FR.04: logical AND between the filters, if not set, the filter does not apply - if (profile_matches_clear_criteria(*it, profile_id, criteria, existing_evse_id)) { - response.status = ClearChargingProfileStatusEnum::Accepted; - it = evse_profiles.erase(it); - this->database_handler->delete_charging_profile(it->id); - } else { - // At least one of the filters did not match - ++it; - } - } + if (this->database_handler->clear_charging_profiles_matching_criteria(request.chargingProfileId, + request.chargingProfileCriteria)) { + response.status = ClearChargingProfileStatusEnum::Accepted; } + return response; } diff --git a/tests/lib/ocpp/v201/test_database_handler.cpp b/tests/lib/ocpp/v201/test_database_handler.cpp index 8aeb7c6c5..d6e69aa26 100644 --- a/tests/lib/ocpp/v201/test_database_handler.cpp +++ b/tests/lib/ocpp/v201/test_database_handler.cpp @@ -14,6 +14,7 @@ using namespace ocpp; using namespace ocpp::v201; +const int STATION_WIDE_ID = 0; const int DEFAULT_EVSE_ID = 1; class DatabaseHandlerTest : public DatabaseTestingUtils { @@ -472,4 +473,184 @@ TEST_F(DatabaseHandlerTest, DeleteChargingProfileByTransactionId_DeletesByTransa EXPECT_THAT(sut, testing::SizeIs(1)); EXPECT_THAT(sut, testing::Contains(profile1)); EXPECT_THAT(sut, testing::Not(testing::Contains(profile2))); +} + +TEST_F(DatabaseHandlerTest, ClearChargingProfilesMatchingCriteria_WhenGivenProfileId_DeletesProfile) { + const ClearChargingProfile clear_criteria; + + const auto profile_id = 1; + auto p1 = ChargingProfile{ + .id = profile_id, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, p1); + + auto profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 1); + + auto sut = this->database_handler.clear_charging_profiles_matching_criteria(profile_id, clear_criteria); + EXPECT_TRUE(sut); + + profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 0); +} + +TEST_F(DatabaseHandlerTest, ClearChargingProfilesMatchingCriteria_WhenNotGivenProfileId_DoesNotDeleteProfile) { + const ClearChargingProfile clear_criteria; + + const auto profile_id = 1; + auto p1 = ChargingProfile{ + .id = profile_id, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, p1); + + auto profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 1); + + auto sut = this->database_handler.clear_charging_profiles_matching_criteria({}, clear_criteria); + EXPECT_FALSE(sut); + + profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 1); + EXPECT_THAT(profiles, testing::Contains(p1)); +} + +TEST_F(DatabaseHandlerTest, ClearChargingProfilesMatchingCriteria_WhenNotGivenCriteria_DeletesAllProfiles) { + const auto profile_id = 1; + auto p1 = ChargingProfile{ + .id = profile_id, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + auto p2 = ChargingProfile{ + .id = profile_id + 1, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, p1); + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID + 1, p2); + + auto profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 2); + + auto sut = this->database_handler.clear_charging_profiles_matching_criteria({}, {}); + EXPECT_TRUE(sut); + + profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 0); +} + +TEST_F(DatabaseHandlerTest, ClearChargingProfilesMatchingCriteria_WhenAllCriteriaMatch_DeletesProfile) { + const auto purpose = ChargingProfilePurposeEnum::TxDefaultProfile; + const auto stack_level = 1; + + const ClearChargingProfile clear_criteria = { + .evseId = DEFAULT_EVSE_ID, + .chargingProfilePurpose = purpose, + .stackLevel = stack_level, + }; + + auto p1 = ChargingProfile{.id = 1, .stackLevel = stack_level, .chargingProfilePurpose = purpose}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, p1); + + auto profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 1); + + auto sut = this->database_handler.clear_charging_profiles_matching_criteria({}, clear_criteria); + EXPECT_TRUE(sut); + + profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 0); +} + +TEST_F(DatabaseHandlerTest, ClearChargingProfilesMatchingCriteria_UnknownPurpose_DoesNotDeleteProfile) { + const auto different_purpose = ChargingProfilePurposeEnum::TxProfile; + const auto stack_level = 1; + + const ClearChargingProfile clear_criteria = { + .evseId = DEFAULT_EVSE_ID, + .chargingProfilePurpose = different_purpose, + .stackLevel = stack_level, + }; + + auto p1 = ChargingProfile{ + .id = 1, .stackLevel = stack_level, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, p1); + + auto profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 1); + EXPECT_THAT(profiles, testing::Contains(p1)); + + auto sut = this->database_handler.clear_charging_profiles_matching_criteria({}, clear_criteria); + EXPECT_FALSE(sut); + + profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 1); + EXPECT_THAT(profiles, testing::Contains(p1)); +} + +TEST_F(DatabaseHandlerTest, ClearChargingProfilesMatchingCriteria_UnknownStackLevel_DoesNotDeleteProfile) { + const auto purpose = ChargingProfilePurposeEnum::TxDefaultProfile; + const auto different_stack_level = 2; + + const ClearChargingProfile clear_criteria = { + .evseId = DEFAULT_EVSE_ID, + .chargingProfilePurpose = purpose, + .stackLevel = different_stack_level, + }; + + auto p1 = ChargingProfile{.id = 1, .stackLevel = 1, .chargingProfilePurpose = purpose}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, p1); + + auto profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 1); + EXPECT_THAT(profiles, testing::Contains(p1)); + + auto sut = this->database_handler.clear_charging_profiles_matching_criteria({}, clear_criteria); + EXPECT_FALSE(sut); + + profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 1); + EXPECT_THAT(profiles, testing::Contains(p1)); +} + +TEST_F(DatabaseHandlerTest, ClearChargingProfilesMatchingCriteria_UnknownEvseId_DoesNotDeleteProfile) { + const auto purpose = ChargingProfilePurposeEnum::TxDefaultProfile; + const auto stack_level = 1; + const auto different_evse_id = DEFAULT_EVSE_ID + 1; + + const ClearChargingProfile clear_criteria = { + .evseId = different_evse_id, + .chargingProfilePurpose = purpose, + .stackLevel = stack_level, + }; + + auto p1 = ChargingProfile{.id = 1, .stackLevel = stack_level, .chargingProfilePurpose = purpose}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, p1); + + auto profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 1); + EXPECT_THAT(profiles, testing::Contains(p1)); + + auto sut = this->database_handler.clear_charging_profiles_matching_criteria({}, clear_criteria); + EXPECT_FALSE(sut); + + profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 1); + EXPECT_THAT(profiles, testing::Contains(p1)); +} + +TEST_F(DatabaseHandlerTest, ClearChargingProfilesMatchingCriteria_DoesNotDeleteExternalConstraints) { + const auto purpose = ChargingProfilePurposeEnum::ChargingStationExternalConstraints; + const auto stack_level = 1; + + const ClearChargingProfile clear_criteria = { + .evseId = STATION_WIDE_ID, + .chargingProfilePurpose = purpose, + .stackLevel = stack_level, + }; + + auto p1 = ChargingProfile{.id = 1, .stackLevel = stack_level, .chargingProfilePurpose = purpose}; + this->database_handler.insert_or_update_charging_profile(STATION_WIDE_ID, p1); + + auto profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 1); + + auto sut = this->database_handler.clear_charging_profiles_matching_criteria({}, clear_criteria); + EXPECT_FALSE(sut); + + profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 1); + EXPECT_THAT(profiles, testing::Contains(p1)); } \ No newline at end of file From 30103382bcb6a83ca641b32a9659e6d6181ea31b Mon Sep 17 00:00:00 2001 From: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> Date: Wed, 2 Oct 2024 13:57:58 +0000 Subject: [PATCH 11/15] smart_charging, types: Move ReportedChargingProfiles Move the ReportedChargingProfiles struct to types.hpp where it can be used in e.g. DatabaseHandler without causing recursive imports. Signed-off-by: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> --- include/ocpp/v201/smart_charging.hpp | 14 +------------- include/ocpp/v201/types.hpp | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/include/ocpp/v201/smart_charging.hpp b/include/ocpp/v201/smart_charging.hpp index f0baf80ff..2c1d9aa87 100644 --- a/include/ocpp/v201/smart_charging.hpp +++ b/include/ocpp/v201/smart_charging.hpp @@ -16,6 +16,7 @@ #include #include #include +#include namespace ocpp::v201 { @@ -48,19 +49,6 @@ enum class ProfileValidationResultEnum { RequestStartTransactionNonTxProfile }; -/// \brief This enhances the ChargingProfile type by additional paramaters that are required in the -/// ReportChargingProfilesRequest (EvseId, ChargingLimitSourceEnum) -struct ReportedChargingProfile { - ChargingProfile profile; - int32_t evse_id; - ChargingLimitSourceEnum source; - - ReportedChargingProfile(const ChargingProfile& profile, const int32_t evse_id, - const ChargingLimitSourceEnum source) : - profile(profile), evse_id(evse_id), source(source) { - } -}; - /// \brief This is used to associate charging profiles with a source. /// Based on the source a different validation path can be taken. enum class AddChargingProfileSource { diff --git a/include/ocpp/v201/types.hpp b/include/ocpp/v201/types.hpp index 9659934eb..75d1f67a5 100644 --- a/include/ocpp/v201/types.hpp +++ b/include/ocpp/v201/types.hpp @@ -3,6 +3,8 @@ #ifndef V201_TYPES_HPP #define V201_TYPES_HPP +#include + #include #include @@ -142,6 +144,19 @@ enum class MessageType { InternalError, // not in spec, for internal use }; +/// \brief This enhances the ChargingProfile type by additional paramaters that are required in the +/// ReportChargingProfilesRequest (EvseId, ChargingLimitSourceEnum) +struct ReportedChargingProfile { + ChargingProfile profile; + int32_t evse_id; + ChargingLimitSourceEnum source; + + ReportedChargingProfile(const ChargingProfile& profile, const int32_t evse_id, + const ChargingLimitSourceEnum source) : + profile(profile), evse_id(evse_id), source(source) { + } +}; + namespace conversions { /// \brief Converts the given MessageType \p m to std::string /// \returns a string representation of the MessageType From 26af8638fa6f8587ffc5153d3870e673533c1c4c Mon Sep 17 00:00:00 2001 From: Coury Richards <146002925+couryrr-afs@users.noreply.github.com> Date: Thu, 29 Aug 2024 19:01:40 +0000 Subject: [PATCH 12/15] smart_charging, database_handler: Use db for K09 Use new functions on the database handler to implement getting charging profiles by criteria. This allows us to remove more places where we were iterating over the entire list of charging profiles. This commit includes a fix for a bug where we were only accepting searches if one of the sources we were searching for is CSO. Co-authored-by: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> 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> --- include/ocpp/v201/database_handler.hpp | 6 + lib/ocpp/v201/database_handler.cpp | 102 +++- lib/ocpp/v201/smart_charging.cpp | 49 +- tests/lib/ocpp/v201/test_database_handler.cpp | 466 +++++++++++++++++- .../ocpp/v201/test_smart_charging_handler.cpp | 12 +- 5 files changed, 556 insertions(+), 79 deletions(-) diff --git a/include/ocpp/v201/database_handler.hpp b/include/ocpp/v201/database_handler.hpp index dd5d1ec9b..437e5ba14 100644 --- a/include/ocpp/v201/database_handler.hpp +++ b/include/ocpp/v201/database_handler.hpp @@ -3,6 +3,7 @@ #ifndef OCPP_V201_DATABASE_HANDLER_HPP #define OCPP_V201_DATABASE_HANDLER_HPP +#include "ocpp/v201/types.hpp" #include "sqlite3.h" #include #include @@ -195,6 +196,11 @@ class DatabaseHandler : public common::DatabaseHandlerCommon { bool clear_charging_profiles_matching_criteria(const std::optional profile_id, const std::optional& criteria); + /// \brief Get all profiles from table CHARGING_PROFILES matching \p profile_id or \p criteria + std::vector + get_charging_profiles_matching_criteria(const std::optional evse_id, + const ChargingProfileCriterion& criteria); + /// \brief Retrieves the charging profiles stored on \p evse_id std::vector get_charging_profiles_for_evse(const int evse_id); diff --git a/lib/ocpp/v201/database_handler.cpp b/lib/ocpp/v201/database_handler.cpp index 3fa426a31..6f11fb274 100644 --- a/lib/ocpp/v201/database_handler.cpp +++ b/lib/ocpp/v201/database_handler.cpp @@ -3,10 +3,17 @@ #include "everest/logging.hpp" #include "ocpp/common/database/sqlite_statement.hpp" +#include "ocpp/v201/ocpp_enums.hpp" +#include "ocpp/v201/ocpp_types.hpp" +#include +#include +#include #include #include #include #include +#include +#include namespace ocpp { @@ -809,13 +816,7 @@ bool DatabaseHandler::clear_charging_profiles_matching_criteria(const std::optio filters.push_back("EVSE_ID = @evse_id"); } - if (filters.size() > 0) { - delete_query += " WHERE "; - for (int i = 0; i < filters.size() - 1; i++) { - delete_query += " " + filters[i] + " AND"; - } - delete_query += " " + filters[filters.size() - 1]; - } + delete_query += " WHERE " + boost::algorithm::join(filters, " AND "); auto stmt = this->database->new_statement(delete_query); if (criteria->chargingProfilePurpose.has_value()) { @@ -843,6 +844,93 @@ bool DatabaseHandler::clear_charging_profiles_matching_criteria(const std::optio return false; } +std::vector +DatabaseHandler::get_charging_profiles_matching_criteria(const std::optional evse_id, + const ChargingProfileCriterion& criteria) { + auto results = std::vector(); + + std::string select_stmt = "SELECT EVSE_ID, PROFILE, CHARGING_LIMIT_SOURCE FROM CHARGING_PROFILES"; + std::vector where_clauses; + + if (evse_id.has_value()) { + where_clauses.push_back("EVSE_ID = @evse_id"); + } + + if (criteria.chargingProfileId.has_value() && !criteria.chargingProfileId->empty()) { + std::string profile_ids = + boost::algorithm::join(criteria.chargingProfileId.value() | + boost::adaptors::transformed([](int32_t id) { return std::to_string(id); }), + ", "); + + where_clauses.push_back("ID IN (" + profile_ids + ")"); + + select_stmt += " WHERE " + boost::algorithm::join(where_clauses, " AND "); + + auto stmt = this->database->new_statement(select_stmt); + + if (evse_id.has_value()) { + stmt->bind_int("@evse_id", evse_id.value()); + } + + while (stmt->step() != SQLITE_DONE) { + results.push_back(ReportedChargingProfile( + json::parse(stmt->column_text(1)), // profile + stmt->column_int(0), // EVSE ID + conversions::string_to_charging_limit_source_enum(stmt->column_text(2)) // source + )); + } + return results; + } + + if (criteria.chargingProfilePurpose.has_value()) { + where_clauses.push_back("CHARGING_PROFILE_PURPOSE = @charging_profile_purpose"); + } + + if (criteria.stackLevel.has_value()) { + where_clauses.push_back("STACK_LEVEL = @stack_level"); + } + + if (criteria.chargingLimitSource.has_value() && !criteria.chargingLimitSource->empty()) { + std::string sources = boost::algorithm::join( + criteria.chargingLimitSource.value() | boost::adaptors::transformed([](ChargingLimitSourceEnum source) { + return "'" + conversions::charging_limit_source_enum_to_string(source) + "'"; + }), + ", "); + + where_clauses.push_back("CHARGING_LIMIT_SOURCE IN (" + sources + ")"); + } + + if (!where_clauses.empty()) { + select_stmt += " WHERE " + boost::algorithm::join(where_clauses, " AND "); + } + + auto stmt = this->database->new_statement(select_stmt); + + if (criteria.chargingProfilePurpose.has_value()) { + stmt->bind_text("@charging_profile_purpose", + conversions::charging_profile_purpose_enum_to_string(criteria.chargingProfilePurpose.value()), + SQLiteString::Transient); + } + + if (criteria.stackLevel.has_value()) { + stmt->bind_int("@stack_level", criteria.stackLevel.value()); + } + + if (evse_id.has_value()) { + stmt->bind_int("@evse_id", evse_id.value()); + } + + while (stmt->step() != SQLITE_DONE) { + results.push_back( + ReportedChargingProfile(json::parse(stmt->column_text(1)), // profile + stmt->column_int(0), // EVSE ID + conversions::string_to_charging_limit_source_enum(stmt->column_text(2)) // source + )); + } + + return results; +} + std::vector DatabaseHandler::get_charging_profiles_for_evse(const int evse_id) { std::vector profiles; diff --git a/lib/ocpp/v201/smart_charging.cpp b/lib/ocpp/v201/smart_charging.cpp index 8cee68b07..e7cbb5a59 100644 --- a/lib/ocpp/v201/smart_charging.cpp +++ b/lib/ocpp/v201/smart_charging.cpp @@ -127,41 +127,6 @@ std::ostream& operator<<(std::ostream& os, const ProfileValidationResultEnum val return os; } -bool charging_limit_source_exists_and_is_not_CSO(const ChargingProfileCriterion& criteria) { - return criteria.chargingLimitSource.has_value() and - std::find(criteria.chargingLimitSource.value().begin(), criteria.chargingLimitSource.value().end(), - ChargingLimitSourceEnum::CSO) == criteria.chargingLimitSource.value().end(); -} - -template -bool value_exists_and_is_different(const std::optional& requested_value, const T& actual_value) { - return requested_value.has_value() and requested_value.value() != actual_value; -} - -/// \brief Checks if the given filter \p criteria and \p evse_id matches the given \p profile -bool profile_matches_get_criteria(const ChargingProfile& profile, const ChargingProfileCriterion& criteria, - const std::optional requested_evse_id, const int32_t profile_evse_id) { - - if (criteria.chargingProfileId.has_value()) { - const auto profile_ids = criteria.chargingProfileId.value(); - return std::find(profile_ids.begin(), profile_ids.end(), profile.id) != profile_ids.end(); - } - - if (criteria.chargingProfileId.has_value()) { - // no profile matches the id, no need to do check match of the criteria - return false; - } - - if (charging_limit_source_exists_and_is_not_CSO(criteria) or - value_exists_and_is_different(criteria.chargingProfilePurpose, profile.chargingProfilePurpose) or - value_exists_and_is_different(criteria.stackLevel, profile.stackLevel) or - value_exists_and_is_different(requested_evse_id, profile_evse_id)) { - return false; - } - - return true; -} - const int32_t STATION_WIDE_ID = 0; CurrentPhaseType SmartChargingHandler::get_current_phase_type(const std::optional evse_opt) const { @@ -490,19 +455,7 @@ ClearChargingProfileResponse SmartChargingHandler::clear_profiles(const ClearCha std::vector SmartChargingHandler::get_reported_profiles(const GetChargingProfilesRequest& request) const { - std::vector profiles; - - auto charging_profiles = this->database_handler->get_all_charging_profiles_group_by_evse(); - - for (auto& [existing_evse_id, evse_profiles] : charging_profiles) { - for (auto& profile : evse_profiles) { - if (profile_matches_get_criteria(profile, request.chargingProfile, request.evseId, existing_evse_id)) { - auto source = this->database_handler->get_charging_limit_source_for_profile(profile.id); - profiles.push_back(ReportedChargingProfile(profile, existing_evse_id, source)); - } - } - } - return profiles; + return this->database_handler->get_charging_profiles_matching_criteria(request.evseId, request.chargingProfile); } std::vector SmartChargingHandler::get_valid_profiles_for_evse(int32_t evse_id) { diff --git a/tests/lib/ocpp/v201/test_database_handler.cpp b/tests/lib/ocpp/v201/test_database_handler.cpp index d6e69aa26..9d1530821 100644 --- a/tests/lib/ocpp/v201/test_database_handler.cpp +++ b/tests/lib/ocpp/v201/test_database_handler.cpp @@ -4,12 +4,17 @@ #include "comparators.hpp" #include "database_testing_utils.hpp" #include "ocpp/v201/enums.hpp" +#include "ocpp/v201/messages/GetChargingProfiles.hpp" +#include "ocpp/v201/ocpp_enums.hpp" +#include "ocpp/v201/ocpp_types.hpp" #include "gmock/gmock.h" +#include "gtest/gtest.h" #include #include #include #include #include +#include using namespace ocpp; using namespace ocpp::v201; @@ -427,28 +432,6 @@ TEST_F(DatabaseHandlerTest, GetChargingProfilesForEvse_DoesNotGetProfilesOnOther EXPECT_THAT(profiles, testing::Not(testing::Contains(profile2))); } -TEST_F(DatabaseHandlerTest, GetChargingLimitSourceForProfile_RetrievesDefaultSourceForProfile) { - const auto profile_id = 1; - auto p1 = ChargingProfile{ - .id = profile_id, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; - this->database_handler.insert_or_update_charging_profile(1, p1); - - auto sut = this->database_handler.get_charging_limit_source_for_profile(profile_id); - EXPECT_EQ(sut, ChargingLimitSourceEnum::CSO); -} - -TEST_F(DatabaseHandlerTest, GetChargingLimitSourceForProfile_RetrievsSetSourceForProfile) { - const ChargingLimitSourceEnum custom_charging_limit_source = ChargingLimitSourceEnum::EMS; - - const auto profile_id = 1; - auto p1 = ChargingProfile{ - .id = profile_id, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; - this->database_handler.insert_or_update_charging_profile(1, p1, custom_charging_limit_source); - - auto sut = this->database_handler.get_charging_limit_source_for_profile(profile_id); - EXPECT_EQ(sut, ChargingLimitSourceEnum::EMS); -} - TEST_F(DatabaseHandlerTest, DeleteChargingProfileByTransactionId_DeletesByTransactionId) { const auto profile_id = 1; const auto transaction_id = uuid(); @@ -653,4 +636,443 @@ TEST_F(DatabaseHandlerTest, ClearChargingProfilesMatchingCriteria_DoesNotDeleteE profiles = this->database_handler.get_all_charging_profiles(); EXPECT_EQ(profiles.size(), 1); EXPECT_THAT(profiles, testing::Contains(p1)); +} + +TEST_F(DatabaseHandlerTest, GetChargingProfilesMatchingCriteria_NoCriteriaReturnsAll) { + auto profile = ChargingProfile{ + .id = 1, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile); + + auto profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 1); + + std::vector sut = + this->database_handler.get_charging_profiles_matching_criteria(std::nullopt, {}); + EXPECT_EQ(sut.size(), 1); +} + +TEST_F(DatabaseHandlerTest, GetChargingProfilesMatchingCriteria_NoMatchingIdsReturnsEmpty) { + auto profile_id = 1; + auto profile = ChargingProfile{ + .id = profile_id, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile); + + auto profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 1); + + ChargingProfileCriterion criteria = {.chargingProfileId = std::vector{2}}; + + std::vector sut = + this->database_handler.get_charging_profiles_matching_criteria(std::nullopt, criteria); + + EXPECT_EQ(sut.size(), 0); +} + +TEST_F(DatabaseHandlerTest, GetChargingProfilesMatchingCriteria_MatchingIdReturnsSingleProfile) { + auto profile_id_1 = 1; + auto profile_1 = ChargingProfile{ + .id = profile_id_1, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_1); + + auto profile_id_2 = 2; + auto profile_2 = ChargingProfile{ + .id = profile_id_2, .stackLevel = 2, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_2); + + auto profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 2); + + ChargingProfileCriterion criteria = {.chargingProfileId = std::vector{profile_id_1}}; + + std::vector sut = + this->database_handler.get_charging_profiles_matching_criteria(std::nullopt, criteria); + + EXPECT_EQ(sut.size(), 1); +} + +TEST_F(DatabaseHandlerTest, GetChargingProfilesMatchingCriteria_MatchingIdsReturnsMultipleProfiles) { + auto profile_id_1 = 1; + auto profile_1 = ChargingProfile{ + .id = profile_id_1, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_1); + + auto profile_id_2 = 2; + auto profile_2 = ChargingProfile{ + .id = profile_id_2, .stackLevel = 2, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_2); + + auto profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 2); + + ChargingProfileCriterion criteria = {.chargingProfileId = std::vector{profile_id_1, profile_id_2}}; + + std::vector sut = + this->database_handler.get_charging_profiles_matching_criteria(std::nullopt, criteria); + + EXPECT_EQ(sut.size(), 2); +} + +TEST_F(DatabaseHandlerTest, GetChargingProfilesMatchingCriteria_MatchingChargingProfilePurposeReturnsProfiles) { + auto profile_id_1 = 1; + auto profile_1 = ChargingProfile{ + .id = profile_id_1, .stackLevel = 1, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_1); + + auto profile_id_2 = 2; + auto profile_2 = ChargingProfile{ + .id = profile_id_2, .stackLevel = 2, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_2); + + auto profile_id_3 = 3; + auto profile_3 = ChargingProfile{ + .id = profile_id_3, .stackLevel = 2, .chargingProfilePurpose = ChargingProfilePurposeEnum::TxProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_3); + + auto profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 3); + + ChargingProfileCriterion criteria = {.chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + + std::vector sut = + this->database_handler.get_charging_profiles_matching_criteria(std::nullopt, criteria); + + EXPECT_EQ(sut.size(), 2); +} + +TEST_F(DatabaseHandlerTest, GetChargingProfilesMatchingCriteria_MatchingStackLevelReturnsProfiles) { + auto profile_id_1 = 1; + auto stack_level = 1; + auto profile_1 = ChargingProfile{.id = profile_id_1, + .stackLevel = stack_level, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_1); + + auto profile_id_2 = 2; + auto profile_2 = ChargingProfile{.id = profile_id_2, + .stackLevel = stack_level + 1, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_2); + + auto profile_id_3 = 3; + auto profile_3 = ChargingProfile{.id = profile_id_3, + .stackLevel = stack_level + 1, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_3); + + auto profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 3); + + ChargingProfileCriterion criteria = {.stackLevel = 2}; + + std::vector sut = + this->database_handler.get_charging_profiles_matching_criteria(std::nullopt, criteria); + + EXPECT_EQ(sut.size(), 2); +} + +TEST_F(DatabaseHandlerTest, GetChargingProfilesMatchingCriteria_MatchingProfileSourceReturnsProfiles) { + auto profile_id_1 = 1; + auto stack_level = 1; + auto profile_1 = ChargingProfile{.id = profile_id_1, + .stackLevel = stack_level, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_1); + + auto profile_id_2 = 2; + auto profile_2 = ChargingProfile{.id = profile_id_2, + .stackLevel = stack_level + 1, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_2); + + auto profile_id_3 = 3; + auto profile_3 = ChargingProfile{.id = profile_id_3, + .stackLevel = stack_level + 1, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_3, ChargingLimitSourceEnum::EMS); + + auto profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 3); + + ChargingProfileCriterion criteria = {.chargingLimitSource = {{ChargingLimitSourceEnum::CSO}}}; + + std::vector sut = + this->database_handler.get_charging_profiles_matching_criteria(std::nullopt, criteria); + + EXPECT_EQ(sut.size(), 2); +} + +TEST_F(DatabaseHandlerTest, GetChargingProfilesMatchingCriteria_MatchingProfileSourcesReturnsProfiles) { + auto profile_id_1 = 1; + auto stack_level = 1; + auto profile_1 = ChargingProfile{.id = profile_id_1, + .stackLevel = stack_level, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_1); + + auto profile_id_2 = 2; + auto profile_2 = ChargingProfile{.id = profile_id_2, + .stackLevel = stack_level + 1, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_2); + + auto profile_id_3 = 3; + auto profile_3 = ChargingProfile{.id = profile_id_3, + .stackLevel = stack_level + 1, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_3, ChargingLimitSourceEnum::EMS); + + auto profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 3); + + ChargingProfileCriterion criteria = { + .chargingLimitSource = {{ChargingLimitSourceEnum::CSO, ChargingLimitSourceEnum::EMS}}}; + + std::vector sut = + this->database_handler.get_charging_profiles_matching_criteria(std::nullopt, criteria); + + EXPECT_EQ(sut.size(), 3); +} + +TEST_F(DatabaseHandlerTest, GetChargingProfilesMatchingCriteria_AllCriteriaSetReturnsOne) { + auto profile_id_1 = 1; + auto stack_level = 1; + auto profile_1 = ChargingProfile{.id = profile_id_1, + .stackLevel = stack_level, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_1); + + auto profile_id_2 = 2; + auto profile_2 = ChargingProfile{.id = profile_id_2, + .stackLevel = stack_level + 1, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_2); + + auto profile_id_3 = 3; + auto profile_3 = ChargingProfile{.id = profile_id_3, + .stackLevel = stack_level + 1, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_3, ChargingLimitSourceEnum::CSO); + + auto profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 3); + + ChargingProfileCriterion criteria = {.chargingProfilePurpose = ChargingProfilePurposeEnum::TxProfile, + .stackLevel = 2, + .chargingLimitSource = {{ChargingLimitSourceEnum::CSO}}}; + + std::vector sut = + this->database_handler.get_charging_profiles_matching_criteria(std::nullopt, criteria); + + EXPECT_EQ(sut.size(), 1); +} + +TEST_F(DatabaseHandlerTest, GetChargingProfilesMatchingCriteria_AllCriteriaSetReturnsNone) { + auto profile_id_1 = 1; + auto stack_level = 1; + auto profile_1 = ChargingProfile{.id = profile_id_1, + .stackLevel = stack_level, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_1); + + auto profile_id_2 = 2; + auto profile_2 = ChargingProfile{.id = profile_id_2, + .stackLevel = stack_level + 1, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_2); + + auto profile_id_3 = 3; + auto profile_3 = ChargingProfile{.id = profile_id_3, + .stackLevel = stack_level + 1, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_3, ChargingLimitSourceEnum::CSO); + + auto profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 3); + + ChargingProfileCriterion criteria = {.chargingProfilePurpose = ChargingProfilePurposeEnum::TxProfile, + .stackLevel = 2, + .chargingLimitSource = {{ChargingLimitSourceEnum::EMS}}}; + + std::vector sut = + this->database_handler.get_charging_profiles_matching_criteria(std::nullopt, criteria); + + EXPECT_EQ(sut.size(), 0); +} + +TEST_F(DatabaseHandlerTest, GetChargingProfilesMatchingCriteria_AllCriteriaSetReturnsNothing) { + auto profile_id_1 = 1; + auto stack_level = 1; + auto profile_1 = ChargingProfile{.id = profile_id_1, + .stackLevel = stack_level, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_1); + + auto profile_id_2 = 2; + auto profile_2 = ChargingProfile{.id = profile_id_2, + .stackLevel = stack_level + 1, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_2); + + auto profile_id_3 = 3; + auto profile_3 = ChargingProfile{.id = profile_id_3, + .stackLevel = stack_level + 1, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_3, ChargingLimitSourceEnum::CSO); + + auto profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 3); + + ChargingProfileCriterion criteria = {.chargingProfilePurpose = ChargingProfilePurposeEnum::TxProfile, + .stackLevel = 2, + .chargingLimitSource = {{ChargingLimitSourceEnum::EMS}}}; + + std::vector sut = + this->database_handler.get_charging_profiles_matching_criteria(std::nullopt, criteria); + + EXPECT_EQ(sut.size(), 0); +} + +TEST_F(DatabaseHandlerTest, GetChargingProfilesMatchingCriteriaAndEvseId0_AllCriteriaSetReturnsNone) { + auto profile_id_1 = 1; + auto stack_level = 1; + auto profile_1 = ChargingProfile{.id = profile_id_1, + .stackLevel = stack_level, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_1); + + auto profile_id_2 = 2; + auto profile_2 = ChargingProfile{.id = profile_id_2, + .stackLevel = stack_level + 1, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_2); + + auto profile_id_3 = 3; + auto profile_3 = ChargingProfile{.id = profile_id_3, + .stackLevel = stack_level + 1, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_3, ChargingLimitSourceEnum::CSO); + + auto profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 3); + + ChargingProfileCriterion criteria = { + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile, + .stackLevel = 2, + .chargingLimitSource = {{ChargingLimitSourceEnum::CSO}}, + }; + + std::vector sut = + this->database_handler.get_charging_profiles_matching_criteria(0, criteria); + + EXPECT_EQ(sut.size(), 0); +} + +TEST_F(DatabaseHandlerTest, GetChargingProfilesMatchingCriteria_IfProfileIdAndEvseIdGiven_ReturnsMatchingProfile) { + auto profile_id_1 = 1; + auto stack_level = 1; + auto profile_1 = ChargingProfile{.id = profile_id_1, + .stackLevel = stack_level, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_1); + + auto profile_id_2 = 2; + auto profile_2 = ChargingProfile{.id = profile_id_2, + .stackLevel = stack_level + 1, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(STATION_WIDE_ID, profile_2); + + auto profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 2); + + ChargingProfileCriterion criteria = { + .chargingProfileId = {{profile_id_1, profile_id_2}}, + }; + + std::vector sut = + this->database_handler.get_charging_profiles_matching_criteria(STATION_WIDE_ID, criteria); + EXPECT_EQ(sut.size(), 1); + + EXPECT_THAT(sut[0].profile, testing::Eq(profile_2)); +} + +TEST_F(DatabaseHandlerTest, GetChargingProfilesMatchingCriteria_MatchingProfileIds_ReturnsEVSEAndSource) { + auto profile_id_1 = 1; + auto stack_level = 1; + auto profile_1 = ChargingProfile{.id = profile_id_1, + .stackLevel = stack_level, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_1); + + auto profile_id_2 = 2; + auto profile_2 = ChargingProfile{.id = profile_id_2, + .stackLevel = stack_level + 1, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(STATION_WIDE_ID, profile_2, ChargingLimitSourceEnum::EMS); + + auto profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 2); + + ChargingProfileCriterion criteria = { + .chargingProfileId = {{profile_id_1, profile_id_2}}, + }; + + std::vector sut = + this->database_handler.get_charging_profiles_matching_criteria({}, criteria); + EXPECT_EQ(sut.size(), 2); + + EXPECT_THAT(sut, testing::Contains(testing::FieldsAre(profile_1, DEFAULT_EVSE_ID, ChargingLimitSourceEnum::CSO))); + EXPECT_THAT(sut, testing::Contains(testing::FieldsAre(profile_2, STATION_WIDE_ID, ChargingLimitSourceEnum::EMS))); +} + +TEST_F(DatabaseHandlerTest, GetChargingProfilesMatchingCriteria_MatchingCriteria_ReturnsEVSEAndSource) { + auto profile_id_1 = 1; + auto stack_level = 1; + auto profile_1 = ChargingProfile{.id = profile_id_1, + .stackLevel = stack_level, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_1); + + auto profile_id_2 = 2; + auto profile_2 = ChargingProfile{.id = profile_id_2, + .stackLevel = stack_level + 1, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(STATION_WIDE_ID, profile_2, ChargingLimitSourceEnum::EMS); + + auto profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 2); + + ChargingProfileCriterion criteria = { + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile, + }; + + std::vector sut = + this->database_handler.get_charging_profiles_matching_criteria({}, criteria); + EXPECT_EQ(sut.size(), 2); + + EXPECT_THAT(sut, testing::Contains(testing::FieldsAre(profile_1, DEFAULT_EVSE_ID, ChargingLimitSourceEnum::CSO))); + EXPECT_THAT(sut, testing::Contains(testing::FieldsAre(profile_2, STATION_WIDE_ID, ChargingLimitSourceEnum::EMS))); +} + +TEST_F(DatabaseHandlerTest, GetChargingProfilesMatchingCriteria_OnlyEVSEIDSet_ReturnsProfileOnEVSE) { + auto profile_id_1 = 1; + auto stack_level = 1; + auto profile_1 = ChargingProfile{.id = profile_id_1, + .stackLevel = stack_level, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(DEFAULT_EVSE_ID, profile_1); + + auto profile_id_2 = 2; + auto profile_2 = ChargingProfile{.id = profile_id_2, + .stackLevel = stack_level + 1, + .chargingProfilePurpose = ChargingProfilePurposeEnum::TxDefaultProfile}; + this->database_handler.insert_or_update_charging_profile(STATION_WIDE_ID, profile_2, ChargingLimitSourceEnum::EMS); + + auto profiles = this->database_handler.get_all_charging_profiles(); + EXPECT_EQ(profiles.size(), 2); + + std::vector sut = + this->database_handler.get_charging_profiles_matching_criteria(DEFAULT_EVSE_ID, {}); + EXPECT_EQ(sut.size(), 1); + + EXPECT_THAT(sut, testing::Contains(testing::FieldsAre(profile_1, DEFAULT_EVSE_ID, ChargingLimitSourceEnum::CSO))); } \ No newline at end of file diff --git a/tests/lib/ocpp/v201/test_smart_charging_handler.cpp b/tests/lib/ocpp/v201/test_smart_charging_handler.cpp index c75d97623..4615edc96 100644 --- a/tests/lib/ocpp/v201/test_smart_charging_handler.cpp +++ b/tests/lib/ocpp/v201/test_smart_charging_handler.cpp @@ -1246,7 +1246,11 @@ TEST_F(SmartChargingHandlerTestFixtureV201, AddProfile_StoresChargingLimitSource auto response = handler.add_profile(profile, DEFAULT_EVSE_ID, charging_limit_source); EXPECT_THAT(response.status, testing::Eq(ChargingProfileStatusEnum::Accepted)); - auto sut = database_handler->get_charging_limit_source_for_profile(DEFAULT_PROFILE_ID); + ChargingProfileCriterion criteria = { + .chargingProfileId = {{profile.id}}, + }; + auto profiles = this->database_handler->get_charging_profiles_matching_criteria(DEFAULT_EVSE_ID, criteria); + const auto [e, p, sut] = profiles[0]; EXPECT_THAT(sut, ChargingLimitSourceEnum::SO); } @@ -1264,7 +1268,11 @@ TEST_F(SmartChargingHandlerTestFixtureV201, ValidateAndAddProfile_StoresCharging auto response = handler.validate_and_add_profile(profile, DEFAULT_EVSE_ID, charging_limit_source); EXPECT_THAT(response.status, testing::Eq(ChargingProfileStatusEnum::Accepted)); - auto sut = database_handler->get_charging_limit_source_for_profile(DEFAULT_PROFILE_ID); + ChargingProfileCriterion criteria = { + .chargingProfileId = {{profile.id}}, + }; + auto profiles = this->database_handler->get_charging_profiles_matching_criteria(DEFAULT_EVSE_ID, criteria); + const auto [e, p, sut] = profiles[0]; EXPECT_THAT(sut, ChargingLimitSourceEnum::SO); } From dc19c7adf13b677237ef631b637a2f0e3bafdd7f Mon Sep 17 00:00:00 2001 From: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> Date: Thu, 5 Sep 2024 15:16:45 +0000 Subject: [PATCH 13/15] smart_charging: Use db for overlapping validity periods Signed-off-by: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> --- lib/ocpp/v201/smart_charging.cpp | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/lib/ocpp/v201/smart_charging.cpp b/lib/ocpp/v201/smart_charging.cpp index e7cbb5a59..8dddb1f05 100644 --- a/lib/ocpp/v201/smart_charging.cpp +++ b/lib/ocpp/v201/smart_charging.cpp @@ -516,18 +516,25 @@ bool SmartChargingHandler::is_overlapping_validity_period(const ChargingProfile& return false; } - const std::vector profiles = - this->database_handler->get_charging_profiles_for_evse(candidate_evse_id); - return std::any_of(profiles.begin(), profiles.end(), [&candidate_profile](const ChargingProfile& existing_profile) { - if (existing_profile.stackLevel == candidate_profile.stackLevel && - existing_profile.chargingProfileKind == candidate_profile.chargingProfileKind && - existing_profile.id != candidate_profile.id) { - - return candidate_profile.validFrom <= existing_profile.validTo && - candidate_profile.validTo >= existing_profile.validFrom; // reject + auto overlap_stmt = this->database_handler->new_statement( + "SELECT PROFILE, json_extract(PROFILE, '$.chargingProfileKind') AS KIND FROM CHARGING_PROFILES WHERE EVSE_ID = " + "@evse_id AND ID != @profile_id AND CHARGING_PROFILES.STACK_LEVEL = @stack_level AND KIND = @kind"); + + overlap_stmt->bind_int("@evse_id", candidate_evse_id); + overlap_stmt->bind_int("@profile_id", candidate_profile.id); + overlap_stmt->bind_int("@stack_level", candidate_profile.stackLevel); + overlap_stmt->bind_text("@kind", + conversions::charging_profile_kind_enum_to_string(candidate_profile.chargingProfileKind), + common::SQLiteString::Transient); + while (overlap_stmt->step() != SQLITE_DONE) { + ChargingProfile existing_profile = json::parse(overlap_stmt->column_text(0)); + if (candidate_profile.validFrom <= existing_profile.validTo && + candidate_profile.validTo >= existing_profile.validFrom) { + return true; } - return false; - }); + } + + return false; } void SmartChargingHandler::conform_validity_periods(ChargingProfile& profile) const { From 212a8f8f5ca67c87314d348e2b1c18b228b2fb16 Mon Sep 17 00:00:00 2001 From: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> Date: Fri, 6 Sep 2024 15:22:20 +0000 Subject: [PATCH 14/15] dependencies: Rename json_schema_validator dep The name wasn't quite right, leading to some confusion. Signed-off-by: Christopher Davis <150722105+christopher-davis-afs@users.noreply.github.com> --- dependencies.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.yaml b/dependencies.yaml index 181095c94..02c69f4b9 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -3,7 +3,7 @@ nlohmann_json: git: https://github.com/nlohmann/json git_tag: v3.11.2 options: ["JSON_BuildTests OFF", "JSON_MultipleHeaders ON"] -nlohmann_json_schema_validator: +pboettch_json_schema_validator: git: https://github.com/pboettch/json-schema-validator git_tag: 2.3.0 options: From d3ef2560db5791b800c57554aeef9010072efb72 Mon Sep 17 00:00:00 2001 From: Peter Giavotto <146003699+Giavotto@users.noreply.github.com> Date: Tue, 10 Sep 2024 16:18:28 -0400 Subject: [PATCH 15/15] Renamed the method load charging profiles to clear invalid charging profiles, removed logic that adds profiles and replaced it with logic that only removes invalid profiles Signed-off-by: Peter Giavotto <146003699+Giavotto@users.noreply.github.com> --- include/ocpp/v201/charge_point.hpp | 2 +- lib/ocpp/v201/charge_point.cpp | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index b34095350..9d9e7aa08 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -735,7 +735,7 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa std::shared_ptr smart_charging_handler; void handle_message(const EnhancedMessage& message); - void load_charging_profiles(); + void clear_invalid_charging_profiles(); public: /// \brief Construct a new ChargePoint object diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index cf82fbf5f..3f21e6ef5 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -183,8 +183,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(); + // call clear_invalid_charging_profiles when system boots + this->clear_invalid_charging_profiles(); this->connectivity_manager->start(); const std::string firmware_version = @@ -4432,19 +4432,15 @@ void ChargePoint::execute_change_availability_request(ChangeAvailabilityRequest } } -// K01.27 - load profiles from database -void ChargePoint::load_charging_profiles() { +void ChargePoint::clear_invalid_charging_profiles() { try { 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) { try { - if (this->smart_charging_handler->validate_profile(profile, evse_id) == + 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 (const QueryExecutionException& e) {