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
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
set(TEST_TARGET_NAME ${PROJECT_NAME}_EvseManager_tests)
|
||||
|
||||
set(TESTS_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/tests/include")
|
||||
|
||||
add_executable(${TEST_TARGET_NAME})
|
||||
|
||||
add_dependencies(${TEST_TARGET_NAME} ${MODULE_NAME})
|
||||
|
||||
get_target_property(GENERATED_INCLUDE_DIR generate_cpp_files EVEREST_GENERATED_INCLUDE_DIR)
|
||||
|
||||
target_include_directories(${TEST_TARGET_NAME} PRIVATE
|
||||
.. ${TESTS_INCLUDE_DIR}
|
||||
${GENERATED_INCLUDE_DIR}
|
||||
${CMAKE_BINARY_DIR}/generated/modules/${MODULE_NAME}
|
||||
)
|
||||
|
||||
target_sources(${TEST_TARGET_NAME} PRIVATE
|
||||
EventQueueTest.cpp
|
||||
ErrorHandlingTest.cpp
|
||||
IECStateMachineTest.cpp
|
||||
OverVoltageMonitorTest.cpp
|
||||
VoltagePlausibilityMonitorTest.cpp
|
||||
../ErrorHandling.cpp
|
||||
../IECStateMachine.cpp
|
||||
../backtrace.cpp
|
||||
../over_voltage/OverVoltageMonitor.cpp
|
||||
../voltage_plausibility/VoltagePlausibilityMonitor.cpp
|
||||
)
|
||||
|
||||
target_compile_definitions(${TEST_TARGET_NAME} PRIVATE
|
||||
BUILD_TESTING_MODULE_EVSE_MANAGER
|
||||
)
|
||||
|
||||
target_link_libraries(${TEST_TARGET_NAME} PRIVATE
|
||||
GTest::gmock
|
||||
GTest::gtest_main
|
||||
everest::log
|
||||
everest::framework
|
||||
everest::helpers
|
||||
sigslot
|
||||
)
|
||||
|
||||
add_test(${TEST_TARGET_NAME} ${TEST_TARGET_NAME})
|
||||
ev_register_test_target(${TEST_TARGET_NAME})
|
||||
|
||||
set(CHARGER_TEST_TARGET_NAME ${PROJECT_NAME}_EvseManagerCharger_tests)
|
||||
add_executable(${CHARGER_TEST_TARGET_NAME})
|
||||
|
||||
add_dependencies(${CHARGER_TEST_TARGET_NAME} ${MODULE_NAME})
|
||||
|
||||
target_include_directories(${CHARGER_TEST_TARGET_NAME} PRIVATE
|
||||
.. ${TESTS_INCLUDE_DIR}
|
||||
${GENERATED_INCLUDE_DIR}
|
||||
${CMAKE_BINARY_DIR}/generated/modules/${MODULE_NAME}
|
||||
)
|
||||
|
||||
target_sources(${CHARGER_TEST_TARGET_NAME} PRIVATE
|
||||
ChargerTest.cpp
|
||||
../Charger.cpp
|
||||
)
|
||||
|
||||
target_compile_definitions(${CHARGER_TEST_TARGET_NAME} PRIVATE
|
||||
BUILD_TESTING_MODULE_EVSE_MANAGER
|
||||
)
|
||||
|
||||
target_link_libraries(${CHARGER_TEST_TARGET_NAME} PRIVATE
|
||||
GTest::gmock
|
||||
GTest::gtest_main
|
||||
everest::framework
|
||||
everest::helpers
|
||||
sigslot
|
||||
)
|
||||
|
||||
add_test(${CHARGER_TEST_TARGET_NAME} ${CHARGER_TEST_TARGET_NAME})
|
||||
ev_register_test_target(${CHARGER_TEST_TARGET_NAME})
|
||||
@@ -0,0 +1,955 @@
|
||||
// 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
|
||||
@@ -0,0 +1,352 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "ErrorHandling.hpp"
|
||||
#include "EvseManagerStub.hpp"
|
||||
#include "evse_board_supportIntfStub.hpp"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
|
||||
namespace Everest::error {
|
||||
bool operator<(const Error& lhs, const Error& rhs) {
|
||||
return lhs.type < rhs.type;
|
||||
}
|
||||
bool operator==(const Error& lhs, const Error& rhs) {
|
||||
return (lhs.type == rhs.type) && (lhs.sub_type == rhs.sub_type);
|
||||
}
|
||||
} // namespace Everest::error
|
||||
|
||||
namespace {
|
||||
|
||||
struct ErrorHandlingTest : public module::ErrorHandling {
|
||||
using module::ErrorHandling::ErrorHandling;
|
||||
using module::ErrorHandling::raise_inoperative_error;
|
||||
};
|
||||
|
||||
struct ErrorDatabaseStub : public Everest::error::ErrorDatabase {
|
||||
using Error = Everest::error::Error;
|
||||
using ErrorPtr = Everest::error::ErrorPtr;
|
||||
using ErrorFilter = Everest::error::ErrorFilter;
|
||||
|
||||
std::list<Error>& active_errors;
|
||||
|
||||
ErrorDatabaseStub(std::list<Error>& active) : Everest::error::ErrorDatabase(), active_errors(active) {
|
||||
}
|
||||
|
||||
virtual void add_error(ErrorPtr error) {
|
||||
}
|
||||
virtual std::list<ErrorPtr> get_errors(const std::list<ErrorFilter>& filters) const {
|
||||
std::list<ErrorPtr> result;
|
||||
for (const auto& error : active_errors) {
|
||||
result.push_back(std::make_shared<Error>(error));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
virtual std::list<ErrorPtr> edit_errors(const std::list<ErrorFilter>& filters, EditErrorFunc edit_func) {
|
||||
return {};
|
||||
}
|
||||
virtual std::list<ErrorPtr> remove_errors(const std::list<ErrorFilter>& filters) {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
struct EvseManagerModuleAdapterStub : public module::stub::EvseManagerModuleAdapter {
|
||||
std::shared_ptr<Everest::error::ErrorTypeMap> error_type_map;
|
||||
std::shared_ptr<ErrorDatabaseStub> error_database;
|
||||
std::list<Everest::error::ErrorType> error_list;
|
||||
std::list<Everest::error::Error> active_errors;
|
||||
|
||||
EvseManagerModuleAdapterStub() :
|
||||
module::stub::EvseManagerModuleAdapter(),
|
||||
error_type_map(std::make_shared<Everest::error::ErrorTypeMap>()),
|
||||
error_database(std::make_shared<ErrorDatabaseStub>(active_errors)),
|
||||
error_list{} {
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<Everest::error::ErrorManagerImpl> get_error_manager_impl_fn(const std::string& str) {
|
||||
return std::make_shared<Everest::error::ErrorManagerImpl>(
|
||||
error_type_map, std::make_shared<Everest::error::ErrorDatabaseMap>(), error_list,
|
||||
[this](const Everest::error::Error& error) {
|
||||
if (error_raise.find(error.type) == error_raise.end()) {
|
||||
throw std::runtime_error("Error type " + error.type + " not found");
|
||||
}
|
||||
error_raise[error.type](error);
|
||||
active_errors.push_back(error);
|
||||
},
|
||||
[this](const Everest::error::Error& error) {
|
||||
if (error_raise.find(error.type) == error_raise.end()) {
|
||||
throw std::runtime_error("Error type " + error.type + " not found");
|
||||
}
|
||||
error_clear[error.type](error);
|
||||
active_errors.remove(error);
|
||||
},
|
||||
false);
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<Everest::error::ErrorManagerReq> get_error_manager_req_fn(const Requirement& req) {
|
||||
return std::make_shared<Everest::error::ErrorManagerReq>(
|
||||
error_type_map, std::make_shared<Everest::error::ErrorDatabaseMap>(), error_list,
|
||||
[this](const Everest::error::ErrorType& error_type, const Everest::error::ErrorCallback& callback,
|
||||
const Everest::error::ErrorCallback& clear_callback) {
|
||||
error_raise[error_type] = callback;
|
||||
error_clear[error_type] = clear_callback;
|
||||
});
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<Everest::error::ErrorFactory> get_error_factory_fn(const std::string&) {
|
||||
return std::make_shared<Everest::error::ErrorFactory>(error_type_map);
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<Everest::error::ErrorStateMonitor> get_error_state_monitor_impl_fn(const std::string&) {
|
||||
return std::make_shared<Everest::error::ErrorStateMonitor>(error_database);
|
||||
}
|
||||
};
|
||||
|
||||
struct ErrorHandlingTesting : public testing::Test {
|
||||
EvseManagerModuleAdapterStub adapter;
|
||||
std::map<Everest::error::ErrorType, std::string> error_types_map;
|
||||
|
||||
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{};
|
||||
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{};
|
||||
|
||||
std::unique_ptr<ErrorHandlingTest> error_handler;
|
||||
|
||||
void construct(bool _inoperative_error_use_vendor_id) {
|
||||
error_types_map = {{"evse_board_support/VendorWarning", "warning"},
|
||||
{"evse_manager/Inoperative", "inoperative"}};
|
||||
adapter.error_type_map->load_error_types_map(error_types_map);
|
||||
adapter.active_errors.clear();
|
||||
for (const auto& [error, description] : error_types_map) {
|
||||
adapter.error_list.push_back(error);
|
||||
}
|
||||
r_bsp = std::make_unique<module::stub::evse_board_supportIntfStub>(adapter);
|
||||
p_evse = std::make_unique<module::stub::evse_managerImplStub>(&adapter, "manager");
|
||||
error_handler = std::make_unique<ErrorHandlingTest>(r_bsp, r_hlc, r_connector_lock, r_ac_rcd, p_evse, _r_imd,
|
||||
_r_powersupply, _r_powermeter, _r_over_voltage_monitor,
|
||||
_inoperative_error_use_vendor_id);
|
||||
}
|
||||
|
||||
static constexpr std::string_view default_description{"description"};
|
||||
static constexpr std::string_view default_no_vendor_id{"EVerest"};
|
||||
static constexpr std::string_view default_vendor_id{"vendor_id"};
|
||||
|
||||
Everest::error::Error test_description(const std::string& type, const std::string& subtype) {
|
||||
Everest::error::Error error{};
|
||||
error.type = type;
|
||||
error.sub_type = subtype;
|
||||
error.message = "message";
|
||||
error.description = default_description;
|
||||
error.vendor_id = default_vendor_id;
|
||||
error_handler->raise_inoperative_error(error);
|
||||
|
||||
EXPECT_EQ(adapter.active_errors.size(), 1);
|
||||
auto& active = adapter.active_errors.front();
|
||||
EXPECT_EQ(active.type, "evse_manager/Inoperative");
|
||||
EXPECT_EQ(active.sub_type, "");
|
||||
EXPECT_EQ(active.message, error.type);
|
||||
return active;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ErrorHandlingTesting, NoType) {
|
||||
construct(true);
|
||||
auto error = test_description("", "sub-type");
|
||||
EXPECT_EQ(error.description, default_description);
|
||||
EXPECT_EQ(error.vendor_id, default_vendor_id);
|
||||
construct(false);
|
||||
error = test_description("", "sub-type");
|
||||
EXPECT_EQ(error.description, default_description);
|
||||
EXPECT_EQ(error.vendor_id, default_no_vendor_id);
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTesting, TypeNoSlashA) {
|
||||
construct(true);
|
||||
auto error = test_description("type", "sub-type");
|
||||
EXPECT_EQ(error.description, default_description);
|
||||
EXPECT_EQ(error.vendor_id, default_vendor_id);
|
||||
construct(false);
|
||||
error = test_description("type", "sub-type");
|
||||
EXPECT_EQ(error.description, default_description);
|
||||
EXPECT_EQ(error.vendor_id, default_no_vendor_id);
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTesting, TypeNoSlashB) {
|
||||
construct(true);
|
||||
auto error = test_description("type", "");
|
||||
EXPECT_EQ(error.description, default_description);
|
||||
EXPECT_EQ(error.vendor_id, default_vendor_id);
|
||||
construct(false);
|
||||
error = test_description("type", "");
|
||||
EXPECT_EQ(error.description, default_description);
|
||||
EXPECT_EQ(error.vendor_id, default_no_vendor_id);
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTesting, TypeSlashEndA) {
|
||||
construct(true);
|
||||
auto error = test_description("type/", "sub-type");
|
||||
EXPECT_EQ(error.description, "type/sub-type");
|
||||
EXPECT_EQ(error.vendor_id, default_vendor_id);
|
||||
construct(false);
|
||||
error = test_description("type/", "sub-type");
|
||||
EXPECT_EQ(error.description, "type/sub-type");
|
||||
EXPECT_EQ(error.vendor_id, default_no_vendor_id);
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTesting, TypeSlashEndB) {
|
||||
construct(true);
|
||||
auto error = test_description("type/", "");
|
||||
EXPECT_EQ(error.description, "type");
|
||||
EXPECT_EQ(error.vendor_id, default_vendor_id);
|
||||
construct(false);
|
||||
error = test_description("type/", "");
|
||||
EXPECT_EQ(error.description, "type");
|
||||
EXPECT_EQ(error.vendor_id, default_no_vendor_id);
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTesting, TypeSlashBeginA) {
|
||||
construct(true);
|
||||
auto error = test_description("/type", "sub-type");
|
||||
EXPECT_EQ(error.description, "type/sub-type");
|
||||
EXPECT_EQ(error.vendor_id, default_vendor_id);
|
||||
construct(false);
|
||||
error = test_description("/type", "sub-type");
|
||||
EXPECT_EQ(error.description, "type/sub-type");
|
||||
EXPECT_EQ(error.vendor_id, default_no_vendor_id);
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTesting, TypeSlashBeginB) {
|
||||
construct(true);
|
||||
auto error = test_description("/type", "");
|
||||
EXPECT_EQ(error.description, "type");
|
||||
EXPECT_EQ(error.vendor_id, default_vendor_id);
|
||||
construct(false);
|
||||
error = test_description("/type", "");
|
||||
EXPECT_EQ(error.description, "type");
|
||||
EXPECT_EQ(error.vendor_id, default_no_vendor_id);
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTesting, TypeFullA) {
|
||||
construct(true);
|
||||
auto error = test_description("module/type", "sub-type");
|
||||
EXPECT_EQ(error.description, "type/sub-type");
|
||||
EXPECT_EQ(error.vendor_id, default_vendor_id);
|
||||
construct(false);
|
||||
error = test_description("module/type", "sub-type");
|
||||
EXPECT_EQ(error.description, "type/sub-type");
|
||||
EXPECT_EQ(error.vendor_id, default_no_vendor_id);
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTesting, TypeFullB) {
|
||||
construct(true);
|
||||
auto error = test_description("module/type", "");
|
||||
EXPECT_EQ(error.description, "type");
|
||||
EXPECT_EQ(error.vendor_id, default_vendor_id);
|
||||
construct(false);
|
||||
error = test_description("module/type", "");
|
||||
EXPECT_EQ(error.description, "type");
|
||||
EXPECT_EQ(error.vendor_id, default_no_vendor_id);
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTesting, TypeFullC) {
|
||||
construct(true);
|
||||
auto error = test_description("module/type/", "sub-type");
|
||||
EXPECT_EQ(error.description, "type/sub-type");
|
||||
EXPECT_EQ(error.vendor_id, default_vendor_id);
|
||||
construct(false);
|
||||
error = test_description("module/type/", "sub-type");
|
||||
EXPECT_EQ(error.description, "type/sub-type");
|
||||
EXPECT_EQ(error.vendor_id, default_no_vendor_id);
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTesting, TypeFullD) {
|
||||
construct(true);
|
||||
auto error = test_description("module/type/", "");
|
||||
EXPECT_EQ(error.description, "type");
|
||||
EXPECT_EQ(error.vendor_id, default_vendor_id);
|
||||
construct(false);
|
||||
error = test_description("module/type/", "");
|
||||
EXPECT_EQ(error.description, "type");
|
||||
EXPECT_EQ(error.vendor_id, default_no_vendor_id);
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTesting, NoVendorId) {
|
||||
Everest::error::Error raised_error{};
|
||||
raised_error.type = "module/type";
|
||||
raised_error.sub_type = "sub-type";
|
||||
raised_error.message = "message";
|
||||
raised_error.description = default_description;
|
||||
raised_error.vendor_id = "";
|
||||
|
||||
construct(true);
|
||||
error_handler->raise_inoperative_error(raised_error);
|
||||
EXPECT_EQ(adapter.active_errors.size(), 1);
|
||||
auto& error = adapter.active_errors.front();
|
||||
EXPECT_EQ(error.type, "evse_manager/Inoperative");
|
||||
EXPECT_EQ(error.sub_type, "");
|
||||
EXPECT_EQ(error.message, raised_error.type);
|
||||
EXPECT_EQ(error.description, "type/sub-type");
|
||||
EXPECT_EQ(error.vendor_id, default_no_vendor_id);
|
||||
|
||||
construct(false);
|
||||
error_handler->raise_inoperative_error(raised_error);
|
||||
EXPECT_EQ(adapter.active_errors.size(), 1);
|
||||
error = adapter.active_errors.front();
|
||||
EXPECT_EQ(error.type, "evse_manager/Inoperative");
|
||||
EXPECT_EQ(error.sub_type, "");
|
||||
EXPECT_EQ(error.message, raised_error.type);
|
||||
EXPECT_EQ(error.description, "type/sub-type");
|
||||
EXPECT_EQ(error.vendor_id, default_no_vendor_id);
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTesting, IsActive) {
|
||||
Everest::error::Error first_error{};
|
||||
first_error.type = "module/type";
|
||||
first_error.sub_type = "sub-type";
|
||||
first_error.message = "message";
|
||||
first_error.description = "description";
|
||||
first_error.vendor_id = "vendor_id";
|
||||
|
||||
construct(true);
|
||||
EXPECT_FALSE(p_evse->error_state_monitor->is_error_active("evse_manager/Inoperative", ""));
|
||||
|
||||
error_handler->raise_inoperative_error(first_error);
|
||||
EXPECT_EQ(adapter.active_errors.size(), 1);
|
||||
auto error = adapter.active_errors.front();
|
||||
EXPECT_EQ(error.type, "evse_manager/Inoperative");
|
||||
EXPECT_EQ(error.sub_type, "");
|
||||
EXPECT_EQ(error.message, first_error.type);
|
||||
EXPECT_EQ(error.description, "type/sub-type");
|
||||
EXPECT_EQ(error.vendor_id, first_error.vendor_id);
|
||||
|
||||
EXPECT_TRUE(p_evse->error_state_monitor->is_error_active("evse_manager/Inoperative", ""));
|
||||
|
||||
Everest::error::Error raised_error{};
|
||||
raised_error.type = "module/new-type";
|
||||
raised_error.sub_type = "new-sub-type";
|
||||
raised_error.message = "new-message";
|
||||
raised_error.description = "new-description";
|
||||
raised_error.vendor_id = "new-vendor_id";
|
||||
|
||||
// should not be a new error
|
||||
error_handler->raise_inoperative_error(raised_error);
|
||||
EXPECT_EQ(adapter.active_errors.size(), 1);
|
||||
error = adapter.active_errors.front();
|
||||
// should be first error
|
||||
EXPECT_EQ(error.type, "evse_manager/Inoperative");
|
||||
EXPECT_EQ(error.sub_type, "");
|
||||
EXPECT_EQ(error.message, first_error.type);
|
||||
EXPECT_EQ(error.description, "type/sub-type");
|
||||
EXPECT_EQ(error.vendor_id, first_error.vendor_id);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,94 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include <EventQueue.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
namespace {
|
||||
|
||||
enum class ErrorHandlingEvents : std::uint8_t {
|
||||
PreventCharging,
|
||||
PreventChargingWelded,
|
||||
AllErrorsCleared
|
||||
};
|
||||
|
||||
TEST(EventQueue, init) {
|
||||
module::EventQueue<ErrorHandlingEvents> queue;
|
||||
auto events = queue.get_events();
|
||||
EXPECT_EQ(events.size(), 0);
|
||||
}
|
||||
|
||||
TEST(EventQueue, one) {
|
||||
module::EventQueue<ErrorHandlingEvents> queue;
|
||||
auto events = queue.get_events();
|
||||
EXPECT_EQ(events.size(), 0);
|
||||
|
||||
queue.push(ErrorHandlingEvents::PreventCharging);
|
||||
events = queue.get_events();
|
||||
ASSERT_EQ(events.size(), 1);
|
||||
EXPECT_EQ(events[0], ErrorHandlingEvents::PreventCharging);
|
||||
|
||||
events = queue.get_events();
|
||||
EXPECT_EQ(events.size(), 0);
|
||||
}
|
||||
|
||||
TEST(EventQueue, two) {
|
||||
module::EventQueue<ErrorHandlingEvents> queue;
|
||||
auto events = queue.get_events();
|
||||
EXPECT_EQ(events.size(), 0);
|
||||
|
||||
queue.push(ErrorHandlingEvents::PreventCharging);
|
||||
queue.push(ErrorHandlingEvents::PreventChargingWelded);
|
||||
events = queue.get_events();
|
||||
ASSERT_EQ(events.size(), 2);
|
||||
EXPECT_EQ(events[0], ErrorHandlingEvents::PreventCharging);
|
||||
EXPECT_EQ(events[1], ErrorHandlingEvents::PreventChargingWelded);
|
||||
|
||||
events = queue.get_events();
|
||||
EXPECT_EQ(events.size(), 0);
|
||||
}
|
||||
|
||||
TEST(EventQueue, wait) {
|
||||
module::EventQueue<ErrorHandlingEvents> queue;
|
||||
auto events = queue.get_events();
|
||||
EXPECT_EQ(events.size(), 0);
|
||||
|
||||
std::size_t count = 0;
|
||||
std::condition_variable cv;
|
||||
std::mutex mux;
|
||||
bool ready{false};
|
||||
|
||||
std::thread wait_thread([&cv, &count, &queue, &ready, &mux]() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mux);
|
||||
ready = true;
|
||||
}
|
||||
cv.notify_one();
|
||||
auto events = queue.wait();
|
||||
count = events.size();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mux);
|
||||
ready = false;
|
||||
}
|
||||
cv.notify_one();
|
||||
});
|
||||
|
||||
std::unique_lock<std::mutex> ul(mux);
|
||||
cv.wait(ul, [&ready] { return ready; });
|
||||
ASSERT_EQ(count, 0U);
|
||||
|
||||
queue.push(ErrorHandlingEvents::PreventCharging);
|
||||
|
||||
cv.wait(ul, [&ready] { return !ready; });
|
||||
ASSERT_EQ(count, 1U);
|
||||
|
||||
events = queue.get_events();
|
||||
EXPECT_EQ(events.size(), 0);
|
||||
|
||||
wait_thread.join();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,117 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#ifndef EVSEMANAGERSTUB_H_
|
||||
#define EVSEMANAGERSTUB_H_
|
||||
|
||||
#include <ErrorHandling.hpp>
|
||||
#include <ModuleAdapterStub.hpp>
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
namespace module::stub {
|
||||
|
||||
struct evse_managerImplStub : public evse_managerImplBase {
|
||||
evse_managerImplStub(Everest::ModuleAdapter* ev, const std::string& name) : evse_managerImplBase(ev, name) {
|
||||
}
|
||||
evse_managerImplStub() : evse_managerImplBase(nullptr, "manager") {
|
||||
}
|
||||
virtual void init() {
|
||||
}
|
||||
virtual void ready() {
|
||||
}
|
||||
virtual types::evse_manager::Evse handle_get_evse() {
|
||||
return types::evse_manager::Evse();
|
||||
}
|
||||
virtual bool handle_enable(int& connector_id) {
|
||||
return true;
|
||||
}
|
||||
virtual bool handle_disable(int& connector_id) {
|
||||
return true;
|
||||
}
|
||||
virtual bool handle_enable_disable(int& connector_id, types::evse_manager::EnableDisableSource& cmd_source) {
|
||||
return true;
|
||||
}
|
||||
virtual void handle_authorize_response(types::authorization::ProvidedIdToken& provided_token,
|
||||
types::authorization::ValidationResult& validation_result) {
|
||||
}
|
||||
virtual void handle_withdraw_authorization() {
|
||||
}
|
||||
virtual bool handle_reserve(int& reservation_id) {
|
||||
return true;
|
||||
}
|
||||
virtual void handle_cancel_reservation() {
|
||||
}
|
||||
virtual bool handle_pause_charging() {
|
||||
return true;
|
||||
}
|
||||
virtual bool handle_resume_charging() {
|
||||
return true;
|
||||
}
|
||||
virtual bool handle_stop_transaction(types::evse_manager::StopTransactionRequest& request) {
|
||||
return true;
|
||||
}
|
||||
virtual bool handle_force_unlock(int& connector_id) {
|
||||
return true;
|
||||
}
|
||||
virtual void handle_set_external_limits(types::energy::ExternalLimits& value) {
|
||||
}
|
||||
virtual types::evse_manager::SwitchThreePhasesWhileChargingResult
|
||||
handle_switch_three_phases_while_charging(bool& three_phases) {
|
||||
return types::evse_manager::SwitchThreePhasesWhileChargingResult::Success;
|
||||
}
|
||||
virtual bool handle_external_ready_to_start_charging() {
|
||||
return true;
|
||||
}
|
||||
virtual void handle_set_plug_and_charge_configuration(
|
||||
types::evse_manager::PlugAndChargeConfiguration& plug_and_charge_configuration) {
|
||||
}
|
||||
virtual types::evse_manager::UpdateAllowedEnergyTransferModesResult handle_update_allowed_energy_transfer_modes(
|
||||
std::vector<types::iso15118::EnergyTransferMode>& allowed_energy_transfer_modes) {
|
||||
return types::evse_manager::UpdateAllowedEnergyTransferModesResult::Accepted;
|
||||
}
|
||||
};
|
||||
|
||||
struct EvseManagerModuleAdapter : public ModuleAdapterStub {
|
||||
EvseManagerModuleAdapter() : id("evse_manager", "main") {
|
||||
}
|
||||
|
||||
ImplementationIdentifier id;
|
||||
std::map<std::string, Everest::error::ErrorCallback> error_raise;
|
||||
std::map<std::string, Everest::error::ErrorCallback> error_clear;
|
||||
|
||||
virtual std::shared_ptr<Everest::error::ErrorManagerReq> get_error_manager_req_fn(const Requirement& req) {
|
||||
return std::make_shared<Everest::error::ErrorManagerReq>(
|
||||
std::make_shared<Everest::error::ErrorTypeMap>(), std::make_shared<Everest::error::ErrorDatabaseMap>(),
|
||||
std::list<Everest::error::ErrorType>({Everest::error::ErrorType("evse_board_support/VendorWarning")}),
|
||||
[this](const Everest::error::ErrorType& error_type, const Everest::error::ErrorCallback& callback,
|
||||
const Everest::error::ErrorCallback& clear_callback) {
|
||||
error_raise[error_type] = callback;
|
||||
error_clear[error_type] = clear_callback;
|
||||
});
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<Everest::error::ErrorManagerImpl> get_error_manager_impl_fn(const std::string& str) {
|
||||
return std::make_shared<Everest::error::ErrorManagerImpl>(
|
||||
std::make_shared<Everest::error::ErrorTypeMap>(), std::make_shared<Everest::error::ErrorDatabaseMap>(),
|
||||
std::list<Everest::error::ErrorType>(),
|
||||
[this](const Everest::error::Error& error) {
|
||||
std::printf("publish_raised_error\n");
|
||||
if (error_raise.find(error.type) == error_raise.end()) {
|
||||
throw std::runtime_error("Error type " + error.type + " not found");
|
||||
}
|
||||
error_raise[error.type](error);
|
||||
},
|
||||
[this](const Everest::error::Error& error) {
|
||||
std::printf("publish_cleared_error\n");
|
||||
if (error_raise.find(error.type) == error_raise.end()) {
|
||||
throw std::runtime_error("Error type " + error.type + " not found");
|
||||
}
|
||||
error_clear[error.type](error);
|
||||
},
|
||||
false);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace module::stub
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,337 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include "evse_board_supportIntfStub.hpp"
|
||||
#include <EventQueue.hpp>
|
||||
#include <IECStateMachine.hpp>
|
||||
#include <backtrace.hpp>
|
||||
#include <chrono>
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
namespace {
|
||||
|
||||
using BspEvent = types::board_support_common::BspEvent;
|
||||
using Event = types::board_support_common::Event;
|
||||
using Reason = types::evse_board_support::Reason;
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
struct BspStub : public module::stub::ModuleAdapterStub {
|
||||
typedef Result (BspStub::*bsp_fn)(Parameters p);
|
||||
ValueCallback event_cb;
|
||||
std::map<std::string, bsp_fn> _bsp;
|
||||
|
||||
BspStub() : module::stub::ModuleAdapterStub() {
|
||||
_bsp["allow_power_on"] = &BspStub::call_allow_power_on;
|
||||
_bsp["enable"] = &BspStub::call_enable;
|
||||
_bsp["cp_state_X1"] = &BspStub::call_cp_state_X1;
|
||||
_bsp["pwm_on"] = &BspStub::call_pwm_on;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// test interface (calls from BSP)
|
||||
|
||||
void raise_event(Event event) {
|
||||
if (event_cb != nullptr) {
|
||||
BspEvent bsp_event;
|
||||
bsp_event.event = event;
|
||||
event_cb(bsp_event);
|
||||
}
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// BSP calls
|
||||
|
||||
virtual Result call_allow_power_on(Parameters p) {
|
||||
std::cout << "call_allow_power_on(" << p << ")" << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
virtual Result call_enable(Parameters p) {
|
||||
std::cout << "call_enable(" << p << ")" << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
virtual Result call_cp_state_X1(Parameters p) {
|
||||
std::cout << "call_cp_state_X1(" << p << ")" << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
virtual Result call_pwm_on(Parameters p) {
|
||||
std::cout << "call_pwm_on(" << p << ")" << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// internal interface
|
||||
virtual void subscribe_fn(const Requirement&, const std::string& fn, ValueCallback cb) override {
|
||||
std::printf("subscribe_fn(%s)\n", fn.c_str());
|
||||
event_cb = cb;
|
||||
}
|
||||
|
||||
virtual Result call_fn(const Requirement& req, const std::string& fn, Parameters p) override {
|
||||
if (auto itt = _bsp.find(fn); itt == _bsp.end()) {
|
||||
std::cout << "<missing> call_fn(" << fn << "," << p << ")" << std::endl;
|
||||
return std::nullopt;
|
||||
} else {
|
||||
return std::invoke(itt->second, this, p);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST(IECStateMachine, init) {
|
||||
module::stub::ModuleAdapterStub module_adapter = module::stub::ModuleAdapterStub();
|
||||
std::unique_ptr<evse_board_supportIntf> bsp_if =
|
||||
std::make_unique<module::stub::evse_board_supportIntfStub>(module_adapter);
|
||||
module::IECStateMachine state_machine(std::move(bsp_if), true);
|
||||
}
|
||||
|
||||
#if 0
|
||||
// test to demonstrate the output from backtrace
|
||||
|
||||
struct BspStubTimeout : public BspStub {
|
||||
Result call_allow_power_on(Parameters p) {
|
||||
std::cout << "call_allow_power_on(" << p << ")" << std::endl;
|
||||
std::cout << "call_allow_power_on: sleep (" << std::this_thread::get_id() << ")" << std::endl;
|
||||
std::this_thread::sleep_for(200s);
|
||||
std::cout << "call_allow_power_on: finished" << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
TEST(IECStateMachine, init_subscribe) {
|
||||
#ifdef EVEREST_USE_BACKTRACES
|
||||
Everest::install_backtrace_handler();
|
||||
#endif
|
||||
|
||||
BspStubTimeout bsp;
|
||||
std::unique_ptr<evse_board_supportIntf> bsp_if = std::make_unique<module::stub::evse_board_supportIntfStub>(bsp);
|
||||
module::IECStateMachine state_machine(std::move(bsp_if), true);
|
||||
|
||||
state_machine.enable(true);
|
||||
state_machine.allow_power_on(true, Reason::FullPowerCharging);
|
||||
bsp.raise_event(Event::A);
|
||||
bsp.raise_event(Event::B);
|
||||
bsp.raise_event(Event::PowerOn);
|
||||
bsp.raise_event(Event::C);
|
||||
std::cout << "main: sleep (" << std::this_thread::get_id() << ")" << std::endl;
|
||||
std::this_thread::sleep_for(300s);
|
||||
}
|
||||
#endif
|
||||
|
||||
struct BspStubDeadlock : public BspStub {
|
||||
std::vector<std::chrono::time_point<std::chrono::steady_clock>> call_cp_state_X1_times;
|
||||
std::vector<std::chrono::time_point<std::chrono::steady_clock>> call_allow_power_on_times;
|
||||
int count = 0;
|
||||
enum class events_t : std::uint8_t {
|
||||
call_allow_power_on,
|
||||
call_enable,
|
||||
call_cp_state_X1,
|
||||
call_pwm_on,
|
||||
signal_lock,
|
||||
sleeping,
|
||||
};
|
||||
module::EventQueue<events_t> events;
|
||||
|
||||
std::string to_string(module::EventQueue<events_t>::events_t e) {
|
||||
std::string result;
|
||||
for (const auto& i : e) {
|
||||
result += std::to_string(static_cast<std::uint8_t>(i));
|
||||
result += " ";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool contains(module::EventQueue<events_t>::events_t e, events_t event) {
|
||||
for (const auto& i : e) {
|
||||
if (i == event) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto wait() {
|
||||
return events.wait();
|
||||
}
|
||||
|
||||
virtual Result call_allow_power_on(Parameters p) {
|
||||
std::cout << "call_allow_power_on(" << p << ")" << std::endl;
|
||||
call_allow_power_on_times.push_back(std::chrono::steady_clock::now());
|
||||
events.push(events_t::call_allow_power_on);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
virtual Result call_enable(Parameters p) {
|
||||
std::cout << "call_enable(" << p << ")" << std::endl;
|
||||
events.push(events_t::call_enable);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
virtual Result call_cp_state_X1(Parameters p) {
|
||||
std::cout << "call_cp_state_X1(" << p << ")" << std::endl;
|
||||
call_cp_state_X1_times.push_back(std::chrono::steady_clock::now());
|
||||
if (count == 2) {
|
||||
std::cout << "sleeping ..." << std::endl;
|
||||
events.push(events_t::sleeping);
|
||||
std::this_thread::sleep_for(7s);
|
||||
}
|
||||
count++;
|
||||
events.push(events_t::call_cp_state_X1);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
virtual Result call_pwm_on(Parameters p) {
|
||||
std::cout << "call_pwm_on(" << p << ")" << std::endl;
|
||||
events.push(events_t::call_pwm_on);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto elapsed() const {
|
||||
auto last_call_cp_state_X1_time = call_cp_state_X1_times.back();
|
||||
auto last_call_allow_power_on_time = call_allow_power_on_times.back();
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(last_call_allow_power_on_time -
|
||||
last_call_cp_state_X1_time)
|
||||
.count();
|
||||
}
|
||||
};
|
||||
|
||||
TEST(IECStateMachine, deadlock_test) {
|
||||
GTEST_SKIP() << "relies on thread timing so occasionally fails";
|
||||
|
||||
/*
|
||||
* a deadlock was caused by timeout_state_c1 timing out and
|
||||
* trying to raise an event while state_machine() was running
|
||||
* and wanting to stop the timer
|
||||
* 61851-1 state C starts the timer (from x2)
|
||||
*
|
||||
* check the timer runs for 6 seconds
|
||||
*/
|
||||
#ifdef EVEREST_USE_BACKTRACES
|
||||
Everest::install_backtrace_handler();
|
||||
#endif
|
||||
|
||||
BspStubDeadlock bsp;
|
||||
std::unique_ptr<evse_board_supportIntf> bsp_if = std::make_unique<module::stub::evse_board_supportIntfStub>(bsp);
|
||||
module::IECStateMachine state_machine(std::move(bsp_if), true);
|
||||
|
||||
std::uint8_t signal_lock_count = 0;
|
||||
|
||||
state_machine.signal_lock.connect([&signal_lock_count, &bsp]() {
|
||||
signal_lock_count++;
|
||||
std::cout << "signal_lock" << std::endl;
|
||||
// bsp.events.push(BspStubDeadlock::events_t::signal_lock);
|
||||
});
|
||||
|
||||
state_machine.enable(true);
|
||||
auto actions = bsp.wait();
|
||||
ASSERT_EQ(actions.size(), 1);
|
||||
ASSERT_EQ(actions[0], BspStubDeadlock::events_t::call_enable);
|
||||
|
||||
bsp.raise_event(Event::A);
|
||||
actions = bsp.wait();
|
||||
ASSERT_TRUE(bsp.contains(actions, BspStubDeadlock::events_t::call_cp_state_X1));
|
||||
|
||||
bsp.raise_event(Event::B);
|
||||
actions = bsp.wait();
|
||||
// std::cout << bsp.to_string(actions) << std::endl;
|
||||
// call_allow_power_on and/or signal_lock
|
||||
// ASSERT_TRUE(bsp.contains(actions, BspStubDeadlock::events_t::call_allow_power_on));
|
||||
|
||||
state_machine.set_pwm(0.5);
|
||||
actions = bsp.wait();
|
||||
// std::cout << bsp.to_string(actions) << std::endl;
|
||||
ASSERT_TRUE(bsp.contains(actions, BspStubDeadlock::events_t::call_pwm_on));
|
||||
|
||||
state_machine.allow_power_on(true, types::evse_board_support::Reason::FullPowerCharging);
|
||||
std::this_thread::sleep_for(1s);
|
||||
// actions = bsp.wait();
|
||||
// ASSERT_TRUE(bsp.contains(actions, BspStubDeadlock::events_t::signal_lock));
|
||||
|
||||
bsp.raise_event(Event::C);
|
||||
actions = bsp.wait();
|
||||
// std::cout << bsp.to_string(actions) << std::endl;
|
||||
ASSERT_TRUE(bsp.contains(actions, BspStubDeadlock::events_t::call_allow_power_on));
|
||||
|
||||
state_machine.set_cp_state_X1();
|
||||
// expect state_machine to run once and then again when the
|
||||
// timer expires
|
||||
|
||||
std::this_thread::sleep_for(8s);
|
||||
auto elapsed = bsp.elapsed();
|
||||
std::cout << "Elapsed: " << elapsed << std::endl;
|
||||
EXPECT_GT(elapsed, 6000);
|
||||
EXPECT_LT(elapsed, 6500);
|
||||
}
|
||||
|
||||
TEST(IECStateMachine, deadlock_fix) {
|
||||
GTEST_SKIP() << "relies on thread timing so occasionally fails";
|
||||
|
||||
/*
|
||||
* a deadlock was caused by timeout_state_c1 timing out and
|
||||
* trying to raise an event while state_machine() was running
|
||||
* and wanting to stop the timer
|
||||
* 61851-1 state C starts the timer (from X2)
|
||||
*/
|
||||
#ifdef EVEREST_USE_BACKTRACES
|
||||
Everest::install_backtrace_handler();
|
||||
#endif
|
||||
|
||||
BspStubDeadlock bsp;
|
||||
std::unique_ptr<evse_board_supportIntf> bsp_if = std::make_unique<module::stub::evse_board_supportIntfStub>(bsp);
|
||||
module::IECStateMachine state_machine(std::move(bsp_if), true);
|
||||
|
||||
std::uint8_t signal_lock_count = 0;
|
||||
|
||||
state_machine.signal_lock.connect([&signal_lock_count, &bsp]() {
|
||||
signal_lock_count++;
|
||||
std::cout << "signal_lock" << std::endl;
|
||||
// bsp.events.push(BspStubDeadlock::events_t::signal_lock);
|
||||
});
|
||||
|
||||
state_machine.enable(true);
|
||||
auto actions = bsp.wait();
|
||||
ASSERT_EQ(actions.size(), 1);
|
||||
ASSERT_EQ(actions[0], BspStubDeadlock::events_t::call_enable);
|
||||
|
||||
bsp.raise_event(Event::A);
|
||||
actions = bsp.wait();
|
||||
// std::cout << bsp.to_string(actions) << std::endl;
|
||||
ASSERT_TRUE(bsp.contains(actions, BspStubDeadlock::events_t::call_cp_state_X1));
|
||||
|
||||
bsp.raise_event(Event::B);
|
||||
actions = bsp.wait();
|
||||
// call_allow_power_on and/or signal_lock
|
||||
// ASSERT_TRUE(bsp.contains(actions, BspStubDeadlock::events_t::call_allow_power_on));
|
||||
|
||||
state_machine.set_pwm(0.5);
|
||||
actions = bsp.wait();
|
||||
ASSERT_TRUE(bsp.contains(actions, BspStubDeadlock::events_t::call_pwm_on));
|
||||
|
||||
state_machine.allow_power_on(true, types::evse_board_support::Reason::FullPowerCharging);
|
||||
std::this_thread::sleep_for(1s);
|
||||
|
||||
bsp.raise_event(Event::C);
|
||||
actions = bsp.wait();
|
||||
ASSERT_TRUE(bsp.contains(actions, BspStubDeadlock::events_t::call_allow_power_on));
|
||||
|
||||
state_machine.set_cp_state_X1();
|
||||
// expect state_machine to run once and then again when the
|
||||
// timer expires
|
||||
|
||||
actions = bsp.wait();
|
||||
ASSERT_TRUE(bsp.contains(actions, BspStubDeadlock::events_t::call_cp_state_X1));
|
||||
|
||||
// this would previously trigger the deadlock
|
||||
bsp.raise_event(Event::A);
|
||||
actions = bsp.wait();
|
||||
// std::cout << bsp.to_string(actions) << std::endl;
|
||||
ASSERT_TRUE(bsp.contains(actions, BspStubDeadlock::events_t::sleeping));
|
||||
|
||||
std::this_thread::sleep_for(10s);
|
||||
// if there is a deadlock the test won't finish
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,190 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "over_voltage/OverVoltageMonitor.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
using namespace module;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
class OverVoltageMonitorTest : public ::testing::Test {
|
||||
protected:
|
||||
struct CallbackState {
|
||||
std::mutex mtx;
|
||||
std::condition_variable cv;
|
||||
bool called{false};
|
||||
OverVoltageMonitor::FaultType type;
|
||||
std::string reason;
|
||||
};
|
||||
|
||||
static OverVoltageMonitor make_monitor(CallbackState& state, std::chrono::milliseconds duration) {
|
||||
return OverVoltageMonitor(
|
||||
[&state](OverVoltageMonitor::FaultType type, const std::string& reason) {
|
||||
std::lock_guard<std::mutex> lock(state.mtx);
|
||||
state.called = true;
|
||||
state.type = type;
|
||||
state.reason = reason;
|
||||
state.cv.notify_all();
|
||||
},
|
||||
duration);
|
||||
}
|
||||
|
||||
static bool wait_for_callback(CallbackState& state, std::chrono::milliseconds timeout = 500ms) {
|
||||
std::unique_lock<std::mutex> lock(state.mtx);
|
||||
return state.cv.wait_for(lock, timeout, [&state] { return state.called; });
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(OverVoltageMonitorTest, no_fault_below_limits) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, 100ms);
|
||||
|
||||
monitor.set_limits(450.0, 420.0);
|
||||
monitor.start_monitor();
|
||||
|
||||
monitor.update_voltage(400.0);
|
||||
monitor.update_voltage(410.0);
|
||||
|
||||
EXPECT_FALSE(wait_for_callback(state));
|
||||
}
|
||||
|
||||
TEST_F(OverVoltageMonitorTest, emergency_fault_triggers_immediately) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, 200ms);
|
||||
|
||||
monitor.set_limits(450.0, 420.0);
|
||||
monitor.start_monitor();
|
||||
|
||||
monitor.update_voltage(460.0);
|
||||
|
||||
ASSERT_TRUE(wait_for_callback(state));
|
||||
EXPECT_EQ(state.type, OverVoltageMonitor::FaultType::Emergency);
|
||||
}
|
||||
|
||||
TEST_F(OverVoltageMonitorTest, error_fault_triggers_after_duration) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, 100ms);
|
||||
|
||||
monitor.set_limits(450.0, 420.0);
|
||||
monitor.start_monitor();
|
||||
|
||||
monitor.update_voltage(430.0);
|
||||
|
||||
ASSERT_TRUE(wait_for_callback(state, 300ms));
|
||||
EXPECT_EQ(state.type, OverVoltageMonitor::FaultType::Error);
|
||||
}
|
||||
|
||||
TEST_F(OverVoltageMonitorTest, voltage_drop_cancels_error_timer) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, 150ms);
|
||||
|
||||
monitor.set_limits(450.0, 420.0);
|
||||
monitor.start_monitor();
|
||||
|
||||
monitor.update_voltage(430.0); // above error limit
|
||||
std::this_thread::sleep_for(50ms);
|
||||
monitor.update_voltage(410.0); // below error limit
|
||||
|
||||
EXPECT_FALSE(wait_for_callback(state, 300ms));
|
||||
}
|
||||
|
||||
TEST_F(OverVoltageMonitorTest, zero_duration_triggers_immediately) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, 0ms);
|
||||
|
||||
monitor.set_limits(450.0, 420.0);
|
||||
monitor.start_monitor();
|
||||
|
||||
monitor.update_voltage(425.0);
|
||||
|
||||
ASSERT_TRUE(wait_for_callback(state));
|
||||
EXPECT_EQ(state.type, OverVoltageMonitor::FaultType::Error);
|
||||
}
|
||||
|
||||
TEST_F(OverVoltageMonitorTest, fault_is_latched) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, 50ms);
|
||||
|
||||
monitor.set_limits(450.0, 420.0);
|
||||
monitor.start_monitor();
|
||||
|
||||
monitor.update_voltage(430.0);
|
||||
ASSERT_TRUE(wait_for_callback(state));
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state.mtx);
|
||||
state.called = false;
|
||||
}
|
||||
|
||||
// Further voltage updates should not retrigger
|
||||
monitor.update_voltage(460.0);
|
||||
EXPECT_FALSE(wait_for_callback(state, 200ms));
|
||||
}
|
||||
|
||||
TEST_F(OverVoltageMonitorTest, stop_monitor_suppresses_fault) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, 100ms);
|
||||
|
||||
monitor.set_limits(450.0, 420.0);
|
||||
monitor.start_monitor();
|
||||
|
||||
monitor.update_voltage(430.0);
|
||||
monitor.stop_monitor();
|
||||
|
||||
EXPECT_FALSE(wait_for_callback(state, 300ms));
|
||||
}
|
||||
|
||||
TEST_F(OverVoltageMonitorTest, reset_clears_latched_fault_and_allows_retrigger) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, 50ms);
|
||||
|
||||
monitor.set_limits(450.0, 420.0);
|
||||
monitor.start_monitor();
|
||||
|
||||
// First fault
|
||||
monitor.update_voltage(430.0);
|
||||
ASSERT_TRUE(wait_for_callback(state, 300ms));
|
||||
EXPECT_EQ(state.type, OverVoltageMonitor::FaultType::Error);
|
||||
|
||||
// Clear callback state
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state.mtx);
|
||||
state.called = false;
|
||||
}
|
||||
|
||||
// Reset should clear latch and timers
|
||||
monitor.reset();
|
||||
|
||||
// Must restart monitoring explicitly
|
||||
monitor.start_monitor();
|
||||
|
||||
// Second fault should be possible again
|
||||
monitor.update_voltage(430.0);
|
||||
ASSERT_TRUE(wait_for_callback(state, 300ms));
|
||||
EXPECT_EQ(state.type, OverVoltageMonitor::FaultType::Error);
|
||||
}
|
||||
|
||||
TEST_F(OverVoltageMonitorTest, multiple_voltage_updates_update_timer_snapshot) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, 100ms);
|
||||
|
||||
monitor.set_limits(500.0, 420.0);
|
||||
monitor.start_monitor();
|
||||
|
||||
// First update arms the timer
|
||||
monitor.update_voltage(430.0);
|
||||
|
||||
// Subsequent updates while timer is active
|
||||
monitor.update_voltage(435.0);
|
||||
monitor.update_voltage(432.0);
|
||||
monitor.update_voltage(440.0); // highest value
|
||||
|
||||
ASSERT_TRUE(wait_for_callback(state, 300ms));
|
||||
EXPECT_EQ(state.type, OverVoltageMonitor::FaultType::Error);
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "voltage_plausibility/VoltagePlausibilityMonitor.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
using namespace module;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
class VoltagePlausibilityMonitorTest : public ::testing::Test {
|
||||
protected:
|
||||
struct CallbackState {
|
||||
std::mutex mtx;
|
||||
std::condition_variable cv;
|
||||
bool called{false};
|
||||
std::string reason;
|
||||
};
|
||||
|
||||
static VoltagePlausibilityMonitor make_monitor(CallbackState& state, double max_spread_threshold_V,
|
||||
std::chrono::milliseconds fault_duration) {
|
||||
return VoltagePlausibilityMonitor(
|
||||
[&state](const std::string& reason) {
|
||||
std::lock_guard<std::mutex> lock(state.mtx);
|
||||
state.called = true;
|
||||
state.reason = reason;
|
||||
state.cv.notify_all();
|
||||
},
|
||||
max_spread_threshold_V, fault_duration);
|
||||
}
|
||||
|
||||
static bool wait_for_callback(CallbackState& state, std::chrono::milliseconds timeout = 500ms) {
|
||||
std::unique_lock<std::mutex> lock(state.mtx);
|
||||
return state.cv.wait_for(lock, timeout, [&state] { return state.called; });
|
||||
}
|
||||
|
||||
static void clear_callback_state(CallbackState& state) {
|
||||
std::lock_guard<std::mutex> lock(state.mtx);
|
||||
state.called = false;
|
||||
state.reason.clear();
|
||||
}
|
||||
|
||||
static void set_two_sources(VoltagePlausibilityMonitor& monitor, double power_supply_V, double powermeter_V) {
|
||||
monitor.update_power_supply_voltage(power_supply_V);
|
||||
monitor.update_powermeter_voltage(powermeter_V);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(VoltagePlausibilityMonitorTest, no_fault_below_threshold) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, /*max_spread_threshold_V=*/10.0, /*fault_duration=*/50ms);
|
||||
|
||||
monitor.start_monitor();
|
||||
|
||||
set_two_sources(monitor, 400.0, 405.0); // spread 5 V <= 10 V
|
||||
|
||||
EXPECT_FALSE(wait_for_callback(state, 200ms));
|
||||
|
||||
monitor.stop_monitor();
|
||||
}
|
||||
|
||||
TEST_F(VoltagePlausibilityMonitorTest, requires_at_least_two_sources) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, /*max_spread_threshold_V=*/1.0, /*fault_duration=*/50ms);
|
||||
|
||||
monitor.start_monitor();
|
||||
|
||||
monitor.update_power_supply_voltage(400.0); // only one source available
|
||||
|
||||
EXPECT_FALSE(wait_for_callback(state, 200ms));
|
||||
|
||||
monitor.stop_monitor();
|
||||
}
|
||||
|
||||
TEST_F(VoltagePlausibilityMonitorTest, zero_duration_triggers_immediately) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, /*max_spread_threshold_V=*/5.0, /*fault_duration=*/0ms);
|
||||
|
||||
monitor.start_monitor();
|
||||
|
||||
set_two_sources(monitor, 400.0, 420.0); // spread 20 V > 5 V
|
||||
|
||||
ASSERT_TRUE(wait_for_callback(state, 200ms));
|
||||
EXPECT_NE(state.reason.find("fault immediately"), std::string::npos);
|
||||
}
|
||||
|
||||
TEST_F(VoltagePlausibilityMonitorTest, spread_above_threshold_triggers_after_duration) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, /*max_spread_threshold_V=*/5.0, /*fault_duration=*/50ms);
|
||||
|
||||
monitor.start_monitor();
|
||||
|
||||
set_two_sources(monitor, 400.0, 420.0); // arms timer
|
||||
|
||||
ASSERT_TRUE(wait_for_callback(state, 300ms));
|
||||
EXPECT_NE(state.reason.find("for at least"), std::string::npos);
|
||||
EXPECT_NE(state.reason.find("exceeded threshold"), std::string::npos);
|
||||
}
|
||||
|
||||
TEST_F(VoltagePlausibilityMonitorTest, returning_within_threshold_cancels_timer) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, /*max_spread_threshold_V=*/5.0, /*fault_duration=*/120ms);
|
||||
|
||||
monitor.start_monitor();
|
||||
|
||||
set_two_sources(monitor, 400.0, 420.0); // spread 20 V -> arm timer
|
||||
|
||||
std::this_thread::sleep_for(40ms);
|
||||
|
||||
// Bring spread back within the threshold before the timer expires
|
||||
monitor.update_powermeter_voltage(402.0); // spread now 2 V -> cancel timer
|
||||
|
||||
EXPECT_FALSE(wait_for_callback(state, 250ms));
|
||||
}
|
||||
|
||||
TEST_F(VoltagePlausibilityMonitorTest, fault_is_latched_and_not_retriggered) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, /*max_spread_threshold_V=*/1.0, /*fault_duration=*/0ms);
|
||||
|
||||
monitor.start_monitor();
|
||||
|
||||
set_two_sources(monitor, 400.0, 405.0); // immediate fault
|
||||
|
||||
ASSERT_TRUE(wait_for_callback(state, 200ms));
|
||||
|
||||
clear_callback_state(state);
|
||||
|
||||
// After a fault the monitor stops and latches the fault; further updates must not retrigger.
|
||||
set_two_sources(monitor, 410.0, 430.0);
|
||||
EXPECT_FALSE(wait_for_callback(state, 150ms));
|
||||
}
|
||||
|
||||
TEST_F(VoltagePlausibilityMonitorTest, stop_monitor_suppresses_fault) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, /*max_spread_threshold_V=*/5.0, /*fault_duration=*/80ms);
|
||||
|
||||
monitor.start_monitor();
|
||||
|
||||
set_two_sources(monitor, 400.0, 420.0); // arm timer
|
||||
monitor.stop_monitor(); // cancels timer
|
||||
|
||||
EXPECT_FALSE(wait_for_callback(state, 250ms));
|
||||
}
|
||||
|
||||
TEST_F(VoltagePlausibilityMonitorTest, reset_clears_samples_so_old_values_are_not_used) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, /*max_spread_threshold_V=*/5.0, /*fault_duration=*/80ms);
|
||||
|
||||
monitor.start_monitor();
|
||||
|
||||
// Prime both sources with a large spread, then stop and reset.
|
||||
set_two_sources(monitor, 400.0, 420.0);
|
||||
monitor.stop_monitor();
|
||||
monitor.reset();
|
||||
|
||||
// Restart monitoring. Provide only a single new source sample.
|
||||
// If reset() didn't clear the old powermeter sample timestamp, we'd still have 2 sources and might arm/fault.
|
||||
monitor.start_monitor();
|
||||
monitor.update_power_supply_voltage(400.0);
|
||||
|
||||
EXPECT_FALSE(wait_for_callback(state, 250ms));
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#ifndef EVSE_BOARD_SUPPORTINTFSTUB_H_
|
||||
#define EVSE_BOARD_SUPPORTINTFSTUB_H_
|
||||
|
||||
#include "ModuleAdapterStub.hpp"
|
||||
#include <generated/interfaces/evse_board_support/Interface.hpp>
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
namespace module::stub {
|
||||
|
||||
struct evse_board_supportIntfStub : public evse_board_supportIntf {
|
||||
explicit evse_board_supportIntfStub(ModuleAdapterStub& adapter) :
|
||||
evse_board_supportIntf(&adapter, Requirement{"requirement", 1}, "EvseManager", std::nullopt) {
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace module::stub
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user