Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/evsemanager persistent transactions #789

Merged
merged 14 commits into from
Jul 31, 2024
Merged
5 changes: 5 additions & 0 deletions config/config-sil-ocpp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ active_modules:
config_module:
device: auto
supported_ISO15118_2: true
persistent_store:
module: PersistentStore
evse_manager_1:
evse: 1
module: EvseManager
Expand All @@ -39,6 +41,9 @@ active_modules:
hlc:
- module_id: iso15118_charger
implementation_id: charger
store:
- module_id: persistent_store
implementation_id: main
evse_manager_2:
module: EvseManager
evse: 2
Expand Down
2 changes: 1 addition & 1 deletion dependencies.yaml
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: update dependencies

Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ libevse-security:
# OCPP
libocpp:
git: https://github.com/EVerest/libocpp.git
git_tag: 1067cf3ddf27be3a43f8abc519b69da11c4ef921
git_tag: aab1d5785bde141203b1a89b03d66fa91edf8093
cmake_condition: "EVEREST_DEPENDENCY_ENABLED_LIBOCPP"
# Josev
Josev:
Expand Down
6 changes: 5 additions & 1 deletion interfaces/powermeter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ cmds:
type: object
$ref: /powermeter#/TransactionStartResponse
stop_transaction:
description: Stop the transaction on the power meter and return the signed metering information
description: >-
Stop the transaction on the power meter and return the signed metering information.
If the transaction id is an empty string, all ongoing transaction should be cancelled.
This is used on start up to clear dangling transactions that might still be ongoing
in the power meter but are not known to the EvseManager.
arguments:
transaction_id:
description: Transaction id
Expand Down
1 change: 1 addition & 0 deletions modules/EvseManager/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ target_sources(${MODULE_NAME}
IECStateMachine.cpp
ErrorHandling.cpp
backtrace.cpp
PersistentStore.cpp
)

target_link_libraries(${MODULE_NAME}
Expand Down
48 changes: 46 additions & 2 deletions modules/EvseManager/Charger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ namespace module {

Charger::Charger(const std::unique_ptr<IECStateMachine>& bsp, const std::unique_ptr<ErrorHandling>& error_handling,
const std::vector<std::unique_ptr<powermeterIntf>>& r_powermeter_billing,
const std::unique_ptr<PersistentStore>& _store,
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),
store(_store),
connector_type(connector_type),
evse_id(evse_id),
r_powermeter_billing(r_powermeter_billing) {
evse_id(evse_id) {

#ifdef EVEREST_USE_BACKTRACES
Everest::install_backtrace_handler();
Expand Down Expand Up @@ -1169,6 +1171,8 @@ bool Charger::start_transaction() {
}
}

store->store_session(shared_context.session_uuid);

signal_transaction_started_event(shared_context.id_token);
return true;
}
Expand All @@ -1191,11 +1195,51 @@ void Charger::stop_transaction() {
}
}

store->clear_session();

signal_simple_event(types::evse_manager::SessionEventEnum::ChargingFinished);
signal_transaction_finished_event(shared_context.last_stop_transaction_reason,
shared_context.stop_transaction_id_token);
}

void Charger::cleanup_transactions_on_startup() {
// See if we have an open transaction in persistent storage
auto session_uuid = store->get_session();
if (not session_uuid.empty()) {
EVLOG_info << "Cleaning up transaction with UUID " << session_uuid << " on start up";
store->clear_session();

types::evse_manager::TransactionFinished transaction_finished;

// If yes, try to close nicely with the ID we remember and trigger a transaction finished event on success
for (const auto& meter : r_powermeter_billing) {
const auto response = meter->call_stop_transaction(session_uuid);
// If we fail to stop the transaction, it was probably just not active anymore
if (response.status == types::powermeter::TransactionRequestStatus::UNEXPECTED_ERROR) {
EVLOG_warning << "Failed to stop a transaction on the power meter " << response.error.value_or("");
break;
} else if (response.status == types::powermeter::TransactionRequestStatus::OK) {
// Fill in OCMF from the recovered transaction
transaction_finished.start_signed_meter_value = response.start_signed_meter_value;
transaction_finished.signed_meter_value = response.signed_meter_value;
break;
}
}

// Send out event to inform OCPP et al
std::optional<types::authorization::ProvidedIdToken> id_token;
signal_transaction_finished_event(types::evse_manager::StopTransactionReason::PowerLoss, id_token);
}

// Now we did what we could to clean up, so if there are still transactions going on in the power meter close them
// anyway. In this case we cannot generate a transaction finished event for OCPP et al since we cannot match it to
// our transaction anymore.
EVLOG_info << "Cleaning up any other transaction on start up";
for (const auto& meter : r_powermeter_billing) {
meter->call_stop_transaction("");
}
}

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;
Expand Down
10 changes: 7 additions & 3 deletions modules/EvseManager/Charger.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
#include "ErrorHandling.hpp"
#include "EventQueue.hpp"
#include "IECStateMachine.hpp"
#include "PersistentStore.hpp"
#include "scoped_lock_timeout.hpp"
#include "utils.hpp"

Expand All @@ -57,6 +58,7 @@ class Charger {
public:
Charger(const std::unique_ptr<IECStateMachine>& bsp, const std::unique_ptr<ErrorHandling>& error_handling,
const std::vector<std::unique_ptr<powermeterIntf>>& r_powermeter_billing,
const std::unique_ptr<PersistentStore>& store,
const types::evse_board_support::Connector_type& connector_type, const std::string& evse_id);
~Charger();

Expand Down Expand Up @@ -149,6 +151,7 @@ class Charger {

// Signal for EvseEvents
sigslot::signal<types::evse_manager::SessionEventEnum> signal_simple_event;
sigslot::signal<std::string> signal_session_resumed_event;
sigslot::signal<types::evse_manager::StartSessionReason, std::optional<types::authorization::ProvidedIdToken>>
signal_session_started_event;
sigslot::signal<types::authorization::ProvidedIdToken> signal_transaction_started_event;
Expand Down Expand Up @@ -212,6 +215,8 @@ class Charger {
connector_type = t;
}

void cleanup_transactions_on_startup();

private:
utils::Stopwatch stopwatch;

Expand Down Expand Up @@ -358,12 +363,11 @@ class Charger {

const std::unique_ptr<IECStateMachine>& bsp;
const std::unique_ptr<ErrorHandling>& error_handling;

const std::vector<std::unique_ptr<powermeterIntf>>& r_powermeter_billing;
const std::unique_ptr<PersistentStore>& store;
std::atomic<types::evse_board_support::Connector_type> connector_type{
types::evse_board_support::Connector_type::IEC62196Type2Cable};

const std::string evse_id;
const std::vector<std::unique_ptr<powermeterIntf>>& r_powermeter_billing;

// ErrorHandling events
enum class ErrorHandlingEvents : std::uint8_t {
Expand Down
18 changes: 16 additions & 2 deletions modules/EvseManager/EvseManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ inline static types::authorization::ProvidedIdToken create_autocharge_token(std:
}

void EvseManager::init() {

store = std::unique_ptr<PersistentStore>(new PersistentStore(r_store, info.id));

random_delay_enabled = config.uk_smartcharging_random_delay_enable;
random_delay_max_duration = std::chrono::seconds(config.uk_smartcharging_random_delay_max_duration);
if (random_delay_enabled) {
Expand Down Expand Up @@ -159,8 +162,8 @@ void EvseManager::ready() {
error_handling =
std::unique_ptr<ErrorHandling>(new ErrorHandling(r_bsp, r_hlc, r_connector_lock, r_ac_rcd, p_evse, r_imd));

charger = std::unique_ptr<Charger>(
new Charger(bsp, error_handling, r_powermeter_billing(), hw_capabilities.connector_type, config.evse_id));
charger = std::unique_ptr<Charger>(new Charger(bsp, error_handling, r_powermeter_billing(), store,
hw_capabilities.connector_type, config.evse_id));

// Now incoming hardware capabilties can be processed
hw_caps_mutex.unlock();
Expand Down Expand Up @@ -926,6 +929,17 @@ void EvseManager::ready() {
[this] { return initial_powermeter_value_received; });
}

// Resuming left-over transaction from e.g. powerloss. This information allows other modules like to OCPP to be
// informed that the EvseManager is aware of previous sessions so that no individual cleanup is required
const auto session_id = store->get_session();
if (!session_id.empty()) {
charger->signal_session_resumed_event(session_id);
}

// By default cleanup left-over transaction from e.g. power loss
// TOOD: Add resume handling
charger->cleanup_transactions_on_startup();

// start with a limit of 0 amps. We will get a budget from EnergyManager that is locally limited by hw
// caps.
charger->set_max_current(0.0F, date::utc_clock::now() + std::chrono::seconds(120));
Expand Down
8 changes: 7 additions & 1 deletion modules/EvseManager/EvseManager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <generated/interfaces/connector_lock/Interface.hpp>
#include <generated/interfaces/evse_board_support/Interface.hpp>
#include <generated/interfaces/isolation_monitor/Interface.hpp>
#include <generated/interfaces/kvs/Interface.hpp>
#include <generated/interfaces/power_supply_DC/Interface.hpp>
#include <generated/interfaces/powermeter/Interface.hpp>
#include <generated/interfaces/slac/Interface.hpp>
Expand All @@ -41,6 +42,7 @@
#include "CarManufacturer.hpp"
#include "Charger.hpp"
#include "ErrorHandling.hpp"
#include "PersistentStore.hpp"
#include "SessionLog.hpp"
#include "VarContainer.hpp"
#include "scoped_lock_timeout.hpp"
Expand Down Expand Up @@ -110,7 +112,8 @@ class EvseManager : public Everest::ModuleBase {
std::vector<std::unique_ptr<powermeterIntf>> r_powermeter_car_side,
std::vector<std::unique_ptr<slacIntf>> r_slac, std::vector<std::unique_ptr<ISO15118_chargerIntf>> r_hlc,
std::vector<std::unique_ptr<isolation_monitorIntf>> r_imd,
std::vector<std::unique_ptr<power_supply_DCIntf>> r_powersupply_DC, Conf& config) :
std::vector<std::unique_ptr<power_supply_DCIntf>> r_powersupply_DC,
std::vector<std::unique_ptr<kvsIntf>> r_store, Conf& config) :
ModuleBase(info),
mqtt(mqtt_provider),
telemetry(telemetry),
Expand All @@ -127,6 +130,7 @@ class EvseManager : public Everest::ModuleBase {
r_hlc(std::move(r_hlc)),
r_imd(std::move(r_imd)),
r_powersupply_DC(std::move(r_powersupply_DC)),
r_store(std::move(r_store)),
config(config){};

Everest::MqttProvider& mqtt;
Expand All @@ -144,6 +148,7 @@ class EvseManager : public Everest::ModuleBase {
const std::vector<std::unique_ptr<ISO15118_chargerIntf>> r_hlc;
const std::vector<std::unique_ptr<isolation_monitorIntf>> r_imd;
const std::vector<std::unique_ptr<power_supply_DCIntf>> r_powersupply_DC;
const std::vector<std::unique_ptr<kvsIntf>> r_store;
const Conf& config;

// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
Expand Down Expand Up @@ -191,6 +196,7 @@ class EvseManager : public Everest::ModuleBase {

std::unique_ptr<IECStateMachine> bsp;
std::unique_ptr<ErrorHandling> error_handling;
std::unique_ptr<PersistentStore> store;

std::atomic_bool random_delay_enabled{false};
std::atomic_bool random_delay_running{false};
Expand Down
44 changes: 44 additions & 0 deletions modules/EvseManager/PersistentStore.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2024 or leave the date out entirely


#include "PersistentStore.hpp"

namespace module {

PersistentStore::PersistentStore(const std::vector<std::unique_ptr<kvsIntf>>& _r_store, const std::string module_id) :

Check warning on line 8 in modules/EvseManager/PersistentStore.cpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/EvseManager/PersistentStore.cpp#L8

Function parameter 'module_id' should be passed by const reference.
r_store(_r_store) {

if (r_store.size() > 0) {
active = true;
}

session_key = module_id + "_session";
}

void PersistentStore::store_session(const std::string& session_uuid) {
if (active) {
r_store[0]->call_store(session_key, session_uuid);
}
}

void PersistentStore::clear_session() {
if (active) {
r_store[0]->call_store(session_key, "");
}
}

std::string PersistentStore::get_session() {
if (active) {
auto r = r_store[0]->call_load(session_key);
try {
if (std::holds_alternative<std::string>(r)) {
return std::get<std::string>(r);
}
} catch (...) {
return {};
}
}
return {};
}

} // namespace module
33 changes: 33 additions & 0 deletions modules/EvseManager/PersistentStore.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2024 or leave out entirely


/*
The Persistent Store class is an abstraction layer to store any persistent information
(such as sessions) for the EvseManager.
*/

#ifndef EVSE_MANAGER_PERSISTENT_STORE_H_
#define EVSE_MANAGER_PERSISTENT_STORE_H_

#include <generated/interfaces/kvs/Interface.hpp>

namespace module {

class PersistentStore {
public:
// We need the r_bsp reference to be able to talk to the bsp driver module
explicit PersistentStore(const std::vector<std::unique_ptr<kvsIntf>>& r_store, const std::string module_id);

Check warning on line 19 in modules/EvseManager/PersistentStore.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/EvseManager/PersistentStore.hpp#L19

Function parameter 'module_id' should be passed by const reference.

void store_session(const std::string& session_uuid);
void clear_session();
std::string get_session();

private:
const std::vector<std::unique_ptr<kvsIntf>>& r_store;

Check notice on line 26 in modules/EvseManager/PersistentStore.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/EvseManager/PersistentStore.hpp#L26

class member 'PersistentStore::r_store' is never used.
std::string session_key;

Check notice on line 27 in modules/EvseManager/PersistentStore.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/EvseManager/PersistentStore.hpp#L27

class member 'PersistentStore::session_key' is never used.
bool active{false};

Check notice on line 28 in modules/EvseManager/PersistentStore.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/EvseManager/PersistentStore.hpp#L28

class member 'PersistentStore::active' is never used.
};

} // namespace module

#endif // EVSE_MANAGER_PERSISTENT_STORE_H_
9 changes: 8 additions & 1 deletion modules/EvseManager/evse/evse_managerImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,14 @@ void evse_managerImpl::ready() {
publish_selected_protocol(this->mod->selected_protocol);
});

mod->charger->signal_session_resumed_event.connect([this](const std::string& session_id) {
types::evse_manager::SessionEvent session_event;
session_event.uuid = session_id;
session_event.timestamp = Everest::Date::to_rfc3339(date::utc_clock::now());
session_event.event = types::evse_manager::SessionEventEnum::SessionResumed;
publish_session_event(session_event);
});

// Note: Deprecated. Only kept for Node red compatibility, will be removed in the future
// Legacy external mqtt pubs
mod->charger->signal_max_current.connect([this](float c) {
Expand All @@ -339,7 +347,6 @@ void evse_managerImpl::ready() {
mod->mqtt.publish(fmt::format("everest_external/nodered/{}/state/state", mod->config.connector_id),
static_cast<int>(s));
});
// /Deprecated
}

types::evse_manager::Evse evse_managerImpl::handle_get_evse() {
Expand Down
4 changes: 4 additions & 0 deletions modules/EvseManager/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,10 @@ requires:
interface: power_supply_DC
min_connections: 0
max_connections: 1
store:
interface: kvs
min_connections: 0
max_connections: 1
enable_external_mqtt: true
enable_telemetry: true
metadata:
Expand Down
7 changes: 6 additions & 1 deletion modules/OCPP/OCPP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,11 @@ void OCPP::init_evse_subscriptions() {
return;
}

if (session_event.event == types::evse_manager::SessionEventEnum::SessionResumed) {
this->resuming_session_ids.insert(session_event.uuid);
return;
}

if (!this->started) {
EVLOG_info << "OCPP not fully initialized, but received a session event on evse_id: " << evse_id
<< " that will be queued up: " << session_event.event;
Expand Down Expand Up @@ -770,7 +775,7 @@ void OCPP::ready() {
}

const auto boot_reason = conversions::to_ocpp_boot_reason_enum(this->r_system->call_get_boot_reason());
if (this->charge_point->start({}, boot_reason)) {
if (this->charge_point->start({}, boot_reason, this->resuming_session_ids)) {
// signal that we're started
this->started = true;
EVLOG_info << "OCPP initialized";
Expand Down
Loading
Loading