Commit c00ff67c authored by Steffen Vogel's avatar Steffen Vogel 🎅🏼

hook: add new lua hook

parent 150b6bf9
......@@ -103,6 +103,8 @@ pkg_check_modules(JANSSON IMPORTED_TARGET REQUIRED jansson>=2.9)
pkg_check_modules(LIBWEBSOCKETS IMPORTED_TARGET REQUIRED libwebsockets>=2.3.0)
pkg_check_modules(PROTOBUF IMPORTED_TARGET protobuf>=2.6.0)
pkg_check_modules(PROTOBUFC IMPORTED_TARGET libprotobuf-c>=1.1.0)
pkg_check_modules(LUA IMPORTED_TARGET lua>=5.1)
pkg_check_modules(LUAJIT IMPORTED_TARGET luajit>=2.1.0)
pkg_check_modules(CRITERION IMPORTED_TARGET criterion>=2.3.1)
pkg_check_modules(LIBNL3_ROUTE IMPORTED_TARGET libnl-route-3.0>=3.2.27)
pkg_check_modules(LIBIEC61850 IMPORTED_TARGET libiec61850>=1.2)
......@@ -145,7 +147,7 @@ cmake_dependent_option(WITH_PLUGINS "Build plugins"
cmake_dependent_option(WITH_CLIENTS "Build client applications" ON "TOPLEVEL_PROJECT" OFF)
cmake_dependent_option(WITH_DOC "Build documentation" ON "TOPLEVEL_PROJECT" OFF)
cmake_dependent_option(WITH_GRAPHVIZ "Build with Graphviz" ON "CGRAPH_FOUND; GVC_FOUND" OFF)
cmake_dependent_option(WITH_LUA "Build with Lua" ON "LUA_FOUND OR LUAJIT_FOUND" OFF)
cmake_dependent_option(WITH_NODE_AMQP "Build with amqp node-type" ON "RABBITMQ_C_FOUND" OFF)
cmake_dependent_option(WITH_NODE_COMEDI "Build with comedi node-type" ON "COMEDILIB_FOUND" OFF)
cmake_dependent_option(WITH_NODE_FILE "Build with file node-type" ON "" OFF)
......
@include "hook-nodes.conf"
paths = (
{
in = "signal_node"
out = "file_node"
hooks = (
{
type = "script"
script = "lua/test_hook.lua"
},
{
type = "expression"
signals = (
{ name = "sum", unit = "V", expression = "math.sqrt(data.sine + data.random)" }
)
}
)
}
)
......@@ -70,7 +70,7 @@ protected:
struct vpath *path;
struct vnode *node;
vlist signals;
struct vlist signals;
json_t *cfg; /**< A JSON object containing the configuration of the hook. */
......
......@@ -65,6 +65,7 @@
#cmakedefine PROTOBUF_FOUND
#cmakedefine LIBNL3_ROUTE_FOUND
#cmakedefine IBVERBS_FOUND
#cmakedefine LUAJIT_FOUND
/* Library features */
#cmakedefine LWS_DEFLATE_FOUND
......@@ -44,6 +44,10 @@ set(HOOK_SRC
print.cpp
)
if(WITH_LUA)
list(APPEND lua.cpp)
endif()
add_library(hooks STATIC ${HOOK_SRC})
target_include_directories(hooks PUBLIC ${INCLUDE_DIRS})
target_link_libraries(hooks PUBLIC ${LIBRARIES})
/** Lua expressions hook.
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2014-2020, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASnode
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
/** @addtogroup hooks Hook functions
* @{
*/
#include <lua.h>
#include <villas/hook.hpp>
#include <villas/node.h>
#include <villas/sample.h>
namespace villas {
namespace node {
class LuaHook : public Hook {
protected:
std::string script;
lua_State *L;
/* Function indizes */
struct {
int start;
int stop;
int process;
int periodic;
int prepare;
} function;
void sampleToLua(struct sample *smp, int idx = -1)
{
for (unsigned i = 0; i < smp->length; i++) {
struct signal_type *sig = (struct signal_type *) vlist_at(smp->signals, i);
struct signal_data *data = &smp->data[i];
lua_pushnumber(L, i); /* Push the table index */
switch (sig->type) {
case FLOAT:
lua_pushnumber(L, data->f);
break;
case INTEGER:
lua_pushinteger(L, data->i);
break;
case BOOLEAN:
lua_pushboolean(L, data->b);
break;
case COMPLEX:
/* Not supported yet */
case INVALID:
default:
return Reason::ERROR;
}
lua_rawset(L, idx - 2);
}
}
void luaToSample(struct sample *smp, int idx = -1)
{
int ret;
for (smp->length = 0; i < MAX(vlist_length(&signals), smp->capacity); i++) {
struct signal_type *sig = (struct signal_type *) vlist_at(&signals, i);
ret = lua_rawgeti(L, idx, i);
switch (ret) {
case LUA_TBOOLEAN:
break;
case LUA_TNUMBER:
break;
default:
continue;
}
}
}
void parseSignals(json_t *json_sigs)
{
size_t i;
json_t *json_sig;
if (!json_is_array(json_sigs))
throw ConfigError(json_sigs, "node-config-hook-average-signals", "Setting 'signals' must be a list of dicts");
json_array_foreach(json_sigs, i, json_sig)
parseSignal(json_sig);
}
void parseSignal(json_t *json_sig)
{
switch (json_typeof(json_signal)) {
case JSON_STRING:
vlist_push(&signal_names, strdup(json_string_value(json_signal)));
break;
case JSON_INTEGER:
mask.set(json_integer_value(json_signal));
break;
default:
throw ConfigError(json_signal, "node-config-hook-average-signals", "Invalid value for setting 'signals'");
}
}
public:
Hook(struct vpath *p, struct vnode *n, int fl, int prio, bool en = true)
{
L = luaL_newstate();
}
virtual ~Hook()
{
lua_close(L);
}
virtual void parse(json_t *c)
{
int ret;
size_t i;
const char *script_str;
json_error_t err;
json_t *json_signals = nullptr;
assert(state != State::STARTED);
Hook::parse(cfg);
ret = json_unpack_ex(cfg, &err, 0, "{ s: s, s?: o }",
"script", &script_str,
"signals", &json_signals
);
if (ret)
throw ConfigError(cfg, err, "node-config-hook-average");
script = script_str;
if (json_signals)
parseSignals(json_signals);
state = State::PARSED;
}
virtual void check()
{
assert(state == State::PARSED);
state = State::CHECKED;
}
void prepare(struct vlist *sigs)
{
int ret;
/* Load Lua standard libraries */
luaL_openlibs(L);
/* Load our Lua script */
luaL_dofile(script.c_str())
/* Lookup functions */
std::map<const char *, int *> funcs = {
{ "start", &function.start },
{ "stop", &function.stop },
{ "prepare", &function.prepare },
{ "periodic", &function.periodic },
{ "process", &function.process }
};
for (auto it : funcs) {
ret = lua_getglobal(L, it->first);
if (ret == LUA_TFUNCTION) {
*(it->second) = lua_gettop(L);
}
else {
*(it->second) = 0;
lua_pop(L, 1);
}
}
ret = signal_list_copy(&signals, sigs);
if (ret)
throw RuntimeError("Failed to copy signal list");
prepare();
}
/** Called whenever a hook is started; before threads are created. */
virtual void start()
{
int ret;
assert(state == State::PREPARED);
if (functions.start) {
lua_pushvalue(L, functions.start);
lua_call(L, 0, 0);
}
state = State::STARTED;
}
/** Called whenever a hook is stopped; after threads are destoyed. */
virtual void stop()
{
assert(state == State::STARTED);
if (functions.stop) {
lua_pushvalue(L, functions.stop);
lua_call(L, 0, 0);
}
state = State::STOPPED;
}
/** Called whenever a new simulation case is started. This is detected by a sequence no equal to zero. */
virtual void restart()
{
assert(state == State::STARTED);
if (functions.restart) {
lua_pushvalue(L, functions.restart);
lua_call(L, 0, 0);
}
else
Hook::restart();
}
/** Called whenever a sample is processed. */
virtual Reason process(sample *smp)
{
lua_newtable(L);
if (functions.process) {
lua_pushvalue(L, functions.process);
sampleToLua(smp);
lua_call(L, 1, 1);
luaToSample(smp);
}
return Reason::OK;
};
};
/* Register hook */
static char n[] = "lua";
static char d[] = "Implement hook in Lua";
static HookPlugin<LuaHook, n, d, (int) Hook::Flags::NODE_READ | (int) Hook::Flags::NODE_WRITE | (int) Hook::Flags::PATH, 1> p;
} /* namespace node */
} /* namespace villas */
/** @} */
function prepare()
print("Preparing test_hook")
my_global_var = 1337
end
function start()
print("Starting test_hook")
print("Global var: ", my_global_var)
end
function stop()
print("Stopping test_hook")
end
function process(data)
print("Process test_hook")
printf("Test data: ", data)
end
function periodic()
print("Periodic test_hook")
end
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment