diff --git a/config/v201/component_schemas/standardized/ChargingStation.json b/config/v201/component_schemas/standardized/ChargingStation.json index ba7eb654a..f98c4689b 100644 --- a/config/v201/component_schemas/standardized/ChargingStation.json +++ b/config/v201/component_schemas/standardized/ChargingStation.json @@ -35,6 +35,21 @@ "description": "This variable reports current availability state for the ChargingStation", "type": "string" }, + "ChargingStationPhaseRotation": { + "variable_name": "PhaseRotation", + "characteristics": { + "supportsMonitoring": true, + "dataType": "string" + }, + "attributes": [ + { + "type": "Actual", + "mutability": "ReadWrite" + } + ], + "description": "This variable describes the phase rotation of a Component relative to its parent Component, using a three letter string consisting of the letters: R, S, T and x.", + "type": "string" + }, "ChargingStationAvailable": { "variable_name": "Available", "characteristics": { diff --git a/config/v201/config.json b/config/v201/config.json index ac5b50359..134d4a0e8 100644 --- a/config/v201/config.json +++ b/config/v201/config.json @@ -428,6 +428,12 @@ "attributes": { "Actual": "Energy.Active.Import.Register" } + }, + "AlignedDataSendDuringIdle": { + "variable_name": "SendDuringIdle", + "attributes": { + "Actual": false + } } } }, @@ -497,6 +503,12 @@ "attributes": { "Actual": 42 } + }, + "ChargingStationPhaseRotation": { + "variable_name": "PhaseRotation", + "attributes": { + "Actual": "RST" + } } } }, @@ -658,4 +670,4 @@ } } } -] +] \ No newline at end of file diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index f8d2ddd9c..f2e90d88f 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -156,6 +156,9 @@ class ChargePoint : ocpp::ChargingStationBase { std::map conn_state_per_evse; std::chrono::time_point time_disconnected; + MeterValue meter_value; // represents evseId = 0 meter value + std::mutex meter_value_mutex; + /// \brief Used when an 'OnIdle' reset is requested, to perform the reset after the charging has stopped. bool reset_scheduled; /// \brief If `reset_scheduled` is true and the reset is for a specific evse id, it will be stored in this member. @@ -244,6 +247,11 @@ class ChargePoint : ocpp::ChargingStationBase { /// @brief Get the value optional offline flag /// @return true if the charge point is offline. std::nullopt if it is online; bool is_offline(); + + /// \brief Returns the last present meter value for evseId 0 + /// \return MeterValue + MeterValue get_meter_value(); + /* OCPP message requests */ // Functional Block B: Provisioning diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 078728dee..079cacbd1 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -302,7 +302,18 @@ void ChargePoint::on_session_finished(const int32_t evse_id, const int32_t conne } void ChargePoint::on_meter_value(const int32_t evse_id, const MeterValue& meter_value) { - this->evses.at(evse_id)->on_meter_value(meter_value); + if (evse_id == 0) { + std::lock_guard lk(this->meter_value_mutex); + // if evseId = 0 then store in the chargepoint metervalues + this->meter_value = meter_value; + } else { + this->evses.at(evse_id)->on_meter_value(meter_value); + } +} + +MeterValue ChargePoint::get_meter_value() { + std::lock_guard lk(this->meter_value_mutex); + return this->meter_value; } void ChargePoint::on_unavailable(const int32_t evse_id, const int32_t connector_id) { @@ -860,6 +871,7 @@ void ChargePoint::update_aligned_data_interval() { EVLOG_debug << "Next meter value will be sent at: " << next_timestamp.value().to_rfc3339(); this->aligned_meter_values_timer.at( [this]() { + bool transaction_active = false; for (auto const& [evse_id, evse] : this->evses) { auto _meter_value = evse->get_meter_value(); // this will apply configured measurands and possibly reduce the entries of sampledValue @@ -869,6 +881,7 @@ void ChargePoint::update_aligned_data_interval() { ControllerComponentVariables::AlignedDataMeasurands); if (evse->has_active_transaction()) { + transaction_active = true; // because we do not actively read meter values at clock aligned timepoint, we switch the // ReadingContext here for (auto& sampled_value : _meter_value.sampledValue) { @@ -889,12 +902,28 @@ void ChargePoint::update_aligned_data_interval() { this->device_model ->get_optional_value(ControllerComponentVariables::AlignedDataSendDuringIdle) .value_or(false)) { + transaction_active = false; if (!meter_value.sampledValue.empty()) { // J01.FR.14 this is the only case where we send a MeterValue.req this->meter_values_req(evse_id, std::vector(1, meter_value)); } } } + // also send meter values for evseId 0 if no transactions are on going + if (!transaction_active) { + auto _meter_value = this->get_meter_value(); + + // this will apply configured measurands and possibly reduce the entries of sampledValue + // according to the configuration + const auto meter_value = + get_latest_meter_value_filtered(_meter_value, ReadingContextEnum::Sample_Clock, + ControllerComponentVariables::AlignedDataMeasurands); + + if (!meter_value.sampledValue.empty()) { + this->meter_values_req(0, std::vector(1, meter_value)); + } + } + this->update_aligned_data_interval(); }, next_timestamp.value().to_time_point()); diff --git a/lib/ocpp/v201/ctrlr_component_variables.cpp b/lib/ocpp/v201/ctrlr_component_variables.cpp index 3e3aa5b77..eb1a1c7fe 100644 --- a/lib/ocpp/v201/ctrlr_component_variables.cpp +++ b/lib/ocpp/v201/ctrlr_component_variables.cpp @@ -352,6 +352,14 @@ const ComponentVariable& ChargingStationAvailabilityState = { "AvailabilityState", }), }; +const ComponentVariable& ChargingStationPhaseRotation = { + ControllerComponents::ChargingStation, + std::nullopt, + std::optional({ + "PhaseRotation", + }), +}; + const ComponentVariable& ChargingStationAvailable = { ControllerComponents::ChargingStation, std::nullopt,