diff --git a/CMakeLists.txt b/CMakeLists.txt index 07def3f..042a0e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,8 +11,9 @@ include(FindLua) find_package(lua REQUIRED) find_package(InfluxDB REQUIRED) find_package(eclipse-paho-mqtt-c CONFIG REQUIRED) +find_package(croncpp CONFIG REQUIRED) add_executable(thrawn src/main.cpp src src/thrawn_api.cpp src/thrawn_api_db_logger.cpp src/Service.cpp src/Cache.cpp src/LuaEngine.cpp lib/mqtt++/src/mqtt++.cpp src/DBLogger.cpp) -target_link_libraries(thrawn ${LIBS} spdlog::spdlog InfluxData::InfluxDB ${LUA_LIBRARIES} OpenSSL::SSL eclipse-paho-mqtt-c::paho-mqtt3cs-static) +target_link_libraries(thrawn ${LIBS} croncpp::croncpp spdlog::spdlog InfluxData::InfluxDB ${LUA_LIBRARIES} OpenSSL::SSL eclipse-paho-mqtt-c::paho-mqtt3cs-static) target_include_directories(thrawn PRIVATE ${LUA_INCLUDE_DIR}) install(TARGETS thrawn DESTINATION bin) \ No newline at end of file diff --git a/src/LuaEngine.cpp b/src/LuaEngine.cpp index def9535..65d5c49 100644 --- a/src/LuaEngine.cpp +++ b/src/LuaEngine.cpp @@ -1,4 +1,13 @@ #include "LuaEngine.h" +#include + +bool ScheduledService::operator<(const ScheduledService& rhs) const { + return next_run > rhs.next_run; +} + +void ScheduledService::next() { + next_run = cron::cron_next(cron, std::time(0)); +} LuaEngine::LuaEngine() : db_logger("http://localhost:8086", "test") {} diff --git a/src/LuaEngine.h b/src/LuaEngine.h index 7674d8c..98292fb 100644 --- a/src/LuaEngine.h +++ b/src/LuaEngine.h @@ -6,6 +6,20 @@ #include #include #include "DBLogger.h" +#include "croncpp/croncpp.h" +#include +#include + +struct ScheduledService { + cron::cronexpr cron; + std::time_t next_run; + LuaService* service; + std::string callback; + + void next(); + bool operator<(const ScheduledService& rhs) const; +}; + class LuaEngine { private: @@ -13,6 +27,7 @@ class LuaEngine { public: std::function publish; DBLogger db_logger; + std::priority_queue time_schedule; static LuaEngine& get_instance() { static LuaEngine instance; @@ -23,6 +38,7 @@ class LuaEngine { Cache cache; + void load_services(); void start_services(); LuaService* create_service(std::string filename); diff --git a/src/Service.cpp b/src/Service.cpp index f2f2135..9f35ead 100644 --- a/src/Service.cpp +++ b/src/Service.cpp @@ -48,6 +48,9 @@ LuaService::LuaService(std::string filename) { lua_pushcfunction(lua, thrawn_subscribe); lua_setglobal(lua, "thrawn_subscribe"); + lua_pushcfunction(lua, thrawn_schedule); + lua_setglobal(lua, "thrawn_schedule"); + lua_pushcfunction(lua, thrawn_publish); lua_setglobal(lua, "thrawn_publish"); diff --git a/src/main.cpp b/src/main.cpp index fe442e6..3270d2e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,6 +11,7 @@ #include #include #include "mqtt++.h" +#include std::vector lua_scripts_in_folder(const std::string& path_str) { std::vector scripts; @@ -110,6 +111,26 @@ int main(int argc, char* argv[]) { spdlog::info("All services loaded."); while (true) { + time_t now = std::time(0); + + bool check_next = true; + + while (check_next) { + ScheduledService scheduled_service = LuaEngine::get_instance().time_schedule.top(); + if (now > scheduled_service.next_run) { + LuaEngine::get_instance().time_schedule.pop(); + call_thrawn_on_schedule(*scheduled_service.service, now, scheduled_service.callback); + scheduled_service.next(); + spdlog::info("Now: {} Next: {}, Time: {}", now, scheduled_service.next_run, std::time(0)); + LuaEngine::get_instance().time_schedule.push(scheduled_service); + + }else{ + check_next = false; + } + } + + + using namespace std::chrono_literals; std::this_thread::sleep_for(100ms); } diff --git a/src/thrawn_api.cpp b/src/thrawn_api.cpp index e61ff7a..c37f9b6 100644 --- a/src/thrawn_api.cpp +++ b/src/thrawn_api.cpp @@ -4,6 +4,8 @@ #include "LuaEngine.h" #include "spdlog/spdlog.h" #include +#include "croncpp/croncpp.h" + /* HELPER */ @@ -135,6 +137,51 @@ int thrawn_subscribe(lua_State* lua) { return 0; } +int thrawn_schedule(lua_State* lua) { + int paramCount = lua_gettop(lua); + + if(paramCount < 1 || paramCount > 2){ + return -1; + } + + LuaService* service = LuaEngine::get_instance().service_by_lua_state(lua); + + if (service->name == "") { + spdlog::error("Not registered service tried to subscribe to schedule '{}'", lua_tostring(lua, -1)); + + return 0; + } + + try { + ScheduledService scheduled_service; + + std::string cron_str = lua_tostring(lua, -1); + scheduled_service.callback = "thrawn_on_schedule"; + + if (paramCount == 2) { + cron_str = lua_tostring(lua, -2); + scheduled_service.callback = lua_tostring(lua, -1); + } + + scheduled_service.cron = cron::make_cron(cron_str); + + scheduled_service.service = service; + scheduled_service.next(); + + LuaEngine::get_instance().time_schedule.push(scheduled_service); + + spdlog::info("Service '{}' scheduled to '{}'", service->name, cron_str); + } + catch (cron::bad_cronexpr const & ex) + { + spdlog::error("Could not register scheduled job for cron string '{}' and service '{}'", lua_tostring(lua, -1), service->name); + } + + + + return 0; +} + int thrawn_cached_topic(lua_State* lua) { int paramCount = lua_gettop(lua); @@ -170,10 +217,10 @@ int thrawn_topic_components(lua_State* lua) { std::vector string_components; std::stringstream topic(lua_tostring(lua, -1)); - std::string token; - while (std::getline(topic, token, '/')) { - string_components.push_back(token); - } + std::string token; + while (std::getline(topic, token, '/')) { + string_components.push_back(token); + } lua_newtable(lua); int i = 1; @@ -224,6 +271,18 @@ void call_thrawn_on_update(LuaService& service, TopicState state) { if (lua_pcall(service.lua, 1, 0, 0) != 0) { spdlog::error("[{}] Unable to call 'thrawn_on_update'. [Lua: '{}']", service.name, lua_tostring(service.lua,-1)); + return; + } +} + +void call_thrawn_on_schedule(LuaService& service, std::time_t now, const std::string& callback) { + lua_getglobal(service.lua, callback.c_str()); + + lua_pushinteger(service.lua, now); + + if (lua_pcall(service.lua, 1, 0, 0) != 0) { + spdlog::error("[{}] Unable to call 'thrawn_on_schedule'. [Lua: '{}']", service.name); + return; } } \ No newline at end of file diff --git a/src/thrawn_api.h b/src/thrawn_api.h index 8064842..acd5b67 100644 --- a/src/thrawn_api.h +++ b/src/thrawn_api.h @@ -11,6 +11,7 @@ extern "C" #include #include #include "Service.h" +#include //Functions for LUA int thrawn_log(lua_State* lua); @@ -19,6 +20,8 @@ int thrawn_register_service(lua_State* lua); int thrawn_subscribe(lua_State* lua); +int thrawn_schedule(lua_State* lua); + int thrawn_publish(lua_State* lua); int thrawn_create_topic_state(lua_State* lua); @@ -34,6 +37,8 @@ void call_thrawn_on_start(LuaService& service); void call_thrawn_on_update(LuaService& service, TopicState state); +void call_thrawn_on_schedule(LuaService& service, std::time_t now, const std::string& callback); + //Database logging int thrawn_log_light(lua_State* lua); diff --git a/src/thrawn_api_db_logger.cpp b/src/thrawn_api_db_logger.cpp index 6edef1a..e57cc2e 100644 --- a/src/thrawn_api_db_logger.cpp +++ b/src/thrawn_api_db_logger.cpp @@ -12,9 +12,12 @@ int thrawn_log_light(lua_State* lua) { //Param 1: room //Param 2: name //Param 3: is_on - if (!lua_isstring(lua, -3) || !lua_isstring (lua, -2) || !lua_isboolean (lua, -1)) { + if (!check_arguments(lua)) { return -1; } + // if (!lua_isstring(lua, -3) || !lua_isstring (lua, -2) || !lua_isboolean (lua, -1)) { + // return -1; + // } LuaEngine::get_instance().db_logger.write_light(lua_tostring(lua, -3), lua_tostring(lua, -2), lua_toboolean(lua, -1)); @@ -32,9 +35,12 @@ int thrawn_log_light_dimmable(lua_State* lua) { //Param 2: name //Param 3: is_on //Param 4: brightness - if (!lua_isstring(lua, -4) | !lua_isstring(lua, -3) || !lua_isboolean(lua, -2) || !lua_isinteger(lua, -1)) { + if (!check_arguments(lua)) { return -1; } + // if (!lua_isstring(lua, -4) | !lua_isstring(lua, -3) || !lua_isboolean(lua, -2) || !lua_isinteger(lua, -1)) { + // return -1; + // } LuaEngine::get_instance().db_logger.write_light_dimmable(lua_tostring(lua, -4), lua_tostring(lua, -3), lua_toboolean(lua, -2), lua_tointeger(lua, -1)); @@ -53,9 +59,12 @@ int thrawn_log_light_tw(lua_State* lua) { //Param 3: is_on //Param 4: brightness //Param 5: color_temperature - if (!lua_isstring(lua, -5) || !lua_isstring(lua, -4) || !lua_isboolean(lua, -3) || !lua_isinteger(lua, -2) || !lua_isinteger(lua, -1)) { + if (!check_arguments(lua)) { return -1; } + // if (!lua_isstring(lua, -5) || !lua_isstring(lua, -4) || !lua_isboolean(lua, -3) || !lua_isinteger(lua, -2) || !lua_isinteger(lua, -1)) { + // return -1; + // } LuaEngine::get_instance().db_logger.write_light_tuneable_white(lua_tostring(lua, -5), lua_tostring(lua, -4), lua_toboolean(lua, -3), lua_tointeger(lua, -2), lua_tointeger(lua, -1));