diff --git a/include/ocpp/v16/charge_point.hpp b/include/ocpp/v16/charge_point.hpp index 2e435f105..52992165f 100644 --- a/include/ocpp/v16/charge_point.hpp +++ b/include/ocpp/v16/charge_point.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -153,6 +154,13 @@ class ChargePoint { /// \return ChargingSchedules of all connectors std::map get_all_composite_charging_schedules(const int32_t duration_s); + /// \brief Calculates EnhancedChargingSchedule(s) configured by the CSMS of all connectors from now until now + + /// given \p duration_s . EnhancedChargingSchedules contain EnhancedChargingSchedulePeriod(s) that are enhanced by + /// the stackLevel that was provided for the ChargingProfile + /// \param duration_s + /// \return ChargingSchedules of all connectors + std::map get_all_enhanced_composite_charging_schedules(const int32_t duration_s); + /// \brief Stores the given \p powermeter values for the given \p connector . This function can be called when a new /// meter value is present. /// \param connector diff --git a/include/ocpp/v16/charge_point_impl.hpp b/include/ocpp/v16/charge_point_impl.hpp index f2dd6ca0d..7c4477f7c 100644 --- a/include/ocpp/v16/charge_point_impl.hpp +++ b/include/ocpp/v16/charge_point_impl.hpp @@ -453,6 +453,13 @@ class ChargePointImpl : ocpp::ChargingStationBase { /// \return ChargingSchedules of all connectors std::map get_all_composite_charging_schedules(const int32_t duration_s); + /// \brief Calculates EnhancedChargingSchedule(s) configured by the CSMS of all connectors from now until now + + /// given \p duration_s . EnhancedChargingSchedules contain EnhancedChargingSchedulePeriod(s) that are enhanced by + /// the stackLevel that was provided for the ChargingProfile + /// \param duration_s + /// \return ChargingSchedules of all connectors + std::map get_all_enhanced_composite_charging_schedules(const int32_t duration_s); + /// \brief Stores the given \p powermeter values for the given \p connector . This function can be called when a new /// meter value is present. /// \param connector diff --git a/include/ocpp/v16/smart_charging.hpp b/include/ocpp/v16/smart_charging.hpp index 5a0113301..73624ad58 100644 --- a/include/ocpp/v16/smart_charging.hpp +++ b/include/ocpp/v16/smart_charging.hpp @@ -33,6 +33,36 @@ struct PeriodDateTimePair { ocpp::DateTime end_time; }; +/// \brief Enhances ChargingSchedulePeriod with stackLevel +struct EnhancedChargingSchedulePeriod { + int32_t startPeriod; + float limit; + std::optional numberPhases; + int32_t stackLevel; +}; + +/// \brief Conversion from a given EnhancedChargingSchedulePeriod \p k to a given json object \p j +void to_json(json& j, const EnhancedChargingSchedulePeriod& k); + +/// \brief Conversion from a given json object \p j to a given EnhancedChargingSchedulePeriod \p k +void from_json(const json& j, EnhancedChargingSchedulePeriod& k); + +/// \brief Enhances ChargingSchedule by containing std::vector instead of +/// std::vector +struct EnhancedChargingSchedule { + ChargingRateUnit chargingRateUnit; + std::vector chargingSchedulePeriod; + std::optional duration; + std::optional startSchedule; + std::optional minChargingRate; +}; + +/// \brief Conversion from a given EnhancedChargingSchedule \p k to a given json object \p j +void to_json(json& j, const EnhancedChargingSchedule& k); + +/// \brief Conversion from a given json object \p j to a given EnhancedChargingSchedule \p k +void from_json(const json& j, EnhancedChargingSchedule& k); + /// \brief This class handles and maintains incoming ChargingProfiles and contains the logic /// to calculate the composite schedules class SmartChargingHandler { @@ -127,6 +157,12 @@ class SmartChargingHandler { /// /// \brief Calculates the composite schedule for the given \p valid_profiles and the given \p connector_id . /// + EnhancedChargingSchedule calculate_enhanced_composite_schedule(std::vector valid_profiles, + const ocpp::DateTime& start_time, + const ocpp::DateTime& end_time, + const int connector_id, + std::optional charging_rate_unit); + ChargingSchedule calculate_composite_schedule(std::vector valid_profiles, const ocpp::DateTime& start_time, const ocpp::DateTime& end_time, const int connector_id, diff --git a/lib/ocpp/v16/charge_point.cpp b/lib/ocpp/v16/charge_point.cpp index 363fd99ae..c18b480ce 100644 --- a/lib/ocpp/v16/charge_point.cpp +++ b/lib/ocpp/v16/charge_point.cpp @@ -71,10 +71,14 @@ DataTransferResponse ChargePoint::data_transfer(const CiString<255>& vendorId, } std::map ChargePoint::get_all_composite_charging_schedules(const int32_t duration_s) { - return this->charge_point->get_all_composite_charging_schedules(duration_s); } +std::map +ChargePoint::get_all_enhanced_composite_charging_schedules(const int32_t duration_s) { + return this->charge_point->get_all_enhanced_composite_charging_schedules(duration_s); +} + void ChargePoint::on_meter_values(int32_t connector, const Measurement& measurement) { this->charge_point->on_meter_values(connector, measurement); } diff --git a/lib/ocpp/v16/charge_point_impl.cpp b/lib/ocpp/v16/charge_point_impl.cpp index d3953b4df..2ef6bca7d 100644 --- a/lib/ocpp/v16/charge_point_impl.cpp +++ b/lib/ocpp/v16/charge_point_impl.cpp @@ -2668,6 +2668,26 @@ std::map ChargePointImpl::get_all_composite_charging_ return charging_schedules; } +std::map +ChargePointImpl::get_all_enhanced_composite_charging_schedules(const int32_t duration_s) { + + std::map charging_schedules; + + for (int connector_id = 0; connector_id <= this->configuration->getNumberOfConnectors(); connector_id++) { + const auto start_time = ocpp::DateTime(); + const auto duration = std::chrono::seconds(duration_s); + const auto end_time = ocpp::DateTime(start_time.to_time_point() + duration); + + const auto valid_profiles = + this->smart_charging_handler->get_valid_profiles(start_time, end_time, connector_id); + const auto composite_schedule = this->smart_charging_handler->calculate_enhanced_composite_schedule( + valid_profiles, start_time, end_time, connector_id, ChargingRateUnit::A); + charging_schedules[connector_id] = composite_schedule; + } + + return charging_schedules; +} + bool ChargePointImpl::is_pnc_enabled() { return this->configuration->getSupportedFeatureProfilesSet().count(SupportedFeatureProfiles::PnC) and this->configuration->getISO15118PnCEnabled(); diff --git a/lib/ocpp/v16/smart_charging.cpp b/lib/ocpp/v16/smart_charging.cpp index 083d1dafa..d920b01a0 100644 --- a/lib/ocpp/v16/smart_charging.cpp +++ b/lib/ocpp/v16/smart_charging.cpp @@ -8,6 +8,68 @@ using namespace std::chrono; namespace ocpp { namespace v16 { +/// \brief Conversion from a given ChargingSchedulePeriod \p k to a given json object \p j +void to_json(json& j, const EnhancedChargingSchedulePeriod& k) { + // the required parts of the message + j = json{{"startPeriod", k.startPeriod}, {"limit", k.limit}, {"stackLevel", k.stackLevel}}; + // the optional parts of the message + if (k.numberPhases) { + j["numberPhases"] = k.numberPhases.value(); + } +} + +/// \brief Conversion from a given json object \p j to a given ChargingSchedulePeriod \p k +void from_json(const json& j, EnhancedChargingSchedulePeriod& k) { + // the required parts of the message + k.startPeriod = j.at("startPeriod"); + k.limit = j.at("limit"); + k.stackLevel = j.at("stackLevel"); + + // the optional parts of the message + if (j.contains("numberPhases")) { + k.numberPhases.emplace(j.at("numberPhases")); + } +} + +/// \brief Conversion from a given ChargingSchedule \p k to a given json object \p j +void to_json(json& j, const EnhancedChargingSchedule& k) { + // the required parts of the message + j = json{ + {"chargingRateUnit", conversions::charging_rate_unit_to_string(k.chargingRateUnit)}, + {"chargingSchedulePeriod", k.chargingSchedulePeriod}, + }; + // the optional parts of the message + if (k.duration) { + j["duration"] = k.duration.value(); + } + if (k.startSchedule) { + j["startSchedule"] = k.startSchedule.value().to_rfc3339(); + } + if (k.minChargingRate) { + j["minChargingRate"] = k.minChargingRate.value(); + } +} + +/// \brief Conversion from a given json object \p j to a given ChargingSchedule \p k +void from_json(const json& j, EnhancedChargingSchedule& k) { + // the required parts of the message + k.chargingRateUnit = conversions::string_to_charging_rate_unit(j.at("chargingRateUnit")); + for (auto val : j.at("chargingSchedulePeriod")) { + k.chargingSchedulePeriod.push_back(val); + } + + // the optional parts of the message + if (j.contains("duration")) { + k.duration.emplace(j.at("duration")); + } + if (j.contains("startSchedule")) { + k.startSchedule.emplace(j.at("startSchedule").get()); + } + if (j.contains("minChargingRate")) { + k.minChargingRate.emplace(j.at("minChargingRate")); + } +} + bool validate_schedule(const ChargingSchedule& schedule, const int charging_schedule_max_periods, const std::vector& charging_schedule_allowed_charging_rate_units) { @@ -170,6 +232,26 @@ int SmartChargingHandler::get_number_installed_profiles() { } ChargingSchedule SmartChargingHandler::calculate_composite_schedule( + std::vector valid_profiles, const ocpp::DateTime& start_time, const ocpp::DateTime& end_time, + const int connector_id, std::optional charging_rate_unit) { + const auto enhanced_composite_schedule = this->calculate_enhanced_composite_schedule( + valid_profiles, start_time, end_time, connector_id, charging_rate_unit); + ChargingSchedule composite_schedule; + composite_schedule.chargingRateUnit = enhanced_composite_schedule.chargingRateUnit; + composite_schedule.duration = enhanced_composite_schedule.duration; + composite_schedule.startSchedule = enhanced_composite_schedule.startSchedule; + composite_schedule.minChargingRate = enhanced_composite_schedule.minChargingRate; + for (const auto enhanced_period : enhanced_composite_schedule.chargingSchedulePeriod) { + ChargingSchedulePeriod period; + period.startPeriod = enhanced_period.startPeriod; + period.limit = enhanced_period.limit; + period.numberPhases = enhanced_period.numberPhases; + composite_schedule.chargingSchedulePeriod.push_back(period); + } + return composite_schedule; +} + +EnhancedChargingSchedule SmartChargingHandler::calculate_enhanced_composite_schedule( std::vector valid_profiles, const ocpp::DateTime& start_time, const ocpp::DateTime& end_time, const int connector_id, std::optional charging_rate_unit) { // return in amps if not given @@ -177,17 +259,17 @@ ChargingSchedule SmartChargingHandler::calculate_composite_schedule( charging_rate_unit.emplace(ChargingRateUnit::A); } - ChargingSchedule composite_schedule; // the schedule that will be returned + EnhancedChargingSchedule composite_schedule; // the schedule that will be returned composite_schedule.chargingRateUnit = charging_rate_unit.value(); composite_schedule.duration.emplace( duration_cast(end_time.to_time_point() - start_time.to_time_point()).count()); - std::vector periods; + std::vector periods; ocpp::DateTime temp_time(start_time); ocpp::DateTime last_period_end_time(end_time); auto current_period_limit = std::numeric_limits::max(); - auto lowest_limit_for_this_period = std::numeric_limits::max(); + LimitStackLevelPair significant_limit_stack_level_pair = {std::numeric_limits::max(), -1}; // calculate every ChargingSchedulePeriod of result within this while loop while (duration_cast(end_time.to_time_point() - temp_time.to_time_point()).count() > 0) { @@ -225,34 +307,38 @@ ChargingSchedule SmartChargingHandler::calculate_composite_schedule( } } - int tx_limit; // if there is a limit with purpose TxProfile it overrules the limit of purpose TxDefaultProfile if (current_purpose_and_stack_limits.at(ChargingProfilePurposeType::TxProfile).limit != std::numeric_limits::max()) { - tx_limit = current_purpose_and_stack_limits.at(ChargingProfilePurposeType::TxProfile).limit; + significant_limit_stack_level_pair = + current_purpose_and_stack_limits.at(ChargingProfilePurposeType::TxProfile); } else { - tx_limit = current_purpose_and_stack_limits.at(ChargingProfilePurposeType::TxDefaultProfile).limit; + significant_limit_stack_level_pair = + current_purpose_and_stack_limits.at(ChargingProfilePurposeType::TxDefaultProfile); } - // lowest limit for this period is minimum of ChargePointMaxProfile limit or tx_limit - lowest_limit_for_this_period = std::min( - current_purpose_and_stack_limits.at(ChargingProfilePurposeType::ChargePointMaxProfile).limit, tx_limit); + if (current_purpose_and_stack_limits.at(ChargingProfilePurposeType::ChargePointMaxProfile).limit < + significant_limit_stack_level_pair.limit) { + significant_limit_stack_level_pair = + current_purpose_and_stack_limits.at(ChargingProfilePurposeType::ChargePointMaxProfile); + } // insert new period to result only if limit changed or period was found - if (lowest_limit_for_this_period != current_period_limit and - lowest_limit_for_this_period != std::numeric_limits::max()) { + if (significant_limit_stack_level_pair.limit != current_period_limit and + significant_limit_stack_level_pair.limit != std::numeric_limits::max()) { - ChargingSchedulePeriod new_period; + EnhancedChargingSchedulePeriod new_period; const auto start_period = duration_cast(temp_time.to_time_point() - start_time.to_time_point()).count(); new_period.startPeriod = start_period; - new_period.limit = - get_requested_limit(lowest_limit_for_this_period, temp_number_phases, charging_rate_unit.value()); + new_period.limit = get_requested_limit(significant_limit_stack_level_pair.limit, temp_number_phases, + charging_rate_unit.value()); new_period.numberPhases = temp_number_phases; + new_period.stackLevel = significant_limit_stack_level_pair.stack_level; periods.push_back(new_period); last_period_end_time = temp_period_end_time; - current_period_limit = lowest_limit_for_this_period; + current_period_limit = significant_limit_stack_level_pair.limit; } temp_time = this->get_next_temp_time(temp_time, valid_profiles, connector_id); }