Inital commit. Meisten features sind implementiert und müssen getestet werden

This commit is contained in:
2020-07-15 10:49:03 +02:00
commit e1dcd55757
12 changed files with 632 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.vscode
CMakeFiles
build

18
CMakeLists.txt Normal file
View File

@@ -0,0 +1,18 @@
cmake_minimum_required(VERSION 3.10)
project(thrawn)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_EXPORT_COMPILE_COMMANDS true)
include_directories("/usr/local/include")
include_directories("/usr/local/include/mqttpp-0.1")
link_directories("/usr/local/lib/mqttpp-0.1")
find_package(spdlog CONFIG REQUIRED)
include(FindLua)
find_package(lua REQUIRED)
add_executable(thrawn src/main.cpp src src/thrawn_api.cpp src/Service.cpp src/Cache.cpp src/LuaEngine.cpp)
target_link_libraries(thrawn ${LIBS} spdlog::spdlog ${LUA_LIBRARIES} mqttpp)
target_include_directories(thrawn PRIVATE ${LUA_INCLUDE_DIR})
install(TARGETS thrawn DESTINATION bin)

28
src/Cache.cpp Normal file
View File

@@ -0,0 +1,28 @@
#include "Cache.h"
#include <algorithm>
std::vector<TopicState> Cache::topic_states(std::vector<std::string>& topics) {
std::vector<TopicState> 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);
}
}
return states;
}
TopicState Cache::set_topic_value(const std::string& topic, const std::string& value) {
for (auto& state : cache) {
if (state.topic == topic) {
state.value = value;
return state;
}
}
return cache.emplace_back(topic, value);
}

14
src/Cache.h Normal file
View File

@@ -0,0 +1,14 @@
#if !defined(CACHE_H)
#define CACHE_H
#include <string>
#include <vector>
#include "Service.h"
class Cache {
private:
std::vector<TopicState> cache;
public:
std::vector<TopicState> topic_states(std::vector<std::string>& topics);
TopicState set_topic_value(const std::string& topic, const std::string& value);
};
#endif // CACHE_H

32
src/LuaEngine.cpp Normal file
View File

@@ -0,0 +1,32 @@
#include "LuaEngine.h"
LuaEngine::LuaEngine() {
}
void LuaEngine::load_services() {
for (auto& service : services) {
service.load();
}
}
void LuaEngine::start_services() {
for (auto& service : services) {
service.start();
}
}
LuaService* LuaEngine::create_service(std::string filename) {
services.emplace_back(filename);
return &services.back();
}
LuaService* LuaEngine::service_by_lua_state(lua_State* lua) {
for (auto& service : services) {
if (service.lua == lua) {
return &service;
}
}
return nullptr;
}

32
src/LuaEngine.h Normal file
View File

@@ -0,0 +1,32 @@
#if !defined(LUA_ENGINE_H)
#define LUA_ENGINE_H
#include "Service.h"
#include "Cache.h"
#include <vector>
#include <functional>
#include <string>
class LuaEngine {
private:
LuaEngine();
public:
std::function<void(const TopicState& state)> publish;
static LuaEngine& get_instance() {
static LuaEngine instance;
return instance;
}
std::vector<LuaService> services;
Cache cache;
void load_services();
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;
};
#endif // LUA_ENGINE_H

110
src/Service.cpp Normal file
View File

@@ -0,0 +1,110 @@
#include "Service.h"
#include <iostream>
#include "thrawn_api.h"
#include "spdlog/spdlog.h"
std::string TopicState::to_string() {
std::string str = "[ Topic: '";
str += this->topic;
str += "', Value: '";
str += this->value;
str +="' ]";
return str;
}
std::optional<TopicState> TopicState::create_from_lua(lua_State* lua, int lua_table_stack_index) {
if (! lua_istable(lua, lua_table_stack_index)) {
return std::nullopt;
}
TopicState state;
lua_getfield(lua, lua_table_stack_index, "topic");
state.topic = lua_tostring(lua, -1);
lua_pop(lua, 1);
lua_getfield(lua, lua_table_stack_index, "value");
state.value = lua_tostring(lua, -1);
lua_pop(lua, 1);
return state;
}
LuaService::LuaService(std::string filename) {
lua = luaL_newstate();
name = "";
started = false;
loaded = false;
luaL_openlibs(lua);
lua_pushcfunction(lua, thrawn_log);
lua_setglobal(lua, "thrawn_log");
lua_pushcfunction(lua, thrawn_register_service);
lua_setglobal(lua, "thrawn_register_service");
lua_pushcfunction(lua, thrawn_subscribe);
lua_setglobal(lua, "thrawn_subscribe");
lua_pushcfunction(lua, thrawn_publish);
lua_setglobal(lua, "thrawn_publish");
lua_pushcfunction(lua, thrawn_create_topic_state);
lua_setglobal(lua, "thrawn_create_topic_state");
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");
if (luaL_loadfile(lua, filename.c_str()) != 0) {
throw std::runtime_error("Error during loading of '" + filename + "' - [Lua error: '" + lua_tostring(lua,-1) + "'].");
}
}
LuaService::~LuaService() {
if (lua != nullptr) {
lua_close(lua);
}
}
LuaService::LuaService(LuaService&& service) noexcept {
lua = service.lua;
service.lua = nullptr;
name = std::move(service.name);
}
LuaService& LuaService::operator=(LuaService&& service) noexcept {
lua = service.lua;
service.lua = nullptr;
name = std::move(service.name);
return *this;
}
void LuaService::load() {
if (!loaded) {
loaded = true;
if (lua_pcall(lua, 0, 0 ,0) != 0) {
spdlog::error("[{}] Unable to call 'lua_pcall()' in load(). [Lua: '{}']", name, lua_tostring(lua,-1));
}
}
}
void LuaService::start() {
if (!started && loaded) {
started = true;
call_thrawn_on_start(*this);
}
}
void LuaService::on_update(const TopicState& state) {
if (started && loaded) {
call_thrawn_on_update(*this, state);
}
}

50
src/Service.h Normal file
View File

@@ -0,0 +1,50 @@
#if !defined(SERVICE_H)
#define SERVICE_H
extern "C"
{
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}
#include <string>
#include <memory>
#include <vector>
#include <optional>
struct TopicState {
std::string topic;
std::string value;
TopicState() : topic(""), value(""){}
TopicState(const std::string& topic, const std::string& value) : topic(topic), value(value){}
static std::optional<TopicState> create_from_lua(lua_State* lua, int lua_table_stack_index);
std::string to_string();
};
struct LuaService {
public:
lua_State* lua;
std::string name;
std::vector<std::string> subscriptions;
LuaService(std::string filename);
~LuaService();
LuaService(const LuaService&) = delete;
LuaService& operator=(const LuaService&) = delete;
LuaService(LuaService&&) noexcept; //Noexcept is important because of std::vector
LuaService& operator=(LuaService&&) noexcept; //Noexcept is important because of std::vector
void load();
void start();
void on_update(const TopicState& topic_state);
private:
bool loaded;
bool started;
};
#endif // SERVICE_H

8
src/Version.h Normal file
View File

@@ -0,0 +1,8 @@
#if !defined(VERSION_H)
#define VERSION_H
#define THRAWN_MAJOR_VERSION 1
#define THRAWN_MINOR_VERSION 0
#define THRAWN_PATCH_VERSION 0
#endif // VERSION_H

111
src/main.cpp Normal file
View File

@@ -0,0 +1,111 @@
#include <iostream>
#include <vector>
#include <string>
#include "Service.h"
#include "thrawn_api.h"
#include "mqttpp.h"
#include "spdlog/spdlog.h"
#include "Cache.h"
#include "LuaEngine.h"
#include <filesystem>
#include "Version.h"
#include <cstring>
std::vector<std::string> lua_scripts_in_folder(const std::string& path_str) {
std::vector<std::string> scripts;
std::filesystem::path scripts_folder(path_str);
if (std::filesystem::exists(scripts_folder)) {
for (const auto& file: std::filesystem::directory_iterator(scripts_folder)) {
if (file.is_regular_file() && file.path().extension() == ".lua") {
scripts.push_back(file.path().string());
}
}
}else{
throw std::invalid_argument("Folder '" + path_str + "' does not exist.");
}
return scripts;
}
int main(int argc, char* argv[]) {
std::string services_path_str = "/etc/thrawn";
std::string mqttServerURI = "tcp://chimaera:1883";
mqttpp::LastWill last_will {mqttServerURI, "0"};
std::string lastWillConnected = "1";
/*
* Parsing arguments
* Show version or load path
*/
if (argc > 1) {
if (strcmp(argv[1], "-v") == 0 || strcmp(argv[1], "--version") == 0) {
std::cout << "Version: " << THRAWN_MAJOR_VERSION << "." << THRAWN_MINOR_VERSION << "." << THRAWN_PATCH_VERSION << std::endl;
return 0;
}
services_path_str = argv[1];
}
/*
* MQTT
*/
spdlog::info("Starting MQTT connection to '{}' .", mqttServerURI);
mqttpp::Client mqtt(mqttServerURI, last_will);
if (! mqtt.connect()) {
spdlog::error("Failed to establishe mqtt connection to {}'", mqttServerURI);
return 1;
}
mqtt.publish(last_will.topic, lastWillConnected);
mqtt.subscribe("#", [&] (const char* 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) {
service.on_update(TopicState(topic, msg));
}
}
}
});
LuaEngine::get_instance().publish = [&](const TopicState& state) {
mqtt.publish(state.topic, state.value);
};
spdlog::info("Connection to MQTT server '{}' successfull.", mqttServerURI);
try {
for (const auto& script : lua_scripts_in_folder(services_path_str)) {
LuaEngine::get_instance().create_service(script);
}
}catch(const std::exception& e) {
spdlog::error(e.what());
return 1;
}
/*
* LUA Engine
*/
spdlog::info("Starting Thrawn services on Chimaera.");
LuaEngine::get_instance().load_services();
spdlog::info("{} service(s) loaded.", LuaEngine::get_instance().services.size());
LuaEngine::get_instance().start_services();
spdlog::info("All services loaded.");
while (true) {}
return 0;
}

191
src/thrawn_api.cpp Normal file
View File

@@ -0,0 +1,191 @@
#include "thrawn_api.h"
#include <iostream>
#include "Cache.h"
#include "LuaEngine.h"
#include "spdlog/spdlog.h"
/*
HELPER
*/
void push_topic_state_table(lua_State* lua, TopicState state) {
lua_newtable(lua);
lua_pushliteral(lua, "topic");
lua_pushstring(lua, state.topic.c_str());
lua_settable(lua, -3);
lua_pushliteral(lua, "value");
lua_pushstring(lua, state.value.c_str());
lua_settable(lua, -3);
}
void push_list_topic_state(lua_State* lua, std::vector<TopicState> states) {
lua_newtable(lua);
int i = 1;
for (auto& state : states) {
lua_pushinteger(lua, i);
push_topic_state_table(lua, state);
lua_settable(lua, -3);
i++;
}
}
//Ab hier ist ok
int thrawn_log(lua_State* lua)
{
int paramCount = lua_gettop(lua);
if(paramCount != 1)
{
return -1;
}
if (!lua_isstring (lua, -1)) {
return -1;
}
spdlog::info("[{}] {}", LuaEngine::get_instance().service_by_lua_state(lua)->name, lua_tostring(lua, -1));
return 0;
}
int thrawn_register_service(lua_State* lua) {
int paramCount = lua_gettop(lua);
if(paramCount != 1)
{
return -1;
}
LuaEngine::get_instance().service_by_lua_state(lua)->name = lua_tostring(lua, -1);
spdlog::info("Service '{}' registered", lua_tostring(lua, -1));
return 0;
}
int thrawn_create_topic_state(lua_State* lua) {
int paramCount = lua_gettop(lua);
if(paramCount < 0 || paramCount > 3)
{
return -1;
}
TopicState state;
if (paramCount == 1) {
if (!lua_isstring (lua, -1)) {
return -1;
}
state.topic = lua_tostring(lua, -1);
}else if (paramCount == 2) {
if (!lua_isstring (lua, -2) && !lua_isstring (lua, -1)) {
return -1;
}
state.topic = lua_tostring(lua, -2);
state.value = lua_tostring(lua, -1);
}
push_topic_state_table(lua, state);
return 1;
}
int thrawn_topic_state_str(lua_State* lua) {
int paramCount = lua_gettop(lua);
if(paramCount != 1)
{
return -1;
}
std::optional<TopicState> state = TopicState::create_from_lua(lua, -1);
if (! state.has_value()) {
return -1;
}
lua_pushstring(lua, state.value().to_string().c_str());
return 1;
}
int thrawn_subscribe(lua_State* lua) {
int paramCount = lua_gettop(lua);
if(paramCount != 1){
return -1;
}
LuaService* service = LuaEngine::get_instance().service_by_lua_state(lua);
if (service->name == "") {
spdlog::error("Not registered service tried to subscribe to '{}'", lua_tostring(lua, -1));
return 0;
}
service->subscriptions.push_back(lua_tostring(lua, -1));
spdlog::info("Service '{}' subscribed to '{}'", service->name, lua_tostring(lua, -1));
return 0;
}
int thrawn_topic_cache(lua_State* lua) {
int paramCount = lua_gettop(lua);
if(paramCount != 0){
return -1;
}
LuaEngine& lua_engine = LuaEngine::get_instance();
LuaService* service = lua_engine.service_by_lua_state(lua);
push_list_topic_state(lua, lua_engine.cache.topic_states(service->subscriptions));
return 1;
}
// thrawn_publish(topic, value)
int thrawn_publish(lua_State* lua) {
int paramCount = lua_gettop(lua);
if (paramCount != 1) {
return -1;
}
std::optional<TopicState> state = TopicState::create_from_lua(lua, -1);
if (! state.has_value()) {
return -1;
}
LuaEngine::get_instance().publish(state.value());
return 0;
}
void call_thrawn_on_start(LuaService& service) {
lua_getglobal(service.lua, "thrawn_on_start");
if (lua_pcall(service.lua,0, 0, 0) != 0) {
spdlog::error("[{}] Unable to call 'thrawn_on_start'. [Lua: '{}']", service.name, lua_tostring(service.lua,-1));
return;
}
}
void call_thrawn_on_update(LuaService& service, TopicState state) {
lua_getglobal(service.lua, "thrawn_on_update");
push_topic_state_table(service.lua, 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;
}
}

35
src/thrawn_api.h Normal file
View File

@@ -0,0 +1,35 @@
#if !defined(THRAWN_API_H)
#define THRAWN_API_H
extern "C"
{
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}
#include <vector>
#include <string>
#include "Service.h"
//Functions for LUA
int thrawn_log(lua_State* lua);
int thrawn_register_service(lua_State* lua);
int thrawn_subscribe(lua_State* lua);
int thrawn_publish(lua_State* lua);
int thrawn_create_topic_state(lua_State* lua);
int thrawn_topic_state_str(lua_State* lua);
int thrawn_topic_cache(lua_State* lua);
// Function in LUA
void call_thrawn_on_start(LuaService& service);
void call_thrawn_on_update(LuaService& service, TopicState state);
#endif // THRAWN_API_H