// 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 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()); // 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(data, r_evse_manager, r_evse_energy_sink); std::vector> transport_interfaces; if (config.websocket_enabled) { m_websocket_server = std::make_unique( config.websocket_tls_enabled, config.websocket_port, config.websocket_interface); transport_interfaces.push_back(std::shared_ptr(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(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_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& 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(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 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