From fe22e92ff1569aec1dce5a8dc32cf1cea11d0c12 Mon Sep 17 00:00:00 2001 From: Sergey Markov Date: Thu, 19 May 2022 15:27:39 +0300 Subject: [PATCH] readable timestaps in the log --- cmd_line_args.cpp | 28 ++++++++----- cmd_line_args.h | 5 ++- main.cpp | 26 +++++++++--- task_manager.hpp | 103 ++++++++++++++++++++++++++++++++-------------- 4 files changed, 113 insertions(+), 49 deletions(-) diff --git a/cmd_line_args.cpp b/cmd_line_args.cpp index 70fe071..983e1d8 100644 --- a/cmd_line_args.cpp +++ b/cmd_line_args.cpp @@ -6,10 +6,11 @@ static struct option options[] = { - {"help", no_argument, NULL, '?'}, - {"version", no_argument, NULL, 'v'}, - {"config-file", optional_argument, NULL, 'c'}, - {NULL, 0, NULL, 0 } + {"help", no_argument, NULL, '?'}, + {"version", no_argument, NULL, 'v'}, + {"config-file", optional_argument, NULL, 'c'}, + {"initial-pin-state", optional_argument, NULL, 's'}, + {NULL, 0, NULL, 0 } }; static constexpr auto optc = "?v:c:"; @@ -18,9 +19,10 @@ static constexpr auto help_format = "{0} v{1}\n\n" "Usage:\n" "Availabe options are:\n" - "\t--help Show help\n" - "\t--config-file Config file path\n" - "\t--version Version of the {0}\n" + "\t--help Show help\n" + "\t--config-file Config file path\n" + "\t--initial-pin-state Initial state of control pin\n" + "\t--version Version of the {0}\n" "\n" "Usage example:\n" "{0} --config-file=scheduler.conf\n" @@ -31,18 +33,21 @@ static constexpr auto version_format = void usage() { std::cout << fmt::format(help_format, APP_NAME, APP_VERSION) << std::endl; + exit(0); } void version() { std::cout << fmt::format(version_format, APP_NAME, APP_VERSION, APP_GCC) << std::endl; + exit(0); } -parsed_opts parse_opts(int argc, char** argv) { +app_options parse_opts(int argc, char** argv) { if(argc < 1){ exit(1); } std::string config_file {""}; + int initial_pin_state = 0; for(;;) { int opt_idx, c; @@ -59,10 +64,13 @@ parsed_opts parse_opts(int argc, char** argv) { case 'c': config_file = optarg; break; + case 's': + initial_pin_state = std::stoi(optarg); + break; default: exit(1); } } - return {config_file}; -} \ No newline at end of file + return {config_file, initial_pin_state}; +} diff --git a/cmd_line_args.h b/cmd_line_args.h index 9bd4b7d..094f319 100644 --- a/cmd_line_args.h +++ b/cmd_line_args.h @@ -2,9 +2,10 @@ #define CMD_LINE_ARGS_H #include -struct parsed_opts { +struct app_options { std::string config_file; + int initial_pin_state; }; -parsed_opts parse_opts(int argc, char** argv); +app_options parse_opts(int argc, char** argv); #endif // CMD_LINE_ARGS_H diff --git a/main.cpp b/main.cpp index f3f74b4..ca6cf10 100644 --- a/main.cpp +++ b/main.cpp @@ -5,7 +5,10 @@ #include "task_manager.hpp" #include #include +#include +#include +using namespace std::chrono; INITIALIZE_EASYLOGGINGPP @@ -19,25 +22,36 @@ static std::stringstream schedule = std::stringstream( "* * * * * SUN LOW(0)\n" ); +inline std::ostream& operator<< (std::ostream &out, const app_options &opts) { + return out << "Options: { " + << "config-file: " << opts.config_file << ", " + << "initial-pin-state: " << opts.initial_pin_state << " }"; + + +} + int main(int argc, char** argv) { + el::Loggers::reconfigureAllLoggers(el::ConfigurationType::Filename, APP_NAME ".log"); el::Loggers::reconfigureAllLoggers(el::ConfigurationType::ToStandardOutput, "true"); el::Loggers::addFlag(el::LoggingFlag::ColoredTerminalOutput); - parsed_opts opts = parse_opts(argc, argv); + app_options opts = parse_opts(argc, argv); if(opts.config_file.empty()) { - LOG(ERROR) << "Config file is not set!"; - return 0; + LOG(WARNING) << "Config file is not set! So we use hardcoded configuration:" << std::endl + << schedule.str(); } - LOG(INFO) << opts.config_file; - + LOG(INFO) << opts; TaskManager manager{schedule, digitalWrite}; wiringPiSetup (); pinMode (PIN, OUTPUT); + digitalWrite(PIN, opts.initial_pin_state); while(true) { - manager.doWork(); + if(manager.doWork() == 0) { + std::this_thread::sleep_for(1s); + } } return 0; diff --git a/task_manager.hpp b/task_manager.hpp index 24b8000..80de209 100644 --- a/task_manager.hpp +++ b/task_manager.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "easylogging++.h" @@ -24,8 +25,9 @@ private: TaskAction action; callable action_fn; int pin; + std::string schedule; public: - Task(cron::cronexpr cronexpr, std::string command, callable action_fn); + Task(std::string schedule, callable action_fn); Task(Task &task); void doAction(); std::time_t next(); @@ -36,51 +38,52 @@ public: class TaskManager { private: std::map tasks; + std::string human_readable(std::time_t value); public: TaskManager(std::istream &stream, callable action_fn); int doWork(); + + friend std::ostream& operator<< (std::ostream &out, const TaskManager &tasksMgr); }; +inline std::ostream& operator<< (std::ostream &out, const TaskManager &tasksMgr) { + out << "Schedule table:" << std::endl; + for (const auto& [key, value] : tasksMgr.tasks) { + std::tm * ptm = std::localtime(&key); + char buffer[32]; + // Format: Mo, 15.06.2009 20:20:00 + std::strftime(buffer, 32, "%a, %d.%m.%Y %H:%M:%S", ptm); + out << "Next time value: " << buffer << " " << value << std::endl; + } + + return out; +} + inline TaskManager::TaskManager(std::istream &stream, callable action_fn) { std::string line; while (std::getline(stream, line).good()) { - auto fields = cron::utils::split(line, ' '); - fields.erase( - std::remove_if(std::begin(fields), std::end(fields), - [](CRONCPP_STRING_VIEW s) {return s.empty(); }), - std::end(fields)); - if (fields.size() != 7) - throw cron::bad_cronexpr("cron expression must have 7 fields"); - - std::stringstream schedule; - for (int i = 0; i < 6; ++i) { - schedule << fields[i] << ' '; - } - - auto cron = cron::make_cron(schedule.str()); - std::time_t now = std::time(0); - std::time_t next = cron::cron_next(cron, now); + Task task{line, action_fn}; tasks.emplace(std::piecewise_construct, - std::forward_as_tuple(next), - std::forward_as_tuple(cron, fields[6], action_fn)); + std::forward_as_tuple(task.next()), + std::forward_as_tuple(task)); } - LOG(INFO) << "Schedule table:"; - for (const auto& [key, value] : tasks) { - LOG(INFO) << key << ' ' << value; - } + LOG(INFO) << *this; } inline int TaskManager::doWork() { auto it = tasks.begin(); - if(it == tasks.end()) return 0; + if(it == tasks.end()) { + LOG(WARNING) << "Table of tasks is empty!!! Nothing to do!!"; + return 0; + } std::time_t now = std::time(0); std::time_t stop = it->first; - LOG(DEBUG) << "Will sleep till next action " << stop - now << 's'; + LOG(INFO) << "Will sleep " << human_readable(stop - now) << " till next action "; std::this_thread::sleep_for(std::chrono::seconds{stop - now}); @@ -90,11 +93,30 @@ inline int TaskManager::doWork() { std::forward_as_tuple(it->second)); tasks.erase(it); - return 0; + LOG(INFO) << *this; + + return 1; +} + +inline std::string TaskManager::human_readable(std::time_t value) { + std::stringstream result; + + int secs = value % 60; + int mins = std::floor((float)value / 60); + int hours = std::floor((float)mins / 60); + if(mins > 59) { + mins = mins % 60; + } + + result << std::setfill('0') << std::setw(2) << hours << ':' + << std::setfill('0') << std::setw(2) << mins << ':' + << std::setfill('0') << std::setw(2) << secs; + + return result.str(); } inline std::ostream& operator<< (std::ostream &out, const Task &task) { - out << "Action: "; + out << "Schedule: "<< task.schedule << " Action: "; switch (task.action) { case TASK_ACTION_HIGH: @@ -113,12 +135,30 @@ inline std::ostream& operator<< (std::ostream &out, const Task &task) { return out; } -inline Task::Task(cron::cronexpr cronexpr, std::string command, callable action_fn): - cronexpr{cronexpr}, +inline Task::Task(std::string schedule, callable action_fn): + cronexpr{}, action{TASK_ACTION_NONE}, pin{0}, - action_fn{action_fn} + action_fn{action_fn}, + schedule{schedule} { + auto fields = cron::utils::split(schedule, ' '); + fields.erase( + std::remove_if(std::begin(fields), std::end(fields), + [](CRONCPP_STRING_VIEW s) {return s.empty(); }), + std::end(fields)); + if (fields.size() != 7) + throw cron::bad_cronexpr("cron expression must have 7 fields"); + + std::stringstream line; + for (int i = 0; i < 6; ++i) { + line << fields[i] << ' '; + } + + this->schedule = line.str(); + cronexpr = cron::make_cron(this->schedule); + std::string command{fields[6]}; + if(command.rfind("HIGH", 0) == 0) { action = TASK_ACTION_HIGH; } else if((command.rfind("LOW", 0) == 0)) { @@ -140,7 +180,8 @@ inline Task::Task(Task &task): cronexpr{task.cronexpr}, action{task.action}, action_fn{task.action_fn}, - pin{task.pin} + pin{task.pin}, + schedule{task.schedule} {} inline void Task::doAction() {