lua: fix bugs and improve documentation

This commit is contained in:
Steffen Vogel 2021-02-22 23:15:56 +01:00
parent fd57b5539f
commit 3a7f9c05de
4 changed files with 654 additions and 194 deletions

View File

@ -1,5 +1,7 @@
@include "hook-nodes.conf"
stats = 1
paths = (
{
in = "signal_node"
@ -9,21 +11,73 @@ paths = (
{
type = "lua"
# Enables or disables the use of signal names in the process() function
# of the Lua script. If disabled, numeric indices will be used.
use_names = true
# The Lua hook will pass the complete hook configuration to the prepare()
# function. So you can add arbitrary settings here which are then
# consumed by the Lua script.
some_setting = "Hello World"
this = {
is = {
nested = 1234
bool_val = true
}
}
# Script mode: we provide a Lua script containing functions
# for the individual hook points
# Define some or all of the following functions in your Lua script:
#
# prepare(cfg) Called during initialization with a Lua table which contains
# the full hook configuration
# start() Called when the node/path is started
#
# stop() Called when the node/path is stopped
#
# restart() Called when the node/path is restarted.
# Falls back to stop() + start() if absent.
#
# process(smp) Called for each sample which is being processed.
# The sample is passed as a Lua table with the following
# fields:
# - sequence The sequence number of the sample.
# - flags The flags field of the sample.
# - ts_origin The origin timesamp as a Lua table containing
# the following keys:
# 0: seconds
# 1: nanoseconds
# - ts_received The receive timestamp a Lua table containing
# the following keys:
# 0: seconds
# 1: nanoseconds
# - data The sample data as a Lua table container either
# numeric indices or the signal names depending
# on the 'use_names' option of the hook.
#
# periodic() Called periodicalled with the rate of the global 'stats' option.
script = "../lua/hooks/test.lua"
# Expression mode: We provide a mangled signal list including Lua expressions
signals = (
{ name = "sum", type="float", unit = "V", expression = "smp.data.square * 10" },
# You can access any global variable set by the script
{ name = "sequence", type="float", unit = "V", expression = "global_var" },
# Here we set a global variable from the periodic handler
{ name = "temp_aachen", type="float", unit = "°C", expression = "temp_aachen" },
# We can refer to the current time the global Lua variable 't'
{ name = "sum", type="float", unit = "V", expression = "math.sin(2 * math.pi * f * t)" },
{ name = "random", expression = "smp.data.random" }
)
},
{
type = "print"
}
# {
# type = "lua"
# # Expression mode: We provide a mangled signal list including Lua expressions
# signals = (
# { name = "sum", unit = "V", expression = "math.sqrt(data.sine + data.random)" }
# )
# }
)
}
)

View File

@ -22,9 +22,10 @@
#pragma once
#include <villas/hook.hpp>
#include <vector>
#include <mutex>
#include <villas/hook.hpp>
extern "C" {
#include "lua.h"
@ -40,40 +41,42 @@ class LuaSignalExpression {
protected:
int cookie;
int index;
LuaHook *hook;
lua_State *L;
std::string expression;
struct signal *signal;
json_t *cfg;
public:
LuaSignalExpression(int index, json_t *json_sig, LuaHook *h);
~LuaSignalExpression();
LuaSignalExpression(lua_State *L, json_t *json_sig);
void prepare();
void loadExpression(const std::string &expr);
void parseExpression(const std::string &expr);
void evaluate(union signal_data *data);
void evaluate(union signal_data *data, enum SignalType type);
};
class LuaHook : public Hook {
friend LuaSignalExpression;
private:
static const int SELF_REFERENCE = 55;
protected:
std::string script;
std::vector<LuaSignalExpression> expressions;
struct vlist signalsProcessed; /**> Signals as emited by Lua process() function */
struct vlist signalsExpressions; /**> Signals as emited by Lua expressions */
lua_State *L;
std::mutex mutex;
bool useNames;
bool hasExpressions;
bool needsLocking;
/* Function indizes */
struct {
@ -87,15 +90,45 @@ protected:
void parseExpressions(json_t *json_sigs);
void loadScript();
void lookupFunctions();
void setupEnvironment();
/* Lua functions */
int luaInfo(lua_State *L);
int luaWarn(lua_State *L);
int luaError(lua_State *L);
int luaDebug(lua_State *L);
int luaRegisterApiHandler(lua_State *L);
typedef int (LuaHook::*mem_func)(lua_State * L);
// This template wraps a member function into a C-style "free" function compatible with lua.
template <mem_func func>
static int
dispatch(lua_State * L) {
lua_rawgeti(L, LUA_REGISTRYINDEX, SELF_REFERENCE);
void *vptr = lua_touserdata(L, -1);
lua_pop(L, 1);
LuaHook *ptr = static_cast<LuaHook*>(vptr);
return ((*ptr).*func)(L);
}
public:
LuaHook(struct vpath *p, struct vnode *n, int fl, int prio, bool en = true);
virtual ~LuaHook();
virtual void parse(json_t *c);
virtual void parse(json_t *cfg);
virtual void prepare();
/** Periodically called by the main thread. */
virtual void periodic();
/** Called whenever a hook is started; before threads are created. */
virtual void start();
@ -109,6 +142,7 @@ public:
virtual Reason process(sample *smp);
};
} /* namespace node */
} /* namespace villas */

View File

@ -60,25 +60,41 @@ public:
virtual const char * what() const noexcept
{
const char *msg;
switch (err) {
case LUA_ERRSYNTAX:
return "Lua: Syntax error";
msg = "Syntax error";
break;
case LUA_ERRMEM:
return "Lua: memory allocation error";
msg = "Memory allocation error";
break;
case LUA_ERRFILE:
return "Lua: failed to open Lua script";
msg = "Failed to open Lua script";
break;
case LUA_ERRRUN:
return lua_tostring(L, -1);
msg = "Runtime error";
break;
case LUA_ERRERR:
return "Lua: failed to call error handler";
msg = "Failed to call error handler";
break;
default:
return "Lua: unknown error";
msg = "Unknown error";
break;
}
/* Append Lua error message */
std::string emsg = "Lua: ";
emsg += msg;
emsg += ": ";
emsg += lua_tostring(L, -1);
return emsg.c_str();
}
};
@ -101,7 +117,7 @@ lua_totimespec(lua_State *L, struct timespec *ts)
ts->tv_sec = lua_tonumber(L, -1);
lua_rawgeti(L, -2, 1);
ts->tv_sec = lua_tonumber(L, -1);
ts->tv_nsec = lua_tonumber(L, -1);
lua_pop(L, 2);
}
@ -109,11 +125,11 @@ lua_totimespec(lua_State *L, struct timespec *ts)
static void
lua_tosignaldata(lua_State *L, union signal_data *data, enum SignalType targetType, int idx = -1)
{
int ret;
int luaType;
enum SignalType type;
ret = lua_type(L, idx);
switch (ret) {
luaType = lua_type(L, idx);
switch (luaType) {
case LUA_TBOOLEAN:
data->b = lua_toboolean(L, idx);
type = SignalType::BOOLEAN;
@ -125,6 +141,7 @@ lua_tosignaldata(lua_State *L, union signal_data *data, enum SignalType targetTy
break;
default:
warning("Failed to convert Lua type %s to signal data", lua_typename(L, luaType));
return;
}
@ -154,14 +171,6 @@ lua_tosample(lua_State *L, struct sample *smp, struct vlist *signals, bool use_n
lua_getfield(L, idx, "data");
smp->length = lua_objlen(L, -1);
if (smp->length > vlist_length(signals))
smp->length = vlist_length(signals);
if (smp->length > smp->capacity)
smp->length = smp->capacity;
for (unsigned i = 0; i < smp->length; i++) {
struct signal *sig = (struct signal *) vlist_at(signals, i);
@ -183,68 +192,194 @@ lua_pushsample(lua_State *L, struct sample *smp, bool use_names = true)
{
lua_createtable(L, 0, 5);
lua_pushnumber(L, smp->sequence);
lua_setfield(L, -2, "sequence");
lua_pushnumber(L, smp->flags);
lua_setfield(L, -2, "flags");
lua_pushtimespec(L, &smp->ts.origin);
lua_setfield(L, -2, "ts_origin");
lua_pushtimespec(L, &smp->ts.received);
lua_setfield(L, -2, "ts_received");
lua_createtable(L, smp->length, 0);
for (unsigned i = 0; i < smp->length; i++) {
struct signal *sig = (struct signal *) vlist_at(smp->signals, i);
union signal_data *data = &smp->data[i];
switch (sig->type) {
case SignalType::FLOAT:
lua_pushnumber(L, data->f);
break;
case SignalType::INTEGER:
lua_pushinteger(L, data->i);
break;
case SignalType::BOOLEAN:
lua_pushboolean(L, data->b);
break;
case SignalType::COMPLEX:
case SignalType::INVALID:
default:
continue; /* we skip unknown types. Lua will see a nil value in the table */
}
if (use_names)
lua_setfield(L, -1, sig->name);
else
lua_rawseti(L, -2, i);
if (smp->flags & (int) SampleFlags::HAS_SEQUENCE) {
lua_pushnumber(L, smp->sequence);
lua_setfield(L, -2, "sequence");
}
lua_setfield(L, -2, "data");
if (smp->flags & (int) SampleFlags::HAS_TS_ORIGIN) {
lua_pushtimespec(L, &smp->ts.origin);
lua_setfield(L, -2, "ts_origin");
}
if (smp->flags & (int) SampleFlags::HAS_TS_RECEIVED) {
lua_pushtimespec(L, &smp->ts.received);
lua_setfield(L, -2, "ts_received");
}
if (smp->flags & (int) SampleFlags::HAS_DATA) {
lua_createtable(L, smp->length, 0);
for (unsigned i = 0; i < smp->length; i++) {
struct signal *sig = (struct signal *) vlist_at(smp->signals, i);
union signal_data *data = &smp->data[i];
switch (sig->type) {
case SignalType::FLOAT:
lua_pushnumber(L, data->f);
break;
case SignalType::INTEGER:
lua_pushinteger(L, data->i);
break;
case SignalType::BOOLEAN:
lua_pushboolean(L, data->b);
break;
case SignalType::COMPLEX:
case SignalType::INVALID:
default:
continue; /* we skip unknown types. Lua will see a nil value in the table */
}
if (use_names)
lua_setfield(L, -2, sig->name);
else
lua_rawseti(L, -2, i);
}
lua_setfield(L, -2, "data");
}
}
static void
lua_pushjson(lua_State *L, json_t *json)
{
size_t i;
const char *key;
json_t *json_value;
switch (json_typeof(json)) {
case JSON_OBJECT:
lua_newtable(L);
json_object_foreach(json, key, json_value) {
lua_pushjson(L, json_value);
lua_setfield(L, -2, key);
}
break;;
case JSON_ARRAY:
lua_newtable(L);
json_array_foreach(json, i, json_value) {
lua_pushjson(L, json_value);
lua_rawseti(L, -2, i);
}
break;
case JSON_STRING:
lua_pushstring(L, json_string_value(json));
break;
case JSON_REAL:
case JSON_INTEGER:
lua_pushnumber(L, json_integer_value(json));
break;
case JSON_TRUE:
case JSON_FALSE:
lua_pushboolean(L, json_boolean_value(json));
break;
case JSON_NULL:
lua_pushnil(L);
break;
}
}
static json_t * lua_tojson(lua_State *L, int index = -1)
{
double n;
const char *s;
bool b;
switch (lua_type(L, index)) {
case LUA_TFUNCTION:
case LUA_TUSERDATA:
case LUA_TTHREAD:
case LUA_TLIGHTUSERDATA:
case LUA_TNIL:
return json_null();
case LUA_TNUMBER:
n = lua_tonumber(L, index);
return n == (int) n
? json_integer(n)
: json_real(n);
case LUA_TBOOLEAN:
b = lua_toboolean(L, index);
return json_boolean(b);
case LUA_TSTRING:
s = lua_tostring(L, index);
return json_string(s);
case LUA_TTABLE: {
int keys_total = 0, keys_int = 0, key_highest = -1;
lua_pushnil(L);
while (lua_next(L, index) != 0) {
keys_total++;
if (lua_type(L, -2) == LUA_TNUMBER) {
int key = lua_tonumber(L, -1);
if (key == (int) key) {
keys_int++;
if (key > key_highest)
key_highest = key;
}
}
lua_pop(L, 1);
}
bool is_array = keys_total == keys_int && key_highest / keys_int > 0.5;
json_t *json = is_array
? json_array()
: json_object();
lua_pushnil(L);
while (lua_next(L, index) != 0) {
json_t *val = lua_tojson(L, -1);
if (is_array) {
int key = lua_tonumber(L, -2);
json_array_set(json, key, val);
}
else {
const char *key = lua_tostring(L, -2);
if (key) /* Skip table entries whose keys are neither string or number! */
json_object_set(json, key, val);
}
lua_pop(L, 1);
}
return json;
}
}
return nullptr;
}
namespace villas {
namespace node {
LuaSignalExpression::LuaSignalExpression(int index, json_t *json_sig, LuaHook *h) :
LuaSignalExpression::LuaSignalExpression(lua_State *l, json_t *json_sig) :
cookie(0),
index(-1),
hook(h)
L(l)
{
int ret;
json_error_t err;
const char *expr;
json_error_t err;
/* Parse expression */
ret = json_unpack_ex(json_sig, &err, 0, "{ s: s }",
"script", &expr
"expression", &expr
);
if (ret)
throw ConfigError(json_sig, err, "node-config-hook-lua-signals");
@ -254,65 +389,44 @@ LuaSignalExpression::LuaSignalExpression(int index, json_t *json_sig, LuaHook *h
expression = expr;
}
LuaSignalExpression::~LuaSignalExpression()
{
if (cookie)
luaL_unref(hook->L, LUA_REGISTRYINDEX, cookie);
}
void
LuaSignalExpression::prepare()
{
signal = (struct signal *) vlist_at(&hook->signals, index);
loadExpression(expression);
parseExpression(expression);
}
void
LuaSignalExpression::loadExpression(const std::string &expr)
LuaSignalExpression::parseExpression(const std::string &expr)
{
/* Release previous expression */
if (cookie)
luaL_unref(hook->L, LUA_REGISTRYINDEX, cookie);
luaL_unref(L, LUA_REGISTRYINDEX, cookie);
auto fexpr = fmt::format("return {}", expr);
int err = luaL_loadstring(hook->L, fexpr.c_str());
int err = luaL_loadstring(L, fexpr.c_str());
if (err)
throw ConfigError(cfg, "node-config-hook-lua-signals", "Failed to load Lua expression: {}", lua_tostring(hook->L, -1));
throw ConfigError(cfg, "node-config-hook-lua-signals", "Failed to load Lua expression: {}", lua_tostring(L, -1));
cookie = luaL_ref(hook->L, LUA_REGISTRYINDEX);
cookie = luaL_ref(L, LUA_REGISTRYINDEX);
}
void
LuaSignalExpression::evaluate(union signal_data *data)
LuaSignalExpression::evaluate(union signal_data *data, enum SignalType type)
{
int err;
lua_rawgeti(hook->L, LUA_REGISTRYINDEX, cookie);
lua_rawgeti(L, LUA_REGISTRYINDEX, cookie);
err = lua_pcall(hook->L, 0, 1, 0);
err = lua_pcall(L, 0, 1, 0);
if (err) {
throw RuntimeError("Lua: Evaluation failed: {}", lua_tostring(hook->L, -1));
lua_pop(hook->L, 1);
throw RuntimeError("Lua: Evaluation failed: {}", lua_tostring(L, -1));
lua_pop(L, 1);
}
lua_tosignaldata(hook->L, data, signal->type);
lua_tosignaldata(L, data, type);
lua_pop(hook->L, 1);
}
void
LuaHook::parseExpressions(json_t *json_sigs)
{
size_t i;
json_t *json_sig;
if (!json_is_array(json_sigs))
throw ConfigError(json_sigs, "node-config-hook-lua-signals", "Setting 'signals' must be a list of dicts");
json_array_foreach(json_sigs, i, json_sig)
expressions.emplace_back(i, json_sig, this);
lua_pop(L, 1);
}
LuaHook::LuaHook(struct vpath *p, struct vnode *n, int fl, int prio, bool en) :
@ -320,11 +434,43 @@ LuaHook::LuaHook(struct vpath *p, struct vnode *n, int fl, int prio, bool en) :
L(luaL_newstate()),
useNames(true),
hasExpressions(false)
{ }
{
int ret;
ret = signal_list_init(&signalsProcessed);
if (ret)
throw RuntimeError("Failed to initialize signal list");
ret = signal_list_init(&signalsExpressions);
if (ret)
throw RuntimeError("Failed to initialize signal list");
}
LuaHook::~LuaHook()
{
int ret __attribute__((unused));
lua_close(L);
ret = signal_list_destroy(&signalsProcessed);
ret = signal_list_destroy(&signalsExpressions);
}
void
LuaHook::parseExpressions(json_t *json_sigs)
{
int ret;
size_t i;
json_t *json_sig;
signal_list_clear(&signalsExpressions);
ret = signal_list_parse(&signalsExpressions, json_sigs);
if (ret)
throw ConfigError(json_sigs, "node-config-hook-lua-signals", "Setting 'signals' must be a list of dicts");
json_array_foreach(json_sigs, i, json_sig)
expressions.emplace_back(L, json_sig);
hasExpressions = true;
}
void
@ -360,34 +506,10 @@ LuaHook::parse(json_t *c)
}
void
LuaHook::prepare()
LuaHook::lookupFunctions()
{
int ret;
/* Load Lua standard libraries */
luaL_openlibs(L);
/* Load our Lua script */
logger->debug("Loading Lua script: %s", script.c_str());
ret = luaL_loadfile(L, script.c_str());
if (ret)
throw LuaError(L, ret);
ret = lua_pcall(L, 0, LUA_MULTRET, 0);
if (ret)
throw LuaError(L, ret);
/* Prepare Lua expressions */
if (hasExpressions) {
// signal_list_clear(&signals);
for (auto &expr : expressions) {
expr.prepare();
}
}
/* Lookup functions */
std::map<const char *, int *> funcs = {
{ "start", &functions.start },
{ "stop", &functions.stop },
@ -410,88 +532,256 @@ LuaHook::prepare()
lua_pop(L, 1);
}
}
}
if (functions.process && hasExpressions)
throw ConfigError(cfg, "node-config-hook-lua", "Lua expressions in the signal definition can not be combined with a process() function in the script.");
void
LuaHook::loadScript()
{
int ret;
ret = luaL_loadfile(L, script.c_str());
if (ret)
throw LuaError(L, ret);
ret = lua_pcall(L, 0, LUA_MULTRET, 0);
if (ret)
throw LuaError(L, ret);
}
int
LuaHook::luaRegisterApiHandler(lua_State *L)
{
// register_api_handler(path_regex)
return 0;
}
int
LuaHook::luaInfo(lua_State *L)
{
logger->info(luaL_checkstring(L, 1));
return 0;
}
int
LuaHook::luaWarn(lua_State *L)
{
logger->warn(luaL_checkstring(L, 1));
return 0;
}
int
LuaHook::luaError(lua_State *L)
{
logger->error(luaL_checkstring(L, 1));
return 0;
}
int
LuaHook::luaDebug(lua_State *L)
{
logger->debug(luaL_checkstring(L, 1));
return 0;
}
void
LuaHook::setupEnvironment()
{
lua_pushlightuserdata(L, this);
lua_rawseti(L, LUA_REGISTRYINDEX, SELF_REFERENCE);
lua_register(L, "info", &dispatch<&LuaHook::luaInfo>);
lua_register(L, "warn", &dispatch<&LuaHook::luaWarn>);
lua_register(L, "error", &dispatch<&LuaHook::luaError>);
lua_register(L, "debug", &dispatch<&LuaHook::luaDebug>);
lua_register(L, "register_api_handler", &dispatch<&LuaHook::luaRegisterApiHandler>);
}
void
LuaHook::prepare()
{
/* Load Lua standard libraries */
luaL_openlibs(L);
/* Load our Lua script */
logger->debug("Loading Lua script: %s", script.c_str());
setupEnvironment();
loadScript();
lookupFunctions();
/* Check if we need to protect the Lua state with a mutex
* This is the case if we have a periodic callback defined
* As periodic() gets called from the main thread
*/
needsLocking = functions.periodic > 0;
/* Prepare Lua process() */
if (functions.process) {
/* We currently do not support the alteration of
* signal metadata in process() */
signal_list_copy(&signalsProcessed, &signals);
}
/* Prepare Lua expressions */
if (hasExpressions) {
for (auto &expr : expressions)
expr.prepare();
signal_list_clear(&signals);
signal_list_copy(&signals, &signalsExpressions);
}
if (!functions.process && !hasExpressions)
logger->warn("The hook has neither a script or expressions defined. It is a no-op!");
if (functions.prepare) {
auto lockScope = needsLocking
? std::unique_lock<std::mutex>(mutex)
: std::unique_lock<std::mutex>();
logger->debug("Executing Lua function: prepare()");
lua_pushvalue(L, functions.prepare);
lua_call(L, 0, 0);
lua_pushjson(L, cfg);
int ret = lua_pcall(L, 1, 0, 0);
if (ret)
throw LuaError(L, ret);
}
}
/** Called whenever a hook is started; before threads are created. */
void
LuaHook::start()
{
assert(state == State::PREPARED);
auto lockScope = needsLocking
? std::unique_lock<std::mutex>(mutex)
: std::unique_lock<std::mutex>();
if (functions.start) {
logger->debug("Executing Lua function: start()");
lua_pushvalue(L, functions.start);
lua_call(L, 0, 0);
int ret = lua_pcall(L, 0, 0, 0);
if (ret)
throw LuaError(L, ret);
}
state = State::STARTED;
}
/** Called whenever a hook is stopped; after threads are destoyed. */
void
LuaHook::stop()
{
assert(state == State::STARTED);
auto lockScope = needsLocking
? std::unique_lock<std::mutex>(mutex)
: std::unique_lock<std::mutex>();
if (functions.stop) {
logger->debug("Executing Lua function: stop()");
lua_pushvalue(L, functions.stop);
lua_call(L, 0, 0);
int ret = lua_pcall(L, 0, 0, 0);
if (ret)
throw LuaError(L, ret);
}
state = State::STOPPED;
}
/** Called whenever a new simulation case is started. This is detected by a sequence no equal to zero. */
void
LuaHook::restart()
{
auto lockScope = needsLocking
? std::unique_lock<std::mutex>(mutex)
: std::unique_lock<std::mutex>();
assert(state == State::STARTED);
if (functions.restart) {
logger->debug("Executing Lua function: restart()");
lua_pushvalue(L, functions.restart);
lua_call(L, 0, 0);
int ret = lua_pcall(L, 0, 0, 0);
if (ret)
throw LuaError(L, ret);
}
else
Hook::restart();
}
/** Called whenever a sample is processed. */
void
LuaHook::periodic()
{
assert(state == State::STARTED);
if (functions.periodic) {
auto lockScope = needsLocking
? std::unique_lock<std::mutex>(mutex)
: std::unique_lock<std::mutex>();
logger->debug("Executing Lua function: restart()");
lua_pushvalue(L, functions.periodic);
int ret = lua_pcall(L, 0, 0, 0);
if (ret)
throw LuaError(L, ret);
}
}
Hook::Reason
LuaHook::process(sample *smp)
{
/* First evaluate expressions */
if (hasExpressions) {
for (unsigned i = 0; i < expressions.size(); i++)
expressions[i].evaluate(&smp->data[i]);
if (!functions.process && !hasExpressions)
return Reason::OK;
smp->length = expressions.size();
int rtype;
enum Reason reason;
auto lockScope = needsLocking
? std::unique_lock<std::mutex>(mutex)
: std::unique_lock<std::mutex>();
smp->signals = &signals;
}
/* Run the process() function of the script */
/* First, run the process() function of the script */
if (functions.process) {
logger->debug("Executing Lua function: process(smp)");
lua_pushvalue(L, functions.process);
lua_pushsample(L, smp, useNames);
lua_call(L, 1, 1);
lua_tosample(L, smp, &signals, useNames);
lua_pushvalue(L, functions.process);
lua_pushvalue(L, -2); // Push a copy since lua_pcall() will pop it
int ret = lua_pcall(L, 1, 1, 0);
if (ret)
throw LuaError(L, ret);
rtype = lua_type(L, -1);
if (rtype == LUA_TNUMBER) {
reason = (Reason) lua_tonumber(L, -1);
}
else {
logger->warn("Lua process() did not return a valid number. Assuming Reason::OK");
reason = Reason::OK;
}
lua_pop(L, 1);
lua_tosample(L, smp, &signalsProcessed, useNames);
}
else
reason = Reason::OK;
/* After that evaluate expressions */
if (hasExpressions) {
lua_pushsample(L, smp, useNames);
lua_setglobal(L, "smp");
for (unsigned i = 0; i < expressions.size(); i++) {
auto *sig = (struct signal *) vlist_at(&signalsExpressions, i);
expressions[i].evaluate(&smp->data[i], sig->type);
}
smp->length = expressions.size();
}
return Reason::OK;
return reason;
}
/* Register hook */

View File

@ -1,43 +1,125 @@
function prepare()
print("Preparing test_hook")
-- Install with: luarocks install lunajson luasockets
json = require 'lunajson'
http = require 'socket.http'
------------------------ Constants -------------------------
Reason = {
OK = 0,
ERROR = 1,
SKIP_SAMPLE = 2,
STOP_PROCESSING = 3
}
SampleFlags = {
HAS_TS_ORIGIN = 1, -- "(1 << 1)" Include origin timestamp in output.
HAS_TS_RECEIVED = 2, -- "(1 << 2)" Include receive timestamp in output.
HAS_OFFSET = 4, -- "(1 << 3)" Include offset (received - origin timestamp) in output.
HAS_SEQUENCE = 8, -- "(1 << 4)" Include sequence number in output.
HAS_DATA = 16, -- "(1 << 5)" Include values in output.
HAS_ALL = 15, -- "(1 << 6) -1" Enable all output options.
IS_FIRST = 65536, -- "(1 << 16)" This sample is the first of a new simulation case
IS_LAST = 131072 -- "(1 << 17)" This sample is the last of a running simulation case
}
-------------------------- Config --------------------------
city = 'Aachen'
--------------------- Global Variables ---------------------
f = 1 -- in Hz
t = nil -- in seconds
t_start = nil -- in seconds
temp_aachen = 0 -- in deg Celsius
function prepare(cfg)
info("Preparing test_hook")
info("Some setting: ", cfg.some_setting)
info("Some other setting: ", cfg.this.is.nested)
my_global_var = 1337
end
function start()
print("Starting test_hook")
info("Starting test_hook")
print("Global var: ", my_global_var)
info("Global var: ", my_global_var)
info("This is a message with severity info")
warn("This is a message with severity warn")
error("This is a message with severity error")
debug("This is a message with severity debug")
t_start = socket.gettime()
end
function stop()
print("Stopping test_hook")
end
function process(seq, ts_origin, ts_recv, data, flags)
print("Process test_hook")
out = ""
for key, value in pairs(smp) do
out = out .. " " .. value
end
-- smp[1] = smp[1] * 10
print(out)
return smp
end
function periodic()
print("Periodic test_hook")
info("Stopping test_hook")
end
function restart()
print("Restarted test_hook")
info("Restarted test_hook")
end
function process(smp)
info("Process test_hook")
if smp.sequence == 1 then
dump_sample(smp)
end
-- We can adjust signal values inline
smp.data.square = smp.data.square * 10
-- Example for value limiter
if smp.data.random > 10 then
smp.data.random = 10
elseif smp.data.random < -10 then
smp.data.random = -10
end
global_var = smp.sequence
-- Updating time (references in signal expression list of VILLASnode config)
t = socket.gettime() - t_start
if smp.sequence % 2 == 0 then
return Reason.SKIP_SAMPLE
elseif smp.sequence >= 200 then
return Reason.ERROR
end
return Reason.OK
end
function periodic()
info("Periodic test_hook")
local body, statusCode, headers, statusText = http.request('https://wttr.in/' .. city .. '?format=j1')
weather = json.decode(body)
temp_aachen = tonumber(weather.current_condition[1].temp_C)
info(string.format('Temperature in %s is %d °C', city, temp_aachen))
end
function dump_sample(smp)
info(" Sequence: ", smp.sequence)
info(" Flags: ", smp.flags)
info(" TS origin: ", string.format("%d.%09d", smp.ts_origin[0], smp.ts_origin[1]))
info(" TS received: ", string.format("%d.%09d", smp.ts_received[0], smp.ts_received[1]))
info(" Data:")
for key, value in pairs(smp.data) do
info(" " .. key .. ": " .. value)
end
end