TIA Verschiedene Datentypen zu WString, zu Byte Array (UTF-8 kodiert)

DCDCDC

Level-3
Beiträge
1.735
Reaktionspunkte
366
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo zusammen,

Umgebung:
CPU 1217C DC/DC/DC 6ES7 217-1AG40-0XB0 V4.6
TIA Portal V18 Update 2

Ich muss Daten für MQTT Nachrichten aufbereiten, diese werden als UTF-8 kodiertes Byte Array an den Baustein übergeben, der bastelt dann aus dem Topic und der Payload das Telegramm. Soweit so gut.

Aktuell geht es nur darum, die Positionen der Achsen unseres Portals zu übermitteln.

Diese liegen als Real Datentyp vor (kommen über die Schnittstelle als DInt, daraus bastele ich dann die Gleitpunktzahl)

Verwende ich jetzt für die aktuelle Position die Funktion REAL_TO_WSTRING, sieht das ganze so aus:
Screenshot 2023-08-25 104638.png
Screenshot 2023-08-25 104743.png

Ich habe mir aus dem MQTT Baustein den Schnipsel rausgesucht welcher WString in eine UTF-8 kodiertes Byte Array umwandelt kopiert und jetzt für den Fall seperat angewendet. Das passt auch soweit, also der WString stimmt mit dem Inhalt der Nachricht überein:
Screenshot 2023-08-25 105052.png
Screenshot 2023-08-25 105006.png

Zudem werden noch eine ganze Reihe an nicht darstellbaren Zeichen angezeigt, wie man sieht.

Mein Array der Nachricht sieht so aus:
Screenshot 2023-08-25 105742.png

Ich hab das Array mal 100 Byte groß gewählt, weil ich noch nicht weiß, wie lange unsere Payload wird.

Woher kommen die nicht darstellbaren Zeichen, wenn da eine klare 0 drin steht? Hatte in dem Umfang noch nichts mit dem UTF-8 Format zu tun, bzw. schon lange nicht mehr mit Telegrammen gearbeitet welche reine Byte Arrays als Payload haben.

Gibt es eine einfachere Methode als für jeden Datentyp von Werten die ich publishen möchte eine Funktion zu erstellen in der ich erstmal alles in ein Array von WChar umwandele und mir dann meinen WString wieder zusammenbastele?

z.B. möchte ich von der Position nur die drei Stellen vor dem Komma und die ersten drei Nachkommastellen übermitteln, so wie sie auch zB im HMI dargstellt werden.

Das Komma verschiebt sich ja auch abhängig davon ob die Position einstellig, zweistellig oder dreistellig ist, d.h. ich könnte nicht einfach stumpf die Chars zusammensetzen sondern müsste erstmal die Position im Array suchen, an der mein Kommata sitzt?


Hier noch der gesamte Code welcher die Konvertierung in WString und dann ins Byte Array macht.
Code:
// Delete old data
#statPayloadWString := #WSTRING_EMPTY;
#statPayloadPublishMessage := #tempPayloadPublishMessageEmpty;
#statPayloadPos := 0;

// Get current data
#statPayloadWString := REAL_TO_WSTRING(IN := "DB_Axis".X.PositionData.ActualPosition);
#statTopicWString := WSTRING#'Stationsbezeichnung\AxisX\ActualPosition';

// Convert payload to byte array
FOR #tempConvertLoop := #FIRST_CHARACTER TO LEN(#statPayloadWString) DO
    "LMQTT_ConvertToUtf8"(character := #statPayloadWString[#tempConvertLoop],
                          convertedUtf8 => #statConvertedChar,
                          bytesUsed => #tempConvertBytesUsed,
                          error => #tempConvertError,
                          status => #tempConvertStatus);
   
    CASE #tempConvertBytesUsed OF
        #UTF8_1_BYTE:
            #statPayloadPublishMessage[#statPayloadPos] := #statConvertedChar.%B0;
            #statPayloadPos += 1;
        #UTF8_2_BYTE:
            #statPayloadPublishMessage[#statPayloadPos] := #statConvertedChar.%B0;
            #statPayloadPos += 1;
            #statPayloadPublishMessage[#statPayloadPos] := #statConvertedChar.%B1;
            #statPayloadPos += 1;
        #UTF8_3_BYTE:
            #statPayloadPublishMessage[#statPayloadPos] := #statConvertedChar.%B0;
            #statPayloadPos += 1;
            #statPayloadPublishMessage[#statPayloadPos] := #statConvertedChar.%B1;
            #statPayloadPos += 1;
            #statPayloadPublishMessage[#statPayloadPos] := #statConvertedChar.%B2;
            #statPayloadPos += 1;
    END_CASE;
END_FOR;

IF #tempConvertLoop >= LEN(#statPayloadWString) THEN
    "DB_Mqtt".Payload.PublishMessage := #statPayloadPublishMessage;
    "DB_Mqtt".Interface.InOut.MqttTopic := #statTopicWString;
END_IF;

IF ("DB_Mqtt".Payload.PublishMessage = #statPayloadPublishMessage)  AND ("DB_Mqtt".Interface.InOut.MqttTopic = #statTopicWString) AND
    (NOT "DB_Mqtt".Interface.Out.Done) THEN
    "DB_Mqtt".Interface.In.Publish := 1;
END_IF;

Der Code wird bewusst zyklisch ausgeführt, da immer die aktuelle Position übermittelt werden soll.

Edit: Hab gerade gesehen, beim schreiben, dass das erste Byte der Nachricht leer war und hab deswegen #statPayloadPos := 1; zu #statPayloadPos := 0; abgeändert.

Danke soweit und einen schönen Freitag, hoffe das war ausführlich genug! :)
 
Zuletzt bearbeitet:
Hab zumindest jetzt die Werte mit VAL_STRG hinbekommen.

Nur ist die Payload noch voll mit nicht darstellbaren Zeichen?

Screenshot 2023-08-25 142115.png

Das kläre ich aber mit dem Support von Siemens, wieso das auftritt und wie sich das ablöschen lässt.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Aktuell geht es nur darum, die Positionen der Achsen unseres Portals zu übermitteln.
Welche Einheit haben die DINT-Zahlen? µm?
Diese liegen als Real Datentyp vor (kommen über die Schnittstelle als DInt, daraus bastele ich dann die Gleitpunktzahl)
Warum das UmBasteln einer Festpunktzahl (DINT) in eine Gleitpunktzahl?
Dadurch entfernst Du Dich doch eher von Deinem Ziel, als Dich Deinem Ziel zu nähern.
Es kann doch nicht sooo schwierig sein, an der passenden Stelle einen DezimalPunkt bzw. ein DezimalKomma in die Ziffernfolge zu schmuggeln!?
 
UTF-8 in einem Byte abbilden ist nicht komplett möglich, weil UTF-8 die Informationen in bis zu 8 Bytes codiert. Lediglich der ASCIII Anteil läst sich in einem Byte abbilden.

Edit: Ich würde die Werte unmodifiziert übertragen. Abschneiden von Nachkommastellen sollte nur im letzte Schritt bei der Visualisierung geschehen.
 
Welche Einheit haben die DINT-Zahlen? µm?

Warum das UmBasteln einer Festpunktzahl (DINT) in eine Gleitpunktzahl?
Dadurch entfernst Du Dich doch eher von Deinem Ziel, als Dich Deinem Ziel zu nähern.
Es kann doch nicht sooo schwierig sein, an der passenden Stelle einen DezimalPunkt bzw. ein DezimalKomma in die Ziffernfolge zu schmuggeln!?
Genau, DInt hat µm als Einheit, mache daraus nur mm.

Mit den Werten wird nichts produktives gemacht, werden an den digitalen Zwilling übergeben.. die Achsen haben Schrittmotoren mit einer Positionierungenauigkeit von +/- 2mm..

Hab's wie beschrieben mit VAL_STRG gelöst.

UTF-8 in einem Byte abbilden ist nicht komplett möglich, weil UTF-8 die Informationen in bis zu 8 Bytes codiert. Lediglich der ASCIII Anteil läst sich in einem Byte abbilden.

Edit: Ich würde die Werte unmodifiziert übertragen. Abschneiden von Nachkommastellen sollte nur im letzte Schritt bei der Visualisierung geschehen.
"Schneide" sie nicht ab, hab bei der Variable nur "999,999" als Anzeigeformat eingestellt. Die Schrittmotoren sind wenn's hoch kommt auch nur auf 1/10 genau.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
UTF-8 in einem Byte abbilden ist nicht komplett möglich, weil UTF-8 die Informationen in bis zu 8 Bytes codiert. Lediglich der ASCIII Anteil läst sich in einem Byte abbilden.
Ich verstehe auch nicht, weshalb WSTRING benutzt wird. STRING verwendet Zeichen von 8 Bit und WSTRING Zeichen von 16 Bit Länge. In UTF-8 haben die Zeichen aber keine feste, sondern eine variable Länge. Die Ziffern, Punkt, Komma und Vorzeichen gehören aber definitiv zu den ASCII-Zeichen, die (maximal) 8 Bit belegen ... auch in der UTF-8-Darstellung.

Bei Siemens steht am Anfang des Strings die maximale Länge und die aktuell belegte Länge. STRING und WSTRING unterscheiden sich dabei aber darin, dass bei STRING die LängenAngaben je 1 Byte und bei WSTRING je 1 Wort ( = 2 Byte) belegen. Vermutlich wird der STRING bzw. der WSTRING nicht initialisiert und die LängenAngaben fehlen deshalb?
 
Zuletzt bearbeitet:
WString ist bereits UTF-8 codiert. Das verstehen alle anderen Kommunikationspartner bei MQTT und auch bei OPC UA. String in der PLC ist ein Siemens Format.

@Heinileini Ändert sich der Inhalt, wenn man bei der Definition des (W)String die Zeichenanzahl limitiert? Z.B. WString[15]
 
Zuviel Werbung?
-> Hier kostenlos registrieren
...

"Schneide" sie nicht ab, hab bei der Variable nur "999,999" als Anzeigeformat eingestellt. Die Schrittmotoren sind wenn's hoch kommt auch nur auf 1/10 genau.
Ich würde trotzdem Werte immer als Zahlendatentyp an MQTT weiter geben und die Einheit als WString[X]. So kann der Subscriber später selbst entscheiden ob und wie er die Darstellung anpasst.
 
Der Baustein für die MQTT Kommunikation, also auch der Baustein der UTF-8 Konvertierung stammen von Siemens.

Header Informationen usw. werden im MQTT Baustein zusammengebastelt.
Auch im Baustein selbst, wird das Topic, welches im Format WString vorliegt, wird intern noch mal auf Byte Array im UTF-8 Format umkodiert. Hab wie gesagt, den Schnipsel nur aus dem Baustein für mich übernommen, da der Baustein aus dem Topic ein Array bastelt und dann nur noch die Payload mit dazu nimmt und dass dann in den finalen Sendebereich zusammen kommt.

Ist auch erstmal nur eine "fall back"/erste Lösung, da wir aktuell MQTT sonst nur in der Steuerungstechnik auf Beckhoff IPCs verwenden und dass ganze da im json Format published wird.. und ich mir dann wohl für die Endlösung auch ein json Format in TIA basteln darf und das übergebe (und dann mir wohl meinen eigenen Baustein zur MQTT Kommunikation schreiben darf, der von Siemens hat aktuell fast 3000 Zeilen..).

Ich würde trotzdem Werte immer als Zahlendatentyp an MQTT weiter geben und die Einheit als WString[X]. So kann der Subscriber später selbst entscheiden ob und wie er die Darstellung anpasst.
Werde ich am Montag so umsetzen! VAL_STRG gefällt mir auch nicht so sehr und ich würde gerne so wenig wie möglich hin und her konvertieren wollen.
 
WString ist bereits UTF-8 codiert
Das stimmt nicht.
WSTRING benutzt UCS2 als Codierung. UCS2 ist nicht UTF8.
Die Zeichencodes sind gleich, werden aber anders codiert.

Siehe auch:
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Das stimmt nicht.
WSTRING benutzt UCS2 als Codierung. UCS2 ist nicht UTF8.
Die Zeichencodes sind gleich, werden aber anders codiert.

Siehe auch:
Gemäß der Norm IEC 61131-3 folgt der Datentyp WSTRING der UCS-2 Kodierung.

UCS-2-Kodierung bedeutet, dass ein Zeichen exakt mit der festen Länge von 2 Bytes kodiert wird. UCS-2 umfasst die Zeichen der Codepunkte von U+0000 bis U+D7FF und von U+E000 bis U+FFFF. Der String wird mit 0 terminiert.

Wenn das aber so "fest" ist, wieso ist dann bei mir, bzw. von Siemens abgefangen, dass das Zeichen drei Byte groß sein könnte, für Sonderzeichen/Mandarin?
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Das stimmt nicht.
WSTRING benutzt UCS2 als Codierung. UCS2 ist nicht UTF8.
Die Zeichencodes sind gleich, werden aber anders codiert.

Siehe auch:
Stimmt - mein Fehler. Hatte Unicode mit UTF-8 verwechselt.
 
Hallo,

der Support hat mir die Daten-Stream (LStream) Bibliothek empfohlen um meine Daten ins json Format zu wandeln.. ich steige aber noch nicht ganz durch wie der LStream_JsonSerializer Baustein zu befüllen ist, gerade die "tree" Struktur.. es bleibt spannend :D

Bin auch gespannt was passiert, wenn ich den ASCII codierten Stream an Bytes dann noch mal in UTF-8 umkodieren darf:
Aufgrund von Systemverhalten der SIMATIC PLC und da als Datentypen für Texte und Zeichen
aus Speichergründen String/Byte verwendet wurden, ist es nur möglich, ASCII Encoding mit der
Bibliothek zu verwenden. Ein anderes Encoding führt zu undefiniertem Verhalten.
 
Zuletzt bearbeitet:
Hallo,

der LStream_JsonSerializer Baustein funktioniert grundsätzlich, Wandlung nach UTF-8 klappt auch. Mein aktuelles Problem ist mit tree Element..

Ich will drei Werte übergeben also habe ich eine Variable für tree angelegt mit [0..2] of LStream_typeElement. Soweit so gut.. aber aktuell packt der Baustein mir nur die key value pairs aus den ersten beiden Arrays in einen String.

So befülle ich den Tree:
Code:
// Data axis x
        #statJsonTree[0].closingElement := 0;
        #statJsonTree[0].depth := 0;
        #statJsonTree[0].key := 'PositionAxisX';
        #statJsonTree[0].type := 3;
        #statJsonTree[0].value := WSTRING_TO_STRING(DINT_TO_WSTRING("DB_Axis".X.Out.ActPos));
        // Data axis y
        #statJsonTree[1].closingElement := 0;
        #statJsonTree[1].depth := 0;
        #statJsonTree[1].key := 'PositionAxisY';
        #statJsonTree[1].type := 3;
        #statJsonTree[1].value := WSTRING_TO_STRING(DINT_TO_WSTRING("DB_Axis".Y.Out.ActPos));
        // Data axis z
        #statJsonTree[2].closingElement := 0;
        #statJsonTree[2].depth := 0;
        #statJsonTree[2].key := 'PositionAxisZ';
        #statJsonTree[2].type := 3;
        #statJsonTree[2].value := WSTRING_TO_STRING(DINT_TO_WSTRING("DB_Axis".Z.Out.ActPos));

Und das kommt heraus:
Code:
'{"PositionAxisX":+5029,"PositionAxisY":+7461}'

Ich hab auch schon mit den depth und closingElement Parametern gespielt, aber bekomme es nicht so konfiguriert, dass alle drei Werte in einem json übergeben werden.

Es werden auch aktuell nur 45 der verfügbaren 255 Zeichen verwendet, d.h. Platz im Puffer ist genug. Hat jemand eine Idee und den Baustein schon mal eingesetzt?

Danke!

(Leider gibt's zur Bibliothek kein Anwendungsbeispiel auf der SIOS Seite, auch wenn's im Handbuch so drin steht)
 
vielen Dank für Ihre Rückmeldung.
Meine Analyse ergab, dass sich ein Fehler im Anwendungsbeispiel befinden. Sie müssen den Baustein 'LStream_JsonSerializer' wie im Screenshot gezeigt ändern.
🤪

Hier die Änderung:

Zeile 214

Diese:
Code:
#statInfoToWrite := CONCAT(IN1 := CHAR_TO_STRING(#QUTATIONMARK), IN2 := #tree[#statIndexTreeArray].key,
IN3 := CHAR_TO_STRING(#QUTATIONMARK), IN4 := CHAR_TO_STRING(#COLON), IN5 := CHAR_TO_STRING(#QUTATIONMARK),
IN6 := CHAR_TO_STRING(#QUTATIONMARK));

Ersetzen durch:
Code:
#statInfoToWrite := CONCAT(IN1 := CHAR_TO_STRING(#QUTATIONMARK), IN2 := #tree[#statIndexTreeArray].key,
IN3 := CHAR_TO_STRING(#QUTATIONMARK), IN4 := CHAR_TO_STRING(#COLON), IN5 := CHAR_TO_STRING(#QUTATIONMARK),
IN6 := #tree[#statIndexTreeArray].value, IN7:= CHAR_TO_STRING(#QUTATIONMARK));
 
Zuletzt bearbeitet:
🤪

Hier die Änderung:

Zeile 214

Diese:
Code:
#statInfoToWrite := CONCAT(IN1 := CHAR_TO_STRING(#QUTATIONMARK), IN2 := #tree[#statIndexTreeArray].key,
IN3 := CHAR_TO_STRING(#QUTATIONMARK), IN4 := CHAR_TO_STRING(#COLON), IN5 := CHAR_TO_STRING(#QUTATIONMARK),
IN6 := CHAR_TO_STRING(#QUTATIONMARK));

Ersetzen durch:
Code:
#statInfoToWrite := CONCAT(IN1 := CHAR_TO_STRING(#QUTATIONMARK), IN2 := #tree[#statIndexTreeArray].key,
IN3 := CHAR_TO_STRING(#QUTATIONMARK), IN4 := CHAR_TO_STRING(#COLON), IN5 := CHAR_TO_STRING(#QUTATIONMARK),
IN6 := #tree[#statIndexTreeArray].value, IN7:= CHAR_TO_STRING(#QUTATIONMARK));
Bekomme jetzt immer den Fehler #8601, verwende ich den Baustein aus der Bibliothek ohne die Änderung, läuft der Baustein fehlerfrei durch, es fehlen halt nur wie im Support Request angemerkt die Daten aus dem ersten Tree Element :rolleyes:
 
Zurück
Oben