Rind 3 lat temu
commit
3a7d9dac9d
93 zmienionych plików z 19921 dodań i 0 usunięć
  1. 12 0
      .gitignore
  2. 16 0
      README.md
  3. 85 0
      common/common.pro
  4. 262 0
      common/conv.c
  5. 1214 0
      common/conv.h
  6. 28 0
      common/debug.h
  7. 142 0
      common/fileutil.c
  8. 30 0
      common/fileutil.h
  9. 153 0
      common/instance.cpp
  10. 44 0
      common/instance.h
  11. 369 0
      common/logfile.cpp
  12. 105 0
      common/logfile.h
  13. 409 0
      common/mysqlwrap.cpp
  14. 192 0
      common/mysqlwrap.h
  15. 115 0
      common/processclock.cpp
  16. 132 0
      common/processclock.h
  17. 92 0
      common/singleton.c
  18. 22 0
      common/singleton.h
  19. 175 0
      common/strutil.cpp
  20. 36 0
      common/strutil.h
  21. 69 0
      common/uuid.c
  22. 57 0
      common/uuid.h
  23. 8 0
      datalogger/.gitignore
  24. 1352 0
      datalogger/datalogger.cpp
  25. 235 0
      datalogger/datalogger.h
  26. 61 0
      datalogger/datalogger.pro
  27. 63 0
      datalogger/dataloggerclock.cpp
  28. 229 0
      datalogger/logbitvar.cpp
  29. 555 0
      datalogger/logvar.cpp
  30. 198 0
      datalogger/logvar.h
  31. 538 0
      datalogger/main.cpp
  32. 15 0
      libgfaservices.pro
  33. 12 0
      mqttcl/cfg/mqtt.conf.json
  34. 1202 0
      mqttcl/main.cpp
  35. 342 0
      mqttcl/mqttcfg.cpp
  36. 123 0
      mqttcl/mqttcfg.h
  37. 76 0
      mqttcl/mqttcl.pro
  38. 362 0
      mqttcl/mqttclient.cpp
  39. 172 0
      mqttcl/mqttclient.h
  40. 43 0
      mqttcl/mqttdbg.h
  41. 80 0
      mqttcl/mqttjson.h
  42. 208 0
      mqttcl/mqttmsg.cpp
  43. 124 0
      mqttcl/mqttmsg.h
  44. 46 0
      mqttcl/mqttmsgpool.cpp
  45. 49 0
      mqttcl/mqttmsgqueue.cpp
  46. 1463 0
      mqttcl/mqttvar.h
  47. 132 0
      mqttcl/mqttvartbl.cpp
  48. 16 0
      mqttcl/tls/.gitignore
  49. 37 0
      mqttcl/tls/certgen.sh
  50. 11 0
      mqttcl/tls/clear.sh
  51. 18 0
      mqttcl/tls/client.conf
  52. 53 0
      mqttcl/tls/openssl.conf
  53. 29 0
      mqttcl/tls/server.conf
  54. 106 0
      mqttcl/topic.txt
  55. 8 0
      remanent/.gitignore
  56. 149 0
      remanent/dbpersist.cpp
  57. 658 0
      remanent/main.cpp
  58. 64 0
      remanent/remanent.pro
  59. 177 0
      remanent/rembitvar.cpp
  60. 186 0
      remanent/remlogger.cpp
  61. 72 0
      remanent/remlogger.h
  62. 402 0
      remanent/remstrvar.cpp
  63. 602 0
      remanent/remvar.cpp
  64. 347 0
      remanent/remvar.h
  65. 143 0
      remanent/remvartbl.cpp
  66. 8 0
      rest/.gitignore
  67. 35 0
      rest/apikey.cpp
  68. 38 0
      rest/apikey.h
  69. 503 0
      rest/callback.cpp
  70. 54 0
      rest/callback.h
  71. 32 0
      rest/defines.h
  72. 113 0
      rest/helpers.cpp
  73. 15 0
      rest/helpers.h
  74. 87 0
      rest/keys.c
  75. 489 0
      rest/main.cpp
  76. 39 0
      rest/main.h
  77. 221 0
      rest/request-response-format.txt
  78. 70 0
      rest/rest.pro
  79. 101 0
      rest/restbitvar.cpp
  80. 265 0
      rest/reststrvar.cpp
  81. 306 0
      rest/restvar.cpp
  82. 524 0
      rest/restvar.h
  83. 31 0
      rest/restvartbl.cpp
  84. 122 0
      shmqml/shmbitvar.cpp
  85. 62 0
      shmqml/shmqml.pro
  86. 339 0
      shmqml/shmstrvar.cpp
  87. 265 0
      shmqml/shmthread.h
  88. 545 0
      shmqml/shmvar.cpp
  89. 238 0
      shmqml/shmvar.h
  90. 8 0
      summarist/.gitignore
  91. 724 0
      summarist/summarist.cpp
  92. 107 0
      summarist/summarist.h
  93. 55 0
      summarist/summarist.pro

+ 12 - 0
.gitignore

@@ -0,0 +1,12 @@
+# git ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
+
+Debug/
+Profile/
+Release/
+*.pro.user*
+buildall.sh

+ 16 - 0
README.md

@@ -0,0 +1,16 @@
+## libgfaservices
+
+
+Statische Bibliotheken und Headerfiles für die GfA-Services.
+  
+---
+
+Enthält folgende Bibliotheken für den PC und die Toolchain:
+
+* **libcommon(d).a**
+* **libdatalogger(d).a**
+* **libmqttcl(d).a**
+* **libremanent(d).a**
+* **librest(d).a**
+* **libshmqml(d).a**
+* **libsummarist(d).a**

+ 85 - 0
common/common.pro

@@ -0,0 +1,85 @@
+CONFIG   += c++11
+CONFIG   -= qt
+CONFIG   += staticlib
+
+TEMPLATE = lib
+
+QMAKE_CXXFLAGS += -Wstrict-aliasing=0 -Wall -Wextra -Wformat -Wconversion -Wuninitialized -Wno-unused-variable -Wno-unused-parameter -Wundef -Wwrite-strings -std=c++11 -pthread -fPIC -D_GNU_SOURCE -Wno-format-overflow -Wno-format-truncation
+QMAKE_CFLAGS += -Wstrict-aliasing=0 -Wall -Wextra -Wformat -Wconversion -Wuninitialized -Wno-unused-variable -Wno-unused-parameter -Wundef -Wwrite-strings -pthread -fPIC -D_GNU_SOURCE -Wno-format-overflow -Wno-format-truncation
+
+_LIB_PATH = $$[QT_SYSROOT]/usr/lib/gfa/svc/
+_INC_PATH = $$[QT_SYSROOT]/usr/include/gfa/svc/common/
+
+CONFIG(debug, debug|release) {
+	QMAKE_CXXFLAGS -= -Os
+	QMAKE_CFLAGS -= -Os
+    QMAKE_CXXFLAGS += -D_DEBUG
+    QMAKE_CFLAGS += -D_DEBUG
+	TARGET = commond
+	QMAKE_CLEAN += libcommond.a
+}
+
+CONFIG(release, debug|release) {
+    QMAKE_CXXFLAGS += -Wno-unused-parameter -Wno-unused-but-set-variable -Wno-unused-result
+    QMAKE_CFLAGS += -Wno-unused-parameter -Wno-unused-but-set-variable -Wno-unused-result
+	TARGET = common
+	QMAKE_CLEAN += libcommon.a
+}
+
+linux-buildroot-g++ {
+    QMAKE_CXXFLAGS += -D_TARGET_BUILD
+    QMAKE_CFLAGS += -D_TARGET_BUILD
+    QMAKE_CXXFLAGS += -DSITARA_BUILD
+    QMAKE_CFLAGS += -DSITARA_BUILD
+}
+
+HEADERS += \
+    conv.h \
+    debug.h \
+    fileutil.h \
+    instance.h \
+    logfile.h \
+    mysqlwrap.h \
+    singleton.h \
+    strutil.h \
+    processclock.h \
+    uuid.h
+
+SOURCES += \
+    instance.cpp \
+    logfile.cpp \
+    mysqlwrap.cpp \
+    strutil.cpp \
+    processclock.cpp \
+    conv.c \
+    fileutil.c \
+    singleton.c \
+    uuid.c
+
+includes.path = $$_INC_PATH
+includes.extra += -$(INSTALL_FILE) $$PWD/conv.h $(INSTALL_ROOT)$$includes.path
+includes.extra += $$escape_expand(\\n\\t)-$(INSTALL_FILE) $$PWD/debug.h $(INSTALL_ROOT)$$includes.path
+includes.extra += $$escape_expand(\\n\\t)-$(INSTALL_FILE) $$PWD/fileutil.h $(INSTALL_ROOT)$$includes.path
+includes.extra += $$escape_expand(\\n\\t)-$(INSTALL_FILE) $$PWD/instance.h $(INSTALL_ROOT)$$includes.path
+includes.extra += $$escape_expand(\\n\\t)-$(INSTALL_FILE) $$PWD/logfile.h $(INSTALL_ROOT)$$includes.path
+includes.extra += $$escape_expand(\\n\\t)-$(INSTALL_FILE) $$PWD/mysqlwrap.h $(INSTALL_ROOT)$$includes.path
+includes.extra += $$escape_expand(\\n\\t)-$(INSTALL_FILE) $$PWD/singleton.h $(INSTALL_ROOT)$$includes.path
+includes.extra += $$escape_expand(\\n\\t)-$(INSTALL_FILE) $$PWD/strutil.h $(INSTALL_ROOT)$$includes.path
+includes.extra += $$escape_expand(\\n\\t)-$(INSTALL_FILE) $$PWD/processclock.h $(INSTALL_ROOT)$$includes.path
+includes.extra += $$escape_expand(\\n\\t)-$(INSTALL_FILE) $$PWD/uuid.h $(INSTALL_ROOT)$$includes.path
+includes.uninstall += -$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/conv.h
+includes.uninstall += $$escape_expand(\\n\\t)-$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/debug.h
+includes.uninstall += $$escape_expand(\\n\\t)-$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/fileutil.h
+includes.uninstall += $$escape_expand(\\n\\t)-$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/instance.h
+includes.uninstall += $$escape_expand(\\n\\t)-$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/logfile.h
+includes.uninstall += $$escape_expand(\\n\\t)-$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/mysqlwrap.h
+includes.uninstall += $$escape_expand(\\n\\t)-$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/singleton.h
+includes.uninstall += $$escape_expand(\\n\\t)-$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/strutil.h
+includes.uninstall += $$escape_expand(\\n\\t)-$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/processclock.h
+includes.uninstall += $$escape_expand(\\n\\t)-$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/uuid.h
+INSTALLS += includes
+
+library.path = $$_LIB_PATH
+library.extra += -$(INSTALL_FILE) $(TARGET) $(INSTALL_ROOT)$$library.path/$(TARGET)
+library.uninstall += -$(DEL_FILE) $(INSTALL_ROOT)$$library.path/$(TARGET)
+INSTALLS += library

+ 262 - 0
common/conv.c

@@ -0,0 +1,262 @@
+#include <errno.h>
+#include <string.h>
+#include <iconv.h>
+#include "conv.h"
+
+#ifndef __BYTE_ORDER__
+#error __BYTE_ORDER__ not defined!
+#endif	//	__BYTE_ORDER__
+
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+#define _UTF_16			"UTF-16LE//"
+#define _UTF_32			"UTF-32LE//"
+#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+#define _UTF_16			"UTF-16BE//"
+#define _UTF_32			"UTF-32BE//"
+#else	//	__BYTE_ORDER__
+#error Invalid Byte Order!
+#endif	//	__BYTE_ORDER__
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static size_t _Conv(iconv_t cd, char **pszIn, size_t nCbIn, char **pszOut, size_t nCbOut, size_t *pnTruncated)
+{
+    size_t nRet = nCbOut;
+    iconv(cd, pszIn, &nCbIn, pszOut, &nCbOut);
+    if(pnTruncated)
+        *pnTruncated = nCbIn;
+    return nRet - nCbOut;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+size_t wcs16len(const char16_t *psz)
+{
+	const char16_t *p = psz;
+	while(*p)
+		p++;
+	return (size_t)(p - psz);
+}
+
+size_t wcs32len(const char32_t *psz)
+{
+	const char32_t *p = psz;
+	while(*p)
+		p++;
+	return (size_t)(p - psz);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+size_t EncToUtf8(const char *pszEncIn, const void *pszIn, size_t nCbIn, char *pszOut, size_t nCbOut)
+{
+	if(pszEncIn && pszIn && pszOut && nCbOut && nCbIn != (size_t)-1)
+	{
+	    iconv_t desc = iconv_open("UTF-8//", pszEncIn);
+
+		if(desc != (iconv_t)-1)
+		{
+			size_t nRet;
+			size_t nTrunc;
+			char *pIn = (char*)pszIn;
+			memset(pszOut, 0, nCbOut);
+			nRet = _Conv(desc, &pIn, nCbIn, &pszOut, nCbOut - 1, &nTrunc);
+			iconv_close(desc);
+			return nRet;
+		}
+	}
+	
+	return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+size_t Utf8ToLatin1(const char *pszIn, size_t nCbIn, char *pszOut, size_t nCbOut)
+{
+	if(pszIn && pszOut && nCbOut)
+	{
+	    iconv_t desc = iconv_open("LATIN1//", "UTF-8//");
+
+		if(desc != (iconv_t)-1)
+		{
+			size_t nRet;
+			size_t nTrunc;
+			char *pIn = (char*)pszIn;
+			if(nCbIn == (size_t)-1)
+				nCbIn = strlen(pszIn);
+			memset(pszOut, 0, nCbOut);
+			nRet = _Conv(desc, &pIn, nCbIn, &pszOut, nCbOut - 1, &nTrunc);
+			iconv_close(desc);
+			return nRet;
+		}
+	}
+	
+	return 0;
+}
+
+size_t Latin1ToUtf8(const char *pszIn, size_t nCbIn, char *pszOut, size_t nCbOut)
+{
+	if(pszIn && pszOut && nCbOut)
+	{
+	    iconv_t desc = iconv_open("UTF-8//", "LATIN1//");
+
+		if(desc != (iconv_t)-1)
+		{
+			size_t nRet;
+			size_t nTrunc;
+			char *pIn = (char*)pszIn;
+			if(nCbIn == (size_t)-1)
+				nCbIn = strlen(pszIn);
+			memset(pszOut, 0, nCbOut);
+			nRet = _Conv(desc, &pIn, nCbIn, &pszOut, nCbOut - 1, &nTrunc);
+			iconv_close(desc);
+			return nRet;
+		}
+	}
+	
+	return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+size_t Utf8ToUtf16(const char *pszIn, size_t nCbIn, char16_t *pszOut, size_t nCChOut)
+{
+	if(pszIn && pszOut && nCChOut)
+	{
+        iconv_t desc = iconv_open(_UTF_16, "UTF-8//");
+
+		if(desc != (iconv_t)-1)
+		{
+			size_t nRet;
+			size_t nTrunc;
+			char *pIn = (char*)pszIn;
+			if(nCbIn == (size_t)-1)
+				nCbIn = strlen(pszIn);
+			memset(pszOut, 0, nCChOut * sizeof(char16_t));
+			nRet = _Conv(desc, &pIn, nCbIn, (char**)&pszOut, (nCChOut - 1) * sizeof(char16_t), &nTrunc);
+			iconv_close(desc);
+			return nRet;
+		}
+	}
+	
+	return 0;
+}
+
+size_t Utf16ToUtf8(const char16_t *pszIn, size_t nCChIn, char *pszOut, size_t nCbOut)
+{
+	if(pszIn && pszOut && nCbOut)
+	{
+	    iconv_t desc = iconv_open("UTF-8//", _UTF_16);
+
+		if(desc != (iconv_t)-1)
+		{
+			size_t nRet;
+			size_t nTrunc;
+			char *pIn = (char*)pszIn;
+			if(nCChIn == (size_t)-1)
+				nCChIn = wcs16len(pszIn);
+			memset(pszOut, 0, nCbOut);
+			nRet = _Conv(desc, &pIn, nCChIn * sizeof(char16_t), &pszOut, nCbOut - 1, &nTrunc);
+			iconv_close(desc);
+			return nRet;
+		}
+	}
+	
+	return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+size_t Utf8ToUtf32(const char *pszIn, size_t nCbIn, char32_t *pszOut, size_t nCChOut)
+{
+	if(pszIn && pszOut && nCChOut)
+	{
+	    iconv_t desc = iconv_open(_UTF_32, "UTF-8//");
+
+		if(desc != (iconv_t)-1)
+		{
+			size_t nRet;
+			size_t nTrunc;
+			char *pIn = (char*)pszIn;
+			if(nCbIn == (size_t)-1)
+				nCbIn = strlen(pszIn);
+			memset(pszOut, 0, nCChOut * sizeof(char32_t));
+			nRet = _Conv(desc, &pIn, nCbIn, (char**)&pszOut, (nCChOut - 1) * sizeof(char32_t), &nTrunc);
+			iconv_close(desc);
+			return nRet;
+		}
+	}
+	
+	return 0;
+}
+
+size_t Utf32ToUtf8(const char32_t *pszIn, size_t nCChIn, char *pszOut, size_t nCbOut)
+{
+	if(pszIn && pszOut && nCbOut)
+	{
+	    iconv_t desc = iconv_open("UTF-8//", _UTF_32);
+
+		if(desc != (iconv_t)-1)
+		{
+			size_t nRet;
+			size_t nTrunc;
+			char *pIn = (char*)pszIn;
+			if(nCChIn == (size_t)-1)
+				nCChIn = wcs32len(pszIn);
+			memset(pszOut, 0, nCbOut);
+			nRet = _Conv(desc, &pIn, nCChIn * sizeof(char32_t), &pszOut, nCbOut - 1, &nTrunc);
+			iconv_close(desc);
+			return nRet;
+		}
+	}
+	
+	return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+size_t Utf8ToWcs(const char *pszIn, size_t nCbIn, wchar_t *pszOut, size_t nCChOut)
+{
+	if(pszIn && pszOut && nCChOut)
+	{
+	    iconv_t desc = iconv_open("WCHAR_T//", "UTF-8//");
+
+		if(desc != (iconv_t)-1)
+		{
+			size_t nRet;
+			size_t nTrunc;
+			char *pIn = (char*)pszIn;
+			if(nCbIn == (size_t)-1)
+				nCbIn = strlen(pszIn);
+			memset(pszOut, 0, nCChOut * sizeof(wchar_t));
+			nRet = _Conv(desc, &pIn, nCbIn, (char**)&pszOut, (nCChOut - 1) * sizeof(wchar_t), &nTrunc);
+			iconv_close(desc);
+			return nRet;
+		}
+	}
+	
+	return 0;
+}
+
+size_t WcsToUtf8(const wchar_t *pszIn, size_t nCChIn, char *pszOut, size_t nCbOut)
+{
+	if(pszIn && pszOut && nCbOut)
+	{
+	    iconv_t desc = iconv_open("UTF-8//", "WCHAR_T//");
+
+		if(desc != (iconv_t)-1)
+		{
+			size_t nRet;
+			size_t nTrunc;
+			char *pIn = (char*)pszIn;
+			if(nCChIn == (size_t)-1)
+				nCChIn = wcslen(pszIn);
+			memset(pszOut, 0, nCbOut);
+			nRet = _Conv(desc, &pIn, nCChIn * sizeof(wchar_t), &pszOut, nCbOut - 1, &nTrunc);
+			iconv_close(desc);
+			return nRet;
+		}
+	}
+	
+	return 0;
+}

+ 1214 - 0
common/conv.h

@@ -0,0 +1,1214 @@
+// conv.h :
+//
+
+#if !defined(AGD_CONV_H__C4E0A89C_BB99_4553_9C9E_C0468037DFF2__INCLUDED_)
+#define AGD_CONV_H__C4E0A89C_BB99_4553_9C9E_C0468037DFF2__INCLUDED_
+
+#include <wchar.h>
+#include <uchar.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif	//	__cplusplus
+
+/////////////////////////////////////////////////////////////////////////////
+// conv.h - Declarations:
+
+size_t wcs16len(const char16_t *psz);
+size_t wcs32len(const char32_t *psz);
+
+size_t EncToUtf8(const char *pszEncIn, const void *pszIn, size_t nCbIn, char *pszOut, size_t nCbOut);
+
+size_t Latin1ToUtf8(const char *pszIn, size_t nCbIn, char *pszOut, size_t nCbOut);
+size_t Utf8ToLatin1(const char *pszIn, size_t nCbIn, char *pszOut, size_t nCbOut);
+
+size_t Utf16ToUtf8(const char16_t *pszIn, size_t nCChIn, char *pszOut, size_t nCbOut);
+size_t Utf8ToUtf16(const char *pszIn, size_t nCbIn, char16_t *pszOut, size_t nCChOut);
+
+size_t Utf32ToUtf8(const char32_t *pszIn, size_t nCChIn, char *pszOut, size_t nCbOut);
+size_t Utf8ToUtf32(const char *pszIn, size_t nCbIn, char32_t *pszOut, size_t nCChOut);
+
+size_t WcsToUtf8(const wchar_t *pszIn, size_t nCChIn, char *pszOut, size_t nCbOut);
+size_t Utf8ToWcs(const char *pszIn, size_t nCbIn, wchar_t *pszOut, size_t nCChOut);
+
+/////////////////////////////////////////////////////////////////////////////
+#ifdef __cplusplus
+}
+#endif	//	__cplusplus
+#endif	//	!defined(AGD_CONV_H__C4E0A89C_BB99_4553_9C9E_C0468037DFF2__INCLUDED_)
+
+/*
+437//
+500//
+500V1//
+850//
+851//
+852//
+855//
+856//
+857//
+860//
+861//
+862//
+863//
+864//
+865//
+866//
+866NAV//
+869//
+874//
+904//
+1026//
+1046//
+1047//
+8859_1//
+8859_2//
+8859_3//
+8859_4//
+8859_5//
+8859_6//
+8859_7//
+8859_8//
+8859_9//
+10646-1:1993//
+10646-1:1993/UCS4/
+ANSI_X3.4-1968//
+ANSI_X3.4-1986//
+ANSI_X3.4//
+ANSI_X3.110-1983//
+ANSI_X3.110//
+ARABIC//
+ARABIC7//
+ARMSCII-8//
+ASCII//
+ASMO-708//
+ASMO_449//
+BALTIC//
+BIG-5//
+BIG-FIVE//
+BIG5-HKSCS//
+BIG5//
+BIG5HKSCS//
+BIGFIVE//
+BRF//
+BS_4730//
+CA//
+CN-BIG5//
+CN-GB//
+CN//
+CP-AR//
+CP-GR//
+CP-HU//
+CP037//
+CP038//
+CP273//
+CP274//
+CP275//
+CP278//
+CP280//
+CP281//
+CP282//
+CP284//
+CP285//
+CP290//
+CP297//
+CP367//
+CP420//
+CP423//
+CP424//
+CP437//
+CP500//
+CP737//
+CP770//
+CP771//
+CP772//
+CP773//
+CP774//
+CP775//
+CP803//
+CP813//
+CP819//
+CP850//
+CP851//
+CP852//
+CP855//
+CP856//
+CP857//
+CP860//
+CP861//
+CP862//
+CP863//
+CP864//
+CP865//
+CP866//
+CP866NAV//
+CP868//
+CP869//
+CP870//
+CP871//
+CP874//
+CP875//
+CP880//
+CP891//
+CP901//
+CP902//
+CP903//
+CP904//
+CP905//
+CP912//
+CP915//
+CP916//
+CP918//
+CP920//
+CP921//
+CP922//
+CP930//
+CP932//
+CP933//
+CP935//
+CP936//
+CP937//
+CP939//
+CP949//
+CP950//
+CP1004//
+CP1008//
+CP1025//
+CP1026//
+CP1046//
+CP1047//
+CP1070//
+CP1079//
+CP1081//
+CP1084//
+CP1089//
+CP1097//
+CP1112//
+CP1122//
+CP1123//
+CP1124//
+CP1125//
+CP1129//
+CP1130//
+CP1132//
+CP1133//
+CP1137//
+CP1140//
+CP1141//
+CP1142//
+CP1143//
+CP1144//
+CP1145//
+CP1146//
+CP1147//
+CP1148//
+CP1149//
+CP1153//
+CP1154//
+CP1155//
+CP1156//
+CP1157//
+CP1158//
+CP1160//
+CP1161//
+CP1162//
+CP1163//
+CP1164//
+CP1166//
+CP1167//
+CP1250//
+CP1251//
+CP1252//
+CP1253//
+CP1254//
+CP1255//
+CP1256//
+CP1257//
+CP1258//
+CP1282//
+CP1361//
+CP1364//
+CP1371//
+CP1388//
+CP1390//
+CP1399//
+CP4517//
+CP4899//
+CP4909//
+CP4971//
+CP5347//
+CP9030//
+CP9066//
+CP9448//
+CP10007//
+CP12712//
+CP16804//
+CPIBM861//
+CSA7-1//
+CSA7-2//
+CSASCII//
+CSA_T500-1983//
+CSA_T500//
+CSA_Z243.4-1985-1//
+CSA_Z243.4-1985-2//
+CSA_Z243.419851//
+CSA_Z243.419852//
+CSDECMCS//
+CSEBCDICATDE//
+CSEBCDICATDEA//
+CSEBCDICCAFR//
+CSEBCDICDKNO//
+CSEBCDICDKNOA//
+CSEBCDICES//
+CSEBCDICESA//
+CSEBCDICESS//
+CSEBCDICFISE//
+CSEBCDICFISEA//
+CSEBCDICFR//
+CSEBCDICIT//
+CSEBCDICPT//
+CSEBCDICUK//
+CSEBCDICUS//
+CSEUCKR//
+CSEUCPKDFMTJAPANESE//
+CSGB2312//
+CSHPROMAN8//
+CSIBM037//
+CSIBM038//
+CSIBM273//
+CSIBM274//
+CSIBM275//
+CSIBM277//
+CSIBM278//
+CSIBM280//
+CSIBM281//
+CSIBM284//
+CSIBM285//
+CSIBM290//
+CSIBM297//
+CSIBM420//
+CSIBM423//
+CSIBM424//
+CSIBM500//
+CSIBM803//
+CSIBM851//
+CSIBM855//
+CSIBM856//
+CSIBM857//
+CSIBM860//
+CSIBM863//
+CSIBM864//
+CSIBM865//
+CSIBM866//
+CSIBM868//
+CSIBM869//
+CSIBM870//
+CSIBM871//
+CSIBM880//
+CSIBM891//
+CSIBM901//
+CSIBM902//
+CSIBM903//
+CSIBM904//
+CSIBM905//
+CSIBM918//
+CSIBM921//
+CSIBM922//
+CSIBM930//
+CSIBM932//
+CSIBM933//
+CSIBM935//
+CSIBM937//
+CSIBM939//
+CSIBM943//
+CSIBM1008//
+CSIBM1025//
+CSIBM1026//
+CSIBM1097//
+CSIBM1112//
+CSIBM1122//
+CSIBM1123//
+CSIBM1124//
+CSIBM1129//
+CSIBM1130//
+CSIBM1132//
+CSIBM1133//
+CSIBM1137//
+CSIBM1140//
+CSIBM1141//
+CSIBM1142//
+CSIBM1143//
+CSIBM1144//
+CSIBM1145//
+CSIBM1146//
+CSIBM1147//
+CSIBM1148//
+CSIBM1149//
+CSIBM1153//
+CSIBM1154//
+CSIBM1155//
+CSIBM1156//
+CSIBM1157//
+CSIBM1158//
+CSIBM1160//
+CSIBM1161//
+CSIBM1163//
+CSIBM1164//
+CSIBM1166//
+CSIBM1167//
+CSIBM1364//
+CSIBM1371//
+CSIBM1388//
+CSIBM1390//
+CSIBM1399//
+CSIBM4517//
+CSIBM4899//
+CSIBM4909//
+CSIBM4971//
+CSIBM5347//
+CSIBM9030//
+CSIBM9066//
+CSIBM9448//
+CSIBM12712//
+CSIBM16804//
+CSIBM11621162//
+CSISO4UNITEDKINGDOM//
+CSISO10SWEDISH//
+CSISO11SWEDISHFORNAMES//
+CSISO14JISC6220RO//
+CSISO15ITALIAN//
+CSISO16PORTUGESE//
+CSISO17SPANISH//
+CSISO18GREEK7OLD//
+CSISO19LATINGREEK//
+CSISO21GERMAN//
+CSISO25FRENCH//
+CSISO27LATINGREEK1//
+CSISO49INIS//
+CSISO50INIS8//
+CSISO51INISCYRILLIC//
+CSISO58GB1988//
+CSISO60DANISHNORWEGIAN//
+CSISO60NORWEGIAN1//
+CSISO61NORWEGIAN2//
+CSISO69FRENCH//
+CSISO84PORTUGUESE2//
+CSISO85SPANISH2//
+CSISO86HUNGARIAN//
+CSISO88GREEK7//
+CSISO89ASMO449//
+CSISO90//
+CSISO92JISC62991984B//
+CSISO99NAPLPS//
+CSISO103T618BIT//
+CSISO111ECMACYRILLIC//
+CSISO121CANADIAN1//
+CSISO122CANADIAN2//
+CSISO139CSN369103//
+CSISO141JUSIB1002//
+CSISO143IECP271//
+CSISO150//
+CSISO150GREEKCCITT//
+CSISO151CUBA//
+CSISO153GOST1976874//
+CSISO646DANISH//
+CSISO2022CN//
+CSISO2022JP//
+CSISO2022JP2//
+CSISO2022KR//
+CSISO2033//
+CSISO5427CYRILLIC//
+CSISO5427CYRILLIC1981//
+CSISO5428GREEK//
+CSISO10367BOX//
+CSISOLATIN1//
+CSISOLATIN2//
+CSISOLATIN3//
+CSISOLATIN4//
+CSISOLATIN5//
+CSISOLATIN6//
+CSISOLATINARABIC//
+CSISOLATINCYRILLIC//
+CSISOLATINGREEK//
+CSISOLATINHEBREW//
+CSKOI8R//
+CSKSC5636//
+CSMACINTOSH//
+CSNATSDANO//
+CSNATSSEFI//
+CSN_369103//
+CSPC8CODEPAGE437//
+CSPC775BALTIC//
+CSPC850MULTILINGUAL//
+CSPC862LATINHEBREW//
+CSPCP852//
+CSSHIFTJIS//
+CSUCS4//
+CSUNICODE//
+CSWINDOWS31J//
+CUBA//
+CWI-2//
+CWI//
+CYRILLIC//
+DE//
+DEC-MCS//
+DEC//
+DECMCS//
+DIN_66003//
+DK//
+DS2089//
+DS_2089//
+E13B//
+EBCDIC-AT-DE-A//
+EBCDIC-AT-DE//
+EBCDIC-BE//
+EBCDIC-BR//
+EBCDIC-CA-FR//
+EBCDIC-CP-AR1//
+EBCDIC-CP-AR2//
+EBCDIC-CP-BE//
+EBCDIC-CP-CA//
+EBCDIC-CP-CH//
+EBCDIC-CP-DK//
+EBCDIC-CP-ES//
+EBCDIC-CP-FI//
+EBCDIC-CP-FR//
+EBCDIC-CP-GB//
+EBCDIC-CP-GR//
+EBCDIC-CP-HE//
+EBCDIC-CP-IS//
+EBCDIC-CP-IT//
+EBCDIC-CP-NL//
+EBCDIC-CP-NO//
+EBCDIC-CP-ROECE//
+EBCDIC-CP-SE//
+EBCDIC-CP-TR//
+EBCDIC-CP-US//
+EBCDIC-CP-WT//
+EBCDIC-CP-YU//
+EBCDIC-CYRILLIC//
+EBCDIC-DK-NO-A//
+EBCDIC-DK-NO//
+EBCDIC-ES-A//
+EBCDIC-ES-S//
+EBCDIC-ES//
+EBCDIC-FI-SE-A//
+EBCDIC-FI-SE//
+EBCDIC-FR//
+EBCDIC-GREEK//
+EBCDIC-INT//
+EBCDIC-INT1//
+EBCDIC-IS-FRISS//
+EBCDIC-IT//
+EBCDIC-JP-E//
+EBCDIC-JP-KANA//
+EBCDIC-PT//
+EBCDIC-UK//
+EBCDIC-US//
+EBCDICATDE//
+EBCDICATDEA//
+EBCDICCAFR//
+EBCDICDKNO//
+EBCDICDKNOA//
+EBCDICES//
+EBCDICESA//
+EBCDICESS//
+EBCDICFISE//
+EBCDICFISEA//
+EBCDICFR//
+EBCDICISFRISS//
+EBCDICIT//
+EBCDICPT//
+EBCDICUK//
+EBCDICUS//
+ECMA-114//
+ECMA-118//
+ECMA-128//
+ECMA-CYRILLIC//
+ECMACYRILLIC//
+ELOT_928//
+ES//
+ES2//
+EUC-CN//
+EUC-JISX0213//
+EUC-JP-MS//
+EUC-JP//
+EUC-KR//
+EUC-TW//
+EUCCN//
+EUCJP-MS//
+EUCJP-OPEN//
+EUCJP-WIN//
+EUCJP//
+EUCKR//
+EUCTW//
+FI//
+FR//
+GB//
+GB2312//
+GB13000//
+GB18030//
+GBK//
+GB_1988-80//
+GB_198880//
+GEORGIAN-ACADEMY//
+GEORGIAN-PS//
+GOST_19768-74//
+GOST_19768//
+GOST_1976874//
+GREEK-CCITT//
+GREEK//
+GREEK7-OLD//
+GREEK7//
+GREEK7OLD//
+GREEK8//
+GREEKCCITT//
+HEBREW//
+HP-GREEK8//
+HP-ROMAN8//
+HP-ROMAN9//
+HP-THAI8//
+HP-TURKISH8//
+HPGREEK8//
+HPROMAN8//
+HPROMAN9//
+HPTHAI8//
+HPTURKISH8//
+HU//
+IBM-803//
+IBM-856//
+IBM-901//
+IBM-902//
+IBM-921//
+IBM-922//
+IBM-930//
+IBM-932//
+IBM-933//
+IBM-935//
+IBM-937//
+IBM-939//
+IBM-943//
+IBM-1008//
+IBM-1025//
+IBM-1046//
+IBM-1047//
+IBM-1097//
+IBM-1112//
+IBM-1122//
+IBM-1123//
+IBM-1124//
+IBM-1129//
+IBM-1130//
+IBM-1132//
+IBM-1133//
+IBM-1137//
+IBM-1140//
+IBM-1141//
+IBM-1142//
+IBM-1143//
+IBM-1144//
+IBM-1145//
+IBM-1146//
+IBM-1147//
+IBM-1148//
+IBM-1149//
+IBM-1153//
+IBM-1154//
+IBM-1155//
+IBM-1156//
+IBM-1157//
+IBM-1158//
+IBM-1160//
+IBM-1161//
+IBM-1162//
+IBM-1163//
+IBM-1164//
+IBM-1166//
+IBM-1167//
+IBM-1364//
+IBM-1371//
+IBM-1388//
+IBM-1390//
+IBM-1399//
+IBM-4517//
+IBM-4899//
+IBM-4909//
+IBM-4971//
+IBM-5347//
+IBM-9030//
+IBM-9066//
+IBM-9448//
+IBM-12712//
+IBM-16804//
+IBM037//
+IBM038//
+IBM256//
+IBM273//
+IBM274//
+IBM275//
+IBM277//
+IBM278//
+IBM280//
+IBM281//
+IBM284//
+IBM285//
+IBM290//
+IBM297//
+IBM367//
+IBM420//
+IBM423//
+IBM424//
+IBM437//
+IBM500//
+IBM775//
+IBM803//
+IBM813//
+IBM819//
+IBM848//
+IBM850//
+IBM851//
+IBM852//
+IBM855//
+IBM856//
+IBM857//
+IBM860//
+IBM861//
+IBM862//
+IBM863//
+IBM864//
+IBM865//
+IBM866//
+IBM866NAV//
+IBM868//
+IBM869//
+IBM870//
+IBM871//
+IBM874//
+IBM875//
+IBM880//
+IBM891//
+IBM901//
+IBM902//
+IBM903//
+IBM904//
+IBM905//
+IBM912//
+IBM915//
+IBM916//
+IBM918//
+IBM920//
+IBM921//
+IBM922//
+IBM930//
+IBM932//
+IBM933//
+IBM935//
+IBM937//
+IBM939//
+IBM943//
+IBM1004//
+IBM1008//
+IBM1025//
+IBM1026//
+IBM1046//
+IBM1047//
+IBM1089//
+IBM1097//
+IBM1112//
+IBM1122//
+IBM1123//
+IBM1124//
+IBM1129//
+IBM1130//
+IBM1132//
+IBM1133//
+IBM1137//
+IBM1140//
+IBM1141//
+IBM1142//
+IBM1143//
+IBM1144//
+IBM1145//
+IBM1146//
+IBM1147//
+IBM1148//
+IBM1149//
+IBM1153//
+IBM1154//
+IBM1155//
+IBM1156//
+IBM1157//
+IBM1158//
+IBM1160//
+IBM1161//
+IBM1162//
+IBM1163//
+IBM1164//
+IBM1166//
+IBM1167//
+IBM1364//
+IBM1371//
+IBM1388//
+IBM1390//
+IBM1399//
+IBM4517//
+IBM4899//
+IBM4909//
+IBM4971//
+IBM5347//
+IBM9030//
+IBM9066//
+IBM9448//
+IBM12712//
+IBM16804//
+IEC_P27-1//
+IEC_P271//
+INIS-8//
+INIS-CYRILLIC//
+INIS//
+INIS8//
+INISCYRILLIC//
+ISIRI-3342//
+ISIRI3342//
+ISO-2022-CN-EXT//
+ISO-2022-CN//
+ISO-2022-JP-2//
+ISO-2022-JP-3//
+ISO-2022-JP//
+ISO-2022-KR//
+ISO-8859-1//
+ISO-8859-2//
+ISO-8859-3//
+ISO-8859-4//
+ISO-8859-5//
+ISO-8859-6//
+ISO-8859-7//
+ISO-8859-8//
+ISO-8859-9//
+ISO-8859-9E//
+ISO-8859-10//
+ISO-8859-11//
+ISO-8859-13//
+ISO-8859-14//
+ISO-8859-15//
+ISO-8859-16//
+ISO-10646//
+ISO-10646/UCS2/
+ISO-10646/UCS4/
+ISO-10646/UTF-8/
+ISO-10646/UTF8/
+ISO-CELTIC//
+ISO-IR-4//
+ISO-IR-6//
+ISO-IR-8-1//
+ISO-IR-9-1//
+ISO-IR-10//
+ISO-IR-11//
+ISO-IR-14//
+ISO-IR-15//
+ISO-IR-16//
+ISO-IR-17//
+ISO-IR-18//
+ISO-IR-19//
+ISO-IR-21//
+ISO-IR-25//
+ISO-IR-27//
+ISO-IR-37//
+ISO-IR-49//
+ISO-IR-50//
+ISO-IR-51//
+ISO-IR-54//
+ISO-IR-55//
+ISO-IR-57//
+ISO-IR-60//
+ISO-IR-61//
+ISO-IR-69//
+ISO-IR-84//
+ISO-IR-85//
+ISO-IR-86//
+ISO-IR-88//
+ISO-IR-89//
+ISO-IR-90//
+ISO-IR-92//
+ISO-IR-98//
+ISO-IR-99//
+ISO-IR-100//
+ISO-IR-101//
+ISO-IR-103//
+ISO-IR-109//
+ISO-IR-110//
+ISO-IR-111//
+ISO-IR-121//
+ISO-IR-122//
+ISO-IR-126//
+ISO-IR-127//
+ISO-IR-138//
+ISO-IR-139//
+ISO-IR-141//
+ISO-IR-143//
+ISO-IR-144//
+ISO-IR-148//
+ISO-IR-150//
+ISO-IR-151//
+ISO-IR-153//
+ISO-IR-155//
+ISO-IR-156//
+ISO-IR-157//
+ISO-IR-166//
+ISO-IR-179//
+ISO-IR-193//
+ISO-IR-197//
+ISO-IR-199//
+ISO-IR-203//
+ISO-IR-209//
+ISO-IR-226//
+ISO/TR_11548-1/
+ISO646-CA//
+ISO646-CA2//
+ISO646-CN//
+ISO646-CU//
+ISO646-DE//
+ISO646-DK//
+ISO646-ES//
+ISO646-ES2//
+ISO646-FI//
+ISO646-FR//
+ISO646-FR1//
+ISO646-GB//
+ISO646-HU//
+ISO646-IT//
+ISO646-JP-OCR-B//
+ISO646-JP//
+ISO646-KR//
+ISO646-NO//
+ISO646-NO2//
+ISO646-PT//
+ISO646-PT2//
+ISO646-SE//
+ISO646-SE2//
+ISO646-US//
+ISO646-YU//
+ISO2022CN//
+ISO2022CNEXT//
+ISO2022JP//
+ISO2022JP2//
+ISO2022KR//
+ISO6937//
+ISO8859-1//
+ISO8859-2//
+ISO8859-3//
+ISO8859-4//
+ISO8859-5//
+ISO8859-6//
+ISO8859-7//
+ISO8859-8//
+ISO8859-9//
+ISO8859-9E//
+ISO8859-10//
+ISO8859-11//
+ISO8859-13//
+ISO8859-14//
+ISO8859-15//
+ISO8859-16//
+ISO11548-1//
+ISO88591//
+ISO88592//
+ISO88593//
+ISO88594//
+ISO88595//
+ISO88596//
+ISO88597//
+ISO88598//
+ISO88599//
+ISO88599E//
+ISO885910//
+ISO885911//
+ISO885913//
+ISO885914//
+ISO885915//
+ISO885916//
+ISO_646.IRV:1991//
+ISO_2033-1983//
+ISO_2033//
+ISO_5427-EXT//
+ISO_5427//
+ISO_5427:1981//
+ISO_5427EXT//
+ISO_5428//
+ISO_5428:1980//
+ISO_6937-2//
+ISO_6937-2:1983//
+ISO_6937//
+ISO_6937:1992//
+ISO_8859-1//
+ISO_8859-1:1987//
+ISO_8859-2//
+ISO_8859-2:1987//
+ISO_8859-3//
+ISO_8859-3:1988//
+ISO_8859-4//
+ISO_8859-4:1988//
+ISO_8859-5//
+ISO_8859-5:1988//
+ISO_8859-6//
+ISO_8859-6:1987//
+ISO_8859-7//
+ISO_8859-7:1987//
+ISO_8859-7:2003//
+ISO_8859-8//
+ISO_8859-8:1988//
+ISO_8859-9//
+ISO_8859-9:1989//
+ISO_8859-9E//
+ISO_8859-10//
+ISO_8859-10:1992//
+ISO_8859-14//
+ISO_8859-14:1998//
+ISO_8859-15//
+ISO_8859-15:1998//
+ISO_8859-16//
+ISO_8859-16:2001//
+ISO_9036//
+ISO_10367-BOX//
+ISO_10367BOX//
+ISO_11548-1//
+ISO_69372//
+IT//
+JIS_C6220-1969-RO//
+JIS_C6229-1984-B//
+JIS_C62201969RO//
+JIS_C62291984B//
+JOHAB//
+JP-OCR-B//
+JP//
+JS//
+JUS_I.B1.002//
+KOI-7//
+KOI-8//
+KOI8-R//
+KOI8-RU//
+KOI8-T//
+KOI8-U//
+KOI8//
+KOI8R//
+KOI8U//
+KSC5636//
+L1//
+L2//
+L3//
+L4//
+L5//
+L6//
+L7//
+L8//
+L10//
+LATIN-9//
+LATIN-GREEK-1//
+LATIN-GREEK//
+LATIN1//
+LATIN2//
+LATIN3//
+LATIN4//
+LATIN5//
+LATIN6//
+LATIN7//
+LATIN8//
+LATIN9//
+LATIN10//
+LATINGREEK//
+LATINGREEK1//
+MAC-CENTRALEUROPE//
+MAC-CYRILLIC//
+MAC-IS//
+MAC-SAMI//
+MAC-UK//
+MAC//
+MACCYRILLIC//
+MACINTOSH//
+MACIS//
+MACUK//
+MACUKRAINIAN//
+MIK//
+MS-ANSI//
+MS-ARAB//
+MS-CYRL//
+MS-EE//
+MS-GREEK//
+MS-HEBR//
+MS-MAC-CYRILLIC//
+MS-TURK//
+MS932//
+MS936//
+MSCP949//
+MSCP1361//
+MSMACCYRILLIC//
+MSZ_7795.3//
+MS_KANJI//
+NAPLPS//
+NATS-DANO//
+NATS-SEFI//
+NATSDANO//
+NATSSEFI//
+NC_NC0010//
+NC_NC00-10//
+NC_NC00-10:81//
+NF_Z_62-010//
+NF_Z_62-010_(1973)//
+NF_Z_62-010_1973//
+NF_Z_62010//
+NF_Z_62010_1973//
+NO//
+NO2//
+NS_4551-1//
+NS_4551-2//
+NS_45511//
+NS_45512//
+OS2LATIN1//
+OSF00010001//
+OSF00010002//
+OSF00010003//
+OSF00010004//
+OSF00010005//
+OSF00010006//
+OSF00010007//
+OSF00010008//
+OSF00010009//
+OSF0001000A//
+OSF00010020//
+OSF00010100//
+OSF00010101//
+OSF00010102//
+OSF00010104//
+OSF00010105//
+OSF00010106//
+OSF00030010//
+OSF0004000A//
+OSF0005000A//
+OSF05010001//
+OSF100201A4//
+OSF100201A8//
+OSF100201B5//
+OSF100201F4//
+OSF100203B5//
+OSF1002011C//
+OSF1002011D//
+OSF1002035D//
+OSF1002035E//
+OSF1002035F//
+OSF1002036B//
+OSF1002037B//
+OSF10010001//
+OSF10010004//
+OSF10010006//
+OSF10020025//
+OSF10020111//
+OSF10020115//
+OSF10020116//
+OSF10020118//
+OSF10020122//
+OSF10020129//
+OSF10020352//
+OSF10020354//
+OSF10020357//
+OSF10020359//
+OSF10020360//
+OSF10020364//
+OSF10020365//
+OSF10020366//
+OSF10020367//
+OSF10020370//
+OSF10020387//
+OSF10020388//
+OSF10020396//
+OSF10020402//
+OSF10020417//
+PT//
+PT2//
+PT154//
+R8//
+R9//
+RK1048//
+ROMAN8//
+ROMAN9//
+RUSCII//
+SE//
+SE2//
+SEN_850200_B//
+SEN_850200_C//
+SHIFT-JIS//
+SHIFT_JIS//
+SHIFT_JISX0213//
+SJIS-OPEN//
+SJIS-WIN//
+SJIS//
+SS636127//
+STRK1048-2002//
+ST_SEV_358-88//
+T.61-8BIT//
+T.61//
+T.618BIT//
+TCVN-5712//
+TCVN//
+TCVN5712-1//
+TCVN5712-1:1993//
+THAI8//
+TIS-620//
+TIS620-0//
+TIS620.2529-1//
+TIS620.2533-0//
+TIS620//
+TS-5881//
+TSCII//
+TURKISH8//
+UCS-2//
+UCS-2BE//
+UCS-2LE//
+UCS-4//
+UCS-4BE//
+UCS-4LE//
+UCS2//
+UCS4//
+UHC//
+UJIS//
+UK//
+UNICODE//
+UNICODEBIG//
+UNICODELITTLE//
+US-ASCII//
+US//
+UTF-7//
+UTF-8//
+UTF-16//
+UTF-16BE//
+UTF-16LE//
+UTF-32//
+UTF-32BE//
+UTF-32LE//
+UTF7//
+UTF8//
+UTF16//
+UTF16BE//
+UTF16LE//
+UTF32//
+UTF32BE//
+UTF32LE//
+VISCII//
+WCHAR_T//
+WIN-SAMI-2//
+WINBALTRIM//
+WINDOWS-31J//
+WINDOWS-874//
+WINDOWS-936//
+WINDOWS-1250//
+WINDOWS-1251//
+WINDOWS-1252//
+WINDOWS-1253//
+WINDOWS-1254//
+WINDOWS-1255//
+WINDOWS-1256//
+WINDOWS-1257//
+WINDOWS-1258//
+WINSAMI2//
+WS2//
+YU//
+*/

+ 28 - 0
common/debug.h

@@ -0,0 +1,28 @@
+// debug.h :
+//
+
+#if !defined(AGD_DEBUG_H__39391BF3_0394_4846_BFCB_028512359FF5__INCLUDED_)
+#define AGD_DEBUG_H__39391BF3_0394_4846_BFCB_028512359FF5__INCLUDED_
+
+#include <assert.h>
+#include <stdio.h>
+
+/////////////////////////////////////////////////////////////////////////////
+// debug.h - Declarations:
+
+#ifdef _DEBUG
+#define TRACE(...)					printf(__VA_ARGS__), fflush(stdout)
+#define ETRACE(...)					fprintf(stderr, __VA_ARGS__), fflush(stderr)
+#define ASSERT						assert
+#else	//	_DEBUG
+#define TRACE(...)					(void)0
+#define ETRACE(...)					(void)0
+#define ASSERT(e)					(void)0
+#endif	//	_DEBUG
+
+#ifndef UNUSED
+#define UNUSED(p)					(void)p
+#endif	//	UNUSED
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	!defined(AGD_DEBUG_H__39391BF3_0394_4846_BFCB_028512359FF5__INCLUDED_)

+ 142 - 0
common/fileutil.c

@@ -0,0 +1,142 @@
+//#define _GNU_SOURCE // for canonicalize_file_name
+
+#include <stdio.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include "fileutil.h"
+
+/////////////////////////////////////////////////////////////////////////////
+
+size_t GetFileSize(FILE *fd)
+{
+	long nRet = 0;
+	long nCur = ftell(fd);
+
+	if(!fseek(fd, 0, SEEK_END))
+	{
+		if((nRet = ftell(fd)) < 0)
+			nRet = 0;
+		fseek(fd, nCur, SEEK_SET);
+	}
+
+	return (size_t)nRet;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+int FileExist(const char *file)
+{
+	if(!file || !*file)
+		return 0;
+	struct stat s;
+	memset(&s, 0, sizeof(s));
+	return !stat(file, &s) && S_ISREG(s.st_mode);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+int DirectoryExist(const char *dir)
+{
+	if(!dir || !*dir)
+		return 0;
+	struct stat s;
+	memset(&s, 0, sizeof(s));
+	return !stat(dir, &s) && S_ISDIR(s.st_mode);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// creates a directory with read/write/search permissions for owner and group,
+// and with read/search permissions for others
+
+int CreateDirectory(const char *pszDir)
+{
+	if(mkdir(pszDir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0)
+	{
+		if(errno != EEXIST)
+			return 0;
+	}
+
+	return 1;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+const char* GetAppPath(char *pszPath, size_t nCChPath)
+{
+	char szTmp[32];
+	if(!pszPath || !nCChPath)
+	{
+		errno = EINVAL;
+        return NULL;
+    }
+	sprintf(szTmp, "/proc/%d/exe", getpid());
+	ssize_t nLen = readlink(szTmp, pszPath, nCChPath - 1);
+	if(nLen < 0)
+	{
+		*pszPath = '\0';
+        return NULL;
+    }
+	pszPath[nLen] = '\0';
+	return pszPath;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+const char* GetAppDirectory(char *pszDir, size_t nCChDir)
+{
+	char *pszSl;
+	if(!GetAppPath(pszDir, nCChDir))
+		return NULL;
+	if((pszSl = strrchr(pszDir, '/')))
+	{
+		if(pszSl != pszDir)
+			*pszSl = '\0';
+		else
+			*(pszSl + 1) = '\0';
+	}
+	return pszDir;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+const char*	BuildCanonicalFilePath(const char *pszDir, const char *pszFilespec, char *pszPath, size_t nCChPath)
+{
+	size_t nLen;
+	char szDir[PATH_MAX];
+	char *pszCanPath, szFilePath[PATH_MAX];
+
+	if(!pszFilespec || !*pszFilespec || !pszPath || !nCChPath)
+	{
+		errno = EINVAL;
+        return NULL;
+    }
+
+	if(!pszDir)
+	{
+		memset(szDir, 0, sizeof(szDir));
+		if(!GetAppDirectory(szDir, sizeof(szDir)))
+	        return NULL;
+		pszDir = szDir;
+	}
+
+	snprintf(szFilePath, sizeof(szFilePath), "%s/%s", pszDir, pszFilespec);
+
+	if(!(pszCanPath = canonicalize_file_name(szFilePath)))
+		return NULL;
+
+	nLen = strlen(pszCanPath);
+
+	if(nLen < nCChPath)
+		strcpy(pszPath, pszCanPath);
+	else
+	{
+		errno = ENOMEM;
+		pszPath = NULL;
+	}
+
+	free(pszCanPath);
+	return pszPath;
+}

+ 30 - 0
common/fileutil.h

@@ -0,0 +1,30 @@
+// fileutil.h :
+//
+
+#if !defined(AGD_FILEUTIL_H__35BFEDA3_B89A_4431_B376_4E96713D12C2__INCLUDED_)
+#define AGD_FILEUTIL_H__35BFEDA3_B89A_4431_B376_4E96713D12C2__INCLUDED_
+
+#include <stdio.h>
+#include <sys/stat.h>
+#include <linux/limits.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif	//	__cplusplus
+
+/////////////////////////////////////////////////////////////////////////////
+// fileutil.h - Declarations:
+
+size_t		GetFileSize(FILE *fd);
+int			FileExist(const char *file);
+int			DirectoryExist(const char *dir);
+int			CreateDirectory(const char *pszDir);
+const char*	GetAppPath(char *pszPath, size_t nCChPath);
+const char*	GetAppDirectory(char *pszDir, size_t nCChDir);
+const char*	BuildCanonicalFilePath(const char *pszDir, const char *pszFilespec, char *pszPath, size_t nCChPath);
+
+/////////////////////////////////////////////////////////////////////////////
+#ifdef __cplusplus
+}
+#endif	//	__cplusplus
+#endif	//	!defined(AGD_FILEUTIL_H__35BFEDA3_B89A_4431_B376_4E96713D12C2__INCLUDED_)

+ 153 - 0
common/instance.cpp

@@ -0,0 +1,153 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/file.h>
+#include <linux/limits.h>
+#include <unistd.h>
+#include "instance.h"
+#include "fileutil.h"
+#include "debug.h"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+#define _PIDFILE_ROOT_DIR			"/tmp"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+int CProcessInstance::m_fdPid = -1;
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+CProcessInstance::CProcessInstance(void)
+{
+	m_pid = getpid();
+#if _USE_BINARY_LOCK
+	memset(m_szBinaryPath, 0, sizeof(m_szBinaryPath));
+#else	//	_USE_BINARY_LOCK
+	memset(m_szPidFileDir, 0, sizeof(m_szPidFileDir));
+	memset(m_szPidFilePath, 0, sizeof(m_szPidFilePath));
+#endif	//	_USE_BINARY_LOCK
+}
+
+CProcessInstance::~CProcessInstance(void)
+{
+	UnlockInstance();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+bool CProcessInstance::LockInstance(const char *pszUuid)
+{
+	extern char *__progname;
+	int nRet, nRead, fdPid;
+
+	if(m_fdPid >= 0)
+	{
+		TRACE("%s: Instance lock already acquired!\n", __progname);
+		return true;
+	}
+
+#if _USE_BINARY_LOCK
+	if(!GetAppPath(m_szBinaryPath, sizeof(m_szBinaryPath)))
+	{
+		TRACE("Failed to retrieve binary path!\n");
+		return false;
+	}
+
+	fdPid = open(m_szBinaryPath, O_RDONLY);
+
+	if(fdPid < 0)
+	{
+		TRACE("Failed to open binary file: %s\n", m_szBinaryPath);
+		TRACE("errno: %d\n", errno);
+		return false;
+	}
+#else	//	_USE_BINARY_LOCK
+    sprintf(m_szPidFileDir, "%s/%s", _PIDFILE_ROOT_DIR, pszUuid);
+	sprintf(m_szPidFilePath, "%s/%s/%s.pid", _PIDFILE_ROOT_DIR, pszUuid, __progname);
+
+	if(!DirectoryExist(m_szPidFileDir))
+	{
+		if(!CreateDirectory(m_szPidFileDir))
+		{
+			TRACE("Failed to create run directory!\n");
+			return false;
+		}
+	}
+
+	fdPid = open(m_szPidFilePath, O_CREAT | O_RDWR, 0666);
+
+	if(fdPid < 0)
+	{
+		TRACE("Failed to open PID file: %s\n", m_szPidFilePath);
+		TRACE("errno: %d\n", errno);
+		return false;
+	}
+#endif	//	_USE_BINARY_LOCK
+
+	if((nRet = flock(fdPid, LOCK_EX | LOCK_NB)))
+	{
+		if(errno == EWOULDBLOCK)
+		{
+#if !_USE_BINARY_LOCK
+			char szPid[32];
+			if((nRead = (int)read(fdPid, szPid, sizeof(szPid) - 1)) > 0)
+			{
+				szPid[nRead] = '\0';
+				TRACE("%s: another instance is already running -> PID: %s\n", __progname, szPid);
+			}
+			else
+#endif	//	_USE_BINARY_LOCK
+			{
+				TRACE("%s: another instance is already running!\n", __progname);
+			}
+			close(fdPid);
+			return false;
+		}
+		else
+		{
+			TRACE("flock failed with code %d\n", errno);
+			close(fdPid);
+			return false;
+		}
+	}
+	else
+	{
+#if !_USE_BINARY_LOCK
+		char szPid[32];
+		int nLen = sprintf(szPid, "%d", getpid());
+		write(fdPid, szPid, (size_t)nLen);
+#endif	//	_USE_BINARY_LOCK
+		m_fdPid = fdPid;
+	}
+
+	return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void CProcessInstance::UnlockInstance(void)
+{
+	if(m_fdPid >= 0)
+	{
+		int fdPid = m_fdPid;
+		m_fdPid = -1;
+		flock(fdPid, LOCK_UN);
+		close(fdPid);
+	}
+
+#if _USE_BINARY_LOCK
+	memset(m_szBinaryPath, 0, sizeof(m_szBinaryPath));
+#else	//	_USE_BINARY_LOCK
+	memset(m_szPidFileDir, 0, sizeof(m_szPidFileDir));
+	memset(m_szPidFilePath, 0, sizeof(m_szPidFilePath));
+#endif	//	_USE_BINARY_LOCK
+
+	m_pid = -1;
+}

+ 44 - 0
common/instance.h

@@ -0,0 +1,44 @@
+// instance.h :
+//
+
+#if !defined(AGD_INSTANCE_H__54A0DC49_C449_49EB_B41B_ACF625001760__INCLUDED_)
+#define AGD_INSTANCE_H__54A0DC49_C449_49EB_B41B_ACF625001760__INCLUDED_
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <linux/limits.h>
+
+#ifdef __cplusplus
+
+/////////////////////////////////////////////////////////////////////////////
+// instance.h - Declarations:
+
+#define _USE_BINARY_LOCK			1
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CProcessInstance
+{
+public:
+	CProcessInstance(void);
+	~CProcessInstance(void);
+	
+	bool LockInstance(const char *pszUuid);
+	void UnlockInstance(void);
+
+private:
+#if _USE_BINARY_LOCK
+	char m_szBinaryPath[PATH_MAX];
+#else	//	_USE_BINARY_LOCK
+	char m_szPidFileDir[PATH_MAX];
+	char m_szPidFilePath[PATH_MAX];
+#endif	//	_USE_BINARY_LOCK
+	pid_t m_pid;
+
+private:
+	static int m_fdPid;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	__cplusplus
+#endif	//	!defined(AGD_INSTANCE_H__54A0DC49_C449_49EB_B41B_ACF625001760__INCLUDED_)

+ 369 - 0
common/logfile.cpp

@@ -0,0 +1,369 @@
+#include <pthread.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include "logfile.h"
+#include "fileutil.h"
+
+/////////////////////////////////////////////////////////////////////////////
+
+CLogfile::CLogfile(void) :	m_pf(NULL),
+							m_bAttached(false),
+							m_bClose(false),
+							m_vb(VB_Off),
+							m_nFileSize(0),
+							m_nMaxFileSize(0),
+							m_nMaxBackupFiles(0)
+{
+	m_lsync = PTHREAD_MUTEX_INITIALIZER;
+}
+
+CLogfile::CLogfile(FILE *pf, bool bClose, int verb) :	m_pf(pf),
+														m_bAttached(false),
+														m_bClose(false),
+														m_vb(VB_Off),
+														m_nFileSize(0),
+														m_nMaxFileSize(0),
+														m_nMaxBackupFiles(0)
+{
+	m_lsync = PTHREAD_MUTEX_INITIALIZER;
+
+	if(!Attach(pf, bClose, verb))
+	{
+		StdErr("CLogfile::Attach failed!\n");
+		throw EINVAL;
+	}
+}
+
+CLogfile::CLogfile(const char *pszFilename, bool bAppend, int verb, size_t nMaxFileSize, unsigned int nMaxBackupFiles) :	m_pf(NULL),
+																															m_bAttached(false),
+																															m_bClose(false),
+																															m_vb(VB_Off),
+																															m_nFileSize(0),
+																															m_nMaxFileSize(0),
+																															m_nMaxBackupFiles(0)
+{
+	m_lsync = PTHREAD_MUTEX_INITIALIZER;
+
+	if(!Open(pszFilename, bAppend, verb, nMaxFileSize, nMaxBackupFiles))
+	{
+		StdErr("CLogfile::Open failed!\n");
+		throw errno;
+	}
+}
+
+CLogfile::~CLogfile(void)
+{
+	Close();
+	::pthread_mutex_destroy(&m_lsync);
+}
+
+void CLogfile::SetVerbosity(int verb)
+{
+	if(verb < VB_Off)
+		verb = VB_Off;
+	else if(verb >= VB_Dbg)
+		verb = VB_Dbg;
+	m_vb = (Verbosity)verb;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+bool CLogfile::Open(const char *pszFilename, bool bAppend, int verb, size_t nMaxFileSize, unsigned int nMaxBackupFiles)
+{
+	if(!pszFilename || !*pszFilename)
+		return false;
+
+	if(nMaxBackupFiles < 1)
+		nMaxBackupFiles = 1;
+
+	Close();
+
+	if((m_pf = fopen(pszFilename, bAppend ? "ab" : "wb")))
+	{
+		char szBuf[PATH_MAX];
+		m_strFilePath		= ::realpath(pszFilename, szBuf);
+		m_nFileSize			= GetFileSize();
+		m_bAttached			= false;
+		m_bClose			= true;
+		m_nMaxFileSize		= nMaxFileSize;
+		m_nMaxBackupFiles	= nMaxBackupFiles;
+		SetVerbosity(verb);
+		ProcessFileSizeLimit(m_pf);
+	}
+
+	return m_pf != NULL;
+}
+
+void CLogfile::Close(void)
+{
+	CsLock();
+	if(m_pf)
+	{
+		if(!m_bAttached || m_bClose)
+			fclose(m_pf);
+		m_pf = NULL;
+	}
+
+	m_bClose			= false;
+	m_bAttached			= false;
+	m_nFileSize			= 0;
+	m_nMaxFileSize		= 0;
+	m_nMaxBackupFiles	= 0;
+	m_vb				= VB_Off;
+	m_strFilePath.clear();
+	CsUnlock();
+}
+
+void CLogfile::Flush(void)
+{
+	if(m_pf)
+		fflush(m_pf);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+bool CLogfile::Attach(FILE *pf, bool bClose, int verb)
+{
+	if(!pf)
+	{
+		errno = EINVAL;
+		return false;
+	}
+
+	Close();
+	m_pf = pf;
+	m_bAttached = true;
+	m_bClose = bClose;
+	SetVerbosity(verb);
+	return true;
+}
+
+void CLogfile::Detach(void)
+{
+	CsLock();
+	if(m_bAttached)
+	{
+		m_pf = NULL;
+		m_bClose = false;
+		m_bAttached = false;
+	}
+	CsUnlock();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CLogfile::Log(FILE *pf, const char *pszFormat, va_list args)
+{
+	int nLen;
+	CsLock();
+	if((nLen = vfprintf(pf, pszFormat, args)) > 0)
+	{
+		fflush(pf);
+		m_nFileSize += nLen;
+		ProcessFileSizeLimit(pf);
+	}
+	CsUnlock();
+}
+
+void CLogfile::Log(const char *pszFormat, ...)
+{
+	if(m_pf && pszFormat && *pszFormat)
+	{
+		va_list args;
+		va_start(args, pszFormat);
+		Log(m_pf, pszFormat, args);
+		va_end(args);
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CLogfile::Debug(const char *pszFormat, ...)
+{
+#ifdef _DEBUG
+	if(m_vb >= VB_Dbg)
+	{
+		const char *pszFmt;
+		std::string sFormat;
+
+		if((pszFmt = CreateFormatString(VB_Dbg, pszFormat, sFormat)))
+		{
+			va_list args;
+			va_start(args, pszFormat);
+			Log(m_pf, pszFmt, args);
+			va_end(args);
+		}
+	}
+#endif	//	_DEBUG
+}
+
+void CLogfile::Info(const char *pszFormat, ...)
+{
+	if(m_vb >= VB_Inf)
+	{
+		const char *pszFmt;
+		std::string sFormat;
+
+		if((pszFmt = CreateFormatString(VB_Inf, pszFormat, sFormat)))
+		{
+			va_list args;
+			va_start(args, pszFormat);
+			Log(m_pf, pszFmt, args);
+			va_end(args);
+		}
+	}
+}
+
+void CLogfile::Warning(const char *pszFormat, ...)
+{
+	if(m_vb >= VB_War)
+	{
+		const char *pszFmt;
+		std::string sFormat;
+
+		if((pszFmt = CreateFormatString(VB_War, pszFormat, sFormat)))
+		{
+			va_list args;
+			va_start(args, pszFormat);
+			Log(m_pf, pszFmt, args);
+			va_end(args);
+		}
+	}
+}
+
+void CLogfile::Error(const char *pszFormat, ...)
+{
+	if(m_vb >= VB_Err)
+	{
+		const char *pszFmt;
+		std::string sFormat;
+
+		if((pszFmt = CreateFormatString(VB_Err, pszFormat, sFormat)))
+		{
+			va_list args;
+			va_start(args, pszFormat);
+			Log(m_pf, pszFmt, args);
+			va_end(args);
+		}
+	}
+}
+
+size_t CLogfile::GetFileSize(void)
+{
+	CsLock();
+	size_t s = m_pf ? ::GetFileSize(m_pf) : 0;
+	CsUnlock();
+	return s;
+}
+
+bool CLogfile::ProcessFileSizeLimit(FILE *pf)
+{
+	if(m_nMaxFileSize && !m_bAttached && pf && (pf == m_pf))
+	{
+		if(m_nFileSize >= m_nMaxFileSize)
+		{
+			char szBuf1[PATH_MAX], szBuf2[PATH_MAX];
+			std::string strFilePath = m_strFilePath;
+			Verbosity vb = m_vb;
+			size_t nMaxFileSize = m_nMaxFileSize;
+			int nMaxBackupFiles = m_nMaxBackupFiles;
+
+			fclose(m_pf);
+			m_pf				= NULL;
+			m_bClose			= false;
+			m_bAttached			= false;
+			m_nFileSize			= 0;
+			m_nMaxFileSize		= 0;
+			m_nMaxBackupFiles	= 0;
+			m_vb				= VB_Off;
+
+			m_strFilePath.clear();
+
+			snprintf(szBuf1, sizeof(szBuf1), "%s.%03u", strFilePath.c_str(), nMaxBackupFiles);
+			remove(szBuf1);
+
+			for(int i = nMaxBackupFiles; i > 1; --i)
+			{
+				snprintf(szBuf2, sizeof(szBuf2), "%s.%03u", strFilePath.c_str(), i);
+				snprintf(szBuf1, sizeof(szBuf1), "%s.%03u", strFilePath.c_str(), i - 1);
+				rename(szBuf1, szBuf2);
+			}
+
+			rename(strFilePath.c_str(), szBuf1);
+			return Open(strFilePath.c_str(), false, vb, nMaxFileSize, nMaxBackupFiles);
+		}
+	}
+
+	return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CLogfile::StdOut(const char *pszFormat, ...)
+{
+	if(pszFormat && *pszFormat)
+	{
+		va_list args;
+		va_start(args, pszFormat);
+		vfprintf(stdout, pszFormat, args);
+		va_end(args);
+		fflush(stdout);
+	}
+}
+
+void CLogfile::StdErr(const char *pszFormat, ...)
+{
+	if(pszFormat && *pszFormat)
+	{
+		va_list args;
+		va_start(args, pszFormat);
+		vfprintf(stderr, pszFormat, args);
+		va_end(args);
+		fflush(stderr);
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+const char* CLogfile::CreateFormatString(Verbosity verb, const char *pszFormat, std::string &ret) const
+{
+	time_t ts = time(NULL);
+	char szBuf[128], szTime[64];
+	struct tm *pm = localtime(&ts);
+	strftime(szTime, sizeof(szTime), "%d.%m.%Y %H:%M:%S", pm);
+
+	switch(verb)
+	{
+	case VB_Dbg:
+		sprintf(szBuf, "%s - DEBUG:   ", szTime);
+		break;
+	case VB_Inf:
+		sprintf(szBuf, "%s - INFO:    ", szTime);
+		break;
+	case VB_War:
+		sprintf(szBuf, "%s - WARNING: ", szTime);
+		break;
+	case VB_Err:
+		sprintf(szBuf, "%s - ERROR:   ", szTime);
+		break;
+	default:
+		return NULL;
+	}
+
+	ret = szBuf;
+	ret += pszFormat;
+	return ret.c_str();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CLogfile::CsLock(void)
+{
+	::pthread_mutex_lock(&m_lsync);
+}
+
+void CLogfile::CsUnlock(void)
+{
+	::pthread_mutex_unlock(&m_lsync);
+}

+ 105 - 0
common/logfile.h

@@ -0,0 +1,105 @@
+// logfile.h :
+//
+
+#if !defined(AGD_LOGFILE_H__4DFE59C7_5C38_431E_AD27_9E88C67490A8__INCLUDED_)
+#define AGD_LOGFILE_H__4DFE59C7_5C38_431E_AD27_9E88C67490A8__INCLUDED_
+
+#include <stdio.h>
+#include <stdarg.h>
+
+#ifdef __cplusplus
+
+#include <string>
+
+/////////////////////////////////////////////////////////////////////////////
+// logfile.h - Declarations:
+
+#define _MAX_LOGFILE_SIZE			(0x1ul << 20)	// 1 MiB
+#define _MAX_BACKUP_FILES			1
+
+/////////////////////////////////////////////////////////////////////////////
+
+#ifdef _DEBUG
+#define _DEF_VERBOSITY				VB_Dbg
+#else	//	_DEBUG
+#define _DEF_VERBOSITY				VB_Inf
+#endif	//	_DEBUG
+
+class CLogfile // thread safe
+{
+public:
+	enum Verbosity
+	{
+		VB_Off,	// 0, do not log anything
+		VB_Err,	// 1, log errors only
+		VB_War,	// 2, log errors and warnings
+		VB_Inf,	// 3, log errors, warnings and infos
+		VB_Dbg	// 4, log errors, warnings, infos and debug info
+	};
+
+	/////////////////////////////////////////////////////////////////////////
+
+public:
+	CLogfile(void);
+	CLogfile(FILE *fd, bool bClose = false, int verb = _DEF_VERBOSITY);
+	CLogfile(const char *pszFilename, bool bAppend = true, int verb = _DEF_VERBOSITY, size_t nMaxFileSize = _MAX_LOGFILE_SIZE, unsigned int nMaxBackupFiles = _MAX_BACKUP_FILES);
+	virtual ~CLogfile(void);
+
+	bool Open(const char *pszFilename, bool bAppend = true, int verb = _DEF_VERBOSITY, size_t nMaxFileSize = _MAX_LOGFILE_SIZE, unsigned int nMaxBackupFiles = _MAX_BACKUP_FILES);
+	void Close(void);
+	void Flush(void);
+
+	bool Attach(FILE *fd, bool bClose = false, int verb = _DEF_VERBOSITY);
+	void Detach(void);
+
+	void Lock(void) {
+		CsLock();}
+	void Unlock(void) {
+		CsUnlock();}
+
+	void SetVerbosity(int verb);
+	int GetVerbosity(void) const {
+		return m_vb;}
+
+	void Debug(const char *pszFormat, ...);		// output = date time - DEBUG:   message
+	void Info(const char *pszFormat, ...);		// output = date time - INFO:    message
+	void Warning(const char *pszFormat, ...);	// output = date time - WARNING: message
+	void Error(const char *pszFormat, ...);		// output = date time - ERROR:   message
+	void Log(const char *pszFormat, ...);		// plain message
+
+	static void StdOut(const char *pszFormat, ...); // not thread safe
+	static void StdErr(const char *pszFormat, ...); // not thread safe
+	
+	size_t GetFileSize(void);
+
+	/////////////////////////////////////////////////////////////////////////
+
+protected:
+	virtual const char* CreateFormatString(Verbosity verb, const char *pszFormat, std::string &ret) const;
+
+	/////////////////////////////////////////////////////////////////////////
+
+private:
+	void Log(FILE *fd, const char *pszFormat, va_list args);
+	void CsLock(void);
+	void CsUnlock(void);
+	bool ProcessFileSizeLimit(FILE *fd);
+
+	/////////////////////////////////////////////////////////////////////////
+
+private:
+	FILE *m_pf;
+	std::string m_strFilePath;
+	bool m_bAttached;
+	bool m_bClose;
+	Verbosity m_vb;
+	pthread_mutex_t m_lsync;
+	size_t m_nFileSize;
+	size_t m_nMaxFileSize;
+	unsigned int m_nMaxBackupFiles;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+#endif	//	__cplusplus
+#endif	//	!defined(AGD_LOGFILE_H__4DFE59C7_5C38_431E_AD27_9E88C67490A8__INCLUDED_)

+ 409 - 0
common/mysqlwrap.cpp

@@ -0,0 +1,409 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <utility>
+#include <math.h>
+#include "mysqlwrap.h"
+
+CMySqlDB::CMySqlDB(void) : m_fIsInit(false)
+{
+	memset(&m_mySql, 0, sizeof(m_mySql));
+	Init();
+}
+
+CMySqlDB::~CMySqlDB(void)
+{
+	Close();
+}
+
+bool CMySqlDB::Init(void) throw()
+{
+	if(!m_fIsInit)
+		m_fIsInit = !!mysql_init(&m_mySql);
+	return m_fIsInit;
+}
+
+void CMySqlDB::Close(void) throw()
+{
+	if(m_fIsInit)
+	{
+		mysql_close(&m_mySql);
+		m_fIsInit = false;
+		memset(&m_mySql, 0, sizeof(m_mySql));
+	}
+}
+
+bool CMySqlDB::Connect(	const char *host,
+						const char *user,
+						const char *pass,
+						const char *db,
+						unsigned int port,
+						const char *unix_socket,
+						unsigned long client_flag)
+{
+	if(!Init())
+		return false;
+	return !!mysql_real_connect(&m_mySql, host, user, pass, db, port, unix_socket, client_flag);
+}
+
+int CMySqlDB::SetCharSet(const char *pszCharset)
+{
+	return !mysql_set_character_set(&m_mySql, pszCharset);
+}
+
+int CMySqlDB::Options(enum mysql_option option, const void *arg)
+{
+	return !mysql_options(&m_mySql, option, arg); 
+}
+
+CMySqlResult CMySqlDB::Query(const char *sql)
+{
+	bool bErr = true;
+	MYSQL_RES *pRes = NULL;
+	
+	if(m_fIsInit)
+	{
+		if(!(bErr = !!mysql_query(&m_mySql, sql)))
+			pRes = mysql_store_result(&m_mySql);
+	}
+
+	return CMySqlResult(pRes, bErr);
+}
+
+int CMySqlDB::SelectDB(const char *db)
+{
+	return mysql_select_db(&m_mySql, db);
+}
+
+std::string CMySqlDB::EscapeStringQuote(const char *from, char quote)
+{
+	if(	m_fIsInit &&
+		from && *from)
+	{
+		size_t len = strlen(from);
+		size_t lenBuf = len * 2 + 1;
+		char *buf = (char*)alloca(lenBuf);
+#if MYSQL_VERSION_ID >= 50706
+		if(mysql_real_escape_string_quote(&m_mySql, buf, from, (unsigned long)len, quote))
+#else	//	MYSQL_VERSION_ID
+        (void)quote;
+		if(mysql_real_escape_string(&m_mySql, buf, from, (unsigned long)len))
+#endif	//	MYSQL_VERSION_ID
+			return buf;
+	}
+
+	return "";
+}
+
+std::string CMySqlDB::EscapeString(const char *from)
+{
+	if(	m_fIsInit &&
+		from && *from)
+	{
+		size_t len = strlen(from);
+		size_t lenBuf = len * 2 + 1;
+		char *buf = (char*)alloca(lenBuf);
+		if(mysql_real_escape_string(&m_mySql, buf, from, (unsigned long)len))
+			return buf;
+	}
+
+	return "";
+}
+
+std::string CMySqlDB::LastError(void)
+{
+	if(m_fIsInit)
+		return mysql_error(&m_mySql);
+	else
+		return "MySQL not initialized!";
+}
+
+unsigned int CMySqlDB::LastErrno(void)
+{
+	if(m_fIsInit)
+		return mysql_errno(&m_mySql);
+	else
+		return -1;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+CMySqlResult::CMySqlResult(MYSQL_RES *pRes, bool err) :	m_pRes(pRes),
+														m_bError(err)
+{
+}
+
+CMySqlResult::~CMySqlResult(void)
+{
+	Free();
+}
+
+void CMySqlResult::Free(void)
+{
+	if(m_pRes)
+	{
+		mysql_free_result(m_pRes);
+		m_pRes = NULL;
+	}
+}
+
+my_ulonglong CMySqlResult::RowCount(void) const
+{
+	if(m_pRes)
+		return mysql_num_rows(m_pRes);
+	return 0;
+}
+
+unsigned int CMySqlResult::FieldCount(void) const
+{
+	if(m_pRes)
+		return mysql_num_fields(m_pRes);
+	return 0;
+}
+
+MYSQL_ROW CMySqlResult::FetchRow(void)
+{
+	if(m_pRes)
+		return mysql_fetch_row(m_pRes);
+	return NULL;
+}
+
+bool CMySqlResult::FetchRow(CMySqlRow &row) const
+{
+	if(m_pRes)
+	{
+		MYSQL_ROW pRow = mysql_fetch_row(m_pRes);
+		if(pRow)
+			return row.Create(FieldCount(), FetchFields(), pRow);
+	}
+
+	return false;
+}
+
+const MYSQL_FIELD * CMySqlResult::FetchFields(void) const
+{
+	if(m_pRes)
+		return mysql_fetch_fields(m_pRes);
+	return NULL;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+
+const CMySqlVar CMySqlRow::m_vNul;
+
+CMySqlRow::CMySqlRow(void) : m_bValid(false), m_nFieldCount(0)
+{
+}
+
+CMySqlRow::~CMySqlRow(void)
+{
+}
+
+void CMySqlRow::Clear(void)
+{
+	m_bValid = false;
+	m_nFieldCount = 0;
+	m_fields.clear();
+}
+
+bool CMySqlRow::Create(unsigned int nFieldCount, const MYSQL_FIELD *pFields, MYSQL_ROW pRow)
+{
+	Clear();
+	
+	if((nFieldCount > 0) && pFields && pRow)
+	{
+		for(m_nFieldCount = 0; m_nFieldCount < nFieldCount; ++m_nFieldCount)
+		{
+			std::string s(pFields[m_nFieldCount].name);
+			if(!m_fields.emplace(s, std::move(CMySqlVar(pFields[m_nFieldCount], pRow[m_nFieldCount]))).second)
+			{
+				Clear();
+				return false;
+			}
+		}
+		
+		m_bValid = true;
+	}
+	
+	return m_bValid;
+}
+
+const CMySqlVar& CMySqlRow::Value(const char *pszFieldname) const
+{
+	if(m_bValid)
+	{
+		CMySqlFieldMap::const_iterator it;
+		if(FindField(pszFieldname, it))
+			return it->second;
+	}
+
+	return m_vNul;
+}
+
+CMySqlRow::CMySqlFieldMap::const_iterator CMySqlRow::begin(void)
+{
+	return m_fields.begin();
+}
+
+CMySqlRow::CMySqlFieldMap::const_iterator CMySqlRow::end(void)
+{
+	return m_fields.end();
+}
+
+bool CMySqlRow::FindField(const char *pszFieldname, CMySqlFieldMap::const_iterator &it) const
+{
+	if(m_bValid)
+	{
+		it = m_fields.find(pszFieldname);
+		return (it != m_fields.end());
+	}
+
+	return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+
+CMySqlVar::CMySqlVar(void)
+{
+	Clear();
+}
+
+CMySqlVar::CMySqlVar(const MYSQL_FIELD &rField, const char *pszVal)
+{
+	FromField(rField, pszVal);
+}
+
+CMySqlVar::CMySqlVar(CMySqlVar &&o) : 	m_bValid(std::move(o.m_bValid)), m_bInteger(std::move(o.m_bInteger)), m_bReal(std::move(o.m_bReal)), m_bUnsigned(std::move(o.m_bUnsigned)), m_bString(std::move(o.m_bString)),
+										m_strVal(std::move(o.m_strVal)), m_strFieldname(std::move(o.m_strFieldname)), m_sqlFt(std::move(o.m_sqlFt)), m_numVal(std::move(o.m_numVal))
+{
+	if(this != &o)
+		o.Clear();
+}
+
+CMySqlVar::~CMySqlVar(void)
+{
+}
+
+void CMySqlVar::Clear(void)
+{
+	m_bValid		= false;
+	m_bUnsigned		= false;
+	m_bInteger		= false;
+	m_bReal			= false;
+	m_bString		= false;
+	m_strFieldname.clear();
+	m_strVal.clear();
+	m_sqlFt			= MYSQL_TYPE_NULL;
+	m_numVal.uVal	= 0;
+}
+
+bool CMySqlVar::FromField(const MYSQL_FIELD &rField, const char *pszVal)
+{
+	Clear();
+
+	if(pszVal)
+	{
+		char *pszEndPtr;
+		bool bUnsigned = !!(rField.flags & UNSIGNED_FLAG);
+		m_strFieldname = rField.name;
+
+		switch(rField.type)
+		{
+		case MYSQL_TYPE_BIT:
+		case MYSQL_TYPE_TINY:		//  8 Bit =>  8 Bit
+		case MYSQL_TYPE_SHORT:		// 16 Bit => 16 Bit
+		case MYSQL_TYPE_INT24:		// 24 Bit => 32 Bit
+		case MYSQL_TYPE_LONG:		// 32 Bit => 32 Bit
+		case MYSQL_TYPE_LONGLONG:	// 64 Bit => 64 Bit
+			if(bUnsigned)
+			{
+				int64_t val = strtoll(pszVal, &pszEndPtr, 10);
+				if(!*pszVal || ((val == LLONG_MIN || val == LLONG_MAX) && errno == ERANGE) || *pszEndPtr)
+					m_bValid = false;
+				else
+				{
+					m_sqlFt = rField.type;
+					m_strVal = pszVal;
+					m_numVal.iVal = val;
+					m_bUnsigned = true;
+					m_bInteger = true;
+					m_bValid = true;
+				}
+			}
+			else
+			{
+				uint64_t val = strtoull(pszVal, &pszEndPtr, 10);
+				if(!*pszVal || (val == ULLONG_MAX && errno == ERANGE) || *pszEndPtr)
+					m_bValid = false;
+				else
+				{
+					m_sqlFt = rField.type;
+					m_strVal = pszVal;
+					m_numVal.uVal = val;
+					m_bInteger = true;
+					m_bValid = true;
+				}
+			}
+			break;
+
+		case MYSQL_TYPE_FLOAT:
+		case MYSQL_TYPE_DOUBLE:
+		case MYSQL_TYPE_DECIMAL:
+		case MYSQL_TYPE_NEWDECIMAL:
+			{
+				double val = strtod(pszVal, &pszEndPtr);
+				if(!*pszVal || ((val == HUGE_VAL || val == -HUGE_VAL) && errno == ERANGE) || *pszEndPtr)
+					m_bValid = false;
+				else
+				{
+					m_sqlFt = rField.type;
+					m_strVal = pszVal;
+					m_numVal.fVal = val;
+					m_bReal = true;
+					m_bValid = true;
+				}
+			}
+			break;
+
+		case MYSQL_TYPE_VARCHAR:
+		case MYSQL_TYPE_VAR_STRING:
+		case MYSQL_TYPE_STRING:
+			m_sqlFt = rField.type;
+			m_strVal = pszVal;
+			m_bString = true;
+			m_bValid = true;
+			break;
+
+		case MYSQL_TYPE_DATE:
+		case MYSQL_TYPE_TIME:
+		case MYSQL_TYPE_TIMESTAMP:
+		case MYSQL_TYPE_DATETIME:
+		case MYSQL_TYPE_YEAR:
+		case MYSQL_TYPE_NEWDATE:
+		case MYSQL_TYPE_ENUM:
+		case MYSQL_TYPE_SET:
+		case MYSQL_TYPE_TINY_BLOB:
+		case MYSQL_TYPE_MEDIUM_BLOB:
+		case MYSQL_TYPE_LONG_BLOB:
+		case MYSQL_TYPE_BLOB:
+		case MYSQL_TYPE_GEOMETRY:
+			break;
+
+		case MYSQL_TYPE_NULL:
+			m_sqlFt = rField.type;
+			m_strVal = "";
+			m_bValid = true;
+			break;
+
+		default:
+			break;
+		}
+	}
+
+	return m_bValid;
+}

+ 192 - 0
common/mysqlwrap.h

@@ -0,0 +1,192 @@
+// mysqlwrap.h :
+//
+
+#if !defined(AGD_MYSQLWRAP_H__0FDF8710_B26A_4631_A025_705731DF316E__INCLUDED_)
+#define AGD_MYSQLWRAP_H__0FDF8710_B26A_4631_A025_705731DF316E__INCLUDED_
+
+#include <stdint.h>
+#include <mysql/mysql.h>
+#include <string>
+#include <unordered_map>
+#include <functional>
+#include "debug.h"
+
+/////////////////////////////////////////////////////////////////////////////
+// mysqlwrap.h - Declarations:
+
+class CMySqlVar
+{
+public:
+	CMySqlVar(void);
+	CMySqlVar(CMySqlVar &&o);
+	CMySqlVar(const MYSQL_FIELD &rField, const char *pszVal);
+	virtual ~CMySqlVar(void);
+
+	void Clear(void);
+
+	bool FromField(const MYSQL_FIELD &rField, const char *pszVal);
+
+	inline bool IsValid(void) const {
+		return m_bValid;}
+
+	inline bool IsNumeric(void) const {
+		return m_bValid && (m_bInteger || m_bReal);}
+
+	inline bool IsReal(void) const {
+		return m_bValid && m_bReal;}
+
+	inline bool IsUnsigned(void) const {
+		return m_bValid && m_bUnsigned;}
+
+	inline bool IsNull(void) const {
+		return m_sqlFt == MYSQL_TYPE_NULL;}
+		
+	inline enum enum_field_types GetSqlFieldType(void) const {
+		return m_sqlFt;}
+
+	inline const char* StrVal(void) const {
+		ASSERT(!IsNull());
+		return m_strVal.c_str();}
+
+	inline size_t CopyStrVal(char *s, size_t len, size_t pos = 0) const {
+		return m_strVal.copy(s, len, pos);}
+
+	inline const char* Fieldname(void) const {
+		return m_strFieldname.c_str();}
+
+	inline int FieldnameCmp(const char *pszName) const {
+		return m_strFieldname.compare(pszName);}
+
+	operator int64_t (void) const {
+		ASSERT(m_bInteger && !IsNull());
+		return m_bInteger ? m_numVal.iVal : 0;}
+
+	operator uint64_t (void) const {
+		ASSERT(m_bInteger && !IsNull());
+		return m_bInteger ? m_numVal.uVal : 0;}
+
+#ifndef _TARGET_BUILD
+	operator long long (void) const {
+		ASSERT(m_bInteger && !IsNull());
+		return m_bInteger ? (long long)m_numVal.iVal : 0;}
+
+	operator unsigned long long (void) const {
+		ASSERT(m_bInteger && !IsNull());
+		return m_bInteger ? (unsigned long long)m_numVal.uVal : 0;}
+#endif  //  _TARGET_BUILD
+
+	operator bool (void) const {
+		ASSERT(m_bInteger && !IsNull());
+		return m_bInteger ? !!m_numVal.uVal : false;}
+
+	operator double (void) const {
+		ASSERT(m_bReal && !IsNull());
+		return m_bReal ? m_numVal.fVal : 0.0;}
+
+private:
+	bool m_bValid;
+	bool m_bInteger;
+	bool m_bReal;
+	bool m_bUnsigned;
+	bool m_bString;
+	std::string m_strVal;
+	std::string m_strFieldname;
+	enum enum_field_types m_sqlFt;
+	union
+	{
+		int64_t		iVal;
+		uint64_t	uVal;
+		double		fVal;
+	}m_numVal;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CMySqlRow
+{
+public:
+	typedef std::unordered_map<std::string, CMySqlVar> CMySqlFieldMap;
+
+public:
+	CMySqlRow(void);
+	~CMySqlRow(void);
+
+public:
+	void Clear(void);
+	bool Create(unsigned int nFieldCount, const MYSQL_FIELD *pFields, MYSQL_ROW row);
+	const CMySqlVar& Value(const char *pszFieldname) const;
+
+	inline const CMySqlVar& operator [](const char *pszFieldname) const {
+		return Value(pszFieldname);}
+
+	inline bool IsValid(void) const {
+		return m_bValid;}
+		
+	CMySqlFieldMap::const_iterator begin(void);
+	CMySqlFieldMap::const_iterator end(void);
+
+private:
+	bool FindField(const char *pszFieldname, CMySqlFieldMap::const_iterator &it) const;
+
+private:
+	bool m_bValid;
+	unsigned int m_nFieldCount;
+	CMySqlFieldMap m_fields;
+	static const CMySqlVar m_vNul;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CMySqlResult
+{
+public:
+	CMySqlResult(MYSQL_RES *pRes, bool err);
+	virtual ~CMySqlResult(void);
+
+public:
+	void Free(void);
+
+	my_ulonglong RowCount(void) const;
+	unsigned int FieldCount(void) const;
+	MYSQL_ROW FetchRow(void);
+	bool FetchRow(CMySqlRow &row) const;
+	const MYSQL_FIELD * FetchFields(void) const;
+
+	inline bool error(void) const {
+		return m_bError;}
+
+private:
+	MYSQL_RES *m_pRes;
+	bool m_bError;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CMySqlDB
+{
+public:
+	CMySqlDB(void);
+	virtual ~CMySqlDB(void);
+
+public:
+	bool Init(void) throw();
+	void Close(void) throw();
+
+	bool Connect(const char *host, const char *user, const char *pass, const char *db, unsigned int port = 0, const char *unix_socket = NULL, unsigned long client_flag = 0);
+	CMySqlResult Query(const char *sql);
+	int SelectDB(const char *db);
+	int SetCharSet(const char *pszCharset);
+	int Options(enum mysql_option option, const void *arg);
+
+	std::string EscapeStringQuote(const char *from, char quote);
+	std::string EscapeString(const char *from);
+	std::string LastError(void);
+	unsigned int LastErrno(void);
+
+private:
+	MYSQL m_mySql;
+	bool m_fIsInit;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	!defined(AGD_MYSQLWRAP_H__0FDF8710_B26A_4631_A025_705731DF316E__INCLUDED_)

+ 115 - 0
common/processclock.cpp

@@ -0,0 +1,115 @@
+//////////////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////////
+
+#include <errno.h>
+#include <string.h>
+#include "processclock.h"
+#include "strutil.h"
+
+//////////////////////////////////////////////////////////////////////////////////
+
+#define _NSEC_PER_USEC				_PC_NS_PER_US
+#define _NSEC_PER_MSEC				(_NSEC_PER_USEC * _PC_US_PER_MS)
+#define _NSEC_PER_SEC				(_NSEC_PER_MSEC * _PC_MS_PER_S)
+#define _NSEC_PER_MIN				(_NSEC_PER_SEC * 60LL)
+#define _NSEC_PER_HOUR				(_NSEC_PER_MIN * 60LL)
+
+//////////////////////////////////////////////////////////////////////////////////
+
+CProcessClock::CProcessClock(bool bInitAtConstr)
+{
+	if(bInitAtConstr)
+	{
+		clock_gettime(CLOCK_MONOTONIC, &m_tsDueTime);
+		m_bIsInit = true;
+	}
+	else
+	{
+		memset(&m_tsDueTime, 0, sizeof(m_tsDueTime));
+		m_bIsInit = false;
+	}
+	
+	for(int i = 0; i < _PC_NUM_STOPCLOCKS; i++)
+	{
+		StopclockReset(i);
+	}
+}
+
+CProcessClock::~CProcessClock(void)
+{
+}
+
+//////////////////////////////////////////////////////////////////////////////////
+
+pc_time64_t CProcessClock::GetNanoTick(struct timespec *pts)
+{
+	struct timespec ts;
+	if(!pts)
+		pts = &ts;
+	clock_gettime(CLOCK_MONOTONIC, pts);
+	return Timespec2NanoSec(pts);
+}
+
+pc_time64_t CProcessClock::ClockGetElapsed(unsigned int nClockIndex) const
+{
+	if(nClockIndex >= _PC_NUM_STOPCLOCKS)
+		return -1;
+	struct timespec tsCur;
+	clock_gettime(CLOCK_MONOTONIC, &tsCur);
+	pc_time64_t n1 = Timespec2NanoSec(&m_sc[nClockIndex].tsStopclock);
+	pc_time64_t n2 = Timespec2NanoSec(&tsCur);
+	pc_time64_t nElapsed = n2 - n1;
+	return nElapsed;
+}
+
+CProcessClock::SleepResult CProcessClock::NSleep(pc_time64_t nInterval, bool bResumeOnIntr)
+{
+	int nRet;
+	pc_time64_t dt;
+
+	if(!m_bIsInit)
+	{
+		clock_gettime(CLOCK_MONOTONIC, &m_tsDueTime);
+		m_bIsInit = true;
+	}
+
+	dt = IncTime(&m_tsDueTime, nInterval);
+
+	if(dt < GetNanoTick())
+	{
+		clock_gettime(CLOCK_MONOTONIC, &m_tsDueTime);
+		return SR_TimerUnderrun;
+	}
+
+	if((nRet = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &m_tsDueTime, NULL)))
+	{
+		if(bResumeOnIntr)
+		{
+			while(nRet == EINTR)
+			{
+				nRet = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &m_tsDueTime, NULL);
+			}
+		}
+	}
+
+	return !nRet ? SR_Ok : ((nRet == EINTR) ? SR_Interrupt : SR_Error);
+}
+
+std::string CProcessClock::Interval2String(pc_time64_t nInterval)
+{
+	if(nInterval < 0)
+		return "(error negative value)";
+
+	if(nInterval < _NSEC_PER_USEC)
+		return formatString("%lld ns", nInterval);
+	else if(nInterval < _NSEC_PER_MSEC)
+		return formatString("%.1f us", (double)nInterval / (double)_NSEC_PER_USEC);
+	else if(nInterval < _NSEC_PER_SEC)
+		return formatString("%.2f ms", (double)nInterval / (double)_NSEC_PER_MSEC);
+	else if(nInterval < _NSEC_PER_MIN)
+		return formatString("%.2f sec", (double)nInterval / (double)_NSEC_PER_SEC);
+	else if(nInterval < _NSEC_PER_HOUR)
+		return formatString("%.2f min", (double)nInterval / (double)_NSEC_PER_MIN);
+	else
+		return formatString("%.2f h", (double)nInterval / (double)_NSEC_PER_HOUR);
+}

+ 132 - 0
common/processclock.h

@@ -0,0 +1,132 @@
+// processclock.h :
+//
+
+#if !defined(AGD_PROCESSCLOCK_H__20BF9E10_0AB6_4173_B84C_B006ED1C9A76__INCLUDED_)
+#define AGD_PROCESSCLOCK_H__20BF9E10_0AB6_4173_B84C_B006ED1C9A76__INCLUDED_
+
+#include <time.h>
+#include <stdint.h>
+#include <string>
+
+#define _PC_NS_PER_US					1000LL	// nanoseconds per microsecond
+#define _PC_US_PER_MS					1000LL	// microseconds per millisecond
+#define _PC_MS_PER_S					1000LL	// milliseconds per second
+
+#define _PC_NS_PER_MS					(_PC_US_PER_MS * _PC_NS_PER_US)					// nanoseconds per millisecond
+#define _PC_NS_PER_S					(_PC_MS_PER_S * _PC_US_PER_MS * _PC_NS_PER_US)	// nanoseconds per second
+#define _PC_US_PER_S					(_PC_MS_PER_S * _PC_US_PER_MS)					// microseconds per second
+
+#define _PC_NUM_STOPCLOCKS				8
+
+typedef long long int					pc_time64_t;
+
+
+#ifdef __cplusplus
+/////////////////////////////////////////////////////////////////////////////
+// processclock.h - Declarations:
+
+class CProcessClock
+{
+public:
+	typedef enum
+	{
+		SR_Ok,
+		SR_TimerUnderrun,
+		SR_Interrupt,
+		SR_Error
+	}
+	SleepResult;
+
+typedef struct _STOPCLOCK
+{
+	struct timespec tsStopclock;
+#if 0
+	pc_time64_t nMinElapsed;
+	pc_time64_t nMaxElapsed;
+#endif
+}STOPCLOCK, *LPSTOPCLOCK;
+typedef const STOPCLOCK *LPCSTOPCLOCK;
+
+/////////////////////////////////////////////////////////////////////////////
+
+public:
+	CProcessClock(bool bInitAtConstr = false);
+	virtual ~CProcessClock(void);
+
+	static pc_time64_t GetNanoTick(struct timespec *pts = NULL);
+
+	inline static pc_time64_t GetMicroTick(struct timespec *pts = NULL){
+		return GetNanoTick(pts) / _PC_NS_PER_US;
+	}
+
+	inline static pc_time64_t GetMilliTick(struct timespec *pts = NULL){
+		return GetNanoTick(pts) / _PC_NS_PER_MS;
+	}
+
+	inline static pc_time64_t Timespec2NanoSec(const struct timespec *pts){
+		return (pc_time64_t)pts->tv_sec * _PC_NS_PER_S + (pc_time64_t)pts->tv_nsec;
+	}
+
+	inline static void NanoSec2Timespec(pc_time64_t n, struct timespec *pts){
+		if(pts){
+			pts->tv_sec	 = (time_t)(n / _PC_NS_PER_S);
+			pts->tv_nsec = (time_t)(n % _PC_NS_PER_S);
+		}
+	}
+
+	inline static pc_time64_t CompareTimespec(const struct timespec *pts1, const struct timespec *pts2){
+		pc_time64_t n1 = Timespec2NanoSec(pts1);
+		pc_time64_t n2 = Timespec2NanoSec(pts2);
+		return n1 - n2;
+	}
+	
+	static std::string Interval2String(pc_time64_t nInterval);
+
+	SleepResult NSleep(pc_time64_t nInterval, bool bResumeOnIntr = true);			// interval in nanoseconds
+
+	inline SleepResult USleep(pc_time64_t nInterval, bool bResumeOnIntr = true){	// interval in microseconds
+		return NSleep(nInterval * _PC_NS_PER_US, bResumeOnIntr);
+	}
+
+	SleepResult MSleep(pc_time64_t nInterval, bool bResumeOnIntr = true){			// interval in milliseconds
+		return NSleep(nInterval * _PC_NS_PER_MS, bResumeOnIntr);
+	}
+	
+	SleepResult Sleep(pc_time64_t nInterval, bool bResumeOnIntr = true){			// interval in seconds
+		return NSleep(nInterval * _PC_NS_PER_S, bResumeOnIntr);
+	}
+
+    inline pc_time64_t ClockTrigger(unsigned int nClockIndex = 0){
+		if(nClockIndex >= _PC_NUM_STOPCLOCKS)
+			return -1;
+		return GetNanoTick(&m_sc[nClockIndex].tsStopclock);
+	}
+
+	pc_time64_t ClockGetElapsed(unsigned int nClockIndex = 0) const;
+	inline std::string ClockGetElapsedAsString(unsigned int nClockIndex = 0) const {
+		return Interval2String(ClockGetElapsed(nClockIndex));
+	}
+
+	void StopclockReset(unsigned int nClockIndex = 0){
+		if(nClockIndex < _PC_NUM_STOPCLOCKS){
+			memset(&m_sc[nClockIndex].tsStopclock, 0, sizeof(struct timespec));
+		}
+	}
+
+private:
+	inline pc_time64_t IncTime(struct timespec *pts, pc_time64_t ns) const {
+		pc_time64_t n = Timespec2NanoSec(pts);
+		n += ns;
+		NanoSec2Timespec(n, pts);
+		return n;
+	}
+
+private:
+	struct timespec m_tsDueTime;
+	STOPCLOCK m_sc[_PC_NUM_STOPCLOCKS];
+	bool m_bIsInit;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	__cplusplus
+#endif	//	!defined(AGD_PROCESSCLOCK_H__20BF9E10_0AB6_4173_B84C_B006ED1C9A76__INCLUDED_)

+ 92 - 0
common/singleton.c

@@ -0,0 +1,92 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/file.h>
+#include <unistd.h>
+#include "singleton.h"
+
+static int g_fdPid = -1;
+
+
+int AcquirePidFileLock(const char *pszProcessName, const char *pszPidFile)
+{
+	int nRet, nRead, fdPid;
+
+	if(g_fdPid >= 0)
+	{
+		printf("\n%s: Pidfile lock already acquired!\n\n", pszProcessName);
+		return 1;
+	}
+
+	fdPid = open(pszPidFile, O_CREAT | O_RDWR, 0666);
+
+	if(fdPid < 0)
+	{
+		printf("Failed to open PID file: %s\n", pszPidFile);
+		printf("error: %d\n", errno);
+		return 0;
+	}
+
+	if((nRet = flock(fdPid, LOCK_EX | LOCK_NB)))
+	{
+		if(errno == EWOULDBLOCK)
+		{
+			char szPid[32];
+			if((nRead = (int)read(fdPid, szPid, sizeof(szPid) - 1)) > 0)
+			{
+				szPid[nRead] = '\0';
+				printf("\n%s: another instance is already running -> PID: %s\n\n", pszProcessName, szPid);
+			}
+			else
+				printf("\n%s: another instance is already running!\n\n", pszProcessName);
+			close(fdPid);
+			return 0;
+		}
+		else
+		{
+			printf("flock failed with code %d\n", errno);
+			close(fdPid);
+			return 0;
+		}
+	}
+	else
+	{
+		char szPid[32];
+		int nLen = sprintf(szPid, "%d", getpid());
+		write(fdPid, szPid, (size_t)nLen);
+		g_fdPid = fdPid;
+	}
+
+	return 1;
+}
+
+void ReleasePidFileLock(void)
+{
+	if(g_fdPid >= 0)
+	{
+		int fdPid = g_fdPid;
+		g_fdPid = -1;
+		flock(fdPid, LOCK_UN);
+		close(fdPid);
+	}
+}
+
+int GetPidFilePID(const char *pszPidFile)
+{
+	int nRet = -1;
+	char szPid[32];
+	ssize_t nRead;
+	int fdPid = open(pszPidFile, O_RDONLY, 0666);
+
+	if(fdPid >= 0)
+	{
+		if((nRead = read(fdPid, szPid, sizeof(szPid) - 1)) > 0)
+		{
+			szPid[nRead] = '\0';
+			nRet = atoi(szPid);
+		}
+		close(fdPid);
+	}
+
+	return nRet;
+}

+ 22 - 0
common/singleton.h

@@ -0,0 +1,22 @@
+// singleton.h :
+//
+
+#if !defined(AGD_SINGLETON_H__19298C2E_564D_4BA5_82FE_3D5C9A705C55__INCLUDED_)
+#define AGD_SINGLETON_H__19298C2E_564D_4BA5_82FE_3D5C9A705C55__INCLUDED_
+
+#ifdef __cplusplus
+extern "C" {
+#endif	//	__cplusplus
+
+/////////////////////////////////////////////////////////////////////////////
+// singleton.h - Declarations:
+
+int		AcquirePidFileLock(const char *pszProcessName, const char *pszPidFile);
+void	ReleasePidFileLock(void);
+int		GetPidFilePID(const char *pszPidFile);
+
+/////////////////////////////////////////////////////////////////////////////
+#ifdef __cplusplus
+}
+#endif	//	__cplusplus
+#endif	//	!defined(AGD_SINGLETON_H__19298C2E_564D_4BA5_82FE_3D5C9A705C55__INCLUDED_)

+ 175 - 0
common/strutil.cpp

@@ -0,0 +1,175 @@
+#include <algorithm>
+#include <stdarg.h>
+#include "strutil.h"
+
+
+//////////////////////////////////////////////////////////////////////////////////
+// strucase
+
+std::string & strucase(std::string &s)
+{
+	std::transform(s.begin(), s.end(), s.begin(), ::toupper);
+	return s;
+}
+
+std::string strucase(std::string s)
+{
+	std::transform(s.begin(), s.end(), s.begin(), ::toupper);
+	return s;
+}
+
+std::string strucase(const char *ps)
+{
+	std::string s(ps);
+	std::transform(s.begin(), s.end(), s.begin(), ::toupper);
+	return s;
+}
+
+//////////////////////////////////////////////////////////////////////////////////
+// strlcase
+
+std::string & strlcase(std::string &s)
+{
+	std::transform(s.begin(), s.end(), s.begin(), ::tolower);
+	return s;
+}
+
+std::string strlcase(std::string s)
+{
+	std::transform(s.begin(), s.end(), s.begin(), ::tolower);
+	return s;
+}
+
+std::string strlcase(const char *ps)
+{
+	std::string s(ps);
+	std::transform(s.begin(), s.end(), s.begin(), ::tolower);
+	return s;
+}
+
+//////////////////////////////////////////////////////////////////////////////////
+// trim from start (in place)
+
+std::string & ltrim(std::string &s)
+{
+    s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun<int, int>(std::isspace))));
+	return s;
+}
+
+std::string & ltrim(std::string &s, const char *sep)
+{
+	std::size_t f = s.find_first_not_of(sep);
+	if(f != std::string::npos)
+		s.erase(0, f);
+	return s;
+}
+
+//////////////////////////////////////////////////////////////////////////////////
+// trim from end (in place)
+
+std::string & rtrim(std::string &s)
+{
+    s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
+	return s;
+}
+
+std::string & rtrim(std::string &s, const char *sep)
+{
+	std::size_t f = s.find_last_not_of(sep);
+	if(f != std::string::npos)
+		s.erase(f + 1);
+	return s;
+}
+
+//////////////////////////////////////////////////////////////////////////////////
+// trim from both ends (in place)
+
+std::string & trim(std::string &s)
+{
+    return ltrim(rtrim(s));
+}
+
+std::string & trim(std::string &s, const char *sep)
+{
+    return ltrim(rtrim(s, sep), sep);
+}
+
+//////////////////////////////////////////////////////////////////////////////////
+// trim from both ends (copy)
+
+std::string trim(const std::string &s)
+{
+	std::string t(s);
+    return ltrim(rtrim(t));
+}
+
+std::string trim(const std::string &s, const char *sep)
+{
+	std::string t(s);
+    return ltrim(rtrim(t, sep), sep);
+}
+
+//////////////////////////////////////////////////////////////////////////////////
+// split string
+
+int strsplit(const std::string &str, const char *sep, std::vector<std::string> &vec)
+{
+	int nElems = 0;
+	std::regex reg(sep);
+	const std::sregex_token_iterator end;
+
+	for(std::sregex_token_iterator iter(str.begin(), str.end(), reg); iter != end; ++iter)
+	{
+		vec.push_back(*iter);
+		nElems++;
+	}
+
+	return nElems;
+}
+
+//////////////////////////////////////////////////////////////////////////////////
+// formatString
+
+std::string formatString(const char *fmt, ...)
+{
+	int n;
+	std::string s;
+	char *p = NULL;
+	va_list ap;
+	va_start(ap, fmt);
+	n = ::vasprintf(&p, fmt, ap);
+	va_end(ap);
+	if(n >= 0)
+	{
+		s = p;
+		free(p);
+	}
+	return s;
+}
+
+//////////////////////////////////////////////////////////////////////////////////
+// strFormatByteSize
+
+std::string strFormatByteSize(uint64_t s, unsigned int dec)
+{
+	char szBuf[64];
+
+	if(dec > 16)
+		dec = 16;
+
+	if(s < 1024)
+        sprintf(szBuf, "%ju Byte", s);
+	else
+	{
+		static const char *a[] = {"KiB", "MiB", "GiB", "TiB", "PiB", "EiB"};
+        uint64_t e = (uint64_t)1 << 60;
+		int i;
+
+		for(i = 5; (i > 0) && (e > s); --i, e >>= 10)
+			;
+
+		sprintf(szBuf, "%.*f %s", dec, (double)s / (double)e, a[i]);
+	}
+
+	return szBuf;
+}

+ 36 - 0
common/strutil.h

@@ -0,0 +1,36 @@
+// strutil.h :
+//
+
+#if !defined(AGD_STRUTIL_H__63CB60B8_AF48_4ACA_BCB7_188E70BB5286__INCLUDED_)
+#define AGD_STRUTIL_H__63CB60B8_AF48_4ACA_BCB7_188E70BB5286__INCLUDED_
+
+#ifdef __cplusplus
+#include <stdint.h>
+#include <string>
+#include <vector>
+#include <regex>
+
+/////////////////////////////////////////////////////////////////////////////
+// strutil.h - Declarations:
+
+std::string & strucase(std::string &s);
+std::string strucase(std::string s);
+std::string strucase(const char *ps);
+std::string & strlcase(std::string &s);
+std::string strlcase(std::string s);
+std::string strlcase(const char *ps);
+std::string & ltrim(std::string &s);
+std::string & ltrim(std::string &s, const char *sep);
+std::string & rtrim(std::string &s);
+std::string & rtrim(std::string &s, const char *sep);
+std::string & trim(std::string &s);
+std::string & trim(std::string &s, const char *sep);
+std::string trim(const std::string &s);
+std::string trim(const std::string &s, const char *sep);
+int strsplit(const std::string &str, const char *sep, std::vector<std::string> &vec);
+std::string strFormatByteSize(uint64_t s, unsigned int dec = 1);
+std::string formatString(const char *fmt, ...);
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	__cplusplus
+#endif	//	!defined(AGD_STRUTIL_H__63CB60B8_AF48_4ACA_BCB7_188E70BB5286__INCLUDED_)

+ 69 - 0
common/uuid.c

@@ -0,0 +1,69 @@
+#ifdef _WIN32
+#define _CRT_SECURE_NO_WARNINGS
+#include <windows.h>
+#endif	//	_WIN32
+
+#include "uuid.h"
+#include <stdio.h>
+#include <string.h>
+
+DEFINE_UUID(UUID_NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+
+int _uuid_parse(const char *pszIn, uuid_t *uuid)
+{
+	int i, nRet = 0;
+
+	if(pszIn && uuid && strlen(pszIn) == _UUID_STRING_LEN)
+	{
+		int n[8];
+
+		nRet = sscanf(pszIn,
+				"%08x-%04hx-%04hx-%02x%02x-%02x%02x%02x%02x%02x%02x",
+				&uuid->Data1,
+				&uuid->Data2,
+				&uuid->Data3,
+				&n[0], &n[1],
+				&n[2], &n[3], &n[4], &n[5], &n[6], &n[7]);
+
+		if(nRet == 11)
+		{
+			for(i = 0; i < 8; i++)
+			{
+				uuid->Data4[i] = (unsigned char)n[i];
+			}
+		}
+	}
+
+	return nRet == 11;
+}
+
+int _uuid_unparse(const uuid_t *uuid, char *pszOut, size_t nCChOut)
+{
+	if(!uuid || !pszOut || nCChOut <= _UUID_STRING_LEN)
+		return 0;
+
+	return sprintf(pszOut, "%08x-%04hx-%04hx-%02hx%02hx-%02hx%02hx%02hx%02hx%02hx%02hx",
+				uuid->Data1,
+				uuid->Data2,
+				uuid->Data3,
+				uuid->Data4[0], uuid->Data4[1],
+				uuid->Data4[2], uuid->Data4[3], uuid->Data4[4], uuid->Data4[5], uuid->Data4[6], uuid->Data4[7]);
+}
+
+int _uuid_compare(const uuid_t *uuid1, const uuid_t *uuid2)
+{
+	if(uuid1 && uuid2)
+		return memcmp(uuid1, uuid2, sizeof(uuid_t));
+	return 0;
+}
+
+void _uuid_copy(uuid_t *uuDest, const uuid_t *uuSrc)
+{
+	if(uuDest && uuSrc)
+		memcpy(uuDest, uuSrc, sizeof(uuid_t));
+}
+
+int _uuid_is_null(const uuid_t *uuid)
+{
+	return !_uuid_compare(uuid, &UUID_NULL);
+}

+ 57 - 0
common/uuid.h

@@ -0,0 +1,57 @@
+// uuid.h :
+//
+
+#if !defined(AGD_UUID_H__E2CBFDC3_1EA5_4220_8D44_D0C26219B529__INCLUDED_)
+#define AGD_UUID_H__E2CBFDC3_1EA5_4220_8D44_D0C26219B529__INCLUDED_
+
+#include <inttypes.h>
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif	//	__cplusplus
+
+/////////////////////////////////////////////////////////////////////////////
+// uuid.h - Declarations:
+
+#ifdef __linux__
+typedef struct _UUID
+{
+    uint32_t Data1;
+    uint16_t Data2;
+    uint16_t Data3;
+    uint8_t  Data4[8];
+}UUID;
+#define uuid_t UUID
+#else	//	__linux__
+#ifdef _MSC_VER
+#include <guiddef.h>
+#ifndef uuid_t
+#define uuid_t GUID
+#endif  //    uuid_t
+#endif	//	_MSC_VER
+#endif	//	__linux__
+
+#define DEFINE_UUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
+											const uuid_t name = {l, w1, w2, {b1, b2, b3, b4, b5, b6, b7, b8}}
+#define DECLARE_UUID(v)						extern const uuid_t v
+
+#define _UUID_STRING_LEN					36
+
+/////////////////////////////////////////////////////////////////////////////
+
+DECLARE_UUID(UUID_NULL);
+
+/////////////////////////////////////////////////////////////////////////////
+
+int		_uuid_parse		(const char *pszIn, uuid_t *uuid);
+int		_uuid_unparse	(const uuid_t *uuid, char *pszOut, size_t nCChOut);
+int		_uuid_compare	(const uuid_t *uuid1, const uuid_t *uuid2);
+void	_uuid_copy		(uuid_t *uuDest, const uuid_t *uuSrc);
+int		_uuid_is_null	(const uuid_t *uuid);
+
+/////////////////////////////////////////////////////////////////////////////
+#ifdef __cplusplus
+}
+#endif	//	__cplusplus
+#endif	//	!defined(AGD_UUID_H__E2CBFDC3_1EA5_4220_8D44_D0C26219B529__INCLUDED_)

+ 8 - 0
datalogger/.gitignore

@@ -0,0 +1,8 @@
+# git ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
+
+*.pro.user

+ 1352 - 0
datalogger/datalogger.cpp

@@ -0,0 +1,1352 @@
+#include <string.h>
+#include <malloc.h>
+#include <limits.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include "fileutil.h"
+#include "strutil.h"
+#include "datalogger.h"
+#include "processclock.h"
+#include "debug.h"
+
+/////////////////////////////////////////////////////////////////////////////
+
+#define _USE_MODIFIED_INDEX					1
+#define _TRACK_TIME							1
+#define _FILE_SIZE_DELETE_MARGIN_PERCENT	10
+#define _SIZE_GUARD_TIMESTAMP_FILE_NAME		"sguard.ts"
+#define _INVALID_TIMESTAMP_VALUE			((time_t)-1)
+
+/////////////////////////////////////////////////////////////////////////////
+
+CDataLogger::CDataLogger(LPCDLPARAMS pdlp, CLogfile &rlf) : m_lf(rlf),
+															m_tidSGThread(0),
+															m_bSGHasSizeLimitPrerequisites(false),
+															m_bSGInProgress(false),
+															m_bSGConfigured(false),
+															m_nSGCurPassUTC(0),
+															m_nSGLastPassUTC(0),
+															m_condmtx1(PTHREAD_MUTEX_INITIALIZER),
+															m_cond1(PTHREAD_COND_INITIALIZER),
+															m_nLastLogTimestamp(0),
+															m_bBadDateLogsDetected(false)
+{
+	memset(&m_dlp, 0, sizeof(m_dlp));
+	memset(&m_gv, 0, sizeof(m_gv));
+	memset(&m_mtx, 0, sizeof(m_mtx));
+	memset(&m_mutexAttr, 0, sizeof(m_mutexAttr));
+
+	if(pdlp)
+		memcpy(&m_dlp, pdlp, sizeof(m_dlp));
+
+	if(!(m_bSGConfigured = !!m_dlp.nMaxSize || !!m_dlp.nMaxAge))
+		m_lf.Error("Size guard not configured.\n");
+
+	if(m_dlp.pszBaseDir)
+		strncpy(m_szAppDir, m_dlp.pszBaseDir, sizeof(m_szAppDir) - 1);
+	else
+		::GetAppDirectory(m_szAppDir, sizeof(m_szAppDir));
+
+	::pthread_mutexattr_init(&m_mutexAttr);
+	::pthread_mutexattr_settype(&m_mutexAttr, PTHREAD_MUTEX_RECURSIVE);
+	::pthread_mutex_init(&m_mtx, &m_mutexAttr);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+CDataLogger::~CDataLogger(void)
+{
+	Release();
+	::pthread_mutex_destroy(&m_mtx);
+	::pthread_mutexattr_destroy(&m_mutexAttr);
+	::pthread_cond_destroy(&m_cond1);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CDataLogger::Release(void)
+{
+	if(m_tidSGThread)
+	{
+		::pthread_mutex_lock(&m_condmtx1);
+		bool bSgInProg = m_bSGInProgress;
+		::pthread_mutex_unlock(&m_condmtx1);
+		if(bSgInProg)
+			CLogfile::StdErr("\nDatalogger is terminating. This may take some time ...\nPlease wait, before powering off the device!\n");
+		Lock();
+		m_lf.Lock();
+		::pthread_cancel(m_tidSGThread);
+		m_lf.Unlock();
+		Unlock();
+		::pthread_join(m_tidSGThread, NULL);
+		m_tidSGThread = 0;
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+unsigned long CDataLogger::GetTagID(const char *pszVarPath, int nDataType, int nLogType)
+{
+	CMySqlDB db;
+	const char *pszID = NULL;
+	char *pszEndptr;
+	unsigned long nID = ULONG_MAX;
+
+	std::string sSql;
+	sSql = formatString("select `tagid` from `%s` where `path` = '%s' and `dataType` = %d and `logType` = %d", m_dlp.szTagsTable, pszVarPath, nDataType, nLogType);
+
+	if(!db.Connect("localhost", m_dlp.szDBUser, m_dlp.szDBPass, m_dlp.szDBName))
+	{
+		m_lf.Error("CDataLogger::GetTagID: DB Error: %s\n", db.LastError().c_str());
+		return nID;
+	}
+
+	CMySqlResult res = db.Query(sSql.c_str());
+	bool bError = res.error();
+
+	if(bError)
+		m_lf.Error("CDataLogger::GetTagID: DB Error: %s\n", db.LastError().c_str());
+	else
+	{
+		my_ulonglong nCount = res.RowCount();
+
+		if(nCount > 0)
+		{
+			MYSQL_ROW pRow = res.FetchRow();
+			pszID = pRow[0];
+			nID = strtoul(pszID, &pszEndptr, 10);
+		}
+		else
+		{
+			sSql = formatString("insert into `%s` (`dataType`, `logType`, `path`) values(%d, %d, '%s')", m_dlp.szTagsTable, nDataType, nLogType, pszVarPath);
+			res = db.Query(sSql.c_str());
+			bError = res.error();
+
+			if(bError)
+				m_lf.Error("DB Error: %s\n", db.LastError().c_str());
+			else
+			{
+				sSql = formatString("select `tagid` from `%s` where `path`='%s'", m_dlp.szTagsTable, pszVarPath);
+				CMySqlResult res = db.Query(sSql.c_str());
+				bool bError = res.error();
+
+				if(bError)
+					m_lf.Error("DB Error: %s\n", db.LastError().c_str());
+				else if(res.RowCount() > 0)
+				{
+					MYSQL_ROW pRow = res.FetchRow();
+					pszID = pRow[0];
+					nID = strtoul(pszID, &pszEndptr, 10);
+				}
+			}
+		}
+	}
+
+	return nID;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+bool CDataLogger::TableFileExists(const char *pszTableName)
+{
+	char szDataFile[PATH_MAX];
+	sprintf(szDataFile, "%s%s/%s.ibd", m_gv.szDataDir, m_dlp.szDBName, pszTableName);
+	return !!FileExist(szDataFile);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+int64_t CDataLogger::TableFileSize(const char *pszTableName)
+{
+	struct stat statbuf;
+	char szDataFile[PATH_MAX];
+	sprintf(szDataFile, "%s%s/%s.ibd", m_gv.szDataDir, m_dlp.szDBName, pszTableName);
+	if(stat(szDataFile, &statbuf) == -1)
+		return 0;
+	return (int64_t)statbuf.st_size;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+bool CDataLogger::QueryServerVariable(CMySqlDB &rdb, const char *pszVarname, CMySqlVar &val)
+{
+	if(!pszVarname || !*pszVarname)
+	{
+		m_lf.Error("CDataLogger::QueryServerVariable: No Variable name!\n");
+		return false;
+	}
+
+	bool bError;
+	std::string sSql;
+	sSql = formatString("select @@%s;", pszVarname);
+	CMySqlResult res = rdb.Query(sSql.c_str());
+	bError = res.error();
+
+	if(!bError)
+	{
+		bError = true;
+
+		do
+		{
+			my_ulonglong nRowCount		= res.RowCount();
+			unsigned int nFldCount		= res.FieldCount();
+			const MYSQL_FIELD *pFields	= res.FetchFields();
+
+			if(nRowCount != 1 || nFldCount != 1 || !pFields)
+				break;
+
+			MYSQL_ROW pRow = res.FetchRow();
+
+			if(!pRow || !*pRow || !**pRow)
+				break;
+
+			bError = !val.FromField(pFields[0], pRow[0]);
+		}
+		while(false);
+	}
+
+	return !bError;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+#if 0
+bool CDataLogger::QueryStatusVariable(CMySqlDB &rdb, const char *pszVarname, CMySqlVar &val)
+{
+	if(!pszVarname || !*pszVarname)
+	{
+		m_lf.Error("CDataLogger::QueryStatusVariable: No Variable name!\n");
+		return false;
+	}
+
+	bool bError;
+	std::string sSql;
+	sSql = formatString("show session status like '%s'", pszVarname);
+	CMySqlResult res = rdb.Query(sSql.c_str());
+	bError = res.error();
+
+	if(!bError)
+	{
+		bError = true;
+
+		do
+		{
+			my_ulonglong nRowCount		= res.RowCount();
+			unsigned int nFldCount		= res.FieldCount();
+			const MYSQL_FIELD *pFields	= res.FetchFields();
+
+			if(nRowCount != 1 || nFldCount != 2 || !pFields)
+				break;
+
+			MYSQL_ROW pRow = res.FetchRow();
+
+			if(!pRow || !pRow[1] || !*pRow[1])
+				break;
+
+			bError = !val.FromField(pFields[1], pRow[1]);
+		}
+		while(false);
+	}
+
+	return !bError;
+}
+#endif
+/////////////////////////////////////////////////////////////////////////////
+
+bool CDataLogger::ReadGlobalOptions(CMySqlDB &rdb)
+{
+	CMySqlVar val;
+	memset(&m_gv, 0, sizeof(m_gv));
+
+	if(QueryServerVariable(rdb, "datadir", val))
+	{
+		memset(m_gv.szDataDir, 0, sizeof(m_gv.szDataDir));
+		if(!val.IsNull())
+			val.CopyStrVal(m_gv.szDataDir, sizeof(m_gv.szDataDir) - 1, 0);
+	}
+
+	if(QueryServerVariable(rdb, "innodb_file_per_table", val))
+	{
+		if(!val.IsNull())
+			m_gv.bInnoDbFilePerTable = val;
+	}
+
+	if(QueryServerVariable(rdb, "innodb_strict_mode", val))
+	{
+		if(!val.IsNull())
+			m_gv.bInnoDbIsStrictMode = val;
+	}
+
+	if(QueryServerVariable(rdb, "innodb_file_format", val))
+	{
+		memset(m_gv.szInnoDbFileFormat, 0, sizeof(m_gv.szInnoDbFileFormat));
+		if(!val.IsNull())
+		{
+			val.CopyStrVal(m_gv.szInnoDbFileFormat, sizeof(m_gv.szInnoDbFileFormat) - 1, 0);
+			m_gv.bInnoDbIsBarracuda = !strcasecmp(m_gv.szInnoDbFileFormat, "barracuda");
+		}
+	}
+
+	return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CDataLogger::SizeGuardTrigger(time_t ts)
+{
+	if(m_bSGConfigured && m_tidSGThread)
+	{
+		::pthread_mutex_lock(&m_condmtx1);
+
+		if(	!m_bSGInProgress &&
+			!SizeGuardDayWorkDone(ts))
+		{
+			unsigned long long nMnUTC = (unsigned long long)_MIDNIGHT_TIMESTAMP_UTC(ts);
+
+			if(SizeGuardLastPassRead() != nMnUTC)
+			{
+				m_nSGCurPassUTC = ts;
+				::pthread_cond_signal(&m_cond1);
+			}
+			else
+				m_nSGLastPassUTC = nMnUTC;
+		}
+
+		::pthread_mutex_unlock(&m_condmtx1);
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+#if 0
+bool CDataLogger::QueryTableSizes(CMySqlDB &rdb, const char *pszTableName)
+{
+	bool bError;
+	char *pszEndptr;
+	MYSQL_ROW pRow;
+    uint64_t nDataLength = 0, nDataFree = 0, nIndexLength = 0, nFreeExtents, nTotalExtents;
+	std::string sSql;
+	sSql = formatString("select `DATA_LENGTH`, `INDEX_LENGTH`, `DATA_FREE` from `information_schema`.`TABLES` where (`TABLE_SCHEMA` = '%s') AND (`TABLE_NAME` = '%s');", m_dlp.szDBName, pszTableName);
+
+	CMySqlResult res = rdb.Query(sSql.c_str());
+	bError = res.error();
+
+	if(bError)
+	{
+		m_lf.Error("CDataLogger::QueryTableSizes: DB Error: %s\n", rdb.LastError().c_str());
+		return false;
+	}
+
+	pRow = res.FetchRow();
+	nDataLength = strtoull(pRow[0], &pszEndptr, 10);
+	nIndexLength = strtoull(pRow[1], &pszEndptr, 10);
+	nDataFree = strtoull(pRow[2], &pszEndptr, 10);
+
+	//////////////////////////////////////////////////////////////////////////////////////////////
+
+	sSql = formatString("select `FREE_EXTENTS`, `TOTAL_EXTENTS`, `DATA_FREE` from `information_schema`.`FILES` where `FILE_NAME` like '%%demo/%s.ibd';", pszTableName);
+	CMySqlResult res2 = rdb.Query(sSql.c_str());
+	bError = res2.error();
+
+	if(bError)
+	{
+		m_lf.Error("CDataLogger::QueryTableSizes: DB Error: %s\n", rdb.LastError().c_str());
+		return false;
+	}
+
+	pRow = res2.FetchRow();
+	nFreeExtents = strtoull(pRow[0], &pszEndptr, 10);
+	nTotalExtents = strtoull(pRow[1], &pszEndptr, 10);
+	int64_t nFileSize = TableFileSize(pszTableName);
+
+    m_lf.Info("Data length: %ju, Data free: %ju, Total extents: %ju, Free extents: %ju, File size: %ju.\n", nDataLength + nIndexLength, nDataFree, nTotalExtents, nFreeExtents, nFileSize);
+    TRACE("Data length: %ju, Data free: %ju, Total extents: %ju, Free extents: %ju, File size: %ju.\n", nDataLength + nIndexLength, nDataFree, nTotalExtents, nFreeExtents, nFileSize);
+
+	return true;
+}
+#endif
+/////////////////////////////////////////////////////////////////////////////
+
+bool CDataLogger::CheckTable(CMySqlDB &rdb, const char *pszTableName, bool &bExists, bool &bUseResortTable, bool &bIsInnoDB)
+{
+	bool bError;
+	std::string sSql;
+	const char *pszEngine = NULL;
+	uint64_t nDataLength = 0, nIndexLength = 0;
+	int64_t nFileSize = -1;
+	bExists = bUseResortTable = false;
+	bIsInnoDB = false;
+
+	sSql = formatString("select `ENGINE`, `DATA_LENGTH`, `INDEX_LENGTH` from `information_schema`.`TABLES` where (`TABLE_SCHEMA` = '%s') AND (`TABLE_NAME` = '%s')", m_dlp.szDBName, pszTableName);
+	CMySqlResult res1 = rdb.Query(sSql.c_str());
+	bError = res1.error();
+
+	if(bError)
+	{
+		m_lf.Error("CDataLogger::CheckTables: DB Error: %s\n", rdb.LastError().c_str());
+		return false;
+	}
+	else
+	{
+		my_ulonglong nCount = res1.RowCount();
+
+		if(nCount > 0)
+		{
+			MYSQL_ROW pRow = res1.FetchRow();
+			unsigned int nFc = res1.FieldCount();
+			const MYSQL_FIELD *pFields = res1.FetchFields();
+			CMySqlVar vals[3];
+
+			bExists = true;
+
+			for(unsigned int i = 0; i < nFc; i++)
+			{
+				vals[i].FromField(pFields[i], pRow[i]);
+
+				if(!vals[i].FieldnameCmp("ENGINE"))
+				{
+					pszEngine = vals[i].StrVal();
+					bIsInnoDB = !strcasecmp(pszEngine, "InnoDB");
+				}
+				else if(!vals[i].FieldnameCmp("DATA_LENGTH"))
+					nDataLength = (uint64_t)vals[i];
+				else if(!vals[i].FieldnameCmp("INDEX_LENGTH"))
+					nIndexLength = (uint64_t)vals[i];
+			}
+
+			if(bIsInnoDB && m_gv.bInnoDbFilePerTable)
+			{
+				if(!TableFileExists(pszTableName))
+					m_lf.Warning("'innodb_file_per_table' configured, but table '%s' does not have a data file!\n", pszTableName);
+				else
+					nFileSize = TableFileSize(pszTableName);
+			}
+
+			if(nFileSize >= 0)
+				m_lf.Info("Found info on table '%s': Engine: '%s', Data size: %s, Index size: %s, File size: %s\n", pszTableName, pszEngine ? pszEngine : "unknown", strFormatByteSize(nDataLength, 1).c_str(), strFormatByteSize(nIndexLength, 1).c_str(), strFormatByteSize(nFileSize, 1).c_str());
+			else
+				m_lf.Info("Found info on table '%s': Engine: '%s', Data size: %s, Index size: %s\n", pszTableName, pszEngine ? pszEngine : "unknown", strFormatByteSize(nDataLength, 1).c_str(), strFormatByteSize(nIndexLength, 1).c_str());
+
+			if(!pszEngine)
+				m_lf.Warning("Failed to determine the Database engine of Table '%s'!\n", pszTableName);
+			else if(strcmp(pszEngine, _DL_DATABSE_ENGINE_NAME))
+				m_lf.Warning("Current Database engine of Table '%s' is '%s'! Please consider to migrate to '%s'!\n", pszTableName, pszEngine, _DL_DATABSE_ENGINE_NAME);
+
+			sSql = formatString("select * from `%s` limit 0, 10", pszTableName);
+			CMySqlResult res2 = rdb.Query(sSql.c_str()); // try to query table
+
+			if(res2.error())	// if the query results in an error, the table may be corrupt!
+			{
+				char szTblName[256];
+				m_lf.Error("Failed to query table '%s': %s\n", pszTableName, rdb.LastError().c_str());
+
+				// if the table is corrupt, try to rename it and create a new instane of the original table.
+				sprintf(szTblName, "%s_corrupt_%08zX", pszTableName, (size_t)time(NULL));
+				sSql = formatString("rename table `%s` to `%s`", pszTableName, szTblName);
+				CMySqlResult res3 = rdb.Query(sSql.c_str()); // try to rename the corrupt table
+
+				if(res3.error())
+				{
+					// if the renaming fails, try to create a new working table with a different name
+					m_lf.Error("Failed to rename table '%s': %s\n", pszTableName, rdb.LastError().c_str());
+					bExists = false;
+					bUseResortTable = true;
+				}
+				else
+				{
+					m_lf.Warning("Renamed table '%s' to '%s'\n", pszTableName, szTblName);
+					bExists = false;
+				}
+			}
+		}
+	}
+
+	return !bError;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+bool CDataLogger::InitDatabase(bool bEnforceCreate)
+{
+	CMySqlDB db;
+	time_t nMaxLogTimestamp = m_nLastLogTimestamp = 0;
+	bool bExists, bUseResortTable, bDummy;
+
+	m_lf.Info("Connecting to Database server @ 'localhost'.\n");
+
+	if(!db.Connect("localhost", m_dlp.szDBUser, m_dlp.szDBPass, NULL))
+	{
+		m_lf.Error("CDataLogger::InitDatabase: DB Error: %s\n", db.LastError().c_str());
+		return false;
+	}
+
+	m_lf.Info("Success!\n");
+
+	if(!ReadGlobalOptions(db))
+		return false;
+
+	if(!m_gv.bInnoDbIsBarracuda || !m_gv.bInnoDbFilePerTable || !m_gv.bInnoDbIsStrictMode)
+		m_lf.Warning("InnoDB file format: '%s', InnoDB table space: '%s', InnoDB strict mode: %s.\n", m_gv.szInnoDbFileFormat, m_gv.bInnoDbFilePerTable ? "File per table" : "System", m_gv.bInnoDbIsStrictMode ? "yes" : "no");
+	else
+		m_lf.Info("InnoDB file format: '%s', InnoDB table space: '%s', InnoDB strict mode: %s.\n", m_gv.szInnoDbFileFormat, m_gv.bInnoDbFilePerTable ? "File per table" : "System", m_gv.bInnoDbIsStrictMode ? "yes" : "no");
+
+	if(!CreateDatabase(db, bEnforceCreate))
+		return false;
+
+	m_lf.Info("Opening Database '%s'.\n", m_dlp.szDBName);
+
+	if(db.SelectDB(m_dlp.szDBName))
+	{
+		m_lf.Error("CDataLogger::InitDatabase: DB Error: %s\n", db.LastError().c_str());
+		return false;
+	}
+
+	m_lf.Info("Success!\n");
+
+	if(!CheckTable(db, m_dlp.szTagsTable, bExists, bUseResortTable, bDummy))
+		return false;
+
+	if(!bExists)
+	{
+		if(bUseResortTable)
+		{
+			char szOrigTable[_DL_MAX_TABLE_NAME_LENGTH];
+			memcpy(szOrigTable, m_dlp.szTagsTable, sizeof(szOrigTable));
+			sprintf(m_dlp.szTagsTable, "%s_resort_%08zX", szOrigTable, (size_t)time(NULL));
+			m_lf.Warning("Resorting to work-around table '%s'.\n", m_dlp.szTagsTable);
+		}
+
+		if(!CreateTagsTable(db))
+			return false;
+	}
+	
+	if(!AlterTagsTable(db)) // 23.10.2020, extend logtype enum if not up to date
+		return false;
+
+	if(!CheckTable(db, m_dlp.szLogsTable, bExists, bUseResortTable, m_gv.bLogsTblIsInnoDB))
+		return false;
+
+	if(!bExists)
+	{
+		if(bUseResortTable)
+		{
+			char szOrigTable[_DL_MAX_TABLE_NAME_LENGTH];
+			memcpy(szOrigTable, m_dlp.szLogsTable, sizeof(szOrigTable));
+			sprintf(m_dlp.szLogsTable, "%s_resort_%08zX", szOrigTable, (size_t)time(NULL));
+			m_lf.Warning("Resorting to work-around table '%s'.\n", m_dlp.szLogsTable);
+		}
+
+		if(!CreateLogsTable(db))
+			return false;
+
+#if _DL_DATABSE_ENGINE == _DL_DATABASE_ENGINE_INNODB
+		m_gv.bLogsTblIsInnoDB = true;
+#endif	//	_DL_DATABSE_ENGINE == _DL_DATABASE_ENGINE_INNODB
+	}
+
+	if(m_bSGConfigured)
+	{
+		if(!(m_bSGHasSizeLimitPrerequisites = m_gv.bLogsTblIsInnoDB && m_gv.bInnoDbFilePerTable && TableFileExists(m_dlp.szLogsTable)))
+		{
+			m_lf.Warning("SG: Database configuration: InnoDB: %s, File-per-table tablespace: %s\n", m_gv.bLogsTblIsInnoDB ? "yes" : "no", m_gv.bInnoDbFilePerTable ? "yes" : "no");
+			m_lf.Warning("SG: The current database configuration does not support monitoring of size limits. Size guard will operate on age limits only!\n");
+		}
+
+		::pthread_mutex_lock(&m_condmtx1);
+		if(::pthread_create(&m_tidSGThread, NULL, &CDataLogger::SizeGuardWorker, reinterpret_cast<void*>(this)))
+		{
+			m_tidSGThread = 0;
+			m_lf.Error("SG: Failed to start size monitoring thread!\n");
+			::pthread_mutex_unlock(&m_condmtx1);
+			return false;
+		}
+
+		::pthread_cond_wait(&m_cond1, &m_condmtx1);
+		::pthread_mutex_unlock(&m_condmtx1);
+	}
+
+	if(!CreateLogsBDTable(db))
+		return false;
+
+	if(((nMaxLogTimestamp = GetLastLogTimestamp(db)) != _INVALID_TIMESTAMP_VALUE))
+		m_nLastLogTimestamp = nMaxLogTimestamp;
+
+	m_lf.Info("Database initialization complete.\n");
+
+	return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+bool CDataLogger::CreateDatabase(CMySqlDB &rdb, bool bEnforceCreate)
+{
+	bool bError = false;
+	std::string sSql;
+
+	if(bEnforceCreate)
+	{
+		sSql = formatString("drop database if exists `%s`", m_dlp.szDBName);
+		CMySqlResult res = rdb.Query(sSql.c_str());
+		bError = res.error();
+
+		if(bError)
+			m_lf.Error("CDataLogger::InitDatabase: DB Error: %s\n", rdb.LastError().c_str());
+	}
+
+	if(!bError)
+	{
+		sSql = formatString("create database if not exists `%s`", m_dlp.szDBName);
+		CMySqlResult res = rdb.Query(sSql.c_str());
+		bError = res.error();
+
+		if(bError)
+			m_lf.Error("CDataLogger::InitDatabase: DB Error: %s\n", rdb.LastError().c_str());
+	}
+
+	return !bError;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+bool CDataLogger::CreateTagsTable(CMySqlDB &rdb)
+{
+	std::string sSql;
+
+	const char *pszFormat =
+	"CREATE TABLE IF NOT EXISTS `%s` (" \
+	" `tagid` smallint(6) unsigned NOT NULL AUTO_INCREMENT," \
+	" `dataType` enum('bool', 'I1', 'UI1', 'I2', 'UI2', 'I4', 'UI4', 'I8', 'UI8', 'float', 'double', 'string') NOT NULL," \
+	" `logType` enum('IC', 'IU', 'VC', 'VU', 'ICR', 'IUR', 'VCR', 'VUR') NOT NULL," \
+	" `path` varchar(%u) NOT NULL," \
+	" PRIMARY KEY (`tagid`, `dataType`, `logType`)," \
+	" KEY `path` (`path`)" \
+	") ENGINE=%s DEFAULT CHARSET=ascii";
+
+	m_lf.Info("Creating Table '%s' (if not exists) using engine '%s'.\n", m_dlp.szTagsTable, _DL_DATABSE_ENGINE_NAME);
+
+	sSql = formatString(pszFormat, m_dlp.szTagsTable, _DL_MAX_VARPATH_LENGTH, _DL_DATABSE_ENGINE_NAME);
+	CMySqlResult res = rdb.Query(sSql.c_str());
+	bool bError = res.error();
+
+	if(bError)
+		m_lf.Error("CDataLogger::CreateTagsTable: DB Error: %s\n", rdb.LastError().c_str());
+	else
+		m_lf.Info("Success!\n");
+
+	return !bError;
+}
+
+bool CDataLogger::AlterTagsTable(CMySqlDB &rdb)
+{
+	std::string sSql = formatString("ALTER TABLE `%s` MODIFY COLUMN `logType` enum('IC','IU','VC','VU','ICR','IUR','VCR','VUR') NOT NULL", m_dlp.szTagsTable);
+	CMySqlResult res = rdb.Query(sSql.c_str());
+	bool bError = res.error();
+	if(bError)
+		m_lf.Error("CDataLogger::AlterTagsTable: DB Error: %s\n", rdb.LastError().c_str());
+	return !bError;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+bool CDataLogger::CreateLogsTable(CMySqlDB &rdb)
+{
+	std::string sSql;
+
+	const char *pszFormat =
+#if _USE_MODIFIED_INDEX
+	"CREATE TABLE IF NOT EXISTS `%s` (" \
+	"	`tagid` smallint(6) unsigned NOT NULL," \
+	"	`tslog` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP," \
+	"	`value` double," \
+	"	`valueMin` double DEFAULT NULL," \
+	"	`valueMax` double DEFAULT NULL," \
+	"	PRIMARY KEY (`tslog`, `tagid`)," \
+	"	KEY `tagid` (`tagid`)" \
+	") ENGINE=%s DEFAULT CHARSET=ascii ROW_FORMAT=COMPRESSED";
+#else //	_USE_MODIFIED_INDEX
+	"CREATE TABLE IF NOT EXISTS `%s` (" \
+	"	`tagid` smallint(6) unsigned NOT NULL," \
+	"	`tslog` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP," \
+	"	`value` double," \
+	"	`valueMin` double DEFAULT NULL," \
+	"	`valueMax` double DEFAULT NULL," \
+	"	PRIMARY KEY (`tagid`, `tslog`)," \
+	"	KEY `tslog` (`tslog`)" \
+	") ENGINE=%s DEFAULT CHARSET=ascii ROW_FORMAT=COMPRESSED";
+#endif	//	_USE_MODIFIED_INDEX
+
+	m_lf.Info("Creating Table '%s' if not exists using engine '%s'.\n", m_dlp.szLogsTable, _DL_DATABSE_ENGINE_NAME);
+
+	sSql = formatString(pszFormat, m_dlp.szLogsTable, _DL_DATABSE_ENGINE_NAME);
+	CMySqlResult res = rdb.Query(sSql.c_str());
+	bool bError = res.error();
+
+	if(bError)
+		m_lf.Error("CDataLogger::CreateLogsTable: DB Error: %s\n", rdb.LastError().c_str());
+	else
+		m_lf.Info("Success!\n");
+
+	return !bError;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+bool CDataLogger::CreateLogsBDTable(CMySqlDB &rdb)
+{
+	std::string sSql;
+
+	const char *pszFormat =
+	"CREATE TABLE IF NOT EXISTS `%s` (" \
+	"	`id` int unsigned unsigned NOT NULL AUTO_INCREMENT," \
+	"	`tagid` smallint(6) unsigned NOT NULL," \
+	"	`tslog` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP," \
+	"	`value` double," \
+	"	`valueMin` double DEFAULT NULL," \
+	"	`valueMax` double DEFAULT NULL," \
+	"	PRIMARY KEY (`id`)," \
+	"	KEY `tslog` (`tslog`)," \
+	"	KEY `tagid` (`tagid`)" \
+	") ENGINE=%s DEFAULT CHARSET=ascii ROW_FORMAT=COMPRESSED";
+
+	m_lf.Info("Creating Table '%s' if not exists using engine '%s'.\n", m_dlp.szLogsTableBD, _DL_DATABSE_ENGINE_NAME);
+
+	sSql = formatString(pszFormat, m_dlp.szLogsTableBD, _DL_DATABSE_ENGINE_NAME);
+	CMySqlResult res = rdb.Query(sSql.c_str());
+	bool bError = res.error();
+
+	if(bError)
+		m_lf.Error("CDataLogger::CreateLogsBDTable: DB Error: %s\n", rdb.LastError().c_str());
+	else
+		m_lf.Info("Success!\n");
+
+	return !bError;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+bool CDataLogger::Log(unsigned long nTagID, double fValue, double fMin, double fMax, time_t nTimestamp, int nIndex, LogTypes lt, bool bNull, bool bNoBadDateCheck)
+{
+	DL_LOG_ENTRY log;
+	log.nTagID		= nTagID;
+	log.nTimestamp	= nTimestamp;
+	log.fValue		= fValue;
+	log.fMin		= fMin;
+	log.fMax		= fMax;
+	log.nIndex		= nIndex;
+	log.lt			= lt;
+	log.bNull		= bNull;
+
+	if(bNoBadDateCheck || (nTimestamp > m_nLastLogTimestamp))
+		m_logs.push_back(log);
+	else
+		m_logsBD.push_back(log);
+	return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+time_t CDataLogger::GetLastLogTimestamp(CMySqlDB &rdb)
+{
+	time_t nTs = _INVALID_TIMESTAMP_VALUE;
+	std::string sSql;
+
+	do
+	{
+		sSql = formatString("select unix_timestamp(max(`tslog`)) from `%s`", m_dlp.szLogsTable);
+		CMySqlResult res = rdb.Query(sSql.c_str());
+
+		if(res.error())
+		{
+			m_lf.Error("CDataLogger::GetLastLogTimestamp: DB Error: %s\n", rdb.LastError().c_str());
+			break;
+		}
+		else
+		{
+			CMySqlVar val;
+			my_ulonglong nRowCount		= res.RowCount();
+			unsigned int nFldCount		= res.FieldCount();
+			const MYSQL_FIELD *pFields	= res.FetchFields();
+
+			if(nRowCount != 1 || nFldCount != 1 || !pFields)
+			{
+//				m_lf.Error("CDataLogger::GetLastLogTimestamp: Unexpected error!\n");
+				break;
+			}
+
+			MYSQL_ROW pRow = res.FetchRow();
+
+			if(	!pRow ||
+				!val.FromField(pFields[0], pRow[0]))
+			{
+//				m_lf.Error("CDataLogger::GetLastLogTimestamp: Unexpected error!\n");
+				break;
+			}
+			
+			nTs = (time_t)(uint64_t)val;
+		}
+	}
+	while(false);
+
+	return nTs;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+bool CDataLogger::Flush(time_t nTimestamp)
+{
+	::pthread_mutex_lock(&m_condmtx1);
+	bool bGoodBadTransition = false, bSgInProg = m_bSGInProgress;
+	::pthread_mutex_unlock(&m_condmtx1);
+
+	time_t nMaxLogTimestamp = 0, nFirstGoodTimestamp = 0;
+	size_t nCountValidDate = m_logs.size();
+	size_t nCountBadDate = m_logsBD.size();
+	
+	if(!nCountValidDate && !nCountBadDate)
+		return true;	// nothing to do
+		
+	TRACE("Trying to flush %zu logs ...\n", nCountValidDate + nCountBadDate);
+	
+	if(bSgInProg && nCountValidDate)
+	{
+		TRACE("Flush: SG in progress - flushing of %zu logs deferred!\n", nCountValidDate);
+		m_lf.Info("Flush: SG in progress - flushing of %zu logs deferred!\n", nCountValidDate);
+		nCountValidDate = 0; // size guard operates on the valid-date-logs-table only, so we can safely flush invalid-date-logs
+		
+		if(!nCountBadDate)
+			return true; // no error
+	}
+	
+	if(!m_bBadDateLogsDetected && nCountBadDate)
+	{
+		m_bBadDateLogsDetected = true;
+		bGoodBadTransition = true;
+	}
+	else if(m_bBadDateLogsDetected && !nCountBadDate)
+	{
+		m_bBadDateLogsDetected = false;
+		bGoodBadTransition = true;
+	}
+
+	CMySqlDB db;
+	std::string strUnlock, strSaveFgnKey, strDisableFgnKey, strRestoreFgnKey, strSaveTZ, strSetTZ, strRestoreTZ;
+	bool bError = false;
+	std::string sSql;
+
+	if(!db.Connect("localhost", m_dlp.szDBUser, m_dlp.szDBPass, m_dlp.szDBName))
+	{
+		m_lf.Error("CDataLogger::Flush: DB Error: %s\n", db.LastError().c_str());
+		return false;
+	}
+
+	strUnlock = formatString("unlock tables");
+	strSaveFgnKey = formatString("set @old_foreign_key_checks = @@foreign_key_checks");
+	strDisableFgnKey = formatString("set foreign_key_checks = 0");
+	strRestoreFgnKey = formatString("set foreign_key_checks = @old_foreign_key_checks");
+	strSaveTZ = formatString("set @old_time_zone = @@time_zone");
+	strSetTZ = formatString("set time_zone = '+00:00'");
+	strRestoreTZ = formatString("set time_zone = @old_time_zone");
+
+	db.Query(strSaveTZ.c_str());
+	db.Query(strSetTZ.c_str());
+	db.Query(strSaveFgnKey.c_str());
+	db.Query(strDisableFgnKey.c_str());
+
+	if(nCountValidDate > 0)
+	{
+		std::string strSql, strLock;
+		strLock = formatString("lock tables `%s` write", m_dlp.szLogsTable);
+		strSql.reserve(130 + 111 * nCountValidDate);
+
+		auto itFirst = m_logs.begin();
+		const DL_LOG_ENTRY &rle0 = *itFirst;
+		if(nMaxLogTimestamp < rle0.nTimestamp)
+			nMaxLogTimestamp = rle0.nTimestamp;
+		nFirstGoodTimestamp = rle0.nTimestamp;
+
+		if(m_dlp.bMinMax && _IS_INTERVAL_LOGTYPE(rle0.lt))
+			sSql = formatString("insert into `%s` (`tagid`, `tslog`, `value`, `valueMin`, `valueMax`) values (%lu, timestamp(from_unixtime(%lu)), %.20g, %.20g, %.20g)", m_dlp.szLogsTable, rle0.nTagID, rle0.nTimestamp, rle0.fValue, rle0.fMin, rle0.fMax);
+		else
+		{
+			if(!rle0.bNull)
+				sSql = formatString("insert into `%s` (`tagid`, `tslog`, `value`, `valueMin`, `valueMax`) values (%lu, timestamp(from_unixtime(%lu)), %.20g, NULL, NULL)", m_dlp.szLogsTable, rle0.nTagID, rle0.nTimestamp, rle0.fValue);
+			else
+				sSql = formatString("insert into `%s` (`tagid`, `tslog`, `value`, `valueMin`, `valueMax`) values (%lu, timestamp(from_unixtime(%lu)), NULL, NULL, NULL)", m_dlp.szLogsTable, rle0.nTagID, rle0.nTimestamp);
+		}
+		strSql = sSql;
+
+		for(++itFirst; itFirst < m_logs.end(); itFirst++)
+		{
+			const DL_LOG_ENTRY &rle = *itFirst;
+			if(nMaxLogTimestamp < rle.nTimestamp)
+				nMaxLogTimestamp = rle.nTimestamp;
+
+			if(m_dlp.bMinMax && _IS_INTERVAL_LOGTYPE(rle.lt))
+				sSql = formatString(",(%lu, timestamp(from_unixtime(%lu)), %.20g, %.20g, %.20g)", rle.nTagID, rle.nTimestamp, rle.fValue, rle.fMin, rle.fMax);
+			else
+			{
+				if(!rle.bNull)
+					sSql = formatString(",(%lu, timestamp(from_unixtime(%lu)), %.20g, NULL, NULL)", rle.nTagID, rle.nTimestamp, rle.fValue);
+				else
+					sSql = formatString(",(%lu, timestamp(from_unixtime(%lu)), NULL, NULL, NULL)", rle.nTagID, rle.nTimestamp);
+			}
+			strSql += sSql;
+		}
+
+		if(TryLock())
+		{
+			db.Query(strLock.c_str());
+			CMySqlResult res = db.Query(strSql.c_str());
+			bError = res.error();
+			db.Query(strUnlock.c_str());
+			Unlock();
+
+			if(bError)
+				m_lf.Error("CDataLogger::Flush: DB Error: %s\n", db.LastError().c_str());
+			else
+				m_nLastLogTimestamp = nMaxLogTimestamp;
+
+			m_logs.clear();
+		}
+	}
+
+	if(bGoodBadTransition)
+	{
+		if(m_bBadDateLogsDetected)
+		{
+			bool bError;
+			char szTs[64];
+			unsigned long long nNextAIVal = 0;
+			sSql = formatString("select auto_increment from `information_schema`.`TABLES` where `TABLE_SCHEMA` = '%s' and `TABLE_NAME` = '%s';", m_dlp.szDBName, m_dlp.szLogsTableBD);
+			CMySqlResult res = db.Query(sSql.c_str());
+
+			if(!(bError = res.error()))
+			{
+				CMySqlVar val;
+				my_ulonglong nRowCount		= res.RowCount();
+				unsigned int nFldCount		= res.FieldCount();
+				const MYSQL_FIELD *pFields	= res.FetchFields();
+
+				do
+				{
+					if(nRowCount != 1 || nFldCount != 1 || !pFields)
+						break;
+
+					MYSQL_ROW pRow = res.FetchRow();
+
+					if(!pRow || !*pRow || !**pRow)
+						break;
+
+					if(!val.FromField(pFields[0], pRow[0]))
+						break;
+
+					nNextAIVal = val;
+				}
+				while(false);
+			}
+			
+			Timestamp2String(m_nLastLogTimestamp, szTs, sizeof(szTs));
+
+			if(nNextAIVal)
+			{
+				TRACE("Flush: Transition valid -> invalid date detected! The last Timestamp in Table `%s` was '%s+00:00'. Subsequent logs will be written to table `%s` starting with `id` %llu.\n", m_dlp.szLogsTable, szTs, m_dlp.szLogsTableBD, nNextAIVal);
+				m_lf.Warning("Flush: Transition valid -> invalid date detected! The last Timestamp in Table `%s` was '%s+00:00'. Subsequent logs will be written to table `%s` starting with `id` %llu.\n", m_dlp.szLogsTable, szTs, m_dlp.szLogsTableBD, nNextAIVal);
+			}
+			else
+			{
+				TRACE("Flush: Transition valid -> invalid date detected! The last Timestamp in Table `%s` was '%s+00:00'. Subsequent logs will be written to table `%s`.\n", m_dlp.szLogsTable, szTs, m_dlp.szLogsTableBD);
+				m_lf.Warning("Flush: Transition valid -> invalid date detected! The last Timestamp in Table `%s` was '%s+00:00'. Subsequent logs will be written to table `%s`.\n", m_dlp.szLogsTable, szTs, m_dlp.szLogsTableBD);
+			}
+		}
+		else
+		{
+			bool bError;
+			char szTs[64];
+			unsigned long long nLastID = 0;
+			sSql = formatString("select max(`id`) from `%s`.`%s`;", m_dlp.szDBName, m_dlp.szLogsTableBD);
+			CMySqlResult res = db.Query(sSql.c_str());
+
+			if(!(bError = res.error()))
+			{
+				CMySqlVar val;
+				my_ulonglong nRowCount		= res.RowCount();
+				unsigned int nFldCount		= res.FieldCount();
+				const MYSQL_FIELD *pFields	= res.FetchFields();
+
+				do
+				{
+					if(nRowCount != 1 || nFldCount != 1 || !pFields)
+						break;
+
+					MYSQL_ROW pRow = res.FetchRow();
+
+					if(!pRow || !*pRow || !**pRow)
+						break;
+
+					if(!val.FromField(pFields[0], pRow[0]))
+						break;
+
+					nLastID = val;
+				}
+				while(false);
+			}
+			
+			Timestamp2String(nFirstGoodTimestamp, szTs, sizeof(szTs));
+
+			if(nLastID)
+			{
+				TRACE("Flush: Transition invalid -> valid date detected! The last `id` in table `%s` was %llu. Subsequent logs will be written to table `%s` beginning with timestamp '%s+00:00'.\n", m_dlp.szLogsTableBD, nLastID, m_dlp.szLogsTable, szTs);
+				m_lf.Warning("Flush: Transition invalid -> valid date detected! The last `id` in table `%s` was %llu. Subsequent logs will be written to table `%s` beginning with timestamp '%s+00:00'.\n", m_dlp.szLogsTableBD, nLastID, m_dlp.szLogsTable, szTs);
+			}
+			else
+			{
+				TRACE("Flush: Transition invalid -> valid date detected! Subsequent logs will be written to table `%s` beginning with timestamp '%s+00:00'.\n", m_dlp.szLogsTable, szTs);
+				m_lf.Warning("Flush: Transition invalid -> valid date detected! Subsequent logs will be written to table `%s` beginning with timestamp '%s+00:00'.\n", m_dlp.szLogsTable, szTs);
+			}
+		}
+	}
+
+	if(nCountBadDate > 0)
+	{
+		std::string strSql, strLock;
+		strLock = formatString("lock tables `%s` write", m_dlp.szLogsTableBD);
+		strSql.reserve(130 + 111 * nCountBadDate);
+
+		auto itFirst = m_logsBD.begin();
+		const DL_LOG_ENTRY &rle0 = *itFirst;
+
+		if(m_dlp.bMinMax && _IS_INTERVAL_LOGTYPE(rle0.lt))
+			sSql = formatString("insert into `%s` (`tagid`, `tslog`, `value`, `valueMin`, `valueMax`) values (%lu, timestamp(from_unixtime(%lu)), %.20g, %.20g, %.20g)", m_dlp.szLogsTableBD, rle0.nTagID, rle0.nTimestamp, rle0.fValue, rle0.fMin, rle0.fMax);
+		else
+		{
+			if(!rle0.bNull)
+				sSql = formatString("insert into `%s` (`tagid`, `tslog`, `value`, `valueMin`, `valueMax`) values (%lu, timestamp(from_unixtime(%lu)), %.20g, NULL, NULL)", m_dlp.szLogsTableBD, rle0.nTagID, rle0.nTimestamp, rle0.fValue);
+			else
+				sSql = formatString("insert into `%s` (`tagid`, `tslog`, `value`, `valueMin`, `valueMax`) values (%lu, timestamp(from_unixtime(%lu)), NULL, NULL, NULL)", m_dlp.szLogsTableBD, rle0.nTagID, rle0.nTimestamp);
+		}
+		strSql = sSql;
+
+		for(++itFirst; itFirst < m_logsBD.end(); itFirst++)
+		{
+			const DL_LOG_ENTRY &rle = *itFirst;
+
+			if(m_dlp.bMinMax && _IS_INTERVAL_LOGTYPE(rle.lt))
+				sSql = formatString(",(%lu, timestamp(from_unixtime(%lu)), %.20g, %.20g, %.20g)", rle.nTagID, rle.nTimestamp, rle.fValue, rle.fMin, rle.fMax);
+			else
+			{
+				if(!rle.bNull)
+					sSql = formatString(",(%lu, timestamp(from_unixtime(%lu)), %.20g, NULL, NULL)", rle.nTagID, rle.nTimestamp, rle.fValue);
+				else
+					sSql = formatString(",(%lu, timestamp(from_unixtime(%lu)), NULL, NULL, NULL)", rle.nTagID, rle.nTimestamp);
+			}
+			strSql += sSql;
+		}
+
+		db.Query(strLock.c_str());
+		CMySqlResult res = db.Query(strSql.c_str());
+		bError = res.error();
+		db.Query(strUnlock.c_str());
+
+		if(bError)
+			m_lf.Error("CDataLogger::Flush: DB Error: %s\n", db.LastError().c_str());
+
+		m_logsBD.clear();
+	}
+
+	db.Query(strRestoreFgnKey.c_str());
+	db.Query(strRestoreTZ.c_str());
+
+	return !bError;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+unsigned long long CDataLogger::SizeGuardLastPassRead(void)
+{
+	char szPath[PATH_MAX];
+	sprintf(szPath, "%s/%s", m_szAppDir, _SIZE_GUARD_TIMESTAMP_FILE_NAME);
+	FILE *pf = fopen(szPath, "rb");
+	unsigned long long ts = 0;
+
+	if(pf)
+	{
+        if(fread(&ts, sizeof(ts), 1, pf) != 1)
+			ts = 0;
+		fclose(pf);
+	}
+
+	return ts;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CDataLogger::SizeGuardLastPassWrite(unsigned long long ts)
+{
+	char szPath[PATH_MAX];
+	sprintf(szPath, "%s/%s", m_szAppDir, _SIZE_GUARD_TIMESTAMP_FILE_NAME);
+	FILE *pf = fopen(szPath, "wb");
+
+	if(pf)
+	{
+		fwrite(&ts, sizeof(ts), 1, pf);
+		fclose(pf);
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+size_t CDataLogger::Timestamp2String(time_t t, char *pszBuffer, size_t nCbBuffer)
+{
+	const struct tm * ptm = gmtime(&t);
+	return strftime(pszBuffer, nCbBuffer, "%F %T", ptm);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+const char* CDataLogger::Ns2String(unsigned long long nNs, char *pszBuffer, size_t nCbBuffer)
+{
+	*pszBuffer = '\0';
+
+	if(nNs < 1000)
+		snprintf(pszBuffer, nCbBuffer, "%llu ns", nNs);
+	else if(nNs < 1000000)
+		snprintf(pszBuffer, nCbBuffer, "%.1f us", (double)nNs / 1000.0);
+	else
+		return Ms2String((double)nNs / 1000000.0, pszBuffer, nCbBuffer);
+
+	return pszBuffer;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+const char* CDataLogger::Ms2String(double fMs, char *pszBuffer, size_t nCbBuffer)
+{
+	*pszBuffer = '\0';
+
+	if(fMs < 1000.0)
+		snprintf(pszBuffer, nCbBuffer, "%.2f ms", fMs);
+	else if(fMs < 60000.0)
+		snprintf(pszBuffer, nCbBuffer, "%.2f sec", fMs / 1000.0);
+	else if(fMs < 3600000.0)
+		snprintf(pszBuffer, nCbBuffer, "%.2f min", fMs / 60000.0);
+	else
+		snprintf(pszBuffer, nCbBuffer, "%.2f h", fMs / 3600000.0);
+	
+	return pszBuffer;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+bool CDataLogger::DoSizeGuard(void)
+{
+	if(!m_bSGConfigured)
+		return true;
+
+	CMySqlDB db;
+	bool bError;
+	MYSQL_ROW pRow;
+	unsigned long long nElapsed;
+	char szT1[32], szT2[32], szMs[32];
+	CProcessClock pc;
+    CMySqlVar vMinTs, vCountDelete;
+    int64_t nMinTs, nTsCurMidnightUTC, nTsDeleteUpTo;
+	std::string sSql;
+
+	/////////////////////////////////////////////////////////////////////////
+
+	nTsCurMidnightUTC = _MIDNIGHT_TIMESTAMP_UTC(m_nSGCurPassUTC);
+
+	if(SizeGuardDayWorkDone(nTsCurMidnightUTC))
+		return true;
+	m_nSGLastPassUTC = nTsCurMidnightUTC;
+
+	if(SizeGuardLastPassRead() == (unsigned long long)(nTsCurMidnightUTC))
+		return true;
+	SizeGuardLastPassWrite(nTsCurMidnightUTC);
+
+	/////////////////////////////////////////////////////////////////////////
+
+	if(!db.Connect("localhost", m_dlp.szDBUser, m_dlp.szDBPass, NULL))
+	{
+		m_lf.Error("CDataLogger::DoSizeGuard(%d): - DB Error: %s\n", __LINE__, db.LastError().c_str());
+		return false;
+	}
+
+	sSql = formatString("select unix_timestamp(min(`tslog`)) from `%s`.`%s`;", m_dlp.szDBName, m_dlp.szLogsTable);
+	m_lf.Info("SG: \"%s\".\n", sSql.c_str());
+
+	pc.ClockTrigger();
+	CMySqlResult res = db.Query(sSql.c_str());
+	nElapsed = pc.ClockGetElapsed();
+	
+	bError = res.error();
+
+	if(bError)
+	{
+		m_lf.Error("CDataLogger::DoSizeGuard(%d): \"%s\" - DB Error: %s\n", __LINE__, sSql.c_str(), db.LastError().c_str());
+		return false;
+	}
+
+	if(!(pRow = res.FetchRow()))
+	{
+		m_lf.Error("CDataLogger::DoSizeGuard(%d): Unexpected Error!\n", __LINE__);
+		return false;
+	}
+
+	my_ulonglong nRowCount		= res.RowCount();
+	unsigned int nFldCount		= res.FieldCount();
+	const MYSQL_FIELD *pFields	= res.FetchFields();
+
+	if(nRowCount != 1 || nFldCount != 1)
+	{
+		m_lf.Error("CDataLogger::DoSizeGuard(%d): Unexpected Error!\n", __LINE__);
+		return false;
+	}
+
+	if(!vMinTs.FromField(pFields[0], pRow[0]))
+	{
+		m_lf.Error("CDataLogger::DoSizeGuard(%d): Unexpected Error!\n", __LINE__);
+		return false;
+	}
+
+	nMinTs = vMinTs;
+
+	m_lf.Info("SG: operation completed in %s. Min. timestamp: %ju\n", Ns2String(nElapsed, szMs, sizeof(szMs)), nMinTs);
+
+	/////////////////////////////////////////////////////////////////////////
+	// process max. size
+
+	if(m_bSGHasSizeLimitPrerequisites && m_dlp.nMaxSize)
+	{
+		unsigned long long nFileSize		= TableFileSize(m_dlp.szLogsTable);
+		unsigned long long nGuardThreshold	= m_dlp.nMaxSize * (100 - _FILE_SIZE_DELETE_MARGIN_PERCENT) / 100;
+
+		m_lf.Info("SG: File size: %s.\n", strFormatByteSize(nFileSize, 1).c_str());
+
+		if(nFileSize > nGuardThreshold)
+		{
+			Timestamp2String(nTsCurMidnightUTC - _SECONDS_PER_DAY, szT1, sizeof(szT1));
+			Timestamp2String(nTsCurMidnightUTC - 1, szT2, sizeof(szT2));
+			sSql = formatString("select count(*) from `%s`.`%s` where `tslog` between '%s' and '%s';", m_dlp.szDBName, m_dlp.szLogsTable, szT1, szT2);
+			m_lf.Info("SG: \"%s\"\n", sSql.c_str());
+
+			pc.ClockTrigger();
+			CMySqlResult res = db.Query(sSql.c_str());
+			bError = res.error();
+
+			if(bError)
+			{
+				m_lf.Error("CDataLogger::DoSizeGuard(%d): \"%s\" - DB Error: %s\n", __LINE__, sSql.c_str(), db.LastError().c_str());
+				return false;
+			}
+			else
+			{
+				nElapsed = pc.ClockGetElapsed();
+
+				nRowCount	= res.RowCount();
+				nFldCount	= res.FieldCount();
+				pFields		= res.FetchFields();
+				pRow		= res.FetchRow();
+
+				if(pRow && pFields && nRowCount == 1 && nFldCount == 1 && vCountDelete.FromField(pFields[0], pRow[0]))
+				{
+					unsigned long long nCountDelete = (uint64_t)vCountDelete;
+					m_lf.Info("SG: operation completed in %s. Estimated number of rows to delete: %llu.\n", Ns2String(nElapsed, szMs, sizeof(szMs)), nCountDelete);
+
+					if(nCountDelete > 0)
+					{
+#if _USE_MODIFIED_INDEX
+						sSql = formatString("delete from `%s`.`%s` limit %llu;", m_dlp.szDBName, m_dlp.szLogsTable, nCountDelete);
+#else	//	_USE_MODIFIED_INDEX
+						sSql = formatString("delete from `%s`.`%s` where `tslog` <= (select `tslog` from (select `tslog` from `demo`.`logs` order by `tslog` asc limit %llu, 1) x);", m_dlp.szDBName, m_dlp.szLogsTable, nCountDelete - 1);
+#endif	//	_USE_MODIFIED_INDEX
+						m_lf.Info("SG: triggered on size limit: \"%s\".\n", sSql.c_str());
+
+						pc.ClockTrigger();
+						if(db.Query(sSql.c_str()).error())
+						{
+							m_lf.Error("CDataLogger::DoSizeGuard(%d): \"%s\" - DB Error: %s\n", __LINE__, sSql.c_str(), db.LastError().c_str());
+							return false;
+						}
+						else
+						{
+							nElapsed = pc.ClockGetElapsed();
+							m_lf.Info("SG: operation completed in %s.\n", Ns2String(nElapsed, szMs, sizeof(szMs)));
+						}
+					}
+				}
+				else
+				{
+					m_lf.Error("CDataLogger::DoSizeGuard(%d): Unexpected Error!\n", __LINE__);
+					return false;
+				}
+			}
+		}
+	}
+
+	/////////////////////////////////////////////////////////////////////////
+	// process max. age entries
+
+	if(m_dlp.nMaxAge)
+	{
+		nTsDeleteUpTo = nTsCurMidnightUTC - m_dlp.nMaxAge * _SECONDS_PER_DAY;
+
+		if(nMinTs <= nTsDeleteUpTo)
+		{
+			Timestamp2String(nTsDeleteUpTo, szT1, sizeof(szT1));
+			sSql = formatString("delete from `%s`.`%s` where `tslog` < '%s';", m_dlp.szDBName, m_dlp.szLogsTable, szT1);
+			m_lf.Info("SG: triggered on age limit: \"%s\".\n", sSql.c_str());
+
+			pc.ClockTrigger();
+			CMySqlResult res = db.Query(sSql.c_str());
+			nElapsed = pc.ClockGetElapsed();
+			bError = res.error();
+
+			if(bError)
+			{
+				m_lf.Error("CDataLogger::DoSizeGuard(%d): DB Error: %s\n", __LINE__, db.LastError().c_str());
+				return false;
+			}
+			else
+			{
+				m_lf.Info("SG: operation completed in %s.\n", Ns2String(nElapsed, szMs, sizeof(szMs)));
+			}
+		}
+	}
+
+	return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+// size guard worker thread
+
+void* CDataLogger::SizeGuardWorker(void* pParam)
+{
+	unsigned long long nElapsed;
+	char szMs[32];
+	CProcessClock pc;
+	CDataLogger *pThis = reinterpret_cast<CDataLogger*>(pParam);
+
+	::pthread_mutex_lock(&pThis->m_condmtx1);
+	::pthread_cond_signal(&pThis->m_cond1);
+
+	while(true)
+	{
+		pThis->m_bSGInProgress = false;
+		::pthread_cond_wait(&pThis->m_cond1, &pThis->m_condmtx1);
+		pThis->m_bSGInProgress = true;
+		::pthread_mutex_unlock(&pThis->m_condmtx1);
+		TRACE("Size guard start.\n");
+		pThis->Lock();
+		pc.ClockTrigger();
+		pThis->DoSizeGuard();
+		nElapsed = pc.ClockGetElapsed();
+		pThis->Unlock();
+        pThis->m_lf.Info("SG: finished in %s.\n", Ns2String(nElapsed, szMs, sizeof(szMs)));
+		TRACE("Size guard end - [%s].\n", szMs);
+		::pthread_yield();
+		::pthread_mutex_lock(&pThis->m_condmtx1);
+	}
+
+	return NULL;
+}

+ 235 - 0
datalogger/datalogger.h

@@ -0,0 +1,235 @@
+// datalogger.h :
+//
+
+#if !defined(AGD_DATALOGGER_H__21BAEB7B_E478_4ED1_B9F6_FDBCE620C55B__INCLUDED_)
+#define AGD_DATALOGGER_H__21BAEB7B_E478_4ED1_B9F6_FDBCE620C55B__INCLUDED_
+
+#include <limits.h>
+#include <vector>
+#include <pthread.h>
+#ifndef _LIBBUILD
+#include <gfa/svc/common/mysqlwrap.h>
+#include <gfa/svc/common/logfile.h>
+#else	//	_LIBBUILD
+#include "common/mysqlwrap.h"
+#include "common/logfile.h"
+#endif	//	_LIBBUILD
+
+/////////////////////////////////////////////////////////////////////////////
+// datalogger.h - Declarations:
+
+#define _DL_DATABASE_ENGINE_INNODB		1
+#define _DL_DATABASE_ENGINE_MYISAM		2
+
+/////////////////////////////////////////////////////////////////////////////
+
+#define _DL_USE_NEW_TABLES_LAYOUT		1
+#define _DL_DATABSE_ENGINE				_DL_DATABASE_ENGINE_INNODB
+
+/////////////////////////////////////////////////////////////////////////////
+
+#if _DL_DATABSE_ENGINE == _DL_DATABASE_ENGINE_INNODB
+#define _DL_DATABSE_ENGINE_NAME			"InnoDB"
+#define _DL_MAX_VARPATH_LENGTH			767
+#elif _DL_DATABSE_ENGINE == _DL_DATABASE_ENGINE_MYISAM
+#define _DL_DATABSE_ENGINE_NAME			"MyISAM"
+#define _DL_MAX_VARPATH_LENGTH			1000
+#else	//	_DL_DATABSE_ENGINE
+#error Invalid database engine!
+#endif	//	_DL_DATABSE_ENGINE
+
+/////////////////////////////////////////////////////////////////////////////
+
+#define _DL_NANOSECS_PER_SEC			1000000000
+#define _DL_MAX_DB_NAME_LENGTH			64
+#define _DL_MAX_DB_USER_LENGTH			64
+#define _DL_MAX_DB_PASS_LENGTH			64
+#define _DL_MAX_TABLE_NAME_LENGTH		64
+
+#define _SECONDS_PER_DAY				86400
+#define _MIDNIGHT_TIMESTAMP_UTC(t)		((t) / _SECONDS_PER_DAY * _SECONDS_PER_DAY)
+
+typedef enum _LogTypes
+{
+	LT_NoLog,
+	LT_IntervalConditional,
+	LT_IntervalUnconditional,
+	LT_ValueChangeConditional,
+	LT_ValueChangeUnconditional,
+	LT_IntervalConditionalRem,
+	LT_IntervalUnconditionalRem,
+	LT_ValueChangeConditionalRem,
+	LT_ValueChangeUnconditionalRem
+}LogTypes, *LPLogTypes;
+
+#define _IS_VALID_LOGTYPE(lt)			(((lt) > LT_NoLog) && ((lt) <= LT_ValueChangeUnconditionalRem))
+#define _IS_VALUE_CHANGE_LOGTYPE(lt)	(((lt) == LT_ValueChangeConditional) || ((lt) == LT_ValueChangeUnconditional) || ((lt) == LT_ValueChangeConditionalRem) || ((lt) == LT_ValueChangeUnconditionalRem))
+#define _IS_INTERVAL_LOGTYPE(lt)		(((lt) == LT_IntervalConditional) || ((lt) == LT_IntervalUnconditional) || ((lt) == LT_IntervalConditionalRem) || ((lt) == LT_IntervalUnconditionalRem))
+#define _IS_CONDITIONAL_LOGTYPE(lt)		(((lt) == LT_ValueChangeConditional) || ((lt) == LT_IntervalConditional) || ((lt) == LT_ValueChangeConditionalRem) || ((lt) == LT_IntervalConditionalRem))
+#define _IS_UNCONDITIONAL_LOGTYPE(lt)	(((lt) == LT_IntervalUnconditional) || ((lt) == LT_ValueChangeUnconditional) || ((lt) == LT_IntervalUnconditionalRem) || ((lt) == LT_ValueChangeUnconditionalRem))
+#define _IS_DB_PERSISTENT_LOGTYPE(lt)	(((lt) >= LT_IntervalConditionalRem) && ((lt) <= LT_ValueChangeUnconditionalRem))
+
+typedef struct _DLPARAMS
+{
+	char szDBName[_DL_MAX_DB_NAME_LENGTH];
+	char szDBUser[_DL_MAX_DB_USER_LENGTH];
+	char szDBPass[_DL_MAX_DB_PASS_LENGTH];
+	char szTagsTable[_DL_MAX_TABLE_NAME_LENGTH];
+	char szLogsTable[_DL_MAX_TABLE_NAME_LENGTH];
+	char szLogsTableBD[_DL_MAX_TABLE_NAME_LENGTH]; // bad date logs table
+	const char *pszBaseDir;
+	unsigned int nIntvSample;		// sample interval
+	unsigned int nIntvLog;			// log interval
+	unsigned int nIntvFlush;		// flush interval
+	unsigned int nMaxAge;			// Max age of Logs entries in days
+	unsigned long long nMaxSize;	// max. Logs table size in Byte
+	bool bMinMax;					// log min max values as well?
+}DLPARAMS, *LPDLPARAMS;
+typedef const DLPARAMS *LPCDLPARAMS;
+
+
+typedef struct _DL_LOG_ENTRY
+{
+	unsigned long nTagID;
+	time_t nTimestamp;
+	double fValue;
+	double fMin;
+	double fMax;
+	int nIndex;
+	LogTypes lt;
+	bool bNull;
+}DL_LOG_ENTRY, *LPDL_LOG_ENTRY;
+typedef const DL_LOG_ENTRY *LPCDL_LOG_ENTRY;
+
+typedef struct _MYSQL_GLOBAL_VARS
+{
+	bool bLogsTblIsInnoDB;
+	bool bInnoDbFilePerTable;
+	bool bInnoDbIsBarracuda;
+	bool bInnoDbIsStrictMode;
+	char szInnoDbFileFormat[64];
+	char szDataDir[PATH_MAX];
+}MYSQL_GLOBAL_VARS, *LPMYSQL_GLOBAL_VARS;
+typedef const MYSQL_GLOBAL_VARS *LPCMYSQL_GLOBAL_VARS;
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CDataLogger
+{
+public:
+	CDataLogger(LPCDLPARAMS pdlp, CLogfile &rlf);
+	virtual ~CDataLogger(void);
+	
+	bool InitDatabase(bool bEnforceCreate = false);
+	void Release(void);
+	unsigned long GetTagID(const char *pszVarPath, int nDataType, int nLogType);
+	bool Log(unsigned long nTagID, double fValue, double fMin, double fMax, time_t nTimestamp, int nIndex, LogTypes lt, bool bNull = false, bool bNoBadDateCheck = false);
+	bool Flush(time_t nTimestamp);
+	bool TableFileExists(const char *pszTableName);
+	int64_t TableFileSize(const char *pszTableName);
+	void SizeGuardTrigger(time_t ts);
+
+	time_t LastLogTimestamp(void){
+		return m_nLastLogTimestamp;}
+
+	bool SizeGuardDayWorkDone(time_t ts){
+		return m_nSGLastPassUTC == (unsigned long long)_MIDNIGHT_TIMESTAMP_UTC(ts);}
+
+	bool Lock(void){
+		return !::pthread_mutex_lock(&m_mtx);}
+
+	bool TryLock(void){
+		return !::pthread_mutex_trylock(&m_mtx);}
+
+	bool Unlock(void){
+		return !::pthread_mutex_unlock(&m_mtx);}
+
+private:
+	bool CreateDatabase(CMySqlDB &rdb, bool bEnforceCreate = false);
+	bool CreateTagsTable(CMySqlDB &rdb);
+	bool AlterTagsTable(CMySqlDB &rdb);
+	bool CreateLogsTable(CMySqlDB &rdb);
+	bool CreateLogsBDTable(CMySqlDB &rdb);
+
+	bool CheckTable(CMySqlDB &rdb, const char *pszTableName, bool &bExists, bool &bUseResortTable, bool &bIsInnoDB);
+//	bool QueryTableSizes(CMySqlDB &rdb, const char *pszTableName);
+	bool DoSizeGuard(void);
+	bool ReadGlobalOptions(CMySqlDB &rdb);
+	bool QueryServerVariable(CMySqlDB &rdb, const char *pszVarname, CMySqlVar &val);
+//	bool QueryStatusVariable(CMySqlDB &rdb, const char *pszVarname, CMySqlVar &val);
+	static void* SizeGuardWorker(void* pParam);
+	unsigned long long SizeGuardLastPassRead(void);
+	void SizeGuardLastPassWrite(unsigned long long ts);
+	static size_t Timestamp2String(time_t t, char *pszBuffer, size_t nCbBuffer);
+	static const char* Ns2String(unsigned long long nNs, char *pszBuffer, size_t nCbBuffer);
+	static const char* Ms2String(double fMs, char *pszBuffer, size_t nCbBuffer);
+	time_t GetLastLogTimestamp(CMySqlDB &rdb);
+	
+private:
+	CLogfile &m_lf;
+	DLPARAMS m_dlp;
+	MYSQL_GLOBAL_VARS m_gv;
+    char m_szAppDir[PATH_MAX];
+	std::vector<DL_LOG_ENTRY> m_logs;
+	std::vector<DL_LOG_ENTRY> m_logsBD;
+    pthread_t m_tidSGThread;
+    bool m_bSGHasSizeLimitPrerequisites;
+    bool m_bSGInProgress;
+    bool m_bSGConfigured;
+    unsigned long long m_nSGCurPassUTC;
+    unsigned long long m_nSGLastPassUTC;
+   	pthread_mutex_t m_mtx;
+	pthread_mutexattr_t m_mutexAttr;
+   	pthread_mutex_t m_condmtx1;
+	pthread_cond_t m_cond1;
+	time_t m_nLastLogTimestamp;
+	bool m_bBadDateLogsDetected;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CDataLoggerClock
+{
+public:
+	CDataLoggerClock(unsigned long long nSampleTimeNs);
+	virtual ~CDataLoggerClock(void);
+
+	static unsigned long long GetNanoTick(struct timespec *pts = NULL);
+
+	inline static unsigned long long GetMicroTick(struct timespec *pts = NULL){
+		return GetNanoTick(pts) / 1000;}
+
+	inline static unsigned long long GetMilliTick(struct timespec *pts = NULL){
+		return GetNanoTick(pts) / 1000000;}
+
+	inline static unsigned long long Timespec2NanoSec(const struct timespec *pts){
+		return (unsigned long long)pts->tv_sec * _DL_NANOSECS_PER_SEC + (unsigned long long)pts-> tv_nsec;}
+
+	inline static void NanoSec2Timespec(unsigned long long n, struct timespec *pts){
+		if(pts){
+			pts->tv_sec	 = n / _DL_NANOSECS_PER_SEC;
+			pts->tv_nsec = n % _DL_NANOSECS_PER_SEC;
+		}}
+
+	inline static long long CompareTimespec(const struct timespec *pts1, const struct timespec *pts2){
+		unsigned long long n1 = Timespec2NanoSec(pts1);
+		unsigned long long n2 = Timespec2NanoSec(pts2);
+		return (long long)(n1 - n2);}
+
+	bool Sleep(bool &bTimerUnderrun, bool fAvoidCatchUpRaceOnTimerUnderrun);
+
+private:
+	inline unsigned long long IncTime(struct timespec *pts, long long ns) const {
+		unsigned long long n = Timespec2NanoSec(pts);
+		n += ns;
+		NanoSec2Timespec(n, pts);
+		return n;}
+
+private:
+	struct timespec m_tsDueTime;
+	bool m_fSleepStarted;
+	const long long m_nSampleTimeNs;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	!defined(AGD_DATALOGGER_H__21BAEB7B_E478_4ED1_B9F6_FDBCE620C55B__INCLUDED_)

+ 61 - 0
datalogger/datalogger.pro

@@ -0,0 +1,61 @@
+TEMPLATE = lib
+CONFIG   += c++11
+CONFIG   -= qt
+CONFIG   += staticlib
+
+QMAKE_CXXFLAGS_DEBUG += -Wno-unused-parameter -Wno-unused-but-set-variable -pthread
+QMAKE_CXXFLAGS += -Wstrict-aliasing=0 -pthread
+QMAKE_LIBS += -lm -lstdc++ -pthread -lgfaipcd -l:libcommon.a -lmysqlclient
+QMAKE_LIBDIR += $$OUT_PWD/../common $$[QT_SYSROOT]/usr/lib/gfa
+QMAKE_RPATHDIR += /usr/lib/gfa
+INCLUDEPATH += ../  ../common $$(GEBGFADEV)
+
+QMAKE_CXXFLAGS += -D_DATALOGGER -D_LIBBUILD -Wno-format-overflow
+QMAKE_CFLAGS += -D_DATALOGGER -D_LIBBUILD -Wno-format-overflow
+
+_LIB_PATH = $$[QT_SYSROOT]/usr/lib/gfa/svc/
+_INC_PATH = $$[QT_SYSROOT]/usr/include/gfa/svc/datalogger/
+
+CONFIG(debug, debug|release) {
+	QMAKE_CXXFLAGS -= -Os
+	QMAKE_CFLAGS -= -Os
+    QMAKE_CXXFLAGS += -D_DEBUG
+    QMAKE_CFLAGS += -D_DEBUG
+	TARGET = dataloggerd
+	QMAKE_CLEAN += libdataloggerd.a
+}
+
+CONFIG(release, debug|release) {
+    QMAKE_CXXFLAGS += -Wno-unused-parameter -Wno-unused-but-set-variable
+    QMAKE_CFLAGS += -Wno-unused-parameter -Wno-unused-but-set-variable
+	TARGET = datalogger
+	QMAKE_CLEAN += libdatalogger.a
+}
+
+linux-buildroot-g++ {
+    QMAKE_CXXFLAGS += -D_TARGET_BUILD
+    QMAKE_CFLAGS += -D_TARGET_BUILD
+    QMAKE_CXXFLAGS += -DSITARA_BUILD
+    QMAKE_CFLAGS += -DSITARA_BUILD
+}
+
+SOURCES += logvar.cpp \
+    logbitvar.cpp \
+    datalogger.cpp \
+    dataloggerclock.cpp
+
+HEADERS += \
+    logvar.h \
+    datalogger.h
+
+includes.path = $$_INC_PATH
+includes.extra += -$(INSTALL_FILE) $$PWD/logvar.h $(INSTALL_ROOT)$$includes.path
+includes.extra += $$escape_expand(\\n\\t)-$(INSTALL_FILE) $$PWD/datalogger.h $(INSTALL_ROOT)$$includes.path
+includes.uninstall += -$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/logvar.h
+includes.uninstall += $$escape_expand(\\n\\t)-$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/datalogger.h
+INSTALLS += includes
+
+library.path = $$_LIB_PATH
+library.extra += -$(INSTALL_FILE) $(TARGET) $(INSTALL_ROOT)$$library.path/$(TARGET)
+library.uninstall += -$(DEL_FILE) $(INSTALL_ROOT)$$library.path/$(TARGET)
+INSTALLS += library

+ 63 - 0
datalogger/dataloggerclock.cpp

@@ -0,0 +1,63 @@
+//////////////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////////
+
+#include <errno.h>
+#include "datalogger.h"
+
+//////////////////////////////////////////////////////////////////////////////////
+
+CDataLoggerClock::CDataLoggerClock(unsigned long long nSampleTimeNs) : m_nSampleTimeNs(nSampleTimeNs)
+{
+	m_fSleepStarted = false;
+}
+
+CDataLoggerClock::~CDataLoggerClock(void)
+{
+}
+
+//////////////////////////////////////////////////////////////////////////////////
+
+unsigned long long CDataLoggerClock::GetNanoTick(struct timespec *pts)
+{
+	struct timespec ts;
+	if(!pts)
+		pts = &ts;
+	clock_gettime(CLOCK_MONOTONIC, pts);
+	return Timespec2NanoSec(pts);
+}
+
+bool CDataLoggerClock::Sleep(bool &bTimerUnderrun, bool fAvoidCatchUpRaceOnTimerUnderrun)
+{
+	int nRet;
+	unsigned long long dt;
+
+	if(!m_fSleepStarted)
+	{
+		clock_gettime(CLOCK_MONOTONIC, &m_tsDueTime);
+		m_fSleepStarted = true;
+		return true;
+	}
+
+	dt = IncTime(&m_tsDueTime, m_nSampleTimeNs);
+
+	if(fAvoidCatchUpRaceOnTimerUnderrun)
+	{
+		if(dt < GetNanoTick())
+		{
+			bTimerUnderrun = true;
+			clock_gettime(CLOCK_MONOTONIC, &m_tsDueTime);
+			return true;
+		}
+	}
+
+	if((nRet = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &m_tsDueTime, NULL)))
+	{
+		while(nRet && (errno == EINTR))
+		{
+			errno = 0;
+			nRet = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &m_tsDueTime, NULL);
+		}
+	}
+
+	return !nRet;
+}

+ 229 - 0
datalogger/logbitvar.cpp

@@ -0,0 +1,229 @@
+#include <limits.h>
+#include <limits>
+#include "logvar.h"
+#include "debug.h"
+
+#define GET_BOOL_VAL(p, m)				(!!(*p & m))
+#define SET_BIT(p, m)					(*p |= m)
+#define CLR_BIT(p, m)					(*p &= ~m)
+#define STORE_BIT(p, m, b)				(b) ? SET_BIT(p, m) : CLR_BIT(p, m)
+
+/////////////////////////////////////////////////////////////////////////////
+
+CLogBitVariable::CLogBitVariable(LogTypes lt, void *pData, size_t nOffset, unsigned int nBitNr, bool *pbLogCond, HSHM hShm, const char *pszName, CLoggable *pParent)
+                            : m_lt(lt), m_pbLogCond(pbLogCond), m_bOldLogCond(false), m_name(pszName), m_pszPath(NULL), m_fLastSample(0), m_fSamples(0), m_hShm(hShm),
+                            m_pParent(pParent), m_nUpdates(0), m_nTagID(0), m_nSampleCount(0), m_bCvIsBit(false), m_bIsDbPersistant(_IS_DB_PERSISTENT_LOGTYPE(lt))
+{
+	if(!pData || !hShm || nBitNr > 7)
+	{
+		ASSERT(false);
+	}
+	
+	m_bIsCondLog		= _IS_CONDITIONAL_LOGTYPE(m_lt);
+	m_bIsIntervalLog	= _IS_INTERVAL_LOGTYPE(m_lt);
+
+	if(m_bIsCondLog && !m_pbLogCond)
+	{
+		ASSERT(false);
+	}
+
+    m_fMin				= std::numeric_limits<double>::infinity();
+    m_fMax				= -std::numeric_limits<double>::infinity();
+    m_pnByte			= (uint8_t*)pData + nOffset;
+    m_nMask				= 0x01 << nBitNr;
+    Lock();
+    m_cacheVal			= GET_BOOL_VAL(m_pnByte, m_nMask);
+    Unlock();
+}
+
+CLogBitVariable::CLogBitVariable(LogTypes lt, void *pData, size_t nOffset, unsigned int nBitNr, size_t nCvOffset, unsigned int nCvBitNr, HSHM hShm, const char *pszName, CLoggable *pParent)
+							: m_lt(lt), m_pbLogCond(NULL), m_bOldLogCond(false), m_name(pszName), m_pszPath(NULL), m_fLastSample(0), m_fSamples(0), m_pParent(pParent), m_nUpdates(0),
+							m_nTagID(0), m_nSampleCount(0), m_bCvIsBit(true), m_bIsDbPersistant(_IS_DB_PERSISTENT_LOGTYPE(lt))
+{
+	if(!pData || !hShm || nBitNr > 7 || nCvBitNr > 7)
+	{
+		ASSERT(false);
+	}
+
+	m_bIsCondLog		= _IS_CONDITIONAL_LOGTYPE(m_lt);
+	m_bIsIntervalLog	= _IS_INTERVAL_LOGTYPE(m_lt);
+    m_hShm				= hShm;
+    m_fMin				= std::numeric_limits<double>::infinity();
+    m_fMax				= -std::numeric_limits<double>::infinity();
+    m_pnByte			= (uint8_t*)pData + nOffset;
+    m_nMask				= 0x01 << nBitNr;
+    m_pnCvByte			= (uint8_t*)pData + nCvOffset;
+    m_nCvMask			= 0x01 << nCvBitNr;
+    Lock();
+    m_cacheVal			= GET_BOOL_VAL(m_pnByte, m_nMask);
+    Unlock();
+}
+
+CLogBitVariable::~CLogBitVariable(void)
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CLogBitVariable::InitPath(CLoggable *pParent, const char *pszMemberName)
+{
+	if(!pszMemberName)
+		pszMemberName = "";
+
+	if(pParent)
+	{
+		m_path = pParent->GetPath();
+		m_path += "/";
+		m_path += pszMemberName;
+	}
+	else
+	{
+		m_path = pszMemberName;
+	}
+
+	m_pszPath = m_path.c_str();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CLogBitVariable::InitTagID(CDataLogger &rdl)
+{
+	if((m_nTagID = rdl.GetTagID(m_pszPath, (int)CLogVariable::VT_bool, (int)m_lt)) == ULONG_MAX)
+	{
+        ASSERT(false);
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+bool CLogBitVariable::CanLog(bool fLock)
+{
+	if(!m_bIsCondLog)
+		return true;
+
+	bool bRet;
+
+	if(fLock)
+		Lock();
+
+	if(m_bCvIsBit)
+		bRet = GET_BOOL_VAL(m_pnCvByte, m_nCvMask);
+	else
+		bRet = *m_pbLogCond;
+
+	if(fLock)
+		Unlock();
+	
+	return bRet;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CLogBitVariable::Sample(void)
+{
+	if(!m_bIsIntervalLog)
+		return;
+
+	Lock();
+	if(CanLog(false))
+	{
+		m_fLastSample = GET_BOOL_VAL(m_pnByte, m_nMask) ? 1.0 : 0.0;
+		if(m_fMin > m_fLastSample)
+			m_fMin = m_fLastSample;
+		if(m_fMax < m_fLastSample)
+			m_fMax = m_fLastSample;
+		m_fSamples += m_fLastSample;
+		m_nSampleCount++;
+	}
+	Unlock();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CLogBitVariable::LogInterval(time_t nTimestamp, CDataLogger &rdl)
+{
+	if(!m_bIsIntervalLog)
+	{
+		m_fSamples		= 0;
+		m_nSampleCount	= 0;
+		return;
+	}
+
+	if(!CanLog(true))
+	{
+		m_fSamples		= 0;
+		m_nSampleCount	= 0;
+		return;
+	}
+
+	if(m_nSampleCount > 0)
+	{
+		if(!m_bIsDbPersistant)
+		{
+			if(m_nSampleCount > 1)
+				m_fSamples /= (double)m_nSampleCount;
+			rdl.Log(m_nTagID, m_fSamples, m_fMin, m_fMax, nTimestamp, -1, m_lt);
+		}
+		else
+		{
+			rdl.Log(m_nTagID, m_fLastSample, m_fMin, m_fMax, nTimestamp, -1, m_lt);
+		}
+	}
+
+	m_fSamples		= 0;
+    m_fMin			= std::numeric_limits<double>::infinity();
+    m_fMax			= -std::numeric_limits<double>::infinity();
+	m_nSampleCount	= 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CLogBitVariable::LogValueChanged(time_t nTimestamp, CDataLogger &rdl, bool bStartup, bool bWriteNull)
+{
+	if(m_bIsIntervalLog)
+		return;
+	
+	if(bWriteNull) // force write of a NULL value and exit
+	{
+		rdl.Log(m_nTagID, 0.0, 0.0, 0.0, nTimestamp, -1, m_lt, true, bStartup);
+		return;
+	}
+
+    bool val, bCondition, bConditionChanged;
+
+	Lock();
+	if((bCondition = CanLog(false)))
+		val = GET_BOOL_VAL(m_pnByte, m_nMask);
+	Unlock();
+
+	bConditionChanged = (m_bOldLogCond != bCondition) || bStartup;
+	m_bOldLogCond = bCondition;
+
+	if(!bCondition && !bConditionChanged)
+		return;
+
+	if(bCondition && ((m_cacheVal != val) || bConditionChanged))	// log value, if either the value has changed or a condition transition from 0 to 1 has occured
+	{
+		m_cacheVal = val;
+		double fVal = m_cacheVal ? 1.0 : 0.0;
+		rdl.Log(m_nTagID, fVal, 0.0, 0.0, nTimestamp, -1, m_lt);
+	}
+	else if(!bCondition && bConditionChanged)	// log a NULL value , if a condition transition from 1 to 0 has occured
+	{
+		rdl.Log(m_nTagID, 0.0, 0.0, 0.0, nTimestamp, -1, m_lt, true);
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CLogBitVariable::Lock(void)
+{
+	::GfaIpcLockSHM(m_hShm);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CLogBitVariable::Unlock(void)
+{
+	::GfaIpcUnlockSHM(m_hShm);
+}

+ 555 - 0
datalogger/logvar.cpp

@@ -0,0 +1,555 @@
+#include <limits.h>
+#include <limits>
+#include "logvar.h"
+#include "debug.h"
+
+#define GET_BOOL_VAL(p, m)				(!!(*p & m))
+#define SET_BIT(p, m)					(*p |= m)
+#define CLR_BIT(p, m)					(*p &= ~m)
+#define STORE_BIT(p, m, b)				(b) ? SET_BIT(p, m) : CLR_BIT(p, m)
+
+/////////////////////////////////////////////////////////////////////////////
+
+CLogVariable::CLogVariable(LogTypes lt, void *pData, bool *pbLogCond, const std::type_info &rti, HSHM hShm, const char *pszName, int nIndex, CLoggable *pParent)
+							: m_lt(lt), m_pbLogCond(pbLogCond), m_bOldLogCond(false), m_name(pszName), m_pszPath(NULL), m_nIndex(nIndex), m_vt(VT_Invalid), m_data({NULL}),
+							m_fLastSample(0), m_fSamples(0), m_pParent(pParent), m_nUpdates(0), m_nTagID(0), m_nSampleCount(0), m_pnCvByte(NULL), m_nCvMask(0), m_bCvIsBit(false), m_bIsDbPersistant(_IS_DB_PERSISTENT_LOGTYPE(lt))
+{
+	if(!pData || !hShm)
+	{
+		ASSERT(false);
+	}
+
+	m_bIsCondLog		= _IS_CONDITIONAL_LOGTYPE(m_lt);
+	m_bIsIntervalLog	= _IS_INTERVAL_LOGTYPE(m_lt);
+
+	if(m_bIsCondLog && !m_pbLogCond)
+	{
+		ASSERT(false);
+	}
+
+    m_data.pVoid		= pData;
+    m_hShm				= hShm;
+    m_fMin				= std::numeric_limits<double>::infinity();
+    m_fMax				= -std::numeric_limits<double>::infinity();
+
+    if(rti == typeid(bool))
+    {
+    	m_vt = VT_bool;
+        m_cache.boolVal = *m_data.pBool;
+    }
+    else if(rti == typeid(char))
+    {
+#ifdef __CHAR_UNSIGNED__
+    	m_vt = VT_UI1;
+        m_cache.UI1Val = *m_data.pUI1;
+#else
+        m_vt = VT_I1;
+        m_cache.I1Val = *m_data.pI1;
+#endif
+    }
+    else if(rti == typeid(signed char))
+    {
+        m_vt = VT_I1;
+        m_cache.I1Val = *m_data.pI1;
+    }
+    else if(rti == typeid(unsigned char))
+    {
+    	m_vt = VT_UI1;
+        m_cache.UI1Val = *m_data.pUI1;
+    }
+    else if(rti == typeid(short))
+    {
+    	m_vt = VT_I2;
+        m_cache.I2Val = *m_data.pI2;
+    }
+    else if(rti == typeid(unsigned short))
+    {
+    	m_vt = VT_UI2;
+        m_cache.UI2Val = *m_data.pUI2;
+    }
+    else if(rti == typeid(int))
+    {
+    	m_vt = VT_I4;
+        m_cache.I4Val = *m_data.pI4;
+    }
+    else if(rti == typeid(unsigned int))
+    {
+    	m_vt = VT_UI4;
+        m_cache.UI4Val = *m_data.pUI4;
+    }
+    else if(rti == typeid(long))
+    {
+    	if(sizeof(long) == sizeof(long long))
+    	{
+	    	m_vt = VT_I8;
+            m_cache.I8Val = *m_data.pI8;
+	    }
+    	else
+    	{
+	    	m_vt = VT_I4;
+	        m_cache.I4Val = *m_data.pI4;
+	    }
+    }
+    else if(rti == typeid(unsigned long))
+    {
+    	if(sizeof(unsigned long) == sizeof(unsigned long long))
+    	{
+	    	m_vt = VT_UI8;
+            m_cache.UI8Val = *m_data.pUI8;
+	    }
+    	else
+    	{
+	    	m_vt = VT_UI4;
+	        m_cache.UI4Val = *m_data.pUI4;
+	    }
+    }
+    else if(rti == typeid(long long))
+    {
+    	m_vt = VT_I8;
+		m_cache.I8Val = *m_data.pI8;
+    }
+    else if(rti == typeid(unsigned long long))
+    {
+    	m_vt = VT_UI8;
+		m_cache.UI8Val = *m_data.pUI8;
+    }
+    else if(rti == typeid(float))
+    {
+    	m_vt = VT_float;
+        m_cache.FloatVal = *m_data.pFloat;
+    }
+    else if(rti == typeid(double))
+    {
+    	m_vt = VT_double;
+        m_cache.DoubleVal = *m_data.pDouble;
+    }
+    else
+	{
+		ASSERT(false);
+	}
+}
+
+CLogVariable::CLogVariable(LogTypes lt, void *pData, void *pBase, size_t nCvOffset, unsigned int nCvBitNr, const std::type_info &rti, HSHM hShm, const char *pszName, int nIndex, CLoggable *pParent) :
+				m_lt(lt), m_bOldLogCond(false), m_name(pszName), m_pszPath(NULL), m_nIndex(nIndex), m_fLastSample(0), m_fSamples(0), m_pParent(pParent), m_nUpdates(0), m_nTagID(0), m_nSampleCount(0), m_bCvIsBit(true), m_bIsDbPersistant(_IS_DB_PERSISTENT_LOGTYPE(lt))
+{
+	if(!pData || !hShm || nCvBitNr > 7)
+	{
+		ASSERT(false);
+	}
+
+	m_bIsCondLog		= _IS_CONDITIONAL_LOGTYPE(m_lt);
+	m_bIsIntervalLog	= _IS_INTERVAL_LOGTYPE(m_lt);
+    m_data.pVoid		= pData;
+    m_hShm				= hShm;
+    m_fMin				= std::numeric_limits<double>::infinity();
+    m_fMax				= -std::numeric_limits<double>::infinity();
+    m_pnCvByte			= (uint8_t*)pBase + nCvOffset;
+    m_nCvMask			= 0x01 << nCvBitNr;
+
+    if(rti == typeid(bool))
+    {
+    	m_vt = VT_bool;
+        m_cache.boolVal = *m_data.pBool;
+    }
+    else if(rti == typeid(char))
+    {
+#ifdef __CHAR_UNSIGNED__
+    	m_vt = VT_UI1;
+        m_cache.UI1Val = *m_data.pUI1;
+#else
+        m_vt = VT_I1;
+        m_cache.I1Val = *m_data.pI1;
+#endif
+    }
+    else if(rti == typeid(signed char))
+    {
+        m_vt = VT_I1;
+        m_cache.I1Val = *m_data.pI1;
+    }
+    else if(rti == typeid(unsigned char))
+    {
+    	m_vt = VT_UI1;
+        m_cache.UI1Val = *m_data.pUI1;
+    }
+    else if(rti == typeid(short))
+    {
+    	m_vt = VT_I2;
+        m_cache.I2Val = *m_data.pI2;
+    }
+    else if(rti == typeid(unsigned short))
+    {
+    	m_vt = VT_UI2;
+        m_cache.UI2Val = *m_data.pUI2;
+    }
+    else if(rti == typeid(int))
+    {
+    	m_vt = VT_I4;
+        m_cache.I4Val = *m_data.pI4;
+    }
+    else if(rti == typeid(unsigned int))
+    {
+    	m_vt = VT_UI4;
+        m_cache.UI4Val = *m_data.pUI4;
+    }
+    else if(rti == typeid(long))
+    {
+    	if(sizeof(long) == sizeof(long long))
+    	{
+	    	m_vt = VT_I8;
+            m_cache.I8Val = *m_data.pI8;
+	    }
+    	else
+    	{
+	    	m_vt = VT_I4;
+	        m_cache.I4Val = *m_data.pI4;
+	    }
+    }
+    else if(rti == typeid(unsigned long))
+    {
+    	if(sizeof(unsigned long) == sizeof(unsigned long long))
+    	{
+	    	m_vt = VT_UI8;
+            m_cache.UI8Val = *m_data.pUI8;
+	    }
+    	else
+    	{
+	    	m_vt = VT_UI4;
+	        m_cache.UI4Val = *m_data.pUI4;
+	    }
+    }
+    else if(rti == typeid(long long))
+    {
+    	m_vt = VT_I8;
+		m_cache.I8Val = *m_data.pI8;
+    }
+    else if(rti == typeid(unsigned long long))
+    {
+    	m_vt = VT_UI8;
+		m_cache.UI8Val = *m_data.pUI8;
+    }
+    else if(rti == typeid(float))
+    {
+    	m_vt = VT_float;
+        m_cache.FloatVal = *m_data.pFloat;
+    }
+    else if(rti == typeid(double))
+    {
+    	m_vt = VT_double;
+        m_cache.DoubleVal = *m_data.pDouble;
+    }
+    else
+	{
+		ASSERT(false);
+	}
+}
+
+CLogVariable::~CLogVariable(void)
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CLogVariable::InitPath(CLoggable *pParent, const char *pszMemberName)
+{
+	if(!pszMemberName)
+		pszMemberName = "";
+
+	if(pParent)
+	{
+		m_path = pParent->GetPath();
+		m_path += "/";
+		m_path += pszMemberName;
+
+		if(m_nIndex >= 0)
+		{
+			char szIndex[32];
+			sprintf(szIndex, "[%d]", m_nIndex);
+			m_path += szIndex;
+		}
+	}
+	else
+	{
+		m_path = pszMemberName;
+	}
+
+	m_pszPath = m_path.c_str();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CLogVariable::InitTagID(CDataLogger &rdl)
+{
+	if((m_nTagID = rdl.GetTagID(m_pszPath, (int)m_vt, (int)m_lt)) == ULONG_MAX)
+	{
+		ASSERT(false);
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+bool CLogVariable::CanLog(bool fLock)
+{
+	if(!m_bIsCondLog)
+		return true;
+
+	bool bRet;
+
+	if(fLock)
+		Lock();
+
+	if(m_bCvIsBit)
+		bRet = GET_BOOL_VAL(m_pnCvByte, m_nCvMask);
+	else
+		bRet = *m_pbLogCond;
+
+	if(fLock)
+		Unlock();
+
+	return bRet;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CLogVariable::Sample(void)
+{
+	if(!m_bIsIntervalLog)
+		return;
+
+	if(!CanLog(true))
+		return;
+
+	Lock();
+	switch(m_vt)
+	{
+	case VT_bool:
+		m_fLastSample = (*m_data.pBool) ? 1.0 : 0.0;
+		break;
+	case VT_I1:
+		m_fLastSample = (double)*m_data.pI1;
+		break;
+	case VT_UI1:
+		m_fLastSample = (double)*m_data.pUI1;
+		break;
+	case VT_I2:
+		m_fLastSample = (double)*m_data.pI2;
+		break;
+	case VT_UI2:
+		m_fLastSample = (double)*m_data.pUI2;
+		break;
+	case VT_I4:
+		m_fLastSample = (double)*m_data.pI4;
+		break;
+	case VT_UI4:
+		m_fLastSample = (double)*m_data.pUI4;
+		break;
+	case VT_I8:
+		m_fLastSample = (double)*m_data.pI8;
+		break;
+	case VT_UI8:
+		m_fLastSample = (double)*m_data.pUI8;
+		break;
+	case VT_float:
+		m_fLastSample = (double)*m_data.pFloat;
+		break;
+	case VT_double:
+		m_fLastSample = *m_data.pDouble;
+		break;
+    default:
+        Unlock();
+        ASSERT(false);
+        return;
+	}
+
+	if(m_fMin > m_fLastSample)
+		m_fMin = m_fLastSample;
+	if(m_fMax < m_fLastSample)
+		m_fMax = m_fLastSample;
+	m_fSamples += m_fLastSample;
+	m_nSampleCount++;
+
+	Unlock();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CLogVariable::LogInterval(time_t nTimestamp, CDataLogger &rdl)
+{
+	if(!m_bIsIntervalLog)
+	{
+		m_fSamples		= 0;
+		m_nSampleCount	= 0;
+		return;
+	}
+
+	if(!CanLog(true))
+	{
+		m_fSamples		= 0;
+		m_nSampleCount	= 0;
+		return;
+	}
+
+	if(m_nSampleCount > 0)
+	{
+		if(!m_bIsDbPersistant)
+		{
+			if(m_nSampleCount > 1)
+				m_fSamples /= (double)m_nSampleCount;
+			rdl.Log(m_nTagID, m_fSamples, m_fMin, m_fMax, nTimestamp, m_nIndex, m_lt);
+		}
+		else
+		{
+			rdl.Log(m_nTagID, m_fLastSample, m_fMin, m_fMax, nTimestamp, m_nIndex, m_lt);
+		}
+	}
+
+	m_fSamples		= 0;
+    m_fMin			= std::numeric_limits<double>::infinity();
+    m_fMax			= -std::numeric_limits<double>::infinity();
+	m_nSampleCount	= 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CLogVariable::LogValueChanged(time_t nTimestamp, CDataLogger &rdl, bool bStartup, bool bWriteNull)
+{
+	if(m_bIsIntervalLog)
+		return;
+	
+	if(bWriteNull) // force write of a NULL value and exit
+	{
+		rdl.Log(m_nTagID, 0.0, 0.0, 0.0, nTimestamp, m_nIndex, m_lt, true, bStartup);
+		return;
+	}
+
+	double val;
+	bool bCondition, bConditionChanged, bDoLog = false;
+
+	bCondition = CanLog(true);
+	bConditionChanged = (m_bOldLogCond != bCondition) || bStartup;
+	m_bOldLogCond = bCondition;
+
+	if(!bCondition && !bConditionChanged)
+		return;
+
+	Lock();
+
+	switch(m_vt)
+	{
+	case VT_bool:
+		if(bCondition && ((m_cache.boolVal != *m_data.pBool) || bConditionChanged)) // log, if either the value has changed or a condition transition from 0 to 1 has occured
+		{
+			m_cache.boolVal = *m_data.pBool;
+			val = m_cache.boolVal ? 1.0 : 0.0;
+			bDoLog = true;
+		}
+		break;
+	case VT_I1:
+		if(bCondition && ((m_cache.I1Val != *m_data.pI1) || bConditionChanged))
+		{
+			m_cache.I1Val = *m_data.pI1;
+			val = (double)m_cache.I1Val;
+			bDoLog = true;
+		}
+		break;
+	case VT_UI1:
+		if(bCondition && ((m_cache.UI1Val != *m_data.pUI1) || bConditionChanged))
+		{
+			m_cache.UI1Val = *m_data.pUI1;
+			val = (double)m_cache.UI1Val;
+			bDoLog = true;
+		}
+		break;
+	case VT_I2:
+		if(bCondition && ((m_cache.I2Val != *m_data.pI2) || bConditionChanged))
+		{
+			m_cache.I2Val = *m_data.pI2;
+			val = (double)m_cache.I2Val;
+			bDoLog = true;
+		}
+		break;
+	case VT_UI2:
+		if(bCondition && ((m_cache.UI2Val != *m_data.pUI2) || bConditionChanged))
+		{
+			m_cache.UI2Val = *m_data.pUI2;
+			val = (double)m_cache.UI2Val;
+			bDoLog = true;
+		}
+		break;
+	case VT_I4:
+		if(bCondition && ((m_cache.I4Val != *m_data.pI4) || bConditionChanged))
+		{
+			m_cache.I4Val = *m_data.pI4;
+			val = (double)m_cache.I4Val;
+			bDoLog = true;
+		}
+		break;
+	case VT_UI4:
+		if(bCondition && ((m_cache.UI4Val != *m_data.pUI4) || bConditionChanged))
+		{
+			m_cache.UI4Val = *m_data.pUI4;
+			val = (double)m_cache.UI4Val;
+			bDoLog = true;
+		}
+		break;
+	case VT_I8:
+		if(bCondition && ((m_cache.I8Val != *m_data.pI8) || bConditionChanged))
+		{
+			m_cache.I8Val = *m_data.pI8;
+			val = (double)m_cache.I8Val;
+			bDoLog = true;
+		}
+		break;
+	case VT_UI8:
+		if(bCondition && ((m_cache.UI8Val != *m_data.pUI8) || bConditionChanged))
+		{
+			m_cache.UI8Val = *m_data.pUI8;
+			val = (double)m_cache.UI8Val;
+			bDoLog = true;
+		}
+		break;
+	case VT_float:
+		if(bCondition && ((m_cache.FloatVal != *m_data.pFloat) || bConditionChanged))
+		{
+			m_cache.FloatVal = *m_data.pFloat;
+			val = (double)m_cache.FloatVal;
+			bDoLog = true;
+		}
+		break;
+	case VT_double:
+		if(bCondition && ((m_cache.DoubleVal != *m_data.pDouble) || bConditionChanged))
+		{
+			m_cache.DoubleVal = *m_data.pDouble;
+			val = m_cache.DoubleVal;
+			bDoLog = true;
+		}
+		break;
+    default:
+		ASSERT(false);
+        break;
+	}
+
+	Unlock();
+
+	if(bDoLog)	// log value, if either the value has changed or a condition transition from 0 to 1 has occured
+	{
+		rdl.Log(m_nTagID, val, 0.0, 0.0, nTimestamp, m_nIndex, m_lt);
+	}
+	else if(!bCondition && bConditionChanged)	// log a NULL value , if a condition transition from 1 to 0 has occured
+	{
+		rdl.Log(m_nTagID, 0.0, 0.0, 0.0, nTimestamp, m_nIndex, m_lt, true);
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CLogVariable::Lock(void)
+{
+	::GfaIpcLockSHM(m_hShm);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CLogVariable::Unlock(void)
+{
+	::GfaIpcUnlockSHM(m_hShm);
+}

+ 198 - 0
datalogger/logvar.h

@@ -0,0 +1,198 @@
+// logvar.h :
+//
+
+#if !defined(AGD_LOGVAR_H__ED3E1EC3_3899_4602_A824_5B7A58880AAC__INCLUDED_)
+#define AGD_LOGVAR_H__ED3E1EC3_3899_4602_A824_5B7A58880AAC__INCLUDED_
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+#include <string.h>
+#include <string>
+#include <pthread.h>
+#include <typeinfo>
+#include <vector>
+#include <map>
+#include <gfa/gfaipc.h>
+#include "datalogger.h"
+
+/////////////////////////////////////////////////////////////////////////////
+
+typedef union _CHECK_UPDATE_SHM_RETVAL
+{
+	struct
+	{
+		unsigned int nChecked;
+		unsigned int nUpdated;
+	};
+	unsigned long long nRetval;
+}CHECK_UPDATE_SHM_RETVAL, *LPCHECK_UPDATE_SHM_RETVAL;
+typedef const CHECK_UPDATE_SHM_RETVAL *LPCCHECK_UPDATE_SHM_RETVAL;
+
+/////////////////////////////////////////////////////////////////////////////
+// logvar.h - Declarations:
+
+class CLoggable
+{
+public:
+	virtual void InitPath(CLoggable *pParent, const char *pszMemberName)								= 0;
+	virtual const char* GetPath(void) const																= 0;
+	virtual void InitTagID(CDataLogger &rdl)															= 0;
+	virtual void Sample(void)																			= 0;
+	virtual void LogInterval(time_t nTimestamp, CDataLogger &rdl)										= 0;
+	virtual void LogValueChanged(time_t nTimestamp, CDataLogger &rdl, bool bStartup, bool bWriteNull)	= 0;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CLogVariable : public CLoggable
+{
+public:
+	enum VT
+	{
+		VT_Invalid,
+		VT_bool,
+		VT_I1,
+		VT_UI1,
+		VT_I2,
+		VT_UI2,
+		VT_I4,
+		VT_UI4,
+		VT_I8,
+		VT_UI8,
+		VT_float,
+		VT_double,
+		VT_string
+	};
+
+	typedef union
+	{
+		void *pVoid;
+		bool *pBool;
+		signed char *pI1;
+		unsigned char *pUI1;
+		signed short *pI2;
+		unsigned short *pUI2;
+		signed int *pI4;
+		unsigned int *pUI4;
+		signed long long *pI8;
+		unsigned long long *pUI8;
+		float *pFloat;
+		double *pDouble;
+	}V_Ptr;
+
+	typedef union
+	{
+		bool boolVal;
+		signed char I1Val;
+		unsigned char UI1Val;
+		signed short I2Val;
+		unsigned short UI2Val;
+		signed int I4Val;
+		unsigned int UI4Val;
+		signed long long I8Val;
+		unsigned long long UI8Val;
+		float FloatVal;
+		double DoubleVal;
+	}V_Val;
+
+public:
+	CLogVariable(LogTypes lt, void *pData, bool *pbLogCond, const std::type_info &rti, HSHM hShm, const char *pszName, int nIndex, CLoggable *pParent);
+	CLogVariable(LogTypes lt, void *pData, void *pBase, size_t nCvOffset, unsigned int nCvBitNr, const std::type_info &rti, HSHM hShm, const char *pszName, int nIndex, CLoggable *pParent);
+	virtual ~CLogVariable(void);
+
+public:
+	virtual void InitPath(CLoggable *pParent, const char *pszMemberName);
+	virtual const char* GetPath(void) const {
+		return m_pszPath;}
+	virtual void InitTagID(CDataLogger &rdl);
+	virtual void Sample(void);
+	virtual void LogInterval(time_t nTimestamp, CDataLogger &rdl);
+	virtual void LogValueChanged(time_t nTimestamp, CDataLogger &rdl, bool bStartup, bool bWriteNull);
+
+private:
+    void Lock(void);
+    void Unlock(void);
+    bool CanLog(bool fLock);
+
+private:
+	LogTypes m_lt;
+	bool *m_pbLogCond;
+	bool m_bOldLogCond;
+    std::string m_name;
+    std::string m_path;
+    const char *m_pszPath;
+    int m_nIndex;
+    enum VT m_vt;
+    volatile V_Ptr m_data;
+    V_Val m_cache;
+    double m_fLastSample;
+    double m_fSamples;
+    double m_fMin;
+    double m_fMax;
+    HSHM m_hShm;
+    CLoggable *m_pParent;
+    unsigned long long m_nUpdates;
+    unsigned long m_nTagID;
+    unsigned int m_nSampleCount;
+    bool m_bIsIntervalLog;
+    bool m_bIsCondLog;
+    uint8_t *m_pnCvByte;
+    uint8_t m_nCvMask;
+    bool m_bCvIsBit;
+    bool m_bIsDbPersistant;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CLogBitVariable : public CLoggable
+{
+
+public:
+	CLogBitVariable(LogTypes lt, void *pData, size_t nOffset, unsigned int nBitNr, bool *pbLogCond, HSHM hShm, const char *pszName, CLoggable *pParent);
+	CLogBitVariable(LogTypes lt, void *pData, size_t nOffset, unsigned int nBitNr, size_t nCvOffset, unsigned int nCvBitNr, HSHM hShm, const char *pszName, CLoggable *pParent);
+	virtual ~CLogBitVariable(void);
+
+public:
+	virtual void InitPath(CLoggable *pParent, const char *pszMemberName);
+	virtual const char* GetPath(void) const {
+		return m_pszPath;}
+	virtual void InitTagID(CDataLogger &rdl);
+	virtual void Sample(void);
+	virtual void LogInterval(time_t nTimestamp, CDataLogger &rdl);
+	virtual void LogValueChanged(time_t nTimestamp, CDataLogger &rdl, bool bStartup, bool bWriteNull);
+
+private:
+    void Lock(void);
+    void Unlock(void);
+    bool CanLog(bool fLock);
+
+private:
+	LogTypes m_lt;
+	bool *m_pbLogCond;
+	bool m_bOldLogCond;
+    std::string m_name;
+    std::string m_path;
+    const char *m_pszPath;
+	bool m_cacheVal;
+    double m_fLastSample;
+    double m_fSamples;
+    double m_fMin;
+    double m_fMax;
+    HSHM m_hShm;
+    CLoggable *m_pParent;
+    unsigned long long m_nUpdates;
+    unsigned long m_nTagID;
+    unsigned int m_nSampleCount;
+    bool m_bIsIntervalLog;
+    bool m_bIsCondLog;
+    uint8_t *m_pnByte;
+    uint8_t m_nMask;
+    uint8_t *m_pnCvByte;
+    uint8_t m_nCvMask;
+    bool m_bCvIsBit;
+    bool m_bIsDbPersistant;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	!defined(AGD_LOGVAR_H__ED3E1EC3_3899_4602_A824_5B7A58880AAC__INCLUDED_)

+ 538 - 0
datalogger/main.cpp

@@ -0,0 +1,538 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+#include <signal.h>
+#include <unistd.h>
+#include <linux/limits.h>
+#include "projal.h"
+#include "logfile.h"
+#include "fileutil.h"
+#include "strutil.h"
+#include "instance.h"
+#include "processclock.h"
+#include "debug.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// app control
+
+#define _APPID						GFA_APPCTRL_APPID_DATALOGGER
+#define _APPNAME					"Datalogger"
+#define _DEPENDENCIES				((appid_t)(GFA_APPCTRL_APPID_REMANENT))
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#define _TRACK_TIME					1
+#define _WRITE_PAST_NULL_AT_STARTUP	1
+#define _LOGFILE_NAME				"datalogger.log"
+
+#define _SIG_BLOCK(s)				sigprocmask(SIG_BLOCK, (s), NULL)
+#define _SIG_UNBLOCK(s)				sigprocmask(SIG_UNBLOCK, (s), NULL)
+
+#define BYTES_FROM_MiB(mib)			((mib) * 1024 * 1024)
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static volatile bool				g_fRun			= false;
+static volatile bool				g_fPauseImp		= false;
+static volatile bool				g_fPauseCmd		= false;
+static volatile bool				g_fZombie		= false;
+static appid_t						g_nDepRunning	= 0;
+static sigset_t						g_set;
+static CLogfile						g_lf;
+int									g_nLastSig		= -1;
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+static const char* _GetBaseDir(std::string &rstrBaseDir)
+{
+	char szBaseDir[PATH_MAX];
+	const char *pszBaseDir = NULL;
+
+#ifdef _LOG_BASE_DIR
+	pszBaseDir = _LOG_BASE_DIR;
+	if(!pszBaseDir || !*pszBaseDir || !::DirectoryExist(pszBaseDir))
+	{
+		CLogfile::StdErr("Invalid base directory config! Using app directory!\n");
+		pszBaseDir = ::GetAppDirectory(szBaseDir, sizeof(szBaseDir));
+	}
+	rstrBaseDir = pszBaseDir;
+#else	//	_LOG_BASE_DIR
+    UNUSED(pszBaseDir);
+	rstrBaseDir = ::GetAppDirectory(szBaseDir, sizeof(szBaseDir));
+#endif	//	_LOG_BASE_DIR
+
+	rtrim(rstrBaseDir, "/");
+	return rstrBaseDir.c_str();
+}
+
+static void _SigHandler(int sig)
+{
+	g_nLastSig = sig;
+	g_fRun = g_fPauseImp = g_fPauseCmd = g_fZombie = false;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static void _ProcessCtrlMessages(HAPPCTRL hAC, HAPPINFO hAI)
+{
+    ctrlmsg_t nCtrlMsg;
+
+	while(g_fRun && (nCtrlMsg = ::GfaIpcAppCtrlGetNextCtrlMsg(hAI)))
+	{
+		switch(nCtrlMsg)
+		{
+		case GFA_APPCTRL_CTRLMSG_STOP:
+			g_fRun = false;
+			g_fPauseImp = false;
+			g_fPauseCmd = false;
+			g_fZombie = false;
+			g_lf.Info("Received Control Message 'Stop'\n");
+			return;
+		case GFA_APPCTRL_CTRLMSG_PAUSE:
+			if(!g_fPauseCmd)
+			{
+				g_fPauseCmd = true;
+				if(!g_fPauseImp)
+				{
+					::GfaIpcAppCtrlSetState(hAC, GIAS_Paused);
+					g_lf.Info("Received Control Message 'Pause'\n");
+					g_lf.Info("Enter state %s ...\n", ::GfaIpcAppCtrlGetStateText(GIAS_Paused));
+					TRACE("%-8s: State: %s\n", "Me", ::GfaIpcAppCtrlGetStateText(GIAS_Paused));
+				}
+			}
+			break;
+		case GFA_APPCTRL_CTRLMSG_RESUME:
+			if(g_fPauseCmd)
+			{
+				g_fPauseCmd = false;
+				if(!g_fPauseImp)
+				{
+					g_lf.Info("Received Control Message 'Resume'\n");
+					g_lf.Info("Enter state %s ...\n", ::GfaIpcAppCtrlGetStateText(GIAS_Running));
+					::GfaIpcAppCtrlSetState(hAC, GIAS_Running);
+					TRACE("%-8s: State: %s\n", "Me", ::GfaIpcAppCtrlGetStateText(GIAS_Running));
+				}
+			}
+			break;
+		default:
+			break;
+		}
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static void _ProcessStateEvents(HAPPCTRL hAC, HAPPINFO hAI)
+{
+    appid_t nAppIdSrc;
+    bool fOldPaused = g_fPauseImp;
+	char szDispName[128];
+
+	while(g_fRun && (nAppIdSrc = ::GfaIpcAppCtrlGetNextStateEvtSrc(hAI)))
+	{
+		GfaIpcAppStates state = ::GfaIpcAppCtrlGetState(hAC, nAppIdSrc);
+		GfaIpcAppCtrlGetDisplayName(hAC, nAppIdSrc, szDispName, sizeof(szDispName));
+		TRACE("%-8s: State: %s\n", szDispName, ::GfaIpcAppCtrlGetStateText(state));
+		
+		if(nAppIdSrc & _DEPENDENCIES)
+		{
+			if(state == GIAS_Running)
+			{
+				g_lf.Info("%s -> %s.\n", szDispName, ::GfaIpcAppCtrlGetStateText(state));
+				g_nDepRunning |= nAppIdSrc;
+			}
+			else
+			{
+				g_lf.Warning("%s -> %s.\n", szDispName, ::GfaIpcAppCtrlGetStateText(state));
+				g_nDepRunning &= ~nAppIdSrc;
+			}
+		}
+	}
+
+	if(g_fRun)
+	{
+		g_fPauseImp = (g_nDepRunning != _DEPENDENCIES);
+
+		if(!g_fPauseCmd && (fOldPaused != g_fPauseImp))
+		{
+			fOldPaused = g_fPauseImp;
+			GfaIpcAppStates newState = g_fPauseImp ? GIAS_Paused : GIAS_Running;
+			::GfaIpcAppCtrlSetState(hAC, newState);
+			if(g_fPauseImp)
+				g_lf.Warning("Enter state %s ...\n", ::GfaIpcAppCtrlGetStateText(newState));
+			else
+				g_lf.Info("Enter state %s ...\n", ::GfaIpcAppCtrlGetStateText(newState));
+		}
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+int main(int argc, char **argv)
+{
+	int nRet = -1;
+	HSHM hShm = NULL;
+	HAPPCTRL hAC = NULL;
+	HAPPINFO hAI;
+	void *pShm = NULL;
+	DLPARAMS dlp;
+    CProcessInstance pi;
+    unsigned long nSamplesPerLog, nLogsPerFlush;
+	char szLogFile[PATH_MAX];
+	std::string strBaseDir;
+	const char *pszBaseDir = NULL;
+#if _TRACK_TIME
+	unsigned long long nElapsed;
+	double fTime;
+	CProcessClock pc;
+#endif	//	_TRACK_TIME
+	unsigned long long nUsecWorkTime = 0;
+	CProcessClock pcWork;
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    // check for multiple instances
+
+    if(!pi.LockInstance(UUID_SHM))
+    {
+		CLogfile::StdErr("Failed to start instance!\n");
+        return -1;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+	// configure signal handling
+
+	struct sigaction sa;
+	::sigfillset(&g_set);
+	sigaddset(&g_set, SIGUSR1);
+	memset(&sa, 0, sizeof(sa));
+
+	sa.sa_handler = _SigHandler;
+    sigaction(SIGHUP, &sa, NULL);	// handles user's terminal disconnect
+    sigaction(SIGQUIT, &sa, NULL);	// handles Ctrl + '\'
+	sigaction(SIGTERM, &sa, NULL);	// handles normal termination
+	sigaction(SIGABRT, &sa, NULL);	// handles abnormal termination (i.e. abort())
+	sigaction(SIGINT, &sa, NULL);	// handles Ctrl + 'C'
+
+	sa.sa_handler = SIG_IGN;
+    sigaction(SIGTSTP, &sa, NULL);	// ignores Ctrl + 'Z'
+    sigaction(SIGSTOP, &sa, NULL);	// ignores Stop
+    sigaction(SIGCONT, &sa, NULL);	// ignores Continue
+    sigaction(SIGCHLD, &sa, NULL);	// ignores child process termination
+    sigaction(0, &sa, NULL);		// ignores shell termination
+
+	do
+	{
+		g_fZombie = true;
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+		// get the base directory for output files
+
+		if(!pszBaseDir)
+			pszBaseDir = _GetBaseDir(strBaseDir);
+
+		CLogfile::StdOut("Using base directory \"%s\".\n", pszBaseDir);
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+		// initialize log file
+
+		sprintf(szLogFile, "%s/%s", pszBaseDir, _LOGFILE_NAME);
+
+		if(!g_lf.Open(szLogFile, true, CLogfile::VB_Inf))
+		{
+			CLogfile::StdErr("Failed to create/open log file!\n");
+			break;
+		}
+
+		g_lf.Info("Process enter.\n");
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+		// initialize app control
+
+		g_lf.Info("Acquire AppCtrl-Handle.\n");
+
+		if(!(hAC = ::GfaIpcAppCtrlAcquire(_APPID, _APPNAME, _LOG_INTV_SAMPLE * 1000, _LOG_INTV_SAMPLE * 3000)))
+		{
+			g_lf.Error("Failed to acquire AppCtrl-Handle!\n");
+			break;
+		}
+
+		::GfaIpcAppCtrlSetState(hAC, GIAS_Initializing);
+		g_lf.Info("Enter state %s ...\n", ::GfaIpcAppCtrlGetStateText(GIAS_Initializing));
+
+		if(!::GfaIpcAppCtrlSubscribeStateEvents(hAC, _DEPENDENCIES))
+		{
+			g_lf.Error("Failed to subscribe state event notifications!\n");
+			break;
+		}
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+		// validate config parameters
+
+		if(strlen(_LOG_DB_NAME) >= _DL_MAX_DB_NAME_LENGTH)
+		{
+	        g_lf.Error("Database name too long!\n");
+			break;
+		}
+
+		if(strlen(_LOG_DB_USER) >= _DL_MAX_DB_USER_LENGTH)
+		{
+	        g_lf.Error("Database username too long!\n");
+			break;
+		}
+
+		if(strlen(_LOG_DB_PASS) >= _DL_MAX_DB_PASS_LENGTH)
+		{
+	        g_lf.Error("Database password too long!\n");
+			break;
+		}
+
+		if(strlen(_LOG_TAGS_TABLE) >= _DL_MAX_TABLE_NAME_LENGTH)
+		{
+	        g_lf.Error("Tag table name too long!\n");
+			break;
+		}
+
+		if(strlen(_LOG_LOGS_TABLE) >= _DL_MAX_TABLE_NAME_LENGTH)
+		{
+	        g_lf.Error("Log table name too long!\n");
+			break;
+		}
+
+		if((_LOG_INTV_LOG < _LOG_INTV_SAMPLE) || (_LOG_INTV_LOG % _LOG_INTV_SAMPLE))
+		{
+	        g_lf.Error("The log interval must be an integer multiple of the sample interval!\n");
+			break;
+		}
+
+		if((_LOG_INTV_WRITE < _LOG_INTV_LOG) || (_LOG_INTV_WRITE % _LOG_INTV_LOG))
+		{
+	        g_lf.Error("The flush interval must be an integer multiple of the log interval!\n");
+			break;
+		}
+
+		nSamplesPerLog	= _LOG_INTV_LOG / _LOG_INTV_SAMPLE;
+		nLogsPerFlush	= _LOG_INTV_WRITE / _LOG_INTV_LOG;
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+		// configure data logger
+
+		memset(&dlp, 0, sizeof(dlp));
+		strncpy(dlp.szDBName, _LOG_DB_NAME, _DL_MAX_DB_NAME_LENGTH - 1);
+		strncpy(dlp.szDBUser, _LOG_DB_USER, _DL_MAX_DB_USER_LENGTH - 1);
+		strncpy(dlp.szDBPass, _LOG_DB_PASS, _DL_MAX_DB_PASS_LENGTH - 1);
+		strncpy(dlp.szTagsTable, _LOG_TAGS_TABLE, _DL_MAX_TABLE_NAME_LENGTH - 1);
+		strncpy(dlp.szLogsTable, _LOG_LOGS_TABLE, _DL_MAX_TABLE_NAME_LENGTH - 1);
+		snprintf(dlp.szLogsTableBD, _DL_MAX_TABLE_NAME_LENGTH - 1, "%s_bad_date", _LOG_LOGS_TABLE);
+		dlp.nIntvSample	= _LOG_INTV_SAMPLE;
+		dlp.nIntvLog	= _LOG_INTV_LOG;
+		dlp.nIntvFlush	= _LOG_INTV_WRITE;
+		dlp.bMinMax		= _LOG_INTV_MIN_MAX;
+		dlp.pszBaseDir	= pszBaseDir;
+
+#if _LOG_MAX_AGE
+		dlp.nMaxAge = _LOG_MAX_AGE;
+		g_lf.Info("Max. age of log entries: %lu days.\n", dlp.nMaxAge);
+#else	//	_LOG_MAX_AGE
+		dlp.nMaxAge = 0;
+		g_lf.Info("Max. age of log entries not defined.\n");
+#endif	//	_LOG_MAX_AGE
+
+#if _LOG_MAX_SIZE
+		dlp.nMaxSize = BYTES_FROM_MiB(_LOG_MAX_SIZE);
+		g_lf.Info("Max. size for logs table: %u MiB.\n", _LOG_MAX_SIZE);
+#else	//	_LOG_MAX_SIZE
+		dlp.nMaxSize = 0;
+		g_lf.Info("Max. size for logs table not defined.\n");
+#endif	//	_LOG_MAX_SIZE
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+		// acquire SHM
+
+		if(!(hShm = ::acquire_shm(sizeof(shm_t), 1)))
+		{
+	        g_lf.Error("Unable to acquire SHM Handle!\n");
+	        break;
+		}
+
+		g_lf.Info("Acquired SHM Handle.\n");
+
+        if(!(pShm = ::GfaIpcAcquirePointer(hShm)))
+        {
+			g_lf.Error("Unable to acquire SHM Pointer!\n");
+	        break;
+        }
+
+		g_lf.Info("Acquired SHM Pointer.\n");
+        ::GfaIpcDumpSHMROT();
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+		// init datalogger and SHM
+
+		CDataLoggerClock dlc(_LOG_INTV_SAMPLE * 1000000);
+		CDataLogger dl(&dlp, g_lf);
+    	CShm_t shm(pShm, hShm);
+
+		if(!dl.InitDatabase(false))
+	        break;
+
+        shm.InitPath(NULL, NULL);
+        shm.InitTagID(dl);
+
+		bool bTimerUnderrun = false;
+		unsigned long nSamples = 0, nLogs = 0;
+		time_t t = time(NULL);
+
+		_SIG_BLOCK(&g_set);
+#if _WRITE_PAST_NULL_AT_STARTUP
+		time_t t2 = dl.LastLogTimestamp();
+		if(t2)
+			shm.LogValueChanged(t2 + 1, dl, true, true);
+#endif	//	_WRITE_PAST_NULL_AT_STARTUP
+		shm.LogValueChanged(t, dl, true, false);
+#if _TRACK_TIME
+		pc.ClockTrigger();
+#endif	//	_TRACK_TIME
+		dl.Flush(t);
+		_SIG_UNBLOCK(&g_set);
+#if _TRACK_TIME
+		nElapsed = pc.ClockGetElapsed();
+		fTime	= (double)nElapsed / 1000000.0;
+		TRACE("Flush (%.2f ms).\n", fTime);
+#endif	//	_TRACK_TIME
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+		// enter running loop
+
+		g_fZombie = false;
+		g_fRun = true;
+		::GfaIpcAppCtrlSetState(hAC, GIAS_Running);
+		g_lf.Info("Enter state %s ...\n", ::GfaIpcAppCtrlGetStateText(GIAS_Running));
+
+		while(g_fRun)
+		{
+			////////////////////////////////////////////////////////////////////////////////////////
+			// update app control info
+
+			if((hAI = ::GfaIpcAppCtrlInfoUpdate(hAC, nUsecWorkTime)))
+			{
+				_ProcessCtrlMessages(hAC, hAI);
+				if(!g_fRun)
+					break;
+				_ProcessStateEvents(hAC, hAI);
+			}
+
+			////////////////////////////////////////////////////////////////////////////////////////
+
+			if(!dlc.Sleep(bTimerUnderrun, true))
+			{
+				g_fRun = g_fPauseImp = g_fPauseCmd = false;
+				g_fZombie = true;
+				g_lf.Error("CDataLoggerClock::Sleep Error!\n", t);
+				break;
+			}
+
+			////////////////////////////////////////////////////////////////////////////////////////
+
+			t = time(NULL);
+			pcWork.ClockTrigger();
+
+			if(!bTimerUnderrun && !g_fPauseImp && !g_fPauseCmd)
+			{
+				_SIG_BLOCK(&g_set);
+
+				shm.LogValueChanged(t, dl, false, false);
+	            shm.Sample();
+				++nSamples;
+
+				if(nSamples == nSamplesPerLog)
+				{
+#if _TRACK_TIME
+					pc.ClockTrigger();
+#endif	//	_TRACK_TIME
+                    shm.LogInterval(t, dl);
+#if _TRACK_TIME
+					nElapsed = pc.ClockGetElapsed();
+					fTime	= (double)nElapsed / 1000000.0;
+					TRACE("Log (%.2f ms).\n", fTime);
+#endif	//	_TRACK_TIME
+					++nLogs;
+					nSamples = 0;
+				}
+
+				if(nLogs == nLogsPerFlush)
+				{
+#if _TRACK_TIME
+					pc.ClockTrigger();
+#endif	//	_TRACK_TIME
+					dl.Flush(time(NULL));
+#if _TRACK_TIME
+					nElapsed = pc.ClockGetElapsed();
+					fTime	= (double)nElapsed / 1000000.0;
+					TRACE("Flush (%.2f ms).\n", fTime);
+#endif	//	_TRACK_TIME
+					nLogs = 0;
+					dl.SizeGuardTrigger(t);
+				}
+
+				_SIG_UNBLOCK(&g_set);
+			}
+			else if(bTimerUnderrun)
+			{
+				g_lf.Warning("Timer underrun (%ld)!\n", t);
+				bTimerUnderrun = false;
+			}
+
+			nUsecWorkTime = pcWork.ClockGetElapsed() / 1000;
+		}
+
+		dl.Flush(time(NULL));
+	}
+	while(false);
+
+	if(g_nLastSig >= 0)
+	{
+		g_lf.Info("Received signal '%s'.\n", strsignal(g_nLastSig));
+		g_nLastSig = -1;
+	}
+
+	if(hShm)
+	{
+		if(pShm)
+		{
+			g_lf.Info("Releasing SHM Pointer ...\n");
+			::GfaIpcReleasePointer(hShm, pShm);
+		}
+
+		g_lf.Info("Releasing SHM Handle ...\n");
+    	::GfaIpcReleaseSHM(hShm);
+    }
+	
+	if(g_fZombie)
+	{
+		if(hAC)
+			::GfaIpcAppCtrlSetState(hAC, GIAS_Zombie);
+		TRACE("Enter Zombie state ...\n");
+		g_lf.Warning("Enter Zombie state ...\n");
+		g_lf.Flush();
+		pause();
+
+		if(g_nLastSig >= 0)
+			g_lf.Info("Received signal '%s'.\n", strsignal(g_nLastSig));
+	}
+
+	if(hAC)
+	{
+		g_lf.Info("Enter state %s ...\n", ::GfaIpcAppCtrlGetStateText(GIAS_Terminating));
+		::GfaIpcAppCtrlSetState(hAC, GIAS_Terminating);
+		g_lf.Info("Releasing App Control ...\n");
+		::GfaIpcAppCtrlRelease(hAC);
+	}
+
+	g_lf.Info("Process exit.\n\n");
+	g_lf.Close();
+	CLogfile::StdErr("Datalogger exit.\n");
+	return nRet;
+}

+ 15 - 0
libgfaservices.pro

@@ -0,0 +1,15 @@
+BUILD_CFG = 1
+
+TEMPLATE = subdirs
+
+SUBDIRS += \
+    common \
+    remanent \
+    datalogger \
+    summarist \
+    rest \
+    mqttcl \
+    shmqml
+
+CONFIG += qtc_runnable
+CONFIG += ordered

+ 12 - 0
mqttcl/cfg/mqtt.conf.json

@@ -0,0 +1,12 @@
+{
+	"brokerAddr":		"herrschuster.local",
+	"brokerPort":		8883,
+	"defaultQos":		2,
+	"defaultRetain":	false,
+	"devicePrefix":		"VarioNet",
+	"tlsMode":			1,
+	"tlsCaCrtFile":		"/home/wrk/share/config/services/mqttcl/tls/local/client/ca.crt",
+	"tlsClCrtFile":		"/home/wrk/share/config/services/mqttcl/tls/local/client/client.crt",
+	"tlsClKeyFile":		"/home/wrk/share/config/services/mqttcl/tls/local/client/client.key",
+	"tlsPsk":			"1234567890abcdef"
+}

+ 1202 - 0
mqttcl/main.cpp

@@ -0,0 +1,1202 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+#include <stdio.h>
+#include <signal.h>
+#include <linux/limits.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <linux/if_arp.h>
+#include <netinet/in.h>
+#include <ifaddrs.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <getopt.h>
+#include "strutil.h"
+#include "fileutil.h"
+#include "instance.h"
+#include "processclock.h"
+#include "debug.h"
+#include "projal.h"
+#include "mqttclient.h"
+#include "mqttcfg.h"
+#include "mqttdbg.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if _ENABLE_MEM_TRACE
+#include <mcheck.h>
+class CMtrace
+{
+public:
+	CMtrace(void) {
+	    putenv("MALLOC_TRACE=/home/wrk/share/config/services/Debug/Desktop_Qt_5_7_0_GCC_64bit/mqttcl/mtrace.log");
+		mtrace();
+	}
+	~CMtrace(void){
+//		muntrace();
+	}
+};
+#endif	//	_ENABLE_MEM_TRACE
+
+
+
+#if _TRACK_TIMES
+static CProcessClock g_pc;
+unsigned long g_nDbgCounter1 = 0;
+unsigned long g_nDbgCounter2 = 0;
+unsigned long g_nDbgCounter3 = 0;
+#endif	//	_TRACK_TIMES
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// app control
+
+#define _APPID						GFA_APPCTRL_APPID_MQTTCL
+#define _APPNAME					"MqttCl"
+#define _DEPENDENCIES				((appid_t)(GFA_APPCTRL_APPID_REMANENT))
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+#define _UPDATE_INTERVAL_MS			100
+#define _RECONN_INTERVAL_MS			1000
+#define _LOGFILE_NAME				"mqttcl.log"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#define _SIG_BLOCK(s)				sigprocmask(SIG_BLOCK, (s), NULL)
+#define _SIG_UNBLOCK(s)				sigprocmask(SIG_UNBLOCK, (s), NULL)
+
+#define _NSEC_FROM_MSEC(ms)			((ms) * _PC_NS_PER_MS)
+#define _USEC_FROM_MSEC(ms)			((ms) * _PC_NS_PER_US)
+
+#define _TOPIC_CTRL_KEY_BINLE		"binLe"
+#define _TOPIC_CTRL_KEY_BINBE		"binBe"
+#define _TOPIC_CTRL_KEY_JSON		"json"
+#define _TOPIC_CTRL_KEY_PBUF		"pBuf"
+#define _TOPIC_CTRL_KEY_QOS			"qos"
+#define _TOPIC_CTRL_KEY_RETAIN		"retained"
+#define _TOPIC_CTRL_KEY_REM_RETAINED	"delRetained"
+#define _TOPIC_CMD_CTRL				"CONTROL"
+#define _TOPIC_CMD_SET				"SET"
+#define _TOPIC_CMD_STATUS			"STATUS"
+
+typedef enum _TOPIC_CTRL_CMD
+{
+	TCC_Control,
+	TCC_SetBinLe,
+	TCC_SetBinBe,
+	TCC_SetJson,
+	TCC_SetPBuf,
+	TCC_Status
+}TOPIC_CTRL_CMD, *LPTOPIC_CTRL_CMD;
+typedef const TOPIC_CTRL_CMD *LPCTOPIC_CTRL_CMD;
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+typedef enum _MQTT_CLIENT_STATES // when modifying, don't forget to adjust g_pszStateNames!!!
+{
+	CLS_NotInit,
+	CLS_SetTLS,
+	CLS_Unconnected,
+	CLS_Connect,
+	CLS_Reconnect,
+	CLS_Connecting,
+	CLS_Connected,
+	CLS_Subscribe,
+	CLS_Subscribing,
+	CLS_Subscribed,
+	CLS_ProcMsg,
+	CLS_ShutDown,
+	CLS_Err,
+	CLS_Unsubscribe,
+	CLS_Disconnect,
+	CLS_Cleanup,
+	CLS_Paused,
+	CLS_Exit
+}MQTT_CLIENT_STATES, *LPMQTT_CLIENT_STATES;
+typedef const MQTT_CLIENT_STATES *LPCMQTT_CLIENT_STATES;
+
+static const char *g_pszStateNames[] =
+{
+	"Not Init",
+	"Set TLS",
+	"Unconnected",
+	"Connect",
+	"Reconnect",
+	"Connecting",
+	"Connected",
+	"Subscribe",
+	"Subscribing",
+	"Subscribed",
+	"ProcMsg",
+	"ShutDown",
+	"Error",
+	"Unsubscribe",
+	"Disconnect",
+	"Cleanup",
+	"Paused",
+	"Exit"
+};
+
+static const char * _GetClientStateString(MQTT_CLIENT_STATES cs)
+{
+	if(cs >= CLS_NotInit && cs <= CLS_Exit)
+		return g_pszStateNames[cs];
+	return "";
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+typedef struct _SUB_CTRL_TOPIC
+{
+	_SUB_CTRL_TOPIC(const std::string &&s) : sTopic(s)
+	{
+		this->nVarOffset = s.length() - 2;
+	}
+	std::string sTopic;
+	size_t nVarOffset;
+}SUB_CTRL_TOPIC, *LPSUB_CTRL_TOPIC;
+typedef const SUB_CTRL_TOPIC *LPCSUB_CTRL_TOPIC;
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+//static std::string _CreateDeviceID(void);
+
+static volatile bool				g_fRun			= false;
+static volatile bool				g_fPauseImp		= false;
+static volatile bool				g_fPauseCmd		= false;
+static volatile bool				g_fZombie		= false;
+static appid_t						g_nDepRunning	= 0;
+static sigset_t						g_set;
+static CLogfile						g_lf;
+static int							g_nLastSig = -1;
+static shm_t						g_shmShadow;
+static MQTT_CLIENT_STATES			g_cs = CLS_NotInit;
+static MQTT_CLIENT_STATES			g_csLast = CLS_NotInit;
+static bool							g_bConnected = false;
+static int							g_nSubcribed = 0;
+static bool							g_bIntr = false;
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+static const char* _GetBaseDir(std::string &rstrBaseDir)
+{
+	char szBaseDir[PATH_MAX];
+	rstrBaseDir = ::GetAppDirectory(szBaseDir, sizeof(szBaseDir));
+	rtrim(rstrBaseDir, "/");
+	return rstrBaseDir.c_str();
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+static void _SigHandler(int sig)
+{
+	g_nLastSig = sig;
+	g_bIntr = true;
+	g_fPauseImp = g_fPauseCmd = g_fZombie = false;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+static MQTT_CLIENT_STATES _cl_usleep(unsigned int t, MQTT_CLIENT_STATES csNext)
+{
+	if(usleep(t) < 0 && errno == EINTR)
+		return CLS_ShutDown;
+	return csNext;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+static void _ProcessIncoming(CMqttClient &rcl, CMqttVarTable &vt, CMqttClConfig &cfg, LPCSUB_CTRL_TOPIC pCtrlMap)
+{
+	CMqttVar *pVar;
+	CMqttMessage *pMsg;
+	std::string sVarPath;
+
+	while((pMsg = rcl.PopRcvMsg()))
+	{
+		if(pMsg->TopicMatchesSub(pCtrlMap[TCC_Control].sTopic.c_str()))
+		{
+			sVarPath = pMsg->GetTopic(pCtrlMap[TCC_Control].nVarOffset);
+
+			if((pVar = vt.Find(sVarPath.c_str())))
+			{
+				bool bChanged = false;
+				int nType, nQos;
+				CJson_t jtRoot, jtVal;
+				std::string err;
+				uint32_t nMaskOn = 0, nMaskOff = 0;
+
+				if(pMsg->GetPayloadAsJSON(jtRoot, err))
+				{
+					if(jtRoot.GetValue(_TOPIC_CTRL_KEY_RETAIN, jtVal))
+					{
+						switch((nType = jtVal.Type()))
+						{
+						case JSON_TRUE:
+							bChanged = pVar->SetRetained(true) || bChanged;
+							break;
+						case JSON_FALSE:
+							bChanged = pVar->SetRetained(false) || bChanged;
+							break;
+						default:
+							g_lf.Error("%s: Invalid data type for JSON key '%s': %d\n", pMsg->GetTopic().c_str(), _TOPIC_CTRL_KEY_RETAIN, nType);
+							break;
+						}
+					}
+
+					if(jtRoot.GetValue(_TOPIC_CTRL_KEY_REM_RETAINED, jtVal))
+					{
+						switch((nType = jtVal.Type()))
+						{
+						case JSON_TRUE:
+							pVar->RemoveRetained(cfg.GetDeviceID(), cfg.GetShmID(), rcl.GetMsgQueueSnd(), true);
+							break;
+						case JSON_FALSE:
+							g_lf.Warning("%s: command \"%s\":false has no effect!\n", pMsg->GetTopic().c_str(), _TOPIC_CTRL_KEY_REM_RETAINED);
+							break;
+						default:
+							g_lf.Error("%s: Invalid data type for JSON key '%s': %d\n", pMsg->GetTopic().c_str(), _TOPIC_CTRL_KEY_REM_RETAINED, nType);
+							break;
+						}
+					}
+
+					if(jtRoot.GetValue(_TOPIC_CTRL_KEY_QOS, jtVal))
+					{
+						switch((nType = jtVal.Type()))
+						{
+						case JSON_INTEGER:
+							nQos = (int)json_integer_value(jtVal);
+							if(nQos < MQTTCL_MIN_QOS)
+								g_lf.Warning("%s: Invalid value for QOS: %d! Value adjusted to %d\n", pMsg->GetTopic().c_str(), nQos, MQTTCL_MIN_QOS);
+							else if(nQos > MQTTCL_MAX_QOS)
+								g_lf.Warning("%s: Invalid value for QOS: %d! Value adjusted to %d\n", pMsg->GetTopic().c_str(), nQos, MQTTCL_MAX_QOS);
+							bChanged = pVar->SetQoS(nQos) || bChanged;
+							break;
+						default:
+							g_lf.Error("%s: Invalid data type for JSON key '%s': %d\n", pMsg->GetTopic().c_str(), _TOPIC_CTRL_KEY_QOS, nType);
+							break;
+						}
+					}
+
+					if(jtRoot.GetValue(_TOPIC_CTRL_KEY_BINLE, jtVal))
+					{
+						switch((nType = jtVal.Type()))
+						{
+						case JSON_TRUE:
+							nMaskOn |= MQTT_VALUE_BINLE;
+							break;
+						case JSON_FALSE:
+							nMaskOff |= MQTT_VALUE_BINLE;
+							break;
+						default:
+							g_lf.Error("%s: Invalid data type for JSON key '%s': %d\n", pMsg->GetTopic().c_str(), _TOPIC_CTRL_KEY_BINLE, nType);
+							break;
+						}
+					}
+
+					if(jtRoot.GetValue(_TOPIC_CTRL_KEY_BINBE, jtVal))
+					{
+						switch((nType = jtVal.Type()))
+						{
+						case JSON_TRUE:
+							nMaskOn |= MQTT_VALUE_BINBE;
+							break;
+						case JSON_FALSE:
+							nMaskOff |= MQTT_VALUE_BINBE;
+							break;
+						default:
+							g_lf.Error("%s: Invalid data type for JSON key '%s': %d\n", pMsg->GetTopic().c_str(), _TOPIC_CTRL_KEY_BINBE, nType);
+							break;
+						}
+					}
+
+					if(jtRoot.GetValue(_TOPIC_CTRL_KEY_JSON, jtVal))
+					{
+						switch((nType = jtVal.Type()))
+						{
+						case JSON_TRUE:
+							nMaskOn |= MQTT_VALUE_JSON;
+							break;
+						case JSON_FALSE:
+							nMaskOff |= MQTT_VALUE_JSON;
+							break;
+						default:
+							g_lf.Error("%s: Invalid data type for JSON key '%s': %d\n", pMsg->GetTopic().c_str(), _TOPIC_CTRL_KEY_JSON, nType);
+							break;
+						}
+					}
+
+					if(jtRoot.GetValue(_TOPIC_CTRL_KEY_PBUF, jtVal))
+					{
+						switch((nType = jtVal.Type()))
+						{
+						case JSON_TRUE:
+							nMaskOn |= MQTT_VALUE_PBUF;
+							break;
+						case JSON_FALSE:
+							nMaskOff |= MQTT_VALUE_PBUF;
+							break;
+						default:
+							g_lf.Error("%s: Invalid data type for JSON key '%s': %d\n", pMsg->GetTopic().c_str(), _TOPIC_CTRL_KEY_PBUF, nType);
+							break;
+						}
+					}
+
+					if(nMaskOff)
+					{
+						if(pVar->DisablePublish(nMaskOff, &vt))
+						{
+							bChanged = true;
+				        }
+					}
+
+					if(nMaskOn)
+					{
+						if(pVar->EnablePublish(nMaskOn, &vt))
+						{
+							bChanged = true;
+				        }
+					}
+
+#if _DUMP_ENABLED_VARS
+					if(bChanged)
+			            vt.DumpPubEnabled();
+#endif	//	_DUMP_ENABLED_VARS
+				}
+				else
+				{
+					g_lf.Error("%s: JSON error: %s!\n", pMsg->GetTopic().c_str(), err.c_str());
+				}
+			}
+		}
+		else
+		{
+			int nLocks = 0;
+			static const uint32_t nFormats[] =
+			{
+				0,
+				MQTT_VALUE_BINLE,
+				MQTT_VALUE_BINBE,
+				MQTT_VALUE_JSON,
+				MQTT_VALUE_PBUF
+			};
+			
+			for(int i = TCC_SetBinLe; i <= TCC_SetPBuf; i++)
+			{
+				if(pMsg->TopicMatchesSub(pCtrlMap[i].sTopic.c_str()))
+				{
+					sVarPath = pMsg->GetTopic(pCtrlMap[i].nVarOffset);
+
+					if((pVar = vt.Find(sVarPath.c_str())))
+					{
+						if(pVar->PublishEnabled())
+						{
+							if(!pVar->SetShmValue(nFormats[i], pMsg, nLocks))
+							{
+								// !!!
+							}
+						}
+						else
+						{
+							// !!!
+						}
+
+						break;
+					}
+					else
+					{
+						// !!!
+						break;
+					}
+				}
+			}
+		}
+
+		pMsg->Release();
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+static int _ProcessOutgoing(CMqttClient &rcl)
+{
+	int nRet = 0;
+	CMqttMessage *pMsg;
+
+	while((pMsg = rcl.PopSndMsg()))
+	{
+		rcl.publish(pMsg);
+		pMsg->Release();
+		++nRet;
+	}
+
+	return nRet;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+static int _Subscribe(CMqttClient &rcl, int nDefaultQOS, LPCSUB_CTRL_TOPIC pCtrlMap, size_t nLenMap)
+{
+	int nRet;
+
+	for(size_t i = 0; i < nLenMap; i++)
+	{
+		if((nRet = rcl.subscribe(NULL, pCtrlMap[i].sTopic.c_str(),  nDefaultQOS)) != MOSQ_ERR_SUCCESS)
+			break;
+		TRACE("Subscribed: '%s' - QOS: %d\n", pCtrlMap[i].sTopic.c_str(), nDefaultQOS);
+	}
+
+	return nRet;
+}
+
+static void _Unsubscribe(CMqttClient &rcl, LPCSUB_CTRL_TOPIC pCtrlMap, size_t nLenMap)
+{
+	for(size_t i = 0; i < nLenMap; i++)
+	{
+		rcl.unsubscribe(NULL, pCtrlMap[i].sTopic.c_str());
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+static void _OnClientEvents(LPCMQTT_GENERIC_NOTIFICATION pntf, void *pctx)
+{
+	switch(pntf->evt)
+	{
+	case NEVT_Log:
+		if(pntf->log.level == MOSQ_LOG_ERR)
+		{
+			TRACE("[%d] - %s\n", pntf->log.level, pntf->log.str);
+			g_lf.Error("%s\n", pntf->log.str);
+		}
+		break;
+
+	case NEVT_Connect:
+		if(pntf->con.rc == MOSQ_ERR_SUCCESS)
+			g_bConnected = true;
+		break;
+
+	case NEVT_Disconnect:
+		break;
+
+	case NEVT_Subscribe:
+		++g_nSubcribed;
+		break;
+
+	case NEVT_Unsubscribe:
+		break;
+
+	default:
+		break;
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static void _ProcessCtrlMessages(HAPPCTRL hAC, HAPPINFO hAI)
+{
+    ctrlmsg_t nCtrlMsg;
+
+	while(!g_bIntr && (nCtrlMsg = ::GfaIpcAppCtrlGetNextCtrlMsg(hAI)))
+	{
+		switch(nCtrlMsg)
+		{
+		case GFA_APPCTRL_CTRLMSG_STOP:
+			g_bIntr = true;
+			g_fPauseImp = false;
+			g_fPauseCmd = false;
+			g_fZombie = false;
+			g_lf.Info("Received Control Message 'Stop'\n");
+			break;
+		case GFA_APPCTRL_CTRLMSG_PAUSE:
+			if(!g_fPauseCmd)
+			{
+				g_fPauseCmd = true;
+				if(!g_fPauseImp)
+				{
+					::GfaIpcAppCtrlSetState(hAC, GIAS_Paused);
+					g_lf.Info("Received Control Message 'Pause'\n");
+					g_lf.Info("Enter state %s ...\n", ::GfaIpcAppCtrlGetStateText(GIAS_Paused));
+					TRACE("%-8s: State: %s\n", "Me", ::GfaIpcAppCtrlGetStateText(GIAS_Paused));
+				}
+			}
+			break;
+		case GFA_APPCTRL_CTRLMSG_RESUME:
+			if(g_fPauseCmd)
+			{
+				g_fPauseCmd = false;
+				if(!g_fPauseImp)
+				{
+					g_lf.Info("Received Control Message 'Resume'\n");
+					g_lf.Info("Enter state %s ...\n", ::GfaIpcAppCtrlGetStateText(GIAS_Running));
+					::GfaIpcAppCtrlSetState(hAC, GIAS_Running);
+					TRACE("%-8s: State: %s\n", "Me", ::GfaIpcAppCtrlGetStateText(GIAS_Running));
+				}
+			}
+			break;
+		default:
+			break;
+		}
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static void _ProcessStateEvents(HAPPCTRL hAC, HAPPINFO hAI)
+{
+    appid_t nAppIdSrc;
+    bool fOldPaused = g_fPauseImp;
+	char szDispName[128];
+
+	while(!g_bIntr && (nAppIdSrc = ::GfaIpcAppCtrlGetNextStateEvtSrc(hAI)))
+	{
+		GfaIpcAppStates state = ::GfaIpcAppCtrlGetState(hAC, nAppIdSrc);
+		GfaIpcAppCtrlGetDisplayName(hAC, nAppIdSrc, szDispName, sizeof(szDispName));
+		TRACE("%-8s: State: %s\n", szDispName, ::GfaIpcAppCtrlGetStateText(state));
+		
+		if(nAppIdSrc & _DEPENDENCIES)
+		{
+			if(state == GIAS_Running)
+			{
+				g_lf.Info("%s -> %s.\n", szDispName, ::GfaIpcAppCtrlGetStateText(state));
+				g_nDepRunning |= nAppIdSrc;
+			}
+			else
+			{
+				g_lf.Warning("%s -> %s.\n", szDispName, ::GfaIpcAppCtrlGetStateText(state));
+				g_nDepRunning &= ~nAppIdSrc;
+			}
+		}
+	}
+
+	if(!g_bIntr)
+	{
+		g_fPauseImp = (g_nDepRunning != _DEPENDENCIES);
+
+		if(!g_fPauseCmd && (fOldPaused != g_fPauseImp))
+		{
+			fOldPaused = g_fPauseImp;
+			GfaIpcAppStates newState = g_fPauseImp ? GIAS_Paused : GIAS_Running;
+			::GfaIpcAppCtrlSetState(hAC, newState);
+			if(g_fPauseImp)
+				g_lf.Warning("Enter state %s ...\n", ::GfaIpcAppCtrlGetStateText(newState));
+			else
+				g_lf.Info("Enter state %s ...\n", ::GfaIpcAppCtrlGetStateText(newState));
+		}
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+
+int main(int /*argc*/, char **/*argv*/)
+{
+	int nRet = 0;
+    CProcessInstance pi;
+    std::string sDevID;
+	char szLogFile[PATH_MAX];
+	std::string strBaseDir;
+	const char *pszBaseDir = NULL;
+	HAPPCTRL hAC = NULL;
+	HAPPINFO hAI;
+	HSHM hShm = NULL;
+	void *pShm = NULL;
+	unsigned long long nUsecWorkTime = 0;
+	int nTlsMode;
+	CProcessClock pcWork;
+	
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    // check for multiple instances
+
+    if(!pi.LockInstance(UUID_SHM))
+    {
+		CLogfile::StdErr("Failed to start instance!\n");
+        return -1;
+    }
+
+	////////////////////////////////////////////////////////////////////////////////////////////////
+	// configure signal handling
+
+	struct sigaction sa;
+	::sigfillset(&g_set);
+	sigaddset(&g_set, SIGUSR1);
+	memset(&sa, 0, sizeof(sa));
+
+	sa.sa_handler = _SigHandler;
+    sigaction(SIGHUP, &sa, NULL);	// handles user's terminal disconnect
+    sigaction(SIGQUIT, &sa, NULL);	// handles Ctrl + '\'
+	sigaction(SIGTERM, &sa, NULL);	// handles normal termination
+	sigaction(SIGABRT, &sa, NULL);	// handles abnormal termination (i.e. abort())
+	sigaction(SIGINT, &sa, NULL);	// handles Ctrl + 'C'
+
+	sa.sa_handler = SIG_IGN;
+    sigaction(SIGTSTP, &sa, NULL);	// ignores Ctrl + 'Z'
+    sigaction(SIGSTOP, &sa, NULL);	// ignores Stop
+    sigaction(SIGCONT, &sa, NULL);	// ignores Continue
+    sigaction(SIGCHLD, &sa, NULL);	// ignores child process termination
+    sigaction(0, &sa, NULL);		// ignores shell termination
+
+	do
+	{
+		g_fZombie = true;
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+		// get the base directory for output files
+
+		if(!pszBaseDir)
+			pszBaseDir = _GetBaseDir(strBaseDir);
+
+		CLogfile::StdOut("Using base directory \"%s\".\n", pszBaseDir);
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+		// initialize log file
+
+		sprintf(szLogFile, "%s/%s", pszBaseDir, _LOGFILE_NAME);
+
+		if(!g_lf.Open(szLogFile))
+		{
+			CLogfile::StdErr("Failed to create/open log file!\n");
+			nRet = -1;
+			break;
+		}
+
+		g_lf.Info("Process started.\n");
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+		// initialize app control
+
+		g_lf.Info("Acquire AppCtrl-Handle.\n");
+
+		if(!(hAC = ::GfaIpcAppCtrlAcquire(_APPID, _APPNAME, _USEC_FROM_MSEC(_UPDATE_INTERVAL_MS), _USEC_FROM_MSEC(_RECONN_INTERVAL_MS) * 3)))
+		{
+			g_lf.Error("Failed to acquire AppCtrl-Handle!\n");
+			break;
+		}
+
+		::GfaIpcAppCtrlSetState(hAC, GIAS_Initializing);
+		g_lf.Info("Enter state %s ...\n", ::GfaIpcAppCtrlGetStateText(GIAS_Initializing));
+
+		if(!::GfaIpcAppCtrlSubscribeStateEvents(hAC, _DEPENDENCIES))
+		{
+			g_lf.Error("Failed to subscribe state event notifications!\n");
+			break;
+		}
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+		// parse the config file
+
+	    CMqttClConfig cfg(UUID_SHM);
+
+	    if(!cfg.LoadCfg(MQTTCL_CONFIG_FILE_PATH, g_lf))
+	    {
+			nRet = -1;
+			break;
+	    }
+
+		nTlsMode = cfg.GetTLSMode();
+//		TRACE("%s/%s\n", cfg.GetDeviceID(), cfg.GetShmID());
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+		// client control topic map
+
+		const SUB_CTRL_TOPIC subCtrlMap[] =
+		{
+			formatString("%s/%s/%s/#", cfg.GetDeviceID(), cfg.GetShmID(), _TOPIC_CMD_CTRL),
+			formatString("%s/%s/%s/%s/#", cfg.GetDeviceID(), cfg.GetShmID(), MQTT_TOPIC_VALUE_BINLE, _TOPIC_CMD_SET),
+			formatString("%s/%s/%s/%s/#", cfg.GetDeviceID(), cfg.GetShmID(), MQTT_TOPIC_VALUE_BINBE, _TOPIC_CMD_SET),
+			formatString("%s/%s/%s/%s/#", cfg.GetDeviceID(), cfg.GetShmID(), MQTT_TOPIC_VALUE_JSON, _TOPIC_CMD_SET),
+			formatString("%s/%s/%s/%s/#", cfg.GetDeviceID(), cfg.GetShmID(), MQTT_TOPIC_VALUE_PBUF, _TOPIC_CMD_SET),
+//			formatString("%s/%s/%s/#", cfg.GetDeviceID(), cfg.GetShmID(), _TOPIC_CMD_STATUS)
+		};
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+
+	    if(!(hShm = ::acquire_shm(sizeof(shm_t), 1)))
+	    {
+			g_lf.Error("GfaIpcAcquireSHM failed!\n");
+	    	break;
+	    }
+    
+		g_lf.Info("Acquired SHM Handle.\n");
+
+        if(!(pShm = ::GfaIpcAcquirePointer(hShm)))
+        {
+			g_lf.Error("GfaIpcAcquirePointer failed!\n");
+        	break;
+        }
+
+		g_lf.Info("Acquired SHM Pointer.\n");
+        ::GfaIpcDumpSHMROT();
+        
+    	memcpy(&g_shmShadow, (const shm_t*)pShm, sizeof(shm_t));
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+
+    	int nErr, nLocked = 0, nNumConn = 0;
+		bool bReconnect, bConnPending;
+		std::string strErr;
+    	CMqttVarTable vtbl;
+    	CShm_t shm(pShm, &g_shmShadow, hShm, NULL, -1, 0, cfg.GetDefaultQOS(), cfg.GetDefaultRetain());
+        shm.InitPath(NULL, NULL);
+        shm.CreateMembersTable(vtbl);
+
+#if _DUMP_ENABLED_VARS
+        vtbl.DumpPubEnabled();
+#endif	//	_DUMP_ENABLED_VARS
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+
+		CMqttClient mqttCl(cfg.GetDeviceID());
+		mqttCl.SetClientEventCallback(_OnClientEvents, NEVT_Connect | NEVT_Disconnect | NEVT_Subscribe | NEVT_Unsubscribe | NEVT_Message | NEVT_Log, NULL);
+
+		g_fZombie = false;
+		g_fRun = true;
+		::GfaIpcAppCtrlSetState(hAC, GIAS_Running);
+		g_lf.Info("Enter state %s ...\n", ::GfaIpcAppCtrlGetStateText(GIAS_Running));
+
+		while(g_fRun)
+		{
+			////////////////////////////////////////////////////////////////////////////////////////
+			// update app control info
+
+			if((hAI = ::GfaIpcAppCtrlInfoUpdate(hAC, nUsecWorkTime)))
+			{
+				_ProcessCtrlMessages(hAC, hAI);
+				_ProcessStateEvents(hAC, hAI);
+			}
+
+			if(g_fPauseImp || g_fPauseCmd)
+			{
+				if(g_cs < CLS_ShutDown)
+				{
+					g_csLast = g_cs;
+					g_cs = CLS_ShutDown;
+				}
+				else
+				{
+					nUsecWorkTime = 0;
+				}
+			}
+
+			pcWork.ClockTrigger();
+
+			switch(g_cs)
+			{
+			case CLS_NotInit:
+				if((nErr = CMqttClient::Init()) == MOSQ_ERR_SUCCESS)
+				{
+					g_cs = CLS_SetTLS;
+				}
+				else if(!CMqttClient::EvalError(nErr, bReconnect, bConnPending, g_bIntr, strErr))
+				{
+					g_csLast = g_cs;
+					g_cs = CLS_Err;
+				}
+				break;
+
+			case CLS_SetTLS:
+				if(nTlsMode == MQTTCL_TLS_MODE_CRT)
+				{
+					g_lf.Info("Using TLS with certificates.\n");
+
+					if((nErr = mqttCl.tls_set(cfg.GetTlsCaCrtFile(), NULL, cfg.GetTlsClCrtFile(), cfg.GetTlsClKeyFile(), NULL)) != MOSQ_ERR_SUCCESS)
+					{
+						CMqttClient::EvalError(nErr, bReconnect, bConnPending, g_bIntr, strErr);
+
+						if(g_bIntr)
+						{
+							g_csLast = g_cs;
+							g_cs = CLS_ShutDown;
+						}
+						else
+						{
+							g_csLast = g_cs;
+							g_cs = CLS_Err;
+						}
+					}
+					else
+					{
+						g_cs = CLS_Unconnected;
+						g_lf.Info("Connecting to broker @ %s:%u ...\n", cfg.GetBrokerAddr(), cfg.GetBrokerPort());
+					}
+				}
+				else if(nTlsMode == MQTTCL_TLS_MODE_PSK)
+				{
+					g_lf.Info("Using TLS with PSK.\n");
+
+					if((nErr = mqttCl.tls_psk_set(cfg.GetTlsPSK(), cfg.GetDeviceID(), NULL)) != MOSQ_ERR_SUCCESS)
+					{
+						CMqttClient::EvalError(nErr, bReconnect, bConnPending, g_bIntr, strErr);
+
+						if(g_bIntr)
+						{
+							g_csLast = g_cs;
+							g_cs = CLS_ShutDown;
+						}
+						else
+						{
+							g_csLast = g_cs;
+							g_cs = CLS_Err;
+						}
+					}
+					else
+					{
+						g_cs = CLS_Unconnected;
+						g_lf.Info("Connecting to broker @ %s:%u ...\n", cfg.GetBrokerAddr(), cfg.GetBrokerPort());
+					}
+				}
+				else
+				{
+					g_cs = CLS_Unconnected;
+					g_lf.Info("Connecting to broker @ %s:%u ...\n", cfg.GetBrokerAddr(), cfg.GetBrokerPort());
+				}
+				break;
+
+			case CLS_Unconnected:
+				if(g_bConnected)
+				{
+					g_bConnected = false;
+					g_lf.Warning("Lost connection to broker @ %s:%u. Tying to reconnect ...\n", cfg.GetBrokerAddr(), cfg.GetBrokerPort());
+				}
+				if(!nNumConn)
+					g_cs = CLS_Connect;
+				else
+					g_cs = CLS_Reconnect;
+				break;
+
+			case CLS_Connect:
+				if((nErr = mqttCl.connect(cfg.GetBrokerAddr(), cfg.GetBrokerPort())) == MOSQ_ERR_SUCCESS)
+					g_cs = CLS_Connecting;
+				else if(!CMqttClient::EvalError(nErr, bReconnect, bConnPending, g_bIntr, strErr))
+				{
+					if(bConnPending)
+						g_cs = CLS_Connecting;
+					else if(bReconnect)
+					{
+						g_csLast = g_cs;
+						g_cs = _cl_usleep(_USEC_FROM_MSEC(_RECONN_INTERVAL_MS), g_cs);
+					}
+					else if(g_bIntr)
+					{
+						g_csLast = g_cs;
+						g_cs = CLS_ShutDown;
+					}
+					else
+					{
+						g_csLast = g_cs;
+						g_cs = CLS_Err;
+					}
+				}
+				break;
+
+			case CLS_Reconnect:
+				if((nErr = mqttCl.reconnect()) == MOSQ_ERR_SUCCESS)
+					g_cs = CLS_Connecting;
+				else if(!CMqttClient::EvalError(nErr, bReconnect, bConnPending, g_bIntr, strErr))
+				{
+					if(bConnPending)
+						g_cs = CLS_Connecting;
+					else if(bReconnect)
+					{
+						g_csLast = g_cs;
+						g_cs = _cl_usleep(_USEC_FROM_MSEC(_RECONN_INTERVAL_MS), g_cs);
+					}
+					else if(g_bIntr)
+					{
+						g_csLast = g_cs;
+						g_cs = CLS_ShutDown;
+					}
+					else
+					{
+						g_csLast = g_cs;
+						g_cs = CLS_Err;
+					}
+				}
+				break;
+
+			case CLS_Connecting:
+				if(!mqttCl.TimedLoop(_NSEC_FROM_MSEC(_UPDATE_INTERVAL_MS), nErr, bReconnect, bConnPending, g_bIntr, strErr))
+				{
+					if(bReconnect)
+					{
+						g_csLast = g_cs;
+						g_cs = _cl_usleep(_USEC_FROM_MSEC(_RECONN_INTERVAL_MS), CLS_Unconnected);
+					}
+					else if(g_bIntr)
+					{
+						g_csLast = g_cs;
+						g_cs = CLS_ShutDown;
+					}
+					else if(!bConnPending)
+					{
+						g_csLast = g_cs;
+						g_cs = CLS_Err;
+					}
+				}
+				else if(g_bConnected)
+				{
+					g_cs = CLS_Connected;
+				}
+				break;
+
+			case CLS_Connected:
+				g_lf.Info("Connected to broker @ %s:%u.\n", cfg.GetBrokerAddr(), cfg.GetBrokerPort());
+				++nNumConn;
+				g_nSubcribed = 0;
+				g_cs = CLS_Subscribe;
+				break;
+
+			case CLS_Subscribe:
+				g_lf.Info("Subscribing control-topics ...\n");
+				if((nErr = _Subscribe(mqttCl, cfg.GetDefaultQOS(), subCtrlMap, _COUNTOF(subCtrlMap))) == MOSQ_ERR_SUCCESS)
+					g_cs = CLS_Subscribing;
+				else if(!CMqttClient::EvalError(nErr, bReconnect, bConnPending, g_bIntr, strErr))
+				{
+					if(bConnPending)
+						g_cs = CLS_Connecting;
+					else if(bReconnect)
+						g_cs = CLS_Unconnected;
+					else if(g_bIntr)
+					{
+						g_csLast = g_cs;
+						g_cs = CLS_ShutDown;
+					}
+					else
+					{
+						g_csLast = g_cs;
+						g_cs = CLS_Err;
+					}
+				}
+
+				break;
+
+			case CLS_Subscribing:
+				if(!mqttCl.TimedLoop(_NSEC_FROM_MSEC(_UPDATE_INTERVAL_MS), nErr, bReconnect, bConnPending, g_bIntr, strErr))
+				{
+					if(bReconnect)
+						g_cs = CLS_Unconnected;
+					else if(bConnPending)
+						g_cs = CLS_Connecting;
+					else if(g_bIntr)
+					{
+						g_csLast = g_cs;
+						g_cs = CLS_ShutDown;
+					}
+					else
+					{
+						g_csLast = g_cs;
+						g_cs = CLS_Err;
+					}
+				}
+				else if(g_nSubcribed == _COUNTOF(subCtrlMap))
+				{
+					g_cs = CLS_Subscribed;
+				}
+				break;
+
+			case CLS_Subscribed:
+				g_lf.Info("Subscriptions acknowledged.\n");
+				g_lf.Info("Enter SHM processing loop ...\n");
+				g_cs = CLS_ProcMsg;
+				break;
+
+			case CLS_ProcMsg:
+				if(mqttCl.TimedLoop(_NSEC_FROM_MSEC(_UPDATE_INTERVAL_MS), nErr, bReconnect, bConnPending, g_bIntr, strErr))
+				{
+#if _TRACK_TIMES
+					std::string s1, s2;
+					pc_time64_t elapsed;
+					g_nDbgCounter1 = g_nDbgCounter2 = g_nDbgCounter3 = 0;
+					g_pc.ClockTrigger();
+#endif	//	_TRACK_TIMES
+
+					_ProcessIncoming(mqttCl, vtbl, cfg, subCtrlMap);
+
+#if _TRACK_TIMES
+					if(g_nDbgCounter1)
+					{
+						elapsed = g_pc.ClockGetElapsed();
+						s1 = CProcessClock::Interval2String(elapsed);
+						s2 = CProcessClock::Interval2String(elapsed / g_nDbgCounter1);
+						TRACE("_ProcessIncoming (%lu variables): %s (%s per var)\n", g_nDbgCounter1, s1.c_str(), s2.c_str());
+					}
+					g_pc.ClockTrigger();
+#endif	//	_TRACK_TIMES
+
+					_SIG_BLOCK(&g_set);
+					vtbl.CheckShmAndPublish(cfg.GetDeviceID(), cfg.GetShmID(), mqttCl.GetMsgQueueSnd(), nLocked);
+					_SIG_UNBLOCK(&g_set);
+#if _TRACK_TIMES
+					g_nDbgCounter2 = mqttCl.GetMsgQueueSnd().Size();
+					if(g_nDbgCounter2)
+					{
+						elapsed = g_pc.ClockGetElapsed();
+						s1 = CProcessClock::Interval2String(elapsed);
+						s2 = CProcessClock::Interval2String(elapsed / g_nDbgCounter2);
+						TRACE("CheckShmAndPublish (%lu variables): %s (%s per var)\n", g_nDbgCounter2, s1.c_str(), s2.c_str());
+						g_pc.ClockTrigger();
+					}
+					g_nDbgCounter3 = 
+#endif	//	_TRACK_TIMES
+
+					_ProcessOutgoing(mqttCl);
+
+#if _TRACK_TIMES
+					if(g_nDbgCounter3)
+					{
+						elapsed = g_pc.ClockGetElapsed();
+						s1 = CProcessClock::Interval2String(elapsed);
+						s2 = CProcessClock::Interval2String(elapsed / g_nDbgCounter3);
+						TRACE("_ProcessOutgoing (%lu variables): %s (%s per var)\n", g_nDbgCounter3, s1.c_str(), s2.c_str());
+					}
+#endif	//	_TRACK_TIMES
+				}
+				else
+				{
+					if(bReconnect)
+						g_cs = CLS_Unconnected;
+					else if(bConnPending)
+						g_cs = CLS_Connecting;
+					else if(g_bIntr)
+					{
+						g_csLast = g_cs;
+						g_cs = CLS_ShutDown;
+					}
+					else
+					{
+						g_csLast = g_cs;
+						g_cs = CLS_Err;
+					}
+				}
+				break;
+
+			case CLS_Err:
+				g_lf.Error("[%s] - %s\n", _GetClientStateString(g_csLast), strErr.c_str());
+				TRACE("Error: %s\n", strErr.c_str());
+				g_fZombie = true;
+				g_cs = CLS_ShutDown;
+				break;
+
+			case CLS_ShutDown:
+				if(g_bIntr && g_nLastSig >= 0)
+				{
+					g_lf.Info("Received signal '%s'.\n", strsignal(g_nLastSig));
+					g_nLastSig = -1;
+				}
+				g_lf.Info("Process shutting down ...\n");
+				if(g_csLast >= CLS_Subscribed)
+					g_cs = CLS_Unsubscribe;
+				else if(g_csLast >= CLS_Connected)
+					g_cs = CLS_Disconnect;
+				else if(g_csLast > CLS_NotInit)
+					g_cs = CLS_Cleanup;
+				else if((g_fPauseImp || g_fPauseCmd) && !g_bIntr)
+					g_cs = CLS_Paused;
+				else
+					g_cs = CLS_Exit;
+				break;
+
+			case CLS_Unsubscribe:
+				g_lf.Info("Unsubscribe control-topics.\n");
+				_Unsubscribe(mqttCl, subCtrlMap, _COUNTOF(subCtrlMap));
+				g_nSubcribed = 0;
+				g_cs = CLS_Disconnect;
+				break;
+
+			case CLS_Disconnect:
+				g_lf.Info("Disconnect from broker @ %s:%u.\n", cfg.GetBrokerAddr(), cfg.GetBrokerPort());
+				mqttCl.disconnect();
+				g_bConnected = false;
+				g_cs = CLS_Cleanup;
+				break;
+
+			case CLS_Cleanup:
+				g_lf.Info("Clean up.\n");
+				CMqttClient::Cleanup();
+				if((g_fPauseImp || g_fPauseCmd) && !g_bIntr)
+					g_cs = CLS_Paused;
+				else
+					g_cs = CLS_Exit;
+				break;
+
+			case CLS_Paused:
+				if(!g_fPauseImp && !g_fPauseCmd)
+				{
+					if(!g_bIntr)
+						g_cs = CLS_NotInit;
+					else
+						g_cs = CLS_Exit;
+				}
+				else
+				{
+					usleep(_USEC_FROM_MSEC(_UPDATE_INTERVAL_MS));
+					continue;
+				}
+				break;
+
+			case CLS_Exit:
+				g_fRun = false;
+				break;
+			}
+
+			nUsecWorkTime = pcWork.ClockGetElapsed() / 1000;
+		}
+	}
+	while(false);
+
+	////////////////////////////////////////////////////////////////////////////////////////////////
+
+	if(hShm)
+	{
+		if(pShm)
+		{
+			g_lf.Info("Release SHM Pointer.\n");
+			::GfaIpcReleasePointer(hShm, pShm);
+		}
+
+		g_lf.Info("Release SHM Handle.\n");
+	    ::GfaIpcReleaseSHM(hShm);
+    }
+	
+	if(g_fZombie)
+	{
+		if(hAC)
+			::GfaIpcAppCtrlSetState(hAC, GIAS_Zombie);
+		TRACE("Enter Zombie state ...\n");
+		g_lf.Warning("Enter Zombie state ...\n");
+		g_lf.Flush();
+		pause();
+
+		if(g_nLastSig >= 0)
+			g_lf.Info("Received signal '%s'.\n", strsignal(g_nLastSig));
+	}
+
+	if(hAC)
+	{
+		g_lf.Info("Enter state %s ...\n", ::GfaIpcAppCtrlGetStateText(GIAS_Terminating));
+		::GfaIpcAppCtrlSetState(hAC, GIAS_Terminating);
+		g_lf.Info("Releasing App Control ...\n");
+		::GfaIpcAppCtrlRelease(hAC);
+	}
+
+	g_lf.Info("Process exit.\n\n");
+	g_lf.Close();
+	CLogfile::StdErr("MqttCl exit.\n");
+	return nRet;
+}

+ 342 - 0
mqttcl/mqttcfg.cpp

@@ -0,0 +1,342 @@
+#include <stdio.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "fileutil.h"
+#include "strutil.h"
+#include "mqttcfg.h"
+#include "debug.h"
+
+#define _DEFAULT_CFG_FILE_NAME					"mqttcl.cfg.json"
+
+#define _CFG_KEY_NAME_BROKER_ADDR				"brokerAddr"
+#define _CFG_KEY_NAME_BROKER_PORT				"brokerPort"
+#define _CFG_KEY_NAME_DEFAULT_QOS				"defaultQos"
+#define _CFG_KEY_NAME_DEFAULT_RETAIN			"defaultRetain"
+#define _CFG_KEY_NAME_DEVICE_PREFIX				"devicePrefix"
+#define _CFG_KEY_NAME_DEVICE_ID					"deviceID"
+#define _CFG_KEY_NAME_TLS_MODE					"tlsMode"
+#define _CFG_KEY_NAME_TLS_CA_CRT_FILE			"tlsCaCrtFile"
+#define _CFG_KEY_NAME_TLS_CL_CRT_FILE			"tlsClCrtFile"
+#define _CFG_KEY_NAME_TLS_CL_KEY_FILE			"tlsClKeyFile"
+#define _CFG_KEY_NAME_TLS_PSK					"tlsPsk"
+
+/////////////////////////////////////////////////////////////////////////////
+
+CMqttClConfig::CMqttClConfig(const char *pszShmUuid) : m_strShmID(formatString("SHM-%s", strucase(pszShmUuid).c_str())), m_nBrokerPort(0), m_nDefaultQOS(0), m_bDefaultRetain(false), m_nTlsMode(0)
+{
+}
+
+CMqttClConfig::~CMqttClConfig(void)
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// LoadCfg
+
+bool CMqttClConfig::LoadCfg(const char *pszCfgFilePath, CLogfile &rlf)
+{
+	char szCfgFilePath[PATH_MAX];
+	std::string strErr;
+
+	if(!pszCfgFilePath)
+	{	// use default config file path
+		pszCfgFilePath = ::BuildCanonicalFilePath(NULL, _DEFAULT_CFG_FILE_NAME, szCfgFilePath, sizeof(szCfgFilePath));
+	}
+
+	/////////////////////////////////////////////////////////////////////////
+	// load and parse config file
+
+	json_t *pjtCfg;
+	json_error_t err;
+
+	if(!(pjtCfg = ::json_load_file(pszCfgFilePath, JSON_REJECT_DUPLICATES, &err)))
+	{
+		rlf.Error("CMqttClConfig::LoadCfg: %s!\n", err.text);
+		return false;
+	}
+
+	CJson_t jtCfg(pjtCfg, true);
+
+	/////////////////////////////////////////////////////////////////////////
+	// tlsMode
+
+	if(!GetIntValue(jtCfg, _CFG_KEY_NAME_TLS_MODE, m_nTlsMode, strErr))
+	{
+		m_nTlsMode = MQTTCL_TLS_MODE_OFF;
+		rlf.Warning("CMqttClConfig::LoadCfg: %s! TLS will not be used!\n", strErr.c_str());
+	}
+	else if(m_nTlsMode < MQTTCL_TLS_MODE_OFF)
+	{
+		rlf.Warning("CMqttClConfig::LoadCfg: Invalid TLS mode: %d! TLS will be disabled!\n", m_nTlsMode);
+		m_nTlsMode = MQTTCL_TLS_MODE_OFF;
+	}
+	else if(m_nTlsMode > MQTTCL_TLS_MODE_PSK)
+	{
+		rlf.Warning("CMqttClConfig::LoadCfg: Invalid TLS mode: %d! TLS mode will be set to PSK!\n", m_nTlsMode);
+		m_nTlsMode = MQTTCL_TLS_MODE_PSK;
+	}
+
+	/////////////////////////////////////////////////////////////////////////
+	// brokerAddr
+
+	if(!GetStringValue(jtCfg, _CFG_KEY_NAME_BROKER_ADDR, m_strBrokerAddr, strErr))
+	{
+		rlf.Error("CMqttClConfig::LoadCfg: %s!\n", strErr.c_str());
+		return false;
+	}
+
+	/////////////////////////////////////////////////////////////////////////
+	// brokerPort
+
+	if(!GetIntValue(jtCfg, _CFG_KEY_NAME_BROKER_PORT, m_nBrokerPort, strErr))
+	{
+		m_nBrokerPort = (m_nTlsMode > MQTTCL_TLS_MODE_OFF) ? 8883 : 1883;
+		rlf.Warning("CMqttClConfig::LoadCfg: %s! Using default broker port %d!\n", strErr.c_str(), m_nBrokerPort);
+	}
+	else if(m_nBrokerPort < 0 || m_nBrokerPort > 0xffff)
+	{
+		rlf.Error("CMqttClConfig::LoadCfg: Invalid broker port number: %d!\n", m_nBrokerPort);
+		return false;
+	}
+
+	/////////////////////////////////////////////////////////////////////////
+	// defaultQos
+
+	if(!GetIntValue(jtCfg, _CFG_KEY_NAME_DEFAULT_QOS, m_nDefaultQOS, strErr))
+	{
+		m_nDefaultQOS = MQTTCL_DEFAULT_QOS;
+		rlf.Warning("CMqttClConfig::LoadCfg: %s! Using default QOS: %d!\n", strErr.c_str(), m_nDefaultQOS);
+	}
+	else if(m_nDefaultQOS < MQTTCL_MIN_QOS)
+	{
+		rlf.Warning("CMqttClConfig::LoadCfg: Invalid QOS: %d - using %d!\n", m_nDefaultQOS, MQTTCL_MIN_QOS);
+		m_nDefaultQOS = MQTTCL_MIN_QOS;
+	}
+	else if(m_nDefaultQOS > MQTTCL_MAX_QOS)
+	{
+		rlf.Warning("CMqttClConfig::LoadCfg: Invalid QOS: %d - using %d!\n", m_nDefaultQOS, MQTTCL_MAX_QOS);
+		m_nDefaultQOS = MQTTCL_MAX_QOS;
+	}
+
+	/////////////////////////////////////////////////////////////////////////
+	// defaultRetain
+
+	if(!GetBoolValue(jtCfg, _CFG_KEY_NAME_DEFAULT_RETAIN, m_bDefaultRetain, strErr))
+	{
+		m_bDefaultRetain = MQTTCL_DEFAULT_RETAIN;
+		rlf.Warning("CMqttClConfig::LoadCfg: %s! Using default retain \"%s\"!\n", strErr.c_str(), m_bDefaultRetain ? "true" : "false");
+	}
+
+	/////////////////////////////////////////////////////////////////////////
+	// devicePrefix
+
+	if(!GetStringValue(jtCfg, _CFG_KEY_NAME_DEVICE_PREFIX, m_strDevicePrefix, strErr))
+	{
+		m_strDevicePrefix = MQTTCL_DEVICE_PREFIX;
+		rlf.Warning("CMqttClConfig::LoadCfg: %s! Using default device prefix \"%s\"!\n", strErr.c_str(), m_strDevicePrefix.c_str());
+	}
+
+	/////////////////////////////////////////////////////////////////////////
+	// deviceID
+
+	if(GetStringValue(jtCfg, _CFG_KEY_NAME_DEVICE_ID, m_strDeviceID, strErr))
+	{
+		rlf.Info("CMqttClConfig::LoadCfg: Using configured device ID: \"%s\"!\n", m_strDeviceID.c_str());
+	}
+	else
+	{
+		m_strDeviceID = CreateDeviceID(m_strDevicePrefix.c_str());
+	}
+
+	/////////////////////////////////////////////////////////////////////////
+	// m_nTlsMode == (MQTTCL_TLS_MODE_CRT || MQTTCL_TLS_MODE_PSK)
+
+	if(m_nTlsMode == MQTTCL_TLS_MODE_CRT)
+	{
+		/////////////////////////////////////////////////////////////////////
+		// tlsCaCrtFile
+
+		if(!GetStringValue(jtCfg, _CFG_KEY_NAME_TLS_CA_CRT_FILE, m_strTlsCaCrtFile, strErr))
+		{
+			rlf.Error("CMqttClConfig::LoadCfg: %s!\n", strErr.c_str());
+			return false;
+		}
+
+		/////////////////////////////////////////////////////////////////////
+		// tlsClCrtFile
+
+		if(!GetStringValue(jtCfg, _CFG_KEY_NAME_TLS_CL_CRT_FILE, m_strTlsClCrtFile, strErr))
+		{
+			rlf.Error("CMqttClConfig::LoadCfg: %s!\n", strErr.c_str());
+			return false;
+		}
+
+		/////////////////////////////////////////////////////////////////////
+		// tlsClKeyFile
+
+		if(!GetStringValue(jtCfg, _CFG_KEY_NAME_TLS_CL_KEY_FILE, m_strTlsClKeyFile, strErr))
+		{
+			rlf.Error("CMqttClConfig::LoadCfg: %s!\n", strErr.c_str());
+			return false;
+		}
+	}
+	else if(m_nTlsMode == MQTTCL_TLS_MODE_PSK)
+	{
+		/////////////////////////////////////////////////////////////////////
+		// tlsPsk
+
+		if(!GetStringValue(jtCfg, _CFG_KEY_NAME_TLS_PSK, m_strTlsPSK, strErr))
+		{
+			rlf.Error("CMqttClConfig::LoadCfg: %s!\n", strErr.c_str());
+			return false;
+		}
+	}
+
+	return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+bool CMqttClConfig::GetValue(CJson_t &rjtParent, const char *pszKey, CJson_t &rjtVal, std::string &strErr)
+{
+	if(!rjtParent.GetValue(pszKey, rjtVal))
+	{
+		strErr = formatString("Key \"%s\" not found", pszKey);
+		return false;
+	}
+	return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+bool CMqttClConfig::GetBoolValue(CJson_t &rjtParent, const char *pszKey, bool &rbVal, std::string &strErr)
+{
+	CJson_t jtVal;
+
+	if(GetValue(rjtParent, pszKey, jtVal, strErr))
+	{
+		int nType;
+
+		switch((nType = jtVal.Type()))
+		{
+		case JSON_TRUE:
+		case JSON_FALSE:
+			rbVal = (nType == JSON_TRUE);
+			return true;
+		default:
+			strErr = formatString("\"%s\" (type=%d) is not a boolean value", pszKey, nType);
+			return false;
+		}
+	}
+
+	return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+bool CMqttClConfig::GetIntValue(CJson_t &rjtParent, const char *pszKey, int &rnVal, std::string &strErr)
+{
+	CJson_t jtVal;
+
+	if(GetValue(rjtParent, pszKey, jtVal, strErr))
+	{
+		if(json_is_integer(jtVal.operator const json_t*()))
+		{
+			rnVal = (int)::json_integer_value(jtVal);
+			return true;
+		}
+
+		strErr = formatString("\"%s\" (type=%d) is not an integer value", pszKey, jtVal.Type());
+	}
+
+	return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+bool CMqttClConfig::GetStringValue(CJson_t &rjtParent, const char *pszKey, std::string &rstrVal, std::string &strErr)
+{
+	CJson_t jtVal;
+
+	if(GetValue(rjtParent, pszKey, jtVal, strErr))
+	{
+		if(json_is_string(jtVal.operator const json_t*()))
+		{
+			rstrVal = ::json_string_value(jtVal);
+			return true;
+		}
+
+		strErr = formatString("\"%s\" (type=%d) is not a string value", pszKey, jtVal.Type());
+	}
+
+	return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+sa_family_t CMqttClConfig::GetDevIdInterfaceName(char *pszItfName, size_t nCChItfName, const char *pszRequested)
+{
+	sa_family_t nFamily = 0;
+	struct ifaddrs *ifaddr, *ifa;
+	memset(pszItfName, 0, nCChItfName);
+
+	if(getifaddrs(&ifaddr) == 0)
+	{
+		for(ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next)
+		{
+			if(!ifa->ifa_addr || (ifa->ifa_flags & IFF_LOOPBACK))
+				continue;
+			if(	ifa->ifa_addr->sa_family == AF_INET ||
+				ifa->ifa_addr->sa_family == AF_INET6)
+			{
+				if(!strcmp(ifa->ifa_name, pszRequested))
+				{
+					strncpy(pszItfName, ifa->ifa_name, nCChItfName - 1);
+					nFamily = ifa->ifa_addr->sa_family;
+					break;
+				}
+				else if(!nFamily)
+				{
+					strncpy(pszItfName, ifa->ifa_name, nCChItfName - 1);
+					nFamily = ifa->ifa_addr->sa_family;
+				}
+			}
+		}
+
+		freeifaddrs(ifaddr);
+	}
+
+	return nFamily;
+}
+
+const char* CMqttClConfig::GetMacAddress(std::string &s)
+{
+	int fd;
+	struct ifreq ifr;
+	s.clear();
+
+	if((ifr.ifr_addr.sa_family = CMqttClConfig::GetDevIdInterfaceName(ifr.ifr_name, sizeof(ifr.ifr_name), "eth0")))
+	{
+		if((fd = socket(ifr.ifr_addr.sa_family, SOCK_DGRAM, 0)) >= 0)
+		{
+			if(	(ioctl(fd, SIOCGIFHWADDR, &ifr) == 0) &&
+				(ifr.ifr_hwaddr.sa_family == ARPHRD_ETHER))
+			{
+				const char *m = (const char*)ifr.ifr_hwaddr.sa_data;
+				s = formatString("%0.2hhX:%0.2hhX:%0.2hhX:%0.2hhX:%0.2hhX:%0.2hhX" , m[0], m[1], m[2], m[3], m[4], m[5]);
+			}
+
+			close(fd);
+		}
+	}
+
+	return s.c_str();
+}
+
+std::string CMqttClConfig::CreateDeviceID(const char *pszDevicePrefix)
+{
+	std::string m, s;
+	s = formatString("%s-%s", pszDevicePrefix, CMqttClConfig::GetMacAddress(m));
+	return s;
+}

+ 123 - 0
mqttcl/mqttcfg.h

@@ -0,0 +1,123 @@
+// mqttcfg.h :
+//
+
+#if !defined(AGD_MQTTCFG_H__35CD3AFA_36BC_442B_83B7_D12E407E6B3A__INCLUDED_)
+#define AGD_MQTTCFG_H__35CD3AFA_36BC_442B_83B7_D12E407E6B3A__INCLUDED_
+
+#include <string>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <linux/if_arp.h>
+#include <netinet/in.h>
+#include <ifaddrs.h>
+#include <sys/ioctl.h>
+#ifndef _LIBBUILD
+#include <gfa/svc/common/logfile.h>
+#include <gfa/svc/mqttcl/mqttjson.h>
+#else	//	_LIBBUILD
+#include "common/logfile.h"
+#include "mqttjson.h"
+#endif	//	_LIBBUILD
+
+#ifdef __cplusplus
+
+/////////////////////////////////////////////////////////////////////////////
+// mqttcfg.h - Declarations:
+
+#define MQTTCL_TLS_MODE_OFF		0 // no TLS
+#define MQTTCL_TLS_MODE_CRT		1 // TLS using certificates
+#define MQTTCL_TLS_MODE_PSK		2 // TLS using preshared key
+
+#define MQTTCL_MIN_QOS			0
+#define MQTTCL_MAX_QOS			2
+#define MQTTCL_DEFAULT_QOS		MQTTCL_MAX_QOS
+
+#define MQTTCL_DEFAULT_RETAIN	false
+#define MQTTCL_DEVICE_PREFIX	"GfA"
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CMqttClConfig
+{
+public:
+	CMqttClConfig(const char *pszShmUuid);
+	virtual ~CMqttClConfig(void);
+	
+	bool LoadCfg(const char *pszCfgFilePath, CLogfile &rlf);
+
+	inline const char* GetBrokerAddr(void) const {
+		return m_strBrokerAddr.empty() ? NULL : m_strBrokerAddr.c_str();
+	}
+	
+	inline unsigned short GetBrokerPort(void) const {
+		return (unsigned short)m_nBrokerPort;
+	}
+	
+	inline int GetDefaultQOS(void) const {
+		return m_nDefaultQOS;
+	}
+	
+	inline int GetTLSMode(void) const {
+		return m_nTlsMode;
+	}
+	
+	inline bool GetDefaultRetain(void) const {
+		return m_bDefaultRetain;
+	}
+
+	inline const char* GetDevicePrefix(void) const {
+		return m_strDevicePrefix.empty() ? NULL : m_strDevicePrefix.c_str();
+	}
+
+	inline const char* GetTlsCaCrtFile(void) const {
+		return m_strTlsCaCrtFile.empty() ? NULL : m_strTlsCaCrtFile.c_str();
+	}
+
+	inline const char* GetTlsClCrtFile(void) const {
+		return m_strTlsClCrtFile.empty() ? NULL : m_strTlsClCrtFile.c_str();
+	}
+
+	inline const char* GetTlsClKeyFile(void) const {
+		return m_strTlsClKeyFile.empty() ? NULL : m_strTlsClKeyFile.c_str();
+	}
+
+	inline const char* GetTlsPSK(void) const {
+		return m_strTlsPSK.empty() ? NULL : m_strTlsPSK.c_str();
+	}
+
+	inline const char* GetDeviceID(void) const {
+		return m_strDeviceID.empty() ? NULL : m_strDeviceID.c_str();
+	}
+
+	inline const char* GetShmID(void) const {
+		return m_strShmID.empty() ? NULL : m_strShmID.c_str();
+	}
+
+	static sa_family_t GetDevIdInterfaceName(char *pszItfName, size_t nCChItfName, const char *pszRequested);
+	static const char* GetMacAddress(std::string &s);
+	static std::string CreateDeviceID(const char *pszDevicePrefix);
+
+private:
+	bool GetValue(CJson_t &rjtParent, const char *pszKey, CJson_t &rjtVal, std::string &strErr);
+	bool GetBoolValue(CJson_t &rjtParent, const char *pszKey, bool &rbVal, std::string &strErr);
+	bool GetIntValue(CJson_t &rjtParent, const char *pszKey, int &rnVal, std::string &strErr);
+	bool GetStringValue(CJson_t &rjtParent, const char *pszKey, std::string &rstrVal, std::string &strErr);
+
+private:
+	std::string	m_strBrokerAddr;
+	std::string	m_strDevicePrefix;
+	std::string	m_strTlsCaCrtFile;
+	std::string	m_strTlsClCrtFile;
+	std::string	m_strTlsClKeyFile;
+	std::string	m_strTlsPSK;
+	std::string	m_strDeviceID;
+	std::string	m_strShmID;
+	int			m_nBrokerPort;
+	int			m_nDefaultQOS;
+	bool		m_bDefaultRetain;
+	int			m_nTlsMode;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	__cplusplus
+#endif	//	!defined(AGD_MQTTCFG_H__35CD3AFA_36BC_442B_83B7_D12E407E6B3A__INCLUDED_)

+ 76 - 0
mqttcl/mqttcl.pro

@@ -0,0 +1,76 @@
+TEMPLATE = lib
+CONFIG   += c++11
+CONFIG   -= qt
+CONFIG   += staticlib
+
+QMAKE_CXXFLAGS_DEBUG += -Wno-unused-parameter -Wno-unused-but-set-variable -pthread
+QMAKE_CXXFLAGS += -Wstrict-aliasing=0 -pthread
+QMAKE_LIBS += -lm -lstdc++ -pthread -lgfaipc -ljansson -l:libcommon.a -lmosquittopp -lmosquitto
+QMAKE_LIBDIR += $$OUT_PWD/../common $$[QT_SYSROOT]/usr/lib/gfa
+QMAKE_RPATHDIR += /usr/lib/gfa
+
+QMAKE_CXXFLAGS += -D_MQTTCL -D_LIBBUILD -Wno-deprecated-declarations
+QMAKE_CFLAGS += -D_MQTTCL -D_LIBBUILD -Wno-deprecated-declarations
+
+_LIB_PATH = $$[QT_SYSROOT]/usr/lib/gfa/svc/
+_INC_PATH = $$[QT_SYSROOT]/usr/include/gfa/svc/mqttcl/
+
+CONFIG(debug, debug|release) {
+	QMAKE_CXXFLAGS -= -Os
+	QMAKE_CFLAGS -= -Os
+    QMAKE_CXXFLAGS += -D_DEBUG
+    QMAKE_CFLAGS += -D_DEBUG
+	TARGET = mqttcld
+	QMAKE_CLEAN += libmqttcld.a
+}
+
+CONFIG(release, debug|release) {
+    QMAKE_CXXFLAGS += -Wno-unused-parameter -Wno-unused-but-set-variable
+    QMAKE_CFLAGS += -Wno-unused-parameter -Wno-unused-but-set-variable
+	TARGET = mqttcl
+	QMAKE_CLEAN += libmqttcl.a
+}
+
+linux-buildroot-g++ {
+    QMAKE_CXXFLAGS += -D_TARGET_BUILD
+    QMAKE_CFLAGS += -D_TARGET_BUILD
+    QMAKE_CXXFLAGS += -DSITARA_BUILD
+    QMAKE_CFLAGS += -DSITARA_BUILD
+}
+
+INCLUDEPATH += ../  ../common $$(GEBGFADEV)
+
+SOURCES += \
+    mqttclient.cpp \
+    mqttmsg.cpp \
+    mqttmsgqueue.cpp \
+    mqttmsgpool.cpp \
+    mqttvartbl.cpp \
+    mqttcfg.cpp
+
+HEADERS += mqttclient.h \
+    mqttmsg.h \
+    mqttvar.h \
+    mqttcfg.h \
+    mqttjson.h \
+    mqttdbg.h
+
+includes.path = $$_INC_PATH
+includes.extra += -$(INSTALL_FILE) $$PWD/mqttclient.h $(INSTALL_ROOT)$$includes.path
+includes.extra += $$escape_expand(\\n\\t)-$(INSTALL_FILE) $$PWD/mqttmsg.h $(INSTALL_ROOT)$$includes.path
+includes.extra += $$escape_expand(\\n\\t)-$(INSTALL_FILE) $$PWD/mqttvar.h $(INSTALL_ROOT)$$includes.path
+includes.extra += $$escape_expand(\\n\\t)-$(INSTALL_FILE) $$PWD/mqttcfg.h $(INSTALL_ROOT)$$includes.path
+includes.extra += $$escape_expand(\\n\\t)-$(INSTALL_FILE) $$PWD/mqttjson.h $(INSTALL_ROOT)$$includes.path
+includes.extra += $$escape_expand(\\n\\t)-$(INSTALL_FILE) $$PWD/mqttdbg.h $(INSTALL_ROOT)$$includes.path
+includes.uninstall += -$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/mqttclient.h
+includes.uninstall += $$escape_expand(\\n\\t)-$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/mqttmsg.h
+includes.uninstall += $$escape_expand(\\n\\t)-$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/mqttvar.h
+includes.uninstall += $$escape_expand(\\n\\t)-$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/mqttcfg.h
+includes.uninstall += $$escape_expand(\\n\\t)-$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/mqttjson.h
+includes.uninstall += $$escape_expand(\\n\\t)-$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/mqttdbg.h
+INSTALLS += includes
+
+library.path = $$_LIB_PATH
+library.extra += -$(INSTALL_FILE) $(TARGET) $(INSTALL_ROOT)$$library.path/$(TARGET)
+library.uninstall += -$(DEL_FILE) $(INSTALL_ROOT)$$library.path/$(TARGET)
+INSTALLS += library

+ 362 - 0
mqttcl/mqttclient.cpp

@@ -0,0 +1,362 @@
+#include <errno.h>
+#include <unistd.h>
+#include "mqttclient.h"
+
+using namespace mosqpp;
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+template <typename T>
+static T _min(T v1, T v2)
+{
+	return v1 < v2 ? v1 : v2;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+#define MQTTCL_DEFAULT_LOOP_TIMEOUT					30
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+CMqttClient::CMqttClient(const char *id) :
+							mosquittopp(id),
+							m_pEvtCallback(NULL),
+							m_evtMask(0),
+							m_pUserParam(NULL)
+{
+}
+
+CMqttClient::~CMqttClient(void)
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+int CMqttClient::Init(void)
+{
+	return lib_init();
+}
+
+int CMqttClient::Cleanup(void)
+{
+	return lib_cleanup();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+int CMqttClient::publish(CMqttMessage *pMsg)
+{
+	return mosquittopp::publish(&pMsg->mid, pMsg->topic, pMsg->payloadlen, pMsg->payload, pMsg->qos, pMsg->retain);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+CMqttMessage* CMqttClient::PopRcvMsg(void)
+{
+	return m_rcvMsg.Pop();
+}
+
+void CMqttClient::PushRcvMsg(CMqttMessage *pMsg)
+{
+	m_rcvMsg.Push(pMsg);
+}
+
+CMqttMessage* CMqttClient::PopSndMsg(void)
+{
+	return m_sndMsg.Pop();
+}
+
+void CMqttClient::PushSndMsg(CMqttMessage *pMsg)
+{
+	m_sndMsg.Push(pMsg);
+}
+
+int CMqttClient::timed_loop(pc_time64_t nLoopTime)
+{
+	int nRet, nLoopTimeout;
+	pc_time64_t nRemaining = nLoopTime;
+	m_pc.ClockTrigger();
+
+	do
+	{
+		nLoopTimeout = _min(MQTTCL_DEFAULT_LOOP_TIMEOUT, (int)(nRemaining / _PC_NS_PER_MS));
+		if((nRet = loop(nLoopTimeout)) != MOSQ_ERR_SUCCESS)
+			break;
+		if((nRemaining = nLoopTime - m_pc.ClockGetElapsed()) > (10 * _PC_NS_PER_MS))
+		{
+			m_pc.MSleep(10);
+			nRemaining = nLoopTime - m_pc.ClockGetElapsed();
+		}
+	}
+	while(nRemaining > (MQTTCL_DEFAULT_LOOP_TIMEOUT * _PC_NS_PER_MS));
+
+	return nRet;
+}
+/*
+MOSQ_ERR_CONN_PENDING = -1,
+MOSQ_ERR_SUCCESS = 0,
+MOSQ_ERR_NOMEM = 1,
+MOSQ_ERR_PROTOCOL = 2,
+MOSQ_ERR_INVAL = 3,
+MOSQ_ERR_NO_CONN = 4,
+MOSQ_ERR_CONN_REFUSED = 5,
+MOSQ_ERR_NOT_FOUND = 6,
+MOSQ_ERR_CONN_LOST = 7,
+MOSQ_ERR_TLS = 8,
+MOSQ_ERR_PAYLOAD_SIZE = 9,
+MOSQ_ERR_NOT_SUPPORTED = 10,
+MOSQ_ERR_AUTH = 11,
+MOSQ_ERR_ACL_DENIED = 12,
+MOSQ_ERR_UNKNOWN = 13,
+MOSQ_ERR_ERRNO = 14,
+MOSQ_ERR_EAI = 15,
+MOSQ_ERR_PROXY = 16
+*/
+
+bool CMqttClient::EvalError(int nErr, bool &rbReconnect, bool &rbConnPending, bool &rbIntr, std::string &strErr)
+{
+	bool bRet;
+	rbReconnect = rbConnPending = false;
+	if(rbIntr)
+		return false;
+
+	switch(nErr)
+	{
+	case MOSQ_ERR_SUCCESS:		// no error
+		rbReconnect = false;
+		rbConnPending = false;
+		bRet = true;
+		break;
+
+	case MOSQ_ERR_CONN_PENDING:	// just wait
+		rbReconnect = false;
+		rbConnPending = true;
+		bRet = false;
+		break;
+
+	case MOSQ_ERR_NO_CONN:		// (maybe) recoverable errors
+	case MOSQ_ERR_CONN_LOST:
+		rbReconnect = true;
+		rbConnPending = false;
+		bRet = false;
+		break;
+	
+	case MOSQ_ERR_ERRNO:		// system error
+		switch(errno)
+		{
+		case ECONNREFUSED:
+		case ENETUNREACH:
+		case ENETRESET:
+		case ENETDOWN:
+		case ETIMEDOUT:
+		case ECONNRESET:
+		case ECONNABORTED:
+		case ENOTCONN:
+		case ESHUTDOWN:
+		case EHOSTDOWN:
+		case EHOSTUNREACH:
+			rbReconnect = true;
+			rbConnPending = false;
+			bRet = false;
+			break;
+
+		case EISCONN:
+		case EALREADY:
+		case EINPROGRESS:
+			rbReconnect = false;
+			rbConnPending = true;
+			bRet = false;
+			break;
+
+		case EINTR:
+			rbIntr = true;
+            // fall through
+		default:
+			TRACE("errno: %d\n", errno);
+			rbReconnect = false;
+			rbConnPending = false;
+			bRet = false;
+			break;
+		}
+		break;
+
+	default:					// unrecoverable errors
+		rbReconnect = false;
+		rbConnPending = false;
+		bRet = false;
+		break;
+	}
+
+	if(!bRet)
+	{
+		const char *pszErr = ::mosquitto_strerror(nErr);
+		strErr = pszErr;
+	}
+
+	return bRet;
+}
+
+bool CMqttClient::TimedLoop(pc_time64_t nLoopTime /* in nano seconds */, int &rnErr, bool &rbReconnect, bool &rbConnPending, bool &rbIntr, std::string &strErr)
+{
+	bool bRet;
+	int nLoopTimeout;
+	pc_time64_t nRemaining = nLoopTime;
+
+	m_pc.ClockTrigger();
+
+	do
+	{
+		nLoopTimeout = (int)_min((pc_time64_t)(MQTTCL_DEFAULT_LOOP_TIMEOUT * _PC_NS_PER_MS), nRemaining);
+		rnErr = loop(nLoopTimeout / _PC_NS_PER_MS);
+		
+		if(rbIntr)
+		{
+			rbReconnect = false;
+			rbConnPending = false;
+			bRet = false;
+			rnErr = MOSQ_ERR_ERRNO;
+			break;
+		}
+		
+		bRet = EvalError(rnErr, rbReconnect, rbConnPending, rbIntr, strErr);
+		nRemaining = nLoopTime - m_pc.ClockGetElapsed();
+
+		if(bRet && !rbReconnect && (nRemaining > (10 * _PC_NS_PER_MS)))
+		{
+			if(usleep(10 * _PC_US_PER_MS) < 0)
+			{
+				if(errno == EINTR)
+				{
+					rbReconnect = false;
+					rbConnPending = false;
+					bRet = false;
+					rnErr = MOSQ_ERR_ERRNO;
+					rbIntr = true;
+					break;
+				}
+			}
+			nRemaining = nLoopTime - m_pc.ClockGetElapsed();
+		}
+		else
+		{
+			break;
+		}
+	}
+	while(nRemaining >= (MQTTCL_DEFAULT_LOOP_TIMEOUT * _PC_NS_PER_MS));
+
+	return bRet;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void CMqttClient::on_connect(int rc)
+{
+	if(m_pEvtCallback && m_evtMask & NEVT_Connect)
+	{
+		MQTT_GENERIC_NOTIFICATION ntf;
+		ntf.evt	= NEVT_Connect;
+		ntf.con.rc = rc;
+		(*m_pEvtCallback)(&ntf, m_pUserParam);
+	}
+}
+
+void CMqttClient::on_connect_with_flags(int rc, int flags)
+{
+	if(m_pEvtCallback && m_evtMask & NEVT_Connect_With_Flags)
+	{
+		MQTT_GENERIC_NOTIFICATION ntf;
+		ntf.evt	= NEVT_Connect_With_Flags;
+		ntf.con.rc = rc;
+		ntf.con.flags = flags;
+		(*m_pEvtCallback)(&ntf, m_pUserParam);
+	}
+}
+
+void CMqttClient::on_disconnect(int rc)
+{
+	if(m_pEvtCallback && m_evtMask & NEVT_Disconnect)
+	{
+		MQTT_GENERIC_NOTIFICATION ntf;
+		ntf.evt	= NEVT_Disconnect;
+		ntf.con.rc = rc;
+		(*m_pEvtCallback)(&ntf, m_pUserParam);
+	}
+}
+
+void CMqttClient::on_subscribe(int mid, int qos_count, const int *granted_qos)
+{
+	if(m_pEvtCallback && m_evtMask & NEVT_Subscribe)
+	{
+		MQTT_GENERIC_NOTIFICATION ntf;
+		ntf.evt	= NEVT_Subscribe;
+		ntf.sub.mid = mid;
+		ntf.sub.qos_count = qos_count;
+		ntf.sub.granted_qos = granted_qos;
+		(*m_pEvtCallback)(&ntf, m_pUserParam);
+	}
+}
+
+void CMqttClient::on_unsubscribe(int mid)
+{
+	if(m_pEvtCallback && m_evtMask & NEVT_Unsubscribe)
+	{
+		MQTT_GENERIC_NOTIFICATION ntf;
+		ntf.evt	= NEVT_Unsubscribe;
+		ntf.sub.mid = mid;
+		(*m_pEvtCallback)(&ntf, m_pUserParam);
+	}
+}
+
+void CMqttClient::on_publish(int mid)
+{
+	if(m_pEvtCallback && m_evtMask & NEVT_Publish)
+	{
+		MQTT_GENERIC_NOTIFICATION ntf;
+		ntf.evt	= NEVT_Publish;
+		ntf.pub.mid = mid;
+		(*m_pEvtCallback)(&ntf, m_pUserParam);
+	}
+}
+
+void CMqttClient::on_message(const struct mosquitto_message *message)
+{
+	CMqttMessage *pMsg = CMqttMessage::CreateMessage(message);
+	PushRcvMsg(pMsg);
+
+	if(m_pEvtCallback && m_evtMask & NEVT_Message)
+	{
+		MQTT_GENERIC_NOTIFICATION ntf;
+		ntf.evt	= NEVT_Message;
+		ntf.msg.msg = pMsg;
+		(*m_pEvtCallback)(&ntf, m_pUserParam);
+	}
+}
+
+void CMqttClient::on_log(int level, const char *str)
+{
+	if(m_pEvtCallback && m_evtMask & NEVT_Log)
+	{
+		MQTT_GENERIC_NOTIFICATION ntf;
+		ntf.evt	= NEVT_Log;
+		ntf.log.level = level;
+		ntf.log.str = str;
+		(*m_pEvtCallback)(&ntf, m_pUserParam);
+	}
+}
+
+void CMqttClient::on_error(void)
+{
+	if(m_pEvtCallback && m_evtMask & NEVT_Error)
+	{
+		MQTT_GENERIC_NOTIFICATION ntf;
+		ntf.evt	= NEVT_Error;
+		(*m_pEvtCallback)(NULL, m_pUserParam);
+	}
+}

+ 172 - 0
mqttcl/mqttclient.h

@@ -0,0 +1,172 @@
+// mqttclient.h :
+//
+// https://mosquitto.org/man/mosquitto-8.html
+
+#if !defined(AGD_MQTTCLIENT_H__36D28DC5_D30B_43A6_B6EE_84BF95E02490__INCLUDED_)
+#define AGD_MQTTCLIENT_H__36D28DC5_D30B_43A6_B6EE_84BF95E02490__INCLUDED_
+
+#include <string.h>
+#ifndef _LIBBUILD
+#include <gfa/svc/mqttcl/mqttmsg.h>
+#include <gfa/svc/common/processclock.h>
+#else	//	_LIBBUILD
+#include "mqttmsg.h"
+#include "processclock.h"
+#endif	//	_LIBBUILD
+
+#ifdef __cplusplus
+
+/////////////////////////////////////////////////////////////////////////////
+// mqttclient.h - Declarations:
+/////////////////////////////////////////////////////////////////////////////
+
+typedef enum _MQTT_NOTIFICATION_EVENTS
+{
+	NEVT_Connect			= 0x0001,
+	NEVT_Connect_With_Flags	= 0x0002,
+	NEVT_Disconnect			= 0x0004,
+	NEVT_Subscribe			= 0x0008,
+	NEVT_Unsubscribe		= 0x0010,
+	NEVT_Publish			= 0x0020,
+	NEVT_Message			= 0x0040,
+	NEVT_Log				= 0x0080,
+	NEVT_Error				= 0x0100
+}MQTT_NOTIFICATION_EVENTS, *LPMQTT_NOTIFICATION_EVENTS;
+typedef const MQTT_NOTIFICATION_EVENTS *LPCMQTT_NOTIFICATION_EVENTS;
+
+/////////////////////////////////////////////////////////////////////////////
+
+typedef struct _MQTT_EVENT_CONNECT
+{
+	int rc;
+	int flags;
+}MQTT_EVENT_CONNECT, *LPMQTT_EVENT_CONNECT;
+typedef const MQTT_EVENT_CONNECT *LPCMQTT_EVENT_CONNECT;
+
+/////////////////////////////////////////////////////////////////////////////
+
+typedef struct _MQTT_EVENT_SUBSCRIBE
+{
+	int mid;
+	int qos_count;
+	const int *granted_qos;
+}MQTT_EVENT_SUBSCRIBE, *LPMQTT_EVENT_SUBSCRIBE;
+typedef const MQTT_EVENT_SUBSCRIBE *LPCMQTT_EVENT_SUBSCRIBE;
+
+/////////////////////////////////////////////////////////////////////////////
+
+typedef struct _MQTT_EVENT_PUBLISH
+{
+	int mid;
+}MQTT_EVENT_PUBLISH, *LPMQTT_EVENT_PUBLISH;
+typedef const MQTT_EVENT_PUBLISH *LPCMQTT_EVENT_PUBLISH;
+
+/////////////////////////////////////////////////////////////////////////////
+
+typedef struct _MQTT_EVENT_MESSAGE
+{
+	const CMqttMessage *msg;
+}MQTT_EVENT_MESSAGE, *LPMQTT_EVENT_MESSAGE;
+typedef const MQTT_EVENT_MESSAGE *LPCMQTT_EVENT_MESSAGE;
+
+/////////////////////////////////////////////////////////////////////////////
+
+typedef struct _MQTT_EVENT_LOG
+{
+	int level;
+	const char *str;
+}MQTT_EVENT_LOG, *LPMQTT_EVENT_LOG;
+typedef const MQTT_EVENT_LOG *LPCMQTT_EVENT_LOG;
+
+/////////////////////////////////////////////////////////////////////////////
+
+typedef struct _MQTT_GENERIC_NOTIFICATION
+{
+	_MQTT_GENERIC_NOTIFICATION(void)
+	{
+#pragma GCC diagnostic push
+#if __GNUC__ >= 8
+#pragma GCC diagnostic ignored "-Wclass-memaccess"
+#endif
+		memset(this, 0, sizeof(*this));
+#pragma GCC diagnostic pop
+	}
+	MQTT_NOTIFICATION_EVENTS evt;
+	union
+	{
+		MQTT_EVENT_CONNECT		con;
+		MQTT_EVENT_SUBSCRIBE	sub;
+		MQTT_EVENT_PUBLISH		pub;
+		MQTT_EVENT_MESSAGE		msg;
+		MQTT_EVENT_LOG			log;
+	};
+}MQTT_GENERIC_NOTIFICATION, *LPMQTT_GENERIC_NOTIFICATION;
+typedef const MQTT_GENERIC_NOTIFICATION *LPCMQTT_GENERIC_NOTIFICATION;
+
+/////////////////////////////////////////////////////////////////////////////
+
+typedef void (*PFN_MQTT_EVENTS_CALLBACK)(LPCMQTT_GENERIC_NOTIFICATION, void*);
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CMqttClient : public mosqpp::mosquittopp
+{
+public:
+	CMqttClient(const char *id);
+	virtual ~CMqttClient(void);
+
+	/////////////////////////////////////////////////////////////////////////
+	//
+	
+	static int Init(void);
+	static int Cleanup(void);
+	
+	int publish(CMqttMessage *pMsg);
+
+	CMqttMessage* PopRcvMsg(void);
+	void PushRcvMsg(CMqttMessage *pMsg);
+	CMqttMessage* PopSndMsg(void);
+	void PushSndMsg(CMqttMessage *pMsg);
+	
+	CMqttMessageQueue& GetMsgQueueRcv(void) {
+		return m_rcvMsg;}
+	CMqttMessageQueue& GetMsgQueueSnd(void) {
+		return m_sndMsg;}
+	
+	void SetClientEventCallback(PFN_MQTT_EVENTS_CALLBACK pfnEvtCallback, uint32_t evtMask, void *pUserParam)
+	{
+		m_pEvtCallback	= pfnEvtCallback;
+		m_evtMask		= evtMask;
+		m_pUserParam	= pUserParam;
+	}
+	
+	int timed_loop(pc_time64_t nLoopTime);
+	bool TimedLoop(pc_time64_t nLoopTime, int &rnErr, bool &rbReconnect, bool &rbConnPending, bool &rbIntr, std::string &strErr);
+	
+	static bool EvalError(int nErr, bool &rbReconnect, bool &rbConnPending, bool &rbIntr, std::string &strErr);
+
+	/////////////////////////////////////////////////////////////////////////
+	//
+
+    virtual void on_connect(int rc);
+    virtual void on_connect_with_flags(int rc, int flags);
+    virtual void on_disconnect(int rc);
+    virtual void on_subscribe(int mid, int qos_count, const int *granted_qos);
+    virtual void on_unsubscribe(int mid);
+    virtual void on_publish(int mid);
+    virtual void on_message(const struct mosquitto_message *message);
+    virtual void on_log(int level, const char *str);
+    virtual void on_error(void);
+
+private:
+    CProcessClock m_pc;
+	CMqttMessageQueue m_rcvMsg;
+	CMqttMessageQueue m_sndMsg;
+	PFN_MQTT_EVENTS_CALLBACK m_pEvtCallback;
+	uint32_t m_evtMask;
+	void *m_pUserParam;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	__cplusplus
+#endif	//	!defined(AGD_MQTTCLIENT_H__36D28DC5_D30B_43A6_B6EE_84BF95E02490__INCLUDED_)

+ 43 - 0
mqttcl/mqttdbg.h

@@ -0,0 +1,43 @@
+// mqttdbg.h :
+//
+
+#if !defined(AGD_MQTTDBG_H__CF1D8BCC_D25B_4C56_803A_77228E3D986A__INCLUDED_)
+#define AGD_MQTTDBG_H__CF1D8BCC_D25B_4C56_803A_77228E3D986A__INCLUDED_
+
+/////////////////////////////////////////////////////////////////////////////
+// mqttdbg.h - Declarations:
+
+#define _ENABLE_MEM_TRACE				0
+#define _DUMP_ENABLED_VARS				0
+#define _DUMP_CONTROL_CHANGED			0
+#define _RETURN_BIN_AS_STRING			0
+#define _TRACK_TIMES					0
+
+/////////////////////////////////////////////////////////////////////////////
+
+#ifndef _DEBUG
+
+#undef _ENABLE_MEM_TRACE
+#define _ENABLE_MEM_TRACE				0
+
+#undef _DUMP_ENABLED_VARS
+#define _DUMP_ENABLED_VARS				0
+
+#undef _DUMP_CONTROL_CHANGED
+#define _DUMP_CONTROL_CHANGED			0
+
+#undef _RETURN_BIN_AS_STRING
+#define _RETURN_BIN_AS_STRING			0
+
+#endif	//	_DEBUG
+
+/////////////////////////////////////////////////////////////////////////////
+
+#if _TRACK_TIMES
+extern unsigned long g_nDbgCounter1;
+extern unsigned long g_nDbgCounter2;
+extern unsigned long g_nDbgCounter3;
+#endif	//	_TRACK_TIMES
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	!defined(AGD_MQTTDBG_H__CF1D8BCC_D25B_4C56_803A_77228E3D986A__INCLUDED_)

+ 80 - 0
mqttcl/mqttjson.h

@@ -0,0 +1,80 @@
+// mqttjson.h :
+//
+
+#if !defined(AGD_MQTTJSON_H__DAA1F894_3266_4778_855A_77CFA4A5156A__INCLUDED_)
+#define AGD_MQTTJSON_H__DAA1F894_3266_4778_855A_77CFA4A5156A__INCLUDED_
+
+#include <jansson.h>
+
+#ifdef __cplusplus
+/////////////////////////////////////////////////////////////////////////////
+// mqttjson.h - Declarations:
+
+class CJson_t
+{
+public:
+	CJson_t(void) : m_pjt(NULL), m_bFree(false)
+	{
+	}
+
+	CJson_t(json_t *pjt, bool bFree = true) : m_pjt(pjt), m_bFree(bFree)
+	{
+	}
+
+	~CJson_t(void)
+	{
+		if(m_pjt && m_bFree)
+			json_decref(m_pjt);
+	}
+
+	CJson_t& operator = (json_t *pjt)
+	{
+		m_pjt = pjt;
+		return *this;
+	}
+
+	operator json_t*(void) {
+		return m_pjt;}
+
+	operator const json_t*(void) const {
+		return m_pjt;}
+	
+	bool Attach(json_t *pjt, bool bFree) {
+		if(!pjt)
+			return false;
+		m_pjt = pjt;
+		m_bFree = bFree;
+		return true;}
+	
+	bool GetValue(const char *key, CJson_t &val)
+	{
+		if(IsValid() && key && *key)
+		{
+			json_t *pjt = json_object_get(m_pjt, key);
+			if(!pjt)
+				return false;
+			return val.Attach(pjt, false);
+		}
+		return false;
+	}
+
+	bool IsValid(void) const {
+		return !!m_pjt;}
+
+	bool IsArray(void) {
+		return json_is_array(m_pjt);}
+
+	bool IsObject(void) {
+		return json_is_object(m_pjt);}
+
+	int Type(void) const {
+		return IsValid() ? json_typeof(m_pjt) : -1;}
+
+private:
+	json_t *m_pjt;
+	bool m_bFree;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	__cplusplus
+#endif	//	!defined(AGD_MQTTJSON_H__DAA1F894_3266_4778_855A_77CFA4A5156A__INCLUDED_)

+ 208 - 0
mqttcl/mqttmsg.cpp

@@ -0,0 +1,208 @@
+#include <string.h>
+#include "mqttmsg.h"
+#include "strutil.h"
+
+
+static CMqttMessagePool g_msgPool;
+
+#ifdef _TARGET_BUILD
+void mosquitto_message_free_contents(struct mosquitto_message *message)
+{
+	if(message)
+	{
+		if(message->topic)
+		{
+			free(message->topic);
+			message->topic = NULL;
+		}
+
+		if(message->payload)
+		{
+			free(message->payload);
+			message->payload = NULL;
+		}
+	}
+}
+#endif	//	_TARGET_BUILD
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+static void* _memdup(const void *p, size_t s)
+{
+	void *m = NULL;
+	if(p && s)
+	{
+		if((m = malloc(s)))
+			memcpy(m, p, s);
+	}
+	return m;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+CMqttMessage::CMqttMessage(void) : m_bFreeTopic(false), m_bFreePayload(false), m_bIsAvail(false), m_bIsPool(false)
+{
+	memset(static_cast<mosquitto_message*>(this), 0, sizeof(struct mosquitto_message));
+	memset(m_szTopic, 0, sizeof(m_szTopic));
+	memset(m_bufPayload, 0, sizeof(m_bufPayload));
+}
+
+CMqttMessage::~CMqttMessage(void)
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void CMqttMessage::Release(bool bFreePool)
+{
+	if(topic && !m_bFreeTopic)
+		topic = NULL;
+	if(payload && !m_bFreePayload)
+		payload = NULL;
+
+	::mosquitto_message_free_contents(static_cast<mosquitto_message*>(this));
+	memset(static_cast<mosquitto_message*>(this), 0, sizeof(struct mosquitto_message));
+
+	if(m_bIsPool && !bFreePool)
+		g_msgPool.ReturnMsgToPool(this);
+	else
+		delete this;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+CMqttMessage* CMqttMessage::CreateMessage(void)
+{
+	CMqttMessage *pMsg = new CMqttMessage;
+	return pMsg;
+}
+
+CMqttMessage* CMqttMessage::CreateMessage(const struct mosquitto_message *pSrc)
+{
+	if(!pSrc)
+		return NULL;
+	return CreateMessage(pSrc->topic, pSrc->payload, pSrc->payloadlen, pSrc->qos, pSrc->retain, pSrc->mid);
+}
+
+CMqttMessage* CMqttMessage::CreateMessage(const char *topic, const void *payload, int payloadlen, int qos, bool retain, int mid)
+{
+	CMqttMessage *pMsg = g_msgPool.GetMsgFromPool();
+	if(!pMsg)
+		pMsg = new CMqttMessage;
+	pMsg->mid = mid;
+
+	if(topic && *topic)
+	{
+		size_t nLen = strlen(topic);
+		if(nLen >= sizeof(m_szTopic))
+		{
+			pMsg->topic = strdup(topic);
+			pMsg->m_bFreeTopic = true;
+			TRACE("Dynamic memory allocation of %zu Bytes for topic required!\n", nLen);
+		}
+		else
+		{
+			strcpy(pMsg->m_szTopic, topic);
+			pMsg->topic = pMsg->m_szTopic;
+			pMsg->m_bFreeTopic = false;
+		}
+	}
+
+	if(payload && payloadlen)
+	{
+		if(payloadlen > (int)sizeof(m_bufPayload))
+		{
+			pMsg->payload = _memdup(payload, payloadlen);
+			pMsg->m_bFreePayload = true;
+			TRACE("Dynamic memory allocation of %d Bytes for payload required!\n", payloadlen);
+		}
+		else
+		{
+			memcpy(pMsg->m_bufPayload, payload, payloadlen);
+			pMsg->payload = pMsg->m_bufPayload;
+			pMsg->m_bFreePayload = false;
+		}
+	}
+
+	pMsg->payloadlen	= payloadlen;
+	pMsg->qos			= qos;
+	pMsg->retain		= retain;
+	return pMsg;
+}
+
+CMqttMessage* CMqttMessage::CreateRemoveRetainedMessage(const char *topic, int qos, int mid)
+{
+	return CMqttMessage::CreateMessage(topic, NULL, 0, qos, true, mid);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+bool CMqttMessage::TopicMatchesSub(const char *pszSub)
+{
+	bool bRes;
+	if(::mosquitto_topic_matches_sub(pszSub, topic, &bRes) == MOSQ_ERR_SUCCESS)
+		return bRes;
+	return false;
+}
+
+bool CMqttMessage::TopicTokenize(std::vector<std::string> &tokArr, size_t nStart)
+{
+	int nRet;
+	if(!topic || !*topic)
+		return false;
+	size_t nLenTok = strlen(topic);
+	if(nLenTok < nStart)
+		return false;
+	int nCount;
+	char **ppszTopics;
+	if((nRet = ::mosquitto_sub_topic_tokenise(topic + nStart, &ppszTopics, &nCount)) == MOSQ_ERR_SUCCESS)
+	{
+		for(int i = 0; i < nCount; i++)
+		{
+			tokArr.push_back(ppszTopics[i]);
+		}
+		::mosquitto_sub_topic_tokens_free(&ppszTopics, nCount);
+	}
+	return (nRet == MOSQ_ERR_SUCCESS);
+}
+
+std::string CMqttMessage::GetTopic(size_t nStart)
+{
+	if(!topic || !*topic)
+		return "";
+	size_t nLenTok = strlen(topic);
+	if(nLenTok < nStart)
+		return "";
+	return (topic + nStart);
+}
+
+bool CMqttMessage::GetPayloadAsJSON(CJson_t &json, std::string &sErr)
+{
+	if(payload && payloadlen)
+	{
+		json_t *pjtRet;
+		json_error_t err;
+		std::string s((const char*)payload, (size_t)payloadlen);
+
+		if((pjtRet = ::json_loads(s.c_str(), JSON_REJECT_DUPLICATES, &err)))
+		{
+			sErr.clear();
+			return json.Attach(pjtRet, true);
+		}
+		else
+		{
+			sErr = formatString("%s", err.text);
+		}
+	}
+	else
+	{
+		sErr = formatString("empty payload");
+	}
+
+	return false;
+}

+ 124 - 0
mqttcl/mqttmsg.h

@@ -0,0 +1,124 @@
+// mqttmsg.h :
+//
+
+#if !defined(AGD_MQTTMSG_H__A96D51EB_6025_49B5_AE1E_C483E489A73B__INCLUDED_)
+#define AGD_MQTTMSG_H__A96D51EB_6025_49B5_AE1E_C483E489A73B__INCLUDED_
+
+#include <queue>
+#include <vector>
+#include <array>
+#include <pthread.h>
+#include <mosquittopp.h>
+#ifndef _LIBBUILD
+#include <gfa/svc/mqttcl/mqttjson.h>
+#include <gfa/svc/common/debug.h>
+#include <gfa/svc/common/logfile.h>
+#else	//	_LIBBUILD
+#include "mqttjson.h"
+#include "common/debug.h"
+#include "common/logfile.h"
+#endif	//	_LIBBUILD
+
+#ifdef __cplusplus
+
+#define _TOPIC_BUFFER_LENGTH			192
+#define _PAYLOAD_BUFFER_LENGTH			320
+#define _MSG_POOL_SIZE					256
+
+#ifdef _TARGET_BUILD
+extern "C" void mosquitto_message_free_contents(struct mosquitto_message *message);
+#endif	//	_TARGET_BUILD
+
+/////////////////////////////////////////////////////////////////////////////
+// mqttmsg.h - Declarations:
+
+class CMqttMessage : public mosquitto_message
+{
+private:
+	CMqttMessage(void);
+	virtual ~CMqttMessage(void);
+	
+public:
+	static CMqttMessage* CreateMessage(void);
+	static CMqttMessage* CreateMessage(const struct mosquitto_message *pMsg);
+	static CMqttMessage* CreateMessage(const char *topic, const void *payload, int payloadlen, int qos, bool retain, int mid = 0);
+
+	static CMqttMessage* CreateRemoveRetainedMessage(const char *topic, int qos, int mid = 0);
+
+	void Release(bool bFreePool = false);
+	
+public:
+	bool TopicMatchesSub(const char *pszSub);
+	bool TopicTokenize(std::vector<std::string> &tokArr, size_t nStart = 0);
+	std::string GetTopic(size_t nStart = 0);
+	bool GetPayloadAsJSON(CJson_t &json, std::string &sErr);
+
+	inline void SetAvailable(bool bAvail) {
+		m_bIsAvail = bAvail;}
+
+	inline void SetPoolMsg(bool bIsPool) {
+		m_bIsPool = bIsPool;}
+
+	inline bool IsAvailable(void) const {
+		return m_bIsAvail;}
+
+	inline bool IsPoolMsg(bool bIsPool) const {
+		return m_bIsPool;}
+
+private:
+	char m_szTopic[_TOPIC_BUFFER_LENGTH];
+	unsigned char m_bufPayload[_PAYLOAD_BUFFER_LENGTH];
+	bool m_bFreeTopic;
+	bool m_bFreePayload;
+	bool m_bIsAvail;
+	bool m_bIsPool;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CMqttMessagePool
+{
+public:
+	CMqttMessagePool(void);
+	virtual ~CMqttMessagePool(void);
+	
+	CMqttMessage* GetMsgFromPool(void);
+	void ReturnMsgToPool(CMqttMessage *pMsg);
+
+private:
+	std::array<CMqttMessage*, _MSG_POOL_SIZE> m_pool;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CMqttMessageQueue
+{
+public:
+	CMqttMessageQueue(void);
+	virtual ~CMqttMessageQueue(void);
+	
+	void Push(CMqttMessage *pMsg);
+	CMqttMessage* Pop(void);
+	size_t Size(void) const {
+		return m_queue.size();
+	}
+
+private:
+	bool Lock(void){
+		return !::pthread_mutex_lock(&m_mtx);}
+
+	bool TryLock(void){
+		return !::pthread_mutex_trylock(&m_mtx);}
+
+	bool Unlock(void){
+		return !::pthread_mutex_unlock(&m_mtx);}
+
+private:
+	std::queue<CMqttMessage*> m_queue;
+   	pthread_mutex_t m_mtx;
+	pthread_mutexattr_t m_mtxAtt;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	__cplusplus
+#endif	//	!defined(AGD_MQTTMSG_H__A96D51EB_6025_49B5_AE1E_C483E489A73B__INCLUDED_)

+ 46 - 0
mqttcl/mqttmsgpool.cpp

@@ -0,0 +1,46 @@
+#include <string.h>
+#include "mqttmsg.h"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+CMqttMessagePool::CMqttMessagePool(void)
+{
+	for(int i = 0; i < _MSG_POOL_SIZE; i++)
+	{
+		CMqttMessage *pMsg = CMqttMessage::CreateMessage();
+		pMsg->SetPoolMsg(true);
+		pMsg->SetAvailable(true);
+		m_pool[i] = pMsg;
+	}
+}
+
+CMqttMessagePool::~CMqttMessagePool(void)
+{
+	for(auto i = m_pool.begin(); i != m_pool.end(); ++i)
+	{
+		CMqttMessage *pMsg = *i;
+		pMsg->Release(true);
+	}
+}
+
+CMqttMessage* CMqttMessagePool::GetMsgFromPool(void)
+{
+	for(auto i = m_pool.begin(); i != m_pool.end(); ++i)
+	{
+		CMqttMessage *pMsg = *i;
+		
+		if(pMsg->IsAvailable())
+		{
+			pMsg->SetAvailable(false);
+			return pMsg;
+		}
+	}
+	return NULL;
+}
+
+void CMqttMessagePool::ReturnMsgToPool(CMqttMessage *pMsg)
+{
+	if(pMsg)
+		pMsg->SetAvailable(true);
+}

+ 49 - 0
mqttcl/mqttmsgqueue.cpp

@@ -0,0 +1,49 @@
+#include <string.h>
+#include "mqttmsg.h"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+CMqttMessageQueue::CMqttMessageQueue(void)
+{
+	memset(&m_mtx, 0, sizeof(m_mtx));
+	memset(&m_mtxAtt, 0, sizeof(m_mtxAtt));
+	::pthread_mutexattr_init(&m_mtxAtt);
+	::pthread_mutexattr_settype(&m_mtxAtt, PTHREAD_MUTEX_RECURSIVE);
+	::pthread_mutex_init(&m_mtx, &m_mtxAtt);
+}
+
+CMqttMessageQueue::~CMqttMessageQueue(void)
+{
+	CMqttMessage *pMsg;
+	while((pMsg = Pop()))
+		pMsg->Release();
+	::pthread_mutex_destroy(&m_mtx);
+	::pthread_mutexattr_destroy(&m_mtxAtt);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void CMqttMessageQueue::Push(CMqttMessage *pMsg)
+{
+	Lock();
+	m_queue.push(pMsg);
+	Unlock();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+CMqttMessage* CMqttMessageQueue::Pop(void)
+{
+	CMqttMessage *pMsg = NULL;
+	Lock();
+	if(!m_queue.empty())
+	{
+		pMsg = m_queue.front();
+		m_queue.pop();
+	}
+	Unlock();
+	return pMsg;
+}

+ 1463 - 0
mqttcl/mqttvar.h

@@ -0,0 +1,1463 @@
+// mqttvar.h :
+//
+
+#if !defined(AGD_MQTTVAR_H__CA5925ED_7757_407C_86F2_E7687DFFCCFA__INCLUDED_)
+#define AGD_MQTTVAR_H__CA5925ED_7757_407C_86F2_E7687DFFCCFA__INCLUDED_
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+#include <map>
+#include <forward_list>
+#include <utility>
+#include <endian.h>
+#include <gfa/gfaipc.h>
+#include <typeinfo>
+#include <typeindex>
+#ifndef _LIBBUILD
+#include <gfa/svc/common/strutil.h>
+#include <gfa/svc/common/conv.h>
+#include <gfa/svc/common/debug.h>
+#include <gfa/svc/mqttcl/mqttmsg.h>
+#include <gfa/svc/mqttcl/mqttjson.h>
+#include <gfa/svc/mqttcl/mqttdbg.h>
+#else	//	_LIBBUILD
+#include "common/strutil.h"
+#include "common/conv.h"
+#include "common/debug.h"
+#include "mqttmsg.h"
+#include "mqttjson.h"
+#include "mqttdbg.h"
+#endif	//	_LIBBUILD
+
+/////////////////////////////////////////////////////////////////////////////
+
+#define MQTT_VALUE_BINLE				0x00000001
+#define MQTT_VALUE_BINBE				0x00000002
+#define MQTT_VALUE_JSON					0x00000004
+#define MQTT_VALUE_PBUF					0x00000008
+#define MQTT_VALUE_ALL_FORMATS			(MQTT_VALUE_BINLE | MQTT_VALUE_BINBE | MQTT_VALUE_JSON | MQTT_VALUE_PBUF)
+
+#define MQTT_TOPIC_CMD_VALUE			"VALUE"
+#define MQTT_TOPIC_VALUE_BINLE			"BINLE"
+#define MQTT_TOPIC_VALUE_BINBE			"BINBE"
+#define MQTT_TOPIC_VALUE_JSON			"JSON"
+#define MQTT_TOPIC_VALUE_PBUF			"PBUF"
+
+#ifdef _DEBUG
+#define MQTT_JSON_OUTPUT_FLAGS			JSON_MAX_INDENT
+#else	//	_DEBUG
+#define MQTT_JSON_OUTPUT_FLAGS			JSON_COMPACT
+#endif	//	_DEBUG
+
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+#define _UTF_16							"UTF-16LE//"
+#define _UTF_32							"UTF-32LE//"
+#define _UNICODE						"WCHAR_T//"
+#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+#define _UTF_16							"UTF-16BE//"
+#define _UTF_32							"UTF-32BE//"
+#define _UNICODE						"WCHAR_T//"
+#else	//	__BYTE_ORDER__
+#error Invalid or unsupported byte order!
+#endif	//	__BYTE_ORDER__
+
+#define MQTTCL_MIN_QOS					0
+#define MQTTCL_MAX_QOS					2
+
+#ifdef __cplusplus
+/////////////////////////////////////////////////////////////////////////////
+
+template <typename T>
+static void _swap_val(T &v)
+{
+	// should static assert that T is a POD
+	if(sizeof(T) > 1)
+	{
+		char &raw = reinterpret_cast<char&>(v);
+		std::reverse(&raw, &raw + sizeof(T));
+	}
+}
+
+template <typename T>
+static const T* _copy_swap_string_chars(const T *s, T *v, size_t cch)
+{
+	if(sizeof(T) > 1 && cch > 0)
+	{
+		for(T *pv = v; cch > 0; --cch, ++pv, ++s)
+		{
+			*pv = *s;
+			if(*pv)
+				_swap_val(*pv);
+			else
+				break;
+		}
+		return v;
+	}
+	else
+    {
+        memcpy(v, s, cch);
+        return v;
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// mqttvar.h - Declarations:
+
+class CMqttVar;
+
+class CMqttVarTable
+{
+public:
+	typedef bool (*_PFNCMP)(const char*, const char*);
+
+public:
+	CMqttVarTable(void);
+	virtual ~CMqttVarTable(void);
+
+	void AddVar(CMqttVar *pv);
+	CMqttVar* Find(const char *key) const;
+
+	inline size_t size(void) const {
+		return m_map.size();}
+
+	bool AddToPubTable(CMqttVar *pv);
+	bool RemoveFromPubTable(CMqttVar *pv);
+	void CheckShmAndPublish(const char *pszTopicDevID, const char *pszTopicShmID, CMqttMessageQueue &rmq, int &nLocks);
+	void DumpPubEnabled(uint32_t nPubMask = MQTT_VALUE_ALL_FORMATS);
+	void TracePubVar(const char *pszPath, uint32_t nMask, int nQos, bool bRetained);
+
+private:
+	std::map<const char*, CMqttVar*, _PFNCMP> m_map;
+	std::map<const char*, CMqttVar*, _PFNCMP> m_pub;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CMqttVar
+{
+public:
+	CMqttVar(CMqttVar &&m) noexcept :
+						m_hShm(std::move(m.m_hShm)),
+						m_pParent(std::move(m.m_pParent)),
+						m_nPubMask(std::move(m.m_nPubMask)),
+						m_nPubMaskForcedOnce(m_nPubMask),
+						m_path(std::move(m.m_path)),
+						m_pszPath(m_path.c_str()),
+						m_nCbVarpath(std::move(m.m_nCbVarpath)),
+						m_name(std::move(m.m_name)),
+						m_nIndex(std::move(m.m_nIndex)),
+						m_nQos(std::move(m.m_nQos)),
+						m_bRetain(std::move(m.m_bRetain)),
+						m_nRetainedPubMask(std::move(m.m_nRetainedPubMask))
+	{
+	}
+
+	CMqttVar(HSHM hShm, const char *pszName, int nIndex, uint32_t nPubMask, int nQos, bool bRetain, CMqttVar *pParent) :
+						m_hShm(hShm),
+						m_pParent(pParent),
+						m_nPubMask(nPubMask),
+						m_nPubMaskForcedOnce(m_nPubMask),
+						m_pszPath(NULL),
+						m_nCbVarpath(0),
+						m_name(pszName ? pszName : ""),
+						m_nIndex(nIndex),
+						m_nQos(nQos),
+						m_bRetain(bRetain),
+						m_nRetainedPubMask(0)
+	{
+	}
+
+	virtual ~CMqttVar(void)
+	{
+	}
+
+public:
+	virtual void CheckShmAndPublish(const char *pszTopicDevID, const char *pszTopicShmID, CMqttMessageQueue &rmq, int &nLocks) = 0;
+	virtual bool SetShmValue(uint32_t nValFormat, CMqttMessage *pMsg, int &nLocks) {
+		return false;
+	}
+
+	virtual void CreateMembersTable(CMqttVarTable &vt)	{
+		vt.AddVar(this);}
+
+	virtual bool EnablePublish(uint32_t nMask, CMqttVarTable *pvt) {
+			if((m_nPubMask & nMask) != nMask)
+			{
+#if _DUMP_CONTROL_CHANGED
+				uint32_t nEnable = ((nMask ^ m_nPubMask) & nMask);
+#endif	//	_DUMP_CONTROL_CHANGED
+				m_nPubMaskForcedOnce |= ((nMask ^ m_nPubMask) & nMask);
+				m_nPubMask |= nMask;
+
+				if(pvt)
+				{
+					pvt->AddToPubTable(this);
+#if _TRACK_TIMES
+					g_nDbgCounter1++;
+#endif	//	_TRACK_TIMES
+#if _DUMP_CONTROL_CHANGED
+					TraceFormatChange(nEnable, true);
+#endif	//	_DUMP_CONTROL_CHANGED
+					return true;
+				}
+			}
+			return false;
+		}
+
+	virtual bool DisablePublish(uint32_t nMask, CMqttVarTable *pvt) {
+			if(m_nPubMask & nMask)
+			{
+#if _DUMP_CONTROL_CHANGED
+				uint32_t nDisable = (((~nMask) ^ m_nPubMask) & nMask);
+#endif	//	_DUMP_CONTROL_CHANGED
+				m_nPubMask &= ~nMask;
+				m_nPubMaskForcedOnce &= ~nMask;
+
+				if(pvt)
+				{
+					pvt->RemoveFromPubTable(this);
+#if _DUMP_CONTROL_CHANGED
+					TraceFormatChange(nDisable, false);
+#endif	//	_DUMP_CONTROL_CHANGED
+					return true;
+				}
+			}
+			return false;
+		}
+
+	bool PublishEnabled(uint32_t nMask = MQTT_VALUE_ALL_FORMATS) const {
+		return !!(m_nPubMask & nMask);}
+
+	uint32_t GetPublishMask(void) const {
+		return m_nPubMask;}
+
+	uint32_t GetForcedPublishMask(void) const {
+		return m_nPubMaskForcedOnce;}
+
+	void ClearForcedPublishMask(void) {
+		m_nPubMaskForcedOnce = 0;}
+
+	virtual void InitPath(CMqttVar *pParent, const char *pszMemberName, int nIndex = -1) {
+		CreatePath(pParent, pszMemberName, nIndex, m_path);
+		m_pszPath = m_path.c_str();
+		m_nCbVarpath = m_path.length();
+	}
+
+	virtual const char* GetPath(void) const {
+		return m_pszPath;}
+	
+	int GetQoS(void) const {
+		return m_nQos;}
+
+	virtual bool SetQoS(int nQos) {
+		if(nQos < MQTTCL_MIN_QOS)
+			nQos = MQTTCL_MIN_QOS;
+		else if(nQos > MQTTCL_MAX_QOS)
+			nQos = MQTTCL_MAX_QOS;
+		if(m_nQos != nQos) {
+			TraceQOSChange(m_nQos, nQos);
+			m_nQos = nQos;
+			return true;
+		}
+		return false;
+	}
+	
+	bool GetRetained(void) const {
+		return m_bRetain;}
+	
+	virtual bool SetRetained(bool bRetain) {
+		if(m_bRetain != bRetain) {
+			m_bRetain = bRetain;
+			TraceRetainChange(bRetain);
+			return true;
+		}
+		else
+			return false;
+	}
+
+	void SetRetainedPubMask(uint32_t nMask) {
+		m_nRetainedPubMask |= nMask;
+	}
+
+	virtual void RemoveRetained(const char *pszTopicDevID, const char *pszTopicShmID, CMqttMessageQueue &rmq, bool bForce)
+	{
+		if(!m_bRetain)
+		{
+			std::string s;
+			CMqttMessage *pMsg;	
+
+			if((m_nRetainedPubMask & MQTT_VALUE_BINLE) || bForce)
+			{
+				s = CreateTopic(pszTopicDevID, pszTopicShmID, MQTT_TOPIC_VALUE_BINLE, MQTT_TOPIC_CMD_VALUE);
+				pMsg = CMqttMessage::CreateRemoveRetainedMessage(s.c_str(), GetQoS());
+				rmq.Push(pMsg);
+				m_nRetainedPubMask &= ~MQTT_VALUE_BINLE;
+			}
+
+			if((m_nRetainedPubMask & MQTT_VALUE_BINBE) || bForce)
+			{
+				s = CreateTopic(pszTopicDevID, pszTopicShmID, MQTT_TOPIC_VALUE_BINBE, MQTT_TOPIC_CMD_VALUE);
+				pMsg = CMqttMessage::CreateRemoveRetainedMessage(s.c_str(), GetQoS());
+				rmq.Push(pMsg);
+				m_nRetainedPubMask &= ~MQTT_VALUE_BINBE;
+			}
+
+			if((m_nRetainedPubMask & MQTT_VALUE_JSON) || bForce)
+			{
+				s = CreateTopic(pszTopicDevID, pszTopicShmID, MQTT_TOPIC_VALUE_JSON, MQTT_TOPIC_CMD_VALUE);
+				pMsg = CMqttMessage::CreateRemoveRetainedMessage(s.c_str(), GetQoS());
+				rmq.Push(pMsg);
+				m_nRetainedPubMask &= ~MQTT_VALUE_JSON;
+			}
+
+			if((m_nRetainedPubMask & MQTT_VALUE_PBUF) || bForce)
+			{
+				s = CreateTopic(pszTopicDevID, pszTopicShmID, MQTT_TOPIC_VALUE_PBUF, MQTT_TOPIC_CMD_VALUE);
+				pMsg = CMqttMessage::CreateRemoveRetainedMessage(s.c_str(), GetQoS());
+				rmq.Push(pMsg);
+				m_nRetainedPubMask &= ~MQTT_VALUE_PBUF;
+			}
+		}
+	}
+
+protected:
+	static void CreatePath(CMqttVar *pParent, const char *pszMemberName, int nIndex, std::string &rPath)
+	{
+		char szIndex[32] = {0};
+
+		if(!pszMemberName || !*pszMemberName)
+			pszMemberName = "/";
+
+		if(nIndex >= 0)
+		{
+			sprintf(szIndex, "/%d", nIndex);
+		}
+
+		if(pParent)
+		{
+			rPath = pParent->GetPath();
+			auto len = rPath.size();
+
+			if(len > 0)
+			{
+				auto rend = rPath.rbegin();
+				if(*rend != '/')
+					rPath += "/";
+			}
+
+			rPath += pszMemberName;
+			rPath += szIndex;
+		}
+		else
+		{
+			rPath = pszMemberName;
+			rPath += szIndex;
+		}
+	}
+
+	std::string CreateTopic(const char *pszTopicDevID, const char *pszTopicShmID, const char *pszValueFormat, const char *pszTopicCmd)
+	{
+		return ::formatString("%s/%s/%s/%s%s", pszTopicDevID, pszTopicShmID, pszValueFormat, pszTopicCmd, m_pszPath);
+	}
+
+	void Lock(int &nLocks)
+	{
+		if(!nLocks)
+		{
+			::GfaIpcLockSHM(m_hShm);
+			++nLocks;
+		}
+	}
+
+	void Unlock(int &nLocks)
+	{
+		if(nLocks)
+		{
+			--nLocks;
+			::GfaIpcUnlockSHM(m_hShm);
+		}
+	}
+
+	const char* GetName(void) const {
+		return m_name.c_str();}
+
+	int GetIndex(void) const {
+		return m_nIndex;}
+
+private:
+	void TraceFormatChange(uint32_t nMask, bool bOn)
+	{
+#ifdef _DUMP_CONTROL_CHANGED
+		if(m_pszPath && nMask)
+		{
+			int nCount = 0;
+			TRACE("%s ==> %s ", m_pszPath, bOn ? "ON" : "OFF");
+
+			if(nMask & MQTT_VALUE_BINLE)
+			{
+				TRACE("%s", MQTT_TOPIC_VALUE_BINLE);
+				++nCount;
+			}
+
+			if(nMask & MQTT_VALUE_BINBE)
+			{
+				if(nCount)
+					TRACE(", ");
+				TRACE("%s", MQTT_TOPIC_VALUE_BINBE);
+				++nCount;
+			}
+
+			if(nMask & MQTT_VALUE_JSON)
+			{
+				if(nCount)
+					TRACE(", ");
+				TRACE("%s", MQTT_TOPIC_VALUE_JSON);
+				++nCount;
+			}
+
+			if(nMask & MQTT_VALUE_PBUF)
+			{
+				if(nCount)
+					TRACE(", ");
+				TRACE("%s", MQTT_TOPIC_VALUE_PBUF);
+				++nCount;
+			}
+
+			TRACE("\n");
+		}
+#endif	//	_DUMP_CONTROL_CHANGED
+	}
+
+	void TraceQOSChange(int nQosOld, int nQosNew)
+	{
+#ifdef _DUMP_CONTROL_CHANGED
+		if(m_pszPath)
+		{
+			TRACE("%s ==> QOS: %d -> %d\n", m_pszPath, nQosOld, nQosNew);
+		}
+#endif	//	_DUMP_CONTROL_CHANGED
+	}
+
+	void TraceRetainChange(bool bRetained)
+	{
+#ifdef _DUMP_CONTROL_CHANGED
+		if(m_pszPath)
+		{
+			TRACE("%s ==> Retain: %s\n", m_pszPath, bRetained ? "true" : "false");
+		}
+#endif	//	_DUMP_CONTROL_CHANGED
+	}
+
+private:
+    HSHM m_hShm;
+    CMqttVar *m_pParent;
+    uint32_t m_nPubMask;
+    uint32_t m_nPubMaskForcedOnce;
+    std::string m_path;
+    const char *m_pszPath;
+    size_t m_nCbVarpath;
+    std::string m_name;
+    int m_nIndex;
+    int m_nQos;
+    bool m_bRetain;
+    uint32_t m_nRetainedPubMask;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+template <typename T>
+class CMqttVariable : public CMqttVar
+{
+public:
+	CMqttVariable(CMqttVariable &&m) noexcept :
+					CMqttVar(std::move(m)),
+					m_pData(std::move(m.m_pData)),
+					m_pShadow(std::move(m.m_pShadow)),
+					m_bIsBool(std::move(m.m_bIsBool))
+	{
+	}
+
+	CMqttVariable(void *pData, void *pShadow, HSHM hShm, const char *pszName, int nIndex, uint32_t nPubMask, int nQos, bool bRetain, CMqttVar *pParent) :
+					CMqttVar(hShm, pszName, nIndex, nPubMask, nQos, bRetain, pParent),
+					m_pData((volatile T*)pData),
+					m_pShadow((T*)pShadow),
+					m_bIsBool(std::type_index(typeid(T)) == std::type_index(typeid(bool)))
+	{
+		ASSERT(m_pData);
+		ASSERT(m_pShadow);
+		ASSERT(hShm);
+		int nLocks = 0;
+		Lock(nLocks);
+		*m_pShadow = *m_pData;
+		Unlock(nLocks);
+	}
+
+	/////////////////////////////////////////////////////////////////////////
+
+public:
+	virtual void CheckShmAndPublish(const char *pszTopicDevID, const char *pszTopicShmID, CMqttMessageQueue &rmq, int &nLocks)
+	{
+		uint32_t nPubMaskForcedOnce = GetForcedPublishMask();
+		ClearForcedPublishMask();
+
+		if(!PublishEnabled())
+			return;
+		if(!UpdateShadowBuffer(nLocks))
+		{
+			if(nPubMaskForcedOnce & MQTT_VALUE_BINLE)
+				PublishBinLE(pszTopicDevID, pszTopicShmID, rmq, nLocks);
+			if(nPubMaskForcedOnce & MQTT_VALUE_BINBE)
+				PublishBinBE(pszTopicDevID, pszTopicShmID, rmq, nLocks);
+			if(nPubMaskForcedOnce & MQTT_VALUE_JSON)
+				PublishJson(pszTopicDevID, pszTopicShmID, rmq, nLocks);
+			if(nPubMaskForcedOnce & MQTT_VALUE_PBUF)
+				PublishPBuf(pszTopicDevID, pszTopicShmID, rmq, nLocks);
+		}
+		else
+		{
+			if(PublishEnabled(MQTT_VALUE_BINLE))
+				PublishBinLE(pszTopicDevID, pszTopicShmID, rmq, nLocks);
+			if(PublishEnabled(MQTT_VALUE_BINBE))
+				PublishBinBE(pszTopicDevID, pszTopicShmID, rmq, nLocks);
+			if(PublishEnabled(MQTT_VALUE_JSON))
+				PublishJson(pszTopicDevID, pszTopicShmID, rmq, nLocks);
+			if(PublishEnabled(MQTT_VALUE_PBUF))
+				PublishPBuf(pszTopicDevID, pszTopicShmID, rmq, nLocks);
+		}
+	}
+
+	virtual bool SetShmValue(uint32_t nValFormat, CMqttMessage *pMsg, int &nLocks) {
+		switch(nValFormat)
+		{
+		case MQTT_VALUE_BINLE:
+			return SetShmValueLE(pMsg, nLocks);
+		case MQTT_VALUE_BINBE:
+			return SetShmValueBE(pMsg, nLocks);
+		case MQTT_VALUE_JSON:
+			return SetShmValueJson(pMsg, nLocks);
+		case MQTT_VALUE_PBUF:
+			return SetShmValuePBuf(pMsg, nLocks);
+		default:
+			break;
+		}
+		return false;
+	}
+
+	/////////////////////////////////////////////////////////////////////////
+
+private:
+	bool SetShmValueLE(CMqttMessage *pMsg, int &nLocks)
+	{
+		if(pMsg->payload && ((size_t)pMsg->payloadlen == sizeof(T)))
+		{
+			T val = *(T*)pMsg->payload;
+#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+			_swap_val(val);
+#endif	//	__BYTE_ORDER__
+			Lock(nLocks);
+			*m_pData = val;
+			Unlock(nLocks);
+			return true;
+		}
+		return false;
+	}
+
+	bool SetShmValueBE(CMqttMessage *pMsg, int &nLocks)
+	{
+		if(pMsg->payload && ((size_t)pMsg->payloadlen == sizeof(T)))
+		{
+			T val = *(T*)pMsg->payload;
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+			_swap_val(val);
+#endif	//	__BYTE_ORDER__
+			Lock(nLocks);
+			*m_pData = val;
+			Unlock(nLocks);
+			return true;
+		}
+		return false;
+	}
+
+	bool SetShmValueJson(CMqttMessage *pMsg, int &nLocks)
+	{
+		bool bRet = false;
+
+		if(pMsg)
+		{
+			T val;
+			CJson_t jtRoot, jtVal;
+			std::string sErr;
+
+			if(pMsg->GetPayloadAsJSON(jtRoot, sErr))
+			{
+				if(jtRoot.GetValue("value", jtVal))
+				{
+					const json_t *pjt = jtVal;
+
+					if(m_bIsBool)
+					{
+						if(json_is_boolean(pjt))
+						{
+							val = (T)json_boolean_value(pjt);
+							bRet = true;
+						}
+						else if(json_is_integer(pjt))
+						{
+							val = !!::json_integer_value(pjt);
+							bRet = true;
+						}
+					}
+					else if(std::is_integral<T>::value)
+					{
+						if(json_is_integer(pjt))
+						{
+							val = (T)::json_integer_value(pjt);
+							bRet = true;
+						}
+						else if(json_is_boolean(pjt))
+						{
+							val = (T)(json_boolean_value(pjt) ? 1 : 0);
+							bRet = true;
+						}
+					}
+					else if(std::is_floating_point<T>::value)
+					{
+						if(json_is_real(pjt))
+						{
+							val = (T)::json_real_value(pjt);
+							bRet = true;
+						}
+						else if(json_is_integer(pjt))
+						{
+							val = (T)::json_integer_value(pjt);
+							bRet = true;
+						}
+						else if(json_is_boolean(pjt))
+						{
+							val = (T)(json_boolean_value(pjt) ? 1 : 0);
+							bRet = true;
+						}
+					}
+					
+					if(bRet)
+					{
+						Lock(nLocks);
+						*m_pData = val;
+						Unlock(nLocks);
+					}
+				}
+			}
+		}
+		return bRet;
+	}
+
+	bool SetShmValuePBuf(CMqttMessage *pMsg, int &nLocks)
+	{
+		TRACE("%s:\n\tProtocol buffers not implemented!\n", GetPath());
+		return false;
+	}
+
+	/////////////////////////////////////////////////////////////////////////
+
+	void PublishBinLE(const char *pszTopicDevID, const char *pszTopicShmID, CMqttMessageQueue &rmq, int &nLocks)
+	{
+		std::string s = CreateTopic(pszTopicDevID, pszTopicShmID, MQTT_TOPIC_VALUE_BINLE, MQTT_TOPIC_CMD_VALUE);
+#if _RETURN_BIN_AS_STRING
+		std::string v = toString(*m_pShadow);
+		CMqttMessage *pMsg = CMqttMessage::CreateMessage(s.c_str(), v.c_str(), v.length(), GetQoS(), GetRetained());
+#else	//	_RETURN_BIN_AS_STRING
+		T val = *m_pShadow;
+#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+		_swap_val(val);
+#endif	//	__BYTE_ORDER__
+		CMqttMessage *pMsg = CMqttMessage::CreateMessage(s.c_str(), &val, sizeof(val), GetQoS(), GetRetained());
+#endif	//	_RETURN_BIN_AS_STRING
+		rmq.Push(pMsg);
+		if(GetRetained())
+			SetRetainedPubMask(MQTT_VALUE_BINLE);
+	}
+
+	void PublishBinBE(const char *pszTopicDevID, const char *pszTopicShmID, CMqttMessageQueue &rmq, int &nLocks)
+	{
+		std::string s = CreateTopic(pszTopicDevID, pszTopicShmID, MQTT_TOPIC_VALUE_BINBE, MQTT_TOPIC_CMD_VALUE);
+#if _RETURN_BIN_AS_STRING
+		std::string v = toString(*m_pShadow);
+		CMqttMessage *pMsg = CMqttMessage::CreateMessage(s.c_str(), v.c_str(), v.length(), GetQoS(), GetRetained());
+#else	//	_RETURN_BIN_AS_STRING
+		T val = *m_pShadow;
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+		_swap_val(val);
+#endif	//	__BYTE_ORDER__
+		CMqttMessage *pMsg = CMqttMessage::CreateMessage(s.c_str(), &val, sizeof(val), GetQoS(), GetRetained());
+#endif	//	_RETURN_BIN_AS_STRING
+		rmq.Push(pMsg);
+		if(GetRetained())
+			SetRetainedPubMask(MQTT_VALUE_BINBE);
+	}
+
+	void PublishJson(const char *pszTopicDevID, const char *pszTopicShmID, CMqttMessageQueue &rmq, int &nLocks)
+	{
+		std::string s = CreateTopic(pszTopicDevID, pszTopicShmID, MQTT_TOPIC_VALUE_JSON, MQTT_TOPIC_CMD_VALUE);
+		CJson_t jtValue(json_object());
+		CJson_t jtPath(json_string(GetPath()));
+		CJson_t jtIndex(json_integer(GetIndex()));
+		CJson_t jtName(json_string(GetName()));
+		CJson_t jtVal(GetJsonValue());
+		json_object_set(jtValue, "path", jtPath);
+		json_object_set(jtValue, "index", jtIndex);
+		json_object_set(jtValue, "name", jtName);
+		json_object_set(jtValue, "value", jtVal);
+		char *pszJson = json_dumps(jtValue, MQTT_JSON_OUTPUT_FLAGS);
+		std::string v = pszJson;
+		free(pszJson);
+		CMqttMessage *pMsg = CMqttMessage::CreateMessage(s.c_str(), v.c_str(), v.length(), GetQoS(), GetRetained());
+		rmq.Push(pMsg);
+		if(GetRetained())
+			SetRetainedPubMask(MQTT_VALUE_JSON);
+	}
+
+	void PublishPBuf(const char *pszTopicDevID, const char *pszTopicShmID, CMqttMessageQueue &rmq, int &nLocks)
+	{
+		std::string s = CreateTopic(pszTopicDevID, pszTopicShmID, MQTT_TOPIC_VALUE_PBUF, MQTT_TOPIC_CMD_VALUE);
+		TRACE("%s:\n\tProtocol buffers not implemented!\n", s.c_str());
+	}
+
+	std::string toString(T v)
+	{
+		if(std::is_integral<T>::value)
+		{
+			if(std::is_signed<T>::value)
+			{
+				long long r = (long long)v;
+				return formatString("%lld", r);
+			}
+			else if(std::is_unsigned<T>::value)
+			{
+				unsigned long long r = (unsigned long long)v;
+				return formatString("%llu", r);
+			}
+		}
+		else if(std::is_floating_point<T>::value)
+		{
+			double r = (double)v;
+			return formatString("%.20g", r);
+		}
+
+		return "";
+	}
+
+	json_t* GetJsonValue(void) const
+	{
+		if(std::is_integral<T>::value)
+			return m_bIsBool ? json_boolean(!!*m_pShadow) : ::json_integer((json_int_t)*m_pShadow);
+		else if(std::is_floating_point<T>::value)
+			return ::json_real((double)*m_pShadow);
+		ASSERT(false);
+		return NULL;
+	}
+
+	bool UpdateShadowBuffer(int &nLocks)
+	{
+		Lock(nLocks);
+		bool bChanged = *m_pShadow != *m_pData;
+		if(bChanged)
+		{
+			*m_pShadow = *m_pData;
+			TRACE("Changed: %s\n", GetPath());
+		}
+		Unlock(nLocks);
+		return bChanged;
+	}
+
+private:
+    volatile T *m_pData;
+    T *m_pShadow;
+    bool m_bIsBool;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+template <typename T, int ST>
+class CMqttStringVariable : public CMqttVar
+{
+public:
+	typedef enum
+	{
+		VT_Invalid,	// 0
+		VT_Latin1,	// 1
+		VT_UTF_8,	// 2
+		VT_UTF_16,	// 3
+		VT_UTF_32,	// 4
+		VT_Unicode,	// 5
+		VT_Last
+	}VT;
+
+public:
+	CMqttStringVariable(CMqttStringVariable &&m) noexcept :
+						CMqttVar(std::move(m)),
+						m_nCChBuffer(std::move(m.m_nCChBuffer)),
+						m_nCbBuffer(std::move(m.m_nCbBuffer)),
+						m_nCbString(std::move(m.m_nCbString)),
+						m_pszData(std::move(m.m_pszData)),
+						m_pszShadow(std::move(m.m_pszShadow))
+	{
+	}
+
+	CMqttStringVariable(void *pData, void *pShadow, HSHM hShm, const char *pszName, size_t nCChBuffer, int nIndex, uint32_t nPubMask, int nQos, bool bRetain, CMqttVar *pParent) :
+						CMqttVar(hShm, pszName, nIndex, nPubMask, nQos, bRetain, pParent),
+						m_nCChBuffer(nCChBuffer),
+						m_nCbBuffer(nCChBuffer * sizeof(T)),
+						m_nCbString(0),
+						m_pszData((T*)pData),
+						m_pszShadow((T*)pShadow)
+	{
+		ASSERT(m_pszData);
+		ASSERT(m_pszShadow);
+		ASSERT(m_nCChBuffer > 0);
+		int nLocks = 0;
+		Lock(nLocks);
+		zeroTerm(m_nCChBuffer - 1);
+		m_nCbString = slen(m_pszData) * sizeof(T);
+		memcpy(m_pszShadow, (const void*)m_pszData, m_nCbBuffer);
+		Unlock(nLocks);
+	}
+
+public:
+	virtual void CheckShmAndPublish(const char *pszTopicDevID, const char *pszTopicShmID, CMqttMessageQueue &rmq, int &nLocks)
+	{
+		uint32_t nPubMaskForcedOnce = GetForcedPublishMask();
+		ClearForcedPublishMask();
+
+		if(!PublishEnabled())
+			return;
+		if(!UpdateShadowBuffer(nLocks))
+		{
+			if(nPubMaskForcedOnce & MQTT_VALUE_BINLE)
+				PublishBinLE(pszTopicDevID, pszTopicShmID, rmq, nLocks);
+			if(nPubMaskForcedOnce & MQTT_VALUE_BINBE)
+				PublishBinBE(pszTopicDevID, pszTopicShmID, rmq, nLocks);
+			if(nPubMaskForcedOnce & MQTT_VALUE_JSON)
+				PublishJson(pszTopicDevID, pszTopicShmID, rmq, nLocks);
+			if(nPubMaskForcedOnce & MQTT_VALUE_PBUF)
+				PublishPBuf(pszTopicDevID, pszTopicShmID, rmq, nLocks);
+		}
+		else
+		{
+			if(PublishEnabled(MQTT_VALUE_BINLE))
+				PublishBinLE(pszTopicDevID, pszTopicShmID, rmq, nLocks);
+			if(PublishEnabled(MQTT_VALUE_BINBE))
+				PublishBinBE(pszTopicDevID, pszTopicShmID, rmq, nLocks);
+			if(PublishEnabled(MQTT_VALUE_JSON))
+				PublishJson(pszTopicDevID, pszTopicShmID, rmq, nLocks);
+			if(PublishEnabled(MQTT_VALUE_PBUF))
+				PublishPBuf(pszTopicDevID, pszTopicShmID, rmq, nLocks);
+		}
+	}
+
+	virtual bool SetShmValue(uint32_t nValFormat, CMqttMessage *pMsg, int &nLocks) {
+		switch(nValFormat)
+		{
+		case MQTT_VALUE_BINLE:
+			return SetShmValueLE(pMsg, nLocks);
+		case MQTT_VALUE_BINBE:
+			return SetShmValueBE(pMsg, nLocks);
+		case MQTT_VALUE_JSON:
+			return SetShmValueJson(pMsg, nLocks);
+		case MQTT_VALUE_PBUF:
+			return SetShmValuePBuf(pMsg, nLocks);
+		default:
+			break;
+		}
+		return false;
+	}
+
+private:
+	bool SetShmValueLE(CMqttMessage *pMsg, int &nLocks)
+	{
+		if(pMsg->payload && pMsg->payloadlen > 0)
+		{
+			if((size_t)pMsg->payloadlen / sizeof(T) < m_nCChBuffer)
+			{
+				Lock(nLocks);
+				m_nCbString = (size_t)pMsg->payloadlen;
+#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+				_copy_swap_string_chars((const T*)pMsg->payload, (T*)m_pszData, m_nCbString / sizeof(T));
+#else	//	__BYTE_ORDER__
+				memcpy((void*)m_pszData, pMsg->payload, m_nCbString);
+#endif	//	__BYTE_ORDER__
+				zeroTerm(m_nCbString / sizeof(T));
+				Unlock(nLocks);
+				return true;
+			}
+		}
+		else
+		{
+			Lock(nLocks);
+			*m_pszData = '\0';
+			m_nCbString = 0;
+			Unlock(nLocks);
+			return true;
+		}
+		return false;
+	}
+
+	bool SetShmValueBE(CMqttMessage *pMsg, int &nLocks)
+	{
+		if(pMsg->payload && pMsg->payloadlen > 0)
+		{
+			if((size_t)pMsg->payloadlen / sizeof(T) < m_nCChBuffer)
+			{
+				Lock(nLocks);
+				m_nCbString = (size_t)pMsg->payloadlen;
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+				_copy_swap_string_chars((const T*)pMsg->payload, (T*)m_pszData, m_nCbString / sizeof(T));
+#else	//	__BYTE_ORDER__
+				memcpy((void*)m_pszData, pMsg->payload, m_nCbString);
+#endif	//	__BYTE_ORDER__
+				zeroTerm(m_nCbString / sizeof(T));
+				Unlock(nLocks);
+				return true;
+			}
+		}
+		else
+		{
+			Lock(nLocks);
+			*m_pszData = '\0';
+			m_nCbString = 0;
+			Unlock(nLocks);
+			return true;
+		}
+		return false;
+	}
+
+	bool SetShmValueJson(CMqttMessage *pMsg, int &nLocks)
+	{
+		if(pMsg)
+		{
+			CJson_t jtRoot, jtVal;
+			std::string sErr;
+
+			if(pMsg->GetPayloadAsJSON(jtRoot, sErr))
+			{
+				if(jtRoot.GetValue("value", jtVal))
+				{
+					const json_t *pjt = jtVal;
+
+					if(json_is_string(pjt))
+					{
+						std::string s(::json_string_value(jtVal));
+						if(s.length() > 0)
+						{
+							Lock(nLocks);
+							if(fromUTF8(s.c_str(), s.length(), (T*)m_pszData, m_nCbBuffer))
+								m_nCbString = slen(m_pszData) * sizeof(T);
+							Unlock(nLocks);
+						}
+						else
+						{
+							Lock(nLocks);
+							*m_pszData = '\0';
+							m_nCbString = 0;
+							Unlock(nLocks);
+						}
+						return true;
+					}
+				}
+			}
+		}
+		return false;
+	}
+
+	bool SetShmValuePBuf(CMqttMessage *pMsg, int &nLocks)
+	{
+		TRACE("%s:\n\tProtocol buffers not implemented!\n", GetPath());
+		return false;
+	}
+
+	void PublishBinLE(const char *pszTopicDevID, const char *pszTopicShmID, CMqttMessageQueue &rmq, int &nLocks)
+	{
+		std::string s = CreateTopic(pszTopicDevID, pszTopicShmID, MQTT_TOPIC_VALUE_BINLE, MQTT_TOPIC_CMD_VALUE);
+
+#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+		T szBuf[m_nCChBuffer];
+		CMqttMessage *pMsg = CMqttMessage::CreateMessage(s.c_str(), _copy_swap_string_chars(m_pszShadow, szBuf, m_nCChBuffer), m_nCbString, GetQoS(), GetRetained());
+#else	//	__BYTE_ORDER__
+		CMqttMessage *pMsg = CMqttMessage::CreateMessage(s.c_str(), m_pszShadow, m_nCbString, GetQoS(), GetRetained());
+#endif	//	__BYTE_ORDER__
+		rmq.Push(pMsg);
+	}
+
+	void PublishBinBE(const char *pszTopicDevID, const char *pszTopicShmID, CMqttMessageQueue &rmq, int &nLocks)
+	{
+		std::string s = CreateTopic(pszTopicDevID, pszTopicShmID, MQTT_TOPIC_VALUE_BINBE, MQTT_TOPIC_CMD_VALUE);
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+		T szBuf[m_nCChBuffer];
+		CMqttMessage *pMsg = CMqttMessage::CreateMessage(s.c_str(), _copy_swap_string_chars(m_pszShadow, szBuf, m_nCChBuffer), m_nCbString, GetQoS(), GetRetained());
+#else	//	__BYTE_ORDER__
+		CMqttMessage *pMsg = CMqttMessage::CreateMessage(s.c_str(), m_pszShadow, m_nCbString, GetQoS(), GetRetained());
+#endif	//	__BYTE_ORDER__
+		rmq.Push(pMsg);
+	}
+
+	void PublishJson(const char *pszTopicDevID, const char *pszTopicShmID, CMqttMessageQueue &rmq, int &nLocks)
+	{
+		std::string s = CreateTopic(pszTopicDevID, pszTopicShmID, MQTT_TOPIC_VALUE_JSON, MQTT_TOPIC_CMD_VALUE);
+		CJson_t jtValue(json_object());
+		CJson_t jtPath(json_string(GetPath()));
+		CJson_t jtIndex(json_integer(GetIndex()));
+		CJson_t jtName(json_string(GetName()));
+		CJson_t jtVal(GetJsonValue());
+		json_object_set(jtValue, "path", jtPath);
+		json_object_set(jtValue, "index", jtIndex);
+		json_object_set(jtValue, "name", jtName);
+		json_object_set(jtValue, "value", jtVal);
+		char *pszJson = json_dumps(jtValue, MQTT_JSON_OUTPUT_FLAGS);
+		std::string v = pszJson;
+		free(pszJson);
+		CMqttMessage *pMsg = CMqttMessage::CreateMessage(s.c_str(), v.c_str(), v.length(), GetQoS(), GetRetained());
+		rmq.Push(pMsg);
+	}
+
+	void PublishPBuf(const char *pszTopicDevID, const char *pszTopicShmID, CMqttMessageQueue &rmq, int &nLocks)
+	{
+		std::string s = CreateTopic(pszTopicDevID, pszTopicShmID, MQTT_TOPIC_VALUE_PBUF, MQTT_TOPIC_CMD_VALUE);
+		TRACE("%s:\n\tProtocol buffers not implemented!\n", s.c_str());
+	}
+
+	json_t* GetJsonValue(void) const
+	{
+		char szBuf[m_nCbBuffer * 4];
+		const char *pszUtf8 = toUTF8(m_pszShadow, m_nCbString, szBuf, sizeof(szBuf));
+		if(pszUtf8)
+			return json_string(pszUtf8);
+		return NULL;
+	}
+
+	const char* toUTF8(const T *pszIn, size_t nCbIn, char *pszOut, size_t nCbOut) const
+	{
+		switch(ST)
+		{
+		case VT_Latin1:
+			Latin1ToUtf8(pszIn, nCbIn, pszOut, nCbOut);
+			break;
+		case VT_UTF_8:
+			strncpy(pszOut, (const char*)pszIn, nCbOut - 1);
+			pszOut[nCbOut - 1] = '\0';
+			break;
+		case VT_UTF_16:
+			EncToUtf8(_UTF_16, pszIn, nCbIn, pszOut, nCbOut);
+			break;
+		case VT_UTF_32:
+			EncToUtf8(_UTF_32, pszIn, nCbIn, pszOut, nCbOut);
+			break;
+		case VT_Unicode:
+			EncToUtf8(_UNICODE, pszIn, nCbIn, pszOut, nCbOut);
+			break;
+		default:
+			ASSERT(false);
+			return NULL;
+		}
+		
+		return pszOut;
+	}
+
+	const T* fromUTF8(const char *pszIn, size_t nCbIn, T *pszOut, size_t nCbOut) const
+	{
+		size_t nRet;
+
+		switch(ST)
+		{
+		case VT_Latin1:
+			nRet = Utf8ToLatin1(pszIn, nCbIn, pszOut, nCbOut);
+			pszOut[nRet / sizeof(T)] = T('\0');
+			break;
+		case VT_UTF_8:
+			strncpy((char*)pszOut, pszIn, nCbOut - 1);
+			pszOut[nCbOut - 1] = T('\0');
+			break;
+		case VT_UTF_16:
+			nRet = Utf8ToUtf16(pszIn, nCbIn, (char16_t*)pszOut, nCbOut);
+			pszOut[nRet / sizeof(T)] = T('\0');
+			break;
+		case VT_UTF_32:
+			nRet = Utf8ToUtf32(pszIn, nCbIn, (char32_t*)pszOut, nCbOut);
+			pszOut[nRet / sizeof(T)] = T('\0');
+			break;
+		case VT_Unicode:
+			nRet = Utf8ToWcs(pszIn, nCbIn, (wchar_t*)pszOut, nCbOut);
+			pszOut[nRet / sizeof(T)] = T('\0');
+			break;
+		default:
+			ASSERT(false);
+			return NULL;
+		}
+		
+		return pszOut;
+	}
+
+	size_t slen(volatile const T *s)
+	{
+		volatile const T *p = s;
+		while(*p) ++p;
+		return p - s;
+	}
+
+	void zeroTerm(size_t at)
+	{
+		m_pszData[at] = (T)'\0';
+	}
+
+	bool UpdateShadowBuffer(int &nLocks)
+	{
+		Lock(nLocks);
+		bool bChanged = !!memcmp(m_pszShadow, (const void*)m_pszData, m_nCbString + sizeof(T));
+		if(bChanged)
+		{
+			zeroTerm(m_nCChBuffer - 1);
+			m_nCbString = slen(m_pszData) * sizeof(T);
+			memcpy(m_pszShadow, (const void*)m_pszData, m_nCbString + sizeof(T));
+			TRACE("Changed: %s\n", GetPath());
+		}
+		Unlock(nLocks);
+		return bChanged;
+	}
+
+private:
+	const size_t m_nCChBuffer;
+	const size_t m_nCbBuffer;
+	size_t m_nCbString;
+    volatile T *m_pszData;
+    T *m_pszShadow;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+#define GET_BOOL_VAL(p, m)				(!!(*p & m))
+#define SET_BIT(p, m)					(*p |= m)
+#define CLR_BIT(p, m)					(*p &= ~m)
+#define STORE_BIT(p, m, b)				(b) ? SET_BIT(p, m) : CLR_BIT(p, m)
+
+class CMqttBitVariable : public CMqttVar
+{
+public:
+	CMqttBitVariable(CMqttBitVariable &&m) noexcept :
+					CMqttVar(std::move(m)),
+					m_mask(std::move(m.m_mask)),
+					m_pDataByte(std::move(m.m_pDataByte)),
+					m_pShadowByte(std::move(m.m_pShadowByte))
+	{
+	}
+
+	CMqttBitVariable(void *pData, void *pShadow, HSHM hShm, const char *pszName, size_t nOffset, int nBitNr, uint32_t nPubMask, int nQos, bool bRetain, CMqttVar *pParent) :
+					CMqttVar(hShm, pszName, -1, nPubMask, nQos, bRetain, pParent),
+					m_mask(1 << nBitNr),
+					m_pDataByte((uint8_t*)pData + nOffset),
+					m_pShadowByte((uint8_t*)pShadow + nOffset)
+	{
+		ASSERT(m_pDataByte);
+		ASSERT(m_pShadowByte);
+		int nLocks = 0;
+		Lock(nLocks);
+		STORE_BIT(m_pShadowByte, m_mask, GET_BOOL_VAL(m_pDataByte, m_mask));
+		Unlock(nLocks);
+	}
+
+public:
+	virtual void CheckShmAndPublish(const char *pszTopicDevID, const char *pszTopicShmID, CMqttMessageQueue &rmq, int &nLocks)
+	{
+		uint32_t nPubMaskForcedOnce = GetForcedPublishMask();
+		ClearForcedPublishMask();
+
+		if(!PublishEnabled())
+			return;
+		if(!UpdateShadowBuffer(nLocks))
+		{
+			if(nPubMaskForcedOnce & MQTT_VALUE_BINLE)
+				PublishBinLE(pszTopicDevID, pszTopicShmID, rmq, nLocks);
+			if(nPubMaskForcedOnce & MQTT_VALUE_BINBE)
+				PublishBinBE(pszTopicDevID, pszTopicShmID, rmq, nLocks);
+			if(nPubMaskForcedOnce & MQTT_VALUE_JSON)
+				PublishJson(pszTopicDevID, pszTopicShmID, rmq, nLocks);
+			if(nPubMaskForcedOnce & MQTT_VALUE_PBUF)
+				PublishPBuf(pszTopicDevID, pszTopicShmID, rmq, nLocks);
+		}
+		else
+		{
+			if(PublishEnabled(MQTT_VALUE_BINLE))
+				PublishBinLE(pszTopicDevID, pszTopicShmID, rmq, nLocks);
+			if(PublishEnabled(MQTT_VALUE_BINBE))
+				PublishBinBE(pszTopicDevID, pszTopicShmID, rmq, nLocks);
+			if(PublishEnabled(MQTT_VALUE_JSON))
+				PublishJson(pszTopicDevID, pszTopicShmID, rmq, nLocks);
+			if(PublishEnabled(MQTT_VALUE_PBUF))
+				PublishPBuf(pszTopicDevID, pszTopicShmID, rmq, nLocks);
+		}
+	}
+
+	virtual bool SetShmValue(uint32_t nValFormat, CMqttMessage *pMsg, int &nLocks) {
+		switch(nValFormat)
+		{
+		case MQTT_VALUE_BINLE:
+		case MQTT_VALUE_BINBE:
+			return SetShmValueBin(pMsg, nLocks);
+		case MQTT_VALUE_JSON:
+			return SetShmValueJson(pMsg, nLocks);
+		case MQTT_VALUE_PBUF:
+			return SetShmValuePBuf(pMsg, nLocks);
+		default:
+			break;
+		}
+		return false;
+	}
+
+private:
+	bool SetShmValueBin(CMqttMessage *pMsg, int &nLocks)
+	{
+		if(pMsg->payload && ((size_t)pMsg->payloadlen == sizeof(bool)))
+		{
+			bool bVal = !!(*(bool*)pMsg->payload);
+			Lock(nLocks);
+			STORE_BIT(m_pDataByte, m_mask, bVal);
+			Unlock(nLocks);
+			return true;
+		}
+		return false;
+	}
+
+	bool SetShmValueJson(CMqttMessage *pMsg, int &nLocks)
+	{
+		bool bRet = false;
+
+		if(pMsg)
+		{
+			bool bVal;
+			CJson_t jtRoot, jtVal;
+			std::string sErr;
+
+			if(pMsg->GetPayloadAsJSON(jtRoot, sErr))
+			{
+				if(jtRoot.GetValue("value", jtVal))
+				{
+					const json_t *pjt = jtVal;
+
+					if(json_is_boolean(pjt))
+					{
+						bVal = json_boolean_value(pjt);
+						bRet = true;
+					}
+					else if(json_is_integer(pjt))
+					{
+						bVal = !!::json_integer_value(pjt);
+						bRet = true;
+					}
+					
+					if(bRet)
+					{
+						Lock(nLocks);
+						STORE_BIT(m_pDataByte, m_mask, bVal);
+						Unlock(nLocks);
+					}
+				}
+			}
+		}
+		return bRet;
+	}
+
+	bool SetShmValuePBuf(CMqttMessage *pMsg, int &nLocks)
+	{
+		TRACE("%s:\n\tProtocol buffers not implemented!\n", GetPath());
+		return false;
+	}
+
+	void PublishBinLE(const char *pszTopicDevID, const char *pszTopicShmID, CMqttMessageQueue &rmq, int &nLocks)
+	{
+		std::string s = CreateTopic(pszTopicDevID, pszTopicShmID, MQTT_TOPIC_VALUE_BINLE, MQTT_TOPIC_CMD_VALUE);
+		bool  val = GET_BOOL_VAL(m_pShadowByte, m_mask);
+#if _RETURN_BIN_AS_STRING
+		std::string v = val ? "true" : "false";
+		CMqttMessage *pMsg = CMqttMessage::CreateMessage(s.c_str(), v.c_str(), v.length(), GetQoS(), GetRetained());
+#else	//	_RETURN_BIN_AS_STRING
+		CMqttMessage *pMsg = CMqttMessage::CreateMessage(s.c_str(), &val, sizeof(val), GetQoS(), GetRetained());
+#endif	//	_RETURN_BIN_AS_STRING
+		rmq.Push(pMsg);
+	}
+
+	void PublishBinBE(const char *pszTopicDevID, const char *pszTopicShmID, CMqttMessageQueue &rmq, int &nLocks)
+	{
+		std::string s = CreateTopic(pszTopicDevID, pszTopicShmID, MQTT_TOPIC_VALUE_BINBE, MQTT_TOPIC_CMD_VALUE);
+		bool  val = GET_BOOL_VAL(m_pShadowByte, m_mask);
+#if _RETURN_BIN_AS_STRING
+		std::string v = val ? "true" : "false";
+		CMqttMessage *pMsg = CMqttMessage::CreateMessage(s.c_str(), v.c_str(), v.length(), GetQoS(), GetRetained());
+#else	//	_RETURN_BIN_AS_STRING
+		CMqttMessage *pMsg = CMqttMessage::CreateMessage(s.c_str(), &val, sizeof(val), GetQoS(), GetRetained());
+#endif	//	_RETURN_BIN_AS_STRING
+		rmq.Push(pMsg);
+	}
+
+	void PublishJson(const char *pszTopicDevID, const char *pszTopicShmID, CMqttMessageQueue &rmq, int &nLocks)
+	{
+		std::string s = CreateTopic(pszTopicDevID, pszTopicShmID, MQTT_TOPIC_VALUE_JSON, MQTT_TOPIC_CMD_VALUE);
+		CJson_t jtValue(json_object());
+		CJson_t jtPath(json_string(GetPath()));
+		CJson_t jtIndex(json_integer(GetIndex()));
+		CJson_t jtName(json_string(GetName()));
+		CJson_t jtVal(GetJsonValue());
+		json_object_set(jtValue, "path", jtPath);
+		json_object_set(jtValue, "index", jtIndex);
+		json_object_set(jtValue, "name", jtName);
+		json_object_set(jtValue, "value", jtVal);
+		char *pszJson = json_dumps(jtValue, MQTT_JSON_OUTPUT_FLAGS);
+		std::string v = pszJson;
+		free(pszJson);
+		CMqttMessage *pMsg = CMqttMessage::CreateMessage(s.c_str(), v.c_str(), v.length(), GetQoS(), GetRetained());
+		rmq.Push(pMsg);
+	}
+
+	void PublishPBuf(const char *pszTopicDevID, const char *pszTopicShmID, CMqttMessageQueue &rmq, int &nLocks)
+	{
+		std::string s = CreateTopic(pszTopicDevID, pszTopicShmID, MQTT_TOPIC_VALUE_PBUF, MQTT_TOPIC_CMD_VALUE);
+		TRACE("%s:\n\tProtocol buffers not implemented!\n", s.c_str());
+	}
+
+	json_t* GetJsonValue(void) const
+	{
+		return json_boolean(GET_BOOL_VAL(m_pShadowByte, m_mask));
+	}
+
+	bool UpdateShadowBuffer(int &nLocks)
+	{
+		bool bVal;
+		Lock(nLocks);
+		bool bChanged = GET_BOOL_VAL(m_pShadowByte, m_mask) != (bVal = GET_BOOL_VAL(m_pDataByte, m_mask));
+		if(bChanged)
+		{
+			STORE_BIT(m_pShadowByte, m_mask, bVal);
+			TRACE("Changed: %s\n", GetPath());
+		}
+		Unlock(nLocks);
+		return bChanged;
+	}
+
+private:
+	uint8_t m_mask;
+	volatile uint8_t *m_pDataByte;
+	uint8_t *m_pShadowByte;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+template <typename T, typename V>
+class CMqttVarArray :	public CMqttVar,
+						public std::vector<T>
+{
+public:
+	CMqttVarArray(void *pData, void *pShadow, HSHM hShm, const char *pszName, size_t nElemCount, uint32_t nPubMask, int nQos, bool bRetain, CMqttVar *pParent)
+				: CMqttVar(hShm, pszName, -1, 0, nQos, bRetain, pParent)
+	{
+		for(size_t i = 0; i < nElemCount; i++)
+		{
+			this->emplace_back(&((V*)pData)[i], &((V*)pShadow)[i], hShm, pszName, i, nPubMask, nQos, bRetain, static_cast<CMqttVar*>(this));
+		}
+	}
+
+	CMqttVarArray(void *pData, void *pShadow, HSHM hShm, const char *pszName, size_t nElemCount, size_t nCChData, uint32_t nPubMask, int nQos, bool bRetain, CMqttVar *pParent)
+				: CMqttVar(hShm, pszName, -1, 0, nQos, bRetain, pParent)
+	{
+		for(size_t i = 0; i < nElemCount; i++)
+		{
+			this->emplace_back(&((V*)pData)[i * nCChData], &((V*)pShadow)[i * nCChData], hShm, pszName, nCChData, i, nPubMask, nQos, bRetain, static_cast<CMqttVar*>(this));
+		}
+	}
+
+public:
+	virtual void CreateMembersTable(CMqttVarTable &vt)
+	{
+		CMqttVar::CreateMembersTable(vt);
+		for(auto i = this->begin(); i != this->end(); ++i)
+		{
+			CMqttVar &rVar = static_cast<CMqttVar&>(*i);
+	    	rVar.CreateMembersTable(vt);
+		}
+	}
+
+	virtual void InitPath(CMqttVar *pParent, const char *pszMemberName, int nIndex = -1)
+	{
+		CMqttVar::InitPath(pParent, pszMemberName, nIndex);
+		int j = 0;
+		for(auto i = this->begin(); i != this->end(); ++i, ++j)
+		{
+			CMqttVar &rVar = static_cast<CMqttVar&>(*i);
+			rVar.InitPath(pParent, pszMemberName, j);
+		}
+	}
+
+	virtual void CheckShmAndPublish(const char *pszTopicDevID, const char *pszTopicShmID, CMqttMessageQueue &rmq, int &nLocks)
+	{
+		Lock(nLocks);
+		for(auto i = this->begin(); i != this->end(); ++i)
+		{
+			CMqttVar &rVar = static_cast<CMqttVar&>(*i);
+			rVar.CheckShmAndPublish(pszTopicDevID, pszTopicShmID, rmq, nLocks);
+		}
+		Unlock(nLocks);
+	}
+
+	virtual bool EnablePublish(uint32_t nMask, CMqttVarTable *pvt)
+	{
+		bool bRet = false;
+		for(auto i = this->begin(); i != this->end(); ++i)
+		{
+			CMqttVar &rVar = static_cast<CMqttVar&>(*i);
+			bRet = rVar.EnablePublish(nMask, pvt) || bRet;
+		}
+		return bRet;
+	}
+
+	virtual bool DisablePublish(uint32_t nMask, CMqttVarTable *pvt)
+	{
+		bool bRet = false;
+		for(auto i = this->begin(); i != this->end(); ++i)
+		{
+			CMqttVar &rVar = static_cast<CMqttVar&>(*i);
+			bRet = rVar.DisablePublish(nMask, pvt) || bRet;
+		}
+		return bRet;
+	}
+
+	virtual bool SetQoS(int nQos)
+	{
+		bool bRet = false;
+		for(auto i = this->begin(); i != this->end(); ++i)
+		{
+			CMqttVar &rVar = static_cast<CMqttVar&>(*i);
+			bRet = rVar.SetQoS(nQos) || bRet;
+		}
+		return bRet;
+	}
+
+	virtual bool SetRetained(bool bRetain)
+	{
+		bool bRet = false;
+		for(auto i = this->begin(); i != this->end(); ++i)
+		{
+			CMqttVar &rVar = static_cast<CMqttVar&>(*i);
+			bRet = rVar.SetRetained(bRetain) || bRet;
+		}
+		return bRet;
+	}
+
+	virtual void RemoveRetained(const char *pszTopicDevID, const char *pszTopicShmID, CMqttMessageQueue &rmq, bool bForce)
+	{
+		for(auto i = this->begin(); i != this->end(); ++i)
+		{
+			CMqttVar &rVar = static_cast<CMqttVar&>(*i);
+			rVar.RemoveRetained(pszTopicDevID, pszTopicShmID, rmq, bForce);
+		}
+	}
+};
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	__cplusplus
+#endif	//	!defined(AGD_MQTTVAR_H__CA5925ED_7757_407C_86F2_E7687DFFCCFA__INCLUDED_)

+ 132 - 0
mqttcl/mqttvartbl.cpp

@@ -0,0 +1,132 @@
+#include <algorithm>
+#include "mqttvar.h"
+#include "mqttdbg.h"
+
+/////////////////////////////////////////////////////////////////////////////
+
+static bool _fncomp(const char *p1, const char *p2)
+{
+	return strcmp(p1, p2) < 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+CMqttVarTable::CMqttVarTable(void) : m_map(_fncomp), m_pub(_fncomp)
+{
+}
+
+CMqttVarTable::~CMqttVarTable(void)
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CMqttVarTable::AddVar(CMqttVar *pVar)
+{
+	m_map[pVar->GetPath()] = pVar;
+	AddToPubTable(pVar);
+}
+
+CMqttVar* CMqttVarTable::Find(const char *key) const
+{
+	std::map<const char*, CMqttVar*>::const_iterator it = m_map.find(key);
+	if(it == m_map.end())
+		return NULL;
+	return it->second;
+}
+
+bool CMqttVarTable::AddToPubTable(CMqttVar *pv)
+{
+	if(pv->PublishEnabled())
+	{
+		if(m_pub.find(pv->GetPath()) == m_pub.end())
+		{
+			m_pub[pv->GetPath()] = pv;
+			return true;
+		}
+	}
+	return false;
+}
+
+bool CMqttVarTable::RemoveFromPubTable(CMqttVar *pv)
+{
+	if(!pv->PublishEnabled())
+	{
+		if(m_pub.find(pv->GetPath()) != m_pub.end())
+		{
+			m_pub.erase(pv->GetPath());
+			return true;
+		}
+	}
+	return false;
+}
+
+void CMqttVarTable::CheckShmAndPublish(const char *pszTopicDevID, const char *pszTopicPrefix, CMqttMessageQueue &rmq, int &nLocks)
+{
+	for(auto i = m_pub.begin(); i != m_pub.end(); ++i)
+	{
+		CMqttVar *pVar = i->second;
+		pVar->CheckShmAndPublish(pszTopicDevID, pszTopicPrefix, rmq, nLocks);
+	}
+}
+
+void CMqttVarTable::DumpPubEnabled(uint32_t nPubMask)
+{
+#ifdef _DUMP_ENABLED_VARS
+	TRACE("\nList of published variables:\n");
+	for(auto i = m_pub.begin(); i != m_pub.end(); ++i)
+	{
+		CMqttVar *pVar = i->second;
+		uint32_t nMask = pVar->GetPublishMask();
+		
+		if(nMask & nPubMask)
+		{
+			TracePubVar(pVar->GetPath(), nMask & nPubMask, pVar->GetQoS(), pVar->GetRetained());
+		}
+	}
+	TRACE("\n");
+#endif	//	_DUMP_ENABLED_VARS
+}
+
+void CMqttVarTable::TracePubVar(const char *pszPath, uint32_t nMask, int nQos, bool bRetained)
+{
+#ifdef _DUMP_ENABLED_VARS
+	if(pszPath && nMask)
+	{
+		int nCount = 0;
+		TRACE("%s - [", pszPath);
+
+		if(nMask & MQTT_VALUE_BINLE)
+		{
+			TRACE("%s", MQTT_TOPIC_VALUE_BINLE);
+			++nCount;
+		}
+
+		if(nMask & MQTT_VALUE_BINBE)
+		{
+			if(nCount)
+				TRACE(", ");
+			TRACE("%s", MQTT_TOPIC_VALUE_BINBE);
+			++nCount;
+		}
+
+		if(nMask & MQTT_VALUE_JSON)
+		{
+			if(nCount)
+				TRACE(", ");
+			TRACE("%s", MQTT_TOPIC_VALUE_JSON);
+			++nCount;
+		}
+
+		if(nMask & MQTT_VALUE_PBUF)
+		{
+			if(nCount)
+				TRACE(", ");
+			TRACE("%s", MQTT_TOPIC_VALUE_PBUF);
+			++nCount;
+		}
+
+		TRACE("] - [QOS: %d] - [Retained: %s]\n", nQos, bRetained ? "true" : "false");
+	}
+#endif	//	_DUMP_ENABLED_VARS
+}

+ 16 - 0
mqttcl/tls/.gitignore

@@ -0,0 +1,16 @@
+# git ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
+
+client/
+local/
+*.crt
+*.csr
+*.key
+index.*
+serial.*
+*.txt
+*.pem

+ 37 - 0
mqttcl/tls/certgen.sh

@@ -0,0 +1,37 @@
+#!/usr/bin/env bash
+
+CFG_DIR=$(dirname "$0")
+CWD=$(pwd)
+
+cd $CFG_DIR
+
+clear.sh
+
+#Selbst signiertes Zertifikat erstellen (CA):
+openssl req -batch -x509 -config openssl.conf -newkey rsa:4096 -sha256 -nodes -out ca.crt -outform PEM
+
+#Privat Schlüssel und Signier request für Server erstellen (localhost):
+openssl req -batch -config server.conf -newkey rsa:2048 -sha256 -nodes -out server.csr -outform PEM
+
+#Privat Schlüssel und Signier request für Client erstellen:
+openssl req -batch -config client.conf -newkey rsa:2048 -sha256 -nodes -out client.csr -outform PEM
+
+#CA Datenbank anlegen (minimal):
+touch index.txt
+
+#Zertifiakte signieren:
+openssl ca -batch -create_serial -config openssl.conf -policy signing_policy -extensions signing_req -out server.crt -infiles server.csr
+openssl ca -batch -config openssl.conf -policy signing_policy -extensions signing_req -out client.crt -infiles client.csr
+
+sudo cp -f ca.crt /etc/mosquitto/ca_certificates
+sudo cp -f server.crt /etc/mosquitto/certs
+sudo cp -f server.key /etc/mosquitto/certs
+
+mkdir -p ./client
+cp -f ca.crt ./client
+cp -f client.crt ./client
+cp -f client.key ./client
+
+sudo /etc/init.d/mosquitto restart
+
+cd $CWD

+ 11 - 0
mqttcl/tls/clear.sh

@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+
+CFG_DIR=$(dirname "$0")
+CWD=$(pwd)
+
+cd $CFG_DIR
+
+rm -f *.crt *.csr *.key index.* serial.* serial.* *.txt *.pem client/*
+rmdir client &> /dev/null || true
+
+cd $CWD

+ 18 - 0
mqttcl/tls/client.conf

@@ -0,0 +1,18 @@
+[ req ]
+default_bits       = 2048
+default_keyfile    = client.key
+distinguished_name = distinguished_name
+req_extensions     = client_extensions
+
+####################################################################
+[ distinguished_name ]
+
+commonName           = Common Name
+commonName_default   = Client
+
+####################################################################
+[ client_extensions ]
+
+subjectKeyIdentifier = hash
+basicConstraints     = CA:FALSE
+keyUsage             = digitalSignature, keyEncipherment

+ 53 - 0
mqttcl/tls/openssl.conf

@@ -0,0 +1,53 @@
+HOME            = .
+RANDFILE        = $ENV::HOME/.rnd
+
+####################################################################
+[ ca ]
+default_ca    = CA_default
+
+[ CA_default ]
+
+default_days     = 1000
+default_crl_days = 1000
+default_md       = sha256
+preserve         = no
+x509_extensions = ca_extensions
+copy_extensions = copy
+
+certificate   = ca.crt
+private_key   = ca.key
+new_certs_dir = .
+database      = index.txt
+serial        = serial.txt
+
+####################################################################
+[ req ]
+default_bits       = 4096
+default_keyfile    = ca.key
+distinguished_name = distinguished_name
+x509_extensions    = ca_extensions
+string_mask        = utf8only
+
+####################################################################
+[ distinguished_name ]
+commonName         = Common Name
+commonName_default = CA
+
+####################################################################
+[ ca_extensions ]
+
+subjectKeyIdentifier   = hash
+authorityKeyIdentifier = keyid:always, issuer
+basicConstraints       = critical, CA:true
+keyUsage               = keyCertSign, cRLSign
+
+####################################################################
+[ signing_policy ]
+commonName             = supplied
+
+####################################################################
+[ signing_req ]
+subjectKeyIdentifier   = hash
+authorityKeyIdentifier = keyid,issuer
+basicConstraints       = CA:FALSE
+keyUsage               = digitalSignature, keyEncipherment

+ 29 - 0
mqttcl/tls/server.conf

@@ -0,0 +1,29 @@
+[ req ]
+default_bits       = 2048
+default_keyfile    = server.key
+distinguished_name = distinguished_name
+req_extensions     = server_extensions
+utf8               = yes
+string_mask        = utf8only
+
+####################################################################
+[ distinguished_name ]
+commonName           = Common Name
+commonName_default   = Server
+
+####################################################################
+[ server_extensions ]
+
+subjectKeyIdentifier = hash
+basicConstraints     = CA:FALSE
+keyUsage             = digitalSignature, keyEncipherment
+subjectAltName       = @alternate_names
+
+####################################################################
+[ alternate_names ]
+
+DNS.1  = localhost
+DNS.2  = 127.0.0.1
+IP.1   = 127.0.0.1
+IP.2   = ::1
+IP.3   = 192.168.0.106

+ 106 - 0
mqttcl/topic.txt

@@ -0,0 +1,106 @@
+////////////////////////////////////////////////////////////////////////////////////
+
+Allgemeines Topic-Format:
+
+<DeviceID>/<ShmID>/<Format>/<Command>/<VarPath>
+
+<DeviceID>:	Kombination aus Device-Prefix (wird in der Config-Datei konfiguriert (default: "GfA")) und der MAC-Adresse der ersten Ethernetschnittstelle (falls vorhanden "eth0").
+<ShmID>:	Kombination aus dem Prefix "SHM" und der UUID der SHM.
+
+(MAC-Adresse und UUID in Großbuchstaben).
+
+
+////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////
+
+Topic zum Publishen von Variablen-Werten:
+
+<Format>:	"BINLE" (little endian), "BINBE" (big endian), "JSON" oder "PBUF" (Protocol Buffers, nicht implementiert).
+<Command>:	"VALUE".
+
+Beispiel:
+
+"VarioNet-6C:EC:EB:67:5B:13/SHM-F93A047F-D3D0-40E3-965D-08D17E393A19/JSON/VALUE/struct1/struct2/var3": Wert von "struct1.struct2.var3" im JSON-Format.
+
+"VarioNet-6C:EC:EB:67:5B:13/SHM-F93A047F-D3D0-40E3-965D-08D17E393A19/JSON/VALUE/struct1/array1/2": Wert von "struct1.array1[2]" im JSON-Format. ("array1" ist hier ein Array aus skalaren Werten.)
+
+"VarioNet-6C:EC:EB:67:5B:13/SHM-F93A047F-D3D0-40E3-965D-08D17E393A19/BINLE/VALUE/struct1/array2/0/var3": Wert von "struct1.array2[0].var3" im Binärformat (little endian). ("array2" ist hier ein Array aus Strukturen.)
+
+
+
+Das JSON-Format ist wie folgt definiert:
+
+{
+	"path": "/<VarPath>",
+	"index": <Index oder -1>,
+	"name": "<VarName>",
+	"value": <Scalar>
+}
+
+Beispiel:
+
+{
+	"path": "/struct1/struct2/var3",	// vollständiger Pfad
+	"index": -1,						// Variable ist kein Arrayelement
+	"name": "var3",						// Variablenname
+	"value": 1.25						// Wert von "struct1.struct2.var3"
+}
+
+{
+	"path": "/struct1/array1/2",		// vollständiger Pfad
+	"index": 2,							// Index des Arrayelementes
+	"name": "array1",					// Variablenname (ohne Index)
+	"value": "Hello world!"				// Wert von "struct1.array1[2]", ("array1" ist in diesem Beispiel ein Array aus Strings)
+}
+
+{
+	"path": "/struct1/array2/0/var3",	// vollständiger Pfad
+	"index": -1,						// Variable ist kein Arrayelement
+	"name": "var3",						// Variablenname
+	"value": 2563						// Wert von "struct1.array2[0].var3"
+}
+
+
+
+////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////
+
+Topic zum Setzen von Variablen-Werten:
+
+<Format>:	"BINLE" (little endian), "BINBE" (big endian), "JSON" oder "PBUF" (Protocol Buffers, nicht implementiert).
+<Command>:	"SET".
+Beispiel:
+
+"VarioNet-6C:EC:EB:67:5B:13/SHM-F93A047F-D3D0-40E3-965D-08D17E393A19/JSON/SET/struct1/struct2/var3", Payload: {"value":14.5}: Setzt den Wert von "struct1.struct2.var3" im JSON-Format.
+
+"VarioNet-6C:EC:EB:67:5B:13/SHM-F93A047F-D3D0-40E3-965D-08D17E393A19/BINLE/SET/struct1/array2/1/var4", Payload: [binärer Wert]: Setzt den Wert von "struct1.array2[1].var4" im Binärformat (little endian).
+
+
+////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////
+
+Topic-Format zum dynamischen Ein/Ausschalten des Publishing von Variablen und zum Setzen von Parametern.
+Das Topic enthält hier keine <Format> Komponente:
+
+<DeviceID>/<ShmID>/<Command>/<VarPath>
+
+<Command>:	"CONTROL".
+Payload:	JSON-Objekt:
+			{
+				"binLe":		<boolean>, // Binär Little Endian ein/aus
+				"binBe":		<boolean>, // Binär Big Endian ein/aus
+				"json":			<boolean>, // JSON ein/aus
+				"pBuf":			<boolean>, // Protokol Buffers ein/aus (nicht implementiert)
+				"qos":			<number>, // Quality Of Service. 0-2. Nur Integer! Ungültige Werte werden auf den Bereich 0-2 korrigiert.
+				"retained":		<boolean>, // Retain ein/aus
+				"delRetained"	<boolean> // Retained Werte dieser Variablen im Broker löschen (siehe Anmerkung).
+			}
+
+Anmerkung:
+
+Mit "retained" wird bestimmt, ob eine Variable als retained gepublished wird oder nicht. Sobald das einmal geschehen ist, behält der Broker eine Kopie des Wertes in seinem Cache, welcher jedem Client beim Abonnieren des Topics automatisch gesendet wird. Ein späteres Setzen von "retained" auf false hat zur Folge, dass weiter publishings des Wertes als nicht retained erfolgen. Trotzdem behält der Broker aber den letzten Wert, der als retained gepublished wurde weiter im Cache und versendet den Wert auch weiterhin an neue Clients! Um eine "retained"-Variable komplett aus dem Broker zu löschen, wurde "delRetained" eingeführt. Wird "delRetained" auf false gesetzt, hat es keine Auswirkung, "delRetained":true löscht retained Variablen aller Formate aus dem Brkoer, allerdings muss "retained" für diese Variable im selben oder bereits einem vorigen Aufruf auf false gesetzt worden sein.
+
+Beispiel:
+
+"VarioNet-64:31:50:47:BA:DF/SHM-F93A047F-D3D0-40E3-965D-08D17E393A19/CONTROL/struct1/struct2/var3", Payload: {"binLe":false, "json":true, "qos":1}:
+	Schaltet das Publishing der Variablen "struct1.struct2.var3" im Binärformat Little Endian aus und von JSON ein. Setzt QOS auf 1. Alle anderen Parameter bleiben unverändert.

+ 8 - 0
remanent/.gitignore

@@ -0,0 +1,8 @@
+# git ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
+
+*.pro.user

+ 149 - 0
remanent/dbpersist.cpp

@@ -0,0 +1,149 @@
+#include <vector>
+#include "strutil.h"
+#include "mysqlwrap.h"
+#include "remvar.h"
+#include "debug.h"
+
+/////////////////////////////////////////////////////////////////////////////
+
+#define LOGS_FIELDNAME_VALUE					"value"
+#define TAGS_FIELDNAME_TAGID					"tagid"
+#define TAGS_FIELDNAME_PATH						"path"
+
+/////////////////////////////////////////////////////////////////////////////
+
+CDbPersist::CDbPersist(const char *pszDatabaseName, const char *pszTagsTableName, const char *pszLogsTableName, const char *pszDbUser, const char *pszDbPass) :
+	m_sDatabaseName(pszDatabaseName), m_sTagsTableName(pszTagsTableName), m_sLogsTableName(pszLogsTableName), m_sUser(pszDbUser), m_sPass(pszDbPass)
+{
+}
+
+CDbPersist::~CDbPersist(void)
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+bool CDbPersist::Init(CMySqlDB &db)
+{
+	if(!db.Options(MYSQL_SET_CHARSET_NAME, "utf8"))
+	{
+		m_lastError = db.LastError();
+		return false;
+	}
+
+	if(!db.Options(MYSQL_INIT_COMMAND, "SET NAMES utf8"))
+	{
+		m_lastError = db.LastError();
+		return false;
+	}
+
+	if(!db.Connect("localhost", m_sUser.c_str(), m_sPass.c_str(), NULL))
+	{
+		m_lastError = db.LastError();
+		return false;
+	}
+
+	if(db.SelectDB(m_sDatabaseName.c_str()))
+	{
+		m_lastError = db.LastError();
+		return false;
+	}
+
+	std::string sql = formatString("SELECT * FROM `%s` WHERE `logType` IN ('ICR', 'IUR', 'VCR', 'VUR');", m_sTagsTableName.c_str());
+	CMySqlResult res = db.Query(sql.c_str());
+
+	if(!res.error())
+	{
+		CMySqlRow row;
+
+		while(res.FetchRow(row))
+		{
+			CFieldMap fields([](const std::string &s1, const std::string &s2)
+			{
+				return s1.compare(s2) < 0;
+			});
+
+			for(const auto &field : row)
+			{
+				fields[field.first] = field.second.StrVal();
+			}
+
+			m_tags.push_back(fields);
+		}
+
+		return true;
+	}
+	else
+	{
+		m_lastError = db.LastError();
+	}
+
+	return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+int CDbPersist::RestoreValues(const CRemVarTable &map, CLogfile &rlf)
+{
+	int nRet = 0;
+	CMySqlDB db;
+
+	if(!Init(db))
+	{
+        TRACE("Error initializing Datalogger Database: %s!\n", m_lastError.c_str());
+        rlf.Error("Error initializing Datalogger Database: %s!\n", m_lastError.c_str());
+		return -1;
+	}
+
+	for(auto &fields : m_tags)
+	{
+		std::string tagID	= fields[TAGS_FIELDNAME_TAGID];
+		std::string path	= fields[TAGS_FIELDNAME_PATH];
+		CRemanent *pVar		= map.Find(path.c_str());
+
+		if(pVar)
+		{
+			std::string sql = formatString("SELECT `%s` FROM `%s` WHERE `%s` = %s AND `value` IS NOT NULL ORDER BY `tslog` DESC LIMIT 1;", LOGS_FIELDNAME_VALUE, m_sLogsTableName.c_str(), TAGS_FIELDNAME_TAGID, tagID.c_str());
+			CMySqlResult res = db.Query(sql.c_str());
+
+			if(!res.error())
+			{
+				CMySqlRow row;
+
+				if(res.FetchRow(row))
+				{
+					const CMySqlVar &var = row[LOGS_FIELDNAME_VALUE];
+
+					if(var.IsValid() && var.IsReal())
+					{
+//						TRACE("Restoring value %s (%s) from Database: %.2f!\n", tagID.c_str(), path.c_str(), (double)var);
+						if(pVar->SetDoubleValue(var, true))
+							++nRet;
+						else
+						{
+						}
+					}
+					else
+					{
+						m_lastError = db.LastError();
+						TRACE("Unable to  retrieve value %s (%s) from Database: %s!\n", tagID.c_str(), path.c_str(), m_lastError.c_str());
+						rlf.Warning("Unable to  retrieve value %s (%s) from Database: %s!\n", tagID.c_str(), path.c_str(), m_lastError.c_str());
+					}
+				}
+			}
+			else
+			{
+				m_lastError = db.LastError();
+				TRACE("Error retrieving value %s (%s) from Database: %s!\n", tagID.c_str(), path.c_str(), m_lastError.c_str());
+				rlf.Error("Error retrieving value %s (%s) from Database: %s!\n", tagID.c_str(), path.c_str(), m_lastError.c_str());
+			}
+		}
+		else
+		{
+			TRACE("Unable to find %s (%s) in VarTable!\n", tagID.c_str(), path.c_str());
+			rlf.Warning("Unable to find %s (%s) in VarTable!\n", tagID.c_str(), path.c_str());
+		}
+	}
+
+	return nRet;
+}

+ 658 - 0
remanent/main.cpp

@@ -0,0 +1,658 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+#include "projal.h"
+#include "logfile.h"
+#include "fileutil.h"
+#include "strutil.h"
+#include <getopt.h>
+#include <signal.h>
+#include <unistd.h>
+#include <linux/limits.h>
+#include <climits>
+#include "processclock.h"
+#include "instance.h"
+#include "debug.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#define _USE_SIG_INFO						0
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// app control
+
+#define _APPID								GFA_APPCTRL_APPID_REMANENT
+#define _APPNAME							"Remanent"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#define _DEFAULT_SCAN_INTERVAL_MS			1000 // 1 sec
+#define _MIN_SCAN_INTERVAL_MS				100 // 100 ms
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#define _NANOSECS_PER_SEC					1000000000
+#define _MAX_WRITE_CREDITS					60
+#define _MAX_WRITES_PER_MINUTE				6
+#define _SECS_PER_WRITE_INTERVAL			(60 / _MAX_WRITES_PER_MINUTE)
+
+#define _LOGFILE_NAME						"remanent.log"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#define _SIG_BLOCK(s)						sigprocmask(SIG_BLOCK, (s), NULL)
+#define _SIG_UNBLOCK(s)						sigprocmask(SIG_UNBLOCK, (s), NULL)
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static volatile bool						g_fRun			= false;
+static volatile bool						g_fPauseImp		= false;
+static volatile bool						g_fPauseCmd		= false;
+static volatile bool						g_fZombie		= false;
+static int									g_nLastSig		= -1;
+static sigset_t								g_set;
+static CLogfile								g_lf;
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+#if _USE_SIG_INFO
+static void _OnSigAction(int sig, siginfo_t *psi, void *p)
+{
+	TRACE("Received signal '%s', PID: %d, Code: %d.\n", strsignal(sig), psi->si_pid, psi->si_code);
+	g_lf.Info("Received signal '%s', PID: %d, Code: %d.\n", strsignal(sig), psi->si_pid, psi->si_code);
+
+	if((sig != SIGINT) || !psi->si_pid)
+	{
+		g_nLastSig = sig;
+		g_fRun = g_fPauseImp = g_fPauseCmd = g_fZombie = false;
+	}
+}
+#else
+static void _SigHandler(int sig)
+{
+	g_nLastSig = sig;
+	g_fRun = g_fPauseImp = g_fPauseCmd = g_fZombie = false;
+//	g_lf.Info("Received signal '%s'.\n", strsignal(g_nLastSig));
+}
+#endif
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+static long long _TimeSpecDiffNs(const struct timespec *pts1, const struct timespec *pts2)
+{
+	long long ns1 = (long long)pts1->tv_sec * _NANOSECS_PER_SEC + pts1->tv_nsec;
+	long long ns2 = (long long)pts2->tv_sec * _NANOSECS_PER_SEC + pts2->tv_nsec;
+	return (ns2 - ns1);
+}
+
+static bool _IntervalElapsed(struct timespec *pts, long nIntvSeconds)
+{
+	struct timespec ts;
+	clock_gettime(CLOCK_MONOTONIC, &ts);
+	long long diff = _TimeSpecDiffNs(pts, &ts) / _NANOSECS_PER_SEC;
+
+	if(diff >= (long long)nIntvSeconds)
+	{
+		pts->tv_sec += nIntvSeconds;
+		return true;
+	}
+
+	return false;
+}
+
+static const char* _GetBaseDir(std::string &rstrBaseDir)
+{
+	char szBaseDir[PATH_MAX];
+	const char *pszBaseDir = NULL;
+
+#ifdef _LOG_BASE_DIR
+	pszBaseDir = _LOG_BASE_DIR;
+	if(!pszBaseDir || !*pszBaseDir || !::DirectoryExist(pszBaseDir))
+	{
+		CLogfile::StdErr("Invalid base directory config! Using app directory!\n");
+		pszBaseDir = ::GetAppDirectory(szBaseDir, sizeof(szBaseDir));
+	}
+	rstrBaseDir = pszBaseDir;
+#else	//	_LOG_BASE_DIR
+    UNUSED(pszBaseDir);
+	rstrBaseDir = ::GetAppDirectory(szBaseDir, sizeof(szBaseDir));
+#endif	//	_LOG_BASE_DIR
+
+	rtrim(rstrBaseDir, "/");
+	return rstrBaseDir.c_str();
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static void _ProcessCtrlMessages(HAPPCTRL hAC, HAPPINFO hAI)
+{
+    ctrlmsg_t nCtrlMsg;
+
+	while(g_fRun && (nCtrlMsg = ::GfaIpcAppCtrlGetNextCtrlMsg(hAI)))
+	{
+		switch(nCtrlMsg)
+		{
+		case GFA_APPCTRL_CTRLMSG_STOP:
+			g_fRun = false;
+			g_fPauseImp = false;
+			g_fPauseCmd = false;
+			g_fZombie = false;
+			g_lf.Info("Received Control Message 'Stop'\n");
+			break;
+		case GFA_APPCTRL_CTRLMSG_PAUSE:
+			if(!g_fPauseCmd)
+			{
+				g_fPauseCmd = true;
+				if(!g_fPauseImp)
+				{
+					::GfaIpcAppCtrlSetState(hAC, GIAS_Paused);
+					g_lf.Info("Received Control Message 'Pause'\n");
+					g_lf.Info("Enter state %s ...\n", ::GfaIpcAppCtrlGetStateText(GIAS_Paused));
+					TRACE("%-8s: State: %s\n", "Me", ::GfaIpcAppCtrlGetStateText(GIAS_Paused));
+				}
+			}
+			break;
+		case GFA_APPCTRL_CTRLMSG_RESUME:
+			if(g_fPauseCmd)
+			{
+				g_fPauseCmd = false;
+				if(!g_fPauseImp)
+				{
+					g_lf.Info("Received Control Message 'Resume'\n");
+					g_lf.Info("Enter state %s ...\n", ::GfaIpcAppCtrlGetStateText(GIAS_Running));
+					::GfaIpcAppCtrlSetState(hAC, GIAS_Running);
+					TRACE("%-8s: State: %s\n", "Me", ::GfaIpcAppCtrlGetStateText(GIAS_Running));
+				}
+			}
+			break;
+		default:
+			break;
+		}
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+int main(int argc, char **argv)
+{
+	CProcessInstance pi;
+	int nRet = 0, c;
+	HSHM hShm = NULL;
+	void *pShm = NULL;
+	HAPPCTRL hAC = NULL;
+	HAPPINFO hAI;
+	Json::Value root;
+	bool bDbgInvolved = false;
+	bool bHasJSON = false;
+	char szWriteFile[PATH_MAX];
+	char szBackupFile[PATH_MAX];
+	char szLogFile[PATH_MAX];
+	CHECK_UPDATE_SHM_RETVAL rv;
+	struct timespec tsWriteCredits, tsScanIntv;
+	unsigned long cntVals = 0;
+	char *pszEndPtr = NULL;
+	clock64_t nCycleIntervalMS = _DEFAULT_SCAN_INTERVAL_MS;
+	int nWriteCredits = _MAX_WRITE_CREDITS;
+	unsigned int nPendingChanges = 0;
+	struct sigaction sa;
+	std::string strBaseDir;
+	const char *pszBaseDir = NULL;
+	unsigned long long nUsecWorkTime = 0;
+	CProcessClock pcWork, pcPerf;
+	std::string sPerf;
+
+	////////////////////////////////////////////////////////////////////////////////////////////////
+	// check for multiple instances
+
+    if(!pi.LockInstance(UUID_SHM))
+	{
+		CLogfile::StdErr("Failed to start instance!\n");
+		return -1;
+	}
+
+	////////////////////////////////////////////////////////////////////////////////////////////////
+	// parse command line options
+
+	while((c = getopt(argc, argv, "b:c:d")) != -1)
+	{
+		switch(c)
+		{
+		case 'b':
+			if(DirectoryExist(optarg))
+			{
+				strBaseDir = optarg;
+				rtrim(strBaseDir, "/");
+				pszBaseDir = strBaseDir.c_str();
+			}
+			else
+			{
+				CLogfile::StdErr("Invalid base directory option!\n");
+			}
+			break;
+		case 'c':
+			nCycleIntervalMS = strtoll(optarg, &pszEndPtr, 10);
+
+			if((((nCycleIntervalMS == LLONG_MIN) || (nCycleIntervalMS == LLONG_MAX)) && (errno == ERANGE)) || (pszEndPtr && *pszEndPtr))
+			{
+				nCycleIntervalMS = _DEFAULT_SCAN_INTERVAL_MS;
+				CLogfile::StdErr("Invalid cycle interval: \"%s\"! Setting default value (%u)\n", optarg, _DEFAULT_SCAN_INTERVAL_MS);
+			}
+			else if(nCycleIntervalMS < _MIN_SCAN_INTERVAL_MS)
+			{
+				nCycleIntervalMS = _MIN_SCAN_INTERVAL_MS;
+				CLogfile::StdErr("Invalid cycle interval: \"%s\"! Limiting to minimal value (%u)\n", optarg, _MIN_SCAN_INTERVAL_MS);
+			}
+			break;
+		case 'd':
+			bDbgInvolved = true;
+			break;
+		case '?':
+			break;
+		}
+	}
+
+	do
+	{
+		g_fZombie = true;
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+		// get the base directory for output files if not provided by cmd line
+
+		if(!pszBaseDir)
+			pszBaseDir = _GetBaseDir(strBaseDir);
+
+		CLogfile::StdOut("Using base directory \"%s\".\n", pszBaseDir);
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+		// initialize log file
+
+		sprintf(szLogFile, "%s/%s", pszBaseDir, _LOGFILE_NAME);
+
+		if(!g_lf.Open(szLogFile, true, CLogfile::VB_Inf))
+		{
+			CLogfile::StdErr("Failed to create/open log file!\n");
+			nRet = -1;
+			break;
+		}
+
+		g_lf.Info("Process started.\n");
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+		// initialize app control
+
+		g_lf.Info("Acquire AppCtrl-Handle.\n");
+
+		if(!(hAC = ::GfaIpcAppCtrlAcquire(_APPID, _APPNAME, nCycleIntervalMS * 1000, nCycleIntervalMS * 3000)))
+		{
+			g_lf.Error("Failed to acquire AppCtrl-Handle!\n");
+			nRet = -1;
+			break;
+		}
+
+		::GfaIpcAppCtrlSetState(hAC, GIAS_Initializing);
+		g_lf.Info("Enter state %s ...\n", ::GfaIpcAppCtrlGetStateText(GIAS_Initializing));
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+		// validate config parameters
+
+#ifdef _LOG_DB_NAME
+
+		time_t ts;
+		bool bLogDbActive = false;
+
+		if(strlen(_LOG_DB_NAME) >= _RL_MAX_DB_NAME_LENGTH)
+		{
+			g_lf.Error("Log database name too long! %s\n", _LOG_DB_NAME);
+			nRet = -1;
+			break;
+		}
+
+		if(strlen(_LOG_DB_USER) >= _RL_MAX_DB_USER_LENGTH)
+		{
+			g_lf.Error("Log database user name too long! %s\n", _LOG_DB_USER);
+			nRet = -1;
+			break;
+		}
+
+		if(strlen(_LOG_DB_PASS) >= _RL_MAX_DB_PASS_LENGTH)
+		{
+			g_lf.Error("Log database password too long!\n");
+			nRet = -1;
+			break;
+		}
+
+		if(strlen(_LOG_LOGS_TABLE) >= _RL_MAX_TABLE_NAME_LENGTH)
+		{
+			g_lf.Error("Log table name too long! %s\n", _LOG_LOGS_TABLE);
+			nRet = -1;
+			break;
+		}
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+		// configure remanent logger
+
+		RLPARAMS rlp;
+		memset(&rlp, 0, sizeof(rlp));
+		strncpy(rlp.szDBName, _LOG_DB_NAME, _RL_MAX_DB_NAME_LENGTH - 1);
+		strncpy(rlp.szDBUser, _LOG_DB_USER, _RL_MAX_DB_USER_LENGTH - 1);
+		strncpy(rlp.szDBPass, _LOG_DB_PASS, _RL_MAX_DB_PASS_LENGTH - 1);
+		strncpy(rlp.szLogsTable, _LOG_LOGS_TABLE, _RL_MAX_TABLE_NAME_LENGTH - 1);
+#endif	//	_LOG_DB_NAME
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+
+	    _CreateJSONWriteFileName(pszBaseDir, szWriteFile,  _COUNTOF(szWriteFile));
+	    _CreateJSONBackupFileName(pszBaseDir, szBackupFile, _COUNTOF(szBackupFile));
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+		// configure signal handling
+
+		::sigfillset(&g_set);
+		memset(&sa, 0, sizeof(sa));
+
+#if _USE_SIG_INFO
+		sa.sa_flags = SA_SIGINFO;
+		sa.sa_sigaction = _OnSigAction;
+#else
+		sa.sa_handler = _SigHandler;
+#endif
+	    sigaction(SIGHUP, &sa, NULL);	// handles user's terminal disconnect
+	    sigaction(SIGQUIT, &sa, NULL);	// handles Ctrl + '\'
+		sigaction(SIGTERM, &sa, NULL);	// handles normal termination
+		sigaction(SIGABRT, &sa, NULL);	// handles abnormal termination (i.e. abort())
+		sigaction(SIGINT, &sa, NULL);	// handles Ctrl + 'C'
+
+#if _USE_SIG_INFO
+		sa.sa_flags = 0;
+#endif
+		sa.sa_handler = SIG_IGN;
+	    sigaction(SIGTSTP, &sa, NULL);	// ignores Ctrl + 'Z'
+	    sigaction(SIGSTOP, &sa, NULL);	// ignores Stop
+	    sigaction(SIGCONT, &sa, NULL);	// ignores Continue
+	    sigaction(SIGCHLD, &sa, NULL);	// ignores child process termination
+	    sigaction(0, &sa, NULL);		// ignores shell termination
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+		// configure timers
+
+		clock_gettime(CLOCK_MONOTONIC, &tsWriteCredits);
+		tsScanIntv.tv_sec	= nCycleIntervalMS / 1000;
+		tsScanIntv.tv_nsec	= (nCycleIntervalMS % 1000) * 1000000;
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+		// read input SHM file, if any ...
+
+		pcPerf.ClockTrigger();
+	    bHasJSON = ::ParseJSONFile(pszBaseDir, root, g_lf);
+		sPerf = CProcessClock::Interval2String(pcPerf.ClockGetElapsed());
+		
+		if(bHasJSON)
+		{
+			g_lf.Info("Parsing time: %s\n", sPerf.c_str());
+			TRACE("JSON parsing time: %s\n", sPerf.c_str());
+		}
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+
+	    if(!(hShm = ::acquire_shm(sizeof(shm_t), 1)))
+	    {
+			g_lf.Error("Unable to acquire SHM Handle!\n");
+			nRet = -1;
+			break;
+	    }
+
+		g_lf.Info("Acquired SHM Handle.\n");
+
+        if(!(pShm = ::GfaIpcAcquirePointer(hShm)))
+        {
+			g_lf.Error("Unable to acquire SHM Pointer!\n");
+			nRet = -1;
+			break;
+        }
+
+		g_lf.Info("Acquired SHM Pointer.\n");
+        ::GfaIpcDumpSHMROT();
+
+#ifdef _LOG_DB_NAME
+		CRemLogger rl(&rlp);
+		if(!(bLogDbActive = rl.InitDatabase(false)))
+		{
+			g_lf.Error("Failed to initialize Log Database - %s\n", rl.LastError().c_str());
+			g_lf.Warning("Logging will be disabled!\n");
+		}
+#endif	//	_LOG_DB_NAME
+
+    	CRemVarTable map;
+        CShm_t shm(pShm, hShm);
+
+		pcPerf.ClockTrigger();
+        shm.InitPath(NULL, NULL);
+		sPerf = CProcessClock::Interval2String(pcPerf.ClockGetElapsed());
+		TRACE("InitPath (%s).\n", sPerf.c_str());
+		pcPerf.ClockTrigger();
+        shm.CreateMembersTable(map);
+		sPerf = CProcessClock::Interval2String(pcPerf.ClockGetElapsed());
+		TRACE("CreateMembersTable (%s).\n", sPerf.c_str());
+
+		if(bHasJSON)
+		{
+			pcPerf.ClockTrigger();
+        	cntVals = map.LoadJSONValues(root);
+			sPerf = CProcessClock::Interval2String(pcPerf.ClockGetElapsed());
+			g_lf.Info("Successfully restored %lu values from JSON file (%s).\n", cntVals, sPerf.c_str());
+			TRACE("Successfully restored %lu remanent values from JSON file (%s).\n", cntVals, sPerf.c_str());
+        }
+        else
+        {
+			_SIG_BLOCK(&g_set);
+			bool bRet = ::WriteJSONFile(szWriteFile, szBackupFile, static_cast<CRemanent&>(shm));
+			_SIG_UNBLOCK(&g_set);
+			if(!bRet)
+			{
+				g_lf.Error("Failed to initialize JSON file! - %s\n", strerror(errno));
+				TRACE("Failed to initialize JSON file! - %s\n", strerror(errno));
+				break;
+			}
+		}
+
+#ifdef _DL_DB_NAME
+		CDbPersist perst(_DL_DB_NAME, _DL_TAGS_TABLE, _DL_LOGS_TABLE, _DL_DB_USER, _DL_DB_PASS);
+		pcPerf.ClockTrigger();
+		int nRestored = perst.RestoreValues(map, g_lf);
+		sPerf = CProcessClock::Interval2String(pcPerf.ClockGetElapsed());
+
+		if(nRestored > 0)
+		{
+			g_lf.Info("Successfully restored %d Database-persistent values from Datalogger Database (%s).\n", nRestored, sPerf.c_str());
+			TRACE("Successfully restored %d Database-persistent values from Datalogger Database (%s).\n", nRestored, sPerf.c_str());
+		}
+#endif	//	_DL_DB_NAME
+
+		g_fZombie = false;
+		g_fRun = true;
+		::GfaIpcAppCtrlSetState(hAC, GIAS_Running);
+   		g_lf.Info("Enter monitoring loop.\n");
+
+        while(g_fRun)
+    	{
+			////////////////////////////////////////////////////////////////////////////////////////
+			// update app control info
+
+			if((hAI = ::GfaIpcAppCtrlInfoUpdate(hAC, nUsecWorkTime)))
+			{
+				_ProcessCtrlMessages(hAC, hAI);
+				if(!g_fRun)
+					break;
+//				_ProcessStateEvents(hAC, hAI);
+			}
+
+			////////////////////////////////////////////////////////////////////////////////////////
+
+            if(::nanosleep(&tsScanIntv, NULL) < 0)
+            {
+            	if(!g_fRun)
+            		break;
+            }
+
+			pcWork.ClockTrigger();
+
+			if(!g_fPauseImp && !g_fPauseCmd)
+			{
+				if(_IntervalElapsed(&tsWriteCredits, _SECS_PER_WRITE_INTERVAL))
+				{
+					if(nWriteCredits < _MAX_WRITE_CREDITS)
+					{
+						nWriteCredits++;
+			            TRACE("%d sec elapsed - write credits: %d\n", _SECS_PER_WRITE_INTERVAL, nWriteCredits);
+					}
+				}
+
+	        	rv.nRetval = shm.CheckUpdateShm(true);
+
+	        	if(rv.nUpdated || nPendingChanges)
+	    		{
+	    			if(nWriteCredits > 0)
+	    			{
+	    				if(nPendingChanges)
+	    				{
+	    					g_lf.Warning("Reacquired write credit(s). Total %d\n", nWriteCredits);
+	    				}
+
+			            TRACE("%u SHM values changed ...\n", rv.nUpdated ? rv.nUpdated : nPendingChanges);
+
+						_SIG_BLOCK(&g_set);
+						bool bRet = ::WriteJSONFile(szWriteFile, szBackupFile, static_cast<CRemanent&>(shm));
+						_SIG_UNBLOCK(&g_set);
+			            if(!bRet)
+			            {
+							g_lf.Error("Failed to write JSON file! - %s\n", strerror(errno));
+							TRACE("Failed to write JSON file! - %s\n", strerror(errno));
+							g_fRun = false;
+							g_fZombie = true;
+							break;
+			            }
+			            nWriteCredits--;
+			        	nPendingChanges = 0;
+#ifdef _LOG_DB_NAME
+						if(bLogDbActive)
+						{
+							ts = ::time(NULL);
+				        	shm.Log(ts, rl);
+				        	if(!rl.Flush(ts))
+				        	{
+								g_lf.Error("Failed to log to Database - %s\n", rl.LastError().c_str());
+								g_lf.Warning("Logging will be disabled!\n");
+								bLogDbActive = false;
+				        	}
+				        }
+#endif	//	_LOG_DB_NAME
+			            TRACE("write credits left: %d\n", nWriteCredits);
+			        }
+			        else if(!nPendingChanges)
+			        {
+			            std::vector<const CRemanent*> vars;
+			        	nPendingChanges = rv.nUpdated;
+
+			            TRACE("%u SHM value(s) changed, but no write credits left!!!\n", rv.nUpdated);
+						g_lf.Warning("%u SHM value(s) changed, but no write credits left.\n", rv.nUpdated);
+
+#ifdef _DEBUG
+			            if(map.GetMaxUpdateVariables(vars, 5))
+		            	{
+		            		for(auto i = vars.begin(); i != vars.end(); i++)
+		            		{
+		            			const CRemanent *pVar = *i;
+		            			TRACE("%s -> %llu\n", pVar->GetPath(), pVar->GetUpdateCount());
+		            		}
+		            	}
+#endif	//	_DEBUG
+			        }
+
+		    		g_lf.Flush();
+	    		}
+	    	}
+
+			nUsecWorkTime = pcWork.ClockGetElapsed() / 1000;
+		}
+
+        TRACE("Process terminating...\n");
+   		g_lf.Info("Leave monitoring loop.\n");
+
+   		if(!g_fZombie && !bDbgInvolved)
+   		{
+			_SIG_BLOCK(&g_set);
+			bool bRet = ::WriteJSONFile(szWriteFile, szBackupFile, static_cast<CRemanent&>(shm));
+			_SIG_UNBLOCK(&g_set);
+	        if(!bRet)
+	        {
+				g_lf.Error("Failed to write JSON file! - %s\n", strerror(errno));
+				TRACE("Failed to write JSON file! - %s\n", strerror(errno));
+				g_fZombie = true;
+	        }
+	    }
+
+#ifdef _LOG_DB_NAME
+		if(bLogDbActive)
+		{
+			ts = ::time(NULL);
+	    	shm.Log(ts, rl);
+        	if(!rl.Flush(ts))
+        	{
+				g_lf.Error("Failed to log to Database on exit - %s\n", rl.LastError().c_str());
+				bLogDbActive = false;
+        	}
+	    }
+#endif	//	_LOG_DB_NAME
+	}
+	while(false);
+
+	if(g_nLastSig >= 0)
+	{
+		g_lf.Info("Received signal '%s'.\n", strsignal(g_nLastSig));
+		g_nLastSig = -1;
+	}
+
+	if(hShm)
+	{
+		if(pShm)
+		{
+			g_lf.Info("Releasing SHM Pointer ...\n");
+		    ::GfaIpcReleasePointer(hShm, pShm);
+	    }
+
+		g_lf.Info("Releasing SHM Handle ...\n");
+	    ::GfaIpcReleaseSHM(hShm);
+    }
+
+	if(g_fZombie)
+	{
+		if(hAC)
+			::GfaIpcAppCtrlSetState(hAC, GIAS_Zombie);
+		TRACE("Enter Zombie state ...\n");
+		g_lf.Warning("Enter Zombie state ...\n");
+		g_lf.Flush();
+		pause();
+
+		if(g_nLastSig >= 0)
+			g_lf.Info("Received signal '%s'.\n", strsignal(g_nLastSig));
+	}
+
+	if(hAC)
+	{
+		g_lf.Info("Enter state %s ...\n", ::GfaIpcAppCtrlGetStateText(GIAS_Terminating));
+		::GfaIpcAppCtrlSetState(hAC, GIAS_Terminating);
+		g_lf.Info("Releasing App Control ...\n");
+		::GfaIpcAppCtrlRelease(hAC);
+	}
+
+	g_lf.Info("Process exit.\n\n");
+	g_lf.Close();
+	CLogfile::StdErr("Remanent exit.\n");
+	return 0;
+}
+

+ 64 - 0
remanent/remanent.pro

@@ -0,0 +1,64 @@
+TEMPLATE = lib
+CONFIG   += c++11
+CONFIG   -= qt
+CONFIG   += staticlib
+
+QMAKE_CXXFLAGS_DEBUG += -Wno-unused-parameter -Wno-unused-but-set-variable
+QMAKE_CXXFLAGS += -Wstrict-aliasing=0
+# QMAKE_LIBS += -lm -lstdc++ -lgfaipc -ljsoncpp -pthread -l:libcommon.a -lmysqlclient
+# QMAKE_LIBDIR += $$OUT_PWD/../common $$[QT_SYSROOT]/usr/lib/gfa
+QMAKE_RPATHDIR += /usr/lib/gfa
+
+QMAKE_CXXFLAGS += -D_REMANENT -D_LIBBUILD
+QMAKE_CFLAGS += -D_REMANENT -D_LIBBUILD
+
+_LIB_PATH = $$[QT_SYSROOT]/usr/lib/gfa/svc/
+_INC_PATH = $$[QT_SYSROOT]/usr/include/gfa/svc/remanent/
+
+CONFIG(debug, debug|release) {
+	QMAKE_CXXFLAGS -= -Os
+	QMAKE_CFLAGS -= -Os
+    QMAKE_CXXFLAGS += -D_DEBUG
+    QMAKE_CFLAGS += -D_DEBUG
+	TARGET = remanentd
+	QMAKE_CLEAN += libremanentd.a
+}
+
+CONFIG(release, debug|release) {
+    QMAKE_CXXFLAGS += -Wno-unused-parameter -Wno-unused-but-set-variable
+    QMAKE_CFLAGS += -Wno-unused-parameter -Wno-unused-but-set-variable
+	TARGET = remanent
+	QMAKE_CLEAN += libremanent.a
+}
+
+linux-buildroot-g++ {
+    QMAKE_CXXFLAGS += -D_TARGET_BUILD
+    QMAKE_CFLAGS += -D_TARGET_BUILD
+    QMAKE_CXXFLAGS += -DSITARA_BUILD
+    QMAKE_CFLAGS += -DSITARA_BUILD
+}
+
+INCLUDEPATH += ../ ../common $$(GEBGFADEV)
+
+SOURCES += remstrvar.cpp \
+    rembitvar.cpp \
+    remvar.cpp \
+    remvartbl.cpp \
+    remlogger.cpp \
+    dbpersist.cpp
+
+HEADERS += \
+    remvar.h \
+    remlogger.h
+
+includes.path = $$_INC_PATH
+includes.extra += -$(INSTALL_FILE) $$PWD/remvar.h $(INSTALL_ROOT)$$includes.path
+includes.extra += $$escape_expand(\\n\\t)-$(INSTALL_FILE) $$PWD/remlogger.h $(INSTALL_ROOT)$$includes.path
+includes.uninstall += -$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/remvar.h
+includes.uninstall += $$escape_expand(\\n\\t)-$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/remlogger.h
+INSTALLS += includes
+
+library.path = $$_LIB_PATH
+library.extra += -$(INSTALL_FILE) $(TARGET) $(INSTALL_ROOT)$$library.path/$(TARGET)
+library.uninstall += -$(DEL_FILE) $(INSTALL_ROOT)$$library.path/$(TARGET)
+INSTALLS += library

+ 177 - 0
remanent/rembitvar.cpp

@@ -0,0 +1,177 @@
+#include "remvar.h"
+
+#define GET_BOOL_VAL(p, m)				(!!(*p & m))
+#define SET_BIT(p, m)					(*p |= m)
+#define CLR_BIT(p, m)					(*p &= ~m)
+#define STORE_BIT(p, m, b)				(b) ? SET_BIT(p, m) : CLR_BIT(p, m)
+
+/////////////////////////////////////////////////////////////////////////////
+
+CRemBitVariable::CRemBitVariable(void *pData, size_t nOffset, int nBitNr, HSHM hShm, bool bIsDbPersitent, const char *pszName, CRemanent *pParent)
+                            : m_name(pszName), m_pszPath(NULL), m_hShm(hShm), m_pParent(pParent), m_nUpdates(0), m_fLogValue(0.0), m_bMustLog(false), m_bIsDbPersitent(bIsDbPersitent), m_nCbVarpath(0)
+{
+	if(!pData || !hShm || (nBitNr > 7))
+	{
+		ASSERT(false);
+		return;
+	}
+
+	m_mask = (1 << nBitNr);
+    m_pShmByte = (uint8_t*)pData + nOffset;
+    Lock();
+    m_cacheVal = GET_BOOL_VAL(m_pShmByte, m_mask);
+    Unlock();
+}
+
+CRemBitVariable::~CRemBitVariable(void)
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CRemBitVariable::InitPath(CRemanent *pParent, const char *pszMemberName)
+{
+	if(!pszMemberName)
+		pszMemberName = "";
+
+	if(pParent)
+	{
+		m_path = pParent->GetPath();
+		if(m_path.size() > 0)
+			m_path += "/";
+		m_path += pszMemberName;
+	}
+	else
+	{
+		m_path = pszMemberName;
+	}
+
+	m_pszPath = m_path.c_str();
+	m_nCbVarpath = m_path.length();
+}
+
+bool CRemBitVariable::SaveJSON(FILE *f, int nIndent, bool bValueOnly, bool bWriteComma)
+{
+	if(m_bIsDbPersitent)
+		return true;
+
+	if(bWriteComma)
+	{
+		if(fprintf(f, ",") < 0)
+			return false;
+	}
+
+	if(!bValueOnly)
+	{
+	    if(fprintf(f, "\n") < 0)
+			return false;
+
+		if(nIndent > 0)
+		{
+		    if(fprintf(f, "%*s", nIndent * _JSON_SPACES_PER_TAB, "") < 0)
+				return false;
+		}
+
+	    if(fprintf(f, "\"%s\": ", m_name.c_str()) < 0)
+			return false;
+	}
+
+    if(fprintf(f, "%s", m_cacheVal ? "true" : "false") < 0)
+		return false;
+	return true;
+}
+
+void CRemBitVariable::CreateMembersTable(CRemVarTable &vt)
+{
+	if(!m_bIsDbPersitent)
+		vt.AddVar(static_cast<CRemanent*>(this));
+}
+
+bool CRemBitVariable::SetJSONValue(const Json::Value &jv, bool fLock)
+{
+	bool bRet = true;
+
+	if(fLock)
+		Lock();
+
+	switch(jv.type())
+	{
+	case Json::booleanValue:
+		m_cacheVal = jv.asBool();
+		break;
+	case Json::intValue:
+		m_cacheVal = !!jv.asInt();
+		break;
+	default:
+		bRet = false;
+	}
+
+	if(bRet)
+	{
+		STORE_BIT(m_pShmByte, m_mask, m_cacheVal);
+	}
+
+	if(fLock)
+		Unlock();
+	return bRet;
+}
+
+bool CRemBitVariable::SetDoubleValue(double val, bool fLock)
+{
+	m_cacheVal = !!val;
+	if(fLock) Lock();
+	STORE_BIT(m_pShmByte, m_mask, m_cacheVal);
+	if(fLock) Unlock();
+	return true;
+}
+
+unsigned long long CRemBitVariable::CheckUpdateShm(bool fLock)
+{
+	bool bVal;
+	CHECK_UPDATE_SHM_RETVAL rv = {1, 0};
+
+	if(m_bIsDbPersitent)
+		return rv.nRetval;
+
+	if(fLock)
+		Lock();
+
+    bVal = GET_BOOL_VAL(m_pShmByte, m_mask);
+
+	if(m_cacheVal != bVal)
+	{
+		m_cacheVal = bVal;
+		m_fLogValue = m_cacheVal ? 1.0 : 0.0;
+		rv.nUpdated = 1;
+	}
+
+	if(fLock)
+		Unlock();
+
+	if(rv.nUpdated)
+	{
+		m_bMustLog = true;
+		m_nUpdates++;
+	}
+
+	return rv.nRetval;
+}
+
+void CRemBitVariable::Log(time_t ts, CRemLogger &rlogger)
+{
+	if(!m_bIsDbPersitent && m_bMustLog)
+	{
+		rlogger.Log(m_pszPath, m_nCbVarpath, m_fLogValue, NULL, 0, ts, true);
+		m_bMustLog = false;
+	}
+}
+
+void CRemBitVariable::Lock(void)
+{
+	::GfaIpcLockSHM(m_hShm);
+}
+
+void CRemBitVariable::Unlock(void)
+{
+	::GfaIpcUnlockSHM(m_hShm);
+}

+ 186 - 0
remanent/remlogger.cpp

@@ -0,0 +1,186 @@
+#include <string.h>
+#include <malloc.h>
+#include <limits.h>
+#include "remlogger.h"
+
+/////////////////////////////////////////////////////////////////////////////
+
+CRemLogger::CRemLogger(LPCRLPARAMS pdlp)
+{
+	memset(&m_rlp, 0, sizeof(m_rlp));
+
+	if(pdlp)
+		memcpy(&m_rlp, pdlp, sizeof(m_rlp));
+}
+
+CRemLogger::~CRemLogger(void)
+{
+}
+
+bool CRemLogger::InitDatabase(bool bCreateAlways)
+{
+	CMySqlDB db;
+
+	if(!db.Options(MYSQL_SET_CHARSET_NAME, "utf8"))
+	{
+		m_lastError = db.LastError();
+		return false;
+	}
+
+	if(!db.Options(MYSQL_INIT_COMMAND, "SET NAMES utf8"))
+	{
+		m_lastError = db.LastError();
+		return false;
+	}
+
+	if(!db.Connect("localhost", m_rlp.szDBUser, m_rlp.szDBPass, NULL))
+	{
+		m_lastError = db.LastError();
+		return false;
+	}
+
+	if(!CreateDatabase(db, bCreateAlways))
+		return false;
+
+	if(db.SelectDB(m_rlp.szDBName))
+	{
+		m_lastError = db.LastError();
+		return false;
+	}
+
+	if(!CreateLogsTable(db))
+		return false;
+
+	return true;
+}
+
+bool CRemLogger::CreateDatabase(CMySqlDB &rdb, bool bCreateAlways)
+{
+	char szSql[_RL_MAX_DB_NAME_LENGTH + 64];
+
+	if(bCreateAlways)
+	{
+		sprintf(szSql, "drop database if exists `%s`", m_rlp.szDBName);
+		rdb.Query(szSql);
+	}
+
+	sprintf(szSql, "create database if not exists `%s`", m_rlp.szDBName);
+	CMySqlResult res = rdb.Query(szSql);
+	bool bError = res.error();
+
+	if(bError)
+		m_lastError = rdb.LastError();
+
+	return !bError;
+}
+
+bool CRemLogger::CreateLogsTable(CMySqlDB &rdb)
+{
+	char szSql[_RL_MAX_TABLE_NAME_LENGTH + 512];
+	const char *pszFormat =
+	"CREATE TABLE IF NOT EXISTS `%s` (" \
+	" `id` int(11) unsigned NOT NULL AUTO_INCREMENT," \
+	" `tslog` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," \
+	" `path` varchar(%u) CHARACTER SET ascii NOT NULL," \
+	" `nvalue` double DEFAULT NULL," \
+	" `svalue` varchar(%u) DEFAULT NULL," \
+	" KEY `nvalue` (`nvalue`)," \
+	" KEY `path` (`path`)," \
+	" KEY `tslog` (`tslog`)," \
+	" PRIMARY KEY (`id`)" \
+	") ENGINE=InnoDB DEFAULT CHARSET=utf8";
+
+	sprintf(szSql, pszFormat, m_rlp.szLogsTable, _RL_MAX_VARPATH_LENGTH, _RL_MAX_STRVAL_LENGTH);
+	CMySqlResult res = rdb.Query(szSql);
+	bool bError = res.error();
+
+	if(bError)
+		m_lastError = rdb.LastError();
+
+	return !bError;
+}
+
+bool CRemLogger::Log(const char *pszVarPath, size_t nCbVarPath, double fValue, const char *pszStrValue, size_t nCbStrValue, time_t nTimestamp, bool fNumeric)
+{
+	REM_LOG_ENTRY log;
+	memset(&log, 0, sizeof(log));
+	log.nTimestamp = nTimestamp;
+	log.fNumeric = fNumeric;
+	nCbVarPath = _MIN(nCbVarPath, (_RL_MAX_VARPATH_LENGTH - 1));
+	memcpy(log.szVarPath, pszVarPath, nCbVarPath);
+
+	if(fNumeric)
+	{
+		log.fValue = fValue;
+	}
+	else
+	{
+		nCbStrValue = _MIN(nCbStrValue, (_RL_MAX_STRVAL_LENGTH - 1));
+		memcpy(log.szStrVal, pszStrValue, nCbStrValue);
+	}
+
+	if(m_logs.size() < _RL_MAX_BACKLOG)
+		m_logs.push_back(log);
+	return true;
+}
+
+bool CRemLogger::Flush(time_t nTimestamp)
+{
+	CMySqlDB db;
+
+	if(!db.Options(MYSQL_SET_CHARSET_NAME, "utf8"))
+	{
+		m_lastError = db.LastError();
+		return false;
+	}
+
+	if(!db.Options(MYSQL_INIT_COMMAND, "SET NAMES utf8"))
+	{
+		m_lastError = db.LastError();
+		return false;
+	}
+
+	std::string strSql;
+	bool bError = false;
+	char szSql[_RL_MAX_TABLE_NAME_LENGTH + _RL_MAX_VARPATH_LENGTH + _RL_MAX_STRVAL_LENGTH * 2 + 100];
+
+	if(!db.Connect("localhost", m_rlp.szDBUser, m_rlp.szDBPass, m_rlp.szDBName))
+	{
+		m_lastError = db.LastError();
+		return false;
+	}
+
+	auto nSize = m_logs.size();
+
+	if(nSize > 0)
+	{
+		strSql.reserve((_RL_MAX_VARPATH_LENGTH + _RL_MAX_STRVAL_LENGTH * 2 + 50) * nSize);
+
+		auto itFirst = m_logs.begin();
+		const REM_LOG_ENTRY &rle0 = *itFirst;
+		if(rle0.fNumeric)
+			sprintf(szSql, "insert into `%s` (`path`, `nvalue`, `svalue`) values ('%s', %.20g, NULL)", m_rlp.szLogsTable, rle0.szVarPath, rle0.fValue);
+		else
+			sprintf(szSql, "insert into `%s` (`path`, `nvalue`, `svalue`) values ('%s', NULL, '%s')", m_rlp.szLogsTable, rle0.szVarPath, db.EscapeString(rle0.szStrVal).c_str());
+		strSql = szSql;
+
+		for(++itFirst; itFirst < m_logs.end(); itFirst++)
+		{
+			const REM_LOG_ENTRY &rle = *itFirst;
+			if(rle.fNumeric)
+				sprintf(szSql, ",('%s', %.20g, NULL)", rle.szVarPath, rle.fValue);
+			else
+				sprintf(szSql, ",('%s', NULL, '%s')", rle.szVarPath, db.EscapeString(rle.szStrVal).c_str());
+			strSql += szSql;
+		}
+
+		CMySqlResult res = db.Query(strSql.c_str());
+		bError = res.error();
+
+		if(bError)
+			m_lastError = db.LastError();
+	}
+
+	m_logs.clear();
+	return !bError;
+}

+ 72 - 0
remanent/remlogger.h

@@ -0,0 +1,72 @@
+// remlogger.h :
+//
+
+#if !defined(AGD_REMLOGGER_H__21BAEB7B_E478_4ED1_B9F6_FDBCE620C56B__INCLUDED_)
+#define AGD_REMLOGGER_H__21BAEB7B_E478_4ED1_B9F6_FDBCE620C56B__INCLUDED_
+
+#include <vector>
+#ifndef _LIBBUILD
+#include <gfa/svc/common/mysqlwrap.h>
+#else	//	_LIBBUILD
+#include "mysqlwrap.h"
+#endif	//	_LIBBUILD
+
+/////////////////////////////////////////////////////////////////////////////
+// datalogger.h - Declarations:
+
+#define _RL_MAX_DB_NAME_LENGTH			64
+#define _RL_MAX_DB_USER_LENGTH			64
+#define _RL_MAX_DB_PASS_LENGTH			64
+#define _RL_MAX_TABLE_NAME_LENGTH		64
+#define _RL_MAX_VARPATH_LENGTH			1000
+#define _RL_MAX_STRVAL_LENGTH			1000
+#define _RL_MAX_BACKLOG					100
+#define _MIN(x, y)						((x) < (y) ? (x) : (y))
+
+typedef struct _RLPARAMS
+{
+	char szDBName[_RL_MAX_DB_NAME_LENGTH];
+	char szDBUser[_RL_MAX_DB_USER_LENGTH];
+	char szDBPass[_RL_MAX_DB_PASS_LENGTH];
+	char szLogsTable[_RL_MAX_TABLE_NAME_LENGTH];
+}RLPARAMS, *LPRLPARAMS;
+typedef const RLPARAMS *LPCRLPARAMS;
+
+
+typedef struct _REM_LOG_ENTRY
+{
+	time_t nTimestamp;
+	bool fNumeric;
+	double fValue;
+	char szVarPath[_RL_MAX_VARPATH_LENGTH];
+	char szStrVal[_RL_MAX_STRVAL_LENGTH];
+}REM_LOG_ENTRY, *LPREM_LOG_ENTRY;
+typedef const REM_LOG_ENTRY *LPCREM_LOG_ENTRY;
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CRemLogger
+{
+public:
+	CRemLogger(LPCRLPARAMS pdlp);
+	virtual ~CRemLogger(void);
+	
+	bool InitDatabase(bool bCreateAlways = false);
+	bool Log(const char *pszVarPath, size_t nCbVarPath, double fValue, const char *pszStrValue, size_t nCbStrValue, time_t nTimestamp, bool fNumeric);
+	bool Flush(time_t nTimestamp);
+	
+	std::string LastError(void) const {
+		return m_lastError;}
+
+private:
+	bool CreateDatabase(CMySqlDB &rdb, bool bCreateAlways = false);
+	bool CreateLogsTable(CMySqlDB &rdb);
+	
+private:
+	RLPARAMS m_rlp;
+	std::vector<REM_LOG_ENTRY> m_logs;
+	std::string m_lastError;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	!defined(AGD_REMLOGGER_H__21BAEB7B_E478_4ED1_B9F6_FDBCE620C56B__INCLUDED_)

+ 402 - 0
remanent/remstrvar.cpp

@@ -0,0 +1,402 @@
+#include "remvar.h"
+#include "conv.h"
+
+#define _IS_VALID_VT(vt)		((vt > CRemStringVariable::VT_Invalid) && (vt < CRemStringVariable::VT_Last))
+#define __min(x, y) 			((x) < (y) ? (x) : (y))
+#define __max(x, y) 			((x) > (y) ? (x) : (y))
+
+/////////////////////////////////////////////////////////////////////////////
+
+CRemStringVariable::CRemStringVariable(void *pData, size_t nCChData, VT vt, const std::type_info &rti, HSHM hShm, bool bIsDbPersitent, const char *pszName, int nIndex, CRemanent *pParent)
+										: m_name(pszName), m_pszPath(NULL), m_nIndex(nIndex), m_vt(VT_Invalid), m_data({NULL}), m_cache({NULL}), m_nCbString(0),
+										m_pParent(pParent), m_nUpdates(0), m_bMustLog(false), m_bIsDbPersitent(bIsDbPersitent), m_nCbVarpath(0), m_nCbLog(0)
+{
+    if(!pData || !hShm || !nCChData || !_IS_VALID_VT(vt))
+	{
+		ASSERT(false);
+		return;
+	}
+
+	m_vt = vt;
+    m_data.pVoid = pData;
+    m_hShm = hShm;
+
+    memset(m_szLog, 0, sizeof(m_szLog));
+
+    if( (rti == typeid(char)) ||
+        (rti == typeid(signed char)) ||
+        (rti == typeid(unsigned char)))
+    {
+    	switch(vt)
+    	{
+   		case VT_Latin1:
+   		case VT_UTF_8:
+			m_nCbBuffer = nCChData;
+			m_cache.pVoid = ::malloc(m_nCbBuffer);
+			zeroTerm(m_data, nCChData - 1);
+			memcpy(m_cache.pVoid, m_data.pVoid, m_nCbBuffer);
+			m_nCbString = strlen(m_cache.pszMbs);
+   			break;
+   		case VT_UTF_16:
+   		case VT_UTF_32:
+		case VT_Unicode:
+			ASSERT(false);
+   			return;
+   		default:
+			ASSERT(false);
+   			return;
+    	}
+    }
+    else if(rti == typeid(char16_t))
+	{
+    	switch(vt)
+    	{
+   		case VT_UTF_16:
+			m_nCbBuffer = nCChData * sizeof(char16_t);
+			m_cache.pVoid = malloc(m_nCbBuffer);
+			zeroTerm(m_data, nCChData - 1);
+			memcpy(m_cache.pVoid, m_data.pVoid, m_nCbBuffer);
+			m_nCbString = wcs16len(m_cache.pszWc16) * sizeof(char16_t);
+   			break;
+		case VT_Unicode:
+   		case VT_Latin1:
+   		case VT_UTF_8:
+   		case VT_UTF_32:
+			ASSERT(false);
+   			return;
+   		default:
+			ASSERT(false);
+   			return;
+    	}
+	}
+    else if(rti == typeid(char32_t))
+	{
+    	switch(vt)
+    	{
+   		case VT_UTF_32:
+			m_nCbBuffer = nCChData * sizeof(char32_t);
+			m_cache.pVoid = malloc(m_nCbBuffer);
+			zeroTerm(m_data, nCChData - 1);
+			memcpy(m_cache.pVoid, m_data.pVoid, m_nCbBuffer);
+			m_nCbString = wcs32len(m_cache.pszWc32) * sizeof(char32_t);
+   			break;
+		case VT_Unicode:
+   		case VT_Latin1:
+   		case VT_UTF_8:
+   		case VT_UTF_16:
+			ASSERT(false);
+   			return;
+   		default:
+			ASSERT(false);
+   			return;
+    	}
+	}
+    else if(rti == typeid(wchar_t))
+	{
+    	switch(vt)
+    	{
+		case VT_Unicode:
+			m_nCbBuffer = nCChData * sizeof(wchar_t);
+			m_cache.pVoid = malloc(m_nCbBuffer);
+			zeroTerm(m_data, nCChData - 1);
+			memcpy(m_cache.pVoid, m_data.pVoid, m_nCbBuffer);
+			m_nCbString = wcslen(m_cache.pszWcs) * sizeof(wchar_t);
+   			break;
+   		case VT_Latin1:
+   		case VT_UTF_8:
+   		case VT_UTF_16:
+   		case VT_UTF_32:
+			ASSERT(false);
+   			return;
+   		default:
+			ASSERT(false);
+   			return;
+    	}
+	}
+    else
+	{
+		ASSERT(false);
+	}
+}
+
+CRemStringVariable::~CRemStringVariable(void)
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CRemStringVariable::InitPath(CRemanent *pParent, const char *pszMemberName)
+{
+	if(!pszMemberName)
+		pszMemberName = "";
+
+	if(pParent)
+	{
+		m_path = pParent->GetPath();
+		if(m_path.size() > 0)
+			m_path += "/";
+		m_path += pszMemberName;
+
+		if(m_nIndex >= 0)
+		{
+			char szIndex[32];
+			sprintf(szIndex, "[%d]", m_nIndex);
+			m_path += szIndex;
+		}
+	}
+	else
+	{
+		m_path = pszMemberName;
+	}
+
+	m_pszPath = m_path.c_str();
+	m_nCbVarpath = m_path.length();
+}
+
+#if 0
+static int _fnprintf(FILE *f, size_t n, const char * format, ...)
+{
+	if(!n)
+		return 0;
+
+	int nRet;
+	char *pszBuffer = malloc(n);
+	va_list args;
+	va_start (args, format);
+	nRet = vsnprintf(pszBuffer, n, format, args);
+	va_end (args);
+
+	if(nRet > 0)
+		nRet = (int)fwrite(pszBuffer, nRet, 1, f);
+
+	free(pszBuffer);
+	return nRet;
+}
+#endif
+
+bool CRemStringVariable::SaveJSON(FILE *f, int nIndent, bool bValueOnly, bool bWriteComma)
+{
+	if(m_bIsDbPersitent)
+		return true;
+
+	size_t nToWrite;
+
+	if(bWriteComma)
+	{
+		if(fprintf(f, ",") < 0)
+			return false;
+	}
+
+	if(!bValueOnly)
+	{
+	    if(fprintf(f, "\n") < 0)
+			return false;
+
+		if(nIndent > 0)
+		{
+		    if(fprintf(f, "%*s", nIndent * _JSON_SPACES_PER_TAB, "") < 0)
+				return false;
+		}
+
+	    if(fprintf(f, "\"%s\": ", m_name.c_str()) < 0)
+			return false;
+	}
+
+	switch(m_vt)
+	{
+	case VT_Latin1:
+	case VT_UTF_8:
+		if(fputc('\"', f) == EOF)
+			return false;
+		nToWrite = _MIN(m_nCbString, (m_nCbBuffer - 1));
+		if(fwrite(m_cache.pszMbs, 1, nToWrite, f) != nToWrite)
+			return false;
+		if(fputc('\"', f) == EOF)
+			return false;
+		break;
+	default:
+		break;
+	}
+
+	return true;
+}
+
+void CRemStringVariable::CreateMembersTable(CRemVarTable &vt)
+{
+	if(!m_bIsDbPersitent)
+		vt.AddVar(static_cast<CRemanent*>(this));
+}
+
+bool CRemStringVariable::SetJSONValue(const Json::Value &jv, bool fLock)
+{
+	if(jv.type() != Json::stringValue)
+		return false;
+
+	const char *pszStr = jv.asCString();
+
+	if(!pszStr || !*pszStr)
+	{
+		memset(m_cache.pVoid, 0, m_nCbBuffer);
+		if(fLock) Lock();
+		memset(m_data.pVoid, 0, m_nCbBuffer);
+		if(fLock) Unlock();
+		return true;
+	}
+
+	size_t nLen = strlen(pszStr);
+
+	switch(m_vt)
+	{
+	case VT_Latin1:
+		Utf8ToLatin1(pszStr, nLen, m_cache.pszMbs, m_nCbBuffer);
+		m_nCbString = strlen(m_cache.pszMbs);
+		break;
+	case VT_UTF_8:
+		if(nLen >= m_nCbBuffer)
+			nLen = m_nCbBuffer - 1;
+		memset(m_cache.pszMbs, 0, m_nCbBuffer);
+		memcpy(m_cache.pszMbs, pszStr, nLen);
+		m_nCbString = nLen;
+	    break;
+	case VT_UTF_16:
+		Utf8ToUtf16(pszStr, nLen, m_cache.pszWc16, m_nCbBuffer / sizeof(char16_t));
+		m_nCbString = wcs16len(m_cache.pszWc16) * sizeof(char16_t);
+	    break;
+	case VT_UTF_32:
+		Utf8ToUtf32(pszStr, nLen, m_cache.pszWc32, m_nCbBuffer / sizeof(char32_t));
+		m_nCbString = wcs32len(m_cache.pszWc32) * sizeof(char32_t);
+	    break;
+	case VT_Unicode:
+		Utf8ToWcs(pszStr, nLen, m_cache.pszWcs, m_nCbBuffer / sizeof(wchar_t));
+		m_nCbString = wcslen(m_cache.pszWcs) * sizeof(wchar_t);
+		break;
+	default:
+		return false;
+	}
+
+	if(fLock) Lock();
+	memcpy(m_data.pVoid, m_cache.pVoid, m_nCbBuffer);
+	if(fLock) Unlock();
+	return true;
+}
+
+unsigned long long CRemStringVariable::CheckUpdateShm(bool fLock)
+{
+	CHECK_UPDATE_SHM_RETVAL rv = {1, 0};
+
+	if(m_bIsDbPersitent)
+		return rv.nRetval;
+
+	size_t nCChBuf = 0;
+
+	if(fLock)
+		Lock();
+
+	if(shmChanged(false))
+	{
+		switch(m_vt)
+		{
+		case VT_Latin1:
+			nCChBuf = m_nCbBuffer - 1;
+			zeroTerm(m_data, nCChBuf);
+			m_nCbString = strlen(m_data.pszMbs);
+			m_nCbLog = ::Latin1ToUtf8(m_data.pszMbs, m_nCbString, m_szLog, _RL_MAX_STRVAL_LENGTH);
+			break;
+		case VT_UTF_8:
+			nCChBuf = m_nCbBuffer - 1;
+			zeroTerm(m_data, nCChBuf);
+			m_nCbString = strlen(m_data.pszMbs);
+			m_nCbLog = _MIN(m_nCbString, _RL_MAX_STRVAL_LENGTH - 1);
+			memcpy(m_szLog, m_data.pszMbs, m_nCbLog);
+			m_szLog[m_nCbLog] = '\0';
+			break;
+		case VT_UTF_16:
+			nCChBuf = m_nCbBuffer / sizeof(char16_t) - 1;
+			zeroTerm(m_data, nCChBuf);
+			m_nCbString = wcs16len(m_data.pszWc16) * sizeof(char16_t);
+			m_nCbLog = ::Utf16ToUtf8(m_data.pszWc16, m_nCbString / sizeof(char16_t), m_szLog, _RL_MAX_STRVAL_LENGTH);
+			break;
+		case VT_UTF_32:
+			nCChBuf = m_nCbBuffer / sizeof(char32_t) - 1;
+			zeroTerm(m_data, nCChBuf);
+			m_nCbString = wcs32len(m_data.pszWc32) * sizeof(char32_t);
+			m_nCbLog = ::Utf32ToUtf8(m_data.pszWc32, m_nCbString / sizeof(char32_t), m_szLog, _RL_MAX_STRVAL_LENGTH);
+			break;
+		case VT_Unicode:
+			nCChBuf = m_nCbBuffer / sizeof(wchar_t) - 1;
+			zeroTerm(m_data, nCChBuf);
+			m_nCbString = wcslen(m_data.pszWcs) * sizeof(wchar_t);
+			m_nCbLog = ::WcsToUtf8(m_data.pszWcs, m_nCbString / sizeof(wchar_t), m_szLog, _RL_MAX_STRVAL_LENGTH);
+			break;
+		default:
+			if(fLock)
+				Unlock();
+			return rv.nRetval;
+		}
+
+		memcpy(m_cache.pVoid, m_data.pVoid, m_nCbBuffer);
+		rv.nUpdated = 1;
+	}
+
+	if(fLock)
+		Unlock();
+
+	if(rv.nUpdated)
+	{
+		m_bMustLog = true;
+		m_nUpdates++;
+	}
+
+	return rv.nRetval;
+}
+
+void CRemStringVariable::Log(time_t ts, CRemLogger &rlogger)
+{
+	if(!m_bIsDbPersitent && m_bMustLog)
+	{
+		rlogger.Log(m_pszPath, m_nCbVarpath, 0.0, m_szLog, m_nCbLog, ts, false);
+		m_bMustLog = false;
+	}
+}
+
+bool CRemStringVariable::shmChanged(bool fLock)
+{
+	bool bRet;
+	if(fLock) Lock();
+	bRet =  !!memcmp(m_cache.pVoid, m_data.pVoid, m_nCbString + 1);
+	if(fLock) Unlock();
+	return bRet;
+}
+
+void CRemStringVariable::Lock(void)
+{
+	::GfaIpcLockSHM(m_hShm);
+}
+
+void CRemStringVariable::Unlock(void)
+{
+	::GfaIpcUnlockSHM(m_hShm);
+}
+
+void CRemStringVariable::zeroTerm(volatile V_Ptr &rp, size_t at)
+{
+	switch(m_vt)
+	{
+	case VT_Latin1:
+	case VT_UTF_8:
+		rp.pszMbs[at] = '\0';
+		break;
+	case VT_UTF_16:
+		rp.pszWc16[at] = (char16_t)0;
+		break;
+	case VT_UTF_32:
+		rp.pszWc32[at] = (char32_t)0;
+		break;
+	case VT_Unicode:
+		rp.pszWcs[at] = L'\0';
+		break;
+	default:
+		ASSERT(false);
+		return;
+	}
+}

+ 602 - 0
remanent/remvar.cpp

@@ -0,0 +1,602 @@
+#include "remvar.h"
+
+/////////////////////////////////////////////////////////////////////////////
+
+CRemVariable::CRemVariable(void *pData, const std::type_info &rti, HSHM hShm, bool bIsDbPersitent, const char *pszName, int nIndex, CRemanent *pParent)
+							: m_name(pszName), m_pszPath(NULL), m_nIndex(nIndex), m_vt(VT_Invalid), m_data({NULL}), m_pParent(pParent), m_nUpdates(0), m_fLogValue(0.0),
+							m_bMustLog(false), m_bIsDbPersitent(bIsDbPersitent), m_nCbVarpath(0)
+{
+    if(!pData || !hShm)
+	{
+		ASSERT(false);
+		return;
+	}
+
+    m_data.pVoid = pData;
+    m_hShm = hShm;
+
+    if(rti == typeid(bool))
+    {
+    	m_vt = VT_bool;
+        m_cache.boolVal = *m_data.pBool;
+    }
+    else if(rti == typeid(char))
+    {
+#ifdef __CHAR_UNSIGNED__
+    	m_vt = VT_UI1;
+        m_cache.UI1Val = *m_data.pUI1;
+#else
+        m_vt = VT_I1;
+        m_cache.I1Val = *m_data.pI1;
+#endif
+    }
+    else if(rti == typeid(signed char))
+    {
+        m_vt = VT_I1;
+        m_cache.I1Val = *m_data.pI1;
+    }
+    else if(rti == typeid(unsigned char))
+    {
+    	m_vt = VT_UI1;
+        m_cache.UI1Val = *m_data.pUI1;
+    }
+    else if(rti == typeid(short))
+    {
+    	m_vt = VT_I2;
+        m_cache.I2Val = *m_data.pI2;
+    }
+    else if(rti == typeid(unsigned short))
+    {
+    	m_vt = VT_UI2;
+        m_cache.UI2Val = *m_data.pUI2;
+    }
+    else if(rti == typeid(int))
+    {
+    	m_vt = VT_I4;
+        m_cache.I4Val = *m_data.pI4;
+    }
+    else if(rti == typeid(unsigned int))
+    {
+    	m_vt = VT_UI4;
+        m_cache.UI4Val = *m_data.pUI4;
+    }
+    else if(rti == typeid(long))
+    {
+    	if(sizeof(long) == sizeof(long long))
+    	{
+	    	m_vt = VT_I8;
+            m_cache.I8Val = *m_data.pI8;
+	    }
+    	else
+    	{
+	    	m_vt = VT_I4;
+	        m_cache.I4Val = *m_data.pI4;
+	    }
+    }
+    else if(rti == typeid(unsigned long))
+    {
+    	if(sizeof(unsigned long) == sizeof(unsigned long long))
+    	{
+	    	m_vt = VT_UI8;
+            m_cache.UI8Val = *m_data.pUI8;
+	    }
+    	else
+    	{
+	    	m_vt = VT_UI4;
+	        m_cache.UI4Val = *m_data.pUI4;
+	    }
+    }
+    else if(rti == typeid(long long))
+    {
+    	m_vt = VT_I8;
+		m_cache.I8Val = *m_data.pI8;
+    }
+    else if(rti == typeid(unsigned long long))
+    {
+    	m_vt = VT_UI8;
+		m_cache.UI8Val = *m_data.pUI8;
+    }
+    else if(rti == typeid(float))
+    {
+    	m_vt = VT_float;
+        m_cache.FloatVal = *m_data.pFloat;
+    }
+    else if(rti == typeid(double))
+    {
+    	m_vt = VT_double;
+        m_cache.DoubleVal = *m_data.pDouble;
+    }
+    else
+	{
+		ASSERT(false);
+	}
+}
+
+CRemVariable::~CRemVariable(void)
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CRemVariable::InitPath(CRemanent *pParent, const char *pszMemberName)
+{
+	if(!pszMemberName)
+		pszMemberName = "";
+
+	if(pParent)
+	{
+		m_path = pParent->GetPath();
+		if(m_path.size() > 0)
+			m_path += "/";
+		m_path += pszMemberName;
+
+		if(m_nIndex >= 0)
+		{
+			char szIndex[32];
+			sprintf(szIndex, "[%d]", m_nIndex);
+			m_path += szIndex;
+		}
+	}
+	else
+	{
+		m_path = pszMemberName;
+	}
+
+	m_pszPath = m_path.c_str();
+	m_nCbVarpath = m_path.length();
+}
+
+bool CRemVariable::SaveJSON(FILE *f, int nIndent, bool bValueOnly, bool bWriteComma)
+{
+	if(m_bIsDbPersitent)
+		return true;
+
+	if(bWriteComma)
+	{
+		if(fprintf(f, ",") < 0)
+			return false;
+	}
+
+	if(!bValueOnly)
+	{
+	    if(fprintf(f, "\n") < 0)
+			return false;
+
+		if(nIndent > 0)
+		{
+		    if(fprintf(f, "%*s", nIndent * _JSON_SPACES_PER_TAB, "") < 0)
+				return false;
+		}
+	}
+
+	if(!bValueOnly)
+	{
+	    if(fprintf(f, "\"%s\": ", m_name.c_str()) < 0)
+			return false;
+	}
+
+	switch(m_vt)
+	{
+	case VT_bool:
+	    if(fprintf(f, "%s", m_cache.boolVal ? "true" : "false") < 0)
+			return false;
+		break;
+	case VT_I1:
+	    if(fprintf(f, "%hhd", m_cache.I1Val) < 0)
+			return false;
+		break;
+	case VT_UI1:
+	    if(fprintf(f, "%hhu", m_cache.UI1Val) < 0)
+			return false;
+		break;
+	case VT_I2:
+	    if(fprintf(f, "%hd", m_cache.I2Val) < 0)
+			return false;
+		break;
+	case VT_UI2:
+	    if(fprintf(f, "%hu", m_cache.UI2Val) < 0)
+			return false;
+		break;
+	case VT_I4:
+	    if(fprintf(f, "%d", m_cache.I4Val) < 0)
+			return false;
+		break;
+	case VT_UI4:
+	    if(fprintf(f, "%u", m_cache.UI4Val) < 0)
+			return false;
+		break;
+	case VT_I8:
+	    if(fprintf(f, "%lld", m_cache.I8Val) < 0)
+			return false;
+		break;
+	case VT_UI8:
+	    if(fprintf(f, "%llu", m_cache.UI8Val) < 0)
+			return false;
+		break;
+	case VT_float:
+	    if(fprintf(f, "%.10g", m_cache.FloatVal) < 0)
+			return false;
+		break;
+	case VT_double:
+	    if(fprintf(f, "%.20g", m_cache.DoubleVal) < 0)
+			return false;
+		break;
+    default:
+        break;
+	}
+
+	return true;
+}
+
+void CRemVariable::CreateMembersTable(CRemVarTable &vt)
+{
+	vt.AddVar(static_cast<CRemanent*>(this));
+}
+
+bool CRemVariable::SetJSONValue(const Json::Value &jv, bool fLock)
+{
+	switch(jv.type())
+	{
+	case Json::booleanValue:
+		if(m_vt == VT_bool)
+		{
+			m_cache.boolVal = jv.asBool();
+			if(fLock) Lock();
+			*m_data.pBool = m_cache.boolVal;
+			if(fLock) Unlock();
+		}
+		else
+			return false;
+		break;
+
+	case Json::intValue:
+		switch(m_vt)
+		{
+		case VT_I1:
+			m_cache.I1Val = jv.asInt();
+			if(fLock) Lock();
+			*m_data.pI1 = m_cache.I1Val;
+			if(fLock) Unlock();
+			break;
+		case VT_UI1:
+			m_cache.UI1Val = jv.asInt();
+			if(fLock) Lock();
+			*m_data.pUI1 = m_cache.UI1Val;
+			if(fLock) Unlock();
+			break;
+		case VT_I2:
+			m_cache.I2Val = jv.asInt();
+			if(fLock) Lock();
+			*m_data.pI2 = m_cache.I2Val;
+			if(fLock) Unlock();
+			break;
+		case VT_UI2:
+			m_cache.UI2Val = jv.asInt();
+			if(fLock) Lock();
+			*m_data.pUI2 = m_cache.UI2Val;
+			if(fLock) Unlock();
+			break;
+		case VT_I4:
+			m_cache.I4Val = jv.asInt();
+			if(fLock) Lock();
+			*m_data.pI4 = m_cache.I4Val;
+			if(fLock) Unlock();
+			break;
+		case VT_UI4:
+			m_cache.UI4Val = jv.asUInt();
+			if(fLock) Lock();
+			*m_data.pUI4 = m_cache.UI4Val;
+			if(fLock) Unlock();
+			break;
+		case VT_I8:
+			m_cache.I8Val = jv.asInt64();
+			if(fLock) Lock();
+			*m_data.pI8 = m_cache.I8Val;
+			if(fLock) Unlock();
+			break;
+		case VT_UI8:
+			m_cache.UI8Val = jv.asUInt64();
+			if(fLock) Lock();
+			*m_data.pUI8 = m_cache.UI8Val;
+			if(fLock) Unlock();
+			break;
+		case VT_float:
+			m_cache.FloatVal = jv.asFloat();
+			if(fLock) Lock();
+			*m_data.pFloat = m_cache.FloatVal;
+			if(fLock) Unlock();
+			break;
+		case VT_double:
+			m_cache.DoubleVal = jv.asDouble();
+			if(fLock) Lock();
+			*m_data.pDouble = m_cache.DoubleVal;
+			if(fLock) Unlock();
+			break;
+		default:
+			return false;
+		}
+		break;
+
+	case Json::uintValue:
+		switch(m_vt)
+		{
+		case VT_UI1:
+			m_cache.UI1Val = jv.asUInt();
+			if(fLock) Lock();
+			*m_data.pUI1 = m_cache.UI1Val;
+			if(fLock) Unlock();
+			break;
+		case VT_UI2:
+			m_cache.UI2Val = jv.asUInt();
+			if(fLock) Lock();
+			*m_data.pUI2 = m_cache.UI2Val;
+			if(fLock) Unlock();
+			break;
+		case VT_UI4:
+			m_cache.UI4Val = jv.asUInt();
+			if(fLock) Lock();
+			*m_data.pUI4 = m_cache.UI4Val;
+			if(fLock) Unlock();
+			break;
+		case VT_UI8:
+			m_cache.UI8Val = jv.asUInt64();
+			if(fLock) Lock();
+			*m_data.pUI8 = m_cache.UI8Val;
+			if(fLock) Unlock();
+			break;
+		case VT_float:
+			m_cache.FloatVal = jv.asFloat();
+			if(fLock) Lock();
+			*m_data.pFloat = m_cache.FloatVal;
+			if(fLock) Unlock();
+			break;
+		case VT_double:
+			m_cache.DoubleVal = jv.asDouble();
+			if(fLock) Lock();
+			*m_data.pDouble = m_cache.DoubleVal;
+			if(fLock) Unlock();
+			break;
+		default:
+			return false;
+		}
+		break;
+
+	case Json::realValue:
+		switch(m_vt)
+		{
+		case VT_float:
+			m_cache.FloatVal = jv.asFloat();
+			if(fLock) Lock();
+			*m_data.pFloat = m_cache.FloatVal;
+			if(fLock) Unlock();
+			break;
+		case VT_double:
+			m_cache.DoubleVal = jv.asDouble();
+			if(fLock) Lock();
+			*m_data.pDouble = m_cache.DoubleVal;
+			if(fLock) Unlock();
+			break;
+		default:
+			return false;
+		}
+		break;
+
+	default:
+		return false;
+	}
+
+	return true;
+}
+
+bool CRemVariable::SetDoubleValue(double val, bool fLock)
+{
+	switch(m_vt)
+	{
+	case VT_bool:
+		m_cache.boolVal = !!val;
+		if(fLock) Lock();
+		*m_data.pBool = m_cache.boolVal;
+		if(fLock) Unlock();
+		break;
+	case VT_I1:
+		m_cache.I1Val = (signed char)val;
+		if(fLock) Lock();
+		*m_data.pI1 = m_cache.I1Val;
+		if(fLock) Unlock();
+		break;
+	case VT_UI1:
+		m_cache.UI1Val = (unsigned char)val;
+		if(fLock) Lock();
+		*m_data.pUI1 = m_cache.UI1Val;
+		if(fLock) Unlock();
+		break;
+	case VT_I2:
+		m_cache.I2Val = (signed short)val;
+		if(fLock) Lock();
+		*m_data.pI2 = m_cache.I2Val;
+		if(fLock) Unlock();
+		break;
+	case VT_UI2:
+		m_cache.UI2Val = (unsigned short)val;
+		if(fLock) Lock();
+		*m_data.pUI2 = m_cache.UI2Val;
+		if(fLock) Unlock();
+		break;
+	case VT_I4:
+		m_cache.I4Val = (signed int)val;
+		if(fLock) Lock();
+		*m_data.pI4 = m_cache.I4Val;
+		if(fLock) Unlock();
+		break;
+	case VT_UI4:
+		m_cache.UI4Val = (unsigned int)val;
+		if(fLock) Lock();
+		*m_data.pUI4 = m_cache.UI4Val;
+		if(fLock) Unlock();
+		break;
+	case VT_I8:
+		m_cache.I8Val = (signed long long)val;
+		if(fLock) Lock();
+		*m_data.pI8 = m_cache.I8Val;
+		if(fLock) Unlock();
+		break;
+	case VT_UI8:
+		m_cache.UI8Val = (unsigned long long)val;
+		if(fLock) Lock();
+		*m_data.pUI8 = m_cache.UI8Val;
+		if(fLock) Unlock();
+		break;
+	case VT_float:
+		m_cache.FloatVal = (float)val;
+		if(fLock) Lock();
+		*m_data.pFloat = m_cache.FloatVal;
+		if(fLock) Unlock();
+		break;
+	case VT_double:
+		m_cache.DoubleVal = val;
+		if(fLock) Lock();
+		*m_data.pDouble = m_cache.DoubleVal;
+		if(fLock) Unlock();
+		break;
+	default:
+		return false;
+	}
+
+	return true;
+}
+
+unsigned long long CRemVariable::CheckUpdateShm(bool fLock)
+{
+	CHECK_UPDATE_SHM_RETVAL rv = {1, 0};
+
+	if(m_bIsDbPersitent)
+		return rv.nRetval;
+
+	if(fLock)
+		Lock();
+
+	switch(m_vt)
+	{
+	case VT_bool:
+		if(m_cache.boolVal != *m_data.pBool)
+		{
+			m_cache.boolVal = *m_data.pBool;
+			m_fLogValue = m_cache.boolVal ? 1.0 : 0.0;
+			rv.nUpdated = 1;
+		}
+		break;
+	case VT_I1:
+		if(m_cache.I1Val != *m_data.pI1)
+		{
+			m_cache.I1Val = *m_data.pI1;
+			m_fLogValue = (double)m_cache.I1Val;
+			rv.nUpdated = 1;
+		}
+		break;
+	case VT_UI1:
+		if(m_cache.UI1Val != *m_data.pUI1)
+		{
+			m_cache.UI1Val = *m_data.pUI1;
+			m_fLogValue = (double)m_cache.UI1Val;
+			rv.nUpdated = 1;
+		}
+		break;
+	case VT_I2:
+		if(m_cache.I2Val != *m_data.pI2)
+		{
+			m_cache.I2Val = *m_data.pI2;
+			m_fLogValue = (double)m_cache.I2Val;
+			rv.nUpdated = 1;
+		}
+		break;
+	case VT_UI2:
+		if(m_cache.UI2Val != *m_data.pUI2)
+		{
+			m_cache.UI2Val = *m_data.pUI2;
+			m_fLogValue = (double)m_cache.UI2Val;
+			rv.nUpdated = 1;
+		}
+		break;
+	case VT_I4:
+		if(m_cache.I4Val != *m_data.pI4)
+		{
+			m_cache.I4Val = *m_data.pI4;
+			m_fLogValue = (double)m_cache.I4Val;
+			rv.nUpdated = 1;
+		}
+		break;
+	case VT_UI4:
+		if(m_cache.UI4Val != *m_data.pUI4)
+		{
+			m_cache.UI4Val = *m_data.pUI4;
+			m_fLogValue = (double)m_cache.UI4Val;
+			rv.nUpdated = 1;
+		}
+		break;
+	case VT_I8:
+		if(m_cache.I8Val != *m_data.pI8)
+		{
+			m_cache.I8Val = *m_data.pI8;
+			m_fLogValue = (double)m_cache.I8Val;
+			rv.nUpdated = 1;
+		}
+		break;
+	case VT_UI8:
+		if(m_cache.UI8Val != *m_data.pUI8)
+		{
+			m_cache.UI8Val = *m_data.pUI8;
+			m_fLogValue = (double)m_cache.UI8Val;
+			rv.nUpdated = 1;
+		}
+		break;
+	case VT_float:
+		if(m_cache.FloatVal != *m_data.pFloat)
+		{
+			m_cache.FloatVal = *m_data.pFloat;
+			m_fLogValue = (double)m_cache.FloatVal;
+			rv.nUpdated = 1;
+		}
+		break;
+	case VT_double:
+		if(m_cache.DoubleVal != *m_data.pDouble)
+		{
+			m_cache.DoubleVal = *m_data.pDouble;
+			m_fLogValue = m_cache.DoubleVal;
+			rv.nUpdated = 1;
+		}
+		break;
+    default:
+    	rv.nUpdated = 0;
+        break;
+	}
+
+	if(fLock)
+		Unlock();
+
+	if(rv.nUpdated)
+	{
+		m_bMustLog = true;
+		m_nUpdates++;
+	}
+
+	return rv.nRetval;
+}
+
+void CRemVariable::Log(time_t ts, CRemLogger &rlogger)
+{
+	if(!m_bIsDbPersitent && m_bMustLog)
+	{
+		rlogger.Log(m_pszPath, m_nCbVarpath, m_fLogValue, NULL, 0, ts, true);
+		m_bMustLog = false;
+	}
+}
+
+void CRemVariable::Lock(void)
+{
+	::GfaIpcLockSHM(m_hShm);
+}
+
+void CRemVariable::Unlock(void)
+{
+	::GfaIpcUnlockSHM(m_hShm);
+}

+ 347 - 0
remanent/remvar.h

@@ -0,0 +1,347 @@
+// remvar.h :
+//
+
+#if !defined(AGD_REMVAR_H__B91C5ED5_B980_4B06_9CEA_886DB0518B29__INCLUDED_)
+#define AGD_REMVAR_H__B91C5ED5_B980_4B06_9CEA_886DB0518B29__INCLUDED_
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <string>
+#include <pthread.h>
+#include <typeinfo>
+#include <vector>
+#include <map>
+#include <gfa/gfaipc.h>
+#ifdef SITARA_BUILD
+#include <json/json.h>
+#else	//	SITARA_BUILD
+#include <jsoncpp/json/json.h>
+#endif	//	SITARA_BUILD
+#ifndef _LIBBUILD
+#include <gfa/svc/common/logfile.h>
+#include <gfa/svc/common/debug.h>
+#include <gfa/svc/remanent/remlogger.h>
+#else	//	_LIBBUILD
+#include "common/logfile.h"
+#include "common/debug.h"
+#include "remlogger.h"
+#endif	//	_LIBBUILD
+
+/////////////////////////////////////////////////////////////////////////////
+
+#define _XML_SPACES_PER_TAB					4
+#define _JSON_SPACES_PER_TAB				4
+#define _TRACK_TYPES_AT_LOAD				0
+#define _TRACK_REM_WRITES					0
+#define _IMPLEMENT_XML						0
+
+/////////////////////////////////////////////////////////////////////////////
+
+typedef union _CHECK_UPDATE_SHM_RETVAL
+{
+	struct
+	{
+		unsigned int nChecked;
+		unsigned int nUpdated;
+	};
+	unsigned long long nRetval;
+}CHECK_UPDATE_SHM_RETVAL, *LPCHECK_UPDATE_SHM_RETVAL;
+typedef const CHECK_UPDATE_SHM_RETVAL *LPCCHECK_UPDATE_SHM_RETVAL;
+
+/////////////////////////////////////////////////////////////////////////////
+// remvar.h - Declarations:
+
+class CRemVarTable;
+
+class CRemanent
+{
+public:
+	virtual void CreateMembersTable(CRemVarTable &vt)								= 0;
+	virtual bool SaveJSON(FILE *f, int nIndent, bool bValueOnly, bool bWriteComma)	= 0;
+	virtual bool SetJSONValue(const Json::Value &jv, bool fLock){
+		UNUSED(jv);
+		UNUSED(fLock);
+		return false;}
+	virtual bool SetDoubleValue(double val, bool fLock){
+		UNUSED(val);
+		UNUSED(fLock);
+		return false;}
+	virtual unsigned long long CheckUpdateShm(bool fLock)							= 0;
+	virtual void InitPath(CRemanent *pParent, const char *pszMemberName)			= 0;
+	virtual const char* GetPath(void) const											= 0;
+	virtual unsigned long long GetUpdateCount(void) const							= 0;
+	virtual void Log(time_t ts, CRemLogger &rlogger)								= 0;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CRemVarTable
+{
+public:
+	typedef bool (*_PFNCMP)(const char*, const char*);
+public:
+	CRemVarTable(void);
+	virtual ~CRemVarTable(void);
+
+	void AddVar(CRemanent *pv);
+	CRemanent* Find(const char *key) const;
+	unsigned long LoadJSONValues(const Json::Value &jv);
+	bool GetMaxUpdateVariables(std::vector<const CRemanent*> &vars, size_t nMaxVars);
+
+private:
+	void ParseJsonMember(const Json::Value &rMem, std::string &sname, unsigned long &parsed);
+	void ParseJsonScalar(const Json::Value &rScl, std::string &sname, unsigned long &parsed);
+	void ParseJsonArray(const Json::Value &rArr, std::string &sname, unsigned long &parsed);
+	void ParseJsonObject(const Json::Value &rObj, std::string &sname, unsigned long &parsed);
+
+private:
+	std::map<const char*, CRemanent*, _PFNCMP> m_map;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CDbPersist
+{
+public:
+	typedef bool (*_PFNCMP)(const std::string&, const std::string&);
+	typedef std::map<std::string, std::string, _PFNCMP> CFieldMap;
+	typedef std::vector<CFieldMap> CTagsMap;
+
+public:
+	CDbPersist(const char *pszDatabaseName, const char *pszTagsTableName, const char *pszLogsTableName, const char *pszDbUser, const char *pszDbPass);
+	virtual ~CDbPersist(void);
+
+	int RestoreValues(const CRemVarTable &map, CLogfile &rlf);
+
+	const CTagsMap &GetTagsMap(void) const {
+		return m_tags;}
+private:
+	bool Init(CMySqlDB &db);
+
+private:
+	std::string m_sDatabaseName;
+	std::string m_sTagsTableName;
+	std::string m_sLogsTableName;
+	std::string m_sUser;
+	std::string m_sPass;
+	CTagsMap m_tags;
+	std::string m_lastError;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CRemVariable : public CRemanent
+{
+public:
+	enum VT
+	{
+		VT_Invalid,
+		VT_bool,
+		VT_I1,
+		VT_UI1,
+		VT_I2,
+		VT_UI2,
+		VT_I4,
+		VT_UI4,
+		VT_I8,
+		VT_UI8,
+		VT_float,
+		VT_double,
+		VT_string
+	};
+
+	typedef union
+	{
+		void *pVoid;
+		bool *pBool;
+		signed char *pI1;
+		unsigned char *pUI1;
+		signed short *pI2;
+		unsigned short *pUI2;
+		signed int *pI4;
+		unsigned int *pUI4;
+		signed long long *pI8;
+		unsigned long long *pUI8;
+		float *pFloat;
+		double *pDouble;
+	}V_Ptr;
+
+	typedef union
+	{
+		bool boolVal;
+		signed char I1Val;
+		unsigned char UI1Val;
+		signed short I2Val;
+		unsigned short UI2Val;
+		signed int I4Val;
+		unsigned int UI4Val;
+		signed long long I8Val;
+		unsigned long long UI8Val;
+		float FloatVal;
+		double DoubleVal;
+	}V_Val;
+
+public:
+	CRemVariable(void *pData, const std::type_info &rti, HSHM hShm, bool bIsDbPersitent, const char *pszName, int nIndex, CRemanent *pParent);
+	virtual ~CRemVariable(void);
+
+	virtual void CreateMembersTable(CRemVarTable &vt);
+	virtual bool SaveJSON(FILE *f, int nIndent, bool bValueOnly, bool bWriteComma);
+	virtual bool SetJSONValue(const Json::Value &jv, bool fLock);
+	virtual bool SetDoubleValue(double val, bool fLock);
+	virtual unsigned long long CheckUpdateShm(bool fLock);
+	virtual void InitPath(CRemanent*pParent, const char *pszMemberName);
+	virtual const char* GetPath(void) const {
+		return m_pszPath;}
+	virtual unsigned long long GetUpdateCount(void) const {
+		return m_nUpdates;}
+	virtual void Log(time_t ts, CRemLogger &rlogger);
+
+private:
+    void Lock(void);
+    void Unlock(void);
+
+private:
+    std::string m_name;
+    std::string m_path;
+    const char *m_pszPath;
+    int m_nIndex;
+    enum VT m_vt;
+    volatile V_Ptr m_data;
+    V_Val m_cache;
+    HSHM m_hShm;
+    CRemanent *m_pParent;
+    unsigned long long m_nUpdates;
+    double m_fLogValue;
+    bool m_bMustLog;
+    bool m_bIsDbPersitent;
+    size_t m_nCbVarpath;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CRemStringVariable : public CRemanent
+{
+public:
+	typedef enum
+	{
+		VT_Invalid,	// 0
+		VT_Latin1,	// 1
+		VT_UTF_8,	// 2
+		VT_UTF_16,	// 3
+		VT_UTF_32,	// 4
+		VT_Unicode,	// 5
+		VT_Last
+	}VT;
+
+	typedef union
+	{
+		void *pVoid;
+		char *pszMbs;
+		char16_t *pszWc16;
+		char32_t *pszWc32;
+		wchar_t *pszWcs;
+	}V_Ptr;
+
+public:
+	CRemStringVariable(void *pData, size_t nCChData, VT vt, const std::type_info &rti, HSHM hShm, bool bIsDbPersitent, const char *pszName, int nIndex, CRemanent *pParent);
+	virtual ~CRemStringVariable(void);
+
+	virtual void CreateMembersTable(CRemVarTable &vt);
+	virtual bool SaveJSON(FILE *f, int nIndent, bool bValueOnly, bool bWriteComma);
+	virtual bool SetJSONValue(const Json::Value &jv, bool fLock);
+	virtual unsigned long long CheckUpdateShm(bool fLock);
+	virtual void InitPath(CRemanent*pParent, const char *pszMemberName);
+	virtual const char* GetPath(void) const {
+		return m_pszPath;}
+	virtual unsigned long long GetUpdateCount(void) const {
+		return m_nUpdates;}
+	virtual void Log(time_t ts, CRemLogger &rlogger);
+
+private:
+    bool shmChanged(bool fLock);
+    void Lock(void);
+    void Unlock(void);
+    void zeroTerm(volatile V_Ptr &rp, size_t at);
+
+private:
+    std::string m_name;
+    std::string m_path;
+    const char *m_pszPath;
+    int m_nIndex;
+    VT m_vt;
+    volatile V_Ptr m_data;
+    V_Ptr m_cache;
+    size_t m_nCbBuffer;
+    size_t m_nCbString;
+    HSHM m_hShm;
+    CRemanent *m_pParent;
+    unsigned long long m_nUpdates;
+    bool m_bMustLog;
+    bool m_bIsDbPersitent;
+    size_t m_nCbVarpath;
+    char m_szLog[_RL_MAX_STRVAL_LENGTH];
+    size_t m_nCbLog;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CRemBitVariable : public CRemanent
+{
+public:
+	enum VT
+	{
+		VT_Invalid,
+		VT_bool,
+		VT_I1,
+		VT_UI1,
+		VT_I2,
+		VT_UI2,
+		VT_I4,
+		VT_UI4,
+		VT_I8,
+		VT_UI8,
+		VT_float,
+		VT_double,
+		VT_string
+	};
+
+public:
+	CRemBitVariable(void *pData, size_t nOffset, int nBitNr, HSHM hShm, bool bIsDbPersitent, const char *pszName, CRemanent *pParent);
+	virtual ~CRemBitVariable(void);
+
+	virtual void CreateMembersTable(CRemVarTable &vt);
+	virtual bool SaveJSON(FILE *f, int nIndent, bool bValueOnly, bool bWriteComma);
+	virtual bool SetJSONValue(const Json::Value &jv, bool fLock);
+	virtual bool SetDoubleValue(double val, bool fLock);
+	virtual unsigned long long CheckUpdateShm(bool fLock);
+	virtual void InitPath(CRemanent*pParent, const char *pszMemberName);
+	virtual const char* GetPath(void) const {
+		return m_pszPath;}
+	virtual unsigned long long GetUpdateCount(void) const {
+		return m_nUpdates;}
+	virtual void Log(time_t ts, CRemLogger &rlogger);
+
+private:
+    void Lock(void);
+    void Unlock(void);
+
+private:
+    std::string m_name;
+    std::string m_path;
+    const char *m_pszPath;
+	uint8_t *m_pShmByte;
+	uint8_t m_mask;
+	bool m_cacheVal;
+    HSHM m_hShm;
+    CRemanent *m_pParent;
+    unsigned long long m_nUpdates;
+    double m_fLogValue;
+    bool m_bMustLog;
+    bool m_bIsDbPersitent;
+    size_t m_nCbVarpath;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	!defined(AGD_REMVAR_H__B91C5ED5_B980_4B06_9CEA_886DB0518B29__INCLUDED_)

+ 143 - 0
remanent/remvartbl.cpp

@@ -0,0 +1,143 @@
+#include "remvar.h"
+#include <algorithm>
+
+/////////////////////////////////////////////////////////////////////////////
+
+static bool _fncomp(const char *p1, const char *p2)
+{
+	return strcmp(p1, p2) < 0;
+}
+
+CRemVarTable::CRemVarTable(void) : m_map(_fncomp)
+{
+}
+
+CRemVarTable::~CRemVarTable(void)
+{
+}
+
+void CRemVarTable::AddVar(CRemanent *pVar)
+{
+	m_map[pVar->GetPath()] = pVar;
+}
+
+CRemanent* CRemVarTable::Find(const char *key) const
+{
+	std::map<const char*, CRemanent*>::const_iterator it = m_map.find(key);
+	if(it == m_map.end())
+		return NULL;
+	return it->second;
+}
+
+bool CRemVarTable::GetMaxUpdateVariables(std::vector<const CRemanent*> &vars, size_t nMaxVars)
+{
+	unsigned long long nUpdateCount;
+	vars.clear();
+	std::map<const char*, CRemanent*>::const_iterator iBegin, iEnd;
+	iEnd = m_map.cend();
+
+	for(iBegin = m_map.cbegin(); iBegin != iEnd; iBegin++)
+	{
+		const CRemanent *pVar1 = iBegin->second;
+
+		if((nUpdateCount = pVar1->GetUpdateCount()))
+		{
+			std::vector<const CRemanent*>::iterator vBegin, vEnd;
+			vEnd = vars.end();
+
+			for(vBegin = vars.begin(); vBegin != vEnd; vBegin++)
+			{
+				const CRemanent *pVar2 = *vBegin;
+
+				if(pVar2->GetUpdateCount() < nUpdateCount)
+					break;
+			}
+
+			vars.insert(vBegin, pVar1);
+
+			if(vars.size() > nMaxVars)
+				vars.pop_back();
+		}
+	}
+
+	return true;
+}
+
+unsigned long CRemVarTable::LoadJSONValues(const Json::Value &jv)
+{
+	unsigned long parsed = 0;
+	std::string s;
+	ParseJsonMember(jv, s, parsed);
+	return parsed;
+}
+
+void CRemVarTable::ParseJsonMember(const Json::Value &jv, std::string &sname, unsigned long &parsed)
+{
+	std::string s(sname);
+	Json::ValueType vt = jv.type();
+
+	switch(vt)
+	{
+	case Json::nullValue:
+		break;
+	case Json::intValue:
+	case Json::uintValue:
+	case Json::realValue:
+	case Json::stringValue:
+	case Json::booleanValue:
+		ParseJsonScalar(jv, s, parsed);
+		break;
+	case Json::arrayValue:
+		ParseJsonArray(jv, s, parsed);
+		break;
+	case Json::objectValue:
+		ParseJsonObject(jv, s, parsed);
+		break;
+	}
+}
+
+void CRemVarTable::ParseJsonObject(const Json::Value &jv, std::string &sname, unsigned long &parsed)
+{
+	Json::Value::Members members = jv.getMemberNames();
+
+	for(Json::Value::Members::const_iterator mem = members.begin(); mem != members.end(); mem++)
+	{
+		std::string s(sname);
+		const std::string &rName = *mem;
+		const Json::Value &m = jv[rName];
+		if(s.size() > 0)
+			s += "/";
+		s += rName;
+		ParseJsonMember(m, s, parsed);
+	}
+}
+
+void CRemVarTable::ParseJsonArray(const Json::Value &jv, std::string &sname, unsigned long &parsed)
+{
+	int nIdx = 0;
+	char szIndex[32];
+
+	for(Json::Value::const_iterator val = jv.begin(); val != jv.end(); val++)
+	{
+		std::string s(sname);
+		sprintf(szIndex, "[%d]", nIdx++);
+		s += szIndex;
+		ParseJsonMember(*val, s, parsed);
+	}
+}
+
+void CRemVarTable::ParseJsonScalar(const Json::Value &jv, std::string &sname, unsigned long &parsed)
+{
+	CRemanent *pVar = Find(sname.c_str());
+	if(pVar)
+	{
+		if(pVar->SetJSONValue(jv, true))
+			parsed++;
+		else
+			printf("Failed to load value %s!\n", sname.c_str());
+	}
+	else
+	{
+		printf("Failed to load value %s!\n", sname.c_str());
+	}
+}

+ 8 - 0
rest/.gitignore

@@ -0,0 +1,8 @@
+# git ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
+
+*.pro.user

+ 35 - 0
rest/apikey.cpp

@@ -0,0 +1,35 @@
+#include "main.h"
+
+/////////////////////////////////////////////////////////////////////////////
+
+CApiKeys::CApiKeys(void)
+{
+}
+
+CApiKeys::~CApiKeys(void)
+{
+}
+
+void CApiKeys::PushKey(const API_KEY &rak)
+{
+	m_apiKeys.push_back(rak);
+}
+
+bool CApiKeys::Sha256HashString(const char *pszString, char *pszDigest, size_t nCChDigest)
+{
+	if(!pszString || !pszDigest || (nCChDigest < SHA256_DIGEST_LENGTH))
+		return false;
+
+	SHA256_CTX context;
+
+    if(!SHA256_Init(&context))
+        return false;
+
+    if(!SHA256_Update(&context, pszString, strlen(pszString)))
+        return false;
+
+    if(!SHA256_Final((unsigned char*)pszDigest, &context))
+        return false;
+
+	return true;
+}

+ 38 - 0
rest/apikey.h

@@ -0,0 +1,38 @@
+// apikey.h :
+//
+
+#if !defined(AGD_APIKEY_H__CF87A9D4_2E2B_46AE_A07A_A07D1551521F__INCLUDED_)
+#define AGD_APIKEY_H__CF87A9D4_2E2B_46AE_A07A_A07D1551521F__INCLUDED_
+
+#include <functional>
+#include <vector>
+#include <string>
+#include <openssl/sha.h>
+
+/////////////////////////////////////////////////////////////////////////////
+// apikey.h - Declarations:
+
+typedef struct _API_KEY
+{
+	std::string strHash;
+	unsigned int nMask;
+}API_KEY, *LPAPI_KEY;
+typedef const API_KEY *LPCAPI_KEY;
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CApiKeys
+{
+public:
+	CApiKeys(void);
+	~CApiKeys(void);
+	
+	bool Sha256HashString(const char *pszString, char *pszDigest, size_t nCChDigest);
+	void PushKey(const API_KEY &rak);
+
+private:
+	std::vector<API_KEY> m_apiKeys;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	!defined(AGD_APIKEY_H__CF87A9D4_2E2B_46AE_A07A_A07D1551521F__INCLUDED_)

+ 503 - 0
rest/callback.cpp

@@ -0,0 +1,503 @@
+#include "main.h"
+#include "helpers.h"
+#include "callback.h"
+
+/////////////////////////////////////////////////////////////////////////////
+
+static void _SetCORSResponseHeaders(struct _u_response * response, bool bAllowCredentials = true);
+
+/////////////////////////////////////////////////////////////////////////////
+
+#define _SIG_BLOCK(s)				pthread_sigmask(SIG_BLOCK, (s), NULL)
+#define _SIG_UNBLOCK(s)				pthread_sigmask(SIG_UNBLOCK, (s), NULL)
+
+/////////////////////////////////////////////////////////////////////////////
+// FileBodyResponseCallback (serves static files)
+
+int FileBodyResponseCallback(const struct _u_request * request, struct _u_response * response, void * user_data)
+{
+	UNUSED(request);
+	LPFILEREQUEST pfr = (LPFILEREQUEST)user_data;
+
+	if(pfr)
+	{
+		FILE *pf = fopen(pfr->strFilename.c_str(), "rb");
+
+		if(pf)
+		{
+			size_t nSize = GetFileSize(pf);
+			char *pszContent = (char*)malloc(nSize + 1);
+			nSize = fread(pszContent, 1, nSize, pf);
+			fclose(pf);
+			pszContent[nSize] = '\0';
+			_SetCORSResponseHeaders(response);
+			ulfius_add_header_to_response(response, "Content-Type", pfr->strContentType.c_str());
+			ulfius_set_binary_body_response(response, 200, pszContent, nSize);
+			free(pszContent);
+			return U_CALLBACK_CONTINUE;
+		}
+		else
+		{
+			_SetCORSResponseHeaders(response);
+			ulfius_set_empty_body_response(response, 404);
+		}
+	}
+	else
+	{
+		_SetCORSResponseHeaders(response);
+		ulfius_set_empty_body_response(response, 500);
+	}
+
+	return U_CALLBACK_CONTINUE;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// GetShmPostResponseCallback_M - getshm - POST Request, returns a linear map
+
+int GetShmPostResponseCallback_M(const struct _u_request * request, struct _u_response * response, void * user_data)
+{
+	LPCSHM_REQUEST_PARAMS psrp = (LPCSHM_REQUEST_PARAMS)user_data;
+
+	if(!psrp || !psrp->pMap || !psrp->pszUuid)
+	{
+		_SetCORSResponseHeaders(response);
+		ulfius_set_empty_body_response(response, 500);
+		return U_CALLBACK_CONTINUE;
+	}
+
+	CRestVarTable &map = *psrp->pMap;
+	json_error_t jErr;
+	CJson_t jtRequ(ulfius_get_json_body_request(request, &jErr)); // new reference!
+	CJson_t jtUuid(json_string(psrp->pszUuid)); // new reference!
+
+	if(jtRequ.IsValid())
+	{
+		CJson_t jtResponse(json_object()); // new reference!
+		CJson_t jtArrValues(json_array()); // new reference!
+		json_object_set(jtResponse, "uuid", jtUuid);
+
+		if(jtRequ.IsArray())
+		{
+			json_object_set(jtResponse, "request", jtRequ);
+
+			size_t nIndex;
+			json_t *pjtEle;
+
+			json_array_foreach(jtRequ, nIndex, pjtEle)
+			{
+				CJson_t jtPath(json_object_get(pjtEle, "path"), false); // borrowed reference!
+
+				if(!jtPath.IsValid())
+				{
+					_SetCORSResponseHeaders(response);
+					ulfius_set_empty_body_response(response, 400);
+					return U_CALLBACK_CONTINUE;
+				}
+
+				const char *pszPath = json_string_value(jtPath);
+				CRest *pRest = map.Find(pszPath);
+
+				if(!pRest)
+				{
+					CJson_t jtStatus(CRest::CreateStatusObject((int)nIndex, -1, "Invalid path!", pszPath));
+					json_array_append(jtArrValues, jtStatus);
+				}
+				else
+				{
+					sigset_t sset;
+					::sigfillset(&sset);
+					_SIG_BLOCK(&sset);
+					pRest->GetValue((int)nIndex, jtArrValues); // new reference!
+					_SIG_UNBLOCK(&sset);
+				}
+			}
+
+			json_object_set(jtResponse, "response", jtArrValues);
+			_SetCORSResponseHeaders(response);
+			ulfius_set_json_body_response(response, 200, jtResponse);
+		}
+		else if(jtRequ.IsObject())
+		{
+			CJson_t jtArrReq(json_array()); // new reference!
+			json_array_append(jtArrReq, jtRequ);
+			json_object_set(jtResponse, "request", jtArrReq);
+
+			CJson_t jtPath(json_object_get(jtRequ, "path"), false); // borrowed reference!
+
+			if(!jtPath.IsValid())
+			{
+				_SetCORSResponseHeaders(response);
+				ulfius_set_empty_body_response(response, 400);
+				return U_CALLBACK_CONTINUE;
+			}
+
+			const char *pszPath = json_string_value(jtPath);
+			CRest *pRest = map.Find(pszPath);
+
+			if(!pRest)
+			{
+				CJson_t jtStatus(CRest::CreateStatusObject(0, -1, "Invalid path!", pszPath));
+				json_array_append(jtArrValues, jtStatus);
+			}
+			else
+			{
+				sigset_t sset;
+				::sigfillset(&sset);
+				_SIG_BLOCK(&sset);
+				pRest->GetValue(0, jtArrValues); // new reference!
+				_SIG_UNBLOCK(&sset);
+			}
+
+			json_object_set(jtResponse, "response", jtArrValues);
+			_SetCORSResponseHeaders(response);
+			ulfius_set_json_body_response(response, 200, jtResponse);
+		}
+		else
+		{
+			_SetCORSResponseHeaders(response);
+			ulfius_set_empty_body_response(response, 400);
+			return U_CALLBACK_CONTINUE;
+		}
+	}
+	else
+	{
+		fprintf(stderr, "%s\n", jErr.text);
+		_SetCORSResponseHeaders(response);
+		ulfius_set_empty_body_response(response, 500);
+	}
+
+	return U_CALLBACK_CONTINUE;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// GetShmPostResponseCallback_O - getshm - POST Request, returns a structured object
+
+int GetShmPostResponseCallback_O(const struct _u_request * request, struct _u_response * response, void * user_data)
+{
+	LPCSHM_REQUEST_PARAMS psrp = (LPCSHM_REQUEST_PARAMS)user_data;
+
+	if(!psrp || !psrp->pMap || !psrp->pszUuid)
+	{
+		_SetCORSResponseHeaders(response);
+		ulfius_set_empty_body_response(response, 500);
+		return U_CALLBACK_CONTINUE;
+	}
+
+	CRestVarTable &map = *psrp->pMap;
+	json_error_t jErr;
+	CJson_t jtRequ(ulfius_get_json_body_request(request, &jErr)); // new reference!
+	CJson_t jtUuid(json_string(psrp->pszUuid)); // new reference!
+
+	if(jtRequ)
+	{
+		CJson_t jtPath(json_object_get(jtRequ, "path"), false); // borrowed reference!
+
+		if(!jtPath)
+		{
+			_SetCORSResponseHeaders(response);
+			ulfius_set_empty_body_response(response, 500);
+			return U_CALLBACK_CONTINUE;
+		}
+
+		const char *pszPath = json_string_value(jtPath);
+		CRest *pRest = map.Find(pszPath);
+
+		if(!pRest)
+		{
+			_SetCORSResponseHeaders(response);
+			ulfius_set_empty_body_response(response, 404);
+			return U_CALLBACK_CONTINUE;
+		}
+
+		sigset_t sset;
+		::sigfillset(&sset);
+		_SIG_BLOCK(&sset);
+		CJson_t jtVal(pRest->GetValue(0)); // new reference!
+		_SIG_UNBLOCK(&sset);
+
+		if(jtVal)
+		{
+			CJson_t jtObj(json_object()); // new reference!
+			json_object_set(jtObj, "request", jtRequ);
+			json_object_set(jtObj, "response", jtVal);
+			json_object_set(jtObj, "uuid", jtUuid);
+			_SetCORSResponseHeaders(response);
+			ulfius_set_json_body_response(response, 200, jtObj);
+		}
+		else
+		{
+			_SetCORSResponseHeaders(response);
+			ulfius_set_empty_body_response(response, 500);
+		}
+	}
+	else
+	{
+		fprintf(stderr, "%s\n", jErr.text);
+		_SetCORSResponseHeaders(response);
+		ulfius_set_empty_body_response(response, 500);
+	}
+
+	return U_CALLBACK_CONTINUE;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// SetShmPostResponseCallback - setshm - POST Request
+
+int SetShmPostResponseCallback(const struct _u_request * request, struct _u_response * response, void * user_data)
+{
+	LPCSHM_REQUEST_PARAMS psrp = (LPCSHM_REQUEST_PARAMS)user_data;
+
+	if(!psrp || !psrp->pMap || !psrp->pszUuid)
+	{
+		_SetCORSResponseHeaders(response);
+		ulfius_set_empty_body_response(response, 500);
+		return U_CALLBACK_CONTINUE;
+	}
+
+	CRestVarTable &map = *psrp->pMap;
+	json_error_t jErr;
+	CJson_t jtRequ(ulfius_get_json_body_request(request, &jErr)); // new reference!
+	CJson_t jtUuid(json_string(psrp->pszUuid)); // new reference!
+
+	if(jtRequ.IsValid())
+	{
+		CJson_t jtResponse(json_object()); // new reference!
+		CJson_t jtArrStatus(json_array()); // new reference!
+		json_object_set(jtResponse, "uuid", jtUuid);
+		json_object_set(jtResponse, "response", jtArrStatus);
+
+		if(jtRequ.IsArray())
+		{
+			json_object_set(jtResponse, "request", jtRequ);
+
+			size_t nIndex;
+			json_t *pjtEle;
+
+			json_array_foreach(jtRequ, nIndex, pjtEle)
+			{
+				CJson_t jtPath(json_object_get(pjtEle, "path"), false); // borrowed reference!
+				CJson_t jtVal(json_object_get(pjtEle, "value"), false); // borrowed reference!
+
+				if(!jtPath.IsValid() || !jtVal.IsValid())
+				{
+					_SetCORSResponseHeaders(response);
+					ulfius_set_empty_body_response(response, 400);
+					return U_CALLBACK_CONTINUE;
+				}
+
+				const char *pszPath = json_string_value(jtPath);
+				CRest *pRest = map.Find(pszPath);
+
+				if(!pRest)
+				{
+					CJson_t jtStatus(CRest::CreateStatusObject((int)nIndex, -1, "Invalid path!", pszPath));
+					json_array_append(jtArrStatus, jtStatus);
+				}
+				else
+				{
+					sigset_t sset;
+					::sigfillset(&sset);
+					_SIG_BLOCK(&sset);
+					CJson_t jtStatus(pRest->SetValue((int)nIndex, jtVal)); // new reference!
+					_SIG_UNBLOCK(&sset);
+					json_array_append(jtArrStatus, jtStatus);
+				}
+			}
+
+			_SetCORSResponseHeaders(response);
+			ulfius_set_json_body_response(response, 200, jtResponse);
+		}
+		else if(jtRequ.IsObject())
+		{
+			CJson_t jtArrReq(json_array()); // new reference!
+			json_array_append(jtArrReq, jtRequ);
+			json_object_set(jtResponse, "request", jtArrReq);
+
+			CJson_t jtPath(json_object_get(jtRequ, "path"), false); // borrowed reference!
+			CJson_t jtVal(json_object_get(jtRequ, "value"), false); // borrowed reference!
+
+			if(!jtPath.IsValid() || !jtVal.IsValid())
+			{
+				_SetCORSResponseHeaders(response);
+				ulfius_set_empty_body_response(response, 500);
+				return U_CALLBACK_CONTINUE;
+			}
+
+			const char *pszPath = json_string_value(jtPath);
+			CRest *pRest = map.Find(pszPath);
+
+			if(!pRest)
+			{
+				_SetCORSResponseHeaders(response);
+				ulfius_set_empty_body_response(response, 404);
+				return U_CALLBACK_CONTINUE;
+			}
+
+			sigset_t sset;
+			::sigfillset(&sset);
+			_SIG_BLOCK(&sset);
+			CJson_t jtStatus(pRest->SetValue(0, jtVal)); // new reference!
+			_SIG_UNBLOCK(&sset);
+
+			json_array_append(jtArrStatus, jtStatus);
+			_SetCORSResponseHeaders(response);
+			ulfius_set_json_body_response(response, 200, jtResponse);
+		}
+		else
+		{
+			_SetCORSResponseHeaders(response);
+			ulfius_set_empty_body_response(response, 400);
+			return U_CALLBACK_CONTINUE;
+		}
+	}
+	else
+	{
+		fprintf(stderr, "%s\n", jErr.text);
+		_SetCORSResponseHeaders(response);
+		ulfius_set_empty_body_response(response, 500);
+	}
+
+	return U_CALLBACK_CONTINUE;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// GetShmGetResponseCallback - getshm - GET Request
+
+int GetShmGetResponseCallback(const struct _u_request * request, struct _u_response * response, void * user_data)
+{
+	static size_t nPrefixLen = strlen(GET_SHM_PREFIX);
+	LPCSHM_REQUEST_PARAMS psrp = (LPCSHM_REQUEST_PARAMS)user_data;
+
+	if(!psrp || !psrp->pMap || !psrp->pszUuid)
+	{
+		_SetCORSResponseHeaders(response);
+		ulfius_set_empty_body_response(response, 500);
+		return U_CALLBACK_COMPLETE;
+	}
+
+	CJson_t jtUuid(json_string(psrp->pszUuid)); // new reference!
+	const char *pszPath = request->http_url;
+	CURL *hUrl = NULL;
+	char *pszCUrl = NULL, *pszUrlParams = NULL;
+
+	if(!pszPath || !*pszPath || strlen(pszPath) == nPrefixLen)
+		pszPath = "/";
+	else
+	{
+		hUrl = curl_easy_init();
+		pszPath += nPrefixLen;
+		pszCUrl = curl_easy_unescape(hUrl, pszPath, 0, NULL);
+		pszPath = pszCUrl;
+		pszUrlParams = strchr(pszCUrl, '?');
+		if(pszUrlParams)
+		{
+			*pszUrlParams++ = '\0';
+/*			char **ppRet;
+			split_string(pszUrlParams, "&", &ppRet);
+			free_string_array(ppRet);*/
+		}
+	}
+
+	CRestVarTable &map = *psrp->pMap;
+	CRest *pRest = map.Find(pszPath);
+
+	if(!pRest)
+	{
+		if(pszCUrl)
+			curl_free(pszCUrl);
+		if(hUrl)
+			curl_easy_cleanup(hUrl);
+		_SetCORSResponseHeaders(response);
+		ulfius_set_empty_body_response(response, 404);
+		return U_CALLBACK_COMPLETE;
+	}
+
+	sigset_t sset;
+	::sigfillset(&sset);
+
+	_SIG_BLOCK(&sset);
+	CJson_t jtVal(pRest->GetValue(0)); // new reference!
+	_SIG_UNBLOCK(&sset);
+
+	if(jtVal.IsValid())
+	{
+		CJson_t jtRequ(json_object()); // new reference!
+		CJson_t jtPath(json_string(pszPath));
+		json_object_set(jtRequ, "path", jtPath);
+
+		CJson_t jtObj(json_object()); // new reference!
+		json_object_set(jtObj, "request", jtRequ);
+		json_object_set(jtObj, "response", jtVal);
+		json_object_set(jtObj, "uuid", jtUuid);
+
+		_SetCORSResponseHeaders(response);
+		ulfius_add_header_to_response(response, "Content-Disposition", "attachment; filename=\"shm.json\"");
+		ulfius_set_json_body_response(response, 200, jtObj);
+	}
+	else
+	{
+		if(pszCUrl)
+			curl_free(pszCUrl);
+		if(hUrl)
+			curl_easy_cleanup(hUrl);
+		_SetCORSResponseHeaders(response);
+		ulfius_set_empty_body_response(response, 500);
+		return U_CALLBACK_COMPLETE;
+	}
+
+	if(pszCUrl)
+		curl_free(pszCUrl);
+	if(hUrl)
+		curl_easy_cleanup(hUrl);
+
+	return U_CALLBACK_CONTINUE;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// OptionsResponseCallback - OPTIONS Request (CORS preflight ...)
+
+int OptionsResponseCallback(const struct _u_request * request, struct _u_response * response, void * user_data)
+{
+	UNUSED(user_data);
+	const char *pszHeaders = u_map_get_case(request->map_header, "Access-Control-Request-Headers");
+	if(!pszHeaders)
+		pszHeaders = "Content-Type";
+	ulfius_add_header_to_response(response, "Allow", "POST, GET, OPTIONS");
+	ulfius_add_header_to_response(response, "Access-Control-Allow-Methods", "POST, GET, OPTIONS");
+	ulfius_add_header_to_response(response, "Access-Control-Allow-Headers", pszHeaders);
+	_SetCORSResponseHeaders(response);
+	ulfius_set_empty_body_response(response, 200); // Ok
+	return U_CALLBACK_COMPLETE;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// NotAllowedResponseCallback
+
+int NotAllowedResponseCallback(const struct _u_request * request, struct _u_response * response, void * user_data)
+{
+	UNUSED(request);
+	UNUSED(user_data);
+	ulfius_set_empty_body_response(response, 405); // Method Not Allowed
+	return U_CALLBACK_COMPLETE;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// NotImplementedResponseCallback
+
+int NotImplementedResponseCallback(const struct _u_request * request, struct _u_response * response, void * user_data)
+{
+	UNUSED(request);
+	UNUSED(user_data);
+	ulfius_set_empty_body_response(response, 501); // Not Implemented
+	return U_CALLBACK_COMPLETE;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// _SetCORSResponseHeaders
+
+static void _SetCORSResponseHeaders(struct _u_response * response, bool bAllowCredentials)
+{
+	ulfius_add_header_to_response(response, "Access-Control-Allow-Origin", "*");
+	if(bAllowCredentials)
+		ulfius_add_header_to_response(response, "Access-Control-Allow-Credentials", "true");
+}

+ 54 - 0
rest/callback.h

@@ -0,0 +1,54 @@
+// callback.h :
+//
+
+#if !defined(AGD_CALLBACK_H__135A83F6_AF3D_44DB_B484_5B5C85ECD020__INCLUDED_)
+#define AGD_CALLBACK_H__135A83F6_AF3D_44DB_B484_5B5C85ECD020__INCLUDED_
+
+#include "main.h"
+#include "restvar.h"
+
+/////////////////////////////////////////////////////////////////////////////
+// callback.h - Declarations:
+
+#define GET_SHM_PREFIX				"/getshm"
+#define SET_SHM_PREFIX				"/setshm"
+
+/////////////////////////////////////////////////////////////////////////////
+
+typedef int (*pfn_FILE_REQUEST_CALLBACK)(const struct _u_request*, struct _u_response*, void*);
+
+/////////////////////////////////////////////////////////////////////////////
+
+typedef struct _FILEREQUEST
+{
+	std::string strMethod;
+	std::string strURL;
+	std::string strFilename;
+	std::string strContentType;
+	pfn_FILE_REQUEST_CALLBACK pfnCallback;
+	bool bString;
+}FILEREQUEST, *LPFILEREQUEST;
+typedef const FILEREQUEST *LPCFILEREQUEST;
+
+/////////////////////////////////////////////////////////////////////////////
+
+typedef struct _SHM_REQUEST_PARAMS
+{
+	CRestVarTable *pMap;
+	const char *pszUuid;
+}SHM_REQUEST_PARAMS, *LPSHM_REQUEST_PARAMS;
+typedef const SHM_REQUEST_PARAMS *LPCSHM_REQUEST_PARAMS;
+
+/////////////////////////////////////////////////////////////////////////////
+
+extern "C" int FileBodyResponseCallback(const struct _u_request * request, struct _u_response * response, void * user_data);
+extern "C" int GetShmPostResponseCallback_O(const struct _u_request * request, struct _u_response * response, void * user_data); // returns a structured object
+extern "C" int GetShmPostResponseCallback_M(const struct _u_request * request, struct _u_response * response, void * user_data); // returns a linear map
+extern "C" int SetShmPostResponseCallback(const struct _u_request * request, struct _u_response * response, void * user_data);
+extern "C" int GetShmGetResponseCallback(const struct _u_request * request, struct _u_response * response, void * user_data);
+extern "C" int OptionsResponseCallback(const struct _u_request * request, struct _u_response * response, void * user_data);
+extern "C" int NotAllowedResponseCallback(const struct _u_request * request, struct _u_response * response, void * user_data);
+extern "C" int NotImplementedResponseCallback(const struct _u_request * request, struct _u_response * response, void * user_data);
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	!defined(AGD_CALLBACK_H__135A83F6_AF3D_44DB_B484_5B5C85ECD020__INCLUDED_)

+ 32 - 0
rest/defines.h

@@ -0,0 +1,32 @@
+// defines.h :
+//
+
+#if !defined(AGD_DEFINES_H__DDEAB8B0_8BCD_468E_B490_46C8E63A29D4__INCLUDED_)
+#define AGD_DEFINES_H__DDEAB8B0_8BCD_468E_B490_46C8E63A29D4__INCLUDED_
+
+/////////////////////////////////////////////////////////////////////////////
+// defines.h - Declarations:
+
+#define _REST_USE_SSL				0
+#define _REST_LINEAR_GET			1
+
+/////////////////////////////////////////////////////////////////////////////
+
+#ifdef __cplusplus
+template <class T>
+inline static const T& __min(const T& a, const T& b)
+{
+	return (a < b) ? a : b;
+}
+
+template <class T>
+inline static const T& __max(const T& a, const T& b)
+{
+	return (a > b) ? a : b;
+}
+#else	//	__cplusplus
+#define __min(x, y) 				(((x) < (y)) ? (x) : (y))
+#define __max(x, y) 				(((x) > (y)) ? (x) : (y))
+#endif	//	__cplusplus
+
+#endif	//	!defined(AGD_DEFINES_H__DDEAB8B0_8BCD_468E_B490_46C8E63A29D4__INCLUDED_)

+ 113 - 0
rest/helpers.cpp

@@ -0,0 +1,113 @@
+#include "helpers.h"
+#include "callback.h"
+
+/////////////////////////////////////////////////////////////////////////////
+
+static std::vector<FILEREQUEST> g_afr;
+
+/////////////////////////////////////////////////////////////////////////////
+
+int InitializeStaticFiles(const char *pszRootDir, struct _u_instance *pInst, json_error_t &rJerr)
+{
+	int nRet = 0;
+	char szFilename[PATH_MAX + 32];
+    sprintf(szFilename, "%s/staticfiles.json", pszRootDir);
+
+    if(FileExist(szFilename))
+    {
+		CJson_t jtRoot(json_load_file(szFilename, JSON_REJECT_DUPLICATES, &rJerr)); // new reference!
+
+		if(jtRoot.IsValid())
+		{
+			const char *pszVal;
+			bool bVal;
+
+			if(!jtRoot.IsArray())
+			{
+				ETRACE("staticfiles.json contains no array!\n");
+				return -1;
+			}
+
+			for(size_t i = 0; i < json_array_size(jtRoot); i++)
+			{
+				FILEREQUEST fr;
+				CJson_t jtArr(json_array_get(jtRoot, i), false); // borrowed reference!
+
+				if(jtArr.IsValid())
+				{
+					if(!jtArr.IsObject())
+					{
+						ETRACE("staticfiles.json: element %zu is no object!\n", i);
+						return -1;
+					}
+
+					json_t *pjtMethod = json_object_get(jtArr, "method");
+					pszVal = json_string_value(pjtMethod);
+					fr.strMethod = pszVal;
+
+					json_t *pjtUrl = json_object_get(jtArr, "URL");
+					pszVal = json_string_value(pjtUrl);
+					fr.strURL = pszVal;
+
+					json_t *pjtFilename = json_object_get(jtArr, "fileName");
+					pszVal = json_string_value(pjtFilename);
+					sprintf(szFilename, "%s/%s", pszRootDir, pszVal);
+					fr.strFilename = szFilename;
+
+					json_t *pjtContentType = json_object_get(jtArr, "contentType");
+					pszVal = json_string_value(pjtContentType);
+					fr.strContentType = pszVal;
+
+					json_t *pjtIsString = json_object_get(jtArr, "isString");
+					bVal = json_boolean_value(pjtIsString);
+					fr.bString = bVal;
+
+					fr.pfnCallback = &FileBodyResponseCallback;
+
+					g_afr.push_back(fr);
+				}
+				else
+				{
+					ETRACE("Error!\n");
+					return -1;
+				}
+			}
+		}
+		else
+		{
+			if(rJerr.column == -1 || rJerr.line == -1)
+			{
+				TRACE("staticfiles.json: no static files configured.\n");
+				return 0;
+			}
+			else
+			{
+				ETRACE("%s: line %d, column %d: %s.\n", szFilename, rJerr.line, rJerr.column, rJerr.text);
+				return -1;
+			}
+		}
+    }
+    else
+    {
+//		TRACE("No static files found.\n");
+		return 0;
+    }
+
+	// Endpoint list declaration
+	for(auto it = g_afr.begin(); it != g_afr.end(); it++)
+	{
+		FILEREQUEST &rfr = *it;
+
+		if(ulfius_add_endpoint_by_val(pInst, rfr.strMethod.c_str(), rfr.strURL.c_str(), NULL, 0, rfr.pfnCallback, &rfr) != U_OK)
+		{
+			ETRACE("Error adding endpoint for URL '%s'!\n", rfr.strURL.c_str());
+			return -1;
+		}
+		else
+		{
+			nRet++;
+		}
+	}
+
+    return nRet;
+}

+ 15 - 0
rest/helpers.h

@@ -0,0 +1,15 @@
+// helpers.h :
+//
+
+#if !defined(AGD_HELPERS_H__2F944BB6_EA30_4239_9A06_DA28B59CC2EE__INCLUDED_)
+#define AGD_HELPERS_H__2F944BB6_EA30_4239_9A06_DA28B59CC2EE__INCLUDED_
+
+#include "main.h"
+
+/////////////////////////////////////////////////////////////////////////////
+// helpers.h - Declarations:
+
+int InitializeStaticFiles(const char *pszRootDir, struct _u_instance *pInst, json_error_t &rJerr);
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	!defined(AGD_HELPERS_H__2F944BB6_EA30_4239_9A06_DA28B59CC2EE__INCLUDED_)

+ 87 - 0
rest/keys.c

@@ -0,0 +1,87 @@
+static const char *g_pszKeyPem =
+"-----BEGIN PRIVATE KEY-----" \
+"MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDtyGM/xrLWPjnW" \
+"1dtr8+/nJVFzdgnjRjKcYaNZHUlDZHyO/f0tb8a1tVBJM4opER0kxGqDl6eaZ1QG" \
+"b5mPyHjl0RYgj3CTrdyeQqQCkSZwA6NXjbE8QSUJbkoFDKVmAxP3/neEbFT38jx3" \
+"NVoiwnPsMk2sh9FilL4k1ZHjl5fr73akcNSnstSHXADq86Fmz5AHU8F73QlvefDm" \
+"pFJWWsb1xxsG+Ia6Hge2IxgcePWKnRr8UMnxh0SOxdoreR3J8o10o+pEc/TCwHYS" \
+"5V4jqOwRS/MA/ymuJWySXpR7/qfGY/emCArPIPZO414uIeKL7N6Vwd5nFCk8mE3m" \
+"9lWzxNOnLqrWBcOOkVHhtbAGCJ3qgzHagYcSJvUrCei+pNHv//OtWuDpt4LKkfWD" \
+"sZgKfOYs1AuoTcV0dThb1Y/IN9lYsWd7B4HAmC2uflf5JQHtnnRRft5Juco0q9kw" \
+"0mAa7Ed+sSBvFtHmW3ahnam5NUsPY/wPW12wk4q+MkrSaz9JWQrEZAQeI2RwhEmo" \
+"LCTu6/4t9b0xmwevdzFN3VhH13ZLOIpNyjcGYqeaF5F1gG0MNd8ecV5BYmiGINMb" \
+"RrlLsHYUYhUk/iBuOcM4QeNKRqsY2JQjY8wcJ7loUa+p4etcyEhNoKwMNaJCMiuX" \
+"TY+J0aQG8wxeMjtP+w4Fwgcog/sPtQIDAQABAoICAGaSzfWiEl2HRE4fvOLJvRCa" \
+"MZO8NLG9A3IgUGXN01/mBjFs8X1bPX3/NB4PxKbIgijtLgGMNznNHxsTo0u6MCT7" \
+"WC89kVOzaXs/xh5xXQJvH1RShEU42PBDily5QtRJuaB+PjJ6xQCcl2bZNgPOnFEk" \
+"tgsaH8IF5ixfkOp754ZYq3ZuUd3xr+qgTqkL5fFC0p/Giq2TAVH8aWYlNnuX90Ho" \
+"3QjZxsyczbjNVMZbi5h7Azwa4bJ2JqXsRZwBSE9oeYr6dsGGv+wPOCAYn86EPSmM" \
+"+MvSRrmf3UEa2bTPerKDRz5xM7B8SKRpQqRl66Nuz+oMUpNmyA8iqGBcnUXKP36H" \
+"DMDRUIG8gHft51rFwbKrx8bR8tU+/rjyRlQx09bPtiG9i1JBSKAo/1PW+uWJOR0N" \
+"qA2beniguBFc4EWfqkSgMc/le6zPmg3ntccvICpVgwKrgR55TQvEa9diVHQAdl55" \
+"0VTfjgj6+PcLAvSnzw1hwbSW4JUGxV155ti6ZH/IEYffKOfrb+p7ipmIwuVzuHox" \
+"Khhx/62CHctt1XDM3fdUmVntB3+V/YefWMVhz6oy//wp+iMFdzknwVQTEp/zCKZE" \
+"MaiJjN0CbGeK/kIQ1/WiUwfL4z6qmECBmG5PFcfhO1kZftihp3FidzR12R5VPICM" \
+"/96Bmp8zN29/RNaOAy4hAoIBAQD/oYlB4hLJtOnvBvo9HaLbrrXeAmGsXtVAjRMR" \
+"nLg3jCZOkB/ckuo3wIbSjfnACiqLftkgBbIZkHwA/GwqVCcn6KhzegXTKo+7vyHD" \
+"3A2v8bkgYnl9T9ZnQbU6p5qO1WpL0N4c848K94o/W57DO8siLyKYTJNL+f61uCHn" \
+"uWTTi0WHKmfcfBgI8nG3OGXtpB95Bgqg9ks6wY6FfPao2XcL2XxprnnNLcoVcPXU" \
+"DPorUc/Un44HpiKrlJ/ldiFXFOBdgqrjpVyDr8AWZRLhtLs/cS3aQxyM+3RrwIkp" \
+"ZcbXH58RPYiijoQQIzavV5EjUYYOkTQ4sN/QP7WYOGqux0hpAoIBAQDuIEGLje46" \
+"gP4OlVX3FrE058lRww0GU97PYuj+waGfz3WObe4UZLiKPZCF1roIb/pKK4I0XiJL" \
+"228aPZ7KUpArQPmBAlIpRdNJfIR/etFK1YDLLzKLf4dyYzMhmx+Qxrp3ejEnoBc2" \
+"Zlov4Qb1+omahc+k6VOd4E4MrL6lk99RkpWhXBRI/eXUXu1VJTNjlvjI+dH+JpXQ" \
+"tDDc3GOSCC01mpnGTRZIOYh207aD+HFHKCiphzXTX2HYqnwsVAtNSRrDp/C07pKL" \
+"1royabuixeXU0pDaYTbAjqyuJ4+Pnlxw+KHERPVbmqyTYW1Uweq7klFI4axpLm4M" \
+"vKGCTRmACANtAoIBAQCOlsBFK6FnOa6zUaULqhhJia6QY6uvxkYVd5uHW2+2LErB" \
+"z8q18o0LBC0wDVmeMQ2COgNKxWKOt/UzZ5m+Eqq0rCYqI4pUO41pIDfpVDmeRqsI" \
+"0WUK7RmfBxvb19lyisB8AcmjRIgpieJnbOel5xhrvPcDFNOksZHOMyNWaic/iJQD" \
+"zTeZ2O5fLz0XAjJ41+9mRmRvXHKiF400EHxVuteYmHu5R0VRmZT7WMMGwSZaO4w+" \
+"AbHrulPkeUana7k1JcPw5Q4FZRuxqLrwwvhBRdqcjLkS4hRu32QVJpRbrxW2sUC9" \
+"ESHB6tTnW30JWULv/G1WetAyPxbYxnC4s99uFmLhAoIBAC4Oxx67AQ0Hzhv7/nig" \
+"Rv44ptPNqkNDDaGHPBGqWhyS/+XglWAUay0x4zY/5hOjtQGLkM+YNYH+m2qz6Hax" \
+"qXj0RE4TqNiazNc14lrV4o1a5mJmapxYulYBoe4qFtzl00Mwuzky2cqH2YHRQItr" \
+"PhpJL3RyPN4YeIAzGVzlURhyBccEDFhh44ZEJhuF6n6kwNH+oANMa0XG1HyjlA62" \
+"zifEE2IrntexaReys2arMx3UQqknod2UYVSLBSF90AHO1gJqBUC5tFaf3bvKf6Ro" \
+"KqATajkS/IgaEIRO2pGoy7B+JqvTurFRJXgMLB1stH47OsWiALsHY/nKsmKFjx0Y" \
+"b7ECggEBAKBgxZE1QfxZ1/CnFz4B/FAMqA8MdKa2fgVUnAqJ6uOXyEsTjazO0jhx" \
+"tZWOheHmnDzuLA9AQCBWG6vDIQ9wsTzoKoiYye8HkoepkSO8Za+V7yFyuSW4SZb7" \
+"l+UdFw4TDvgzSZhfor3AG+NnkcOVIZkDQiIamp6A+BLLYRGgpC3DskBmiibC+u3a" \
+"tXOkARqKNBkWPFpY/FKbR6Ss1npVsP2SiQxJpfschr7JUDTrhhLCMhAY4ZR8Q3vd" \
+"mGxULDwX6wUcO49yr7nal4TNX6OZejjtVC1h0jSFBahXM7rhwpGur0WGL5JxkTAZ" \
+"4aG7+sOFaX+emA/IBTddOTJAcBiOL5Y=" \
+"-----END PRIVATE KEY-----";
+
+static const char *g_pszCertPem =
+"-----BEGIN CERTIFICATE-----" \
+"MIIFkzCCA3ugAwIBAgIJALCGPwi6fyahMA0GCSqGSIb3DQEBCwUAMGAxCzAJBgNV" \
+"BAYTAkFUMQ8wDQYDVQQIDAZTdHlyaWExDTALBgNVBAcMBEdyYXoxDDAKBgNVBAoM" \
+"A0dmQTEMMAoGA1UECwwDR2ZBMRUwEwYDVQQDDAwqLmdmYS5kZXZpY2UwHhcNMTgw" \
+"MjA4MTUxMjA5WhcNMTkwMjA4MTUxMjA5WjBgMQswCQYDVQQGEwJBVDEPMA0GA1UE" \
+"CAwGU3R5cmlhMQ0wCwYDVQQHDARHcmF6MQwwCgYDVQQKDANHZkExDDAKBgNVBAsM" \
+"A0dmQTEVMBMGA1UEAwwMKi5nZmEuZGV2aWNlMIICIjANBgkqhkiG9w0BAQEFAAOC" \
+"Ag8AMIICCgKCAgEA7chjP8ay1j451tXba/Pv5yVRc3YJ40YynGGjWR1JQ2R8jv39" \
+"LW/GtbVQSTOKKREdJMRqg5enmmdUBm+Zj8h45dEWII9wk63cnkKkApEmcAOjV42x" \
+"PEElCW5KBQylZgMT9/53hGxU9/I8dzVaIsJz7DJNrIfRYpS+JNWR45eX6+92pHDU" \
+"p7LUh1wA6vOhZs+QB1PBe90Jb3nw5qRSVlrG9ccbBviGuh4HtiMYHHj1ip0a/FDJ" \
+"8YdEjsXaK3kdyfKNdKPqRHP0wsB2EuVeI6jsEUvzAP8priVskl6Ue/6nxmP3pggK" \
+"zyD2TuNeLiHii+zelcHeZxQpPJhN5vZVs8TTpy6q1gXDjpFR4bWwBgid6oMx2oGH" \
+"Eib1KwnovqTR7//zrVrg6beCypH1g7GYCnzmLNQLqE3FdHU4W9WPyDfZWLFneweB" \
+"wJgtrn5X+SUB7Z50UX7eSbnKNKvZMNJgGuxHfrEgbxbR5lt2oZ2puTVLD2P8D1td" \
+"sJOKvjJK0ms/SVkKxGQEHiNkcIRJqCwk7uv+LfW9MZsHr3cxTd1YR9d2SziKTco3" \
+"BmKnmheRdYBtDDXfHnFeQWJohiDTG0a5S7B2FGIVJP4gbjnDOEHjSkarGNiUI2PM" \
+"HCe5aFGvqeHrXMhITaCsDDWiQjIrl02PidGkBvMMXjI7T/sOBcIHKIP7D7UCAwEA" \
+"AaNQME4wHQYDVR0OBBYEFLY1+QWvIHu7LummMnTKY6JUbexHMB8GA1UdIwQYMBaA" \
+"FLY1+QWvIHu7LummMnTKY6JUbexHMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEL" \
+"BQADggIBAC/lSoFXQmjfugYaRWQpTc6J7TSppMttc51SVuL/pFCGYWVn2mwIqigI" \
+"0g5J0HaMAuGrDrENJlgxMf00ixDdpeET59XfqkJiPd5BRY1Ef1GoGEYp3cMlHHhe" \
+"8yHTH08U/8UMmHHF/yKxrqS0aKUJ9bjuQq5BuqvtBx1/Q75FUbkGyZJg58j94Lnm" \
+"u5rRjMEZ+p0/1uRWiG7jU9xvXXL1BZ9NrSsNSpl9lQ3wk9sD0sudMiiywbuoNpkU" \
+"lf066Gcy/fUCx4xOx9HXBBTGfnbHnwRMcnLnoIipxxuGomS7/BSasGF01my6nqK/" \
+"DMQrJXg+tJ9o4Iaf9MgVXqLFnjFNpGjfuN+KMYZ5flK7J+X4iPWL4TVjJ+B2e6rF" \
+"ftaiP8dQ7yhX2OU6zZ7lHOJKznOJ71z4RPrxiwDX+oSA1oBheKL0yFPbVPXF/3qS" \
+"ucD8THX2FvGwjh/Fu4R7+qUIaCT5qmKW+mo8Zp51aeXRp3La9sO5GUClgEFACZ+y" \
+"PgHDbMaaUX/7erLWdjCqEHmZ1qGGQ9tkp2BlTaVq8d+ucJf2ui6xzmHsUamoR8yg" \
+"RRXYJwojLQjs3V5Azws86HcLH0USfMTYTNzFXUdGBQH3o5qOV/gjPK+UEa5nLlom" \
+"l5fFkaSa/767EqQfVhK4Cy8wdpxsDQeLVIZOhBOQ3eVxH7O7IyEO" \
+"-----END CERTIFICATE-----";

+ 489 - 0
rest/main.cpp

@@ -0,0 +1,489 @@
+#include "main.h"
+#include "projal.h"
+#include "helpers.h"
+#include "callback.h"
+#include "instance.h"
+
+#if _REST_USE_SSL
+#include "keys.c"
+#endif	//	_REST_USE_SSL
+
+#if _REST_LINEAR_GET
+#define GetShmPostResponseCallback	GetShmPostResponseCallback_M
+#else	//	_REST_LINEAR_GET
+#define GetShmPostResponseCallback	GetShmPostResponseCallback_O
+#endif	//	_REST_LINEAR_GET
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// app control
+
+#define _APPID						GFA_APPCTRL_APPID_REST
+#define _APPNAME					"REST"
+#define _DEPENDENCIES				((appid_t)(GFA_APPCTRL_APPID_REMANENT))
+#define _REST_CYCLE_INTV_MS			200
+
+/////////////////////////////////////////////////////////////////////////////
+
+static volatile bool				g_fRun			= false;
+static volatile bool				g_fPauseImp		= false;
+static volatile bool				g_fPauseCmd		= false;
+static volatile bool				g_fZombie		= false;
+static appid_t						g_nDepRunning	= 0;
+static sigset_t						g_set;
+int									g_nLastSig = -1;
+
+/////////////////////////////////////////////////////////////////////////////
+
+static void _SigHandler(int sig)
+{
+	g_nLastSig = sig;
+	g_fRun = g_fPauseImp = g_fPauseCmd = g_fZombie = false;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+static void _ProcessCtrlMessages(HAPPCTRL hAC, HAPPINFO hAI)
+{
+    ctrlmsg_t nCtrlMsg;
+
+	while(g_fRun && (nCtrlMsg = ::GfaIpcAppCtrlGetNextCtrlMsg(hAI)))
+	{
+		switch(nCtrlMsg)
+		{
+		case GFA_APPCTRL_CTRLMSG_STOP:
+			g_fRun = false;
+			g_fPauseImp = false;
+			g_fPauseCmd = false;
+			g_fZombie = false;
+			TRACE("Received Control Message 'Stop'\n");
+			return;
+		case GFA_APPCTRL_CTRLMSG_PAUSE:
+			if(!g_fPauseCmd)
+			{
+				g_fPauseCmd = true;
+				if(!g_fPauseImp)
+				{
+					::GfaIpcAppCtrlSetState(hAC, GIAS_Paused);
+					TRACE("Received Control Message 'Pause'\n");
+					TRACE("%-8s: State: %s\n", "Me", ::GfaIpcAppCtrlGetStateText(GIAS_Paused));
+				}
+			}
+			break;
+		case GFA_APPCTRL_CTRLMSG_RESUME:
+			if(g_fPauseCmd)
+			{
+				g_fPauseCmd = false;
+				if(!g_fPauseImp)
+				{
+					TRACE("Received Control Message 'Resume'\n");
+					::GfaIpcAppCtrlSetState(hAC, GIAS_Running);
+					TRACE("%-8s: State: %s\n", "Me", ::GfaIpcAppCtrlGetStateText(GIAS_Running));
+				}
+			}
+			break;
+		default:
+			break;
+		}
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+static void _ProcessStateEvents(HAPPCTRL hAC, HAPPINFO hAI)
+{
+    appid_t nAppIdSrc;
+    bool fOldPaused = g_fPauseImp;
+	char szDispName[128];
+
+	while(g_fRun && (nAppIdSrc = ::GfaIpcAppCtrlGetNextStateEvtSrc(hAI)))
+	{
+		GfaIpcAppStates state = ::GfaIpcAppCtrlGetState(hAC, nAppIdSrc);
+		GfaIpcAppCtrlGetDisplayName(hAC, nAppIdSrc, szDispName, sizeof(szDispName));
+		TRACE("%-8s: State: %s\n", szDispName, ::GfaIpcAppCtrlGetStateText(state));
+		
+		if(nAppIdSrc & _DEPENDENCIES)
+		{
+			if(state == GIAS_Running)
+			{
+				TRACE("%s -> %s.\n", szDispName, ::GfaIpcAppCtrlGetStateText(state));
+				g_nDepRunning |= nAppIdSrc;
+			}
+			else
+			{
+				TRACE("%s -> %s.\n", szDispName, ::GfaIpcAppCtrlGetStateText(state));
+				g_nDepRunning &= ~nAppIdSrc;
+			}
+		}
+	}
+
+	if(g_fRun)
+	{
+		g_fPauseImp = (g_nDepRunning != _DEPENDENCIES);
+
+		if(!g_fPauseCmd && (fOldPaused != g_fPauseImp))
+		{
+			fOldPaused = g_fPauseImp;
+			GfaIpcAppStates newState = g_fPauseImp ? GIAS_Paused : GIAS_Running;
+			::GfaIpcAppCtrlSetState(hAC, newState);
+			if(g_fPauseImp)
+			{
+				TRACE("Enter state %s ...\n", ::GfaIpcAppCtrlGetStateText(newState));
+			}
+			else
+			{
+				TRACE("Enter state %s ...\n", ::GfaIpcAppCtrlGetStateText(newState));
+			}
+		}
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// main
+
+int main(int argc, char *argv[])
+{
+	UNUSED(argc);
+	UNUSED(argv);
+
+	int nRet = 0;
+	HSHM hShm = NULL;
+	void *pShm = NULL;
+	HAPPCTRL hAC = NULL;
+	HAPPINFO hAI;
+	struct _u_instance instance;
+	CURLcode cuGlobInit = CURL_LAST;
+	int ulfInit = U_ERROR, ulfStart = U_ERROR;
+	bool bUlfFrmwrkStarted = false;
+
+    /////////////////////////////////////////////////////////////////////////
+    // check for multiple instances
+
+    CProcessInstance pi;
+
+    if(!pi.LockInstance(UUID_SHM))
+    {
+        ETRACE("Failed to start instance!\n");
+        return -1;
+    }
+
+	/////////////////////////////////////////////////////////////////////////
+
+	do
+	{
+		char szRootDir[PATH_MAX];
+		const char *pszRootDir;
+		json_error_t jerr;
+
+		g_fZombie = true;
+
+		/////////////////////////////////////////////////////////////
+		// configure signal handling
+
+		struct sigaction sa;
+		memset(&sa, 0, sizeof(sa));
+		sigfillset(&g_set);
+
+		// handle signals
+		sa.sa_handler = _SigHandler;
+	    sigaction(SIGHUP, &sa, NULL);	// handles user's terminal disconnect
+	    sigaction(SIGQUIT, &sa, NULL);	// handles Ctrl + '\'
+		sigaction(SIGTERM, &sa, NULL);	// handles normal termination
+		sigaction(SIGABRT, &sa, NULL);	// handles abnormal termination (i.e. abort())
+		sigaction(SIGINT, &sa, NULL);	// handles Ctrl + 'C'
+
+		// ignore signals
+		sa.sa_handler = SIG_IGN;
+	    sigaction(SIGTSTP, &sa, NULL);	// ignores Ctrl + 'Z'
+	    sigaction(SIGSTOP, &sa, NULL);	// ignores Stop
+	    sigaction(SIGCONT, &sa, NULL);	// ignores Continue
+	    sigaction(SIGCHLD, &sa, NULL);	// ignores child process termination
+	    sigaction(0, &sa, NULL);		// ignores shell termination
+
+		/////////////////////////////////////////////////////////////
+		// initialize app control
+
+		if(!(hAC = ::GfaIpcAppCtrlAcquire(_APPID, _APPNAME, _REST_CYCLE_INTV_MS * 1000, _REST_CYCLE_INTV_MS * 3000)))
+		{
+			ETRACE("Failed to acquire AppCtrl-Handle!\n");
+			nRet = -1;
+			break;
+		}
+
+		::GfaIpcAppCtrlSetState(hAC, GIAS_Initializing);
+		TRACE("Enter state %s ...\n", ::GfaIpcAppCtrlGetStateText(GIAS_Initializing));
+
+		if(!::GfaIpcAppCtrlSubscribeStateEvents(hAC, _DEPENDENCIES))
+		{
+			ETRACE("Failed to subscribe state event notifications!\n");
+			nRet = -1;
+			break;
+		}
+
+		/////////////////////////////////////////////////////////////////////
+
+		pszRootDir = GetAppDirectory(szRootDir, _COUNTOF(szRootDir));
+
+		/////////////////////////////////////////////////////////////////////
+		// initialize CURL
+
+		if((cuGlobInit = curl_global_init(CURL_GLOBAL_ALL)) != CURLE_OK)
+		{
+			ETRACE("Failed to initialize CURL!\n");
+			nRet = -1;
+			break;
+		}
+
+	    if(!(hShm = acquire_shm(sizeof(shm_t))))
+	    {
+			ETRACE("GfaIpcAcquireSHM failed!\n");
+			nRet = -1;
+			break;
+	    }
+
+	    if(!(pShm = GfaIpcAcquirePointer(hShm)))
+	    {
+			ETRACE("GfaIpcAcquirePointer failed!\n");
+			nRet = -1;
+			break;
+	    }
+	    
+#ifdef _DEBUG
+		GfaIpcDumpSHMROT();
+		fflush(stdout);
+#endif	//	_DEBUG
+
+		/////////////////////////////////////////////////////////////////////
+
+		if((ulfInit = ulfius_init_instance(&instance, _REST_PORT, NULL, NULL)) != U_OK)
+		{
+			ETRACE("ulfius_init_instance failed!\n");
+			nRet = -1;
+			break;
+		}
+			
+		/////////////////////////////////////////////////////////////////////
+		// SHM and SHM variables table
+
+		CRestVarTable map;
+		CShm_t shm(pShm, hShm);
+		shm.InitPath(NULL, NULL);
+		shm.CreateMembersTable(map);
+
+		/////////////////////////////////////////////////////////////////////
+		// initialize request parameters
+
+		SHM_REQUEST_PARAMS srp;
+		srp.pMap	= &map;
+		srp.pszUuid	= UUID_SHM;
+
+		/////////////////////////////////////////////////////////////////////
+		/////////////////////////////////////////////////////////////////////
+		// add handler functions
+		// initialize static files if any
+		if(InitializeStaticFiles(pszRootDir, &instance, jerr) < 0)
+		{
+			ETRACE("InitializeStaticFiles failed!\n");
+			nRet = -1;
+			break;
+		}
+
+		/////////////////////////////////////////////////////////////////////
+		// POST
+		if(ulfius_add_endpoint_by_val(&instance, "POST", GET_SHM_PREFIX, NULL, 0, &GetShmPostResponseCallback, &srp) != U_OK)
+		{
+			ETRACE("ulfius_add_endpoint_by_val failed!\n");
+			nRet = -1;
+			break;
+		}
+		if(ulfius_add_endpoint_by_val(&instance, "POST", SET_SHM_PREFIX, NULL, 0, &SetShmPostResponseCallback, &srp) != U_OK)
+		{
+			ETRACE("ulfius_add_endpoint_by_val failed!\n");
+			nRet = -1;
+			break;
+		}
+
+#if _REST_IMPLEMENT_GET
+		/////////////////////////////////////////////////////////////////////
+		// GET
+		if(ulfius_add_endpoint_by_val(&instance, "GET", GET_SHM_PREFIX, "/*", 0, &GetShmGetResponseCallback, &srp) != U_OK)
+		{
+			ETRACE("ulfius_add_endpoint_by_val failed!\n");
+			nRet = -1;
+			break;
+		}
+#endif	//	_REST_IMPLEMENT_GET
+
+		/////////////////////////////////////////////////////////////////////
+		// OPTIONS
+		if(ulfius_add_endpoint_by_val(&instance, "OPTIONS", NULL, "/*", 0, &OptionsResponseCallback, NULL) != U_OK)
+		{
+			ETRACE("ulfius_add_endpoint_by_val failed!\n");
+			nRet = -1;
+			break;
+		}
+
+		/////////////////////////////////////////////////////////////////////
+		// handler for HTTP verbs that are not allowed
+		if(ulfius_add_endpoint_by_val(&instance, "HEAD", NULL, "/*", 0, &NotAllowedResponseCallback, NULL) != U_OK)
+		{
+			ETRACE("ulfius_add_endpoint_by_val failed!\n");
+			nRet = -1;
+			break;
+		}
+		if(ulfius_add_endpoint_by_val(&instance, "PUT", NULL, "/*", 0, &NotAllowedResponseCallback, NULL) != U_OK)
+		{
+			ETRACE("ulfius_add_endpoint_by_val failed!\n");
+			nRet = -1;
+			break;
+		}
+		if(ulfius_add_endpoint_by_val(&instance, "DELETE", NULL, "/*", 0, &NotAllowedResponseCallback, NULL) != U_OK)
+		{
+			ETRACE("ulfius_add_endpoint_by_val failed!\n");
+			nRet = -1;
+			break;
+		}
+		if(ulfius_add_endpoint_by_val(&instance, "CONNECT", NULL, "/*", 0, &NotAllowedResponseCallback, NULL) != U_OK)
+		{
+			ETRACE("ulfius_add_endpoint_by_val failed!\n");
+			nRet = -1;
+			break;
+		}
+		if(ulfius_add_endpoint_by_val(&instance, "TRACE", NULL, "/*", 0, &NotAllowedResponseCallback, NULL) != U_OK)
+		{
+			ETRACE("ulfius_add_endpoint_by_val failed!\n");
+			nRet = -1;
+			break;
+		}
+		if(ulfius_add_endpoint_by_val(&instance, "PATCH", NULL, "/*", 0, &NotAllowedResponseCallback, NULL) != U_OK)
+		{
+			ETRACE("ulfius_add_endpoint_by_val failed!\n");
+			nRet = -1;
+			break;
+		}
+
+		/////////////////////////////////////////////////////////////////////
+		// start service
+
+#if _REST_USE_SSL
+	    if((ulfStart = ulfius_start_secure_framework(&instance, g_pszKeyPem, g_pszCertPem)) != U_OK)
+#else	// _REST_USE_SSL
+		if((ulfStart = ulfius_start_framework(&instance)) != U_OK)
+#endif	// _REST_USE_SSL
+		{
+			ETRACE("ulfius_start_framework failed!\n");
+			nRet = -1;
+			break;
+		}
+
+		TRACE("Service started at port %hu.\n", instance.port);
+
+		bUlfFrmwrkStarted = true;
+		g_fZombie = false;
+		g_fRun = true;
+		::GfaIpcAppCtrlSetState(hAC, GIAS_Running);
+	}
+	while(false);
+
+	/////////////////////////////////////////////////////////////////////////
+	/////////////////////////////////////////////////////////////////////////
+	/////////////////////////////////////////////////////////////////////////
+
+	while(g_fRun)
+	{
+		////////////////////////////////////////////////////////////////////////////////////////
+		// update app control info
+
+		if((hAI = ::GfaIpcAppCtrlInfoUpdate(hAC, 0)))
+		{
+			_ProcessCtrlMessages(hAC, hAI);
+			if(!g_fRun)
+				break;
+			_ProcessStateEvents(hAC, hAI);
+		}
+		
+		if(bUlfFrmwrkStarted && (g_fPauseImp || g_fPauseCmd))
+		{
+			ulfius_stop_framework(&instance);
+			bUlfFrmwrkStarted = false;
+			TRACE("Service exit.\n");
+		}
+		else if(!bUlfFrmwrkStarted && !g_fPauseImp && !g_fPauseCmd)
+		{
+#if _REST_USE_SSL
+		    if((ulfStart = ulfius_start_secure_framework(&instance, g_pszKeyPem, g_pszCertPem)) != U_OK)
+#else	// _REST_USE_SSL
+			if((ulfStart = ulfius_start_framework(&instance)) != U_OK)
+#endif	// _REST_USE_SSL
+			{
+				ETRACE("ulfius_start_framework failed!\n");
+				g_fZombie = true;
+				g_fRun = false;
+				nRet = -1;
+				break;
+			}
+
+			bUlfFrmwrkStarted = true;
+			TRACE("Service started at port %hu.\n", instance.port);
+		}
+
+		usleep(_REST_CYCLE_INTV_MS * 1000);
+	}
+
+	/////////////////////////////////////////////////////////////////////////
+	/////////////////////////////////////////////////////////////////////////
+	/////////////////////////////////////////////////////////////////////////
+
+	if(g_nLastSig >= 0)
+	{
+		TRACE("Received signal '%s'.\n", strsignal(g_nLastSig));
+		g_nLastSig = -1;
+	}
+
+	if(bUlfFrmwrkStarted)
+	{
+		ulfius_stop_framework(&instance);
+		TRACE("Service exit.\n");
+	}
+
+	if(ulfInit == U_OK)
+		ulfius_clean_instance(&instance);
+
+	if(hShm)
+	{
+		if(pShm)
+		{
+			TRACE("Releasing SHM Pointer ...\n");
+			::GfaIpcReleasePointer(hShm, pShm);
+		}
+
+		TRACE("Releasing SHM Handle ...\n");
+    	::GfaIpcReleaseSHM(hShm);
+    }
+
+	if(cuGlobInit == CURLE_OK)
+	   	curl_global_cleanup();
+	
+	if(g_fZombie)
+	{
+		if(hAC)
+			::GfaIpcAppCtrlSetState(hAC, GIAS_Zombie);
+		TRACE("Enter Zombie state ...\n");
+		pause();
+
+		if(g_nLastSig >= 0)
+		{
+			TRACE("Received signal '%s'.\n", strsignal(g_nLastSig));
+		}
+	}
+
+	if(hAC)
+	{
+//		TRACE("Enter state %s ...\n", ::GfaIpcAppCtrlGetStateText(GIAS_Terminating));
+		::GfaIpcAppCtrlSetState(hAC, GIAS_Terminating);
+		TRACE("Releasing App Control ...\n");
+		::GfaIpcAppCtrlRelease(hAC);
+	}
+
+	TRACE("Process exit.\n");
+	return nRet;
+}

+ 39 - 0
rest/main.h

@@ -0,0 +1,39 @@
+// main.h :
+//
+
+#if !defined(AGD_MAIN_H__DBD15368_61A6_4741_99B4_4F79DED9B2E4__INCLUDED_)
+#define AGD_MAIN_H__DBD15368_61A6_4741_99B4_4F79DED9B2E4__INCLUDED_
+
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <curl/curl.h>
+#include <linux/limits.h>
+#include <sys/stat.h>
+#include <signal.h>
+#include <string>
+#include <vector>
+#include <assert.h>
+
+#ifndef _LIBBUILD
+#include <gfa/svc/common/debug.h>
+#include <gfa/svc/common/fileutil.h>
+#include <gfa/svc/rest/defines.h>
+#else	//	_LIBBUILD
+#include "common/debug.h"
+#include "common/fileutil.h"
+#include "defines.h"
+#endif	//	_LIBBUILD
+
+//#include "apikey.h"
+
+extern "C"
+{
+#include <ulfius.h>
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// main.h - Declarations:
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	!defined(AGD_MAIN_H__DBD15368_61A6_4741_99B4_4F79DED9B2E4__INCLUDED_)

+ 221 - 0
rest/request-response-format.txt

@@ -0,0 +1,221 @@
+In der momentanen Version gibt der POST-Request auf "/getshm" eine lineare 
+Tabelle von Werten zurück. Der Request hat entweder das gewohnte Format, d. 
+h. es wird ein Request-Objetkt an den Server gesendet, oder (neu!) es kann 
+auch ein Array aus Request-Objekten gesendet werden.
+
+/////////////////////////////////////////////////////////////////////////////
+
+Beispiel Objekt:
+
+{
+	"path": "/Anlage_Allgemein_Konfig_Daten/K_Anlagen_Name",
+	"cookie": "idValues1"
+}
+
+Beispiel Array:
+
+[
+	{
+		"path": "/Anlage_Allgemein_Konfig_Daten/K_Anlagen_Name",
+		"cookie": "idValues1"
+	},
+	{
+		"path": "/Anlage_Allgemein_Konfig_Daten/K_Email_Adresse_An[125]",
+		"cookie": "idValues2"
+	},
+	{
+		"path": "/Anlage_Allgemein_Konfig_Daten/PID_Regler_P",
+		"cookie": "idValues3"
+	}
+]
+
+/////////////////////////////////////////////////////////////////////////////
+
+Antwort auf Objekt-Request:
+
+{
+	"uuid": "6f58640d-9fb4-44e4-b2d3-a5e3125f9e26",
+	"request": [
+		{
+			"path": "/Anlage_Allgemein_Konfig_Daten/K_Anlagen_Name",
+			"cookie": "idValues1"
+		}
+	],
+	"response": [
+		{
+			"responseType": 0,
+			"requestIndex": 0,
+			"valName": "K_Anlagen_Name",
+			"valType": 2,
+			"path": "/Anlage_Allgemein_Konfig_Daten/K_Anlagen_Name",
+			"index": -1,
+			"value": "Monty Python's Flyin"
+		}
+	]
+}
+
+Antwort auf Array-Request:
+
+{
+	"uuid": "6f58640d-9fb4-44e4-b2d3-a5e3125f9e26",
+	"request": [
+		{
+			"path": "/Anlage_Allgemein_Konfig_Daten/K_Anlagen_Name",
+			"cookie": "idValues1"
+		},
+		{
+			"path": "/Anlage_Allgemein_Konfig_Daten/K_Email_Adresse_An[125]",
+			"cookie": "idValues2"
+		},
+		{
+			"path": "/Anlage_Allgemein_Konfig_Daten/PID_Regler_P",
+			"cookie": "idValues3"
+		}
+	],
+	"response": [
+		{
+			"responseType": 0,
+			"requestIndex": 0,
+			"valName": "K_Anlagen_Name",
+			"valType": 2,
+			"path": "/Anlage_Allgemein_Konfig_Daten/K_Anlagen_Name",
+			"index": -1,
+			"value": "Monty Python's Flyin"
+		},
+		{
+			"responseType": 1,
+			"requestIndex": 1,
+			"message": "Invalid path!",
+			"code": -1,
+			"path": "/Anlage_Allgemein_Konfig_Daten/K_Email_Adresse_An[125]"
+		},
+		{
+			"responseType": 0,
+			"requestIndex": 2,
+			"valName": "PID_Regler_P",
+			"valType": 4,
+			"path": "/Anlage_Allgemein_Konfig_Daten/PID_Regler_P",
+			"index": -1,
+			"value": 123.456
+		}
+	]
+}
+
+Zu beachten ist, dass die Elemente "request" und "response" in beiden Fällen 
+Arrays sind, im Fall eines Einzel-Requests mit nur einem Element!
+Die Objekte im response-Array können 2 unterschiedliche Typen sein. 
+Unterscheiden lässt sich der Typ am Member "responseType", das in jedem Fall 
+vorhanden ist.
+
+responseType : 0 => es handelt sich um ein Wert-Objekt, das die gewünschte 
+Variable zurückgibt.
+responseType : 1 => es handelt sich um ein Status-Objekt, das im Fehlerfall 
+zurückgegeben wird.
+
+Das Wert-Objekt hat nun zu bereits bekannten Members noch folgndes:
+requestIndex: gibt den Index des korrespondierenden Requests im 
+request-Array an.
+
+Das Status-Objekt enthält nun ebenfalls die Members requestIndex und path.
+
+Somit enthalten beide Objekte immer folgende Members:
+responseType
+requestIndex
+path
+
+/////////////////////////////////////////////////////////////////////////////
+
+Dieser Aufbau gilt sinngemäss nun auch für "/setshm"! Hier werden zwar 
+keine Werte zurückgegeben, d. h. "responseType" ist immer 1, aber die 
+Status-Objekte sind nun zwecks Konsistenz auch in das response-Array 
+gewandert und haben das selbe Format! Auch hier kann der Request entweder 
+ein Objekt oder ein Array aus diesen Objekten sein!
+
+Beispiel Request:
+
+[
+	{
+		"path": "/Anlage_Allgemein_Konfig_Daten/K_Anlagen_Name",
+		"value": "Monty Python's Flying Circus",
+		"cookie": "idValues1"
+	},
+	{
+		"path": "/Anlage_Allgemein_Konfig_Daten/K_Email_Adresse_An[125]",
+		"value": "Blah blah bluh!",
+		"cookie": "idValues2"
+	},
+	{
+		"path": "/Anlage_Allgemein_Konfig_Daten/PID_Regler_P",
+		"value": 123.456,
+		"cookie": "idValues3"
+	},
+	{
+		"path": "/Stall_Konfig_Daten[3]",
+		"value": 1,
+		"cookie": "idValues4"
+	}
+]
+
+Beispiel Response:
+
+{
+	"uuid": "6f58640d-9fb4-44e4-b2d3-a5e3125f9e26",
+	"request": [
+		{
+			"path": "/Anlage_Allgemein_Konfig_Daten/K_Anlagen_Name",
+			"cookie": "idValues1",
+			"value": "Monty Python's Flying Circus"
+		},
+		{
+			"path": "/Anlage_Allgemein_Konfig_Daten/K_Email_Adresse_An[125]",
+			"cookie": "idValues2",
+			"value": "Blah blah bluh!"
+		},
+		{
+			"path": "/Anlage_Allgemein_Konfig_Daten/PID_Regler_P",
+			"cookie": "idValues3",
+			"value": 123.456
+		},
+		{
+			"path": "/Stall_Konfig_Daten[3]",
+			"cookie": "idValues4",
+			"value": 1
+		}
+	],
+	"response": [
+		{
+			"message": "",
+			"responseType": 1,
+			"requestIndex": 0,
+			"code": 0,
+			"path": "/Anlage_Allgemein_Konfig_Daten/K_Anlagen_Name"
+		},
+		{
+			"message": "Invalid path!",
+			"responseType": 1,
+			"requestIndex": 1,
+			"code": -1,
+			"path": "/Anlage_Allgemein_Konfig_Daten/K_Email_Adresse_An[125]"
+		},
+		{
+			"message": "",
+			"responseType": 1,
+			"requestIndex": 2,
+			"code": 0,
+			"path": "/Anlage_Allgemein_Konfig_Daten/PID_Regler_P"
+		},
+		{
+			"message": "Cannot assign a value to an object!",
+			"responseType": 1,
+			"requestIndex": 3,
+			"code": -1,
+			"path": "/Stall_Konfig_Daten[3]"
+		}
+	]
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+Achtung: HTTP-Statuscodes != 200 werden jetzt nur mehr zurückgegeben, wenn 
+der Request fehlerhaft ist. Bei einem ungültigen Pfad gibt der Server 200 
+zurück und liefert das entsprechende Status-Objekt!

+ 70 - 0
rest/rest.pro

@@ -0,0 +1,70 @@
+TEMPLATE = lib
+CONFIG   += c++11
+CONFIG   -= qt
+CONFIG   += staticlib
+
+QMAKE_CXXFLAGS_DEBUG += -Wno-unused-parameter -Wno-unused-but-set-variable
+QMAKE_CXXFLAGS += -Wstrict-aliasing=0 -D_REST -D_LIBBUILD
+QMAKE_CFLAGS += -D_REST -D_LIBBUILD
+# QMAKE_LIBDIR += $$OUT_PWD/../common $$[QT_SYSROOT]/usr/lib/gfa
+# QMAKE_LIBS += -lm -lstdc++ -lgfaipc -lulfius -ljansson -pthread -lpthread -lorcania -lcurl -lssl -lcrypto -l:libcommon.a
+QMAKE_RPATHDIR += /usr/lib/gfa
+
+_LIB_PATH = $$[QT_SYSROOT]/usr/lib/gfa/svc/
+_INC_PATH = $$[QT_SYSROOT]/usr/include/gfa/svc/rest/
+
+CONFIG(debug, debug|release) {
+    QMAKE_CXXFLAGS -= -Os
+    QMAKE_CFLAGS -= -Os
+    QMAKE_CXXFLAGS += -D_DEBUG
+    QMAKE_CFLAGS += -D_DEBUG
+	TARGET = restd
+	QMAKE_CLEAN += librestd.a
+}
+
+CONFIG(release, debug|release) {
+    QMAKE_CXXFLAGS += -Wno-unused-parameter -Wno-unused-but-set-variable
+    QMAKE_CFLAGS += -Wno-unused-parameter -Wno-unused-but-set-variable
+	TARGET = rest
+	QMAKE_CLEAN += librest.a
+}
+
+linux-buildroot-g++ {
+    QMAKE_CXXFLAGS += -D_TARGET_BUILD
+    QMAKE_CFLAGS += -D_TARGET_BUILD
+    QMAKE_CXXFLAGS += -DSITARA_BUILD
+    QMAKE_CFLAGS += -DSITARA_BUILD
+}
+
+INCLUDEPATH += ../ ../common $$(GEBGFADEV)
+
+SOURCES += restvartbl.cpp \
+    restvar.cpp \
+    reststrvar.cpp \
+    restbitvar.cpp \
+    callback.cpp \
+    helpers.cpp
+
+HEADERS += main.h \
+    restvar.h \
+    helpers.h \
+    defines.h \
+    callback.h
+
+includes.path = $$_INC_PATH
+includes.extra += -$(INSTALL_FILE) $$PWD/main.h $(INSTALL_ROOT)$$includes.path
+includes.extra += $$escape_expand(\\n\\t)-$(INSTALL_FILE) $$PWD/restvar.h $(INSTALL_ROOT)$$includes.path
+includes.extra += $$escape_expand(\\n\\t)-$(INSTALL_FILE) $$PWD/helpers.h $(INSTALL_ROOT)$$includes.path
+includes.extra += $$escape_expand(\\n\\t)-$(INSTALL_FILE) $$PWD/defines.h $(INSTALL_ROOT)$$includes.path
+includes.extra += $$escape_expand(\\n\\t)-$(INSTALL_FILE) $$PWD/callback.h $(INSTALL_ROOT)$$includes.path
+includes.uninstall += -$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/main.h
+includes.uninstall += $$escape_expand(\\n\\t)-$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/restvar.h
+includes.uninstall += $$escape_expand(\\n\\t)-$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/helpers.h
+includes.uninstall += $$escape_expand(\\n\\t)-$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/defines.h
+includes.uninstall += $$escape_expand(\\n\\t)-$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/callback.h
+INSTALLS += includes
+
+library.path = $$_LIB_PATH
+library.extra += -$(INSTALL_FILE) $(TARGET) $(INSTALL_ROOT)$$library.path/$(TARGET)
+library.uninstall += -$(DEL_FILE) $(INSTALL_ROOT)$$library.path/$(TARGET)
+INSTALLS += library

+ 101 - 0
rest/restbitvar.cpp

@@ -0,0 +1,101 @@
+#include "restvar.h"
+
+#define GET_BOOL_VAL(p, m)				(!!(*p & m))
+#define SET_BIT(p, m)					(*p |= m)
+#define CLR_BIT(p, m)					(*p &= ~m)
+#define STORE_BIT(p, m, b)				(b) ? SET_BIT(p, m) : CLR_BIT(p, m)
+
+/////////////////////////////////////////////////////////////////////////////
+
+CRestBitVariable::CRestBitVariable(void *pData, size_t nOffset, int nBitNr, HSHM hShm, const char *pszName, CRest *pParent)
+                            : m_name(pszName), m_pszPath(NULL), m_hShm(hShm), m_pParent(pParent), m_nCbVarpath(0)
+{
+	if(!pData || !hShm || (nBitNr > 7))
+	{
+		ASSERT(false);
+		return;
+	}
+
+	m_mask = (1 << nBitNr);
+    m_pShmByte = (uint8_t*)pData + nOffset;
+}
+
+CRestBitVariable::~CRestBitVariable(void)
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CRestBitVariable::CreateMembersTable(CRestVarTable &vt)
+{
+	vt.AddVar(static_cast<CRest*>(this));
+}
+
+void CRestBitVariable::InitPath(CRest *pParent, const char *pszMemberName, int nIndex)
+{
+	CRest::CreatePath(pParent, pszMemberName, nIndex, m_path);
+	m_pszPath = m_path.c_str();
+	m_nCbVarpath = m_path.length();
+}
+
+void CRestBitVariable::GetValue(int nReqIndex, json_t *pjtMap)
+{
+	json_array_append_new(pjtMap, GetValue(nReqIndex));
+}
+
+json_t* CRestBitVariable::GetValue(int nReqIndex)
+{
+	json_t *pjtVal = NULL;
+
+	Lock();
+	bool bVal = GET_BOOL_VAL(m_pShmByte, m_mask);
+	Unlock();
+
+	if((pjtVal = json_boolean(bVal)))
+		return CreateValueObject(nReqIndex, m_pszPath, -1, json_typeof(pjtVal), m_name.c_str(), pjtVal);
+	else
+		return CreateStatusObject(nReqIndex, -1, "Unexpected Error!", m_pszPath);
+}
+
+json_t* CRestBitVariable::SetValue(int nReqIndex, json_t *pjtVal)
+{
+	if(!pjtVal)
+		return CreateStatusObject(nReqIndex, -1, "Unexpected error!", GetPath());
+
+	json_int_t jInt;
+	int nCode = 0;
+	const char *pszMsg = NULL;
+	int nType = json_typeof(pjtVal);
+	
+	switch(nType)
+	{
+	case JSON_INTEGER:
+		jInt = json_integer_value(pjtVal);
+		STORE_BIT(m_pShmByte, m_mask, !!jInt);
+		break;
+	case JSON_TRUE:
+		SET_BIT(m_pShmByte, m_mask);
+		break;
+	case JSON_FALSE:
+		CLR_BIT(m_pShmByte, m_mask);
+		break;
+	default:
+		nCode = 4;
+		pszMsg = "Cannot assign incompatible JSON type!";
+		break;
+	}
+
+	return CreateStatusObject(nReqIndex, nCode, pszMsg, GetPath());
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CRestBitVariable::Lock(void)
+{
+	::GfaIpcLockSHM(m_hShm);
+}
+
+void CRestBitVariable::Unlock(void)
+{
+	::GfaIpcUnlockSHM(m_hShm);
+}

+ 265 - 0
rest/reststrvar.cpp

@@ -0,0 +1,265 @@
+#include "restvar.h"
+#include "conv.h"
+
+#define _IS_VALID_VT(vt)		((vt > CRestStringVariable::VT_Invalid) && (vt < CRestStringVariable::VT_Last))
+
+/////////////////////////////////////////////////////////////////////////////
+
+CRestStringVariable::CRestStringVariable(void *pData, size_t nCChData, VT vt, const std::type_info &rti, HSHM hShm, const char *pszName, int nIndex, CRest *pParent)
+										: m_name(pszName), m_pszPath(NULL), m_nIndex(nIndex), m_vt(VT_Invalid), m_data({NULL}), m_nCbBuffer(0), m_pszUTF8(NULL), m_nCbUTF8(0),
+										m_pParent(pParent), m_nCbVarpath(0)
+{
+	if(!pData || !hShm || !nCChData || !_IS_VALID_VT(vt))
+	{
+		ASSERT(false);
+		return;
+	}
+
+	m_vt = vt;
+    m_data.pVoid = pData;
+    m_hShm = hShm;
+
+    if( (rti == typeid(char)) ||
+        (rti == typeid(signed char)) ||
+        (rti == typeid(unsigned char)))
+    {
+    	switch(vt)
+    	{
+   		case VT_Latin1:
+   		case VT_UTF_8:
+			m_nCbBuffer = nCChData;
+			zeroTerm(m_data, nCChData - 1);
+   			break;
+   		default:
+			ASSERT(false);
+   			return;
+    	}
+    }
+    else if(rti == typeid(char16_t))
+	{
+    	switch(vt)
+    	{
+   		case VT_UTF_16:
+			m_nCbBuffer = nCChData * sizeof(char16_t);
+			zeroTerm(m_data, nCChData - 1);
+   			break;
+   		default:
+			ASSERT(false);
+   			return;
+    	}
+	}
+    else if(rti == typeid(char32_t))
+	{
+    	switch(vt)
+    	{
+   		case VT_UTF_32:
+			m_nCbBuffer = nCChData * sizeof(char32_t);
+			zeroTerm(m_data, nCChData - 1);
+   			break;
+   		default:
+			ASSERT(false);
+   			return;
+    	}
+	}
+    else if(rti == typeid(wchar_t))
+	{
+    	switch(vt)
+    	{
+		case VT_Unicode:
+			m_nCbBuffer = nCChData * sizeof(wchar_t);
+			zeroTerm(m_data, nCChData - 1);
+   			break;
+   		default:
+			ASSERT(false);
+   			return;
+    	}
+	}
+    else
+	{
+		ASSERT(false);
+	}
+
+	m_nCbUTF8 = m_nCbBuffer * 4;
+	m_pszUTF8 = new char[m_nCbUTF8];
+	memset(m_pszUTF8, 0, m_nCbUTF8);
+}
+
+CRestStringVariable::~CRestStringVariable(void)
+{
+	if(m_pszUTF8)
+		delete [] m_pszUTF8;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CRestStringVariable::CreateMembersTable(CRestVarTable &vt)
+{
+	vt.AddVar(static_cast<CRest*>(this));
+}
+
+void CRestStringVariable::InitPath(CRest *pParent, const char *pszMemberName, int nIndex)
+{
+	CRest::CreatePath(pParent, pszMemberName, nIndex, m_path);
+	m_pszPath = m_path.c_str();
+	m_nCbVarpath = m_path.length();
+}
+
+void CRestStringVariable::GetValue(int nReqIndex, json_t* pjtMap)
+{
+	json_array_append_new(pjtMap, GetValue(nReqIndex));
+}
+
+json_t* CRestStringVariable::GetValue(int nReqIndex)
+{
+	json_t *pjtVal		= NULL;
+	size_t nCChBuf		= 0;
+	size_t nCChString	= 0;
+	memset(m_pszUTF8, 0, m_nCbUTF8);
+
+	Lock();
+
+	switch(m_vt)
+	{
+	case VT_Latin1:
+		nCChBuf = m_nCbBuffer - 1;
+		zeroTerm(m_data, nCChBuf);
+		nCChString = strlen(m_data.pszMbs);
+		::Latin1ToUtf8(m_data.pszMbs, nCChString, m_pszUTF8, m_nCbUTF8);
+		pjtVal = json_string(m_pszUTF8);
+		break;
+	case VT_UTF_8:
+		nCChBuf = m_nCbBuffer - 1;
+		zeroTerm(m_data, nCChBuf);
+		nCChString = strlen(m_data.pszMbs);
+		memcpy(m_pszUTF8, m_data.pszMbs, nCChString);
+		pjtVal = json_string(m_pszUTF8);
+		break;
+	case VT_UTF_16:
+		nCChBuf = m_nCbBuffer / sizeof(char16_t) - 1;
+		zeroTerm(m_data, nCChBuf);
+		nCChString = wcs16len(m_data.pszWc16);
+		::Utf16ToUtf8(m_data.pszWc16, nCChString, m_pszUTF8, m_nCbUTF8);
+		pjtVal = json_string(m_pszUTF8);
+		break;
+	case VT_UTF_32:
+		nCChBuf = m_nCbBuffer / sizeof(char32_t) - 1;
+		zeroTerm(m_data, nCChBuf);
+		nCChString = wcs32len(m_data.pszWc32);
+		::Utf32ToUtf8(m_data.pszWc32, nCChString, m_pszUTF8, m_nCbUTF8);
+		pjtVal = json_string(m_pszUTF8);
+		break;
+	case VT_Unicode:
+		nCChBuf = m_nCbBuffer / sizeof(wchar_t) - 1;
+		zeroTerm(m_data, nCChBuf);
+		nCChString = wcslen(m_data.pszWcs);
+		::WcsToUtf8(m_data.pszWcs, nCChString, m_pszUTF8, m_nCbUTF8);
+		pjtVal = json_string(m_pszUTF8);
+		break;
+	default:
+		ASSERT(false);
+		break;
+	}
+
+    Unlock();
+
+	if(pjtVal)
+		return CreateValueObject(nReqIndex, m_pszPath, m_nIndex, json_typeof(pjtVal), m_name.c_str(), pjtVal);
+	else
+		return CreateStatusObject(nReqIndex, -1, "Unexpected Error!", m_pszPath);
+}
+
+json_t* CRestStringVariable::SetValue(int nReqIndex, json_t *pjtVal)
+{
+	if(!pjtVal)
+		return CreateStatusObject(nReqIndex, -1, "Unexpected error!", GetPath());
+
+	int nCode = 0;
+	const char *pszMsg = NULL;
+	size_t nCChBuf = 0, nCChString = 0;
+	int nType = json_typeof(pjtVal);
+	
+	if(nType == JSON_STRING)
+	{
+		const char *pszUtf8 = json_string_value(pjtVal);
+        size_t nLen = strlen(pszUtf8);
+
+		Lock();
+
+		switch(m_vt)
+		{
+		case VT_Latin1:
+			nCChBuf = m_nCbBuffer;
+			nCChString = ::Utf8ToLatin1(pszUtf8, nLen, m_data.pszMbs, nCChBuf);
+			zeroTerm(m_data, nCChString);
+			break;
+		case VT_UTF_8:
+			nCChBuf = m_nCbBuffer - 1;
+			nCChString = __min(nLen, nCChBuf);
+			memcpy(m_data.pszMbs, pszUtf8, nCChString);
+			zeroTerm(m_data, nCChString);
+			break;
+		case VT_UTF_16:
+			nCChBuf = m_nCbBuffer / sizeof(char16_t);
+			nCChString = ::Utf8ToUtf16(pszUtf8, nLen, m_data.pszWc16, nCChBuf) / sizeof(char16_t);
+			zeroTerm(m_data, nCChString);
+			break;
+		case VT_UTF_32:
+			nCChBuf = m_nCbBuffer / sizeof(char32_t);
+			nCChString = ::Utf8ToUtf32(pszUtf8, nLen, m_data.pszWc32, nCChBuf) / sizeof(char32_t);
+			zeroTerm(m_data, nCChString);
+			break;
+		case VT_Unicode:
+			nCChBuf = m_nCbBuffer / sizeof(wchar_t);
+			nCChString = ::Utf8ToWcs(pszUtf8, nLen, m_data.pszWcs, nCChBuf) / sizeof(wchar_t);
+			zeroTerm(m_data, nCChString);
+			break;
+		default:
+			ASSERT(false);
+			break;
+		}
+
+		Unlock();
+	}
+	else
+	{
+		pszMsg = "Cannot assign incompatible JSON type to string variable!";
+		nCode = 1;
+	}
+
+	return CreateStatusObject(nReqIndex, nCode, pszMsg, GetPath());
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CRestStringVariable::Lock(void)
+{
+	::GfaIpcLockSHM(m_hShm);
+}
+
+void CRestStringVariable::Unlock(void)
+{
+	::GfaIpcUnlockSHM(m_hShm);
+}
+
+void CRestStringVariable::zeroTerm(volatile V_Ptr &rp, size_t at)
+{
+	switch(m_vt)
+	{
+	case VT_Latin1:
+	case VT_UTF_8:
+		rp.pszMbs[at] = '\0';
+		break;
+	case VT_UTF_16:
+		rp.pszWc16[at] = (char16_t)0;
+		break;
+	case VT_UTF_32:
+		rp.pszWc32[at] = (char32_t)0;
+		break;
+	case VT_Unicode:
+		rp.pszWcs[at] = L'\0';
+		break;
+	default:
+		ASSERT(false);
+		break;
+	}
+}

+ 306 - 0
rest/restvar.cpp

@@ -0,0 +1,306 @@
+#include "restvar.h"
+
+/////////////////////////////////////////////////////////////////////////////
+
+CRestVariable::CRestVariable(void *pData, const std::type_info &rti, HSHM hShm, const char *pszName, int nIndex, CRest *pParent)
+							: m_name(pszName), m_pszPath(NULL), m_nIndex(nIndex), m_vt(VT_Invalid), m_data({NULL}), m_pParent(pParent), m_nCbVarpath(0)
+{
+	if(!pData || !hShm)
+	{
+		ASSERT(false);
+		return;
+	}
+
+    m_data.pVoid = pData;
+    m_hShm = hShm;
+
+    if(rti == typeid(bool))
+    {
+    	m_vt = VT_bool;
+    }
+    else if(rti == typeid(char))
+    {
+#ifdef __CHAR_UNSIGNED__
+    	m_vt = VT_UI1;
+#else
+        m_vt = VT_I1;
+#endif
+    }
+    else if(rti == typeid(signed char))
+    {
+        m_vt = VT_I1;
+    }
+    else if(rti == typeid(unsigned char))
+    {
+    	m_vt = VT_UI1;
+    }
+    else if(rti == typeid(short))
+    {
+    	m_vt = VT_I2;
+    }
+    else if(rti == typeid(unsigned short))
+    {
+    	m_vt = VT_UI2;
+    }
+    else if(rti == typeid(int))
+    {
+    	m_vt = VT_I4;
+    }
+    else if(rti == typeid(unsigned int))
+    {
+    	m_vt = VT_UI4;
+    }
+    else if(rti == typeid(long))
+    {
+    	if(sizeof(long) == sizeof(long long))
+    	{
+	    	m_vt = VT_I8;
+	    }
+    	else
+    	{
+	    	m_vt = VT_I4;
+	    }
+    }
+    else if(rti == typeid(unsigned long))
+    {
+    	if(sizeof(unsigned long) == sizeof(unsigned long long))
+    	{
+	    	m_vt = VT_UI8;
+	    }
+    	else
+    	{
+	    	m_vt = VT_UI4;
+	    }
+    }
+    else if(rti == typeid(long long))
+    {
+    	m_vt = VT_I8;
+    }
+    else if(rti == typeid(unsigned long long))
+    {
+    	m_vt = VT_UI8;
+    }
+    else if(rti == typeid(float))
+    {
+    	m_vt = VT_float;
+    }
+    else if(rti == typeid(double))
+    {
+    	m_vt = VT_double;
+    }
+    else
+	{
+		ASSERT(false);
+	}
+}
+
+CRestVariable::~CRestVariable(void)
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CRestVariable::CreateMembersTable(CRestVarTable &vt)
+{
+	vt.AddVar(static_cast<CRest*>(this));
+}
+
+void CRestVariable::InitPath(CRest *pParent, const char *pszMemberName, int nIndex)
+{
+	CRest::CreatePath(pParent, pszMemberName, nIndex, m_path);
+	m_pszPath = m_path.c_str();
+	m_nCbVarpath = m_path.length();
+}
+
+void CRestVariable::GetValue(int nReqIndex, json_t *pjtMap)
+{
+	json_array_append_new(pjtMap, GetValue(nReqIndex));
+}
+
+json_t* CRestVariable::GetValue(int nReqIndex)
+{
+	json_t *pjtVal = NULL;
+
+	Lock();
+	
+	switch(m_vt)
+	{
+	case VT_bool:
+		pjtVal = json_boolean(*m_data.pBool);
+		break;
+	case VT_I1:
+		pjtVal = json_integer((json_int_t)*m_data.pI1);
+		break;
+	case VT_UI1:
+		pjtVal = json_integer((json_int_t)*m_data.pUI1);
+		break;
+	case VT_I2:
+		pjtVal = json_integer((json_int_t)*m_data.pI2);
+		break;
+	case VT_UI2:
+		pjtVal = json_integer((json_int_t)*m_data.pUI2);
+		break;
+	case VT_I4:
+		pjtVal = json_integer((json_int_t)*m_data.pI4);
+		break;
+	case VT_UI4:
+		pjtVal = json_integer((json_int_t)*m_data.pUI4);
+		break;
+	case VT_I8:
+		pjtVal = json_integer((json_int_t)*m_data.pI8);
+		break;
+	case VT_UI8:
+		pjtVal = json_integer((json_int_t)*m_data.pUI8);
+		break;
+	case VT_float:
+		pjtVal = json_real((double)*m_data.pFloat);
+		break;
+	case VT_double:
+		pjtVal = json_real(*m_data.pDouble);
+		break;
+    default:
+		ASSERT(false);
+		break;
+	}
+
+	Unlock();
+
+	if(pjtVal)
+		return CreateValueObject(nReqIndex, m_pszPath, m_nIndex, json_typeof(pjtVal), m_name.c_str(), pjtVal);
+	else
+		return CreateStatusObject(nReqIndex, -1, "Unexpected Error!", m_pszPath);
+}
+
+json_t* CRestVariable::SetValue(int nReqIndex, json_t *pjtVal)
+{
+	if(!pjtVal)
+		return CreateStatusObject(nReqIndex, -1, "Unexpected error!", GetPath());
+
+	json_int_t jInt;
+	double jReal;
+	int nCode = 0;
+	const char *pszMsg = NULL;
+	int nType = json_typeof(pjtVal);
+	
+	switch(nType)
+	{
+	case JSON_INTEGER:
+		jInt = json_integer_value(pjtVal);
+		switch(m_vt)
+		{
+		case VT_bool:
+			Lock();
+			*m_data.pBool = !!jInt;
+			Unlock();
+			break;
+		case VT_I1:
+			Lock();
+			*m_data.pI1 = (signed char)jInt;
+			Unlock();
+			break;
+		case VT_UI1:
+			Lock();
+			*m_data.pUI1 = (unsigned char)jInt;
+			Unlock();
+			break;
+		case VT_I2:
+			Lock();
+			*m_data.pI2 = (signed short)jInt;
+			Unlock();
+			break;
+		case VT_UI2:
+			Lock();
+			*m_data.pUI2 = (unsigned short)jInt;
+			Unlock();
+			break;
+		case VT_I4:
+			Lock();
+			*m_data.pI4 = (signed int)jInt;
+			Unlock();
+			break;
+		case VT_UI4:
+			Lock();
+			*m_data.pUI4 = (unsigned int)jInt;
+			Unlock();
+			break;
+		case VT_I8:
+			Lock();
+			*m_data.pI8 = (signed long long)jInt;
+			Unlock();
+			break;
+		case VT_UI8:
+			Lock();
+			*m_data.pUI8 = (unsigned long long)jInt;
+			Unlock();
+			break;
+		case VT_float:
+			Lock();
+			*m_data.pFloat = (float)jInt;
+			Unlock();
+			break;
+		case VT_double:
+			Lock();
+			*m_data.pDouble = (double)jInt;
+			Unlock();
+			break;
+		default:
+			nCode = 1;
+			pszMsg = "Cannot assign JSON integer value! Variable is not an integer or real type!";
+			break;
+		}
+		break;
+	case JSON_REAL:
+		jReal = json_real_value(pjtVal);
+		switch(m_vt)
+		{
+		case VT_float:
+			Lock();
+			*m_data.pFloat = (float)jReal;
+			Unlock();
+			break;
+		case VT_double:
+			Lock();
+			*m_data.pDouble = jReal;
+			Unlock();
+			break;
+		default:
+			nCode = 2;
+			pszMsg = "Cannot assign JSON real value! Variable is not of type real!";
+			break;
+		}
+		break;
+	case JSON_TRUE:
+	case JSON_FALSE:
+		switch(m_vt)
+		{
+		case VT_bool:
+			Lock();
+			*m_data.pBool = (nType == JSON_TRUE);
+			Unlock();
+			break;
+		default:
+			nCode = 3;
+			pszMsg = "Cannot assign JSON bool value! Variable is not of type bool!";
+			break;
+		}
+		break;
+	default:
+		nCode = 4;
+		pszMsg = "Cannot assign incompatible JSON type!";
+		break;
+	}
+
+	return CreateStatusObject(nReqIndex, nCode, pszMsg, GetPath());
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void CRestVariable::Lock(void)
+{
+	::GfaIpcLockSHM(m_hShm);
+}
+
+void CRestVariable::Unlock(void)
+{
+	::GfaIpcUnlockSHM(m_hShm);
+}

+ 524 - 0
rest/restvar.h

@@ -0,0 +1,524 @@
+// restvar.h :
+//
+
+#if !defined(AGD_RESTVAR_H__77EB9782_2B70_4713_A649_F4695F617197__INCLUDED_)
+#define AGD_RESTVAR_H__77EB9782_2B70_4713_A649_F4695F617197__INCLUDED_
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <string>
+#include <pthread.h>
+#include <typeinfo>
+#include <vector>
+#include <map>
+#include <gfa/gfaipc.h>
+#ifndef _LIBBUILD
+#include <gfa/svc/rest/defines.h>
+#include <gfa/svc/common/debug.h>
+#else	//	_LIBBUILD
+#include "defines.h"
+#include "common/debug.h"
+#endif	//	_LIBBUILD
+
+extern "C"
+{
+#include <ulfius.h>
+}
+
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+// class CJson_t
+
+class CJson_t
+{
+public:
+	CJson_t(json_t *pjt, bool bFree = true) : m_pjt(pjt), m_bFree(bFree)
+	{
+	}
+
+	~CJson_t(void)
+	{
+		if(m_pjt && m_bFree)
+			json_decref(m_pjt);
+	}
+
+	CJson_t& operator = (json_t *pjt)
+	{
+		m_pjt = pjt;
+		return *this;
+	}
+
+	operator json_t*(void) {
+		return m_pjt;}
+
+	operator const json_t*(void) const {
+		return m_pjt;}
+
+	bool IsValid(void) const {
+		return !!m_pjt;}
+
+	bool IsArray(void) {
+		return json_is_array(m_pjt);}
+
+	bool IsObject(void) {
+		return json_is_object(m_pjt);}
+
+	int Type(void) const {
+		return IsValid() ? json_typeof(m_pjt) : -1;}
+
+private:
+	json_t *m_pjt;
+	bool m_bFree;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CRestVarTable;
+
+class CRest
+{
+public:
+	virtual void CreateMembersTable(CRestVarTable &vt)								= 0;
+	virtual void InitPath(CRest *pParent, const char *pszMemberName, int nIndex)	= 0;
+	virtual const char* GetPath(void) const											= 0;
+	virtual void GetValue(int nReqIndex, json_t* pjtMap)							= 0;
+	virtual json_t* GetValue(int nReqIndex)											= 0;
+	virtual json_t* SetValue(int nReqIndex, json_t* pjtVal)
+	{
+		UNUSED(pjtVal);
+		return CreateStatusObject(nReqIndex, -1, "Cannot assign a value to an object!", GetPath());
+	}
+
+	static json_t* CreateStatusObject(int nReqIndex, int nStatusCode, const char *pszStatusMsg = NULL, const char *pszPath = NULL)
+	{
+		if(!pszStatusMsg)
+			pszStatusMsg = "";
+		CJson_t jtStatus(json_object(), false);
+		CJson_t jtResponseType(json_integer(1));
+		CJson_t jtReqIndex(json_integer(nReqIndex));
+		CJson_t jtCode(json_integer(nStatusCode));
+		CJson_t jtMsg(json_string(pszStatusMsg));
+		json_object_set(jtStatus, "responseType", jtResponseType);
+		json_object_set(jtStatus, "requestIndex", jtReqIndex);
+		json_object_set(jtStatus, "code", jtCode);
+		json_object_set(jtStatus, "message", jtMsg);
+		if(pszPath && *pszPath)
+		{
+			CJson_t jtPath(json_string(pszPath));
+			json_object_set(jtStatus, "path", jtPath);
+		}
+		return jtStatus;
+	}
+
+	static json_t* CreateValueObject(int nReqIndex, const char *pszPath, int nIndex, int nValType, const char *pszValName, json_t *pjtVal)
+	{
+		CJson_t jtValue(json_object(), false);
+		CJson_t jtResponseType(json_integer(0));
+		CJson_t jtReqIndex(json_integer(nReqIndex));
+		CJson_t jtPath(json_string(pszPath));
+		CJson_t jtIndex(json_integer(nIndex));
+		CJson_t jtType(json_integer(nValType));
+		CJson_t jtName(json_string(pszValName));
+		json_object_set(jtValue, "responseType", jtResponseType);
+		json_object_set(jtValue, "requestIndex", jtReqIndex);
+		json_object_set(jtValue, "path", jtPath);
+		json_object_set(jtValue, "index", jtIndex);
+		json_object_set(jtValue, "valType", jtType);
+		json_object_set(jtValue, "valName", jtName);
+		json_object_set(jtValue, "value", pjtVal);
+		return jtValue;
+	}
+
+	static void CreatePath(CRest *pParent, const char *pszMemberName, int nIndex, std::string &rPath)
+	{
+		char szIndex[32] = {0};
+
+		if(!pszMemberName || !*pszMemberName)
+			pszMemberName = "/";
+
+		if(nIndex >= 0)
+		{
+			sprintf(szIndex, "[%d]", nIndex);
+		}
+
+		if(pParent)
+		{
+			rPath = pParent->GetPath();
+			auto len = rPath.size();
+
+			if(len > 0)
+			{
+				auto rend = rPath.rbegin();
+				if(*rend != '/')
+					rPath += "/";
+			}
+
+			rPath += pszMemberName;
+			rPath += szIndex;
+		}
+		else
+		{
+			rPath = pszMemberName;
+			rPath += szIndex;
+		}
+
+//		printf("%s\n", rPath.c_str());
+	}
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CRestVarTable
+{
+public:
+	typedef bool (*_PFNCMP)(const char*, const char*);
+
+public:
+	CRestVarTable(void);
+	virtual ~CRestVarTable(void);
+
+	void AddVar(CRest *pv);
+	CRest* Find(const char *key) const;
+	size_t size(void)
+	{
+		return m_map.size();
+	}
+
+private:
+	std::map<const char*, CRest*, _PFNCMP> m_map;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CRestVariable : public CRest
+{
+public:
+	enum VT
+	{
+		VT_Invalid,
+		VT_bool,
+		VT_I1,
+		VT_UI1,
+		VT_I2,
+		VT_UI2,
+		VT_I4,
+		VT_UI4,
+		VT_I8,
+		VT_UI8,
+		VT_float,
+		VT_double,
+		VT_string
+	};
+
+	typedef union
+	{
+		void *pVoid;
+		bool *pBool;
+		signed char *pI1;
+		unsigned char *pUI1;
+		signed short *pI2;
+		unsigned short *pUI2;
+		signed int *pI4;
+		unsigned int *pUI4;
+		signed long long *pI8;
+		unsigned long long *pUI8;
+		float *pFloat;
+		double *pDouble;
+	}V_Ptr;
+
+	typedef union
+	{
+		bool boolVal;
+		signed char I1Val;
+		unsigned char UI1Val;
+		signed short I2Val;
+		unsigned short UI2Val;
+		signed int I4Val;
+		unsigned int UI4Val;
+		signed long long I8Val;
+		unsigned long long UI8Val;
+		float FloatVal;
+		double DoubleVal;
+	}V_Val;
+
+public:
+	CRestVariable(void *pData, const std::type_info &rti, HSHM hShm, const char *pszName, int nIndex, CRest *pParent);
+	virtual ~CRestVariable(void);
+
+	virtual void CreateMembersTable(CRestVarTable &vt);
+	virtual void InitPath(CRest*pParent, const char *pszMemberName, int nIndex = -1);
+	virtual const char* GetPath(void) const {
+		return m_pszPath;}
+	virtual void GetValue(int nReqIndex, json_t* pjtMap);
+	virtual json_t* GetValue(int nReqIndex);
+	virtual json_t* SetValue(int nReqIndex, json_t* pjtVal);
+
+private:
+    void Lock(void);
+    void Unlock(void);
+
+private:
+    std::string m_name;
+    std::string m_path;
+    const char *m_pszPath;
+    int m_nIndex;
+    enum VT m_vt;
+    volatile V_Ptr m_data;
+    HSHM m_hShm;
+    CRest *m_pParent;
+    size_t m_nCbVarpath;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CRestStringVariable : public CRest
+{
+public:
+	typedef enum
+	{
+		VT_Invalid,	// 0
+		VT_Latin1,	// 1
+		VT_UTF_8,	// 2
+		VT_UTF_16,	// 3
+		VT_UTF_32,	// 4
+		VT_Unicode,	// 5
+		VT_Last
+	}VT;
+
+	typedef union
+	{
+		void *pVoid;
+		char *pszMbs;
+		char16_t *pszWc16;
+		char32_t *pszWc32;
+		wchar_t *pszWcs;
+	}V_Ptr;
+
+public:
+	CRestStringVariable(void *pData, size_t nCChData, VT vt, const std::type_info &rti, HSHM hShm, const char *pszName, int nIndex, CRest *pParent);
+	virtual ~CRestStringVariable(void);
+
+	virtual void CreateMembersTable(CRestVarTable &vt);
+	virtual void InitPath(CRest*pParent, const char *pszMemberName, int nIndex = -1);
+	virtual const char* GetPath(void) const {
+		return m_pszPath;}
+	virtual void GetValue(int nReqIndex, json_t* pjtMap);
+	virtual json_t* GetValue(int nReqIndex);
+	virtual json_t* SetValue(int nReqIndex, json_t* pjtVal);
+
+private:
+    void Lock(void);
+    void Unlock(void);
+    void zeroTerm(volatile V_Ptr &rp, size_t at);
+
+private:
+    std::string m_name;
+    std::string m_path;
+    const char *m_pszPath;
+    int m_nIndex;
+    VT m_vt;
+    volatile V_Ptr m_data;
+    size_t m_nCbBuffer;
+    char *m_pszUTF8;
+    size_t m_nCbUTF8;
+    HSHM m_hShm;
+    CRest *m_pParent;
+    size_t m_nCbVarpath;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CRestBitVariable : public CRest
+{
+public:
+	enum VT
+	{
+		VT_Invalid,
+		VT_bool,
+		VT_I1,
+		VT_UI1,
+		VT_I2,
+		VT_UI2,
+		VT_I4,
+		VT_UI4,
+		VT_I8,
+		VT_UI8,
+		VT_float,
+		VT_double,
+		VT_string
+	};
+
+	typedef union
+	{
+		void *pVoid;
+		bool *pBool;
+		signed char *pI1;
+		unsigned char *pUI1;
+		signed short *pI2;
+		unsigned short *pUI2;
+		signed int *pI4;
+		unsigned int *pUI4;
+		signed long long *pI8;
+		unsigned long long *pUI8;
+		float *pFloat;
+		double *pDouble;
+	}V_Ptr;
+
+	typedef union
+	{
+		bool boolVal;
+		signed char I1Val;
+		unsigned char UI1Val;
+		signed short I2Val;
+		unsigned short UI2Val;
+		signed int I4Val;
+		unsigned int UI4Val;
+		signed long long I8Val;
+		unsigned long long UI8Val;
+		float FloatVal;
+		double DoubleVal;
+	}V_Val;
+
+public:
+	CRestBitVariable(void *pData, size_t nOffset, int nBitNr, HSHM hShm, const char *pszName, CRest *pParent);
+	virtual ~CRestBitVariable(void);
+
+	virtual void CreateMembersTable(CRestVarTable &vt);
+	virtual void InitPath(CRest*pParent, const char *pszMemberName, int nIndex = -1);
+	virtual const char* GetPath(void) const {
+		return m_pszPath;}
+	virtual void GetValue(int nReqIndex, json_t* pjtMap);
+	virtual json_t* GetValue(int nReqIndex);
+	virtual json_t* SetValue(int nReqIndex, json_t* pjtVal);
+
+private:
+    void Lock(void);
+    void Unlock(void);
+
+private:
+    std::string m_name;
+    std::string m_path;
+	uint8_t *m_pShmByte;
+	uint8_t m_mask;
+    const char *m_pszPath;
+    HSHM m_hShm;
+    CRest *m_pParent;
+    size_t m_nCbVarpath;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+template <typename T, typename V>
+class CRestArray :	public CRest,
+					public std::vector<T*>
+{
+public:
+	CRestArray(void *pData, const std::type_info &rti, HSHM hShm, const char *pszName, size_t nElemCount, CRest *pParent) : m_name(pszName)
+	{
+		UNUSED(pParent);
+		for(size_t i = 0; i < nElemCount; i++)
+		{
+			this->push_back(new T(&((V*)pData)[i], rti, hShm, pszName, i, static_cast<CRest*>(this)));
+		}
+	}
+
+	CRestArray(void *pData, size_t nCChData, CRestStringVariable::VT vt, const std::type_info &rti, HSHM hShm, const char *pszName, size_t nElemCount, CRest *pParent) : m_name(pszName)
+	{
+		UNUSED(pParent);
+		for(size_t i = 0; i < nElemCount; i++)
+		{
+			this->push_back(new T(((V*)pData) + nCChData * i, nCChData, vt, rti, hShm, pszName, i, static_cast<CRest*>(this)));
+		}
+	}
+
+	CRestArray(void *pData, HSHM hShm, const char *pszName, size_t nElemCount, CRest *pParent) : m_name(pszName)
+	{
+		UNUSED(pParent);
+		for(size_t i = 0; i < nElemCount; i++)
+		{
+			this->push_back(new T(&((V*)pData)[i], hShm, i, static_cast<CRest*>(this)));
+		}
+	}
+
+	virtual ~CRestArray(void)
+	{
+	    for(int i = 0; i < (int)this->size(); i++)
+	    {
+	        delete this->at(i);
+		}
+	}
+
+
+public:
+	virtual void CreateMembersTable(CRestVarTable &vt)
+	{
+		vt.AddVar(static_cast<CRest*>(this));
+
+	    for(int i = 0; i < (int)this->size(); i++)
+	    {
+	    	this->at(i)->CreateMembersTable(vt);
+	    }
+	}
+
+	virtual void InitPath(CRest *pParent, const char *pszMemberName, int nIndex = -1)
+	{
+		CRest::CreatePath(pParent, pszMemberName, nIndex, m_path);
+		m_pszPath = m_path.c_str();
+		m_nCbVarpath = m_path.length();
+
+	    for(int i = 0; i < (int)this->size(); i++)
+	    {
+	        this->at(i)->InitPath(pParent, pszMemberName, i);
+		}
+	}
+
+	virtual const char* GetPath(void) const {
+		return m_pszPath;}
+
+	virtual void GetValue(int nReqIndex, json_t* pjtMap)
+	{
+	    for(int i = 0; i < (int)this->size(); i++)
+	    {
+	    	this->at(i)->GetValue(nReqIndex, pjtMap);
+	    }
+	}
+
+	virtual json_t* GetValue(int nReqIndex)
+	{
+		CJson_t jtArr(json_array());
+
+	    for(int i = 0; i < (int)this->size(); i++)
+	    {
+	    	json_array_append_new(jtArr, this->at(i)->GetValue(nReqIndex));
+	    }
+
+		CJson_t jtObj(json_object(), false);
+		CJson_t jtPath(json_string(m_pszPath));
+		CJson_t jtIndex(json_integer(-1));
+		CJson_t jtType(json_integer(jtArr.Type()));
+		CJson_t jtName(json_string(m_name.c_str()));
+		json_object_set(jtObj, "path", jtPath);
+		json_object_set(jtObj, "index", jtIndex);
+		json_object_set(jtObj, "valType", jtType);
+		json_object_set(jtObj, "valName", jtName);
+		json_object_set(jtObj, "value", jtArr);
+
+		return jtObj;
+	}
+
+	virtual json_t* SetValue(int nReqIndex, json_t* pjtVal)
+	{
+		UNUSED(pjtVal);
+		return CreateStatusObject(nReqIndex, -1, "Cannot assign a value to an array!", GetPath());
+	}
+
+private:
+    std::string m_name;
+    std::string m_path;
+    const char *m_pszPath;
+    size_t m_nCbVarpath;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	!defined(AGD_RESTVAR_H__77EB9782_2B70_4713_A649_F4695F617197__INCLUDED_)

+ 31 - 0
rest/restvartbl.cpp

@@ -0,0 +1,31 @@
+#include "restvar.h"
+#include <algorithm>
+#include "debug.h"
+
+/////////////////////////////////////////////////////////////////////////////
+
+static bool _fncomp(const char *p1, const char *p2)
+{
+	return strcmp(p1, p2) < 0;
+}
+
+CRestVarTable::CRestVarTable(void) : m_map(_fncomp)
+{
+}
+
+CRestVarTable::~CRestVarTable(void)
+{
+}
+
+void CRestVarTable::AddVar(CRest *pVar)
+{
+	m_map[pVar->GetPath()] = pVar;
+}
+
+CRest* CRestVarTable::Find(const char *key) const
+{
+	std::map<const char*, CRest*>::const_iterator it = m_map.find(key);
+	if(it == m_map.end())
+		return NULL;
+	return it->second;
+}

+ 122 - 0
shmqml/shmbitvar.cpp

@@ -0,0 +1,122 @@
+#include "shmvar.h"
+#include <QDebug>
+
+#define GET_BOOL_VAL(p, m)				(!!(*p & m))
+#define SET_BIT(p, m)					(*p |= m)
+#define CLR_BIT(p, m)					(*p &= ~m)
+#define STORE_BIT(p, m, b)				(b) ? SET_BIT(p, m) : CLR_BIT(p, m)
+
+CShmBitVariable::CShmBitVariable(void *pData, size_t nOffset, int nBitNr, HSHM hShm, const char *pszName, QObject *pParent)
+								: QObject(pParent), m_varName(pszName), m_nBitNr(nBitNr)
+{
+	if(!pData || !hShm || (nBitNr > 7))
+	{
+		Q_ASSERT_X(false, "CShmBitVariable::CShmBitVariable", "Invalid parameter!");
+		return;
+	}
+
+	m_mask = (1 << nBitNr);
+    m_pShmByte = (uint8_t*)pData + nOffset;
+    m_cacheVal = GET_BOOL_VAL(m_pShmByte, m_mask);
+    m_hShm = hShm;
+    setObjectName(QStringLiteral("CShmBitVariable"));
+}
+
+CShmBitVariable::~CShmBitVariable(void)
+{
+}
+
+void CShmBitVariable::valRaw(QVariant &v)
+{
+	bool bVal = GET_BOOL_VAL(m_pShmByte, m_mask);
+	v.setValue(bVal);
+}
+
+QVariant CShmBitVariable::val(void)
+{
+	QVariant v;
+	Lock();
+	valRaw(v);
+	Unlock();
+    return v;
+}
+
+void CShmBitVariable::setVal(const QVariant &val)
+{
+	if(val.isValid() && !val.isNull())
+	{
+#if _TRACK_QT_WRITES
+		char szDebugOut[256];
+#endif	//	_TRACK_QT_WRITES
+
+		if(val.canConvert<bool>())
+		{
+			Lock();
+			bool bVal = GET_BOOL_VAL(m_pShmByte, m_mask);
+			m_cacheVal = val.toBool();
+
+			if(bVal != m_cacheVal)
+			{
+				STORE_BIT(m_pShmByte, m_mask, m_cacheVal);
+				emitChanged(false);
+#if _TRACK_QT_WRITES
+				sprintf(szDebugOut, "CShmBitVariable::setVal: Bit %s : %c", m_varName.toStdString().c_str(), m_cacheVal ? '1' : '0');
+    			qDebug() << szDebugOut;
+#endif	//	_TRACK_QT_WRITES
+			}
+			Unlock();
+		}
+	}
+}
+
+unsigned int CShmBitVariable::vt(void) const
+{
+	return VT_bool;
+}
+
+unsigned long long CShmBitVariable::CheckUpdateShm(bool fLock)
+{
+	CHECK_UPDATE_SHM_RETVAL rv = {1, 0};
+
+	if(fLock)
+		Lock();
+		
+	bool bVal = GET_BOOL_VAL(m_pShmByte, m_mask);
+
+	if(m_cacheVal != bVal)
+	{
+	    m_cacheVal = bVal;
+		emitChanged(false);
+		rv.nUpdated = 1;
+	}
+
+	if(fLock)
+		Unlock();
+
+	return rv.nRetval;
+}
+
+void CShmBitVariable::Lock(void)
+{
+	::GfaIpcLockSHM(m_hShm);
+//	qDebug() << "CShmBitVariable::Lock";
+}
+
+void CShmBitVariable::Unlock(void)
+{
+//	qDebug() << "CShmBitVariable::Unlock";
+	::GfaIpcUnlockSHM(m_hShm);
+}
+
+void CShmBitVariable::emitChanged(bool fLock)
+{
+//    qDebug() << "val changed!";
+	if(fLock)
+	    emit valChanged(val());
+	else
+	{
+	    QVariant v;
+	    valRaw(v);
+	    emit valChanged(v);
+	}
+}

+ 62 - 0
shmqml/shmqml.pro

@@ -0,0 +1,62 @@
+TEMPLATE = lib
+QT += qml quick
+CONFIG   += c++11
+# CONFIG   -= qt
+CONFIG   += staticlib
+
+QMAKE_CXXFLAGS_DEBUG += -Wno-unused-parameter -Wno-unused-but-set-variable -Wno-old-style-cast -pthread
+QMAKE_CXXFLAGS += -Wno-old-style-cast -Wstrict-aliasing=0 -pthread
+QMAKE_RPATHDIR += /usr/lib/gfa
+
+QMAKE_CXXFLAGS += -D_DL_SHMQML -D_LIBBUILD -Wno-deprecated-copy
+QMAKE_CFLAGS += -D_DL_SHMQML -D_LIBBUILD -Wno-deprecated-copy
+
+DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
+
+_LIB_PATH = $$[QT_SYSROOT]/usr/lib/gfa/svc/
+_INC_PATH = $$[QT_SYSROOT]/usr/include/gfa/svc/shmqml/
+
+CONFIG(debug, debug|release) {
+	QMAKE_CXXFLAGS -= -Os
+	QMAKE_CFLAGS -= -Os
+    QMAKE_CXXFLAGS += -D_DEBUG
+    QMAKE_CFLAGS += -D_DEBUG
+	TARGET = shmqmld
+	QMAKE_CLEAN += libshmqmld.a
+}
+
+CONFIG(release, debug|release) {
+    QMAKE_CXXFLAGS += -Wno-unused-parameter -Wno-unused-but-set-variable
+    QMAKE_CFLAGS += -Wno-unused-parameter -Wno-unused-but-set-variable
+	TARGET = shmqml
+	QMAKE_CLEAN += libshmqml.a
+}
+
+linux-buildroot-g++ {
+    QMAKE_CXXFLAGS += -D_TARGET_BUILD
+    QMAKE_CFLAGS += -D_TARGET_BUILD
+    QMAKE_CXXFLAGS += -DSITARA_BUILD
+    QMAKE_CFLAGS += -DSITARA_BUILD
+}
+
+INCLUDEPATH += ../  ../common $$(GEBGFADEV)
+
+SOURCES += shmbitvar.cpp \
+		shmstrvar.cpp \
+		shmvar.cpp
+
+
+HEADERS += shmvar.h \
+		shmthread.h
+
+includes.path = $$_INC_PATH
+includes.extra += -$(INSTALL_FILE) $$PWD/shmvar.h $(INSTALL_ROOT)$$includes.path
+includes.extra += $$escape_expand(\\n\\t)-$(INSTALL_FILE) $$PWD/shmthread.h $(INSTALL_ROOT)$$includes.path
+includes.uninstall += -$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/shmvar.h
+includes.uninstall += $$escape_expand(\\n\\t)-$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/shmthread.h
+INSTALLS += includes
+
+library.path = $$_LIB_PATH
+library.extra += -$(INSTALL_FILE) $(TARGET) $(INSTALL_ROOT)$$library.path/$(TARGET)
+library.uninstall += -$(DEL_FILE) $(INSTALL_ROOT)$$library.path/$(TARGET)
+INSTALLS += library

+ 339 - 0
shmqml/shmstrvar.cpp

@@ -0,0 +1,339 @@
+#include "shmvar.h"
+#include "conv.h"
+#include <QDebug>
+
+
+#define _IS_VALID_VT(vt)		((vt > CShmStringVariable::VT_Invalid) && (vt < CShmStringVariable::VT_Last))
+#define __min(x, y) 			((x) < (y) ? (x) : (y))
+#define __max(x, y) 			((x) > (y) ? (x) : (y))
+
+CShmStringVariable::CShmStringVariable(void *pData, size_t nCChData, VT vt, const std::type_info &rti, HSHM hShm, const char *pszName, int nIndex, QObject *pParent)
+										: QObject(pParent), m_vt(VT_Invalid), m_data({NULL}), m_cache({NULL}), m_nCbString(0), m_varName(pszName)
+{
+	if(!pData || !hShm || !nCChData || !_IS_VALID_VT(vt))
+	{
+		Q_ASSERT_X(false, "CShmStringVariable::CShmStringVariable", "Invalid parameter!");
+		return;
+	}
+
+	m_vt = vt;
+    m_data.pVoid = pData;
+    m_hShm = hShm;
+    setObjectName(QStringLiteral("CShmStringVariable"));
+    if((m_nIndex = nIndex) >= 0)
+	{
+		m_varName += QString("%1%2%3").arg('[').arg(nIndex).arg(']');
+	}
+
+    if( (rti == typeid(char)) ||
+        (rti == typeid(signed char)) ||
+        (rti == typeid(unsigned char)))
+    {
+    	switch(vt)
+    	{
+   		case VT_Latin1:
+   		case VT_UTF_8:
+			m_nCbBuffer = nCChData;
+			m_cache.pVoid = ::malloc(m_nCbBuffer);
+			zeroTerm(m_data, nCChData - 1);
+			memcpy(m_cache.pVoid, m_data.pVoid, m_nCbBuffer);
+			m_nCbString = strlen(m_cache.pszMbs);
+   			break;
+   		case VT_UTF_16:
+   		case VT_UTF_32:
+		case VT_Unicode:
+            Q_ASSERT_X(false, "CShmStringVariable::CShmStringVariable", "data type does not support encoding type!");
+   			return;
+   		default:
+			Q_ASSERT_X(false, "CShmStringVariable::CShmStringVariable", "Invalid string type!");
+   			return;
+    	}
+    }
+    else if(rti == typeid(char16_t))
+	{
+    	switch(vt)
+    	{
+   		case VT_UTF_16:
+			m_nCbBuffer = nCChData * sizeof(char16_t);
+			m_cache.pVoid = malloc(m_nCbBuffer);
+			zeroTerm(m_data, nCChData - 1);
+			memcpy(m_cache.pVoid, m_data.pVoid, m_nCbBuffer);
+			m_nCbString = wcs16len(m_cache.pszWc16) * sizeof(char16_t);
+   			break;
+		case VT_Unicode:
+   		case VT_Latin1:
+   		case VT_UTF_8:
+   		case VT_UTF_32:
+            Q_ASSERT_X(false, "CShmStringVariable::CShmStringVariable", "data type does not support encoding type!");
+   			return;
+   		default:
+			Q_ASSERT_X(false, "CShmStringVariable::CShmStringVariable", "Invalid string type!");
+   			return;
+    	}
+	}
+    else if(rti == typeid(char32_t))
+	{
+    	switch(vt)
+    	{
+   		case VT_UTF_32:
+			m_nCbBuffer = nCChData * sizeof(char32_t);
+			m_cache.pVoid = malloc(m_nCbBuffer);
+			zeroTerm(m_data, nCChData - 1);
+			memcpy(m_cache.pVoid, m_data.pVoid, m_nCbBuffer);
+			m_nCbString = wcs32len(m_cache.pszWc32) * sizeof(char32_t);
+   			break;
+		case VT_Unicode:
+   		case VT_Latin1:
+   		case VT_UTF_8:
+   		case VT_UTF_16:
+            Q_ASSERT_X(false, "CShmStringVariable::CShmStringVariable", "data type does not support encoding type!");
+   			return;
+   		default:
+			Q_ASSERT_X(false, "CShmStringVariable::CShmStringVariable", "Invalid string type!");
+   			return;
+    	}
+	}
+    else if(rti == typeid(wchar_t))
+	{
+    	switch(vt)
+    	{
+		case VT_Unicode:
+			m_nCbBuffer = nCChData * sizeof(wchar_t);
+			m_cache.pVoid = malloc(m_nCbBuffer);
+			zeroTerm(m_data, nCChData - 1);
+			memcpy(m_cache.pVoid, m_data.pVoid, m_nCbBuffer);
+			m_nCbString = wcslen(m_cache.pszWcs) * sizeof(wchar_t);
+   			break;
+   		case VT_Latin1:
+   		case VT_UTF_8:
+   		case VT_UTF_16:
+   		case VT_UTF_32:
+            Q_ASSERT_X(false, "CShmStringVariable::CShmStringVariable", "data type does not support encoding type!");
+   			return;
+   		default:
+			Q_ASSERT_X(false, "CShmStringVariable::CShmStringVariable", "Invalid string type!");
+   			return;
+    	}
+	}
+    else
+	{
+        Q_ASSERT_X(false, "CShmStringVariable::CShmStringVariable", "Unrecognized data type!");
+	}
+}
+
+CShmStringVariable::~CShmStringVariable(void)
+{
+	if(m_cache.pVoid)
+		::free(m_cache.pVoid);
+}
+
+void CShmStringVariable::valRaw(QString &v)
+{
+	switch(m_vt)
+	{
+	case VT_Latin1:
+		v = QString::fromLatin1(m_data.pszMbs, m_nCbString);
+		break;
+	case VT_UTF_8:
+		v = QString::fromUtf8(m_data.pszMbs, m_nCbString);
+		break;
+	case VT_UTF_16:
+		v = QString::fromUtf16(m_data.pszWc16, m_nCbString / sizeof(char16_t));
+		break;
+	case VT_UTF_32:
+		v = QString::fromUcs4(m_data.pszWc32, m_nCbString / sizeof(char32_t));
+		break;
+	case VT_Unicode:
+		v = QString::fromWCharArray(m_data.pszWcs, m_nCbString / sizeof(wchar_t));
+		break;
+	default:
+//		Q_ASSERT_X(false, "CShmStringVariable::valRaw", "Unrecognized string type!");
+		break;
+	}
+}
+
+QString CShmStringVariable::val(void)
+{
+	QString v;
+	Lock();
+	valRaw(v);
+	Unlock();
+    return v;
+}
+
+void CShmStringVariable::setVal(const QString &val)
+{
+	QByteArray ba;
+	QVector<uint> vec;
+	size_t nCbVal = 0, nCChVal = 0;
+	wchar_t *pszData;
+	const void *pData;
+
+	switch(m_vt)
+	{
+	case VT_Latin1:
+		ba = val.toLatin1();
+		nCbVal = ba.size();
+		nCbVal = __min(nCbVal, m_nCbBuffer - 1);
+		nCChVal = nCbVal;
+		pData = ba.data();
+		break;
+	case VT_UTF_8:
+		ba = val.toUtf8();
+		nCbVal = ba.size();
+		nCbVal = __min(nCbVal, m_nCbBuffer - 1);
+		nCChVal = nCbVal;
+		pData = ba.data();
+		break;
+	case VT_UTF_16:
+		nCbVal = val.size() * sizeof(char16_t);
+		nCbVal = __min(nCbVal, m_nCbBuffer - sizeof(char16_t));
+		nCChVal = nCbVal / sizeof(char16_t);
+		pData = val.utf16();
+		break;
+	case VT_UTF_32:
+		vec = val.toUcs4();
+		nCbVal = vec.size() * sizeof(char32_t);
+		nCbVal = __min(nCbVal, m_nCbBuffer - sizeof(char32_t));
+		nCChVal = nCbVal / sizeof(char32_t);
+		pData = vec.data();
+		break;
+	case VT_Unicode:
+		nCChVal = val.size();
+		nCbVal = nCChVal * sizeof(wchar_t);
+		pszData = (wchar_t*)alloca(nCbVal);
+		val.toWCharArray(pszData);
+		nCbVal = __min(nCbVal, m_nCbBuffer - sizeof(wchar_t));
+		nCChVal = nCbVal / sizeof(wchar_t);
+		pData = pszData;
+		break;
+	default:
+        Q_ASSERT_X(false, "CShmStringVariable::setVal", "Unrecognized string type!");
+		return;
+	}
+	
+	Lock();
+	m_nCbString = nCbVal;
+	memcpy(m_cache.pVoid, pData, m_nCbString);
+	zeroTerm(m_cache, nCChVal);
+	
+	if(shmChanged(false))
+	{
+		memcpy(m_data.pVoid, m_cache.pVoid, m_nCbString);
+		zeroTerm(m_data, nCChVal);
+		emitChanged(false);
+	}
+	Unlock();
+}
+
+void CShmStringVariable::zeroTerm(volatile V_Ptr &rp, size_t at)
+{
+	switch(m_vt)
+	{
+	case VT_Latin1:
+	case VT_UTF_8:
+		rp.pszMbs[at] = '\0';
+		break;
+	case VT_UTF_16:
+		rp.pszWc16[at] = (char16_t)0;
+		break;
+	case VT_UTF_32:
+		rp.pszWc32[at] = (char32_t)0;
+		break;
+	case VT_Unicode:
+		rp.pszWcs[at] = L'\0';
+		break;
+	default:
+//        Q_ASSERT_X(false, "CShmStringVariable::zeroTerm", "Unrecognized string type!");
+		return;
+	}
+}
+
+unsigned int CShmStringVariable::vt(void) const
+{
+	return CShmVariable::VT_string;
+}
+
+unsigned long long CShmStringVariable::CheckUpdateShm(bool fLock)
+{
+	CHECK_UPDATE_SHM_RETVAL rv = {1, 0};
+	size_t nCChBuf = 0;
+
+	if(fLock)
+		Lock();
+	
+	if(shmChanged(false))
+	{
+		switch(m_vt)
+		{
+		case VT_Latin1:
+		case VT_UTF_8:
+			nCChBuf = m_nCbBuffer - 1;
+			zeroTerm(m_data, nCChBuf);
+			m_nCbString = strlen(m_data.pszMbs);
+			break;
+		case VT_UTF_16:
+			nCChBuf = m_nCbBuffer / sizeof(char16_t) - 1;
+			zeroTerm(m_data, nCChBuf);
+			m_nCbString = wcs16len(m_data.pszWc16) * sizeof(char16_t);
+			break;
+		case VT_UTF_32:
+			nCChBuf = m_nCbBuffer / sizeof(char32_t) - 1;
+			zeroTerm(m_data, nCChBuf);
+			m_nCbString = wcs32len(m_data.pszWc32) * sizeof(char32_t);
+			break;
+		case VT_Unicode:
+			nCChBuf = m_nCbBuffer / sizeof(wchar_t) - 1;
+			zeroTerm(m_data, nCChBuf);
+			m_nCbString = wcslen(m_data.pszWcs) * sizeof(wchar_t);
+			break;
+		default:
+			return rv.nRetval;
+		}
+
+		memcpy(m_cache.pVoid, m_data.pVoid, m_nCbBuffer);
+		emitChanged(false);
+		rv.nUpdated = 1;
+	}
+
+	if(fLock)
+		Unlock();
+
+	return rv.nRetval;
+}
+
+bool CShmStringVariable::shmChanged(bool fLock)
+{
+	bool bRet;
+	if(fLock)
+		Lock();
+	bRet =  !!memcmp(m_cache.pVoid, m_data.pVoid, m_nCbString + 1);
+	if(fLock)
+		Unlock();
+	return bRet;
+}
+
+void CShmStringVariable::Lock(void)
+{
+	::GfaIpcLockSHM(m_hShm);
+//	qDebug() << "CShmStringVariable::Lock";
+}
+
+void CShmStringVariable::Unlock(void)
+{
+//	qDebug() << "CShmStringVariable::Unlock";
+	::GfaIpcUnlockSHM(m_hShm);
+}
+
+void CShmStringVariable::emitChanged(bool fLock)
+{
+//	qDebug() << "CShmStringVariable: val changed!";
+	if(fLock)
+		emit valChanged(val());
+	else
+	{
+		QString v;
+		valRaw(v);
+		emit valChanged(v);
+	}
+}

+ 265 - 0
shmqml/shmthread.h

@@ -0,0 +1,265 @@
+// shmthread.h :
+//
+
+#if !defined(AGD_SHMTHREAD_H__C09C5E02_A3A0_4171_BA2E_045C3FE0C05C__INCLUDED_)
+#define AGD_SHMTHREAD_H__C09C5E02_A3A0_4171_BA2E_045C3FE0C05C__INCLUDED_
+
+#include <time.h>
+#include <pthread.h>
+#include <gfa/gfaipc.h>
+#include <QDebug>
+#ifndef _LIBBUILD
+#include <gfa/svc/shmqml/shmvar.h>
+#else	//	_LIBBUILD
+#include "shmvar.h"
+#endif	//	_LIBBUILD
+
+
+#define _SHM_SNEAK_PEEK					1
+#define _TRACK_TIME						0
+#define _TRACK_UPDATES					0
+
+#define _SHM_SCAN_INTERVAL_MS			100
+
+/////////////////////////////////////////////////////////////////////////////
+
+#define _INVALID_THREAD_ID				0
+
+/////////////////////////////////////////////////////////////////////////////
+// shmthread.h - Declarations:
+
+template<typename T, typename S>
+class CShmWatcher
+{
+public:
+	CShmWatcher(HSHM hShm, T &rT, const void *pShm);
+	virtual ~CShmWatcher(void);
+
+	bool StartWatch(unsigned long nMsInterval = _SHM_SCAN_INTERVAL_MS);
+	void StopWatch(void);
+
+	static void* ShmWatchWorker(void *pParam);
+
+	inline void Lock(void){
+		(*m_pfnLock)(m_hShm);
+	}
+
+	inline void Unlock(void){
+		(*m_pfnUnlock)(m_hShm);
+	}
+	
+	void SetLockUnlockFunctions(PFN_GFA_IPC_LOCK_SHM pfnLock, PFN_GFA_IPC_UNLOCK_SHM pfnUnlock) {
+		if(pfnLock && pfnUnlock)
+		{
+			Lock();
+			PFN_GFA_IPC_UNLOCK_SHM pfnUlTmp = m_pfnUnlock;
+			m_pfnLock = pfnLock;
+			m_pfnUnlock = pfnUnlock;
+			(*pfnUlTmp)(m_hShm);
+		}
+		else
+		{
+			Lock();
+			PFN_GFA_IPC_UNLOCK_SHM pfnUlTmp = m_pfnUnlock;
+			m_pfnLock = ::GfaIpcLockSHM;
+			m_pfnUnlock = ::GfaIpcUnlockSHM;
+			(*pfnUlTmp)(m_hShm);
+		}
+	}
+
+#if _SHM_SNEAK_PEEK
+	inline bool ShmPeek(void){
+		Lock();
+		bool bRet = !!memcmp(m_pCache, m_pShm, sizeof(S));
+		Unlock();
+		return bRet;}
+
+	inline void UpdateCache(void){
+		Lock();
+		memcpy(m_pCache, m_pShm, sizeof(S));
+		Unlock();}
+#endif	//	_SHM_SNEAK_PEEK
+
+private:
+	struct timespec m_tsInterval;
+    pthread_t m_threadID;
+    HSHM m_hShm;
+	T &m_rT;
+	S *m_pCache;
+	const void *m_pShm;
+	PFN_GFA_IPC_LOCK_SHM m_pfnLock;
+	PFN_GFA_IPC_UNLOCK_SHM m_pfnUnlock;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+
+template<typename T, typename S>
+CShmWatcher<T, S>::CShmWatcher(HSHM hShm, T &rT, const void *pShm) :	m_threadID(_INVALID_THREAD_ID),
+						                                                m_hShm(hShm),
+																		m_rT(rT),
+																		m_pCache(NULL),
+																		m_pShm(pShm),
+																		m_pfnLock(::GfaIpcLockSHM),
+																		m_pfnUnlock(::GfaIpcUnlockSHM)
+{
+#if _SHM_SNEAK_PEEK
+	if(m_pShm)
+	{
+		m_pCache = new S;
+		memcpy(m_pCache, pShm, sizeof(S));
+	}
+	else
+	{
+		Q_ASSERT_X(false, "CShmWatcher::CShmWatcher", "Invalid SHM pointer!");
+	}
+#endif	//	_SHM_SNEAK_PEEK
+}
+
+template<typename T, typename S>
+CShmWatcher<T, S>::~CShmWatcher(void)
+{
+	StopWatch();
+
+#if _SHM_SNEAK_PEEK
+	if(m_pCache)
+	{
+		delete m_pCache;
+		m_pCache = NULL;
+	}
+#endif	//	_SHM_SNEAK_PEEK
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+template<typename T, typename S>
+bool CShmWatcher<T, S>::StartWatch(unsigned long nMsInterval)
+{
+	Lock();
+	if(m_threadID != _INVALID_THREAD_ID)
+	{
+		Unlock();
+		return true;
+	}
+	Unlock();
+
+	m_tsInterval.tv_sec		= nMsInterval / 1000;
+	m_tsInterval.tv_nsec	= (nMsInterval % 1000) * 1000000;
+
+	bool bRet = !::pthread_create(&m_threadID, NULL, &CShmWatcher<T, S>::ShmWatchWorker, reinterpret_cast<void*>(this));
+
+	if(!bRet)
+	{
+		m_threadID = _INVALID_THREAD_ID;
+        qDebug() << "CShmWatcher::StartWatch failed!";
+	}
+	else
+	{
+        qDebug() << "CShmWatcher::StartWatch success!";
+	}
+
+	return bRet;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+template<typename T, typename S>
+void CShmWatcher<T, S>::StopWatch(void)
+{
+	pthread_t threadID;
+
+	Lock();
+	if((threadID = m_threadID) != _INVALID_THREAD_ID)
+		m_threadID = _INVALID_THREAD_ID;
+	Unlock();
+
+	if(threadID != _INVALID_THREAD_ID)
+	{
+		::pthread_cancel(threadID);
+		::pthread_join(threadID, NULL);
+        qDebug() << "CShmWatcher::StopWatch: thread exit!";
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+template<typename T, typename S>
+void* CShmWatcher<T, S>::ShmWatchWorker(void *pParam)
+{
+	if(pParam)
+	{
+#if _TRACK_UPDATES
+		CHECK_UPDATE_SHM_RETVAL rv;
+#endif	//	_TRACK_UPDATES
+
+#if _TRACK_TIME
+		struct timespec tsStart, tsEnd;
+#endif	//	_TRACK_TIME
+
+#if _SHM_SNEAK_PEEK
+		bool bUpdt;
+#endif	//	_SHM_SNEAK_PEEK
+
+		CShmWatcher<T, S>* pSelf = reinterpret_cast<CShmWatcher<T, S>*>(pParam);
+
+		do
+		{
+#if _TRACK_TIME
+			unsigned long long nStart, nEnd;
+			double fTime;
+			::clock_gettime(CLOCK_MONOTONIC, &tsStart);
+#endif	//	_TRACK_TIME
+
+#if _SHM_SNEAK_PEEK
+			bUpdt = pSelf->ShmPeek();
+
+			if(bUpdt)
+			{
+#endif	//	_SHM_SNEAK_PEEK
+
+#if _TRACK_UPDATES
+				rv.nRetval = pSelf->m_rT.CheckUpdateShm();
+#else	//	_TRACK_UPDATES
+				pSelf->m_rT.CheckUpdateShm();
+#endif	//	_TRACK_UPDATES
+
+#if _SHM_SNEAK_PEEK
+				pSelf->UpdateCache();
+			}
+#if _TRACK_UPDATES
+            else
+			{
+				rv.nRetval = 0;
+			}
+#endif	//	_TRACK_UPDATES
+#endif	//	_SHM_SNEAK_PEEK
+
+#if _TRACK_TIME
+            ::clock_gettime(CLOCK_MONOTONIC, &tsEnd);
+            nStart	= tsStart.tv_sec * 1000000000 + tsStart.tv_nsec;
+			nEnd	= tsEnd.tv_sec   * 1000000000 + tsEnd.tv_nsec;
+			fTime = (double)(nEnd - nStart) / 1000000.0;
+            qDebug() << "CShmWatcher::ShmWatchWorker: Scan / Update time: - " << fTime << "ms";
+#endif	//	_TRACK_TIME
+
+#if _TRACK_UPDATES
+            qDebug() << "CShmWatcher::ShmWatchWorker: Variables scanned / updated: - " << rv.nChecked << "/" << rv.nUpdated;
+#endif	//	_TRACK_UPDATES
+
+            ::nanosleep(&pSelf->m_tsInterval, NULL);
+        }
+		while(true);
+
+		pSelf->Lock();
+		pSelf->m_threadID = _INVALID_THREAD_ID;
+		pSelf->Unlock();
+
+        qDebug() << "CShmWatcher::ShmWatchWorker: thread ended gracefully!";
+	}
+
+	return NULL;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	!defined(AGD_SHMTHREAD_H__C09C5E02_A3A0_4171_BA2E_045C3FE0C05C__INCLUDED_)

+ 545 - 0
shmqml/shmvar.cpp

@@ -0,0 +1,545 @@
+#include "shmvar.h"
+#include <QDebug>
+
+CShmVariable::CShmVariable(void *pData, const std::type_info &rti, HSHM hShm, const char *pszName, int nIndex, QObject *pParent)
+							: QObject(pParent), m_vt(VT_Invalid), m_data({NULL}), m_varName(pszName)
+{
+	if(!pData || !hShm)
+	{
+		Q_ASSERT_X(false, "CShmVariable::CShmVariable", "Invalid parameter!");
+		return;
+	}
+
+    m_data.pVoid = pData;
+    m_hShm = hShm;
+    setObjectName(QStringLiteral("CShmVariable"));
+    if((m_nIndex = nIndex) >= 0)
+	{
+		m_varName += QString("%1%2%3").arg('[').arg(nIndex).arg(']');
+	}
+
+    if(rti == typeid(bool))
+    {
+    	m_vt = VT_bool;
+        m_cache.boolVal = *m_data.pBool;
+#if _TRACK_TYPES_AT_LOAD
+        qDebug() << "VT_bool";
+#endif	//	_TRACK_TYPES_AT_LOAD    	
+    }
+    else if(rti == typeid(char))
+    {
+#ifdef __CHAR_UNSIGNED__
+    	m_vt = VT_UI1;
+        m_cache.UI1Val = *m_data.pUI1;
+#if _TRACK_TYPES_AT_LOAD
+        qDebug() << "VT_UI1";
+#endif	//	_TRACK_TYPES_AT_LOAD    	
+#else
+        m_vt = VT_I1;
+        m_cache.I1Val = *m_data.pI1;
+#if _TRACK_TYPES_AT_LOAD
+        qDebug() << "VT_I1";
+#endif	//	_TRACK_TYPES_AT_LOAD    	
+#endif
+    }
+    else if(rti == typeid(signed char))
+    {
+        m_vt = VT_I1;
+        m_cache.I1Val = *m_data.pI1;
+#if _TRACK_TYPES_AT_LOAD
+        qDebug() << "VT_I1";
+#endif	//	_TRACK_TYPES_AT_LOAD    	
+    }
+    else if(rti == typeid(unsigned char))
+    {
+    	m_vt = VT_UI1;
+        m_cache.UI1Val = *m_data.pUI1;
+#if _TRACK_TYPES_AT_LOAD
+        qDebug() << "VT_UI1";
+#endif	//	_TRACK_TYPES_AT_LOAD    	
+    }
+    else if(rti == typeid(short))
+    {
+    	m_vt = VT_I2;
+        m_cache.I2Val = *m_data.pI2;
+#if _TRACK_TYPES_AT_LOAD
+        qDebug() << "VT_I2";
+#endif	//	_TRACK_TYPES_AT_LOAD    	
+    }
+    else if(rti == typeid(unsigned short))
+    {
+    	m_vt = VT_UI2;
+        m_cache.UI2Val = *m_data.pUI2;
+#if _TRACK_TYPES_AT_LOAD
+        qDebug() << "VT_UI2";
+#endif	//	_TRACK_TYPES_AT_LOAD    	
+    }
+    else if(rti == typeid(int))
+    {
+    	m_vt = VT_I4;
+        m_cache.I4Val = *m_data.pI4;
+#if _TRACK_TYPES_AT_LOAD
+        qDebug() << "VT_I4";
+#endif	//	_TRACK_TYPES_AT_LOAD    	
+    }
+    else if(rti == typeid(unsigned int))
+    {
+    	m_vt = VT_UI4;
+        m_cache.UI4Val = *m_data.pUI4;
+#if _TRACK_TYPES_AT_LOAD
+        qDebug() << "VT_UI4";
+#endif	//	_TRACK_TYPES_AT_LOAD    	
+    }
+    else if(rti == typeid(long))
+    {
+    	if(sizeof(long) == sizeof(long long))
+    	{
+	    	m_vt = VT_I8;
+            m_cache.I8Val = *m_data.pI8;
+#if _TRACK_TYPES_AT_LOAD
+            qDebug() << "VT_I8";
+#endif	//	_TRACK_TYPES_AT_LOAD    	
+	    }
+    	else
+    	{
+	    	m_vt = VT_I4;
+	        m_cache.I4Val = *m_data.pI4;
+#if _TRACK_TYPES_AT_LOAD
+            qDebug() << "VT_I4";
+#endif	//	_TRACK_TYPES_AT_LOAD    	
+	    }
+    }
+    else if(rti == typeid(unsigned long))
+    {
+    	if(sizeof(unsigned long) == sizeof(unsigned long long))
+    	{
+	    	m_vt = VT_UI8;
+            m_cache.UI8Val = *m_data.pUI8;
+#if _TRACK_TYPES_AT_LOAD
+            qDebug() << "VT_UI8";
+#endif	//	_TRACK_TYPES_AT_LOAD    	
+	    }
+    	else
+    	{
+	    	m_vt = VT_UI4;
+	        m_cache.UI4Val = *m_data.pUI4;
+#if _TRACK_TYPES_AT_LOAD
+            qDebug() << "VT_UI4";
+#endif	//	_TRACK_TYPES_AT_LOAD    	
+	    }
+    }
+    else if(rti == typeid(long long))
+    {
+    	m_vt = VT_I8;
+		m_cache.I8Val = *m_data.pI8;
+#if _TRACK_TYPES_AT_LOAD
+        qDebug() << "VT_I8";
+#endif	//	_TRACK_TYPES_AT_LOAD    	
+    }
+    else if(rti == typeid(unsigned long long))
+    {
+    	m_vt = VT_UI8;
+		m_cache.UI8Val = *m_data.pUI8;
+#if _TRACK_TYPES_AT_LOAD
+        qDebug() << "VT_UI8";
+#endif	//	_TRACK_TYPES_AT_LOAD    	
+    }
+    else if(rti == typeid(float))
+    {
+    	m_vt = VT_float;
+        m_cache.FloatVal = *m_data.pFloat;
+#if _TRACK_TYPES_AT_LOAD
+        qDebug() << "VT_float";
+#endif	//	_TRACK_TYPES_AT_LOAD    	
+    }
+    else if(rti == typeid(double))
+    {
+    	m_vt = VT_double;
+        m_cache.DoubleVal = *m_data.pDouble;
+#if _TRACK_TYPES_AT_LOAD
+        qDebug() << "VT_double";
+#endif	//	_TRACK_TYPES_AT_LOAD    	
+    }
+    else
+	{
+		Q_ASSERT_X(false, "CShmVariable::CShmVariable", "Unknown data type!");
+	}
+}
+
+CShmVariable::~CShmVariable(void)
+{
+}
+
+void CShmVariable::valRaw(QVariant &v)
+{
+	switch(m_vt)
+	{
+	case VT_bool:
+		v.setValue(*m_data.pBool);
+		break;
+	case VT_I1:
+		v.setValue((signed int)*m_data.pI1);
+		break;
+	case VT_UI1:
+		v.setValue((unsigned int)*m_data.pUI1);
+		break;
+	case VT_I2:
+		v.setValue((signed int)*m_data.pI2);
+		break;
+	case VT_UI2:
+		v.setValue((unsigned int)*m_data.pUI2);
+		break;
+	case VT_I4:
+        v.setValue(*m_data.pI4);
+		break;
+	case VT_UI4:
+        v.setValue(*m_data.pUI4);
+		break;
+	case VT_I8:
+        v.setValue(*m_data.pI8);
+		break;
+	case VT_UI8:
+        v.setValue(*m_data.pUI8);
+		break;
+	case VT_float:
+		v.setValue(*m_data.pFloat);
+		break;
+	case VT_double:
+		v.setValue(*m_data.pDouble);
+		break;
+    default:
+        break;
+	}
+}
+
+QVariant CShmVariable::val(void)
+{
+	QVariant v;
+	Lock();
+	valRaw(v);
+	Unlock();
+    return v;
+}
+
+void CShmVariable::setVal(const QVariant &val)
+{
+	if(val.isValid() && !val.isNull())
+	{
+#if _TRACK_QT_WRITES
+		char szDebugOut[256];
+#endif	//	_TRACK_QT_WRITES
+
+		Lock();
+
+		switch(m_vt)
+		{
+		case VT_bool:
+			if(val.canConvert<bool>())
+			{
+				m_cache.boolVal = val.toBool();
+				if(*m_data.pBool != m_cache.boolVal)
+				{
+					*m_data.pBool = m_cache.boolVal;
+					emitChanged(false);
+#if _TRACK_QT_WRITES
+					sprintf(szDebugOut, "CShmVariable::setVal: bool %s@(%p) - %s", m_varName.toStdString().c_str(), m_data.pVoid, m_cache.boolVal ? "true" : "false");
+        			qDebug() << szDebugOut;
+#endif	//	_TRACK_QT_WRITES
+				}
+			}
+			break;
+		case VT_I1:
+			if(val.canConvert<int>())
+			{
+				m_cache.I1Val = (signed char)val.toInt();
+				if(*m_data.pI1 != m_cache.I1Val)
+				{
+					*m_data.pI1 = m_cache.I1Val;
+					emitChanged(false);
+#if _TRACK_QT_WRITES
+					sprintf(szDebugOut, "CShmVariable::setVal: signed char %s@(%p) - %hhd", m_varName.toStdString().c_str(), m_data.pVoid, m_cache.I1Val);
+        			qDebug() << szDebugOut;
+#endif	//	_TRACK_QT_WRITES
+				}
+			}
+			break;
+		case VT_UI1:
+			if(val.canConvert<int>())
+			{
+				m_cache.UI1Val = (unsigned char)val.toInt();
+				if(*m_data.pUI1 != m_cache.UI1Val)
+				{
+					*m_data.pUI1 = m_cache.UI1Val;
+					emitChanged(false);
+#if _TRACK_QT_WRITES
+					sprintf(szDebugOut, "CShmVariable::setVal: unsigned char %s@(%p) - %hhu", m_varName.toStdString().c_str(), m_data.pVoid, m_cache.UI1Val);
+        			qDebug() << szDebugOut;
+#endif	//	_TRACK_QT_WRITES
+				}
+			}
+			break;
+		case VT_I2:
+			if(val.canConvert<int>())
+			{
+				m_cache.I2Val = (signed short)val.toInt();
+				if(*m_data.pI2 != m_cache.I2Val)
+				{
+					*m_data.pI2 = m_cache.I2Val;
+					emitChanged(false);
+#if _TRACK_QT_WRITES
+					sprintf(szDebugOut, "CShmVariable::setVal: signed short %s@(%p) - %hd", m_varName.toStdString().c_str(), m_data.pVoid, m_cache.I2Val);
+        			qDebug() << szDebugOut;
+#endif	//	_TRACK_QT_WRITES
+				}
+			}
+			break;
+		case VT_UI2:
+			if(val.canConvert<int>())
+			{
+				m_cache.UI2Val = (unsigned short)val.toInt();
+				if(*m_data.pUI2 != m_cache.UI2Val)
+				{
+					*m_data.pUI2 = m_cache.UI2Val;
+					emitChanged(false);
+#if _TRACK_QT_WRITES
+					sprintf(szDebugOut, "CShmVariable::setVal: unsigned short %s@(%p) - %hu", m_varName.toStdString().c_str(), m_data.pVoid, m_cache.UI2Val);
+        			qDebug() << szDebugOut;
+#endif	//	_TRACK_QT_WRITES
+				}
+			}
+			break;
+		case VT_I4:
+			if(val.canConvert<int>())
+			{
+				m_cache.I4Val = (signed int)val.toInt();
+				if(*m_data.pI4 != m_cache.I4Val)
+				{
+					*m_data.pI4 = m_cache.I4Val;
+					emitChanged(false);
+#if _TRACK_QT_WRITES
+					sprintf(szDebugOut, "CShmVariable::setVal: signed int %s@(%p) - %d", m_varName.toStdString().c_str(), m_data.pVoid, m_cache.I4Val);
+        			qDebug() << szDebugOut;
+#endif	//	_TRACK_QT_WRITES
+				}
+			}
+			break;
+		case VT_UI4:
+			if(val.canConvert<int>())
+			{
+				m_cache.UI4Val = (unsigned int)val.toInt();
+				if(*m_data.pUI4 != m_cache.UI4Val)
+				{
+					*m_data.pUI4 = m_cache.UI4Val;
+					emitChanged(false);
+#if _TRACK_QT_WRITES
+					sprintf(szDebugOut, "CShmVariable::setVal: unsigned int %s@(%p) - %u", m_varName.toStdString().c_str(), m_data.pVoid, m_cache.UI4Val);
+        			qDebug() << szDebugOut;
+#endif	//	_TRACK_QT_WRITES
+				}
+			}
+			break;
+		case VT_I8:
+			if(val.canConvert<long long>())
+			{
+				m_cache.I8Val = (signed long long)val.toLongLong();
+				if(*m_data.pI8 != m_cache.I8Val)
+				{
+					*m_data.pI8 = m_cache.I8Val;
+					emitChanged(false);
+#if _TRACK_QT_WRITES
+					sprintf(szDebugOut, "CShmVariable::setVal: signed long long %s@(%p) - %lld", m_varName.toStdString().c_str(), m_data.pVoid, m_cache.I8Val);
+        			qDebug() << szDebugOut;
+#endif	//	_TRACK_QT_WRITES
+				}
+			}
+			break;
+		case VT_UI8:
+			if(val.canConvert<long long>())
+			{
+				m_cache.UI8Val = (unsigned long long)val.toLongLong();
+				if(*m_data.pUI8 != m_cache.UI8Val)
+				{
+					*m_data.pUI8 = m_cache.UI8Val;
+					emitChanged(false);
+#if _TRACK_QT_WRITES
+					sprintf(szDebugOut, "CShmVariable::setVal: unsigned long long %s@(%p) - %llu", m_varName.toStdString().c_str(), m_data.pVoid, m_cache.UI8Val);
+        			qDebug() << szDebugOut;
+#endif	//	_TRACK_QT_WRITES
+				}
+			}
+			break;
+		case VT_float:
+			if(val.canConvert<float>())
+			{
+				m_cache.FloatVal = (float)val.toFloat();
+				if(*m_data.pFloat != m_cache.FloatVal)
+				{
+					*m_data.pFloat = m_cache.FloatVal;
+					emitChanged(false);
+#if _TRACK_QT_WRITES
+					sprintf(szDebugOut, "CShmVariable::setVal: float %s@(%p) - %f", m_varName.toStdString().c_str(), m_data.pVoid, m_cache.FloatVal);
+        			qDebug() << szDebugOut;
+#endif	//	_TRACK_QT_WRITES
+				}
+			}
+			break;
+		case VT_double:
+			if(val.canConvert<double>())
+			{
+				m_cache.DoubleVal = (double)val.toDouble();
+				if(*m_data.pDouble != m_cache.DoubleVal)
+				{
+					*m_data.pDouble = m_cache.DoubleVal;
+					emitChanged(false);
+#if _TRACK_QT_WRITES
+					sprintf(szDebugOut, "CShmVariable::setVal: double %s@(%p) - %f", m_varName.toStdString().c_str(), m_data.pVoid, m_cache.DoubleVal);
+        			qDebug() << szDebugOut;
+#endif	//	_TRACK_QT_WRITES
+				}
+			}
+			break;
+		default:
+	        qDebug() << "CShmVariable::setVal: invalid vartype!";
+			break;
+		}
+
+		Unlock();
+	}
+}
+
+unsigned int CShmVariable::vt(void) const
+{
+	return m_vt;
+}
+
+unsigned long long CShmVariable::CheckUpdateShm(bool fLock)
+{
+	CHECK_UPDATE_SHM_RETVAL rv = {1, 0};
+
+	if(fLock)
+		Lock();
+
+	switch(m_vt)
+	{
+	case VT_bool:
+		if(m_cache.boolVal != *m_data.pBool)
+		{
+			m_cache.boolVal = *m_data.pBool;
+			emitChanged(false);
+			rv.nUpdated = 1;
+		}
+		break;
+	case VT_I1:
+		if(m_cache.I1Val != *m_data.pI1)
+		{
+			m_cache.I1Val = *m_data.pI1;
+			emitChanged(false);
+			rv.nUpdated = 1;
+		}
+		break;
+	case VT_UI1:
+		if(m_cache.UI1Val != *m_data.pUI1)
+		{
+			m_cache.UI1Val = *m_data.pUI1;
+			emitChanged(false);
+			rv.nUpdated = 1;
+		}
+		break;
+	case VT_I2:
+		if(m_cache.I2Val != *m_data.pI2)
+		{
+			m_cache.I2Val = *m_data.pI2;
+			emitChanged(false);
+			rv.nUpdated = 1;
+		}
+		break;
+	case VT_UI2:
+		if(m_cache.UI2Val != *m_data.pUI2)
+		{
+			m_cache.UI2Val = *m_data.pUI2;
+			emitChanged(false);
+			rv.nUpdated = 1;
+		}
+		break;
+	case VT_I4:
+		if(m_cache.I4Val != *m_data.pI4)
+		{
+			m_cache.I4Val = *m_data.pI4;
+			emitChanged(false);
+			rv.nUpdated = 1;
+		}
+		break;
+	case VT_UI4:
+		if(m_cache.UI4Val != *m_data.pUI4)
+		{
+			m_cache.UI4Val = *m_data.pUI4;
+			emitChanged(false);
+			rv.nUpdated = 1;
+		}
+		break;
+	case VT_I8:
+		if(m_cache.I8Val != *m_data.pI8)
+		{
+			m_cache.I8Val = *m_data.pI8;
+			emitChanged(false);
+			rv.nUpdated = 1;
+		}
+		break;
+	case VT_UI8:
+		if(m_cache.UI8Val != *m_data.pUI8)
+		{
+			m_cache.UI8Val = *m_data.pUI8;
+			emitChanged(false);
+			rv.nUpdated = 1;
+		}
+		break;
+	case VT_float:
+		if(m_cache.FloatVal != *m_data.pFloat)
+		{
+			m_cache.FloatVal = *m_data.pFloat;
+			emitChanged(false);
+			rv.nUpdated = 1;
+		}
+		break;
+	case VT_double:
+		if(m_cache.DoubleVal != *m_data.pDouble)
+		{
+			m_cache.DoubleVal = *m_data.pDouble;
+			emitChanged(false);
+			rv.nUpdated = 1;
+		}
+		break;
+    default:
+        break;
+	}
+
+	if(fLock)
+		Unlock();
+
+	return rv.nRetval;
+}
+
+void CShmVariable::Lock(void)
+{
+	::GfaIpcLockSHM(m_hShm);
+//	qDebug() << "CShmVariable::Lock";
+}
+
+void CShmVariable::Unlock(void)
+{
+//	qDebug() << "CShmVariable::Unlock";
+	::GfaIpcUnlockSHM(m_hShm);
+}
+
+void CShmVariable::emitChanged(bool fLock)
+{
+//    qDebug() << "val changed!";
+	if(fLock)
+	    emit valChanged(val());
+	else
+	{
+	    QVariant v;
+	    valRaw(v);
+	    emit valChanged(v);
+	}
+}

+ 238 - 0
shmqml/shmvar.h

@@ -0,0 +1,238 @@
+// shmvar.h :
+//
+
+#if !defined(AGD_SHMVAR_H__8E807F2F_C6E5_4D6B_8193_F29943D450B3__INCLUDED_)
+#define AGD_SHMVAR_H__8E807F2F_C6E5_4D6B_8193_F29943D450B3__INCLUDED_
+
+#include <pthread.h>
+#include <QObject>
+#include <QVariant>
+#include <QByteArray> 
+#include <gfa/gfaipc.h>
+#include <typeinfo>
+
+/////////////////////////////////////////////////////////////////////////////
+
+
+#define _TRACK_TYPES_AT_LOAD				0
+#define _TRACK_QT_WRITES					0
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+typedef union _CHECK_UPDATE_SHM_RETVAL
+{
+	struct
+	{
+		unsigned int nChecked;
+		unsigned int nUpdated;
+	};
+	unsigned long long nRetval;
+}CHECK_UPDATE_SHM_RETVAL, *LPCHECK_UPDATE_SHM_RETVAL;
+typedef const CHECK_UPDATE_SHM_RETVAL *LPCCHECK_UPDATE_SHM_RETVAL;
+
+/////////////////////////////////////////////////////////////////////////////
+// shmvar.h - Declarations:
+
+class CShmVariable : public QObject
+{
+    Q_OBJECT
+    Q_PROPERTY(QVariant val READ val WRITE setVal NOTIFY valChanged USER true)
+    Q_PROPERTY(unsigned int vt READ vt CONSTANT)
+
+public:
+	enum VT
+	{
+		VT_Invalid,
+		VT_bool,
+		VT_I1,
+		VT_UI1,
+		VT_I2,
+		VT_UI2,
+		VT_I4,
+		VT_UI4,
+		VT_I8,
+		VT_UI8,
+		VT_float,
+		VT_double,
+		VT_string
+	};
+
+	Q_ENUM(VT)
+	
+	typedef union
+	{
+		void *pVoid;
+		bool *pBool;
+		signed char *pI1;
+		unsigned char *pUI1;
+		signed short *pI2;
+		unsigned short *pUI2;
+		signed int *pI4;
+		unsigned int *pUI4;
+		signed long long *pI8;
+		unsigned long long *pUI8;
+		float *pFloat;
+		double *pDouble;
+	}V_Ptr;
+
+	typedef union
+	{
+		bool boolVal;
+		signed char I1Val;
+		unsigned char UI1Val;
+		signed short I2Val;
+		unsigned short UI2Val;
+		signed int I4Val;
+		unsigned int UI4Val;
+		signed long long I8Val;
+		unsigned long long UI8Val;
+		float FloatVal;
+		double DoubleVal;
+	}V_Val;
+
+public:
+    explicit CShmVariable(void *pData, const std::type_info &rti, HSHM hShm, const char *pszName, int nIndex, QObject *pParent = 0);
+    virtual ~CShmVariable(void);
+
+    QVariant val(void);
+	void setVal(const QVariant &val);
+	unsigned int vt(void) const;
+	unsigned long long CheckUpdateShm(bool fLock);
+
+signals:
+	void valChanged(const QVariant &val) const;
+
+private:
+    void valRaw(QVariant &v);
+	void emitChanged(bool fLock = false);
+    void Lock(void);
+    void Unlock(void);
+
+private:
+    enum VT m_vt;
+    volatile V_Ptr m_data;
+    V_Val m_cache;
+    HSHM m_hShm;
+    QString m_varName;
+    int m_nIndex;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CShmStringVariable : public QObject
+{
+    Q_OBJECT
+    Q_PROPERTY(QString val READ val WRITE setVal NOTIFY valChanged USER true)
+    Q_PROPERTY(unsigned int vt READ vt CONSTANT)
+
+public:
+	typedef enum
+	{
+		VT_Invalid,	// 0
+		VT_Latin1,	// 1
+		VT_UTF_8,	// 2
+		VT_UTF_16,	// 3
+		VT_UTF_32,	// 4
+		VT_Unicode,	// 5
+		VT_Last
+	}VT;
+
+	typedef union
+	{
+		void *pVoid;
+		char *pszMbs;
+		char16_t *pszWc16;
+		char32_t *pszWc32;
+		wchar_t *pszWcs;
+	}V_Ptr;
+
+public:
+    explicit CShmStringVariable(void *pData, size_t nCChData, VT vt, const std::type_info &rti, HSHM hShm, const char *pszName, int nIndex, QObject *pParent = 0);
+    virtual ~CShmStringVariable(void);
+
+    QString val(void);
+	void setVal(const QString &val);
+	unsigned int vt(void) const;
+	unsigned long long CheckUpdateShm(bool fLock);
+
+signals:
+	void valChanged(const QString &val) const;
+
+private:
+    void valRaw(QString &v);
+	void emitChanged(bool fLock = false);
+    bool shmChanged(bool fLock);
+    void Lock(void);
+    void Unlock(void);
+    void zeroTerm(volatile V_Ptr &rp, size_t at);
+
+private:
+    VT m_vt;
+    volatile V_Ptr m_data;
+    V_Ptr m_cache;
+    size_t m_nCbBuffer;
+    size_t m_nCbString;
+    HSHM m_hShm;
+    QString m_varName;
+    int m_nIndex;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CShmBitVariable : public QObject
+{
+    Q_OBJECT
+    Q_PROPERTY(QVariant val READ val WRITE setVal NOTIFY valChanged USER true)
+    Q_PROPERTY(unsigned int vt READ vt CONSTANT)
+
+public:
+	enum VT
+	{
+		VT_Invalid,
+		VT_bool,
+		VT_I1,
+		VT_UI1,
+		VT_I2,
+		VT_UI2,
+		VT_I4,
+		VT_UI4,
+		VT_I8,
+		VT_UI8,
+		VT_float,
+		VT_double,
+		VT_string
+	};
+
+	Q_ENUM(VT)
+
+public:
+    explicit CShmBitVariable(void *pData, size_t nOffset, int nBitNr, HSHM hShm, const char *pszName, QObject *pParent = 0);
+    virtual ~CShmBitVariable(void);
+
+    QVariant val(void);
+	void setVal(const QVariant &val);
+	unsigned int vt(void) const;
+	unsigned long long CheckUpdateShm(bool fLock);
+
+signals:
+	void valChanged(const QVariant &val) const;
+
+private:
+    void valRaw(QVariant &v);
+	void emitChanged(bool fLock = false);
+    void Lock(void);
+    void Unlock(void);
+
+private:
+	uint8_t *m_pShmByte;
+	uint8_t m_mask;
+	bool m_cacheVal;
+    HSHM m_hShm;
+    QString m_varName;
+    int m_nIndex;
+    int m_nBitNr;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	!defined(AGD_SHMVAR_H__8E807F2F_C6E5_4D6B_8193_F29943D450B3__INCLUDED_)

+ 8 - 0
summarist/.gitignore

@@ -0,0 +1,8 @@
+# git ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
+
+*.pro.user

+ 724 - 0
summarist/summarist.cpp

@@ -0,0 +1,724 @@
+/////////////////////////////////////////////////////////////////////////////
+//
+
+#include <string.h>
+#include "fileutil.h"
+#include "strutil.h"
+#include "summarist.h"
+#include "debug.h"
+
+#define _SUMMARIST_TIMESTAMP_FILE_NAME		"sum.ts"
+#define _SECS_PER_DAY						86400
+
+static const unsigned long g_nFactors[] = {1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 16, 18, 20, 24, 25, 30, 36, 40, 45, 48, 50, 60, 72, 75, 80, 90, 100, 120, 144, 150, 180, 200, 225, 240, 300, 360, 400, 450, 600, 720, 900, 1200, 1800, 3600};
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+CSummarist::CSummarist(LPCSUMMARIST_PARAMS psup, CLogfile &rlf) : m_rlf(rlf), m_nProcFrequ(1), m_tzo(_SUM_INVALID_TIMESTAMP_VALUE), m_tsBase(_SUM_INVALID_TIMESTAMP_VALUE)
+{
+	memset(&m_sup, 0, sizeof(m_sup));
+	if(psup)
+		memcpy(&m_sup, psup, sizeof(m_sup));
+
+	if(m_sup.pszBaseDir)
+		strncpy(m_szAppDir, m_sup.pszBaseDir, sizeof(m_szAppDir) - 1);
+	else
+		::GetAppDirectory(m_szAppDir, sizeof(m_szAppDir));
+}
+
+CSummarist::~CSummarist(void)
+{
+	Release();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void CSummarist::Release(void)
+{
+	m_vTimeWnds.clear();
+	m_vTimeFrameStart.clear();
+	m_ilTagList.clear();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+bool CSummarist::Initialze(const unsigned long *pTimeWnds, size_t nCntTimeWnds, bool bDropTables)
+{
+	if(!InitTimeWnd(pTimeWnds, nCntTimeWnds))
+		return false;
+
+	if(!InitTables(bDropTables))
+		return false;
+
+	return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+std::string CSummarist::CreateSumTableName(unsigned long tWnd) const
+{
+	return formatString("%s_sum_%lu", m_sup.szLogsTable, tWnd);
+}
+
+std::string CSummarist::CreateSumTableName(int nIndex) const
+{
+	unsigned long tWnd;
+	if((tWnd = GetTimeWindow(nIndex)))
+		return CreateSumTableName(tWnd);
+	return "";
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+time_t CSummarist::GetTimezoneOffset(CMySqlDB &rdb)
+{
+	time_t tzo = _SUM_INVALID_TIMESTAMP_VALUE;
+	CMySqlResult res = rdb.Query("SELECT TIME_TO_SEC(TIMEDIFF(NOW(), UTC_TIMESTAMP()))");
+
+	if(res.error())
+	{
+		m_rlf.Error("CSummarist::GetTimezoneOffset: DB Error: %s\n", rdb.LastError().c_str());
+	}
+	else
+	{
+		CMySqlVar valTZ;
+		my_ulonglong nRowCount		= res.RowCount();
+		unsigned int nFldCount		= res.FieldCount();
+		const MYSQL_FIELD *pFields	= res.FetchFields();
+		MYSQL_ROW pRow				= res.FetchRow();
+
+		if(nRowCount == 1 && nFldCount == 1 && pFields && pRow && valTZ.FromField(pFields[0], pRow[0]))
+			tzo = (time_t)(uint64_t)valTZ;
+	}
+
+	return tzo;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+bool CSummarist::CreateTagList(CMySqlDB &rdb)
+{
+	bool bRet;
+	std::string sSql;
+	m_ilTagList.clear();
+
+	sSql = formatString("SET SESSION group_concat_max_len = %u", _SUM_GROUP_CONCAT_MAX_LEN);
+
+	if(rdb.Query(sSql.c_str()).error())
+	{
+		m_rlf.Error("CSummarist::CreateTagList: DB Error: %s\n", rdb.LastError().c_str());
+		return false;
+	}
+
+	sSql = formatString("SELECT GROUP_CONCAT(`tagid` ORDER BY `tagid` SEPARATOR ',') FROM `%s`", m_sup.szITagsView);
+
+	CMySqlResult res = rdb.Query(sSql.c_str());
+
+	if(!(bRet = !res.error()))
+	{
+		m_rlf.Error("CSummarist::CreateTagList: DB Error: %s\n", rdb.LastError().c_str());
+	}
+	else
+	{
+		my_ulonglong nRowCount		= res.RowCount();
+		unsigned int nFldCount		= res.FieldCount();
+		const MYSQL_FIELD *pFields	= res.FetchFields();
+		MYSQL_ROW pRow				= res.FetchRow();
+
+        if((nRowCount == 1) && (nFldCount == 1) && pFields && (pFields->type != MYSQL_TYPE_BLOB) && pRow)
+            m_ilTagList = pRow[0];
+		else
+			bRet = false;
+	}
+
+	return bRet;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+unsigned long CSummarist::GetTimeWindow(size_t nIndex) const
+{
+	if(nIndex < TimeWndCount())
+		return m_vTimeWnds[nIndex];
+	return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+bool CSummarist::Connect(CMySqlDB &rdb)
+{
+	if(!rdb.Connect("localhost", m_sup.szDBUser, m_sup.szDBPass, NULL))
+		return false;
+	else if(rdb.SelectDB(m_sup.szDBName))
+	{
+		rdb.Close();
+		return false;
+	}
+
+	m_tzo = GetTimezoneOffset(rdb);
+
+	rdb.Query("SET @OLD_FOREIGN_KEY_CHECKS = @@FOREIGN_KEY_CHECKS");
+	rdb.Query("SET FOREIGN_KEY_CHECKS = 0");
+	rdb.Query("SET @OLD_TIME_ZONE = @@TIME_ZONE");
+	return !rdb.Query("SET TIME_ZONE = '+00:00'").error();
+}
+
+void CSummarist::Close(CMySqlDB &rdb)
+{
+	rdb.Query("SET FOREIGN_KEY_CHECKS = @OLD_FOREIGN_KEY_CHECKS");
+	rdb.Query("SET TIME_ZONE = @OLD_TIME_ZONE");
+	rdb.Close();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+bool CSummarist::LockTables(CMySqlDB &rdb, const char *pszTableName)
+{
+    std::string sSql = formatString("LOCK TABLES `%s` READ, `%s` WRITE", m_sup.szLogsTable, pszTableName);
+	return !rdb.Query(sSql.c_str()).error();
+}
+
+bool CSummarist::UnlockTables(CMySqlDB &rdb)
+{
+	return !rdb.Query("UNLOCK TABLES").error();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+bool CSummarist::DropTables(CMySqlDB &rdb)
+{
+	bool bError;
+	std::string tbls;
+	std::string sSql = formatString("SHOW TABLES LIKE '%s_sum_%%'", m_sup.szLogsTable);
+	CMySqlResult res = rdb.Query(sSql.c_str());
+
+	if((bError = res.error()))
+	{
+		m_rlf.Error("CSummarist::DropTables: DB Error: %s\n", rdb.LastError().c_str());
+	}
+	else
+	{
+		MYSQL_ROW pRow;
+
+		if((pRow = res.FetchRow()))
+			tbls = formatString("`%s`", *pRow);
+
+		while((pRow = res.FetchRow()))
+			tbls += formatString(", `%s`", *pRow);
+
+        sSql = formatString("DROP TABLE IF EXISTS %s", tbls.c_str());
+        m_rlf.Warning("Dropping summary tables: %s!\n", tbls.c_str());
+
+		CMySqlResult res = rdb.Query(sSql.c_str());
+
+		if((bError = res.error()))
+            m_rlf.Error("CSummarist::DropTables: DB Error: %s\n", rdb.LastError().c_str());
+	}
+
+	return !bError;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+const char* CSummarist::Timestamp2String(time_t t, char *pszBuffer, size_t nCbBuffer)
+{
+	static char szTimestamp[128];
+
+	if(!pszBuffer)
+	{
+		pszBuffer = szTimestamp;
+		nCbBuffer = sizeof(szTimestamp);
+	}
+
+	const struct tm * ptm = gmtime(&t);
+	strftime(pszBuffer, nCbBuffer, "%F %T", ptm);
+	return pszBuffer;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+time_t CSummarist::ReadTimestampBase(void)
+{
+	char szPath[PATH_MAX];
+	sprintf(szPath, "%s/%s", m_szAppDir, _SUMMARIST_TIMESTAMP_FILE_NAME);
+	time_t ts = _SUM_INVALID_TIMESTAMP_VALUE;
+	FILE *pf = fopen(szPath, "rb");
+
+	if(pf)
+	{
+        if(fread(&ts, sizeof(ts), 1, pf) != 1)
+			ts = _SUM_INVALID_TIMESTAMP_VALUE;
+		fclose(pf);
+	}
+
+	return ts;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+time_t CSummarist::GetBaseTimestamp(time_t tsMinLog)
+{
+	if(m_tsBase == _SUM_INVALID_TIMESTAMP_VALUE)
+	{
+		m_tsBase = ReadTimestampBase();
+		if(m_tsBase == _SUM_INVALID_TIMESTAMP_VALUE)
+		{
+			m_tsBase = tsMinLog - (tsMinLog % _SECS_PER_DAY);
+			WriteTimestampBase(m_tsBase);
+		}
+	}
+	return m_tsBase;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void CSummarist::WriteTimestampBase(time_t ts)
+{
+	char szPath[PATH_MAX];
+	sprintf(szPath, "%s/%s", m_szAppDir, _SUMMARIST_TIMESTAMP_FILE_NAME);
+	FILE *pf = fopen(szPath, "wb");
+
+	if(pf)
+	{
+		fwrite(&ts, sizeof(ts), 1, pf);
+		fclose(pf);
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+bool CSummarist::GetNextTimeFrame(CMySqlDB &rdb, size_t nIndex, time_t tsLast, time_t tsBase, time_t &rtsStart, time_t &rtsEnd) const
+{
+	time_t tsNext, tWnd = (time_t)GetTimeWindow(nIndex);
+
+	if(tWnd)
+	{
+		if(tsLast == _SUM_INVALID_TIMESTAMP_VALUE)
+			tsLast = tsBase;
+		if(	GetNextTimestampFromLogs(rdb, tsLast, tsNext) && (tsNext != _SUM_INVALID_TIMESTAMP_VALUE))
+			rtsStart = tsNext - ((tsNext - tsBase) % tWnd);
+		else
+			rtsStart = tsLast - ((tsLast - tsBase) % tWnd) + tWnd;
+
+		rtsEnd = rtsStart + tWnd;
+		return true;
+	}
+
+	return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+bool CSummarist::SetNextTimeFrameStart(size_t nIndex, time_t tsStart)
+{
+	if(nIndex < (size_t)m_vTimeFrameStart.size())
+	{
+		m_vTimeFrameStart[nIndex] = tsStart;
+		return true;
+	}
+	return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+time_t CSummarist::GetNextDueTimeFrameStart(void) const
+{
+	time_t ts = _SUM_INVALID_TIMESTAMP_VALUE;
+
+	for(auto i = m_vTimeFrameStart.begin(); i != m_vTimeFrameStart.end(); ++i)
+	{
+		time_t t = *i;
+		if((t != _SUM_INVALID_TIMESTAMP_VALUE) && ((ts == _SUM_INVALID_TIMESTAMP_VALUE) || (ts > t)))
+			ts = t;
+	}
+
+	return ts;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+bool CSummarist::InitTables(bool bDropTables)
+{
+	CMySqlDB db;
+	m_rlf.Info("Connecting to database server @'localhost'.\n");
+
+	if(!Connect(db))
+	{
+		m_rlf.Error("CSummarist::InitTables: DB Error: %s\n", db.LastError().c_str());
+		return false;
+	}
+
+	m_rlf.Info("Success!\n");
+
+	if(bDropTables)
+	{
+		return DropTables(db);
+	}
+
+    for(auto i = m_vTimeWnds.begin(); i != m_vTimeWnds.end(); ++i)
+	{
+		unsigned long tWnd = *i;
+		std::string sName = CreateSumTableName(tWnd);
+		std::string sSql = formatString("CREATE TABLE IF NOT EXISTS `%s` LIKE `%s`", sName.c_str(), m_sup.szLogsTable);
+		m_rlf.Info("Creating summary table '%s' (if not exists).\n", sName.c_str());
+
+		CMySqlResult res = db.Query(sSql.c_str());
+		bool bError = res.error();
+
+		if(bError)
+		{
+            m_rlf.Error("CSummarist::InitTables: DB Error: %s\n", db.LastError().c_str());
+			return false;
+		}
+		else
+            m_rlf.Info("Success!\n");
+	}
+
+	std::string sSql = formatString("CREATE OR REPLACE ALGORITHM = MERGE VIEW `%s` AS SELECT `tagid` FROM `%s` WHERE `logType` IN ('IC', 'IU', 'ICR', 'IUR') ORDER BY `tagid`", m_sup.szITagsView, m_sup.szTagsTable);
+	m_rlf.Info("Creating view '%s' (if not exists).\n", m_sup.szITagsView);
+
+	CMySqlResult res = db.Query(sSql.c_str());
+	bool bError = res.error();
+
+	if(bError)
+	{
+        m_rlf.Error("CSummarist::InitTables: DB Error: %s\n", db.LastError().c_str());
+		return false;
+	}
+	else
+        m_rlf.Info("Success!\n");
+
+	if(!CreateTagList(db))
+		return false;
+
+	return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+bool CSummarist::InitTimeWnd(const unsigned long *pTimeWnds, size_t nCntTimeWnds)
+{
+	m_nProcFrequ = 1;
+
+	if(m_sup.nLogIntv % 1000)
+	{
+		m_rlf.Error("Incompatible datalogger log interval: %lu\n", m_sup.nLogIntv);
+		return false;
+	}
+
+	for(size_t i = 0; i < nCntTimeWnds; i++)
+	{
+		m_vTimeWnds.push_back(pTimeWnds[i]);
+		m_vTimeFrameStart.push_back(_SUM_INVALID_TIMESTAMP_VALUE);
+	}
+
+	size_t nCountWnds = m_vTimeWnds.size();
+
+	if(!nCountWnds)
+	{
+		m_rlf.Error("No time window specified!\n");
+		return false;
+	}
+	else if(nCountWnds > 1)
+	{
+		// sort time window sizes ascending
+		std::sort(m_vTimeWnds.begin(), m_vTimeWnds.end());
+	}
+
+	unsigned long tWndMin = m_vTimeWnds[0];
+
+    for(auto i = m_vTimeWnds.begin(); i != m_vTimeWnds.end(); ++i)
+	{
+		unsigned long tWnd = *i;
+		unsigned long nMod = tWnd % _SUM_TIME_WND_BASE;
+
+		if(tWnd < (2 * m_sup.nLogIntv / 1000)) // a time window must at least contain two logs to not being useless!
+		{
+			m_rlf.Error("Time window length (%lu) must be at least a double of the log interval (%lu)!\n", tWnd, m_sup.nLogIntv / 1000);
+			return false;
+		}
+		else if(nMod && (_SUM_TIME_WND_BASE % nMod)) // time window length must be either an integer factor or a multiple of an integer factor of 3600
+		{
+			m_rlf.Error("Time window length must be either an integer factor or a multiple of an integer factor of 3600: %lu\n", tWnd);
+			return false;
+		}
+		else if(tWndMin && (tWnd % tWndMin))
+		{
+			tWndMin = 0;
+		}
+	}
+
+	if(!tWndMin)
+	{
+		// find the greatest common divisor of all time windows. this will be the processing frequency starting at a full hour boundary.
+		for(int j = (int)(_countof(g_nFactors) - 1); j >= 0; --j)
+		{
+			bool bDiv = true;
+			unsigned long nFac = g_nFactors[j];
+
+			for(auto i = m_vTimeWnds.begin(); i != m_vTimeWnds.end(); ++i)
+			{
+				unsigned long tWnd = *i;
+
+				if((nFac > tWnd) || (tWnd % nFac))
+				{
+					bDiv = false;
+					break;
+				}
+			}
+
+			if(bDiv)
+			{
+				m_nProcFrequ = nFac;
+				m_rlf.Info("Processing frequency: %lu\n", m_nProcFrequ);
+				break;
+			}
+		}
+	}
+	else
+	{
+		m_nProcFrequ = tWndMin;
+		m_rlf.Info("Processing frequency: %lu\n", m_nProcFrequ);
+	}
+
+	return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+bool CSummarist::GetLastSummarizeTimestamp(CMySqlDB &rdb, size_t nIndex, time_t &rtsLast) const
+{
+	unsigned long tWnd	= GetTimeWindow(nIndex);
+
+	if(tWnd)
+	{
+		std::string sTbl = CreateSumTableName(tWnd);
+		return GetLastSummarizeTimestamp(rdb, sTbl.c_str(), rtsLast);
+	}
+
+	rtsLast = _SUM_INVALID_TIMESTAMP_VALUE;
+	return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+bool CSummarist::GetLastSummarizeTimestamp(CMySqlDB &rdb, const char *pszTableName, time_t &rtsLast) const
+{
+	rtsLast = _SUM_INVALID_TIMESTAMP_VALUE;
+	std::string sSql = formatString("SELECT UNIX_TIMESTAMP(MAX(`tslog`)) FROM `%s`", pszTableName);
+
+	CMySqlResult res = rdb.Query(sSql.c_str());
+
+	if(res.error())
+	{
+		m_rlf.Error("CSummarist::GetLastSummarizeTimestamp: DB Error: %s\n", rdb.LastError().c_str());
+	}
+	else
+	{
+		do
+		{
+			CMySqlVar val;
+			my_ulonglong nRowCount		= res.RowCount();
+			unsigned int nFldCount		= res.FieldCount();
+			const MYSQL_FIELD *pFields	= res.FetchFields();
+
+			if(nRowCount != 1 || nFldCount != 1 || !pFields)
+			{
+				break;
+			}
+
+			MYSQL_ROW pRow = res.FetchRow();
+
+			if(	!pRow ||
+				!val.FromField(pFields[0], pRow[0]))
+			{
+				break;
+			}
+
+			rtsLast = (time_t)(uint64_t)val;
+		}
+		while(false);
+
+		return true;
+	}
+
+	return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+bool CSummarist::GetMinMaxTimestampFromLogs(CMySqlDB &rdb, time_t &rMin, time_t &rMax) const
+{
+	bool bRet = true;
+	int nCntTs = 0;
+	std::string sSql[2];
+	CMySqlVar val[2];
+
+	rMin = _SUM_INVALID_TIMESTAMP_VALUE;
+	rMax = _SUM_INVALID_TIMESTAMP_VALUE;
+	sSql[0] = formatString("SELECT UNIX_TIMESTAMP(`tslog`) FROM `%s` WHERE (`tagid` IN (%s)) AND (`value` IS NOT NULL) ORDER BY `tslog` ASC LIMIT 1;", m_sup.szLogsTable, m_ilTagList.c_str());
+	sSql[1] = formatString("SELECT UNIX_TIMESTAMP(`tslog`) FROM `%s` WHERE (`tagid` IN (%s)) AND (`value` IS NOT NULL) ORDER BY `tslog` DESC LIMIT 1;", m_sup.szLogsTable, m_ilTagList.c_str());
+
+	for(int i = 0; i < 2; ++i)
+	{
+		CMySqlResult res = rdb.Query(sSql[i].c_str());
+
+		if(res.error())
+		{
+			m_rlf.Error("CSummarist::GetMinMaxTimestampFromLogs: DB Error: %s\n", rdb.LastError().c_str());
+			bRet = false;
+			break;
+		}
+		else
+		{
+			my_ulonglong nRowCount		= res.RowCount();
+			unsigned int nFldCount		= res.FieldCount();
+			const MYSQL_FIELD *pFields	= res.FetchFields();
+			MYSQL_ROW pRow				= res.FetchRow();
+
+			if(nRowCount != 1 || nFldCount != 1 || !pFields || !pRow || !val[i].FromField(pFields[0], pRow[0]))
+				break;
+
+			++nCntTs;
+		}
+	}
+
+	if(bRet && nCntTs == 2)
+	{
+		rMin = (time_t)(uint64_t)val[0];
+		rMax = (time_t)(uint64_t)val[1];
+	}
+
+	return bRet;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+bool CSummarist::GetNextTimestampFromLogs(CMySqlDB &rdb, time_t tsLast, time_t &rNext) const
+{
+    std::string sSql = formatString("SELECT UNIX_TIMESTAMP(`tslog`) FROM `%s` WHERE (`tslog` > '%s') AND (`value` IS NOT NULL) AND (`tagid` IN (%s)) ORDER BY `tslog` ASC LIMIT 1;", m_sup.szLogsTable, Timestamp2String(tsLast), m_ilTagList.c_str());
+	CMySqlResult res = rdb.Query(sSql.c_str());
+
+	if(res.error())
+	{
+		m_rlf.Error("CSummarist::GetNextTimestampFromLogs: DB Error: %s\n", rdb.LastError().c_str());
+		return false;
+	}
+	else
+	{
+		CMySqlVar valNext;
+		my_ulonglong nRowCount		= res.RowCount();
+		unsigned int nFldCount		= res.FieldCount();
+		const MYSQL_FIELD *pFields	= res.FetchFields();
+		MYSQL_ROW pRow				= res.FetchRow();
+
+		if(nRowCount != 1 || nFldCount != 1 || !pFields)
+		{
+			rNext = _SUM_INVALID_TIMESTAMP_VALUE;
+		}
+		else if(!pRow || !valNext.FromField(pFields[0], pRow[0]))
+		{
+			rNext = _SUM_INVALID_TIMESTAMP_VALUE;
+		}
+		else
+		{
+			rNext = (time_t)(uint64_t)valNext;
+		}
+
+		return true;
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+bool CSummarist::Summarize(CMySqlDB &rdb, size_t nIndex, time_t tsFrom, time_t tsTo)
+{
+	if(nIndex >= TimeWndCount())
+	{
+		m_rlf.Error("CSummarist::Summarize: invalid index: %z\n", nIndex);
+		return false;
+	}
+
+#define _SUM_FMT_STRING				"REPLACE INTO `%s` " \
+									"SELECT `tagid`, MAX(`tslog`) `tslog`, AVG(`value`) `value`, MIN(`valueMin`) `valueMin`, MAX(`valueMax`) `valueMax` FROM `%s` " \
+									"WHERE (`tslog` BETWEEN '%s' AND '%s') AND (`value` IS NOT NULL) AND (`%s`.`tagid` IN (%s)) " \
+									"GROUP BY `%s`.`tagid`"
+
+	char szFrom[64], szTo[64];
+	std::string sTName = CreateSumTableName((int)nIndex);
+	Timestamp2String(tsFrom, szFrom, sizeof(szFrom));
+	Timestamp2String(tsTo - 1, szTo, sizeof(szTo));
+	std::string sSql = formatString(_SUM_FMT_STRING, sTName.c_str(), m_sup.szLogsTable, szFrom, szTo, m_sup.szLogsTable, m_ilTagList.c_str(), m_sup.szLogsTable);
+	std::string sTbl = formatString("%s, %s", sTName.c_str(), m_sup.szLogsTable);
+
+
+    TRACE("%-16s- Summarize: %s <= logs <  %s\n", sTName.c_str(), szFrom, Timestamp2String(tsTo));
+    if(!LockTables(rdb, sTName.c_str()))
+	{
+		m_rlf.Error("CSummarist::Summarize: DB Error: %s\n", rdb.LastError().c_str());
+		return false;
+	}
+
+	CMySqlResult res = rdb.Query(sSql.c_str());
+
+	if(res.error())
+	{
+		m_rlf.Error("CSummarist::Summarize: DB Error: %s\n", rdb.LastError().c_str());
+		return false;
+	}
+
+	UnlockTables(rdb);
+    return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+bool CSummarist::ProcessOutdated(CMySqlDB &rdb, size_t nIndex, time_t tsUpTo)
+{
+	if(nIndex >= TimeWndCount())
+	{
+		m_rlf.Error("CSummarist::ProcessOutdated: invalid index: %z\n", nIndex);
+		return false;
+	}
+
+	char szUpTo[64];
+	std::string sTName = CreateSumTableName((int)nIndex);
+	Timestamp2String(tsUpTo, szUpTo, sizeof(szUpTo));
+	std::string sSql = formatString("DELETE FROM `%s` where `tslog` < '%s'", sTName.c_str(), szUpTo);
+
+	CMySqlResult res = rdb.Query(sSql.c_str());
+
+	if(res.error())
+	{
+		m_rlf.Error("CSummarist::ProcessOutdated: DB Error: %s\n", rdb.LastError().c_str());
+		return false;
+	}
+
+    TRACE("%-16s- ProcessOutdated: sumlogs < %s\n", sTName.c_str(), szUpTo);
+	return true;
+}

+ 107 - 0
summarist/summarist.h

@@ -0,0 +1,107 @@
+// summarist.h :
+//
+
+#if !defined(AGD_SUMMARIST_H__0BCA7040_8648_4F3A_9C89_694B8C36DEC9__INCLUDED_)
+#define AGD_SUMMARIST_H__0BCA7040_8648_4F3A_9C89_694B8C36DEC9__INCLUDED_
+
+#include <algorithm>    // std::sort
+#include <vector>       // std::vector
+#ifndef _LIBBUILD
+#include <gfa/svc/common/mysqlwrap.h>
+#include <gfa/svc/common/logfile.h>
+#else	//	_LIBBUILD
+#include "common/mysqlwrap.h"
+#include "common/logfile.h"
+#endif	//	_LIBBUILD
+
+/////////////////////////////////////////////////////////////////////////////
+// summarist.h - Declarations:
+
+#define _countof(a)						(sizeof(a) / sizeof(*a))
+#define _SUM_INVALID_TIMESTAMP_VALUE	((time_t)-1)
+#define _SUM_TIME_WND_BASE				3600
+
+#define _SUM_MAX_DB_NAME_LENGTH			64
+#define _SUM_MAX_DB_USER_LENGTH			64
+#define _SUM_MAX_DB_PASS_LENGTH			64
+#define _SUM_MAX_TABLE_NAME_LENGTH		64
+#define _SUM_GROUP_CONCAT_MAX_LEN		65536
+
+/////////////////////////////////////////////////////////////////////////////
+
+typedef struct _SUMMARIST_PARAMS
+{
+	char szDBName[_SUM_MAX_DB_NAME_LENGTH];
+	char szDBUser[_SUM_MAX_DB_USER_LENGTH];
+	char szDBPass[_SUM_MAX_DB_PASS_LENGTH];
+	char szTagsTable[_SUM_MAX_TABLE_NAME_LENGTH];
+	char szLogsTable[_SUM_MAX_TABLE_NAME_LENGTH];
+	char szITagsView[_SUM_MAX_TABLE_NAME_LENGTH];
+	const char *pszBaseDir;
+	unsigned long nSampleIntv;
+	unsigned long nLogIntv;
+	unsigned long nWriteIntv;
+}SUMMARIST_PARAMS, *LPSUMMARIST_PARAMS;
+typedef const SUMMARIST_PARAMS *LPCSUMMARIST_PARAMS;
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CSummarist
+{
+public:
+	CSummarist(LPCSUMMARIST_PARAMS psup, CLogfile &rlf);
+	virtual ~CSummarist(void);
+
+	bool Initialze(const unsigned long *pTimeWnds, size_t nCntTimeWnds, bool bDropTables = false);
+	void Release(void);
+	bool Connect(CMySqlDB &rdb);
+	void Close(CMySqlDB &rdb);
+	
+	inline unsigned long GetProcessingFrequenzy(void) const {
+		return m_nProcFrequ;}
+	inline size_t TimeWndCount(void) const {
+		return m_vTimeWnds.size();}
+	inline size_t TimeZoneOffset(void) const {
+		return m_tzo;}
+	unsigned long GetTimeWindow(size_t nIndex) const;
+	bool GetMinMaxTimestampFromLogs(CMySqlDB &rdb, time_t &rMin, time_t &rMax) const;
+	bool GetNextTimestampFromLogs(CMySqlDB &rdb, time_t tsLast, time_t &rNext) const;
+	std::string CreateSumTableName(unsigned long tWnd) const;
+	std::string CreateSumTableName(int nIndex) const;
+	bool GetLastSummarizeTimestamp(CMySqlDB &rdb, const char *pszTableName, time_t &rtsLast) const;
+	bool GetLastSummarizeTimestamp(CMySqlDB &rdb, size_t nIndex, time_t &rtsLast) const;
+	time_t GetBaseTimestamp(time_t tsMinLog);
+	bool GetNextTimeFrame(CMySqlDB &rdb, size_t nIndex, time_t tsLast, time_t tsBase, time_t &rtsStart, time_t &rtsEnd) const;
+	bool SetNextTimeFrameStart(size_t nIndex, time_t tsStart);
+	time_t GetNextDueTimeFrameStart(void) const;
+	bool Summarize(CMySqlDB &rdb, size_t nIndex, time_t tsFrom, time_t tsTo);
+	bool ProcessOutdated(CMySqlDB &rdb, size_t nIndex, time_t tsUpTo);
+
+public:
+	static const char* Timestamp2String(time_t t, char *pszBuffer = NULL, size_t nCbBuffer = 0);
+
+private:
+	bool InitTimeWnd(const unsigned long *pTimeWnds, size_t nCntTimeWnds);
+	bool InitTables(bool bDropTables);
+	bool DropTables(CMySqlDB &rdb);
+    bool LockTables(CMySqlDB &rdb, const char *pszTableName);
+	bool UnlockTables(CMySqlDB &rdb);
+	time_t GetTimezoneOffset(CMySqlDB &rdb);
+	bool CreateTagList(CMySqlDB &rdb);
+	time_t ReadTimestampBase(void);
+	void WriteTimestampBase(time_t ts);
+
+private:
+	CLogfile &m_rlf;
+	std::vector<unsigned long> m_vTimeWnds;
+	std::vector<time_t> m_vTimeFrameStart;
+	std::string m_ilTagList;
+	unsigned long m_nProcFrequ;
+	SUMMARIST_PARAMS m_sup;
+    char m_szAppDir[PATH_MAX];
+    time_t m_tzo;
+    time_t m_tsBase;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	!defined(AGD_SUMMARIST_H__0BCA7040_8648_4F3A_9C89_694B8C36DEC9__INCLUDED_)

+ 55 - 0
summarist/summarist.pro

@@ -0,0 +1,55 @@
+TEMPLATE = lib
+CONFIG   += c++11
+CONFIG   -= qt
+CONFIG   += staticlib
+
+QMAKE_CXXFLAGS_DEBUG += -Wno-unused-parameter -Wno-unused-but-set-variable -Wno-old-style-cast -pthread
+QMAKE_CXXFLAGS += -Wno-old-style-cast -Wstrict-aliasing=0 -pthread
+# QMAKE_LIBS += -lm -lstdc++ -pthread -lgfaipc -l:libcommon.a -lmysqlclient
+# QMAKE_LIBDIR += $$OUT_PWD/../common $$[QT_SYSROOT]/usr/lib/gfa
+QMAKE_RPATHDIR += /usr/lib/gfa
+
+QMAKE_CXXFLAGS += -D_DL_SUMMARIST -D_LIBBUILD -Wno-format-overflow
+QMAKE_CFLAGS += -D_DL_SUMMARIST -D_LIBBUILD -Wno-format-overflow
+
+_LIB_PATH = $$[QT_SYSROOT]/usr/lib/gfa/svc/
+_INC_PATH = $$[QT_SYSROOT]/usr/include/gfa/svc/summarist/
+
+CONFIG(debug, debug|release) {
+	QMAKE_CXXFLAGS -= -Os
+	QMAKE_CFLAGS -= -Os
+    QMAKE_CXXFLAGS += -D_DEBUG
+    QMAKE_CFLAGS += -D_DEBUG
+	TARGET = summaristd
+	QMAKE_CLEAN += libsummaristd.a
+}
+
+CONFIG(release, debug|release) {
+    QMAKE_CXXFLAGS += -Wno-unused-parameter -Wno-unused-but-set-variable
+    QMAKE_CFLAGS += -Wno-unused-parameter -Wno-unused-but-set-variable
+	TARGET = summarist
+	QMAKE_CLEAN += libsummarist.a
+}
+
+linux-buildroot-g++ {
+    QMAKE_CXXFLAGS += -D_TARGET_BUILD
+    QMAKE_CFLAGS += -D_TARGET_BUILD
+    QMAKE_CXXFLAGS += -DSITARA_BUILD
+    QMAKE_CFLAGS += -DSITARA_BUILD
+}
+
+INCLUDEPATH += ../  ../common $$(GEBGFADEV)
+
+SOURCES += summarist.cpp
+
+HEADERS += summarist.h
+
+includes.path = $$_INC_PATH
+includes.extra += -$(INSTALL_FILE) $$PWD/summarist.h $(INSTALL_ROOT)$$includes.path
+includes.uninstall += -$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/summarist.h
+INSTALLS += includes
+
+library.path = $$_LIB_PATH
+library.extra += -$(INSTALL_FILE) $(TARGET) $(INSTALL_ROOT)$$library.path/$(TARGET)
+library.uninstall += -$(DEL_FILE) $(INSTALL_ROOT)$$library.path/$(TARGET)
+INSTALLS += library