Skip to content

Commit

Permalink
Implement SetChargingProfileRequest and basic database storage/loading (
Browse files Browse the repository at this point in the history
#711)

* smart_charging: Make profile and evse_id order consistent

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

* charge_point: Make `handle_message` protected

This makes it possible to use from our unit tests.

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

* charge_point: Add SmartChargingHandler

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

* smart_charging: Make SmartChargingHandler mockable

Also add the mock class for it.

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

* charge_point: Handle SetChargingProfileRequest

Add a handler for SetChargingProfileRequest with unit tests that
ensure that we add valid profiles - and only valid profiles.

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

* doc: Update SmartCharging status

Add functional requirements handled by the implementation of
`handle_set_charging_profile_req()` and a few missed FRs covered
by `validate_profile()`.

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

* charge_point: Test that set_charging_profile callback is called

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

* added ability to load profile from database

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

* charge_point: Reject ChargingStationExternalConstraints profiles in SetChargingProfileRequest

Implements K01.FR.22

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

* updated database statements for tests

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

* added columns to database and updated equality checks

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

* smart_charging: Add combined validation and add method

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

* Updated FR45 to use supplyPhases

Signed-off-by: Peter Giavotto <[email protected]>

* database_handler: Correct spelling in database tests

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

* database_handler: Test profile equality

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

* database_handler: Rename function to get all profiles grouped by evse id

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

* moved charing profile equality to test since it is not a full comparison

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

* removed duplicate include

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

---------

Signed-off-by: Christopher Davis <[email protected]>
Signed-off-by: Coury Richards <[email protected]>
Signed-off-by: Peter Giavotto <[email protected]>
Co-authored-by: Coury Richards <[email protected]>
Co-authored-by: Peter Giavotto <[email protected]>
  • Loading branch information
3 people authored Aug 9, 2024
1 parent 8476c0d commit 1c688a6
Show file tree
Hide file tree
Showing 16 changed files with 890 additions and 68 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE CHARGING_PROFILES;
7 changes: 7 additions & 0 deletions config/v201/core_migrations/5_up-charging_profiles_db.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CREATE TABLE CHARGING_PROFILES (
ID INT PRIMARY KEY NOT NULL,
EVSE_ID INT NOT NULL,
STACK_LEVEL INT NOT NULL,
CHARGING_PROFILE_PURPOSE TEXT NOT NULL,
PROFILE TEXT NOT NULL
);
14 changes: 7 additions & 7 deletions doc/ocpp_201_status.md
Original file line number Diff line number Diff line change
Expand Up @@ -1238,29 +1238,29 @@ This document contains the status of which OCPP 2.0.1 numbered functional requir
| K01.FR.06 | 🌐 | |
| K01.FR.07 | ⛽️ | Notified through the `signal_set_charging_profiles` callback. |
| K01.FR.08 | 🌐 | `TxDefaultProfile`s are supported. |
| K01.FR.09 | | |
| K01.FR.09 | | |
| K01.FR.10 || |
| K01.FR.11 | | |
| K01.FR.12 | | |
| K01.FR.13 | | |
| K01.FR.14 || |
| K01.FR.15 || |
| K01.FR.16 | | |
| K01.FR.16 | | |
| K01.FR.17 | | |
| K01.FR.19 | | |
| K01.FR.20 || Suggests `ACPhaseSwitchingSupported` should be per EVSE, conflicting with the rest of the spec. |
| K01.FR.21 | | |
| K01.FR.22 | | |
| K01.FR.26 | | |
| K01.FR.27 | | |
| K01.FR.28 | | |
| K01.FR.26 | | |
| K01.FR.27 | | |
| K01.FR.28 | | |
| K01.FR.29 | | |
| K01.FR.30 | | |
| K01.FR.31 | | |
| K01.FR.32 || |
| K01.FR.33 | | |
| K01.FR.33 | | |
| K01.FR.34 || |
| K01.FR.35 | | |
| K01.FR.35 | | |
| K01.FR.36 || |
| K01.FR.37 | | |
| K01.FR.38 | 🌐 💂 | `ChargingStationMaxProfile`s with `Relative` for `chargingProfileKind` are rejected. |
Expand Down
13 changes: 11 additions & 2 deletions include/ocpp/v201/charge_point.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <ocpp/v201/monitoring_updater.hpp>
#include <ocpp/v201/ocpp_types.hpp>
#include <ocpp/v201/ocsp_updater.hpp>
#include <ocpp/v201/smart_charging.hpp>
#include <ocpp/v201/types.hpp>
#include <ocpp/v201/utils.hpp>

Expand Down Expand Up @@ -52,6 +53,7 @@
#include <ocpp/v201/messages/Reset.hpp>
#include <ocpp/v201/messages/SecurityEventNotification.hpp>
#include <ocpp/v201/messages/SendLocalList.hpp>
#include <ocpp/v201/messages/SetChargingProfile.hpp>
#include <ocpp/v201/messages/SetMonitoringBase.hpp>
#include <ocpp/v201/messages/SetMonitoringLevel.hpp>
#include <ocpp/v201/messages/SetNetworkProfile.hpp>
Expand Down Expand Up @@ -525,8 +527,6 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa
/// device model
void remove_network_connection_profiles_below_actual_security_profile();

void handle_message(const EnhancedMessage<v201::MessageType>& message);

void message_callback(const std::string& message);
void update_aligned_data_interval();

Expand Down Expand Up @@ -724,6 +724,9 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa
void handle_change_availability_req(Call<ChangeAvailabilityRequest> call);
void handle_heartbeat_response(CallResult<HeartbeatResponse> call);

// Functional Block K: Smart Charging
void handle_set_charging_profile_req(Call<SetChargingProfileRequest> call);

// Functional Block L: Firmware management
void handle_firmware_update_req(Call<UpdateFirmwareRequest> call);

Expand Down Expand Up @@ -779,6 +782,12 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa
/// If \param persist is set to true, the change will be persisted across a reboot
void execute_change_availability_request(ChangeAvailabilityRequest request, bool persist);

protected:
std::shared_ptr<SmartChargingHandlerInterface> smart_charging_handler;

void handle_message(const EnhancedMessage<v201::MessageType>& message);
void load_charging_profiles();

public:
/// \brief Construct a new ChargePoint object
/// \param evse_connector_structure Map that defines the structure of EVSE and connectors of the chargepoint. The
Expand Down
14 changes: 14 additions & 0 deletions include/ocpp/v201/database_handler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,20 @@ class DatabaseHandler : public common::DatabaseHandlerCommon {
/// \param transaction_id transaction id of the transaction to clear from.
/// \return true if succeeded
void transaction_delete(const std::string& transaction_id);

/// 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);

/// \brief Deletes the profile with the given \p profile_id
void delete_charging_profile(const int profile_id);

/// \brief Deletes all profiles from table CHARGING_PROFILES
void clear_charging_profiles();

/// \brief Retrieves all ChargingProfiles
virtual std::map<int32_t, std::vector<v201::ChargingProfile>> get_all_charging_profiles_group_by_evse();
};

} // namespace v201
Expand Down
32 changes: 27 additions & 5 deletions include/ocpp/v201/smart_charging.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,28 @@ namespace conversions {
/// \brief Converts the given ProfileValidationResultEnum \p e to human readable string
/// \returns a string representation of the ProfileValidationResultEnum
std::string profile_validation_result_to_string(ProfileValidationResultEnum e);

/// \brief Converts the given ProfileValidationResultEnum \p e to a OCPP reasonCode.
/// \returns a reasonCode
std::string profile_validation_result_to_reason_code(ProfileValidationResultEnum e);
} // namespace conversions

std::ostream& operator<<(std::ostream& os, const ProfileValidationResultEnum validation_result);

class SmartChargingHandlerInterface {
public:
virtual ~SmartChargingHandlerInterface() = default;

virtual SetChargingProfileResponse validate_and_add_profile(ChargingProfile& profile, int32_t evse_id) = 0;

virtual ProfileValidationResultEnum validate_profile(ChargingProfile& profile, int32_t evse_id) = 0;

virtual SetChargingProfileResponse add_profile(ChargingProfile& profile, int32_t evse_id) = 0;
};

/// \brief This class handles and maintains incoming ChargingProfiles and contains the logic
/// to calculate the composite schedules
class SmartChargingHandler {
class SmartChargingHandler : public SmartChargingHandlerInterface {
private:
EvseManagerInterface& evse_manager;
std::shared_ptr<DeviceModel>& device_model;
Expand All @@ -65,19 +80,26 @@ class SmartChargingHandler {
std::map<int32_t, std::vector<ChargingProfile>> charging_profiles;

public:
SmartChargingHandler(EvseManagerInterface& evse_manager, std::shared_ptr<DeviceModel>& device_model);
SmartChargingHandler(EvseManagerInterface& evse_manager, std::shared_ptr<DeviceModel>& device_model,
std::shared_ptr<ocpp::v201::DatabaseHandler> database_handler);

///
/// \brief validates the given \p profile according to the specification,
/// adding it to our stored list of profiles if valid.
///
SetChargingProfileResponse validate_and_add_profile(ChargingProfile& profile, int32_t evse_id) override;

///
/// \brief validates the given \p profile according to the specification.
/// If a profile does not have validFrom or validTo set, we conform the values
/// to a representation that fits the spec.
///
ProfileValidationResultEnum validate_profile(ChargingProfile& profile, int32_t evse_id);
ProfileValidationResultEnum validate_profile(ChargingProfile& profile, int32_t evse_id) override;

///
/// \brief Adds a given \p profile and associated \p evse_id to our stored list of profiles
///
SetChargingProfileResponse add_profile(int32_t evse_id, ChargingProfile& profile);
SetChargingProfileResponse add_profile(ChargingProfile& profile, int32_t evse_id) override;

///
/// \brief Retrieves existing profiles on system.
Expand Down Expand Up @@ -117,7 +139,7 @@ class SmartChargingHandler {
/// \brief Checks a given \p profile and associated \p evse_id validFrom and validTo range
/// This method assumes that the existing profile will have dates set for validFrom and validTo
///
bool is_overlapping_validity_period(int evse_id, const ChargingProfile& profile) const;
bool is_overlapping_validity_period(const ChargingProfile& profile, int32_t evse_id) const;

///
/// \brief Checks a given \p profile does not have an id that conflicts with an existing profile
Expand Down
66 changes: 66 additions & 0 deletions lib/ocpp/v201/charge_point.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ ChargePoint::ChargePoint(const std::map<int32_t, int32_t>& evse_connector_struct
evse_connector_structure, *this->device_model, this->database_handler, component_state_manager,
transaction_meter_value_callback, this->callbacks.pause_charging_callback);

this->smart_charging_handler =
std::make_shared<SmartChargingHandler>(*this->evse_manager, this->device_model, this->database_handler);

// configure logging
this->configure_message_logging_format(message_log_path);

Expand Down Expand Up @@ -218,6 +221,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();
this->start_websocket();

if (this->bootreason == BootReasonEnum::RemoteReset) {
Expand Down Expand Up @@ -1311,6 +1316,9 @@ void ChargePoint::handle_message(const EnhancedMessage<v201::MessageType>& messa
case MessageType::CustomerInformation:
this->handle_customer_information_req(json_message);
break;
case MessageType::SetChargingProfile:
this->handle_set_charging_profile_req(json_message);
break;
case MessageType::SetMonitoringBase:
this->handle_set_monitoring_base_req(json_message);
break;
Expand Down Expand Up @@ -3147,6 +3155,39 @@ void ChargePoint::handle_heartbeat_response(CallResult<HeartbeatResponse> call)
}
}

void ChargePoint::handle_set_charging_profile_req(Call<SetChargingProfileRequest> call) {
EVLOG_debug << "Received SetChargingProfileRequest: " << call.msg << "\nwith messageId: " << call.uniqueId;
auto msg = call.msg;
SetChargingProfileResponse response;
response.status = ChargingProfileStatusEnum::Rejected;

// K01.FR.22: Reject ChargingStationExternalConstraints profiles in SetChargingProfileRequest
if (msg.chargingProfile.chargingProfilePurpose == ChargingProfilePurposeEnum::ChargingStationExternalConstraints) {
response.statusInfo = StatusInfo();
response.statusInfo->reasonCode = "InvalidValue";
response.statusInfo->additionalInfo = "ChargingStationExternalConstraintsInSetChargingProfileRequest";
EVLOG_debug << "Rejecting SetChargingProfileRequest:\n reasonCode: " << response.statusInfo->reasonCode.get()
<< "\nadditionalInfo: " << response.statusInfo->additionalInfo->get();

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

return;
}

response = this->smart_charging_handler->validate_and_add_profile(msg.chargingProfile, msg.evseId);
if (response.status == ChargingProfileStatusEnum::Accepted) {
EVLOG_debug << "Accepting SetChargingProfileRequest";
this->callbacks.set_charging_profiles_callback();
} else {
EVLOG_debug << "Rejecting SetChargingProfileRequest:\n reasonCode: " << response.statusInfo->reasonCode.get()
<< "\nadditionalInfo: " << response.statusInfo->additionalInfo->get();
}

ocpp::CallResult<SetChargingProfileResponse> call_result(response, call.uniqueId);
this->send<SetChargingProfileResponse>(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 Expand Up @@ -3818,6 +3859,31 @@ void ChargePoint::execute_change_availability_request(ChangeAvailabilityRequest
}
}

// K01.27 - load profiles from database
void ChargePoint::load_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) ==
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) {
EVLOG_warning << "Failed database operation for ChargingProfiles: " << e.what();
}
}
}
} catch (const std::exception& e) {
EVLOG_warning << "Unknown error while loading charging profiles from database: " << e.what();
}
}

std::vector<GetVariableResult>
ChargePoint::get_variables(const std::vector<GetVariableData>& get_variable_data_vector) {
std::vector<GetVariableResult> response;
Expand Down
56 changes: 56 additions & 0 deletions lib/ocpp/v201/database_handler.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/message_queue.hpp>
#include <ocpp/v201/database_handler.hpp>
#include <ocpp/v201/types.hpp>
Expand Down Expand Up @@ -719,5 +720,60 @@ void DatabaseHandler::transaction_delete(const std::string& transaction_id) {
}
}

void DatabaseHandler::insert_or_update_charging_profile(const int evse_id, const v201::ChargingProfile& profile) {
// 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)";
auto stmt = this->database->new_statement(sql);

json json_profile(profile);

stmt->bind_int("@id", profile.id);
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));
stmt->bind_text("@profile", json_profile.dump(), SQLiteString::Transient);

if (stmt->step() != SQLITE_DONE) {
throw QueryExecutionException(this->database->get_error_message());
}
}

void 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);

stmt->bind_int("@profile_id", profile_id);
if (stmt->step() != SQLITE_DONE) {
throw QueryExecutionException(this->database->get_error_message());
}
}

void DatabaseHandler::clear_charging_profiles() {
this->database->clear_table("CHARGING_PROFILES");
}

std::map<int32_t, std::vector<v201::ChargingProfile>> DatabaseHandler::get_all_charging_profiles_group_by_evse() {
std::map<int32_t, std::vector<v201::ChargingProfile>> map;

std::string sql = "SELECT EVSE_ID, PROFILE FROM CHARGING_PROFILES";

auto stmt = this->database->new_statement(sql);

while (stmt->step() != SQLITE_DONE) {
auto evse_id = stmt->column_int(0);
auto profile = json::parse(stmt->column_text(1));

auto profiles = map[evse_id];
profiles.emplace_back(profile);

map[evse_id] = profiles;
}

return map;
}

} // namespace v201
} // namespace ocpp
Loading

0 comments on commit 1c688a6

Please sign in to comment.