Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add schedule validation #521

Merged
merged 1 commit into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion include/ocpp/v201/smart_charging.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@ enum class ProfileValidationResultEnum {
TxProfileEvseIdNotGreaterThanZero,
TxProfileTransactionNotOnEvse,
TxProfileEvseHasNoActiveTransaction,
TxProfileConflictingStackLevel
TxProfileConflictingStackLevel,
ChargingProfileNoChargingSchedulePeriods,
ChargingProfileFirstStartScheduleIsNotZero,
ChargingProfileMissingRequiredStartSchedule,
ChargingProfileExtraneousStartSchedule,
ChargingSchedulePeriodsOutOfOrder,
ChargingSchedulePeriodInvalidPhaseToUse,
};

/// \brief This class handles and maintains incoming ChargingProfiles and contains the logic
Expand All @@ -39,6 +45,9 @@ class SmartChargingHandler {
///
ProfileValidationResultEnum validate_tx_profile(const ChargingProfile& profile, Evse& evse) const;

/// \brief validates that the given \p profile has valid charging schedules
ProfileValidationResultEnum validate_profile_schedules(const ChargingProfile& profile) const;

/// \brief Adds a given \p profile to our stored list of profiles
void add_profile(const ChargingProfile& profile);
};
Expand Down
52 changes: 52 additions & 0 deletions lib/ocpp/v201/smart_charging.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest

#include "everest/logging.hpp"
#include "ocpp/common/types.hpp"
#include "ocpp/v201/enums.hpp"
#include "ocpp/v201/ocpp_types.hpp"
Expand Down Expand Up @@ -45,6 +46,57 @@ ProfileValidationResultEnum SmartChargingHandler::validate_tx_profile(const Char
return ProfileValidationResultEnum::Valid;
}

/* TODO: Implement the following functional requirements:
* - K01.FR.20
* - K01.FR.34
* - K01.FR.43
* - K01.FR.45
* - K01.FR.48
*/
ProfileValidationResultEnum SmartChargingHandler::validate_profile_schedules(const ChargingProfile& profile) const {
auto schedules = profile.chargingSchedule;

for (auto schedule : schedules) {
// A schedule must have at least one chargingSchedulePeriod
if (schedule.chargingSchedulePeriod.empty()) {
return ProfileValidationResultEnum::ChargingProfileNoChargingSchedulePeriods;
}

auto charging_schedule_period = schedule.chargingSchedulePeriod[0];

for (auto i = 0; i < schedule.chargingSchedulePeriod.size(); i++) {
// K01.FR.19
if (charging_schedule_period.numberPhases != 1 && charging_schedule_period.phaseToUse.has_value()) {
return ProfileValidationResultEnum::ChargingSchedulePeriodInvalidPhaseToUse;
}

// K01.FR.31
if (i == 0 && charging_schedule_period.startPeriod != 0) {
return ProfileValidationResultEnum::ChargingProfileFirstStartScheduleIsNotZero;
// K01.FR.35
} else if (i != 0) {
auto next_charging_schedule_period = schedule.chargingSchedulePeriod[i];
if (next_charging_schedule_period.startPeriod <= charging_schedule_period.startPeriod) {
return ProfileValidationResultEnum::ChargingSchedulePeriodsOutOfOrder;
} else {
charging_schedule_period = next_charging_schedule_period;
}
}
}

// K01.FR.40
if (profile.chargingProfileKind != ChargingProfileKindEnum::Relative && !schedule.startSchedule.has_value()) {
return ProfileValidationResultEnum::ChargingProfileMissingRequiredStartSchedule;
// K01.FR.41
} else if (profile.chargingProfileKind == ChargingProfileKindEnum::Relative &&
schedule.startSchedule.has_value()) {
return ProfileValidationResultEnum::ChargingProfileExtraneousStartSchedule;
}
}

return ProfileValidationResultEnum::Valid;
}

void SmartChargingHandler::add_profile(const ChargingProfile& profile) {
charging_profiles.push_back(profile);
}
Expand Down
128 changes: 125 additions & 3 deletions tests/lib/ocpp/v201/test_smart_charging_handler.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "date/tz.h"
#include "ocpp/v201/ctrlr_component_variables.hpp"
#include "ocpp/v201/device_model_storage_sqlite.hpp"
#include "ocpp/v201/ocpp_types.hpp"
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <filesystem>
Expand All @@ -18,6 +19,7 @@
#include <optional>

#include <sstream>
#include <vector>

namespace ocpp::v201 {

Expand Down Expand Up @@ -47,11 +49,60 @@ class ChargepointTestFixtureV201 : public testing::Test {
};
}

ChargingProfile create_tx_profile(ChargingSchedule charging_schedule, std::string transaction_id,
int stack_level = 1) {
ChargingSchedule create_charge_schedule(ChargingRateUnitEnum charging_rate_unit,
std::vector<ChargingSchedulePeriod> charging_schedule_period,
std::optional<ocpp::DateTime> start_schedule = std::nullopt) {
int32_t id;
std::optional<CustomData> custom_data;
std::optional<int32_t> duration;
std::optional<float> min_charging_rate;
std::optional<SalesTariff> sales_tariff;

return ChargingSchedule{
id,
charging_rate_unit,
charging_schedule_period,
custom_data,
start_schedule,
duration,
min_charging_rate,
sales_tariff,
};
}

std::vector<ChargingSchedulePeriod> create_charging_schedule_periods(int32_t start_period) {
auto charging_schedule_period = ChargingSchedulePeriod{
.startPeriod = start_period,
};

return {charging_schedule_period};
}

std::vector<ChargingSchedulePeriod> create_charging_schedule_periods(std::vector<int32_t> start_periods) {
auto charging_schedule_periods = std::vector<ChargingSchedulePeriod>();
for (auto start_period : start_periods) {
auto charging_schedule_period = ChargingSchedulePeriod{
.startPeriod = start_period,
};
charging_schedule_periods.push_back(charging_schedule_period);
}

return charging_schedule_periods;
}

std::vector<ChargingSchedulePeriod>
create_charging_schedule_periods_with_phases(int32_t start_period, int32_t numberPhases, int32_t phaseToUse) {
auto charging_schedule_period =
ChargingSchedulePeriod{.startPeriod = start_period, .numberPhases = numberPhases, .phaseToUse = phaseToUse};

return {charging_schedule_period};
}

ChargingProfile
create_tx_profile(ChargingSchedule charging_schedule, std::string transaction_id, int stack_level = 1,
ChargingProfileKindEnum charging_profile_kind = ChargingProfileKindEnum::Absolute) {
auto charging_profile_id = 1;
auto charging_profile_purpose = ChargingProfilePurposeEnum::TxProfile;
auto charging_profile_kind = ChargingProfileKindEnum::Absolute;
auto recurrency_kind = RecurrencyKindEnum::Daily;
std::vector<ChargingSchedule> charging_schedules = {charging_schedule};
return ChargingProfile{.id = charging_profile_id,
Expand Down Expand Up @@ -230,4 +281,75 @@ TEST_F(ChargepointTestFixtureV201,
EXPECT_THAT(sut, testing::Eq(ProfileValidationResultEnum::Valid));
}

TEST_F(ChargepointTestFixtureV201, K01FR19_NumberPhasesOtherThan1AndPhaseToUseSet_ThenProfileInvalid) {
auto periods = create_charging_schedule_periods_with_phases(0, 0, 1);
auto profile = create_tx_profile(
create_charge_schedule(ChargingRateUnitEnum::A, periods, ocpp::DateTime("2024-01-17T17:00:00")), uuid(), 1,
ChargingProfileKindEnum::Relative);

auto sut = handler.validate_profile_schedules(profile);

EXPECT_THAT(sut, testing::Eq(ProfileValidationResultEnum::ChargingSchedulePeriodInvalidPhaseToUse));
}

TEST_F(ChargepointTestFixtureV201, K01_IfChargingSchedulePeriodsAreMissing_ThenProfileIsInvalid) {
auto profile = create_tx_profile(create_charge_schedule(ChargingRateUnitEnum::A), uuid());

auto sut = handler.validate_profile_schedules(profile);

EXPECT_THAT(sut, testing::Eq(ProfileValidationResultEnum::ChargingProfileNoChargingSchedulePeriods));
}

TEST_F(ChargepointTestFixtureV201, K01FR31_IfStartPeriodOfFirstChargingSchedulePeriodIsNotZero_ThenProfileIsInvalid) {
auto periods = create_charging_schedule_periods(1);
auto profile = create_tx_profile(create_charge_schedule(ChargingRateUnitEnum::A, periods), uuid());

auto sut = handler.validate_profile_schedules(profile);

EXPECT_THAT(sut, testing::Eq(ProfileValidationResultEnum::ChargingProfileFirstStartScheduleIsNotZero));
}

TEST_F(ChargepointTestFixtureV201, K01FR35_IfChargingSchedulePeriodsAreNotInChonologicalOrder_ThenProfileIsInvalid) {
auto periods = create_charging_schedule_periods({0, 2, 1});
auto profile = create_tx_profile(create_charge_schedule(ChargingRateUnitEnum::A, periods), uuid());

auto sut = handler.validate_profile_schedules(profile);

EXPECT_THAT(sut, testing::Eq(ProfileValidationResultEnum::ChargingSchedulePeriodsOutOfOrder));
}

TEST_F(ChargepointTestFixtureV201,
K01FR40_IfChargingProfileKindIsAbsoluteAndStartScheduleDoesNotExist_ThenProfileIsInvalid) {
auto periods = create_charging_schedule_periods(0);
auto profile = create_tx_profile(create_charge_schedule(ChargingRateUnitEnum::A, periods), uuid(), 1,
ChargingProfileKindEnum::Absolute);

auto sut = handler.validate_profile_schedules(profile);

EXPECT_THAT(sut, testing::Eq(ProfileValidationResultEnum::ChargingProfileMissingRequiredStartSchedule));
}

TEST_F(ChargepointTestFixtureV201,
K01FR40_IfChargingProfileKindIsRecurringAndStartScheduleDoesNotExist_ThenProfileIsInvalid) {
auto periods = create_charging_schedule_periods(0);
auto profile = create_tx_profile(create_charge_schedule(ChargingRateUnitEnum::A, periods), uuid(), 1,
ChargingProfileKindEnum::Recurring);

auto sut = handler.validate_profile_schedules(profile);

EXPECT_THAT(sut, testing::Eq(ProfileValidationResultEnum::ChargingProfileMissingRequiredStartSchedule));
}

TEST_F(ChargepointTestFixtureV201,
K01FR41_IfChargingProfileKindIsRelativeAndStartScheduleDoesExist_ThenProfileIsInvalid) {
auto periods = create_charging_schedule_periods(0);
auto profile = create_tx_profile(
create_charge_schedule(ChargingRateUnitEnum::A, periods, ocpp::DateTime("2024-01-17T17:00:00")), uuid(), 1,
ChargingProfileKindEnum::Relative);

auto sut = handler.validate_profile_schedules(profile);

EXPECT_THAT(sut, testing::Eq(ProfileValidationResultEnum::ChargingProfileExtraneousStartSchedule));
}

} // namespace ocpp::v201
Loading