Files
Eric F d398a6ced2 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
2026-06-08 00:38:27 -04:00

455 lines
22 KiB
C++

// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "RpcApi.hpp"
#include "helpers/Conversions.hpp"
#include "helpers/ErrorHandler.hpp"
#include <cmath>
namespace module {
void RpcApi::init() {
for (const auto& evse_manager : r_evse_manager) {
// create one DataStore object per EVSE
auto& evse_data = this->data.evses.emplace_back(std::make_unique<data::DataStoreEvse>());
// subscribe to evse_manager interface variables
this->subscribe_evse_manager(evse_manager, *evse_data);
}
if (r_evse_energy_sink.empty()) {
EVLOG_warning << "No EVSE energy sinks configured. Configuration of EVSE external limits will not be possible.";
}
// Check how EVSEs are mapped
this->check_evse_mapping();
// Create the request handler
m_request_handler = std::make_unique<RpcApiRequestHandler>(data, r_evse_manager, r_evse_energy_sink);
std::vector<std::shared_ptr<server::TransportInterface>> transport_interfaces;
if (config.websocket_enabled) {
m_websocket_server = std::make_unique<server::WebSocketServer>(
config.websocket_tls_enabled, config.websocket_port, config.websocket_interface);
transport_interfaces.push_back(std::shared_ptr<server::TransportInterface>(std::move(m_websocket_server)));
}
if (transport_interfaces.empty()) {
throw std::runtime_error("No other transports currently available, please enable websocket transport");
}
m_rpc_handler = std::make_unique<rpc::RpcHandler>(std::move(transport_interfaces), data,
std::move(m_request_handler), config.max_decimal_places_other);
subscribe_global_errors();
}
void RpcApi::ready() {
// get charger information (cmd not available during init())
if (r_charger_information.size() > 0) {
types::json_rpc_api::ChargerInfoObj charger_info;
const auto info = r_charger_information[0]->call_get_charger_information();
// mandatory members
charger_info.vendor = info.vendor;
charger_info.model = info.model;
charger_info.serial = info.chargepoint_serial.value_or("unknown");
charger_info.firmware_version = info.firmware_version.value_or("unknown");
// optional members
if (info.friendly_name.has_value()) {
charger_info.friendly_name = info.friendly_name.value();
}
if (info.manufacturer.has_value()) {
charger_info.manufacturer = info.manufacturer.value();
}
if (info.manufacturer_url.has_value()) {
charger_info.manufacturer_url = info.manufacturer_url.value();
}
if (info.model_number.has_value()) {
charger_info.model_no = info.model_number.value();
}
if (info.model_revision.has_value()) {
charger_info.revision = info.model_revision.value();
}
if (info.board_revision.has_value()) {
charger_info.board_revision = info.board_revision.value();
}
this->data.chargerinfo.set_data(charger_info);
} else {
this->data.chargerinfo.set_unknown();
}
// Start server instances
m_rpc_handler->start_server();
}
void RpcApi::check_evse_session_event(data::DataStoreEvse& evse_data,
const types::evse_manager::SessionEvent& session_event) {
// store the session info in the data store
types::json_rpc_api::EVSEStateEnum evse_state =
types::json_rpc_api::evse_manager_session_event_to_evse_state(session_event);
evse_data.evsestatus.set_state(evse_state);
evse_data.sessioninfo.update_state(session_event);
if (evse_state == types::json_rpc_api::EVSEStateEnum::Charging) {
evse_data.evsestatus.set_charging_allowed(true);
}
if (session_event.source.has_value()) {
const auto source = session_event.source.value();
evse_data.sessioninfo.set_enable_disable_source(
types::evse_manager::enable_source_to_string(source.enable_source),
types::evse_manager::enable_state_to_string(source.enable_state), source.enable_priority);
if (source.enable_state == types::evse_manager::Enable_state::Disable) {
evse_data.evsestatus.set_available(false);
} else if (source.enable_state == types::evse_manager::Enable_state::Enable) {
evse_data.evsestatus.set_available(true);
}
}
if (session_event.event == types::evse_manager::SessionEventEnum::TransactionStarted) {
if (session_event.transaction_started.has_value()) {
const auto transaction_started = session_event.transaction_started.value();
const auto energy_Wh_import = transaction_started.meter_value.energy_Wh_import.total;
evse_data.sessioninfo.set_start_energy_import_wh(energy_Wh_import);
if (transaction_started.meter_value.energy_Wh_export.has_value()) {
const auto energy_Wh_export = transaction_started.meter_value.energy_Wh_export.value().total;
evse_data.sessioninfo.set_start_energy_export_wh(energy_Wh_export);
} else {
evse_data.sessioninfo.start_energy_export_wh_was_set = false;
}
}
} else if (session_event.event == types::evse_manager::SessionEventEnum::TransactionFinished) {
if (session_event.transaction_finished.has_value()) {
const auto transaction_finished = session_event.transaction_finished.value();
const auto energy_Wh_import = transaction_finished.meter_value.energy_Wh_import.total;
evse_data.sessioninfo.set_end_energy_import_wh(energy_Wh_import);
if (transaction_finished.meter_value.energy_Wh_export.has_value()) {
const auto energy_Wh_export = transaction_finished.meter_value.energy_Wh_export.value().total;
evse_data.sessioninfo.set_end_energy_export_wh(energy_Wh_export);
} else {
evse_data.sessioninfo.end_energy_export_wh_was_set = false;
}
}
evse_data.evsestatus.set_charged_energy_wh(evse_data.sessioninfo.get_charged_energy_wh());
evse_data.evsestatus.set_discharged_energy_wh(evse_data.sessioninfo.get_discharged_energy_wh());
evse_data.evsestatus.set_charging_duration_s(evse_data.sessioninfo.get_charging_duration_s().count());
}
}
void RpcApi::subscribe_evse_manager(const std::unique_ptr<evse_managerIntf>& evse_manager,
data::DataStoreEvse& evse_data) {
evse_manager->subscribe_powermeter([this, &evse_data](const types::powermeter::Powermeter& powermeter) {
this->meterdata_var_to_datastore(powermeter, evse_data.meterdata);
evse_data.sessioninfo.set_latest_energy_import_wh(powermeter.energy_Wh_import.total);
if (powermeter.energy_Wh_export.has_value()) {
evse_data.sessioninfo.set_latest_energy_export_wh(powermeter.energy_Wh_export.value().total);
}
if (powermeter.power_W.has_value()) {
evse_data.sessioninfo.set_latest_total_w(powermeter.power_W.value().total);
}
// update duration and energy values in the EVSE status store
evse_data.evsestatus.set_charging_duration_s(evse_data.sessioninfo.get_charging_duration_s().count());
evse_data.evsestatus.set_charged_energy_wh(evse_data.sessioninfo.get_charged_energy_wh());
evse_data.evsestatus.set_discharged_energy_wh(evse_data.sessioninfo.get_discharged_energy_wh());
});
evse_manager->subscribe_hw_capabilities(
[this, &evse_data](const types::evse_board_support::HardwareCapabilities& hwcaps) {
// there is only one connector supported currently
this->hwcaps_var_to_datastore(hwcaps, evse_data.hardwarecapabilities);
// also update evse_max_phase_count
evse_data.evsestatus.set_ac_charge_param_evse_max_phase_count(hwcaps.max_phase_count_import);
});
evse_manager->subscribe_evse_id([&evse_data](const std::string& evse_id) {
// set the EVSE id in the data store
evse_data.evseinfo.set_id(evse_id);
});
evse_manager->subscribe_session_event([this, &evse_data](types::evse_manager::SessionEvent session_event) {
this->check_evse_session_event(evse_data, session_event);
});
evse_manager->subscribe_selected_protocol([&evse_data](const std::string& selected_protocol) {
const auto var_selected_protocol =
types::json_rpc_api::evse_manager_protocol_to_charge_protocol(selected_protocol);
evse_data.evsestatus.set_charge_protocol(var_selected_protocol);
});
evse_manager->subscribe_enforced_limits([&evse_data](const types::energy::EnforcedLimits& enforced_limits) {
// set the external limits in the data store
if (evse_data.evseinfo.get_is_ac_transfer_mode()) {
const auto& max_current = enforced_limits.limits_root_side.ac_max_current_A;
if (max_current.has_value()) {
evse_data.evsestatus.set_ac_charge_param_evse_max_current(max_current.value().value);
}
RPCDataTypes::ACChargeStatusObj ac_charge_status;
const auto& max_phase_count = enforced_limits.limits_root_side.ac_max_phase_count;
ac_charge_status.evse_active_phase_count = max_phase_count.has_value() ? max_phase_count.value().value : 3;
evse_data.evsestatus.set_ac_charge_status(ac_charge_status);
} else {
evse_data.evsestatus.set_ac_charge_param(std::nullopt);
}
});
evse_manager->subscribe_supported_energy_transfer_modes(
[&evse_data](const std::vector<types::iso15118::EnergyTransferMode>& supported_energy_transfer_modes) {
// convert to rpc type
bool is_ac_transfer_mode = false;
const auto rpc_supported_energy_transfer_modes =
RPCDataTypes::iso15118_energy_transfer_modes_to_json_rpc_api(supported_energy_transfer_modes,
is_ac_transfer_mode);
evse_data.evseinfo.set_supported_energy_transfer_modes(rpc_supported_energy_transfer_modes);
evse_data.evseinfo.set_is_ac_transfer_mode(is_ac_transfer_mode);
});
evse_manager->subscribe_ev_info([&evse_data](types::evse_manager::EVInfo ev_info) {
RPCDataTypes::DisplayParametersObj display_parameters;
if (ev_info.soc.has_value()) {
// for some reason, soc in types::evse_manager::EVInfo is declared as float;
// all integers from 0 to 100 can be exactly represented in float, but let's
// (l)round them just in case:
display_parameters.present_soc = std::lround(ev_info.soc.value());
}
if (ev_info.battery_capacity.has_value()) {
display_parameters.battery_energy_capacity = ev_info.battery_capacity;
}
if (ev_info.estimated_time_full.has_value()) {
// calculate the distance of estimated_time_full to now, and if greater 0,
// report it
const auto time_until =
Everest::Date::from_rfc3339(ev_info.estimated_time_full.value()) - date::utc_clock::now();
const auto secs = std::chrono::duration_cast<std::chrono::seconds>(time_until).count();
if (secs > 0) {
display_parameters.remaining_time_to_maximum_soc = secs;
}
}
// pass an empty optional object if no member was set
std::optional<RPCDataTypes::DisplayParametersObj> result{};
if (display_parameters != RPCDataTypes::DisplayParametersObj()) {
result = display_parameters;
}
evse_data.evsestatus.set_display_parameters(result);
});
}
void RpcApi::subscribe_global_errors() {
// Subscribe to global error events
const auto error_handler = [this](const Everest::error::Error& error) {
const auto tmp_error = types::json_rpc_api::everest_error_to_rpc_error(error);
helpers::handle_error_raised(this->data, tmp_error);
};
const auto error_cleared_handler = [this](const Everest::error::Error& error) {
const auto tmp_error = types::json_rpc_api::everest_error_to_rpc_error(error);
helpers::handle_error_cleared(this->data, tmp_error);
};
subscribe_global_all_errors(error_handler, error_cleared_handler);
}
void RpcApi::meterdata_var_to_datastore(const types::powermeter::Powermeter& powermeter,
data::MeterDataStore& meter_data) {
types::json_rpc_api::MeterDataObj meter_data_new; // default initialized
if (const auto _data = meter_data.get_data(); _data.has_value()) {
// initialize with existing values
meter_data_new = _data.value();
}
// mandatory objects from the EVerest powermeter interface variable
// timestamp
meter_data_new.timestamp = powermeter.timestamp;
// energy_Wh_import
if (powermeter.energy_Wh_import.L1.has_value()) {
meter_data_new.energy_Wh_import.L1 = powermeter.energy_Wh_import.L1.value();
}
if (powermeter.energy_Wh_import.L2.has_value()) {
meter_data_new.energy_Wh_import.L2 = powermeter.energy_Wh_import.L2.value();
}
if (powermeter.energy_Wh_import.L3.has_value()) {
meter_data_new.energy_Wh_import.L3 = powermeter.energy_Wh_import.L3.value();
}
meter_data_new.energy_Wh_import.total = powermeter.energy_Wh_import.total;
// optional objects from the EVerest powermeter interface
if (powermeter.current_A.has_value()) {
meter_data_new.current_A.emplace();
const auto& inobj = powermeter.current_A.value();
if (inobj.L1.has_value()) {
meter_data_new.current_A.value().L1 = inobj.L1.value();
}
if (inobj.L2.has_value()) {
meter_data_new.current_A.value().L2 = inobj.L2.value();
}
if (inobj.L3.has_value()) {
meter_data_new.current_A.value().L3 = inobj.L3.value();
}
}
if (powermeter.energy_Wh_export.has_value()) {
// a shortcut reference to the input data sub-object
const auto& inobj = powermeter.energy_Wh_export.value();
// a shortcut reference to the output data sub-object optional
auto& export_opt = meter_data_new.energy_Wh_export;
// keep original (copied) optional value, or emplace empty if non exist
auto& newobj = export_opt.emplace(export_opt.value_or(types::json_rpc_api::Energy_Wh_export{}));
if (inobj.L1.has_value()) {
newobj.L1 = inobj.L1.value();
}
if (inobj.L2.has_value()) {
newobj.L2 = inobj.L2.value();
}
if (inobj.L3.has_value()) {
newobj.L3 = inobj.L3.value();
}
newobj.total = inobj.total;
}
if (powermeter.frequency_Hz.has_value()) {
// a shortcut reference to the input data sub-object
const auto& inobj = powermeter.frequency_Hz.value();
// a shortcut reference to the output data sub-object optional
auto& frequency_optional = meter_data_new.frequency_Hz;
// keep original (copied) optional value, or emplace empty if non exist
auto& newobj = frequency_optional.emplace(frequency_optional.value_or(types::json_rpc_api::Frequency_Hz{}));
newobj.L1 = inobj.L1;
if (inobj.L2.has_value()) {
newobj.L2 = inobj.L2.value();
}
if (inobj.L3.has_value()) {
newobj.L3 = inobj.L3.value();
}
}
if (powermeter.meter_id.has_value()) {
meter_data_new.meter_id = powermeter.meter_id.value();
}
// serial_number is not yet available
if (powermeter.phase_seq_error.has_value()) {
meter_data_new.phase_seq_error = powermeter.phase_seq_error.value();
}
if (powermeter.power_W.has_value()) {
// a shortcut reference to the input data sub-object
const auto& inobj = powermeter.power_W.value();
// a shortcut reference to the output data sub-object optional
auto& export_opt = meter_data_new.power_W;
// keep original (copied) optional value, or emplace empty if non exist
auto& newobj = export_opt.emplace(export_opt.value_or(types::json_rpc_api::Power_W{}));
if (inobj.L1.has_value()) {
newobj.L1 = inobj.L1.value();
}
if (inobj.L2.has_value()) {
newobj.L2 = inobj.L2.value();
}
if (inobj.L3.has_value()) {
newobj.L3 = inobj.L3.value();
}
newobj.total = inobj.total;
}
if (powermeter.voltage_V.has_value()) {
// a shortcut reference to the input data sub-object
const auto& inobj = powermeter.voltage_V.value();
// a shortcut reference to the output data sub-object optional
auto& export_opt = meter_data_new.voltage_V;
// keep original (copied) optional value, or emplace empty if non exist
auto& newobj = export_opt.emplace(export_opt.value_or(types::json_rpc_api::Voltage_V{}));
if (inobj.L1.has_value()) {
newobj.L1 = inobj.L1.value();
}
if (inobj.L2.has_value()) {
newobj.L2 = inobj.L2.value();
}
if (inobj.L3.has_value()) {
newobj.L3 = inobj.L3.value();
}
}
// submit changes
// Note: timestamp will skew this, as it will always change, and therefore always trigger a notification for the
// complete dataset
meter_data.set_data(meter_data_new);
}
void RpcApi::hwcaps_var_to_datastore(const types::evse_board_support::HardwareCapabilities& hwcaps,
data::HardwareCapabilitiesStore& hw_caps_data) {
types::json_rpc_api::HardwareCapabilitiesObj hw_caps_data_new; // default initialized
if (const auto _data = hw_caps_data.get_data(); _data.has_value()) {
// initialize with existing values
hw_caps_data_new = _data.value();
}
// mandatory objects from the EVerest hw_capabilites interface variable
hw_caps_data_new.max_current_A_export = hwcaps.max_current_A_import;
hw_caps_data_new.max_current_A_import = hwcaps.max_current_A_import;
hw_caps_data_new.max_phase_count_export = hwcaps.max_phase_count_export;
hw_caps_data_new.max_phase_count_import = hwcaps.max_phase_count_import;
hw_caps_data_new.min_current_A_export = hwcaps.min_current_A_export;
hw_caps_data_new.min_current_A_import = hwcaps.min_current_A_import;
hw_caps_data_new.min_phase_count_export = hwcaps.min_phase_count_export;
hw_caps_data_new.min_phase_count_import = hwcaps.min_phase_count_import;
hw_caps_data_new.phase_switch_during_charging = hwcaps.supports_changing_phases_during_charging;
// submit changes
hw_caps_data.set_data(hw_caps_data_new);
}
bool RpcApi::check_evse_mapping() {
// Iterate over the configured EVSE mapping and configure the data store accordingly
if (r_evse_manager.size() != this->data.evses.size()) {
throw std::runtime_error("The number of EVSE managers does not match the number of EVSE data stores.");
}
// As long as the EvseManager only supports one statically configured connector, we extract the
// connector id from the mapping. Only the connector type is retrieved from the EvseManager.
// Iterate over all over the mapping of the EVSE's and configure the data store accordingly
for (std::size_t idx = 0; idx < r_evse_manager.size(); idx++) {
const auto& evse_manager = r_evse_manager[idx];
const auto& evse_data = this->data.evses[idx];
// Initialize connector index for the case of no mapping information
types::json_rpc_api::ConnectorInfoObj connector;
connector.index = 1; // default connector id
connector.type = types::json_rpc_api::ConnectorTypeEnum::Unknown; // default type
evse_data->evseinfo.set_available_connector(connector);
evse_data->evsestatus.set_active_connector_index(connector.index); // TODO: support multiple connectors
// create one DataStore object per EVSE sink
if (const auto _mapping = evse_manager->get_mapping(); _mapping.has_value()) {
// Write EVSE index and connector index to the datastore
evse_data->evseinfo.set_index(_mapping.value().evse);
if (_mapping.value().connector.has_value()) {
// Initialize connector index
connector.index = _mapping.value().connector.value();
types::evse_manager::Evse evse = evse_manager->call_get_evse();
if (!evse.connectors.empty() && evse.connectors[0].type.has_value()) {
try {
connector.type = types::json_rpc_api::string_to_connector_type_enum(
types::evse_manager::connector_type_enum_to_string(
evse.connectors[0].type.value())); // use the first connector type
} catch (const std::out_of_range& e) {
EVLOG_debug << "Unknown connector type for connector index " << connector.index;
}
} else {
EVLOG_debug << "No connector type determined for connector index " << connector.index;
}
evse_data->evseinfo.set_available_connector(connector);
evse_data->evsestatus.set_active_connector_index(connector.index); // TODO: support multiple connectors
} else {
EVLOG_debug << "No connector index configured in the EVSE mapping, using default connector index "
<< connector.index;
}
} else {
// no mappings, setting limits et.al. will not work
// begin with index 1, as 0 is reserved for the complete charger
const auto evse_index = idx + 1;
EVLOG_warning << "No mapping found for EVSE manager with module_id \"" << evse_manager->module_id
<< "\". No control of limits is possible for this EVSE.";
EVLOG_warning << "Assigning index " << evse_index << " and connector index " << connector.index;
evse_data->evseinfo.set_index(evse_index);
}
}
return true;
}
} // namespace module