Skip to content

Commit

Permalink
Merge branch 'main' into feature/327-use-case-h01-reservation
Browse files Browse the repository at this point in the history
  • Loading branch information
maaikez committed Nov 12, 2024
2 parents 045bdaf + af3d08e commit 04b2c15
Show file tree
Hide file tree
Showing 27 changed files with 414 additions and 66 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.14)

project(ocpp
VERSION 0.18.0
VERSION 0.19.0
DESCRIPTION "A C++ implementation of the Open Charge Point Protocol"
LANGUAGES CXX
)
Expand Down
5 changes: 5 additions & 0 deletions config/v16/profile_schemas/Internal.json
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,11 @@
"TxProfile"
]
},
"IgnoredProfilePurposesOffline": {
"$comment": "Allows configuration of comma seperated list of ChargingProfilePurposes that are ignored in the composite schedule caluclation when offline.",
"type": "string",
"readOnly": false
},
"MaxCompositeScheduleDuration": {
"$comment": "Maximum duration in seconds of GetCompositeSchedule.req. For GetCompositeSchedule.req with a greater duration the schedule for only the MaxCompositeScheduleDuration will be calculated",
"type": "integer",
Expand Down
17 changes: 17 additions & 0 deletions config/v201/component_config/standardized/SmartChargingCtrlr.json
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,23 @@
"description": "Supply voltage of the grid. This value is only used in case a conversion between smart charging amp and watt limits is required"
}
},
"IgnoredProfilePurposesOffline": {
"variable_name": "IgnoredProfilePurposesOffline",
"characteristics": {
"valuesList": "ChargingStationMaxProfile,TxDefaultProfile,TxProfile",
"supportsMonitoring": true,
"dataType": "MemberList"
},
"attributes": [
{
"type": "Actual",
"mutability": "ReadWrite",
"value": ""
}
],
"description": "Allows configuration of comma seperated list of ChargingProfilePurposes that are ignored in the composite schedule caluclation when offline.",
"type": "string"
},
"required": [
"ChargingProfileMaxStackLevel",
"ChargingScheduleChargingRateUnit",
Expand Down
2 changes: 1 addition & 1 deletion dependencies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ date:
options: ["BUILD_TZ_LIB ON", "HAS_REMOTE_API 0", "USE_AUTOLOAD 0", "USE_SYSTEM_TZ_DB ON"]
libevse-security:
git: https://github.com/EVerest/libevse-security.git
git_tag: v0.8.0
git_tag: v0.9.1
libwebsockets:
git: https://github.com/warmcat/libwebsockets.git
git_tag: v4.3.3
Expand Down
4 changes: 4 additions & 0 deletions include/ocpp/common/schemas.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ class Schemas {
public:
/// \brief Creates a new Schemas object looking for the root schema file in relation to the provided \p main_dir
explicit Schemas(fs::path schemas_path);
/// \brief Creates a new Schemas object using the supplied JSON schema
explicit Schemas(const json& schema_in);
/// \brief Creates a new Schemas object using the supplied JSON schema
explicit Schemas(json&& schema_in);

/// \brief Provides the config schema
/// \returns the config schema as as json object
Expand Down
3 changes: 3 additions & 0 deletions include/ocpp/v16/charge_point_configuration.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ class ChargePointConfiguration {
KeyValue getLogRotationMaximumFileCountKeyValue();
std::vector<ChargingProfilePurposeType> getSupportedChargingProfilePurposeTypes();
KeyValue getSupportedChargingProfilePurposeTypesKeyValue();
std::vector<ChargingProfilePurposeType> getIgnoredProfilePurposesOffline();
std::optional<KeyValue> getIgnoredProfilePurposesOfflineKeyValue();
bool setIgnoredProfilePurposesOffline(const std::string& ignored_profile_purposes_offline);
int32_t getMaxCompositeScheduleDuration();
KeyValue getMaxCompositeScheduleDurationKeyValue();
std::optional<int32_t> getCompositeScheduleDefaultLimitAmps();
Expand Down
9 changes: 5 additions & 4 deletions include/ocpp/v16/smart_charging.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,12 @@ class SmartChargingHandler {
void clear_all_profiles();

///
/// \brief Gets all valid profiles within the given absoulte \p start_time and absolute \p end_time for the given \p
/// connector_id
/// \brief Gets all valid profiles within the given absoulte \p start_time and absolute \p end_time for the given
/// \p connector_id . Only profiles that are not contained in \p purposes_to_ignore are included in the response.
///
std::vector<ChargingProfile> get_valid_profiles(const ocpp::DateTime& start_time, const ocpp::DateTime& end_time,
const int connector_id);
std::vector<ChargingProfile>
get_valid_profiles(const ocpp::DateTime& start_time, const ocpp::DateTime& end_time, const int connector_id,
const std::set<ChargingProfilePurposeType>& purposes_to_ignore = {});
///
/// \brief Calculates the enhanced composite schedule for the given \p valid_profiles and the given \p connector_id
///
Expand Down
4 changes: 3 additions & 1 deletion include/ocpp/v201/charge_point.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,9 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa

void trigger_authorization_cache_cleanup();
void cache_cleanup_handler();
GetCompositeScheduleResponse get_composite_schedule_internal(const GetCompositeScheduleRequest& request);
GetCompositeScheduleResponse
get_composite_schedule_internal(const GetCompositeScheduleRequest& request,
const std::set<ChargingProfilePurposeEnum>& profiles_to_ignore = {});

/// \brief Removes all network connection profiles below the actual security profile and stores the new list in the
/// device model
Expand Down
3 changes: 2 additions & 1 deletion include/ocpp/v201/connectivity_manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,8 @@ class ConnectivityManager {
void next_network_configuration_priority();

/// @brief Cache all the network connection profiles. Must be called once during initialization
void cache_network_connection_profiles();
/// \return True if the network connection profiles could be cached, else False.
bool cache_network_connection_profiles();
};

} // namespace v201
Expand Down
1 change: 1 addition & 0 deletions include/ocpp/v201/ctrlr_component_variables.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ extern const RequiredComponentVariable& SupplyVoltage;
extern const ComponentVariable& Phases3to1;
extern const RequiredComponentVariable& ChargingProfileMaxStackLevel;
extern const RequiredComponentVariable& ChargingScheduleChargingRateUnit;
extern const ComponentVariable& IgnoredProfilePurposesOffline;
extern const ComponentVariable& TariffCostCtrlrAvailableTariff;
extern const ComponentVariable& TariffCostCtrlrAvailableCost;
extern const RequiredComponentVariable& TariffCostCtrlrCurrency;
Expand Down
13 changes: 9 additions & 4 deletions include/ocpp/v201/smart_charging.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ 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 std::vector<ChargingProfile>
get_valid_profiles(int32_t evse_id, const std::set<ChargingProfilePurposeEnum>& purposes_to_ignore = {}) = 0;

virtual CompositeSchedule calculate_composite_schedule(std::vector<ChargingProfile>& valid_profiles,
const ocpp::DateTime& start_time,
Expand Down Expand Up @@ -153,9 +154,12 @@ 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.
/// \brief Retrieves all profiles that should be considered for calculating the composite schedule. Only profiles
/// that belong to the given \p evse_id and that are not contained in \p purposes_to_ignore are included in the
/// response.
///
std::vector<ChargingProfile> get_valid_profiles(int32_t evse_id) override;
std::vector<ChargingProfile>
get_valid_profiles(int32_t evse_id, const std::set<ChargingProfilePurposeEnum>& purposes_to_ignore = {}) override;

///
/// \brief Calculates the composite schedule for the given \p valid_profiles and the given \p connector_id
Expand Down Expand Up @@ -215,7 +219,8 @@ class SmartChargingHandler : public SmartChargingHandlerInterface {
private:
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);
std::vector<ChargingProfile>
get_valid_profiles_for_evse(int32_t evse_id, const std::set<ChargingProfilePurposeEnum>& purposes_to_ignore = {});
void conform_schedule_number_phases(int32_t profile_id, ChargingSchedulePeriod& charging_schedule_period) const;
void conform_validity_periods(ChargingProfile& profile) const;
CurrentPhaseType get_current_phase_type(const std::optional<EvseInterface*> evse_opt) const;
Expand Down
5 changes: 5 additions & 0 deletions include/ocpp/v201/utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#ifndef V201_UTILS_HPP
#define V201_UTILS_HPP

#include <set>

#include <ocpp/v201/ocpp_types.hpp>
#include <ocpp/v201/types.hpp>
namespace ocpp {
Expand Down Expand Up @@ -71,6 +73,9 @@ std::optional<float> get_total_power_active_import(const MeterValue& meter_value
/// \brief Determines if a given \p security_event is critical as defined in the OCPP 2.0.1 appendix
bool is_critical(const std::string& security_event);

/// \brief Converts the given \p csl of ChargingProfilePurpose strings into a std::set<ChargingProfilePurposeEnum>
std::set<ChargingProfilePurposeEnum> get_purposes_to_ignore(const std::string& csl, const bool is_offline);

} // namespace utils
} // namespace v201
} // namespace ocpp
Expand Down
4 changes: 2 additions & 2 deletions lib/ocpp/common/ocpp_logging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ LogRotationStatus MessageLogging::rotate_log(const std::string& file_basename) {
std::filesystem::rename(file, new_file_name);
EVLOG_info << "Renaming: " << file.string() << " -> " << new_file_name.string();
if (status == LogRotationStatus::NotRotated) {
status == LogRotationStatus::Rotated;
status = LogRotationStatus::Rotated;
}
} else {
// try parsing the .x extension and log an error if this was not possible
Expand All @@ -210,7 +210,7 @@ LogRotationStatus MessageLogging::rotate_log(const std::string& file_basename) {
EVLOG_info << "Renaming: " << file.string() << " -> " << new_file_name.string();
std::filesystem::rename(file, new_file_name);
if (status == LogRotationStatus::NotRotated) {
status == LogRotationStatus::Rotated;
status = LogRotationStatus::Rotated;
}
}

Expand Down
12 changes: 12 additions & 0 deletions lib/ocpp/common/schemas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ Schemas::Schemas(fs::path schemas_path) : schemas_path(schemas_path) {
}
}

Schemas::Schemas(const json& schema_in) : schema(schema_in) {
validator = std::make_shared<json_validator>(
[this](const json_uri& uri, json& schema) { this->loader(uri, schema); }, Schemas::format_checker);
validator->set_root_schema(this->schema);
}

Schemas::Schemas(json&& schema_in) : schema(std::move(schema_in)) {
validator = std::make_shared<json_validator>(
[this](const json_uri& uri, json& schema) { this->loader(uri, schema); }, Schemas::format_checker);
validator->set_root_schema(this->schema);
}

void Schemas::load_root_schema() {
fs::path config_schema_path = this->schemas_path / "Config.json";

Expand Down
6 changes: 4 additions & 2 deletions lib/ocpp/common/websocket/websocket_libwebsockets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ static int callback_minimal(struct lws* wsi, enum lws_callback_reasons reason, v
if (owner not_eq nullptr) {
return data->get_owner()->process_callback(wsi, static_cast<int>(reason), user, in, len);
} else {
EVLOG_warning << "callback_minimal called, but data->owner is nullptr. Reason: " << reason;
EVLOG_debug << "callback_minimal called, but data->owner is nullptr. Reason: " << reason;
}
}
}
Expand Down Expand Up @@ -838,7 +838,9 @@ void WebsocketLibwebsockets::reconnect(long delay) {
}

void WebsocketLibwebsockets::close(const WebsocketCloseReason code, const std::string& reason) {
EVLOG_info << "Closing websocket: " << reason;
if (!reason.empty()) {
EVLOG_info << "Closing websocket: " << reason;
}

{
std::lock_guard<std::mutex> lk(this->reconnect_mutex);
Expand Down
97 changes: 89 additions & 8 deletions lib/ocpp/v16/charge_point_configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -341,11 +341,52 @@ std::vector<ChargingProfilePurposeType> ChargePointConfiguration::getSupportedCh
std::vector<ChargingProfilePurposeType> supported_purpose_types;
const auto str_list = this->config["Internal"]["SupportedChargingProfilePurposeTypes"];
for (const auto& str : str_list) {
supported_purpose_types.push_back(conversions::string_to_charging_profile_purpose_type(str));
try {
supported_purpose_types.push_back(conversions::string_to_charging_profile_purpose_type(str));
} catch (const StringToEnumException& e) {
EVLOG_warning << "Could not convert element of SupportedChargingProfilePurposeTypes: " << str;
}
}
return supported_purpose_types;
}

std::vector<ChargingProfilePurposeType> ChargePointConfiguration::getIgnoredProfilePurposesOffline() {
if (not this->config["Internal"].contains("IgnoredProfilePurposesOffline")) {
return {};
}

std::vector<ChargingProfilePurposeType> purpose_types;
const auto str_list = split_string(this->config["Internal"]["IgnoredProfilePurposesOffline"], ',');
for (const auto& str : str_list) {
try {
purpose_types.push_back(conversions::string_to_charging_profile_purpose_type(str));
} catch (const StringToEnumException& e) {
EVLOG_warning << "Could not convert element of IgnoredProfilePurposesOffline: " << str;
}
}
return purpose_types;
}

bool ChargePointConfiguration::setIgnoredProfilePurposesOffline(const std::string& ignored_profile_purposes_offline) {
if (this->getIgnoredProfilePurposesOfflineKeyValue() == std::nullopt) {
return false;
}

const auto profile_purposes = split_string(ignored_profile_purposes_offline, ',');
for (const auto purpose : profile_purposes) {
try {
conversions::string_to_charging_profile_purpose_type(purpose);
} catch (const StringToEnumException& e) {
EVLOG_warning << "Could not convert element of IgnoredProfilePurposesOffline: " << purpose;
return false;
}
}

this->config["Internal"]["IgnoredProfilePurposesOffline"] = ignored_profile_purposes_offline;
this->setInUserConfig("Internal", "IgnoredProfilePurposesOffline", ignored_profile_purposes_offline);
return true;
}

int32_t ChargePointConfiguration::getMaxCompositeScheduleDuration() {
return this->config["Internal"]["MaxCompositeScheduleDuration"];
}
Expand Down Expand Up @@ -664,7 +705,31 @@ KeyValue ChargePointConfiguration::getSupportedChargingProfilePurposeTypesKeyVal
kv.readonly = true;
std::vector<std::string> purpose_types;
for (const auto& entry : this->getSupportedChargingProfilePurposeTypes()) {
purpose_types.push_back(conversions::charging_profile_purpose_type_to_string(entry));
try {
purpose_types.push_back(conversions::charging_profile_purpose_type_to_string(entry));
} catch (const EnumToStringException& e) {
EVLOG_warning << "Could not convert element of SupportedChargingProfilePurposeTypes to string";
}
}
kv.value.emplace(to_csl(purpose_types));
return kv;
}

std::optional<KeyValue> ChargePointConfiguration::getIgnoredProfilePurposesOfflineKeyValue() {
if (not this->config["Internal"].contains("IgnoredProfilePurposesOffline")) {
return std::nullopt;
}

KeyValue kv;
kv.key = "IgnoredProfilePurposesOffline";
kv.readonly = false;
std::vector<std::string> purpose_types;
for (const auto& entry : this->getIgnoredProfilePurposesOffline()) {
try {
purpose_types.push_back(conversions::charging_profile_purpose_type_to_string(entry));
} catch (const EnumToStringException& e) {
EVLOG_warning << "Could not convert element of IgnoredProfilePurposesOffline to string";
}
}
kv.value.emplace(to_csl(purpose_types));
return kv;
Expand Down Expand Up @@ -2858,20 +2923,28 @@ ConfigurationStatus ChargePointConfiguration::setCustomKey(CiString<50> key, CiS
}
std::lock_guard<std::recursive_mutex> lock(this->configuration_mutex);
try {
const auto type = this->custom_schema["properties"][key]["type"];
const auto type = custom_schema["properties"][key]["type"];
json new_value;
if (type == "integer") {
this->config["Custom"][key] = std::stoi(value.get());
new_value = std::stoi(value.get());
} else if (type == "number") {
this->config["Custom"][key] = std::stof(value.get());
new_value = std::stof(value.get());
} else if (type == "string" or type == "array") {
this->config["Custom"][key] = value.get();
new_value = value.get();
} else if (type == "boolean") {
this->config["Custom"][key] = ocpp::conversions::string_to_bool(value.get());
new_value = ocpp::conversions::string_to_bool(value.get());
} else {
return ConfigurationStatus::Rejected;
}

// validate the updated key against the schema
Schemas schema(custom_schema);
json model;
model[key] = new_value;
schema.get_validator()->validate(model); // throws exception on error
config["Custom"][key] = new_value;
} catch (const std::exception& e) {
EVLOG_warning << "Could not set custom configuration key";
EVLOG_warning << "Could not set custom configuration key: " << e.what();
return ConfigurationStatus::Rejected;
}

Expand Down Expand Up @@ -2942,6 +3015,9 @@ std::optional<KeyValue> ChargePointConfiguration::get(CiString<50> key) {
if (key == "SupportedChargingProfilePurposeTypes") {
return this->getSupportedChargingProfilePurposeTypesKeyValue();
}
if (key == "IgnoredProfilePurposesOffline") {
return this->getIgnoredProfilePurposesOfflineKeyValue();
}
if (key == "MaxCompositeScheduleDuration") {
return this->getMaxCompositeScheduleDurationKeyValue();
}
Expand Down Expand Up @@ -3263,6 +3339,11 @@ std::vector<KeyValue> ChargePointConfiguration::get_all_key_value() {

ConfigurationStatus ChargePointConfiguration::set(CiString<50> key, CiString<500> value) {
std::lock_guard<std::recursive_mutex> lock(this->configuration_mutex);
if (key == "IgnoredProfilePurposesOffline") {
if (this->setIgnoredProfilePurposesOffline(value) == false) {
return ConfigurationStatus::Rejected;
}
}
if (key == "AllowOfflineTxForUnknownId") {
if (this->getAllowOfflineTxForUnknownId() == std::nullopt) {
return ConfigurationStatus::NotSupported;
Expand Down
Loading

0 comments on commit 04b2c15

Please sign in to comment.