Bladeren bron

Änderungen und Fixes an Datalogger, Summarist, Rest und MqttCl.

Rind 3 jaren geleden
bovenliggende
commit
7bf761d149
17 gewijzigde bestanden met toevoegingen van 583 en 1858 verwijderingen
  1. 1 0
      .gitignore
  2. 37 5
      datalogger/datalogger.cpp
  3. 0 538
      datalogger/main.cpp
  4. 18 6
      mqttcl/cfg/mqtt.conf.json
  5. 0 1202
      mqttcl/main.cpp
  6. 142 2
      mqttcl/mqttcfg.cpp
  7. 99 5
      mqttcl/mqttcfg.h
  8. 4 5
      mqttcl/mqttclient.cpp
  9. 9 1
      mqttcl/mqttvar.h
  10. 2 7
      rest/.gitignore
  11. 124 1
      rest/helpers.cpp
  12. 2 0
      rest/helpers.h
  13. 73 0
      rest/plugin.h
  14. 13 11
      rest/rest.pro
  15. 58 43
      rest/restvar.h
  16. 0 31
      rest/restvartbl.cpp
  17. 1 1
      summarist/summarist.cpp

+ 1 - 0
.gitignore

@@ -10,3 +10,4 @@ Profile/
 Release/
 *.pro.user*
 buildall.sh
+*.bak

+ 37 - 5
datalogger/datalogger.cpp

@@ -1,4 +1,5 @@
 #include <string.h>
+#include <cmath>
 #include <malloc.h>
 #include <limits.h>
 #include <signal.h>
@@ -19,6 +20,14 @@
 
 /////////////////////////////////////////////////////////////////////////////
 
+inline static bool _IsLoggable(double d)
+{
+	int c = std::fpclassify(d);
+	return (c == FP_NORMAL) || (c == FP_ZERO);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
 CDataLogger::CDataLogger(LPCDLPARAMS pdlp, CLogfile &rlf) : m_lf(rlf),
 															m_tidSGThread(0),
 															m_bSGHasSizeLimitPrerequisites(false),
@@ -659,7 +668,7 @@ bool CDataLogger::CreateLogsTable(CMySqlDB &rdb)
 	"	`valueMin` double DEFAULT NULL," \
 	"	`valueMax` double DEFAULT NULL," \
 	"	PRIMARY KEY (`tslog`, `tagid`)," \
-	"	KEY `tagid` (`tagid`)" \
+	"	KEY `taglog` (`tagid`, `tslog`)" \
 	") ENGINE=%s DEFAULT CHARSET=ascii ROW_FORMAT=COMPRESSED";
 #else //	_USE_MODIFIED_INDEX
 	"CREATE TABLE IF NOT EXISTS `%s` (" \
@@ -724,6 +733,8 @@ bool CDataLogger::CreateLogsBDTable(CMySqlDB &rdb)
 
 bool CDataLogger::Log(unsigned long nTagID, double fValue, double fMin, double fMax, time_t nTimestamp, int nIndex, LogTypes lt, bool bNull, bool bNoBadDateCheck)
 {
+	bNull = bNull || !_IsLoggable(fValue) || !_IsLoggable(fMin) || !_IsLoggable(fMax);
+
 	DL_LOG_ENTRY log;
 	log.nTagID		= nTagID;
 	log.nTimestamp	= nTimestamp;
@@ -738,6 +749,7 @@ bool CDataLogger::Log(unsigned long nTagID, double fValue, double fMin, double f
 		m_logs.push_back(log);
 	else
 		m_logsBD.push_back(log);
+
 	return true;
 }
 
@@ -863,7 +875,12 @@ bool CDataLogger::Flush(time_t 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);
+		{
+			if(!rle0.bNull)
+				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
+				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);
+		}
 		else
 		{
 			if(!rle0.bNull)
@@ -880,7 +897,12 @@ bool CDataLogger::Flush(time_t 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);
+			{
+				if(!rle0.bNull)
+					sSql = formatString(",(%lu, timestamp(from_unixtime(%lu)), %.20g, %.20g, %.20g)", rle.nTagID, rle.nTimestamp, rle.fValue, rle.fMin, rle.fMax);
+				else
+					sSql = formatString(",(%lu, timestamp(from_unixtime(%lu)), NULL, NULL, NULL)", rle.nTagID, rle.nTimestamp);
+			}
 			else
 			{
 				if(!rle.bNull)
@@ -1014,7 +1036,12 @@ bool CDataLogger::Flush(time_t nTimestamp)
 		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);
+		{
+			if(!rle0.bNull)
+				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
+				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);
+		}
 		else
 		{
 			if(!rle0.bNull)
@@ -1029,7 +1056,12 @@ bool CDataLogger::Flush(time_t nTimestamp)
 			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);
+			{
+				if(!rle.bNull)
+					sSql = formatString(",(%lu, timestamp(from_unixtime(%lu)), %.20g, %.20g, %.20g)", rle.nTagID, rle.nTimestamp, rle.fValue, rle.fMin, rle.fMax);
+				else
+					sSql = formatString(",(%lu, timestamp(from_unixtime(%lu)), NULL, NULL, NULL)", rle.nTagID, rle.nTimestamp);
+			}
 			else
 			{
 				if(!rle.bNull)

+ 0 - 538
datalogger/main.cpp

@@ -1,538 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////////////////////////
-//
-
-#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;
-}

+ 18 - 6
mqttcl/cfg/mqtt.conf.json

@@ -1,12 +1,24 @@
 {
-	"brokerAddr":		"herrschuster.local",
+	"brokerAddr":		"server.addr",
 	"brokerPort":		8883,
 	"defaultQos":		2,
 	"defaultRetain":	false,
-	"devicePrefix":		"VarioNet",
+	"devicePrefix":		"MyClient",
 	"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"
+	"tlsCaCrtFile":		"/path/to/ca.crt",
+	"tlsClCrtFile":		"/path/to/client.crt",
+	"tlsClKeyFile":		"/path/to/client.key",
+	"tlsPsk":			"1234567890abcdef",
+	"lastWillTopic":	"LAST_WILL_CONNECT",
+	"lastWillMessage":	"X",
+	"lastWillQos":		2,
+	"lastWillRetain":	true,
+	"lastWillOnExit":	true,
+	"lastWillOnExitMsg": "E",
+	"connectTopic":		"LAST_WILL_CONNECT",
+	"connectMessage":	"C",
+	"connectQos":		2,
+	"connectRetain":	true,
+	"maxKeepAlive":		10,
+	"topicPrefix":		"client1"
 }

+ 0 - 1202
mqttcl/main.cpp

@@ -1,1202 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////////////////////////
-//
-
-#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;
-}

+ 142 - 2
mqttcl/mqttcfg.cpp

@@ -20,10 +20,35 @@
 #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"
+#define _CFG_KEY_NAME_LAST_WILL_MESSAGE			"lastWillMessage"
+#define _CFG_KEY_NAME_LAST_WILL_TOPIC			"lastWillTopic"
+#define _CFG_KEY_NAME_LAST_WILL_QOS				"lastWillQos"
+#define _CFG_KEY_NAME_LAST_WILL_RETAIN			"lastWillRetain"
+#define _CFG_KEY_NAME_LAST_WILL_ON_EXIT			"lastWillOnExit"
+#define _CFG_KEY_NAME_LAST_WILL_ON_EXIT_MSG		"lastWillOnExitMsg"
+#define _CFG_KEY_NAME_CONNECT_MESSAGE			"connectMessage"
+#define _CFG_KEY_NAME_CONNECT_TOPIC				"connectTopic"
+#define _CFG_KEY_NAME_CONNECT_QOS				"connectQos"
+#define _CFG_KEY_NAME_CONNECT_RETAIN			"connectRetain"
+#define _CFG_KEY_NAME_MAX_KEEP_ALIVE			"maxKeepAlive"
+#define _CFG_KEY_NAME_PREFIX_STRING				"topicPrefix"
 
 /////////////////////////////////////////////////////////////////////////////
 
-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(const char *pszShmUuid) :	m_strShmID(formatString("SHM-%s", strucase(pszShmUuid).c_str())),
+														m_nBrokerPort(0),
+														m_nDefaultQOS(0),
+														m_bDefaultRetain(MQTTCL_DEFAULT_RETAIN),
+														m_nLastWillQos(m_nDefaultQOS),
+														m_bLastWillRetain(MQTTCL_DEFAULT_RETAIN),
+														m_bHasLastWill(false),
+														m_bLastWillOnExit(false),
+														m_nConnectQos(m_nDefaultQOS),
+														m_bConnectRetain(MQTTCL_DEFAULT_RETAIN),
+														m_bHasConnect(false),
+														m_bHasPrefix(false),
+														m_nTlsMode(0),
+														m_nMaxKeepAlive(MQTTCL_DEFAULT_MAX_KEEP_ALIVE_TIME)
 {
 }
 
@@ -128,6 +153,112 @@ bool CMqttClConfig::LoadCfg(const char *pszCfgFilePath, CLogfile &rlf)
 		rlf.Warning("CMqttClConfig::LoadCfg: %s! Using default retain \"%s\"!\n", strErr.c_str(), m_bDefaultRetain ? "true" : "false");
 	}
 
+	/////////////////////////////////////////////////////////////////////////
+	// lastWillMessage
+
+	if(!GetStringValue(jtCfg, _CFG_KEY_NAME_LAST_WILL_MESSAGE, m_strLastWillMessage, strErr))
+	{
+		rlf.Info("CMqttClConfig::LoadCfg: %s! No Last Will Message provided.\n", strErr.c_str());
+	}
+
+	if((m_bHasLastWill = !m_strLastWillMessage.empty()))
+	{
+		/////////////////////////////////////////////////////////////////////
+		// lastWillTopic
+
+		if(!GetStringValue(jtCfg, _CFG_KEY_NAME_LAST_WILL_TOPIC, m_strLastWillTopic, strErr))
+		{
+			m_strLastWillTopic = MQTTCL_DEFAULT_LAST_WILL_CONNECT_TOPIC;
+			rlf.Info("CMqttClConfig::LoadCfg: %s! Setting Last Will Topic to default: \"%s\".\n", strErr.c_str()), m_strLastWillTopic.c_str();
+		}
+
+		/////////////////////////////////////////////////////////////////////
+		// lastWillQos
+
+		if(!GetIntValue(jtCfg, _CFG_KEY_NAME_LAST_WILL_QOS, m_nLastWillQos, strErr))
+		{
+			m_nLastWillQos = m_nDefaultQOS;
+			rlf.Warning("CMqttClConfig::LoadCfg: %s! Using default Last Will Qos: %d!\n", strErr.c_str(), m_nLastWillQos);
+		}
+
+		/////////////////////////////////////////////////////////////////////
+		// lastWillRetain
+
+		if(!GetBoolValue(jtCfg, _CFG_KEY_NAME_LAST_WILL_RETAIN, m_bLastWillRetain, strErr))
+		{
+			m_bLastWillRetain = MQTTCL_DEFAULT_RETAIN;
+			rlf.Warning("CMqttClConfig::LoadCfg: %s! Using default Last Will retain: %s!\n", strErr.c_str(), m_bLastWillRetain ? "true" : "false");
+		}
+
+		/////////////////////////////////////////////////////////////////////
+		// lastWillOnExit
+
+		if(!GetBoolValue(jtCfg, _CFG_KEY_NAME_LAST_WILL_ON_EXIT, m_bLastWillOnExit, strErr))
+		{
+			m_bLastWillOnExit = false;
+			rlf.Warning("CMqttClConfig::LoadCfg: %s! No Last Will on Exit.\n", strErr.c_str());
+		}
+		
+		if(m_bLastWillOnExit)
+		{
+			/////////////////////////////////////////////////////////////////
+			// lastWillOnExitMsg
+
+			if(!GetStringValue(jtCfg, _CFG_KEY_NAME_LAST_WILL_ON_EXIT_MSG, m_strLastWillOnExitMsg, strErr))
+			{
+				m_strLastWillOnExitMsg = m_strLastWillMessage;
+				rlf.Info("CMqttClConfig::LoadCfg: %s! Setting Last Will Exit Message to default: \"%s\".\n", strErr.c_str()), m_strLastWillOnExitMsg.c_str();
+			}
+		}
+	}
+
+	/////////////////////////////////////////////////////////////////////////
+	// connectMessage
+
+	if(!GetStringValue(jtCfg, _CFG_KEY_NAME_CONNECT_MESSAGE, m_strConnectMessage, strErr))
+	{
+		rlf.Info("CMqttClConfig::LoadCfg: %s! No Connect Message provided.\n", strErr.c_str());
+	}
+
+	if((m_bHasConnect = !m_strConnectMessage.empty()))
+	{
+		/////////////////////////////////////////////////////////////////////
+		// connectTopic
+
+		if(!GetStringValue(jtCfg, _CFG_KEY_NAME_CONNECT_TOPIC, m_strConnectTopic, strErr))
+		{
+			m_strConnectTopic = m_strLastWillTopic;
+			rlf.Info("CMqttClConfig::LoadCfg: %s! Setting Connect Topic to default: \"%s\".\n", strErr.c_str()), m_strLastWillTopic.c_str();
+		}
+
+		/////////////////////////////////////////////////////////////////////
+		// connectQos
+
+		if(!GetIntValue(jtCfg, _CFG_KEY_NAME_CONNECT_QOS, m_nConnectQos, strErr))
+		{
+			m_nConnectQos = m_nLastWillQos;
+			rlf.Warning("CMqttClConfig::LoadCfg: %s! Using default Connect Qos: %d!\n", strErr.c_str(), m_nConnectQos);
+		}
+
+		/////////////////////////////////////////////////////////////////////
+		// connectRetain
+
+		if(!GetBoolValue(jtCfg, _CFG_KEY_NAME_CONNECT_RETAIN, m_bConnectRetain, strErr))
+		{
+			m_bConnectRetain = m_bLastWillRetain;
+			rlf.Warning("CMqttClConfig::LoadCfg: %s! Using default Last Will retain: %s!\n", strErr.c_str(), m_bConnectRetain ? "true" : "false");
+		}
+	}
+
+	/////////////////////////////////////////////////////////////////////////
+	// maxKeepAlive
+
+	if(!GetIntValue(jtCfg, _CFG_KEY_NAME_MAX_KEEP_ALIVE, m_nMaxKeepAlive, strErr))
+	{
+		m_nMaxKeepAlive = MQTTCL_DEFAULT_MAX_KEEP_ALIVE_TIME;
+		rlf.Warning("CMqttClConfig::LoadCfg: %s! Using default Keep-alive time: %d!\n", strErr.c_str(), m_nMaxKeepAlive);
+	}
+
 	/////////////////////////////////////////////////////////////////////////
 	// devicePrefix
 
@@ -149,6 +280,14 @@ bool CMqttClConfig::LoadCfg(const char *pszCfgFilePath, CLogfile &rlf)
 		m_strDeviceID = CreateDeviceID(m_strDevicePrefix.c_str());
 	}
 
+	/////////////////////////////////////////////////////////////////////////
+	// topicPrefix string
+
+	if(!GetStringValue(jtCfg, _CFG_KEY_NAME_PREFIX_STRING, m_strPrefix, strErr))
+	{
+		m_strPrefix = formatString("%s/%s", GetDeviceID(), GetShmID());
+	}
+
 	/////////////////////////////////////////////////////////////////////////
 	// m_nTlsMode == (MQTTCL_TLS_MODE_CRT || MQTTCL_TLS_MODE_PSK)
 
@@ -264,12 +403,13 @@ bool CMqttClConfig::GetStringValue(CJson_t &rjtParent, const char *pszKey, std::
 		if(json_is_string(jtVal.operator const json_t*()))
 		{
 			rstrVal = ::json_string_value(jtVal);
-			return true;
+			return !rstrVal.empty();
 		}
 
 		strErr = formatString("\"%s\" (type=%d) is not a string value", pszKey, jtVal.Type());
 	}
 
+	rstrVal.clear();
 	return false;
 }
 

+ 99 - 5
mqttcl/mqttcfg.h

@@ -35,6 +35,9 @@
 #define MQTTCL_DEFAULT_RETAIN	false
 #define MQTTCL_DEVICE_PREFIX	"GfA"
 
+#define MQTTCL_DEFAULT_LAST_WILL_CONNECT_TOPIC				"LAST_WILL_CONNECT"
+#define MQTTCL_DEFAULT_MAX_KEEP_ALIVE_TIME					60
+
 /////////////////////////////////////////////////////////////////////////////
 
 class CMqttClConfig
@@ -42,25 +45,25 @@ 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;
 	}
@@ -93,6 +96,78 @@ public:
 		return m_strShmID.empty() ? NULL : m_strShmID.c_str();
 	}
 
+	inline const char* GetPrefix(void) const {
+		return m_strPrefix.empty() ? NULL : m_strPrefix.c_str();
+	}
+
+	inline const char* GetLastWillTopic(void) const {
+		return m_strLastWillTopic.empty() ? NULL : m_strLastWillTopic.c_str();
+	}
+
+	inline const char* GetLastWillMessage(void) const {
+		return m_strLastWillMessage.empty() ? NULL : m_strLastWillMessage.c_str();
+	}
+
+	inline int GetLastWillMessageLength(void) const {
+		return (int)m_strLastWillMessage.length();
+	}
+
+	inline int GetLastWillQOS(void) const {
+		return m_nLastWillQos;
+	}
+
+	inline bool GetLastWillRetain(void) const {
+		return m_bLastWillRetain;
+	}
+
+	inline bool HasLastWill(void) const {
+		return m_bHasLastWill;
+	}
+
+	inline bool HasLastWillOnExit(void) const {
+		return m_bLastWillOnExit;
+	}
+
+	inline const char* GetLastWillOnExitMessage(void) const {
+		return m_strLastWillOnExitMsg.empty() ? NULL : m_strLastWillOnExitMsg.c_str();
+	}
+
+	inline int GetLastWillOnExitMessageLength(void) const {
+		return (int)m_strLastWillOnExitMsg.length();
+	}
+
+	inline const char* GetConnectTopic(void) const {
+		return m_strConnectTopic.empty() ? NULL : m_strConnectTopic.c_str();
+	}
+
+	inline const char* GetConnectMessage(void) const {
+		return m_strConnectMessage.empty() ? NULL : m_strConnectMessage.c_str();
+	}
+
+	inline int GetConnectMessageLength(void) const {
+		return (int)m_strConnectMessage.length();
+	}
+
+	inline int GetConnectQOS(void) const {
+		return m_nConnectQos;
+	}
+
+	inline bool GetConnectRetain(void) const {
+		return m_bConnectRetain;
+	}
+
+	inline bool HasConnectMsg(void) const {
+		return m_bHasConnect;
+	}
+
+	inline bool HasPrefix(void) const {
+		return m_bHasPrefix;
+	}
+	
+	inline int GetKeepAliveTime(void) const {
+		return m_nMaxKeepAlive;
+	}
+
 	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);
@@ -112,10 +187,29 @@ private:
 	std::string	m_strTlsPSK;
 	std::string	m_strDeviceID;
 	std::string	m_strShmID;
+	std::string	m_strPrefix;
 	int			m_nBrokerPort;
 	int			m_nDefaultQOS;
 	bool		m_bDefaultRetain;
+
+	std::string	m_strLastWillTopic;
+	std::string	m_strLastWillMessage;
+	int			m_nLastWillQos;
+	bool		m_bLastWillRetain;
+	bool		m_bHasLastWill;
+	bool		m_bLastWillOnExit;
+	std::string	m_strLastWillOnExitMsg;
+
+	std::string	m_strConnectTopic;
+	std::string	m_strConnectMessage;
+	int			m_nConnectQos;
+	bool		m_bConnectRetain;
+	bool		m_bHasConnect;
+	
+	bool		m_bHasPrefix;
+
 	int			m_nTlsMode;
+	int			m_nMaxKeepAlive;
 };
 
 /////////////////////////////////////////////////////////////////////////////

+ 4 - 5
mqttcl/mqttclient.cpp

@@ -21,11 +21,10 @@ static T _min(T v1, T v2)
 /////////////////////////////////////////////////////////////////////////////
 //
 
-CMqttClient::CMqttClient(const char *id) :
-							mosquittopp(id),
-							m_pEvtCallback(NULL),
-							m_evtMask(0),
-							m_pUserParam(NULL)
+CMqttClient::CMqttClient(const char *id) :	mosquittopp(id),
+											m_pEvtCallback(NULL),
+											m_evtMask(0),
+											m_pUserParam(NULL)
 {
 }
 

+ 9 - 1
mqttcl/mqttvar.h

@@ -358,7 +358,15 @@ protected:
 
 	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);
+		if((!pszTopicDevID || !*pszTopicDevID) && (!pszTopicShmID && !*pszTopicShmID))
+			return "";
+		else if(pszTopicDevID && *pszTopicDevID && pszTopicShmID && *pszTopicShmID)
+			return ::formatString("%s/%s/%s/%s%s", pszTopicDevID, pszTopicShmID, pszValueFormat, pszTopicCmd, m_pszPath);
+		else
+		{
+			const char *pszTopic = (pszTopicDevID && *pszTopicDevID) ? pszTopicDevID : pszTopicShmID;
+			return ::formatString("%s/%s/%s%s", pszTopic, pszValueFormat, pszTopicCmd, m_pszPath);
+		}
 	}
 
 	void Lock(int &nLocks)

+ 2 - 7
rest/.gitignore

@@ -1,8 +1,3 @@
-# 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
+*.bak
+/html/

+ 124 - 1
rest/helpers.cpp

@@ -1,9 +1,132 @@
+#include <dlfcn.h>
+#include <dirent.h>
 #include "helpers.h"
 #include "callback.h"
+#include "plugin.h"
+
+/////////////////////////////////////////////////////////////////////////////
+// {75ed2211-0a25-4a55-962b-31d3b0e6123a}
+DEFINE_UUID(UUID_REST_PLUGIN,
+	0x75ed2211, 0x0a25, 0x4a55, 0x96, 0x2b, 0x31, 0xd3, 0xb0, 0xe6, 0x12, 0x3a);
+
+/////////////////////////////////////////////////////////////////////////////
+
+typedef struct _PLUGIN
+{
+	std::string sName;
+	void *hLib;
+}PLUGIN;
 
 /////////////////////////////////////////////////////////////////////////////
 
 static std::vector<FILEREQUEST> g_afr;
+static std::vector<PLUGIN> g_aPlugins;
+
+/////////////////////////////////////////////////////////////////////////////
+
+static bool _IsSharedLib(const char *pszFilename)
+{
+	char *pSave = nullptr, szFilename[PATH_MAX], *pszTok;
+	strcpy(szFilename, pszFilename);
+	
+	if((pszTok = strtok_r(szFilename, ".", &pSave)))
+	{
+		while((pszTok = strtok_r(nullptr, ".", &pSave)))
+		{
+			if(!strcmp(pszTok, "so"))
+				return true;
+		}
+	}
+
+	return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+int LoadPlugins(const char *pszRootDir, struct _u_instance *pInst, json_error_t &rJerr, void *pCtx)
+{
+	DIR *d;
+	int nLoaded = 0;
+	struct dirent *dir;
+	char szPluginDir[PATH_MAX + 32];
+	char szPluginLib[PATH_MAX + 32 + 256];
+    sprintf(szPluginDir, "%s/plugin", pszRootDir);
+
+	if((d = opendir(szPluginDir)))
+	{
+		while((dir = readdir(d)))
+		{
+			if((dir->d_type == DT_REG) &&
+				_IsSharedLib(dir->d_name))
+			{
+			    sprintf(szPluginLib, "%s/plugin/%s", pszRootDir, dir->d_name);
+				void *hLib = dlopen(szPluginLib, RTLD_NOW);	
+
+				if(hLib)
+				{
+					PFN_GETPLUGININFO GetPluginInfo = (PFN_GETPLUGININFO)dlsym(hLib, "GetPluginInfo");
+					
+					if(GetPluginInfo)
+					{
+						const REST_PLUGIN_INFO *ppi = nullptr;
+
+						if((ppi = GetPluginInfo()))
+						{
+							if(!_uuid_compare(&UUID_REST_PLUGIN, &ppi->signature))
+							{
+								PLUGIN plugin;
+								plugin.sName = ppi->pszPluginName;
+								plugin.hLib = hLib;
+								g_aPlugins.push_back(plugin);
+								
+								for(size_t i = 0; i < ppi->nHandlerCnt; ++i)
+								{
+									const REST_REQUEST_HANDLER &rh = ppi->handler[i];
+									PFN_REST_REQUEST_HANDLER RequestHandler = (PFN_REST_REQUEST_HANDLER)dlsym(hLib, rh.pszFunctionName);
+									
+									if(RequestHandler)
+									{
+										if(ulfius_add_endpoint_by_val(pInst, rh.pszMethod, rh.pszVUrl, nullptr, 0, RequestHandler, pCtx) == U_OK)
+											++nLoaded;
+									}
+								}
+							}
+							else
+							{
+								dlclose(hLib);
+							}
+						}
+						else
+						{
+							dlclose(hLib);
+						}
+					}
+					else
+					{
+						dlclose(hLib);
+					}
+				}
+			}
+		}
+
+		closedir(d);
+	}
+
+	return nLoaded;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void UnloadPlugins(void)
+{
+	for(PLUGIN &p : g_aPlugins)
+	{
+		if(p.hLib)
+			dlclose(p.hLib);
+	}
+	
+	g_aPlugins.clear();
+}
 
 /////////////////////////////////////////////////////////////////////////////
 
@@ -98,7 +221,7 @@ int InitializeStaticFiles(const char *pszRootDir, struct _u_instance *pInst, jso
 	{
 		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)
+		if(ulfius_add_endpoint_by_val(pInst, rfr.strMethod.c_str(), rfr.strURL.c_str(), nullptr, 0, rfr.pfnCallback, &rfr) != U_OK)
 		{
 			ETRACE("Error adding endpoint for URL '%s'!\n", rfr.strURL.c_str());
 			return -1;

+ 2 - 0
rest/helpers.h

@@ -10,6 +10,8 @@
 // helpers.h - Declarations:
 
 int InitializeStaticFiles(const char *pszRootDir, struct _u_instance *pInst, json_error_t &rJerr);
+int LoadPlugins(const char *pszRootDir, struct _u_instance *pInst, json_error_t &rJerr, void *pCtx);
+void UnloadPlugins(void);
 
 /////////////////////////////////////////////////////////////////////////////
 #endif	//	!defined(AGD_HELPERS_H__2F944BB6_EA30_4239_9A06_DA28B59CC2EE__INCLUDED_)

+ 73 - 0
rest/plugin.h

@@ -0,0 +1,73 @@
+// plugin.h :
+//
+
+#if !defined(AGD_PLUGIN_H__98AA983E_C855_424B_ADEE_D6A881108474__INCLUDED_)
+#define AGD_PLUGIN_H__98AA983E_C855_424B_ADEE_D6A881108474__INCLUDED_
+
+#include <stdint.h>
+#include <stdbool.h>
+#ifndef _LIBBUILD
+#include <gfa/svc/common/uuid.h>
+#else	//	_LIBBUILD
+#include "common/uuid.h"
+#endif	//	_LIBBUILD
+
+#ifdef __cplusplus
+extern "C" {
+#endif	//	__cplusplus
+
+#include <ulfius.h>
+#include <gfa/gfaipc.h>
+
+/////////////////////////////////////////////////////////////////////////////
+// plugin.h - Declarations:
+
+typedef struct _REST_REQUEST_HANDLER
+{
+	const char *pszHandlerName;
+	const char *pszMethod;
+	const char *pszVUrl;
+	const char *pszMime;
+	const char *pszFunctionName;
+}REST_REQUEST_HANDLER, *LPREST_REQUEST_HANDLER;
+typedef const REST_REQUEST_HANDLER *LPCREST_REQUEST_HANDLER;
+
+typedef struct _REST_PLUGIN_INFO
+{
+	uuid_t signature;
+	const char *pszPluginName;
+	size_t nHandlerCnt;
+	REST_REQUEST_HANDLER handler[1];
+}REST_PLUGIN_INFO, *LPREST_PLUGIN_INFO;
+typedef const REST_PLUGIN_INFO *LPCREST_PLUGIN_INFO;
+
+typedef struct _REST_PLUGIN_REQUEST_HANDLER_PARAMS
+{
+	uuid_t uuidShm;
+	HSHM hShm;
+	void *pShm;
+	void *pParam;
+}REST_PLUGIN_REQUEST_HANDLER_PARAMS, *LPREST_PLUGIN_REQUEST_HANDLER_PARAMS;
+typedef const REST_PLUGIN_REQUEST_HANDLER_PARAMS *LPCREST_PLUGIN_REQUEST_HANDLER_PARAMS;
+
+DECLARE_UUID(UUID_REST_PLUGIN);
+
+/////////////////////////////////////////////////////////////////////////////
+
+typedef const REST_PLUGIN_INFO* (*PFN_GETPLUGININFO)(void);
+typedef int (*PFN_REST_REQUEST_HANDLER)(const struct _u_request*, struct _u_response*, void*);
+
+/*
+Prototype example:
+
+extern "C" int PluginRequestHandler(const struct _u_request *request, struct _u_response *response, void *user_data)
+{
+...
+}
+*/
+
+/////////////////////////////////////////////////////////////////////////////
+#ifdef __cplusplus
+}
+#endif	//	__cplusplus
+#endif	//	!defined(AGD_PLUGIN_H__98AA983E_C855_424B_ADEE_D6A881108474__INCLUDED_)

+ 13 - 11
rest/rest.pro

@@ -38,18 +38,18 @@ linux-buildroot-g++ {
 
 INCLUDEPATH += ../ ../common $$(GEBGFADEV)
 
-SOURCES += restvartbl.cpp \
-    restvar.cpp \
-    reststrvar.cpp \
-    restbitvar.cpp \
-    callback.cpp \
-    helpers.cpp
+SOURCES +=	restvar.cpp \
+            reststrvar.cpp \
+			restbitvar.cpp \
+			callback.cpp \
+			helpers.cpp
 
-HEADERS += main.h \
-    restvar.h \
-    helpers.h \
-    defines.h \
-    callback.h
+HEADERS +=	main.h \
+            restvar.h \
+			helpers.h \
+			defines.h \
+			callback.h \
+			plugin.h
 
 includes.path = $$_INC_PATH
 includes.extra += -$(INSTALL_FILE) $$PWD/main.h $(INSTALL_ROOT)$$includes.path
@@ -57,11 +57,13 @@ includes.extra += $$escape_expand(\\n\\t)-$(INSTALL_FILE) $$PWD/restvar.h $(INST
 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.extra += $$escape_expand(\\n\\t)-$(INSTALL_FILE) $$PWD/plugin.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
+includes.uninstall += $$escape_expand(\\n\\t)-$(DEL_FILE) $(INSTALL_ROOT)$$includes.path/plugin.h
 INSTALLS += includes
 
 library.path = $$_LIB_PATH

+ 58 - 43
rest/restvar.h

@@ -90,7 +90,7 @@ public:
 		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)
+	static json_t* CreateStatusObject(int nReqIndex, int nStatusCode, const char *pszStatusMsg = nullptr, const char *pszPath = nullptr)
 	{
 		if(!pszStatusMsg)
 			pszStatusMsg = "";
@@ -172,21 +172,39 @@ public:
 class CRestVarTable
 {
 public:
-	typedef bool (*_PFNCMP)(const char*, const char*);
+	typedef bool (*_PFNCMP)(const std::string&, const std::string&);
 
 public:
-	CRestVarTable(void);
-	virtual ~CRestVarTable(void);
+	CRestVarTable(void) : m_map(fncomp) {
+	};
+
+	virtual ~CRestVarTable(void) {
+	};
+
+	void AddVar(CRest *pVar) {
+		if(pVar)
+			m_map[std::string(pVar->GetPath())] = pVar;
+	}
+
+	CRest* Find(const char *key) const {
+		std::map<std::string, CRest*>::const_iterator it = m_map.find(std::string(key));
+		if(it == m_map.end())
+			return nullptr;
+		return it->second;
+	}
 
-	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;
+	static bool fncomp(const std::string &p1, const std::string &p2) {
+		return p1.compare(p2) < 0;
+	}
+
+private:
+	std::map<std::string, CRest*, _PFNCMP> m_map;
 };
 
 /////////////////////////////////////////////////////////////////////////////
@@ -249,7 +267,8 @@ public:
 	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;}
+		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);
@@ -302,7 +321,8 @@ public:
 	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;}
+		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);
@@ -387,7 +407,8 @@ public:
 	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;}
+		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);
@@ -414,8 +435,7 @@ 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)
-	{
+	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++)
 		{
@@ -423,8 +443,7 @@ public:
 		}
 	}
 
-	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)
-	{
+	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++)
 		{
@@ -432,8 +451,7 @@ public:
 		}
 	}
 
-	CRestArray(void *pData, HSHM hShm, const char *pszName, size_t nElemCount, CRest *pParent) : m_name(pszName)
-	{
+	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++)
 		{
@@ -441,56 +459,54 @@ public:
 		}
 	}
 
-	virtual ~CRestArray(void)
-	{
-	    for(int i = 0; i < (int)this->size(); i++)
+	virtual ~CRestArray(void) {
+		for(CRest *p : *this)
 	    {
-	        delete this->at(i);
+	    	if(p)
+		        delete p;
 		}
 	}
 
 
 public:
-	virtual void CreateMembersTable(CRestVarTable &vt)
-	{
+	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);
+		for(CRest *p : *this) {
+	    	if(p)
+	    		p->CreateMembersTable(vt);
 	    }
 	}
 
-	virtual void InitPath(CRest *pParent, const char *pszMemberName, int nIndex = -1)
-	{
+	virtual void InitPath(CRest *pParent, const char *pszMemberName, int nIndex = -1) {
+		int i = 0;
 		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);
+		for(CRest *p : *this) {
+	    	if(p)
+	    		p->InitPath(pParent, pszMemberName, i++);
 		}
 	}
 
 	virtual const char* GetPath(void) const {
-		return m_pszPath;}
+		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 void GetValue(int nReqIndex, json_t* pjtMap) {
+		for(CRest *p : *this) {
+	    	if(p)
+	    		p->GetValue(nReqIndex, pjtMap);
 	    }
 	}
 
-	virtual json_t* GetValue(int nReqIndex)
-	{
+	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));
+		for(CRest *p : *this) {
+	    	if(p)
+		    	json_array_append_new(jtArr, p->GetValue(nReqIndex));
 	    }
 
 		CJson_t jtObj(json_object(), false);
@@ -507,8 +523,7 @@ public:
 		return jtObj;
 	}
 
-	virtual json_t* SetValue(int nReqIndex, json_t* pjtVal)
-	{
+	virtual json_t* SetValue(int nReqIndex, json_t* pjtVal) {
 		UNUSED(pjtVal);
 		return CreateStatusObject(nReqIndex, -1, "Cannot assign a value to an array!", GetPath());
 	}

+ 0 - 31
rest/restvartbl.cpp

@@ -1,31 +0,0 @@
-#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;
-}

+ 1 - 1
summarist/summarist.cpp

@@ -131,7 +131,7 @@ bool CSummarist::CreateTagList(CMySqlDB &rdb)
 		const MYSQL_FIELD *pFields	= res.FetchFields();
 		MYSQL_ROW pRow				= res.FetchRow();
 
-        if((nRowCount == 1) && (nFldCount == 1) && pFields && (pFields->type != MYSQL_TYPE_BLOB) && pRow && pRow[0])
+        if((nRowCount == 1) && (nFldCount == 1) && pFields && pRow && pRow[0])
             m_ilTagList = pRow[0];
 		else
 			bRet = false;