first version of cron, does not read configuation from file
This commit is contained in:
917
include/croncpp.h
Normal file
917
include/croncpp.h
Normal file
@@ -0,0 +1,917 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <sstream>
|
||||||
|
#include <bitset>
|
||||||
|
#include <cctype>
|
||||||
|
#include <ctime>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#if __cplusplus > 201402L
|
||||||
|
#include <string_view>
|
||||||
|
#define CRONCPP_IS_CPP17
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace cron
|
||||||
|
{
|
||||||
|
#ifdef CRONCPP_IS_CPP17
|
||||||
|
#define CRONCPP_STRING_VIEW std::string_view
|
||||||
|
#define CRONCPP_STRING_VIEW_NPOS std::string_view::npos
|
||||||
|
#define CRONCPP_CONSTEXPTR constexpr
|
||||||
|
#else
|
||||||
|
#define CRONCPP_STRING_VIEW std::string const &
|
||||||
|
#define CRONCPP_STRING_VIEW_NPOS std::string::npos
|
||||||
|
#define CRONCPP_CONSTEXPTR
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using cron_int = uint8_t;
|
||||||
|
|
||||||
|
constexpr std::time_t INVALID_TIME = static_cast<std::time_t>(-1);
|
||||||
|
|
||||||
|
constexpr size_t INVALID_INDEX = static_cast<size_t>(-1);
|
||||||
|
|
||||||
|
class cronexpr;
|
||||||
|
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
enum class cron_field
|
||||||
|
{
|
||||||
|
second,
|
||||||
|
minute,
|
||||||
|
hour_of_day,
|
||||||
|
day_of_week,
|
||||||
|
day_of_month,
|
||||||
|
month,
|
||||||
|
year
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Traits>
|
||||||
|
static bool find_next(cronexpr const & cex,
|
||||||
|
std::tm& date,
|
||||||
|
size_t const dot);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct bad_cronexpr : public std::runtime_error
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit bad_cronexpr(CRONCPP_STRING_VIEW message) :
|
||||||
|
std::runtime_error(message.data())
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct cron_standard_traits
|
||||||
|
{
|
||||||
|
static const cron_int CRON_MIN_SECONDS = 0;
|
||||||
|
static const cron_int CRON_MAX_SECONDS = 59;
|
||||||
|
|
||||||
|
static const cron_int CRON_MIN_MINUTES = 0;
|
||||||
|
static const cron_int CRON_MAX_MINUTES = 59;
|
||||||
|
|
||||||
|
static const cron_int CRON_MIN_HOURS = 0;
|
||||||
|
static const cron_int CRON_MAX_HOURS = 23;
|
||||||
|
|
||||||
|
static const cron_int CRON_MIN_DAYS_OF_WEEK = 0;
|
||||||
|
static const cron_int CRON_MAX_DAYS_OF_WEEK = 6;
|
||||||
|
|
||||||
|
static const cron_int CRON_MIN_DAYS_OF_MONTH = 1;
|
||||||
|
static const cron_int CRON_MAX_DAYS_OF_MONTH = 31;
|
||||||
|
|
||||||
|
static const cron_int CRON_MIN_MONTHS = 1;
|
||||||
|
static const cron_int CRON_MAX_MONTHS = 12;
|
||||||
|
|
||||||
|
static const cron_int CRON_MAX_YEARS_DIFF = 4;
|
||||||
|
|
||||||
|
#ifdef CRONCPP_IS_CPP17
|
||||||
|
static const inline std::vector<std::string> DAYS = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" };
|
||||||
|
static const inline std::vector<std::string> MONTHS = { "NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };
|
||||||
|
#else
|
||||||
|
static std::vector<std::string>& DAYS()
|
||||||
|
{
|
||||||
|
static std::vector<std::string> days = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" };
|
||||||
|
return days;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::string>& MONTHS()
|
||||||
|
{
|
||||||
|
static std::vector<std::string> months = { "NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };
|
||||||
|
return months;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
struct cron_oracle_traits
|
||||||
|
{
|
||||||
|
static const cron_int CRON_MIN_SECONDS = 0;
|
||||||
|
static const cron_int CRON_MAX_SECONDS = 59;
|
||||||
|
|
||||||
|
static const cron_int CRON_MIN_MINUTES = 0;
|
||||||
|
static const cron_int CRON_MAX_MINUTES = 59;
|
||||||
|
|
||||||
|
static const cron_int CRON_MIN_HOURS = 0;
|
||||||
|
static const cron_int CRON_MAX_HOURS = 23;
|
||||||
|
|
||||||
|
static const cron_int CRON_MIN_DAYS_OF_WEEK = 1;
|
||||||
|
static const cron_int CRON_MAX_DAYS_OF_WEEK = 7;
|
||||||
|
|
||||||
|
static const cron_int CRON_MIN_DAYS_OF_MONTH = 1;
|
||||||
|
static const cron_int CRON_MAX_DAYS_OF_MONTH = 31;
|
||||||
|
|
||||||
|
static const cron_int CRON_MIN_MONTHS = 0;
|
||||||
|
static const cron_int CRON_MAX_MONTHS = 11;
|
||||||
|
|
||||||
|
static const cron_int CRON_MAX_YEARS_DIFF = 4;
|
||||||
|
|
||||||
|
#ifdef CRONCPP_IS_CPP17
|
||||||
|
static const inline std::vector<std::string> DAYS = { "NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" };
|
||||||
|
static const inline std::vector<std::string> MONTHS = { "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };
|
||||||
|
#else
|
||||||
|
|
||||||
|
static std::vector<std::string>& DAYS()
|
||||||
|
{
|
||||||
|
static std::vector<std::string> days = { "NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" };
|
||||||
|
return days;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::string>& MONTHS()
|
||||||
|
{
|
||||||
|
static std::vector<std::string> months = { "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };
|
||||||
|
return months;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
struct cron_quartz_traits
|
||||||
|
{
|
||||||
|
static const cron_int CRON_MIN_SECONDS = 0;
|
||||||
|
static const cron_int CRON_MAX_SECONDS = 59;
|
||||||
|
|
||||||
|
static const cron_int CRON_MIN_MINUTES = 0;
|
||||||
|
static const cron_int CRON_MAX_MINUTES = 59;
|
||||||
|
|
||||||
|
static const cron_int CRON_MIN_HOURS = 0;
|
||||||
|
static const cron_int CRON_MAX_HOURS = 23;
|
||||||
|
|
||||||
|
static const cron_int CRON_MIN_DAYS_OF_WEEK = 1;
|
||||||
|
static const cron_int CRON_MAX_DAYS_OF_WEEK = 7;
|
||||||
|
|
||||||
|
static const cron_int CRON_MIN_DAYS_OF_MONTH = 1;
|
||||||
|
static const cron_int CRON_MAX_DAYS_OF_MONTH = 31;
|
||||||
|
|
||||||
|
static const cron_int CRON_MIN_MONTHS = 1;
|
||||||
|
static const cron_int CRON_MAX_MONTHS = 12;
|
||||||
|
|
||||||
|
static const cron_int CRON_MAX_YEARS_DIFF = 4;
|
||||||
|
|
||||||
|
#ifdef CRONCPP_IS_CPP17
|
||||||
|
static const inline std::vector<std::string> DAYS = { "NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" };
|
||||||
|
static const inline std::vector<std::string> MONTHS = { "NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };
|
||||||
|
#else
|
||||||
|
static std::vector<std::string>& DAYS()
|
||||||
|
{
|
||||||
|
static std::vector<std::string> days = { "NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" };
|
||||||
|
return days;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::string>& MONTHS()
|
||||||
|
{
|
||||||
|
static std::vector<std::string> months = { "NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };
|
||||||
|
return months;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
class cronexpr;
|
||||||
|
|
||||||
|
template <typename Traits = cron_standard_traits>
|
||||||
|
static cronexpr make_cron(CRONCPP_STRING_VIEW expr);
|
||||||
|
|
||||||
|
class cronexpr
|
||||||
|
{
|
||||||
|
std::bitset<60> seconds;
|
||||||
|
std::bitset<60> minutes;
|
||||||
|
std::bitset<24> hours;
|
||||||
|
std::bitset<7> days_of_week;
|
||||||
|
std::bitset<31> days_of_month;
|
||||||
|
std::bitset<12> months;
|
||||||
|
std::string expr;
|
||||||
|
|
||||||
|
friend bool operator==(cronexpr const & e1, cronexpr const & e2);
|
||||||
|
friend bool operator!=(cronexpr const & e1, cronexpr const & e2);
|
||||||
|
|
||||||
|
template <typename Traits>
|
||||||
|
friend bool detail::find_next(cronexpr const & cex,
|
||||||
|
std::tm& date,
|
||||||
|
size_t const dot);
|
||||||
|
|
||||||
|
friend std::string to_cronstr(cronexpr const& cex);
|
||||||
|
friend std::string to_string(cronexpr const & cex);
|
||||||
|
|
||||||
|
template <typename Traits>
|
||||||
|
friend cronexpr make_cron(CRONCPP_STRING_VIEW expr);
|
||||||
|
};
|
||||||
|
|
||||||
|
inline bool operator==(cronexpr const & e1, cronexpr const & e2)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
e1.seconds == e2.seconds &&
|
||||||
|
e1.minutes == e2.minutes &&
|
||||||
|
e1.hours == e2.hours &&
|
||||||
|
e1.days_of_week == e2.days_of_week &&
|
||||||
|
e1.days_of_month == e2.days_of_month &&
|
||||||
|
e1.months == e2.months;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool operator!=(cronexpr const & e1, cronexpr const & e2)
|
||||||
|
{
|
||||||
|
return !(e1 == e2);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string to_string(cronexpr const & cex)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
cex.seconds.to_string() + " " +
|
||||||
|
cex.minutes.to_string() + " " +
|
||||||
|
cex.hours.to_string() + " " +
|
||||||
|
cex.days_of_month.to_string() + " " +
|
||||||
|
cex.months.to_string() + " " +
|
||||||
|
cex.days_of_week.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string to_cronstr(cronexpr const& cex)
|
||||||
|
{
|
||||||
|
return cex.expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace utils
|
||||||
|
{
|
||||||
|
inline std::time_t tm_to_time(std::tm& date)
|
||||||
|
{
|
||||||
|
return std::mktime(&date);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::tm* time_to_tm(std::time_t const * date, std::tm* const out)
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
errno_t err = localtime_s(out, date);
|
||||||
|
return 0 == err ? out : nullptr;
|
||||||
|
#else
|
||||||
|
return localtime_r(date, out);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::tm to_tm(CRONCPP_STRING_VIEW time)
|
||||||
|
{
|
||||||
|
std::tm result;
|
||||||
|
#if __cplusplus > 201103L
|
||||||
|
std::istringstream str(time.data());
|
||||||
|
str.imbue(std::locale(setlocale(LC_ALL, nullptr)));
|
||||||
|
|
||||||
|
str >> std::get_time(&result, "%Y-%m-%d %H:%M:%S");
|
||||||
|
if (str.fail()) throw std::runtime_error("Parsing date failed!");
|
||||||
|
#else
|
||||||
|
int year = 1900;
|
||||||
|
int month = 1;
|
||||||
|
int day = 1;
|
||||||
|
int hour = 0;
|
||||||
|
int minute = 0;
|
||||||
|
int second = 0;
|
||||||
|
sscanf(time.data(), "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second);
|
||||||
|
result.tm_year = year - 1900;
|
||||||
|
result.tm_mon = month - 1;
|
||||||
|
result.tm_mday = day;
|
||||||
|
result.tm_hour = hour;
|
||||||
|
result.tm_min = minute;
|
||||||
|
result.tm_sec = second;
|
||||||
|
#endif
|
||||||
|
result.tm_isdst = -1; // DST info not available
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string to_string(std::tm const & tm)
|
||||||
|
{
|
||||||
|
#if __cplusplus > 201103L
|
||||||
|
std::ostringstream str;
|
||||||
|
str.imbue(std::locale(setlocale(LC_ALL, nullptr)));
|
||||||
|
str << std::put_time(&tm, "%Y-%m-%d %H:%M:%S");
|
||||||
|
if (str.fail()) throw std::runtime_error("Writing date failed!");
|
||||||
|
|
||||||
|
return str.str();
|
||||||
|
#else
|
||||||
|
char buff[70] = {0};
|
||||||
|
strftime(buff, sizeof(buff), "%Y-%m-%d %H:%M:%S", &tm);
|
||||||
|
return std::string(buff);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string to_upper(std::string text)
|
||||||
|
{
|
||||||
|
std::transform(std::begin(text), std::end(text),
|
||||||
|
std::begin(text), [](char const c) { return static_cast<char>(std::toupper(c)); });
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::string> split(CRONCPP_STRING_VIEW text, char const delimiter)
|
||||||
|
{
|
||||||
|
std::vector<std::string> tokens;
|
||||||
|
std::string token;
|
||||||
|
std::istringstream tokenStream(text.data());
|
||||||
|
while (std::getline(tokenStream, token, delimiter))
|
||||||
|
{
|
||||||
|
tokens.push_back(token);
|
||||||
|
}
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
CRONCPP_CONSTEXPTR inline bool contains(CRONCPP_STRING_VIEW text, char const ch) noexcept
|
||||||
|
{
|
||||||
|
return CRONCPP_STRING_VIEW_NPOS != text.find_first_of(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
|
||||||
|
inline cron_int to_cron_int(CRONCPP_STRING_VIEW text)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return static_cast<cron_int>(std::stoul(text.data()));
|
||||||
|
}
|
||||||
|
catch (std::exception const & ex)
|
||||||
|
{
|
||||||
|
throw bad_cronexpr(ex.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string replace_ordinals(
|
||||||
|
std::string text,
|
||||||
|
std::vector<std::string> const & replacement)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < replacement.size(); ++i)
|
||||||
|
{
|
||||||
|
auto pos = text.find(replacement[i]);
|
||||||
|
if (std::string::npos != pos)
|
||||||
|
text.replace(pos, 3 ,std::to_string(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::pair<cron_int, cron_int> make_range(
|
||||||
|
CRONCPP_STRING_VIEW field,
|
||||||
|
cron_int const minval,
|
||||||
|
cron_int const maxval)
|
||||||
|
{
|
||||||
|
cron_int first = 0;
|
||||||
|
cron_int last = 0;
|
||||||
|
if (field.size() == 1 && field[0] == '*')
|
||||||
|
{
|
||||||
|
first = minval;
|
||||||
|
last = maxval;
|
||||||
|
}
|
||||||
|
else if (!utils::contains(field, '-'))
|
||||||
|
{
|
||||||
|
first = to_cron_int(field);
|
||||||
|
last = first;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto parts = utils::split(field, '-');
|
||||||
|
if (parts.size() != 2)
|
||||||
|
throw bad_cronexpr("Specified range requires two fields");
|
||||||
|
|
||||||
|
first = to_cron_int(parts[0]);
|
||||||
|
last = to_cron_int(parts[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (first > maxval || last > maxval)
|
||||||
|
{
|
||||||
|
throw bad_cronexpr("Specified range exceeds maximum");
|
||||||
|
}
|
||||||
|
if (first < minval || last < minval)
|
||||||
|
{
|
||||||
|
throw bad_cronexpr("Specified range is less than minimum");
|
||||||
|
}
|
||||||
|
if (first > last)
|
||||||
|
{
|
||||||
|
throw bad_cronexpr("Specified range start exceeds range end");
|
||||||
|
}
|
||||||
|
|
||||||
|
return { first, last };
|
||||||
|
}
|
||||||
|
|
||||||
|
template <size_t N>
|
||||||
|
static void set_cron_field(
|
||||||
|
CRONCPP_STRING_VIEW value,
|
||||||
|
std::bitset<N>& target,
|
||||||
|
cron_int const minval,
|
||||||
|
cron_int const maxval)
|
||||||
|
{
|
||||||
|
if(value.length() > 0 && value[value.length()-1] == ',')
|
||||||
|
throw bad_cronexpr("Value cannot end with comma");
|
||||||
|
|
||||||
|
auto fields = utils::split(value, ',');
|
||||||
|
if (fields.empty())
|
||||||
|
throw bad_cronexpr("Expression parsing error");
|
||||||
|
|
||||||
|
for (auto const & field : fields)
|
||||||
|
{
|
||||||
|
if (!utils::contains(field, '/'))
|
||||||
|
{
|
||||||
|
#ifdef CRONCPP_IS_CPP17
|
||||||
|
auto[first, last] = detail::make_range(field, minval, maxval);
|
||||||
|
#else
|
||||||
|
auto range = detail::make_range(field, minval, maxval);
|
||||||
|
auto first = range.first;
|
||||||
|
auto last = range.second;
|
||||||
|
#endif
|
||||||
|
for (cron_int i = first - minval; i <= last - minval; ++i)
|
||||||
|
{
|
||||||
|
target.set(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto parts = utils::split(field, '/');
|
||||||
|
if (parts.size() != 2)
|
||||||
|
throw bad_cronexpr("Incrementer must have two fields");
|
||||||
|
|
||||||
|
#ifdef CRONCPP_IS_CPP17
|
||||||
|
auto[first, last] = detail::make_range(parts[0], minval, maxval);
|
||||||
|
#else
|
||||||
|
auto range = detail::make_range(parts[0], minval, maxval);
|
||||||
|
auto first = range.first;
|
||||||
|
auto last = range.second;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!utils::contains(parts[0], '-'))
|
||||||
|
{
|
||||||
|
last = maxval;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto delta = detail::to_cron_int(parts[1]);
|
||||||
|
if(delta <= 0)
|
||||||
|
throw bad_cronexpr("Incrementer must be a positive value");
|
||||||
|
|
||||||
|
for (cron_int i = first - minval; i <= last - minval; i += delta)
|
||||||
|
{
|
||||||
|
target.set(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Traits>
|
||||||
|
static void set_cron_days_of_week(
|
||||||
|
std::string value,
|
||||||
|
std::bitset<7>& target)
|
||||||
|
{
|
||||||
|
auto days = utils::to_upper(value);
|
||||||
|
auto days_replaced = detail::replace_ordinals(
|
||||||
|
days,
|
||||||
|
#ifdef CRONCPP_IS_CPP17
|
||||||
|
Traits::DAYS
|
||||||
|
#else
|
||||||
|
Traits::DAYS()
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
|
||||||
|
if (days_replaced.size() == 1 && days_replaced[0] == '?')
|
||||||
|
days_replaced[0] = '*';
|
||||||
|
|
||||||
|
set_cron_field(
|
||||||
|
days_replaced,
|
||||||
|
target,
|
||||||
|
Traits::CRON_MIN_DAYS_OF_WEEK,
|
||||||
|
Traits::CRON_MAX_DAYS_OF_WEEK);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Traits>
|
||||||
|
static void set_cron_days_of_month(
|
||||||
|
std::string value,
|
||||||
|
std::bitset<31>& target)
|
||||||
|
{
|
||||||
|
if (value.size() == 1 && value[0] == '?')
|
||||||
|
value[0] = '*';
|
||||||
|
|
||||||
|
set_cron_field(
|
||||||
|
value,
|
||||||
|
target,
|
||||||
|
Traits::CRON_MIN_DAYS_OF_MONTH,
|
||||||
|
Traits::CRON_MAX_DAYS_OF_MONTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Traits>
|
||||||
|
static void set_cron_month(
|
||||||
|
std::string value,
|
||||||
|
std::bitset<12>& target)
|
||||||
|
{
|
||||||
|
auto month = utils::to_upper(value);
|
||||||
|
auto month_replaced = replace_ordinals(
|
||||||
|
month,
|
||||||
|
#ifdef CRONCPP_IS_CPP17
|
||||||
|
Traits::MONTHS
|
||||||
|
#else
|
||||||
|
Traits::MONTHS()
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
|
||||||
|
set_cron_field(
|
||||||
|
month_replaced,
|
||||||
|
target,
|
||||||
|
Traits::CRON_MIN_MONTHS,
|
||||||
|
Traits::CRON_MAX_MONTHS);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <size_t N>
|
||||||
|
inline size_t next_set_bit(
|
||||||
|
std::bitset<N> const & target,
|
||||||
|
size_t /*minimum*/,
|
||||||
|
size_t /*maximum*/,
|
||||||
|
size_t offset)
|
||||||
|
{
|
||||||
|
for (auto i = offset; i < N; ++i)
|
||||||
|
{
|
||||||
|
if (target.test(i)) return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return INVALID_INDEX;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void add_to_field(
|
||||||
|
std::tm& date,
|
||||||
|
cron_field const field,
|
||||||
|
int const val)
|
||||||
|
{
|
||||||
|
switch (field)
|
||||||
|
{
|
||||||
|
case cron_field::second:
|
||||||
|
date.tm_sec += val;
|
||||||
|
break;
|
||||||
|
case cron_field::minute:
|
||||||
|
date.tm_min += val;
|
||||||
|
break;
|
||||||
|
case cron_field::hour_of_day:
|
||||||
|
date.tm_hour += val;
|
||||||
|
break;
|
||||||
|
case cron_field::day_of_week:
|
||||||
|
case cron_field::day_of_month:
|
||||||
|
date.tm_mday += val;
|
||||||
|
date.tm_isdst = -1;
|
||||||
|
break;
|
||||||
|
case cron_field::month:
|
||||||
|
date.tm_mon += val;
|
||||||
|
date.tm_isdst = -1;
|
||||||
|
break;
|
||||||
|
case cron_field::year:
|
||||||
|
date.tm_year += val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (INVALID_TIME == utils::tm_to_time(date))
|
||||||
|
throw bad_cronexpr("Invalid time expression");
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void set_field(
|
||||||
|
std::tm& date,
|
||||||
|
cron_field const field,
|
||||||
|
int const val)
|
||||||
|
{
|
||||||
|
switch (field)
|
||||||
|
{
|
||||||
|
case cron_field::second:
|
||||||
|
date.tm_sec = val;
|
||||||
|
break;
|
||||||
|
case cron_field::minute:
|
||||||
|
date.tm_min = val;
|
||||||
|
break;
|
||||||
|
case cron_field::hour_of_day:
|
||||||
|
date.tm_hour = val;
|
||||||
|
break;
|
||||||
|
case cron_field::day_of_week:
|
||||||
|
date.tm_wday = val;
|
||||||
|
break;
|
||||||
|
case cron_field::day_of_month:
|
||||||
|
date.tm_mday = val;
|
||||||
|
date.tm_isdst = -1;
|
||||||
|
break;
|
||||||
|
case cron_field::month:
|
||||||
|
date.tm_mon = val;
|
||||||
|
date.tm_isdst = -1;
|
||||||
|
break;
|
||||||
|
case cron_field::year:
|
||||||
|
date.tm_year = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (INVALID_TIME == utils::tm_to_time(date))
|
||||||
|
throw bad_cronexpr("Invalid time expression");
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void reset_field(
|
||||||
|
std::tm& date,
|
||||||
|
cron_field const field)
|
||||||
|
{
|
||||||
|
switch (field)
|
||||||
|
{
|
||||||
|
case cron_field::second:
|
||||||
|
date.tm_sec = 0;
|
||||||
|
break;
|
||||||
|
case cron_field::minute:
|
||||||
|
date.tm_min = 0;
|
||||||
|
break;
|
||||||
|
case cron_field::hour_of_day:
|
||||||
|
date.tm_hour = 0;
|
||||||
|
break;
|
||||||
|
case cron_field::day_of_week:
|
||||||
|
date.tm_wday = 0;
|
||||||
|
break;
|
||||||
|
case cron_field::day_of_month:
|
||||||
|
date.tm_mday = 1;
|
||||||
|
date.tm_isdst = -1;
|
||||||
|
break;
|
||||||
|
case cron_field::month:
|
||||||
|
date.tm_mon = 0;
|
||||||
|
date.tm_isdst = -1;
|
||||||
|
break;
|
||||||
|
case cron_field::year:
|
||||||
|
date.tm_year = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (INVALID_TIME == utils::tm_to_time(date))
|
||||||
|
throw bad_cronexpr("Invalid time expression");
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void reset_all_fields(
|
||||||
|
std::tm& date,
|
||||||
|
std::bitset<7> const & marked_fields)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < marked_fields.size(); ++i)
|
||||||
|
{
|
||||||
|
if (marked_fields.test(i))
|
||||||
|
reset_field(date, static_cast<cron_field>(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void mark_field(
|
||||||
|
std::bitset<7> & orders,
|
||||||
|
cron_field const field)
|
||||||
|
{
|
||||||
|
if (!orders.test(static_cast<size_t>(field)))
|
||||||
|
orders.set(static_cast<size_t>(field));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <size_t N>
|
||||||
|
static size_t find_next(
|
||||||
|
std::bitset<N> const & target,
|
||||||
|
std::tm& date,
|
||||||
|
unsigned int const minimum,
|
||||||
|
unsigned int const maximum,
|
||||||
|
unsigned int const value,
|
||||||
|
cron_field const field,
|
||||||
|
cron_field const next_field,
|
||||||
|
std::bitset<7> const & marked_fields)
|
||||||
|
{
|
||||||
|
auto next_value = next_set_bit(target, minimum, maximum, value);
|
||||||
|
if (INVALID_INDEX == next_value)
|
||||||
|
{
|
||||||
|
add_to_field(date, next_field, 1);
|
||||||
|
reset_field(date, field);
|
||||||
|
next_value = next_set_bit(target, minimum, maximum, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (INVALID_INDEX == next_value || next_value != value)
|
||||||
|
{
|
||||||
|
set_field(date, field, static_cast<int>(next_value));
|
||||||
|
reset_all_fields(date, marked_fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
return next_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Traits>
|
||||||
|
static size_t find_next_day(
|
||||||
|
std::tm& date,
|
||||||
|
std::bitset<31> const & days_of_month,
|
||||||
|
size_t day_of_month,
|
||||||
|
std::bitset<7> const & days_of_week,
|
||||||
|
size_t day_of_week,
|
||||||
|
std::bitset<7> const & marked_fields)
|
||||||
|
{
|
||||||
|
unsigned int count = 0;
|
||||||
|
unsigned int maximum = 366;
|
||||||
|
while (
|
||||||
|
(!days_of_month.test(day_of_month - Traits::CRON_MIN_DAYS_OF_MONTH) ||
|
||||||
|
!days_of_week.test(day_of_week - Traits::CRON_MIN_DAYS_OF_WEEK))
|
||||||
|
&& count++ < maximum)
|
||||||
|
{
|
||||||
|
add_to_field(date, cron_field::day_of_month, 1);
|
||||||
|
|
||||||
|
day_of_month = date.tm_mday;
|
||||||
|
day_of_week = date.tm_wday;
|
||||||
|
|
||||||
|
reset_all_fields(date, marked_fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
return day_of_month;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Traits>
|
||||||
|
static bool find_next(cronexpr const & cex,
|
||||||
|
std::tm& date,
|
||||||
|
size_t const dot)
|
||||||
|
{
|
||||||
|
bool res = true;
|
||||||
|
|
||||||
|
std::bitset<7> marked_fields{ 0 };
|
||||||
|
std::bitset<7> empty_list{ 0 };
|
||||||
|
|
||||||
|
unsigned int second = date.tm_sec;
|
||||||
|
auto updated_second = find_next(
|
||||||
|
cex.seconds,
|
||||||
|
date,
|
||||||
|
Traits::CRON_MIN_SECONDS,
|
||||||
|
Traits::CRON_MAX_SECONDS,
|
||||||
|
second,
|
||||||
|
cron_field::second,
|
||||||
|
cron_field::minute,
|
||||||
|
empty_list);
|
||||||
|
|
||||||
|
if (second == updated_second)
|
||||||
|
{
|
||||||
|
mark_field(marked_fields, cron_field::second);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int minute = date.tm_min;
|
||||||
|
auto update_minute = find_next(
|
||||||
|
cex.minutes,
|
||||||
|
date,
|
||||||
|
Traits::CRON_MIN_MINUTES,
|
||||||
|
Traits::CRON_MAX_MINUTES,
|
||||||
|
minute,
|
||||||
|
cron_field::minute,
|
||||||
|
cron_field::hour_of_day,
|
||||||
|
marked_fields);
|
||||||
|
if (minute == update_minute)
|
||||||
|
{
|
||||||
|
mark_field(marked_fields, cron_field::minute);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
res = find_next<Traits>(cex, date, dot);
|
||||||
|
if (!res) return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int hour = date.tm_hour;
|
||||||
|
auto updated_hour = find_next(
|
||||||
|
cex.hours,
|
||||||
|
date,
|
||||||
|
Traits::CRON_MIN_HOURS,
|
||||||
|
Traits::CRON_MAX_HOURS,
|
||||||
|
hour,
|
||||||
|
cron_field::hour_of_day,
|
||||||
|
cron_field::day_of_week,
|
||||||
|
marked_fields);
|
||||||
|
if (hour == updated_hour)
|
||||||
|
{
|
||||||
|
mark_field(marked_fields, cron_field::hour_of_day);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
res = find_next<Traits>(cex, date, dot);
|
||||||
|
if (!res) return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int day_of_week = date.tm_wday;
|
||||||
|
unsigned int day_of_month = date.tm_mday;
|
||||||
|
auto updated_day_of_month = find_next_day<Traits>(
|
||||||
|
date,
|
||||||
|
cex.days_of_month,
|
||||||
|
day_of_month,
|
||||||
|
cex.days_of_week,
|
||||||
|
day_of_week,
|
||||||
|
marked_fields);
|
||||||
|
if (day_of_month == updated_day_of_month)
|
||||||
|
{
|
||||||
|
mark_field(marked_fields, cron_field::day_of_month);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
res = find_next<Traits>(cex, date, dot);
|
||||||
|
if (!res) return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int month = date.tm_mon;
|
||||||
|
auto updated_month = find_next(
|
||||||
|
cex.months,
|
||||||
|
date,
|
||||||
|
Traits::CRON_MIN_MONTHS,
|
||||||
|
Traits::CRON_MAX_MONTHS,
|
||||||
|
month,
|
||||||
|
cron_field::month,
|
||||||
|
cron_field::year,
|
||||||
|
marked_fields);
|
||||||
|
if (month != updated_month)
|
||||||
|
{
|
||||||
|
if (date.tm_year - dot > Traits::CRON_MAX_YEARS_DIFF)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
res = find_next<Traits>(cex, date, dot);
|
||||||
|
if (!res) return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Traits>
|
||||||
|
static cronexpr make_cron(CRONCPP_STRING_VIEW expr)
|
||||||
|
{
|
||||||
|
cronexpr cex;
|
||||||
|
|
||||||
|
if (expr.empty())
|
||||||
|
throw bad_cronexpr("Invalid empty cron expression");
|
||||||
|
|
||||||
|
auto fields = utils::split(expr, ' ');
|
||||||
|
fields.erase(
|
||||||
|
std::remove_if(std::begin(fields), std::end(fields),
|
||||||
|
[](CRONCPP_STRING_VIEW s) {return s.empty(); }),
|
||||||
|
std::end(fields));
|
||||||
|
if (fields.size() != 6)
|
||||||
|
throw bad_cronexpr("cron expression must have six fields");
|
||||||
|
|
||||||
|
detail::set_cron_field(fields[0], cex.seconds, Traits::CRON_MIN_SECONDS, Traits::CRON_MAX_SECONDS);
|
||||||
|
detail::set_cron_field(fields[1], cex.minutes, Traits::CRON_MIN_MINUTES, Traits::CRON_MAX_MINUTES);
|
||||||
|
detail::set_cron_field(fields[2], cex.hours, Traits::CRON_MIN_HOURS, Traits::CRON_MAX_HOURS);
|
||||||
|
|
||||||
|
detail::set_cron_days_of_week<Traits>(fields[5], cex.days_of_week);
|
||||||
|
|
||||||
|
detail::set_cron_days_of_month<Traits>(fields[3], cex.days_of_month);
|
||||||
|
|
||||||
|
detail::set_cron_month<Traits>(fields[4], cex.months);
|
||||||
|
|
||||||
|
cex.expr = expr;
|
||||||
|
|
||||||
|
return cex;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Traits = cron_standard_traits>
|
||||||
|
static std::tm cron_next(cronexpr const & cex, std::tm date)
|
||||||
|
{
|
||||||
|
time_t original = utils::tm_to_time(date);
|
||||||
|
if (INVALID_TIME == original) return {};
|
||||||
|
|
||||||
|
if (!detail::find_next<Traits>(cex, date, date.tm_year))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
time_t calculated = utils::tm_to_time(date);
|
||||||
|
if (INVALID_TIME == calculated) return {};
|
||||||
|
|
||||||
|
if (calculated == original)
|
||||||
|
{
|
||||||
|
add_to_field(date, detail::cron_field::second, 1);
|
||||||
|
if (!detail::find_next<Traits>(cex, date, date.tm_year))
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Traits = cron_standard_traits>
|
||||||
|
static std::time_t cron_next(cronexpr const & cex, std::time_t const & date)
|
||||||
|
{
|
||||||
|
std::tm val;
|
||||||
|
std::tm* dt = utils::time_to_tm(&date, &val);
|
||||||
|
if (dt == nullptr) return INVALID_TIME;
|
||||||
|
|
||||||
|
time_t original = utils::tm_to_time(*dt);
|
||||||
|
if (INVALID_TIME == original) return INVALID_TIME;
|
||||||
|
|
||||||
|
if(!detail::find_next<Traits>(cex, *dt, dt->tm_year))
|
||||||
|
return INVALID_TIME;
|
||||||
|
|
||||||
|
time_t calculated = utils::tm_to_time(*dt);
|
||||||
|
if (INVALID_TIME == calculated) return calculated;
|
||||||
|
|
||||||
|
if (calculated == original)
|
||||||
|
{
|
||||||
|
add_to_field(*dt, detail::cron_field::second, 1);
|
||||||
|
if(!detail::find_next<Traits>(cex, *dt, dt->tm_year))
|
||||||
|
return INVALID_TIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils::tm_to_time(*dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Traits = cron_standard_traits>
|
||||||
|
static std::chrono::system_clock::time_point cron_next(cronexpr const & cex, std::chrono::system_clock::time_point const & time_point) {
|
||||||
|
return std::chrono::system_clock::from_time_t(cron_next<Traits>(cex, std::chrono::system_clock::to_time_t(time_point)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -180,6 +180,12 @@ namespace drivers
|
|||||||
|
|
||||||
const std::string PCF85063::datetime2str(const datetime_t &datetime)
|
const std::string PCF85063::datetime2str(const datetime_t &datetime)
|
||||||
{
|
{
|
||||||
|
tm dtime = datetime2tm(datetime);
|
||||||
|
const std::string buf(std::asctime(&dtime));
|
||||||
|
return buf.substr(0, std::min(buf.find('\n'),buf.find('\r')));
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::tm PCF85063::datetime2tm(const datetime_t& datetime) {
|
||||||
tm dtime;
|
tm dtime;
|
||||||
dtime.tm_sec = datetime.second;
|
dtime.tm_sec = datetime.second;
|
||||||
dtime.tm_min = datetime.minute;
|
dtime.tm_min = datetime.minute;
|
||||||
@@ -188,8 +194,7 @@ namespace drivers
|
|||||||
dtime.tm_mday = datetime.day;
|
dtime.tm_mday = datetime.day;
|
||||||
dtime.tm_mon = datetime.month - 1;
|
dtime.tm_mon = datetime.month - 1;
|
||||||
dtime.tm_year = datetime.year - 1900; // time offset in structure according cpp reference
|
dtime.tm_year = datetime.year - 1900; // time offset in structure according cpp reference
|
||||||
const std::string buf(std::asctime(&dtime));
|
return dtime;
|
||||||
return buf.substr(0, std::min(buf.find('\n'),buf.find('\r')));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint8_t PCF85063::decToBcd(const int val)
|
const uint8_t PCF85063::decToBcd(const int val)
|
||||||
|
|||||||
@@ -68,8 +68,6 @@ namespace drivers
|
|||||||
|
|
||||||
class PCF85063
|
class PCF85063
|
||||||
{
|
{
|
||||||
I2C &m_i2c;
|
|
||||||
uint8_t m_address;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
typedef struct
|
typedef struct
|
||||||
@@ -102,13 +100,18 @@ namespace drivers
|
|||||||
const bool getAlarmFlag(uint8_t &flags);
|
const bool getAlarmFlag(uint8_t &flags);
|
||||||
|
|
||||||
const std::string getTimeStr();
|
const std::string getTimeStr();
|
||||||
|
|
||||||
static const std::string datetime2str(const datetime_t &datetime);
|
static const std::string datetime2str(const datetime_t &datetime);
|
||||||
|
static const std::tm datetime2tm(const datetime_t& datetime);
|
||||||
static const PCF85063::datetime_t fromEpoch(const time_t currentTime);
|
static const PCF85063::datetime_t fromEpoch(const time_t currentTime);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const uint8_t decToBcd(const int val);
|
const uint8_t decToBcd(const int val);
|
||||||
const int bcdToDec(const uint8_t val);
|
const int bcdToDec(const uint8_t val);
|
||||||
|
|
||||||
|
private:
|
||||||
|
I2C &m_i2c;
|
||||||
|
uint8_t m_address;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ namespace commands
|
|||||||
|
|
||||||
static TimerHandle_t s_irrigationPumpTimer = NULL;
|
static TimerHandle_t s_irrigationPumpTimer = NULL;
|
||||||
|
|
||||||
|
// define command callback type
|
||||||
|
using Command = std::function<const ArduinoJson::JsonDocument(const devices_t &, const ArduinoJson::JsonDocument &)>;
|
||||||
|
|
||||||
class Commands
|
class Commands
|
||||||
{
|
{
|
||||||
Commands() = delete;
|
Commands() = delete;
|
||||||
@@ -86,7 +89,7 @@ namespace commands
|
|||||||
static const ArduinoJson::JsonDocument getIrrigation(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms);
|
static const ArduinoJson::JsonDocument getIrrigation(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms);
|
||||||
};
|
};
|
||||||
|
|
||||||
static const std::map<const std::string, std::function<const ArduinoJson::JsonDocument(const devices_t &, const ArduinoJson::JsonDocument &)>> commandMap = {
|
static const std::map<const std::string, Command> commandMap = {
|
||||||
{"setConfig", Commands::setConfig},
|
{"setConfig", Commands::setConfig},
|
||||||
{"getConfig", Commands::getConfig},
|
{"getConfig", Commands::getConfig},
|
||||||
|
|
||||||
|
|||||||
139
src/cronjobs.cpp
Normal file
139
src/cronjobs.cpp
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
#include <cronjobs.h>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#define STACK_DEPTH 4096
|
||||||
|
#define PRIORITY 3
|
||||||
|
|
||||||
|
Cron::Cron(devices_t &dev) : m_dev(dev)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Cron::~Cron()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool Cron::loadEvents(fs::File &file)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool Cron::addEvent(const std::string &name, const std::string &command, const std::string &expr)
|
||||||
|
{
|
||||||
|
if (m_cronMap.contains(name))
|
||||||
|
{
|
||||||
|
LOG_ERROR("Cron event [", name.c_str(), "] already scheduled");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (name.empty() || command.empty() || expr.empty())
|
||||||
|
{
|
||||||
|
LOG_ERROR("Cron event invalid parameters");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const auto eventExpr(cron::make_cron(expr));
|
||||||
|
ArduinoJson::JsonDocument action;
|
||||||
|
if (ArduinoJson::deserializeJson(action, command) != ArduinoJson::DeserializationError::Ok)
|
||||||
|
{
|
||||||
|
LOG_ERROR("Cron unable to deserialize command [", command.c_str(), "]");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto cmd = action["cmd"].as<std::string>();
|
||||||
|
const auto params = action["params"].as<JsonObject>();
|
||||||
|
if (!commands::commandMap.contains(cmd))
|
||||||
|
{
|
||||||
|
LOG_ERROR("Cron unknown command [", command.c_str(), "]");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
LOG_INFO("Cron added event [", name.c_str(), "]");
|
||||||
|
drivers::PCF85063::datetime_t now;
|
||||||
|
if (!m_dev.rtc.readDatetime(now))
|
||||||
|
{
|
||||||
|
LOG_ERROR("Cron unable to update current time");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::tm nowTm = drivers::PCF85063::datetime2tm(now);
|
||||||
|
m_cronMap[name] = std::make_tuple(cmd, eventExpr, cron::cron_next(eventExpr, nowTm), params);
|
||||||
|
}
|
||||||
|
catch (cron::bad_cronexpr const &ex)
|
||||||
|
{
|
||||||
|
LOG_ERROR("Cron failed to parse expression [", expr.c_str(), "] ->", ex.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool Cron::delEvent(const std::string &name)
|
||||||
|
{
|
||||||
|
if (!m_cronMap.contains(name))
|
||||||
|
{
|
||||||
|
LOG_WARN("Cron event [", name.c_str(), "] does not exist");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_cronMap.erase(name);
|
||||||
|
LOG_INFO("Cron removed event [", name.c_str(), "]");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cronLoop(void* params) {
|
||||||
|
auto cron = (Cron*)(params);
|
||||||
|
while (true) {
|
||||||
|
cron->processEvents();
|
||||||
|
delay(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cron::startCron() {
|
||||||
|
if (!m_cronTaskHandle) {
|
||||||
|
LOG_INFO("Cron starting loop");
|
||||||
|
xTaskCreate(cronLoop, "cronLoop", STACK_DEPTH, this, PRIORITY, &m_cronTaskHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cron::stopCron() {
|
||||||
|
if (m_cronTaskHandle) {
|
||||||
|
LOG_WARN("Cron stopping loop");
|
||||||
|
vTaskDelete(m_cronTaskHandle);
|
||||||
|
m_cronTaskHandle = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool Cron::processEvents()
|
||||||
|
{
|
||||||
|
LOG_DEBUG("Cron processEvents [", m_cronMap.size(), "]");
|
||||||
|
|
||||||
|
drivers::PCF85063::datetime_t now;
|
||||||
|
if (!m_dev.rtc.readDatetime(now))
|
||||||
|
{
|
||||||
|
LOG_ERROR("Cron unable to update current time");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tm nowTm = drivers::PCF85063::datetime2tm(now);
|
||||||
|
|
||||||
|
for (auto &event : m_cronMap)
|
||||||
|
{
|
||||||
|
auto &eventName = event.first;
|
||||||
|
auto &eventAction = event.second;
|
||||||
|
|
||||||
|
auto &cmd = std::get<0>(eventAction);
|
||||||
|
auto &cronexrp = std::get<1>(eventAction);
|
||||||
|
auto &next = std::get<2>(eventAction);
|
||||||
|
auto ¶ms = std::get<3>(eventAction);
|
||||||
|
|
||||||
|
const auto nowPoint = std::chrono::system_clock::from_time_t(std::mktime(&nowTm));
|
||||||
|
const auto nextEventPoint = std::chrono::system_clock::from_time_t(std::mktime(&next));
|
||||||
|
|
||||||
|
LOG_DEBUG("Cron current time [", std::asctime(&nowTm), "]");
|
||||||
|
LOG_DEBUG("Cron checking event [", eventName.c_str(), "] executionTime [", std::asctime(&next), "]");
|
||||||
|
|
||||||
|
if (nextEventPoint <= nowPoint) // execution time hs passed, run event
|
||||||
|
{
|
||||||
|
LOG_INFO("Cron executing event [", eventName.c_str(), "]");
|
||||||
|
commands::commandMap.at(cmd)(m_dev, params); // here the magic happens
|
||||||
|
next = cron::cron_next(cronexrp, nowTm); // update next execution time only if event was executed
|
||||||
|
} // otherwise time tracking is lost
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
34
src/cronjobs.h
Normal file
34
src/cronjobs.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define DEBUGLOG_DEFAULT_LOG_LEVEL_DEBUG
|
||||||
|
|
||||||
|
#include <DebugLog.h>
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#include <PCF85063_Driver.h>
|
||||||
|
|
||||||
|
#include <commands.h>
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <croncpp.h>
|
||||||
|
|
||||||
|
class Cron
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
Cron(devices_t &dev);
|
||||||
|
~Cron();
|
||||||
|
|
||||||
|
const bool loadEvents(fs::File &file);
|
||||||
|
const bool addEvent(const std::string &name, const std::string &command, const std::string &expr);
|
||||||
|
const bool delEvent(const std::string &name);
|
||||||
|
|
||||||
|
void startCron();
|
||||||
|
void stopCron();
|
||||||
|
const bool processEvents();
|
||||||
|
|
||||||
|
private:
|
||||||
|
devices_t &m_dev;
|
||||||
|
TaskHandle_t m_cronTaskHandle;
|
||||||
|
std::map<std::string, std::tuple<std::string, cron::cronexpr, std::tm, ArduinoJson::JsonDocument>> m_cronMap;
|
||||||
|
};
|
||||||
19
src/main.cpp
19
src/main.cpp
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include <config.h>
|
#include <config.h>
|
||||||
#include <commands.h>
|
#include <commands.h>
|
||||||
|
#include <cronjobs.h>
|
||||||
#include <mqtt.h>
|
#include <mqtt.h>
|
||||||
|
|
||||||
#include <devices.h>
|
#include <devices.h>
|
||||||
@@ -53,6 +54,24 @@ void loop()
|
|||||||
//////////////// NETWORK ////////////////
|
//////////////// NETWORK ////////////////
|
||||||
auto mqtt = MQTTwrapper();
|
auto mqtt = MQTTwrapper();
|
||||||
//////////////// NETWORK ////////////////
|
//////////////// NETWORK ////////////////
|
||||||
|
|
||||||
|
|
||||||
|
//////////////// CRONJOB ////////////////
|
||||||
|
auto cron = Cron(devices);
|
||||||
|
ArduinoJson::JsonDocument job;
|
||||||
|
job["cmd"] = "setIrrigation";
|
||||||
|
job["params"]["zone"] = "zone1";
|
||||||
|
job["params"]["timeOn"] = 30;
|
||||||
|
job["params"]["timePause"] = 2;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string buf;
|
||||||
|
serializeJson(job,buf);
|
||||||
|
LOG_INFO("Example Cronjob -> ", buf.c_str());
|
||||||
|
cron.addEvent("exampleCronEvent", buf, "0 */10 * * * *");
|
||||||
|
cron.startCron();
|
||||||
|
};
|
||||||
|
//////////////// CRONJOB ////////////////
|
||||||
|
|
||||||
//////////////// MQTT ////////////////
|
//////////////// MQTT ////////////////
|
||||||
/////////////// CALLBACK //////////////
|
/////////////// CALLBACK //////////////
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#define STACK_DEPTH 8192
|
#define STACK_DEPTH 8192
|
||||||
#define BUFFER_SIZE 2048
|
#define BUFFER_SIZE 2048
|
||||||
#define PRIOTITY 2
|
#define PRIORITY 2
|
||||||
|
|
||||||
MQTTwrapper::MQTTwrapper() : m_config(Config::getInstance()), m_tcp(NetworkClient()), m_client(PubSubClient(m_tcp)), m_loopHandle(NULL)
|
MQTTwrapper::MQTTwrapper() : m_config(Config::getInstance()), m_tcp(NetworkClient()), m_client(PubSubClient(m_tcp)), m_loopHandle(NULL)
|
||||||
{
|
{
|
||||||
@@ -27,7 +27,7 @@ const bool MQTTwrapper::connect()
|
|||||||
LOG_INFO("MQTT client connected to", m_config.m_mqttHost.c_str());
|
LOG_INFO("MQTT client connected to", m_config.m_mqttHost.c_str());
|
||||||
if (m_loopHandle == NULL)
|
if (m_loopHandle == NULL)
|
||||||
{
|
{
|
||||||
xTaskCreate(clientLoop, "mqttLoop", STACK_DEPTH, this, PRIOTITY, &m_loopHandle);
|
xTaskCreate(clientLoop, "mqttLoop", STACK_DEPTH, this, PRIORITY, &m_loopHandle);
|
||||||
m_client.setCallback(MQTTwrapper::callback);
|
m_client.setCallback(MQTTwrapper::callback);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
Reference in New Issue
Block a user