Ü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.
- Öffnen der CSV Datei oder anlegen einer neuen Datei mithilfe von Bausteinen der CAA File (File Access)
- Eintragen der Daten in die .csv Datei (CAA file.Write)
- 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)⇒
OK (3)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
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-
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.
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?
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.
Beste Grüße
Matthias
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?
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.
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?