Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1) - OpenOCPP extracted (firmware OCPP 1.6J/2.0.1) - ShapeShifter library installed (pip install -e) - ShapeShifter specification extracted - EVerest extracted TODO updated with progress
This commit is contained in:
419
tools/EVerest-main/modules/API/RpcApi/data/DataStore.cpp
Normal file
419
tools/EVerest-main/modules/API/RpcApi/data/DataStore.cpp
Normal file
@@ -0,0 +1,419 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright chargebyte GmbH and Contributors to EVerest
|
||||
|
||||
#include "DataStore.hpp"
|
||||
#include "GenericInfoStore.hpp"
|
||||
#include "SessionInfo.hpp"
|
||||
#include <everest/logging.hpp>
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
|
||||
namespace data {
|
||||
|
||||
static bool almost_equal(float a, float b, float epsilon = std::numeric_limits<float>::epsilon() * 100) {
|
||||
return std::fabs(a - b) <= epsilon * std::fmax(1.0f, std::fmax(std::fabs(a), std::fabs(b)));
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::array<std::string_view, NUMBER_OF_EVSE_STATUS_FIELDS> evse_status_field_names{
|
||||
"active_connector_index", "charging_allowed", "state",
|
||||
"error_present", "charge_protocol", "charging_duration_s",
|
||||
"charged_energy_wh", "discharged_energy_wh", "available"};
|
||||
|
||||
static_assert(evse_status_field_names.size() == NUMBER_OF_EVSE_STATUS_FIELDS,
|
||||
"evse_status_field_names size should be in sync with EVSEStatusField enum definition");
|
||||
|
||||
} // namespace
|
||||
|
||||
// we currently don't get this info from the system yet, so allow setting to unknown
|
||||
void ChargerInfoStore::set_unknown() {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
this->dataobj.vendor = "unknown";
|
||||
this->dataobj.model = "unknown";
|
||||
this->dataobj.serial = "unknown";
|
||||
this->dataobj.firmware_version = "unknown";
|
||||
// pretend we got something
|
||||
this->data_is_valid = true;
|
||||
}
|
||||
|
||||
void ChargerErrorsStore::add_error(const types::json_rpc_api::ErrorObj& error) {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
// Check if the error already exists in the vector
|
||||
for (const auto& existing_error : this->dataobj) {
|
||||
if (existing_error.uuid == error.uuid) {
|
||||
// Error already exists, no need to add it again
|
||||
throw std::runtime_error("Error with UUID " + error.uuid + " already exists in the store.");
|
||||
}
|
||||
}
|
||||
this->dataobj.push_back(error);
|
||||
this->data_is_valid = true; // set the data as valid, since we have a valid error now
|
||||
data_lock.unlock();
|
||||
// Notify that data has changed
|
||||
this->notify_data_changed();
|
||||
}
|
||||
|
||||
void ChargerErrorsStore::clear_error(const types::json_rpc_api::ErrorObj& error) {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
// Find and remove the error from the vector
|
||||
for (auto it = this->dataobj.begin(); it != this->dataobj.end(); ++it) {
|
||||
// String comparison for uuid
|
||||
if (it->uuid == error.uuid) {
|
||||
this->dataobj.erase(it);
|
||||
data_lock.unlock();
|
||||
// Notify that data has changed
|
||||
this->notify_data_changed();
|
||||
return; // Exit after removing the first matching error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EVSEInfoStore::set_supported_energy_transfer_modes(
|
||||
const std::vector<types::json_rpc_api::EnergyTransferModeEnum>& supported_energy_transfer_modes) {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
this->dataobj.supported_energy_transfer_modes = supported_energy_transfer_modes;
|
||||
}
|
||||
void EVSEInfoStore::set_index(int32_t index) {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
this->dataobj.index = index;
|
||||
}
|
||||
void EVSEInfoStore::set_id(const std::string& id) {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
this->dataobj.id = id;
|
||||
this->data_is_valid = true; // set the data as valid, since we have a valid id now
|
||||
}
|
||||
void EVSEInfoStore::set_available_connectors(const std::vector<RPCDataTypes::ConnectorInfoObj>& connectors) {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
this->dataobj.available_connectors = connectors;
|
||||
}
|
||||
|
||||
void EVSEInfoStore::set_available_connector(types::json_rpc_api::ConnectorInfoObj& available_connector) {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
// Iterate through the vector and set the connector with the given index
|
||||
for (auto& connector : this->dataobj.available_connectors) {
|
||||
if (connector.index == available_connector.index) {
|
||||
connector = available_connector; // Update the existing connector
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If the connector with the given index is not found, add it to the vector
|
||||
this->dataobj.available_connectors.push_back(available_connector);
|
||||
}
|
||||
|
||||
bool EVSEInfoStore::get_is_ac_transfer_mode() const {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
return this->is_ac_transfer_mode;
|
||||
}
|
||||
|
||||
int32_t EVSEInfoStore::get_index() {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
return this->dataobj.index;
|
||||
}
|
||||
|
||||
void EVSEInfoStore::set_is_ac_transfer_mode(bool is_ac) {
|
||||
this->is_ac_transfer_mode = is_ac;
|
||||
}
|
||||
|
||||
std::vector<types::json_rpc_api::ConnectorInfoObj> EVSEInfoStore::get_available_connectors() const {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
return this->dataobj.available_connectors;
|
||||
}
|
||||
|
||||
void EVSEStatusStore::update_data_is_valid() {
|
||||
// shall be called only when a lock on this->data_mutex is held
|
||||
if (this->data_is_valid) {
|
||||
return; // No need to update if data is already valid
|
||||
}
|
||||
|
||||
std::ostringstream missing_fields;
|
||||
bool has_missing_fields{false};
|
||||
|
||||
for (size_t i = 0; i < this->field_status.size(); ++i) {
|
||||
if (!this->field_status.test(i)) {
|
||||
if (has_missing_fields) {
|
||||
missing_fields << ',';
|
||||
}
|
||||
missing_fields << evse_status_field_names.at(i);
|
||||
has_missing_fields = true;
|
||||
}
|
||||
}
|
||||
|
||||
this->data_is_valid = !has_missing_fields;
|
||||
if (has_missing_fields) {
|
||||
EVLOG_debug << "EVSEStatusStore: Missing fields [" << missing_fields.str() << "], data is invalid.";
|
||||
} else {
|
||||
EVLOG_debug << "EVSEStatusStore: All required fields are set, data is now valid.";
|
||||
}
|
||||
}
|
||||
|
||||
void EVSEStatusStore::set_field_status(EVSEStatusField field) {
|
||||
this->field_status.set(to_underlying_value(field));
|
||||
}
|
||||
|
||||
void EVSEStatusStore::set_ac_charge_param_evse_max_current(float current_limit) {
|
||||
std::unique_lock<std::mutex> cv_lock(mtx_current_limit_applied);
|
||||
|
||||
// current_limit with 0 is not valid and means internally that no energy is
|
||||
// available. The energy available state is already notified via the EVSE state, thus it
|
||||
// is not necessary to forward current_limit=0 to the API clients
|
||||
if (current_limit == 0.0f) {
|
||||
return;
|
||||
}
|
||||
this->configured_current_limit = current_limit;
|
||||
|
||||
// Check if a new current limit is requested from the API
|
||||
if (this->requested_current_limit != 0.0f) {
|
||||
// Check if the requested limit is applied
|
||||
this->cv_current_limit_applied.notify_all();
|
||||
// We are skipping applying the new current limit, as long as a new limit is requested from the API and not yet
|
||||
// applied
|
||||
return;
|
||||
}
|
||||
// Apply the new current limit
|
||||
EVLOG_debug << "Applying new current limit: " << this->configured_current_limit;
|
||||
this->set_ac_charge_param_evse_current_limit_internal(this->configured_current_limit);
|
||||
}
|
||||
|
||||
bool EVSEStatusStore::wait_until_current_limit_applied(float requested_limit, std::chrono::milliseconds timeout_ms) {
|
||||
std::unique_lock<std::mutex> lock(mtx_current_limit_applied);
|
||||
bool is_current_limit_applied{false};
|
||||
this->requested_current_limit = requested_limit;
|
||||
if (this->cv_current_limit_applied.wait_for(lock, timeout_ms, [this] {
|
||||
return almost_equal(this->configured_current_limit, this->requested_current_limit);
|
||||
})) {
|
||||
this->requested_current_limit = 0.0f; // reset the request
|
||||
is_current_limit_applied = true;
|
||||
EVLOG_debug << "Current limit applied: " << this->configured_current_limit
|
||||
<< " (requested: " << this->requested_current_limit << ")";
|
||||
} else {
|
||||
EVLOG_debug << "timed out waiting for current limit to be applied, configured: "
|
||||
<< this->configured_current_limit << ", requested: " << this->requested_current_limit;
|
||||
// If there is already a new limit configured, notify it now
|
||||
this->requested_current_limit = 0.0f; // reset the request
|
||||
set_ac_charge_param_evse_current_limit_internal(this->configured_current_limit);
|
||||
}
|
||||
|
||||
return is_current_limit_applied;
|
||||
}
|
||||
|
||||
void EVSEStatusStore::set_ac_charge_param_evse_max_phase_count(int32_t phase_count) {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
auto& ac_charge_param = this->dataobj.ac_charge_param;
|
||||
|
||||
if (!ac_charge_param.has_value()) {
|
||||
ac_charge_param.emplace();
|
||||
}
|
||||
|
||||
auto& evse_phase_count = ac_charge_param.value().evse_max_phase_count;
|
||||
|
||||
if (evse_phase_count != phase_count) {
|
||||
evse_phase_count = phase_count;
|
||||
data_lock.unlock();
|
||||
this->notify_data_changed();
|
||||
}
|
||||
}
|
||||
|
||||
void EVSEStatusStore::set_ac_charge_param_evse_current_limit_internal(float max_current) {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
auto& ac_charge_param = this->dataobj.ac_charge_param;
|
||||
|
||||
if (!ac_charge_param.has_value()) {
|
||||
ac_charge_param.emplace();
|
||||
}
|
||||
|
||||
auto& evse_max_current = ac_charge_param.value().evse_max_current;
|
||||
|
||||
if (!almost_equal(evse_max_current, max_current)) {
|
||||
evse_max_current = max_current;
|
||||
data_lock.unlock();
|
||||
this->notify_data_changed();
|
||||
}
|
||||
}
|
||||
|
||||
EVSEStatusStore::EVSEStatusStore() {
|
||||
// Initialize data store with default values
|
||||
this->set_charging_duration_s(0);
|
||||
this->set_charged_energy_wh(0.0f);
|
||||
this->set_discharged_energy_wh(0.0f);
|
||||
this->set_error_present(false);
|
||||
this->set_charging_allowed(true);
|
||||
this->set_available(true);
|
||||
}
|
||||
|
||||
// Example set method using the enum
|
||||
void EVSEStatusStore::set_active_connector_index(int32_t active_connector_index) {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex); // check if data has changed
|
||||
this->set_field_status(EVSEStatusField::ActiveConnectorIndex);
|
||||
this->update_data_is_valid();
|
||||
// check if data has changed
|
||||
if (this->dataobj.active_connector_index != active_connector_index) {
|
||||
this->dataobj.active_connector_index = active_connector_index;
|
||||
data_lock.unlock();
|
||||
this->notify_data_changed();
|
||||
}
|
||||
}
|
||||
// set the charging allowed flag
|
||||
void EVSEStatusStore::set_charging_allowed(bool charging_allowed) {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
this->set_field_status(EVSEStatusField::ChargingAllowed);
|
||||
this->update_data_is_valid();
|
||||
// check if data has changed
|
||||
if (this->dataobj.charging_allowed != charging_allowed) {
|
||||
this->dataobj.charging_allowed = charging_allowed;
|
||||
data_lock.unlock();
|
||||
this->notify_data_changed();
|
||||
}
|
||||
}
|
||||
// set the EVSE state
|
||||
void EVSEStatusStore::set_state(types::json_rpc_api::EVSEStateEnum state) {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
this->set_field_status(EVSEStatusField::State);
|
||||
this->update_data_is_valid();
|
||||
// check if data has changed
|
||||
if (this->dataobj.state != state) {
|
||||
this->dataobj.state = state;
|
||||
data_lock.unlock();
|
||||
this->notify_data_changed();
|
||||
}
|
||||
}
|
||||
|
||||
// set EVSE errors
|
||||
void EVSEStatusStore::set_error_present(const bool error_present) {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
this->set_field_status(EVSEStatusField::ErrorPresent);
|
||||
this->update_data_is_valid();
|
||||
// check if data has changed
|
||||
if (this->dataobj.error_present != error_present) {
|
||||
this->dataobj.error_present = error_present;
|
||||
data_lock.unlock();
|
||||
this->notify_data_changed();
|
||||
}
|
||||
}
|
||||
// set the charge protocol
|
||||
void EVSEStatusStore::set_charge_protocol(types::json_rpc_api::ChargeProtocolEnum charge_protocol) {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
this->set_field_status(EVSEStatusField::ChargeProtocol);
|
||||
this->update_data_is_valid();
|
||||
// check if data has changed
|
||||
if (this->dataobj.charge_protocol != charge_protocol) {
|
||||
this->dataobj.charge_protocol = charge_protocol;
|
||||
data_lock.unlock();
|
||||
this->notify_data_changed();
|
||||
}
|
||||
}
|
||||
// set the charging duration in seconds
|
||||
void EVSEStatusStore::set_charging_duration_s(int32_t charging_duration_s) {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
this->set_field_status(EVSEStatusField::ChargingDurationS);
|
||||
this->update_data_is_valid();
|
||||
// check if data has changed
|
||||
if (this->dataobj.charging_duration_s != charging_duration_s) {
|
||||
this->dataobj.charging_duration_s = charging_duration_s;
|
||||
data_lock.unlock();
|
||||
this->notify_data_changed();
|
||||
}
|
||||
}
|
||||
// set the charged energy in Wh
|
||||
void EVSEStatusStore::set_charged_energy_wh(float charged_energy_wh) {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
this->set_field_status(EVSEStatusField::ChargedEnergyWh);
|
||||
this->update_data_is_valid();
|
||||
// check if data has changed
|
||||
if (this->dataobj.charged_energy_wh != charged_energy_wh) {
|
||||
this->dataobj.charged_energy_wh = charged_energy_wh;
|
||||
data_lock.unlock();
|
||||
this->notify_data_changed();
|
||||
}
|
||||
}
|
||||
// set the discharged energy in Wh
|
||||
void EVSEStatusStore::set_discharged_energy_wh(float discharged_energy_wh) {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
this->set_field_status(EVSEStatusField::DischargedEnergyWh);
|
||||
this->update_data_is_valid();
|
||||
// check if data has changed
|
||||
if (this->dataobj.discharged_energy_wh != discharged_energy_wh) {
|
||||
this->dataobj.discharged_energy_wh = discharged_energy_wh;
|
||||
data_lock.unlock();
|
||||
this->notify_data_changed();
|
||||
}
|
||||
}
|
||||
// set the available flag
|
||||
void EVSEStatusStore::set_available(bool available) {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
this->set_field_status(EVSEStatusField::Available);
|
||||
this->update_data_is_valid();
|
||||
// check if data has changed
|
||||
if (this->dataobj.available != available) {
|
||||
this->dataobj.available = available;
|
||||
data_lock.unlock();
|
||||
this->notify_data_changed();
|
||||
}
|
||||
}
|
||||
// set the AC charge parameters
|
||||
void EVSEStatusStore::set_ac_charge_param(const std::optional<RPCDataTypes::ACChargeParametersObj>& ac_charge_param) {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
// check if data has changed
|
||||
if (this->dataobj.ac_charge_param != ac_charge_param) {
|
||||
this->dataobj.ac_charge_param = ac_charge_param;
|
||||
data_lock.unlock();
|
||||
this->notify_data_changed();
|
||||
}
|
||||
}
|
||||
// set the DC charge parameters
|
||||
void EVSEStatusStore::set_dc_charge_param(const std::optional<RPCDataTypes::DCChargeParametersObj>& dc_charge_param) {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
// check if data has changed
|
||||
if (this->dataobj.dc_charge_param != dc_charge_param) {
|
||||
this->dataobj.dc_charge_param = dc_charge_param;
|
||||
data_lock.unlock();
|
||||
this->notify_data_changed();
|
||||
}
|
||||
}
|
||||
// set the AC charge status
|
||||
void EVSEStatusStore::set_ac_charge_status(const std::optional<RPCDataTypes::ACChargeStatusObj>& ac_charge_status) {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
// check if data has changed
|
||||
if (this->dataobj.ac_charge_status != ac_charge_status) {
|
||||
this->dataobj.ac_charge_status = ac_charge_status;
|
||||
data_lock.unlock();
|
||||
this->notify_data_changed();
|
||||
}
|
||||
}
|
||||
// set the DC charge status
|
||||
void EVSEStatusStore::set_dc_charge_status(const std::optional<RPCDataTypes::DCChargeStatusObj>& dc_charge_status) {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
// check if data has changed
|
||||
if (this->dataobj.dc_charge_status != dc_charge_status) {
|
||||
this->dataobj.dc_charge_status = dc_charge_status;
|
||||
data_lock.unlock();
|
||||
this->notify_data_changed();
|
||||
}
|
||||
}
|
||||
// set the display parameters
|
||||
void EVSEStatusStore::set_display_parameters(
|
||||
const std::optional<RPCDataTypes::DisplayParametersObj>& display_parameters) {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
// check if data has changed
|
||||
if (this->dataobj.display_parameters != display_parameters) {
|
||||
this->dataobj.display_parameters = display_parameters;
|
||||
data_lock.unlock();
|
||||
this->notify_data_changed();
|
||||
}
|
||||
}
|
||||
|
||||
types::json_rpc_api::EVSEStateEnum EVSEStatusStore::get_state() const {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
return this->dataobj.state;
|
||||
}
|
||||
|
||||
std::optional<RPCDataTypes::ACChargeParametersObj> EVSEStatusStore::get_ac_charge_param() {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
return this->dataobj.ac_charge_param;
|
||||
}
|
||||
|
||||
std::optional<RPCDataTypes::DCChargeParametersObj> EVSEStatusStore::get_dc_charge_param() {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
return this->dataobj.dc_charge_param;
|
||||
}
|
||||
|
||||
} // namespace data
|
||||
167
tools/EVerest-main/modules/API/RpcApi/data/DataStore.hpp
Normal file
167
tools/EVerest-main/modules/API/RpcApi/data/DataStore.hpp
Normal file
@@ -0,0 +1,167 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright chargebyte GmbH and Contributors to EVerest
|
||||
#ifndef DATASTORE_HPP
|
||||
#define DATASTORE_HPP
|
||||
|
||||
#include "GenericInfoStore.hpp"
|
||||
#include "SessionInfo.hpp"
|
||||
#include <atomic>
|
||||
#include <bitset>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <everest/logging.hpp>
|
||||
#include <type_traits>
|
||||
|
||||
namespace RPCDataTypes = types::json_rpc_api;
|
||||
|
||||
namespace data {
|
||||
|
||||
enum class EVSEStatusField {
|
||||
ActiveConnectorIndex,
|
||||
ChargingAllowed,
|
||||
State,
|
||||
ErrorPresent,
|
||||
ChargeProtocol,
|
||||
ChargingDurationS,
|
||||
ChargedEnergyWh,
|
||||
DischargedEnergyWh,
|
||||
Available,
|
||||
Count
|
||||
};
|
||||
|
||||
template <typename T> constexpr auto to_underlying_value(T value) {
|
||||
return static_cast<std::underlying_type_t<T>>(value);
|
||||
}
|
||||
|
||||
static constexpr auto NUMBER_OF_EVSE_STATUS_FIELDS = to_underlying_value(EVSEStatusField::Count);
|
||||
|
||||
static_assert(NUMBER_OF_EVSE_STATUS_FIELDS == to_underlying_value(EVSEStatusField::Available) + 1,
|
||||
"NUMBER_OF_EVSE_STATUS_FIELDS should be in sync with EVSEStatusField enum definition");
|
||||
|
||||
class ChargerInfoStore : public GenericInfoStore<RPCDataTypes::ChargerInfoObj> {
|
||||
public:
|
||||
// we currently don't get this info from the system yet, so allow setting to unknown
|
||||
void set_unknown();
|
||||
};
|
||||
|
||||
class ChargerErrorsStore : public GenericInfoStore<std::vector<types::json_rpc_api::ErrorObj>> {
|
||||
public:
|
||||
void add_error(const types::json_rpc_api::ErrorObj& error);
|
||||
void clear_error(const types::json_rpc_api::ErrorObj& error);
|
||||
};
|
||||
|
||||
class EVSEInfoStore : public GenericInfoStore<RPCDataTypes::EVSEInfoObj> {
|
||||
public:
|
||||
void set_supported_energy_transfer_modes(
|
||||
const std::vector<types::json_rpc_api::EnergyTransferModeEnum>& supported_energy_transfer_modes);
|
||||
void set_index(int32_t index);
|
||||
void set_id(const std::string& id);
|
||||
void set_available_connectors(const std::vector<RPCDataTypes::ConnectorInfoObj>& connectors);
|
||||
void set_available_connector(types::json_rpc_api::ConnectorInfoObj& available_connector);
|
||||
void set_is_ac_transfer_mode(bool is_ac);
|
||||
bool get_is_ac_transfer_mode() const;
|
||||
int32_t get_index();
|
||||
std::vector<types::json_rpc_api::ConnectorInfoObj> get_available_connectors() const;
|
||||
|
||||
private:
|
||||
std::atomic<bool> is_ac_transfer_mode;
|
||||
};
|
||||
|
||||
class EVSEStatusStore : public GenericInfoStore<RPCDataTypes::EVSEStatusObj> {
|
||||
private:
|
||||
std::bitset<NUMBER_OF_EVSE_STATUS_FIELDS> field_status{0};
|
||||
|
||||
std::condition_variable cv_current_limit_applied;
|
||||
std::mutex mtx_current_limit_applied;
|
||||
float requested_current_limit{0.0f};
|
||||
float configured_current_limit{0.0f};
|
||||
|
||||
void update_data_is_valid();
|
||||
void set_field_status(EVSEStatusField field);
|
||||
// Internal method to set the current limit in the AC charge parameters without checking for pending requests
|
||||
void set_ac_charge_param_evse_current_limit_internal(float max_current);
|
||||
|
||||
public:
|
||||
EVSEStatusStore();
|
||||
|
||||
// Set the active connector index
|
||||
void set_active_connector_index(int32_t active_connector_index);
|
||||
// set the charging allowed flag
|
||||
void set_charging_allowed(bool charging_allowed);
|
||||
// set the EVSE state
|
||||
void set_state(types::json_rpc_api::EVSEStateEnum state);
|
||||
|
||||
// set EVSE errors
|
||||
void set_error_present(const bool error_present);
|
||||
// set the charge protocol
|
||||
void set_charge_protocol(types::json_rpc_api::ChargeProtocolEnum charge_protocol);
|
||||
// set the charging duration in seconds
|
||||
void set_charging_duration_s(int32_t charging_duration_s);
|
||||
// set the charged energy in Wh
|
||||
void set_charged_energy_wh(float charged_energy_wh);
|
||||
// set the discharged energy in Wh
|
||||
void set_discharged_energy_wh(float discharged_energy_wh);
|
||||
// set the available flag
|
||||
void set_available(bool available);
|
||||
// set the AC charge parameters
|
||||
void set_ac_charge_param(const std::optional<RPCDataTypes::ACChargeParametersObj>& ac_charge_param);
|
||||
// set the DC charge parameters
|
||||
void set_dc_charge_param(const std::optional<RPCDataTypes::DCChargeParametersObj>& dc_charge_param);
|
||||
// set the AC charge status
|
||||
void set_ac_charge_status(const std::optional<RPCDataTypes::ACChargeStatusObj>& ac_charge_status);
|
||||
// set the DC charge status
|
||||
void set_dc_charge_status(const std::optional<RPCDataTypes::DCChargeStatusObj>& dc_charge_status);
|
||||
// set the display parameters
|
||||
void set_display_parameters(const std::optional<RPCDataTypes::DisplayParametersObj>& display_parameters);
|
||||
// set the AC max phase count in the AC charge parameters
|
||||
void set_ac_charge_param_evse_max_phase_count(int32_t phase_count);
|
||||
// set the AC current limit and notify any waiting request threads
|
||||
void set_ac_charge_param_evse_max_current(float current_limit);
|
||||
// wait until the current limit is applied or timeout occurs
|
||||
bool wait_until_current_limit_applied(float current_limit, std::chrono::milliseconds timeout_ms);
|
||||
|
||||
types::json_rpc_api::EVSEStateEnum get_state() const;
|
||||
std::optional<RPCDataTypes::ACChargeParametersObj> get_ac_charge_param();
|
||||
std::optional<RPCDataTypes::DCChargeParametersObj> get_dc_charge_param();
|
||||
};
|
||||
class HardwareCapabilitiesStore : public GenericInfoStore<RPCDataTypes::HardwareCapabilitiesObj> {};
|
||||
class MeterDataStore : public GenericInfoStore<RPCDataTypes::MeterDataObj> {};
|
||||
|
||||
// This is the data store for a single EVSE. An EVSE can have multiple connectors.
|
||||
struct DataStoreEvse {
|
||||
EVSEInfoStore evseinfo;
|
||||
EVSEStatusStore evsestatus;
|
||||
MeterDataStore meterdata;
|
||||
HardwareCapabilitiesStore hardwarecapabilities;
|
||||
SessionInfoStore sessioninfo;
|
||||
};
|
||||
|
||||
// This is the main data store for the charger. A charger can have multiple EVSEs, each with multiple connectors.
|
||||
// For more information see 3-Tier model definition of OCPP 2.0.
|
||||
struct DataStoreCharger {
|
||||
ChargerInfoStore chargerinfo;
|
||||
ChargerErrorsStore chargererrors;
|
||||
std::string everest_version;
|
||||
std::vector<std::unique_ptr<DataStoreEvse>> evses;
|
||||
|
||||
// get the EVSE data with a specific id
|
||||
data::DataStoreEvse* get_evse_store(const int32_t evse_index) {
|
||||
if (evses.empty()) {
|
||||
EVLOG_error << "No EVSEs found in the data store.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (const auto& evse : evses) {
|
||||
const auto tmp_index = evse->evseinfo.get_index();
|
||||
if (tmp_index == evse_index) {
|
||||
return evse.get();
|
||||
}
|
||||
}
|
||||
EVLOG_error << "EVSE index " << evse_index << " not found in data store.";
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace data
|
||||
|
||||
#endif // DATASTORE_HPP
|
||||
@@ -0,0 +1,82 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright chargebyte GmbH and Contributors to EVerest
|
||||
#ifndef GENERICINFOSTORE_HPP
|
||||
#define GENERICINFOSTORE_HPP
|
||||
|
||||
#include <atomic>
|
||||
#include <functional> // for std::function
|
||||
#include <mutex>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <optional>
|
||||
#include <types/json_rpc_api/json_rpc_api.hpp>
|
||||
#include <vector>
|
||||
|
||||
// This contains types for all the data objects
|
||||
|
||||
namespace data {
|
||||
|
||||
template <typename T> class GenericInfoStore {
|
||||
protected:
|
||||
// the associated data store
|
||||
T dataobj;
|
||||
// protect the data object
|
||||
// NB: mutable in order to be able to lock the mutex in const functions
|
||||
mutable std::mutex data_mutex;
|
||||
// function to call when changes occurred
|
||||
std::function<void(const decltype(dataobj)&)> notification_callback;
|
||||
|
||||
// override this if structures need special (non-default) initialization
|
||||
virtual void init_data(){};
|
||||
// whether the non-optional values are valid, so that the RPC interface can generate an error
|
||||
std::atomic<bool> data_is_valid{false};
|
||||
|
||||
public:
|
||||
explicit GenericInfoStore() {
|
||||
this->init_data();
|
||||
};
|
||||
// if the returned value has no value, the data is incomplete or not available
|
||||
std::optional<T> get_data() const {
|
||||
if (this->data_is_valid) {
|
||||
std::unique_lock<std::mutex> data_lock{this->data_mutex};
|
||||
return this->dataobj;
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
// set the data object. This method may need to be overridden with custom copy functions if the data
|
||||
// object is not a simple type e.g. pointers and has no copy/assignment operator
|
||||
// Note: all setters, also in derived classes, must use the data mutex
|
||||
// e.g. std::unique_lock<std::mutex> data_lock(this->data_mutex)
|
||||
virtual void set_data(const T& in) {
|
||||
// check for changes
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
if (in != this->dataobj) {
|
||||
this->dataobj = in;
|
||||
this->data_is_valid = true;
|
||||
data_lock.unlock();
|
||||
// call the notification callback if it is set
|
||||
notify_data_changed();
|
||||
}
|
||||
}
|
||||
|
||||
// notify that data has changed
|
||||
void notify_data_changed() {
|
||||
std::unique_lock<std::mutex> data_lock(this->data_mutex);
|
||||
if (this->notification_callback && this->data_is_valid) {
|
||||
// create a copy of the data object
|
||||
T data_copy = this->dataobj;
|
||||
// unlock explicitly before entering callback
|
||||
data_lock.unlock();
|
||||
this->notification_callback(data_copy);
|
||||
}
|
||||
}
|
||||
|
||||
// register a callback which is triggered when any data in the associated data store changes
|
||||
void register_notification_callback(const std::function<void(const T&)>& callback) {
|
||||
this->notification_callback = callback;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace data
|
||||
|
||||
#endif // GENERICINFOSTORE_HPP
|
||||
196
tools/EVerest-main/modules/API/RpcApi/data/SessionInfo.cpp
Normal file
196
tools/EVerest-main/modules/API/RpcApi/data/SessionInfo.cpp
Normal file
@@ -0,0 +1,196 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "SessionInfo.hpp"
|
||||
|
||||
namespace data {
|
||||
|
||||
static void to_json(json& j, const SessionInfoStore::Error& e) {
|
||||
j = json{{"type", e.type}, {"description", e.description}, {"severity", e.severity}};
|
||||
}
|
||||
|
||||
SessionInfoStore::SessionInfoStore() :
|
||||
start_energy_import_wh(0),
|
||||
end_energy_import_wh(0),
|
||||
start_energy_export_wh(0),
|
||||
end_energy_export_wh(0),
|
||||
latest_total_w(0),
|
||||
state(State::Unknown) {
|
||||
this->start_time_point = date::utc_clock::now();
|
||||
this->end_time_point = this->start_time_point;
|
||||
|
||||
uk_random_delay_remaining.countdown_s = 0;
|
||||
uk_random_delay_remaining.current_limit_after_delay_A = 0.;
|
||||
uk_random_delay_remaining.current_limit_during_delay_A = 0;
|
||||
}
|
||||
|
||||
bool SessionInfoStore::is_state_charging(const SessionInfoStore::State current_state) {
|
||||
if (current_state == State::AuthRequired || current_state == State::Charging ||
|
||||
current_state == State::ChargingPausedEV || current_state == State::ChargingPausedEVSE) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SessionInfoStore::reset() {
|
||||
std::lock_guard<std::mutex> lock(this->session_info_mutex);
|
||||
this->state = State::Unknown;
|
||||
this->start_energy_import_wh = 0;
|
||||
this->end_energy_import_wh = 0;
|
||||
this->start_energy_export_wh = 0;
|
||||
this->end_energy_export_wh = 0;
|
||||
this->start_time_point = date::utc_clock::now();
|
||||
this->latest_total_w = 0;
|
||||
this->permanent_fault = false;
|
||||
}
|
||||
|
||||
void SessionInfoStore::update_state(const types::evse_manager::SessionEvent event) {
|
||||
std::lock_guard<std::mutex> lock(this->session_info_mutex);
|
||||
using Event = types::evse_manager::SessionEventEnum;
|
||||
|
||||
// using switch since some code analysis tools can detect missing cases
|
||||
// (when new events are added)
|
||||
switch (event.event) {
|
||||
case Event::Enabled:
|
||||
this->state = State::Unplugged;
|
||||
break;
|
||||
case Event::Disabled:
|
||||
this->state = State::Disabled;
|
||||
break;
|
||||
case Event::AuthRequired:
|
||||
this->state = State::AuthRequired;
|
||||
break;
|
||||
case Event::Authorized:
|
||||
[[fallthrough]];
|
||||
case Event::PrepareCharging:
|
||||
[[fallthrough]];
|
||||
case Event::SessionStarted:
|
||||
[[fallthrough]];
|
||||
case Event::SessionResumed:
|
||||
[[fallthrough]];
|
||||
case Event::TransactionStarted:
|
||||
this->state = State::Preparing;
|
||||
break;
|
||||
case Event::ChargingStarted:
|
||||
this->state = State::Charging;
|
||||
break;
|
||||
case Event::ChargingPausedEV:
|
||||
this->state = State::ChargingPausedEV;
|
||||
break;
|
||||
case Event::ChargingPausedEVSE:
|
||||
this->state = State::ChargingPausedEVSE;
|
||||
break;
|
||||
case Event::ChargingFinished:
|
||||
this->state = State::Finished;
|
||||
break;
|
||||
case Event::StoppingCharging:
|
||||
this->state = State::FinishedEV;
|
||||
break;
|
||||
case Event::TransactionFinished: {
|
||||
if (event.transaction_finished->reason == types::evse_manager::StopTransactionReason::Local) {
|
||||
this->state = State::FinishedEVSE;
|
||||
} else {
|
||||
this->state = State::Finished;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Event::PluginTimeout:
|
||||
this->state = State::AuthTimeout;
|
||||
break;
|
||||
case Event::ReservationStart:
|
||||
this->state = State::Reserved;
|
||||
break;
|
||||
case Event::ReservationEnd:
|
||||
[[fallthrough]];
|
||||
case Event::SessionFinished:
|
||||
this->state = State::Unplugged;
|
||||
break;
|
||||
/// explicitly fall through all the SessionEventEnum values we are not handling
|
||||
case Event::Deauthorized:
|
||||
[[fallthrough]];
|
||||
case Event::SwitchingPhases:
|
||||
[[fallthrough]];
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SessionInfoStore::set_start_energy_import_wh(int32_t start_energy_import_wh) {
|
||||
std::lock_guard<std::mutex> lock(this->session_info_mutex);
|
||||
this->start_energy_import_wh = start_energy_import_wh;
|
||||
this->end_energy_import_wh = start_energy_import_wh;
|
||||
this->start_time_point = date::utc_clock::now();
|
||||
this->end_time_point = this->start_time_point;
|
||||
}
|
||||
|
||||
void SessionInfoStore::set_end_energy_import_wh(int32_t end_energy_import_wh) {
|
||||
std::lock_guard<std::mutex> lock(this->session_info_mutex);
|
||||
this->end_energy_import_wh = end_energy_import_wh;
|
||||
this->end_time_point = date::utc_clock::now();
|
||||
}
|
||||
|
||||
void SessionInfoStore::set_latest_energy_import_wh(int32_t latest_energy_wh) {
|
||||
std::lock_guard<std::mutex> lock(this->session_info_mutex);
|
||||
if (this->is_state_charging(this->state)) {
|
||||
this->end_time_point = date::utc_clock::now();
|
||||
this->end_energy_import_wh = latest_energy_wh;
|
||||
}
|
||||
}
|
||||
|
||||
void SessionInfoStore::set_start_energy_export_wh(int32_t start_energy_export_wh) {
|
||||
std::lock_guard<std::mutex> lock(this->session_info_mutex);
|
||||
this->start_energy_export_wh = start_energy_export_wh;
|
||||
this->end_energy_export_wh = start_energy_export_wh;
|
||||
this->start_energy_export_wh_was_set = true;
|
||||
}
|
||||
|
||||
void SessionInfoStore::set_end_energy_export_wh(int32_t end_energy_export_wh) {
|
||||
std::lock_guard<std::mutex> lock(this->session_info_mutex);
|
||||
this->end_energy_export_wh = end_energy_export_wh;
|
||||
this->end_energy_export_wh_was_set = true;
|
||||
}
|
||||
|
||||
void SessionInfoStore::set_latest_energy_export_wh(int32_t latest_export_energy_wh) {
|
||||
std::lock_guard<std::mutex> lock(this->session_info_mutex);
|
||||
if (this->is_state_charging(this->state)) {
|
||||
this->end_energy_export_wh = latest_export_energy_wh;
|
||||
this->end_energy_export_wh_was_set = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SessionInfoStore::set_latest_total_w(double latest_total_w) {
|
||||
std::lock_guard<std::mutex> lock(this->session_info_mutex);
|
||||
this->latest_total_w = latest_total_w;
|
||||
}
|
||||
|
||||
void SessionInfoStore::set_uk_random_delay_remaining(const types::uk_random_delay::CountDown& cd) {
|
||||
std::lock_guard<std::mutex> lock(this->session_info_mutex);
|
||||
this->uk_random_delay_remaining = cd;
|
||||
}
|
||||
|
||||
void SessionInfoStore::set_enable_disable_source(const std::string& active_source, const std::string& active_state,
|
||||
const int active_priority) {
|
||||
std::lock_guard<std::mutex> lock(this->session_info_mutex);
|
||||
this->active_enable_disable_source = active_source;
|
||||
this->active_enable_disable_state = active_state;
|
||||
this->active_enable_disable_priority = active_priority;
|
||||
}
|
||||
|
||||
float SessionInfoStore::get_charged_energy_wh() const {
|
||||
std::lock_guard<std::mutex> lock(this->session_info_mutex);
|
||||
return this->end_energy_import_wh - this->start_energy_import_wh;
|
||||
}
|
||||
|
||||
float SessionInfoStore::get_discharged_energy_wh() const {
|
||||
std::lock_guard<std::mutex> lock(this->session_info_mutex);
|
||||
if (this->start_energy_export_wh_was_set && this->end_energy_export_wh_was_set) {
|
||||
return this->end_energy_export_wh - this->start_energy_export_wh;
|
||||
}
|
||||
return 0.0f; // No discharged energy if export values are not set
|
||||
}
|
||||
|
||||
std::chrono::seconds SessionInfoStore::get_charging_duration_s() const {
|
||||
std::lock_guard<std::mutex> lock(this->session_info_mutex);
|
||||
return std::chrono::duration_cast<std::chrono::seconds>(this->end_time_point - this->start_time_point);
|
||||
}
|
||||
} // namespace data
|
||||
97
tools/EVerest-main/modules/API/RpcApi/data/SessionInfo.hpp
Normal file
97
tools/EVerest-main/modules/API/RpcApi/data/SessionInfo.hpp
Normal file
@@ -0,0 +1,97 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
// Description: This file defines the SessionInfoStore class, which is used to manage session information for EV
|
||||
// charging sessions. It includes methods to update session state, set energy readings, and calculate charged and
|
||||
// discharged energy. The code of SessionInfoStore class is mostly a copy of the EVerest API module.
|
||||
|
||||
#ifndef SESSIONINFO_HPP
|
||||
#define SESSIONINFO_HPP
|
||||
|
||||
// headers for required interface implementations
|
||||
#include <generated/types/evse_manager.hpp>
|
||||
#include <generated/types/uk_random_delay.hpp>
|
||||
|
||||
// insert your custom include headers here
|
||||
#include <date/date.h>
|
||||
#include <date/tz.h>
|
||||
#include <mutex>
|
||||
|
||||
namespace data {
|
||||
|
||||
class SessionInfoStore {
|
||||
public:
|
||||
SessionInfoStore();
|
||||
|
||||
struct Error {
|
||||
std::string type;
|
||||
std::string description;
|
||||
std::string severity;
|
||||
};
|
||||
|
||||
bool start_energy_export_wh_was_set{
|
||||
false}; ///< Indicate if start export energy value (optional) has been received or not
|
||||
bool end_energy_export_wh_was_set{
|
||||
false}; ///< Indicate if end export energy value (optional) has been received or not
|
||||
|
||||
void reset();
|
||||
void update_state(const types::evse_manager::SessionEvent event);
|
||||
void set_start_energy_import_wh(int32_t start_energy_import_wh);
|
||||
void set_end_energy_import_wh(int32_t end_energy_import_wh);
|
||||
void set_latest_energy_import_wh(int32_t latest_energy_wh);
|
||||
void set_start_energy_export_wh(int32_t start_energy_export_wh);
|
||||
void set_end_energy_export_wh(int32_t end_energy_export_wh);
|
||||
void set_latest_energy_export_wh(int32_t latest_export_energy_wh);
|
||||
void set_latest_total_w(double latest_total_w);
|
||||
void set_uk_random_delay_remaining(const types::uk_random_delay::CountDown& c);
|
||||
void set_enable_disable_source(const std::string& active_source, const std::string& active_state,
|
||||
const int active_priority);
|
||||
void set_permanent_fault(bool f) {
|
||||
permanent_fault = f;
|
||||
}
|
||||
|
||||
float get_charged_energy_wh() const;
|
||||
float get_discharged_energy_wh() const;
|
||||
std::chrono::seconds get_charging_duration_s() const;
|
||||
|
||||
/// \brief Converts this struct into a serialized json object
|
||||
operator std::string();
|
||||
|
||||
private:
|
||||
mutable std::mutex session_info_mutex;
|
||||
int32_t start_energy_import_wh; ///< Energy reading (import) at the beginning of this charging session in Wh
|
||||
int32_t end_energy_import_wh; ///< Energy reading (import) at the end of this charging session in Wh
|
||||
int32_t start_energy_export_wh; ///< Energy reading (export) at the beginning of this charging session in Wh
|
||||
int32_t end_energy_export_wh; ///< Energy reading (export) at the end of this charging session in Wh
|
||||
types::uk_random_delay::CountDown uk_random_delay_remaining; ///< Remaining time of a UK smart charging regs
|
||||
///< delay. Set to 0 if no delay is active
|
||||
std::chrono::time_point<date::utc_clock> start_time_point; ///< Start of the charging session
|
||||
std::chrono::time_point<date::utc_clock> end_time_point; ///< End of the charging session
|
||||
double latest_total_w; ///< Latest total power reading in W
|
||||
|
||||
enum class State {
|
||||
Unknown,
|
||||
Unplugged,
|
||||
Disabled,
|
||||
Preparing,
|
||||
Reserved,
|
||||
AuthRequired,
|
||||
ChargingPausedEV,
|
||||
ChargingPausedEVSE,
|
||||
Charging,
|
||||
AuthTimeout,
|
||||
Finished,
|
||||
FinishedEVSE,
|
||||
FinishedEV
|
||||
} state;
|
||||
|
||||
bool is_state_charging(const SessionInfoStore::State current_state);
|
||||
|
||||
std::string active_enable_disable_source{"Unspecified"};
|
||||
std::string active_enable_disable_state{"Enabled"};
|
||||
int active_enable_disable_priority{0};
|
||||
bool permanent_fault{false};
|
||||
};
|
||||
} // namespace data
|
||||
|
||||
#endif // SESSIONINFO_HPP
|
||||
Reference in New Issue
Block a user