Files
cariflex/tools/EVerest-main/modules/EVSE/EvseManager/tests/ChargerTest.cpp
Eric F d398a6ced2 Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1)
- OpenOCPP extracted (firmware OCPP 1.6J/2.0.1)
- ShapeShifter library installed (pip install -e)
- ShapeShifter specification extracted
- EVerest extracted

TODO updated with progress
2026-06-08 00:38:27 -04:00

956 lines
40 KiB
C++

// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "generated/types/evse_manager.hpp"
#include <gtest/gtest.h>
#include <Charger.hpp>
#include <memory>
#include <optional>
namespace {
using namespace module;
using namespace types::evse_manager;
// ----------------------------------------------------------------------------
// test classes
// class that provides access to internal state from the Charger class
struct ChargerDerived : public Charger {
using Charger::Charger;
using Charger::get_enable_disable_source_table;
using Charger::get_shared_context;
using Charger::run_state_machine;
// updated when a non-zero connector is used to enable_disable()
constexpr const auto& connector_enabled() {
return get_shared_context().connector_enabled;
}
constexpr const auto& current_state() {
return get_shared_context().current_state;
}
constexpr void current_state(EvseState state) {
get_shared_context().current_state = state;
}
constexpr const auto& flag_disable_requested() {
return get_shared_context().flag_disable_requested;
}
};
// class that creates a consistent starting state for tests
struct ChargerTest : public testing::Test {
// charger requirements
std::unique_ptr<IECStateMachine> charger_bsp;
std::unique_ptr<ErrorHandling> charger_error_handling;
std::vector<std::unique_ptr<powermeterIntf>> charger_powermeter_billing;
std::unique_ptr<PersistentStore> charger_store;
// error handling requirements
std::unique_ptr<evse_board_supportIntf> error_handler_bsp;
std::vector<std::unique_ptr<ISO15118_chargerIntf>> error_handler_hlc;
std::vector<std::unique_ptr<connector_lockIntf>> error_handler_connector_lock;
std::vector<std::unique_ptr<ac_rcdIntf>> error_handler_ac_rcd;
std::unique_ptr<evse_managerImplBase> error_handler_evse;
std::vector<std::unique_ptr<isolation_monitorIntf>> error_handler_imd;
std::vector<std::unique_ptr<power_supply_DCIntf>> error_handler_powersupply;
std::vector<std::unique_ptr<powermeterIntf>> error_handler_powermeter;
std::vector<std::unique_ptr<over_voltage_monitorIntf>> error_handler_over_voltage_monitor;
std::unique_ptr<ChargerDerived> charger;
ChargerTest() :
charger_error_handling(std::make_unique<ErrorHandling>(
error_handler_bsp, error_handler_hlc, error_handler_connector_lock, error_handler_ac_rcd,
error_handler_evse, error_handler_imd, error_handler_powersupply, error_handler_powermeter,
error_handler_over_voltage_monitor, false)) {
}
void SetUp() override {
reset_last_event();
charger = std::make_unique<ChargerDerived>(
charger_bsp, charger_error_handling, charger_powermeter_billing, charger_store,
types::evse_board_support::Connector_type::IEC62196Type2Socket, "EVSETEST");
charger->signal_simple_event.connect(&ChargerTest::session_event, this);
}
void TearDown() override {
charger.reset(nullptr);
}
void session_event(SessionEventEnum event) {
last_event = event;
}
static constexpr SessionEventEnum default_event{SessionEventEnum::SessionFinished};
static constexpr EnableDisableSource default_source{Enable_source::Unspecified, Enable_state::Unassigned, 10000};
SessionEventEnum last_event{default_event};
constexpr void reset_last_event() {
last_event = default_event;
}
};
// ----------------------------------------------------------------------------
// tests for enable_disable()
// interesting variables:
// - enable_disable_source_table (not directly available)
// - shared_context.connector_enabled - charger->connector_enabled()
// - shared_context.current_state - charger->current_state()
// - signal_simple_event - last_event
TEST_F(ChargerTest, EnableDisableSourceInit) {
// check the default values on startup
// this is the starting point for all tests
const auto last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, default_source);
EXPECT_EQ(last_event, default_event);
EXPECT_TRUE(charger->connector_enabled());
EXPECT_EQ(charger->current_state(), Charger::EvseState::Idle);
}
TEST_F(ChargerTest, EnableDisableSourceInitPlusStateEnabled) {
charger->current_state(Charger::EvseState::Idle);
// check the default values on startup
// this is the starting point for all tests
auto last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, default_source);
EXPECT_EQ(last_event, default_event);
EXPECT_TRUE(charger->connector_enabled());
EXPECT_EQ(charger->current_state(), Charger::EvseState::Idle);
reset_last_event();
constexpr EnableDisableSource enable_default{Enable_source::Unspecified, Enable_state::Enable, 10000};
charger->enable_disable_initial_state_publish();
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, enable_default);
EXPECT_EQ(last_event, SessionEventEnum::Enabled);
EXPECT_TRUE(charger->connector_enabled());
EXPECT_EQ(charger->current_state(), Charger::EvseState::Idle);
// already enabled so no event
reset_last_event();
constexpr EnableDisableSource enable_source{Enable_source::CSMS, Enable_state::Enable, 100};
EXPECT_TRUE(charger->enable_disable(0, enable_source));
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, enable_source);
EXPECT_EQ(last_event, default_event);
}
TEST_F(ChargerTest, EnableDisableSourceInitPlusStateDisabled) {
charger->current_state(Charger::EvseState::Disabled);
// check the default values on startup
// this is the starting point for all tests
auto last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, default_source);
EXPECT_EQ(last_event, default_event);
EXPECT_TRUE(charger->connector_enabled());
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
reset_last_event();
constexpr EnableDisableSource disable_default{Enable_source::Unspecified, Enable_state::Disable, 10000};
charger->enable_disable_initial_state_publish();
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, disable_default);
EXPECT_EQ(last_event, SessionEventEnum::Disabled);
EXPECT_TRUE(charger->connector_enabled());
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
// already disabled so no event
reset_last_event();
constexpr EnableDisableSource disable_source{Enable_source::CSMS, Enable_state::Disable, 100};
EXPECT_FALSE(charger->enable_disable(0, disable_source));
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, disable_source);
EXPECT_EQ(last_event, default_event);
}
TEST_F(ChargerTest, EnableDisableSourceConnectorEnabled0) {
// connector_enabled must only change when a non-zero connector ID is used
ASSERT_TRUE(charger->connector_enabled());
constexpr EnableDisableSource enable_source{Enable_source::CSMS, Enable_state::Enable, 100};
constexpr EnableDisableSource disable_source{Enable_source::CSMS, Enable_state::Disable, 100};
// test with connector ID 0: connector_enabled must not change
EXPECT_FALSE(charger->enable_disable(0, disable_source));
EXPECT_TRUE(charger->connector_enabled());
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
EXPECT_TRUE(charger->enable_disable(0, enable_source));
EXPECT_TRUE(charger->connector_enabled());
// tricky case, connector_enabled is true but the change was to connector 0
// this is what the original code does ...
EXPECT_EQ(charger->current_state(), Charger::EvseState::Idle);
// force connector_enabled false
EXPECT_FALSE(charger->enable_disable(1, disable_source));
EXPECT_FALSE(charger->connector_enabled());
// test with connector ID 0: connector_enabled must not change
EXPECT_FALSE(charger->enable_disable(0, disable_source));
EXPECT_FALSE(charger->connector_enabled());
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
EXPECT_TRUE(charger->enable_disable(0, enable_source));
EXPECT_FALSE(charger->connector_enabled());
// enable on connector 0 does not change state to Idle
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
}
TEST_F(ChargerTest, EnableDisableSourceConnectorEnabled1) {
// connector_enabled must only change when a non-zero connector ID is used
ASSERT_TRUE(charger->connector_enabled());
constexpr EnableDisableSource enable_source{Enable_source::CSMS, Enable_state::Enable, 100};
constexpr EnableDisableSource disable_source{Enable_source::CSMS, Enable_state::Disable, 100};
// test with connector ID 1: connector_enabled must change
EXPECT_FALSE(charger->enable_disable(1, disable_source));
EXPECT_FALSE(charger->connector_enabled());
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
EXPECT_TRUE(charger->enable_disable(1, enable_source));
EXPECT_TRUE(charger->connector_enabled());
EXPECT_EQ(charger->current_state(), Charger::EvseState::Idle);
EXPECT_FALSE(charger->enable_disable(1, disable_source));
EXPECT_FALSE(charger->connector_enabled());
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
}
constexpr EnableDisableSource enable_source_10{Enable_source::CSMS, Enable_state::Enable, 10};
constexpr EnableDisableSource enable_source_100{Enable_source::CSMS, Enable_state::Enable, 100};
constexpr EnableDisableSource enable_source_1000{Enable_source::CSMS, Enable_state::Enable, 1000};
constexpr EnableDisableSource disable_source_10{Enable_source::CSMS, Enable_state::Disable, 10};
constexpr EnableDisableSource disable_source_100{Enable_source::CSMS, Enable_state::Disable, 100};
constexpr EnableDisableSource disable_source_1000{Enable_source::CSMS, Enable_state::Disable, 1000};
constexpr EnableDisableSource unassigned_source_10{Enable_source::CSMS, Enable_state::Unassigned, 10};
constexpr EnableDisableSource unassigned_source_100{Enable_source::CSMS, Enable_state::Unassigned, 100};
constexpr EnableDisableSource unassigned_source_1000{Enable_source::CSMS, Enable_state::Unassigned, 1000};
TEST_F(ChargerTest, EnableDisableTableSingleSource) {
// enable_disable settings are added to a table
// parse_enable_disable_source_table() processes the table and updates
// active_enable_disable_source which is available via get_last_enable_disable_source()
// enable_disable() updates the table and calls parse_enable_disable_source_table()
// EnableDisableTable
// Evaluation will be done based on priorities. 0 is the highest priority, 10000 the lowest
// If all sources are unassigned, the connector is enabled
// If two sources have the same priority, "disabled" has priority over "enabled"
const int connector_id = 1; // use a consistent value
const auto& enable_disable_source_table = charger->get_enable_disable_source_table();
EXPECT_TRUE(enable_disable_source_table.empty());
EXPECT_EQ(enable_disable_source_table.size(), 0);
auto last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, default_source);
// check source change of state
EXPECT_TRUE(charger->enable_disable(connector_id, enable_source_100));
EXPECT_EQ(enable_disable_source_table.size(), 1);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, enable_source_100);
EXPECT_FALSE(charger->enable_disable(connector_id, disable_source_100));
EXPECT_EQ(enable_disable_source_table.size(), 1);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, disable_source_100);
EXPECT_TRUE(charger->enable_disable(connector_id, unassigned_source_100));
EXPECT_EQ(enable_disable_source_table.size(), 1);
last_source = charger->get_last_enable_disable_source();
// unexpected
EXPECT_EQ(last_source, default_source);
// check source change of priority
EXPECT_TRUE(charger->enable_disable(connector_id, enable_source_1000));
EXPECT_EQ(enable_disable_source_table.size(), 1);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, enable_source_1000);
EXPECT_TRUE(charger->enable_disable(connector_id, enable_source_100));
EXPECT_EQ(enable_disable_source_table.size(), 1);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, enable_source_100);
EXPECT_TRUE(charger->enable_disable(connector_id, enable_source_10));
EXPECT_EQ(enable_disable_source_table.size(), 1);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, enable_source_10);
EXPECT_FALSE(charger->enable_disable(connector_id, disable_source_1000));
EXPECT_EQ(enable_disable_source_table.size(), 1);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, disable_source_1000);
EXPECT_FALSE(charger->enable_disable(connector_id, disable_source_100));
EXPECT_EQ(enable_disable_source_table.size(), 1);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, disable_source_100);
EXPECT_FALSE(charger->enable_disable(connector_id, disable_source_10));
EXPECT_EQ(enable_disable_source_table.size(), 1);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, disable_source_10);
// last_source is always the default since Unassigned
// entries are ignored
EXPECT_TRUE(charger->enable_disable(connector_id, unassigned_source_1000));
EXPECT_EQ(enable_disable_source_table.size(), 1);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, default_source);
EXPECT_TRUE(charger->enable_disable(connector_id, unassigned_source_100));
EXPECT_EQ(enable_disable_source_table.size(), 1);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, default_source);
EXPECT_TRUE(charger->enable_disable(connector_id, unassigned_source_10));
EXPECT_EQ(enable_disable_source_table.size(), 1);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, default_source);
}
TEST_F(ChargerTest, EnableDisableTableSourceMulti) {
// enable_disable settings are added to a table
// parse_enable_disable_source_table() processes the table and updates
// active_enable_disable_source which is available via get_last_enable_disable_source()
// enable_disable() updates the table and calls parse_enable_disable_source_table()
// EnableDisableTable
// Evaluation will be done based on priorities. 0 is the highest priority, 10000 the lowest
// If all sources are unassigned, the connector is enabled
// If two sources have the same priority, "disabled" has priority over "enabled"
const int connector_id = 1; // use a consistent value
const auto& enable_disable_source_table = charger->get_enable_disable_source_table();
EXPECT_TRUE(enable_disable_source_table.empty());
EXPECT_EQ(enable_disable_source_table.size(), 0);
auto last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, default_source);
// check multiple sources are added to the table
EXPECT_TRUE(charger->enable_disable(connector_id, enable_source_10));
EXPECT_EQ(enable_disable_source_table.size(), 1);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, enable_source_10);
EXPECT_TRUE(charger->enable_disable(connector_id, {Enable_source::FirmwareUpdate, Enable_state::Enable, 11}));
EXPECT_EQ(enable_disable_source_table.size(), 2);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, enable_source_10);
EXPECT_TRUE(charger->enable_disable(connector_id, {Enable_source::LocalAPI, Enable_state::Enable, 11}));
EXPECT_EQ(enable_disable_source_table.size(), 3);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, enable_source_10);
EXPECT_TRUE(charger->enable_disable(connector_id, {Enable_source::LocalKeyLock, Enable_state::Enable, 11}));
EXPECT_EQ(enable_disable_source_table.size(), 4);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, enable_source_10);
EXPECT_TRUE(charger->enable_disable(connector_id, {Enable_source::MobileApp, Enable_state::Enable, 11}));
EXPECT_EQ(enable_disable_source_table.size(), 5);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, enable_source_10);
EXPECT_TRUE(charger->enable_disable(connector_id, {Enable_source::RemoteKeyLock, Enable_state::Enable, 11}));
EXPECT_EQ(enable_disable_source_table.size(), 6);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, enable_source_10);
const EnableDisableSource disable_source{Enable_source::ServiceTechnician, Enable_state::Disable, 11};
EXPECT_TRUE(charger->enable_disable(connector_id, disable_source));
EXPECT_EQ(enable_disable_source_table.size(), 7);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, enable_source_10);
EXPECT_TRUE(charger->enable_disable(connector_id, {Enable_source::Unspecified, Enable_state::Enable, 11}));
EXPECT_EQ(enable_disable_source_table.size(), 8);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, enable_source_10);
// update CSMS - next highest expected - disable_source
EXPECT_FALSE(charger->enable_disable(connector_id, enable_source_100));
EXPECT_EQ(enable_disable_source_table.size(), 8);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, disable_source);
}
TEST_F(ChargerTest, EnableDisableTablePriority) {
// enable_disable settings are added to a table
// parse_enable_disable_source_table() processes the table and updates
// active_enable_disable_source which is available via get_last_enable_disable_source()
// enable_disable() updates the table and calls parse_enable_disable_source_table()
// EnableDisableTable
// Evaluation will be done based on priorities. 0 is the highest priority, 10000 the lowest
// If all sources are unassigned, the connector is enabled
// If two sources have the same priority, "disabled" has priority over "enabled"
const int connector_id = 1; // use a consistent value
const auto& enable_disable_source_table = charger->get_enable_disable_source_table();
EXPECT_TRUE(enable_disable_source_table.empty());
EXPECT_EQ(enable_disable_source_table.size(), 0);
auto last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, default_source);
// check priority is being respected (via different sources)
// base priority is 10 - higher values must be ignored
EXPECT_FALSE(charger->enable_disable(connector_id, disable_source_10));
EXPECT_EQ(enable_disable_source_table.size(), 1);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, disable_source_10);
// ignored because disable has higher priority over enabled
const EnableDisableSource next_expected{Enable_source::FirmwareUpdate, Enable_state::Enable, 10};
EXPECT_FALSE(charger->enable_disable(connector_id, next_expected));
EXPECT_EQ(enable_disable_source_table.size(), 2);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, disable_source_10);
// ignored because unassigned is ignored
EXPECT_FALSE(charger->enable_disable(connector_id, {Enable_source::LocalAPI, Enable_state::Unassigned, 9}));
EXPECT_EQ(enable_disable_source_table.size(), 3);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, disable_source_10);
// ignored because priority is lower
EXPECT_FALSE(charger->enable_disable(connector_id, {Enable_source::LocalAPI, Enable_state::Disable, 12}));
EXPECT_EQ(enable_disable_source_table.size(), 3);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, disable_source_10);
// ignored because priority is even lower
EXPECT_FALSE(charger->enable_disable(connector_id, {Enable_source::LocalAPI, Enable_state::Enable, 200}));
EXPECT_EQ(enable_disable_source_table.size(), 3);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, disable_source_10);
// overrides CSMS so it is ignored - expected is
EXPECT_TRUE(charger->enable_disable(connector_id, unassigned_source_10));
EXPECT_EQ(enable_disable_source_table.size(), 3);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, next_expected);
// disabled takes priority
const EnableDisableSource last_expected{Enable_source::LocalAPI, Enable_state::Disable, 10};
EXPECT_FALSE(charger->enable_disable(connector_id, last_expected));
EXPECT_EQ(enable_disable_source_table.size(), 3);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, last_expected);
// higher priority
const EnableDisableSource higher_expected{Enable_source::FirmwareUpdate, Enable_state::Enable, 5};
EXPECT_TRUE(charger->enable_disable(connector_id, higher_expected));
EXPECT_EQ(enable_disable_source_table.size(), 3);
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, higher_expected);
}
TEST_F(ChargerTest, EnableDisableSourceEnable0) {
constexpr EnableDisableSource sourceA{Enable_source::CSMS, Enable_state::Unassigned, 100};
constexpr EnableDisableSource sourceB{Enable_source::FirmwareUpdate, Enable_state::Enable, 90};
// enable from default state
EXPECT_TRUE(charger->enable_disable(0, sourceA));
auto last_source = charger->get_last_enable_disable_source();
// Unassigned updates do not change the result from get_last_enable_disable_source()
EXPECT_EQ(last_source, default_source);
// There should not be an update event - default state is enabled
EXPECT_EQ(last_event, default_event);
EXPECT_EQ(charger->current_state(), Charger::EvseState::Idle);
EXPECT_TRUE(charger->connector_enabled()); // default state
reset_last_event();
EXPECT_TRUE(charger->enable_disable(0, sourceB));
last_source = charger->get_last_enable_disable_source();
// Unassigned updates do not change the result from get_last_enable_disable_source()
EXPECT_EQ(last_source, sourceB);
// There should not be an update event - enabled -> enabled
EXPECT_EQ(last_event, default_event);
EXPECT_EQ(charger->current_state(), Charger::EvseState::Idle);
EXPECT_TRUE(charger->connector_enabled()); // default state
}
TEST_F(ChargerTest, EnableDisableSourceEnable1A) {
constexpr EnableDisableSource sourceA{Enable_source::CSMS, Enable_state::Disable, 100};
constexpr EnableDisableSource sourceB{Enable_source::FirmwareUpdate, Enable_state::Enable, 90};
// force a complete disable state
EXPECT_FALSE(charger->enable_disable(1, sourceA));
auto last_source = charger->get_last_enable_disable_source();
// Unassigned updates do not change the result from get_last_enable_disable_source()
EXPECT_EQ(last_source, sourceA);
EXPECT_EQ(last_event, SessionEventEnum::Disabled);
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
EXPECT_FALSE(charger->connector_enabled());
// enable on connector 1
reset_last_event();
EXPECT_TRUE(charger->enable_disable(1, sourceB));
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, sourceB);
EXPECT_EQ(last_event, SessionEventEnum::Enabled);
EXPECT_EQ(charger->current_state(), Charger::EvseState::Idle);
EXPECT_TRUE(charger->connector_enabled());
}
TEST_F(ChargerTest, EnableDisableSourceEnable1B) {
constexpr EnableDisableSource sourceA{Enable_source::CSMS, Enable_state::Disable, 100};
constexpr EnableDisableSource sourceB{Enable_source::FirmwareUpdate, Enable_state::Enable, 90};
// force a complete disable state
EXPECT_FALSE(charger->enable_disable(1, sourceA));
auto last_source = charger->get_last_enable_disable_source();
// Unassigned updates do not change the result from get_last_enable_disable_source()
EXPECT_EQ(last_source, sourceA);
EXPECT_EQ(last_event, SessionEventEnum::Disabled);
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
EXPECT_FALSE(charger->connector_enabled());
// enable on connector 0
reset_last_event();
EXPECT_TRUE(charger->enable_disable(0, sourceB));
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, sourceB);
EXPECT_EQ(last_event, SessionEventEnum::Enabled);
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
EXPECT_FALSE(charger->connector_enabled());
// enable on connector 1
reset_last_event();
EXPECT_TRUE(charger->enable_disable(1, sourceB));
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, sourceB);
// enable -> enable so no event
EXPECT_EQ(last_event, default_event);
EXPECT_EQ(charger->current_state(), Charger::EvseState::Idle);
EXPECT_TRUE(charger->connector_enabled());
}
TEST_F(ChargerTest, EnableDisableSourceDisable0) {
constexpr EnableDisableSource sourceA{Enable_source::CSMS, Enable_state::Unassigned, 100};
constexpr EnableDisableSource sourceB{Enable_source::FirmwareUpdate, Enable_state::Disable, 100};
// disable from default state
EXPECT_TRUE(charger->enable_disable(0, sourceA));
auto last_source = charger->get_last_enable_disable_source();
// Unassigned updates do not change the result from get_last_enable_disable_source()
EXPECT_EQ(last_source, default_source);
// This is possibly an error
EXPECT_EQ(last_event, default_event);
EXPECT_EQ(charger->current_state(), Charger::EvseState::Idle);
reset_last_event();
EXPECT_FALSE(charger->enable_disable(0, sourceB));
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, sourceB);
EXPECT_EQ(last_event, SessionEventEnum::Disabled);
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
}
TEST_F(ChargerTest, EnableDisableSourceDisable1) {
constexpr EnableDisableSource sourceA{Enable_source::CSMS, Enable_state::Unassigned, 100};
constexpr EnableDisableSource sourceB{Enable_source::FirmwareUpdate, Enable_state::Disable, 100};
// disable from default state
EXPECT_TRUE(charger->enable_disable(0, sourceA));
auto last_source = charger->get_last_enable_disable_source();
// Unassigned updates do not change the result from get_last_enable_disable_source()
EXPECT_EQ(last_source, default_source);
// This is possibly an error
EXPECT_EQ(last_event, default_event);
EXPECT_EQ(charger->current_state(), Charger::EvseState::Idle);
reset_last_event();
EXPECT_FALSE(charger->enable_disable(0, sourceB));
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, sourceB);
EXPECT_EQ(last_event, SessionEventEnum::Disabled);
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
reset_last_event();
EXPECT_FALSE(charger->enable_disable(1, sourceB));
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, sourceB);
// disable -> disable so no event
EXPECT_EQ(last_event, default_event);
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
}
TEST_F(ChargerTest, EnableDisableSourceDisableEnable) {
constexpr EnableDisableSource sourceA{Enable_source::CSMS, Enable_state::Unassigned, 100};
constexpr EnableDisableSource sourceB{Enable_source::FirmwareUpdate, Enable_state::Disable, 100};
constexpr EnableDisableSource sourceC{Enable_source::LocalAPI, Enable_state::Enable, 90};
// default state
EXPECT_TRUE(charger->enable_disable(0, sourceA));
auto last_source = charger->get_last_enable_disable_source();
// Unassigned updates do not change the result from get_last_enable_disable_source()
EXPECT_EQ(last_source, default_source);
// This is possibly an error
EXPECT_EQ(last_event, default_event);
EXPECT_EQ(charger->current_state(), Charger::EvseState::Idle);
// disable 0
reset_last_event();
EXPECT_FALSE(charger->enable_disable(0, sourceB));
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, sourceB);
EXPECT_EQ(last_event, SessionEventEnum::Disabled);
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
// disable 1
reset_last_event();
EXPECT_FALSE(charger->enable_disable(1, sourceB));
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, sourceB);
// no event: disable -> disable
EXPECT_EQ(last_event, default_event);
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
// enable 0
reset_last_event();
EXPECT_TRUE(charger->enable_disable(0, sourceC));
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, sourceC);
EXPECT_EQ(last_event, SessionEventEnum::Enabled);
// remains disabled because the enable was on connector 0
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
// enable 1
reset_last_event();
EXPECT_TRUE(charger->enable_disable(1, sourceC));
last_source = charger->get_last_enable_disable_source();
EXPECT_EQ(last_source, sourceC);
// enable->enable hence no event
EXPECT_EQ(last_event, default_event);
// updated because not connector 0
EXPECT_EQ(charger->current_state(), Charger::EvseState::Idle);
}
// tests for cancel_transaction() / authorize() interaction
TEST_F(ChargerTest, DelayedAuthorizeAfterCancelTransactionIsIgnored) {
auto& ctx = charger->get_shared_context();
// Simulate an active charging session
ctx.flag_transaction_active = true;
ctx.session_active = true;
ctx.flag_authorized = true;
// External cancellation (e.g. OCPP RemoteStop)
types::evse_manager::StopTransactionRequest stop_request;
stop_request.reason = types::evse_manager::StopTransactionReason::Remote;
EXPECT_TRUE(charger->cancel_transaction(stop_request));
EXPECT_FALSE(ctx.flag_authorized);
EXPECT_TRUE(ctx.flag_externally_cancelled);
// Delayed authorization response arrives after the cancellation
types::authorization::ProvidedIdToken token;
token.id_token.value = "DELAYED_TOKEN";
token.id_token.type = types::authorization::IdTokenType::Central;
token.authorization_type = types::authorization::AuthorizationType::OCPP;
types::authorization::ValidationResult validation_result;
validation_result.authorization_status = types::authorization::AuthorizationStatus::Accepted;
charger->authorize(true, token, validation_result);
// The delayed response must not restore authorization
EXPECT_FALSE(ctx.flag_authorized);
EXPECT_TRUE(ctx.flag_externally_cancelled);
}
// Test that disabling while a transaction is active goes through the proper
// StoppingCharging->Finished->Disabled sequence instead of jumping directly.
TEST_F(ChargerTest, DisableDuringActiveTransaction) {
constexpr EnableDisableSource disable_source{Enable_source::CSMS, Enable_state::Disable, 100};
auto& ctx = charger->get_shared_context();
// Simulate an active charging session with contactors closed
ctx.current_state = Charger::EvseState::Charging;
ctx.flag_transaction_active = true;
ctx.session_active = true;
ctx.flag_authorized = true;
ctx.contactor_open = false;
reset_last_event();
EXPECT_FALSE(charger->enable_disable(1, disable_source));
// enable_disable calls run_state_machine synchronously: Charging->StoppingCharging.
// Must NOT immediately jump to Disabled — session needs proper teardown.
EXPECT_EQ(ctx.current_state, Charger::EvseState::StoppingCharging);
EXPECT_TRUE(charger->flag_disable_requested());
EXPECT_EQ(ctx.last_stop_transaction_reason, StopTransactionReason::EVSEDisabled);
// Simulate relay opening and transaction already stopped
ctx.contactor_open = true;
ctx.flag_transaction_active = false;
// State machine: StoppingCharging->Finished->Disabled (all in one loop)
reset_last_event();
charger->run_state_machine();
EXPECT_EQ(ctx.current_state, Charger::EvseState::Disabled);
EXPECT_EQ(last_event, SessionEventEnum::Disabled);
}
// Test that disabling while in WaitingForAuthentication (no transaction yet)
// ends the session and transitions to Disabled without going through StoppingCharging.
TEST_F(ChargerTest, DisableDuringWaitingForAuthentication) {
constexpr EnableDisableSource disable_source{Enable_source::CSMS, Enable_state::Disable, 100};
auto& ctx = charger->get_shared_context();
// Simulate EV plugged in, waiting for auth — no transaction started yet
ctx.current_state = Charger::EvseState::WaitingForAuthentication;
ctx.session_active = true;
ctx.flag_ev_plugged_in = true;
ctx.flag_transaction_active = false;
ctx.flag_authorized = false;
reset_last_event();
EXPECT_FALSE(charger->enable_disable(1, disable_source));
// run_state_machine is called synchronously inside enable_disable, so by
// the time enable_disable returns the state machine has already driven
// WaitingForAuthentication -> Finished -> Disabled.
EXPECT_EQ(ctx.current_state, Charger::EvseState::Disabled);
EXPECT_EQ(last_event, SessionEventEnum::Disabled);
}
// Test that disabling while in Idle (no session) immediately transitions to Disabled
TEST_F(ChargerTest, DisableDuringIdle) {
constexpr EnableDisableSource disable_source{Enable_source::CSMS, Enable_state::Disable, 100};
auto& ctx = charger->get_shared_context();
// Simulate EV plugged in, no session active
ctx.current_state = Charger::EvseState::Idle;
ctx.session_active = false;
ctx.flag_ev_plugged_in = true;
ctx.flag_transaction_active = false;
ctx.flag_authorized = false;
reset_last_event();
EXPECT_FALSE(charger->enable_disable(1, disable_source));
// Must immediately transition to Disabled
EXPECT_EQ(ctx.current_state, Charger::EvseState::Disabled);
EXPECT_EQ(last_event, SessionEventEnum::Disabled);
}
} // namespace
// ----------------------------------------------------------------------------
// the following code is stubs to enable testing Charger in isolation.
// If these stubs are needed elsewhere then they could go into separate files
// ----------------------------------------------------------------------------
// backtrace stub
namespace Everest {
void signal_handler(int signo) {
}
void install_backtrace_handler() {
}
void request_backtrace(pthread_t id) {
}
} // namespace Everest
namespace module {
// ----------------------------------------------------------------------------
// IECStateMachine stub
IECStateMachine::IECStateMachine(const std::unique_ptr<evse_board_supportIntf>& r_bsp_,
bool lock_connector_in_state_b_) :
r_bsp(r_bsp_) {
}
void IECStateMachine::process_bsp_event(const types::board_support_common::BspEvent& bsp_event) {
}
void IECStateMachine::allow_power_on(bool value, types::evse_board_support::Reason reason) {
}
std::optional<double> IECStateMachine::read_pp_ampacity() {
return std::nullopt;
}
void IECStateMachine::switch_three_phases_while_charging(bool n) {
}
void IECStateMachine::setup(bool has_ventilation) {
}
void IECStateMachine::set_overcurrent_limit(double amps) {
}
void IECStateMachine::set_pwm(double value) {
}
void IECStateMachine::set_cp_state_X1() {
}
void IECStateMachine::set_cp_state_F() {
}
void IECStateMachine::enable(bool en) {
}
void IECStateMachine::connector_force_unlock() {
}
const std::string cpevent_to_string(CPEvent e) {
switch (e) {
case CPEvent::CarPluggedIn:
return "CarPluggedIn";
case CPEvent::CarRequestedPower:
return "CarRequestedPower";
case CPEvent::PowerOn:
return "PowerOn";
case CPEvent::PowerOff:
return "PowerOff";
case CPEvent::CarRequestedStopPower:
return "CarRequestedStopPower";
case CPEvent::CarUnplugged:
return "CarUnplugged";
case CPEvent::EFtoBCD:
return "EFtoBCD";
case CPEvent::BCDtoEF:
return "BCDtoEF";
case CPEvent::BCDtoE:
return "BCDtoE";
}
throw std::out_of_range("No known string conversion for provided enum of type CPEvent");
}
// ----------------------------------------------------------------------------
// ErrorHandling stub
ErrorHandling::ErrorHandling(const std::unique_ptr<evse_board_supportIntf>& r_bsp,
const std::vector<std::unique_ptr<ISO15118_chargerIntf>>& r_hlc,
const std::vector<std::unique_ptr<connector_lockIntf>>& r_connector_lock,
const std::vector<std::unique_ptr<ac_rcdIntf>>& r_ac_rcd,
const std::unique_ptr<evse_managerImplBase>& _p_evse,
const std::vector<std::unique_ptr<isolation_monitorIntf>>& _r_imd,
const std::vector<std::unique_ptr<power_supply_DCIntf>>& _r_powersupply,
const std::vector<std::unique_ptr<powermeterIntf>>& _r_powermeter,
const std::vector<std::unique_ptr<over_voltage_monitorIntf>>& _r_over_voltage_monitor,
bool _inoperative_error_use_vendor_id) :
r_bsp(r_bsp),
r_hlc(r_hlc),
r_connector_lock(r_connector_lock),
r_ac_rcd(r_ac_rcd),
p_evse(p_evse),
r_imd(_r_imd),
r_powersupply(r_powersupply),
r_powermeter(_r_powermeter),
r_over_voltage_monitor(_r_over_voltage_monitor),
inoperative_error_use_vendor_id(_inoperative_error_use_vendor_id) {
}
void ErrorHandling::raise_overcurrent_error(const std::string& description) {
}
void ErrorHandling::clear_overcurrent_error() {
}
void ErrorHandling::raise_over_voltage_error(Everest::error::Severity severity, const std::string& description) {
}
void ErrorHandling::clear_over_voltage_error() {
}
void ErrorHandling::raise_internal_error(const std::string& description) {
}
void ErrorHandling::clear_internal_error() {
}
void ErrorHandling::raise_authorization_timeout_error(const std::string& description) {
}
void ErrorHandling::clear_authorization_timeout_error() {
}
void ErrorHandling::raise_powermeter_transaction_start_failed_error(const std::string& description) {
}
void ErrorHandling::clear_powermeter_transaction_start_failed_error() {
}
void ErrorHandling::raise_isolation_resistance_fault(const std::string& description, const std::string& sub_type) {
}
void ErrorHandling::clear_isolation_resistance_fault(const std::string& sub_type) {
}
void ErrorHandling::raise_cable_check_fault(const std::string& description) {
}
void ErrorHandling::clear_cable_check_fault() {
}
void ErrorHandling::clear_voltage_plausibility_fault() {
}
// ----------------------------------------------------------------------------
// SessionLog stub
SessionLog::SessionLog() {
}
SessionLog::~SessionLog() {
}
void SessionLog::setPath(const std::string& path) {
}
void SessionLog::setMqtt(const std::function<void(const nlohmann::json& data)>& mqtt_provider) {
}
void SessionLog::enable() {
}
std::optional<std::filesystem::path> SessionLog::startSession(const std::string& suffix_string) {
return {};
}
void SessionLog::stopSession() {
}
void SessionLog::car(bool iso15118, const std::string& msg) {
}
void SessionLog::car(bool iso15118, const std::string& msg, const std::string& xml, const std::string& xml_hex,
const std::string& xml_base64, const std::string& json_str) {
}
void SessionLog::evse(bool iso15118, const std::string& msg) {
}
void SessionLog::evse(bool iso15118, const std::string& msg, const std::string& xml, const std::string& xml_hex,
const std::string& xml_base64, const std::string& json_str) {
}
void SessionLog::xmlOutput(bool e) {
}
void SessionLog::sys(const std::string& msg) {
}
SessionLog session_log;
// ----------------------------------------------------------------------------
// PersistentStore stub
PersistentStore::PersistentStore(const std::vector<std::unique_ptr<kvsIntf>>& r_store, const std::string module_id) :
r_store(r_store) {
}
void PersistentStore::store_session(const std::string& session_uuid) {
}
void PersistentStore::clear_session() {
}
std::string PersistentStore::get_session() {
return {};
}
} // namespace module