diff --git a/CMakeLists.txt b/CMakeLists.txt index a158e66..07def3f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,9 +9,10 @@ find_package(spdlog CONFIG REQUIRED) find_package(OpenSSL REQUIRED) include(FindLua) find_package(lua REQUIRED) +find_package(InfluxDB REQUIRED) find_package(eclipse-paho-mqtt-c CONFIG REQUIRED) -add_executable(thrawn src/main.cpp src src/thrawn_api.cpp src/Service.cpp src/Cache.cpp src/LuaEngine.cpp lib/mqtt++/src/mqtt++.cpp) -target_link_libraries(thrawn ${LIBS} spdlog::spdlog ${LUA_LIBRARIES} OpenSSL::SSL eclipse-paho-mqtt-c::paho-mqtt3cs-static) +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_include_directories(thrawn PRIVATE ${LUA_INCLUDE_DIR}) install(TARGETS thrawn DESTINATION bin) \ No newline at end of file diff --git a/lib/mqtt++ b/lib/mqtt++ index a7b570a..cc9f7bc 160000 --- a/lib/mqtt++ +++ b/lib/mqtt++ @@ -1 +1 @@ -Subproject commit a7b570ab18dbc685b8e17242239746bc436c3f55 +Subproject commit cc9f7bc6ebf66001c5c0fd5b742a825eae7a2e3a diff --git a/src/Cache.cpp b/src/Cache.cpp index 642f968..c96e6a0 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -1,22 +1,19 @@ #include "Cache.h" #include -std::vector Cache::topic_states(std::vector& topics) { - std::vector states; - - for (const auto& topic : topics) { - auto hit = std::find_if(cache.begin(), cache.end(), [&](TopicState& cache_state) { - return cache_state.topic == topic; - }); - if (hit != cache.end()) { - states.push_back(*hit); +#include "mqtt++.h" +#include + +std::optional Cache::cached_topic_value(const std::string& topic) { + for (const auto& state : m_cache) { + if (state.topic == topic) { + return state.value; } } - - return states; + return std::nullopt; } TopicState Cache::set_topic_value(const std::string& topic, const std::string& value) { - for (auto& state : cache) { + for (auto& state : m_cache) { if (state.topic == topic) { state.value = value; @@ -24,5 +21,5 @@ TopicState Cache::set_topic_value(const std::string& topic, const std::string& v } } - return cache.emplace_back(topic, value); + return m_cache.emplace_back(topic, value); } \ No newline at end of file diff --git a/src/Cache.h b/src/Cache.h index 3652a2a..f8c0b05 100644 --- a/src/Cache.h +++ b/src/Cache.h @@ -3,12 +3,13 @@ #include #include #include "Service.h" +#include class Cache { private: - std::vector cache; + std::vector m_cache; public: - std::vector topic_states(std::vector& topics); TopicState set_topic_value(const std::string& topic, const std::string& value); + std::optional cached_topic_value(const std::string& topic); }; #endif // CACHE_H diff --git a/src/DBLogger.cpp b/src/DBLogger.cpp new file mode 100644 index 0000000..6ca5c78 --- /dev/null +++ b/src/DBLogger.cpp @@ -0,0 +1,53 @@ +/* +auto influxdb = + + influxdb->write(influxdb::Point{"test"} + .addField("value2", "mu") + .addTag("host", "localhost") + ); + */ +#include "DBLogger.h" +#include "InfluxDBFactory.h" +#include "spdlog/spdlog.h" +//url = http://localhost:8086 +//databse = test +DBLogger::DBLogger(const std::string& url, const std::string& database) { + m_influx = influxdb::InfluxDBFactory::Get(url + "/?db=" + database); +} + +void DBLogger::write_light(const std::string_view room, const std::string_view name, const bool is_on) { + try { + m_influx->write(influxdb::Point("light") + .addTag("room", room) + .addTag("name", name) + .addField("on", is_on ? 1 : 0)); + }catch (const std::exception& e) { + spdlog::error("Could not perform influx write for '{}' in room '{}'", room, name); + } + +} + +void DBLogger::write_light_dimmable(const std::string_view room, const std::string_view name, const bool is_on, const int brightness) { + try { + m_influx->write(influxdb::Point("light") + .addTag("room", room) + .addTag("name", name) + .addField("on", is_on ? 1 : 0) + .addField("brightness", brightness)); + }catch (const std::exception& e) { + spdlog::error("Could not perform influx write for '{}' in room '{}'", room, name); + } +} + +void DBLogger::write_light_tuneable_white(const std::string_view room, const std::string_view name, const bool is_on, const int brightness, const int color_temperature) { + try { + m_influx->write(influxdb::Point("light") + .addTag("room", room) + .addTag("name", name) + .addField("on", is_on ? 1 : 0) + .addField("brightness", brightness) + .addField("color_temp", color_temperature)); + }catch (const std::exception& e) { + spdlog::error("Could not perform influx write for '{}' in room '{}'", room, name); + } +} \ No newline at end of file diff --git a/src/DBLogger.h b/src/DBLogger.h new file mode 100644 index 0000000..91644ba --- /dev/null +++ b/src/DBLogger.h @@ -0,0 +1,20 @@ +#if !defined(DBLOGGER_H) +#define DBLOGGER_H + +#include +#include "InfluxDB.h" +#include +#include + +class DBLogger { + private: + std::unique_ptr m_influx; + public: + void write_light(const std::string_view room, const std::string_view name, const bool is_on); + void write_light_dimmable(const std::string_view room, const std::string_view name, const bool is_on, const int brightness); + void write_light_tuneable_white(const std::string_view room, const std::string_view name, const bool is_on, const int brightness, const int color_temperature); + + DBLogger(const std::string& url, const std::string& database); +}; + +#endif // DBLOGGER_H diff --git a/src/LuaEngine.cpp b/src/LuaEngine.cpp index 1ebed9b..def9535 100644 --- a/src/LuaEngine.cpp +++ b/src/LuaEngine.cpp @@ -1,7 +1,6 @@ #include "LuaEngine.h" -LuaEngine::LuaEngine() { -} +LuaEngine::LuaEngine() : db_logger("http://localhost:8086", "test") {} void LuaEngine::load_services() { for (auto& service : services) { diff --git a/src/LuaEngine.h b/src/LuaEngine.h index 6f14dd1..7674d8c 100644 --- a/src/LuaEngine.h +++ b/src/LuaEngine.h @@ -5,12 +5,15 @@ #include #include #include +#include "DBLogger.h" class LuaEngine { private: LuaEngine(); public: std::function publish; + DBLogger db_logger; + static LuaEngine& get_instance() { static LuaEngine instance; @@ -24,9 +27,34 @@ class LuaEngine { void start_services(); LuaService* create_service(std::string filename); LuaService* service_by_lua_state(lua_State* lua); - LuaEngine(LuaEngine const&) = delete; void operator=(LuaEngine const&) = delete; }; +template +inline bool check_arguments(lua_State* lua, int stack_pos) { + return check_arguments(lua, stack_pos+1) && check_arguments(lua, stack_pos); +} + +template +inline bool check_arguments(lua_State* lua) { + int num_arg = sizeof...(Args) + 1; + return check_arguments(lua, num_arg * -1); +} + +template <> +inline bool check_arguments(lua_State* lua, int stack_pos) { + return lua_isstring(lua, stack_pos); +} + +template <> +inline bool check_arguments(lua_State* lua, int stack_pos) { + return lua_isnumber(lua, stack_pos); +} + +template <> +inline bool check_arguments(lua_State* lua, int stack_pos) { + return lua_isnumber(lua, stack_pos); +} + #endif // LUA_ENGINE_H diff --git a/src/Service.cpp b/src/Service.cpp index cbe513f..f2f2135 100644 --- a/src/Service.cpp +++ b/src/Service.cpp @@ -57,8 +57,21 @@ LuaService::LuaService(std::string filename) { lua_pushcfunction(lua, thrawn_topic_state_str); lua_setglobal(lua, "thrawn_topic_state_str"); - lua_pushcfunction(lua, thrawn_topic_cache); - lua_setglobal(lua, "thrawn_topic_cache"); + lua_pushcfunction(lua, thrawn_cached_topic); + lua_setglobal(lua, "thrawn_cached_topic"); + + lua_pushcfunction(lua, thrawn_topic_components); + lua_setglobal(lua, "thrawn_topic_components"); + + lua_pushcfunction(lua, thrawn_log_light); + lua_setglobal(lua, "thrawn_log_light"); + + lua_pushcfunction(lua, thrawn_log_light_dimmable); + lua_setglobal(lua, "thrawn_log_light_dimmable"); + + lua_pushcfunction(lua, thrawn_log_light_tw); + lua_setglobal(lua, "thrawn_log_light_tw"); + if (luaL_loadfile(lua, filename.c_str()) != 0) { diff --git a/src/Version.h b/src/Version.h index eea9ec8..20eefe1 100644 --- a/src/Version.h +++ b/src/Version.h @@ -3,6 +3,6 @@ #define THRAWN_MAJOR_VERSION 1 #define THRAWN_MINOR_VERSION 0 -#define THRAWN_PATCH_VERSION 4 +#define THRAWN_PATCH_VERSION 5 #endif // VERSION_H diff --git a/src/main.cpp b/src/main.cpp index f474bfa..fe442e6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -39,7 +39,6 @@ int main(int argc, char* argv[]) { std::string version_topic_value = std::to_string(THRAWN_MAJOR_VERSION) + "." + std::to_string(THRAWN_MINOR_VERSION) + "." + std::to_string(THRAWN_PATCH_VERSION); std::string lastWillConnected = "1"; - /* * Parsing arguments * Show version or load path @@ -69,12 +68,12 @@ int main(int argc, char* argv[]) { mqtt.publish(last_will.topic, lastWillConnected); mqtt.publish(version_topic, version_topic_value); - mqtt.subscribe("#", [&] (const char* topic, const std::string& msg) { + mqtt.subscribe("#", [&] (const std::string& topic, const std::string& msg) { LuaEngine::get_instance().cache.set_topic_value(topic, msg); for (auto& service : LuaEngine::get_instance().services) { for (const std::string& sub_topic : service.subscriptions) { - if (sub_topic == topic) { + if (mqttpp::sub_matches_topic(sub_topic, topic)) { service.on_update(TopicState(topic, msg)); } } diff --git a/src/thrawn_api.cpp b/src/thrawn_api.cpp index 75446c6..e61ff7a 100644 --- a/src/thrawn_api.cpp +++ b/src/thrawn_api.cpp @@ -3,7 +3,7 @@ #include "Cache.h" #include "LuaEngine.h" #include "spdlog/spdlog.h" - +#include /* HELPER */ @@ -23,8 +23,8 @@ void push_list_topic_state(lua_State* lua, std::vector states) { int i = 1; for (auto& state : states) { - lua_pushinteger(lua, i); - push_topic_state_table(lua, state); + lua_pushstring(lua, state.topic.c_str()); + lua_pushstring(lua, state.value.c_str()); lua_settable(lua, -3); i++; @@ -39,9 +39,9 @@ int thrawn_log(lua_State* lua) if(paramCount != 1) { return -1; - } + } - if (!lua_isstring (lua, -1)) { + if (!check_arguments(lua)) { return -1; } @@ -135,20 +135,58 @@ int thrawn_subscribe(lua_State* lua) { return 0; } -int thrawn_topic_cache(lua_State* lua) { +int thrawn_cached_topic(lua_State* lua) { int paramCount = lua_gettop(lua); - if(paramCount != 0){ + if (paramCount != 1){ return -1; } - LuaEngine& lua_engine = LuaEngine::get_instance(); - LuaService* service = lua_engine.service_by_lua_state(lua); + if (!lua_isstring(lua, -1)) { + return -1; + } - push_list_topic_state(lua, lua_engine.cache.topic_states(service->subscriptions)); + const auto& cached_value = LuaEngine::get_instance().cache.cached_topic_value(lua_tostring(lua, -1)); - return 1; + if (cached_value) { + lua_pushstring(lua, cached_value.value().c_str()); + }else { + lua_pushnil(lua); + } + + return 1; } + +int thrawn_topic_components(lua_State* lua) { + int paramCount = lua_gettop(lua); + + if (paramCount != 1) { + return -1; + } + + if (!lua_isstring(lua, -1)) { + return -1; + } + + std::vector string_components; + std::stringstream topic(lua_tostring(lua, -1)); + std::string token; + while (std::getline(topic, token, '/')) { + string_components.push_back(token); + } + + lua_newtable(lua); + int i = 1; + for (const std::string& comp : string_components) { + lua_pushinteger(lua, i); + lua_pushstring(lua, comp.c_str()); + lua_settable(lua, -3); + i++; + } + + return 1; +} + // thrawn_publish(topic, value) int thrawn_publish(lua_State* lua) { int paramCount = lua_gettop(lua); diff --git a/src/thrawn_api.h b/src/thrawn_api.h index a3279bd..8064842 100644 --- a/src/thrawn_api.h +++ b/src/thrawn_api.h @@ -25,11 +25,21 @@ int thrawn_create_topic_state(lua_State* lua); int thrawn_topic_state_str(lua_State* lua); -int thrawn_topic_cache(lua_State* lua); +int thrawn_topic_components(lua_State* lua); + +int thrawn_cached_topic(lua_State* lua); // Function in LUA void call_thrawn_on_start(LuaService& service); void call_thrawn_on_update(LuaService& service, TopicState state); +//Database logging + +int thrawn_log_light(lua_State* lua); + +int thrawn_log_light_dimmable(lua_State* lua); + +int thrawn_log_light_tw(lua_State* lua); + #endif // THRAWN_API_H \ No newline at end of file diff --git a/src/thrawn_api_db_logger.cpp b/src/thrawn_api_db_logger.cpp new file mode 100644 index 0000000..6edef1a --- /dev/null +++ b/src/thrawn_api_db_logger.cpp @@ -0,0 +1,63 @@ +#include "thrawn_api.h" +#include "LuaEngine.h" + +//void write_light(const std::string_view name, const bool is_on); +int thrawn_log_light(lua_State* lua) { + int paramCount = lua_gettop(lua); + + if (paramCount != 3) { + return -1; + } + + //Param 1: room + //Param 2: name + //Param 3: is_on + 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)); + + return 0; +} + +int thrawn_log_light_dimmable(lua_State* lua) { + int paramCount = lua_gettop(lua); + + if (paramCount != 4) { + return -1; + } + + //Param 1: room + //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)) { + 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)); + + return 0; +} + +int thrawn_log_light_tw(lua_State* lua) { + int paramCount = lua_gettop(lua); + + if (paramCount != 5) { + return -1; + } + + //Param 1: room + //Param 2: name + //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)) { + 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)); + + return 0; +} \ No newline at end of file