diff options
author | Romain Forlot <romain.forlot@iot.bzh> | 2017-09-10 19:46:02 +0200 |
---|---|---|
committer | Romain Forlot <romain.forlot@iot.bzh> | 2017-12-14 11:00:25 +0100 |
commit | 75cfbf3d0206f12422091e6479c508c69445bf4a (patch) | |
tree | 200085512507e7ee5adc43b377d34ff5953db8de | |
parent | bdf2d19cf8a3d6005456508ca52dba74cd9c103f (diff) |
Controller src integration
Change-Id: I0b18cd55057a784d183a5ba02c332810a34d1fca
Signed-off-by: Romain Forlot <romain.forlot@iot.bzh>
26 files changed, 3073 insertions, 162 deletions
diff --git a/afb-utilities b/afb-utilities -Subproject f352a03a19ebe8a013a96387ec2cd28b98ca069 +Subproject fdf2c27980f756cf63890576d80eef20b78fe98 diff --git a/conf.d/app-templates b/conf.d/app-templates -Subproject e9a682f1c694eec242c3cebccc09380f621ade0 +Subproject b05e4422c52fe1e80c9552f2057c8c817c4d2fe diff --git a/conf.d/cmake/config.cmake b/conf.d/cmake/config.cmake index d57f225..d2f31ae 100644 --- a/conf.d/cmake/config.cmake +++ b/conf.d/cmake/config.cmake @@ -77,7 +77,7 @@ set (PKG_REQUIRED_LIST # Prefix path where will be installed the files # Default: /usr/local (need root permission to write in) # ------------------------------------------------------ -#set(CMAKE_INSTALL_PREFIX $ENV{HOME}/opt) +set(CMAKE_INSTALL_PREFIX $ENV{HOME}/opt) # Customize link option # ----------------------------- @@ -125,6 +125,9 @@ set (PKG_REQUIRED_LIST # -O2 # CACHE STRING "Compilation flags for RELEASE build type.") +add_definitions("-DCONTROL_CONFIG_PRE=\"init\"") +add_definitions("-DCTL_PLUGIN_MAGIC=3286576532") + # (BUG!!!) as PKG_CONFIG_PATH does not work [should be an env variable] # --------------------------------------------------------------------- set(CMAKE_PREFIX_PATH ${CMAKE_INSTALL_PREFIX}/lib64/pkgconfig ${CMAKE_INSTALL_PREFIX}/lib/pkgconfig) diff --git a/conf.d/project/etc/sig-sources.json b/conf.d/project/etc/sig-sources.json index 95de2fa..6051e3e 100644 --- a/conf.d/project/etc/sig-sources.json +++ b/conf.d/project/etc/sig-sources.json @@ -10,50 +10,51 @@ { "api": "low-can", "info": "Low level binding to handle CAN bus communications", - "init": { + "actions": [{ + "label": "init", "function": "c/lua (depend on name) function to initialize binding", "args": { "arg": "first argument" } - }, - "get_signal": { + }, { + "label": "getSignal", "function": "c/lua (depend on name) function to get signals", "args": { "arg": "first argument" } - } - }, - { + }] + }, { "api": "gps", "info": "Low level binding which retrieve Satellite positionning values", - "init": { + "actions": [{ + "label": "init", "function": "c/lua (depend on name) function to initialize binding", "args": { "arg": "first argument" } - }, - "get_signal": { + }, { + "label": "getSignal", "function": "c/lua (depend on name) function to get signals", "args": { "arg": "first argument" } - } - }, - { + }] + }, { "api": "mraa", "info": "Low level binding which retrieve different values from several sensors like gyroscope, accelerometer, etc", - "init": { + "actions": [{ + "label": "init", "function": "c/lua (depend on name) function to initialize binding", "args": { "arg": "first argument" } - }, - "get_signal": { + }, { + "label": "getSignal", "function": "c/lua (depend on name) function to get signals", "args": { "arg": "first argument" } - } + }] } ] } diff --git a/controller/CMakeLists.txt b/controller/CMakeLists.txt new file mode 100644 index 0000000..b3d0c32 --- /dev/null +++ b/controller/CMakeLists.txt @@ -0,0 +1,44 @@ +########################################################################### +# Copyright 2015, 2016, 2017 IoT.bzh +# +# author: Fulup Ar Foll <fulup@iot.bzh> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +########################################################################### + +# Include LUA only when requested +if(CONTROL_SUPPORT_LUA) + message(STATUS "Notice: LUA Controler Support Selected") + set(CTL_LUA_SOURCE ctl-lua.c ctl-timer.c) + ADD_COMPILE_OPTIONS(-DCONTROL_SUPPORT_LUA) +else(CONTROL_SUPPORT_LUA) + message(STATUS "Warning: LUA Without Support ") +endif(CONTROL_SUPPORT_LUA) + + +# Add target to project dependency list +PROJECT_TARGET_ADD(afb-controller) + + # Define project Targets + ADD_LIBRARY(${TARGET_NAME} STATIC ctl-action.c ctl-config.c ctl-onload.c ctl-plugin.c ${CTL_LUA_SOURCE}) + + # Library dependencies (include updates automatically) + TARGET_LINK_LIBRARIES(${TARGET_NAME} + afb-utilities + ) + + # Define target includes for this target client + TARGET_INCLUDE_DIRECTORIES(${TARGET_NAME} + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} + ) + diff --git a/controller/ctl-action.c b/controller/ctl-action.c new file mode 100644 index 0000000..b24269c --- /dev/null +++ b/controller/ctl-action.c @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2016 "IoT.bzh" + * Author Fulup Ar Foll <fulup@iot.bzh> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, something express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Reference: + * Json load using json_unpack https://jansson.readthedocs.io/en/2.9/apiref.html#parsing-and-validating-values + */ + +#define _GNU_SOURCE +#include <stdio.h> +#include <string.h> + +#include "ctl-config.h" + + +PUBLIC int ActionExecOne(CtlActionT* action, json_object *queryJ) { + int err; + + + switch (action->type) { + case CTL_TYPE_API: + { + json_object *returnJ; + + // if query is empty increment usage count and pass args + if (!queryJ || json_object_get_type(queryJ) != json_type_object) { + json_object_get(action->argsJ); + queryJ = action->argsJ; + } else if (action->argsJ) { + + // Merge queryJ and argsJ before sending request + if (json_object_get_type(action->argsJ) == json_type_object) { + + json_object_object_foreach(action->argsJ, key, val) { + json_object_object_add(queryJ, key, val); + } + } else { + json_object_object_add(queryJ, "args", action->argsJ); + } + } + + json_object_object_add(queryJ, "label", json_object_new_string(action->source.label)); + + int err = afb_service_call_sync(action->api, action->call, queryJ, &returnJ); + if (err) { + static const char*format = "ActionExecOne(Api) api=%s verb=%s args=%s"; + AFB_ERROR(format, action->api, action->call, action->source.label); + goto OnErrorExit; + } + break; + } + +#ifdef CONTROL_SUPPORT_LUA + case CTL_TYPE_LUA: + err = LuaCallFunc(action, queryJ); + if (err) { + AFB_ERROR("ActionExecOne(Lua) label=%s func=%s args=%s", action->source.label, action->call, json_object_get_string(action->argsJ)); + goto OnErrorExit; + } + break; +#endif + + case CTL_TYPE_CB: + err = (*action->actionCB) (&action->source, action->argsJ, queryJ); + if (err) { + AFB_ERROR("ActionExecOne(Callback) label%s func=%s args=%s", action->source.label, action->call, json_object_get_string(action->argsJ)); + goto OnErrorExit; + } + break; + + default: + { + AFB_ERROR("ActionExecOne(unknown) API type label=%s", action->source.label); + goto OnErrorExit; + } + } + + return 0; + +OnErrorExit: + return -1; +} + + +// unpack individual action object + +PUBLIC int ActionLoadOne(CtlActionT *action, json_object *actionJ) { + char *api = NULL, *verb = NULL, *lua = NULL; + int err, modeCount = 0; + json_object *callbackJ=NULL; + + err = wrap_json_unpack(actionJ, "{ss,s?s,s?o,s?s,s?s,s?s,s?o !}" + , "label", &action->source.label, "info", &action->source.info, "callback", &callbackJ, "lua", &lua, "api", &api, "verb", &verb, "args", &action->argsJ); + if (err) { + AFB_ERROR("ACTION-LOAD-ONE Missing something label|info|callback|lua|(api+verb)|args in:\n-- %s", json_object_get_string(actionJ)); + goto OnErrorExit; + } + + if (lua) { + action->type = CTL_TYPE_LUA; + action->call = lua; + modeCount++; + } + + if (api && verb) { + action->type = CTL_TYPE_API; + action->api = api; + action->call = verb; + modeCount++; + } + + if (callbackJ) { + action->type = CTL_TYPE_CB; + modeCount++; + err = PluginGetCB (action, callbackJ); + if (err) goto OnErrorExit; + + } + + // make sure at least one mode is selected + if (modeCount == 0) { + AFB_ERROR("ACTION-LOAD-ONE No Action Selected lua|callback|(api+verb) in %s", json_object_get_string(actionJ)); + goto OnErrorExit; + } + + if (modeCount > 1) { + AFB_ERROR("ACTION-LOAD-ONE:ToMany arguments lua|callback|(api+verb) in %s", json_object_get_string(actionJ)); + goto OnErrorExit; + } + return 0; + +OnErrorExit: + return 1; +}; + +PUBLIC CtlActionT *ActionLoad(json_object *actionsJ) { + int err; + CtlActionT *actions; + + // action array is close with a nullvalue; + if (json_object_get_type(actionsJ) == json_type_array) { + int count = json_object_array_length(actionsJ); + actions = calloc(count + 1, sizeof (CtlActionT)); + + for (int idx = 0; idx < count; idx++) { + json_object *actionJ = json_object_array_get_idx(actionsJ, idx); + err = ActionLoadOne(&actions[idx], actionJ); + if (err) goto OnErrorExit; + } + + } else { + actions = calloc(2, sizeof (CtlActionT)); + err = ActionLoadOne(&actions[0], actionsJ); + if (err) goto OnErrorExit; + } + + return actions; + +OnErrorExit: + return NULL; + +} diff --git a/controller/ctl-config.c b/controller/ctl-config.c new file mode 100644 index 0000000..2e3fc40 --- /dev/null +++ b/controller/ctl-config.c @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2016 "IoT.bzh" + * Author Fulup Ar Foll <fulup@iot.bzh> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, something express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Reference: + * Json load using json_unpack https://jansson.readthedocs.io/en/2.9/apiref.html#parsing-and-validating-values + */ + +#define _GNU_SOURCE +#include <stdio.h> +#include <string.h> + +#include "filescan-utils.h" +#include "ctl-config.h" + + +// Load control config file + +PUBLIC char* CtlConfigSearch(const char *dirList) { + int index, err; + char controlFile [CONTROL_MAXPATH_LEN]; + + strncpy(controlFile, CONTROL_CONFIG_PRE "-", CONTROL_MAXPATH_LEN); + strncat(controlFile, GetBinderName(), CONTROL_MAXPATH_LEN); + + // search for default dispatch config file + json_object* responseJ = ScanForConfig(dirList, CTL_SCAN_RECURSIVE, controlFile, ".json"); + + // We load 1st file others are just warnings + for (index = 0; index < json_object_array_length(responseJ); index++) { + json_object *entryJ = json_object_array_get_idx(responseJ, index); + + char *filename; + char*fullpath; + err = wrap_json_unpack(entryJ, "{s:s, s:s !}", "fullpath", &fullpath, "filename", &filename); + if (err) { + AFB_ERROR("CTL-INIT HOOPs invalid JSON entry= %s", json_object_get_string(entryJ)); + return NULL; + } + + if (index == 0) { + if (strcasestr(filename, controlFile)) { + char filepath[CONTROL_MAXPATH_LEN]; + strncpy(filepath, fullpath, sizeof (filepath)); + strncat(filepath, "/", sizeof (filepath)); + strncat(filepath, filename, sizeof (filepath)); + return (strdup(filepath)); + } + } + } + + // no config found + return NULL; +} + +PUBLIC int CtlConfigExec(CtlConfigT *ctlConfig) { + // best effort to initialise everything before starting + if (ctlConfig->requireJ) { + + void DispatchRequireOneApi(json_object * bindindJ) { + const char* requireBinding = json_object_get_string(bindindJ); + int err = afb_daemon_require_api(requireBinding, 1); + if (err) { + AFB_WARNING("CTL-LOAD-CONFIG:REQUIRE Fail to get=%s", requireBinding); + } + } + + if (json_object_get_type(ctlConfig->requireJ) == json_type_array) { + for (int idx = 0; idx < json_object_array_length(ctlConfig->requireJ); idx++) { + DispatchRequireOneApi(json_object_array_get_idx(ctlConfig->requireJ, idx)); + } + } else { + DispatchRequireOneApi(ctlConfig->requireJ); + } + } + +#ifdef CONTROL_SUPPORT_LUA + int err= LuaConfigExec(); + if (err) goto OnErrorExit; +#endif + + // Loop on every section and process config + int errcount=0; + for (int idx = 0; ctlConfig->sections[idx].key != NULL; idx++) { + errcount += ctlConfig->sections[idx].loadCB(&ctlConfig->sections[idx], NULL); + } + return errcount; + +OnErrorExit: + return 1; +} + +PUBLIC CtlConfigT *CtlConfigLoad(const char* filepath, CtlSectionT *sections) { + json_object *ctlConfigJ; + CtlConfigT *ctlConfig = calloc(1, sizeof (CtlConfigT)); + int err; + +#ifdef CONTROL_SUPPORT_LUA + err= LuaConfigLoad(); + if (err) goto OnErrorExit; +#endif + + // Search for config in filepath + filepath = CtlConfigSearch(filepath); + + if (!filepath) { + AFB_ERROR("CTL-LOAD-CONFIG No JSON Config found invalid JSON %s ", filepath); + goto OnErrorExit; + } + + // Load JSON file + ctlConfigJ = json_object_from_file(filepath); + if (!ctlConfigJ) { + AFB_ERROR("CTL-LOAD-CONFIG Not invalid JSON %s ", filepath); + goto OnErrorExit; + } + + AFB_INFO("CTL-LOAD-CONFIG: loading config filepath=%s", filepath); + + json_object *metadataJ; + int done = json_object_object_get_ex(ctlConfigJ, "metadata", &metadataJ); + if (done) { + CtlConfigT *ctlConfig = calloc(1, sizeof (CtlConfigT)); + err = wrap_json_unpack(metadataJ, "{ss,ss,ss,s?s,s?o !}", "label", &ctlConfig->label, "version", &ctlConfig->version + , "api", &ctlConfig->api, "info", &ctlConfig->info, "require", &ctlConfig->requireJ); + if (err) { + AFB_ERROR("CTL-LOAD-CONFIG:METADATA Missing something label|api|version|[info]|[require] in:\n-- %s", json_object_get_string(metadataJ)); + goto OnErrorExit; + } + + // Should replace this with API name change + if (ctlConfig->api) { + err = afb_daemon_rename_api(ctlConfig->api); + if (err) AFB_WARNING("Fail to rename api to:%s", ctlConfig->api); + } + + } + + //load config sections + err = 0; + ctlConfig->sections = sections; + for (int idx = 0; sections[idx].key != NULL; idx++) { + json_object * sectionJ; + int done = json_object_object_get_ex(ctlConfigJ, sections[idx].key, §ionJ); + if (!done) { + AFB_ERROR("CtlConfigLoad: fail to find '%s' section in config '%s'", sections[idx].key, filepath); + err++; + } else { + err += sections[idx].loadCB(§ions[idx], sectionJ); + } + + } + if (err) goto OnErrorExit; + + return (ctlConfig); + +OnErrorExit: + free(ctlConfig); + return NULL; +} + + + + diff --git a/controller/ctl-config.h b/controller/ctl-config.h new file mode 100644 index 0000000..2e3a16a --- /dev/null +++ b/controller/ctl-config.h @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2016 "IoT.bzh" + * Author Fulup Ar Foll <fulup@iot.bzh> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, something express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Reference: + * Json load using json_unpack https://jansson.readthedocs.io/en/2.9/apiref.html#parsing-and-validating-values + */ + +#ifndef _CTL_CONFIG_INCLUDE_ +#define _CTL_CONFIG_INCLUDE_ + +#define _GNU_SOURCE +#define AFB_BINDING_VERSION 2 +#include <afb/afb-binding.h> +#include <json-c/json.h> +#include <filescan-utils.h> +#include <wrap-json.h> + +#include "ctl-plugin.h" + +#ifndef CONTROL_MAXPATH_LEN + #define CONTROL_MAXPATH_LEN 255 +#endif + +#ifndef CONTROL_CONFIG_PRE + #define CONTROL_CONFIG_PRE "onload" +#endif + +#ifndef CTL_PLUGIN_EXT + #define CTL_PLUGIN_EXT ".ctlso" +#endif + + + +typedef enum { + CTL_TYPE_NONE=0, + CTL_TYPE_API, + CTL_TYPE_CB, + CTL_TYPE_LUA, +} CtlActionTypeT; + + +typedef struct { + CtlActionTypeT type; + const char* api; + const char* call; + json_object *argsJ; + int (*actionCB)(CtlSourceT *source, json_object *argsJ, json_object *queryJ); + CtlSourceT source; +} CtlActionT; + +typedef struct { + const char* label; + const char *info; + CtlActionT *actions; +} DispatchHandleT; + +typedef struct ConfigSectionS { + const char *key; + const char *label; + const char *info; + int (*loadCB)(struct ConfigSectionS *section, json_object *sectionJ); + void *handle; +} CtlSectionT; + +typedef struct { + const char* api; + const char* label; + const char *info; + const char *version; + json_object *requireJ; + CtlSectionT *sections; +} CtlConfigT; + + +#ifdef CONTROL_SUPPORT_LUA + #include "ctl-lua.h" +#else + typedef void* Lua2cWrapperT; +#endif + + +// ctl-action.c +PUBLIC CtlActionT *ActionLoad(json_object *actionsJ); +PUBLIC int ActionExecOne(CtlActionT* action, json_object *queryJ); +PUBLIC int ActionLoadOne(CtlActionT *action, json_object *actionJ); + +// ctl-config.c +PUBLIC CtlConfigT *CtlConfigLoad(const char* filepath, CtlSectionT *sections); +PUBLIC int CtlConfigExec(CtlConfigT *ctlConfig); + +// ctl-onload.c +PUBLIC int OnloadConfig(CtlSectionT *section, json_object *actionsJ); + + +// ctl-plugin.c +PUBLIC int PluginConfig(CtlSectionT *section, json_object *pluginsJ); +PUBLIC int PluginGetCB (CtlActionT *action , json_object *callbackJ); + + +#endif /* _CTL_CONFIG_INCLUDE_ */
\ No newline at end of file diff --git a/controller/ctl-lua.c b/controller/ctl-lua.c new file mode 100644 index 0000000..7b68fe2 --- /dev/null +++ b/controller/ctl-lua.c @@ -0,0 +1,1081 @@ +/* + * Copyright (C) 2016 "IoT.bzh" + * Author Fulup Ar Foll <fulup@iot.bzh> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ref: + * (manual) https://www.lua.org/manual/5.3/manual.html + * (lua->C) http://www.troubleshooters.com/codecorn/lua/lua_c_calls_lua.htm#_Anatomy_of_a_Lua_Call + * (lua/C Var) http://acamara.es/blog/2012/08/passing-variables-from-lua-5-2-to-c-and-vice-versa/ + * (Lua/C Lib)https://john.nachtimwald.com/2014/07/12/wrapping-a-c-library-in-lua/ + * (Lua/C Table) https://gist.github.com/SONIC3D/10388137 + * (Lua/C Nested table) https://stackoverflow.com/questions/45699144/lua-nested-table-from-lua-to-c + * (Lua/C Wrapper) https://stackoverflow.com/questions/45699950/lua-passing-handle-to-function-created-with-newlib + * + */ + +#define _GNU_SOURCE +#include <stdio.h> +#include <string.h> + +#include "ctl-config.h" + +#define LUA_FIST_ARG 2 // when using luaL_newlib calllback receive libtable as 1st arg +#define LUA_MSG_MAX_LENGTH 512 +#define JSON_ERROR (json_object*)-1 +static afb_req NULL_AFBREQ = {}; + + +static lua_State* luaState; + +#define CTX_MAGIC 123456789 +#define CTX_TOKEN "AFB_ctx" + +typedef struct { + char *name; + int count; + afb_event event; +} LuaAfbEvent; +static LuaAfbEvent *luaDefaultEvt; + +typedef struct { + int ctxMagic; + afb_req request; + void *handle; + char *info; +} LuaAfbContextT; + +typedef struct { + const char *callback; + json_object *context; + void *handle; +} LuaCallServiceT; + +typedef enum { + AFB_MSG_INFO, + AFB_MSG_WARNING, + AFB_MSG_NOTICE, + AFB_MSG_DEBUG, + AFB_MSG_ERROR, +} LuaAfbMessageT; + +/* + * Note(Fulup): I fail to use luaL_setmetatable and replaced it with a simple opaque + * handle while waiting for someone smarter than me to find a better solution + * https://stackoverflow.com/questions/45596493/lua-using-lua-newuserdata-from-lua-pcall + */ + +STATIC LuaAfbContextT *LuaCtxCheck (lua_State *luaState, int index) { + LuaAfbContextT *afbContext; + //luaL_checktype(luaState, index, LUA_TUSERDATA); + //afbContext = (LuaAfbContextT *)luaL_checkudata(luaState, index, CTX_TOKEN); + luaL_checktype(luaState, index, LUA_TLIGHTUSERDATA); + afbContext = (LuaAfbContextT *) lua_touserdata(luaState, index); + if (afbContext == NULL && afbContext->ctxMagic != CTX_MAGIC) { + luaL_error(luaState, "Fail to retrieve user data context=%s", CTX_TOKEN); + AFB_ERROR ("afbContextCheck error retrieving afbContext"); + return NULL; + } + return afbContext; +} + +STATIC LuaAfbContextT *LuaCtxPush (lua_State *luaState, afb_req request, void *handle, const char* info) { + // LuaAfbContextT *afbContext = (LuaAfbContextT *)lua_newuserdata(luaState, sizeof(LuaAfbContextT)); + // luaL_setmetatable(luaState, CTX_TOKEN); + LuaAfbContextT *afbContext = (LuaAfbContextT *)calloc(1, sizeof(LuaAfbContextT)); + lua_pushlightuserdata(luaState, afbContext); + if (!afbContext) { + AFB_ERROR ("LuaCtxPush fail to allocate user data context"); + return NULL; + } + afbContext->ctxMagic=CTX_MAGIC; + afbContext->info=strdup(info); + afbContext->request= request; + afbContext->handle= handle; + return afbContext; +} + +STATIC void LuaCtxFree (LuaAfbContextT *afbContext) { + free(afbContext->info); + free(afbContext); +} + +// Push a json structure on the stack as a LUA table +STATIC int LuaPushArgument (json_object *argsJ) { + + //AFB_NOTICE("LuaPushArgument argsJ=%s", json_object_get_string(argsJ)); + + json_type jtype= json_object_get_type(argsJ); + switch (jtype) { + case json_type_object: { + lua_newtable (luaState); + json_object_object_foreach (argsJ, key, val) { + int done = LuaPushArgument (val); + if (done) { + lua_setfield(luaState,-2, key); + } + } + break; + } + case json_type_array: { + int length= json_object_array_length(argsJ); + lua_newtable (luaState); + for (int idx=0; idx < length; idx ++) { + json_object *val=json_object_array_get_idx(argsJ, idx); + LuaPushArgument (val); + lua_seti (luaState,-2, idx); + } + break; + } + case json_type_int: + lua_pushinteger(luaState, json_object_get_int(argsJ)); + break; + case json_type_string: + lua_pushstring(luaState, json_object_get_string(argsJ)); + break; + case json_type_boolean: + lua_pushboolean(luaState, json_object_get_boolean(argsJ)); + break; + case json_type_double: + lua_pushnumber(luaState, json_object_get_double(argsJ)); + break; + case json_type_null: + AFB_WARNING("LuaPushArgument: NULL object type %s", json_object_get_string(argsJ)); + return 0; + break; + + default: + AFB_ERROR("LuaPushArgument: unsupported Json object type %s", json_object_get_string(argsJ)); + return 0; + } + return 1; +} + +STATIC json_object *LuaPopOneArg (lua_State* luaState, int idx); + +// Move a table from Internal Lua representation to Json one +// Numeric table are transformed in json array, string one in object +// Mix numeric/string key are not supported +STATIC json_object *LuaTableToJson (lua_State* luaState, int index) { + #define LUA_KEY_INDEX -2 + #define LUA_VALUE_INDEX -1 + + int idx; + int tableType; + json_object *tableJ= NULL; + + lua_pushnil(luaState); // 1st key + if (index < 0) index--; + for (idx=1; lua_next(luaState, index) != 0; idx++) { + + // uses 'key' (at index -2) and 'value' (at index -1) + if (lua_type(luaState,LUA_KEY_INDEX) == LUA_TSTRING) { + + if (!tableJ) { + tableJ= json_object_new_object(); + tableType=LUA_TSTRING; + } else if (tableType != LUA_TSTRING){ + AFB_ERROR("MIX Lua Table with key string/numeric not supported"); + return NULL; + } + + const char *key= lua_tostring(luaState, LUA_KEY_INDEX); + json_object *argJ= LuaPopOneArg(luaState, LUA_VALUE_INDEX); + json_object_object_add(tableJ, key, argJ); + + } else { + if (!tableJ) { + tableJ= json_object_new_array(); + tableType=LUA_TNUMBER; + } else if(tableType != LUA_TNUMBER) { + AFB_ERROR("MIX Lua Table with key numeric/string not supported"); + return NULL; + } + + json_object *argJ= LuaPopOneArg(luaState, LUA_VALUE_INDEX); + json_object_array_add(tableJ, argJ); + } + + + lua_pop(luaState, 1); // removes 'value'; keeps 'key' for next iteration + } + + // Query is empty free empty json object + if (idx == 1) { + json_object_put(tableJ); + return NULL; + } + return tableJ; +} + +STATIC json_object *LuaPopOneArg (lua_State* luaState, int idx) { + json_object *value=NULL; + + int luaType = lua_type(luaState, idx); + switch(luaType) { + case LUA_TNUMBER: { + lua_Number number= lua_tonumber(luaState, idx);; + int nombre = (int)number; // evil trick to determine wether n fits in an integer. (stolen from ltcl.c) + if (number == nombre) { + value= json_object_new_int((int)number); + } else { + value= json_object_new_double(number); + } + break; + } + case LUA_TBOOLEAN: + value= json_object_new_boolean(lua_toboolean(luaState, idx)); + break; + case LUA_TSTRING: + value= json_object_new_string(lua_tostring(luaState, idx)); + break; + case LUA_TTABLE: + value= LuaTableToJson(luaState, idx); + break; + case LUA_TNIL: + value=json_object_new_string("nil") ; + break; + case LUA_TUSERDATA: + value=json_object_new_int64((int64_t)lua_touserdata(luaState, idx)); // store userdata as int !!! + break; + + default: + AFB_NOTICE ("LuaPopOneArg: script returned Unknown/Unsupported idx=%d type:%d/%s", idx, luaType, lua_typename(luaState, luaType)); + value=NULL; + } + + return value; +} + +static json_object *LuaPopArgs (lua_State* luaState, int start) { + json_object *responseJ; + + int stop = lua_gettop(luaState); + if(stop-start <0) return NULL; + + // start at 2 because we are using a function array lib + if (start == stop) { + responseJ=LuaPopOneArg (luaState, start); + } else { + // loop on remaining return arguments + responseJ= json_object_new_array(); + for (int idx=start; idx <= stop; idx++) { + json_object *argJ=LuaPopOneArg (luaState, idx); + if (!argJ) goto OnErrorExit; + json_object_array_add(responseJ, argJ); + } + } + + return responseJ; + + OnErrorExit: + return NULL; +} + + +STATIC int LuaFormatMessage(lua_State* luaState, LuaAfbMessageT action) { + char *message; + + json_object *responseJ= LuaPopArgs(luaState, LUA_FIST_ARG); + + if (!responseJ) { + luaL_error(luaState,"LuaFormatMessage empty message"); + goto OnErrorExit; + } + + // if we have only on argument just return the value. + if (json_object_get_type(responseJ)!=json_type_array || json_object_array_length(responseJ) <2) { + message= (char*)json_object_get_string(responseJ); + goto PrintMessage; + } + + // extract format and push all other parameter on the stack + message = alloca (LUA_MSG_MAX_LENGTH); + const char *format = json_object_get_string(json_object_array_get_idx(responseJ, 0)); + + int arrayIdx=1; + int targetIdx=0; + + for (int idx=0; format[idx] !='\0'; idx++) { + + if (format[idx]=='%' && format[idx] !='\0') { + json_object *slotJ= json_object_array_get_idx(responseJ, arrayIdx); + //if (slotJ) AFB_NOTICE("**** idx=%d slotJ=%s", arrayIdx, json_object_get_string(slotJ)); + + + switch (format[++idx]) { + case 'd': + if (slotJ) targetIdx += snprintf (&message[targetIdx], LUA_MSG_MAX_LENGTH-targetIdx,"%d", json_object_get_int(slotJ)); + else targetIdx += snprintf (&message[targetIdx], LUA_MSG_MAX_LENGTH-targetIdx,"nil"); + arrayIdx++; + break; + case 'f': + if (slotJ) targetIdx += snprintf (&message[targetIdx], LUA_MSG_MAX_LENGTH-targetIdx,"%f", json_object_get_double(slotJ)); + else targetIdx += snprintf (&message[targetIdx], LUA_MSG_MAX_LENGTH-targetIdx,"nil"); + arrayIdx++; + break; + + case'%': + message[targetIdx]='%'; + targetIdx++; + break; + + case 's': + default: + if (slotJ) targetIdx += snprintf (&message[targetIdx], LUA_MSG_MAX_LENGTH-targetIdx,"%s", json_object_get_string(slotJ)); + else targetIdx += snprintf (&message[targetIdx], LUA_MSG_MAX_LENGTH-targetIdx,"nil"); + arrayIdx++; + } + + } else { + if (targetIdx >= LUA_MSG_MAX_LENGTH) { + AFB_WARNING ("LuaFormatMessage: message[%s] owerverflow LUA_MSG_MAX_LENGTH=%d", format, LUA_MSG_MAX_LENGTH); + targetIdx --; // move backward for EOL + break; + } else { + message[targetIdx++] = format[idx]; + } + } + } + message[targetIdx]='\0'; + +PrintMessage: + switch (action) { + case AFB_MSG_WARNING: + AFB_WARNING ("%s", message); + break; + case AFB_MSG_NOTICE: + AFB_NOTICE ("%s", message); + break; + case AFB_MSG_DEBUG: + AFB_DEBUG ("%s", message); + break; + case AFB_MSG_INFO: + AFB_INFO ("%s", message); + break; + case AFB_MSG_ERROR: + default: + AFB_ERROR ("%s", message); + } + return 0; // nothing return to lua + + OnErrorExit: // on argument to return (the error message) + return 1; +} + +STATIC int LuaPrintInfo(lua_State* luaState) { + int err=LuaFormatMessage (luaState, AFB_MSG_INFO); + return err; +} + +STATIC int LuaPrintError(lua_State* luaState) { + int err=LuaFormatMessage (luaState, AFB_MSG_ERROR); + return err; // no value return +} + +STATIC int LuaPrintWarning(lua_State* luaState) { + int err=LuaFormatMessage (luaState, AFB_MSG_WARNING); + return err; +} + +STATIC int LuaPrintNotice(lua_State* luaState) { + int err=LuaFormatMessage (luaState, AFB_MSG_NOTICE); + return err; +} + +STATIC int LuaPrintDebug(lua_State* luaState) { + int err=LuaFormatMessage (luaState, AFB_MSG_DEBUG); + return err; +} + +STATIC int LuaAfbSuccess(lua_State* luaState) { + LuaAfbContextT *afbContext= LuaCtxCheck(luaState, LUA_FIST_ARG); + if (!afbContext) goto OnErrorExit; + + // ignore context argument + json_object *responseJ= LuaPopArgs(luaState, LUA_FIST_ARG+1); + if (responseJ == JSON_ERROR) return 1; + + afb_req_success(afbContext->request, responseJ, NULL); + + LuaCtxFree(afbContext); + return 0; + + OnErrorExit: + lua_error(luaState); + return 1; +} + +STATIC int LuaAfbFail(lua_State* luaState) { + LuaAfbContextT *afbContext= LuaCtxCheck(luaState, LUA_FIST_ARG); + if (!afbContext) goto OnErrorExit; + + json_object *responseJ= LuaPopArgs(luaState, LUA_FIST_ARG+1); + if (responseJ == JSON_ERROR) return 1; + + afb_req_fail(afbContext->request, afbContext->info, json_object_get_string(responseJ)); + + LuaCtxFree(afbContext); + return 0; + + OnErrorExit: + lua_error(luaState); + return 1; +} + +STATIC void LuaAfbServiceCB(void *handle, int iserror, struct json_object *responseJ) { + LuaCallServiceT *contextCB= (LuaCallServiceT*)handle; + int count=1; + + lua_getglobal(luaState, contextCB->callback); + + // push error status & response + lua_pushboolean(luaState, iserror); + count+= LuaPushArgument(responseJ); + count+= LuaPushArgument(contextCB->context); + + int err=lua_pcall(luaState, count, LUA_MULTRET, 0); + if (err) { + AFB_ERROR ("LUA-SERICE-CB:FAIL response=%s err=%s", json_object_get_string(responseJ), lua_tostring(luaState,-1) ); + } + + free (contextCB); +} + + +STATIC int LuaAfbService(lua_State* luaState) { + int count = lua_gettop(luaState); + + // note: argument start at 2 because of AFB: table + if (count <5 || !lua_isstring(luaState, 2) || !lua_isstring(luaState, 3) || !lua_istable(luaState, 4) || !lua_isstring(luaState, 5)) { + lua_pushliteral (luaState, "ERROR: syntax AFB:service(api, verb, {[Lua Table]})"); + goto OnErrorExit; + } + + // get api/verb+query + const char *api = lua_tostring(luaState,2); + const char *verb= lua_tostring(luaState,3); + json_object *queryJ= LuaTableToJson(luaState, 4); + if (queryJ == JSON_ERROR) return 1; + + LuaCallServiceT *contextCB = calloc (1, sizeof(LuaCallServiceT)); + contextCB->callback= lua_tostring(luaState, 5); + contextCB->context = LuaPopArgs(luaState, 6); + + afb_service_call(api, verb, queryJ, LuaAfbServiceCB, contextCB); + + return 0; // no value return + + OnErrorExit: + lua_error(luaState); + return 1; +} + +STATIC int LuaAfbServiceSync(lua_State* luaState) { + int count = lua_gettop(luaState); + json_object *responseJ; + + // note: argument start at 2 because of AFB: table + if (count <3 || !lua_isstring(luaState, 2) || !lua_isstring(luaState, 3) || !lua_istable(luaState, 4)) { + lua_pushliteral (luaState, "ERROR: syntax AFB:servsync(api, verb, {[Lua Table]})"); + goto OnErrorExit; + } + + // get api/verb+query + const char *api = lua_tostring(luaState,2); + const char *verb= lua_tostring(luaState,3); + json_object *queryJ= LuaTableToJson(luaState, 4); + + int iserror=afb_service_call_sync (api, verb, queryJ, &responseJ); + + // push error status & response + count=1; lua_pushboolean(luaState, iserror); + count+= LuaPushArgument(responseJ); + + return count; // return count values + + OnErrorExit: + lua_error(luaState); + return 1; +} + +STATIC int LuaAfbEventPush(lua_State* luaState) { + LuaAfbEvent *afbevt; + int index; + + // if no private event handle then use default binding event + if (lua_islightuserdata(luaState, LUA_FIST_ARG)) { + afbevt = (LuaAfbEvent*) lua_touserdata(luaState, LUA_FIST_ARG); + index=LUA_FIST_ARG+1; + } else { + index=LUA_FIST_ARG; + afbevt=luaDefaultEvt; + } + + if (!afb_event_is_valid(afbevt->event)) { + lua_pushliteral (luaState, "LuaAfbMakePush-Fail invalid event"); + goto OnErrorExit; + } + + json_object *ctlEventJ= LuaTableToJson(luaState, index); + if (!ctlEventJ) { + lua_pushliteral (luaState, "LuaAfbEventPush-Syntax is AFB:signal ([evtHandle], {lua table})"); + goto OnErrorExit; + } + + int done = afb_event_push(afbevt->event, ctlEventJ); + if (!done) { + lua_pushliteral (luaState, "LuaAfbEventPush-Fail No Subscriber to event"); + AFB_ERROR ("LuaAfbEventPush-Fail name subscriber event=%s count=%d", afbevt->name, afbevt->count); + goto OnErrorExit; + } + afbevt->count++; + return 0; + + OnErrorExit: + lua_error(luaState); + return 1; +} + +STATIC int LuaAfbEventSubscribe(lua_State* luaState) { + LuaAfbEvent *afbevt; + + LuaAfbContextT *afbContext= LuaCtxCheck(luaState, LUA_FIST_ARG); + if (!afbContext) { + lua_pushliteral (luaState, "LuaAfbEventSubscribe-Fail Invalid request handle"); + goto OnErrorExit; + } + + // if no private event handle then use default binding event + if (lua_islightuserdata(luaState, LUA_FIST_ARG+1)) { + afbevt = (LuaAfbEvent*) lua_touserdata(luaState, LUA_FIST_ARG+1); + } else { + afbevt=luaDefaultEvt; + } + + if (!afb_event_is_valid(afbevt->event)) { + lua_pushliteral (luaState, "LuaAfbMakePush-Fail invalid event handle"); + goto OnErrorExit; + } + + int err = afb_req_subscribe(afbContext->request, afbevt->event); + if (err) { + lua_pushliteral (luaState, "LuaAfbEventSubscribe-Fail No Subscriber to event"); + AFB_ERROR ("LuaAfbEventPush-Fail name subscriber event=%s count=%d", afbevt->name, afbevt->count); + goto OnErrorExit; + } + afbevt->count++; + return 0; + + OnErrorExit: + lua_error(luaState); + return 1; +} + +STATIC int LuaAfbEventMake(lua_State* luaState) { + int count = lua_gettop(luaState); + LuaAfbEvent *afbevt=calloc(1,sizeof(LuaAfbEvent)); + + if (count != LUA_FIST_ARG || !lua_isstring(luaState, LUA_FIST_ARG)) { + lua_pushliteral (luaState, "LuaAfbEventMake-Syntax is evtHandle= AFB:event ('myEventName')"); + goto OnErrorExit; + } + + // event name should be the only argument + afbevt->name= strdup (lua_tostring(luaState,LUA_FIST_ARG)); + + // create a new binder event + afbevt->event = afb_daemon_make_event(afbevt->name); + if (!afb_event_is_valid(afbevt->event)) { + lua_pushliteral (luaState, "LuaAfbEventMake-Fail to Create Binder event"); + goto OnErrorExit; + } + + // push event handler as a LUA opaque handle + lua_pushlightuserdata(luaState, afbevt); + return 1; + + OnErrorExit: + lua_error(luaState); + return 1; +} + +// Function call from LUA when lua2c plugin L2C is used +PUBLIC int Lua2cWrapper(lua_State* luaState, char *funcname, Lua2cFunctionT callback) { + + json_object *argsJ= LuaPopArgs(luaState, LUA_FIST_ARG+1); + int response = (*callback) (funcname, argsJ); + + // push response to LUA + lua_pushinteger(luaState, response); + return 1; +} + +// Call a Lua function from a control action +PUBLIC int LuaCallFunc (CtlActionT *action, json_object *queryJ) { + + int err, count; + + json_object* argsJ = action->argsJ; + const char* func = action->call; + + // load function (should exist in CONTROL_PATH_LUA + lua_getglobal(luaState, func); + + // push source on the stack + count=1; + lua_pushstring(luaState, action->source.label); + + // push argsJ on the stack + if (!argsJ) { + lua_pushnil(luaState); + count++; + } else { + count+= LuaPushArgument (argsJ); + } + + // push queryJ on the stack + if (!queryJ) { + lua_pushnil(luaState); + count++; + } else { + count+= LuaPushArgument (queryJ); + } + + // effectively exec LUA script code + err=lua_pcall(luaState, count, 1, 0); + if (err) { + AFB_ERROR("LuaCallFunc Fail calling %s error=%s", func, lua_tostring(luaState,-1)); + goto OnErrorExit; + } + + // return LUA script value + int rc= (int)lua_tointeger(luaState, -1); + return rc; + + OnErrorExit: + return -1; +} + + +// Execute LUA code from received API request +STATIC void LuaDoAction (LuaDoActionT action, afb_req request) { + + int err, count=0; + + json_object* queryJ = afb_req_json(request); + + switch (action) { + + case LUA_DOSTRING: { + const char *script = json_object_get_string(queryJ); + err=luaL_loadstring(luaState, script); + if (err) { + AFB_ERROR ("LUA-DO-COMPILE:FAIL String=%s err=%s", script, lua_tostring(luaState,-1) ); + goto OnErrorExit; + } + // Push AFB client context on the stack + LuaAfbContextT *afbContext= LuaCtxPush(luaState, request,NULL,script); + if (!afbContext) goto OnErrorExit; + + break; + } + + case LUA_DOCALL: { + const char *func; + json_object *argsJ=NULL; + + err= wrap_json_unpack (queryJ, "{s:s, s?o !}", "target", &func, "args", &argsJ); + if (err) { + AFB_ERROR ("LUA-DOCALL-SYNTAX missing target|args query=%s", json_object_get_string(queryJ)); + goto OnErrorExit; + } + + // load function (should exist in CONTROL_PATH_LUA + lua_getglobal(luaState, func); + + // Push AFB client context on the stack + LuaAfbContextT *afbContext= LuaCtxPush(luaState, request, NULL, func); + if (!afbContext) goto OnErrorExit; + + // push query on the stack + if (!argsJ) { + lua_pushnil(luaState); + count++; + } else { + count+= LuaPushArgument (argsJ); + } + + break; + } + + case LUA_DOSCRIPT: { // Fulup need to fix argument passing + char *filename; char*fullpath; + char luaScriptPath[CONTROL_MAXPATH_LEN]; + int index; + + // scan luascript search path once + static json_object *luaScriptPathJ =NULL; + + // extract value from query + const char *target=NULL,*func=NULL; + json_object *argsJ=NULL; + err= wrap_json_unpack (queryJ, "{s:s,s?s,s?s,s?o !}","target", &target,"path",&luaScriptPathJ,"function",&func,"args",&argsJ); + if (err) { + AFB_ERROR ("LUA-DOSCRIPT-SYNTAX:missing target|[path]|[function]|[args] query=%s", json_object_get_string(queryJ)); + goto OnErrorExit; + } + + // search for filename=script in CONTROL_LUA_PATH + if (!luaScriptPathJ) { + strncpy(luaScriptPath,CONTROL_DOSCRIPT_PRE, sizeof(luaScriptPath)); + strncat(luaScriptPath,"-", sizeof(luaScriptPath)); + strncat(luaScriptPath,target, sizeof(luaScriptPath)); + luaScriptPathJ= ScanForConfig(CONTROL_LUA_PATH , CTL_SCAN_RECURSIVE,luaScriptPath,".lua"); + } + for (index=0; index < json_object_array_length(luaScriptPathJ); index++) { + json_object *entryJ=json_object_array_get_idx(luaScriptPathJ, index); + + err= wrap_json_unpack (entryJ, "{s:s, s:s !}", "fullpath", &fullpath,"filename", &filename); + if (err) { + AFB_ERROR ("LUA-DOSCRIPT-SCAN:HOOPs invalid config file path = %s", json_object_get_string(entryJ)); + goto OnErrorExit; + } + + if (index > 0) AFB_WARNING("LUA-DOSCRIPT-SCAN:Ignore second script=%s path=%s", filename, fullpath); + else { + strncpy (luaScriptPath, fullpath, sizeof(luaScriptPath)); + strncat (luaScriptPath, "/", sizeof(luaScriptPath)); + strncat (luaScriptPath, filename, sizeof(luaScriptPath)); + } + } + + err= luaL_loadfile(luaState, luaScriptPath); + if (err) { + AFB_ERROR ("LUA-DOSCRIPT HOOPs Error in LUA loading scripts=%s err=%s", luaScriptPath, lua_tostring(luaState,-1)); + goto OnErrorExit; + } + + // script was loaded we need to parse to make it executable + err=lua_pcall(luaState, 0, 0, 0); + if (err) { + AFB_ERROR ("LUA-DOSCRIPT:FAIL to load %s", luaScriptPath); + goto OnErrorExit; + } + + // if no func name given try to deduct from filename + if (!func && (func=(char*)GetMidleName(filename))!=NULL) { + strncpy(luaScriptPath,"_", sizeof(luaScriptPath)); + strncat(luaScriptPath,func, sizeof(luaScriptPath)); + func=luaScriptPath; + } + if (!func) { + AFB_ERROR ("LUA-DOSCRIPT:FAIL to deduct funcname from %s", filename); + goto OnErrorExit; + } + + // load function (should exist in CONTROL_PATH_LUA + lua_getglobal(luaState, func); + + // Push AFB client context on the stack + LuaAfbContextT *afbContext= LuaCtxPush(luaState, request, NULL, func); + if (!afbContext) goto OnErrorExit; + + // push function arguments + if (!argsJ) { + lua_pushnil(luaState); + count++; + } else { + count+= LuaPushArgument(argsJ); + } + + break; + } + + default: + AFB_ERROR ("LUA-DOSCRIPT-ACTION unknown query=%s", json_object_get_string(queryJ)); + goto OnErrorExit; + } + + // effectively exec LUA code (afb_reply/fail done later from callback) + err=lua_pcall(luaState, count+1, 0, 0); + if (err) { + AFB_ERROR ("LUA-DO-EXEC:FAIL query=%s err=%s", json_object_get_string(queryJ), lua_tostring(luaState,-1)); + goto OnErrorExit; + } + return; + + OnErrorExit: + afb_req_fail(request,"LUA:ERROR", lua_tostring(luaState,-1)); + return; +} + +PUBLIC void ctlapi_execlua (afb_req request) { + LuaDoAction (LUA_DOSTRING, request); +} + +PUBLIC void ctlapi_request (afb_req request) { + LuaDoAction (LUA_DOCALL, request); +} + +PUBLIC void ctlapi_debuglua (afb_req request) { + LuaDoAction (LUA_DOSCRIPT, request); +} + +STATIC int LuaTimerClear (lua_State* luaState) { + + // Get Timer Handle + LuaAfbContextT *afbContext= LuaCtxCheck(luaState, LUA_FIST_ARG); + if (!afbContext) goto OnErrorExit; + + // retrieve useful information opaque handle + TimerHandleT *timerHandle = (TimerHandleT*)afbContext->handle; + + AFB_NOTICE ("LuaTimerClear timer=%s", timerHandle->label); + TimerEvtStop(timerHandle); + + return 0; //happy end + +OnErrorExit: + return 1; +} +STATIC int LuaTimerGet (lua_State* luaState) { + + // Get Timer Handle + LuaAfbContextT *afbContext= LuaCtxCheck(luaState, LUA_FIST_ARG); + if (!afbContext) goto OnErrorExit; + + // retrieve useful information opaque handle + TimerHandleT *timerHandle = (TimerHandleT*)afbContext->handle; + + // create response as a JSON object + json_object *responseJ= json_object_new_object(); + json_object_object_add(responseJ,"label", json_object_new_string(timerHandle->label)); + json_object_object_add(responseJ,"delay", json_object_new_int(timerHandle->delay)); + json_object_object_add(responseJ,"count", json_object_new_int(timerHandle->count)); + + // return JSON object as Lua table + int count=LuaPushArgument(responseJ); + + // free json object + json_object_put(responseJ); + + return count; // return argument + +OnErrorExit: + return 0; +} + +// Timer Callback + +// Set timer +STATIC int LuaTimerSetCB (void *handle) { + LuaCallServiceT *contextCB =(LuaCallServiceT*) handle; + TimerHandleT *timerHandle = (TimerHandleT*) contextCB->handle; + int count; + + // push timer handle and user context on Lua stack + lua_getglobal(luaState, contextCB->callback); + + // Push timer handle + LuaAfbContextT *afbContext= LuaCtxPush(luaState, NULL_AFBREQ, contextCB->handle, timerHandle->label); + if (!afbContext) goto OnErrorExit; + count=1; + + // Push user Context + count+= LuaPushArgument(contextCB->context); + + int err=lua_pcall(luaState, count, LUA_MULTRET, 0); + if (err) { + AFB_ERROR ("LUA-TIMER-CB:FAIL response=%s err=%s", json_object_get_string(contextCB->context), lua_tostring(luaState,-1)); + goto OnErrorExit; + } + + // get return parameter + if (!lua_isboolean(luaState, -1)) { + return (lua_toboolean(luaState, -1)); + } + + // timer last run free context resource + if (timerHandle->count == 1) { + LuaCtxFree(afbContext); + } + return 0; // By default we are happy + + OnErrorExit: + return 1; // stop timer +} + +STATIC int LuaTimerSet(lua_State* luaState) { + const char *label=NULL, *info=NULL; + int delay=0, count=0; + + json_object *timerJ = LuaPopOneArg(luaState, LUA_FIST_ARG); + const char *callback = lua_tostring(luaState, LUA_FIST_ARG + 1); + json_object *contextJ = LuaPopOneArg(luaState, LUA_FIST_ARG + 2); + + if (lua_gettop(luaState) != LUA_FIST_ARG+2 || !timerJ || !callback || !contextJ) { + lua_pushliteral(luaState, "LuaTimerSet-Syntax timerset (timerT, 'callback', contextT)"); + goto OnErrorExit; + } + + int err = wrap_json_unpack(timerJ, "{ss, s?s si, si !}", "label", &label, "info", &info, "delay", &delay, "count", &count); + if (err) { + lua_pushliteral(luaState, "LuaTimerSet-Syntax timerT={label:xxx delay:ms, count:xx}"); + goto OnErrorExit; + } + + // everything look fine create timer structure + TimerHandleT *timerHandle = malloc (sizeof (TimerHandleT)); + timerHandle->delay=delay; + timerHandle->count=count; + timerHandle->label=label; + + // Allocate handle to store context and callback + LuaCallServiceT *contextCB = calloc (1, sizeof(LuaCallServiceT)); + contextCB->callback= callback; + contextCB->context = contextJ; + contextCB->handle = timerHandle; + + // fire timer + TimerEvtStart (timerHandle, LuaTimerSetCB, contextCB); + + return 0; // Happy No Return Function + +OnErrorExit: + lua_error(luaState); + return 1; // return error code +} + +// Register a new L2c list of LUA user plugin commands +PUBLIC void LuaL2cNewLib(const char *label, luaL_Reg *l2cFunc, int count) { + // luaL_newlib(luaState, l2cFunc); macro does not work with pointer :( + luaL_checkversion(luaState); + lua_createtable(luaState, 0, count+1); + luaL_setfuncs(luaState,l2cFunc,0); + lua_setglobal(luaState, label); +} + +static const luaL_Reg afbFunction[] = { + {"timerclear", LuaTimerClear}, + {"timerget" , LuaTimerGet}, + {"timerset" , LuaTimerSet}, + {"notice" , LuaPrintNotice}, + {"info" , LuaPrintInfo}, + {"warning" , LuaPrintWarning}, + {"debug" , LuaPrintDebug}, + {"error" , LuaPrintError}, + {"servsync" , LuaAfbServiceSync}, + {"service" , LuaAfbService}, + {"success" , LuaAfbSuccess}, + {"fail" , LuaAfbFail}, + {"subscribe" , LuaAfbEventSubscribe}, + {"evtmake" , LuaAfbEventMake}, + {"evtpush" , LuaAfbEventPush}, + + {NULL, NULL} /* sentinel */ +}; + +// Load Lua Interpreter +PUBLIC int LuaConfigLoad () { + + + // open a new LUA interpretor + luaState = luaL_newstate(); + if (!luaState) { + AFB_ERROR ("LUA_INIT: Fail to open lua interpretor"); + goto OnErrorExit; + } + + // load auxiliary libraries + luaL_openlibs(luaState); + + // redirect print to AFB_NOTICE + luaL_newlib(luaState, afbFunction); + lua_setglobal(luaState, "AFB"); + + return 0; + + OnErrorExit: + return 1; +} + +// Create Binding Event at Init Exec Time +PUBLIC int LuaConfigExec () { + + int err, index; + // create default lua event to send test pause/resume + luaDefaultEvt=calloc(1,sizeof(LuaAfbEvent)); + luaDefaultEvt->name=CONTROL_LUA_EVENT; + luaDefaultEvt->event = afb_daemon_make_event(CONTROL_LUA_EVENT); + if (!afb_event_is_valid(luaDefaultEvt->event)) { + AFB_ERROR ("POLCTL_INIT: Cannot register lua-events=%s ", CONTROL_LUA_EVENT); + goto OnErrorExit;; + } + + // search for default policy config file + char fullprefix[CONTROL_MAXPATH_LEN]; + strncpy (fullprefix, CONTROL_CONFIG_PRE "-", sizeof(fullprefix)); + strncat (fullprefix, GetBinderName(), sizeof(fullprefix)); + strncat (fullprefix, "-", sizeof(fullprefix)); + + const char *dirList= getenv("CONTROL_LUA_PATH"); + if (!dirList) dirList=CONTROL_LUA_PATH; + + // special case for no lua even when avaliable + if (!strcasecmp ("/dev/null", dirList)) { + return 0; + } + + json_object *luaScriptPathJ = ScanForConfig(dirList , CTL_SCAN_RECURSIVE, fullprefix, "lua"); + + // load+exec any file found in LUA search path + for (index=0; index < json_object_array_length(luaScriptPathJ); index++) { + json_object *entryJ=json_object_array_get_idx(luaScriptPathJ, index); + + char *filename; char*fullpath; + err= wrap_json_unpack (entryJ, "{s:s, s:s !}", "fullpath", &fullpath,"filename", &filename); + if (err) { + AFB_ERROR ("LUA-INIT HOOPs invalid config file path = %s", json_object_get_string(entryJ)); + goto OnErrorExit; + } + + char filepath[CONTROL_MAXPATH_LEN]; + strncpy(filepath, fullpath, sizeof(filepath)); + strncat(filepath, "/", sizeof(filepath)); + strncat(filepath, filename, sizeof(filepath)); + err= luaL_loadfile(luaState, filepath); + if (err) { + AFB_ERROR ("LUA-LOAD HOOPs Error in LUA loading scripts=%s err=%s", filepath, lua_tostring(luaState,-1)); + goto OnErrorExit; + } + + // exec/compil script + err = lua_pcall(luaState, 0, 0, 0); + if (err) { + AFB_ERROR ("LUA-LOAD HOOPs Error in LUA exec scripts=%s err=%s", filepath, lua_tostring(luaState,-1)); + goto OnErrorExit; + } + } + + // no policy config found remove control API from binder + if (index == 0) { + AFB_WARNING ("POLICY-INIT:WARNING (setenv CONTROL_LUA_PATH) No LUA '%s*.lua' in '%s'", fullprefix, dirList); + } + + AFB_DEBUG ("Audio control-LUA Init Done"); + return 0; + + OnErrorExit: + return 1; +} diff --git a/controller/ctl-lua.h b/controller/ctl-lua.h new file mode 100644 index 0000000..6f2ce7d --- /dev/null +++ b/controller/ctl-lua.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2016 "IoT.bzh" + * Author Fulup Ar Foll <fulup@iot.bzh> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, something express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Reference: + * Json load using json_unpack https://jansson.readthedocs.io/en/2.9/apiref.html#parsing-and-validating-values + */ + +#ifndef _LUA_CTL_INCLUDE_ +#define _LUA_CTL_INCLUDE_ + +#define _GNU_SOURCE + +// prefix start debug script +#ifndef CONTROL_DOSCRIPT_PRE +#define CONTROL_DOSCRIPT_PRE "debug" +#endif + +// default event name used by LUA +#ifndef CONTROL_LUA_EVENT +#define CONTROL_LUA_EVENT "luaevt" +#endif + +// default use same search path for config.json and script.lua +#ifndef CONTROL_LUA_PATH +#define CONTROL_LUA_PATH CONTROL_CONFIG_PATH +#endif + +// standard lua include file +#ifdef CONTROL_SUPPORT_LUA +#include "lua.h" +#include "lauxlib.h" +#include "lualib.h" +#endif + +#include "ctl-timer.h" + +PUBLIC int LuaLibInit (); + +typedef int (*Lua2cFunctionT)(char *funcname, json_object *argsJ); +typedef int (*Lua2cWrapperT) (lua_State* luaState, char *funcname, Lua2cFunctionT callback); + +#define CTLP_LUALOAD Lua2cWrapperT Lua2cWrap; +#define CTLP_LUA2C(FuncName, label,argsJ, context) static int FuncName(char*label,json_object*argsJ);\ + int lua2c_ ## FuncName(lua_State* luaState){return((*Lua2cWrap)(luaState, MACRO_STR_VALUE(FuncName), FuncName, PLUGIN_NAME));};\ + static int FuncName(char* label, json_object* argsJ, void* context) + +typedef enum { + LUA_DOCALL, + LUA_DOSTRING, + LUA_DOSCRIPT, +} LuaDoActionT; + + +PUBLIC int LuaConfigLoad(); +PUBLIC int LuaConfigExec(); +PUBLIC void LuaL2cNewLib(const char *label, luaL_Reg *l2cFunc, int count); +PUBLIC int Lua2cWrapper(lua_State* luaState, char *funcname, Lua2cFunctionT callback); +PUBLIC int LuaCallFunc (CtlActionT *action, json_object *queryJ) ; +PUBLIC void ctlapi_lua_docall (afb_req request); +PUBLIC void ctlapi_lua_dostring (afb_req request); +PUBLIC void ctlapi_lua_doscript (afb_req request); + + +#endif
\ No newline at end of file diff --git a/controller/ctl-onload.c b/controller/ctl-onload.c new file mode 100644 index 0000000..97bd109 --- /dev/null +++ b/controller/ctl-onload.c @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2016 "IoT.bzh" + * Author Fulup Ar Foll <fulup@iot.bzh> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, something express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#define _GNU_SOURCE +#include <stdio.h> +#include <string.h> + +#include "ctl-config.h" + +// onload section receive one action or an array of actions +PUBLIC int OnloadConfig(CtlSectionT *section, json_object *actionsJ) { + CtlActionT *actions; + + // Load time parse actions in config file + if (actionsJ != NULL) { + actions= ActionLoad(actionsJ); + section->handle=actions; + + if (!actions) { + AFB_ERROR ("OnloadLoad config fail processing onload actions"); + goto OnErrorExit; + } + + } else { + // Exec time process onload action now + actions=(CtlActionT*)section->handle; + if (!actions) { + AFB_ERROR ("OnloadLoad Cannot Exec Non Existing Onload Action"); + goto OnErrorExit; + } + + for (int idx=0; actions[idx].source.label != NULL; idx ++) { + ActionExecOne(&actions[idx], NULL); + } + } + + return 0; + +OnErrorExit: + return 1; + +} diff --git a/controller/ctl-plugin.c b/controller/ctl-plugin.c new file mode 100644 index 0000000..f589035 --- /dev/null +++ b/controller/ctl-plugin.c @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2016 "IoT.bzh" + * Author Fulup Ar Foll <fulup@iot.bzh> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, something express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Reference: + * Json load using json_unpack https://jansson.readthedocs.io/en/2.9/apiref.html#parsing-and-validating-values + */ + +#define _GNU_SOURCE +#include <string.h> +#include <dlfcn.h> + +#include "ctl-config.h" + +static CtlPluginT *ctlPlugins=NULL; + +PUBLIC int PluginGetCB (CtlActionT *action , json_object *callbackJ) { + const char *plugin=NULL, *function=NULL; + json_object *argsJ; + int idx; + + if (!ctlPlugins) { + AFB_ERROR ("PluginGetCB plugin section missing cannot call '%s'", json_object_get_string(callbackJ)); + goto OnErrorExit; + } + + + int err = wrap_json_unpack(callbackJ, "{ss,ss,s?s,s?o!}", "plugin", &plugin, "function", &function, "args", &argsJ); + if (err) { + AFB_ERROR("PluginGet missing plugin|function|[args] in %s", json_object_get_string(callbackJ)); + goto OnErrorExit; + } + + for (idx=0; ctlPlugins[idx].label != NULL; idx++) { + if (!strcasecmp (ctlPlugins[idx].label, plugin)) break; + } + + if (!ctlPlugins[idx].label) { + AFB_ERROR ("PluginGetCB no plugin with label=%s", plugin); + goto OnErrorExit; + } + + action->actionCB = dlsym(ctlPlugins[idx].dlHandle, function); + action->source.context = ctlPlugins[idx].context; + + if (!action->actionCB) { + AFB_ERROR ("PluginGetCB no plugin=%s no function=%s", plugin, function); + goto OnErrorExit; + } + return 0; + +OnErrorExit: + return 1; + +} + +// Wrapper to Lua2c plugin command add context and delegate to LuaWrapper +STATIC int DispatchOneL2c(lua_State* luaState, char *funcname, Lua2cFunctionT callback) { +#ifndef CONTROL_SUPPORT_LUA + AFB_ERROR("CTL-ONE-L2C: LUA support not selected (cf:CONTROL_SUPPORT_LUA) in config.cmake"); + return 1; +#else + int err=Lua2cWrapper(luaState, funcname, callback); + return err; +#endif +} + +STATIC int PluginLoadOne (CtlPluginT *ctlPlugin, json_object *pluginJ, void* handle) { + json_object *lua2csJ = NULL, *actionsJ = NULL; + const char*ldSearchPath = NULL, *basename = NULL; + void *dlHandle; + + + // plugin initialises at 1st load further init actions should be place into onload section + if (!pluginJ) return 0; + + int err = wrap_json_unpack(pluginJ, "{ss,s?s,s?s,s?s,ss,s?o,s?o!}", + "label", &ctlPlugin->label, "version", &ctlPlugin->version, "info", &ctlPlugin->info, "ldpath", &ldSearchPath, "basename", &basename, "lua2c", &lua2csJ, "actions", &actionsJ); + if (err) { + AFB_ERROR("CTL-PLUGIN-LOADONE Plugin missing label|basename|version|[info]|[ldpath]|[lua2c]|[actions] in:\n-- %s", json_object_get_string(pluginJ)); + goto OnErrorExit; + } + + // if search path not in Json config file, then try default + if (!ldSearchPath) ldSearchPath = CONTROL_PLUGIN_PATH; + + // search for default policy config file + json_object *pluginPathJ = ScanForConfig(ldSearchPath, CTL_SCAN_RECURSIVE, basename, CTL_PLUGIN_EXT); + if (!pluginPathJ || json_object_array_length(pluginPathJ) == 0) { + AFB_ERROR("CTL-PLUGIN-LOADONE Missing plugin=%s*%s (config ldpath?) search=\n-- %s", basename, CTL_PLUGIN_EXT, ldSearchPath); + goto OnErrorExit; + } + + char *filename; + char*fullpath; + err = wrap_json_unpack(json_object_array_get_idx(pluginPathJ, 0), "{s:s, s:s !}", "fullpath", &fullpath, "filename", &filename); + if (err) { + AFB_ERROR("CTL-PLUGIN-LOADONE HOOPs invalid plugin file path=\n-- %s", json_object_get_string(pluginPathJ)); + goto OnErrorExit; + } + + if (json_object_array_length(pluginPathJ) > 1) { + AFB_WARNING("CTL-PLUGIN-LOADONE plugin multiple instances in searchpath will use %s/%s", fullpath, filename); + } + + char pluginpath[CONTROL_MAXPATH_LEN]; + strncpy(pluginpath, fullpath, sizeof (pluginpath)); + strncat(pluginpath, "/", sizeof (pluginpath)); + strncat(pluginpath, filename, sizeof (pluginpath)); + dlHandle = dlopen(pluginpath, RTLD_NOW); + if (!dlHandle) { + AFB_ERROR("CTL-PLUGIN-LOADONE Fail to load pluginpath=%s err= %s", pluginpath, dlerror()); + goto OnErrorExit; + } + + CtlPluginMagicT *ctlPluginMagic = (CtlPluginMagicT*) dlsym(dlHandle, "CtlPluginMagic"); + if (!ctlPluginMagic || ctlPluginMagic->magic != CTL_PLUGIN_MAGIC) { + AFB_ERROR("CTL-PLUGIN-LOADONE symbol'CtlPluginMagic' missing or != CTL_PLUGIN_MAGIC plugin=%s", pluginpath); + goto OnErrorExit; + } else { + AFB_NOTICE("CTL-PLUGIN-LOADONE %s successfully registered", ctlPluginMagic->label); + } + + // store dlopen handle to enable onload action at exec time + ctlPlugin->dlHandle = dlHandle; + + // Jose hack to make verbosity visible from sharelib + struct afb_binding_data_v2 *afbHidenData = dlsym(dlHandle, "afbBindingV2data"); + if (afbHidenData) *afbHidenData = afbBindingV2data; + + // Push lua2cWrapper @ into plugin + Lua2cWrapperT *lua2cInPlug = dlsym(dlHandle, "Lua2cWrap"); +#ifndef CONTROL_SUPPORT_LUA + if (lua2cInPlug) *lua2cInPlug = NULL; +#else + // Lua2cWrapper is part of binder and not expose to dynamic link + if (lua2csJ && lua2cInPlug) { + *lua2cInPlug = DispatchOneL2c; + + int Lua2cAddOne(luaL_Reg *l2cFunc, const char* l2cName, int index) { + char funcName[CONTROL_MAXPATH_LEN]; + strncpy(funcName, "lua2c_", sizeof (funcName)); + strncat(funcName, l2cName, sizeof (funcName)); + + Lua2cFunctionT l2cFunction = (Lua2cFunctionT) dlsym(dlHandle, funcName); + if (!l2cFunction) { + AFB_ERROR("CTL-PLUGIN-LOADONE symbol'%s' missing err=%s", funcName, dlerror()); + return 1; + } + l2cFunc[index].func = (void*) l2cFunction; + l2cFunc[index].name = strdup(l2cName); + + return 0; + } + + int errCount = 0; + luaL_Reg *l2cFunc = NULL; + + // look on l2c command and push them to LUA + if (json_object_get_type(lua2csJ) == json_type_array) { + int length = json_object_array_length(lua2csJ); + l2cFunc = calloc(length + 1, sizeof (luaL_Reg)); + for (int count = 0; count < length; count++) { + int err; + const char *l2cName = json_object_get_string(json_object_array_get_idx(lua2csJ, count)); + err = Lua2cAddOne(l2cFunc, l2cName, count); + if (err) errCount++; + } + } else { + l2cFunc = calloc(2, sizeof (luaL_Reg)); + const char *l2cName = json_object_get_string(lua2csJ); + errCount = Lua2cAddOne(l2cFunc, l2cName, 0); + } + if (errCount) { + AFB_ERROR("CTL-PLUGIN-LOADONE %d symbols not found in plugin='%s'", errCount, pluginpath); + goto OnErrorExit; + } + } +#endif + DispatchPluginInstallCbT ctlPluginOnload = dlsym(dlHandle, "CtlPluginOnload"); + if (ctlPluginOnload) { + ctlPlugin->context = (*ctlPluginOnload) (ctlPlugin, handle); + } + return 0; + +OnErrorExit: + return 1; +} + + +PUBLIC int PluginConfig(CtlSectionT *section, json_object *pluginsJ) { + int err=0; + + if (json_object_get_type(pluginsJ) == json_type_array) { + int length = json_object_array_length(pluginsJ); + ctlPlugins = calloc (length+1, sizeof(CtlPluginT)); + for (int idx=0; idx < length; idx++) { + json_object *pluginJ = json_object_array_get_idx(pluginsJ, idx); + err += PluginLoadOne(&ctlPlugins[idx], pluginJ, section->handle); + } + } else { + ctlPlugins = calloc (2, sizeof(CtlPluginT)); + err += PluginLoadOne(&ctlPlugins[0], pluginsJ, section->handle); + } + + return err; +}
\ No newline at end of file diff --git a/controller/ctl-plugin.h b/controller/ctl-plugin.h new file mode 100644 index 0000000..c0aa2e2 --- /dev/null +++ b/controller/ctl-plugin.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2016 "IoT.bzh" + * Author Fulup Ar Foll <fulup@iot.bzh> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, something express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + +#ifndef _CTL_PLUGIN_INCLUDE_ +#define _CTL_PLUGIN_INCLUDE_ + +#define _GNU_SOURCE +#include <json-c/json.h> + +#ifndef CTL_PLUGIN_MAGIC + #define CTL_PLUGIN_MAGIC 852369147 +#endif + +#ifndef PUBLIC + #define PUBLIC +#endif + +#ifndef STATIC + #define STATIC static +#endif + +#ifndef UNUSED_ARG + #define UNUSED_ARG(x) UNUSED_ ## x __attribute__((__unused__)) + #define UNUSED_FUNCTION(x) __attribute__((__unused__)) UNUSED_ ## x +#endif + +typedef struct { + char *label; + char *info; + afb_req request; + void *context; +} CtlSourceT; + + +typedef struct { + long magic; + char *label; + void *handle; +} CtlPluginMagicT; + + +typedef struct { + const char *label; + const char *info; + const char *version; + void *context; + void *dlHandle; +} CtlPluginT; + +typedef void*(*DispatchPluginInstallCbT)(CtlPluginT *plugin, void* handle); + + +#define MACRO_STR_VALUE(arg) #arg +#define CTLP_REGISTER(pluglabel) CtlPluginMagicT CtlPluginMagic={.magic=CTL_PLUGIN_MAGIC,.label=pluglabel}; struct afb_binding_data_v2; +#define CTLP_ONLOAD(plugin, handle) void* CtlPluginOnload(CtlPluginT *plugin, void* handle) +#define CTLP_CAPI(funcname, source, argsJ, queryJ, context) int funcname(CtlSourceT *source, json_object* argsJ, json_object* queryJ, void* context) + +#endif /* _CTL_PLUGIN_INCLUDE_ */
\ No newline at end of file diff --git a/controller/ctl-timer.c b/controller/ctl-timer.c new file mode 100644 index 0000000..1de6f99 --- /dev/null +++ b/controller/ctl-timer.c @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2016 "IoT.bzh" + * Author Fulup Ar Foll <fulup@iot.bzh> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE +#include <stdio.h> +#include <string.h> +#include <time.h> + +#include "ctl-config.h" +#include "ctl-timer.h" + +#define DEFAULT_PAUSE_DELAY 3000 +#define DEFAULT_TEST_COUNT 1 +typedef struct { + int value; + const char *label; +} AutoTestCtxT; + +static afb_event afbevt; + +STATIC int TimerNext (sd_event_source* source, uint64_t timer, void* handle) { + TimerHandleT *timerHandle = (TimerHandleT*) handle; + int done; + uint64_t usec; + + // Rearm timer if needed + timerHandle->count --; + if (timerHandle->count == 0) { + sd_event_source_unref(source); + free (handle); + return 0; + } + else { + // otherwise validate timer for a new run + sd_event_now(afb_daemon_get_event_loop(), CLOCK_MONOTONIC, &usec); + sd_event_source_set_enabled(source, SD_EVENT_ONESHOT); + sd_event_source_set_time(source, usec + timerHandle->delay*1000); + } + + done= timerHandle->callback(timerHandle->context); + if (!done) goto OnErrorExit; + + return 0; + +OnErrorExit: + AFB_WARNING("TimerNext Callback Fail Tag=%s", timerHandle->label); + return -1; +} + +PUBLIC void TimerEvtStop(TimerHandleT *timerHandle) { + + sd_event_source_unref(timerHandle->evtSource); + free (timerHandle); +} + + +PUBLIC void TimerEvtStart(TimerHandleT *timerHandle, timerCallbackT callback, void *context) { + uint64_t usec; + + // populate CB handle + timerHandle->callback=callback; + timerHandle->context=context; + + // set a timer with ~250us accuracy + sd_event_now(afb_daemon_get_event_loop(), CLOCK_MONOTONIC, &usec); + sd_event_add_time(afb_daemon_get_event_loop(), &timerHandle->evtSource, CLOCK_MONOTONIC, usec+timerHandle->delay*1000, 250, TimerNext, timerHandle); +} + +PUBLIC afb_event TimerEvtGet(void) { + return afbevt; +} + + +// Create Binding Event at Init +PUBLIC int TimerEvtInit () { + + // create binder event to send test pause/resume + afbevt = afb_daemon_make_event("control"); + if (!afb_event_is_valid(afbevt)) { + AFB_ERROR ("POLCTL_INIT: Cannot register ctl-events"); + return 1; + } + + AFB_DEBUG ("Audio Control-Events Init Done"); + return 0; +} + diff --git a/controller/ctl-timer.h b/controller/ctl-timer.h new file mode 100644 index 0000000..b08299f --- /dev/null +++ b/controller/ctl-timer.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2016 "IoT.bzh" + * Author Fulup Ar Foll <fulup@iot.bzh> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#ifndef CTL_TIMER_INCLUDE +#define CTL_TIMER_INCLUDE + +#include <systemd/sd-event.h> + +// ctl-timer.c +// ---------------------- +typedef int (*timerCallbackT)(void *context); + +typedef struct TimerHandleS { + int count; + int delay; + const char*label; + void *context; + timerCallbackT callback; + sd_event_source *evtSource; +} TimerHandleT; + +PUBLIC int TimerEvtInit (void); +PUBLIC afb_event TimerEvtGet(void); +PUBLIC void TimerEvtStart(TimerHandleT *timerHandle, timerCallbackT callback, void *context); +PUBLIC void TimerEvtStop(TimerHandleT *timerHandle); + +#endif // CTL_TIMER_INCLUDE diff --git a/signal-composer-binding/CMakeLists.txt b/signal-composer-binding/CMakeLists.txt index 52f8418..78db103 100644 --- a/signal-composer-binding/CMakeLists.txt +++ b/signal-composer-binding/CMakeLists.txt @@ -21,7 +21,7 @@ PROJECT_TARGET_ADD(signal-composer) # Define project Targets - add_library(${TARGET_NAME} MODULE ${TARGET_NAME}-binding.cpp ${TARGET_NAME}.cpp ctl-lua.c) + add_library(${TARGET_NAME} MODULE ${TARGET_NAME}-binding.cpp ${TARGET_NAME}.cpp ctl-lua.c ctl-dispatch.c) # Binder exposes a unique public entry point SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES diff --git a/signal-composer-binding/ctl-dispatch.c b/signal-composer-binding/ctl-dispatch.c new file mode 100644 index 0000000..b84c86e --- /dev/null +++ b/signal-composer-binding/ctl-dispatch.c @@ -0,0 +1,705 @@ +/* + * Copyright (C) 2016 "IoT.bzh" + * Author Fulup Ar Foll <fulup@iot.bzh> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, something express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Reference: + * Json load using json_unpack https://jansson.readthedocs.io/en/2.9/apiref.html#parsing-and-validating-values + */ + +#define _GNU_SOURCE +#include <stdio.h> +#include <string.h> +#include <dlfcn.h> +#include <sys/prctl.h> +#include <filescan-utils.h> +#include <wrap-json.h> + +#include "ctl-lua.h" +#include "signal-composer-binding.hpp" + +typedef void*(*DispatchPluginInstallCbT)(const char* label, const char*version, const char*info); + +static afb_req NULL_AFBREQ = {}; + +typedef struct { + const char* id; + const char* source; + const char* class; + DispatchActionT* onReceived; +} DispatchSignalT; + +typedef struct { + const char* label; + const char* info; + const char* ssource; + const char* sclass; + DispatchActionT* actions; +} DispatchHandleT; + +typedef struct { + const char *label; + const char *info; + void *context; + char *sharelib; + void *dlHandle; + luaL_Reg *l2cFunc; + int l2cCount; +} DispatchPluginT; + +typedef struct { + const char* label; + const char *info; + const char *version; + DispatchPluginT *plugin; + DispatchHandleT **sources; + DispatchHandleT **signals; +} DispatchConfigT; + +// global config handle +static DispatchConfigT *configHandle = NULL; + +static int DispatchSignalToIndex(DispatchHandleT **signals, const char* controlLabel) { + + for (int idx = 0; signals[idx]; idx++) { + if (!strcasecmp(controlLabel, signals[idx]->label)) return idx; + } + return -1; +} + +static int DispatchOneSignal(DispatchSourceT source, DispatchHandleT **signals, const char* controlLabel, json_object *queryJ, afb_req request) { + int err; + + if (!configHandle) { + AFB_ERROR("DISPATCH-CTL-API: (Hoops/Bug!!!) No Config Loaded"); + return -1; + } + + if (!configHandle->signals) { + AFB_ERROR("DISPATCH-CTL-API: No Signal Action in Json config label=%s version=%s", configHandle->label, configHandle->version); + return -1; + } + + int index = DispatchSignalToIndex(signals, controlLabel); + if (index < 0 || !signals[index]->actions) { + AFB_ERROR("DISPATCH-CTL-API:NotFound/Error label=%s in Json Signal Config File", controlLabel); + return -1; + } + + // Fulup (Bug/Feature) in current version is unique to every onload profile + if (configHandle->plugin && configHandle->plugin->l2cCount) { + LuaL2cNewLib (configHandle->plugin->label, configHandle->plugin->l2cFunc, configHandle->plugin->l2cCount); + } + + // loop on action for this control + DispatchActionT *actions = signals[index]->actions; + for (int idx = 0; actions[idx].label; idx++) { + + switch (actions[idx].mode) { + case CTL_MODE_API: + { + json_object *returnJ; + + // if query is empty increment usage count and pass args + if (!queryJ || json_object_get_type(queryJ) != json_type_object) { + json_object_get(actions[idx].argsJ); + queryJ= actions[idx].argsJ; + } else if (actions[idx].argsJ) { + + // Merge queryJ and argsJ before sending request + if (json_object_get_type(actions[idx].argsJ) == json_type_object) { + json_object_object_foreach(actions[idx].argsJ, key, val) { + json_object_object_add(queryJ, key, val); + } + } else { + json_object_object_add(queryJ, "args", actions[idx].argsJ); + } + } + + int err = afb_service_call_sync(actions[idx].api, actions[idx].call, queryJ, &returnJ); + if (err) { + static const char*format = "DispatchOneSignal(Api) api=%s verb=%s args=%s"; + if (afb_req_is_valid(request))afb_req_fail_f(request, "DISPATCH-CTL-MODE:API", format, actions[idx].label, actions[idx].api, actions[idx].call); + else AFB_ERROR(format, actions[idx].api, actions[idx].call, actions[idx].label); + return -1; + } + break; + } + +#ifdef CONTROL_SUPPORT_LUA + case CTL_MODE_LUA: + err = LuaCallFunc(source, &actions[idx], queryJ); + if (err) { + static const char*format = "DispatchOneSignal(Lua) label=%s func=%s args=%s"; + if (afb_req_is_valid(request)) afb_req_fail_f(request, "DISPATCH-CTL-MODE:Lua", format, actions[idx].label, actions[idx].call, json_object_get_string(actions[idx].argsJ)); + else AFB_ERROR(format, actions[idx].label, actions[idx].call, json_object_get_string(actions[idx].argsJ)); + return -1; + } + break; +#endif + + case CTL_MODE_CB: + err = (*actions[idx].actionCB) (source, actions[idx].label, actions[idx].argsJ, queryJ, configHandle->plugin->context); + if (err) { + static const char*format = "DispatchOneSignal(Callback) label%s func=%s args=%s"; + if (afb_req_is_valid(request)) afb_req_fail_f(request, "DISPATCH-CTL-MODE:Cb", format, actions[idx].label, actions[idx].call, json_object_get_string(actions[idx].argsJ)); + else AFB_ERROR(format, actions[idx].label, actions[idx].call, json_object_get_string(actions[idx].argsJ)); + return -1; + } + break; + + default: + { + static const char*format = "DispatchOneSignal(unknown) mode control=%s action=%s"; + AFB_ERROR(format, signals[index]->label); + if (afb_req_is_valid(request))afb_req_fail_f(request, "DISPATCH-CTL-MODE:Unknown", format, signals[index]->label); + } + } + } + + // everything when fine + if (afb_req_is_valid(request)) afb_req_success(request, NULL, signals[index]->label); + return 0; +} + +// Event name is mapped on control label and executed as a standard control +/* +void DispatchOneEvent(const char *evtLabel, json_object *eventJ) { + DispatchHandleT **events = configHandle->events; + + (void) DispatchOneSignal(CTL_SOURCE_EVENT, events, evtLabel, eventJ, NULL_AFBREQ); +} +*/ +// Event name is mapped on control label and executed as a standard control + +int DispatchSources() { + if (!configHandle) return 1; + + int err = 0; + DispatchHandleT **sources = configHandle->sources; + ssize_t i = 0, nSources = sizeof(*sources) / sizeof(DispatchHandleT); + + while(i < nSources) { + const char* sourceLabel = sources[i++]->label; + err = DispatchOneSignal(CTL_SOURCE_ONLOAD, sources, sourceLabel, NULL, NULL_AFBREQ); + } + + return err; +} + +void ctlapi_dispatch(afb_req request) { + DispatchHandleT **signals = configHandle->signals; + json_object *queryJ, *argsJ=NULL; + const char *target; + DispatchSourceT source= CTL_SOURCE_UNKNOWN; + + queryJ = afb_req_json(request); + int err = wrap_json_unpack(queryJ, "{s:s, s?i s?o !}", "target", &target, "source", &source, "args", &argsJ); + if (err) { + afb_req_fail_f(request, "CTL-DISPTACH-INVALID", "missing target or args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + + (void) DispatchOneSignal(source, signals, target, argsJ, request); +} + +// Wrapper to Lua2c plugin command add context dans delegate to LuaWrapper +int DispatchOneL2c(lua_State* luaState, char *funcname, Lua2cFunctionT callback) { +#ifndef CONTROL_SUPPORT_LUA + AFB_ERROR("DISPATCH-ONE-L2C: LUA support not selected (cf:CONTROL_SUPPORT_LUA) in config.cmake"); + return 1; +#else + int err=Lua2cWrapper(luaState, funcname, callback, configHandle->plugin->context); + return err; +#endif +} + + +// List Avaliable Configuration Files + +void ctlapi_config(struct afb_req request) { + json_object*tmpJ; + char *dirList; + // Compile some default directories to browse + char defaultConfPath[CONTROL_MAXPATH_LEN]; + strncpy(defaultConfPath, GetBindingDirPath(), sizeof(GetBindingDirPath())); + strncat(defaultConfPath, "/etc:", sizeof(defaultConfPath) - strlen(defaultConfPath) - 1); + strncat(defaultConfPath, GetBindingDirPath(), sizeof(defaultConfPath) - strlen(defaultConfPath) - 1); + strncat(defaultConfPath, "/data", sizeof(defaultConfPath) - strlen(defaultConfPath) - 1); + + + json_object* queryJ = afb_req_json(request); + if (queryJ && json_object_object_get_ex(queryJ, "cfgpath", &tmpJ)) { + dirList = strdup(json_object_get_string(tmpJ)); + } else { + + dirList = getenv("CONTROL_CONFIG_PATH"); + if (!dirList) dirList = defaultConfPath; + AFB_NOTICE("CONFIG-MISSING: use default CONTROL_CONFIG_PATH=%s", defaultConfPath); + } + + // get list of config file + struct json_object *responseJ = ScanForConfig(dirList, CTL_SCAN_RECURSIVE, "onload", "json"); + + if (json_object_array_length(responseJ) == 0) { + afb_req_fail(request, "CONFIGPATH:EMPTY", "No Config Found in CONTROL_CONFIG_PATH"); + } else { + afb_req_success(request, responseJ, NULL); + } + + return; +} + +// unpack individual action object + +static int DispatchLoadOneAction(DispatchConfigT *controlConfig, json_object *actionJ, DispatchActionT *action) { + char *api = NULL, *verb = NULL, *callback = NULL, *lua = NULL, *function = NULL; + int err, modeCount = 0; + + err = wrap_json_unpack(actionJ, "{s?s,s?s,s?s,s?s,s?s,s?s,s?s,s?o !}" + , "label", &action->label + , "info", &action->info + , "function", &function + , "callback", &callback + , "lua", &lua + , "api", &api, "verb", &verb + , "args", &action->argsJ); + if (err) { + AFB_ERROR("DISPATCH-LOAD-ACTION Missing something label|info|callback|lua|(api+verb)|args in %s", json_object_get_string(actionJ)); + return -1; + } + + // Generic way to specify a C or LUA actions + if(function) { + if(strcasestr(function, "lua")) { + action->mode = CTL_MODE_LUA; + action->call = function; + modeCount++; + } + else if(controlConfig->plugin) { + action->mode = CTL_MODE_CB; + action->call = callback; + modeCount++; + + action->actionCB = dlsym(controlConfig->plugin->dlHandle, callback); + if (!action->actionCB) { + AFB_ERROR("DISPATCH-LOAD-ACTION fail to find calbback=%s in %s", callback, controlConfig->plugin->sharelib); + return -1; + } + } + // If label not already set then use function name + if(! &action->label) action->label = function; + } + + if (lua) { + action->mode = CTL_MODE_LUA; + action->call = lua; + modeCount++; + // If label not already set then use function name + if(! &action->label) action->label = lua; + } + + if (api && verb) { + action->mode = CTL_MODE_API; + action->api = api; + action->call = verb; + modeCount++; + char* apiVerb = strdup(api); + apiVerb = strncat(apiVerb, "/", sizeof(*apiVerb) - strlen(apiVerb) - 1); + apiVerb = strncat(apiVerb, verb, sizeof(*apiVerb) - strlen(apiVerb) - 1); + // If label not already set then use function name + if(! &action->label) action->label = apiVerb; + free(apiVerb); + } + + if (callback && controlConfig->plugin) { + action->mode = CTL_MODE_CB; + action->call = callback; + modeCount++; + + action->actionCB = dlsym(controlConfig->plugin->dlHandle, callback); + if (!action->actionCB) { + AFB_ERROR("DISPATCH-LOAD-ACTION fail to find calbback=%s in %s", callback, controlConfig->plugin->sharelib); + return -1; + } + } + + // make sure at least one mode is selected + if (modeCount == 0) { + AFB_ERROR("DISPATCH-LOAD-ACTION No Action Selected lua|callback|(api+verb) in %s", json_object_get_string(actionJ)); + return -1; + } + + if (modeCount > 1) { + AFB_ERROR("DISPATCH-LOAD-ACTION:Too Many arguments lua|callback|(api+verb) in %s", json_object_get_string(actionJ)); + return -1; + } + return 0; +}; + +static DispatchActionT *DispatchLoadActions(DispatchConfigT *controlConfig, json_object *actionsJ) { + int err; + DispatchActionT *actions; + + // action array is close with a nullvalue; + if (json_object_get_type(actionsJ) == json_type_array) { + int count = json_object_array_length(actionsJ); + actions = calloc(count + 1, sizeof (DispatchActionT)); + + for (int idx = 0; idx < count; idx++) { + json_object *actionJ = json_object_array_get_idx(actionsJ, idx); + err = DispatchLoadOneAction(controlConfig, actionJ, &actions[idx]); + if (err) return NULL; + } + + } else { + actions = calloc(2, sizeof (DispatchActionT)); + err = DispatchLoadOneAction(controlConfig, actionsJ, &actions[0]); + if (err) return NULL; + } + + return actions; +} + +static void DispatchLoadPlugin(DispatchConfigT *controlConfig, json_object* sourcesJ, json_object* pluginJ) +{ + int err = 0; + json_object *lua2csJ = NULL; + DispatchPluginT *dPlugin= calloc(1, sizeof(DispatchPluginT)); + controlConfig->plugin = dPlugin; + const char*ldSearchPath=NULL; + + err = wrap_json_unpack(pluginJ, "{ss,s?s,s?s,ss,s?o!}", + "label", &dPlugin->label, "info", &dPlugin->info, "ldpath", &ldSearchPath, "sharelib", &dPlugin->sharelib, "lua2c", &lua2csJ); + if (err) { + AFB_ERROR("DISPATCH-LOAD-CONFIG:ONLOAD Plugin missing label|[info]|sharelib|[lua2c] in %s", json_object_get_string(sourcesJ)); + return; + } + + // if search path not in Json config file, then try default + if (!ldSearchPath) ldSearchPath= strncat(GetBindingDirPath(), "/data", sizeof(GetBindingDirPath()) - strlen(GetBindingDirPath()) - 1); + + // search for default policy config file + json_object *pluginPathJ = ScanForConfig(ldSearchPath, CTL_SCAN_RECURSIVE, dPlugin->sharelib, NULL); + if (!pluginPathJ || json_object_array_length(pluginPathJ) == 0) { + AFB_ERROR("DISPATCH-LOAD-CONFIG:PLUGIN Missing plugin=%s in path=%s", dPlugin->sharelib, ldSearchPath); + return; + } + + char *filename; + char*fullpath; + err = wrap_json_unpack(json_object_array_get_idx(pluginPathJ, 0), "{s:s, s:s !}", "fullpath", &fullpath, "filename", &filename); + if (err) { + AFB_ERROR("DISPATCH-LOAD-CONFIG:PLUGIN HOOPs invalid plugin file path = %s", json_object_get_string(pluginPathJ)); + return; + } + + if (json_object_array_length(pluginPathJ) > 1) { + AFB_WARNING("DISPATCH-LOAD-CONFIG:PLUGIN plugin multiple instances in searchpath will use %s/%s", fullpath, filename); + } + + char pluginpath[CONTROL_MAXPATH_LEN]; + strncpy(pluginpath, fullpath, sizeof (pluginpath)); + strncat(pluginpath, "/", sizeof (pluginpath)-strlen(pluginpath)-1); + strncat(pluginpath, filename, sizeof (pluginpath)-strlen(pluginpath)-1); + dPlugin->dlHandle = dlopen(pluginpath, RTLD_NOW); + if (!dPlugin->dlHandle) { + AFB_ERROR("DISPATCH-LOAD-CONFIG:PLUGIN Fail to load pluginpath=%s err= %s", pluginpath, dlerror()); + return; + } + + CtlPluginMagicT *ctlPluginMagic = (CtlPluginMagicT*) dlsym(dPlugin->dlHandle, "CtlPluginMagic"); + if (!ctlPluginMagic || ctlPluginMagic->magic != CTL_PLUGIN_MAGIC) { + AFB_ERROR("DISPATCH-LOAD-CONFIG:Plugin symbol'CtlPluginMagic' missing or != CTL_PLUGIN_MAGIC plugin=%s", pluginpath); + return; + } else { + AFB_NOTICE("DISPATCH-LOAD-CONFIG:Plugin %s successfully registered", ctlPluginMagic->label); + } + + // Jose hack to make verbosity visible from sharelib + struct afb_binding_data_v2 *afbHidenData = dlsym(dPlugin->dlHandle, "afbBindingV2data"); + if (afbHidenData) *afbHidenData = afbBindingV2data; + + // Push lua2cWrapper @ into plugin + Lua2cWrapperT *lua2cInPlug = dlsym(dPlugin->dlHandle, "Lua2cWrap"); +#ifndef CONTROL_SUPPORT_LUA + if (lua2cInPlug) *lua2cInPlug = NULL; +#else + // Lua2cWrapper is part of binder and not expose to dynamic link + if (lua2cInPlug) *lua2cInPlug = DispatchOneL2c; + + { + int Lua2cAddOne(luaL_Reg *l2cFunc, const char* l2cName, int index) { + char funcName[CONTROL_MAXPATH_LEN]; + strncpy(funcName, "lua2c_", sizeof(funcName)); + strncat(funcName, l2cName, sizeof(funcName)-strlen(funcName)-1); + + Lua2cFunctionT l2cFunction= (Lua2cFunctionT)dlsym(dPlugin->dlHandle, funcName); + if (!l2cFunction) { + AFB_ERROR("DISPATCH-LOAD-CONFIG:Plugin symbol'%s' missing err=%s", funcName, dlerror()); + return 1; + } + l2cFunc[index].func=(void*)l2cFunction; + l2cFunc[index].name=strdup(l2cName); + + return 0; + } + + int errCount = 0; + luaL_Reg *l2cFunc=NULL; + int count=0; + + // look on l2c command and push them to LUA + if (json_object_get_type(lua2csJ) == json_type_array) { + int length = json_object_array_length(lua2csJ); + l2cFunc = calloc(length + 1, sizeof (luaL_Reg)); + for (count=0; count < length; count++) { + int err; + const char *l2cName = json_object_get_string(json_object_array_get_idx(lua2csJ, count)); + err = Lua2cAddOne(l2cFunc, l2cName, count); + if (err) errCount++; + } + } else { + l2cFunc = calloc(2, sizeof (luaL_Reg)); + const char *l2cName = json_object_get_string(lua2csJ); + errCount = Lua2cAddOne(l2cFunc, l2cName, 0); + count=1; + } + if (errCount) { + AFB_ERROR("DISPATCH-LOAD-CONFIG:Plugin %d symbols not found in plugin='%s'", errCount, pluginpath); + return; + } else { + dPlugin->l2cFunc= l2cFunc; + dPlugin->l2cCount= count; + } + } +#endif + DispatchPluginInstallCbT ctlPluginOnload = dlsym(dPlugin->dlHandle, "CtlPluginOnload"); + if (ctlPluginOnload) { + dPlugin->context = (*ctlPluginOnload) (controlConfig->label, controlConfig->version, controlConfig->info); + } +} + +static DispatchHandleT *DispatchLoadSignal(DispatchConfigT *controlConfig, json_object *controlJ) { + json_object *actionsJ, *permissionsJ; + int err; + + DispatchHandleT *dispatchHandle = calloc(1, sizeof (DispatchHandleT)); + err = wrap_json_unpack(controlJ, "{ss,s?s,ss,ss,s?o,so !}" + , "id", &dispatchHandle->label + , "info", &dispatchHandle->info + , "source", &dispatchHandle->ssource + , "class", &dispatchHandle->sclass + , "permissions", &permissionsJ + , "onReceived", &actionsJ); + if (err) { + AFB_ERROR("DISPATCH-LOAD-CONFIG:CONTROL Missing something label|[info]|actions in %s", json_object_get_string(controlJ)); + return NULL; + } + + dispatchHandle->actions = DispatchLoadActions(controlConfig, actionsJ); + if (!dispatchHandle->actions) { + AFB_ERROR("DISPATCH-LOAD-CONFIG:CONTROL Error when parsing actions %s", dispatchHandle->label); + return NULL; + } + return dispatchHandle; +} + +static DispatchHandleT *DispatchLoadSource(DispatchConfigT *controlConfig, json_object *sourcesJ) { + json_object *actionsJ = NULL, *pluginJ = NULL; + int err; + + DispatchHandleT *dispatchSources = calloc(1, sizeof (DispatchHandleT)); + err = wrap_json_unpack(sourcesJ, "{ss,s?s,s?o,s?o,s?o !}", + "api", &dispatchSources->label, "info", &dispatchSources->info, "plugin", &pluginJ, "actions", &actionsJ); + if (err) { + AFB_ERROR("DISPATCH-LOAD-CONFIG:ONLOAD Missing something label|[info]|[plugin]|[actions] in %s", json_object_get_string(sourcesJ)); + return NULL; + } + + // best effort to initialise everything before starting + err = afb_daemon_require_api(dispatchSources->label, 1); + if (err) { + AFB_WARNING("DISPATCH-LOAD-CONFIG:REQUIRE Fail to get=%s", dispatchSources->label); + } + + if (pluginJ) { + DispatchLoadPlugin(controlConfig, sourcesJ, pluginJ); + } + + dispatchSources->actions = DispatchLoadActions(controlConfig, actionsJ); + if (!dispatchSources->actions) { + AFB_ERROR("DISPATCH-LOAD-CONFIG:ONLOAD Error when parsing actions %s", dispatchSources->label); + return NULL; + } + return dispatchSources; +} + +static DispatchConfigT *DispatchLoadConfig(const char* filepath) { + json_object *controlConfigJ, *ignoreJ; + int err; + + // Load JSON file + controlConfigJ = json_object_from_file(filepath); + if (!controlConfigJ) { + AFB_ERROR("DISPATCH-LOAD-CONFIG:JsonLoad invalid JSON %s ", filepath); + return NULL; + } + + AFB_INFO("DISPATCH-LOAD-CONFIG: loading config filepath=%s", filepath); + + json_object *metadataJ = NULL, *sourcesJ = NULL, *signalsJ = NULL; + err = wrap_json_unpack(controlConfigJ, "{s?s,s?o,s?o,s?o !}", "$schema", &ignoreJ, "metadata", &metadataJ, "sources", &sourcesJ, "signals", &signalsJ); + if (err) { + AFB_ERROR("DISPATCH-LOAD-CONFIG Missing something metadata|[sources]|[signals] in %s", json_object_get_string(controlConfigJ)); + return NULL; + } + + DispatchConfigT *controlConfig = calloc(1, sizeof (DispatchConfigT)); + if (metadataJ) { + const char*ctlname=NULL; + err = wrap_json_unpack(metadataJ, "{ss,s?s,s?s,ss !}", "label", &controlConfig->label, "version", &controlConfig->version, "name", &ctlname, "info", &controlConfig->info); + if (err) { + AFB_ERROR("DISPATCH-LOAD-CONFIG:METADATA Missing something label|version|[label] in %s", json_object_get_string(metadataJ)); + return NULL; + } + + // if ctlname is provided change process name now + if (ctlname) { + err= prctl(PR_SET_NAME, ctlname,NULL,NULL,NULL); + if (err) AFB_WARNING("Fail to set Process Name to:%s",ctlname); + } + } + + if (sourcesJ) { + DispatchHandleT *dispatchHandle; + + if (json_object_get_type(sourcesJ) != json_type_array) { + controlConfig->sources = (DispatchHandleT**) calloc(2, sizeof (void*)); + dispatchHandle = DispatchLoadSource(controlConfig, sourcesJ); + controlConfig->sources[0] = dispatchHandle; + } else { + int length = json_object_array_length(sourcesJ); + controlConfig->sources = (DispatchHandleT**) calloc(length + 1, sizeof (void*)); + + for (int jdx = 0; jdx < length; jdx++) { + json_object *sourcesJ = json_object_array_get_idx(sourcesJ, jdx); + dispatchHandle = DispatchLoadSource(controlConfig, sourcesJ); + controlConfig->sources[jdx] = dispatchHandle; + } + } + } + + if (signalsJ) { + DispatchHandleT* dispatchHandle; + + if (json_object_get_type(signalsJ) != json_type_array) { + controlConfig->signals = (DispatchHandleT**) calloc(2, sizeof (void*)); + dispatchHandle = DispatchLoadSignal(controlConfig, signalsJ); + controlConfig->signals[0] = dispatchHandle; + } else { + int length = json_object_array_length(signalsJ); + controlConfig->signals = (DispatchHandleT**) calloc(length + 1, sizeof (void*)); + + for (int jdx = 0; jdx < length; jdx++) { + json_object *controlJ = json_object_array_get_idx(signalsJ, jdx); + dispatchHandle = DispatchLoadSignal(controlConfig, controlJ); + controlConfig->signals[jdx] = dispatchHandle; + } + } + } +/* + if (eventsJ) { + DispatchHandleT *dispatchHandle; + + if (json_object_get_type(eventsJ) != json_type_array) { + controlConfig->events = (DispatchHandleT**) calloc(2, sizeof (void*)); + dispatchHandle = DispatchLoadSignal(controlConfig, eventsJ); + controlConfig->events[0] = dispatchHandle; + } else { + int length = json_object_array_length(eventsJ); + controlConfig->events = (DispatchHandleT**) calloc(length + 1, sizeof (void*)); + + for (int jdx = 0; jdx < length; jdx++) { + json_object *eventJ = json_object_array_get_idx(eventsJ, jdx); + dispatchHandle = DispatchLoadSignal(controlConfig, eventJ); + controlConfig->events[jdx] = dispatchHandle; + } + } + } +*/ + return controlConfig; +} + + +// Load default config file at init + +int DispatchInit() { + int index, luaLoaded = 0; + char controlFile [CONTROL_MAXPATH_LEN]; + // Compile some default directories to browse + char defaultConfPath[CONTROL_MAXPATH_LEN]; + strncpy(defaultConfPath, GetBindingDirPath(), sizeof(GetBindingDirPath())); + strncat(defaultConfPath, "/etc:", sizeof(defaultConfPath) - strlen(defaultConfPath) - 1); + strncat(defaultConfPath, GetBindingDirPath(), sizeof(defaultConfPath) - strlen(defaultConfPath) - 1); + strncat(defaultConfPath, "/data", sizeof(defaultConfPath) - strlen(defaultConfPath) - 1); + + const char *dirList = getenv("CONTROL_CONFIG_PATH"); + if (!dirList) dirList = defaultConfPath; + + strncpy(controlFile, CONTROL_CONFIG_PRE "-", CONTROL_MAXPATH_LEN); + strncat(controlFile, GetBinderName(), CONTROL_MAXPATH_LEN-strlen(controlFile)-1); + + // search for default dispatch config file + json_object* responseJ = ScanForConfig(dirList, CTL_SCAN_RECURSIVE, controlFile, "json"); + + // We load 1st file others are just warnings + for (index = 0; index < json_object_array_length(responseJ); index++) { + json_object *entryJ = json_object_array_get_idx(responseJ, index); + + char *filename; + char*fullpath; + int err = wrap_json_unpack(entryJ, "{s:s, s:s !}", "fullpath", &fullpath, "filename", &filename); + if (err) { + AFB_ERROR("DISPATCH-INIT HOOPs invalid JSON entry= %s", json_object_get_string(entryJ)); + return -1; + } + + if (index == 0) { + if (strcasestr(filename, controlFile)) { + char filepath[CONTROL_MAXPATH_LEN]; + strncpy(filepath, fullpath, sizeof (filepath)); + strncat(filepath, "/", sizeof (filepath)-strlen(filepath)-1); + strncat(filepath, filename, sizeof (filepath)-strlen(filepath)-1); + configHandle = DispatchLoadConfig(filepath); + if (!configHandle) { + AFB_ERROR("DISPATCH-INIT:ERROR Fail loading [%s]", filepath); + return -1; + } + luaLoaded = 1; + break; + } + } else { + AFB_WARNING("DISPATCH-INIT:WARNING Secondary Signal Config Ignored %s/%s", fullpath, filename); + } + } + + // no dispatch config found remove control API from binder + if (!luaLoaded) { + AFB_WARNING("DISPATCH-INIT:WARNING (setenv CONTROL_CONFIG_PATH) No Config '%s-*.json' in '%s'", controlFile, dirList); + } + + AFB_NOTICE("DISPATCH-INIT:SUCCES: Signal Dispatch Init"); + return 0; +} diff --git a/signal-composer-binding/ctl-lua.c b/signal-composer-binding/ctl-lua.c index e8e48fa..2773696 100644 --- a/signal-composer-binding/ctl-lua.c +++ b/signal-composer-binding/ctl-lua.c @@ -80,17 +80,17 @@ typedef enum { */ static LuaAfbContextT *LuaCtxCheck (lua_State *luaState, int index) { - LuaAfbContextT *afbContext; - //luaL_checktype(luaState, index, LUA_TUSERDATA); - //afbContext = (LuaAfbContextT *)luaL_checkudata(luaState, index, CTX_TOKEN); - luaL_checktype(luaState, index, LUA_TLIGHTUSERDATA); - afbContext = (LuaAfbContextT *) lua_touserdata(luaState, index); - if (afbContext == NULL && afbContext->ctxMagic != CTX_MAGIC) { - luaL_error(luaState, "Fail to retrieve user data context=%s", CTX_TOKEN); - AFB_ERROR ("afbContextCheck error retrieving afbContext"); - return NULL; - } - return afbContext; + LuaAfbContextT *afbContext; + //luaL_checktype(luaState, index, LUA_TUSERDATA); + //afbContext = (LuaAfbContextT *)luaL_checkudata(luaState, index, CTX_TOKEN); + luaL_checktype(luaState, index, LUA_TLIGHTUSERDATA); + afbContext = (LuaAfbContextT *) lua_touserdata(luaState, index); + if (afbContext == NULL && afbContext->ctxMagic != CTX_MAGIC) { + luaL_error(luaState, "Fail to retrieve user data context=%s", CTX_TOKEN); + AFB_ERROR ("afbContextCheck error retrieving afbContext"); + return NULL; + } + return afbContext; } static LuaAfbContextT *LuaCtxPush (lua_State *luaState, afb_req request, void *handle, const char* info) { @@ -265,7 +265,7 @@ static json_object *LuaPopArgs (lua_State* luaState, int start) { json_object *responseJ; int stop = lua_gettop(luaState); - if(stop-start <0) return NULL; + if(stop-start <0) goto OnErrorExit; // start at 2 because we are using a function array lib if (start == stop) { @@ -277,13 +277,13 @@ static json_object *LuaPopArgs (lua_State* luaState, int start) { json_object *argJ=LuaPopOneArg (luaState, idx); if (!argJ) goto OnErrorExit; json_object_array_add(responseJ, argJ); - } + } } return responseJ; - OnErrorExit: - return NULL; + OnErrorExit: + return NULL; } @@ -660,18 +660,14 @@ int LuaCallFunc (DispatchSourceT source, DispatchActionT *action, json_object *q err=lua_pcall(luaState, count, 1, 0); if (err) { AFB_ERROR("LuaCallFunc Fail calling %s error=%s", func, lua_tostring(luaState,-1)); - goto OnErrorExit; + return -1; } // return LUA script value int rc= (int)lua_tointeger(luaState, -1); return rc; - - OnErrorExit: - return -1; } - // Execute LUA code from received API request static void LuaDoAction (LuaDoActionT action, afb_req request) { @@ -727,7 +723,6 @@ static void LuaDoAction (LuaDoActionT action, afb_req request) { char *filename; char*fullpath; char luaScriptPath[CONTROL_MAXPATH_LEN]; int index; - BPaths BindingPaths = GetBindingDirsPath(); // scan luascript search path once static json_object *luaScriptPathJ =NULL; @@ -743,10 +738,10 @@ static void LuaDoAction (LuaDoActionT action, afb_req request) { // search for filename=script in CONTROL_LUA_PATH if (!luaScriptPathJ) { - strncpy(luaScriptPath,CONTROL_DOSCRIPT_PRE, sizeof(luaScriptPath)); + strncpy(luaScriptPath, CONTROL_DOSCRIPT_PRE, sizeof(luaScriptPath)); strncat(luaScriptPath,"-", sizeof(luaScriptPath)-strlen(luaScriptPath)-1); strncat(luaScriptPath,target, sizeof(luaScriptPath)-strlen(luaScriptPath)-1); - luaScriptPathJ= ScanForConfig(BindingPaths.etcdir, CTL_SCAN_RECURSIVE,luaScriptPath,".lua"); + luaScriptPathJ= ScanForConfig(strncat(GetBindingDirPath(), "/etc", sizeof(GetBindingDirPath()) - strlen(GetBindingDirPath()) - 1), CTL_SCAN_RECURSIVE,luaScriptPath,".lua"); } for (index=0; index < json_object_array_length(luaScriptPathJ); index++) { json_object *entryJ=json_object_array_get_idx(luaScriptPathJ, index); @@ -841,7 +836,7 @@ static int LuaTimerClear (lua_State* luaState) { // Get Timer Handle LuaAfbContextT *afbContext= LuaCtxCheck(luaState, LUA_FIST_ARG); - if (!afbContext) goto OnErrorExit; + if (!afbContext) return -1; // retrieve useful information opaque handle TimerHandleT *timerHandle = (TimerHandleT*)afbContext->handle; @@ -850,15 +845,12 @@ static int LuaTimerClear (lua_State* luaState) { TimerEvtStop(timerHandle); return 0; //happy end - -OnErrorExit: - return 1; } static int LuaTimerGet (lua_State* luaState) { // Get Timer Handle LuaAfbContextT *afbContext= LuaCtxCheck(luaState, LUA_FIST_ARG); - if (!afbContext) goto OnErrorExit; + if (!afbContext) return 0; // retrieve useful information opaque handle TimerHandleT *timerHandle = (TimerHandleT*)afbContext->handle; @@ -876,9 +868,6 @@ static int LuaTimerGet (lua_State* luaState) { json_object_put(responseJ); return count; // return argument - -OnErrorExit: - return 0; } // Timer Callback @@ -894,7 +883,7 @@ static int LuaTimerSetCB (void *handle) { // Push timer handle LuaAfbContextT *afbContext= LuaCtxPush(luaState, NULL_AFBREQ, contextCB->handle, timerHandle->label); - if (!afbContext) goto OnErrorExit; + if (!afbContext) return 1; count=1; // Push user Context @@ -903,7 +892,7 @@ static int LuaTimerSetCB (void *handle) { int err=lua_pcall(luaState, count, LUA_MULTRET, 0); if (err) { AFB_ERROR ("LUA-TIMER-CB:FAIL response=%s err=%s", json_object_get_string(contextCB->context), lua_tostring(luaState,-1)); - goto OnErrorExit; + return 1; } // get return parameter @@ -916,9 +905,6 @@ static int LuaTimerSetCB (void *handle) { LuaCtxFree(afbContext); } return 0; // By default we are happy - - OnErrorExit: - return 1; // stop timer } static int LuaTimerSet(lua_State* luaState) { @@ -997,12 +983,12 @@ int LuaLibInit () { // search for default policy config file char fullprefix[CONTROL_MAXPATH_LEN]; - strncpy (fullprefix, CONTROL_CONFIG_PRE "-", sizeof(fullprefix)); + strncpy (fullprefix, CONTROL_CONFIG_PRE, sizeof(fullprefix)); strncat (fullprefix, GetBinderName(), sizeof(fullprefix)-strlen(fullprefix)-1); strncat (fullprefix, "-", sizeof(fullprefix)-strlen(fullprefix)-1); const char *dirList= getenv("CONTROL_LUA_PATH"); - if (!dirList) dirList= GetBindingDirsPath().etcdir; + if (!dirList) dirList= strncat(GetBindingDirPath(), "/etc", sizeof(GetBindingDirPath()) - strlen(GetBindingDirPath()) - 1); json_object *luaScriptPathJ = ScanForConfig(dirList , CTL_SCAN_RECURSIVE, fullprefix, "lua"); @@ -1026,7 +1012,7 @@ int LuaLibInit () { luaDefaultEvt->event = afb_daemon_make_event(CONTROL_LUA_EVENT); if (!afb_event_is_valid(luaDefaultEvt->event)) { AFB_ERROR ("POLCTL_INIT: Cannot register lua-events=%s ", CONTROL_LUA_EVENT); - goto OnErrorExit;; + goto OnErrorExit; } // load+exec any file found in LUA search path diff --git a/signal-composer-binding/ctl-lua.h b/signal-composer-binding/ctl-lua.h index df2379f..c5f472d 100644 --- a/signal-composer-binding/ctl-lua.h +++ b/signal-composer-binding/ctl-lua.h @@ -12,60 +12,6 @@ #include "signal-composer-binding.hpp" -#ifndef CONTROL_DOSCRIPT_PRE -#define CONTROL_DOSCRIPT_PRE "doscript" -#endif - -#ifndef CONTROL_CONFIG_PRE -#define CONTROL_CONFIG_PRE "onload" -#endif - -#ifndef CONTROL_LUA_EVENT -#define CONTROL_LUA_EVENT "luaevt" -#endif - -typedef int (*timerCallbackT)(void *context); - -typedef struct TimerHandleS { - int count; - int delay; - const char*label; - void *context; - timerCallbackT callback; - sd_event_source *evtSource; -} TimerHandleT; - -int TimerEvtInit (void); -afb_event TimerEvtGet(void); -void TimerEvtStart(TimerHandleT *timerHandle, timerCallbackT callback, void *context); -void TimerEvtStop(TimerHandleT *timerHandle); - -typedef enum { - CTL_MODE_NONE=0, - CTL_MODE_API, - CTL_MODE_CB, - CTL_MODE_LUA, -} CtlRequestModeT; - -typedef enum { - CTL_SOURCE_CLOSE=-1, - CTL_SOURCE_UNKNOWN=0, - CTL_SOURCE_ONLOAD=1, - CTL_SOURCE_OPEN=2, - CTL_SOURCE_EVENT=3, -} DispatchSourceT; - -typedef struct DispatchActionS{ - const char *info; - const char* label; - CtlRequestModeT mode; - const char* api; - const char* call; - json_object *argsJ; - int timeout; - int (*actionCB)(DispatchSourceT source, const char*label, json_object *argsJ, json_object *queryJ, void *context); -} DispatchActionT; - typedef enum { LUA_DOCALL, LUA_DOSTRING, @@ -73,3 +19,10 @@ typedef enum { } LuaDoActionT; typedef int (*Lua2cFunctionT)(char *funcname, json_object *argsJ, void*context); + +typedef int (*Lua2cWrapperT) (lua_State* luaState, char *funcname, Lua2cFunctionT callback); + +int LuaLibInit (); +void LuaL2cNewLib(const char *label, luaL_Reg *l2cFunc, int count); +int Lua2cWrapper(lua_State* luaState, char *funcname, Lua2cFunctionT callback, void *context); +int LuaCallFunc (DispatchSourceT source, DispatchActionT *action, json_object *queryJ) ; diff --git a/signal-composer-binding/signal-composer-apidef.h b/signal-composer-binding/signal-composer-apidef.h index f986692..a652de5 100644 --- a/signal-composer-binding/signal-composer-apidef.h +++ b/signal-composer-binding/signal-composer-apidef.h @@ -4,45 +4,45 @@ static const char _afb_description_v2_signals_composer[] = "a-3.0/default-schema.json\",\"info\":{\"description\":\"\",\"title\":\"s" "ignals-composer-service\",\"version\":\"4.0\",\"x-binding-c-generator\":" "{\"api\":\"signals-composer\",\"version\":2,\"prefix\":\"\",\"postfix\":" - "\"\",\"start\":null,\"onevent\":\"onEvent\",\"init\":\"init_service\",\"" - "scope\":\"\",\"private\":false}},\"servers\":[{\"url\":\"ws://{host}:{po" - "rt}/api/monitor\",\"description\":\"Signals composer API connected to lo" - "w level AGL services\",\"variables\":{\"host\":{\"default\":\"localhost\"" - "},\"port\":{\"default\":\"1234\"}},\"x-afb-events\":[{\"$ref\":\"#/compo" - "nents/schemas/afb-event\"}]}],\"components\":{\"schemas\":{\"afb-reply\"" - ":{\"$ref\":\"#/components/schemas/afb-reply-v2\"},\"afb-event\":{\"$ref\"" - ":\"#/components/schemas/afb-event-v2\"},\"afb-reply-v2\":{\"title\":\"Ge" - "neric response.\",\"type\":\"object\",\"required\":[\"jtype\",\"request\"" - "],\"properties\":{\"jtype\":{\"type\":\"string\",\"const\":\"afb-reply\"" - "},\"request\":{\"type\":\"object\",\"required\":[\"status\"],\"propertie" - "s\":{\"status\":{\"type\":\"string\"},\"info\":{\"type\":\"string\"},\"t" - "oken\":{\"type\":\"string\"},\"uuid\":{\"type\":\"string\"},\"reqid\":{\"" - "type\":\"string\"}}},\"response\":{\"type\":\"object\"}}},\"afb-event-v2" - "\":{\"type\":\"object\",\"required\":[\"jtype\",\"event\"],\"properties\"" - ":{\"jtype\":{\"type\":\"string\",\"const\":\"afb-event\"},\"event\":{\"t" - "ype\":\"string\"},\"data\":{\"type\":\"object\"}}}},\"x-permissions\":{}" - ",\"responses\":{\"200\":{\"description\":\"A complex object array respon" - "se\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" - "nts/schemas/afb-reply\"}}}}}},\"paths\":{\"/subscribe\":{\"description\"" - ":\"Subscribe to a signal object\",\"parameters\":[{\"in\":\"query\",\"na" - "me\":\"event\",\"required\":false,\"schema\":{\"type\":\"string\"}}],\"r" - "esponses\":{\"200\":{\"$ref\":\"#/components/responses/200\"}}},\"/unsub" - "scribe\":{\"description\":\"Unsubscribe previously suscribed signal obje" - "cts.\",\"parameters\":[{\"in\":\"query\",\"name\":\"event\",\"required\"" - ":false,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"$ref" - "\":\"#/components/responses/200\"}}},\"/get\":{\"description\":\"Get inf" - "ormations about a resource or element\",\"responses\":{\"200\":{\"$ref\"" - ":\"#/components/responses/200\"}}},\"/load\":{\"description\":\"Load con" - "fig file in directory passed as argument searching for pattern 'sig' in " - "filename\",\"parameters\":[{\"in\":\"query\",\"name\":\"path\",\"require" - "d\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"$r" - "ef\":\"#/components/responses/200\"}}}}}" + "\"\",\"start\":null,\"onevent\":\"onEvent\",\"preinit\":\"loadConf\",\"i" + "nit\":\"execConf\",\"scope\":\"\",\"private\":false}},\"servers\":[{\"ur" + "l\":\"ws://{host}:{port}/api/monitor\",\"description\":\"Signals compose" + "r API connected to low level AGL services\",\"variables\":{\"host\":{\"d" + "efault\":\"localhost\"},\"port\":{\"default\":\"1234\"}},\"x-afb-events\"" + ":[{\"$ref\":\"#/components/schemas/afb-event\"}]}],\"components\":{\"sch" + "emas\":{\"afb-reply\":{\"$ref\":\"#/components/schemas/afb-reply-v2\"},\"" + "afb-event\":{\"$ref\":\"#/components/schemas/afb-event-v2\"},\"afb-reply" + "-v2\":{\"title\":\"Generic response.\",\"type\":\"object\",\"required\":" + "[\"jtype\",\"request\"],\"properties\":{\"jtype\":{\"type\":\"string\",\"" + "const\":\"afb-reply\"},\"request\":{\"type\":\"object\",\"required\":[\"" + "status\"],\"properties\":{\"status\":{\"type\":\"string\"},\"info\":{\"t" + "ype\":\"string\"},\"token\":{\"type\":\"string\"},\"uuid\":{\"type\":\"s" + "tring\"},\"reqid\":{\"type\":\"string\"}}},\"response\":{\"type\":\"obje" + "ct\"}}},\"afb-event-v2\":{\"type\":\"object\",\"required\":[\"jtype\",\"" + "event\"],\"properties\":{\"jtype\":{\"type\":\"string\",\"const\":\"afb-" + "event\"},\"event\":{\"type\":\"string\"},\"data\":{\"type\":\"object\"}}" + "}},\"x-permissions\":{},\"responses\":{\"200\":{\"description\":\"A comp" + "lex object array response\",\"content\":{\"application/json\":{\"schema\"" + ":{\"$ref\":\"#/components/schemas/afb-reply\"}}}}}},\"paths\":{\"/subscr" + "ibe\":{\"description\":\"Subscribe to a signal object\",\"parameters\":[" + "{\"in\":\"query\",\"name\":\"event\",\"required\":false,\"schema\":{\"ty" + "pe\":\"string\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/respo" + "nses/200\"}}},\"/unsubscribe\":{\"description\":\"Unsubscribe previously" + " suscribed signal objects.\",\"parameters\":[{\"in\":\"query\",\"name\":" + "\"event\",\"required\":false,\"schema\":{\"type\":\"string\"}}],\"respon" + "ses\":{\"200\":{\"$ref\":\"#/components/responses/200\"}}},\"/get\":{\"d" + "escription\":\"Get informations about a resource or element\",\"response" + "s\":{\"200\":{\"$ref\":\"#/components/responses/200\"}}},\"/loadConf\":{" + "\"description\":\"Load config file in directory passed as argument searc" + "hing for pattern 'sig' in filename\",\"parameters\":[{\"in\":\"query\",\"" + "name\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"r" + "esponses\":{\"200\":{\"$ref\":\"#/components/responses/200\"}}}}}" ; void subscribe(struct afb_req req); void unsubscribe(struct afb_req req); void get(struct afb_req req); - void load(struct afb_req req); + void loadConf(struct afb_req req); static const struct afb_verb_v2 _afb_verbs_v2_signals_composer[] = { { @@ -67,8 +67,8 @@ static const struct afb_verb_v2 _afb_verbs_v2_signals_composer[] = { .session = AFB_SESSION_NONE_V2 }, { - .verb = "load", - .callback = load, + .verb = "loadConf", + .callback = loadConf, .auth = NULL, .info = "Load config file in directory passed as argument searching for pattern 'sig' in filename", .session = AFB_SESSION_NONE_V2 @@ -87,8 +87,8 @@ const struct afb_binding_v2 afbBindingV2 = { .specification = _afb_description_v2_signals_composer, .info = "", .verbs = _afb_verbs_v2_signals_composer, - .preinit = NULL, - .init = init_service, + .preinit = loadConf, + .init = execConf, .onevent = onEvent, .noconcurrency = 0 }; diff --git a/signal-composer-binding/signal-composer-apidef.json b/signal-composer-binding/signal-composer-apidef.json index 78080f1..7085dc2 100644 --- a/signal-composer-binding/signal-composer-apidef.json +++ b/signal-composer-binding/signal-composer-apidef.json @@ -12,7 +12,8 @@ "postfix": "", "start": null , "onevent": "onEvent", - "init": "init_service", + "preinit": "loadConf", + "init": "execConf", "scope": "", "private": false } @@ -130,7 +131,7 @@ "200": {"$ref": "#/components/responses/200"} } }, - "/load": { + "/loadConf": { "description": "Load config file in directory passed as argument searching for pattern 'sig' in filename", "parameters": [ { diff --git a/signal-composer-binding/signal-composer-binding.cpp b/signal-composer-binding/signal-composer-binding.cpp index 24b81f6..2ca1dec 100644 --- a/signal-composer-binding/signal-composer-binding.cpp +++ b/signal-composer-binding/signal-composer-binding.cpp @@ -15,12 +15,15 @@ * limitations under the License. */ +#include <ctl-config.h> + #include "signal-composer-binding.hpp" #include "signal-composer-apidef.h" #include "wrap-json.h" #include "signal-composer.hpp" SignalComposer SigComp; +static CtlConfigT *ctlConfig=NULL; /// @brief callback for receiving message from low binding. Treatment itself is made in SigComp class. void onEvent(const char *event, json_object *object) @@ -46,7 +49,7 @@ void unsubscribe(afb_req request) } /// @brief verb that loads JSON configuration (old SigComp.json file now) -void load(afb_req request) +void loadConf(afb_req request) { json_object* args = afb_req_json(request); const char* confd; @@ -79,12 +82,23 @@ int ticked(sd_event_source *source, uint64_t t, void* data) return 0; } -/// @brief Initialize the binding. -/// -/// @return Exit code, zero if success. -int init_service() +int loadConf() { - AFB_DEBUG("SigComp level binding is initializing"); - AFB_NOTICE("SigComp level binding is initialized and running"); - return 0; + int errcount=0; + + ctlConfig = CtlConfigLoad(); + + #ifdef CONTROL_SUPPORT_LUA + errcount += LuaLibInit(); + #endif + + return errcount; +} + +int execConf() +{ + int err = CtlConfigExec(); + + AFB_DEBUG ("Signal Composer Control configuration Done errcount=%d", errcount); + return errcount; } diff --git a/signal-composer-binding/signal-composer-binding.hpp b/signal-composer-binding/signal-composer-binding.hpp index 3b512f3..7bf1df1 100644 --- a/signal-composer-binding/signal-composer-binding.hpp +++ b/signal-composer-binding/signal-composer-binding.hpp @@ -13,6 +13,7 @@ extern "C" }; #endif - void onEvent(const char *event, struct json_object *object); - int init_service(); - int ticked(sd_event_source *source, uint64_t t, void *data); +void onEvent(const char *event, struct json_object *object); +int loadConf(); +int execConf(); +int ticked(sd_event_source *source, uint64_t t, void *data); diff --git a/signal-composer-binding/signal-conf.cpp b/signal-composer-binding/signal-conf.cpp new file mode 100644 index 0000000..73f2450 --- /dev/null +++ b/signal-composer-binding/signal-conf.cpp @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2015, 2016 "IoT.bzh" + * Author "Romain Forlot" <romain.forlot@iot.bzh> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ diff --git a/signal-composer-binding/signal-conf.hpp b/signal-composer-binding/signal-conf.hpp new file mode 100644 index 0000000..3698135 --- /dev/null +++ b/signal-composer-binding/signal-conf.hpp @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2015, 2016 "IoT.bzh" + * Author "Romain Forlot" <romain.forlot@iot.bzh> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <json.hpp> + + class signalConfiguration +{ +private: + nlohmann::json config_; + +public: + void parseConfig(std::string configPath); +} diff --git a/signal-composer-binding/signal.hpp b/signal-composer-binding/signal.hpp new file mode 100644 index 0000000..94878c9 --- /dev/null +++ b/signal-composer-binding/signal.hpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2015, 2016 "IoT.bzh" + * Author "Romain Forlot" <romain.forlot@iot.bzh> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#pragma once + +#include <string> +#include <memory> +#include <vector> + +class Signal; + +class Signal +{ +private: + std::string api_; + std::string name_; + std::vector<> history_; + float frequency_; + st::string unit_; + float min_; + float max_; + float last_; + + std::vector<std::shared_ptr<Signal>> Observers_; + +public: + void notify(); + void infiniteRecursionCheck(); + void attach(); + void detach(); + void notify(); +} |