CSV-Datei in TwinCAT einlesen

The Blue

Level-2
Beiträge
105
Reaktionspunkte
12
Zuviel Werbung?
-> Hier kostenlos registrieren
Es gibt zwar schon einige Beiträge zu csv-Dateien,
aber leider habe ich nichts passendes zu meinem Problem gefunden.

ich muss zum ersten Mal eine csv datei in Twincat 3 einlesen.
Da ich mit dem zerteilen einer jeden Zeile Schwierigkeiten habe,
habe die CSVExample.zip von der infosys-Seite zur Hand genommen.

Das Programm hat aber ein Problem.

Ist eine Zeile länger als 255 Zeichen,
wird der Teil, ab dem letzten Trennzeichen verworfen
und der nächste Eintrag beginnt mit dem 256ten Zeichen.

Also steht in einer Zelle „orgWarner…“ statt „BorgWarner…“
Und „1025“ statt „DE00034267292K0000000000000001025"
Hier die ersten Zeilen der csv-Datei:
Zählpunktbezeich...:;DE000342672925610000300010E000045;DE0003426729200000000000000001067;DE0003426729200000000000000001001;DE0003426729200000000000000001066;DE0000226729255K0400900000E000098;DE00034267292K0000000000000001026;DE00034267292K0000000000000001025;DE00034267292K0000000000000001062;DE0003426729200000000000000011001
OBIS...............:;1-1:1.29.0;1-1:1.29.0;1-1:1.29.0;1-1:1.29.0;1-1:1.29.0;1-1:1.29.0;1-1:1.29.0;1-1:1.29.0;1-1:1.29.0
01.04.2021 00:15:00;230;131;2456;0;9,392;71,896;114,124;111,24;364
01.04.2021 00:30:00;228;140;2504;0;8,9;79,312;124,632;121,54;364

Das Programm erkennt, das die Zeile noch nicht zuEnde ist,
also wurde ja der Fall, dass die Zeichenkette größer 255 Zeichen sein kann,
berücksichtigt.

Der FB_FileGets gibt diesen String aus:
'Zählpunktbezeich...:;DE000342672925610000300010E000045;DE0003426729200000000000000001067;DE0003426729200000000000000001001;DE0003426729200000000000000001066;DE0000226729255K0400900000E000098;DE00034267292K0000000000000001026;DE00034267292K000000000000000'
Der Teil ab dem letzten Trennzeichen müsste also vor den nächten String gesetzt werden,
oder so - aber ich weiß nicht, wie.

Hat jemand einen Lösungsvorschlag für mich?
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Dann lies Dir nochmal die Beschreibung zum Beispiel durch, da hast Du nämlich etwas entscheidendes übersehen. Es werden die Standardstringfunktionen genutzt und da ist bei 255 Zeichen Schluß. Im Fall von CSV sogar bei 253, weil noch CR und LF dazukommen.
Du wirst vermutlich über den Umweg ARRAY OF BYTE gehen müssen und dann das Ganze mit MEMCPY wieder in Strings packen.
 
Das Programm scheint doch aber prinzipiell in der Lage zu sein...
Es wird kein Fehler ausgegeben
und erst nach dem CRLF wird im Datenbank Array die nächste Zeile begonnen.
csv2array.PNG
Der FB_FileGets und FB_CSVMemBufferReader kann jeweils nur 255 Zeicher verarbeiten.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo,

ich habe den "gleichen" Fall beim Schreiben gehabt. Der Datensatz einer Zeile hatte die Gesamtlänge von 253 Zeichen überschritten.
Abhilfe hat das öffnen der CSV Datei im BinaryMode, anstatt im TextMode (wie im Standard-Beispiel) gebracht.
Die Daten wurden nach dem FileOpen (Binarymode) per FB_CSVMemBufferWriter => in einen BUFFER kopiert und anschliesend komplett per FB_FileWrite (Buffer) in die CSV Datei geschrieben.
In der Hilfe bei Beckhoff steht auch (siehe Tabelle)
[TABLE="width: 500"]
[TR]
[TD][/TD]
[TD]TextMode[/TD]
[TD]BinaryMode[/TD]
[/TR]
[TR]
[TD]Maximale Datensatzlänge die geschrieben/gelesen werden kann[/TD]
[TD]Auf 253 Zeichen begrenzt (Datensatz + CRLF). D.h. die Datensatzlänge darf 253 Zeichen nicht überschreiten.[/TD]
[TD]Die maximale Datensatzlänge ist theoretisch unbegrenzt[/TD]
[/TR]
[/TABLE]

Demnach sollte das auch im Umgekehrter Reihenfolge gehen ...?! Probiert habe ich das aber selbst noch nicht, beim CSV-READ haben bei mir die 253 Zeichen bisher ausgereicht.
Viell. nützt ja diese Info etwas.

VG!
 
Ich habe es jetzt so gelöst

und wüste gerne Eure Meinung

Code:
FUNCTION_BLOCK FB_CSV_EVU_Read
(* Reading of CSV file in text mode. None of the field value is containing any non-printing control characters like line feed, carriage return, quotation marks, semicolon... *)
VAR_INPUT
    bRead            : BOOL := FALSE;(* Rising edge starts program execution *)
    sNetId            : T_AmsNetId := '';    (* TwinCAT system network address *)
    sFileName        : T_MaxString := 'C:\Temp\TextModeGen.csv';(* CSV source file path and name *)
    sTrennzeichen    : STRING := ';';   
END_VAR
VAR_IN_OUT
    database        : ARRAY[0..Constanten.MAX_CSV_ROWS, 0..Constanten.MAX_CSV_COLUMNS ] OF STRING(Constanten.MAX_CSV_FIELD_LENGTH);(* Target PLC database *)
END_VAR    
VAR_OUTPUT
    bBusy            : BOOL;
    bError            : BOOL;
    nErrId            : UDINT;

END_VAR


VAR
    sCSVLine        : T_MaxString := '';(* Single CSV text line (row, record), we are using string as record buffer (your are able to see read fields)  *)
    sCSVField        : T_MaxString := '';(* Single CSV field value (column, record field) *)
    
    nRow             : UDINT     := 0;(* Row number (record) *)
    nColumn            : UDINT     := 0;(* Column number (record field) *)
    hFile            : UINT        := 0;(* File handle of the source file *)
    step            : DWORD     := 0;
    fbFileOpen        : FB_FileOpen;(* Opens file *)
    fbFileClose        : FB_FileClose;(* Closes file *)
    fbFileGets        : FB_FileGets;(* Reads one record (line) *)

    sRest            : STRING;
    nCSVField        : INT;
    nLF                : int;
END_VAR

Code:
CASE step OF
    0:    (* Wait for rising edge at bRead variable *)
        IF bRead THEN
            bRead         := FALSE;
            bBusy         := TRUE;
            bError        := FALSE;
            nErrId        := 0;
            hFile        := 0;
            nRow         := 0;
            nColumn        := 0;
            MEMSET( ADR( database ), 0, SIZEOF( database ) );
            step         := 1;
            
        END_IF

    1:    (* Open source file *)
        fbFileOpen(  bExecute     := FALSE  );
        fbFileOpen( sNetId         := sNetId, 
                    sPathName     := sFileName, 
                    nMode         := FOPEN_MODEREAD OR FOPEN_MODETEXT,(* Open file in TEXT mode! *)
                    ePath         := PATH_GENERIC, 
                    bExecute     := TRUE );
        step := 2;

    2:(* Wait until open not busy *)
        fbFileOpen( bExecute     := FALSE, 
                    bError         => bError, 
                    nErrID         => nErrID, 
                    hFile         => hFile );
        IF NOT fbFileOpen.bBusy THEN
            IF NOT fbFileOpen.bError THEN
                step := 3;
            ELSE(* Error: file not found? *)
                step := 100;
            END_IF
        END_IF

    3:    (* Read single line (record) *)
        fbFileGets( bExecute := FALSE );
        fbFileGets( sNetId      := sNetId, 
                    hFile      := hFile, 
                    bExecute := TRUE );
        step := 4;

    4:(* Wait until read not busy *)
        fbFileGets( bExecute     := FALSE, 
                    bError         => bError, 
                    nErrID         => nErrID, 
                    sLine         => sCSVLine );
        IF NOT fbFileGets.bBusy THEN
            IF NOT fbFileGets.bError THEN
                IF fbFileGets.bEOF THEN
                    step := 10;(* End of file reached => Close source file *)
                ELSE
                    step := 5;
                END_IF
            ELSE(* Error *)
                step := 100;
            END_IF
        END_IF

    5:(* Parse single line (record) *)
        
        REPEAT
            nCSVField := find(sCSVLine,sTrennzeichen);
            
            IF find(sCSVLine,'$N') > find(sCSVLine,'$R') THEN
                nLF := find(sCSVLine,'$N');
            ELSE
                nLF := find(sCSVLine,'$R');
            END_IF
            
            IF nCSVField = 0 THEN
                nCSVField := nLF;
            END_IF
            
            IF nCSVField > 0 THEN
                sCSVField := left(sCSVLine,nCSVField -1);
                sCSVField := concat(sRest,sCSVField);
                sRest := '';
                sCSVLine := delete(sCSVLine,nCSVField + 1,0);
                    
                
                IF ( nRow <= Constanten.MAX_CSV_ROWS ) THEN
                    IF ( nColumn <= Constanten.MAX_CSV_COLUMNS ) THEN
                        database[nRow, nColumn] := CSVFIELD_TO_STRING( sCSVField, FALSE );(* TODO: Save or use the field value in your application *)            
                    END_IF
                END_IF

                nColumn := nColumn + 1;(* Increment number of read columns *)
                IF len(sCSVLine) = 0 THEN(* CRLF == TRUE => End of reacord reached *)
                    nRow         := nRow + 1;(* Increment number of read records *)
                    nColumn     := 0;(* Reset number of columns *)
                END_IF
            ELSIF (nCSVField = nLF) THEN
                sRest := sCSVLine;
                step := 3;
            ELSE(* Error: End of record reached or all fields read *)
                    
                //step := 3;(* Try to read next line *)
                step := 10;
            END_IF
        UNTIL nCSVField = 0 AND nLF = 0
        END_REPEAT

    10:    (* Close source file *)
        fbFileClose( bExecute := FALSE );
        fbFileClose(     sNetId     := sNetId, 
                        hFile     := hFile, 
                        bExecute:= TRUE );
        step := 11;

    11:(* Wait until close not busy *)
        fbFileClose(     bExecute:= FALSE, 
                        bError     => bError, 
                        nErrID     => nErrID );
        IF ( NOT fbFileClose.bBusy ) THEN
            hFile := 0;
            step := 100;
        END_IF

    100: (* Error or ready step => cleanup *)
        IF ( hFile <> 0 ) THEN
            step := 10; (* Close the source file *)
        ELSE
            bBusy := FALSE;
            step := 0;    (* Ready *)
        END_IF

END_CASE

Wird kein Trennzeichen oder CRLF mehr gefunden,
wird der Rest gespeichert und vor den nächsten TeilString gesetzt.
 
Kann man so machen - funktioniert soweit (hab's mal getestet). Wie

oliver.tonn

und meine Wenigkeit schon geschrieben haben, wäre der Weg über einen BYTE Puffer sicherlich eleganter.

Ich hab mal (aus Eigeninteresse) den oben beschriebenen Code in eine Variante BinaryMode umgewandelt. Auf die Schnelle wird hier aber nur 1 Zeile behandelt und auch die Spaltenanzahl ist fest auf 50 gesetzt. D.h. für eine umfassende CSV Datei zum einlesen müsste ggf. der Puffer vergrößert werden und der Parseteil dynamisiert werden. Habs mit der im Beitrag 1 beschriebenen CSV Zeile probiert ...

Hauptsächliche Änderung:
  • FileGet wird zu "FB_FileRead"
  • Das Parsen der einzelnen CSVFields übernimmt "FB_CSVMemBufferReader"

Code:
FUNCTION_BLOCK FB_CSV_EVU_Read
(* Reading of CSV file in text mode. None of the field value is containing any non-printing control characters like line feed, carriage return, quotation marks, semicolon... *)
VAR_INPUT
    bRead            : BOOL := FALSE;(* Rising edge starts program execution *)
    sNetId            : T_AmsNetId := '';    (* TwinCAT system network address *)
    sFileName        : T_MaxString := 'D:\PS\neu.csv';(* CSV source file path and name *)
    sTrennzeichen    : STRING := ';';   
END_VAR
VAR
    database        : ARRAY[0..10, 0..50 ] OF STRING(100);(* Target PLC database *)
END_VAR   
VAR_OUTPUT
    bBusy            : BOOL;
    bError            : BOOL;
    nErrId            : UDINT;

END_VAR


VAR
    sCSVLine        : T_MaxString := '';(* Single CSV text line (row, record), we are using string as record buffer (your are able to see read fields)  *)
    sCSVField        : T_MaxString := '';(* Single CSV field value (column, record field) *)
    
    nRow             : UDINT     := 0;(* Row number (record) *)
    nColumn            : UDINT     := 0;(* Column number (record field) *)
    hFile            : UINT        := 0;(* File handle of the source file *)
    step            : DWORD     := 0;
    fbFileOpen        : FB_FileOpen;(* Opens file *)
    fbFileClose     : FB_FileClose;(* Closes file *)
    fbFileRead      : FB_FileRead;(* Reads one record (line) *)
    fbRead            : FB_CSVMemBufferReader;

    record        : ARRAY[0..25000] OF BYTE;(* Binary (row) data buffer. The size of this buffer have to be > length of longest record line + 2 (CRLF)  *)
    cbRecord    : UDINT := 0;(* Number of bytes in row buffer *)

    
    sRest            : STRING;
    nCSVField        : INT;
    nLF                : int;
END_VAR

Code:
CASE step OF
    0:    (* Wait for rising edge at bRead variable *)
        IF bRead THEN
            bRead         := FALSE;
            bBusy         := TRUE;
            bError        := FALSE;
            nErrId        := 0;
            hFile        := 0;
            nRow         := 0;
            nColumn        := 0;
            MEMSET( ADR( database ), 0, SIZEOF( database ) );
            step         := 1;
            
        END_IF

    1:    (* Open source file *)
        fbFileOpen(  bExecute     := FALSE  );
        fbFileOpen( sNetId         := sNetId,
                    sPathName     := sFileName,
                    nMode         := FOPEN_MODEREAD OR FOPEN_MODEBINARY,(* Open file in Binary mode! *)
                    ePath         := PATH_GENERIC,
                    bExecute     := TRUE );
        step := 2;

    2:(* Wait until open not busy *)
        fbFileOpen( bExecute     := FALSE,
                    bError         => bError,
                    nErrID         => nErrID,
                    hFile         => hFile );
        IF NOT fbFileOpen.bBusy THEN
            IF NOT fbFileOpen.bError THEN
                step := 3;
            ELSE(* Error: file not found? *)
                step := 100;
            END_IF
        END_IF

    3:    (* Read single line (record) *)
        fbFileRead( bExecute := FALSE );
        fbFileRead( sNetId      := sNetId,
                    hFile      := hFile,
                    bExecute := TRUE,
                    pReadBuff    := ADR(record),
                    cbReadLen:= SIZEOF(record)
                 );
        step := 4;

    4:(* Wait until read not busy *)
        fbFileRead( bExecute     := FALSE,
                    bError         => bError,
                    nErrID         => nErrID,
                   );
        IF NOT fbFileRead.bBusy THEN
            IF NOT fbFileRead.bError THEN
                IF fbFileRead.bEOF THEN
                    step := 10;(* End of file reached => Close source file *)
                ELSE
                    cbRecord := fbFileRead.cbRead;
                    step := 5;
                END_IF
            ELSE(* Error *)
                step := 100;
            END_IF
        END_IF

    5:(* Parse single line (record) *)

        nRow:=0;
        fbRead.eCmd := eEnumCmd_First;(* Write first field value *)
           FOR nColumn := 0 TO 50 BY 1 DO

         fbRead(
                    pBuffer := ADR( record ),
                    cbBuffer := SIZEOF( record ),
                    
                                );(* bCRLF == TRUE => Write CRLF after the last field value *)
        IF fbRead.bOk THEN
            database[nRow,nColumn]:=fbRead.getValue;
            fbRead.eCmd             := eEnumCmd_Next;(* Write next field value *)
        ELSIF fbRead.bCRLF THEN
            step := 10;            // hier ggf. neue Zeile abfragen
        ELSE(* Error *)
            step := 100;
            RETURN;
        END_IF
        END_FOR(* FOR nColumn := 0... *)


    10:    (* Close source file *)
        fbFileClose( bExecute := FALSE );
        fbFileClose(     sNetId     := sNetId,
                        hFile     := hFile,
                        bExecute:= TRUE );
        step := 11;

    11:(* Wait until close not busy *)
        fbFileClose(     bExecute:= FALSE,
                        bError     => bError,
                        nErrID     => nErrID );
        IF ( NOT fbFileClose.bBusy ) THEN
            hFile := 0;
            step := 100;
        END_IF

    100: (* Error or ready step => cleanup *)
        IF ( hFile <> 0 ) THEN
            step := 10; (* Close the source file *)
        ELSE
            bBusy := FALSE;
            step := 0;    (* Ready *)
        END_IF

END_CASE
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo Zusammen,

ich habe derzeit dasselbe Problem das angeführt wurde. Da ich allerdings nei bei Twincat bin weiß ich mir nicht zu helfen. Ich vermute ebenfalls das die obere character grenzen überschritten wird und meine werte daher falsch/unvollständig eingelesen werden. Ich habe versucht den minarymode read nazuvollzieheh allerdings bekomme ich das nicht zum laufe. Kann mit einer weiterhelfen in der hoffnung wenn ich das beispiel zum laufen bekomme ich das auf meinen fall angewendet bekommen sollte

Grüße
Erdem
 
Zurück
Oben