Data Logging – CSV in CODESYS erstellen

CODESYS-Blog DataLogger

Über kurz oder lang wird euch beim Programmieren das Thema „Data Logging“ beschäftigen. Hierfür gibt es zahlreiche Beispiele. So kann zum Beispiel in einer Fertigungsanlage die Protokollierung von Messdaten oder Umweltdaten bei der Fehlersuche bzw. zur Fehlerbehebung herangezogen werden. Ebenso ist durch eine umfassende Protokollierung der Maschinendaten eine Analyse über den Zustand der Anlage möglich – Stichwort „Big Data und Predictive Maintenance“. Ihr seht also es gibt eine Vielzahl an Gründen, die für ein Logging der Daten sprechen.

Mögliche Varianten des Loggers

Genauso viele Möglichkeiten gibt es, das Logging durchzuführen. Hierbei kann das Logging durch die Komponenten der Maschine selbst erfolgen, oder durch einen extern angebrachten Datenlogger. In diesem kurzen Blog-Beitrag möchte ich mich dem Data Logging in CODESYS widmen. Auch hier gibt es schon mehrere Möglichkeiten. Ich kann externe Software einsetzten und über OPC, OPC-UA, Modbus, etc. Daten abgreifen und Loggen. Genauso gibt es unzählige Möglichkeiten in CODESYS Werte zu Loggen. Es können zum Beispiel die Daten über ein Web Service in eine DB eingetragen werden, oder ich kann in CODESYS durch ein Gateway die Daten direkt in eine Datenbank schreiben. Das alles sind großartige Möglichkeiten.

Wir betrachten hier jedoch zunächst einmal die einfachste Form. Ich werde die Daten in eine CSV-Datei schreiben -> eine großartige Möglichkeit den Umgang mit Dateien in CODESYS zu erlernen. Hierfür gibt es schon vorgefertigte Bibliotheken und Funktionen. Z.B. die OSCAT Library und die CSV utility Funktionen im CODESYS Store.

DataLogging mithilfe von .csv und Funktionsbausteinen

Prinzipiell sind hierfür nur 3 Schritte und die Verwendung der „File Access“ Bibliothek erforderlich.

  1. Öffnen der CSV Datei oder anlegen einer neuen Datei mithilfe von Bausteinen der CAA File (File Access)
  2. Eintragen der Daten in die .csv Datei (CAA file.Write)
  3. Schließen der geöffneten Datei nach erfolgreichem Schreiben (CAA file.Close)

Bibliothek einfügen

Zunächst müssen wir die „File Access“ Bibliothek in CODESYS einbinden. Klicke hierzu Bibliotheksverwalter (1) ⇒ Bibliothek hinzufügen (2)

CODESYS Bibliotheksverwalter
CODESYS Bibliotheksverwalter

Suchmaske „File“ eingeben (1) ⇒ Bibliothek auswählen (2) OK (3)

Liste der CODESYS Bibliotheken
Liste der CODESYS Bibliotheken

CAA File Funktionsbausteine

Damit wir Daten in unser Dateisystem schreiben können, benötigen wir einige Bausteine aus der „File Access“ Bibliothek. Diese können wir in einer Variablenliste deklarieren. Für den Zugriff auf die Bausteine benötigen wir den Namespace „File“.  Weiter oben im Artikel hatte ich erwähnt, dass prinzipiell nur drei Schritte notwendig sind: öffnen, werte eintragen und Datei wieder schließen. Genau dafür benötigen wir die drei Bausteine.

PROGRAM PLC_PRG
VAR
	//function block for file handling
	fopen  : FILE.Open;
  	fwrite : FILE.Write;
  	fclose : FILE.Close;
END_VAR
CAA FileAccess Bausteine für Data Logging
CAA FileAccess Bausteine für Data Logging

1. Open

Mit dem Funktionsbaustein zum Öffnen der Datei könnt Ihr erntescheiden ob Ihr eine neue Datei erstellen wollt, oder eine vorhandene öffnen und euren Inhalt anfügen wollt. Dazu könnt Ihr am Parametereingang „sFileMode“ verschiedene Werte angeben:

  • File.MODE.MRDWR um eine neue Datei zu erstellen
  • File.MODE.MAPPD um die vorhandene Datei zu erweitern
PROGRAM PLC_PRG
//create or open my log-file
        fopen.sFileName:=sFileName;
        fopen.eFileMode:=File.MODE.MRDWR; //File.MODE.MAPPD; << append data
        fopen.xExclusive:=TRUE;
        fopen( xExecute:=TRUE);
        IF fopen.xDone THEN
            hFile:=fopen.hFile;
        END_IF

Wenn die Datei erfolgreich geöffnet wurde, erhaltet Ihr ein sogenannten Datei-Handle (hier hFile variable). Das ist eure temporäre Referenz zu dieser Datei. Wir speichern den Referenzbereich in unserer Variable, da wir über das Datei-Handle beim schreiben/lesen und schließen der Datei dem Betriebssystem genau mitteilen können welchen Speicherbereich wir verarbeiten wollen.

2. Write

Das Schreiben der Daten ist prinzipiell genauso einfach. Hier übergeben wir dem Baustein für den Schreibzugriff einfach die Adresse und die Größe der String Variable die wir in unsere Datei schreiben wollen. Dazu verwenden wir die Funktionen:

  • ADR() : liefert die Adresse des übergebenen Arguments/Variable
  • LEN() : bestimmt die Datenlänge
PROGRAM PLC_PRG
//write log data to file        
        fwrite.hFile:=hFile;
        fwrite.pBuffer:=ADR(sStringToWrite);
        fwrite.szSize:=LEN(sStringToWrite);
        fwrite.udiTimeOut:=100000;	//Timeout
        fwrite( xExecute:=TRUE);
        IF fwrite.xDone THEN
		fwrite( xExecute:=FALSE);
        END_IF

Da wir eine CSV-Datei erstellen wollen, können wir natürlich nicht einfach STRING Variablen in unsere Datei schreiben. Wir müssen den Aufbau unserer Textdatei an das CSV Format anpassen.

Innerhalb der Datei haben wir einige Zeichen mit einer Sonderfunktion zur Strukturierung der Daten.

  • Ein Zeichen wird zur Trennung von Datensätzen benutzt. Dafür wird meistens der Zeilenumbruch verwendet. Wir verwenden hier „$R$N“. Das sind zwar zwei Zeichen, ist aber meistens so üblich.
  • Ein weiteres Zeichen wird zur Trennung von Daten (unseren Spalten) innerhalb der Datensätze benutzt. Das kann mit Semikolon, Doppelpunkt, Tabulatorzeichen, Leerzeichen oder andere Zeichen realisiert werden. Allgemein wird dafür meistens das Komma eingesetzt, welches wir ebenfalls nutzen werden.

Im gesamten Code Beispiel weiter unten, seht Ihr wie mit der STRING Funktion „CONCAT“, mehrere Strings zu genau solch einem CSV Konformen Aufbau zusammen gesetzt werden.

3. Close

Abschließend müssen wir die Datei, nach erfolgreicher Bearbeitung, wieder schließen. Dazu verwenden wir den Baustein fclose. Wie oben erwähnt übergeben wir auch diesem das Datei-Handle, damit klar geregelt ist, welche Datei wir schließen wollen.

PROGRAM PLC_PRG
//close log file
        fclose.hFile:=hFile;
        fclose( xExecute:=TRUE);
        IF fclose.xDone THEN
		fopen( xExecute:=FALSE);
		fwrite( xExecute:=FALSE);
		fclose( xExecute:=FALSE);
        END_IF

Data Logging – Gesamtes CODESYS Programm

Nun, da Ihr die einzelnen Schritte kennt, möchte ich euch noch Beispielhaft ein Code Snippet bereitstellen. Damit könnt Ihr einfach gemessene Daten, aus einem Array (measuredData), in die CSV-Datei schreiben.

PROGRAM PLC_PRG
VAR
	bWriteData:BOOL:=FALSE;		//trigger to write log-data
	uiStpDataLogger:UDINT:=0;	//sequencer
	
	//File name and handle (path is only for windows)
	sFileName:  CAA.FILENAME:= 'C:\CODESYS-Blog\logData.csv';;	//../Log/logData.csv << Relative paths in the local file system
    hFile:      CAA.HANDLE;
	
	//example data
	measuredData: ARRAY [1..5] OF logData := [(index:='16#DEADBEEF',data:='dead beef'),(index:='16#C001CAFE',data:='cool cafe'),(index:='16#BADCAB1E',data:='bad cable'),(index:='16#BADC0DED',data:='bad coded'),(index:='16#C0EDBABE',data:='coed babe')];	//Hexspeak
    sStringToWrite:    STRING:='no data';
	writeIndex:UINT:=1;
	
	//control characters
	CRLF:STRING(2):= '$R$N';
	LF:STRING(1):= '$N';
	
	//function block for file handling
	fopen  : FILE.Open;
  	fwrite : file.Write;
  	fclose : file.Close;
END_VAR


CASE uiStpDataLogger OF
	(*wait for trigger*)
	0: 	IF bWriteData THEN
			bWriteData:=FALSE;
			uiStpDataLogger:=10;
		END_IF

	10:	(* create or open my log-file *)
        fopen.sFileName:=sFileName;
        fopen.eFileMode:=File.MODE.MRDWR; //File.MODE.MAPPD;
        fopen.xExclusive:=TRUE;
        fopen( xExecute:=TRUE);
        IF fopen.xDone THEN
            hFile:=fopen.hFile;
			sStringToWrite:='';
			writeIndex:=1;
            uiStpDataLogger:=20;
        END_IF
		
	20: (*generate data*)
		sStringToWrite:=CONCAT(measuredData[writeIndex].index,',');
		sStringToWrite:=CONCAT(sStringToWrite,measuredData[writeIndex].data);
		sStringToWrite:=CONCAT(sStringToWrite,CRLF);
		//sStringToWrite:=CONCAT(sStringToWrite,LF);
		uiStpDataLogger:=30;
		
	30:	(* write log data to file *)
        fwrite.hFile:=hFile;
        fwrite.pBuffer:=ADR(sStringToWrite);
        fwrite.szSize:=LEN(sStringToWrite);
        fwrite.udiTimeOut:=100000;	//Timeout
        fwrite( xExecute:=TRUE);
        IF fwrite.xDone THEN
			fwrite( xExecute:=FALSE);
			writeIndex:=writeIndex+1;
			IF writeIndex <= 5 THEN
					uiStpDataLogger:=20;
					sStringToWrite:='';
				ELSE
					uiStpDataLogger:=40;
			END_IF
        END_IF
        IF fwrite.xError THEN
			//Error
            uiStpDataLogger:=16#DEADBEEF;
        END_IF
		
	40:  (* close log file *)
        fclose.hFile:=hFile;
        fclose( xExecute:=TRUE);
        IF fclose.xDone THEN
			fopen( xExecute:=FALSE);
			fwrite( xExecute:=FALSE);
			fclose( xExecute:=FALSE);
            uiStpDataLogger:=0;
        END_IF
        IF fclose.xError THEN
            //Error
            uiStpDataLogger:=16#DEADBEEF;
        END_IF

	16#DEADBEEF:
		//ErrorHandling
		;
END_CASE

Fazit

Natürlich sind nicht alle Ausnahmen abgefangen. So kann es zum Beispiel sein, dass Ihr Daten an eine Datei anhängen wollt und diese noch gar nicht existiert. Oder Ihr eventuell gar keine Schreibberechtigung auf die Ressource habt.

Es gibt hier noch so viele Punkte, die beim Data Logging, beachtet werden müssen. Allerdings wollte ich euch mit dem kurzen Beispiel einen netten Überblick verschaffen. Solltet Ihr hier näheres wissen wollen, dann schreibt mir einfach.

5 Kommentare

  1. Hallo Matthias,
    ich habe den Codeschnipsel zum Test in ein leeres ControlWin-Projekt kopiert.
    Nach meinem Verständnis wird in Case 20 der String für die Textausgabe in die CSV-Datei zu:

    16#DEADBEEF,dead beef
    16#C001CAFE,cool cafe
    16#BADCAB1E,bad cable
    16#BADC0DED,bad coded
    16#C0EDBABE,coed babe

    erstellt.
    Der Codesys-Übersetzer stört sichallerdings bei der Deklaration des Arrays an dem selbstdefiniertem Datentyp „logData“.
    In der Hilfe finde ich nichts, was mir aus der Patsche hilft.
    Kannst du mir dazu einen Tip geben?

    1. Hallo Wolfgang,

      Entschuldigung. Hier habe ich wohl ein wichtiges Detail vergessen. Danke dass du mich gefragt hast. Ich habe hierzu einen eigenen Datentype erstellt. Hierzu Rechtsklick auf „Applikation“ – Objekt hinzufügen – DUT und dann den Inhalt wie nachfolgend anpassen.

      Datentyp

      Beste Grüße
      Matthias

  2. Hallo Matthias, ich denke dein Code-Beispiel ist sehr hilfreich, aber was mache ich wenn ich mehr als 2000 Bytes Rückgabewerte habe. Die bekomme ich ja nicht in einen String. Schreibe ich die in mehrere Strings, müsste ich dann alle 256 Bytes in jew. eine neue Datei schreiben? Oder gibt es eine Möglichkeit einen String in beliebiger Länge zu beschreiben?

    1. Hallo Daniel, bitte entschuldige, dass meine Antwort so lange gedauert hat. Ich war/bin beruflich gerade sehr eingespannt. Schau dir mal den Datentyp WSTRING an. Damit kannst du längere Strings verarbeiten.

      1. Hallo Matthias, ich habe noch ein anderes Problem, dass leider viel gravierender ist. Ich habe versucht dein Beispiel in mein Programm einzubauen (Eine über eine Case-Anweisung gesteuerte Schrittkette zum Kommunizieren über die serielle RS232-Schnittstelle) bzw. zu adaptieren. Leider friert das Programm mit den zusätzlichen „DataLogging“-Schritten dann ein. Ich bekomme den Fehler „5603“ vom FB „File.Close“ (invalid handle). Als erstes habe ich vermutet, dass mir im Linux-Filesystem die nötigen Berechtigungen um auf einer FAT-formatierten Partition eines usb-Sticks Dateien anzulegen. Also habe ich die Benutzerrechte in der „fstab“ so festgeschrieben, dass jeder dort lesen und schreiben darf. Leider führte das nicht zu einer Änderung im Programmablauf. Ich bekomme auch weiterhin den Fehler 5603(invalid handle)… Falls Sie dafür eine Erklärung haben?

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website benutzt Cookies und Google Analytics. Wenn du die Website weiter nutzt, gehen wir von deinem Einverständnis aus Weitere Informationen

Die Cookie-Einstellungen auf dieser Website sind auf "Cookies zulassen" eingestellt, um das beste Surferlebnis zu ermöglichen. Wenn du diese Website ohne Änderung der Cookie-Einstellungen verwendest oder auf "Akzeptieren" klickst, erklärst du sich damit einverstanden.

Schließen