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 19, 2024
1 parent aed024b commit 0b3bb4b
Show file tree
Hide file tree
Showing 14 changed files with 135 additions and 28 deletions.
85 changes: 80 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>

Expand All @@ -20,8 +21,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 @@ -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);
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand All @@ -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<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() {
return take_signed_meter_data(shared_context.stop_signed_meter_value);
}

std::optional<types::units_signed::SignedMeterValue> 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
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 "IECStateMachine.hpp"
Expand All @@ -48,7 +54,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 @@ -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<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 @@ -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
Expand Down Expand Up @@ -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<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 @@ -300,6 +325,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 328 in modules/EvseManager/Charger.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/EvseManager/Charger.hpp#L328

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

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::r_powermeter_billing' is never used.

// constants
static constexpr float CHARGER_ABSOLUTE_MAX_CURRENT{80.};
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
4 changes: 2 additions & 2 deletions modules/LemDCBM400600/main/lem_dcbm_400600_controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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};
}
}

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
6 changes: 5 additions & 1 deletion types/evse_manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions types/powermeter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Loading

0 comments on commit 0b3bb4b

Please sign in to comment.