diff --git a/include/ocpp/v201/evse.hpp b/include/ocpp/v201/evse.hpp index 9c365837f7..0dbc59098b 100644 --- a/include/ocpp/v201/evse.hpp +++ b/include/ocpp/v201/evse.hpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -20,26 +21,38 @@ class Evse { private: int32_t evse_id; + DeviceModel& device_model; std::map> id_connector_map; std::function status_notification_callback; std::function reservation_id)> transaction_meter_value_req; + std::function pause_charging_callback; std::unique_ptr transaction; // pointer to active transaction (can be nullptr) MeterValue meter_value; // represents current meter value - std::mutex meter_value_mutex; + std::recursive_mutex meter_value_mutex; Everest::SteadyTimer sampled_meter_values_timer; + /// \brief gets the active import energy meter value from meter_value, normalized to Wh. + std::optional get_active_import_register_meter_value(); + + /// \brief function to check if the max energy has been exceeded, calls pause_charging_callback if so. + void check_max_energy_on_invalid_id(); + public: /// \brief Construct a new Evse object /// \param evse_id id of the evse /// \param number_of_connectors of the evse + /// \param device_model reference to the device model /// \param status_notification_callback that is called when the status of a connector changes - Evse(const int32_t evse_id, const int32_t number_of_connectors, + /// \param pause_charging_callback that is called when the charging should be paused due to max energy on invalid id + /// being exceeded + Evse(const int32_t evse_id, const int32_t number_of_connectors, DeviceModel& device_model, const std::function& status_notification_callback, const std::function reservation_id)>& transaction_meter_value_req); + const std::optional reservation_id)>& transaction_meter_value_req, + const std::function pause_charging_callback); /// \brief Returns an OCPP2.0.1 EVSE type /// \return @@ -74,6 +87,10 @@ class Evse { /// \param reason void close_transaction(const DateTime& timestamp, const MeterValue& meter_stop, const ReasonEnum& reason); + /// \brief Start checking if the max energy on invalid id has exceeded. + /// Will call pause_charging_callback when that happens. + void start_checking_max_energy_on_invalid_id(); + /// \brief Indicates if a transaction is active at this evse /// \return bool has_active_transaction(); diff --git a/include/ocpp/v201/transaction.hpp b/include/ocpp/v201/transaction.hpp index 2982ca8a31..2e4a1d54ac 100644 --- a/include/ocpp/v201/transaction.hpp +++ b/include/ocpp/v201/transaction.hpp @@ -16,6 +16,8 @@ struct EnhancedTransaction : public Transaction { std::optional group_id_token; std::optional reservation_id; int32_t seq_no = 0; + std::optional active_energy_import_start_value; + bool check_max_active_import_energy; int32_t get_seq_no(); Transaction get_transaction(); }; diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 0d7800f7c3..5363c1586c 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -93,9 +93,12 @@ ChargePoint::ChargePoint(const std::map& evse_connector_struct } }; + auto pause_charging_callback = [this, evse_id_]() { this->callbacks.pause_charging_callback(evse_id_); }; + this->evses.insert( - std::make_pair(evse_id, std::make_unique(evse_id, number_of_connectors, status_notification_callback, - transaction_meter_value_callback))); + std::make_pair(evse_id, std::make_unique(evse_id, number_of_connectors, *this->device_model, + status_notification_callback, + transaction_meter_value_callback, pause_charging_callback))); for (int32_t connector_id = 1; connector_id <= number_of_connectors; connector_id++) { // operational status for this connector this->database_handler->insert_availability(evse_id, connector_id, OperationalStatusEnum::Operative, false); @@ -1790,11 +1793,11 @@ void ChargePoint::handle_start_transaction_event_response(const EnhancedMessage< if (this->device_model->get_value(ControllerComponentVariables::StopTxOnInvalidId)) { this->callbacks.stop_transaction_callback(evse_id, ReasonEnum::DeAuthorized); } else { - if (this->device_model->get_optional_value(ControllerComponentVariables::MaxEnergyOnInvalidId) + if (this->device_model->get_optional_value(ControllerComponentVariables::MaxEnergyOnInvalidId) .has_value()) { - // TODO(piet): E05.FR.03 // Energy delivery to the EV SHALL be allowed until the amount of energy specified in // MaxEnergyOnInvalidId has been reached. + this->evses.at(evse_id)->start_checking_max_energy_on_invalid_id(); } else { this->callbacks.pause_charging_callback(evse_id); } diff --git a/lib/ocpp/v201/evse.cpp b/lib/ocpp/v201/evse.cpp index a498dc5396..0119cf4fc5 100644 --- a/lib/ocpp/v201/evse.cpp +++ b/lib/ocpp/v201/evse.cpp @@ -4,20 +4,50 @@ #include #include +#include #include #include namespace ocpp { namespace v201 { -Evse::Evse(const int32_t evse_id, const int32_t number_of_connectors, +// Convert an energy value into Wh +static float get_normalized_energy_value(SampledValue sampled_value) { + float value = sampled_value.value; + // If no unit of measure is present the unit is in Wh so nothing to do + if (sampled_value.unitOfMeasure.has_value()) { + const auto& unit_of_measure = sampled_value.unitOfMeasure.value(); + if (unit_of_measure.unit.has_value()) { + if (unit_of_measure.unit.value() == "kWh") { + value *= 1000.0f; + } else if (unit_of_measure.unit.value() == "Wh") { + // do nothing + } else { + EVLOG_AND_THROW( + std::runtime_error("Attempt to convert an energy value which does not have a correct unit")); + } + } + + if (unit_of_measure.multiplier.has_value()) { + if (unit_of_measure.multiplier.value() != 0) { + value *= powf(10, unit_of_measure.multiplier.value()); + } + } + } + return value; +} + +Evse::Evse(const int32_t evse_id, const int32_t number_of_connectors, DeviceModel& device_model, const std::function& status_notification_callback, const std::function reservation_id)>& transaction_meter_value_req) : + const std::optional reservation_id)>& transaction_meter_value_req, + const std::function pause_charging_callback) : evse_id(evse_id), + device_model(device_model), status_notification_callback(status_notification_callback), transaction_meter_value_req(transaction_meter_value_req), + pause_charging_callback(pause_charging_callback), transaction(nullptr) { for (int connector_id = 1; connector_id <= number_of_connectors; connector_id++) { this->id_connector_map.insert(std::make_pair( @@ -54,6 +84,11 @@ void Evse::open_transaction(const std::string& transaction_id, const int32_t con this->transaction->id_token = id_token; this->transaction->group_id_token = group_id_token; + auto start_value = this->get_active_import_register_meter_value(); + if (start_value.has_value()) { + this->transaction->active_energy_import_start_value = start_value.value(); + } + transaction->meter_values.push_back(meter_start); if (sampled_data_tx_updated_interval > 0) { @@ -79,6 +114,15 @@ void Evse::close_transaction(const DateTime& timestamp, const MeterValue& meter_ this->sampled_meter_values_timer.stop(); } +void Evse::start_checking_max_energy_on_invalid_id() { + if (this->transaction != nullptr) { + this->transaction->check_max_active_import_energy = true; + this->check_max_energy_on_invalid_id(); + } else { + EVLOG_error << "Trying to start \"MaxEnergyOnInvalidId\" checking without an active transaction"; + } +} + bool Evse::has_active_transaction() { return this->transaction != nullptr; } @@ -110,14 +154,47 @@ void Evse::trigger_status_notification_callback(const int32_t connector_id) { } void Evse::on_meter_value(const MeterValue& meter_value) { - std::lock_guard lk(this->meter_value_mutex); + std::lock_guard lk(this->meter_value_mutex); this->meter_value = meter_value; + this->check_max_energy_on_invalid_id(); } MeterValue Evse::get_meter_value() { - std::lock_guard lk(this->meter_value_mutex); + std::lock_guard lk(this->meter_value_mutex); return this->meter_value; } +std::optional Evse::get_active_import_register_meter_value() { + std::lock_guard lk(this->meter_value_mutex); + auto it = std::find_if( + this->meter_value.sampledValue.begin(), this->meter_value.sampledValue.end(), [](const SampledValue& value) { + return value.measurand == MeasurandEnum::Energy_Active_Import_Register and !value.phase.has_value(); + }); + if (it != this->meter_value.sampledValue.end()) { + return get_normalized_energy_value(*it); + } + return std::nullopt; +} + +void Evse::check_max_energy_on_invalid_id() { + // Handle E05.02 + auto max_energy_on_invalid_id = + this->device_model.get_optional_value(ControllerComponentVariables::MaxEnergyOnInvalidId); + auto& transaction = this->transaction; + if (transaction != nullptr and max_energy_on_invalid_id.has_value() and + transaction->active_energy_import_start_value.has_value() and transaction->check_max_active_import_energy) { + const auto opt_energy_value = this->get_active_import_register_meter_value(); + + if (opt_energy_value.has_value()) { + auto charged_energy = opt_energy_value.value() - transaction->active_energy_import_start_value.value(); + + if (charged_energy > static_cast(max_energy_on_invalid_id.value())) { + this->pause_charging_callback(); + transaction->check_max_active_import_energy = false; // No need to check anymore + } + } + } +} + } // namespace v201 } // namespace ocpp