Zuviel Werbung? - > Hier kostenlos beim SPS-Forum registrieren

Ergebnis 1 bis 8 von 8

Thema: Probleme beim CSV Datei schreiben mit ByteArray auf WAGO 750-8202

  1. #1
    Registriert seit
    06.12.2018
    Beiträge
    4
    Danke
    3
    Erhielt 0 Danke für 0 Beiträge

    Unglücklich


    Zuviel Werbung?
    -> Hier kostenlos registrieren
    Hey zusammen,
    ich bin relativ neu in der SPS Welt und versuche gerade mit Hilfe der WAGO Beispiel Syslibfile.lib Projekte eigene CSV Dateien schreiben zu lassen.
    Umgesetzt wird das Projekt auf einem WAGO 750-8202 und in CoDeSys 2.3 programmiert.

    tl;dr:
    In den erfolgreich geschriebenen Dateien finde ich nach dem Block aus Zeitangabe, Werten und Zeilenabschluss-Steuerzeichen zusätzliche Zeichen, welche dort ungewollt geschrieben wurden.
    Könnte der Fehler in meiner Zeigerarithmetik liegen?


    Langfassung:
    In einer WerteGenerieren()-Funktion wird mir ein selbstdefinierter Datentyp befüllt: Darin ein eindimensionales Array mit 10 Werten (gc_ColumnCount := 9 global definiert) und die aktuelle Systemzeit:
    Code:
    TYPE typCSV :
        STRUCT
            dtTageszeit:DT;
            Wert:ARRAY[0..gc_ColumnCount] OF REAL;
        END_STRUCT
    END_TYPE
    Danach wird in einem Konvertierungs-Funktionsblock die Umwandlung von typCSV (EingangsArray) in ein ByteArray vorgenommen.
    Dazu lasse ich das ByteArray: ARRAY[0..gc_RawDataSize] OF BYTE vor jeder Ausführung löschen und nutze im Anschluss die Zeigerarithmetik aus dem WAGO Beispiel Projekt:
    Code:
    FOR z:=0 TO gc_RawDataSize BY 1 DO                                       (* ByteArray löschen *)
        ByteArray[z] := 0;
    END_FOR
    
    ptString := ADR(ByteArray);                                                        (* Pointer auf Anfang des Bytearrays *)
    index := 0;
    
    ptString^ := DT_TO_STRING(EingangsArray.dtTageszeit);                        (* Zeitstempel --> Bytefeld *)
    index := index + LEN(ptString^);                                                    (* feststellen wieviel Byte benutzt sind *)
    ptString := ADR(ByteArray[index]);                                                    (* Pointer auf Ende des Wertes *)
    
    FOR i:=0 TO gc_ColumnCount BY 1 DO
        ptString^:= trenner;                                                            (* Trennzeichen hinter letzten Wert *)
        index := index + LEN(ptString^);
        ptString := ADR(ByteArray[index]);
    
        ptString^ := REAL_TO_STRING(EingangsArray.Wert[i]);                        (* Wert in ByteArray schreiben *)
        index := index + LEN(ptString^);
        ptString := ADR(ByteArray[index]);
    END_FOR
    
    ptString^:= '$r$n';                                                                    (* new line *)
    index := index + LEN(ptString^);
    ptString := ADR(ByteArray[index]);
    Das ByteArray ist natürlich um einiges größer (durch gc_RawDataSize := 100 global festgelegt) als die Summer aller zu schreibenden Werte (usw.) in Byte.

    Der Konvertierungs-Funktionsblock wird im Programm Dateischreiben aufgerufen. Beim Aufruf der Konvertierungsinstanz wird auch die WerteGenerieren()-Funktion aufgerufen.
    Das zurückgegebene ByteArray wird nun wie folgt geschrieben:
    Code:
    Konvertierungsinstanz(trenner:= ';', EingangsArray:= WerteGenerieren(), convert_ready=> , ByteArray=> );
    
    groesse := SysFileGetSize(g_sDateiname);                                            (* Größe der Datei feststellen *)
    IF Konvertierungsinstanz.convert_ready THEN
        handle := SysFileOpen(g_sDateiname, g_zugriff);                                    (* g_zugriff wird mit 'a' initialisiert *)                                                                                           
        groesse := groesse + SysFileWrite(handle, ADR(Konvertierungsinstanz.ByteArray),SIZEOF(Konvertierungsinstanz.ByteArray));
        file_ok := SysFileClose(handle);                                                    (* Datei schließen *)
    END_IF
    Nach dem Schreiben steht alles Gewünschte in der erzeugten / erweiterten Datei.

    Mein Problem ist, dass nach einem Null-Byte hinter dem Zeilenende-Steuerzeichen ein abgehacktes Datum (15 Byte lang) vom Format DATE_AND_TIME folgt.
    Am Besten zu sehen in einem Hex-Editor (Das Markierte ist der unbewusst geschriebene Datumsteil):
    Datei.JPG

    Es ist hinter jedem geschriebenen Daten(ByteArray)block. Manchmal fehlen die Zeichen, manchmal stehen dort irgendwelche Zeichen, aber kein Datum... dann sogar im Null-Teil nach hinten verschoben.
    Habe ich bei meiner Zeigerarithmetik einen Fehler gemacht oder ist es ein anderer Fehler?
    Könnte es auch eine WAGO/Controller spezifische Beschränkung geben, welche ich übersehe?

    Ich lasse extra bei jedem Konvertierungsaufruf das ByteArray aufs neue mit Nullen beschreiben, von daher kann ich mir nicht erklären, warum hinter den Zeilenende-Steuerzeichen keine Nullen stehen...

    Gibt es vielleicht eine andere Methode mein ByteArray zu füllen/ zu löschen oder sollte ich es mit Strings schreiben versuchen?
    Ich will allerdings später viel mehr Werte schreiben, sodass die allgemeine String Längenbeschränkung ein Problem darstellt.

    Vielen Dank für eure Hilfe.
    MfG
    TiI
    Geändert von TiI (06.12.2018 um 18:38 Uhr)
    Zitieren Zitieren Probleme beim CSV Datei schreiben mit ByteArray auf WAGO 750-8202  

  2. #2
    Registriert seit
    13.12.2011
    Beiträge
    2.085
    Danke
    233
    Erhielt 280 Danke für 249 Beiträge

    Standard

    Du führst die einzelnen File-Befehle zu schnell aus. Die einzelnen FBs haben Rückmeldungen, wann sie fertig sind und welchen Status sie haben. Den nächsten Befehl darf man erst ausführen wenn der vorhergehende beendet ist. Der Schreiben Befehl benötigt z.B. meist mehr als einen Zyklus, deswegen kommt bei Dir einiges durcheinander.
    Geändert von oliver.tonn (06.12.2018 um 18:55 Uhr)

  3. Folgender Benutzer sagt Danke zu oliver.tonn für den nützlichen Beitrag:

    TiI (11.12.2018)

  4. #3
    Registriert seit
    23.06.2009
    Ort
    Sassnitz
    Beiträge
    12.754
    Danke
    1.038
    Erhielt 3.753 Danke für 3.031 Beiträge

    Standard

    Deine Zeigerarithmetik ist korrekt. Die unerwarteten Zeichen in der Datei entstehen vermutlich erst beim Datei-Schreiben, was bei Dir zu früh "abgewürgt" wird, wie oliver.tonn schon anmerkte.

    Man könnte die csv-Zeile verständlicher mit CONCAT zusammensetzen. Dann braucht auch nicht das ganze Array vorher gelöscht werden sondern nur das erste Byte ( ByteArray[0] := 0; )
    (Wieviele Zeichen darf ein String lang sein für CONCAT bei WAGO? Sind bei WAGO die String-Funktionen "thread-safe"? Wenn nicht, dann darf CONCAT nicht in verschiedenen Task verwendet werden.)


    Zitat Zitat von TiI Beitrag anzeigen
    Das ByteArray ist natürlich um einiges größer (durch gc_RawDataSize := 100 global festgelegt) als die Summer aller zu schreibenden Werte (usw.) in Byte.
    Vorsicht, ich denke für reale Zahlenwerte ist Dein Bytearray zu klein! Echte REAL-Werte können/werden mehr Nachkommastellen haben. Im ungünstigsten Fall können schon allein die 10 Real-Werte und Semikolons mehr als 100 Zeichen ergeben, dazu kommen noch 22 Zeichen Zeitstempel und 2 Zeichen Zeilenende.

    Gibt es eine genaue Dokumentation, wie bei WAGO die Ausgabe von REAL_TO_STRING aussieht?
    Gibt es bei WAGO eine Funktion um einen Real mit Formatvorgabe in einen String zu konvertieren (z.B. REAL_TO_FMTSTR?)?

    Harald
    Es ist immer wieder überraschend, wie etwas plötzlich funktioniert, sobald man alles richtig macht.

    FAQ: Linkliste SIMATIC-Kommunikation über Ethernet

  5. Folgender Benutzer sagt Danke zu PN/DP für den nützlichen Beitrag:

    TiI (11.12.2018)

  6. #4
    TiI ist offline Neuer Benutzer
    Themenstarter
    Registriert seit
    06.12.2018
    Beiträge
    4
    Danke
    3
    Erhielt 0 Danke für 0 Beiträge

    Standard

    Hey, danke erstmal an euch für die Anregungen:

    in der CoDeSys Programmierungsanleitung von 3S - Smart Software Solutions und in der Beschreibung zur SysFileLib steht leider nichts von einer Methode, wie ich auf das Ende des Schreibvorgangs warten könnte.
    Die SysFileWrite Funktion gibt (nach Erfolg?) nur einen DWORD zurück, welcher die Anzahl der geschriebenen Bytes enthält.

    Da ich in ST schreibe, werden die Befehle doch generell nacheinander abgearbeitet. Von welchem Zyklus reden wir also, wenn mein Schreibvorgang eurer Meinung nach in der Mitte unterbrochen wird? Ich habe dem Task zum Schreiben meiner Datei mehr als 10 sek gegeben, das Ergebnis bleibt leider gleich... also mit den ungewollten abgehackten Datum. Egal ob ich nur 10 Werte und ein 100 Byte Array schreibe oder 400 Werte in ein 3000 Byte Array.

    Wie könnte ich den SysFileWrite Vorgang (in einer Task) "auslagern", sodass sein Schreibvorgang nicht unterbrochen wird?
    Für mich sieht es so aus, als würde der Schreibvorgang keinerlei Probleme haben fertig zu werden.
    Ich überprüfe jetzt vor jedem Schreibvorgang ob mir vorher die SysFileClose Funktion ein TRUE lieferte.
    Nur in diesem Fall erlaube ich beim nächsten zyklischen Aufruf des Schreib-Tasks die SysFileWrite Funktion.
    Laut der der CoDeSys Programmierungsanleitung wird mit dem SysFileClose die Datei endgültig geschrieben und abgelegt.
    Es sollte mir laut meinem Verständnis ein TRUE liefern, wenn der Schreibvorgang geglückt ist und die Datei zum schließen nicht mehr gelockt ist.
    Das deckt sich auch mit dem Dateiinhalt danach.

    Ich bin ein wenig ratlos worauf ich noch prüfen kann oder welche Funktionen ich nutzen sollte
    Würden mir vielleicht die Async Funktionen (SysFileOpenAsync, SysFileWriteAsync, SysFileCloseAsync) weiterhelfen?

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    Um mal das ganze mal mit Strings zu testen, habe ich folgende Konvertierung geschrieben:
    Code:
    WriteBuffer:            STRING(2000);                                        (*  STRING *)
    
    ========================================================
    WriteBuffer := '';
    WriteBuffer := CONCAT(WriteBuffer, DT_TO_STRING(EingangsArray.dtTageszeit));            (* Zeitstempel --> Bytefeld *)
    
    FOR i:=0 TO gc_ColumnCount BY 1 DO
        WriteBuffer := CONCAT( WriteBuffer, trenner );
        WriteBuffer := CONCAT( WriteBuffer, REAL_TO_STRING(EingangsArray.Wert[i]) );
    END_FOR
    
    WriteBuffer := CONCAT( WriteBuffer, '$R$N' );                                                    (* new line *)
    Das hat leider nichts gebracht. Es gibt keine dynamischen Strings in CoDeSys 2.3.
    Alle String Funktionen können nur mit 250 Zeichen umgehen, sodass CONCAT bei größeren Strings nicht mehr funktioniert und ab 255 Zeichen nichts weiter an WriteBuffer anfügen will

    Habt ihr noch weitere Tipps für mich?
    Würden mir die OSCAT Bibliotheken in diesem Fall etwas bringen? Gibt es dort erweiterte / dynamische Datentypen?

    Vielen Dank für eure Hilfe
    VG
    TiI

  7. #5
    TiI ist offline Neuer Benutzer
    Themenstarter
    Registriert seit
    06.12.2018
    Beiträge
    4
    Danke
    3
    Erhielt 0 Danke für 0 Beiträge

    Standard

    Code:
    ptString^:= '$r$n';                                                                    (* new line *)
    
    ___________________________________
    index := index + LEN(ptString^);       <-- gelöscht
    ptString := ADR(ByteArray[index]);    <-- gelöscht
    Da sonst keine weiteren Hinweise kamen, habe ich zur Probe aus dem WAGO Beispiel Programm einfach die letzten 2 Zeilen herausgelöscht.
    Sie erschienen mir recht sinnfrei, denn danach war die Ausführung des Funktionsblocks zuende und es wurde nicht mehr mit dem String-Pointer gearbeitet.

    Das brachte den entscheidenden Erfolg und es tauchen keine ungewollten Zeichen mehr in meinem ByteArray auf.

    Ich vermute der Fehler liegt in der nicht Beachtung der Funktionsweise des Funktionsblocks.
    Der Funktionsblock behält bis zur erneuten Ausführung den Pointer (der vorherigen Instanz). Ohne Löschung der letzten zwei Zeilen liegt im Pointer die letzte (beschriebene) Adresse im ByteArray des vorherigen Schreibvorgangs.
    Bei einem erneuten Aufruf des Funktionsblocks wird der Pointer wieder initialisiert und ihm die Start-Adresse des neuen ByteArrays zugewiesen.
    Auch wenn ich nicht nachvollziehen kann, wann ich dadurch in den Bereich des alten ByteArrays schreibe, wird dort scheinbar ein Schreibvorgang ausgeführt?!
    Das erscheint mir bisher als einzige Erklärung des Phänomens.

    Oder hat jemand eine andere Erklärung dafür?

    Vielen Dank für eure Hilfe.
    TiI

  8. #6
    Registriert seit
    23.06.2009
    Ort
    Sassnitz
    Beiträge
    12.754
    Danke
    1.038
    Erhielt 3.753 Danke für 3.031 Beiträge

    Standard

    Zitat Zitat von TiI Beitrag anzeigen
    habe ich zur Probe aus dem WAGO Beispiel Programm einfach die letzten 2 Zeilen herausgelöscht.
    [...]
    Das brachte den entscheidenden Erfolg und es tauchen keine ungewollten Zeichen mehr in meinem ByteArray auf.

    Ich vermute [...]
    Das erscheint mir bisher als einzige Erklärung des Phänomens.
    Dein Erklärungsversuch passt nicht zum gezeigten Code. In dem Code werden ptString und index vor der Verwendung ordnungsgemäß initialisiert. Und außerhalb von dem gezeigten Konvertierungs-Funktionsblock nicht verwendet.

    Daß die beiden Zeilen im gezeigten Code anscheinend überflüssig sind, hatte ich auch schon bemerkt. Das Weglassen der Zeilen sollte aber keinen Effekt auf das Datei-Schreiben haben. (Es ist oft so, daß in Schleifen beim letzten Durchlauf die Pointer nochmal "sinnfrei" erhöht werden - da stört das auch nicht.)

    Oder werden ptString oder index noch woanders verwendet/ausgewertet? Du hast uns leider eine Menge wichtigen Code vorenthalten/weggekürzt.


    Mir scheint, der Fehler liegt in der Längen-Angabe von SysFileWrite(...):
    Zitat Zitat von TiI Beitrag anzeigen
    Code:
    Konvertierungsinstanz(trenner:= ';', EingangsArray:= WerteGenerieren(), convert_ready=> , ByteArray=> );
    
    groesse := SysFileGetSize(g_sDateiname);                                            (* Größe der Datei feststellen *)
    IF Konvertierungsinstanz.convert_ready THEN
        handle := SysFileOpen(g_sDateiname, g_zugriff);                                    (* g_zugriff wird mit 'a' initialisiert *)
        groesse := groesse + SysFileWrite(handle, ADR(Konvertierungsinstanz.ByteArray),SIZEOF(Konvertierungsinstanz.ByteArray));
        file_ok := SysFileClose(handle);                                                    (* Datei schließen *)
    END_IF
    Das SIZEOF(..Array) dürfte die gesamte Größe des Arrays liefern und nicht nur die von der erstellten Textzeile belegte Länge. Die korrekte Länge steht in der Variable index - wenn das letzte Erhöhen von index oben nicht weggelassen wird!

    Ich würde mal die Programmzeile so ändern:
    Code:
        groesse := groesse + SysFileWrite(handle, ADR(Konvertierungsinstanz.ByteArray),LEN(Konvertierungsinstanz.ByteArray));
    btw.: hier könnte man ebenfalls sagen, das "groesse := groesse + " ist überflüssig, doch wir können ja nicht sehen, ob groesse irgendwo später nochmal verwendet wird.

    Leider kenne ich die Wago-Bibliothek nicht. Es scheint mir so, daß man selber keine Schrittkette für das Dateischreiben erstellen muß, SysFileClose prüft anscheinend selber, ob das Schreiben in die Datei komplett beendet ist. Wie wird bei Dir sichergestellt, daß SysFileClose (und evtl. SysFileWrite?) so oft/so lange immer wieder aufgerufen wird, bis die Datei komplett geschrieben und geschlossen ist? Der Code sieht aus als ob alles nur 1x aufgerufen würde. Besonders das "groesse := groesse + " soll doch bestimmt nicht mehrmals ausgeführt werden?

    Harald
    Es ist immer wieder überraschend, wie etwas plötzlich funktioniert, sobald man alles richtig macht.

    FAQ: Linkliste SIMATIC-Kommunikation über Ethernet

  9. Folgender Benutzer sagt Danke zu PN/DP für den nützlichen Beitrag:

    TiI (14.12.2018)

  10. #7
    TiI ist offline Neuer Benutzer
    Themenstarter
    Registriert seit
    06.12.2018
    Beiträge
    4
    Danke
    3
    Erhielt 0 Danke für 0 Beiträge

    Standard

    Zitat Zitat von PN/DP Beitrag anzeigen
    Oder werden ptString oder index noch woanders verwendet/ausgewertet? Du hast uns leider eine Menge wichtigen Code vorenthalten/weggekürzt.
    Nein ptrString wird nirgends außerhalb des Konvertierungs-Funktionsblock verwendet. Es ist im Deklarationsteil nur im Abschnitt für lokale Variablen VAR.

    Zitat Zitat von PN/DP Beitrag anzeigen
    Mir scheint, der Fehler liegt in der Längen-Angabe von SysFileWrite(...):

    Das SIZEOF(..Array) dürfte die gesamte Größe des Arrays liefern und nicht nur die von der erstellten Textzeile belegte Länge. Die korrekte Länge steht in der Variable index - wenn das letzte Erhöhen von index oben nicht weggelassen wird!

    Ich würde mal die Programmzeile so ändern:
    Code:
        groesse := groesse + SysFileWrite(handle, ADR(Konvertierungsinstanz.ByteArray),LEN(Konvertierungsinstanz.ByteArray));
    btw.: hier könnte man ebenfalls sagen, das "groesse := groesse + " ist überflüssig, doch wir können ja nicht sehen, ob groesse irgendwo später nochmal verwendet wird.
    Die String Funktion LEN() hat laut Dokumentation wieder das Problem nur mit max 255 String Zeichen umgehen zu können. Zudem muss ich bei SysFileWrite die Länge (Size) eines ByteArrays in Form eines DWORD angeben. Als Eingangstyp braucht LEN() eine Variable vom Typ String. Damit fällt das leider raus. (Außerdem sind die String Funktionen laut Doku nicht threadsafe)

    Die groesse Berechnung habe ich mittlerweile herraus gekürzt. Das hatte ich einfach aus dem Programmierbeispiel von WAGO übernommen, dafür aber keine Verwendung gefunden.

    Zitat Zitat von PN/DP Beitrag anzeigen
    Leider kenne ich die Wago-Bibliothek nicht. Es scheint mir so, daß man selber keine Schrittkette für das Dateischreiben erstellen muß, SysFileClose prüft anscheinend selber, ob das Schreiben in die Datei komplett beendet ist. Wie wird bei Dir sichergestellt, daß SysFileClose (und evtl. SysFileWrite?) so oft/so lange immer wieder aufgerufen wird, bis die Datei komplett geschrieben und geschlossen ist? Der Code sieht aus als ob alles nur 1x aufgerufen würde. Besonders das "groesse := groesse + " soll doch bestimmt nicht mehrmals ausgeführt werden?
    Harald
    Nach euren Hinweisen habe ich in der Dokumentations zu SysFileClose gefunden, dass erst nach der Ausgabe von TRUE bei der SysFileClose-Funktion die Datei tatsächlich geschrieben wurde.

    Ich überprüfe in meinem Code nun den BOOL Wert der SysFileClose Funktion auf TRUE. Wenn der Schreibvorgang erfolgreich war, wird ein Zähler dafür hochgezählt.
    Wird bei der Überprüfung ein FALSE festgestellt, wird ein Fehlschlag-Zähler hoch gesetzt.
    Dieser Fehlschlagzähler steht immer auf 0. Demnach sollte der Schreibvorgang niemals fehlschlagen.

    Deshalb hatte ich vermutet, dass eine andere Funktion(sblock) auf mein Array aus der Konvertierung zugreift, während ich das Dateischreiben schon in Auftrag gab.
    Heißt es wird von etwas ins ByteArray geschrieben, auch wenn der Konvertierungs-Funktionsblock schon durchlaufen wurde...

  11. #8
    Registriert seit
    23.06.2009
    Ort
    Sassnitz
    Beiträge
    12.754
    Danke
    1.038
    Erhielt 3.753 Danke für 3.031 Beiträge

    Standard


    Zuviel Werbung?
    -> Hier kostenlos registrieren
    Die Stringfunktion LEN() verwendest Du schon oft in der Konvertierungsinstanz.
    Deine Textzeile ist bisher noch viel kürzer als 255 Zeichen (Du wolltest sogar mal mit 101 Byte hinkommen )

    Du könntest die Zeilenlänge (entspricht index am Ende) aus der Konvertierungsinstanz ausgeben:
    Code:
    VAR_OUTPUT
      ...
      TextLen : UDINT ; //oder DWORD (was allerdings nicht ganz korrekt wäre)
    END_VAR
    
    ...
    ptString^:= '$r$n';               (* new line *)
    
    index := index + LEN(ptString^);
    TextLen := INT_TO_UDINT(index); //oder INT_TO_DWORD
    ... und an SysFileWrite übergeben:
    Code:
        groesse := SysFileWrite(handle, ADR(Konvertierungsinstanz.ByteArray), Konvertierungsinstanz.TextLen);
    Harald
    Es ist immer wieder überraschend, wie etwas plötzlich funktioniert, sobald man alles richtig macht.

    FAQ: Linkliste SIMATIC-Kommunikation über Ethernet

Ähnliche Themen

  1. Antworten: 18
    Letzter Beitrag: 13.04.2017, 15:43
  2. Antworten: 1
    Letzter Beitrag: 24.03.2015, 07:36
  3. Zeilenumbruch beim .csv Datei schreiben
    Von Beycker im Forum CODESYS und IEC61131
    Antworten: 3
    Letzter Beitrag: 24.09.2014, 14:13
  4. Wago: CSV Datei auf FTP Server schreiben
    Von beginner87 im Forum WAGO
    Antworten: 7
    Letzter Beitrag: 30.09.2013, 21:52
  5. Antworten: 1
    Letzter Beitrag: 22.10.2012, 12:42

Lesezeichen

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
  •