diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..2b41f40 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,72 @@ +{ + "cmake.generator": "Unix Makefiles", + "files.associations": { + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "*.tcc": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "codecvt": "cpp", + "condition_variable": "cpp", + "csignal": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "list": "cpp", + "map": "cpp", + "set": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "string": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "fstream": "cpp", + "initializer_list": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "mutex": "cpp", + "new": "cpp", + "ostream": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "thread": "cpp", + "cinttypes": "cpp", + "typeinfo": "cpp", + "any": "cpp", + "complex": "cpp", + "iomanip": "cpp", + "typeindex": "cpp", + "variant": "cpp", + "future": "cpp", + "regex": "cpp", + "shared_mutex": "cpp" + } +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 814155f..7cc4d5b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,6 @@ cmake_minimum_required(VERSION 3.15.0) +set (CMAKE_CXX_STANDARD 17) set(CMAKE_TOOLCHAIN_FILE cmake/toolchain.cmake) project(miner_scheduler VERSION 0.1.0) @@ -11,10 +12,13 @@ add_definitions(-DAPP_GCC="${CMAKE_C_COMPILER_VERSION}") include(ExternalProject) include(cmake/fmt.cmake) include(cmake/wiringOP.cmake) +include(cmake/easyloggingpp.cmake) +include(cmake/croncpp.cmake) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) add_executable(miner_scheduler main.cpp cmd_line_args.cpp) -add_dependencies(miner_scheduler fmt wiringOP) +add_dependencies(miner_scheduler fmt wiringOP easyloggingpp croncpp) +target_link_options(miner_scheduler PRIVATE -static-libgcc -static-libstdc++) diff --git a/cmake/croncpp.cmake b/cmake/croncpp.cmake new file mode 100644 index 0000000..7f9946a --- /dev/null +++ b/cmake/croncpp.cmake @@ -0,0 +1,9 @@ +ExternalProject_Add(croncpp + GIT_REPOSITORY https://github.com/mariusbancila/croncpp.git + CONFIGURE_COMMAND cmake -DCMAKE_INSTALL_PREFIX= +) + +ExternalProject_Get_property(croncpp BINARY_DIR) +set(CRONCPP_BINARY_DIR ${BINARY_DIR}) + +include_directories(${CRONCPP_BINARY_DIR}/include) \ No newline at end of file diff --git a/cmake/easyloggingpp.cmake b/cmake/easyloggingpp.cmake new file mode 100644 index 0000000..a220ed7 --- /dev/null +++ b/cmake/easyloggingpp.cmake @@ -0,0 +1,11 @@ +ExternalProject_Add(easyloggingpp + URL https://github.com/amrayn/easyloggingpp/archive/refs/tags/v9.97.0.tar.gz + CONFIGURE_COMMAND cmake -DCMAKE_INSTALL_PREFIX= + #BUILD_COMMAND "" + #INSTALL_COMMAND "" +) + +ExternalProject_Get_property(easyloggingpp BINARY_DIR) +set(EASYLOGGINGPP_BINARY_DIR ${BINARY_DIR}) + +include_directories(${EASYLOGGINGPP_BINARY_DIR}/include) \ No newline at end of file diff --git a/cmake/wiringOP.cmake b/cmake/wiringOP.cmake index d2add80..494058c 100644 --- a/cmake/wiringOP.cmake +++ b/cmake/wiringOP.cmake @@ -1,7 +1,15 @@ ExternalProject_Add(wiringOP - GIT_REPOSITORY https://github.com/orangepi-xunlong/wiringOP - BUILD_IN_SOURCE true - CONFIGURE_COMMAND ./build clean - BUILD_COMMAND BOARD=orangepipc-h3 ./build - INSTALL_COMMAND "" -) \ No newline at end of file + GIT_REPOSITORY git@git.markow.su:markow/WiringOP.git + CONFIGURE_COMMAND cmake -DBOARD=orangepipc-h3 +) + +ExternalProject_Get_property(wiringOP BINARY_DIR) +set(WIRING_OP_BINARY_DIR ${BINARY_DIR}) + +ExternalProject_Get_property(wiringOP SOURCE_DIR) +set(WIRING_OP_SRC_DIR ${SOURCE_DIR}) + +include_directories(${WIRING_OP_SRC_DIR}/wiringPi) + +link_directories(${WIRING_OP_BINARY_DIR}/lib) +link_libraries(wiringPi.a pthread m rt) \ No newline at end of file diff --git a/main.cpp b/main.cpp index 763850f..f3f74b4 100644 --- a/main.cpp +++ b/main.cpp @@ -1,6 +1,44 @@ +#include #include "cmd_line_args.h" +#include "easylogging++.h" +#include "easylogging++.cc" +#include "task_manager.hpp" +#include +#include + + +INITIALIZE_EASYLOGGINGPP + +#define PIN 0 + +static std::stringstream schedule = std::stringstream( + "0 0 10 ? * MON-SAT HIGH(0)\n" + "0 0 13 ? * MON-SAT LOW(0)\n" + "0 0 15 ? * MON-SAT HIGH(0)\n" + "0 0 19 ? * MON-SAT LOW(0)\n" + "* * * * * SUN LOW(0)\n" +); int main(int argc, char** argv) { - parse_opts(argc, argv); + el::Loggers::reconfigureAllLoggers(el::ConfigurationType::ToStandardOutput, "true"); + el::Loggers::addFlag(el::LoggingFlag::ColoredTerminalOutput); + + parsed_opts opts = parse_opts(argc, argv); + if(opts.config_file.empty()) { + LOG(ERROR) << "Config file is not set!"; + return 0; + } + + LOG(INFO) << opts.config_file; + + TaskManager manager{schedule, digitalWrite}; + + wiringPiSetup (); + pinMode (PIN, OUTPUT); + + while(true) { + manager.doWork(); + } + return 0; } diff --git a/task_manager.hpp b/task_manager.hpp new file mode 100644 index 0000000..24b8000 --- /dev/null +++ b/task_manager.hpp @@ -0,0 +1,157 @@ +#ifndef TASK_MANAGER_HPP +#define TASK_MANAGER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include "easylogging++.h" + +using callable = std::function; + +enum TaskAction { + TASK_ACTION_LOW = 0, + TASK_ACTION_HIGH = 1, + TASK_ACTION_NONE +}; + +class Task { +private: + cron::cronexpr cronexpr; + TaskAction action; + callable action_fn; + int pin; +public: + Task(cron::cronexpr cronexpr, std::string command, callable action_fn); + Task(Task &task); + void doAction(); + std::time_t next(); + + friend std::ostream& operator<< (std::ostream &out, const Task &task); +}; + +class TaskManager { +private: + std::map tasks; +public: + TaskManager(std::istream &stream, callable action_fn); + + int doWork(); +}; + +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); + + tasks.emplace(std::piecewise_construct, + std::forward_as_tuple(next), + std::forward_as_tuple(cron, fields[6], action_fn)); + } + + LOG(INFO) << "Schedule table:"; + for (const auto& [key, value] : tasks) { + LOG(INFO) << key << ' ' << value; + } +} + +inline int TaskManager::doWork() { + auto it = tasks.begin(); + if(it == tasks.end()) 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'; + + std::this_thread::sleep_for(std::chrono::seconds{stop - now}); + + it->second.doAction(); + tasks.emplace(std::piecewise_construct, + std::forward_as_tuple(it->second.next()), + std::forward_as_tuple(it->second)); + tasks.erase(it); + + return 0; +} + +inline std::ostream& operator<< (std::ostream &out, const Task &task) { + out << "Action: "; + switch (task.action) + { + case TASK_ACTION_HIGH: + out << "HIGH"; + break; + + case TASK_ACTION_LOW: + out << "LOW"; + break; + + default: + break; + } + + out << " Pin:" << task.pin; + return out; +} + +inline Task::Task(cron::cronexpr cronexpr, std::string command, callable action_fn): + cronexpr{cronexpr}, + action{TASK_ACTION_NONE}, + pin{0}, + action_fn{action_fn} +{ + if(command.rfind("HIGH", 0) == 0) { + action = TASK_ACTION_HIGH; + } else if((command.rfind("LOW", 0) == 0)) { + action = TASK_ACTION_LOW; + } + + auto start = command.find('('); + auto stop = command.find(')'); + if(start == std::string::npos && stop == std::string::npos) { + throw cron::bad_cronexpr("pin is not set in command"); + } + + LOG(DEBUG) << "pin to parse " << command.substr(start + 1, stop - start - 1); + + pin = std::stoi(command.substr(start + 1, stop - start - 1)); +} + +inline Task::Task(Task &task): + cronexpr{task.cronexpr}, + action{task.action}, + action_fn{task.action_fn}, + pin{task.pin} +{} + +inline void Task::doAction() { + if(action == TASK_ACTION_NONE) return; + + action_fn(pin, action); +} + +inline std::time_t Task::next() { + std::time_t now = std::time(0); + return cron::cron_next(cronexpr, now); +} + +#endif //TASK_MANAGER_HPP \ No newline at end of file