Skip to content

Commit

Permalink
Feature/evsemanager persistent transactions (EVerest#789)
Browse files Browse the repository at this point in the history
* Added SessionEventEnum SessionResumed to allow the evse_manager to indicate that a previous session has been resumed at startup
* Added subscription to this event to OCPP module so that OCPP is aware that the EvseManager is aware of previous transactions. This allows OCPP to not terminate such transactions individually every time

---------

Signed-off-by: Cornelius Claussen <[email protected]>
Signed-off-by: pietfried <[email protected]>
Signed-off-by: florinmihut <[email protected]>
Signed-off-by: Piet Gömpel <[email protected]>
Co-authored-by: pietfried <[email protected]>
Co-authored-by: florinmihut <[email protected]>
Co-authored-by: Piet Gömpel <[email protected]>
  • Loading branch information
4 people authored Jul 31, 2024
1 parent 2452ed7 commit f720edb
Show file tree
Hide file tree
Showing 15 changed files with 186 additions and 12 deletions.
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
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

#include "PersistentStore.hpp"

namespace module {

PersistentStore::PersistentStore(const std::vector<std::unique_ptr<kvsIntf>>& _r_store, const std::string module_id) :
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

/*
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);

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;
std::string session_key;
bool active{false};
};

} // 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

0 comments on commit f720edb

Please sign in to comment.