/* * Copyright (C) 2015, 2016 "IoT.bzh" * Author "Romain Forlot" * * 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 #include #include "signal-composer.hpp" extern "C" void setSignalValueHandle(const char* aName, long long int timestamp, struct SignalValue value) { std::shared_ptr sig = bindingApp::instance().searchSignal(aName); if(sig) {sig->set(timestamp, value);} } bool startsWith(const std::string& str, const std::string& pattern) { size_t sep; if( (sep = str.find(pattern)) != std::string::npos && !sep) {return true;} return false; } static struct pluginCBT pluginHandle = { .setSignalValue = setSignalValueHandle, }; CtlSectionT bindingApp::ctlSections_[] = { [0]={.key="plugins" , .label = "plugins", .info=nullptr, .loadCB=PluginConfig, .handle=&pluginHandle}, [1]={.key="sources" , .label = "sources", .info=nullptr, .loadCB=loadSourcesAPI, .handle=nullptr}, [2]={.key="signals" , .label= "signals", .info=nullptr, .loadCB=loadSignals, .handle=nullptr}, [3]={.key=nullptr, .label=nullptr, .info=nullptr, .loadCB=nullptr, .handle=nullptr} }; bindingApp::bindingApp() {} bindingApp::~bindingApp() { free(ctlConfig_); } int bindingApp::loadConfig(const std::string& filepath) { ctlConfig_ = CtlConfigLoad(filepath.c_str(), ctlSections_); if(ctlConfig_ != nullptr) {return 0;} return -1; } bindingApp& bindingApp::instance() { static bindingApp bApp; return bApp; } SourceAPI* bindingApp::getSourceAPI(const std::string& api) { for(auto& source: sourcesListV_) { if (source.api() == api) {return &source;} } return nullptr; } int bindingApp::initSourcesAPI() { int err = 0; for(auto& src: sourcesListV_) { err += src.init(); } return err; } std::vector bindingApp::parseURI(const std::string& uri) { std::vector uriV; std::string delimiters = "/"; std::string::size_type start = 0; auto pos = uri.find_first_of(delimiters, start); while(pos != std::string::npos) { if(pos != start) // ignore empty tokens uriV.emplace_back(uri, start, pos - start); start = pos + 1; pos = uri.find_first_of(delimiters, start); } if(start < uri.length()) // ignore trailing delimiter uriV.emplace_back(uri, start, uri.length() - start); // add what's left of the string return uriV; } CtlActionT* bindingApp::convert2Action(const std::string& name, json_object* actionJ) { json_object *functionArgsJ = nullptr, *action = nullptr; char *function; const char *plugin; if(actionJ && !wrap_json_unpack(actionJ, "{ss,s?s,s?o !}", "function", &function, "plugin", &plugin, "args", &functionArgsJ)) { action = nullptr; if(startsWith(function, "lua://")) { std::string fName = std::string(function).substr(6); wrap_json_pack(&action, "{ss,ss,so*}", "label", name.c_str(), "lua", fName.c_str(), "args", functionArgsJ); } else if(startsWith(function, "api://")) { std::string uri = std::string(function).substr(6); std::vector uriV = bindingApp::parseURI(uri); if(uriV.size() != 2) { AFB_ERROR("Miss something in uri either plugin name or function name. Uri has to be like: api:///"); return nullptr; } wrap_json_pack(&action, "{ss,ss,ss,so*}", "label", name.c_str(), "api", uriV[0].c_str(), "verb", uriV[1].c_str(), "args", functionArgsJ); } else if(startsWith(function, "plugin://")) { std::string uri = std::string(function).substr(9); std::vector uriV = bindingApp::parseURI(uri); if(uriV.size() != 2) { AFB_ERROR("Miss something in uri either plugin name or function name. Uri has to be like: plugin:///"); return nullptr; } json_object *callbackJ = nullptr; wrap_json_pack(&callbackJ, "{ss,ss,so*}", "plugin", uriV[0].c_str(), "function", uriV[1].c_str(), "args", functionArgsJ); wrap_json_pack(&action, "{ss,so}", "label", name.c_str(), "callback", callbackJ); } else { AFB_ERROR("Wrong function uri specified. You have to specified 'lua://', 'plugin://' or 'api://'. (%s)", function); return nullptr; } } if(action) {return ActionLoad(action);} return nullptr; } int bindingApp::loadOneSourceAPI(json_object* sourceJ) { json_object *initJ = nullptr, *getSignalsJ = nullptr; CtlActionT *initCtl = nullptr, *getSignalsCtl = nullptr; const char *api, *info; int err = wrap_json_unpack(sourceJ, "{ss,s?s,s?o,s?o !}", "api", &api, "info", &info, "init", &initJ, "getSignals", &getSignalsJ); if (err) { AFB_ERROR("Missing something api|[info]|[init]|[getSignals] in %s", json_object_get_string(sourceJ)); return err; } if(ctlConfig_ && ctlConfig_->requireJ) { const char* requireS = json_object_to_json_string(ctlConfig_->requireJ); if(!strcasestr(requireS, api)) {AFB_WARNING("Caution! You don't specify the API source as required in the metadata section. This API '%s' may not be initialized", api);} } if(initJ) {initCtl = convert2Action("init", initJ);} if(getSignalsJ) {getSignalsCtl = convert2Action("getSignals", getSignalsJ);} sourcesListV_.push_back(SourceAPI(api, info, initCtl, getSignalsCtl)); return err; } int bindingApp::loadSourcesAPI(CtlSectionT* section, json_object *sourcesJ) { int err = 0; bindingApp& bApp = instance(); if(sourcesJ) { json_object *sigCompJ = nullptr; // add the signal composer itself as source wrap_json_pack(&sigCompJ, "{ss,ss}", "api", afbBindingV2.api, "info", "Api on behalf the virtual signals are sent"); json_object_array_add(sourcesJ, sigCompJ); if (json_object_get_type(sourcesJ) == json_type_array) { int count = json_object_array_length(sourcesJ); for (int idx = 0; idx < count; idx++) { json_object *sourceJ = json_object_array_get_idx(sourcesJ, idx); err = bApp.loadOneSourceAPI(sourceJ); if (err) return err; } } else { if ((err = bApp.loadOneSourceAPI(sourcesJ))) return err; if (sigCompJ && (err = bApp.loadOneSourceAPI(sigCompJ))) return err; } } else {err += bindingApp::instance().initSourcesAPI();} return err; } int bindingApp::loadOneSignal(json_object* signalJ) { json_object *onReceivedJ = nullptr, *dependsJ = nullptr, *getSignalsArgs = nullptr; CtlActionT* onReceivedCtl; const char *id = nullptr, *event = nullptr, *sClass = nullptr, *unit = nullptr; double frequency=0.0; std::string api; std::vector dependsV; ssize_t sep; int err = wrap_json_unpack(signalJ, "{ss,s?s,s?o,s?o,s?s,s?s,s?F,s?o !}", "id", &id, "event", &event, "depends", &dependsJ, "getSignalsArgs", &getSignalsArgs, "class", &sClass, "unit", &unit, "frequency", &frequency, "onReceived", &onReceivedJ); if (err) { AFB_ERROR("Missing something id|[event|depends]|[getSignalsArgs]|[class]|[unit]|[frequency]|[onReceived] in %s", json_object_get_string(signalJ)); return err; } // Set default sClass is not specified sClass = !sClass ? "state" : sClass; unit = !unit ? "" : unit; // Get an action handler onReceivedCtl = onReceivedJ ? convert2Action("onReceived", onReceivedJ) : nullptr; // event or depends field manadatory if( (!event && !dependsJ) || (event && dependsJ) ) { AFB_ERROR("Missing something id|[event|depends]|[getSignalsArgs]|[class]|[unit]|[frequency]|[onReceived] in %s. Or you declare event AND depends, only one of them is needed.", json_object_get_string(signalJ)); return -1; } // Process depends JSON object to declare virtual signal dependencies if (dependsJ) { if(json_object_get_type(dependsJ) == json_type_array) { int count = json_object_array_length(dependsJ); for(int i = 0; i < count; i++) { std::string sourceStr = json_object_get_string(json_object_array_get_idx(dependsJ, i)); if( (sep = sourceStr.find("/")) != std::string::npos) { AFB_ERROR("Signal composition needs to use signal 'id', don't use full low level signal name"); return -1; } dependsV.push_back(sourceStr); } api = sourcesListV_.rbegin()->api(); } else { std::string sourceStr = json_object_get_string(dependsJ); if( (sep = sourceStr.find("/")) != std::string::npos) { AFB_ERROR("Signal composition needs to use signal 'id', don't use full low level signal name"); return -1; } dependsV.push_back(sourceStr); api = sourcesListV_.rbegin()->api(); } } // Declare a raw signal if(event) { std::string eventStr = std::string(event); if( (sep = eventStr.find("/")) == std::string::npos) { AFB_ERROR("Missing something in event declaration. Has to be like: /"); return -1; } api = eventStr.substr(0, sep); } else { event = ""; } SourceAPI* src = getSourceAPI(api) ? getSourceAPI(api):getSourceAPI("signal-composer"); if(src != nullptr) {src->addSignal(id, event, dependsV, sClass, unit, frequency, onReceivedCtl, getSignalsArgs);} else {err = -1;} return err; } int bindingApp::loadSignals(CtlSectionT* section, json_object *signalsJ) { int err = 0; bindingApp& bApp = instance(); if(signalsJ) { if (json_object_get_type(signalsJ) == json_type_array) { int count = json_object_array_length(signalsJ); for (int idx = 0; idx < count; idx++) { json_object *signalJ = json_object_array_get_idx(signalsJ, idx); err = bApp.loadOneSignal(signalJ); if (err) return err; } } else {err = bApp.loadOneSignal(signalsJ);} } return err; } int bindingApp::loadSignals(json_object* signalsJ) { return loadSignals(nullptr, signalsJ); } std::shared_ptr bindingApp::searchSignal(const std::string& aName) { std::string api; size_t sep = aName.find_first_of("/"); if(sep != std::string::npos) { api = aName.substr(0, sep); SourceAPI* source = getSourceAPI(api); return source->searchSignal(aName); } else { std::vector> allSignals = getAllSignals(); for (std::shared_ptr& sig : allSignals) { if(*sig == aName) {return sig;} } } return nullptr; } std::vector> bindingApp::getAllSignals() { std::vector> allSignals; for( auto& source : sourcesListV_) { std::vector> srcSignals = source.getSignals(); allSignals.insert(allSignals.end(), srcSignals.begin(), srcSignals.end()); } return allSignals; } CtlConfigT* bindingApp::ctlConfig() { return ctlConfig_; } int bindingApp::execSubscription() { int err = 0; for(SourceAPI& srcAPI: sourcesListV_) { if (srcAPI.api() != std::string(ctlConfig_->api)) { err = srcAPI.makeSubscription(); } } return err; } json_object* bindingApp::getSignalValue(const std::string& sig, json_object* options) { const char **opts = nullptr; json_object *response = nullptr; wrap_json_unpack(options, "{s?s?s?s?!}", &opts[0], &opts[1], &opts[2], &opts[3]); std::shared_ptr sigP = searchSignal(sig); wrap_json_pack(&response, "{ss}", "signal", sigP->id().c_str()); for(int idx=0; idx < sizeof(opts); idx++) { bool avg = false, min = false, max = false, last = false; if (strcasestr(opts[idx], "average") && !avg) { avg = true; double value = sigP->average(); json_object_object_add(response, "average", json_object_new_double(value)); } if (strcasestr(opts[idx], "min") && !min) { min = true; double value = sigP->minimum(); json_object_object_add(response, "min", json_object_new_double(value)); } if (strcasestr(opts[idx], "max") && !max) { max = true; double value = sigP->maximum(); json_object_object_add(response, "max", json_object_new_double(value)); } if (strcasestr(opts[idx], "last") && !last) { last = true; struct SignalValue value = sigP->last(); if(value.hasBool) { json_object_object_add(response, "last", json_object_new_boolean(value.boolVal)); } if(value.hasNum) { json_object_object_add(response, "last", json_object_new_double(value.numVal)); } if(value.hasStr) { json_object_object_add(response, "last", json_object_new_string(value.strVal.c_str())); } } } if (!opts) { struct SignalValue value = sigP->last(); if(value.hasBool) { json_object_object_add(response, "last", json_object_new_boolean(value.boolVal)); } if(value.hasNum) { json_object_object_add(response, "last", json_object_new_double(value.numVal)); } if(value.hasStr) { json_object_object_add(response, "last", json_object_new_string(value.strVal.c_str())); } } return response; }