TIA CASE mit BOOL Variablen

DCDCDC

Level-3
Beiträge
3.573
Reaktionspunkte
1.037
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo,

TIA V18 Update 3
1217C DCDCDC V4.6


Ich glaube ich hatte mal in irgendeinem SCL Schnipsel gesehen, dass eine CASE Funktion mit BOOL gelöst wurde. Ist das denn so richtig in Erinnerung von mir?

Hintergrund:
MQTT Nachrichten senden dauert jetzt bei 9 von 16 eingebundenen Topics schon 2-3s bis eine neue Nachricht für ein Topic im Broker erscheint (sprich Zykluszeit ist hier 2-3s)

Ich habe bei mir alles (StartSignal serialisieren, StartSignal senden usw.) in einer CASE Funktion integriert, so wird jedes Thema nacheinander abgearbeitet.

Im Idealfall hätte ich es gerne so, dass nur bei einer Wertänderungen der Datenpunkte eines Topics, eine neue Nachricht abgesetzt wird. Das würde ich über Neu<>Alt vergleich lösen (geht bei mir gut, da alles in UDTs ist).

Problem: Ich übergebe meine Datenpunkte zum Aufbereiten über den temporären Bereich. Hier hätte ich angedacht, die UDTs welche die vorherigen Daten - spricht "Alt" enthalten, im remanenten Bereich abzulegen. Wäre das eine gute Idee?

So hätte ich quasi etwas wie ein Fifo, ich befürchte nur, dass es irgendwann zu dem Fall kommt dass die eine Nachricht nie abgesetzt wird, weil alle anderen davor immer davor "neuer" sind. Wie schaffe ich eine "gleichberechtigung/gleichwertigkeit" der Neu<>Alt Vergleiche?

Was wären andere Möglichkeiten, falls ich das mit der CASE Funktion falsch in Erinnerung hab?

Dankeschön im Voraus!
 
CASE macht nichts anderes aös das, was @DeltaMikeAir dir da gepostet hat - ist nur eine andere Darstellung.

Dein Vorhaben insgesamt habe ich nicht verstanden.
Um was geht es dir denn ? Um Steuerwerte oder Anzeigewerte ?
Steuerwerte würde ich nur übertragen wenn akt_State <> LastState ist. In dem Fall den Request aus deiner Gesamtliste in den Sendebereich übergeben ...
Anzeigewerte würde ich mir zyklsich holen - machst du wahrscheinlich auch.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Hier hätte ich angedacht, die UDTs welche die vorherigen Daten - spricht "Alt" enthalten, im remanenten Bereich abzulegen. Wäre das eine gute Idee?
Du meinst im Static-Bereich des FBs? (oder einem globalen DB).
Klar, du benötigst eine Kopie der zuletzt gesendeten Daten um einen Neu <> Alt Vergleich zu machen.
Der Speicherbereich muss aber nicht remanent sein, außer du willst die Daten behalten wenn die SPS neu startet.

Das mit der "Case & Bool"-Frage verstehe ich auch nicht ganz.....
Ein Case mit Bool ist eine If/else-Abfrage.

Stichwort: Case
Würde es bei deiner Anwendung nicht Sinn machen die jeweiligen Topics mit einer kleinen StateMachine durchzuackern?
 
Stichwort: Case
Würde es bei deiner Anwendung nicht Sinn machen die jeweiligen Topics mit einer kleinen StateMachine durchzuackern?

Ich hab eine StateMachine, mache ich ja im CASE.. nach 1, springe zu 2 usw.. das dauert aktuell 2-3s bis das einmal durchgelaufen ist. Da kommt, wenn alle Topics eingebunden sind, ist das bestimmt bei 3-4s, von der SPS Zykluszeit gar nicht zu sprechen.

Du meinst im Static-Bereich des FBs? (oder einem globalen DB).
Klar, du benötigst eine Kopie der zuletzt gesendeten Daten um einen Neu <> Alt Vergleich zu machen.
Der Speicherbereich muss aber nicht remanent sein, außer du willst die Daten behalten wenn die SPS neu startet.
Remanent brauche ich sie eigentlich nicht, da ich aber bisher nur 4% remanenten Speicher belege, hab ich da noch genug Reserven.. und schaden tut es nicht.

Um was geht es dir denn ? Um Steuerwerte oder Anzeigewerte ?
Steuerwerte würde ich nur übertragen wenn akt_State <> LastState ist. In dem Fall den Request aus deiner Gesamtliste in den Sendebereich übergeben ...
Anzeigewerte würde ich mir zyklsich holen - machst du wahrscheinlich auch.
Die Nachrichten welche ich versende, sind sowohl für die Anzeige als auch fürs Steuern, werden von anderen Kollegen in anderen Projekten weiterverarbeitet.

Da ich über die Steuerung nur für ein MQTT Topic Nachrichten versenden kann, hab ich ja diese StateMachine, damit alle nacheinander durchgearbeitet werden. Ich würde das gerne von "alles immer senden" zu "nur senden wenn Daten aktualisiert sind" ändern.

Mein Problem ist nur, wie ich wirklich sicher stelle, dass alle Topics abgearbeitet werden die aktualisierte Daten hätten, ohne dass eines nicht dran kommt, weil ein anderes zwei Zeilen früher abgefragt wird.

Doch nen FIFO basteln?
 
Mein Problem ist nur, wie ich wirklich sicher stelle, dass alle Topics abgearbeitet werden die aktualisierte Daten hätten, ohne dass eines nicht dran kommt, weil ein anderes zwei Zeilen früher abgefragt wird.
Du kannst Dir ja ein Token bauen: Deine Werte liegen doch bestimmt in einem Array... Dann speicherst Du Dir den Index, an dem Du anfängst, auf Wertänderung zu prüfen. Wenn Du dann den Wert verschickt hast, fängst Du dort wieder an, weiter zu vergleichen. So fängst Du nicht bei jeder Prüfung von vorne an, sondern da, wo Du zuletzt aufgehört hast.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Ich weiß jetzt nicht wie deine Werte aussehen und auch nicht dein JSON-String ... aber kannst du nicht alle relevanten Items in einen JSON packen ... wenn du den sowieso an einen "Kollegen" schickst ? Der braucht dann zum deserialisieren doch auch nur die Itemnamen kennen und den dahinter befindlichen Typ ...
 
Also ich steh immer noch auf dem Schlauch.
Mit SCL geht eine Case-Anweisung mit dem Datentyp Bool definitiv nicht.

Ich kenn andere Sprachen, die per Swich/Case eine Fallunterscheidung mit boolean können,
im Endeffekt ist das aber auch nur eine IF/elsif-Struktur mit dicken Eiern.
Sowas z.B. (Geklaut aus dem arduino-Forum)
Code:
switch (true){
        case (T1 < T2):
            SW = 1;
            break;
        case (T2 < T3):
            SW = 2;
            break;
        case (T3 <T4):
           SW  =3;
           break;
        case (T3 <T4):
            SW  = 4;
            break;
      }

Ich hätte bei dir eher etwa an so etwas gedacht:
Code:
CASE #sStateM OF
    0: //Standby, init eines neuen Durchlaufs
        ;
        //Start des Ablaufs
        #sStateM := 10;
        //====================================================
    10://Topic 1: Prüfen ob senden notwendig
        IF neu <> alt OR max Sendepause erreicht THEN
            #sStateM := 11;
        ELSE
            #sStateM := 19;
        END_IF;
       
    11: //Topic 1: Senden ausführen
        hierSende -Codeausführen
        IFsendenfertig THEN
            #sStateM := 18;
        END_IF;

        IF senden fehlerhaft THEN
            #sStateM += 9000;//In Fehlerbehandlungsblock springen, indem zum aktuellen Schritt ein Festwert addiert wird
        END_IF;
       
    18: //Topic 1: senden erfolgreich abgeschlossen
        #sStateM := 20; //nächstes Topic
       
    19: //Topic 1: Senden nicht notwendig
        #sStateM := 20; //nächstes Topic
        //====================================================
    20://Topic 2: Prüfen ob senden notwendig
        ....undhierdannwiederdiegleicheStrukturwiebeiTopic
        1
       
       
        //====================================================
    9000..9999://Fehlerbehandlung
        hierFehlerbehandlungbearbeiten
       
    ELSE
        #sStateM := 0; //Ungültige Stati abfangen, ggf mit Fehlerbehandlung
END_CASE;

Ich hätte hier die erste Ziffer (0-9) als Schritt des Sendevorgangs für das jeweilige Topic definiert & die weiteren Ziffern als Nummer des Topics genutzt.
=> macht es einfacher beim Debugging die Schritte im Trace zuzuordnen, was wann in welcher Reihenfolge passiert ist.

edit:
Ersetze die Zahlen der Schritte in deinem Code auf jeden Fall noch durch Konstanten.
Ansonsten hast du keine Querverweise & hast extra Spaß einen Copy&Paste Fehler zu suchen
 
Du kannst Dir ja ein Token bauen: Deine Werte liegen doch bestimmt in einem Array... Dann speicherst Du Dir den Index, an dem Du anfängst, auf Wertänderung zu prüfen. Wenn Du dann den Wert verschickt hast, fängst Du dort wieder an, weiter zu vergleichen. So fängst Du nicht bei jeder Prüfung von vorne an, sondern da, wo Du zuletzt aufgehört hast.
Liegen sie leider nicht. Mit Daten, bzw. Datenpunkte/Werte meine ich auch zB Not-Halt Signal, aktuelle Betriebsart usw..

Ich weiß jetzt nicht wie deine Werte aussehen und auch nicht dein JSON-String ... aber kannst du nicht alle relevanten Items in einen JSON packen ... wenn du den sowieso an einen "Kollegen" schickst ? Der braucht dann zum deserialisieren doch auch nur die Itemnamen kennen und den dahinter befindlichen Typ ...
Würde ich alles auf einmal versenden wollen, bräuchte ich eine Payloadgröße von 3-4KB (Array[0..n] of BYTE] und mindestens 300-400 JSON Elemente, das packt die 1217 nicht. Natürlich ist das erstmal ein "String", den aber andere Systeme direkt als JSON File interpretieren und formatieren, ohne mein zutun.

JSON Files sind immer gleich aufgebaut:
Screenshot 2023-11-22 090520.png

Bilden ready Signale:
Code:
IF (NOT #ioInterface.Out.Error)
    AND (#ioInterface.Out.Status = "MQTT_STATUS_CONNECTED")
    // AND (("DB_MqttData".ControlSubscribe.Ready OR "DB_MqttData".ControlSubscribe.Done)) ---// nur schreiben wenn nicht gelesen wird
    AND (NOT "DB_MqttData".ControlSubscribe.Start) ---// nur schreiben wenn nicht gelesen wird
    AND (NOT #ioInterface.In.Subscribe) ---// nur schreiben wenn nicht gelesen wird
    AND (NOT #ioInterface.In.Unsubscribe) THEN ---// nur schreiben wenn nicht gelesen wird
    #tempReadyPublish := 1;
ELSE
    #tempReadyPublish := 0;
END_IF;

IF #statJsonSerializer.done ---// warten bis serialisieren beendet
    AND (NOT #ioInterface.Out.Done) ---// MQTT führt keinen Befehl aus 
    AND (NOT #ioInterface.In.Publish) THEN ---// MQTT published nicht
    #tempDataSerialized := 1;
ELSE
    #tempDataSerialized := 0;
END_IF;

Hier mal ein Ausschnitt aus meinem SwitchCase:
Code:
CASE #ControlPublish.StepNumber OF
    0:
        #ControlPublish.StepName := 'Init';
        #ControlPublish.Start := 0;
        #ControlPublish.Done := 0;
        #ControlPublish.Ready := 1;
        #ioInterface.In.Publish := 0;
        #statJsonSerializer.execute := 0;
        IF #tempReadyPublish THEN
            #ControlPublish.Ready := 0;
            #ControlPublish.Start := 1;
            #ControlPublish.StepNumber := 1;
        END_IF;
    1:
        #DataPrepared.AxisX := 0; ---// Daten aufbereiten Signal zurücksetzen, wird nach dem Aufbereitn auf TRUE geschrieben, damit es nur einmal ausgeführt wird
        #ControlPublish.StepName := 'Publish message axis x';
        #ioInterface.InOut.MqttTopic := "MQTT_TOPIC_PUBLISH_AXIS_X";
        #statJsonSerializer.execute := 1; ---// Serialisieren Start
        IF #tempDataSerialized AND #tempReadyPublish THEN
            #ioInterface.In.Publish := 1;
        END_IF;
        
        IF #ioInterface.Out.Done THEN ---// Warten bis Publish ausgeführt
            #ioInterface.In.Publish := 0;
            #statJsonSerializer.execute := 0;
            #ControlPublish.StepNumber := 2;
        ELSIF #statJsonSerializer.error THEN ---// Das hier an der Stelle ist quatsch, das muss ich noch anpassen
            #ControlPublish.StepNumber := 0;
        END_IF;
        
    2:
        #ControlPublish.StepName := 'Wait for mqtt ready';
        #ioInterface.InOut.MqttTopic := #tempWstring; ---// Topic ablöschen
        IF #tempReadyPublish AND #statDataCleared THEN ---// Warten bis JSON Tree und Payload Array wieder abgelöscht sind
            #ControlPublish.StepNumber := 3;
        END_IF;
        
    3:
        #DataPrepared.AxisY := 0;
        #ControlPublish.StepName := 'Publish message axis y';
        #ioInterface.InOut.MqttTopic := "MQTT_TOPIC_PUBLISH_AXIS_Y";
        #statJsonSerializer.execute := 1;
        IF #tempDataSerialized AND #tempReadyPublish THEN
            #ioInterface.In.Publish := 1;
        END_IF;
        
        IF #ioInterface.Out.Done THEN
            #ioInterface.In.Publish := 0;
            #statJsonSerializer.execute := 0;
            #ControlPublish.StepNumber := 4;
        ELSIF #statJsonSerializer.error THEN
            #ControlPublish.StepNumber := 0;
        END_IF;
        
    4:
        #ControlPublish.StepName := 'Wait for mqtt ready';
        #ioInterface.InOut.MqttTopic := #tempWstring;
        IF #tempReadyPublish AND #statDataCleared THEN
            #ControlPublish.StepNumber := 5;
        END_IF;
... und so weiter ...
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Also ich steh immer noch auf dem Schlauch.
Mit SCL geht eine Case-Anweisung mit dem Datentyp Bool definitiv nicht.

Ich kenn andere Sprachen, die per Swich/Case eine Fallunterscheidung mit boolean können,
im Endeffekt ist das aber auch nur eine IF/elsif-Struktur mit dicken Eiern.
Sowas z.B. (Geklaut aus dem arduino-Forum)
Code:
switch (true){
        case (T1 < T2):
            SW = 1;
            break;
        case (T2 < T3):
            SW = 2;
            break;
        case (T3 <T4):
           SW  =3;
           break;
        case (T3 <T4):
            SW  = 4;
            break;
      }

Ich hätte bei dir eher etwa an so etwas gedacht:
Code:
CASE #sStateM OF
    0: //Standby, init eines neuen Durchlaufs
        ;
        //Start des Ablaufs
        #sStateM := 10;
        //====================================================
    10://Topic 1: Prüfen ob senden notwendig
        IF neu <> alt OR max Sendepause erreicht THEN
            #sStateM := 11;
        ELSE
            #sStateM := 19;
        END_IF;
      
    11: //Topic 1: Senden ausführen
        hierSende -Codeausführen
        IFsendenfertig THEN
            #sStateM := 18;
        END_IF;

        IF senden fehlerhaft THEN
            #sStateM += 9000;//In Fehlerbehandlungsblock springen, indem zum aktuellen Schritt ein Festwert addiert wird
        END_IF;
      
    18: //Topic 1: senden erfolgreich abgeschlossen
        #sStateM := 20; //nächstes Topic
      
    19: //Topic 1: Senden nicht notwendig
        #sStateM := 20; //nächstes Topic
        //====================================================
    20://Topic 2: Prüfen ob senden notwendig
        ....undhierdannwiederdiegleicheStrukturwiebeiTopic
        1
      
      
        //====================================================
    9000..9999://Fehlerbehandlung
        hierFehlerbehandlungbearbeiten
      
    ELSE
        #sStateM := 0; //Ungültige Stati abfangen, ggf mit Fehlerbehandlung
END_CASE;

Ich hätte hier die erste Ziffer (0-9) als Schritt des Sendevorgangs für das jeweilige Topic definiert & die weiteren Ziffern als Nummer des Topics genutzt.
=> macht es einfacher beim Debugging die Schritte im Trace zuzuordnen, was wann in welcher Reihenfolge passiert ist.

edit:
Ersetze die Zahlen der Schritte in deinem Code auf jeden Fall noch durch Konstanten.
Ansonsten hast du keine Querverweise & hast extra Spaß einen Copy&Paste Fehler zu suchen

Danke! Ich werde das mal so in den nächsten paar Tagen angehen können hoffentlich, dann werde ich das mal so umsetzen.
Für Diskurs/Ideen bin ich natürlich gerne offen.
 
Liegen sie leider nicht. Mit Daten, bzw. Datenpunkte/Werte meine ich auch zB Not-Halt Signal, aktuelle Betriebsart usw..
Dann mach Dir doch eine Struktur, in der Du für jeden Wert Current und Old hineinspeicherst. Den Current kannst Du in jedem Zyklus pauschal am zuständigen Programmpunkt aktualisieren. Und durch die Struktur gehst Du dann pro Zyklus bis zum nächsten geänderten Wert.
 
Würde ich alles auf einmal versenden wollen, bräuchte ich eine Payloadgröße von 3-4KB (Array[0..n] of BYTE] und mindestens 300-400 JSON Elemente, das packt die 1217 nicht. Natürlich ist das erstmal ein "String", den aber andere Systeme direkt als JSON File interpretieren und formatieren, ohne mein zutun.
ob deine Steuerung das packt oder nicht kann ich nicht beurteilen.
Wie man JSON-String serialisiert und de-serialisiert ist mir bekannt - nur nicht im SPS-Umfeld eingesetzt ...
Ich hätte jetzt allerdings angenommen, dass du dir den JSON-String selber zusammenschreibst, da du die Schlüssel und die JSON-Syntax ja kennst - also den jeweils sinnvollen Schlüssel und dessen Wert einfach an den String anhängst.
Du musst auch nicht immer alle Schlüssel hineinschreiben - die Gegenseite hat dann halt nur beim de-serialisieren dort keine Werte.
Um da aber etwas mehr dazu sagen zu können müsste man viel mehr über dein Projekt wissen ...
 
Zuviel Werbung?
-> Hier kostenlos registrieren
ob deine Steuerung das packt oder nicht kann ich nicht beurteilen.
Wie man JSON-String serialisiert und de-serialisiert ist mir bekannt - nur nicht im SPS-Umfeld eingesetzt ...
Ich hätte jetzt allerdings angenommen, dass du dir den JSON-String selber zusammenschreibst, da du die Schlüssel und die JSON-Syntax ja kennst - also den jeweils sinnvollen Schlüssel und dessen Wert einfach an den String anhängst.
Du musst auch nicht immer alle Schlüssel hineinschreiben - die Gegenseite hat dann halt nur beim de-serialisieren dort keine Werte.
Um da aber etwas mehr dazu sagen zu können müsste man viel mehr über dein Projekt wissen ...
Ich nutze die LStream Bibliothek fürs serialisieren und de-serialisieren

Ich nutze die Comm Controller Bibliothek für die MQTT Funktion

In der LStream Bibliothek ist auch eine UDT für das key-value-pair, aus den KVPs setzt sich der JSON Tree zusammen, dieser wird dann durch's serialisieren geschoben und raus kommt eben die passende Payload Array[0..n] of Byte für die MQTT Funktion.

Diese Nachricht für die Positiondatensätze der Y-Achse:
Code:
{
    "Position": {
        "00": {
            "AccDcc": 0.000,
            "InPosition": "FALSE",
            "Interlock": "FALSE",
            "Position": 0.000,
            "Velocity": 0.000
        },
        "01": {
            "AccDcc": 100.000,
            "InPosition": "FALSE",
            "Interlock": "TRUE",
            "Position": 0.000,
            "Velocity": 100.000
        },
        "02": {
            "AccDcc": 100.000,
            "InPosition": "FALSE",
            "Interlock": "TRUE",
            "Position": 25.000,
            "Velocity": 100.000
        },
        "03": {
            "AccDcc": 100.000,
            "InPosition": "FALSE",
            "Interlock": "TRUE",
            "Position": 50.000,
            "Velocity": 100.000
        },
        "04": {
            "AccDcc": 100.000,
            "InPosition": "FALSE",
            "Interlock": "TRUE",
            "Position": 75.000,
            "Velocity": 100.000
        },
        "05": {
            "AccDcc": 0.000,
            "InPosition": "FALSE",
            "Interlock": "FALSE",
            "Position": 0.000,
            "Velocity": 0.000
        },
        "06": {
            "AccDcc": 0.000,
            "InPosition": "FALSE",
            "Interlock": "FALSE",
            "Position": 0.000,
            "Velocity": 0.000
        },
        "07": {
            "AccDcc": 0.000,
            "InPosition": "FALSE",
            "Interlock": "FALSE",
            "Position": 0.000,
            "Velocity": 0.000
        },
        "08": {
            "AccDcc": 0.000,
            "InPosition": "FALSE",
            "Interlock": "FALSE",
            "Position": 0.000,
            "Velocity": 0.000
        },
        "09": {
            "AccDcc": 0.000,
            "InPosition": "FALSE",
            "Interlock": "FALSE",
            "Position": 0.000,
            "Velocity": 0.000
        }
    }
}

Braucht diese Datenmenge für die Nachricht:
Screenshot 2023-11-22 094555.png

61 von 65 Elementen und 1003 von 1199 Bytes, obwohl in der Funktion ich nur 30 Elemente brauche (0..29):
Screenshot 2023-11-22 094829.png

Hier soll es aber primär um meine Frage gehen, welche Möglichkeiten ihr kennt/anwendet um in solchen Fällen eine gleichbehandlung der möglichen Cases zu gewährleisten.
 
Ich habs jetzt so gemacht:
Code:
#ioInterface.InOut.MqttTopic := "MQTT_TOPIC_PUBLISH_AXIS_X"; --// Topic laden
        IF #tempDataLoaded THEN  --// Daten umladen
            IF #DataPrepared.AxisX THEN  --// Daten vorbereiten
                IF (#statDataAxisX <> #statDataAxisXOld) THEN  --// Neu<>Alt Vergleich
                    #ControlPublish.StepName := 'Publish message axis x';  --// Hier gehts weiter wenn neu
                    #statJsonSerializer.execute := 1;
                    IF #tempDataSerialized AND #tempReadyPublish THEN
                        #ioInterface.In.Publish := 1;
                    END_IF;
                    IF #ioInterface.Out.Done THEN
                        #statDataAxisXOld := #statDataAxisX;  --// Neu nach Alt umladen
                        #statDataAxisX := "DB_MqttData".Data.AxisEmpty;  --// Neu ablöschen
                        #ioInterface.In.Publish := 0;
                        #statJsonSerializer.execute := 0;
                        #ControlPublish.StepNumber := 2;
                    END_IF;
                ELSE
                    #ControlPublish.StepNumber := 2;  --// Hier geht's hin wenn alt.
                END_IF;
            END_IF;
        END_IF;

Das ist noch Bedarf und Raum für Optimierung (Vergleichen ohne Daten laden/vorbereiten) aber so funktioniert es für heute, dass Daten nur gesendet werden wenn sich Werte ändern.

Optimierung mache ich morgen.
 
@DCDCDC kannst du die Topics zusammenfassen, so das du nicht jedes Topic einzeln synchronisieren musst?
An stelle von:
MQTT/plc/state
MQTT/plc/estop
...
einfach in
MQTT/plc
zusammen fassen.

Dann könnte man lokal alle Werte des Topics in eoner Structur halten und darüber einen Hash bilden. Diesen kann man mit dem alten vergleichen um festzustellen, ob sich was geändert hat. Wenn ja, dann sendest du das zusammengefasste Topic.
Bin mir aber nicht sicher, ob die Siemens Lib das unterstützt.
 
Zurück
Oben