Skip to content

Commit

Permalink
Feature/K08 Get Composite Schedule (#745)
Browse files Browse the repository at this point in the history
* * Added OCPP 2.0.1 Calculate Composite Schedule functionality and tests
* Added Generic optional type equality function for testing option types
* Create v201/profile.hpp to match file structure used in 1.6 CompositeSchedule work
* Added to_string functions to v201/utils to facilitate easier testing, logging and debugging
* Added OCPP 1.6 test_composite_schedule.cpp suite.
* Added ability to use serialized JSON Profiles scenarios for testing in v1.6 and 2.0.1.
* Added JSON Profiles based on specific testing scenarios for v1.6 and 2.0.1.
* Enabled now working 1.6 Composit Schedule tests
* Added v201/test_profile.cpp suite mirroring but refactoring tests done for v1.6 version
* Removed excessive logging

Signed-off-by: Christoph <[email protected]>

* Added sorting of vectors in SmartChargingTestUtils::get_charging_profiles_from_directory() to fix order issue in tests on CI

Signed-off-by: Christoph <[email protected]>

* Updated READMEs to fix linting issues

Signed-off-by: Christoph <[email protected]>

* Updated READMEs to fix more linting issues

Signed-off-by: Christoph <[email protected]>

* Removed unused LimitStackLevelPair struct caught by linter

Signed-off-by: Christoph <[email protected]>

* Added const to functions as per linter

Signed-off-by: Christoph <[email protected]>

* Added pass by const reference to functions as per linter

Signed-off-by: Christoph <[email protected]>

* Updated status doc

Signed-off-by: Christoph <[email protected]>

* Add get_valid_profiles() to Smart Charging Handler.

When retrieving the valid profiles for calculating composite schedules, this function
returns all of the profiles for a given EVSE ID, as well as the station-wide profiles.
It also ensures that the profiles are valid.

The calculations for composite schedule handle the start and end time, so this function
does not account for it at this stage.

Signed-off-by: Christopher Davis <[email protected]>
Signed-off-by: Gianfranco Berardi <[email protected]>

* added calculation for charging station external constraints.
added tests to cover external constraints.
refactored calculation for both max and external to a method.

Signed-off-by: Coury Richards <[email protected]>

* smart_charging: Make more functions mockable

Make `calculate_composite_schedule()` and `get_valid_profiles()` part
of the `SmartChargingHandlerInterface` so they may be overriden for
mocking.

Signed-off-by: Christopher Davis <[email protected]>

* smart_charging: Take chargingRateUnit as optional in `calculate_composite_schedule()`

The internal functions (and the v1.6 impl) take this as optional, but
we were requiring a value.

Signed-off-by: Christopher Davis <[email protected]>

* charge_point: Handle GetCompositeScheduleRequest

Adds a message handler for GetCompositeScheduleRequest and associated
tests within `test_charge_point.cpp`.

Due to the core functionality being tested within `test_composite_schedule.cpp`,
we primarily test that `get_valid_profiles()` and `calculate_composite_schedule()`
are being called within the right contexts:

* We calculate a composite schedule from a list of valid profiles
* We do not calculate a composite schedule when the EVSE ID is unknown
* We do not calculate a composite schedule when the `chargingRateUnit`
sent in the request is not configured.

Signed-off-by: Christopher Davis <[email protected]>

* doc: Update OCPP v2.0.1 status doc

We now cover:

- K08.FR.03
- K08.FR.04
- K08.FR.05
- K08.FR.07

Signed-off-by: Christopher Davis <[email protected]>

* Resolving PR comments from @Pietfried

Signed-off-by: Christoph <[email protected]>

* Moving location of json profiles as per PR comment

Signed-off-by: Christoph <[email protected]>

* Moving static functions only used in file into anonymous namespace as per PR comments

Signed-off-by: Christoph <[email protected]>

* Moving static functions only used in file into anonymous namespace as per PR comments

Signed-off-by: Christoph <[email protected]>

* Added documentation to test utility function as per PR review

Signed-off-by: Christoph <[email protected]>

* Moving equality operators to smart_charging_test_utils as per PR review

Signed-off-by: Christoph <[email protected]>

* Corrected mistake in Status doc as per PR review

Signed-off-by: Christoph <[email protected]>

* Updated constants as per PR review

Signed-off-by: Christoph <[email protected]>

* Updated profile comments  as per PR review

Signed-off-by: Christoph <[email protected]>

* Moved functions only used in tests to tests  as per PR review

Signed-off-by: Christoph <[email protected]>

* Added link to issue resolved in Case One composite schedule scenario test as per PR review

Signed-off-by: Christoph <[email protected]>

* Removed unused struct as per PR review

Signed-off-by: Christoph <[email protected]>

* Moved functions only used in tests to tests as per PR review

Signed-off-by: Christoph <[email protected]>

* Rolled back accidental changes to 1.6 code

Signed-off-by: Christoph <[email protected]>

* Remove profile scenario not used in PR

Signed-off-by: Christoph <[email protected]>

* Added more detailed Grid foundation test as per PR review.

Signed-off-by: Christoph <[email protected]>

* Fixed CalculateProfileEntryType_Param_Test as per PR review.

Signed-off-by: Christoph <[email protected]>

* Updated CalculateChargingSchedule_Overlap test names  as per PR review.

Signed-off-by: Christoph <[email protected]>

* Updated CalculateChargingScheduleCombined_CombinedOverlapT names  as per PR review.

Signed-off-by: Christoph <[email protected]>

* Moved functions only used in tests to tests as per PR review

Signed-off-by: Christoph <[email protected]>

* Added in grabbing transaction session start for relative profiles as per PR review

Signed-off-by: Christoph <[email protected]>

* Added check for evse_id == 0 as per PR review

Signed-off-by: Christoph <[email protected]>

* Touch to rerun GitHub Actions

Signed-off-by: Christoph <[email protected]>

* Touch to rerun GitHub Actions

Signed-off-by: Christoph <[email protected]>

* Touch to rerun GitHub Actions

Signed-off-by: Christoph <[email protected]>

---------

Signed-off-by: Christoph <[email protected]>
Signed-off-by: Christopher Davis <[email protected]>
Signed-off-by: Gianfranco Berardi <[email protected]>
Signed-off-by: Coury Richards <[email protected]>
Co-authored-by: Gianfranco Berardi <[email protected]>
Co-authored-by: Coury Richards <[email protected]>
Co-authored-by: Christopher Davis <[email protected]>
  • Loading branch information
4 people authored Sep 5, 2024
1 parent 5cf14b3 commit 079e12b
Show file tree
Hide file tree
Showing 68 changed files with 5,528 additions and 10 deletions.
14 changes: 7 additions & 7 deletions doc/ocpp_201_status.md
Original file line number Diff line number Diff line change
Expand Up @@ -1341,13 +1341,13 @@ This document contains the status of which OCPP 2.0.1 numbered functional requir

| ID | Status | Remark |
|-----------|--------|--------|
| K08.FR.01 | | |
| K08.FR.02 | | |
| K08.FR.03 | | |
| K08.FR.04 | | |
| K08.FR.05 | | |
| K08.FR.06 | | |
| K08.FR.07 | | |
| K08.FR.01 | | |
| K08.FR.02 | | |
| K08.FR.03 | | |
| K08.FR.04 | | |
| K08.FR.05 | | |
| K08.FR.06 | | |
| K08.FR.07 | | |

## SmartCharging - Get Charging Profiles

Expand Down
23 changes: 23 additions & 0 deletions include/ocpp/common/constants.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2024 Pionix GmbH and Contributors to EVerest

#include <cstdint>

namespace ocpp {

// Time
constexpr std::int32_t DAYS_PER_WEEK = 7;
constexpr std::int32_t HOURS_PER_DAY = 24;
constexpr std::int32_t SECONDS_PER_HOUR = 3600;
constexpr std::int32_t SECONDS_PER_DAY = 86400;

constexpr float DEFAULT_LIMIT_AMPS = 48.0;
constexpr float DEFAULT_LIMIT_WATTS = 33120.0;
constexpr std::int32_t DEFAULT_AND_MAX_NUMBER_PHASES = 3;
constexpr float LOW_VOLTAGE = 230;

constexpr float NO_LIMIT_SPECIFIED = -1.0;
constexpr std::int32_t NO_START_PERIOD = -1;
constexpr std::int32_t EVSEID_NOT_SET = -1;

} // namespace ocpp
1 change: 1 addition & 0 deletions include/ocpp/common/types.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest

#ifndef OCPP_COMMON_TYPES_HPP
#define OCPP_COMMON_TYPES_HPP

Expand Down
2 changes: 2 additions & 0 deletions include/ocpp/v201/charge_point.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include <ocpp/v201/messages/DeleteCertificate.hpp>
#include <ocpp/v201/messages/GetBaseReport.hpp>
#include <ocpp/v201/messages/GetChargingProfiles.hpp>
#include <ocpp/v201/messages/GetCompositeSchedule.hpp>
#include <ocpp/v201/messages/GetInstalledCertificateIds.hpp>
#include <ocpp/v201/messages/GetLocalListVersion.hpp>
#include <ocpp/v201/messages/GetLog.hpp>
Expand Down Expand Up @@ -729,6 +730,7 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa
void handle_set_charging_profile_req(Call<SetChargingProfileRequest> call);
void handle_clear_charging_profile_req(Call<ClearChargingProfileRequest> call);
void handle_get_charging_profiles_req(Call<GetChargingProfilesRequest> call);
void handle_get_composite_schedule_req(Call<GetCompositeScheduleRequest> call);

// Functional Block L: Firmware management
void handle_firmware_update_req(Call<UpdateFirmwareRequest> call);
Expand Down
92 changes: 92 additions & 0 deletions include/ocpp/v201/profile.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2024 Pionix GmbH and Contributors to EVerest

#include <ocpp/v201/ocpp_types.hpp>

namespace ocpp {
namespace v201 {

/// \brief Returns elements from a specific ChargingProfile and ChargingSchedulePeriod
/// for use in the calculation of the CompositeSchedule within a specific slice
/// of time. These are aggregated by Profile.
/// \param in_start The starting time
/// \param in_duration The duration for the specific slice of time
/// \param in_period the details of this period
/// \param in_profile the charging profile
/// \return an entry with smart charging information for a specific period in time
struct period_entry_t {
void init(const ocpp::DateTime& in_start, int in_duration, const ChargingSchedulePeriod& in_period,
const ChargingProfile& in_profile);
bool validate(const ChargingProfile& profile, const ocpp::DateTime& now);

ocpp::DateTime start;
ocpp::DateTime end;
float limit;
std::optional<std::int32_t> number_phases;
std::optional<std::int32_t> phase_to_use;
std::int32_t stack_level;
ChargingRateUnitEnum charging_rate_unit;
std::optional<float> min_charging_rate;

bool equals(const period_entry_t& other) const {
return (start == other.end) && (end == other.end) && (limit == other.limit) &&
(number_phases == other.number_phases) && (stack_level == other.stack_level) &&
(charging_rate_unit == other.charging_rate_unit) && (min_charging_rate == other.min_charging_rate);
}
};

/// \brief calculate the start times for the profile
/// \param now the current date and time
/// \param end the end of the composite schedule
/// \param session_start optional when the charging session started
/// \param profile the charging profile
/// \return a list of the start times of the profile
std::vector<DateTime> calculate_start(const DateTime& now, const DateTime& end,
const std::optional<DateTime>& session_start, const ChargingProfile& profile);

/// \brief Calculates the period entries based upon the indicated time window for every profile passed in.
/// \param now the current date and time
/// \param end the end of the composite schedule
/// \param session_start optional when the charging session started
/// \param profile the charging profile
/// \param period_index the schedule period index
/// \note used by calculate_profile
/// \return the list of start times
std::vector<period_entry_t> calculate_profile_entry(const DateTime& now, const DateTime& end,
const std::optional<DateTime>& session_start,
const ChargingProfile& profile, std::uint8_t period_index);

/// \brief generate an ordered list of valid schedule periods for the profile
/// \param now the current date and time
/// \param end ignore entries beyond this date and time (i.e. that start after end)
/// \param session_start optional when the charging session started
/// \param profile the charging profile
/// \return a list of profile periods with calculated date & time start and end times
/// \note it is valid for there to be gaps (for recurring profiles)
std::vector<period_entry_t> calculate_profile(const DateTime& now, const DateTime& end,
const std::optional<DateTime>& session_start,
const ChargingProfile& profile);

/// \brief calculate the composite schedule for the list of periods
/// \param combined_schedules the list of periods to build into the schedule
/// \param now the start of the composite schedule
/// \param end the end (i.e. duration) of the composite schedule
/// \param charging_rate_unit the units to use (defaults to Amps)
/// \return the calculated composite schedule
CompositeSchedule calculate_composite_schedule(std::vector<period_entry_t>& combined_schedules, const DateTime& now,
const DateTime& end,
std::optional<ChargingRateUnitEnum> charging_rate_unit);

/// \brief calculate the combined composite schedule from all of the different types of
/// CompositeSchedules
/// \param charge_point_max the composite schedule for ChargePointMax profiles
/// \param tx_default the composite schedule for TxDefault profiles
/// \param tx the composite schedule for Tx profiles
/// \return the calculated combined composite schedule
/// \note all composite schedules must have the same units configured
CompositeSchedule calculate_composite_schedule(const CompositeSchedule& charging_station_external_constraints,
const CompositeSchedule& charging_station_max,
const CompositeSchedule& tx_default, const CompositeSchedule& tx);

} // namespace v201
} // namespace ocpp
19 changes: 19 additions & 0 deletions include/ocpp/v201/smart_charging.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ class SmartChargingHandlerInterface {

virtual std::vector<ReportedChargingProfile>
get_reported_profiles(const GetChargingProfilesRequest& request) const = 0;
virtual std::vector<ChargingProfile> get_valid_profiles(int32_t evse_id) = 0;

virtual CompositeSchedule calculate_composite_schedule(std::vector<ChargingProfile>& valid_profiles,
const ocpp::DateTime& start_time,
const ocpp::DateTime& end_time, const int32_t evse_id,
std::optional<ChargingRateUnitEnum> charging_rate_unit) = 0;
};

/// \brief This class handles and maintains incoming ChargingProfiles and contains the logic
Expand Down Expand Up @@ -137,6 +143,18 @@ class SmartChargingHandler : public SmartChargingHandlerInterface {
std::vector<ReportedChargingProfile>
get_reported_profiles(const GetChargingProfilesRequest& request) const override;

/// \brief Retrieves all profiles that should be considered for calculating the composite schedule.
///
std::vector<ChargingProfile> get_valid_profiles(int32_t evse_id) override;

///
/// \brief Calculates the composite schedule for the given \p valid_profiles and the given \p connector_id
///
CompositeSchedule calculate_composite_schedule(std::vector<ChargingProfile>& valid_profiles,
const ocpp::DateTime& start_time, const ocpp::DateTime& end_time,
const int32_t evse_id,
std::optional<ChargingRateUnitEnum> charging_rate_unit) override;

protected:
///
/// \brief validates the existence of the given \p evse_id according to the specification
Expand Down Expand Up @@ -182,6 +200,7 @@ class SmartChargingHandler : public SmartChargingHandlerInterface {
std::vector<ChargingProfile> get_station_wide_profiles() const;
std::vector<ChargingProfile> get_evse_specific_tx_default_profiles() const;
std::vector<ChargingProfile> get_station_wide_tx_default_profiles() const;
std::vector<ChargingProfile> get_valid_profiles_for_evse(int32_t evse_id);
void conform_validity_periods(ChargingProfile& profile) const;
CurrentPhaseType get_current_phase_type(const std::optional<EvseInterface*> evse_opt) const;
};
Expand Down
1 change: 1 addition & 0 deletions lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ if(LIBOCPP_ENABLE_V201)
ocpp/v201/notify_report_requests_splitter.cpp
ocpp/v201/message_queue.cpp
ocpp/v201/ocpp_enums.cpp
ocpp/v201/profile.cpp
ocpp/v201/ocpp_types.cpp
ocpp/v201/ocsp_updater.cpp
ocpp/v201/monitoring_updater.cpp
Expand Down
39 changes: 39 additions & 0 deletions lib/ocpp/v201/charge_point.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1178,6 +1178,9 @@ void ChargePoint::handle_message(const EnhancedMessage<v201::MessageType>& messa
case MessageType::GetChargingProfiles:
this->handle_get_charging_profiles_req(json_message);
break;
case MessageType::GetCompositeSchedule:
this->handle_get_composite_schedule_req(json_message);
break;
case MessageType::SetMonitoringBase:
this->handle_set_monitoring_base_req(json_message);
break;
Expand Down Expand Up @@ -3181,6 +3184,42 @@ void ChargePoint::handle_get_charging_profiles_req(Call<GetChargingProfilesReque
}
}

void ChargePoint::handle_get_composite_schedule_req(Call<GetCompositeScheduleRequest> call) {
EVLOG_debug << "Received GetCompositeScheduleRequest: " << call.msg << "\nwith messageId: " << call.uniqueId;
auto msg = call.msg;
GetCompositeScheduleResponse response;
response.status = GenericStatusEnum::Rejected;

auto supported_charging_rate_units =
this->device_model->get_value<std::string>(ControllerComponentVariables::ChargingScheduleChargingRateUnit);
auto unit_supported = supported_charging_rate_units.find(conversions::charging_rate_unit_enum_to_string(
msg.chargingRateUnit.value())) != supported_charging_rate_units.npos;

// K01.FR.05 & K01.FR.07
if (this->evse_manager->does_evse_exist(msg.evseId) && unit_supported) {
auto start_time = ocpp::DateTime();
auto end_time = ocpp::DateTime(start_time.to_time_point() + std::chrono::seconds(msg.duration));

std::vector<ChargingProfile> valid_profiles = this->smart_charging_handler->get_valid_profiles(msg.evseId);
auto schedule = this->smart_charging_handler->calculate_composite_schedule(valid_profiles, start_time, end_time,
msg.evseId, msg.chargingRateUnit);

response.status = GenericStatusEnum::Accepted;
response.schedule = schedule;
} else {
auto reason = unit_supported ? ProfileValidationResultEnum::EvseDoesNotExist
: ProfileValidationResultEnum::ChargingScheduleChargingRateUnitUnsupported;
response.statusInfo = StatusInfo();
response.statusInfo->reasonCode = conversions::profile_validation_result_to_reason_code(reason);
response.statusInfo->additionalInfo = conversions::profile_validation_result_to_string(reason);
EVLOG_debug << "Rejecting SetChargingProfileRequest:\n reasonCode: " << response.statusInfo->reasonCode.get()
<< "\nadditionalInfo: " << response.statusInfo->additionalInfo->get();
}

ocpp::CallResult<GetCompositeScheduleResponse> call_result(response, call.uniqueId);
this->send<GetCompositeScheduleResponse>(call_result);
}

void ChargePoint::handle_firmware_update_req(Call<UpdateFirmwareRequest> call) {
EVLOG_debug << "Received UpdateFirmwareRequest: " << call.msg << "\nwith messageId: " << call.uniqueId;
if (call.msg.firmware.signingCertificate.has_value() or call.msg.firmware.signature.has_value()) {
Expand Down
Loading

0 comments on commit 079e12b

Please sign in to comment.