Browse Source

First Commit.

Rind 3 năm trước cách đây
commit
92a3a2bc7c

+ 8 - 0
.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]
+# *~
+
+*.user

+ 365 - 0
OEM.cfg

@@ -0,0 +1,365 @@
+uuid =
+(
+	"1dde74fa-8dda-4cda-a576-f6d31549f3cc"	// SHM Version 1.0
+);
+
+logconf = {
+	database = "OEMLog";
+	tagtable = "Tags";
+	logtable = "Logs";
+	user = "root";
+	pass = "root";
+    sampleinterval = 1000;		// Angabe in ms         Abtastrate
+    loginterval = 300000;		// Angabe in ms         Logeintraege
+    saveinterval = 300000;		// Angabe in ms         Remanent speichern
+	logminmax = 1;
+    logsmaxage = 1200;		// Maximales Alter der Logeinträge in Tagen
+    logsmaxsize = 500;		// Maximale Größe der Logs-Tabelle in MiB
+};
+
+logsumconf =
+{
+	timewnd =		// Zeitfenster in Sekunden (timewnd modulo 3600 muss entweder 0 oder ein ganzzahliger Teiler einer Stunde sein)
+	[
+		1800,		// 1/2 Stunde
+		21600,		// 6 Stunden
+		259200		// 1 1/2 Tage
+	];
+	removeoutdated = 1;	// gibt an, ob Logeinträge in den Summarize-Tabellen gelöscht werden, wenn der zugrundeliegende Zeitrahmen aus den Logs entfernt wurde (z.B. durch den size guard). (Default: 0).
+	cusleepintv = 50;	// catch-up sleep interval in ms ( >= 10, wird auf 10 korrigiert, wenn < 10). (Default: 100).
+};
+
+remlogconf = {
+         database = "OEMRem";
+         logtable = "Logs";
+         user = "root";
+         pass = "root";
+};
+
+restconf = {
+         port = 8080;
+         implementget = 1;
+};
+
+mqttconf =
+{
+    cfg_file_path = "/opt/GfA/OEM/mqttcl/mqttcl.conf.json"
+};
+
+sinclude = (
+	"gfa/svc/common/uuid.h"
+);
+
+defines = {
+	MAX_RELAIS = 128;
+
+	MAX_BELL_FRAMES = 1000;
+	MAX_CHIME_FRAMES = 1000;
+	
+	BELL_FRAME_TYPE_RELAIS = 0;
+	BELL_FRAME_TYPE_RNGAUTO = 1;
+
+	MAX_PACKETS = 500;
+	MAX_FRAMES_PER_PACKET = 100;
+
+	MAX_DAYS_PROGRAMS = 300;
+	MAX_PACKETS_PER_DAYS_PROGRAM = 100;
+	MAX_RELAIS_PROGRAMS = 100;
+	RELAIS_PROGRAM_ID_MIN = 1000;
+
+	MAX_PERIODIC_EVENTS = 100;
+	MAX_FIXED_EVENTS = 100;
+	MAX_MOVABLE_EVENTS = 100;
+	MAX_WEEKDAY_DEPENDENT_EVENTS = 100;
+
+	MAX_NAME_LENGTH = 64;
+	MAX_RELAIS_NAME_LENGTH = 32;
+	
+	MAX_FUNCTIONS = 64;
+	MAX_FUNC_TRIGGERS = 8;
+	FUNC_TARGET_PERIODIC_EVENT = 1;
+	FUNC_TARGET_FIXED_EVENT = 2;
+	FUNC_TARGET_MOVABLE_EVENT = 3;
+	FUNC_TARGET_WDEP_EVENT = 4;
+	FUNC_TARGET_DAYS_PROGRAM = 5;
+	FUNC_TARGET_PACKET = 6;
+	FUNC_TARGET_INTERNAL = 7;
+	FUNC_TYPE_GLOBAL_SETTING = 1;
+	FUNC_TYPE_EVENT_ON_OFF = 2;
+	FUNC_TYPE_INSTANT_RINGING = 3;
+	FUNC_TRIGGER_TYPE_KEY = 1;
+	FUNC_TRIGGER_TYPE_INPUT = 2;
+	FUNC_TRIGGER_TYPE_SW_KEY = 3;
+
+
+	MAX_KEYS = 96;
+	MAX_INPUTS = 96;
+	MAX_SW_KEYS = 64;
+	MAX_KEYS_INPUTS = 256;
+	KEY_INPUT_TYPE_FUNCTION_TRIGGER = 1;
+	KEY_INPUT_TYPE_PUSH_BUTTON = 2;
+	KEY_INPUT_TYPE_SWITCH = 3;
+	KEY_INPUT_TYPE_DISPLAY = 4;
+	KEY_INPUT_TARGET_FUNCTION = 1;
+	KEY_INPUT_TARGET_RELAIS = 2;
+	KEY_INPUT_TARGET_RNGAUTO = 3;
+	KEY_INPUT_TARGET_LED = 4;
+	
+	PACKET_TYPE_BELL = 0;
+	PACKET_TYPE_CHIME = 1;
+
+	COUNT_WEEKDAYS = 7;
+	MAX_SUB_CLOCKS = 2;
+	MAX_CHIMES_GENERAL_MUTINGS = 10;
+	SHM_VER_MAJ = 1; // 1dde74fa-8dda-4cda-a576-f6d31549f3ba
+	SHM_VER_MIN = 0; // 1dde74fa-8dda-4cda-a576-f6d31549f3ba
+};
+
+structures = {
+
+	nodecl-uuid_t = {
+		Data1 = "uint32_t, $-rem";
+		Data2 = "uint16_t, $-rem";
+		Data3 = "uint16_t, $-rem";
+		Data4 = "uint8_t, 8, $-rem";
+	};
+
+	RELAIS_ASSIGNMENT = {
+		id = "uint32_t, $-rem";								// Bereich 1 - MAX_RELAIS, unique, 0 = nicht konfiguriert
+		swingOutTime = "uint32_t, $-rem";					// Nachschwingzeit (nur bei Glockenrelais interessant)
+		szName = "$-utf-8, MAX_RELAIS_NAME_LENGTH, $-rem";	// Relaisname, beliebig, max. MAX_RELAIS_NAME_LENGTH - 1 Zeichen
+	};
+
+	BELL_FRAME = {
+		id = "uint32_t, $-rem";						// Bereich 1 - MAX_BELL_FRAMES, unique, 0 = nicht konfiguriert
+		relaisID = "uint32_t, $-rem";				// Bereich 1 - MAX_RELAIS, ID des Relais oder Läutautomaten
+		type = "uint8_t, $-rem";					// Typ, Relais oder Läuteautomat
+		startOffsMin = "uint8_t, $-rem";			// Start-Offset in Minuten
+		startOffsSec = "uint8_t, $-rem";			// Start-Offset in Sekunden
+		durationMin = "uint8_t, $-rem";				// Dauer in Minuten
+		durationSec = "uint8_t, $-rem";				// Dauer in Sekunden
+//		szName = "$-utf-8, MAX_NAME_LENGTH, $-rem";	// Framename, fix formatiert: "REL-rrr/START-mm:ss/DAUER-mm:ss/Rel.Name" oder "LA-rrr/START-mm:ss/DAUER-mm:ss/Rel.Name" (z. B.: "REL-117/START-02:15/DAUER-00:30/Glocke 3 Ges")
+	};
+
+	CHIME_FRAME = {
+		id = "uint32_t, $-rem";						// Bereich 1 - MAX_CHIME_FRAMES, unique, 0 = nicht konfiguriert
+		relaisID = "uint32_t, $-rem";				// Bereich 1 - MAX_RELAIS, ID des Relais
+		durationDecSecOn = "uint8_t, $-rem";		// Einschaltdauer in 1/10 Sekunden
+		durationDecSecOff = "uint8_t, $-rem";		// Ausschaltdauer in 1/10 Sekunden
+//		szName = "$-utf-8, MAX_NAME_LENGTH, $-rem";	// Framename, fix formatiert: "REL-rrr/IMPULS-ds/PAUSE-ds/Rel.Name" (z. B.: "REL-023/IMPULS-50/PAUSE-40/Glocke 5 C#")
+	};
+
+	PACKET = {
+		id = "uint32_t, $-rem";						// Bereich 1 - MAX_PACKETS, unique, 0 = nicht konfiguriert
+		type = "uint32_t, $-rem";					// Paket-Typ, Glocken- oder Schlagwerkpaket
+		frameIDs = "uint32_t, MAX_FRAMES_PER_PACKET, $-rem";	// MAX_FRAMES_PER_PACKET Einträge im Bereich von 1 - MAX_BELL_FRAMES bzw. 1 - MAX_CHIME_FRAMES, enthält eine Liste der auszuführenden Frames des Typs 'type'
+		szName = "$-utf-8, MAX_NAME_LENGTH, $-rem";	// Paketname, beliebig, max. MAX_NAME_LENGTH - 1 Zeichen
+	};
+
+	DAYS_PROGRAM = {
+		id = "uint32_t, $-rem";						// Bereich 1 - MAX_DAYS_PROGRAMS, unique, 0 = nicht konfiguriert, Priorität ausfsteigend
+		packetIDs = "uint32_t, MAX_PACKETS_PER_DAYS_PROGRAM, $-rem";	// MAX_PACKETS_PER_DAYS_PROGRAM Einträge im Bereich von 1 - MAX_PACKETS, enthält eine Liste der auszuführenden Pakete
+		startHour = "uint8_t, MAX_PACKETS_PER_DAYS_PROGRAM, $-rem";		// Startzeit des Paketes - Stunde, MAX_PACKETS_PER_DAYS_PROGRAM Einträge
+		startMin = "uint8_t, MAX_PACKETS_PER_DAYS_PROGRAM, $-rem";		// Startzeit des Paketes - Minute, MAX_PACKETS_PER_DAYS_PROGRAM Einträge
+		startSec = "uint8_t, MAX_PACKETS_PER_DAYS_PROGRAM, $-rem";		// Startzeit des Paketes - Sekunde, MAX_PACKETS_PER_DAYS_PROGRAM Einträge
+		bLowPriority = "bool, $-rem";				// niedrigste Priorität
+		bOpaque = "bool, $-rem";					// deckend oder nicht
+		szName = "$-utf-8, MAX_NAME_LENGTH, $-rem";	// Tagesprogrammname, beliebig, max. MAX_NAME_LENGTH - 1 Zeichen
+	};
+
+	RELAIS_PROGRAM = {
+		id = "uint32_t, $-rem";						// Bereich RELAIS_PROGRAM_ID_MIN - (RELAIS_PROGRAM_ID_MIN + MAX_RELAIS_PROGRAMS - 1), unique, 0 = nicht konfiguriert
+		relaisID = "uint32_t, $-rem";				// Bereich 1 - MAX_RELAIS, ID des Relais
+		hourOn = "uint8_t, $-rem";					// Einschaltzeit, Stunde
+		minOn = "uint8_t, $-rem";					// Einschaltzeit, Minute
+		hourOff = "uint8_t, $-rem";					// Ausschaltzeit, Stunde
+		minOff = "uint8_t, $-rem";					// Ausschaltzeit, Minute
+		szName = "$-utf-8, MAX_NAME_LENGTH, $-rem";	// Relais-Programmname, beliebig, max. MAX_NAME_LENGTH - 1 Zeichen
+	};
+
+	PERIODIC_EVENT = {
+		id = "uint32_t, $-rem";						// Bereich 1 - MAX_PERIODIC_EVENTS, unique, 0 = nicht konfiguriert
+		dayOn = "uint8_t, $-rem";					// Beginn Tag, 1 - 31, Tage > Anzahl der Monatstage werden als letzter Tag des Monats interpretiert, 0 wenn sommer-/winterzeitabhängig oder ganzjährig
+		monthOn = "uint8_t, $-rem";					// Beginn Monat, 1 - 12, 0 wenn sommer-/winterzeitabhängig oder ganzjährig
+		dayOff = "uint8_t, $-rem";					// Ende Tag, 1 - 31, Tage > Anzahl der Monatstage werden als letzter Tag des Monats interpretiert, 0 wenn sommer-/winterzeitabhängig oder ganzjährig
+		monthOff = "uint8_t, $-rem";				// Ende Monat, 1 - 12, 0 wenn sommer-/winterzeitabhängig oder ganzjährig
+		weekDays = "uint8_t, COUNT_WEEKDAYS, $-rem";// Wochentage (Mo. - So.), Werte im Bereich 1 - 2, 0 = aus, 1 = wöchentlich, 2 = vierzehntägig
+		timeFence = "uint8_t, $-rem";				// Gültigkeitsbereich, 0 = durch Datum festgelegt, 1 = nur Sommerzeit, 2 = nur Winterzeit, 3 = ganzjährig
+		year = "uint16_t, $-rem";					// Jahr, 0 = jedes Jahr
+		muted = "bool, $-rem";						// Flag "Ausführung deaktiviert", false = Ausführung aktiv, true = Ausführung deaktiviert
+		progID = "uint32_t, $-rem";					// ID des auszuführenden Programmes im Bereich von 1 - MAX_DAYS_PROGRAMS für Tagesprogramme oder
+													// RELAIS_PROGRAM_ID_MIN - (RELAIS_PROGRAM_ID_MIN + MAX_RELAIS_PROGRAMS - 1) für Relaisprogramme
+		funcID = "uint32_t, $-rem";					// ID der Auslösefunktion oder 0, wenn nicht konfiguriert
+		szName = "$-utf-8, MAX_NAME_LENGTH, $-rem";	// Periodenname, beliebig, max. MAX_NAME_LENGTH - 1 Zeichen
+	};
+
+	FIXED_EVENT = {
+		uuid = "uuid_t, $-rem";						// uuid eines vorkonfigurierten Feiertages
+		id = "uint32_t, $-rem";						// Bereich 1 - MAX_FIXED_EVENTS, unique, 0 = nicht konfiguriert
+		day = "uint8_t, $-rem";						// Tag, 1 - 31, Tage > Anzahl der Monatstage werden als letzter Tag des Monats interpretiert
+		month = "uint8_t, $-rem";					// Monat, 1 - 12
+		year = "uint16_t, $-rem";					// Jahr, 0 = jedes Jahr
+		muted = "bool, $-rem";						// Flag "Ausführung deaktiviert", false = Ausführung aktiv, true = Ausführung deaktiviert
+		progID = "uint32_t, $-rem";					// ID des auszuführenden Programmes im Bereich von 1 - MAX_DAYS_PROGRAMS für Tagesprogramme oder
+													// RELAIS_PROGRAM_ID_MIN - (RELAIS_PROGRAM_ID_MIN + MAX_RELAIS_PROGRAMS - 1) für Relaisprogramme
+		funcID = "uint32_t, $-rem";					// ID der Auslösefunktion oder 0, wenn nicht konfiguriert
+		szName = "$-utf-8, MAX_NAME_LENGTH, $-rem";	// Feiertagsname, beliebig, max. MAX_NAME_LENGTH - 1 Zeichen, oder vorbesetzt mit Namen des vorkonfigurierten Feiertages
+	};
+
+	MOVABLE_EVENT = {
+		uuid = "uuid_t, $-rem";						// uuid des vorkonfigurierten Referenz-Feiertages
+		id = "uint32_t, $-rem";						// Bereich 1 - MAX_MOVABLE_EVENTS, unique, 0 = nicht konfiguriert
+		dayOffs = "int16_t, $-rem";					// +/- Offset in Tagen zum Referenz-Feiertag
+		year = "uint16_t, $-rem";					// Jahr, 0 = jedes Jahr
+		muted = "bool, $-rem";						// Flag "Ausführung deaktiviert", false = Ausführung aktiv, true = Ausführung deaktiviert
+		progID = "uint32_t, $-rem";					// ID des auszuführenden Programmes im Bereich von 1 - MAX_DAYS_PROGRAMS für Tagesprogramme oder
+													// RELAIS_PROGRAM_ID_MIN - (RELAIS_PROGRAM_ID_MIN + MAX_RELAIS_PROGRAMS - 1) für Relaisprogramme
+		funcID = "uint32_t, $-rem";					// ID der Auslösefunktion oder 0, wenn nicht konfiguriert
+		szName = "$-utf-8, MAX_NAME_LENGTH, $-rem";	// Feiertagsname, beliebig, max. MAX_NAME_LENGTH - 1 Zeichen, oder vorbesetzt mit Namen des vorkonfigurierten Feiertages
+	};
+
+	WEEKDAY_DEPENDENT_EVENT = {
+		id = "uint32_t, $-rem";						// Bereich 1 - MAX_WEEKDAY_DEPENDENT_EVENTS, unique, 0 = nicht konfiguriert
+		dayNum = "uint8_t, $-rem";					// Tag, 1 - 5, 1. - 4. bzw. letzter (5) Wochentag im Monat
+		weekDay = "uint8_t, $-rem";					// Wochentag, 1 (Mo) - 7 (So)
+		month = "uint8_t, $-rem";					// Monat, 1 - 12, 0 = jedes Monat
+		muted = "bool, $-rem";						// Flag "Ausführung deaktiviert", false = Ausführung aktiv, true = Ausführung deaktiviert
+		dayOffs = "int16_t, $-rem";					// +/- Offset in Tagen zum Referenz-Feiertag
+		year = "uint16_t, $-rem";					// Jahr, 0 = jedes Jahr
+		progID = "uint32_t, $-rem";					// ID des auszuführenden Programmes im Bereich von 1 - MAX_DAYS_PROGRAMS für Tagesprogramme oder
+													// RELAIS_PROGRAM_ID_MIN - (RELAIS_PROGRAM_ID_MIN + MAX_RELAIS_PROGRAMS - 1) für Relaisprogramme
+		funcID = "uint32_t, $-rem";					// ID der Auslösefunktion oder 0, wenn nicht konfiguriert
+		szName = "$-utf-8, MAX_NAME_LENGTH, $-rem";	// Feiertagsname, beliebig, max. MAX_NAME_LENGTH - 1 Zeichen
+	};
+
+	FUNCTION = {
+		id = "uint32_t, $-rem";						// Bereich 1 - MAX_FUNCTIONS, unique, 0 = nicht konfiguriert
+		triggerIDs = "uint32_t, MAX_FUNC_TRIGGERS, $-rem";	// Liste der Auslöser (Tasten, Eingänge, Software-Tasten), max. MAX_FUNC_TRIGGERS Einträge
+		triggerTypes = "uint8_t, MAX_FUNC_TRIGGERS, $-rem";	// Liste der Auslösertypen (Tasten, Eingänge, Software-Tasten), max. MAX_FUNC_TRIGGERS Einträge
+		targetID = "uint32_t, $-rem";				// Ziel-Event der Funktion
+		targetType = "uint8_t, $-rem";				// Typ des Ziel-Events der Funktion
+		type = "uint8_t, $-rem";					// Funktionstyp - Ein/Aus, Sofortläuten, Programmauswahl
+		szName = "$-utf-8, MAX_NAME_LENGTH, $-rem";	// Funktionsname, beliebig, max. MAX_NAME_LENGTH - 1 Zeichen
+	};
+
+	KEY_INPUT_CFG = {
+		id = "uint32_t, $-rem";						// Bereich 1 - MAX_KEYS bzw. 1 - MAX_INPUTS, unique, 0 = nicht konfiguriert
+		ledInput = "uint32_t, $-rem";				// ID des Eingangs zum Schalten der LED, oder 0 bei Standardverhalten
+		targetID = "uint32_t, $-rem";				// ID der zu schaltenden Einheit, abhängig von targetType
+		targetType = "uint8_t, $-rem";				// Funktion, Relais, Läuteautomat
+		type = "uint8_t, $-rem";					// Funktion, Taster, Schalter, Anzeige
+		szName = "$-utf-8, MAX_NAME_LENGTH, $-rem";	// Feiertagsname, beliebig, max. MAX_NAME_LENGTH - 1 Zeichen, oder vorbesetzt mit Namen des vorkonfigurierten Feiertages
+	};
+
+	KEY_INPUT_MAP = {
+		keys =   "KEY_INPUT_CFG, MAX_KEYS";					// Tabelle Tastenkonfiguration, MAX_KEYS Einträge
+		inputs = "KEY_INPUT_CFG, MAX_INPUTS";				// Tabelle Eingangskonfiguration, MAX_INPUTS Einträge
+		swKeys = "KEY_INPUT_CFG, MAX_SW_KEYS";				// Tabelle Software-Tasten-Konfiguration, MAX_SW_KEYS Einträge
+		image =  "uint8_t, (MAX_KEYS_INPUTS / 8)";			// Live-Speicherabbild der Tasten/Eingänge, MAX_KEYS_INPUTS Bits
+		shadow = "uint8_t, (MAX_KEYS_INPUTS / 8)";			// Kopie Speicherabbild der Tasten/Eingänge, MAX_KEYS_INPUTS Bits
+		state =  "uint8_t, (MAX_KEYS_INPUTS / 8), $-rem";	// Remanenter Status der Flipflop-Tasten/Eingänge, MAX_KEYS_INPUTS Bits
+	};
+
+	MAIN_CLOCK_SETTINGS = {
+		dcf77Enabled = "bool, $-rem";				// DCF77 ein/aus
+	};
+
+	SUB_CLOCK_SETTINGS = {
+		enabled = "bool, $-rem";					// Ein/Aus
+		type = "uint8_t, $-rem";					// Minuten-, Halbminuten- oder Sekundenlinie
+		impulsOn = "uint8_t, $-rem";				// Impulsdauer, für Minuten- und Halbminutenlinien in Sekundenschritten, für Sekundenlinien in Zehntelsekundenschritten
+		impulsOff = "uint8_t, $-rem";				// Pausendauer, für Minuten- und Halbminutenlinien in Sekundenschritten, für Sekundenlinien in Zehntelsekundenschritten
+		hourMode = "uint8_t, $-rem";				// 12 oder 24 Stunden Modus
+		outputID = "uint8_t, $-rem";				// Ausgangs-Nr.
+	};
+
+	CHIMES_PERIOD = {
+		dayOn = "uint8_t, $-rem";					// Beginn Tag, 1 - 31, Tage > Anzahl der Monatstage werden als letzter Tag des Monats interpretiert
+		monthOn = "uint8_t, $-rem";					// Beginn Monat, 1 - 12
+		dayOff = "uint8_t, $-rem";					// Ende Tag, 1 - 31, Tage > Anzahl der Monatstage werden als letzter Tag des Monats interpretiert
+		monthOff = "uint8_t, $-rem";				// Ende Monat, 1 - 12
+		weekDays = "bool, COUNT_WEEKDAYS, $-rem";	// Wochentage (Mo. - So.), true/false
+	};
+
+	CHIMES_NIGHT_MUTING = {
+		enabled = "bool, $-rem";					// Nachtabschaltung Ein/Aus
+		quatersOnly = "bool, $-rem";				// Nur Viertelschlagen abschalten
+		cycle = "uint8_t, $-rem";					// Täglich, Periodisch, Sommer/Winter
+		// wenn cycle = "Täglich", "Periodisch" oder "Sommer/Winter"
+		hourBegin = "uint8_t, $-rem";				// Stunde, Beginn der Abschaltung, wenn cycle = "Sommer/Winter" -> Normalzeit
+		minuteBegin = "uint8_t, $-rem";				// Minute, Beginn der Abschaltung, wenn cycle = "Sommer/Winter" -> Normalzeit
+		hourEnd = "uint8_t, $-rem";					// Stunde, Ende der Abschaltung, wenn cycle = "Sommer/Winter" -> Normalzeit
+		minuteEnd = "uint8_t, $-rem";				// Minute, Ende der Abschaltung, wenn cycle = "Sommer/Winter" -> Normalzeit
+		// wenn cycle = "Periodisch"
+		dayOn = "uint8_t, $-rem";					// Beginn Tag, 1 - 31, Tage > Anzahl der Monatstage werden als letzter Tag des Monats interpretiert, wenn periodisch
+		monthOn = "uint8_t, $-rem";					// Beginn Monat, 1 - 12, wenn periodisch
+		dayOff = "uint8_t, $-rem";					// Ende Tag, 1 - 31, Tage > Anzahl der Monatstage werden als letzter Tag des Monats interpretiert, wenn periodisch
+		monthOff = "uint8_t, $-rem";				// Ende Monat, 1 - 12, wenn periodisch
+		weekDays = "uint8_t, COUNT_WEEKDAYS, $-rem";// Wochentage (Mo. - So.), Werte im Bereich 0 - 1, 0 = aus, 1 = wöchentlich, wenn periodisch
+		// wenn cycle = "Sommer/Winter"
+		hourBeginDST = "uint8_t, $-rem";			// Stunde, Beginn der Abschaltung, wenn cycle = "Sommer/Winter" -> Sommerzeit
+		minuteBeginDST = "uint8_t, $-rem";			// Minute, Beginn der Abschaltung, wenn cycle = "Sommer/Winter" -> Sommerzeit
+		hourEndDST = "uint8_t, $-rem";				// Stunde, Ende der Abschaltung, wenn cycle = "Sommer/Winter" -> Sommerzeit
+		minuteEndDST = "uint8_t, $-rem";			// Minute, Ende der Abschaltung, wenn cycle = "Sommer/Winter" -> Sommerzeit
+	};
+
+	CHIMES_GENERAL_MUTING = {
+		enabled = "bool, $-rem";					// Schlagabschaltung Ein/Aus
+		quatersOnly = "bool, $-rem";				// Nur Viertelschlagen abschalten
+		dayBegin = "uint8_t, $-rem";				// Beginn Tag, 1 - 31, Tage > Anzahl der Monatstage werden als letzter Tag des Monats interpretiert
+		monthBegin = "uint8_t, $-rem";				// Beginn Monat, 1 - 12
+		hourBegin = "uint8_t, $-rem";				// Beginn Stunde, 1 - 24
+		minuteBegin = "uint8_t, $-rem";				// Beginn Minute, 0 - 59
+		dayEnd = "uint8_t, $-rem";					// Ende Tag, 1 - 31, Tage > Anzahl der Monatstage werden als letzter Tag des Monats interpretiert
+		monthEnd = "uint8_t, $-rem";				// Ende Monat, 1 - 12
+		hourEnd = "uint8_t, $-rem";					// Ende Stunde, 1 - 24
+		minuteEnd = "uint8_t, $-rem";				// Ende Minute, 0 - 59
+	};
+
+	CHIMES_SETTINGS = {
+		enabled = "bool, $-rem";					// Schlagwerk Ein/Aus
+		mode = "bool, $-rem";						// Schlagwerk-Modus, Intern/Extern, Extern = "Läuten/Schlagen geleichzeitig"
+		type = "uint8_t, $-rem";					// Viertel (1/4, 1/2, 3/4 und 1h), Halb (1/2 und 1h) oder Voll (nur 1h)
+		repeat = "bool, $-rem";						// Stundenschlag-Wiederholung
+		quaterFullDelay = "uint16_t, $-rem";		// Pause zwischen Vierviertelschlag und Stundenschlag in 1/10 Sekunden.
+		melodyMode = "uint8_t, $-rem";				// Schlagwerkmelodie Modus, 0 = aus, wenn != 0, dann Viertelmodus (1 = nur erstes Paket konfiguriert, 2 = alle Pakete konfiguriert)
+		cycle = "uint8_t, $-rem";					// Täglich, Periodisch
+		periodSettings = "CHIMES_PERIOD";			// periodische Einstellungen, wenn cycle = "Periodisch"
+		genMutings = "CHIMES_GENERAL_MUTING, MAX_CHIMES_GENERAL_MUTINGS";	// Generelle Schlagabschaltungen, MAX_CHIMES_GENERAL_MUTINGS Einträge
+		nightMuting = "CHIMES_NIGHT_MUTING";		// Nachtabschaltung
+		quaterFrame = "CHIME_FRAME";				// Impuls-, Pausendauer und Relais Viertelstundenschlag (1/4, 1/2, 3/4, 4/4) für Default-Konfiguration
+		fullFrame = "CHIME_FRAME";					// Impuls-, Pausendauer und Relais Stundenschlag für Default-Konfiguration
+		quater1Packet = "uint32_t, $-rem";			// Schlagwerkmelodie, Paket-ID Viertelstundenschlag (1/4)
+		quater2Packet = "uint32_t, $-rem";			// Schlagwerkmelodie, Paket-ID Halbstundenschlag (1/2)
+		quater3Packet = "uint32_t, $-rem";			// Schlagwerkmelodie, Paket-ID Dreiviertelstundenschlag (3/4)
+		quater4Packet = "uint32_t, $-rem";			// Schlagwerkmelodie, Paket-ID Vierviertelstundenschlag (volle Stunde, vor Stundenschlag)
+		hourPacket = "uint32_t, $-rem";				// Schlagwerkmelodie, Paket-ID Stundenschlag
+		hourPacket2 = "uint32_t, $-rem";			// Schlagwerkmelodie, Paket-ID 2. Stundenschlag bei Stundenschlag-Wiederholung
+	};
+
+	GLOBAL_SETTINGS = {
+		autoEnabled = "bool, $-rem";						// Automatik ein/aus
+		backlightOffMin = "uint8_t, $-rem";					// Zeit bis zum Abschalten des Backlights, Minuten
+		backlightOffSec = "uint8_t, $-rem";					// Zeit bis zum Abschalten des Backlights, Sekunden
+		mainClock = "MAIN_CLOCK_SETTINGS";					// Einstellungen Hauptuhr
+		subClocks = "SUB_CLOCK_SETTINGS, MAX_SUB_CLOCKS";	// Einstellungen Nebenuhr, MAX_SUB_CLOCKS Linien
+		chimesSettings = "CHIMES_SETTINGS";					// Einstellungen Schlagwerk
+	};
+
+	SHM = {
+		relais = "RELAIS_ASSIGNMENT, MAX_RELAIS";				// Tabelle Relais, MAX_RELAIS Einträge
+		rngAutom = "RELAIS_ASSIGNMENT, MAX_RELAIS";				// Tabelle Läuteautomaten, MAX_RELAIS Einträge
+		bellFrames = "BELL_FRAME, MAX_BELL_FRAMES";				// Tabelle Glocken-Frames, MAX_BELL_FRAMES Einträge
+		chimeFrames = "CHIME_FRAME, MAX_CHIME_FRAMES";			// Tabelle Schlagwerk-Frames, MAX_CHIME_FRAMES Einträge
+		packets = "PACKET, MAX_PACKETS";						// Tabelle Pakete, MAX_PACKETS Einträge
+		daysProgs = "DAYS_PROGRAM, MAX_DAYS_PROGRAMS";			// Tabelle Tages-Programme, MAX_DAYS_PROGRAMS Einträge
+		relProgs = "RELAIS_PROGRAM, MAX_RELAIS_PROGRAMS";		// Tabelle Relais-Programme, MAX_RELAIS_PROGRAMS Einträge
+		periodicEvents = "PERIODIC_EVENT, MAX_PERIODIC_EVENTS";	// Tabelle Periodische Ereignisse, MAX_PERIODIC_EVENTS Einträge
+		fixedEvents = "FIXED_EVENT, MAX_FIXED_EVENTS";			// Tabelle Fixe Ereignisse, MAX_FIXED_EVENTS Einträge
+		movableEvents = "MOVABLE_EVENT, MAX_MOVABLE_EVENTS";	// Tabelle Bewegliche Ereignisse, MAX_MOVABLE_EVENTS Einträge
+		weekdayDepEvents = "WEEKDAY_DEPENDENT_EVENT, MAX_WEEKDAY_DEPENDENT_EVENTS";	// Tabelle Wochentagsabhängige Ereignisse, MAX_WEEKDAY_DEPENDENT_EVENTS Einträge
+		functions = "FUNCTION, MAX_FUNCTIONS";					// Tabelle Funktionen, MAX_FUNCTIONS Einträge
+		keyInputMap = "KEY_INPUT_MAP";							// Tabelle Tasten/Inputs
+		globalSettings = "GLOBAL_SETTINGS";						// Struktur Globale Einstellungen
+	};
+};

+ 11 - 0
OEM.pri

@@ -0,0 +1,11 @@
+###################################################
+
+CFG_INFILE		= OEM.cfg
+CFG_OUTDIR		= .
+CFG_BASENAME	= OEM
+DEPLOY_BASEDIR	= /opt/GfA
+DEPLOY_SUBDIR	= $$CFG_BASENAME
+#GEB_SEARCHDIR	= /path/to/geb/files
+#GEB_CFGFILE		= geb.cfg
+
+###################################################

+ 11 - 0
OEM.pro

@@ -0,0 +1,11 @@
+BUILD_CFG = 1
+include(projal.pri)
+
+TEMPLATE = subdirs
+
+SUBDIRS += \
+		svc \
+		usr
+
+CONFIG += qtc_runnable
+CONFIG += ordered

+ 33 - 0
projal.pri

@@ -0,0 +1,33 @@
+include(OEM.pri)
+
+GEBVARS			= gfagebvars
+SHMCONF			= gfashmconf
+
+###################################################
+
+defined(BUILD_CFG, var) {
+	defined(GEB_SEARCHDIR, var) {
+		defined(GEB_CFGFILE, var) {
+			STRING = $$sprintf("@include \\\"%1/%2\\\"", $${CFG_BASENAME}, $${GEB_CFGFILE})
+			system(echo $${STRING} > geb.cfg.inc)
+			if(!system($${GEBVARS} -i $${GEB_SEARCHDIR} -o $${CFG_BASENAME}/$${GEB_CFGFILE} -fg)) {
+				error(gebvars.bin failed!)
+			}
+		} else {
+			system(echo > geb.cfg.inc)
+		}
+	} else {
+		system(echo > geb.cfg.inc)
+	}
+}
+
+###################################################
+
+defined(BUILD_CFG, var) {
+	if(!system($${SHMCONF} -c $${CFG_INFILE} -b $${CFG_BASENAME} -d $${CFG_OUTDIR})) {
+		error(shmconf.bin failed!)
+	} else {
+		STRING = $$sprintf("\\$${LITERAL_HASH}include \\\"%1/%2_al.h\\\"", $${CFG_OUTDIR}, $${CFG_BASENAME})
+		system(echo $${STRING} > projal.h)
+	}
+}

+ 8 - 0
svc/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

+ 42 - 0
svc/datalogger/datalogger.pro

@@ -0,0 +1,42 @@
+DEPLOY_BASEDIR = $$fromfile(../../projal.pri, DEPLOY_BASEDIR)
+DEPLOY_SUBDIR = $$fromfile(../../projal.pri, DEPLOY_SUBDIR)
+
+TEMPLATE = app
+CONFIG += console c++11
+CONFIG -= app_bundle
+CONFIG -= qt
+
+QMAKE_CXXFLAGS_DEBUG += -Wno-unused-parameter -Wno-unused-but-set-variable -pthread
+QMAKE_CXXFLAGS += -Wstrict-aliasing=0 -pthread
+QMAKE_LIBDIR += $$[QT_SYSROOT]/usr/lib/gfa $$[QT_SYSROOT]/usr/lib/gfa/svc
+QMAKE_RPATHDIR += /usr/lib/gfa
+
+QMAKE_CXXFLAGS += -D_DATALOGGER
+QMAKE_CFLAGS += -D_DATALOGGER
+
+CONFIG(debug, debug|release) {
+	QMAKE_CXXFLAGS -= -Os
+	QMAKE_CFLAGS -= -Os
+    QMAKE_CXXFLAGS += -D_DEBUG
+    QMAKE_CFLAGS += -D_DEBUG
+	QMAKE_LIBS += -lm -lstdc++ -pthread -lgfaipcd -l:libdataloggerd.a -l:libcommond.a -lmysqlclient
+}
+
+CONFIG(release, debug|release) {
+    QMAKE_CXXFLAGS += -Wno-unused-parameter -Wno-unused-but-set-variable
+    QMAKE_CFLAGS += -Wno-unused-parameter -Wno-unused-but-set-variable
+	QMAKE_LIBS += -lm -lstdc++ -pthread -lgfaipcd -l:libdatalogger.a -l:libcommon.a -lmysqlclient
+}
+
+SOURCES += main.cpp
+
+HEADERS +=
+
+INCLUDEPATH += $$(GEBGFADEV) ../../
+
+linux-buildroot-g++ {
+    target.path = $$DEPLOY_BASEDIR/$$DEPLOY_SUBDIR/datalogger
+    INSTALLS += target
+    QMAKE_CXXFLAGS += -D_TARGET_BUILD
+    QMAKE_CFLAGS += -D_TARGET_BUILD
+}

+ 538 - 0
svc/datalogger/main.cpp

@@ -0,0 +1,538 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+#include <signal.h>
+#include <unistd.h>
+#include <linux/limits.h>
+#include <gfa/svc/common/logfile.h>
+#include <gfa/svc/common/fileutil.h>
+#include <gfa/svc/common/strutil.h>
+#include <gfa/svc/common/instance.h>
+#include <gfa/svc/common/processclock.h>
+#include <gfa/svc/common/debug.h>
+#include "projal.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;
+}

+ 8 - 0
svc/mqttcl/.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]
+# *~
+
+tls/

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

@@ -0,0 +1,12 @@
+{
+	"brokerAddr":		"server.addr",
+	"brokerPort":		8883,
+	"defaultQos":		2,
+	"defaultRetain":	false,
+	"devicePrefix":		"OEM",
+	"tlsMode":			1,
+	"tlsCaCrtFile":		"/opt/GfA/OEM/mqttcl/client/ca.crt",
+	"tlsClCrtFile":		"/opt/GfA/OEM/mqttcl/client/client.crt",
+	"tlsClKeyFile":		"/opt/GfA/OEM/mqttcl/client/client.key",
+	"tlsPsk":			"1234567890abcdef"
+}

+ 1202 - 0
svc/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 <gfa/svc/common/strutil.h>
+#include <gfa/svc/common/fileutil.h>
+#include <gfa/svc/common/instance.h>
+#include <gfa/svc/common/processclock.h>
+#include <gfa/svc/common/debug.h>
+#include <gfa/svc/mqttcl/mqttclient.h>
+#include <gfa/svc/mqttcl/mqttcfg.h>
+#include <gfa/svc/mqttcl/mqttdbg.h>
+#include "projal.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;
+}

+ 42 - 0
svc/mqttcl/mqttcl.pro

@@ -0,0 +1,42 @@
+DEPLOY_BASEDIR = $$fromfile(../../projal.pri, DEPLOY_BASEDIR)
+DEPLOY_SUBDIR = $$fromfile(../../projal.pri, DEPLOY_SUBDIR)
+
+TEMPLATE = app
+CONFIG += console c++11
+CONFIG -= app_bundle
+CONFIG -= qt
+
+QMAKE_CXXFLAGS_DEBUG += -Wno-unused-parameter -Wno-unused-but-set-variable -pthread
+QMAKE_CXXFLAGS += -Wstrict-aliasing=0 -pthread
+QMAKE_LIBDIR += $$[QT_SYSROOT]/usr/lib/gfa $$[QT_SYSROOT]/usr/lib/gfa/svc
+QMAKE_RPATHDIR += /usr/lib/gfa
+
+QMAKE_CXXFLAGS += -D_MQTTCL -Wno-deprecated-declarations
+QMAKE_CFLAGS += -D_MQTTCL -Wno-deprecated-declarations
+
+CONFIG(debug, debug|release) {
+        QMAKE_CXXFLAGS -= -Os
+        QMAKE_CFLAGS -= -Os
+    QMAKE_CXXFLAGS += -D_DEBUG
+    QMAKE_CFLAGS += -D_DEBUG
+	QMAKE_LIBS += -lm -lstdc++ -pthread -lgfaipc -ljansson -l:libmqttcld.a -l:libcommond.a -lmosquittopp -lmosquitto
+}
+
+CONFIG(release, debug|release) {
+    QMAKE_CXXFLAGS += -Wno-unused-parameter -Wno-unused-but-set-variable
+    QMAKE_CFLAGS += -Wno-unused-parameter -Wno-unused-but-set-variable
+	QMAKE_LIBS += -lm -lstdc++ -pthread -lgfaipc -ljansson -l:libmqttcl.a -l:libcommon.a -lmosquittopp -lmosquitto
+}
+
+SOURCES += main.cpp
+
+HEADERS += 
+
+INCLUDEPATH += ../../  $$(GEBGFADEV)
+
+linux-buildroot-g++ {
+    target.path = $$DEPLOY_BASEDIR/$$DEPLOY_SUBDIR/mqttcl
+    INSTALLS += target
+    QMAKE_CXXFLAGS += -D_TARGET_BUILD
+    QMAKE_CFLAGS += -D_TARGET_BUILD
+}

+ 106 - 0
svc/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: "OEM")) 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:
+
+"OEM-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.
+
+"OEM-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.)
+
+"OEM-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:
+
+"OEM-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.
+
+"OEM-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:
+
+"OEM-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
svc/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

+ 658 - 0
svc/remanent/main.cpp

@@ -0,0 +1,658 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+#include <getopt.h>
+#include <signal.h>
+#include <unistd.h>
+#include <linux/limits.h>
+#include <climits>
+#include <gfa/svc/common/logfile.h>
+#include <gfa/svc/common/fileutil.h>
+#include <gfa/svc/common/strutil.h>
+#include <gfa/svc/common/processclock.h>
+#include <gfa/svc/common/instance.h>
+#include <gfa/svc/common/debug.h>
+#include "projal.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;
+}
+

+ 44 - 0
svc/remanent/remanent.pro

@@ -0,0 +1,44 @@
+DEPLOY_BASEDIR = $$fromfile(../../projal.pri, DEPLOY_BASEDIR)
+DEPLOY_SUBDIR = $$fromfile(../../projal.pri, DEPLOY_SUBDIR)
+
+TEMPLATE = app
+CONFIG += console c++11
+CONFIG -= app_bundle
+CONFIG -= qt
+
+QMAKE_CXXFLAGS_DEBUG += -Wno-unused-parameter -Wno-unused-but-set-variable
+QMAKE_CXXFLAGS += -Wstrict-aliasing=0
+QMAKE_LIBDIR += $$[QT_SYSROOT]/usr/lib/gfa $$[QT_SYSROOT]/usr/lib/gfa/svc
+QMAKE_RPATHDIR += /usr/lib/gfa
+
+QMAKE_CXXFLAGS += -D_REMANENT
+QMAKE_CFLAGS += -D_REMANENT
+
+CONFIG(debug, debug|release) {
+	QMAKE_CXXFLAGS -= -Os
+	QMAKE_CFLAGS -= -Os
+    QMAKE_CXXFLAGS += -D_DEBUG
+    QMAKE_CFLAGS += -D_DEBUG
+        QMAKE_LIBS += -Wl,--start-group -lm -lstdc++ -lgfaipcd -pthread -ljsoncpp -lmysqlclient -l:libremanentd.a -l:libcommond.a -Wl,--end-group
+}
+
+CONFIG(release, debug|release) {
+    QMAKE_CXXFLAGS += -Wno-unused-parameter -Wno-unused-but-set-variable
+    QMAKE_CFLAGS += -Wno-unused-parameter -Wno-unused-but-set-variable
+        QMAKE_LIBS += -Wl,--start-group -lm -lstdc++ -lgfaipc -pthread -ljsoncpp -lmysqlclient -l:libremanent.a -l:libcommon.a -Wl,--end-group
+}
+
+SOURCES += main.cpp
+
+HEADERS += 
+
+INCLUDEPATH += ../../ ../common $$(GEBGFADEV)
+
+linux-buildroot-g++ {
+    QMAKE_CXXFLAGS += -DSITARA_BUILD
+    QMAKE_CFLAGS += -DSITARA_BUILD
+	QMAKE_CXXFLAGS += -D_TARGET_BUILD
+	QMAKE_CFLAGS += -D_TARGET_BUILD
+	target.path = $$DEPLOY_BASEDIR/$$DEPLOY_SUBDIR/remanent
+    INSTALLS += target
+}

+ 8 - 0
svc/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

+ 489 - 0
svc/rest/main.cpp

@@ -0,0 +1,489 @@
+//#include "main.h"
+#include <gfa/svc/common/instance.h>
+#include <gfa/svc/rest/helpers.h>
+#include <gfa/svc/rest/callback.h>
+#include "projal.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;
+}

+ 31 - 0
svc/rest/main.h

@@ -0,0 +1,31 @@
+// 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>
+#include "debug.h"
+#include "defines.h"
+#include "fileutil.h"
+#include "apikey.h"
+
+extern "C"
+{
+#include <ulfius.h>
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// main.h - Declarations:
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	!defined(AGD_MAIN_H__DBD15368_61A6_4741_99B4_4F79DED9B2E4__INCLUDED_)

+ 221 - 0
svc/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": "/struct1/name",
+	"cookie": "idValues1"
+}
+
+Beispiel Array:
+
+[
+	{
+		"path": "/struct1/name",
+		"cookie": "idValues1"
+	},
+	{
+		"path": "/struct1/emailaddr[125]",
+		"cookie": "idValues2"
+	},
+	{
+		"path": "/struct1/pidctrl",
+		"cookie": "idValues3"
+	}
+]
+
+/////////////////////////////////////////////////////////////////////////////
+
+Antwort auf Objekt-Request:
+
+{
+	"uuid": "6f58640d-9fb4-44e4-b2d3-a5e3125f9e26",
+	"request": [
+		{
+			"path": "/struct1/name",
+			"cookie": "idValues1"
+		}
+	],
+	"response": [
+		{
+			"responseType": 0,
+			"requestIndex": 0,
+			"valName": "name",
+			"valType": 2,
+			"path": "/struct1/name",
+			"index": -1,
+			"value": "Monty Python's Flyin"
+		}
+	]
+}
+
+Antwort auf Array-Request:
+
+{
+	"uuid": "6f58640d-9fb4-44e4-b2d3-a5e3125f9e26",
+	"request": [
+		{
+			"path": "/struct1/name",
+			"cookie": "idValues1"
+		},
+		{
+			"path": "/struct1/emailaddr[125]",
+			"cookie": "idValues2"
+		},
+		{
+			"path": "/struct1/pidctrl",
+			"cookie": "idValues3"
+		}
+	],
+	"response": [
+		{
+			"responseType": 0,
+			"requestIndex": 0,
+			"valName": "name",
+			"valType": 2,
+			"path": "/struct1/name",
+			"index": -1,
+			"value": "Monty Python's Flyin"
+		},
+		{
+			"responseType": 1,
+			"requestIndex": 1,
+			"message": "Invalid path!",
+			"code": -1,
+			"path": "/struct1/emailaddr[125]"
+		},
+		{
+			"responseType": 0,
+			"requestIndex": 2,
+			"valName": "pidctrl",
+			"valType": 4,
+			"path": "/struct1/pidctrl",
+			"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": "/struct1/name",
+		"value": "Monty Python's Flying Circus",
+		"cookie": "idValues1"
+	},
+	{
+		"path": "/struct1/emailaddr[125]",
+		"value": "me@there.out",
+		"cookie": "idValues2"
+	},
+	{
+		"path": "/struct1/pidctrl",
+		"value": 123.456,
+		"cookie": "idValues3"
+	},
+	{
+		"path": "/configdata[3]",
+		"value": 1,
+		"cookie": "idValues4"
+	}
+]
+
+Beispiel Response:
+
+{
+	"uuid": "6f58640d-9fb4-44e4-b2d3-a5e3125f9e26",
+	"request": [
+		{
+			"path": "/struct1/name",
+			"cookie": "idValues1",
+			"value": "Monty Python's Flying Circus"
+		},
+		{
+			"path": "/struct1/emailaddr[125]",
+			"cookie": "idValues2",
+			"value": "me@there.out"
+		},
+		{
+			"path": "/struct1/pidctrl",
+			"cookie": "idValues3",
+			"value": 123.456
+		},
+		{
+			"path": "/configdata[3]",
+			"cookie": "idValues4",
+			"value": 1
+		}
+	],
+	"response": [
+		{
+			"message": "",
+			"responseType": 1,
+			"requestIndex": 0,
+			"code": 0,
+			"path": "/struct1/name"
+		},
+		{
+			"message": "Invalid path!",
+			"responseType": 1,
+			"requestIndex": 1,
+			"code": -1,
+			"path": "/struct1/emailaddr[125]"
+		},
+		{
+			"message": "",
+			"responseType": 1,
+			"requestIndex": 2,
+			"code": 0,
+			"path": "/struct1/pidctrl"
+		},
+		{
+			"message": "Cannot assign a value to an object!",
+			"responseType": 1,
+			"requestIndex": 3,
+			"code": -1,
+			"path": "/configdata[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!

+ 42 - 0
svc/rest/rest.pro

@@ -0,0 +1,42 @@
+DEPLOY_BASEDIR = $$fromfile(../../projal.pri, DEPLOY_BASEDIR)
+DEPLOY_SUBDIR = $$fromfile(../../projal.pri, DEPLOY_SUBDIR)
+
+TEMPLATE = app
+CONFIG += console c++11
+CONFIG -= app_bundle
+CONFIG -= qt
+
+QMAKE_CXXFLAGS_DEBUG += -Wno-unused-parameter -Wno-unused-but-set-variable
+QMAKE_CXXFLAGS += -Wstrict-aliasing=0
+
+QMAKE_CXXFLAGS += -D_REST
+QMAKE_CFLAGS += -D_REST
+QMAKE_LIBDIR += $$[QT_SYSROOT]/usr/lib/gfa $$[QT_SYSROOT]/usr/lib/gfa/svc
+QMAKE_RPATHDIR += /usr/lib/gfa
+
+QMAKE_LIBS += -lm -lstdc++ -lgfaipc -lulfius -ljansson -pthread -lpthread -lorcania -lcurl -lssl -lcrypto
+
+CONFIG(debug, debug|release) {
+    QMAKE_CXXFLAGS -= -Os
+    QMAKE_CFLAGS -= -Os
+    QMAKE_CXXFLAGS += -D_DEBUG
+    QMAKE_CFLAGS += -D_DEBUG
+	QMAKE_LIBS += -l:librestd.a -l:libcommond.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
+	QMAKE_LIBS += -l:librest.a -l:libcommon.a
+}
+
+SOURCES += main.cpp
+
+HEADERS += main.h
+
+INCLUDEPATH += ../../ $$(GEBGFADEV)
+
+linux-buildroot-g++ {
+    target.path = $$DEPLOY_BASEDIR/$$DEPLOY_SUBDIR/rest
+    INSTALLS += target
+}

+ 8 - 0
svc/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

+ 571 - 0
svc/summarist/main.cpp

@@ -0,0 +1,571 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+#include <signal.h>
+#include <linux/limits.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <gfa/gfaipc.h>
+#include <gfa/svc/common/strutil.h>
+#include <gfa/svc/common/logfile.h>
+#include <gfa/svc/common/fileutil.h>
+#include <gfa/svc/common/instance.h>
+#include <gfa/svc/common/processclock.h>
+#include <gfa/svc/common/debug.h>
+#include <gfa/svc/summarist/summarist.h>
+#include "projal.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// app control
+
+#define _APPID						GFA_APPCTRL_APPID_SUMMARIST
+#define _APPNAME					"Summarist"
+//#define _DEPENDENCIES				((appid_t)(GFA_APPCTRL_APPID_REMANENT))
+#define _APP_CTRL_CYCLE_US			1000000
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+#define _TRACK_TIME					1
+#define _LOGFILE_NAME				"summarist.log"
+#define _SIG_BLOCK(s)				sigprocmask(SIG_BLOCK, (s), NULL)
+#define _SIG_UNBLOCK(s)				sigprocmask(SIG_UNBLOCK, (s), NULL)
+#define _US_PER_SEC					1000000
+
+#ifdef _SUMMARIST_CU_SLEEP_INTERVAL
+#if _SUMMARIST_CU_SLEEP_INTERVAL < 10
+#define _CATCH_UP_SLEEP_INTERVAL		10000
+#else	//	_SUMMARIST_CU_SLEEP_INTERVAL < 10
+#define _CATCH_UP_SLEEP_INTERVAL		(_SUMMARIST_CU_SLEEP_INTERVAL * 1000)
+#endif	//	_SUMMARIST_CU_SLEEP_INTERVAL < 10
+#else	//	_SUMMARIST_CU_SLEEP_INTERVAL
+#define _CATCH_UP_SLEEP_INTERVAL		(_US_PER_SEC / 10)
+#endif	//	_SUMMARIST_CU_SLEEP_INTERVAL
+
+#if _TRACK_TIME
+#define _CLOCK_TRIGGER()			g_pc.ClockTrigger(1)
+#define _CLOCK_TRACE(f)				_TraceTime(f)
+#else	//	_TRACK_TIME
+#define _CLOCK_TRIGGER()
+#define _CLOCK_TRACE(f)
+#endif	//	_TRACK_TIME
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+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 CProcessClock				g_pc;
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+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;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+#if _TRACK_TIME
+static void _TraceTime(const char *pszMsg)
+{
+	TRACE("%s (%s).\n", pszMsg ? pszMsg : "", CProcessClock::Interval2String(g_pc.ClockGetElapsed(1)).c_str());
+}
+#endif	//	_TRACK_TIME
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+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));
+		}
+	}
+}*/
+
+void _AppCtrlUsleep(HAPPCTRL hAC, unsigned long nUsSleep, unsigned long long nUsWorkTime)
+{
+	HAPPINFO hAI;
+
+	while(nUsSleep > _APP_CTRL_CYCLE_US)
+	{
+		nUsSleep -= _APP_CTRL_CYCLE_US;
+		usleep(_APP_CTRL_CYCLE_US);
+
+		if((hAI = ::GfaIpcAppCtrlInfoUpdate(hAC, nUsWorkTime)))
+		{
+			_ProcessCtrlMessages(hAC, hAI);
+			if(!g_fRun)
+				return;
+//			_ProcessStateEvents(hAC, hAI);
+		}
+	}
+	
+	if(nUsSleep)
+	{
+		usleep(nUsSleep);
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+
+int main(int argc, char **argv)
+{
+    CProcessInstance pi;
+	HAPPCTRL hAC = NULL;
+	HAPPINFO hAI;
+	char szLogFile[PATH_MAX];
+	std::string strBaseDir;
+	const char *pszBaseDir = NULL;
+	bool bDropTables = false;
+	int c, ca;
+	CProcessClock pcWork;
+	unsigned long long nUsWorkTime = 0;
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    // check for multiple instances
+
+    if(!pi.LockInstance(UUID_SHM))
+    {
+		CLogfile::StdErr("Failed to start instance!\n");
+        return -1;
+    }
+
+	////////////////////////////////////////////////////////////////////////////////////////////////
+
+	while((c = getopt(argc, argv, "d:")) != -1)
+	{
+		switch(c)
+		{
+		case 'd':
+			if((ca = atoi(optarg)) == 666)
+				bDropTables = true;
+			break;
+		}
+	}
+
+	////////////////////////////////////////////////////////////////////////////////////////////////
+	// 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");
+			break;
+		}
+
+#if _SUMMARIST_ENABLED
+
+		g_lf.Info("Process started.\n");
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+		// initialize app control
+
+		g_lf.Info("Acquire AppCtrl-Handle.\n");
+
+		if(!(hAC = ::GfaIpcAppCtrlAcquire(_APPID, _APPNAME, _APP_CTRL_CYCLE_US, _APP_CTRL_CYCLE_US * 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;
+		}*/
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+
+		if(strlen(_DLOG_DB_NAME) >= _SUM_MAX_DB_NAME_LENGTH)
+		{
+	        g_lf.Error("Database name too long!\n");
+			break;
+		}
+
+		if(strlen(_DLOG_DB_USER) >= _SUM_MAX_DB_USER_LENGTH)
+		{
+	        g_lf.Error("Database username too long!\n");
+			break;
+		}
+
+		if(strlen(_DLOG_DB_PASS) >= _SUM_MAX_DB_PASS_LENGTH)
+		{
+	        g_lf.Error("Database password too long!\n");
+			break;
+		}
+
+		if(strlen(_DLOG_TAGS_TABLE) >= _SUM_MAX_TABLE_NAME_LENGTH)
+		{
+	        g_lf.Error("Tag table name too long!\n");
+			break;
+		}
+
+		if(strlen(_DLOG_LOGS_TABLE) >= _SUM_MAX_TABLE_NAME_LENGTH)
+		{
+	        g_lf.Error("Log table name too long!\n");
+			break;
+		}
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+		// initialize summarist
+
+		SUMMARIST_PARAMS sup;
+
+		memset(&sup, 0, sizeof(sup));
+		strncpy(sup.szDBName, _DLOG_DB_NAME, _SUM_MAX_DB_NAME_LENGTH - 1);
+		strncpy(sup.szDBUser, _DLOG_DB_USER, _SUM_MAX_DB_USER_LENGTH - 1);
+		strncpy(sup.szDBPass, _DLOG_DB_PASS, _SUM_MAX_DB_PASS_LENGTH - 1);
+		strncpy(sup.szTagsTable, _DLOG_TAGS_TABLE, _SUM_MAX_TABLE_NAME_LENGTH - 1);
+		strncpy(sup.szLogsTable, _DLOG_LOGS_TABLE, _SUM_MAX_TABLE_NAME_LENGTH - 1);
+		strcpy(sup.szITagsView, "ilTags");
+		sup.nSampleIntv = _DLOG_INTV_SAMPLE;
+		sup.nLogIntv = _DLOG_INTV_LOG;
+		sup.nWriteIntv = _DLOG_INTV_WRITE;
+
+		CSummarist sum(&sup, g_lf);
+
+		if(!sum.Initialze(g_timeWnds, _countof(g_timeWnds), bDropTables))
+			break;
+		else if(bDropTables)
+		{
+			g_fZombie = false;
+			break;
+		}
+
+		////////////////////////////////////////////////////////////////////////////////////////////
+		// do work
+
+		CMySqlDB db;
+		time_t tsMinLogs, tsMaxLogs, tsBase, tsNext;
+#if _SUMMARIST_PROCESS_OUTDATED
+		time_t tsLastOutdated = 0;
+#endif	//	_SUMMARIST_PROCESS_OUTDATED
+
+		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, nUsWorkTime)))
+			{
+				_ProcessCtrlMessages(hAC, hAI);
+				if(!g_fRun)
+					break;
+//				_ProcessStateEvents(hAC, hAI);
+			}
+
+			////////////////////////////////////////////////////////////////////////////////////////
+
+			if(!g_fPauseImp && !g_fPauseCmd)
+			{
+				if(sum.Connect(db))
+				{
+					pcWork.ClockTrigger();
+					unsigned long nUsSleep = sum.GetProcessingFrequenzy() * _US_PER_SEC;
+
+					if(sum.GetMinMaxTimestampFromLogs(db, tsMinLogs, tsMaxLogs))
+					{
+						if((tsMinLogs != _SUM_INVALID_TIMESTAMP_VALUE) && (tsMaxLogs != _SUM_INVALID_TIMESTAMP_VALUE))
+						{
+							tsBase = sum.GetBaseTimestamp(tsMinLogs);
+
+							for(size_t i = 0; (i < sum.TimeWndCount()) && g_fRun; ++i)
+							{
+								time_t tSumLast, tWndFrom, tWndTo;
+
+								if(!sum.GetLastSummarizeTimestamp(db, i, tSumLast))
+								{
+									g_fZombie = true;
+									g_fRun = false;
+									break;
+								}
+
+								if(sum.GetNextTimeFrame(db, i, tSumLast, tsBase, tWndFrom, tWndTo))
+								{
+									if((tWndFrom <= tsMaxLogs) && (tWndTo <= time(NULL)))
+									{
+										_SIG_BLOCK(&g_set);
+										_CLOCK_TRIGGER();
+										if(g_fRun)
+											g_fRun = sum.Summarize(db, i, tWndFrom, tWndTo);
+										_CLOCK_TRACE("Summarize");
+										_SIG_UNBLOCK(&g_set);
+									}
+
+		                            sum.SetNextTimeFrameStart(i, tWndTo);
+		                        }
+							}
+
+							if((tsNext = sum.GetNextDueTimeFrameStart()) != _SUM_INVALID_TIMESTAMP_VALUE)
+							{
+								time_t tsNow = time(NULL);
+
+		                        if(tsNext > tsNow)
+		                            nUsSleep = ((tsNext - tsNow) + 1) * _US_PER_SEC;
+								else if(tsNext < tsMaxLogs)
+									nUsSleep = _CATCH_UP_SLEEP_INTERVAL;
+							}
+
+#if _SUMMARIST_PROCESS_OUTDATED
+							if(g_fRun && (nUsSleep > _CATCH_UP_SLEEP_INTERVAL) && (tsLastOutdated < tsMinLogs))	// not in catch-up mode
+							{
+								g_pc.ClockTrigger();
+								pc_time64_t nElapsed;
+
+								for(size_t i = 0; g_fRun && (i < sum.TimeWndCount()); ++i)
+								{
+									_SIG_BLOCK(&g_set);
+									_CLOCK_TRIGGER();
+									sum.ProcessOutdated(db, i, tsMinLogs);
+									_CLOCK_TRACE("ProcessOutdated");
+									_SIG_UNBLOCK(&g_set);
+								}
+
+		                        nElapsed = g_pc.ClockGetElapsed() / 1000LL;
+								if((pc_time64_t)nUsSleep > nElapsed)
+									nUsSleep -= (unsigned long)nElapsed;
+
+								tsLastOutdated = tsMinLogs;
+							}
+#endif	//	_SUMMARIST_PROCESS_OUTDATED
+						}
+						else
+						{
+							TRACE("GetMinMaxTimestampFromLogs: timestamps invalid - no logs so far?\n");
+							g_lf.Warning("GetMinMaxTimestampFromLogs: timestamps invalid - no logs so far?\n");
+						}
+					}
+					else
+					{
+						TRACE("GetMinMaxTimestampFromLogs failed!\n");
+						g_fZombie = true;
+						g_fRun = false;
+					}
+
+					sum.Close(db);
+					nUsWorkTime = pcWork.ClockGetElapsed() / 1000;
+
+					if(g_fRun)
+					{
+		                TRACE("Sleep: %s ...\n", CProcessClock::Interval2String((pc_time64_t)nUsSleep * 1000LL).c_str());
+		                _AppCtrlUsleep(hAC, nUsSleep, nUsWorkTime);
+					}
+				}
+				else
+				{
+					TRACE("Database connection failed!\n");
+					g_fZombie = true;
+					g_fRun = false;
+				}
+			}
+			else
+			{
+				_AppCtrlUsleep(hAC, _APP_CTRL_CYCLE_US, 0);
+			}
+		} // while(g_fRun)
+	}
+	while(false);
+
+	////////////////////////////////////////////////////////////////////////////////////////////////
+
+	if(g_nLastSig >= 0)
+	{
+		g_lf.Info("Received signal '%s'.\n", strsignal(g_nLastSig));
+		g_nLastSig = -1;
+	}
+	
+	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("Normal process exit.\n\n");
+	g_lf.Close();
+	CLogfile::StdErr("Summarist exit.\n");
+	return 0;
+
+#else	//	_SUMMARIST_ENABLED
+	g_lf.Info("Summarist disabled!\n");
+	return -1;
+#endif	//	_SUMMARIST_ENABLED
+}

+ 43 - 0
svc/summarist/summarist.pro

@@ -0,0 +1,43 @@
+DEPLOY_BASEDIR = $$fromfile(../../projal.pri, DEPLOY_BASEDIR)
+DEPLOY_SUBDIR = $$fromfile(../../projal.pri, DEPLOY_SUBDIR)
+
+TEMPLATE = app
+CONFIG += console c++11
+CONFIG -= app_bundle
+CONFIG -= qt
+
+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 += $$[QT_SYSROOT]/usr/lib/gfa $$[QT_SYSROOT]/usr/lib/gfa/svc
+QMAKE_RPATHDIR += /usr/lib/gfa
+
+QMAKE_CXXFLAGS += -D_DL_SUMMARIST
+QMAKE_CFLAGS += -D_DL_SUMMARIST
+
+CONFIG(debug, debug|release) {
+	QMAKE_CXXFLAGS -= -Os
+	QMAKE_CFLAGS -= -Os
+    QMAKE_CXXFLAGS += -D_DEBUG
+    QMAKE_CFLAGS += -D_DEBUG
+	QMAKE_LIBS += -lm -lstdc++ -pthread -lgfaipc -l:libsummaristd.a -l:libcommond.a -lmysqlclient
+}
+
+CONFIG(release, debug|release) {
+    QMAKE_CXXFLAGS += -Wno-unused-parameter -Wno-unused-but-set-variable
+    QMAKE_CFLAGS += -Wno-unused-parameter -Wno-unused-but-set-variable
+	QMAKE_LIBS += -lm -lstdc++ -pthread -lgfaipc -l:libsummarist.a -l:libcommon.a -lmysqlclient
+}
+
+SOURCES += main.cpp
+
+HEADERS += 
+
+INCLUDEPATH += ../../  $$(GEBGFADEV)
+
+linux-buildroot-g++ {
+    target.path = $$DEPLOY_BASEDIR/$$DEPLOY_SUBDIR/summarist
+    INSTALLS += target
+    QMAKE_CXXFLAGS += -D_TARGET_BUILD
+    QMAKE_CFLAGS += -D_TARGET_BUILD
+}

+ 11 - 0
svc/svc.pro

@@ -0,0 +1,11 @@
+TEMPLATE = subdirs
+
+SUBDIRS += \
+		datalogger \
+		mqttcl \
+		remanent \
+		rest \
+		summarist
+
+CONFIG += qtc_runnable
+CONFIG += ordered

+ 7 - 0
usr/usr.pro

@@ -0,0 +1,7 @@
+TEMPLATE = subdirs
+
+SUBDIRS += \
+		visu
+
+CONFIG += qtc_runnable
+CONFIG += ordered

BIN
usr/visu/img/ata64.png


BIN
usr/visu/img/blank.png


BIN
usr/visu/img/header.png


BIN
usr/visu/img/mmc64.png


BIN
usr/visu/img/next.png


BIN
usr/visu/img/prev.png


BIN
usr/visu/img/usb64.png


+ 132 - 0
usr/visu/main.cpp

@@ -0,0 +1,132 @@
+#include <signal.h>
+#include <atomic>
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+#include <gfa/qappctrl.h>
+#include <gfa/svc/common/instance.h>
+#include <gfa/svc/common/debug.h>
+#include <gfa/svc/shmqml/shmthread.h>
+#include "projal.h"
+
+#define _APP_ID								GFA_APPCTRL_APPID_USER_11
+#define _APP_NAME							"qmlDemo"
+#define _UPDATE_TIMER_INT					50
+#define _UPDATE_TIMER_INT_HEAVY_LOAD		5000
+#define _APP_SUBSCRIPTIONS					(GFA_APPCTRL_APPID_ALL_GFA | GFA_APPCTRL_APPID_USER_11)
+
+#define _SIG_BLOCK(s)						sigprocmask(SIG_BLOCK, (s), NULL)
+#define _SIG_UNBLOCK(s)						sigprocmask(SIG_UNBLOCK, (s), NULL)
+
+static sigset_t								g_set;
+static std::atomic_uintptr_t				g_appPtr(0);
+
+
+static void _SigHandler(int sig)
+{
+	QGuiApplication *pApp = (QGuiApplication*)g_appPtr.load();
+	if(pApp)
+		pApp->exit(-sig);
+}
+
+#undef GfaIpcLockSHM
+#undef GfaIpcUnlockSHM
+
+static void _LockSHM(HSHM hShm)
+{
+	::GfaIpcLockSHMAndSigBlock(hShm, &g_set);
+}
+
+static void _UnlockSHM(HSHM hShm)
+{
+	::GfaIpcUnlockSHMAndSigUnblock(hShm, &g_set);
+}
+
+int main(int argc, char *argv[])
+{
+    /////////////////////////////////////////////////////////////////////////
+    // check for multiple instances
+
+    CProcessInstance pi;
+
+    if(!pi.LockInstance(UUID_SHM))
+    {
+        ETRACE("Failed to start instance!\n");
+        return -1;
+    }
+
+    /////////////////////////////////////////////////////////////////////////
+	// configure signal handling
+
+	struct sigaction sa;
+	::sigfillset(&g_set);
+	::sigdelset(&g_set, SIGSEGV);
+	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
+
+	set_lock_unlock_functions(_LockSHM, _UnlockSHM);
+
+    /////////////////////////////////////////////////////////////////////////
+
+    //qputenv("QT_IM_MODULE", QByteArray("QtFreeVirtualKeyboard"));
+
+    /////////////////////////////////////////////////////////////////////////
+
+	QGfaAppCtrl appCtrl;
+    QGuiApplication app(argc, argv);
+    g_appPtr.store((uintptr_t)&app);
+    QQmlApplicationEngine engine;
+
+    int nRet = -1;
+    HSHM hShm = NULL;
+
+    if(appCtrl.Create(_APP_ID, _APP_NAME, _UPDATE_TIMER_INT, _UPDATE_TIMER_INT_HEAVY_LOAD))
+    {
+		appCtrl.SetLockUnlockFunctions(_LockSHM, _UnlockSHM);
+		appCtrl.SubscribeStateEvents(_APP_SUBSCRIPTIONS);
+		appCtrl.SetState(GIAS_Initializing);
+
+	    if((hShm = acquire_shm(sizeof(shm_t))))
+	    {
+	        void *pShm = ::GfaIpcAcquirePointer(hShm);
+
+	        if(pShm)
+	        {
+	            CShm_t shm(pShm, hShm, NULL);
+	            CShmWatcher<CShm_t, shm_t> watcher(hShm, shm, pShm);
+	            watcher.SetLockUnlockFunctions(_LockSHM, _UnlockSHM);
+	            register_types(engine, &shm);
+
+				appCtrl.RegisterQmlTypes(engine, 1, 0);
+                engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
+				appCtrl.SetState(GIAS_Running);
+
+	            watcher.StartWatch(_SHM_SCAN_INTERVAL_MS);
+	            nRet = app.exec();
+	            watcher.StopWatch();
+
+	            ::GfaIpcReleasePointer(hShm, pShm);
+	        }
+
+	        ::GfaIpcReleaseSHM(hShm);
+	    }
+
+		appCtrl.SetState(GIAS_Terminating);
+		appCtrl.Release();
+	}
+
+    g_appPtr.store(0);
+    return nRet;
+}

+ 14 - 0
usr/visu/qml.qrc

@@ -0,0 +1,14 @@
+<RCC>
+    <qresource prefix="/">
+        <file>qml/main.qml</file>
+        <file>qml/appctrl.qml</file>
+        <file>qml/shm.qml</file>
+        <file>img/ata64.png</file>
+        <file>img/blank.png</file>
+        <file>img/header.png</file>
+        <file>img/mmc64.png</file>
+        <file>img/next.png</file>
+        <file>img/prev.png</file>
+        <file>img/usb64.png</file>
+    </qresource>
+</RCC>

+ 1147 - 0
usr/visu/qml/appctrl.qml

@@ -0,0 +1,1147 @@
+import QtQuick 2.6
+import QtQuick.Window 2.2
+import QtQuick.Controls 1.5
+import QtQuick.Controls.Styles 1.4
+import QtQuick.Controls.Private 1.0
+import com.gfa.ipc.appctrl 1.0
+
+Rectangle {
+    width: 800
+    height: 480
+    
+    function stateColor(state)
+    {
+    	switch(state)
+    	{
+    	case QGfaAppCtrl.STATE_RUNNING:
+    		return "lightgreen";
+    	case QGfaAppCtrl.STATE_PAUSED:
+    		return "lightgrey";
+    	case QGfaAppCtrl.STATE_HANGING:
+    		return "orange";
+    	case QGfaAppCtrl.STATE_ZOMBIE:
+    		return "red";
+    	default:
+    		return "white";
+    	}
+    }
+    
+    function sec2HMS(sec)
+    {
+    	var h = parseInt(sec / 3600);
+    	sec -= h * 3600;
+    	var m = parseInt(sec / 60);
+    	sec -= m * 60;
+    	return "" + h + (m < 10 ? ":0" : ":") + m + (sec < 10 ? ":0" : ":") + sec;
+    }
+
+	/////////////////////////////////////////////////////////////////////////////////////////////////
+
+	Rectangle {
+        x: 10
+        y: 10
+        width: parent.width - 20
+        height: 30
+        radius: 5
+		border.width: 1
+		border.color: "black"
+        color: "lightgreen"
+		Image {
+            anchors.left: parent.left
+			anchors.verticalCenter: parent.verticalCenter
+			fillMode: Image.PreserveAspectFit
+			height: parent.height
+            source: "qrc:/img/prev.png"
+			MouseArea {
+				anchors.fill: parent
+                onClicked: { idPageLoader.source = "shm.qml" }
+			}
+		}
+	    Text {
+	        font.pixelSize: 16
+	        font.bold: true
+			anchors.verticalCenter: parent.verticalCenter
+            anchors.centerIn: parent
+	        text: "GfA App Control"
+	    }
+		Image {
+            anchors.right: parent.right
+			anchors.verticalCenter: parent.verticalCenter
+			fillMode: Image.PreserveAspectFit
+			height: parent.height
+            source: "qrc:/img/blank.png"
+		}
+/*		Image {
+            anchors.right: parent.right
+			anchors.verticalCenter: parent.verticalCenter
+			fillMode: Image.PreserveAspectFit
+			height: parent.height
+            source: "qrc:/img/next.png"
+			MouseArea {
+		        anchors.fill: parent
+				onClicked: { idPageLoader.source = "stgdev.qml" }
+			}
+		}*/
+	}
+
+	/////////////////////////////////////////////////////////////////////////////////////////////////
+
+    Rectangle {
+        x: 10
+        y: 50
+        width: 120
+        height: 420
+
+		Rectangle {
+	        x: 0
+	        y: 40
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pixelSize: 14
+		        font.italic: true
+				anchors.verticalCenter: parent.verticalCenter
+	            anchors.left: parent.left
+		        text: "State:"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 70
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pixelSize: 14
+		        font.italic: true
+				anchors.verticalCenter: parent.verticalCenter
+	            anchors.left: parent.left
+		        text: "Cyc. cur. μs:"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 100
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pixelSize: 14
+		        font.italic: true
+				anchors.verticalCenter: parent.verticalCenter
+	            anchors.left: parent.left
+		        text: "Wkt. max. μs:"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 130
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pixelSize: 14
+		        font.italic: true
+				anchors.verticalCenter: parent.verticalCenter
+	            anchors.left: parent.left
+		        text: "Wkt. cur. μs:"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 160
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pixelSize: 14
+		        font.italic: true
+				anchors.verticalCenter: parent.verticalCenter
+	            anchors.left: parent.left
+		        text: "CPU avg. %:"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 190
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pixelSize: 14
+		        font.italic: true
+				anchors.verticalCenter: parent.verticalCenter
+	            anchors.left: parent.left
+		        text: "CPU cur. %:"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 220
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pixelSize: 14
+		        font.italic: true
+				anchors.verticalCenter: parent.verticalCenter
+	            anchors.left: parent.left
+		        text: "VM RSS KiB:"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 250
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pixelSize: 14
+		        font.italic: true
+				anchors.verticalCenter: parent.verticalCenter
+	            anchors.left: parent.left
+		        text: "VM Size KiB:"
+		    }
+		}
+	}
+
+	/////////////////////////////////////////////////////////////////////////////////////////////////
+
+    Rectangle {
+        x: 140
+        y: 50
+        width: 120
+        height: 420
+
+		property var appInfo: qGfaAppCtrl.appInfo[QGfaAppCtrl.APP_INDEX_REMANENT]
+
+		Rectangle {
+	        x: 0
+	        y: 10
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pointSize: 9
+		        font.bold: true
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.name
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 40
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+	        color: stateColor(parent.appInfo.state)
+		    Text {
+		        font.pixelSize: 14
+		        font.bold: true
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.stateText
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 70
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.cycCur
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 100
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.wktMax
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 130
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.wktCur
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 160
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.cpuAvg.toFixed(2)
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 190
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.cpuCur.toFixed(2)
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 220
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.vmRSS
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 250
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.vmSize
+		    }
+		}
+
+	    Rectangle {
+	        x: 0
+	        y: 300
+	        height: 30
+	        width: parent.width
+			Button {
+	            anchors.fill: parent
+				style: idButtonStyle
+	            enabled: (parent.parent.appInfo.state === QGfaAppCtrl.STATE_RUNNING || parent.parent.appInfo.state === QGfaAppCtrl.STATE_PAUSED)
+	            text: (parent.parent.appInfo.state === QGfaAppCtrl.STATE_PAUSED) ? "Resume" : "Pause"
+	            onClicked: (parent.parent.appInfo.state === QGfaAppCtrl.STATE_PAUSED) ? parent.parent.appInfo.resume() : parent.parent.appInfo.pause()
+			}
+	    }
+
+	    Rectangle {
+	        x: 0
+	        y: 340
+	        height: 30
+	        width: parent.width
+			Button {
+	            text: "Stop"
+				style: idButtonStyle
+	            anchors.fill: parent
+	            onClicked: parent.parent.appInfo.stop()
+                enabled: (parent.parent.appInfo.state !== QGfaAppCtrl.STATE_NOT_RUNNING) && (parent.parent.appInfo.state !== QGfaAppCtrl.STATE_ZOMBIE)
+			}
+	    }
+
+	    Rectangle {
+	        x: 0
+	        y: 380
+	        height: 30
+	        width: parent.width
+			Button {
+	            text: "Kill"
+				style: idButtonStyle
+	            anchors.fill: parent
+	            onClicked: parent.parent.appInfo.kill()
+                enabled: (parent.parent.appInfo.state === QGfaAppCtrl.STATE_ZOMBIE)
+			}
+	    }
+	}
+
+	/////////////////////////////////////////////////////////////////////////////////////////////////
+
+    Rectangle {
+        x: 270
+        y: 50
+        width: 120
+        height: 420
+
+        property var appInfo: qGfaAppCtrl.appInfo[QGfaAppCtrl.APP_INDEX_SYSINFO]
+
+		Rectangle {
+	        x: 0
+	        y: 10
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pointSize: 9
+		        font.bold: true
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.name
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 40
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+	        color: stateColor(parent.appInfo.state)
+		    Text {
+		        font.pixelSize: 14
+		        font.bold: true
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.stateText
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 70
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.cycCur
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 100
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.wktMax
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 130
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.wktCur
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 160
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.cpuAvg.toFixed(2)
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 190
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.cpuCur.toFixed(2)
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 220
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.vmRSS
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 250
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.vmSize
+		    }
+		}
+
+	    Rectangle {
+	        x: 0
+	        y: 300
+	        height: 30
+	        width: parent.width
+			Button {
+	            anchors.fill: parent
+				style: idButtonStyle
+	            enabled: (parent.parent.appInfo.state === QGfaAppCtrl.STATE_RUNNING || parent.parent.appInfo.state === QGfaAppCtrl.STATE_PAUSED)
+	            text: (parent.parent.appInfo.state === QGfaAppCtrl.STATE_PAUSED) ? "Resume" : "Pause"
+	            onClicked: (parent.parent.appInfo.state === QGfaAppCtrl.STATE_PAUSED) ? parent.parent.appInfo.resume() : parent.parent.appInfo.pause()
+			}
+	    }
+
+	    Rectangle {
+	        x: 0
+	        y: 340
+	        height: 30
+	        width: parent.width
+			Button {
+	            text: "Stop"
+				style: idButtonStyle
+	            anchors.fill: parent
+	            onClicked: parent.parent.appInfo.stop()
+                enabled: (parent.parent.appInfo.state !== QGfaAppCtrl.STATE_NOT_RUNNING) && (parent.parent.appInfo.state !== QGfaAppCtrl.STATE_ZOMBIE)
+			}
+	    }
+
+	    Rectangle {
+	        x: 0
+	        y: 380
+	        height: 30
+	        width: parent.width
+			Button {
+	            text: "Kill"
+				style: idButtonStyle
+	            anchors.fill: parent
+	            onClicked: parent.parent.appInfo.kill()
+                enabled: (parent.parent.appInfo.state === QGfaAppCtrl.STATE_ZOMBIE)
+			}
+	    }
+	}
+
+	/////////////////////////////////////////////////////////////////////////////////////////////////
+
+    Rectangle {
+        x: 400
+        y: 50
+        width: 120
+        height: 420
+
+		property var appInfo: qGfaAppCtrl.appInfo[QGfaAppCtrl.APP_INDEX_MQTTCL]
+
+		Rectangle {
+	        x: 0
+	        y: 10
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pointSize: 9
+		        font.bold: true
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.name
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 40
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+	        color: stateColor(parent.appInfo.state)
+		    Text {
+		        font.pixelSize: 14
+		        font.bold: true
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.stateText
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 70
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.cycCur
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 100
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.wktMax
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 130
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.wktCur
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 160
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.cpuAvg.toFixed(2)
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 190
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.cpuCur.toFixed(2)
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 220
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.vmRSS
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 250
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.vmSize
+		    }
+		}
+
+	    Rectangle {
+	        x: 0
+	        y: 300
+	        height: 30
+	        width: parent.width
+			Button {
+	            anchors.fill: parent
+				style: idButtonStyle
+	            enabled: (parent.parent.appInfo.state === QGfaAppCtrl.STATE_RUNNING || parent.parent.appInfo.state === QGfaAppCtrl.STATE_PAUSED)
+	            text: (parent.parent.appInfo.state === QGfaAppCtrl.STATE_PAUSED) ? "Resume" : "Pause"
+	            onClicked: (parent.parent.appInfo.state === QGfaAppCtrl.STATE_PAUSED) ? parent.parent.appInfo.resume() : parent.parent.appInfo.pause()
+			}
+	    }
+
+	    Rectangle {
+	        x: 0
+	        y: 340
+	        height: 30
+	        width: parent.width
+			Button {
+	            text: "Stop"
+				style: idButtonStyle
+	            anchors.fill: parent
+	            onClicked: parent.parent.appInfo.stop()
+                enabled: (parent.parent.appInfo.state !== QGfaAppCtrl.STATE_NOT_RUNNING) && (parent.parent.appInfo.state !== QGfaAppCtrl.STATE_ZOMBIE)
+			}
+	    }
+
+	    Rectangle {
+	        x: 0
+	        y: 380
+	        height: 30
+	        width: parent.width
+			Button {
+	            text: "Kill"
+				style: idButtonStyle
+	            anchors.fill: parent
+	            onClicked: parent.parent.appInfo.kill()
+                enabled: (parent.parent.appInfo.state === QGfaAppCtrl.STATE_ZOMBIE)
+			}
+	    }
+	}
+
+	/////////////////////////////////////////////////////////////////////////////////////////////////
+
+    Rectangle {
+        x: 530
+        y: 50
+        width: 120
+        height: 420
+
+		property var appInfo: qGfaAppCtrl.appInfo[QGfaAppCtrl.APP_INDEX_REST]
+
+		Rectangle {
+	        x: 0
+	        y: 10
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pointSize: 9
+		        font.bold: true
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.name
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 40
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+	        color: stateColor(parent.appInfo.state)
+		    Text {
+		        font.pixelSize: 14
+		        font.bold: true
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.stateText
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 70
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.cycCur
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 100
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.wktMax
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 130
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.wktCur
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 160
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.cpuAvg.toFixed(2)
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 190
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.cpuCur.toFixed(2)
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 220
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.vmRSS
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 250
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.vmSize
+		    }
+		}
+
+	    Rectangle {
+	        x: 0
+	        y: 300
+	        height: 30
+	        width: parent.width
+			Button {
+	            anchors.fill: parent
+				style: idButtonStyle
+	            enabled: (parent.parent.appInfo.state === QGfaAppCtrl.STATE_RUNNING || parent.parent.appInfo.state === QGfaAppCtrl.STATE_PAUSED)
+	            text: (parent.parent.appInfo.state === QGfaAppCtrl.STATE_PAUSED) ? "Resume" : "Pause"
+	            onClicked: (parent.parent.appInfo.state === QGfaAppCtrl.STATE_PAUSED) ? parent.parent.appInfo.resume() : parent.parent.appInfo.pause()
+			}
+	    }
+
+	    Rectangle {
+	        x: 0
+	        y: 340
+	        height: 30
+	        width: parent.width
+			Button {
+	            text: "Stop"
+				style: idButtonStyle
+	            anchors.fill: parent
+	            onClicked: parent.parent.appInfo.stop()
+                enabled: (parent.parent.appInfo.state !== QGfaAppCtrl.STATE_NOT_RUNNING) && (parent.parent.appInfo.state !== QGfaAppCtrl.STATE_ZOMBIE)
+			}
+	    }
+
+	    Rectangle {
+	        x: 0
+	        y: 380
+	        height: 30
+	        width: parent.width
+			Button {
+	            text: "Kill"
+				style: idButtonStyle
+	            anchors.fill: parent
+	            onClicked: parent.parent.appInfo.kill()
+                enabled: (parent.parent.appInfo.state === QGfaAppCtrl.STATE_ZOMBIE)
+			}
+	    }
+	}
+
+	/////////////////////////////////////////////////////////////////////////////////////////////////
+
+    Rectangle {
+        x: 660
+        y: 50
+        width: 120
+        height: 420
+
+        property var appInfo: qGfaAppCtrl.appInfo[QGfaAppCtrl.APP_INDEX_DATALOGGER]
+
+		Rectangle {
+	        x: 0
+	        y: 10
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pointSize: 9
+		        font.bold: true
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.name
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 40
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+	        color: stateColor(parent.appInfo.state)
+		    Text {
+		        font.pixelSize: 14
+		        font.bold: true
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.stateText
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 70
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.cycCur
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 100
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.wktMax
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 130
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.wktCur
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 160
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.cpuAvg.toFixed(2)
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 190
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.cpuCur.toFixed(2)
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 220
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.vmRSS
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 250
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pixelSize: 14
+	            anchors.centerIn: parent
+		        text: parent.parent.appInfo.vmSize
+		    }
+		}
+
+	    Rectangle {
+	        x: 0
+	        y: 300
+	        height: 30
+	        width: parent.width
+			Button {
+	            anchors.fill: parent
+				style: idButtonStyle
+	            enabled: (parent.parent.appInfo.state === QGfaAppCtrl.STATE_RUNNING || parent.parent.appInfo.state === QGfaAppCtrl.STATE_PAUSED)
+	            text: (parent.parent.appInfo.state === QGfaAppCtrl.STATE_PAUSED) ? "Resume" : "Pause"
+	            onClicked: (parent.parent.appInfo.state === QGfaAppCtrl.STATE_PAUSED) ? parent.parent.appInfo.resume() : parent.parent.appInfo.pause()
+			}
+	    }
+
+	    Rectangle {
+	        x: 0
+	        y: 340
+	        height: 30
+	        width: parent.width
+			Button {
+	            text: "Stop"
+				style: idButtonStyle
+	            anchors.fill: parent
+	            onClicked: parent.parent.appInfo.stop()
+                enabled: (parent.parent.appInfo.state !== QGfaAppCtrl.STATE_NOT_RUNNING) && (parent.parent.appInfo.state !== QGfaAppCtrl.STATE_ZOMBIE)
+			}
+	    }
+
+	    Rectangle {
+	        x: 0
+	        y: 380
+	        height: 30
+	        width: parent.width
+			Button {
+	            text: "Kill"
+				style: idButtonStyle
+	            anchors.fill: parent
+	            onClicked: parent.parent.appInfo.kill()
+                enabled: (parent.parent.appInfo.state === QGfaAppCtrl.STATE_ZOMBIE)
+			}
+	    }
+	}
+}

+ 110 - 0
usr/visu/qml/main.qml

@@ -0,0 +1,110 @@
+import QtQuick 2.6
+import QtQuick.Window 2.2
+import QtQuick.Controls 1.5
+import QtQuick.Controls.Styles 1.4
+import QtQuick.Controls.Private 1.0
+import com.gfa.ipc.appctrl 1.0
+
+Window {
+    visible: true
+    width: 800
+    height: 480
+
+/*
+	Component {
+		id: imageDelegate
+		Item {
+			Image {
+				anchors.centerIn: parent
+				fillMode: Image.PreserveAspectFit
+				height:20
+	            source: (styleData.value) === "usb" ? "qrc:/img/usb64.png" : ((styleData.value) === "ata" ? "qrc:/img/ata64.png" : "qrc:/img/mmc64.png")
+			}
+		}
+	}
+
+	Component {
+		id: idTreeItemStyle
+		Text {
+	        font.pointSize: 8
+	        font.italic: styleData.column ? false : true
+			verticalAlignment: Text.AlignVCenter
+			elide: styleData.elideMode
+			color: styleData.textColor
+            text: styleData.value
+		}			
+	}*/
+
+	Component {
+		id: idButtonStyle
+		ButtonStyle {
+			label: Text {
+				renderType: Text.NativeRendering
+				verticalAlignment: Text.AlignVCenter
+				horizontalAlignment: Text.AlignHCenter
+				font.pointSize: 8
+				text: StyleHelpers.stylizeMnemonics(control.text)
+				color: SystemPaletteSingleton.buttonText(control.enabled)
+			}
+		}
+	}
+
+	Component {
+		id: idTableViewHeaderStyle
+		BorderImage {
+			height: Math.round(textItem.implicitHeight * 1.2)
+			source: "qrc:/img/header.png"
+			border.left: 4
+			border.bottom: 2
+			border.top: 2
+			Text {
+				id: textItem
+				anchors.fill: parent
+				verticalAlignment: Text.AlignVCenter
+				horizontalAlignment: styleData.textAlignment
+				anchors.leftMargin: horizontalAlignment === Text.AlignLeft ? 12 : 1
+				anchors.rightMargin: horizontalAlignment === Text.AlignRight ? 8 : 1
+				font.pointSize: 8
+				font.bold: true
+				text: styleData.value
+				elide: Text.ElideRight
+				color: styleData.textColor ? styleData.textColor : "black"
+				renderType: Text.NativeRendering
+			}
+			Rectangle {
+				width: 1
+				height: parent.height - 2
+				y: 1
+				color: "#ccc"
+			}
+		}
+	}
+
+	Component {
+		id: idTableViewItemStyle
+		Item {
+		        height: Math.max(16, label.implicitHeight)
+		        property int implicitWidth: label.implicitWidth + 20
+				Text {
+					id: label
+					objectName: "label"
+					width: parent.width - x - (horizontalAlignment === Text.AlignRight ? 8 : 1)
+					x: (styleData.hasOwnProperty("depth") && styleData.column === 0) ? 0 :
+					horizontalAlignment === Text.AlignRight ? 1 : 8
+					horizontalAlignment: styleData.textAlignment
+					anchors.verticalCenter: parent.verticalCenter
+					anchors.verticalCenterOffset: 1
+					elide: styleData.elideMode
+					font.pointSize: 8
+					text: styleData.value !== undefined ? styleData.value.toString() : ""
+					color: styleData.textColor
+					renderType: Text.NativeRendering
+				}
+	    }
+    }
+
+	Loader {
+		id: idPageLoader
+        source: "shm.qml"
+	}
+}

+ 144 - 0
usr/visu/qml/shm.qml

@@ -0,0 +1,144 @@
+import QtQuick 2.6
+import QtQuick.Window 2.2
+import QtQuick.Controls 1.5
+import QtQuick.Controls.Styles 1.4
+import QtQuick.Controls.Private 1.0
+import com.gfa.ipc.appctrl 1.0
+import com.gfa.shm.oem 1.0
+
+Rectangle {
+    width: 800
+    height: 480
+
+	/////////////////////////////////////////////////////////////////////////////////////////////////
+
+	Rectangle {
+        x: 10
+        y: 10
+        width: parent.width - 20
+        height: 30
+        radius: 5
+		border.width: 1
+		border.color: "black"
+        color: "lightgreen"
+		Image {
+            anchors.left: parent.left
+			anchors.verticalCenter: parent.verticalCenter
+			fillMode: Image.PreserveAspectFit
+			height: parent.height
+            source: "qrc:/img/blank.png"
+		}
+	    Text {
+	        font.pixelSize: 16
+	        font.bold: true
+			anchors.verticalCenter: parent.verticalCenter
+            anchors.centerIn: parent
+            text: "SHM - Bell frames"
+	    }
+		Image {
+            anchors.right: parent.right
+			anchors.verticalCenter: parent.verticalCenter
+			fillMode: Image.PreserveAspectFit
+			height: parent.height
+            source: "qrc:/img/next.png"
+			MouseArea {
+		        anchors.fill: parent
+                onClicked: { idPageLoader.source = "appctrl.qml" }
+			}
+		}
+	}
+
+	/////////////////////////////////////////////////////////////////////////////////////////////////
+
+    Rectangle {
+        x: 10
+        y: 40
+        height: 400
+        width: parent.width - 20
+        
+        ListModel {
+	        id: bellFrameModel
+
+			Component.onCompleted: {
+				var i = 0;
+				bellFrameModel.clear();
+	        	for(i = 0; i < qSHM.bellFrames.length; ++i)
+	        	{
+	        		var bf = qSHM.bellFrames[i];
+	        		if(bf.id.val > 0)
+	        		{
+						bellFrameModel.append({"mid": bf.id.val, "type": bf.type.val, "relaisID": bf.relaisID.val,
+												"startOffsMin": bf.startOffsMin.val, "startOffsSec": bf.startOffsSec.val,
+												"durationMin": bf.durationMin.val, "durationSec": bf.durationSec.val});
+					}
+				}
+			}
+		}
+
+    	TableView {
+	        anchors.fill: parent
+	        model: bellFrameModel
+	        style: TableViewStyle {
+	        	itemDelegate: idTableViewItemStyle
+	        	headerDelegate: idTableViewHeaderStyle
+	        }
+
+			TableViewColumn {
+				role: "mid"
+				width: 50
+				title: "ID"
+			}
+
+			TableViewColumn {
+				role: "type"
+				width: 50
+				title: "Typ"
+			}
+
+			TableViewColumn {
+				role: "relaisID"
+				width: 80
+				title: "Relais"
+			}
+
+			TableViewColumn {
+				role: "startOffsMin"
+				width: 100
+				title: "Start-Min."
+			}
+
+			TableViewColumn {
+				role: "startOffsSec"
+				width: 100
+				title: "Start-Sec."
+			}
+
+			TableViewColumn {
+				role: "durationMin"
+				width: 100
+				title: "Dauer-Min."
+			}
+
+			TableViewColumn {
+				role: "durationSec"
+				width: 100
+				title: "Dauer-Sec."
+			}
+	    }
+    }
+
+	/////////////////////////////////////////////////////////////////////////////////////////////////
+
+    Rectangle {
+        x: 10
+        y: 440
+        height: 30
+        width: parent.width - 20
+		Button {
+            text: "Exit"
+            anchors.fill: parent
+            onClicked: Qt.quit()
+			style: idButtonStyle
+		}
+    }
+}

+ 643 - 0
usr/visu/qml/sysinfo.qml

@@ -0,0 +1,643 @@
+import QtQuick 2.6
+import QtQuick.Window 2.2
+import QtQuick.Controls 1.5
+import QtQuick.Controls.Styles 1.4
+import QtQuick.Controls.Private 1.0
+import com.gfa.ipc.appctrl 1.0
+
+Rectangle {
+    width: 800
+    height: 480
+
+	/////////////////////////////////////////////////////////////////////////////////////////////////
+
+    function sec2HMS(sec, prec)
+    {
+    	if(!prec)
+    		prec = 0;
+    	var d = parseInt(sec / 86400);
+    	sec -= d * 86400;
+    	var h = parseInt(sec / 3600);
+    	sec -= h * 3600;
+    	var m = parseInt(sec / 60);
+    	sec -= m * 60;
+    	return (d ? qsTr("%1d ").arg(d) : qsTr("")) + qsTr("%1:").arg(h) + qsTr("0%1:").arg(m).slice(-3) + qsTr("0%1").arg(sec.toFixed(prec)).slice(-(prec ? prec + 3 : 2));
+    }
+
+	function formatByteSize(bytes, prec)
+	{
+		var s;
+    	if(!prec)
+    		prec = 1;
+		if(bytes < 1024)
+			s = "%1 B".arg(bytes);
+		else
+		{
+			var a = ["KiB", "MiB", "GiB"/*, "TiB", "PiB", "EiB"*/];
+	        var e = parseInt(1 << (a.length * 10));
+			var i;
+
+			for(i = a.length - 1; (i > 0) && (e > bytes); --i, e >>= 10)
+				;
+			s = qsTr("%1 %2").arg((bytes / e).toFixed(prec)).arg(a[i]);
+		}
+
+		return s;
+	}
+
+	/////////////////////////////////////////////////////////////////////////////////////////////////
+
+	Rectangle {
+        x: 10
+        y: 10
+        width: parent.width - 20
+        height: 30
+        radius: 5
+		border.width: 1
+		border.color: "black"
+        color: qGfaAppCtrl.sysInfoRunning ? "lightgreen" : "red"
+		Image {
+            anchors.left: parent.left
+			anchors.verticalCenter: parent.verticalCenter
+			fillMode: Image.PreserveAspectFit
+			height: parent.height
+            source: "qrc:/img/blank.png"
+		}
+	    Text {
+	        font.pixelSize: 16
+	        font.bold: true
+			anchors.verticalCenter: parent.verticalCenter
+            anchors.centerIn: parent
+	        text: "System Info"
+	    }
+		Image {
+            anchors.right: parent.right
+			anchors.verticalCenter: parent.verticalCenter
+			fillMode: Image.PreserveAspectFit
+			height: parent.height
+            source: "qrc:/img/next.png"
+			MouseArea {
+		        anchors.fill: parent
+				onClicked: { idPageLoader.source = "qml/appctrl.qml" }
+			}
+		}
+	}
+
+	/////////////////////////////////////////////////////////////////////////////////////////////////
+
+    Rectangle {
+        x: 10
+        y: 50
+        width: 250
+        height: 30
+        border.width: 1
+		border.color: "black"
+        radius: 5
+	    Text {
+	        font.pixelSize: 14
+	        font.bold: true
+			anchors.verticalCenter: parent.verticalCenter
+            anchors.centerIn: parent
+	        text: "Sitara"
+	    }
+    }
+
+	/////////////////////////////////////////////////////////////////////////////////////////////////
+
+    Rectangle {
+        x: 10
+        y: 90
+        width: 120
+        height: 420
+
+		Rectangle {
+	        x: 0
+	        y: 0
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pixelSize: 14
+		        font.italic: true
+				anchors.verticalCenter: parent.verticalCenter
+	            anchors.left: parent.left
+		        text: "Boot from:"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 30
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pixelSize: 14
+		        font.italic: true
+				anchors.verticalCenter: parent.verticalCenter
+	            anchors.left: parent.left
+		        text: "Mem. total:"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 60
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pixelSize: 14
+		        font.italic: true
+				anchors.verticalCenter: parent.verticalCenter
+	            anchors.left: parent.left
+		        text: "Mem. free:"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 90
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pixelSize: 14
+		        font.italic: true
+				anchors.verticalCenter: parent.verticalCenter
+	            anchors.left: parent.left
+		        text: "Mem. used:"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 120
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pixelSize: 14
+		        font.italic: true
+				anchors.verticalCenter: parent.verticalCenter
+	            anchors.left: parent.left
+		        text: "Mem. Buffers:"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 150
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pixelSize: 14
+		        font.italic: true
+				anchors.verticalCenter: parent.verticalCenter
+	            anchors.left: parent.left
+		        text: "Mem. cached:"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 180
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pixelSize: 14
+		        font.italic: true
+				anchors.verticalCenter: parent.verticalCenter
+	            anchors.left: parent.left
+		        text: "Mem. avail.:"
+		    }
+		}
+
+	    Rectangle {
+	        x: 0
+	        y: 350
+	        height: 30
+	        width: parent.width
+			Button {
+	            text: "Exit"
+	            anchors.fill: parent
+	            onClicked: Qt.quit()
+				style: idButtonStyle
+			}
+	    }
+	}
+
+	/////////////////////////////////////////////////////////////////////////////////////////////////
+
+    Rectangle {
+        x: 140
+        y: 90
+        width: 120
+        height: 420
+
+		property var sysInfo: qGfaAppCtrl.sysInfo
+		property var tivaInfo: qGfaAppCtrl.sysInfo.tivaInfo
+
+		Rectangle {
+	        x: 0
+	        y: 0
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+	        color: parent.sysInfo.bootFromEmmc ? "white" : "orange"
+		    Text {
+		        font.pointSize: 8
+	            anchors.centerIn: parent
+		        text: parent.parent.sysInfo.bootFromEmmc ? "EMMC" : "SD"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 30
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pointSize: 8
+	            anchors.centerIn: parent
+		        text: parent.parent.sysInfo.memTotal !== 0 ? formatByteSize(parent.parent.sysInfo.memTotal * 1024) : "n/a"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 60
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pointSize: 8
+	            anchors.centerIn: parent
+		        text: parent.parent.sysInfo.memFree !== 0 ? formatByteSize(parent.parent.sysInfo.memFree * 1024) : "n/a"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 90
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pointSize: 8
+	            anchors.centerIn: parent
+		        text: parent.parent.sysInfo.memUsed !== 0 ? formatByteSize(parent.parent.sysInfo.memUsed * 1024) : "n/a"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 120
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pointSize: 8
+	            anchors.centerIn: parent
+		        text: parent.parent.sysInfo.memBuffers !== 0 ? formatByteSize(parent.parent.sysInfo.memBuffers * 1024) : "n/a"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 150
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pointSize: 8
+	            anchors.centerIn: parent
+		        text: parent.parent.sysInfo.memCached !== 0 ? formatByteSize(parent.parent.sysInfo.memCached * 1024) : "n/a"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 180
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "black"
+	        radius: 5
+		    Text {
+		        font.pointSize: 8
+	            anchors.centerIn: parent
+		        text: parent.parent.sysInfo.memAvailable !== 0 ? formatByteSize(parent.parent.sysInfo.memAvailable * 1024) : "n/a"
+		    }
+		}
+    }
+
+	/////////////////////////////////////////////////////////////////////////////////////////////////
+	/////////////////////////////////////////////////////////////////////////////////////////////////
+	/////////////////////////////////////////////////////////////////////////////////////////////////
+
+    Rectangle {
+        x: 410
+        y: 50
+        width: 250
+        height: 30
+        border.width: 1
+		border.color: "black"
+        radius: 5
+	    Text {
+	        font.pixelSize: 14
+	        font.bold: true
+			anchors.verticalCenter: parent.verticalCenter
+            anchors.centerIn: parent
+	        text: "Tiva"
+	    }
+    }
+
+    Rectangle {
+        x: 410
+        y: 90
+        width: 120
+        height: 420
+
+		Rectangle {
+	        x: 0
+	        y: 0
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pixelSize: 14
+		        font.italic: true
+				anchors.verticalCenter: parent.verticalCenter
+	            anchors.left: parent.left
+		        text: "Uptime:"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 30
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pixelSize: 14
+		        font.italic: true
+				anchors.verticalCenter: parent.verticalCenter
+	            anchors.left: parent.left
+		        text: "HW-Version:"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 60
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pixelSize: 14
+		        font.italic: true
+				anchors.verticalCenter: parent.verticalCenter
+	            anchors.left: parent.left
+		        text: "SW-Version:"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 90
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pixelSize: 14
+		        font.italic: true
+				anchors.verticalCenter: parent.verticalCenter
+	            anchors.left: parent.left
+		        text: "V Power:"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 120
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pixelSize: 14
+		        font.italic: true
+				anchors.verticalCenter: parent.verticalCenter
+	            anchors.left: parent.left
+		        text: "V Sys.:"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 150
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pixelSize: 14
+		        font.italic: true
+				anchors.verticalCenter: parent.verticalCenter
+	            anchors.left: parent.left
+		        text: "V Bat.:"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 180
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pixelSize: 14
+		        font.italic: true
+				anchors.verticalCenter: parent.verticalCenter
+	            anchors.left: parent.left
+		        text: "V Bak.Bat.:"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 210
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pixelSize: 14
+		        font.italic: true
+				anchors.verticalCenter: parent.verticalCenter
+	            anchors.left: parent.left
+		        text: "Temp. Board °:"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 240
+	        width: parent.width
+	        height: 30
+		    Text {
+		        font.pixelSize: 14
+		        font.italic: true
+				anchors.verticalCenter: parent.verticalCenter
+	            anchors.left: parent.left
+		        text: "Temp. Tiva °:"
+		    }
+		}
+	}
+
+	/////////////////////////////////////////////////////////////////////////////////////////////////
+
+    Rectangle {
+        x: 540
+        y: 90
+        width: 120
+        height: 420
+
+		property var tivaInfo: qGfaAppCtrl.sysInfo.tivaInfo
+		property var spiAvail: !!tivaInfo.voltPowSup
+
+		Rectangle {
+	        x: 0
+	        y: 0
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pointSize: 8
+	            anchors.centerIn: parent
+		        text: parent.parent.spiAvail ? sec2HMS(parent.parent.tivaInfo.upTime) : "n/a"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 30
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pointSize: 8
+	            anchors.centerIn: parent
+		        text: parent.parent.spiAvail ? parent.parent.tivaInfo.hwVersion : "n/a"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 60
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pointSize: 8
+	            anchors.centerIn: parent
+		        text: parent.parent.spiAvail ? parent.parent.tivaInfo.swVersion : "n/a"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 90
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pointSize: 8
+	            anchors.centerIn: parent
+		        text: parent.parent.spiAvail ? parent.parent.tivaInfo.voltPowSup.toFixed(2) : "n/a"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 120
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pointSize: 8
+	            anchors.centerIn: parent
+		        text: parent.parent.spiAvail ? parent.parent.tivaInfo.voltSys.toFixed(2) : "n/a"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 150
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pointSize: 8
+	            anchors.centerIn: parent
+		        text: parent.parent.spiAvail ? parent.parent.tivaInfo.voltBat.toFixed(2) : "n/a"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 180
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pointSize: 8
+	            anchors.centerIn: parent
+		        text: parent.parent.spiAvail ? parent.parent.tivaInfo.voltBakBat.toFixed(2) : "n/a"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 210
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pointSize: 8
+	            anchors.centerIn: parent
+		        text: parent.parent.spiAvail ? parent.parent.tivaInfo.tempBoard.toFixed(1) : "n/a"
+		    }
+		}
+
+		Rectangle {
+	        x: 0
+	        y: 240
+	        width: parent.width
+	        height: 30
+	        border.width: 1
+			border.color: "lightgrey"
+	        radius: 5
+		    Text {
+		        font.pointSize: 8
+	            anchors.centerIn: parent
+		        text: parent.parent.spiAvail ? parent.parent.tivaInfo.tempTiva.toFixed(1) : "n/a"
+		    }
+		}
+    }
+}

+ 122 - 0
usr/visu/var/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);
+	}
+}

+ 339 - 0
usr/visu/var/shmstrvar.cpp

@@ -0,0 +1,339 @@
+#include "shmvar.h"
+#include <gfa/svc/common/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);
+	}
+}

+ 230 - 0
usr/visu/var/shmthread.h

@@ -0,0 +1,230 @@
+// 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>
+#include "shmvar.h"
+
+
+#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);
+
+	void Lock(void){
+		::GfaIpcLockSHM(m_hShm);}
+
+	void Unlock(void){
+		::GfaIpcUnlockSHM(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;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+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)
+{
+#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
usr/visu/var/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
usr/visu/var/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_)

+ 43 - 0
usr/visu/visu.pro

@@ -0,0 +1,43 @@
+DEPLOY_BASEDIR = $$fromfile(../../projal.pri, DEPLOY_BASEDIR)
+DEPLOY_SUBDIR = $$fromfile(../../projal.pri, DEPLOY_SUBDIR)
+
+TEMPLATE = app
+QT += qml quick
+CONFIG += c++11
+
+CONFIG(debug, debug|release) {
+	QMAKE_CXXFLAGS -= -Os
+	QMAKE_CFLAGS -= -Os
+    QMAKE_CXXFLAGS += -D_DEBUG
+    QMAKE_CFLAGS += -D_DEBUG
+	QMAKE_LIBS += -lgfaipcd -lgfaqtd -lgfanetd -l:libshmqmld.a -l:libcommond.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
+	QMAKE_LIBS += -lgfaipc -lgfaqt -lgfanet -l:libshmqmld.a -l:libcommon.a
+}
+
+QMAKE_CXXFLAGS += -D_SHMQT -Wno-deprecated-copy
+QMAKE_CFLAGS += -D_SHMQT -Wno-deprecated-copy
+QMAKE_LIBDIR += $$[QT_SYSROOT]/usr/lib/gfa $$[QT_SYSROOT]/usr/lib/gfa/svc
+QMAKE_RPATHDIR += /usr/lib/gfa
+
+DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
+
+INCLUDEPATH += ../../
+
+SOURCES += main.cpp
+
+HEADERS +=  \
+    ../../SchauerSachs_qt.h
+
+RESOURCES += qml.qrc
+
+linux-buildroot-g++ {
+    target.path = $$DEPLOY_BASEDIR/$$DEPLOY_SUBDIR/visu
+    INSTALLS += target
+	QMAKE_CXXFLAGS += -DSITARA_BUILD
+	QMAKE_CFLAGS += -DSITARA_BUILD
+}