Skip to content

Commit f720edb

Browse files
corneliusclaussenPietfriedflorinmihut
authored
Feature/evsemanager persistent transactions (EVerest#789)
* 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 <cc@pionix.de> Signed-off-by: pietfried <pietgoempel@gmail.com> Signed-off-by: florinmihut <florinmihut1@gmail.com> Signed-off-by: Piet Gömpel <37657534+Pietfried@users.noreply.github.com> Co-authored-by: pietfried <pietgoempel@gmail.com> Co-authored-by: florinmihut <florinmihut1@gmail.com> Co-authored-by: Piet Gömpel <37657534+Pietfried@users.noreply.github.com>
1 parent 2452ed7 commit f720edb

File tree

15 files changed

+186
-12
lines changed

15 files changed

+186
-12
lines changed

config/config-sil-ocpp.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ active_modules:
1313
config_module:
1414
device: auto
1515
supported_ISO15118_2: true
16+
persistent_store:
17+
module: PersistentStore
1618
evse_manager_1:
1719
evse: 1
1820
module: EvseManager
@@ -39,6 +41,9 @@ active_modules:
3941
hlc:
4042
- module_id: iso15118_charger
4143
implementation_id: charger
44+
store:
45+
- module_id: persistent_store
46+
implementation_id: main
4247
evse_manager_2:
4348
module: EvseManager
4449
evse: 2

dependencies.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ libevse-security:
6060
# OCPP
6161
libocpp:
6262
git: https://github.com/EVerest/libocpp.git
63-
git_tag: 1067cf3ddf27be3a43f8abc519b69da11c4ef921
63+
git_tag: aab1d5785bde141203b1a89b03d66fa91edf8093
6464
cmake_condition: "EVEREST_DEPENDENCY_ENABLED_LIBOCPP"
6565
# Josev
6666
Josev:

interfaces/powermeter.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ cmds:
1212
type: object
1313
$ref: /powermeter#/TransactionStartResponse
1414
stop_transaction:
15-
description: Stop the transaction on the power meter and return the signed metering information
15+
description: >-
16+
Stop the transaction on the power meter and return the signed metering information.
17+
If the transaction id is an empty string, all ongoing transaction should be cancelled.
18+
This is used on start up to clear dangling transactions that might still be ongoing
19+
in the power meter but are not known to the EvseManager.
1620
arguments:
1721
transaction_id:
1822
description: Transaction id

modules/EvseManager/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ target_sources(${MODULE_NAME}
1818
IECStateMachine.cpp
1919
ErrorHandling.cpp
2020
backtrace.cpp
21+
PersistentStore.cpp
2122
)
2223

2324
target_link_libraries(${MODULE_NAME}

modules/EvseManager/Charger.cpp

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@ namespace module {
2525

2626
Charger::Charger(const std::unique_ptr<IECStateMachine>& bsp, const std::unique_ptr<ErrorHandling>& error_handling,
2727
const std::vector<std::unique_ptr<powermeterIntf>>& r_powermeter_billing,
28+
const std::unique_ptr<PersistentStore>& _store,
2829
const types::evse_board_support::Connector_type& connector_type, const std::string& evse_id) :
2930
bsp(bsp),
3031
error_handling(error_handling),
32+
r_powermeter_billing(r_powermeter_billing),
33+
store(_store),
3134
connector_type(connector_type),
32-
evse_id(evse_id),
33-
r_powermeter_billing(r_powermeter_billing) {
35+
evse_id(evse_id) {
3436

3537
#ifdef EVEREST_USE_BACKTRACES
3638
Everest::install_backtrace_handler();
@@ -1169,6 +1171,8 @@ bool Charger::start_transaction() {
11691171
}
11701172
}
11711173

1174+
store->store_session(shared_context.session_uuid);
1175+
11721176
signal_transaction_started_event(shared_context.id_token);
11731177
return true;
11741178
}
@@ -1191,11 +1195,51 @@ void Charger::stop_transaction() {
11911195
}
11921196
}
11931197

1198+
store->clear_session();
1199+
11941200
signal_simple_event(types::evse_manager::SessionEventEnum::ChargingFinished);
11951201
signal_transaction_finished_event(shared_context.last_stop_transaction_reason,
11961202
shared_context.stop_transaction_id_token);
11971203
}
11981204

1205+
void Charger::cleanup_transactions_on_startup() {
1206+
// See if we have an open transaction in persistent storage
1207+
auto session_uuid = store->get_session();
1208+
if (not session_uuid.empty()) {
1209+
EVLOG_info << "Cleaning up transaction with UUID " << session_uuid << " on start up";
1210+
store->clear_session();
1211+
1212+
types::evse_manager::TransactionFinished transaction_finished;
1213+
1214+
// If yes, try to close nicely with the ID we remember and trigger a transaction finished event on success
1215+
for (const auto& meter : r_powermeter_billing) {
1216+
const auto response = meter->call_stop_transaction(session_uuid);
1217+
// If we fail to stop the transaction, it was probably just not active anymore
1218+
if (response.status == types::powermeter::TransactionRequestStatus::UNEXPECTED_ERROR) {
1219+
EVLOG_warning << "Failed to stop a transaction on the power meter " << response.error.value_or("");
1220+
break;
1221+
} else if (response.status == types::powermeter::TransactionRequestStatus::OK) {
1222+
// Fill in OCMF from the recovered transaction
1223+
transaction_finished.start_signed_meter_value = response.start_signed_meter_value;
1224+
transaction_finished.signed_meter_value = response.signed_meter_value;
1225+
break;
1226+
}
1227+
}
1228+
1229+
// Send out event to inform OCPP et al
1230+
std::optional<types::authorization::ProvidedIdToken> id_token;
1231+
signal_transaction_finished_event(types::evse_manager::StopTransactionReason::PowerLoss, id_token);
1232+
}
1233+
1234+
// Now we did what we could to clean up, so if there are still transactions going on in the power meter close them
1235+
// anyway. In this case we cannot generate a transaction finished event for OCPP et al since we cannot match it to
1236+
// our transaction anymore.
1237+
EVLOG_info << "Cleaning up any other transaction on start up";
1238+
for (const auto& meter : r_powermeter_billing) {
1239+
meter->call_stop_transaction("");
1240+
}
1241+
}
1242+
11991243
std::optional<types::units_signed::SignedMeterValue>
12001244
Charger::take_signed_meter_data(std::optional<types::units_signed::SignedMeterValue>& in) {
12011245
std::optional<types::units_signed::SignedMeterValue> out;

modules/EvseManager/Charger.hpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
#include "ErrorHandling.hpp"
4646
#include "EventQueue.hpp"
4747
#include "IECStateMachine.hpp"
48+
#include "PersistentStore.hpp"
4849
#include "scoped_lock_timeout.hpp"
4950
#include "utils.hpp"
5051

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

@@ -149,6 +151,7 @@ class Charger {
149151

150152
// Signal for EvseEvents
151153
sigslot::signal<types::evse_manager::SessionEventEnum> signal_simple_event;
154+
sigslot::signal<std::string> signal_session_resumed_event;
152155
sigslot::signal<types::evse_manager::StartSessionReason, std::optional<types::authorization::ProvidedIdToken>>
153156
signal_session_started_event;
154157
sigslot::signal<types::authorization::ProvidedIdToken> signal_transaction_started_event;
@@ -212,6 +215,8 @@ class Charger {
212215
connector_type = t;
213216
}
214217

218+
void cleanup_transactions_on_startup();
219+
215220
private:
216221
utils::Stopwatch stopwatch;
217222

@@ -358,12 +363,11 @@ class Charger {
358363

359364
const std::unique_ptr<IECStateMachine>& bsp;
360365
const std::unique_ptr<ErrorHandling>& error_handling;
361-
366+
const std::vector<std::unique_ptr<powermeterIntf>>& r_powermeter_billing;
367+
const std::unique_ptr<PersistentStore>& store;
362368
std::atomic<types::evse_board_support::Connector_type> connector_type{
363369
types::evse_board_support::Connector_type::IEC62196Type2Cable};
364-
365370
const std::string evse_id;
366-
const std::vector<std::unique_ptr<powermeterIntf>>& r_powermeter_billing;
367371

368372
// ErrorHandling events
369373
enum class ErrorHandlingEvents : std::uint8_t {

modules/EvseManager/EvseManager.cpp

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ inline static types::authorization::ProvidedIdToken create_autocharge_token(std:
4242
}
4343

4444
void EvseManager::init() {
45+
46+
store = std::unique_ptr<PersistentStore>(new PersistentStore(r_store, info.id));
47+
4548
random_delay_enabled = config.uk_smartcharging_random_delay_enable;
4649
random_delay_max_duration = std::chrono::seconds(config.uk_smartcharging_random_delay_max_duration);
4750
if (random_delay_enabled) {
@@ -159,8 +162,8 @@ void EvseManager::ready() {
159162
error_handling =
160163
std::unique_ptr<ErrorHandling>(new ErrorHandling(r_bsp, r_hlc, r_connector_lock, r_ac_rcd, p_evse, r_imd));
161164

162-
charger = std::unique_ptr<Charger>(
163-
new Charger(bsp, error_handling, r_powermeter_billing(), hw_capabilities.connector_type, config.evse_id));
165+
charger = std::unique_ptr<Charger>(new Charger(bsp, error_handling, r_powermeter_billing(), store,
166+
hw_capabilities.connector_type, config.evse_id));
164167

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

932+
// Resuming left-over transaction from e.g. powerloss. This information allows other modules like to OCPP to be
933+
// informed that the EvseManager is aware of previous sessions so that no individual cleanup is required
934+
const auto session_id = store->get_session();
935+
if (!session_id.empty()) {
936+
charger->signal_session_resumed_event(session_id);
937+
}
938+
939+
// By default cleanup left-over transaction from e.g. power loss
940+
// TOOD: Add resume handling
941+
charger->cleanup_transactions_on_startup();
942+
929943
// start with a limit of 0 amps. We will get a budget from EnergyManager that is locally limited by hw
930944
// caps.
931945
charger->set_max_current(0.0F, date::utc_clock::now() + std::chrono::seconds(120));

modules/EvseManager/EvseManager.hpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <generated/interfaces/connector_lock/Interface.hpp>
2323
#include <generated/interfaces/evse_board_support/Interface.hpp>
2424
#include <generated/interfaces/isolation_monitor/Interface.hpp>
25+
#include <generated/interfaces/kvs/Interface.hpp>
2526
#include <generated/interfaces/power_supply_DC/Interface.hpp>
2627
#include <generated/interfaces/powermeter/Interface.hpp>
2728
#include <generated/interfaces/slac/Interface.hpp>
@@ -41,6 +42,7 @@
4142
#include "CarManufacturer.hpp"
4243
#include "Charger.hpp"
4344
#include "ErrorHandling.hpp"
45+
#include "PersistentStore.hpp"
4446
#include "SessionLog.hpp"
4547
#include "VarContainer.hpp"
4648
#include "scoped_lock_timeout.hpp"
@@ -110,7 +112,8 @@ class EvseManager : public Everest::ModuleBase {
110112
std::vector<std::unique_ptr<powermeterIntf>> r_powermeter_car_side,
111113
std::vector<std::unique_ptr<slacIntf>> r_slac, std::vector<std::unique_ptr<ISO15118_chargerIntf>> r_hlc,
112114
std::vector<std::unique_ptr<isolation_monitorIntf>> r_imd,
113-
std::vector<std::unique_ptr<power_supply_DCIntf>> r_powersupply_DC, Conf& config) :
115+
std::vector<std::unique_ptr<power_supply_DCIntf>> r_powersupply_DC,
116+
std::vector<std::unique_ptr<kvsIntf>> r_store, Conf& config) :
114117
ModuleBase(info),
115118
mqtt(mqtt_provider),
116119
telemetry(telemetry),
@@ -127,6 +130,7 @@ class EvseManager : public Everest::ModuleBase {
127130
r_hlc(std::move(r_hlc)),
128131
r_imd(std::move(r_imd)),
129132
r_powersupply_DC(std::move(r_powersupply_DC)),
133+
r_store(std::move(r_store)),
130134
config(config){};
131135

132136
Everest::MqttProvider& mqtt;
@@ -144,6 +148,7 @@ class EvseManager : public Everest::ModuleBase {
144148
const std::vector<std::unique_ptr<ISO15118_chargerIntf>> r_hlc;
145149
const std::vector<std::unique_ptr<isolation_monitorIntf>> r_imd;
146150
const std::vector<std::unique_ptr<power_supply_DCIntf>> r_powersupply_DC;
151+
const std::vector<std::unique_ptr<kvsIntf>> r_store;
147152
const Conf& config;
148153

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

192197
std::unique_ptr<IECStateMachine> bsp;
193198
std::unique_ptr<ErrorHandling> error_handling;
199+
std::unique_ptr<PersistentStore> store;
194200

195201
std::atomic_bool random_delay_enabled{false};
196202
std::atomic_bool random_delay_running{false};
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// Copyright 2023 Pionix GmbH and Contributors to EVerest
3+
4+
#include "PersistentStore.hpp"
5+
6+
namespace module {
7+
8+
PersistentStore::PersistentStore(const std::vector<std::unique_ptr<kvsIntf>>& _r_store, const std::string module_id) :
9+
r_store(_r_store) {
10+
11+
if (r_store.size() > 0) {
12+
active = true;
13+
}
14+
15+
session_key = module_id + "_session";
16+
}
17+
18+
void PersistentStore::store_session(const std::string& session_uuid) {
19+
if (active) {
20+
r_store[0]->call_store(session_key, session_uuid);
21+
}
22+
}
23+
24+
void PersistentStore::clear_session() {
25+
if (active) {
26+
r_store[0]->call_store(session_key, "");
27+
}
28+
}
29+
30+
std::string PersistentStore::get_session() {
31+
if (active) {
32+
auto r = r_store[0]->call_load(session_key);
33+
try {
34+
if (std::holds_alternative<std::string>(r)) {
35+
return std::get<std::string>(r);
36+
}
37+
} catch (...) {
38+
return {};
39+
}
40+
}
41+
return {};
42+
}
43+
44+
} // namespace module
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest
3+
4+
/*
5+
The Persistent Store class is an abstraction layer to store any persistent information
6+
(such as sessions) for the EvseManager.
7+
*/
8+
9+
#ifndef EVSE_MANAGER_PERSISTENT_STORE_H_
10+
#define EVSE_MANAGER_PERSISTENT_STORE_H_
11+
12+
#include <generated/interfaces/kvs/Interface.hpp>
13+
14+
namespace module {
15+
16+
class PersistentStore {
17+
public:
18+
// We need the r_bsp reference to be able to talk to the bsp driver module
19+
explicit PersistentStore(const std::vector<std::unique_ptr<kvsIntf>>& r_store, const std::string module_id);
20+
21+
void store_session(const std::string& session_uuid);
22+
void clear_session();
23+
std::string get_session();
24+
25+
private:
26+
const std::vector<std::unique_ptr<kvsIntf>>& r_store;
27+
std::string session_key;
28+
bool active{false};
29+
};
30+
31+
} // namespace module
32+
33+
#endif // EVSE_MANAGER_PERSISTENT_STORE_H_

0 commit comments

Comments
 (0)