Skip to content

Commit

Permalink
fix: approach updated to allow multiple ready events from the
Browse files Browse the repository at this point in the history
     same EVSE manager

Signed-off-by: James Chapman <[email protected]>
  • Loading branch information
james-ctc committed Oct 1, 2024
1 parent 5ce76be commit 1576d17
Show file tree
Hide file tree
Showing 8 changed files with 342 additions and 34 deletions.
43 changes: 14 additions & 29 deletions modules/API/API.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -269,15 +269,19 @@ void API::init() {
std::vector<std::string> connectors;
std::string var_connectors = this->api_base + "connectors";

evse_manager_ready = this->r_evse_manager.size();
evse_manager_check.set_total(r_evse_manager.size());

for (auto& evse : this->r_evse_manager) {
auto& session_info = this->info.emplace_back(std::make_unique<SessionInfo>());
auto& hw_caps = this->hw_capabilities_str.emplace_back("");
std::string evse_base = this->api_base + evse->module_id;
connectors.push_back(evse->module_id);

evse->subscribe_ready([this](bool ready) { this->notify_evse_manager_ready(ready); });
evse->subscribe_ready([this, &evse](bool ready) {
if (ready) {
this->evse_manager_check.notify_ready(evse->module_id);
}
});

// API variables
std::string var_base = evse_base + "/var/";
Expand Down Expand Up @@ -426,7 +430,7 @@ void API::init() {
} else {
EVLOG_error << "enable: No argument specified, ignoring command";
}
this->wait_evse_manager_ready();
this->evse_manager_check.wait_ready();
evse->call_enable_disable(connector_id, enable_source);
});

Expand All @@ -446,7 +450,7 @@ void API::init() {
} else {
EVLOG_error << "disable: No argument specified, ignoring command";
}
this->wait_evse_manager_ready();
this->evse_manager_check.wait_ready();
evse->call_enable_disable(connector_id, enable_source);
});

Expand All @@ -466,27 +470,27 @@ void API::init() {
} else {
EVLOG_error << "disable: No argument specified, ignoring command";
}
this->wait_evse_manager_ready();
this->evse_manager_check.wait_ready();
evse->call_enable_disable(connector_id, enable_source);
});

std::string cmd_pause_charging = cmd_base + "pause_charging";
this->mqtt.subscribe(cmd_pause_charging, [this, &evse](const std::string& data) {
this->wait_evse_manager_ready();
this->evse_manager_check.wait_ready();
evse->call_pause_charging(); //
});

std::string cmd_resume_charging = cmd_base + "resume_charging";
this->mqtt.subscribe(cmd_resume_charging, [this, &evse](const std::string& data) {
this->wait_evse_manager_ready();
this->evse_manager_check.wait_ready();
evse->call_resume_charging(); //
});

std::string cmd_set_limit = cmd_base + "set_limit_amps";
this->mqtt.subscribe(cmd_set_limit, [this, &evse](const std::string& data) {
try {
const auto external_limits = get_external_limits(data, false);
this->wait_evse_manager_ready();
this->evse_manager_check.wait_ready();
evse->call_set_external_limits(external_limits);
} catch (const std::invalid_argument& e) {
EVLOG_warning << "Invalid limit: No conversion of given input could be performed.";
Expand All @@ -499,7 +503,7 @@ void API::init() {
this->mqtt.subscribe(cmd_set_limit_watts, [this, &evse](const std::string& data) {
try {
const auto external_limits = get_external_limits(data, true);
this->wait_evse_manager_ready();
this->evse_manager_check.wait_ready();
evse->call_set_external_limits(external_limits);
} catch (const std::invalid_argument& e) {
EVLOG_warning << "Invalid limit: No conversion of given input could be performed.";
Expand All @@ -523,7 +527,7 @@ void API::init() {
// perform the same action
types::evse_manager::StopTransactionRequest req;
req.reason = types::evse_manager::StopTransactionReason::UnlockCommand;
this->wait_evse_manager_ready();
this->evse_manager_check.wait_ready();
evse->call_stop_transaction(req);
evse->call_force_unlock(connector_id);
});
Expand Down Expand Up @@ -646,23 +650,4 @@ void API::ready() {
}));
}

void API::wait_evse_manager_ready() {
std::unique_lock lock(evse_manager_mux);
evse_manager_cv.wait(lock, [this] { return this->evse_manager_ready <= 0; });
}

void API::notify_evse_manager_ready(bool ready) {
if (ready) {
bool notify{false};
{
std::lock_guard lock(evse_manager_mux);
evse_manager_ready--;
notify = evse_manager_ready <= 0;
}
if (notify) {
evse_manager_cv.notify_all();
}
}
}

} // namespace module
7 changes: 2 additions & 5 deletions modules/API/API.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include <date/date.h>
#include <date/tz.h>

#include "StartupMonitor.hpp"
#include "limit_decimal_places.hpp"

namespace module {
Expand Down Expand Up @@ -193,11 +194,7 @@ class API : public Everest::ModuleBase {
std::vector<std::thread> api_threads;
bool running = true;

std::condition_variable evse_manager_cv;
std::mutex evse_manager_mux;
std::int16_t evse_manager_ready{0};
void wait_evse_manager_ready();
void notify_evse_manager_ready(bool ready);
StartupMonitor evse_manager_check;

std::list<std::unique_ptr<SessionInfo>> info;
std::list<std::string> hw_capabilities_str;
Expand Down
4 changes: 4 additions & 0 deletions modules/API/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ target_link_libraries(${MODULE_NAME}
target_sources(${MODULE_NAME}
PRIVATE
"limit_decimal_places.cpp"
"StartupMonitor.cpp"
)
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1

Expand All @@ -26,4 +27,7 @@ target_sources(${MODULE_NAME}

# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
# insert other things like install cmds etc here
if(EVEREST_CORE_BUILD_TESTING)
add_subdirectory(tests)
endif()
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
76 changes: 76 additions & 0 deletions modules/API/StartupMonitor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest

#include "StartupMonitor.hpp"
#include <everest/logging.hpp>

#include <memory>

namespace module {

bool StartupMonitor::check_ready() {
bool result{false};
if (ready_set) {
result = ready_set->size() >= n_managers;
}
return result;
}

bool StartupMonitor::set_total(std::uint8_t total) {
bool result{true};
{
std::lock_guard lock(mutex);
if (!ready_set) {
n_managers = total;
if (total == 0) {
managers_ready = true;
} else {
managers_ready = false;
ready_set = std::make_unique<ready_t>();
}
} else {
// already set
EVLOG_error << "Invalid attempt to set number of EVSE managers";
result = false;
}
}
if (total == 0) {
cv.notify_all();
}
return result;
}

void StartupMonitor::wait_ready() {
std::unique_lock lock(mutex);
cv.wait(lock, [this] { return this->managers_ready; });
}

bool StartupMonitor::notify_ready(const std::string& evse_manager_id) {
bool result{true};
bool notify{false};
{
std::lock_guard lock(mutex);
if (ready_set) {
ready_set->insert(evse_manager_id);
notify = StartupMonitor::check_ready();
if (notify) {
managers_ready = true;
n_managers = 0;
ready_set->clear(); // reclaim memory
}
} else {
result = false;
if (managers_ready) {
EVLOG_warning << "EVSE manager ready after complete";
} else {
EVLOG_error << "EVSE manager ready before total number set";
}
}
}
if (notify) {
cv.notify_all();
}
return result;
}

} // namespace module
68 changes: 68 additions & 0 deletions modules/API/StartupMonitor.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef STARTUPMONITOR_HPP
#define STARTUPMONITOR_HPP

#include <condition_variable>
#include <cstdint>
#include <memory>
#include <mutex>
#include <set>
#include <string>

namespace module {

/**
* \brief collect ready responses from all EVSE managers
*
* Provides a mechanism for API code to wait for all EVSE managers to be ready.
* Every EVSE manager is expected to set a `ready` variable to true. This class
* collects the IDs of EVSE managers to check that the expected number are
* ready before allowing API calls to proceed.
*
* \note an EVSE manager is not expected to set `ready` more than once, however
* this class manages this so that the `ready` is only counted once.
*/
class StartupMonitor {
private:
using ready_t = std::set<std::string>;

std::condition_variable cv;
std::mutex mutex;

protected:
std::unique_ptr<ready_t> ready_set; //!< set of received ready responses
std::uint16_t n_managers{0}; //!< total number of EVSE managers
bool managers_ready{false}; //!< all EVSE managers are ready

/**
* \brief check whether all ready responses have been received
* \returns true when the ready set contains at least n_managers responses
*/
bool check_ready();

public:
/**
* \brief set the total number of EVSE managers
* \param[in] total the number of EVSE managers
* \returns false if the total has already been set
*/
bool set_total(std::uint8_t total);

/**
* \brief wait for all EVSE managers to be ready
*/
void wait_ready();

/**
* \brief notify that a specific EVSE manager is ready
* \param[in] evse_manager_id the ID of the EVSE manager
* \returns false if the total has not been set
* \note notify_ready() may be called multiple times with the same evse_manager_id
*/
bool notify_ready(const std::string& evse_manager_id);
};

} // namespace module

#endif // STARTUPMONITOR_HPP
19 changes: 19 additions & 0 deletions modules/API/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
set(TEST_TARGET_NAME ${PROJECT_NAME}_API_tests)
add_executable(${TEST_TARGET_NAME})

add_dependencies(${TEST_TARGET_NAME} ${MODULE_NAME})

target_include_directories(${TEST_TARGET_NAME} PRIVATE
. .. ../../../tests/include
)

target_sources(${TEST_TARGET_NAME} PRIVATE
StartupMonitor_test.cpp
../StartupMonitor.cpp
)

target_link_libraries(${TEST_TARGET_NAME} PRIVATE
GTest::gtest_main
)

add_test(${TEST_TARGET_NAME} ${TEST_TARGET_NAME})
Loading

0 comments on commit 1576d17

Please sign in to comment.