Skip to content

Commit

Permalink
Support starting transaction in EvseManager
Browse files Browse the repository at this point in the history
Co-authored-by: Dima Dorezyuk <[email protected]>
Signed-off-by: Evgeny Petrov <[email protected]>
  • Loading branch information
golovasteek and dorezyuk committed Mar 25, 2024
1 parent 1ab671e commit a31fa0b
Show file tree
Hide file tree
Showing 16 changed files with 146 additions and 32 deletions.
84 changes: 79 additions & 5 deletions modules/EvseManager/Charger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include "Charger.hpp"

#include <generated/types/powermeter.hpp>
#include <math.h>
#include <string.h>
#include <thread>
Expand All @@ -23,8 +24,13 @@
namespace module {

Charger::Charger(const std::unique_ptr<IECStateMachine>& bsp, const std::unique_ptr<ErrorHandling>& error_handling,
const types::evse_board_support::Connector_type& connector_type) :
bsp(bsp), error_handling(error_handling), connector_type(connector_type) {
const std::vector<std::unique_ptr<powermeterIntf>>& 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();
Expand Down Expand Up @@ -296,7 +302,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);
Expand Down Expand Up @@ -377,7 +384,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);

Expand Down Expand Up @@ -1013,13 +1021,30 @@ bool Charger::cancel_transaction(const types::evse_manager::StopTransactionReque
signal_hlc_stop_charging();
} else {
shared_context.current_state = EvseState::ChargingPausedEVSE;
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);
Expand All @@ -1045,20 +1070,69 @@ 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("");
error_handling->raise_powermeter_transaction_start_failed_error(
"Failed to start transaction on the power meter");
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<types::units_signed::SignedMeterValue>
Charger::take_signed_meter_data(std::optional<types::units_signed::SignedMeterValue>& in) {
std::optional<types::units_signed::SignedMeterValue> out;
std::swap(out, in);
return out;
}

std::optional<types::units_signed::SignedMeterValue> Charger::get_stop_signed_meter_value() {
// This is used only inside of the state machine, so we do not need to lock here.
return take_signed_meter_data(shared_context.stop_signed_meter_value);
}

std::optional<types::units_signed::SignedMeterValue> Charger::get_start_signed_meter_value() {
// This is used only inside of the state machine, so we do not need to lock here.
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
Expand Down
33 changes: 30 additions & 3 deletions modules/EvseManager/Charger.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -30,11 +30,17 @@
#include <date/date.h>
#include <date/tz.h>
#include <generated/interfaces/ISO15118_charger/Interface.hpp>
#include <generated/interfaces/powermeter/Interface.hpp>
#include <generated/types/authorization.hpp>
#include <generated/types/evse_manager.hpp>
#include <generated/types/units_signed.hpp>
#include <memory>
#include <mutex>
#include <optional>
#include <queue>
#include <sigslot/signal.hpp>
#include <string>
#include <vector>

#include "ErrorHandling.hpp"
#include "EventQueue.hpp"
Expand All @@ -49,7 +55,8 @@ const std::string IEC62196Type2Socket = "IEC62196Type2Socket";
class Charger {
public:
Charger(const std::unique_ptr<IECStateMachine>& bsp, const std::unique_ptr<ErrorHandling>& error_handling,
const types::evse_board_support::Connector_type& connector_type);
const std::vector<std::unique_ptr<powermeterIntf>>& r_powermeter_billing,
const types::evse_board_support::Connector_type& connector_type, const std::string& evse_id);
~Charger();

enum class ChargeMode {
Expand Down Expand Up @@ -176,7 +183,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<types::units_signed::SignedMeterValue> 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<types::units_signed::SignedMeterValue> get_stop_signed_meter_value();

private:
std::optional<types::units_signed::SignedMeterValue>
take_signed_meter_data(std::optional<types::units_signed::SignedMeterValue>& data);

bool errors_prevent_charging_internal();
float get_max_current_internal();
bool deauthorize_internal();
Expand Down Expand Up @@ -212,7 +234,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
Expand Down Expand Up @@ -250,6 +272,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<types::units_signed::SignedMeterValue> stop_signed_meter_value;
std::optional<types::units_signed::SignedMeterValue> start_signed_meter_value;
} shared_context;

struct ConfigContext {
Expand Down Expand Up @@ -301,6 +326,8 @@ class Charger {
const std::unique_ptr<IECStateMachine>& bsp;
const std::unique_ptr<ErrorHandling>& error_handling;
const types::evse_board_support::Connector_type& connector_type;
const std::string evse_id;

Check notice on line 329 in modules/EvseManager/Charger.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/EvseManager/Charger.hpp#L329

class member 'Charger::evse_id' is never used.
const std::vector<std::unique_ptr<powermeterIntf>>& r_powermeter_billing;

Check notice on line 330 in modules/EvseManager/Charger.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/EvseManager/Charger.hpp#L330

class member 'Charger::r_powermeter_billing' is never used.

// ErrorHandling events
enum class ErrorHandlingEvents : std::uint8_t {
Expand Down
3 changes: 2 additions & 1 deletion modules/EvseManager/EvseManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ void EvseManager::ready() {

hw_capabilities = r_bsp->call_get_hw_capabilities();

charger = std::unique_ptr<Charger>(new Charger(bsp, error_handling, hw_capabilities.connector_type));
charger = std::unique_ptr<Charger>(
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(); });
Expand Down
2 changes: 2 additions & 0 deletions modules/EvseManager/evse/evse_managerImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions modules/GenericPowermeter/main/powermeterImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"};
};
Expand Down
8 changes: 4 additions & 4 deletions modules/LemDCBM400600/main/lem_dcbm_400600_controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,7 @@ LemDCBM400600Controller::start_transaction(const types::powermeter::TransactionR

auto [transaction_min_stop_time, transaction_max_stop_time] = get_transaction_stop_time_bounds();

return {
types::powermeter::TransactionRequestStatus::OK, {}, {}, transaction_min_stop_time, transaction_max_stop_time};
return {types::powermeter::TransactionRequestStatus::OK, {}, transaction_min_stop_time, transaction_max_stop_time};
}

void LemDCBM400600Controller::request_device_to_start_transaction(const types::powermeter::TransactionReq& value) {
Expand Down Expand Up @@ -89,20 +88,21 @@ LemDCBM400600Controller::stop_transaction(const std::string& transaction_id) {
auto signed_meter_value =
types::units_signed::SignedMeterValue{fetch_ocmf_result(transaction_id), "", "OCMF"};
return types::powermeter::TransactionStopResponse{types::powermeter::TransactionRequestStatus::OK,
{}, // Empty start_signed_meter_value
signed_meter_value};
},
this->config.transaction_number_of_http_retries, this->config.transaction_retry_wait_in_milliseconds);
} catch (DCBMUnexpectedResponseException& error) {
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};
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,8 +302,9 @@ TEST_F(LemDCBM400600ControllerTest, test_stop_transaction) {
auto res = controller.stop_transaction("mock_transaction_id");

// Verify
EXPECT_EQ(transaction_request_status_to_string(res.status), "OK");
EXPECT_EQ(res.signed_meter_value.value().signed_meter_data, "mock_ocmf_string");
ASSERT_EQ(transaction_request_status_to_string(res.status), "OK");
ASSERT_TRUE(res.signed_meter_value.has_value());
ASSERT_EQ(res.signed_meter_value.value().signed_meter_data, "mock_ocmf_string");
}

// \brief Test a failed stop transaction with the DCBM returning an invalid response
Expand Down
1 change: 1 addition & 0 deletions modules/MicroMegaWattBSP/powermeter/powermeterImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"};
};
Expand Down
2 changes: 1 addition & 1 deletion modules/PowermeterBSM/main/powermeterImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"};
}
};

Expand Down
14 changes: 4 additions & 10 deletions modules/RsIskraMeter/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -228,6 +227,7 @@ impl TransactionStopResponse {
{
Self {
error: Some(format!("{error:?}")),
start_signed_meter_value: None,
signed_meter_value: None,
status: TransactionRequestStatus::UNEXPECTED_ERROR,
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(),
Expand Down
1 change: 1 addition & 0 deletions modules/YetiDriver/powermeter/powermeterImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"};
};
Expand Down
2 changes: 1 addition & 1 deletion modules/simulation/JsYetiSimulator/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' }));
Expand Down
Loading

0 comments on commit a31fa0b

Please sign in to comment.