Hey,
für mich war das der erste Einsatz mit den Program_Alarm, deswegen hatte ich tatsächlich etwas Zeit benötigt bis das mal lief, die VoT Seite ist tatsächlich sehr simple gehalten. Da hatte mich allerdings aufgehalten das Tabulator Zeichen in Strings nicht richtig verarbeitet werden, aber da erinnere ich mich nur noch dunkel

.
Also ich versuche mal das ganze übersichtlich aufzudrösseln:
- Im Program_Alarm übergebe ich die Meldungsklasse und Meldungsnummer als Zusatztext an SD_1 und SD_2 und an SD_3 den Textlistenindex.
Wichtig ist das die Meldungsnummer mit führenden Nullen übergeben wird, sonst steht später in der Meldungsliste die Texte nicht sauber in "Spalten". Die Meldeklasse haben bei mir immer zwei Buchstaben als Namen, danach wird dann auch die Hintergrundfarbe der Textfelder dynamisiert. Man könnte zum Vereinfachen an der Stelle auch hardcodiert z.B. AV001 reinschreiben in den Zusatztext überlege ich im Nachhinein
*Im folgenden Code habe ich Konstanten durch die Zahlenwerte ersetzt damit es hoffentlich etwas verständlicher ist.
- Die Daten vom Get_Alarm speichere ich in einem eigenen Datentyp (UDT).

- Aufruf vom Get_Alarm:
Code:
// OperateMode steuern
CASE #stGetAlarm.bOpModeGetAlarm OF
1:
IF #instGet_Alarm.xSignedIn THEN
#stGetAlarm.bOpModeGetAlarm := 2; // Read
ELSE
#stGetAlarm.bOpModeGetAlarm := 3; // Logoff
END_IF;
2:
IF #instGet_Alarm.Error THEN
#stGetAlarm.bOpModeGetAlarm := 0; // Init
END_IF;
ELSE
#stGetAlarm.bOpModeGetAlarm := 1; // Login
END_CASE;
// Aufruf Get_Alarm
#instGet_Alarm(OperateMode := #stGetAlarm.bOpModeGetAlarm,
DataMode := 3,
DispClassNr := #stGetAlarm.aDispClassNr,
Lcid := 1,
Data := #stGetAlarm.messageData);
#stGetAlarm.messageData ist vom Systemdatentyp AlarmData, aus dem kopiere ich dann die für mich wichtigen Daten um. Meine Lösung ist jetzt auch erstmal nur einsprachig (deutsch) zum Glück, mit zusätzlichen Sprachen wächst der Speicherbedarf durch die WStrings extrem.
- Meldespeicher verwalten:
Code:
IF #instGet_Alarm.DataReady THEN
// Erzeuge "Meldungsobjekt"
#tyNewMsg.wsText := #stGetAlarm.messageData.AlarmText[0];
#tyNewMsg.wsClass := #stGetAlarm.messageData.AddText_1[0];
#tyNewMsg.wsMsgNo := #stGetAlarm.messageData.AddText_2[0];
#tyNewMsg.ldtTimestamp := #stGetAlarm.messageData.TimeStamp;
// UTC in Lokalzeit wandeln (Zeitzone addieren)
#iRetRdTime := RD_SYS_T(#dtlSysTime);
#iRetRdTime := RD_LOC_T(#dtlLocTime);
#tyNewMsg.ldtTimestamp := #tyNewMsg.ldtTimestamp + T_DIFF(IN1 := #dtlLocTime, IN2 := #dtlSysTime);
#tyNewMsg.uiProdID := #stGetAlarm.messageData.ProducerID;
#tyNewMsg.uiMsgID := #stGetAlarm.messageData.ID_1;
#tyNewMsg.uiPrio := #stGetAlarm.messageData.PRIO;
#tyNewMsg.bState := #stGetAlarm.messageData.State;
// Prüfung ob Meldung schon als gekommen eingetragen ist (Zustand tritt sporadisch bei CPU Start bspw. auf)
#xMsgExists := FALSE;
FOR #i := 0 TO 19 DO
IF #tyNewMsg.uiMsgID = #aActiveMsg[#i].uiMsgID AND #tyNewMsg.bState = 1 THEN //1=kommen
#xMsgExists := TRUE;
EXIT;
END_IF;
END_FOR;
IF NOT #xMsgExists THEN
// Meldung in Archiv eintragen
#iRetMoveBlkVariant := MOVE_BLK_VARIANT(SRC := #aMsgArchiv, COUNT := 19, SRC_INDEX := 0, DEST_INDEX := 1, DEST => #aMsgArchiv);
#aMsgArchiv[0] := #tyNewMsg;
// Meldung in Störungs- u. Warnungsarchiv eintragen
IF #tyNewMsg.uiPrio >= 10 THEN
#iRetMoveBlkVariant := MOVE_BLK_VARIANT(SRC := #aFltWarnArchiv, COUNT := 19, SRC_INDEX := 0, DEST_INDEX := 1, DEST => #aFltWarnArchiv);
#aFltWarnArchiv[0] := #tyNewMsg;
END_IF;
IF #stGetAlarm.messageData.State = 1 THEN
// Kommende Meldung eintragen in Aktive Meldungen
#iRetMoveBlkVariant := MOVE_BLK_VARIANT(SRC := #aActiveMsg, COUNT := 19, SRC_INDEX := 0, DEST_INDEX := 1, DEST => #aActiveMsg);
#aActiveMsg[0] := #tyNewMsg;
ELSIF #stGetAlarm.messageData.State = 0 THEN
// Gehende Meldung austragen
FOR #i := 0 TO 19 DO
// Suche Gehende Meldung
IF #aActiveMsg[#i].uiMsgID = #stGetAlarm.messageData.ID_1 THEN
// Verschiebe Meldungen um eine Stelle (Gegangen Meldung überschreiben)
IF #i < 19 THEN
#iRetMoveBlkVariant := MOVE_BLK_VARIANT(SRC := #aActiveMsg, COUNT := 19 - #i, SRC_INDEX := #i + 1, DEST_INDEX := #i, DEST => #aActiveMsg);
END_IF;
// Lösche letzte Stelle aktiver Meldungen
#aActiveMsg[19] := #tyEmptyMsg;
EXIT;
END_IF;
END_FOR;
END_IF;
END_IF;
END_IF;
- Meldetexte für VoT generieren:
Code:
IF #instGet_Alarm.DataReady OR #usiSEL_MSG_TEXT <> #usiMsgTextTrig THEN
// Generiere Meldetext für VoT alle aktiven Meldungen oder Meldearchiv
#wsMsgText := WString#'';
FOR #i := 0 TO 19 DO
CASE #usiSEL_MSG_TEXT OF
0: // Aktive Meldungen
#wsMsgText := CONCAT_WSTRING(IN1 := #wsMsgText,
IN2 := "fcCreateMsgText"(iNO := #i, tyMESSAGE := #aActiveMsg));
1: // Meldungsarchiv Gesamt
#wsMsgText := CONCAT_WSTRING(IN1 := #wsMsgText,
IN2 := "fcCreateMsgText"(iNO := #i, tyMESSAGE := #aMsgArchiv));
2: // Meldungsarchiv Störungen u. Warnungen
#wsMsgText := CONCAT_WSTRING(IN1 := #wsMsgText,
IN2 := "fcCreateMsgText"(iNO := #i, tyMESSAGE := #aFltWarnArchiv));
END_CASE;
END_FOR;
#wsMSG_TEXT := #wsMsgText;
// Meldezeile Text erzeugen
#wsMSG_ROW_TEXT := "fcCreateMsgText"(iNO := 0, tyMESSAGE := #aActiveMsg);
END_IF;
#usiMsgTextTrig := #usiSEL_MSG_TEXT;
wsMsgText ist ein WString mit 5080 Zeichen als Größe (254 * 20 Meldungen).
- Der fcCreateMsgText stellt immer den Text einer einzelnen Meldung zusammen, für den Zeitstempel nehm ich einen Baustein aus der LGF Bib und habe den angepasst auf WString und die Nanosekunden deaktivert.
Code:
// Init
#iMaxIdx := DINT_TO_INT(UPPER_BOUND(ARR := #tyMESSAGE, DIM := 1));
#wsMsgText := WString#'';
// Arraygrenze prüfen
IF #iNO >= 0 AND #iNO <= #iMaxIdx THEN
// Meldung vorhanden
IF #tyMESSAGE[#iNO].uiMsgID <> 0 THEN
#wsMsgClassNo := CONCAT_WSTRING(IN1 := #tyMESSAGE[#iNO].wsClass,
IN2 := #tyMESSAGE[#iNO].wsMsgNo);
#wsMsgTimestamp := "LGF_DTLToWString_DE"("date" := LDT_TO_DTL(#tyMESSAGE[#iNO].ldtTimestamp),
separator := '.',
nanosecond := FALSE);
IF #tyMESSAGE[#iNO].bState = 1 THEN
#wsMsgState := WSTRING#'K'; // K = Kommen
ELSE
#wsMsgState := WSTRING#'G'; // G = Gehen
END_IF;
// Erzeuge zusammengesetzten Meldetext (Meldeklasse, Meldungsnummer, Zeitstempel, Status K/G, Meldungstext)
#wsMsgText := CONCAT_WSTRING(IN1 := #wsMsgClassNo,
IN2 := WString#' ',
IN3 := #wsMsgTimestamp,
IN4 := WString#' ',
IN5 := #wsMsgState,
IN6 := WString#' ',
IN7 := #tyMESSAGE[#iNO].wsText,
IN8 := WString#';');
END_IF;
END_IF;
#fcCreateMsgText := #wsMsgText;
- Skript in VoT mithilfe von ChatGPT erstellt (Wichtig das die Namen der Textfelder einer Logik folgen (also laufende Nummer am Ende z.B.):
Javascript:
export function txtMsg_0_Text_Trigger(item) {
let fullText = Tags('dbHMI.wsMsgText').Read();
let messages = fullText.split(';');
let maxLength = 20;
// Stelle sicher, dass immer 20 Elemente im Array sind
while (messages.length < maxLength) {
messages.push(''); // Füge leere Einträge hinzu
}
// Schleife, um die 20 Textfelder zu füllen und die Hintergrundfarbe zu setzen
for (let i = 0; i < maxLength; i++) {
let itemName = 'txtMsg_' + i;
let text = messages[i].trim();
// Setze den Text in die Textbox
Screen.Items(itemName).Text = text;
// Bestimme die Hintergrundfarbe basierend auf den ersten beiden Zeichen
let firstTwoChars = text.substring(0, 2); // Erste zwei Zeichen des Textes
let backColor;
switch (firstTwoChars) {
case 'AV':
backColor = HMIRuntime.Math.RGB(255, 0, 0, 255); // Rot
break;
case 'BV':
backColor = HMIRuntime.Math.RGB(255, 255, 0, 255); // Gelb
break;
case 'CV':
backColor = HMIRuntime.Math.RGB(0, 255, 255, 255); // Cyan
break;
default:
backColor = HMIRuntime.Math.RGB(255, 255, 255, 255); // Weiß
break;
}
// Setze die Hintergrundfarbe
Screen.Items(itemName).BackColor = backColor;
}
return;
}
Ich hoffe du kommst damit mal grob klar.
