diff options
Diffstat (limited to 'signal-composer-binding')
-rw-r--r-- | signal-composer-binding/CMakeLists.txt | 41 | ||||
-rw-r--r-- | signal-composer-binding/ctl-lua.c | 1071 | ||||
-rw-r--r-- | signal-composer-binding/ctl-lua.h | 75 | ||||
-rw-r--r-- | signal-composer-binding/signal-composer-apidef.h | 95 | ||||
-rw-r--r-- | signal-composer-binding/signal-composer-apidef.json | 148 | ||||
-rw-r--r-- | signal-composer-binding/signal-composer-binding.cpp | 90 | ||||
-rw-r--r-- | signal-composer-binding/signal-composer-binding.hpp | 18 | ||||
-rw-r--r-- | signal-composer-binding/signal-composer.cpp | 16 | ||||
-rw-r--r-- | signal-composer-binding/signal-composer.hpp | 72 |
9 files changed, 1626 insertions, 0 deletions
diff --git a/signal-composer-binding/CMakeLists.txt b/signal-composer-binding/CMakeLists.txt new file mode 100644 index 0000000..52f8418 --- /dev/null +++ b/signal-composer-binding/CMakeLists.txt @@ -0,0 +1,41 @@ +########################################################################### +# Copyright 2015, 2016, 2017 IoT.bzh +# +# author: Fulup Ar Foll <fulup@iot.bzh> +# contrib: 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. +########################################################################### + +# Add target to project dependency list +PROJECT_TARGET_ADD(signal-composer) + + # Define project Targets + add_library(${TARGET_NAME} MODULE ${TARGET_NAME}-binding.cpp ${TARGET_NAME}.cpp ctl-lua.c) + + # Binder exposes a unique public entry point + SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES + PREFIX "afb-" + LABELS "BINDINGV2" + LINK_FLAGS ${BINDINGS_LINK_FLAG} + OUTPUT_NAME ${TARGET_NAME} + ) + + + TARGET_INCLUDE_DIRECTORIES(${TARGET_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + + # Library dependencies (include updates automatically) + TARGET_LINK_LIBRARIES(${TARGET_NAME} + afb-utilities + ${link_libraries} + ) diff --git a/signal-composer-binding/ctl-lua.c b/signal-composer-binding/ctl-lua.c new file mode 100644 index 0000000..e8e48fa --- /dev/null +++ b/signal-composer-binding/ctl-lua.c @@ -0,0 +1,1071 @@ +/* + * 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 <unistd.h> +#include <sys/stat.h> +#include <string.h> +#include <filescan-utils.h> +#include <wrap-json.h> + +#include "ctl-lua.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 + +#define CTX_MAGIC 123456789 +#define CTX_TOKEN "AFB_ctx" + +static afb_req NULL_AFBREQ = {}; + +static lua_State* luaState; + +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 +int Lua2cWrapper(lua_State* luaState, char *funcname, Lua2cFunctionT callback, void *context) { + + json_object *argsJ= LuaPopArgs(luaState, LUA_FIST_ARG+1); + int response = (*callback) (funcname, argsJ, context); + + // push response to LUA + lua_pushinteger(luaState, response); + return 1; +} + +// Generated some fake event based on watchdog/counter +int LuaCallFunc (DispatchSourceT source, DispatchActionT *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_pushinteger(luaState, source); + + // 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; + BPaths BindingPaths = GetBindingDirsPath(); + + // 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)-strlen(luaScriptPath)-1); + strncat(luaScriptPath,target, sizeof(luaScriptPath)-strlen(luaScriptPath)-1); + luaScriptPathJ= ScanForConfig(BindingPaths.etcdir, 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)-strlen(luaScriptPath)-1); + strncat (luaScriptPath, filename, sizeof(luaScriptPath)-strlen(luaScriptPath)-1); + } + } + + 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)-strlen(luaScriptPath)-1); + 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; +} + +void ctlapi_execlua (afb_req request) { + LuaDoAction (LUA_DOSTRING, request); +} + +void ctlapi_request (afb_req request) { + LuaDoAction (LUA_DOCALL, request); +} + +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 +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 */ +}; + +// Create Binding Event at Init +int LuaLibInit () { + int err, index; + + // search for default policy config file + char fullprefix[CONTROL_MAXPATH_LEN]; + 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; + + json_object *luaScriptPathJ = ScanForConfig(dirList , CTL_SCAN_RECURSIVE, fullprefix, "lua"); + + // 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"); + + // 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;; + } + + // 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)-strlen(filepath)-1); + strncat(filepath, filename, sizeof(filepath)-strlen(filepath)-1); + 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/signal-composer-binding/ctl-lua.h b/signal-composer-binding/ctl-lua.h new file mode 100644 index 0000000..b5b1414 --- /dev/null +++ b/signal-composer-binding/ctl-lua.h @@ -0,0 +1,75 @@ +#ifdef SUSE_LUA_INCDIR +#include <lua5.3/lua.h> +#include <lua5.3/lauxlib.h> +#include <lua5.3/lualib.h> +#else +#include <lua.h> +#include <lauxlib.h> +#include <lualib.h> +#endif + +#include <json-c/json.h> + +#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, + LUA_DOSCRIPT, +} LuaDoActionT; + +typedef int (*Lua2cFunctionT)(char *funcname, json_object *argsJ, void*context); diff --git a/signal-composer-binding/signal-composer-apidef.h b/signal-composer-binding/signal-composer-apidef.h new file mode 100644 index 0000000..f986692 --- /dev/null +++ b/signal-composer-binding/signal-composer-apidef.h @@ -0,0 +1,95 @@ + +static const char _afb_description_v2_signals_composer[] = + "{\"openapi\":\"3.0.0\",\"$schema\":\"http:iot.bzh/download/openapi/schem" + "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\"}}}}}" +; + + void subscribe(struct afb_req req); + void unsubscribe(struct afb_req req); + void get(struct afb_req req); + void load(struct afb_req req); + +static const struct afb_verb_v2 _afb_verbs_v2_signals_composer[] = { + { + .verb = "subscribe", + .callback = subscribe, + .auth = NULL, + .info = "Subscribe to a signal object", + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "unsubscribe", + .callback = unsubscribe, + .auth = NULL, + .info = "Unsubscribe previously suscribed signal objects.", + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "get", + .callback = get, + .auth = NULL, + .info = "Get informations about a resource or element", + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "load", + .callback = load, + .auth = NULL, + .info = "Load config file in directory passed as argument searching for pattern 'sig' in filename", + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = NULL, + .callback = NULL, + .auth = NULL, + .info = NULL, + .session = 0 + } +}; + +const struct afb_binding_v2 afbBindingV2 = { + .api = "signals-composer", + .specification = _afb_description_v2_signals_composer, + .info = "", + .verbs = _afb_verbs_v2_signals_composer, + .preinit = NULL, + .init = init_service, + .onevent = onEvent, + .noconcurrency = 0 +}; + diff --git a/signal-composer-binding/signal-composer-apidef.json b/signal-composer-binding/signal-composer-apidef.json new file mode 100644 index 0000000..78080f1 --- /dev/null +++ b/signal-composer-binding/signal-composer-apidef.json @@ -0,0 +1,148 @@ +{ + "openapi": "3.0.0", + "$schema": "http:iot.bzh/download/openapi/schema-3.0/default-schema.json", + "info": { + "description": "", + "title": "signals-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}:{port}/api/monitor", + "description": "Signals composer API connected to low level AGL services", + "variables": { + "host": { + "default": "localhost" + }, + "port": { + "default": "1234" + } + }, + "x-afb-events": [ + { + "$ref": "#/components/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": "Generic response.", + "type": "object", + "required": [ "jtype", "request" ], + "properties": { + "jtype": { + "type": "string", + "const": "afb-reply" + }, + "request": { + "type": "object", + "required": [ "status" ], + "properties": { + "status": { "type": "string" }, + "info": { "type": "string" }, + "token": { "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": { "type": "string" }, + "data": { "type": "object" } + } + } + }, + "x-permissions": { + }, + "responses": { + "200": { + "description": "A complex object array response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/afb-reply" + } + } + } + } + } + }, + "paths": { + "/subscribe": { + "description": "Subscribe to a signal object", + "parameters": [ + { + "in": "query", + "name": "event", + "required": false, + "schema": { "type": "string" } + } + ], + "responses": { + "200": {"$ref": "#/components/responses/200"} + } + }, + "/unsubscribe": { + "description": "Unsubscribe previously suscribed signal objects.", + "parameters": [ + { + "in": "query", + "name": "event", + "required": false, + "schema": { "type": "string" } + } + ], + "responses": { + "200": {"$ref": "#/components/responses/200"} + } + }, + "/get": { + "description": "Get informations about a resource or element", + "responses": { + "200": {"$ref": "#/components/responses/200"} + } + }, + "/load": { + "description": "Load config file in directory passed as argument searching for pattern 'sig' in filename", + "parameters": [ + { + "in": "query", + "name": "path", + "required": true, + "schema": { "type": "string"} + } + ], + "responses": { + "200": {"$ref": "#/components/responses/200"} + } + } + } +} diff --git a/signal-composer-binding/signal-composer-binding.cpp b/signal-composer-binding/signal-composer-binding.cpp new file mode 100644 index 0000000..24b81f6 --- /dev/null +++ b/signal-composer-binding/signal-composer-binding.cpp @@ -0,0 +1,90 @@ +/* + * 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 "signal-composer-binding.hpp" +#include "signal-composer-apidef.h" +#include "wrap-json.h" +#include "signal-composer.hpp" + +SignalComposer SigComp; + +/// @brief callback for receiving message from low binding. Treatment itself is made in SigComp class. +void onEvent(const char *event, json_object *object) +{ + SigComp.treatMessage(object); +} +/// @brief entry point for client subscription request. Treatment itself is made in SigComp class. +void subscribe(afb_req request) +{ + if(SigComp.subscribe(request)) + afb_req_success(request, NULL, NULL); + else + afb_req_fail(request, "error", NULL); +} + +/// @brief entry point for client un-subscription request. Treatment itself is made in SigComp class. +void unsubscribe(afb_req request) +{ + if(SigComp.unsubscribe(request)) + afb_req_success(request, NULL, NULL); + else + afb_req_fail(request, "error when unsubscribe", NULL); +} + +/// @brief verb that loads JSON configuration (old SigComp.json file now) +void load(afb_req request) +{ + json_object* args = afb_req_json(request); + const char* confd; + + wrap_json_unpack(args, "{s:s}", "path", &confd); + int ret = SigComp.parseConfigAndSubscribe(confd); + if( ret == 0) + afb_req_success(request, NULL, NULL); + else + afb_req_fail_f(request, "Loading configuration or subscription error", "Error code: %d", ret); +} + +/// @brief entry point for get requests. Treatment itself is made in SigComp class. +void get(afb_req request) +{ + json_object *jobj; + if(SigComp.get(request, &jobj)) + { + afb_req_success(request, jobj, NULL); + } else { + afb_req_fail(request, "error", NULL); + } +} + +/// @brief entry point for systemD timers. Treatment itself is made in SigComp class. +/// @param[in] source: systemD timer, t: time of tick, data: interval (ms). +int ticked(sd_event_source *source, uint64_t t, void* data) +{ + SigComp.tick(source, t, data); + return 0; +} + +/// @brief Initialize the binding. +/// +/// @return Exit code, zero if success. +int init_service() +{ + AFB_DEBUG("SigComp level binding is initializing"); + AFB_NOTICE("SigComp level binding is initialized and running"); + return 0; +} diff --git a/signal-composer-binding/signal-composer-binding.hpp b/signal-composer-binding/signal-composer-binding.hpp new file mode 100644 index 0000000..3b512f3 --- /dev/null +++ b/signal-composer-binding/signal-composer-binding.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include <systemd/sd-event.h> + +#ifdef __cplusplus + #include <cstddef> +extern "C" +{ +#endif + #define AFB_BINDING_VERSION 2 + #include <afb/afb-binding.h> +#ifdef __cplusplus +}; +#endif + + void onEvent(const char *event, struct json_object *object); + int init_service(); + int ticked(sd_event_source *source, uint64_t t, void *data); diff --git a/signal-composer-binding/signal-composer.cpp b/signal-composer-binding/signal-composer.cpp new file mode 100644 index 0000000..73f2450 --- /dev/null +++ b/signal-composer-binding/signal-composer.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-composer.hpp b/signal-composer-binding/signal-composer.hpp new file mode 100644 index 0000000..bcfe60f --- /dev/null +++ b/signal-composer-binding/signal-composer.hpp @@ -0,0 +1,72 @@ +/* + * 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 <cstddef> + #include <string> + #include <vector> + #include <set> + #include <map> + #include <json-c/json.h> + #include <systemd/sd-event.h> + extern "C" + { + #define AFB_BINDING_VERSION 2 + #include <afb/afb-binding.h> + }; + + struct TimedEvent { + int interval; + afb_event event; + std::string name; + std::string eventName; + }; + + struct Signal { + std::string name; + std::string source; + std::string sig_class; + std::string type; + }; + + class SignalComposer + { + public: + SignalComposer(); + void treatMessage(json_object *message); + bool subscribe(afb_req request); + bool unsubscribe(afb_req request); + bool get(afb_req request, json_object **json); + void tick(sd_event_source *source, const long &now, void *interv); + void startTimer(const int &t); + ~SignalComposer(); + int parseConfigAndSubscribe(const std::string& confd); + static bool startsWith(const std::string &s, const std::string &val); + static void callBackFromSubscribe(void *handle, int iserror, json_object *result); + private: + std::map<std::string, afb_event> events; + std::map<int, std::vector<TimedEvent>> timedEvents; + std::map<std::string, std::map<std::string, Signal>> registeredObjects; + std::map<std::string, std::set<std::string>> lowMessagesToObjects; + std::set<int> timers; + std::string generateId() const; + json_object *generateJson(const std::string &messageObject, std::vector<std::string> *fields = nullptr); + void registerObjects(const std::string& uri, std::map<std::string, Signal>& properties); + std::map<std::string, std::map<std::string, Signal>> loadDefinitions(json_object* definitionsJ) const; + void loadResources(json_object* resourcesJ, std::map<std::string, std::map<std::string, Signal>>& properties); + int subscribeRegisteredObjects() const; + }; |