diff --git a/lib/everest/ocpp/lib/ocpp/v16/profile.cpp b/lib/everest/ocpp/lib/ocpp/v16/profile.cpp index d26e4d5137..db7e16968b 100644 --- a/lib/everest/ocpp/lib/ocpp/v16/profile.cpp +++ b/lib/everest/ocpp/lib/ocpp/v16/profile.cpp @@ -425,7 +425,8 @@ IntermediateProfile generate_profile_from_periods(std::vector& p IntermediateProfile combined{}; DateTime current = now; - while (current < end) { + // run calculation at least once especially where current >= end + do { // find schedule to use for time: current DateTime earliest = end; DateTime next_earliest = end; @@ -476,7 +477,7 @@ IntermediateProfile generate_profile_from_periods(std::vector& p current = next_earliest; } } - } + } while (current < end); return combined; } diff --git a/lib/everest/ocpp/lib/ocpp/v16/smart_charging.cpp b/lib/everest/ocpp/lib/ocpp/v16/smart_charging.cpp index 86e217e672..c5386e5065 100644 --- a/lib/everest/ocpp/lib/ocpp/v16/smart_charging.cpp +++ b/lib/everest/ocpp/lib/ocpp/v16/smart_charging.cpp @@ -208,8 +208,14 @@ ChargingSchedule SmartChargingHandler::calculate_composite_schedule(const ocpp:: const std::int32_t evse_id, ChargingRateUnit charging_rate_unit, bool is_offline, bool simulate_transaction_active) { + // handle edge case where start_time > end_time + auto start_time_w = start_time; + if (start_time_w > end_time) { + start_time_w = end_time; + } + const auto enhanced_composite_schedule = this->calculate_enhanced_composite_schedule( - start_time, end_time, evse_id, charging_rate_unit, is_offline, simulate_transaction_active); + start_time_w, end_time, evse_id, charging_rate_unit, is_offline, simulate_transaction_active); ChargingSchedule composite_schedule; composite_schedule.chargingRateUnit = enhanced_composite_schedule.chargingRateUnit; composite_schedule.duration = enhanced_composite_schedule.duration; diff --git a/lib/everest/ocpp/lib/ocpp/v2/functional_blocks/smart_charging.cpp b/lib/everest/ocpp/lib/ocpp/v2/functional_blocks/smart_charging.cpp index 0d2bcc1cae..f49069dc0e 100644 --- a/lib/everest/ocpp/lib/ocpp/v2/functional_blocks/smart_charging.cpp +++ b/lib/everest/ocpp/lib/ocpp/v2/functional_blocks/smart_charging.cpp @@ -550,12 +550,18 @@ std::vector generate_evse_intermediates(std::vector end_time + auto start_time = start_t; + if (start_time > end_time) { + start_time = end_time; + } + const CompositeScheduleConfig config{this->context.device_model, is_offline}; std::optional session_start{}; diff --git a/lib/everest/ocpp/lib/ocpp/v2/profile.cpp b/lib/everest/ocpp/lib/ocpp/v2/profile.cpp index 904b8a7b65..4005087b1c 100644 --- a/lib/everest/ocpp/lib/ocpp/v2/profile.cpp +++ b/lib/everest/ocpp/lib/ocpp/v2/profile.cpp @@ -408,7 +408,8 @@ IntermediateProfile generate_profile_from_periods(std::vector& p IntermediateProfile combined{}; DateTime current = now; - while (current < end) { + // run calculation at least once especially where current >= end + do { // find schedule to use for time: current DateTime earliest = end; DateTime next_earliest = end; @@ -477,7 +478,7 @@ IntermediateProfile generate_profile_from_periods(std::vector& p current = next_earliest; } } - } + } while (current < end); return combined; } diff --git a/lib/everest/ocpp/tests/lib/ocpp/v16/test_composite_schedule.cpp b/lib/everest/ocpp/tests/lib/ocpp/v16/test_composite_schedule.cpp index bbebb37e6c..70a6ad529e 100644 --- a/lib/everest/ocpp/tests/lib/ocpp/v16/test_composite_schedule.cpp +++ b/lib/everest/ocpp/tests/lib/ocpp/v16/test_composite_schedule.cpp @@ -928,5 +928,77 @@ TEST_F(CompositeScheduleTestFixture, TxDefaultConnector0) { PeriodEquals(350, 10000.0F))); } +TEST_F(CompositeScheduleTestFixture, ZeroDuration) { + // test relating to libocpp issue 1169 + // incorrect composite schedule when duration is zero + + // using absolute schedule with limit 2000.0, starting 2024-01-17T18:04:00.000Z + // default limit is 33120.0 + + auto handler = create_smart_charging_handler(1, false); + ChargingProfile profile_tx_default_1 = get_charging_profile_from_file("TxProfile_03_Absolute.json"); + + handler->add_tx_default_profile(profile_tx_default_1, 0); + + // start 1 minute into the profile + const DateTime start_time = ocpp::DateTime("2024-01-17T18:05:00.000Z"); + const DateTime end_time = ocpp::DateTime("2024-01-17T18:05:01.000Z"); + const auto expected_limit = 2000.0F; + + // check with end time and a corresponding duration of 1 second + auto result_con0 = + handler->calculate_composite_schedule(start_time, end_time, STATION_WIDE_ID, ChargingRateUnit::W, false, true); + + EXPECT_EQ(result_con0.startSchedule, start_time); + EXPECT_EQ(result_con0.duration, 1); + EXPECT_EQ(result_con0.chargingRateUnit, ChargingRateUnit::W); + + EXPECT_THAT(result_con0.chargingSchedulePeriod, testing::ElementsAre(PeriodEquals(0, expected_limit))); + + auto result_con1 = handler->calculate_composite_schedule(start_time, end_time, 1, ChargingRateUnit::W, false, true); + + EXPECT_EQ(result_con1.startSchedule, start_time); + EXPECT_EQ(result_con1.duration, 1); + EXPECT_EQ(result_con1.chargingRateUnit, ChargingRateUnit::W); + + EXPECT_THAT(result_con1.chargingSchedulePeriod, testing::ElementsAre(PeriodEquals(0, expected_limit))); + + // Now with duration of 0 seconds + result_con0 = handler->calculate_composite_schedule(start_time, start_time, STATION_WIDE_ID, ChargingRateUnit::W, + false, true); + + EXPECT_EQ(result_con0.startSchedule, start_time); + EXPECT_EQ(result_con0.duration, 0); + EXPECT_EQ(result_con0.chargingRateUnit, ChargingRateUnit::W); + + EXPECT_THAT(result_con0.chargingSchedulePeriod, testing::ElementsAre(PeriodEquals(0, expected_limit))); + + result_con1 = handler->calculate_composite_schedule(start_time, start_time, 1, ChargingRateUnit::W, false, true); + + EXPECT_EQ(result_con1.startSchedule, start_time); + EXPECT_EQ(result_con1.duration, 0); + EXPECT_EQ(result_con1.chargingRateUnit, ChargingRateUnit::W); + + EXPECT_THAT(result_con1.chargingSchedulePeriod, testing::ElementsAre(PeriodEquals(0, expected_limit))); + + // Now with duration of -1 second (treated as an instantaneous 0 duration) + result_con0 = + handler->calculate_composite_schedule(end_time, start_time, STATION_WIDE_ID, ChargingRateUnit::W, false, true); + + EXPECT_EQ(result_con0.startSchedule, start_time); + EXPECT_EQ(result_con0.duration, 0); + EXPECT_EQ(result_con0.chargingRateUnit, ChargingRateUnit::W); + + EXPECT_THAT(result_con0.chargingSchedulePeriod, testing::ElementsAre(PeriodEquals(0, expected_limit))); + + result_con1 = handler->calculate_composite_schedule(end_time, start_time, 1, ChargingRateUnit::W, false, true); + + EXPECT_EQ(result_con1.startSchedule, start_time); + EXPECT_EQ(result_con1.duration, 0); + EXPECT_EQ(result_con1.chargingRateUnit, ChargingRateUnit::W); + + EXPECT_THAT(result_con1.chargingSchedulePeriod, testing::ElementsAre(PeriodEquals(0, expected_limit))); +} + } // namespace v16 } // namespace ocpp diff --git a/lib/everest/ocpp/tests/lib/ocpp/v2/test_composite_schedule.cpp b/lib/everest/ocpp/tests/lib/ocpp/v2/test_composite_schedule.cpp index 8937f571d4..771fee2017 100644 --- a/lib/everest/ocpp/tests/lib/ocpp/v2/test_composite_schedule.cpp +++ b/lib/everest/ocpp/tests/lib/ocpp/v2/test_composite_schedule.cpp @@ -1201,4 +1201,51 @@ TEST_F(CompositeScheduleTestFixtureV2, TxDefaultConnector0) { PeriodEquals(350, 10000.0F))); } +TEST_F(CompositeScheduleTestFixtureV2, ZeroDuration) { + // test relating to libocpp issue 1169 + // incorrect composite schedule when duration is zero + + // using absolute schedule with limit 32.0, starting 2024-01-01T12:02:00Z + // default limit is 57.0 (DEFAULT_LIMIT_AMPERE) + + load_charging_profiles_for_evse("singles/Absolute_301.json", STATION_WIDE_ID); + + const DateTime start_time = ocpp::DateTime("2024-01-01T12:02:00Z"); + const DateTime end_time = ocpp::DateTime("2024-01-01T12:02:01Z"); + const auto expected_limit = 32.0F; + + // check with end time and a corresponding duration of 1 second + auto result = handler->calculate_composite_schedule(start_time, end_time, STATION_WIDE_ID, ChargingRateUnitEnum::A, + false, true); + + EXPECT_EQ(result.evseId, STATION_WIDE_ID); + EXPECT_EQ(result.scheduleStart, start_time); + EXPECT_EQ(result.duration, 1); + EXPECT_EQ(result.chargingRateUnit, ChargingRateUnitEnum::A); + + EXPECT_THAT(result.chargingSchedulePeriod, testing::ElementsAre(PeriodEquals(0, expected_limit))); + + // Now with duration of 0 seconds + result = handler->calculate_composite_schedule(start_time, start_time, STATION_WIDE_ID, ChargingRateUnitEnum::A, + false, true); + + EXPECT_EQ(result.evseId, STATION_WIDE_ID); + EXPECT_EQ(result.scheduleStart, start_time); + EXPECT_EQ(result.duration, 0); + EXPECT_EQ(result.chargingRateUnit, ChargingRateUnitEnum::A); + + EXPECT_THAT(result.chargingSchedulePeriod, testing::ElementsAre(PeriodEquals(0, expected_limit))); + + // Now with duration of -1 second + result = handler->calculate_composite_schedule(end_time, start_time, STATION_WIDE_ID, ChargingRateUnitEnum::A, + false, true); + + EXPECT_EQ(result.evseId, STATION_WIDE_ID); + EXPECT_EQ(result.scheduleStart, start_time); + EXPECT_EQ(result.duration, 0); + EXPECT_EQ(result.chargingRateUnit, ChargingRateUnitEnum::A); + + EXPECT_THAT(result.chargingSchedulePeriod, testing::ElementsAre(PeriodEquals(0, expected_limit))); +} + } // namespace ocpp::v2 diff --git a/lib/everest/ocpp/tests/lib/ocpp/v21/test_composite_schedule.cpp b/lib/everest/ocpp/tests/lib/ocpp/v21/test_composite_schedule.cpp index 9398313c39..3cfeebbe20 100644 --- a/lib/everest/ocpp/tests/lib/ocpp/v21/test_composite_schedule.cpp +++ b/lib/everest/ocpp/tests/lib/ocpp/v21/test_composite_schedule.cpp @@ -420,3 +420,45 @@ TEST_F(CompositeScheduleTestFixtureV21, V21DifferentNumberPhases_StationWide) { ChargingRateUnitEnum::W, false, true); EXPECT_EQ(actual, expected); } + +TEST_F(CompositeScheduleTestFixtureV21, ZeroDuration) { + load_charging_profiles_for_evse(BASE_JSON_PATH_V21 + "/station_wide_three_phases/", STATION_WIDE_ID); + const DateTime start_time("2024-01-17T18:00:00.000Z"); + const DateTime end_time("2024-01-17T18:00:01.000Z"); + + ChargingSchedulePeriod period1; + period1.startPeriod = 0; + period1.limit = 12420.0F; + period1.limit_L2 = 12430.0F; + period1.limit_L3 = 12440.0F; + period1.numberPhases = 3; + + CompositeSchedule expected_0; + expected_0.chargingSchedulePeriod = {period1}; + expected_0.evseId = DEFAULT_EVSE_ID; + expected_0.duration = 0; + expected_0.scheduleStart = start_time; + expected_0.chargingRateUnit = ChargingRateUnitEnum::W; + + CompositeSchedule expected_1; + expected_1.chargingSchedulePeriod = {period1}; + expected_1.evseId = DEFAULT_EVSE_ID; + expected_1.duration = 1; + expected_1.scheduleStart = start_time; + expected_1.chargingRateUnit = ChargingRateUnitEnum::W; + + // check with end time and a corresponding duration of 1 second + auto actual = handler->calculate_composite_schedule(start_time, end_time, DEFAULT_EVSE_ID, ChargingRateUnitEnum::W, + false, true); + EXPECT_EQ(actual, expected_1); + + // Now with duration of 0 seconds + actual = handler->calculate_composite_schedule(start_time, start_time, DEFAULT_EVSE_ID, ChargingRateUnitEnum::W, + false, true); + EXPECT_EQ(actual, expected_0); + + // Now with duration of -1 second + actual = handler->calculate_composite_schedule(end_time, start_time, DEFAULT_EVSE_ID, ChargingRateUnitEnum::W, + false, true); + EXPECT_EQ(actual, expected_0); +}