diff --git a/apps/aqhome-react/0BUILD b/apps/aqhome-react/0BUILD
index 374c643..415ba5f 100644
--- a/apps/aqhome-react/0BUILD
+++ b/apps/aqhome-react/0BUILD
@@ -39,16 +39,24 @@
aqhome_react.h
aqhome_react_p.h
+ init.h
+ fini.h
+ loop.h
$(local/typefiles)
+ aqhome_react.c
+ init.c
+ fini.c
+ loop.c
main.c
aqhome
aqhreact_types
+ aqhreact_units
@@ -57,6 +65,7 @@
types
+ units
diff --git a/apps/aqhome-react/aqhome_react.c b/apps/aqhome-react/aqhome_react.c
new file mode 100644
index 0000000..f615349
--- /dev/null
+++ b/apps/aqhome-react/aqhome_react.c
@@ -0,0 +1,86 @@
+/****************************************************************************
+ * This file is part of the project AqHome.
+ * AqHome (c) by 2024 Martin Preuss, all rights reserved.
+ *
+ * The license for this file can be found in the file COPYING which you
+ * should have received along with this file.
+ ****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include
+#endif
+
+#include "./aqhome_react_p.h"
+
+#include
+#include
+
+
+
+AQHOME_REACT *AqHomeReact_new()
+{
+ AQHOME_REACT *aqh;
+
+ GWEN_NEW_OBJECT(AQHOME_REACT, aqh);
+
+ return aqh;
+}
+
+
+
+void AqHomeReact_free(AQHOME_REACT *aqh)
+{
+ if (aqh) {
+ GWEN_MsgEndpoint_free(aqh->brokerEndpoint);
+ GWEN_DB_Group_free(aqh->dbArgs);
+ free(aqh->pidFile);
+
+ GWEN_FREE_OBJECT(aqh);
+
+ }
+}
+
+
+
+GWEN_MSG_ENDPOINT *AqHomeReact_GetBrokerEndpoint(const AQHOME_REACT *aqh)
+{
+ return aqh?(aqh->brokerEndpoint):NULL;
+}
+
+
+
+GWEN_DB_NODE *AqHomeReact_GetDbArgs(const AQHOME_REACT *aqh)
+{
+ return aqh?(aqh->dbArgs):NULL;
+}
+
+
+
+const char *AqHomeReact_GetPidFile(const AQHOME_REACT *aqh)
+{
+ return aqh?aqh->pidFile:NULL;
+}
+
+
+
+void AqHomeReact_SetPidFile(AQHOME_REACT *aqh, const char *s)
+{
+ if (aqh) {
+ free(aqh->pidFile);
+ aqh->pidFile=s?strdup(s):NULL;
+ }
+}
+
+
+
+int AqHomeReact_GetTimeout(const AQHOME_REACT *aqh)
+{
+ return aqh?aqh->timeout:0;
+}
+
+
+
+
+
+
+
diff --git a/apps/aqhome-react/aqhome_react.h b/apps/aqhome-react/aqhome_react.h
index fe4e6bb..8e6863a 100644
--- a/apps/aqhome-react/aqhome_react.h
+++ b/apps/aqhome-react/aqhome_react.h
@@ -1,6 +1,6 @@
/****************************************************************************
* This file is part of the project AqHome.
- * AqHome (c) by 2023 Martin Preuss, all rights reserved.
+ * AqHome (c) by 2024 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
@@ -13,24 +13,22 @@
#include
-#define AQHOME_REACT_DEFAULT_PIDFILE "/var/run/aqhome-react.pid"
-#define AQHOME_REACT_DEFAULT_DATADIR "/var/lib/aqhome-react"
-
-#define AQHOME_REACT_DEFAULT_BROKER_PORT 1899
-#define AQHOME_REACT_DEFAULT_BROKER_CLIENTID "react"
+typedef struct AQHOME_REACT AQHOME_REACT;
+AQHOME_REACT *AqHomeReact_new();
+void AqHomeReact_free(AQHOME_REACT *aqh);
+
+GWEN_MSG_ENDPOINT *AqHomeReact_GetBrokerEndpoint(const AQHOME_REACT *aqh);
+
+GWEN_DB_NODE *AqHomeReact_GetDbArgs(const AQHOME_REACT *aqh);
+
+const char *AqHomeReact_GetPidFile(const AQHOME_REACT *aqh);
+void AqHomeReact_SetPidFile(AQHOME_REACT *aqh, const char *s);
+
+int AqHomeReact_GetTimeout(const AQHOME_REACT *aqh);
-struct AQHOME_REACT {
- GWEN_MSG_ENDPOINT *rootEndpoint;
- GWEN_MSG_ENDPOINT *brokerEndpoint; /* do not free (is part of tree pointed to by rootEndpoint) */
-
- GWEN_DB_NODE *dbArgs;
- char *pidFile;
- int timeout; /* timeout for run e.g. inside valgrind */
-
-};
#endif
diff --git a/apps/aqhome-react/aqhome_react_p.h b/apps/aqhome-react/aqhome_react_p.h
index 3b3d38c..7db0113 100644
--- a/apps/aqhome-react/aqhome_react_p.h
+++ b/apps/aqhome-react/aqhome_react_p.h
@@ -23,15 +23,12 @@
struct AQHOME_REACT {
- GWEN_MSG_ENDPOINT *rootEndpoint;
- GWEN_MSG_ENDPOINT *brokerEndpoint; /* do not free (is part of tree pointed to by rootEndpoint) */
+ GWEN_MSG_ENDPOINT *brokerEndpoint;
GWEN_DB_NODE *dbArgs;
char *pidFile;
int timeout; /* timeout for run e.g. inside valgrind */
- AQH_STORAGE *storage;
-
};
diff --git a/apps/aqhome-react/fini.c b/apps/aqhome-react/fini.c
new file mode 100644
index 0000000..79e9378
--- /dev/null
+++ b/apps/aqhome-react/fini.c
@@ -0,0 +1,61 @@
+/****************************************************************************
+ * This file is part of the project AqHome.
+ * AqHome (c) by 2024 Martin Preuss, all rights reserved.
+ *
+ * The license for this file can be found in the file COPYING which you
+ * should have received along with this file.
+ ****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include
+#endif
+
+#include "./fini.h"
+#include "aqhome-react/aqhome_react_p.h"
+
+#include
+#include
+#include
+#include
+
+#include
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * defines
+ * ------------------------------------------------------------------------------------------------
+ */
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * forward declarations
+ * ------------------------------------------------------------------------------------------------
+ */
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * implementations
+ * ------------------------------------------------------------------------------------------------
+ */
+
+int AqHomeReact_Fini(AQHOME_REACT *aqh)
+{
+
+ GWEN_MsgEndpoint_Disconnect(aqh->brokerEndpoint);
+ GWEN_MsgEndpoint_free(aqh->brokerEndpoint);
+ aqh->brokerEndpoint=NULL;
+
+ if (aqh->pidFile)
+ remove(aqh->pidFile);
+
+ return 0;
+}
+
+
+
+
+
+
diff --git a/apps/aqhome-react/fini.h b/apps/aqhome-react/fini.h
new file mode 100644
index 0000000..8531d37
--- /dev/null
+++ b/apps/aqhome-react/fini.h
@@ -0,0 +1,21 @@
+/****************************************************************************
+ * This file is part of the project AqHome.
+ * AqHome (c) by 2024 Martin Preuss, all rights reserved.
+ *
+ * The license for this file can be found in the file COPYING which you
+ * should have received along with this file.
+ ****************************************************************************/
+
+#ifndef AQHOMEREACT_FINI_H
+#define AQHOMEREACT_FINI_H
+
+
+#include "./aqhome_react.h"
+
+
+int AqHomeReact_Fini(AQHOME_REACT *aqh);
+
+
+#endif
+
+
diff --git a/apps/aqhome-react/init.c b/apps/aqhome-react/init.c
new file mode 100644
index 0000000..e6add70
--- /dev/null
+++ b/apps/aqhome-react/init.c
@@ -0,0 +1,312 @@
+/****************************************************************************
+ * This file is part of the project AqHome.
+ * AqHome (c) by 2024 Martin Preuss, all rights reserved.
+ *
+ * The license for this file can be found in the file COPYING which you
+ * should have received along with this file.
+ ****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include
+#endif
+
+#include "./init.h"
+#include "./aqhome_react_p.h"
+
+#include "aqhome/aqhome.h"
+#include "aqhome/ipc/endpoint_ipc.h"
+#include "aqhome/ipc/endpoint_ipcclient.h"
+#include "aqhome/mqtt/endpoint_mqttc.h"
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * defines
+ * ------------------------------------------------------------------------------------------------
+ */
+
+#define I18N(msg) msg
+#define I18S(msg) msg
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * forward declarations
+ * ------------------------------------------------------------------------------------------------
+ */
+
+static int _createPidFile(const char *pidFilename);
+static int _setupBroker(AQHOME_REACT *aqh, GWEN_DB_NODE *dbArgs);
+static int _readArgs(int argc, char **argv, GWEN_DB_NODE *dbArgs);
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * implementations
+ * ------------------------------------------------------------------------------------------------
+ */
+
+int AqHomeReact_Init(AQHOME_REACT *aqh, int argc, char **argv)
+{
+ int rv;
+ GWEN_DB_NODE *dbArgs;
+ const char *s;
+
+ dbArgs=GWEN_DB_Group_new("args");
+ rv=_readArgs(argc, argv, dbArgs);
+ if (rv<0) {
+ DBG_ERROR(NULL, "Error reading args (%d)", rv);
+ return rv;
+ }
+ AQH_MergeConfigFileIntoConfig(dbArgs, "ConfigFile");
+ aqh->dbArgs=dbArgs;
+
+ aqh->timeout=GWEN_DB_GetIntValue(dbArgs, "timeout", 0, 0);
+
+ s=GWEN_DB_GetCharValue(dbArgs, "pidfile", 0, AQHOME_REACT_DEFAULT_PIDFILE);
+ if (s && *s) {
+ AqHomeReact_SetPidFile(aqh, s);
+ rv=_createPidFile(s);
+ if (rv<0) {
+ DBG_ERROR(NULL, "Error creating PID file (%d)", rv);
+ return rv;
+ }
+ }
+
+ /* TODO: setup react units */
+
+ rv=_setupBroker(aqh, dbArgs);
+ if (rv<0) {
+ DBG_ERROR(NULL, "Error setting up connection to broker (%d)", rv);
+ return rv;
+ }
+
+ return 0;
+}
+
+
+
+int _createPidFile(const char *pidFilename)
+{
+ FILE *f;
+ int pidfd;
+
+ if (remove(pidFilename)==0) {
+ DBG_ERROR(0, "Old PID file existed, removed. (Unclean shutdown?)");
+ }
+
+#ifdef HAVE_SYS_STAT_H
+ pidfd = open(pidFilename, O_EXCL|O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
+ if (pidfd < 0) {
+ DBG_ERROR(NULL, "Could not create PID file \"%s\" (%s), aborting.", pidFilename, strerror(errno));
+ return GWEN_ERROR_IO;
+ }
+
+ f = fdopen(pidfd, "w");
+#else /* HAVE_STAT_H */
+ f=fopen(pidFilename,"w+");
+#endif /* HAVE_STAT_H */
+
+ /* write pid */
+#ifdef HAVE_GETPID
+ fprintf(f,"%d\n",getpid());
+#else
+ fprintf(f,"-1\n");
+#endif
+ if (fclose(f)) {
+ DBG_ERROR(0, "Could not close PID file \"%s\" (%s), aborting.", pidFilename, strerror(errno));
+ return GWEN_ERROR_IO;
+ }
+ return 0;
+}
+
+
+
+int _setupBroker(AQHOME_REACT *aqh, GWEN_DB_NODE *dbArgs)
+{
+ const char *brokerAddress;
+ int brokerPort;
+ const char *brokerClientId;
+
+ brokerAddress=GWEN_DB_GetCharValue(dbArgs, "brokerAddress", 0, NULL);
+ if (!(brokerAddress && *brokerAddress))
+ brokerAddress=GWEN_DB_GetCharValue(dbArgs, "ConfigFile/brokerAddress", 0, "127.0.0.1");
+
+ brokerPort=GWEN_DB_GetIntValue(dbArgs, "brokerPort", 0, -1);
+ if (brokerPort<0)
+ brokerPort=GWEN_DB_GetIntValue(dbArgs, "ConfigFile/brokerPort", 0, AQHOME_REACT_DEFAULT_BROKER_PORT);
+
+ brokerClientId=GWEN_DB_GetCharValue(dbArgs, "brokerClientId", 0, AQHOME_REACT_DEFAULT_BROKER_CLIENTID);
+
+ if (brokerAddress && *brokerAddress && brokerPort) {
+ GWEN_MSG_ENDPOINT *ep;
+ GWEN_MSG_ENDPOINT *ipcBaseEndpoint;
+ int rv;
+
+ ep=AQH_ClientIpcEndpoint_new("brokerIpcClient", 0);
+ ipcBaseEndpoint=AQH_IpcEndpoint_CreateIpcTcpClient(brokerAddress, brokerPort, "brokerPhysEndpoint", 0);
+ AQH_IpcEndpoint_SetServiceName(ipcBaseEndpoint, brokerClientId);
+ GWEN_MsgEndpoint_Tree2_AddChild(ep, ipcBaseEndpoint);
+
+ aqh->brokerEndpoint=ep;
+
+ rv=GWEN_MultilayerEndpoint_StartConnect(ep);
+ if (rv<0 && rv!=GWEN_ERROR_IN_PROGRESS) {
+ DBG_ERROR(NULL, "Error connecting to broker server %s:%d (%d), will retry later", brokerAddress, brokerPort, rv);
+ return rv;
+ }
+ }
+
+ return 0;
+}
+
+
+
+int _readArgs(int argc, char **argv, GWEN_DB_NODE *dbArgs)
+{
+ int rv;
+ const GWEN_ARGS args[]= {
+ {
+ GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
+ GWEN_ArgsType_Char, /* type */
+ "cfgdir", /* name */
+ 0, /* minnum */
+ 1, /* maxnum */
+ "D", /* short option */
+ "cfgdir", /* long option */
+ I18S("Specify the configuration folder"),
+ I18S("Specify the configuration folder")
+ },
+ {
+ GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
+ GWEN_ArgsType_Char, /* type */
+ "charset", /* name */
+ 0, /* minnum */
+ 1, /* maxnum */
+ 0, /* short option */
+ "charset", /* long option */
+ I18S("Specify the output character set"), /* short description */
+ I18S("Specify the output character set") /* long description */
+ },
+ {
+ GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
+ GWEN_ArgsType_Char, /* type */
+ "brokerAddress", /* name */
+ 0, /* minnum */
+ 1, /* maxnum */
+ "ba", /* short option */
+ "brokeraddress", /* long option */
+ I18S("Specify the address of the broker server to connect to (disabled if missing)"),
+ I18S("Specify the address of the broker server to connect to (disabled if missing)")
+ },
+ {
+ GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
+ GWEN_ArgsType_Int, /* type */
+ "brokerPort", /* name */
+ 0, /* minnum */
+ 1, /* maxnum */
+ "bp", /* short option */
+ "brokerport", /* long option */
+ I18S("Specify the port of the broker server (default: 1899)"),
+ I18S("Specify the port of the broker server (default: 1899)")
+ },
+ {
+ GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
+ GWEN_ArgsType_Char, /* type */
+ "brokerClientId", /* name */
+ 0, /* minnum */
+ 1, /* maxnum */
+ NULL, /* short option */
+ "brokerclientid", /* long option */
+ I18S("Specify client id for the broker server (default: \"nodes\")"),
+ I18S("Specify client id for the broker server (default: \"nodes\")")
+ },
+
+ {
+ GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
+ GWEN_ArgsType_Char, /* type */
+ "pidfile", /* name */
+ 0, /* minnum */
+ 1, /* maxnum */
+ "p", /* short option */
+ "pidfile", /* long option */
+ I18S("Specify the PID file"),
+ I18S("Specify the PID file")
+ },
+ {
+ GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
+ GWEN_ArgsType_Int, /* type */
+ "timeout", /* name */
+ 0, /* minnum */
+ 1, /* maxnum */
+ "T", /* short option */
+ "timeout", /* long option */
+ I18S("Specify timeout in second (default: no timeout)"),
+ I18S("Specify timeout in second (default: no timeout)")
+ },
+ {
+ GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
+ GWEN_ArgsType_Char, /* type */
+ "devicefile", /* name */
+ 0, /* minnum */
+ 1, /* maxnum */
+ "d", /* short option */
+ "devicefile", /* long option */
+ I18S("Specify the device file"),
+ I18S("Specify the device file")
+ },
+
+ {
+ GWEN_ARGS_FLAGS_HELP | GWEN_ARGS_FLAGS_LAST, /* flags */
+ GWEN_ArgsType_Int, /* type */
+ "help", /* name */
+ 0, /* minnum */
+ 0, /* maxnum */
+ "h", /* short option */
+ "help",
+ I18S("Show this help screen."),
+ I18S("Show this help screen.")
+ }
+ };
+
+ rv=GWEN_Args_Check(argc, argv, 1, 0, args, dbArgs);
+ if (rv==GWEN_ARGS_RESULT_ERROR) {
+ fprintf(stderr, "ERROR: Could not parse arguments main\n");
+ return GWEN_ERROR_INVALID;
+ }
+ else if (rv==GWEN_ARGS_RESULT_HELP) {
+ GWEN_BUFFER *ubuf;
+
+ ubuf=GWEN_Buffer_new(0, 1024, 0, 1);
+ GWEN_Buffer_AppendArgs(ubuf,
+ I18N("This is version %s.\nUsage: %s [OPTIONS]\n\nOptions:\n"),
+ AQHOME_VERSION_STRING,
+ argv[0]);
+ if (GWEN_Args_Usage(args, ubuf, GWEN_ArgsOutType_Txt)) {
+ fprintf(stderr, "ERROR: Could not create help string\n");
+ return 1;
+ }
+ GWEN_Buffer_AppendString(ubuf, "\n");
+
+ fprintf(stdout, "%s\n", GWEN_Buffer_GetStart(ubuf));
+ GWEN_Buffer_free(ubuf);
+ return GWEN_ERROR_CLOSE;
+ }
+ return 0;
+}
+
+
+
+
+
diff --git a/apps/aqhome-react/init.h b/apps/aqhome-react/init.h
new file mode 100644
index 0000000..b520658
--- /dev/null
+++ b/apps/aqhome-react/init.h
@@ -0,0 +1,21 @@
+/****************************************************************************
+ * This file is part of the project AqHome.
+ * AqHome (c) by 2024 Martin Preuss, all rights reserved.
+ *
+ * The license for this file can be found in the file COPYING which you
+ * should have received along with this file.
+ ****************************************************************************/
+
+#ifndef AQHOMEREACT_INIT_H
+#define AQHOMEREACT_INIT_H
+
+
+#include "./aqhome_react.h"
+
+
+int AqHomeReact_Init(AQHOME_REACT *aqh, int argc, char **argv);
+
+
+#endif
+
+
diff --git a/apps/aqhome-react/loop.c b/apps/aqhome-react/loop.c
new file mode 100644
index 0000000..726d832
--- /dev/null
+++ b/apps/aqhome-react/loop.c
@@ -0,0 +1,103 @@
+/****************************************************************************
+ * This file is part of the project AqHome.
+ * AqHome (c) by 2024 Martin Preuss, all rights reserved.
+ *
+ * The license for this file can be found in the file COPYING which you
+ * should have received along with this file.
+ ****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include
+#endif
+
+#include "./loop.h"
+#include "./aqhome_react_p.h"
+
+#include "aqhome/ipc/data/ipc_data.h"
+#include "aqhome/ipc/data/msg_data_multidata.h"
+#include "aqhome/ipc/msg_ipc_tag16.h"
+
+#include
+
+#include
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * defines
+ * ------------------------------------------------------------------------------------------------
+ */
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * forward declarations
+ * ------------------------------------------------------------------------------------------------
+ */
+
+static void _handleDataResponse(GWEN_MSG *msg);
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * implementations
+ * ------------------------------------------------------------------------------------------------
+ */
+
+void AqHomeReact_Loop(AQHOME_REACT *aqh, int timeoutInMilliSecs)
+{
+ GWEN_MSG *msg;
+
+ GWEN_MsgEndpoint_IoLoop(aqh->brokerEndpoint, timeoutInMilliSecs);
+ msg=GWEN_MsgEndpoint_TakeFirstReceivedMessage(aqh->brokerEndpoint);
+ if (msg) {
+ uint16_t code;
+
+ code=GWEN_IpcMsg_GetCode(msg);
+ if (code==AQH_MSGTYPE_IPC_DATA_DATACHANGED) {
+ DBG_INFO(NULL, "Received expected IPC message");
+ _handleDataResponse(msg);
+ }
+ else if (code==AQH_MSGTYPE_IPC_DATA_RESULT) {
+ DBG_INFO(NULL, "Received IPC result message, ignoring");
+ }
+ else {
+ DBG_INFO(NULL, "Received unexpected message %d (%x)", code, code);
+ }
+ GWEN_Msg_free(msg);
+ }
+}
+
+
+
+void _handleDataResponse(GWEN_MSG *msg)
+{
+ AQH_VALUE *value;
+ const GWEN_TAG16 *tag;
+ unsigned int numberOfPoints;
+ const uint64_t *dataPoints;
+
+ AQH_MultiDataDataIpcMsg_Parse(msg, 0);
+ value=AQH_MultiDataDataIpcMsg_ReadValue(msg);
+
+ tag=AQH_Tag16IpcMsg_FindFirstTagByType(msg, AQH_MSGDATA_MULTIDATA_TAGS_DATA);
+ numberOfPoints=(tag?GWEN_Tag16_GetTagLength(tag):0)/(2*sizeof(uint64_t));
+ dataPoints=tag?((const uint64_t*) GWEN_Tag16_GetTagData(tag)):NULL;
+ if (numberOfPoints>0 && dataPoints) {
+ uint32_t i;
+
+ for(i=0; i
+#endif
+
+#include "./init.h"
+#include "./fini.h"
+#include "./loop.h"
+
+#include "aqhome/aqhome.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef HAVE_SIGNAL_H
+# include
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+/* ------------------------------------------------------------------------------------------------
+ * defines
+ * ------------------------------------------------------------------------------------------------
+ */
+
+#define I18N(msg) msg
+#define I18S(msg) msg
+
+
+#define FULL_DEBUG
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * forward declarations
+ * ------------------------------------------------------------------------------------------------
+ */
+
+static void _serve(AQHOME_REACT *aqh);
+
+#ifdef HAVE_SIGNAL_H
+static int _setSignalHandlers(void);
+static int _setupSigAction(struct sigaction *sa, int sig);
+static void _signalHandler(int s);
+static struct sigaction saINT,saTERM, saHUP, saTSTP, saCONT;
+#endif
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * static vars
+ * ------------------------------------------------------------------------------------------------
+ */
+
+static int stopService=0;
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * implementations
+ * ------------------------------------------------------------------------------------------------
+ */
int main(int argc, char **argv)
{
+ AQHOME_REACT *aqh;
+ GWEN_DB_NODE *dbArgs;
+ int rv;
+ GWEN_GUI *gui;
+ const char *s;
+
+ rv=GWEN_Init();
+ if (rv) {
+ fprintf(stderr, "ERROR: Unable to init Gwen.\n");
+ return 2;
+ }
+
+ GWEN_Logger_Open(0, "aqhome-react", 0, GWEN_LoggerType_Console, GWEN_LoggerFacility_User);
+ GWEN_Logger_SetLevel(0, GWEN_LoggerLevel_Warning);
+ //GWEN_Logger_SetLevel(0, GWEN_LoggerLevel_Info);
+
+ rv=AQH_Init();
+ if (rv<0) {
+ DBG_INFO(NULL, "here (%d)", rv);
+ return 2;
+ }
+
+ aqh=AqHomeReact_new();
+ rv=AqHomeReact_Init(aqh, argc, argv);
+ if (rv<0) {
+ if (rv==GWEN_ERROR_CLOSE)
+ return 1;
+ DBG_INFO(NULL, "here (%d)", rv);
+ return 2;
+ }
+
+ dbArgs=AqHomeReact_GetDbArgs(aqh);
+
+ gui=GWEN_Gui_CGui_new();
+ s=GWEN_DB_GetCharValue(dbArgs, "charset", 0, NULL);
+ if (s && *s)
+ GWEN_Gui_SetCharSet(gui, s);
+ GWEN_Gui_SetGui(gui);
+
+ _serve(aqh);
+
+ AqHomeReact_Fini(aqh);
+ AqHomeReact_free(aqh);
+
+ GWEN_Gui_SetGui(NULL);
+ GWEN_Gui_free(gui);
+
+ return 0;
}
+
+void _serve(AQHOME_REACT *aqh)
+{
+ int rv;
+ int timeout;
+ time_t startTime;
+ time_t lastTimerTime;
+ GWEN_DB_NODE *dbArgs;
+
+ startTime=time(NULL);
+ lastTimerTime=startTime;
+
+ dbArgs=AqHomeReact_GetDbArgs(aqh);
+
+ rv=_setSignalHandlers();
+ if (rv<0) {
+ DBG_ERROR(NULL, "Error setting signal handlers (%d)", rv);
+ return;
+ }
+
+ timeout=GWEN_DB_GetIntValue(dbArgs, "timeout", 0, 0);
+
+ while(!stopService) {
+ time_t now;
+
+ DBG_DEBUG(NULL, "Next loop");
+ AqHomeReact_Loop(aqh, 500);
+
+ now=time(NULL);
+ if (now!=lastTimerTime) {
+ lastTimerTime=now;
+ /* TODO: fire timer */
+ }
+
+ if (timeout) {
+ if ((now-startTime)>timeout) {
+ DBG_ERROR(NULL, "Timeout, stopping service");
+ break;
+ }
+ }
+
+ } /* while */
+
+}
+
+
+
+int _setSignalHandlers(void)
+{
+#ifdef HAVE_SIGNAL_H
+ int rv;
+
+ rv=_setupSigAction(&saINT, SIGINT);
+ if (rv)
+ return rv;
+
+ rv=_setupSigAction(&saTERM, SIGTERM);
+ if (rv)
+ return rv;
+
+ rv=_setupSigAction(&saHUP, SIGHUP);
+ if (rv)
+ return rv;
+
+# ifdef SIGTSTP
+ rv=_setupSigAction(&saTSTP, SIGTSTP);
+ if (rv)
+ return rv;
+# endif
+
+# ifdef SIGCONT
+ rv=_setupSigAction(&saCONT, SIGCONT);
+ if (rv)
+ return rv;
+# endif
+#endif
+ return 0;
+}
+
+
+
+int _setupSigAction(struct sigaction *sa, int sig)
+{
+ sa->sa_handler=_signalHandler;
+ sigemptyset(&sa->sa_mask);
+ sa->sa_flags=0;
+ if (sigaction(sig, sa, 0)) {
+ DBG_ERROR(NULL, "Could not setup signal handler for signal %d", sig);
+ return GWEN_ERROR_IO;
+ }
+
+ return 0;
+}
+
+
+
+void _signalHandler(int s)
+{
+ switch(s) {
+ case SIGINT:
+ case SIGTERM:
+ case SIGHUP:
+ DBG_WARN(0, "Received signal %d, stopping service in next loop.",s);
+ stopService=1;
+ break;
+ default:
+ DBG_WARN(0, "Unknown signal %d",s);
+ break;
+ }
+}
+
+
+
+
diff --git a/apps/aqhome-react/types/inputslot.c b/apps/aqhome-react/types/inputslot.c
index dbcf5dd..c8b6221 100644
--- a/apps/aqhome-react/types/inputslot.c
+++ b/apps/aqhome-react/types/inputslot.c
@@ -12,6 +12,7 @@
#include "./inputslot_p.h"
+#include "aqhome-react/types/unit.h"
#include
#include
@@ -27,6 +28,8 @@ AQHREACT_INPUT_SLOT *AQHREACT_InputSlot_new()
GWEN_NEW_OBJECT(AQHREACT_INPUT_SLOT, inSlot);
GWEN_LIST_INIT(AQHREACT_INPUT_SLOT, inSlot);
+ inSlot->idForUnit=-1;
+
return inSlot;
}
@@ -54,7 +57,7 @@ AQHREACT_INPUT_SLOT *AQHREACT_InputSlot_dup(const AQHREACT_INPUT_SLOT *origSlot)
AQHREACT_InputSlot_SetName(inSlot, origSlot->name);
AQHREACT_InputSlot_SetDescription(inSlot, origSlot->description);
inSlot->idForUnit=origSlot->idForUnit;
- inSlot->flags=origSlot->flags;
+ inSlot->flags=origSlot->flags & ~AQHREACT_UNIT_FLAGS_MULTI; /* don't copy MULTI flag */
inSlot->acceptedDataType=origSlot->acceptedDataType;
/* don't copy current dataObject */
return inSlot;
diff --git a/apps/aqhome-react/types/link.c b/apps/aqhome-react/types/link.c
index 33b3019..81fe0cf 100644
--- a/apps/aqhome-react/types/link.c
+++ b/apps/aqhome-react/types/link.c
@@ -25,7 +25,7 @@ AQHREACT_LINK *AQHREACT_Link_new()
GWEN_NEW_OBJECT(AQHREACT_LINK, lnk);
GWEN_LIST_INIT(AQHREACT_LINK, lnk);
- lnk->targetInputSlotIdx=-1;
+ lnk->targetInputSlotIdForUnit=-1;
return lnk;
}
@@ -60,17 +60,17 @@ void AQHREACT_Link_SetTargetUnitId(AQHREACT_LINK *lnk, const char *s)
-int AQHREACT_Link_GetTargetInputSlotIdx(const AQHREACT_LINK *lnk)
+int AQHREACT_Link_GetTargetInputSlotIdForUnit(const AQHREACT_LINK *lnk)
{
- return lnk?lnk->targetInputSlotIdx:-1;
+ return lnk?lnk->targetInputSlotIdForUnit:-1;
}
-void AQHREACT_Link_SetTargetInputSlotIdx(AQHREACT_LINK *lnk, int i)
+void AQHREACT_Link_SetTargetInputSlotIdForUnit(AQHREACT_LINK *lnk, int i)
{
if (lnk)
- lnk->targetInputSlotIdx=i;;
+ lnk->targetInputSlotIdForUnit=i;
}
diff --git a/apps/aqhome-react/types/link.h b/apps/aqhome-react/types/link.h
index 1de37b1..c6ee371 100644
--- a/apps/aqhome-react/types/link.h
+++ b/apps/aqhome-react/types/link.h
@@ -27,8 +27,8 @@ void AQHREACT_Link_free(AQHREACT_LINK *lnk);
const char *AQHREACT_Link_GetTargetUnitId(const AQHREACT_LINK *lnk);
void AQHREACT_Link_SetTargetUnitId(AQHREACT_LINK *lnk, const char *s);
-int AQHREACT_Link_GetTargetInputSlotIdx(const AQHREACT_LINK *lnk);
-void AQHREACT_Link_SetTargetInputSlotIdx(AQHREACT_LINK *lnk, int i);
+int AQHREACT_Link_GetTargetInputSlotIdForUnit(const AQHREACT_LINK *lnk);
+void AQHREACT_Link_SetTargetInputSlotIdForUnit(AQHREACT_LINK *lnk, int i);
AQHREACT_UNIT *AQHREACT_Link_GetTargetUnit(const AQHREACT_LINK *lnk);
void AQHREACT_Link_SetTargetUnit(AQHREACT_LINK *lnk, AQHREACT_UNIT *unit);
diff --git a/apps/aqhome-react/types/link_p.h b/apps/aqhome-react/types/link_p.h
index 19bd55e..538b467 100644
--- a/apps/aqhome-react/types/link_p.h
+++ b/apps/aqhome-react/types/link_p.h
@@ -17,7 +17,7 @@ struct AQHREACT_LINK {
GWEN_LIST_ELEMENT(AQHREACT_LINK)
char *targetUnitId;
- int targetInputSlotIdx;
+ int targetInputSlotIdForUnit;
AQHREACT_UNIT *targetUnit;
};
diff --git a/apps/aqhome-react/types/outputslot.c b/apps/aqhome-react/types/outputslot.c
index 3cd58c9..82a3b54 100644
--- a/apps/aqhome-react/types/outputslot.c
+++ b/apps/aqhome-react/types/outputslot.c
@@ -28,6 +28,7 @@ AQHREACT_OUTPUT_SLOT *AQHREACT_OutputSlot_new()
GWEN_NEW_OBJECT(AQHREACT_OUTPUT_SLOT, outSlot);
GWEN_LIST_INIT(AQHREACT_OUTPUT_SLOT, outSlot);
outSlot->linkList=AQHREACT_Link_List_new();
+ outSlot->id=-1;
return outSlot;
}
@@ -46,6 +47,21 @@ void AQHREACT_OutputSlot_free(AQHREACT_OUTPUT_SLOT *outSlot)
+int AQHREACT_OutputSlot_GetIdForUnit(const AQHREACT_OUTPUT_SLOT *outSlot)
+{
+ return outSlot?outSlot->id:-1;
+}
+
+
+
+void AQHREACT_OutputSlot_SetIdForUnit(AQHREACT_OUTPUT_SLOT *outSlot, int i)
+{
+ if (outSlot)
+ outSlot->id=i;
+}
+
+
+
const char *AQHREACT_OutputSlot_GetName(const AQHREACT_OUTPUT_SLOT *outSlot)
{
return outSlot?outSlot->name:NULL;
diff --git a/apps/aqhome-react/types/outputslot.h b/apps/aqhome-react/types/outputslot.h
index ed848f5..08790e0 100644
--- a/apps/aqhome-react/types/outputslot.h
+++ b/apps/aqhome-react/types/outputslot.h
@@ -31,6 +31,9 @@ void AQHREACT_OutputSlot_SetName(AQHREACT_OUTPUT_SLOT *outSlot, const char *s);
const char *AQHREACT_OutputSlot_GetDescription(const AQHREACT_OUTPUT_SLOT *outSlot);
void AQHREACT_OutputSlot_SetDescription(AQHREACT_OUTPUT_SLOT *outSlot, const char *s);
+int AQHREACT_OutputSlot_GetIdForUnit(const AQHREACT_OUTPUT_SLOT *outSlot);
+void AQHREACT_OutputSlot_SetIdForUnit(AQHREACT_OUTPUT_SLOT *outSlot, int i);
+
uint32_t AQHREACT_OutputSlot_GetFlags(const AQHREACT_OUTPUT_SLOT *outSlot);
void AQHREACT_OutputSlot_SetFlags(AQHREACT_OUTPUT_SLOT *outSlot, uint32_t f);
void AQHREACT_OutputSlot_AddFlags(AQHREACT_OUTPUT_SLOT *outSlot, uint32_t f);
diff --git a/apps/aqhome-react/types/outputslot_p.h b/apps/aqhome-react/types/outputslot_p.h
index 35b692a..67aa9bd 100644
--- a/apps/aqhome-react/types/outputslot_p.h
+++ b/apps/aqhome-react/types/outputslot_p.h
@@ -16,8 +16,10 @@
struct AQHREACT_OUTPUT_SLOT {
GWEN_LIST_ELEMENT(AQHREACT_OUTPUT_SLOT)
+ int id;
char *name;
char *description;
+ int idForUnit;
uint32_t flags;
int emittedDataType;
diff --git a/apps/aqhome-react/types/unit.c b/apps/aqhome-react/types/unit.c
index 766956c..70f1f65 100644
--- a/apps/aqhome-react/types/unit.c
+++ b/apps/aqhome-react/types/unit.c
@@ -105,6 +105,21 @@ void AQHREACT_Unit_SetId(AQHREACT_UNIT *unit, const char *s)
+int AQHREACT_Unit_GetNextInputSlotId(const AQHREACT_UNIT *unit)
+{
+ return unit?unit->nextInputSlotId:-1;
+}
+
+
+
+void AQHREACT_Unit_SetNextInputSlotId(AQHREACT_UNIT *unit, int i)
+{
+ if (unit)
+ unit->nextInputSlotId=i;
+}
+
+
+
uint32_t AQHREACT_Unit_GetFlags(const AQHREACT_UNIT *unit)
{
return unit?unit->flags:0;
@@ -181,6 +196,108 @@ void AQHREACT_Unit_AddParam(AQHREACT_UNIT *unit, AQHREACT_PARAM *param)
+AQHREACT_PARAM *AQHREACT_Unit_GetParamByName(const AQHREACT_UNIT *unit, const char *paramName)
+{
+ if (unit && unit->paramList && paramName && *paramName) {
+ AQHREACT_PARAM *param;
+
+ param=AQHREACT_Param_List_First(unit->paramList);
+ while(param) {
+ const char *s;
+
+ s=AQHREACT_Param_GetName(param);
+ if (s && *s && strcasecmp(paramName, s)==0)
+ return param;
+ param=AQHREACT_Param_List_Next(param);
+ }
+ }
+ return NULL;
+}
+
+
+
+double AQHREACT_Unit_GetParamValueDouble(const AQHREACT_UNIT *unit, const char *paramName, double defVal)
+{
+ AQHREACT_PARAM *param;
+
+ param=AQHREACT_Unit_GetParamByName(unit, paramName);
+ if (param) {
+ if (AQHREACT_Param_GetDataType(param)==AQHREACT_DATAOBJECTTYPE_DOUBLE) {
+ return AQHREACT_Param_GetDoubleValue(param);
+ }
+ else {
+ DBG_INFO(NULL, "Datatype for param \"%s/%s\" is not DOUBLE", (unit->name)?unit->name:"", paramName);
+ }
+ }
+
+ return defVal;
+}
+
+
+
+void AQHREACT_Unit_SetParamValueDouble(AQHREACT_UNIT *unit, const char *paramName, double val)
+{
+ AQHREACT_PARAM *param;
+
+ param=AQHREACT_Unit_GetParamByName(unit, paramName);
+ if (param) {
+ if (AQHREACT_Param_GetDataType(param)==AQHREACT_DATAOBJECTTYPE_DOUBLE) {
+ AQHREACT_Param_SetDoubleValue(param, val);
+ }
+ else {
+ DBG_INFO(NULL, "Datatype for param \"%s/%s\" is not DOUBLE", (unit->name)?unit->name:"", paramName);
+ }
+ }
+ else {
+ DBG_INFO(NULL, "Param \"%s\" not found in unit %s", paramName, (unit->name)?unit->name:"");
+ }
+}
+
+
+
+const char *AQHREACT_Unit_GetParamValueString(const AQHREACT_UNIT *unit, const char *paramName, const char *defVal)
+{
+ AQHREACT_PARAM *param;
+
+ param=AQHREACT_Unit_GetParamByName(unit, paramName);
+ if (param) {
+ if (AQHREACT_Param_GetDataType(param)==AQHREACT_DATAOBJECTTYPE_STRING) {
+ return AQHREACT_Param_GetStringValue(param);
+ }
+ else {
+ DBG_INFO(NULL, "Datatype for param \"%s/%s\" is not STRING", (unit->name)?unit->name:"", paramName);
+ }
+ }
+ else {
+ DBG_INFO(NULL, "Param \"%s\" not found in unit %s", paramName, (unit->name)?unit->name:"");
+ }
+
+ return defVal;
+}
+
+
+
+void AQHREACT_Unit_SetParamValueString(AQHREACT_UNIT *unit, const char *paramName, const char *val)
+{
+ AQHREACT_PARAM *param;
+
+ param=AQHREACT_Unit_GetParamByName(unit, paramName);
+ if (param) {
+ if (AQHREACT_Param_GetDataType(param)==AQHREACT_DATAOBJECTTYPE_STRING) {
+ AQHREACT_Param_SetStringValue(param, val);
+ }
+ else {
+ DBG_INFO(NULL, "Datatype for param \"%s\" is not STRING", paramName);
+ }
+ }
+ else {
+ DBG_INFO(NULL, "Param \"%s\" not found in unit %s", paramName, (unit->name)?unit->name:"");
+ }
+}
+
+
+
+
AQHREACT_UNIT_INPUTDATA_FN AQHREACT_Unit_SetInputDataFn(AQHREACT_UNIT *unit, AQHREACT_UNIT_INPUTDATA_FN f)
{
if (unit) {
@@ -209,12 +326,12 @@ AQHREACT_UNIT_PROCESS_FN AQHREACT_Unit_SetProcessFn(AQHREACT_UNIT *unit, AQHREAC
-void AQHREACT_Unit_OutputData(AQHREACT_UNIT *unit, int slotIndex, const AQHREACT_DATAOBJECT *dataObject)
+void AQHREACT_Unit_OutputData(AQHREACT_UNIT *unit, int slotIdForUnit, const AQHREACT_DATAOBJECT *dataObject)
{
if (unit && unit->outputSlotList) {
AQHREACT_OUTPUT_SLOT *slot;
- slot=AQHREACT_Unit_GetOutputSlotAt(unit, slotIndex);
+ slot=AQHREACT_Unit_GetOutputSlotByIdForUnit(unit, slotIdForUnit);
if (slot) {
AQHREACT_LINK_LIST *linkList;
@@ -225,12 +342,12 @@ void AQHREACT_Unit_OutputData(AQHREACT_UNIT *unit, int slotIndex, const AQHREACT
lnk=AQHREACT_Link_List_First(linkList);
while(lnk) {
AQHREACT_UNIT *targetUnit;
- int idx;
+ int targetSlotIdForUnit;
targetUnit=AQHREACT_Link_GetTargetUnit(lnk);
- idx=AQHREACT_Link_GetTargetInputSlotIdx(lnk);
- if (targetUnit && idx>=0)
- AQHREACT_Unit_InputData(targetUnit, idx, dataObject);
+ targetSlotIdForUnit=AQHREACT_Link_GetTargetInputSlotIdForUnit(lnk);
+ if (targetUnit && targetSlotIdForUnit>=0)
+ AQHREACT_Unit_InputData(targetUnit, targetSlotIdForUnit, dataObject);
lnk=AQHREACT_Link_List_Next(lnk);
} /* while */
} /* if linkList */
@@ -240,11 +357,41 @@ void AQHREACT_Unit_OutputData(AQHREACT_UNIT *unit, int slotIndex, const AQHREACT
-void AQHREACT_Unit_InputData(AQHREACT_UNIT *unit, int slotIndex, const AQHREACT_DATAOBJECT *dataObject)
+void AQHREACT_Unit_OutputDoubleData(AQHREACT_UNIT *unit, int slotIndex, double data)
+{
+ if (unit && unit->outputSlotList) {
+ AQHREACT_DATAOBJECT *dataObject;
+
+ dataObject=AQHREACT_DataObject_new();
+ AQHREACT_DataObject_SetDataType(dataObject, AQHREACT_DATAOBJECTTYPE_DOUBLE);
+ AQHREACT_DataObject_SetDoubleData(dataObject, data);
+ AQHREACT_Unit_OutputData(unit, slotIndex, dataObject);
+ AQHREACT_DataObject_free(dataObject);
+ }
+}
+
+
+
+void AQHREACT_Unit_OutputStringData(AQHREACT_UNIT *unit, int slotIndex, const char *data)
+{
+ if (unit && unit->outputSlotList) {
+ AQHREACT_DATAOBJECT *dataObject;
+
+ dataObject=AQHREACT_DataObject_new();
+ AQHREACT_DataObject_SetDataType(dataObject, AQHREACT_DATAOBJECTTYPE_STRING);
+ AQHREACT_DataObject_SetStringData(dataObject, data);
+ AQHREACT_Unit_OutputData(unit, slotIndex, dataObject);
+ AQHREACT_DataObject_free(dataObject);
+ }
+}
+
+
+
+void AQHREACT_Unit_InputData(AQHREACT_UNIT *unit, int slotIdForUnit, const AQHREACT_DATAOBJECT *dataObject)
{
if (unit) {
if (unit->inputDataFn)
- (unit->inputDataFn)(unit, slotIndex, dataObject);
+ (unit->inputDataFn)(unit, slotIdForUnit, dataObject);
else {
AQHREACT_Unit_ClearChangeFlagsInUnitAndInputSlots(unit);
}
@@ -261,16 +408,8 @@ int AQHREACT_Unit_Process(AQHREACT_UNIT *unit)
if (unit) {
if (unit->processFn)
return (unit->processFn)(unit);
- else {
- AQHREACT_INPUT_SLOT *slot;
-
- unit->flags&=~AQHREACT_UNIT_FLAGS_CHANGED;
- slot=AQHREACT_InputSlot_List_First(unit->inputSlotList);
- while(slot) {
- AQHREACT_InputSlot_SubFlags(slot, AQHREACT_UNIT_FLAGS_CHANGED);
- slot=AQHREACT_InputSlot_List_Next(slot);
- }
- }
+ else
+ AQHREACT_Unit_ClearChangeFlagsInUnitAndInputSlots(unit);
}
return 0;
@@ -278,18 +417,17 @@ int AQHREACT_Unit_Process(AQHREACT_UNIT *unit)
-AQHREACT_INPUT_SLOT *AQHREACT_Unit_GetInputSlotAt(AQHREACT_UNIT *unit, int idx)
+AQHREACT_INPUT_SLOT *AQHREACT_Unit_GetInputSlotByIdForUnit(const AQHREACT_UNIT *unit, int id)
{
- if (unit && unit->inputSlotList && idx>=0) {
+ if (unit && unit->inputSlotList && id>=0) {
AQHREACT_INPUT_SLOT *slot;
slot=AQHREACT_InputSlot_List_First(unit->inputSlotList);
- while(slot && idx) {
+ while(slot) {
+ if (AQHREACT_InputSlot_GetIdForUnit(slot)==id)
+ return slot;
slot=AQHREACT_InputSlot_List_Next(slot);
- idx--;
}
-
- return slot;
}
return NULL;
@@ -297,15 +435,92 @@ AQHREACT_INPUT_SLOT *AQHREACT_Unit_GetInputSlotAt(AQHREACT_UNIT *unit, int idx)
-AQHREACT_OUTPUT_SLOT *AQHREACT_Unit_GetOutputSlotAt(AQHREACT_UNIT *unit, int idx)
+AQHREACT_INPUT_SLOT *AQHREACT_Unit_GetInputSlotByName(const AQHREACT_UNIT *unit, const char *s)
{
- if (unit && unit->outputSlotList && idx>=0) {
+ if (unit && unit->inputSlotList && s && *s) {
+ AQHREACT_INPUT_SLOT *slot;
+
+ slot=AQHREACT_InputSlot_List_First(unit->inputSlotList);
+ while(slot) {
+ const char *slotName;
+
+ slotName=AQHREACT_InputSlot_GetName(slot);
+ if (slotName && *slotName && strcasecmp(slotName, s)==0)
+ return slot;
+ slot=AQHREACT_InputSlot_List_Next(slot);
+ }
+ }
+
+ return NULL;
+}
+
+
+
+AQHREACT_INPUT_SLOT *AQHREACT_Unit_GetOrCreateUnusedInputSlotByName(AQHREACT_UNIT *unit, const char *s)
+{
+ if (unit && unit->inputSlotList && s && *s) {
+ AQHREACT_INPUT_SLOT *slot;
+
+ slot=AQHREACT_Unit_GetInputSlotByName(unit, s);
+ if (slot) {
+ uint32_t flags;
+ const char *slotName;
+
+ slotName=AQHREACT_InputSlot_GetName(slot);
+ flags=AQHREACT_InputSlot_GetFlags(slot);
+ if (!(flags & AQHREACT_UNIT_FLAGS_INUSE))
+ return slot;
+ if (flags & AQHREACT_UNIT_FLAGS_MULTI) { /* can we just duplicate existing? */
+ AQHREACT_INPUT_SLOT *newSlot;
+
+ newSlot=AQHREACT_InputSlot_dup(slot);
+ AQHREACT_InputSlot_SetIdForUnit(newSlot, (unit->nextInputSlotId)++);
+ AQHREACT_Unit_AddInputSlot(unit, newSlot);
+ return newSlot;
+ }
+ else {
+ DBG_INFO(NULL, "Slot \"%s\" found but in use, no multi flag", slotName);
+ }
+ }
+ }
+
+ return NULL;
+}
+
+
+
+AQHREACT_OUTPUT_SLOT *AQHREACT_Unit_GetOutputSlotByIdForUnit(const AQHREACT_UNIT *unit, int id)
+{
+ if (unit && unit->outputSlotList && id>=0) {
AQHREACT_OUTPUT_SLOT *slot;
slot=AQHREACT_OutputSlot_List_First(unit->outputSlotList);
- while(slot && idx) {
+ while(slot) {
+ if (AQHREACT_OutputSlot_GetIdForUnit(slot)==id)
+ return slot;
+ slot=AQHREACT_OutputSlot_List_Next(slot);
+ }
+ }
+
+ return NULL;
+}
+
+
+
+AQHREACT_OUTPUT_SLOT *AQHREACT_Unit_GetOutputSlotByName(const AQHREACT_UNIT *unit, const char *s)
+{
+ if (unit && unit->outputSlotList && s && *s) {
+ AQHREACT_OUTPUT_SLOT *slot;
+
+ slot=AQHREACT_OutputSlot_List_First(unit->outputSlotList);
+ while(slot) {
+ const char *slotName;
+
+ slotName=AQHREACT_OutputSlot_GetName(slot);
+ if (slotName && *slotName && strcasecmp(slotName, s)==0)
+ return slot;
+
slot=AQHREACT_OutputSlot_List_Next(slot);
- idx--;
}
return slot;
}
@@ -331,5 +546,25 @@ void AQHREACT_Unit_ClearChangeFlagsInUnitAndInputSlots(AQHREACT_UNIT *unit)
+int AQHREACT_Unit_InputHasChanged(const AQHREACT_UNIT *unit)
+{
+ if (unit) {
+ AQHREACT_INPUT_SLOT *slot;
+
+ if (unit->flags & AQHREACT_UNIT_FLAGS_CHANGED)
+ return 1;
+
+ slot=AQHREACT_InputSlot_List_First(unit->inputSlotList);
+ while(slot) {
+ if (AQHREACT_InputSlot_GetFlags(slot) & AQHREACT_UNIT_FLAGS_CHANGED)
+ return 1;
+ slot=AQHREACT_InputSlot_List_Next(slot);
+ }
+ }
+ return 0;
+}
+
+
+
diff --git a/apps/aqhome-react/types/unit.h b/apps/aqhome-react/types/unit.h
index f89a4e7..db20906 100644
--- a/apps/aqhome-react/types/unit.h
+++ b/apps/aqhome-react/types/unit.h
@@ -31,7 +31,7 @@ GWEN_INHERIT_FUNCTION_DEFS(AQHREACT_UNIT)
#include "aqhome-react/types/param.h"
-typedef void (*AQHREACT_UNIT_INPUTDATA_FN)(AQHREACT_UNIT *unit, int slotIndex, const AQHREACT_DATAOBJECT *dataObject);
+typedef void (*AQHREACT_UNIT_INPUTDATA_FN)(AQHREACT_UNIT *unit, int slotIdForUnit, const AQHREACT_DATAOBJECT *dataObject);
typedef int (*AQHREACT_UNIT_PROCESS_FN)(AQHREACT_UNIT *unit);
@@ -48,6 +48,9 @@ void AQHREACT_Unit_SetDescription(AQHREACT_UNIT *unit, const char *s);
const char *AQHREACT_Unit_GetId(const AQHREACT_UNIT *unit);
void AQHREACT_Unit_SetId(AQHREACT_UNIT *unit, const char *s);
+int AQHREACT_Unit_GetNextInputSlotId(const AQHREACT_UNIT *unit);
+void AQHREACT_Unit_SetNextInputSlotId(AQHREACT_UNIT *unit, int i);
+
uint32_t AQHREACT_Unit_GetFlags(const AQHREACT_UNIT *unit);
void AQHREACT_Unit_SetFlags(AQHREACT_UNIT *unit, uint32_t i);
void AQHREACT_Unit_AddFlags(AQHREACT_UNIT *unit, uint32_t i);
@@ -55,22 +58,33 @@ void AQHREACT_Unit_SubFlags(AQHREACT_UNIT *unit, uint32_t i);
AQHREACT_INPUT_SLOT_LIST *AQHREACT_Unit_GetInputSlots(const AQHREACT_UNIT *unit);
void AQHREACT_Unit_AddInputSlot(AQHREACT_UNIT *unit, AQHREACT_INPUT_SLOT *inSlot);
-AQHREACT_INPUT_SLOT *AQHREACT_Unit_GetInputSlotAt(AQHREACT_UNIT *unit, int idx);
+AQHREACT_INPUT_SLOT *AQHREACT_Unit_GetInputSlotByIdForUnit(const AQHREACT_UNIT *unit, int id);
+AQHREACT_INPUT_SLOT *AQHREACT_Unit_GetInputSlotByName(const AQHREACT_UNIT *unit, const char *s);
+
AQHREACT_OUTPUT_SLOT_LIST *AQHREACT_Unit_GetOutputSlots(const AQHREACT_UNIT *unit);
void AQHREACT_Unit_AddOutputSlot(AQHREACT_UNIT *unit, AQHREACT_OUTPUT_SLOT *outSlot);
-AQHREACT_OUTPUT_SLOT *AQHREACT_Unit_GetOutputSlotAt(AQHREACT_UNIT *unit, int idx);
+AQHREACT_OUTPUT_SLOT *AQHREACT_Unit_GetOutputSlotByIdForUnit(const AQHREACT_UNIT *unit, int idx);
+AQHREACT_OUTPUT_SLOT *AQHREACT_Unit_GetOutputSlotByName(const AQHREACT_UNIT *unit, const char *s);
void AQHREACT_Unit_ClearChangeFlagsInUnitAndInputSlots(AQHREACT_UNIT *unit);
+int AQHREACT_Unit_InputHasChanged(const AQHREACT_UNIT *unit);
AQHREACT_PARAM_LIST *AQHREACT_Unit_GetParamList(const AQHREACT_UNIT *unit);
void AQHREACT_Unit_AddParam(AQHREACT_UNIT *unit, AQHREACT_PARAM *param);
-
+AQHREACT_PARAM *AQHREACT_Unit_GetParamByName(const AQHREACT_UNIT *unit, const char *s);
+double AQHREACT_Unit_GetParamValueDouble(const AQHREACT_UNIT *unit, const char *s, double defVal);
+void AQHREACT_Unit_SetParamValueDouble(AQHREACT_UNIT *unit, const char *paramName, double val);
+const char *AQHREACT_Unit_GetParamValueString(const AQHREACT_UNIT *unit, const char *paramName, const char *defVal);
+void AQHREACT_Unit_SetParamValueString(AQHREACT_UNIT *unit, const char *paramName, const char *val);
void AQHREACT_Unit_OutputData(AQHREACT_UNIT *unit, int slotIndex, const AQHREACT_DATAOBJECT *dataObject);
+void AQHREACT_Unit_OutputDoubleData(AQHREACT_UNIT *unit, int slotIndex, double data);
+void AQHREACT_Unit_OutputStringData(AQHREACT_UNIT *unit, int slotIndex, const char *data);
-void AQHREACT_Unit_InputData(AQHREACT_UNIT *unit, int slotIndex, const AQHREACT_DATAOBJECT *dataObject);
+
+void AQHREACT_Unit_InputData(AQHREACT_UNIT *unit, int slotIdForUnit, const AQHREACT_DATAOBJECT *dataObject);
int AQHREACT_Unit_Process(AQHREACT_UNIT *unit);
diff --git a/apps/aqhome-react/types/unit_p.h b/apps/aqhome-react/types/unit_p.h
index ab82de0..0b03e3e 100644
--- a/apps/aqhome-react/types/unit_p.h
+++ b/apps/aqhome-react/types/unit_p.h
@@ -23,6 +23,8 @@ struct AQHREACT_UNIT {
uint32_t flags;
+ int nextInputSlotId;
+
AQHREACT_INPUT_SLOT_LIST *inputSlotList;
AQHREACT_OUTPUT_SLOT_LIST *outputSlotList;
diff --git a/apps/aqhome-react/units/0BUILD b/apps/aqhome-react/units/0BUILD
new file mode 100644
index 0000000..1dbf4ef
--- /dev/null
+++ b/apps/aqhome-react/units/0BUILD
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+ $(gwenhywfar_cflags)
+ -I$(topsrcdir)
+ -I$(topbuilddir)
+ -I$(topsrcdir)/apps
+ -I$(topbuilddir)/apps
+ -I$(builddir)
+ -I$(srcdir)
+
+
+
+ --include=$(builddir)
+ --include=$(srcdir)
+
+
+ $(visibility_cflags)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ u_or.h
+ u_varchanges.h
+ u_valuefilter.h
+
+
+
+ $(local/typefiles)
+ u_or.c
+ u_varchanges.c
+ u_valuefilter.c
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/aqhome-react/units/u_or.c b/apps/aqhome-react/units/u_or.c
new file mode 100644
index 0000000..8ac5458
--- /dev/null
+++ b/apps/aqhome-react/units/u_or.c
@@ -0,0 +1,136 @@
+/****************************************************************************
+ * This file is part of the project AqHome.
+ * AqHome (c) by 2024 Martin Preuss, all rights reserved.
+ *
+ * The license for this file can be found in the file COPYING which you
+ * should have received along with this file.
+ ****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include
+#endif
+
+#include "./u_or.h"
+
+#include
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * defines
+ * ------------------------------------------------------------------------------------------------
+ */
+
+#define AQHOMEREACT_UNIT_OR_INSLOT_INPUT 0
+#define AQHOMEREACT_UNIT_OR_INSLOT_AUTOINPUT 100
+
+#define AQHOMEREACT_UNIT_OR_OUTSLOT_RESULT 0
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * forward declarations
+ * ------------------------------------------------------------------------------------------------
+ */
+
+static int _cbProcessFn(AQHREACT_UNIT *unit);
+static int _sampleInputSlots(const AQHREACT_INPUT_SLOT_LIST *inputSlotList);
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * implementations
+ * ------------------------------------------------------------------------------------------------
+ */
+
+AQHREACT_UNIT *AqHomeReact_UnitOr_new(AQHOME_REACT *aqh)
+{
+ AQHREACT_UNIT *unit;
+ AQHREACT_OUTPUT_SLOT *outputSlot;
+ AQHREACT_INPUT_SLOT *inputSlot;
+
+ unit=AQHREACT_Unit_new();
+ AQHREACT_Unit_SetName(unit, "or");
+ AQHREACT_Unit_SetDescription(unit, "Logical OR inputs");
+ AQHREACT_Unit_SetProcessFn(unit, _cbProcessFn);
+
+ AQHREACT_Unit_SetNextInputSlotId(unit, AQHOMEREACT_UNIT_OR_INSLOT_AUTOINPUT); /* for auto-gen multi-slots */
+
+ outputSlot=AQHREACT_OutputSlot_new();
+ AQHREACT_OutputSlot_SetName(outputSlot, "output");
+ AQHREACT_OutputSlot_SetIdForUnit(outputSlot, AQHOMEREACT_UNIT_OR_OUTSLOT_RESULT);
+ AQHREACT_OutputSlot_SetEmittedDataType(outputSlot, AQHREACT_DATAOBJECTTYPE_DOUBLE);
+ AQHREACT_Unit_AddOutputSlot(unit, outputSlot);
+
+ inputSlot=AQHREACT_InputSlot_new();
+ AQHREACT_InputSlot_SetName(inputSlot, "input");
+ AQHREACT_InputSlot_SetIdForUnit(inputSlot, AQHOMEREACT_UNIT_OR_INSLOT_INPUT);
+ AQHREACT_InputSlot_SetAcceptedDataType(inputSlot, AQHREACT_DATAOBJECTTYPE_DOUBLE);
+ AQHREACT_InputSlot_AddFlags(inputSlot, AQHREACT_UNIT_FLAGS_MULTI);
+ AQHREACT_Unit_AddInputSlot(unit, inputSlot);
+
+ return unit;
+}
+
+
+
+int _cbProcessFn(AQHREACT_UNIT *unit)
+{
+ if (unit && AQHREACT_Unit_InputHasChanged(unit)) {
+ AQHREACT_INPUT_SLOT_LIST *inputSlotList;
+
+ inputSlotList=AQHREACT_Unit_GetInputSlots(unit);
+ if (inputSlotList) {
+ int result;
+
+ result=_sampleInputSlots(inputSlotList);
+ AQHREACT_Unit_ClearChangeFlagsInUnitAndInputSlots(unit);
+ AQHREACT_Unit_OutputDoubleData(unit, AQHOMEREACT_UNIT_OR_OUTSLOT_RESULT, result?1.0:0.0);
+ }
+ }
+
+ return 0;
+}
+
+
+
+int _sampleInputSlots(const AQHREACT_INPUT_SLOT_LIST *inputSlotList)
+{
+ const AQHREACT_INPUT_SLOT *inputSlot;
+ int result=0;
+
+ inputSlot=AQHREACT_InputSlot_List_First(inputSlotList);
+ while(inputSlot) {
+ const char *slotName;
+ const AQHREACT_DATAOBJECT *dataObject;
+
+ slotName=AQHREACT_InputSlot_GetName(inputSlot);
+ dataObject=AQHREACT_InputSlot_GetCurrentDataObject(inputSlot);
+ if (dataObject) {
+ if (AQHREACT_DataObject_GetDataType(dataObject)==AQHREACT_DATAOBJECTTYPE_DOUBLE) {
+ double d;
+
+ d=AQHREACT_DataObject_GetDoubleData(dataObject);
+ if (d>0.0)
+ result|=1;
+ }
+ else {
+ DBG_ERROR(NULL, "Data at input slot \"%s\" isn't DOUBLE, ignoring", slotName?slotName:"");
+ }
+ }
+ else {
+ DBG_ERROR(NULL, "Data at input slot \"%s\" has not current data, ignoring", slotName?slotName:"");
+ }
+
+ inputSlot=AQHREACT_InputSlot_List_Next(inputSlot);
+ }
+
+ return result;
+}
+
+
+
+
+
+
+
diff --git a/apps/aqhome-react/units/u_or.h b/apps/aqhome-react/units/u_or.h
new file mode 100644
index 0000000..bd7b1aa
--- /dev/null
+++ b/apps/aqhome-react/units/u_or.h
@@ -0,0 +1,23 @@
+/****************************************************************************
+ * This file is part of the project AqHome.
+ * AqHome (c) by 2024 Martin Preuss, all rights reserved.
+ *
+ * The license for this file can be found in the file COPYING which you
+ * should have received along with this file.
+ ****************************************************************************/
+
+#ifndef AQHOMEREACT_U_OR_H
+#define AQHOMEREACT_U_OR_H
+
+
+#include "aqhome-react/aqhome_react.h"
+#include "aqhome-react/types/unit.h"
+
+
+AQHREACT_UNIT *AqHomeReact_UnitOr_new(AQHOME_REACT *aqh);
+
+
+
+#endif
+
+
diff --git a/apps/aqhome-react/units/u_valuefilter.c b/apps/aqhome-react/units/u_valuefilter.c
new file mode 100644
index 0000000..160311a
--- /dev/null
+++ b/apps/aqhome-react/units/u_valuefilter.c
@@ -0,0 +1,96 @@
+/****************************************************************************
+ * This file is part of the project AqHome.
+ * AqHome (c) by 2024 Martin Preuss, all rights reserved.
+ *
+ * The license for this file can be found in the file COPYING which you
+ * should have received along with this file.
+ ****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include
+#endif
+
+#include "./u_valuefilter.h"
+
+#include
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * defines
+ * ------------------------------------------------------------------------------------------------
+ */
+
+#define AQHOMEREACT_UNIT_VALUEFILTER_INSLOT_VALUE 0
+
+#define AQHOMEREACT_UNIT_VALUEFILTER_OUTSLOT_RESULT 0
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * forward declarations
+ * ------------------------------------------------------------------------------------------------
+ */
+
+static void _cbInputData(AQHREACT_UNIT *unit, int slotIdForUnit, const AQHREACT_DATAOBJECT *dataObject);
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * implementations
+ * ------------------------------------------------------------------------------------------------
+ */
+
+AQHREACT_UNIT *AqHomeReact_UnitValueFilter_new(AQHOME_REACT *aqh)
+{
+ AQHREACT_UNIT *unit;
+ AQHREACT_OUTPUT_SLOT *outputSlot;
+ AQHREACT_INPUT_SLOT *inputSlot;
+ AQHREACT_PARAM *param;
+
+ unit=AQHREACT_Unit_new();
+ AQHREACT_Unit_SetName(unit, "valuefilter");
+ AQHREACT_Unit_SetDescription(unit, "Filter values received from unit VarChanges by value path");
+ AQHREACT_Unit_SetInputDataFn(unit, _cbInputData);
+
+ outputSlot=AQHREACT_OutputSlot_new();
+ AQHREACT_OutputSlot_SetName(outputSlot, "output");
+ AQHREACT_OutputSlot_SetIdForUnit(outputSlot, AQHOMEREACT_UNIT_VALUEFILTER_OUTSLOT_RESULT);
+ AQHREACT_OutputSlot_SetEmittedDataType(outputSlot, AQHREACT_DATAOBJECTTYPE_DOUBLE);
+ AQHREACT_Unit_AddOutputSlot(unit, outputSlot);
+
+ inputSlot=AQHREACT_InputSlot_new();
+ AQHREACT_InputSlot_SetName(inputSlot, "input");
+ AQHREACT_InputSlot_SetIdForUnit(inputSlot, AQHOMEREACT_UNIT_VALUEFILTER_INSLOT_VALUE);
+ AQHREACT_InputSlot_SetAcceptedDataType(inputSlot, AQHREACT_DATAOBJECTTYPE_DOUBLE);
+ AQHREACT_Unit_AddInputSlot(unit, inputSlot);
+
+ param=AQHREACT_Param_new();
+ AQHREACT_Param_SetName(param, AQHOMEREACT_UNIT_VALUEFILTER_PARAM_VALUENAME);
+ AQHREACT_Param_SetDataType(param, AQHREACT_DATAOBJECTTYPE_STRING);
+ AQHREACT_Unit_AddParam(unit, param);
+
+ return unit;
+}
+
+
+
+void _cbInputData(AQHREACT_UNIT *unit, int slotIdForUnit, const AQHREACT_DATAOBJECT *dataObject)
+{
+ if (unit && dataObject && slotIdForUnit==AQHOMEREACT_UNIT_VALUEFILTER_INSLOT_VALUE) {
+ const char *sSystemValueId;
+
+ sSystemValueId=AQHREACT_DataObject_GetSystemValueId(dataObject);
+ if (sSystemValueId && *sSystemValueId) {
+ const char *sFilter;
+
+ sFilter=AQHREACT_Unit_GetParamValueString(unit, AQHOMEREACT_UNIT_VALUEFILTER_PARAM_VALUENAME, NULL);
+ if (sFilter && *sFilter && strcasecmp(sSystemValueId, sFilter)==0)
+ AQHREACT_Unit_OutputData(unit, AQHOMEREACT_UNIT_VALUEFILTER_OUTSLOT_RESULT, dataObject);
+ }
+ }
+}
+
+
+
+
diff --git a/apps/aqhome-react/units/u_valuefilter.h b/apps/aqhome-react/units/u_valuefilter.h
new file mode 100644
index 0000000..183ac53
--- /dev/null
+++ b/apps/aqhome-react/units/u_valuefilter.h
@@ -0,0 +1,26 @@
+/****************************************************************************
+ * This file is part of the project AqHome.
+ * AqHome (c) by 2024 Martin Preuss, all rights reserved.
+ *
+ * The license for this file can be found in the file COPYING which you
+ * should have received along with this file.
+ ****************************************************************************/
+
+#ifndef AQHOMEREACT_U_VALUEFILTER_H
+#define AQHOMEREACT_U_VALUEFILTER_H
+
+
+#include "aqhome-react/aqhome_react.h"
+#include "aqhome-react/types/unit.h"
+
+
+#define AQHOMEREACT_UNIT_VALUEFILTER_PARAM_VALUENAME "valueName"
+
+
+AQHREACT_UNIT *AqHomeReact_UnitValueFilter_new(AQHOME_REACT *aqh);
+
+
+
+#endif
+
+
diff --git a/apps/aqhome-react/units/u_varchanges.c b/apps/aqhome-react/units/u_varchanges.c
new file mode 100644
index 0000000..497276e
--- /dev/null
+++ b/apps/aqhome-react/units/u_varchanges.c
@@ -0,0 +1,81 @@
+/****************************************************************************
+ * This file is part of the project AqHome.
+ * AqHome (c) by 2024 Martin Preuss, all rights reserved.
+ *
+ * The license for this file can be found in the file COPYING which you
+ * should have received along with this file.
+ ****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include
+#endif
+
+#include "./u_varchanges.h"
+
+#include
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * defines
+ * ------------------------------------------------------------------------------------------------
+ */
+
+#define AQHOMEREACT_UNIT_VARCHANGES_INSLOT_CHANGE 0
+
+#define AQHOMEREACT_UNIT_VARCHANGES_OUTSLOT_RESULT 0
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * forward declarations
+ * ------------------------------------------------------------------------------------------------
+ */
+
+static void _cbInputData(AQHREACT_UNIT *unit, int slotIdForUnit, const AQHREACT_DATAOBJECT *dataObject);
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * implementations
+ * ------------------------------------------------------------------------------------------------
+ */
+
+AQHREACT_UNIT *AqHomeReact_UnitVarChanges_new(AQHOME_REACT *aqh)
+{
+ AQHREACT_UNIT *unit;
+ AQHREACT_OUTPUT_SLOT *outputSlot;
+ AQHREACT_INPUT_SLOT *inputSlot;
+
+ unit=AQHREACT_Unit_new();
+ AQHREACT_Unit_SetName(unit, "varchanges");
+ AQHREACT_Unit_SetDescription(unit, "Propagates changes of values on the data server");
+ AQHREACT_Unit_SetInputDataFn(unit, _cbInputData);
+
+ outputSlot=AQHREACT_OutputSlot_new();
+ AQHREACT_OutputSlot_SetName(outputSlot, "output");
+ AQHREACT_OutputSlot_SetIdForUnit(outputSlot, AQHOMEREACT_UNIT_VARCHANGES_OUTSLOT_RESULT);
+ AQHREACT_OutputSlot_SetEmittedDataType(outputSlot, AQHREACT_DATAOBJECTTYPE_DOUBLE);
+ AQHREACT_Unit_AddOutputSlot(unit, outputSlot);
+
+ inputSlot=AQHREACT_InputSlot_new();
+ AQHREACT_InputSlot_SetName(inputSlot, "input");
+ AQHREACT_InputSlot_SetIdForUnit(inputSlot, AQHOMEREACT_UNIT_VARCHANGES_INSLOT_CHANGE);
+ AQHREACT_InputSlot_SetAcceptedDataType(inputSlot, AQHREACT_DATAOBJECTTYPE_DOUBLE);
+ AQHREACT_Unit_AddInputSlot(unit, inputSlot);
+
+ return unit;
+}
+
+
+
+
+void _cbInputData(AQHREACT_UNIT *unit, int slotIdForUnit, const AQHREACT_DATAOBJECT *dataObject)
+{
+ if (unit && dataObject && slotIdForUnit==0)
+ AQHREACT_Unit_OutputData(unit, AQHOMEREACT_UNIT_VARCHANGES_OUTSLOT_RESULT, dataObject);
+}
+
+
+
+
diff --git a/apps/aqhome-react/units/u_varchanges.h b/apps/aqhome-react/units/u_varchanges.h
new file mode 100644
index 0000000..3897593
--- /dev/null
+++ b/apps/aqhome-react/units/u_varchanges.h
@@ -0,0 +1,23 @@
+/****************************************************************************
+ * This file is part of the project AqHome.
+ * AqHome (c) by 2024 Martin Preuss, all rights reserved.
+ *
+ * The license for this file can be found in the file COPYING which you
+ * should have received along with this file.
+ ****************************************************************************/
+
+#ifndef AQHOMEREACT_U_VARCHANGES_H
+#define AQHOMEREACT_U_VARCHANGES_H
+
+
+#include "aqhome-react/aqhome_react.h"
+#include "aqhome-react/types/unit.h"
+
+
+AQHREACT_UNIT *AqHomeReact_UnitVarChanges_new(AQHOME_REACT *aqh);
+
+
+
+#endif
+
+