Zyklische Daten und Zeitreihen-Datenbanken
Dieses Dokument beschreibt den Umgang mit Zeitreihen und wie zyklisch auftretende Daten in Zeitreihendatenbanken gespeichert werden.
Genutzte Datenbank: InfluxDB
Genutzter Datenbanktyp: TimeSeriesDB
Einleitung
Das Schreiben von Daten in regelmäßigen oder zyklischen Abständen ist ein häufiger Anwendungsfall in der Steuerungstechnik. Dabei sollten die Daten zeitgenau erfasst werden. Da Datenbankkommunikation nicht echtzeitfähig ist, ist es sinnvoll die regelmäßig gemessenen Daten in einem Zwischenspeicher zu speichern. Dafür kann ein Array von der Datenstruktur dienen. Die gesammelten Daten werden daraufhin an den TwinCAT Database Server gesendet und können dort zeitunkritisch verarbeitet und schließlich in der Datenbank gespeichert werden.
Zeit
In der verwendeten Datenbank werden Datensätze jeweils mit einem Zeitstempel abgespeichert. Zusammen mit den Tag-Spalten bilden diese eine eindeutige Identifikation. Falls zwei Datensätze eine identische Identifikation (gleiche Zeitstempel- und Tag-Werte) tragen, überschreibt der neuere den alten Datensatz.
Beispiel:
| time | locationname (tag) | temperature (field) | windspeed(field) |
---|---|---|---|---|
1 | 1581675200630326200 | Verl | 11.5 | 6.3 |
2 | 1581675200630327200 | Verl | 10.3 | 5.2 |
3 | 1581675200630328200 | Verl | 9.8 | 2.8 |
4 | 1581675200630328200 | Hamburg | 14.2 | 14.9 |
4 | 1581675200630328200 | Hamburg | 15.6 | 8.9 |
… | … | … | … | … |
Datensatz Nr. 4 wird von neuem Datensatz überschrieben, da die Identifikation identisch ist. |
Der Zeitstempel in der Datenbank wird standardmäßig als UNIX-Epoch-Zeit abgespeichert. Mit Ausnahme, der selbst erstellten Insert-Befehle werden die Zeitstempel bei den Funktionsbausteinen des TwinCAT 3 Database Servers als TwinCAT-Zeit (Anzahl der 100ns-Schritten seit 1.1.1601) entgegengenommen und konvertiert. Im Falle von Insert-Befehlen werden die Zeiten nicht konvertiert.
Datenbankkonfiguration
In der Datenbankkonfiguration sollte InfluxDB ausgewählt sein. Fügen Sie die Verbindungsparameter zur gewünschten Datenbank ein. Falls noch keine Datenbank vorhanden sein sollte, können Sie die Datenbank über den „Create“-Button erzeugen. Achten Sie auf Ihre Firewall-Einstellungen bzw. Portfreigaben. Eine Tabelle muss nicht angelegt werden, da diese automatisch beim ersten Zugriff von InfluxDB erzeugt wird. Ein festes Tabellenschema gibt es bei InfluxDB nicht. Auch nachträglich können Spalten erweitert oder hinzugefügt werden.
Schreiben zyklischer Daten
Dieses Beispiel soll zeigen, wie mit minimiertem Aufwand Symbole aus der SPS in eine Zeitseriendatenbank geschrieben werden können.
Deklaration:
State: E_DbLogState;
bWriting: BOOL; // Set this bool fla to write the data once into the InfluxDB
dbid: UDINT := 1; // Handle to the configured database
QueryOption_TSDB_Insert : T_QueryOptionTimeSeriesDB_Insert; // defines detailed Queryparameter
fbNoSQLQueryBuilder_TimeSeriesDB : FB_NoSQLQueryBuilder_TimeSeriesDB; // defines database type specific api
fbNoSqlQueryEvt : FB_NoSQLQueryEvt(sNetID := '', tTimeout := T#15S); // functionblock to execute queries
// databuffer for 1 second with 10 ms time delta
windTurbineData: ARRAY[1..100] OF WindTurbineData;
// error handling helper values
TcResult: Tc3_Database.I_TcMessage;
bError: BOOL;
sErrorMessage: STRING(255);
i: INT;
rand : DRAND;
nrand: LREAL;
Deklarieren der Datenquellenstruktur:
In dieser Struktur werden die Attribute „TagName“ und „FieldName“ verwendet, um die Datenfelder als Tags oder Fields zu deklarieren. Standardmäßig werden sie als Fields deklariert. Falls der Spaltenname in der Tabelle vom Symbolnamen in der SPS abweichen soll, können ebenfalls diese Attribute verwendet werden.
Um nicht gesetzte Daten bei der Datenanalyse zu erfassen, können Standardwerte außerhalb des Wertebereichs verwendet werden, um diese bei der Analyse zu erkennen. |
TYPE WindTurbineData :
STRUCT
{attribute 'TagName' := 'ID'}
WindTurbineID : STRING(255);
{attribute 'FieldName' := 'Power'}
Power : LREAL := -1; // [0) [kW]
{attribute 'FieldName' := 'Wind Speed'}
WindSpeed : LREAL := -1; // [0) [m/s]
{attribute 'FieldName' := 'Wind Direction'}
WindDirection : LREAL := -1; // [0,360][°]
END_STRUCT
END_TYPE
(WindTurbineData.tcDUT)
Deklaration des ENUM für die State-Machine:
TYPE E_DbLogState :
(
idle := 0,
init,
writing,
error
);
END_TYPE
Generieren von Beispieldaten:
FOR i := 1 TO 100 BY 1 DO
rand();
nrand := rand.Num;
windTurbineData[i].WindDirection := 240.328 + nrand;
windTurbineData[i].WindSpeed := 7.3292 + nrand;
windTurbineData[i].Power := 1133.1975 + nrand;
windTurbineData[i].WindTurbineID := 'Wind Turbine Verl 13';
END_FOR
CASE State OF
E_DbLogState.idle:
IF bWriting THEN
bWriting := FALSE;
State := E_DbLogState.init;
END_IF
Vorbereiten des Aufrufs:
In diesem Fall wird das Array ‘windTurbineData’ in die Tabelle ‚WindMeasurement‘ der Datenbank geschrieben. Dafür werden die Daten direkt aus dem Prozessabbild gelesen. Um die Adressen im Speicher auszulesen wird der Datentyp angegeben. Die Zeit der Datensätze wird automatisch unter Angabe der Startzeit und dem Zeitabstand generiert. Die Daten müssen dafür korrekt im Array abgespeichert werden. Pro SPS-Zyklus kann beispielsweise ein Datensatz im Array genutzt werden. Es ist sinnvoll für diesen Prozess eine SPS-Task anzulegen. In diesem Bespiel beträgt die Zykluszeit 10 ms.
E_DbLogState.init:
fbNoSQLQueryBuilder_TimeSeriesDB.pQueryOptions := ADR(QueryOption_TSDB_Insert);
fbNoSQLQueryBuilder_TimeSeriesDB.cbQueryOptions := SIZEOF(QueryOption_TSDB_Insert);
QueryOption_TSDB_Insert.sTableName := 'WindMeasurement';
QueryOption_TSDB_Insert.sDataType := 'WindTurbineData';
QueryOption_TSDB_Insert.pSymbol := ADR(windTurbineData);
QueryOption_TSDB_Insert.cbSymbol := SIZEOF(windTurbineData);
QueryOption_TSDB_Insert.nDataCount := 100;
QueryOption_TSDB_Insert.nStartTimestamp := F_GetSystemTime();
QueryOption_TSDB_Insert.nCycleTime := 10000; // (in 100 ns)
State := E_DbLogState.writing;
Schreiben der Daten:
Dieser Aufruf schreibt die schreibt die Daten in die konfigurierte Datenbank mit der entsprechenden Datenbank-ID. Dieser kann mehrere Zyklen dauern, da es sich um einen asynchronen Prozess handelt. Gegebenenfalls müssen mehrere Speicherarrays verwendet werden, um eine lückenlose Aufzeichnung der Daten zu gewährleisten.
E_DbLogState.writing:
IF fbNoSqlQueryEvt.Execute(dbid, fbNoSQLQueryBuilder_TimeSeriesDB) THEN
IF fbNoSqlQueryEvt.bError THEN
TcResult := fbNoSqlQueryEvt.ipTcResult;
State := E_DbLogState.error;
ELSE
State := E_DbLogState.idle;
END_IF
END_IF
Fehlerbehandlung:
Nutzen Sie den Tc3-Eventlogger für Ihre Fehlerbehandlung
E_DbLogState.error:
IF TcResult.RequestEventText(1033, sErrorMessage, SIZEOF(sErrorMessage)) THEN
TcResult.Send(F_GetSystemTime());
State := E_DbLogState.idle;
bError := TRUE;
END_IF
END_CASE
Download: TF6420_BestPractise_TSDB_Cyclic.zip