- 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
245 lines
8.1 KiB
C++
245 lines
8.1 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest
|
|
#ifndef EVEREST_TIMER_HPP
|
|
#define EVEREST_TIMER_HPP
|
|
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <thread>
|
|
|
|
#include <boost/asio.hpp>
|
|
#include <date/date.h>
|
|
#include <date/tz.h>
|
|
|
|
namespace Everest {
|
|
template <typename TimerClock = date::utc_clock> class Timer {
|
|
private:
|
|
std::unique_ptr<boost::asio::basic_waitable_timer<TimerClock>> timer = nullptr;
|
|
std::function<void()> timer_callback;
|
|
std::function<void(const boost::system::error_code& error)> callback_wrapper;
|
|
std::chrono::nanoseconds interval_nanoseconds = std::chrono::nanoseconds(0);
|
|
std::atomic<bool> running = false;
|
|
|
|
boost::asio::io_context io_context;
|
|
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> work;
|
|
std::unique_ptr<std::thread> timer_thread = nullptr;
|
|
|
|
std::mutex mutex;
|
|
|
|
public:
|
|
/// This timer will initialize a boost::asio::io_context
|
|
Timer() :
|
|
work(boost::asio::make_work_guard(this->io_context)),
|
|
timer_thread(std::make_unique<std::thread>([this]() { this->io_context.run(); })) {
|
|
this->timer = std::make_unique<boost::asio::basic_waitable_timer<TimerClock>>(this->io_context);
|
|
}
|
|
|
|
explicit Timer(const std::function<void()>& callback) :
|
|
timer_callback(callback),
|
|
work(boost::asio::make_work_guard(this->io_context)),
|
|
timer_thread(std::make_unique<std::thread>([this]() { this->io_context.run(); })) {
|
|
this->timer = std::make_unique<boost::asio::basic_waitable_timer<TimerClock>>(this->io_context);
|
|
}
|
|
|
|
explicit Timer(boost::asio::io_context* io_context) :
|
|
timer(std::make_unique<boost::asio::basic_waitable_timer<TimerClock>>(*io_context)),
|
|
work(boost::asio::make_work_guard(*io_context)) {
|
|
}
|
|
|
|
Timer(boost::asio::io_context* io_context, const std::function<void()>& callback) :
|
|
timer(std::make_unique<boost::asio::basic_waitable_timer<TimerClock>>(*io_context)),
|
|
timer_callback(callback),
|
|
work(boost::asio::make_work_guard(*io_context)) {
|
|
}
|
|
|
|
/// \brief Cancel the asio timer and join the io_context thread.
|
|
///
|
|
/// The mutex is released BEFORE joining the io_context thread. A timer callback running on
|
|
/// that thread may re-enter \ref at, \ref timeout, \ref interval, or \ref stop — each of which
|
|
/// takes the mutex; if the destructor still held it, \c join would deadlock waiting for the
|
|
/// callback to exit while the callback waited for the mutex.
|
|
~Timer() {
|
|
{
|
|
std::lock_guard<std::mutex> lock(this->mutex);
|
|
if (this->timer) {
|
|
this->timer->cancel();
|
|
}
|
|
}
|
|
if (this->timer_thread) {
|
|
this->io_context.stop();
|
|
this->timer_thread->join();
|
|
}
|
|
}
|
|
|
|
/// Executes the given callback at the given timepoint
|
|
template <class Clock, class Duration = typename Clock::duration>
|
|
void at(const std::function<void()>& callback, const std::chrono::time_point<Clock, Duration>& time_point) {
|
|
std::lock_guard<std::mutex> lock(this->mutex);
|
|
|
|
this->stop_internal();
|
|
this->timer_callback = callback;
|
|
|
|
this->at_internal(time_point);
|
|
}
|
|
|
|
/// Executes a previously configured callback at the given timepoint
|
|
template <class Clock, class Duration = typename Clock::duration>
|
|
void at(const std::chrono::time_point<Clock, Duration>& time_point) {
|
|
std::lock_guard<std::mutex> lock(this->mutex);
|
|
|
|
this->stop_internal();
|
|
this->at_internal<Clock, Duration>(time_point);
|
|
}
|
|
|
|
/// Executes the given callback periodically from now in the given interval
|
|
template <class Rep, class Period>
|
|
void interval(const std::function<void()>& callback, const std::chrono::duration<Rep, Period>& interval) {
|
|
std::lock_guard<std::mutex> lock(this->mutex);
|
|
|
|
this->stop_internal();
|
|
this->timer_callback = callback;
|
|
|
|
this->interval_internal(interval);
|
|
}
|
|
|
|
/// Executes a previously configured callback periodically from now in the given interval
|
|
template <class Rep, class Period> void interval(const std::chrono::duration<Rep, Period>& interval) {
|
|
std::lock_guard<std::mutex> lock(this->mutex);
|
|
|
|
this->stop_internal();
|
|
this->interval_internal(interval);
|
|
}
|
|
|
|
/// Executes the given callback once after the given interval
|
|
template <class Rep, class Period>
|
|
void timeout(const std::function<void()>& callback, const std::chrono::duration<Rep, Period>& interval) {
|
|
std::lock_guard<std::mutex> lock(this->mutex);
|
|
|
|
this->stop_internal();
|
|
this->timer_callback = callback;
|
|
|
|
this->timeout_internal(interval);
|
|
}
|
|
|
|
/// Executes a previously configured callback once after the given interval
|
|
template <class Rep, class Period> void timeout(const std::chrono::duration<Rep, Period>& interval) {
|
|
std::lock_guard<std::mutex> lock(this->mutex);
|
|
|
|
this->stop_internal();
|
|
this->timeout_internal(interval);
|
|
}
|
|
|
|
/// Stops timer from executing its callback
|
|
void stop() {
|
|
std::lock_guard<std::mutex> lock(this->mutex);
|
|
|
|
stop_internal();
|
|
}
|
|
|
|
/// Indicates if the timer is running
|
|
bool is_running() {
|
|
std::lock_guard<std::mutex> lock(this->mutex);
|
|
|
|
return running;
|
|
}
|
|
|
|
private:
|
|
template <class Clock, class Duration = typename Clock::duration>
|
|
void at_internal(const std::chrono::time_point<Clock, Duration>& time_point) {
|
|
if (this->timer_callback == nullptr) {
|
|
return;
|
|
}
|
|
|
|
if (this->timer) {
|
|
running = true;
|
|
|
|
// use asio timer
|
|
this->timer->expires_at(time_point);
|
|
this->timer->async_wait([this](const boost::system::error_code& e) {
|
|
if (e) {
|
|
return;
|
|
}
|
|
|
|
this->timer_callback();
|
|
running = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
template <class Rep, class Period> void interval_internal(const std::chrono::duration<Rep, Period>& interval) {
|
|
this->interval_nanoseconds = interval;
|
|
if (interval_nanoseconds == std::chrono::nanoseconds(0)) {
|
|
return;
|
|
}
|
|
|
|
if (this->timer_callback == nullptr) {
|
|
return;
|
|
}
|
|
|
|
if (this->timer) {
|
|
running = true;
|
|
|
|
// use asio timer
|
|
this->callback_wrapper = [this](const boost::system::error_code& error) {
|
|
if (error) {
|
|
running = false;
|
|
return;
|
|
}
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(this->mutex);
|
|
this->timer->expires_after(
|
|
std::chrono::duration_cast<typename TimerClock::duration>(this->interval_nanoseconds));
|
|
this->timer->async_wait(this->callback_wrapper);
|
|
}
|
|
|
|
this->timer_callback();
|
|
};
|
|
|
|
this->timer->expires_after(
|
|
std::chrono::duration_cast<typename TimerClock::duration>(this->interval_nanoseconds));
|
|
this->timer->async_wait(this->callback_wrapper);
|
|
}
|
|
}
|
|
|
|
template <class Rep, class Period> void timeout_internal(const std::chrono::duration<Rep, Period>& interval) {
|
|
if (this->timer_callback == nullptr) {
|
|
return;
|
|
}
|
|
|
|
if (this->timer) {
|
|
running = true;
|
|
|
|
// use asio timer
|
|
this->timer->expires_after(interval);
|
|
this->timer->async_wait([this](const boost::system::error_code& error) {
|
|
if (error) {
|
|
running = false;
|
|
return;
|
|
}
|
|
|
|
this->timer_callback();
|
|
running = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
void stop_internal() {
|
|
if (this->timer) {
|
|
// asio based timer
|
|
this->timer->cancel();
|
|
}
|
|
|
|
running = false;
|
|
}
|
|
};
|
|
|
|
using SteadyTimer = Timer<date::utc_clock>;
|
|
using SystemTimer = Timer<date::utc_clock>;
|
|
} // namespace Everest
|
|
|
|
#endif // EVEREST_TIMER_HPP
|