diff --git a/modules/EvseManager/Charger.cpp b/modules/EvseManager/Charger.cpp index 5e83a11f76..17c42c8689 100644 --- a/modules/EvseManager/Charger.cpp +++ b/modules/EvseManager/Charger.cpp @@ -10,6 +10,7 @@ #include "Charger.hpp" +#include #include #include @@ -20,8 +21,13 @@ namespace module { Charger::Charger(const std::unique_ptr& bsp, const std::unique_ptr& error_handling, - const types::evse_board_support::Connector_type& connector_type) : - bsp(bsp), error_handling(error_handling), connector_type(connector_type) { + const std::vector>& r_powermeter_billing, + const types::evse_board_support::Connector_type& connector_type, const std::string& evse_id) : + bsp(bsp), + error_handling(error_handling), + r_powermeter_billing(r_powermeter_billing), + connector_type(connector_type), + evse_id(evse_id) { #ifdef EVEREST_USE_BACKTRACES Everest::install_backtrace_handler(); @@ -274,7 +280,8 @@ void Charger::run_state_machine() { // If we are restarting, the transaction may already be active if (not shared_context.transaction_active) { - start_transaction(); + if (!start_transaction()) + break; } const EvseState target_state(EvseState::PrepareCharging); @@ -355,7 +362,8 @@ void Charger::run_state_machine() { } } else if (shared_context.authorized and shared_context.authorized_pnc) { - start_transaction(); + if (!start_transaction()) + break; const EvseState target_state(EvseState::PrepareCharging); @@ -993,11 +1001,32 @@ bool Charger::cancel_transaction(const types::evse_manager::StopTransactionReque shared_context.current_state = EvseState::ChargingPausedEVSE; } + // FIXME(evgeny): We disable pwm here since we need to get signed meter values. + // Maybe state machine should be refactored and cancelTransaction should trigger + // transition to EvseState::Finished, with active transaction. + // This way, the signed meter values will be retrieved there. + pwm_off(); shared_context.transaction_active = false; shared_context.last_stop_transaction_reason = request.reason; if (request.id_tag) { shared_context.stop_transaction_id_token = request.id_tag.value(); } + + for (const auto& meter : r_powermeter_billing) { + const auto response = + meter->call_stop_transaction(shared_context.stop_transaction_id_token.value().id_token.value); + // If we fail to stop the transaction, we ignore since there is no + // path to recovery. Its also not clear what to do + if (response.status == types::powermeter::TransactionRequestStatus::UNEXPECTED_ERROR) { + EVLOG_error << "Failed to stop a transaction on the power meter " << response.error.value_or(""); + break; + } else if (response.status == types::powermeter::TransactionRequestStatus::OK) { + shared_context.start_signed_meter_value = response.start_signed_meter_value; + shared_context.stop_signed_meter_value = response.signed_meter_value; + break; + } + } + signal_simple_event(types::evse_manager::SessionEventEnum::ChargingFinished); signal_transaction_finished_event(shared_context.last_stop_transaction_reason, shared_context.stop_transaction_id_token); @@ -1023,20 +1052,66 @@ void Charger::stop_session() { signal_simple_event(types::evse_manager::SessionEventEnum::SessionFinished); } -void Charger::start_transaction() { +bool Charger::start_transaction() { shared_context.stop_transaction_id_token.reset(); shared_context.transaction_active = true; + + const types::powermeter::TransactionReq req{evse_id, "", "", 0, 0, ""}; + for (const auto& meter : r_powermeter_billing) { + const auto response = meter->call_start_transaction(req); + // If we want to start the session but the meter fail, we stop the charging since + // we can't bill the customer. + if (response.status == types::powermeter::TransactionRequestStatus::UNEXPECTED_ERROR) { + EVLOG_error << "Failed to start a transaction on the power meter " << response.error.value_or(""); + set_faulted(); + return false; + } + } + signal_transaction_started_event(shared_context.id_token); + return true; } void Charger::stop_transaction() { shared_context.transaction_active = false; shared_context.last_stop_transaction_reason = types::evse_manager::StopTransactionReason::EVDisconnected; + + const std::string transaction_id{}; + + for (const auto& meter : r_powermeter_billing) { + const auto response = meter->call_stop_transaction(transaction_id); + // If we fail to stop the transaction, we ignore since there is no + // path to recovery. Its also not clear what to do + if (response.status == types::powermeter::TransactionRequestStatus::UNEXPECTED_ERROR) { + EVLOG_error << "Failed to stop a transaction on the power meter " << response.error.value_or(""); + break; + } else if (response.status == types::powermeter::TransactionRequestStatus::OK) { + shared_context.start_signed_meter_value = response.start_signed_meter_value; + shared_context.stop_signed_meter_value = response.signed_meter_value; + break; + } + } + signal_simple_event(types::evse_manager::SessionEventEnum::ChargingFinished); signal_transaction_finished_event(shared_context.last_stop_transaction_reason, shared_context.stop_transaction_id_token); } +std::optional +Charger::take_signed_meter_data(std::optional& in) { + std::optional out; + std::swap(out, in); + return out; +} + +std::optional Charger::get_stop_signed_meter_value() { + return take_signed_meter_data(shared_context.stop_signed_meter_value); +} + +std::optional Charger::get_start_signed_meter_value() { + return take_signed_meter_data(shared_context.start_signed_meter_value); +} + bool Charger::switch_three_phases_while_charging(bool n) { bsp->switch_three_phases_while_charging(n); return false; // FIXME: implement real return value when protobuf has sync calls diff --git a/modules/EvseManager/Charger.hpp b/modules/EvseManager/Charger.hpp index 3aaaecb8f3..c137625b3e 100644 --- a/modules/EvseManager/Charger.hpp +++ b/modules/EvseManager/Charger.hpp @@ -9,7 +9,7 @@ * IEC 61851-1 compliant AC/DC high level charging logic * * This class provides: - * 1) Hi level state machine that is controlled by a) events from board_support_ac interface + * 1) Hi level state machine that is controlled by a) events from evse_board_support interface * and b) by external commands from higher levels * * The state machine runs in its own (big) thread. After plugin, @@ -30,11 +30,17 @@ #include #include #include +#include #include #include +#include +#include #include +#include #include #include +#include +#include #include "ErrorHandling.hpp" #include "IECStateMachine.hpp" @@ -48,7 +54,8 @@ const std::string IEC62196Type2Socket = "IEC62196Type2Socket"; class Charger { public: Charger(const std::unique_ptr& bsp, const std::unique_ptr& error_handling, - const types::evse_board_support::Connector_type& connector_type); + const std::vector>& r_powermeter_billing, + const types::evse_board_support::Connector_type& connector_type, const std::string& evse_id); ~Charger(); enum class ChargeMode { @@ -175,7 +182,22 @@ class Charger { bool errors_prevent_charging(); + /// @brief Returns the OCMF start data. + /// + /// The data is generated when starting the transaction. The call resets the + /// internal variable and is thus not idempotent. + std::optional get_start_signed_meter_value(); + + /// @brief Returns the OCMF stop data. + /// + /// The data is generated when stopping the transaction. The call resets the + /// internal variable and is thus not idempotent. + std::optional get_stop_signed_meter_value(); + private: + std::optional + take_signed_meter_data(std::optional& data); + bool errors_prevent_charging_internal(); float get_max_current_internal(); bool deauthorize_internal(); @@ -211,7 +233,7 @@ class Charger { void start_session(bool authfirst); void stop_session(); - void start_transaction(); + bool start_transaction(); void stop_transaction(); // This mutex locks all variables related to the state machine @@ -249,6 +271,9 @@ class Charger { // non standard compliant option: time out after a while and switch back to DC to get SoC update bool ac_with_soc_timeout; bool contactor_welded{false}; + + std::optional stop_signed_meter_value; + std::optional start_signed_meter_value; } shared_context; struct ConfigContext { @@ -300,6 +325,8 @@ class Charger { const std::unique_ptr& bsp; const std::unique_ptr& error_handling; const types::evse_board_support::Connector_type& connector_type; + const std::string evse_id; + const std::vector>& r_powermeter_billing; // constants static constexpr float CHARGER_ABSOLUTE_MAX_CURRENT{80.}; diff --git a/modules/EvseManager/EvseManager.cpp b/modules/EvseManager/EvseManager.cpp index 66f9f569d9..716d322f0e 100644 --- a/modules/EvseManager/EvseManager.cpp +++ b/modules/EvseManager/EvseManager.cpp @@ -96,7 +96,8 @@ void EvseManager::ready() { hw_capabilities = r_bsp->call_get_hw_capabilities(); - charger = std::unique_ptr(new Charger(bsp, error_handling, hw_capabilities.connector_type)); + charger = std::unique_ptr( + new Charger(bsp, error_handling, r_powermeter_billing(), hw_capabilities.connector_type, config.evse_id)); if (r_connector_lock.size() > 0) { bsp->signal_lock.connect([this]() { r_connector_lock[0]->call_lock(); }); diff --git a/modules/EvseManager/evse/evse_managerImpl.cpp b/modules/EvseManager/evse/evse_managerImpl.cpp index cb04b47b61..1b00df8c53 100644 --- a/modules/EvseManager/evse/evse_managerImpl.cpp +++ b/modules/EvseManager/evse/evse_managerImpl.cpp @@ -275,6 +275,8 @@ void evse_managerImpl::ready() { transaction_finished.meter_value.energy_Wh_export.value().total; } + transaction_finished.start_signed_meter_value = mod->charger->get_start_signed_meter_value(); + transaction_finished.signed_meter_value = mod->charger->get_stop_signed_meter_value(); mod->telemetry.publish("session", "events", telemetry_data); se.transaction_finished.emplace(transaction_finished); diff --git a/modules/GenericPowermeter/main/powermeterImpl.cpp b/modules/GenericPowermeter/main/powermeterImpl.cpp index 3ffb0b3135..a821d27755 100644 --- a/modules/GenericPowermeter/main/powermeterImpl.cpp +++ b/modules/GenericPowermeter/main/powermeterImpl.cpp @@ -49,6 +49,7 @@ void powermeterImpl::ready() { types::powermeter::TransactionStopResponse powermeterImpl::handle_stop_transaction(std::string& transaction_id) { return {types::powermeter::TransactionRequestStatus::NOT_SUPPORTED, + {}, {}, "Generic powermeter does not support the stop_transaction command"}; }; diff --git a/modules/LemDCBM400600/main/lem_dcbm_400600_controller.cpp b/modules/LemDCBM400600/main/lem_dcbm_400600_controller.cpp index 4af666c3ed..cd412ecaee 100644 --- a/modules/LemDCBM400600/main/lem_dcbm_400600_controller.cpp +++ b/modules/LemDCBM400600/main/lem_dcbm_400600_controller.cpp @@ -96,13 +96,13 @@ LemDCBM400600Controller::stop_transaction(const std::string& transaction_id) { std::string error_message = fmt::format("Failed to stop transaction {}: {}", transaction_id, error.what()); EVLOG_error << error_message; return types::powermeter::TransactionStopResponse{ - types::powermeter::TransactionRequestStatus::UNEXPECTED_ERROR, {}, error_message}; + types::powermeter::TransactionRequestStatus::UNEXPECTED_ERROR, {}, {}, error_message}; } catch (HttpClientError& error) { std::string error_message = fmt::format("Failed to stop transaction {} - connection to device failed: {}", transaction_id, error.what()); EVLOG_error << error_message; return types::powermeter::TransactionStopResponse{ - types::powermeter::TransactionRequestStatus::UNEXPECTED_ERROR, {}, error_message}; + types::powermeter::TransactionRequestStatus::UNEXPECTED_ERROR, {}, {}, error_message}; } } diff --git a/modules/MicroMegaWattBSP/powermeter/powermeterImpl.cpp b/modules/MicroMegaWattBSP/powermeter/powermeterImpl.cpp index 0afdb6d4f3..02af0b8412 100644 --- a/modules/MicroMegaWattBSP/powermeter/powermeterImpl.cpp +++ b/modules/MicroMegaWattBSP/powermeter/powermeterImpl.cpp @@ -38,6 +38,7 @@ void powermeterImpl::ready() { types::powermeter::TransactionStopResponse powermeterImpl::handle_stop_transaction(std::string& transaction_id) { return {types::powermeter::TransactionRequestStatus::NOT_SUPPORTED, + {}, {}, "MicroMegaWattBSP powermeter does not support the stop_transaction command"}; }; diff --git a/modules/PowermeterBSM/main/powermeterImpl.cpp b/modules/PowermeterBSM/main/powermeterImpl.cpp index c42ec40316..c7963c9108 100644 --- a/modules/PowermeterBSM/main/powermeterImpl.cpp +++ b/modules/PowermeterBSM/main/powermeterImpl.cpp @@ -50,7 +50,7 @@ types::powermeter::TransactionStopResponse powermeterImpl::handle_stop_transacti } catch (const std::runtime_error& e) { EVLOG_error << __PRETTY_FUNCTION__ << " Error: " << e.what() << std::endl; - return {types::powermeter::TransactionRequestStatus::UNEXPECTED_ERROR, {}, "get_signed_meter_value_error"}; + return {types::powermeter::TransactionRequestStatus::UNEXPECTED_ERROR, {}, {}, "get_signed_meter_value_error"}; } }; diff --git a/modules/RsIskraMeter/src/main.rs b/modules/RsIskraMeter/src/main.rs index 71c80abe70..14ea9f7daf 100644 --- a/modules/RsIskraMeter/src/main.rs +++ b/modules/RsIskraMeter/src/main.rs @@ -210,7 +210,6 @@ impl TransactionStartResponse { { Self { error: Some(format!("{error:?}")), - signed_meter_value: None, status: TransactionRequestStatus::UNEXPECTED_ERROR, transaction_max_stop_time: None, transaction_min_stop_time: None, @@ -228,6 +227,7 @@ impl TransactionStopResponse { { Self { error: Some(format!("{error:?}")), + start_signed_meter_value: None, signed_meter_value: None, status: TransactionRequestStatus::UNEXPECTED_ERROR, } @@ -728,17 +728,8 @@ impl ReadyState { )?; self.check_signature_status()?; - let signature = self.read_signature()?; - let signed_meter_values = self.read_signed_meter_values()?; Ok(TransactionStartResponse { error: Option::None, - signed_meter_value: Some(SignedMeterValue { - signed_meter_data: create_ocmf(signed_meter_values, signature), - signing_method: String::new(), - encoding_method: "OCMF".to_string(), - public_key: self.read_public_key().ok(), - timestamp: None, - }), transaction_min_stop_time: Option::None, status: TransactionRequestStatus::OK, transaction_max_stop_time: Option::None, @@ -781,6 +772,9 @@ impl ReadyState { let signed_meter_values = self.read_signed_meter_values()?; Ok(TransactionStopResponse { error: Option::None, + // Iskra meter has both start and stop snapshot in one + // OCMF dataset. So we don't need to send the start snapshot. + start_signed_meter_value: None, signed_meter_value: Some(SignedMeterValue { signed_meter_data: create_ocmf(signed_meter_values, signature), signing_method: String::new(), diff --git a/modules/YetiDriver/powermeter/powermeterImpl.cpp b/modules/YetiDriver/powermeter/powermeterImpl.cpp index 017e416472..85559370c7 100644 --- a/modules/YetiDriver/powermeter/powermeterImpl.cpp +++ b/modules/YetiDriver/powermeter/powermeterImpl.cpp @@ -56,6 +56,7 @@ void powermeterImpl::ready() { types::powermeter::TransactionStopResponse powermeterImpl::handle_stop_transaction(std::string& transaction_id) { return {types::powermeter::TransactionRequestStatus::NOT_SUPPORTED, + {}, {}, "YetiDriver powermeter does not support the stop_transaction command"}; }; diff --git a/modules/simulation/JsYetiSimulator/index.js b/modules/simulation/JsYetiSimulator/index.js index 6f541e0a86..30c9fabd06 100644 --- a/modules/simulation/JsYetiSimulator/index.js +++ b/modules/simulation/JsYetiSimulator/index.js @@ -93,7 +93,7 @@ boot_module(async ({ }); setup.provides.powermeter.register.stop_transaction((mod, args) => ({ - status: 'NOT_IMPLEMENTED', + status: 'NOT_SUPPORTED', error: 'YetiDriver does not support stop transaction request.', })); setup.provides.powermeter.register.start_transaction((mod, args) => ({ status: 'OK' })); diff --git a/types/evse_manager.yaml b/types/evse_manager.yaml index 5d1e35c51b..96414a069c 100644 --- a/types/evse_manager.yaml +++ b/types/evse_manager.yaml @@ -215,6 +215,10 @@ types: description: Transaction finished meter value type: object $ref: /powermeter#/Powermeter + start_signed_meter_value: + description: The starting signed meter value report of the stopped transaction. If not included in the `signed_meter_value` object, it must be included here. + type: object + $ref: /units_signed#/SignedMeterValue signed_meter_value: description: The signed meter value report of the stopped transaction type: object @@ -324,7 +328,7 @@ types: session_finished: description: data for the SessionFinished event type: object - $ref: /evse_manager#/SessionFinished + $ref: /evse_manager#/SessionFinished transaction_started: description: data for TransactionStarted event type: object diff --git a/types/powermeter.yaml b/types/powermeter.yaml index 4266eaaf67..138f246e9f 100644 --- a/types/powermeter.yaml +++ b/types/powermeter.yaml @@ -51,10 +51,6 @@ types: error: description: If status is not OK, a verbose error message. type: string - signed_meter_value: - description: The signed meter value report of the started transaction. Must be provided if status is OK. - type: object - $ref: /units_signed#/SignedMeterValue transaction_min_stop_time: description: Earliest point in time the started transaction can be stopped again (if a minimum duration is required by the meter); yields a RFC3339 timestamp. type: string @@ -74,6 +70,10 @@ types: type: object description: Response status that indicates whether the transaction stop request could successfully be performed. $ref: /powermeter#/TransactionRequestStatus + start_signed_meter_value: + description: The signed meter value report for start of transaction. Needs to be filled if meter provides separate values for start and stop. + type: object + $ref: /units_signed#/SignedMeterValue signed_meter_value: description: The signed meter value report of the stopped transaction. Must be provided if status is OK. type: object diff --git a/types/units_signed.yaml b/types/units_signed.yaml index 5a4096b149..de8cbf3cee 100644 --- a/types/units_signed.yaml +++ b/types/units_signed.yaml @@ -153,3 +153,4 @@ types: description: VAR phase C type: object $ref: /units_signed#/SignedMeterValue + \ No newline at end of file