/*
* 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 *uid;
} AutoTestCtxT;
STATIC int TimerNext (sd_event_source* source, uint64_t timer, void* handle) {
TimerHandleT *timerHandle = (TimerHandleT*) handle;
int done;
uint64_t usec;
done= timerHandle->callback(timerHandle);
if (!done) goto OnErrorExit;
// Rearm timer if needed
timerHandle->count --;
if (timerHandle->count == 0) {
sd_event_source_unref(source);
if (timerHandle->freeCB) timerHandle->freeCB(timerHandle->context);
free (handle);
return 0;
}
else {
// otherwise validate timer for a new run
sd_event_now(AFB_GetEventLoop(timerHandle->api), CLOCK_MONOTONIC, &usec);
sd_event_source_set_enabled(source, SD_EVENT_ONESHOT);
sd_event_source_set_time(source, usec + timerHandle->delay*1000);
}
return 0;
OnErrorExit:
AFB_ApiWarning(timerHandle->api, "TimerNext Callback Fail Tag=%s", timerHandle->uid);
return -1;
}
PUBLIC void TimerEvtStop(TimerHandleT *timerHandle) {
sd_event_source_unref(timerHandle->evtSource);
free (timerHandle);
}
PUBLIC void TimerEvtStart(AFB_ApiT apiHandle, TimerHandleT *timerHandle, timerCallbackT callback, void *context) {
uint64_t usec;
// populate CB handle
timerHandle->callback=callback;
timerHandle->context=context;
timerHandle->api=apiHandle;
// set a timer with ~250us accuracy
sd_event_now(AFB_GetEventLoop(apiHandle), CLOCK_MONOTONIC, &usec);
sd_event_add_time(AFB_GetEventLoop(apiHandle), &timerHandle->evtSource, CLOCK_MONOTONIC, usec+timerHandle->delay*1000, 250, TimerNext, timerHandle);
}
// Create Binding Event at Init
PUBLIC int TimerEvtInit (AFB_ApiT apiHandle) {
AFB_ApiDebug (apiHandle, "Timer-Init Done");
return/*
* 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 <string.h>
#include <fnmatch.h>
#include "signal-composer.hpp"
extern "C" void setSignalValueHandle(const char* aName, long long int timestamp, struct SignalValue value)
{
std::shared_ptr<Signal> 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;
}
std::vector<std::string> bindingApp::parseURI(const std::string& uri)
{
std::vector<std::string> 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,
"args", functionArgsJ);
}
else if(startsWith(function, "api://"))
{
std::string uri = std::string(function).substr(6);
std::vector<std::string> 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://<plugin-name>/<function-name>");
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<std::string> 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://<plugin-name>/<function-name>");
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();
json_object *sigCompJ = nullptr;
// add the signal composer itself as source
if(sourcesJ)
{
wrap_json_pack(&sigCompJ, "{ss,ss}",
"api", "signal-composer",
"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;
}
}
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<std::string> 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: <api>/<event>");
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<Signal> 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<std::shared_ptr<Signal>> allSignals = getAllSignals();
for (std::shared_ptr<Signal>& sig : allSignals)
{
if(*sig == aName)
{return sig;}
}
}
return nullptr;
}
std::vector<std::shared_ptr<Signal>> bindingApp::getAllSignals()
{
std::vector<std::shared_ptr<Signal>> allSignals;
for( auto& source : sourcesListV_)
{
std::vector<std::shared_ptr<Signal>> 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<Signal> 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;
}