Skip to content

Commit

Permalink
smart_charging: Add validation for profile schedules
Browse files Browse the repository at this point in the history
Implements:

* K01.FR.19
* K01.FR.31
* K01.FR.35
* K01.FR.40
* K01.FR.41

Does not implement:

* K01.FR.20
* K01.FR.34
* K01.FR.43
* K01.FR.45
* K01.FR.48

These functional requirements rely on functionality
in DeviceModel, the EVSE, or message handling that
is not currently implemented in Everest.

Co-authored-by: Coury Richards <[email protected]>
Signed-off-by: Christopher Davis <[email protected]>
  • Loading branch information
christopher-davis-afs and couryrr-afs committed Mar 8, 2024
1 parent 49f4725 commit 233984a
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 4 deletions.
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

0 comments on commit 233984a

Please sign in to comment.