TIA VoT Meldungen darstellen

Karashoo

Level-1
Beiträge
2
Reaktionspunkte
0
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo Zusammen,

Für ein Projekt auf einer CPU 1510SP-1PN habe ich über die Webapplikation VoT ist eine kleine Visualisierung angelegt. Die Visualisierung ist angelehnt an ein ähnliches Projektm welches ein Unified Comfort Panel besaß. Mein Problem ist nun die darstellung einer Meldeliste.

In den Unified Panels kann über das Werkzeug „Meldeliste“ genau so eine Liste angelegt werden. Dort werden die vorher angelegten HMI-Meldungen mit Zeitstempel angezeigt.

Diese Funktion möchte ich in dem VoT abbilden – allerdings finde ich weder das passendeWerkzeug, noch gibt es HMI-Meldungen.


Als "beste" Alternative habe ich getestet, einen Webbrowser mit Verlinkung auf die Oberfläche der PLC-Webservers einzublenden, wo ich alternativ die Störungen als PLC-Meldungen anlegen und anzeigen kann. Das funktioniert grundlegend, ist aber eher unschön gelöst.

Das ganze mit mehreren Textlisten und einer Datenverwaltung im Background von Grund auf neu zu programmieren würde hier etwas den Rahmen sprengen.

Stand schon einmal jemand vor einem ähnlichen Problem und hat einen sauberen Lösungsansatz/-vorschlag für mich?



In Verwendung ist eine CPU 6ES7 510-1DK03-0AB0 CPU, FW 3.1 und TIA 19 Upd 4

Danke und Grüße
 
Hallo Karashoo,
bisher ist meine einzige Erfahrung mit Unified ein kleines Testprojekt zu VoT und jetzt letztes Jahr habe ich das System etwas ausgereizt bei einer Technikerarbeit.
Da stand ich auch vor dem Problem und habe viel überlegt wie man ein Meldungssystem aufbauen könnte. Allerdings sind die Einschränkungen in dem Bereich sehr groß leider, Textlisten oder fertige Meldeanzeigen (Fenster) oder ähnliches gibt es nicht.
Ich hatte ein Meldesystem, dass auf der SPS verwaltet wird, umgesetzt. Also mit den Bausteinen Program_Alarm, den müsstest du ja auch verwenden für die Anzeige im SPS Diagnosepuffer. Man kann sich mit dem Baustein Get_Alarm an dem Meldesystem quasi anmelden im SPS Programm und erhält dann die anstehenden Meldungen, dazu habe ich dann drei Meldearrays mit je 20 Stellen. Einmal für aktive Meldungen, Meldearchiv gesamt und Meldearchiv (Nur Fehler und Warnungen, ohne Informationsmeldungen). Aus den Meldetexten und den Infos von dem Get_Alarm baue ich daraus einen String zusammen (Meldeklasse, Meldenummer, Zeitstempel, Status Kommen -Gehen und dem Meldetext (teilweise mit dynamischer Textliste im Program_Alarm noch für Zusatzinfos). Diese Meldetexte verkette ich dann zusätzlich um Variablen in VoT zu sparen zu einem langen String mit bis zu >5000 Zeichen Semikolonseperiert und in VoT habe ich ein Bild mit 20 Textfeldern und in dem ersten Textfeld ist ein Skript das die Meldetexte aufsplitten und verteilt (einfach split an dem ; und dann der Reihe nach die Texte den Feldern zuordnen) und auch die Farbe je nach Meldeklasse der Textfelder steuert. Die Meldezeile im oberen Bild funktioniert nach dem selben Prinzip.

Mittlerweile kenne ich auch einen Trick mit dem man sich das zusammensetzen der Meldetexte und anschließende aufsplitten vermeiden lassen würde, aber das führt für eine erste Antwort zu weit denke ich 😅 .


1754475105963.png

Da die Technikerarbeit keiner Geheimhaltung unterliegt könnte ich dir auch das Skript und das SPS Programm zum Get_Alarm und Meldetext Generierung weitergeben.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Hi HolzigerBambus,

vielen Dank für deine ausführliche Antwort. Es ist auf jedenfall sehr beruhigend zu sehen, dass ich nicht als einziger vor diesem Probleme stehe :)

Deine Lösung finde ich sehr sauber und nah an dem 'original' wie es bei den unified Paneln zu finden ist. Die Herangehensweise hatte ich (lediglich von der Grundidee) ähnlich im Kopf - Es war bestimmt eine Menge Arbeit das einmal ans laufen zu bekommen.

Wenn du das Programm und Script wirklich teilen möchtest, könntest du mir das Leben sehr erleichtern.;)
 
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.

1754499712127.png

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:unsure:
*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).
1754500119776.png
- 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.😅
 
Zurück
Oben