Variablen mit dem TwinCAT3 Database Server in eine MicrosoftSQL Datenbank schreiben

Perold

Level-1
Beiträge
40
Reaktionspunkte
1
Zuviel Werbung?
-> Hier kostenlos registrieren
Guten Tag,

ich habe eine Spritzgussmaschine mit einem Beckhoff IPC (CX5120) darin um dort Spritzgusszyklen zu erfassen.
Pro Zyklus beschreibe ich folgende Variablen einer Struktur:
maschine: STRING(80); //maschinenname
startzeit: DT; //wann startete der zyklus
endzeit: DT; //wann endete der zyklus
channel_name: LREAL; //3 für zykluszeit
dauer: DINT; //wie viele millisekunden dauerte der zyklus

Diese Variablen schreibe ich nun nach jedem Zyklus mit dem Funktionsbaustein FB_PLCDBCmd in eine MicrosoftSQL Datenbank.
Das klappt soweit auch ganz gut und meine Datenbank füllt sich mit den entsprechenden Werten.

Nun zu meinem Problem. Die Maschine macht z.B. alle 8 Sekunden einen Zyklus und ich kann beobachten, dass die Variable bBusy des Funktionsbaustein FB_PLCDBCmd für 30 Sekunden TRUE ist.
Der Baustein ist also 30 Sekunden damit beschäftigt einen Datensatz wegzuschreiben und die Zyklen die in diesen 30 Sekunden passieren landen nicht in der Datenbank.

Ich muss also eine Art Puffer schaffen, damit wirklich jeder Zyklus in die Datenbank geschrieben wird, oder aber ich benutze den Funktionsbaustein irgendwie falsch.

Mein Code für den Funktionsbaustein der Datenbankverbindung sieht folgendermaßen aus:
Code:
FUNCTION_BLOCK FB_DatabaseConnection2
VAR_INPUT
    stWebinar        : ST_Webinar;            //als Input Variable oder unter die normalen?
    nState            : INT;
END_VAR
VAR_OUTPUT
END_VAR
VAR
    fbPLCDBCmd        : FB_PLCDBCmd(sNetID := '', tTimeout := T#5S);
    sCmd            : STRING (1000);                                                 
    aPara            : ARRAY[0..5] OF ST_ExpParameter;
    ipTcResult        : I_TcResultEvent;
    sSourcePath        : STRING(255);
    sEventClass        : WSTRING(255);
    eSeverity        : E_Severity;
    nEventID        : UDINT;
    sEventText        : WSTRING(255);
END_VAR
Code:
// set Parameter configuration
aPara[0].sParaName := 'index';    aPara[0].eParaType := E_ExpParameterType.Int32;      aPara[0].nParaSize := 4;
aPara[1].sParaName := 'maschine';   aPara[1].eParaType := E_ExpParameterType.STRING_;   aPara[1].nParaSize := 80;
aPara[2].sParaName := 'startzeit';  aPara[2].eParaType := E_ExpParameterType.DateTime;   aPara[2].nParaSize := 4;
aPara[3].sParaName := 'endzeit';   aPara[3].eParaType := E_ExpParameterType.DateTime;   aPara[3].nParaSize := 4;
aPara[4].sParaName := 'channel_name';       aPara[4].eParaType := E_ExpParameterType.Double64;   aPara[4].nParaSize := 8;
aPara[5].sParaName := 'dauer';     aPara[5].eParaType := E_ExpParameterType.Int32;      aPara[5].nParaSize := 4;


// set command
sCmd := 'INSERT INTO [dbo].[Zyklus_Test] ( [maschine], [startzeit], [endzeit], [channel_name], [dauer]) VALUES ( {maschine}, {startzeit}, {endzeit}, {channel_name}, {dauer})';


    
CASE nState OF
    0:    //Idle
        ;
    
    1:    //FB_PLCDBCmd
    fbPLCDBCmd.Execute(
        hDBID:= 1, 
        pExpression:= ADR(sCmd), 
        cbExpression:= SIZEOF(sCmd), 
        pData:= ADR(stWebinar), 
        cbData:= SIZEOF(stWebinar), 
        pParameter:= ADR(aPara), 
        cbParameter:= SIZEOF(aPara));
        
        IF NOT fbPLCDBCmd.bBusy THEN
            ipTcResult := fbPLCDBCmd.ipTcResultEvent;
            IF NOT fbPLCDBCmd.bError THEN
                nState := 0;
            ELSE
                nState := 200;
            END_IF
        END_IF


    200:    //Event State
        sSourcePath := ipTcResult.SourcePath;
        sEventClass := ipTcResult.EventClassDisplayName;
        eSeverity := ipTcResult.Severity;
        nEventID := ipTcResult.EventId;
        sEventText := ipTcResult.Text;
        nState := 0;
            
END_CASE


Diesen Funktionsbaustein rufe ich aus meinem Hauptprogramm dann pro Zyklus folgendermaßen auf. Zudem übergebe ich die Struktur stWebinar mit meinen Variablen darin und setze den nState auf 1, damit der Baustein ausgeführt wird.
Code:
fbDatabaseConnection2(stWebinar:=stWebinar, nState:= 1);


Hat jemand einen Hinweis für mich, wie ich es schaffe alle 8 Sekunden einen Wert in meine Microsoft SQL Datenbank zu schreiben? Falls ich noch wichtige Angaben vergessen haben sollte bitte melden, liefere ich dann sofort nach.

Gruß
 
Zuletzt bearbeitet:
Hi Perold,

30 Sekunden um einen Insert auszuführen erscheint mir als viel zu lange... ist die Datenbank lokal auf dem CX installiert?

Du fragst das bBusy vom fbPLCDBCmd ab, im Beispiel aus dem InfoSys wird aber der Rückgabewert der Execute-Methode verwendet anstatt dem bBusy-Flag.
Siehe: https://infosys.beckhoff.com/content/1031/tf6420_tc3_database_server/4802034187.html
Probiers mal aus, wirds dann schneller?

Gruß, Neals
 
Zuviel Werbung?
-> Hier kostenlos registrieren
30 Sekunden um einen Insert auszuführen erscheint mir als viel zu lange..
*ACK*

Könnte auch daran liegen, dass die Datenbankverbindung bei jedem Execute-Aufruf geöffnet und anschliessend wieder geschlossen wird.
Wenn Du das vermeiden willst, musst Du wohl den SQL Expert Mode bemühen. Oder die TC2 Database Bibliothek verwenden. Für meinen Geschmack ist die wesentlich einfacher zu handhaben.
 
Vielen Dank für die Anmerkungen.
Der TwinCAT3 DatabaseServer läuft auf meinem Beckhoff IPC und die MicrosoftSQL Datenbank liegt auf unserem Server im Netzwerk.
Bildlich gesprochen also so:
Bildschirmfoto 2018-10-16 um 08.22.40.jpg


Hi Perold,
Du fragst das bBusy vom fbPLCDBCmd ab, im Beispiel aus dem InfoSys wird aber der Rückgabewert der Execute-Methode verwendet anstatt dem bBusy-Flag.
Siehe: https://infosys.beckhoff.com/content/1031/tf6420_tc3_database_server/4802034187.html
Probiers mal aus, wirds dann schneller?

Ich habe es mal entsprechend umgebaut, sofern ich es richtig verstanden habe.
Code:
// set Parameter configurationaPara[0].sParaName := 'index';    aPara[0].eParaType := E_ExpParameterType.Int32;      aPara[0].nParaSize := 4;
aPara[1].sParaName := 'maschine';   aPara[1].eParaType := E_ExpParameterType.STRING_;   aPara[1].nParaSize := 80;
aPara[2].sParaName := 'startzeit';  aPara[2].eParaType := E_ExpParameterType.DateTime;   aPara[2].nParaSize := 4;
aPara[3].sParaName := 'endzeit';   aPara[3].eParaType := E_ExpParameterType.DateTime;   aPara[3].nParaSize := 4;
aPara[4].sParaName := 'channel_name';       aPara[4].eParaType := E_ExpParameterType.Double64;   aPara[4].nParaSize := 8;
aPara[5].sParaName := 'dauer';     aPara[5].eParaType := E_ExpParameterType.Int32;      aPara[5].nParaSize := 4;


// set command
sCmd := 'INSERT INTO [dbo].[Zyklus_Test] ( [maschine], [startzeit], [endzeit], [channel_name], [dauer]) VALUES ( {maschine}, {startzeit}, {endzeit}, {channel_name}, {dauer})';


    
CASE nState OF
    0:    //Idle
        ;
    
    1:    //FB_PLCDBCmd
    IF fbPLCDBCmd.Execute(
        hDBID:= 1, 
        pExpression:= ADR(sCmd), 
        cbExpression:= SIZEOF(sCmd), 
        pData:= ADR(stWebinar), 
        cbData:= SIZEOF(stWebinar), 
        pParameter:= ADR(aPara), 
        cbParameter:= SIZEOF(aPara))
    
    THEN
        IF fbPLCDBCmd.bError THEN
            ipTcResult := fbPLCDBCmd.ipTcResultEvent;
            nState := 200;
        ELSE
            nState := 0;
        END_IF
    END_IF


    200:    //Event State
        sSourcePath := ipTcResult.SourcePath;
        sEventClass := ipTcResult.EventClassDisplayName;
        eSeverity := ipTcResult.Severity;
        nEventID := ipTcResult.EventId;
        sEventText := ipTcResult.Text;
        nState := 0;
            
END_CASE

Ergebnis bleibt leider das Gleiche, normalerweise sollte ich ca. alle 8 Sekunden einen Eintrag in der Datenbank haben.:
Bildschirmfoto 2018-10-16 um 08.44.04.jpg


Ich werde mich mal an dem SQL Expert Mode versuchen, leider gibt es dazu noch kein Beckhoff Webinar, diese finde ich immer ganz hilfreich. Falls ich da scheitern sollte probiere ich mal den Tipp von StructuredTrash mit der Tc2 Database Bibliothek aus.
Rückmeldung folgt.
 
Zudem habe ich noch bemerkt, dass ein zeitnahes beschreiben meiner MicrosoftSQL Datenbank durch 2 verschiedenen Instanzen des PLCDBCmd Bausteins durchaus möglich ist (z.B. im Abstand von einer Sekunde). Da ich noch eine weitere Spritzgussmaschine habe, die ebenfalls auf gleiche Art und Weise in die Datenbank schreibt.
Bildschirmfoto 2018-10-16 um 08.59.29.jpg
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Ich habe mich nun mal an den SQL Expert Mode gewagt. Leider bekomme ich einen Error. Der Übersichtlichkeit halber habe ich es in eine State Maschine gepackt, die ich dann durch händisches verändern der Variable nState schrittweise durchlaufen kann.
Den SQL Befehl, der ja die Variable sCmd ist, habe ich zwecks Fehlersuche erst einmal festgelegt und arbeite nicht mit Platzhaltern.
State 1 wird erfolgreich ausgeführt.
State 2 liefert auch keinen Error laut EventInterface.
State 3 liefert mir dann über das EventInterface folgenden Fehler:
Bildschirmfoto 2018-10-16 um 12.30.12.jpg

Irgendwo habe ich also noch einen Fehler drin...

Code:
FUNCTION_BLOCK FB_DatabaseConnection3
VAR_INPUT
    //stWebinar        : ST_Webinar;
    nState            : INT;
END_VAR
VAR_OUTPUT
END_VAR
VAR
    fbSqlDatabase    : FB_SQLDatabase(sNetID := '', tTimeout := T#5S);
    fbSqlCommand    : FB_SQLCommand(sNetID := '', tTimeout := T#5S);
    sCmd            : STRING (1000);
    ipTcResult        : I_TcResultEvent;
    sSourcePath        : STRING(255);
    sEventClass        : WSTRING(255);
    eSeverity        : E_Severity;
    nEventID        : UDINT;
    sEventText        : WSTRING(255);
END_VAR

Code:
CASE nState OF    0:    // Idle
        
    1:    // open connection
        IF fbSqlDatabase.Connect(1) THEN
            IF fbSqlDatabase.bError THEN
                nState := 255; 
            ELSE
                nState := 0;
            END_IF
        END_IF
        
    2:    // create a command reference
        IF fbSqlDatabase.CreateCmd(ADR(fbSqlCommand)) THEN
            IF fbSqlDatabase.bError THEN
                nState := 255; 
            ELSE
                ipTcResult := fbSqlDatabase.ipTcResultEvent;
                nState := 200; 
            END_IF
        END_IF
        
    3:    // generate and call sql command
        sCmd := 'INSERT INTO [dbo].[Zyklus_Test] ( [maschine], [startzeit], [endzeit], [channel_name], [dauer]) VALUES ( $'DH01$', $'2018-10-16 09:49:02$', $'2018-10-16 09:49:02$', 0.0, 0)';
        
        // call sql command
        IF fbSQLCommand.Execute(ADR(sCmd), SIZEOF(sCmd)) THEN
            IF fbSQLCommand.bError THEN
                ipTcResult := fbSQLCommand.ipTcResultEvent;
                nState := 200; 
            ELSE
                nState := 0; 
            END_IF
        END_IF
        
    4:    // disconnect from database
        IF fbSqlDatabase.Disconnect() THEN
            IF fbSqlDatabase.bError THEN
                nState := 255;
            ELSE
                nState := 0;
            END_IF
        END_IF
        
    200:    // Event State
            sSourcePath := ipTcResult.SourcePath;
            sEventClass := ipTcResult.EventClassDisplayName;
            eSeverity := ipTcResult.Severity;
            nEventID := ipTcResult.EventId;
            sEventText := ipTcResult.Text;
            nState := 0;
            
    255:    // Error State
    
END_CASE

Sieht jemand etwas, was ich nicht sehe ;)?
 
Zuletzt bearbeitet:
Hab mich mal schnell eingelesen. fbSQLCommand muss ein POINTER TO FB_SQLCommand sein. Beim Aufruf der Methode musst Du ihn dann dereferenzieren, also fbSQLCommand^.Execute(...

Edit: Alles gelogen wegen zu oberflächlichem Einlesen.
 
Zuletzt bearbeitet:
Hallo StructuredTrash!

Ich habe nun gefunden, dass das ^ Inhaltsoperator heißt, aber verstanden habe ich die Geschichte noch nicht, gib mir etwas Zeit :p. Ich finde in meinem Code auch nicht die Stelle, in die ich das ^ einsetzen muss.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Code:
VAR
    fbSqlDatabase    : FB_SQLDatabase(sNetID := '', tTimeout := T#5S);

    fbSqlCommand    : POINTER TO FB_SQLCommand;   // Hier einen Pointer auf den FB deklarieren

    sCmd            : STRING (1000);
    ipTcResult        : I_TcResultEvent;
    sSourcePath        : STRING(255);
    sEventClass        : WSTRING(255);
    eSeverity        : E_Severity;
    nEventID        : UDINT;
    sEventText        : WSTRING(255);
END_VAR
Code:
    3:    // generate and call sql command
        sCmd := 'INSERT INTO [dbo].[Zyklus_Test] ( [maschine], [startzeit], [endzeit], [channel_name], [dauer]) VALUES ( $'DH01$', $'2018-10-16 09:49:02$', $'2018-10-16 09:49:02$', 0.0, 0)';
        
        // call sql command

       // Sicherheitshalber den Command-FB initialisieren. Ich weiss nicht, ob das automatisch gemacht wird.
       // Für alle folgenden Zugriffe auf den FB muss sein Pointer mit dem ^ dereferenziert werden.
      fbSQLCommand^.sNetID:='';
      fbSQLCommand^.tTimeOut:=t#5s;

      IF fbSQLCommand^.Execute(ADR(sCmd), SIZEOF(sCmd)) THEN 

            IF fbSQLCommand^.bError THEN

Edit: Alles gelogen wegen zu oberflächlichem Einlesen.
 
Zuletzt bearbeitet:
Mist, habe anscheinend nicht weit genug gelesen. Vergiss, was ich in #7/#9 geschrieben habe.
Ich habe jetzt gerade gesehen, dass der CreateCmd-Methode doch die Adresse eines schon bestehenden statischen Command-FBs übergeben werden soll.
Dann kann ich zur Behebung des Fehlers leider auch nichts beitragen.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Angesichts von 30 s für einen Insert würde ich nicht so schnell aufgeben. Vielleicht versuchst Du es doch einmal mit der TC2 Database Lib, auch weil der in dem anderen Thread genannte FB ebenfalls daraus stammt. Hier mal ein Serviervorschlag. Nicht getestet, aber aus einer funktionierenden Anwendung heraus aufgebaut. Sollte von der Funktionalität etwa Deinem letzten Test-FB entsprechen.
Code:
FUNCTION_BLOCK FB_DataBaseConnectionTC2
VAR_INPUT
    nState:INT;
END_VAR
VAR_OUTPUT
END_VAR
VAR
    fbDBOpen:FB_DBConnectionOpen;    // Open connection
    fbDBClose:FB_DBConnectionClose;    // Close connection
    fbDBInsert:FB_DBRecordInsert_EX;    // Insert record
    sCmd:STRING[1000];
    fbTimr:TON;    // Last DB command exec time output in ET
END_VAR

// Code
CASE nState OF
    0:        // Wait for DB command

    10:    // DB open start
        fbDBOpen(
            sNetID:='',
            hDBID:=1,
            bExecute:=TRUE,
            tTimeOut:=T#50S);
        nState:=11;
    11:    // DB open wait
        fbDBOpen();
        IF NOT fbDBOpen.bBusy THEN
            fbDBOpen(
                bExecute:=FALSE);
            IF NOT fbDBOpen.bError THEN
                nState:=0;
            ELSE
                nState:=12;
            END_IF
        END_IF
    12:    // DB open error

    20:    // DB insert record start
        sCmd:='INSERT INTO [dbo].[Zyklus_Test] ( [maschine], [startzeit], [endzeit], [channel_name], [dauer]) VALUES ( $'DH01$', $'2018-10-16 09:49:02$', $'2018-10-16 09:49:02$', 0.0, 0)';
        fbDBInsert(
            sNetID:='',
            hDBID:=1,
            cbCmdSize:=SIZEOF(sCmd),
            pCmdAddr:=ADR(sCmd),
            bExecute:=TRUE,
            tTimeOut:=T#50S);
        nState:=21;
    21:    // DB insert record wait
        fbDBInsert();
        IF NOT fbDBInsert.bBusy THEN
            fbDBInsert(
                bExecute:=FALSE);
            IF NOT fbDBInsert.bError THEN
                nState:=0;
            ELSE
                nState:=22;
            END_IF
        END_IF
    22:    // DB insert record error
    
    30:    // DB close start
        fbDBClose(
            sNetID:='',
            hDBID:=1,
            bExecute:=TRUE,
            tTimeOut:=T#50S);
        nState:=31;
    31:    // DB close wait
        fbDBClose();
        IF NOT fbDBClose.bBusy THEN
            fbDBClose(
                bExecute:=FALSE);
            IF NOT fbDBClose.bError THEN
                nState:=0;
            ELSE
                nState:=32;
            END_IF
        END_IF
    32:    // DB close error
    
END_CASE

// Exec time output
fbTimr(
    IN:=fbDBOpen.bExecute OR fbDBInsert.bExecute OR fbDBClose.bExecute,
    PT:=T#50S);
 
Moin StructuredTrash!

Ich bin heute morgen etwas weiter gekommen. Deine Lösung musste ich ja quasi nur kopieren und noch die TC2 Database Library unter References hinzufügen. Leider bekam ich einen Fehler in nState 20, also beim fbDBInsert. Da kam bei mir die Vermutung auf, dass etwas mit meinem SQL Kommando nicht stimmt. Also habe ich über den SQL Query Editor versucht das SQL Kommando
Code:
'INSERT INTO [dbo].[Zyklus_Test] ( [maschine], [startzeit], [endzeit], [channel_name], [dauer]) VALUES ( $'DH01$', $'2018-10-16 09:49:02$', $'2018-10-16 09:49:02$', 0.0, 0)'
an den Datenbankserver zu schicken und promt eine Fehlermeldung bekommen. Danach habe ich das Kommando mal weiter abgespeckt, indem ich die Zeitstempel entfernt habe und das SQL Kommando wurde erfolgreich in die Datenbank geschrieben.
Code:
'INSERT INTO [dbo].[Zyklus_Test] ( [maschine], [startzeit], [endzeit], [channel_name], [dauer]) VALUES ( $'DH01$', $'$', $'$', 0.0, 0)'

Nun funktioniert der Datenbankzugriff, sowohl über die TC2 Database Library und über den SQL Expert Mode.

Werde nun noch weiter ausprobieren, ob ich nach wie vor noch das Zeitproblem beim Datenbankzugriff habe und mir Daten verloren gehen.


Dankeschön und Rückmeldung folgt.
 
Zuletzt bearbeitet:
Ach ja, Zeitstempel in MS-SQL, darüber bin ich doch auch schon mal gestolpert. Tausch mal Datum und Monat, also '2018-16-10 09:49:02', dann wird es wohl gehen.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo,

du hast recht wenn ich Monat und Tag austausche funktioniert es...Schwierig zu finden dieser Fehler, da es ja in den ersten 12 Tagen des Monats funktioniert.
Ich habe nun mal probiert aus dem SQL Query Editor mehrere Insert Befehle kurz hintereinander auszuführen und habe dort keine Probleme, alle Inserts landen in der Datenbank. Ich teste weiter.

Gruß
 
So, heute Nacht kam mir die Lösung zu meinem Problem und nun funktioniert das beschreiben der Microsoft SQL Datenbank mit dem SQL Expert Mode in unter 1 Sekunde.

Das Problem war, dass ich den Datenbankzugriff als FunctionBlock aufgerufen habe und somit die StateMaschine immer stehe geblieben ist.
Nun habe ich den Code mal Testweise als Programm geschrieben und seitdem läuft es. Vielen Dank StructuredTrash für deine Hilfe, hat mich auf jeden Fall weiter gebracht!
 
Hallo, ich melde mich nocheinmal. Und zwar bin ich wieder auf ein Problem gestoßen.

Ich benutze ja den SQL Expert Mode um in meine Microsoft SQL Datenbank zu schreiben. Momentan übergebe ich mit der fbSQLCommand.Execute Methode folgendes SQL Kommando an den DatabaseServer.
Code:
'INSERT INTO [dbo].[Zyklus_Test] ( [maschine], [startzeit], [endzeit], [channel_name], [dauer]) VALUES ( $'DH05$', CURRENT_TIMESTAMP, $'$', 3, 0)';

Dieses Kommando ist also starr (bis auf das Keyword CURRENT_TIMESTAMP, welches ja dann vom DatabaseServer mit dem aktuellen Zeitstempel besetzt wird). Gibt es nun die Möglichkeit wie im PLC Expert Mode mit Platzhaltern zu arbeiten, um meine Variablen in das SQL Kommando zu bringen?
Ist CONCAT da das richtige Stichwort?

Vielleicht hat ja jemand einen Hinweis für mich.

edit: Hier habe ich eine ähnliche Fragestellung gefunden:
https://www.sps-forum.de/codesys-un...ql-befehl-mit-variablen.html?highlight=concat
 
Zuletzt bearbeitet:
Zurück
Oben