summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRomain Forlot <romain.forlot@iot.bzh>2017-09-17 19:20:40 +0200
committerRomain Forlot <romain.forlot@iot.bzh>2017-12-14 11:00:25 +0100
commit12097251ec058b1fa9d9202998c829b27ee5554f (patch)
tree415d2592913bfa0b980232ffbee09d6cfb48fd02
parent5bbc6cb56995d23cb8a4eb584ef0161be092da1f (diff)
Get CPP controller plugin works
Context passing variables not working well Change-Id: Ibc67bef353e5cc2e53ef9e5579d106baab92a604 Signed-off-by: Romain Forlot <romain.forlot@iot.bzh>
-rw-r--r--conf.d/project/etc/sig-doors.json72
-rw-r--r--controller/ctl-plugin.c6
-rw-r--r--controller/ctl-plugin.h4
-rw-r--r--plugins/CMakeLists.txt72
-rw-r--r--plugins/low-can.c2
-rw-r--r--plugins/low-can.cpp175
-rw-r--r--signal-composer-binding/signal-composer.cpp40
-rw-r--r--signal-composer-binding/signal.cpp72
-rw-r--r--signal-composer-binding/signal.hpp19
9 files changed, 395 insertions, 67 deletions
diff --git a/conf.d/project/etc/sig-doors.json b/conf.d/project/etc/sig-doors.json
index 1859dbe..ea191e4 100644
--- a/conf.d/project/etc/sig-doors.json
+++ b/conf.d/project/etc/sig-doors.json
@@ -2,35 +2,91 @@
"signals": [
{
"id": "rear_left_window",
- "source": "low-can/messages.windows.rear_left.open"
+ "source": "low-can/messages.windows.rear_left.open",
+ "onReceived": {
+ "plugin": "low-can-callbacks",
+ "function": "isOpen",
+ "args": {
+ "ojoi": "pok"
+ }
+ }
},
{
"id": "rear_left_door",
- "source": "low-can/messages.doors.rear_left.open"
+ "source": "low-can/messages.doors.rear_left.open",
+ "onReceived": {
+ "plugin": "low-can-callbacks",
+ "function": "isOpen",
+ "args": {
+ "ojoi": "pok"
+ }
+ }
},
{
"id": "rear_right_window",
- "source": "low-can/messages.windows.rear_right.open"
+ "source": "low-can/messages.windows.rear_right.open",
+ "onReceived": {
+ "plugin": "low-can-callbacks",
+ "function": "isOpen",
+ "args": {
+ "ojoi": "pok"
+ }
+ }
},
{
"id": "rear_right_door",
- "source": "low-can/messages.doors.rear_right.open"
+ "source": "low-can/messages.doors.rear_right.open",
+ "onReceived": {
+ "plugin": "low-can-callbacks",
+ "function": "isOpen",
+ "args": {
+ "ojoi": "pok"
+ }
+ }
},
{
"id": "front_left_window",
- "source": "low-can/messages.windows.front_left.open"
+ "source": "low-can/messages.windows.front_left.open",
+ "onReceived": {
+ "plugin": "low-can-callbacks",
+ "function": "isOpen",
+ "args": {
+ "ojoi": "pok"
+ }
+ }
},
{
"id": "front_left_door",
- "source": "low-can/messages.doors.front_left.open"
+ "source": "low-can/messages.doors.front_left.open",
+ "onReceived": {
+ "plugin": "low-can-callbacks",
+ "function": "isOpen",
+ "args": {
+ "ojoi": "pok"
+ }
+ }
},
{
"id": "front_right_window",
- "source": "low-can/messages.windows.front_right.open"
+ "source": "low-can/messages.windows.front_right.open",
+ "onReceived": {
+ "plugin": "low-can-callbacks",
+ "function": "isOpen",
+ "args": {
+ "ojoi": "pok"
+ }
+ }
},
{
"id": "front_right_door",
- "source": "low-can/messages.doors.front_right.open"
+ "source": "low-can/messages.doors.front_right.open",
+ "onReceived": {
+ "plugin": "low-can-callbacks",
+ "function": "isOpen",
+ "args": {
+ "ojoi": "pok"
+ }
+ }
},
{
"id": "rear_left",
diff --git a/controller/ctl-plugin.c b/controller/ctl-plugin.c
index b14d591..69f0f09 100644
--- a/controller/ctl-plugin.c
+++ b/controller/ctl-plugin.c
@@ -21,6 +21,9 @@
#define _GNU_SOURCE
#include <string.h>
#include <dlfcn.h>
+#include <link.h>
+#include <stdio.h>
+#include <stdlib.h>
#ifdef CONTROL_SUPPORT_LUA
#include "ctl-lua.h"
@@ -202,7 +205,8 @@ STATIC int PluginLoadOne (CtlPluginT *ctlPlugin, json_object *pluginJ, void* han
}
}
#endif
- DispatchPluginInstallCbT ctlPluginOnload = dlsym(dlHandle, "CtlPluginOnload");
+
+DispatchPluginInstallCbT ctlPluginOnload = dlsym(dlHandle, "CtlPluginOnload");
if (ctlPluginOnload) {
ctlPlugin->context = (*ctlPluginOnload) (ctlPlugin, handle);
}
diff --git a/controller/ctl-plugin.h b/controller/ctl-plugin.h
index 7c1d0f1..1c772c7 100644
--- a/controller/ctl-plugin.h
+++ b/controller/ctl-plugin.h
@@ -49,7 +49,7 @@ typedef struct {
typedef struct {
long magic;
- char *label;
+ const char *label;
void *handle;
} CtlPluginMagicT;
@@ -66,7 +66,7 @@ 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_REGISTER(pluglabel) CtlPluginMagicT CtlPluginMagic={.magic=CTL_PLUGIN_MAGIC,.label=pluglabel,.handle=NULL}; 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)
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
index 2b21228..9145b57 100644
--- a/plugins/CMakeLists.txt
+++ b/plugins/CMakeLists.txt
@@ -7,7 +7,7 @@
# 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
+# 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,
@@ -18,42 +18,46 @@
PROJECT_TARGET_ADD(low-can)
- # Define targets
- ADD_LIBRARY(${TARGET_NAME} MODULE ${TARGET_NAME}.c)
+ # Define targets
+ ADD_LIBRARY(${TARGET_NAME} MODULE ${TARGET_NAME}.cpp)
- # Alsa Plugin properties
- SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES
- LABELS "PLUGIN"
- PREFIX ""
- SUFFIX ".ctlso"
- OUTPUT_NAME ${TARGET_NAME}
- )
+ # Alsa Plugin properties
+ SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES
+ LABELS "PLUGIN"
+ PREFIX ""
+ SUFFIX ".ctlso"
+ OUTPUT_NAME ${TARGET_NAME}
+ )
- # Library dependencies (include updates automatically)
- TARGET_LINK_LIBRARIES(${TARGET_NAME}
- afb-utilities
- ${link_libraries}
- )
+ # Library dependencies (include updates automatically)
+ TARGET_LINK_LIBRARIES(${TARGET_NAME}
+ afb-utilities
+ ${link_libraries}
+ )
- include_directories("../controller")
+ target_include_directories(${TARGET_NAME}
+ PRIVATE "../controller"
+ PRIVATE "../signal-composer-binding")
PROJECT_TARGET_ADD(gps)
- # Define targets
- ADD_LIBRARY(${TARGET_NAME} MODULE ${TARGET_NAME}.c)
-
- # Alsa Plugin properties
- SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES
- LABELS "PLUGIN"
- PREFIX ""
- SUFFIX ".ctlso"
- OUTPUT_NAME ${TARGET_NAME}
- )
-
- # Library dependencies (include updates automatically)
- TARGET_LINK_LIBRARIES(${TARGET_NAME}
- afb-utilities
- ${link_libraries}
- )
-
- include_directories("../controller")
+ # Define targets
+ ADD_LIBRARY(${TARGET_NAME} MODULE ${TARGET_NAME}.c)
+
+ # Alsa Plugin properties
+ SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES
+ LABELS "PLUGIN"
+ PREFIX ""
+ SUFFIX ".ctlso"
+ OUTPUT_NAME ${TARGET_NAME}
+ )
+
+ # Library dependencies (include updates automatically)
+ TARGET_LINK_LIBRARIES(${TARGET_NAME}
+ afb-utilities
+ ${link_libraries}
+ )
+
+ target_include_directories(${TARGET_NAME}
+ PRIVATE "../controller"
+ PRIVATE "../signal-composer-binding")
diff --git a/plugins/low-can.c b/plugins/low-can.c
index 1c2ad74..86f0f5c 100644
--- a/plugins/low-can.c
+++ b/plugins/low-can.c
@@ -68,7 +68,7 @@ CTLP_CAPI (subscribeToLow, source, argsJ, eventJ, context) {
return err;
}
- if(frequency >= 0)
+ if(frequency > 0)
{
wrap_json_pack(&subscribeFilterJ, "{sf}", "frequency", frequency);
}
diff --git a/plugins/low-can.cpp b/plugins/low-can.cpp
new file mode 100644
index 0000000..8479a47
--- /dev/null
+++ b/plugins/low-can.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 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.
+ *
+*/
+
+#define AFB_BINDING_VERSION 2
+#include <afb/afb-binding.h>
+#include <systemd/sd-event.h>
+#include <json-c/json_object.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include "ctl-plugin.h"
+#include "wrap-json.h"
+
+
+#include "signal-composer.hpp"
+
+extern "C"
+{
+
+CTLP_REGISTER("low-can");
+
+typedef struct {
+ bool door;
+ bool window;
+} doorT;
+
+typedef struct {
+ doorT front_left;
+ doorT front_right;
+ doorT rear_left;
+ doorT rear_right;
+} allDoorsCtxT;
+
+typedef struct {
+ bindingApp* bApp;
+ allDoorsCtxT allDoorsCtx;
+} lowCANCtxT;
+
+// Call at initialisation time
+CTLP_ONLOAD(plugin, bAppHandle)
+{
+ lowCANCtxT *pluginCtx= (lowCANCtxT*)calloc (1, sizeof(lowCANCtxT));
+ allDoorsCtxT allDoorsCtx = allDoorsCtxT();
+ ::memset(&allDoorsCtx, 0, sizeof(allDoorsCtxT));
+ pluginCtx->allDoorsCtx = allDoorsCtx;
+
+ pluginCtx->bApp = (bindingApp*)bAppHandle;
+
+ AFB_NOTICE ("Low-can plugin: label='%s' version='%s' info='%s'",
+ plugin->label,
+ plugin->version,
+ plugin->info);
+
+ return (void*)pluginCtx;
+}
+
+CTLP_CAPI (subscribeToLow, source, argsJ, eventJ, context) {
+ json_object* signalArrayJ = NULL, *subscribeArgsJ = NULL, *subscribeFilterJ = NULL, *responseJ = NULL;
+ const char* unit = NULL;
+ double frequency = 0;
+ int err = 0;
+
+ err = wrap_json_unpack(eventJ, "{so,s?s,s?F !}",
+ "signal", &signalArrayJ,
+ "unit", &unit,
+ "frequency", &frequency);
+ if(err)
+ {
+ AFB_ERROR("Problem to unpack JSON object eventJ: %s",
+ json_object_to_json_string(eventJ));
+ return err;
+ }
+
+ if(frequency > 0)
+ {
+ wrap_json_pack(&subscribeFilterJ, "{sf}", "frequency", frequency);
+ }
+
+ for (int idx = 0; idx < json_object_array_length(signalArrayJ); idx++)
+ {
+ json_object* aSignalJ = json_object_array_get_idx(signalArrayJ, idx);
+ err = wrap_json_pack(&subscribeArgsJ, "{ss, so*}",
+ "event", json_object_get_string(aSignalJ),
+ "filter", subscribeFilterJ);
+ if(err)
+ {
+ AFB_ERROR("Error building subscription query object");
+ return err;
+ }
+ AFB_DEBUG("Calling subscribe with %s", json_object_to_json_string_ext(subscribeArgsJ, JSON_C_TO_STRING_PRETTY));
+ err = afb_service_call_sync("low-can", "subscribe", subscribeArgsJ, &responseJ);
+ if(err)
+ {
+ AFB_ERROR("Can't find api 'low-can'");
+ return err;
+ }
+ }
+
+ return err;
+}
+
+CTLP_CAPI (isOpen, source, argsJ, eventJ, context) {
+ const char* eventName;
+ bool eventStatus;
+ double timestamp;
+ lowCANCtxT *pluginCtx=(lowCANCtxT*)context;
+
+ AFB_NOTICE("This is the situation: source:%s, args:%s, event:%s,\n fld: %s, flw: %s, frd: %s, frw: %s, rld: %s, rlw: %s, rrd: %s, rrw: %s",
+ source->label,
+ json_object_to_json_string(argsJ),
+ json_object_to_json_string(eventJ),
+ pluginCtx->allDoorsCtx.front_left.door ? "true":"false",
+ pluginCtx->allDoorsCtx.front_left.window ? "true":"false",
+ pluginCtx->allDoorsCtx.front_right.door ? "true":"false",
+ pluginCtx->allDoorsCtx.front_right.window ? "true":"false",
+ pluginCtx->allDoorsCtx.rear_left.door ? "true":"false",
+ pluginCtx->allDoorsCtx.rear_left.window ? "true":"false",
+ pluginCtx->allDoorsCtx.rear_right.door ? "true":"false",
+ pluginCtx->allDoorsCtx.rear_right.window ? "true":"false"
+ );
+
+ int err = wrap_json_unpack(eventJ, "{ss,s?b,s?F}",
+ "name", &eventName,
+ "value", &eventStatus,
+ "timestamp", &timestamp);
+ if(err)
+ {
+ AFB_ERROR("Error parsing event %s", json_object_to_json_string(eventJ));
+ return -1;
+ }
+
+ if(strcasestr(eventName, "front_left"))
+ {
+ if(strcasestr(eventName, "door")) {pluginCtx->allDoorsCtx.front_left.door = eventStatus;}
+ else if(strcasestr(eventName, "window")) {pluginCtx->allDoorsCtx.front_left.window = eventStatus;}
+ else {AFB_WARNING("Unexpected behavior, this '%s' is not a door ! ", json_object_to_json_string(eventJ));}
+ }
+ else if(strcasestr(eventName, "front_right"))
+ {
+ if(strcasestr(eventName, "door")) {pluginCtx->allDoorsCtx.front_right.door = eventStatus;}
+ else if(strcasestr(eventName, "window")) {pluginCtx->allDoorsCtx.front_right.window = eventStatus;}
+ else {AFB_WARNING("Unexpected behavior, this '%s' is not a door ! ", json_object_to_json_string(eventJ));}
+ }
+ else if(strcasestr(eventName, "rear_left"))
+ {
+ if(strcasestr(eventName, "door")) {pluginCtx->allDoorsCtx.rear_left.door = eventStatus;}
+ else if(strcasestr(eventName, "window")) {pluginCtx->allDoorsCtx.rear_left.window = eventStatus;}
+ else {AFB_WARNING("Unexpected behavior, this '%s' is not a door ! ", json_object_to_json_string(eventJ));}
+ }
+ else if(strcasestr(eventName, "rear_right"))
+ {
+ if(strcasestr(eventName, "door")) {pluginCtx->allDoorsCtx.rear_right.door = eventStatus;}
+ else if(strcasestr(eventName, "window")) {pluginCtx->allDoorsCtx.rear_right.window = eventStatus;}
+ else {AFB_WARNING("Unexpected behavior, this '%s' is not a door ! ", json_object_to_json_string(eventJ));}
+ }
+ else {AFB_WARNING("Unexpected behavior, this '%s' is not a door ! ", json_object_to_json_string(eventJ));}
+
+ return 0;
+}
+}
diff --git a/signal-composer-binding/signal-composer.cpp b/signal-composer-binding/signal-composer.cpp
index 9288e56..8ad8c3b 100644
--- a/signal-composer-binding/signal-composer.cpp
+++ b/signal-composer-binding/signal-composer.cpp
@@ -22,7 +22,7 @@
CtlSectionT bindingApp::ctlSections_[] = {
[0]={.key="plugins" ,.label = "plugins", .info=nullptr,
.loadCB=PluginConfig,
- .handle=nullptr},
+ .handle=&bindingApp::instance()},
[1]={.key="sources" ,.label = "sources", .info=nullptr,
.loadCB=loadSourcesAPI,
.handle=nullptr},
@@ -363,17 +363,43 @@ json_object* bindingApp::getSignalValue(const std::string& sig, json_object* opt
if (strcasestr(opts[idx], "last") && !last)
{
last = true;
- double value = sigP->last();
- json_object_object_add(response, "last",
- json_object_new_double(value));
+ 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)
{
- double value = sigP->last();
- json_object_object_add(response, "last",
- json_object_new_double(value));
+ 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;
diff --git a/signal-composer-binding/signal.cpp b/signal-composer-binding/signal.cpp
index 147aef0..dfa3d5f 100644
--- a/signal-composer-binding/signal.cpp
+++ b/signal-composer-binding/signal.cpp
@@ -22,6 +22,8 @@
#include "signal.hpp"
#include "signal-composer.hpp"
+#define MICRO 1000000
+
Signal::Signal(const std::string& id,
std::vector<std::string>& sources,
const std::string& unit,
@@ -29,6 +31,8 @@ Signal::Signal(const std::string& id,
CtlActionT* onReceived)
:id_(id),
signalSigList_(sources),
+ timestamp_(0.0),
+ value_({0,0,0,0,0,""}),
frequency_(frequency),
unit_(unit),
onReceived_(onReceived)
@@ -90,14 +94,21 @@ json_object* Signal::toJSON() const
return queryJ;
}
-void update(double timestamp, double value)
+void Signal::update(long long int timestamp, struct SignalValue value)
{
- AFB_NOTICE("Got an update from observed signal");
+ AFB_NOTICE("Got an update from observed signal. Timestamp: %lld, vb: %d, vn: %lf, vs: %s", timestamp, value.boolVal, value.numVal, value.strVal.c_str());
}
+/// @brief Notify observers that there is a change and execute callback defined
+/// when signal is received
+///
+/// @param[in] queryJ - JSON query object to transmit to callback function
+///
+/// @return 0 if OK, -1 or other if not.
int Signal::onReceivedCB(json_object *queryJ)
{
- return ActionExecOne(onReceived_, queryJ);
+ notify();
+ return onReceived_ ? ActionExecOne(onReceived_, queryJ) : 0;
}
void Signal::attach(Signal* obs)
@@ -157,15 +168,49 @@ int Signal::recursionCheck()
double Signal::average(int seconds) const
{
- return 0.0;
+ long long int begin = history_.begin()->first,
+ end = begin+(seconds*MICRO);
+ double total = 0.0;
+ int nbElt = 0;
+
+ for (const auto& val: history_)
+ {
+ if(val.first >= end)
+ {break;}
+ if(val.second.hasNum)
+ {
+ total += val.second.numVal;
+ nbElt++;
+ }
+ else
+ {
+ AFB_ERROR("There isn't numerical value to compare with in that signal '%s'. Stored value : bool %d, num %lf, str: %s",
+ id_.c_str(),
+ val.second.boolVal,
+ val.second.numVal,
+ val.second.strVal.c_str());
+ break;
+ }
+ }
+
+ return total / nbElt;
}
double Signal::minimum() const
{
double min = DBL_MAX;
for (auto& v : history_)
{
- double temp_min = v.second;
- if(temp_min < min) { min = temp_min;}
+ if(v.second.hasNum && v.second.numVal < min)
+ {min = v.second.numVal;}
+ else
+ {
+ AFB_ERROR("There isn't numerical value to compare with in that signal '%s'. Stored value : bool %d, num %lf, str: %s",
+ id_.c_str(),
+ v.second.boolVal,
+ v.second.numVal,
+ v.second.strVal.c_str());
+ break;
+ }
}
return min;
}
@@ -175,12 +220,21 @@ double Signal::maximum() const
double max = 0.0;
for (auto& v : history_)
{
- double temp_max = v.second;
- if(temp_max > max) { max = temp_max;}
+ if(v.second.hasNum && v.second.hasNum > max)
+ {max = v.second.numVal;}
+ else
+ {
+ AFB_ERROR("There isn't numerical value to compare with in that signal '%s'. Stored value : bool %d, num %lf, str: %s",
+ id_.c_str(),
+ v.second.boolVal,
+ v.second.numVal,
+ v.second.strVal.c_str());
+ break;
+ }
}
return max;
}
-double Signal::last() const
+struct SignalValue Signal::last() const
{
return history_.rbegin()->second;
}
diff --git a/signal-composer-binding/signal.hpp b/signal-composer-binding/signal.hpp
index ac3b678..34e6ab1 100644
--- a/signal-composer-binding/signal.hpp
+++ b/signal-composer-binding/signal.hpp
@@ -24,19 +24,29 @@
class bindingApp;
+struct SignalValue {
+ bool hasBool = false;
+ bool boolVal;
+ bool hasNum = false;
+ double numVal;
+ bool hasStr = false;
+ std::string strVal;
+};
+
class Signal
{
private:
std::string id_;
std::vector<std::string> signalSigList_;
long long int timestamp_;
- double value_;
- std::map<long long int, double> history_; ///< history_ - Hold signal value history in map with <timestamp, value>
+ struct SignalValue value_;
+ std::map<long long int, struct SignalValue> history_; ///< history_ - Hold signal value history in map with <timestamp, value>
double frequency_;
std::string unit_;
CtlActionT* onReceived_;
std::vector<Signal*> Observers_;
+ void notify();
void attach(Signal *obs);
int recursionCheck(const std::string& origId);
@@ -49,14 +59,13 @@ public:
const std::string id() const;
json_object* toJSON() const;
- void update(long long int timestamp, double value);
+ void update(long long int timestamp, struct SignalValue value);
int onReceivedCB(json_object *queryJ);
void attachToSourceSignals(bindingApp& bApp);
- void notify();
double average(int seconds = 0) const;
double minimum() const;
double maximum() const;
- double last() const;
+ struct SignalValue last() const;
int recursionCheck();
};