Server - Unbuffered Reporting
Dieses Beispiel zeigt die Unbuffered-Reporting-Implementierung in einem TwinCAT IEC61850 Serverprojekt. Die fürs Reporting benötigten UrCBs (unbuffered report control blocks) und Datasets können im TwinCAT Telecontrol Configurator konfiguriert oder aus einer vorhandenen SCL-Datei (z. B. ICD-Datei) importiert werden. Die Instanzen der UrCBs und der Datasets werden während der Codegenerierung automatisch angelegt (normalerweise unterhalb von LLN0). Die Initialwerte der UrCB-Attribute können bereits im TwinCAT Telecontrol Configurator konfiguriert werden.
Download TwinCAT XAE Project (*.zip): Sample26.zip
Das hier beschriebene Beispiel nutzt die Statemachine, welche in dem Kapitel „Allgemeine Server - Projektstruktur“ beschrieben ist. Die States: 0,1,10 und 100 sind identisch zu der dort beschriebenen Statemachine. Andere States wurden für das Beispiel modifiziert oder auch neue States hinzugefügt.
Bei der Implementierung der Unbuffered-Reporting-Funktionalität in einem TwinCAT IEC 61850 Serverprojekt handelt es sich praktisch um eine „Blackbox“. D.h. die komplette Funktionalität ist in einem Baustein bereits gekapselt und die Applikation muss lediglich diesen Baustein aufrufen. Jeder UrCB besitzt einen solchen Baustein-Unterelement mit dem Namen: „Server“. Die SPS-Applikation muss nur die Methode: „Execute“ an dem „Server“-Baustein zyklisch aufrufen. Diese Methode ist für das Versenden der Reports und die Aktualisierung der Statusinformationen in dem dazugehörigen UrCB verantwortlich (siehe Code unten).
Die Konfiguration der Reports kann entweder auf der Serverseite direkt am UrCB oder seitens Clients via Client-Server-Dienste (GetUrCBDataValues und SetUrCBDataValues) vorgenommen werden. Die via Reporting zu kommunizierenden IED-Daten werden mit Hilfe der Datasets festgelegt. In einem Dataset können mehrere Datenreferenzen (Dataset-Member) konfiguriert werden.
Das Beispiel enthält eine einfache Simulation einiger Dataset-Memberwerte. Wenn die Variable „bSimulation“ auf TRUE gesetzt ist, dann werden die Werte alle 2 Sekunden modifiziert. Wie erkennt die UrCB-Server-Implementierung, dass ein Wert modifiziert wurde und ein Report generiert werden soll? Dies geschieht durch den Schreibzugriff auf den Attributwert (z. B.: bValue-, tValue-, iValue-, eValue-, nValue-Eigenschaft). Es wird aber nicht sofort bei jedem einzelnen Schreibzugriff auf einen Attributwert ein neuer Report generiert. Dies wäre unerwünscht. Die „Server“-Implementierung markiert intern die geänderten Dataset-Member als modifiziert. Erst beim Aufruf der „Execute“-Methode wird ein Report generiert und versendet.
Beachten Sie, dass beim Schreiben oder Forcen der Werte im TwinCAT Online-Mode keine Reports generiert werden. Die „Server“-Implementierung kann auf diese Weise nicht erkennen, wenn ein Wert modifiziert wurde. Integrity-Reports und General-Interrogation-Reports werden automatisch entsprechend der Konfiguration am UrCB generiert und versendet. Das Versenden dieser Reports muss in der SPS-Applikation nicht auf eine besondere Weise getriggert werden. Das Versenden dieser Reports übernimmt ebenfalls der zyklische Aufruf der Server-„Execute“-Methode.
Das zeitliche Verhalten der Report-Meldungen kann unabhängig für jede UrCB-Server-Instanz über zwei Eigenschaften „bReplaceBufferedMX“ und „bOverwriteSameCycleChanges“ konfiguriert werden. Weitere Informationen zur Funktionsweise dieser Eigenschaften finden Sie in der Beschreibung des Beispiels: Server-Buffered Reporting.
PROGRAM MAIN
VAR
bSimulation : BOOL:=TRUE; (* Enables/disables simulation of data update/change *)
tSimulation : TIME:=T#2S; (* Cycle time of simulated data update/change *)
fbTimer : TON;
tT : T_UtcTime;
bReplaceBufferedMX : BOOL; (* Toggles bReplaceBufferedMX property of urcb201 *)
bOverwriteSameCycleChanges : BOOL; (* Toggles bOverwriteSameCycleChanges property of urcb301 *)
END_VAR
P_IEC61850MAIN();
fbIED.IEDLD1.LLN0.urcb201.Server.bReplaceBufferedMX:= bReplaceBufferedMX;
fbIED.IEDLD1.LLN0.urcb301.Server.bOverwriteSameCycleChanges:= bOverwriteSameCycleChanges;
fbTimer(IN:=bSimulation, PT:=tSimulation);
IF fbTimer.Q THEN(* Simulate server data update *)
fbTimer(IN:=FALSE);
fbTimer(IN:=bSimulation);
fbIEDServer.GetSystemTime(ipAA:=0, tT=>tT);
(* Simulate "IEDLD1/LLN0.DS1" member value change *)
fbIED.IEDLD1.LEDGGIO1.SPCSO1.t.tValue:= tT;
fbIED.IEDLD1.LEDGGIO1.SPCSO1.stVal.bValue:= NOT fbIED.IEDLD1.LEDGGIO1.SPCSO1.stVal.bValue;
fbIED.IEDLD1.LEDGGIO2.SPCSO1.t.tValue:= tT;
fbIED.IEDLD1.LEDGGIO2.SPCSO1.stVal.bValue:= NOT fbIED.IEDLD1.LEDGGIO2.SPCSO1.stVal.bValue;
fbIED.IEDLD1.LEDGGIO3.SPCSO1.t.tValue:= tT;
fbIED.IEDLD1.LEDGGIO3.SPCSO1.stVal.bValue:= NOT fbIED.IEDLD1.LEDGGIO3.SPCSO1.stVal.bValue;
fbIED.IEDLD1.LEDGGIO4.SPCSO1.t.tValue:= tT;
fbIED.IEDLD1.LEDGGIO4.SPCSO1.stVal.bValue:= NOT fbIED.IEDLD1.LEDGGIO4.SPCSO1.stVal.bValue;
fbIED.IEDLD1.LEDGGIO5.SPCSO1.t.tValue:= tT;
fbIED.IEDLD1.LEDGGIO5.SPCSO1.stVal.bValue:= NOT fbIED.IEDLD1.LEDGGIO5.SPCSO1.stVal.bValue;
fbIED.IEDLD1.LEDGGIO6.SPCSO1.t.tValue:= tT;
fbIED.IEDLD1.LEDGGIO6.SPCSO1.stVal.bValue:= NOT fbIED.IEDLD1.LEDGGIO6.SPCSO1.stVal.bValue;
fbIED.IEDLD1.LEDGGIO7.SPCSO1.t.tValue:= tT;
fbIED.IEDLD1.LEDGGIO7.SPCSO1.stVal.bValue:= NOT fbIED.IEDLD1.LEDGGIO7.SPCSO1.stVal.bValue;
fbIED.IEDLD1.LEDGGIO8.SPCSO1.t.tValue:= tT;
fbIED.IEDLD1.LEDGGIO8.SPCSO1.stVal.bValue:= NOT fbIED.IEDLD1.LEDGGIO8.SPCSO1.stVal.bValue;
(* Simulate "IEDLD1/LLN0.DS2" member value change *)
fbIED.IEDLD1.MMXU1.TotW.t.tValue:= tT;
fbIED.IEDLD1.MMXU1.TotW.mag.f.fValue:= fbIED.IEDLD1.MMXU1.TotW.mag.f.fValue + 0.1;
(* Simulate "IEDLD1/LLN0.DS3" member value change *)
fbIED.IEDLD1.XCBR1.Pos.t.tValue:= tT;
IF fbIED.IEDLD1.XCBR1.Pos.stVal.eValue = E_AcsiDbpos.On THEN
fbIED.IEDLD1.XCBR1.Pos.stVal.eValue:= E_AcsiDbpos.Off;
ELSE
fbIED.IEDLD1.XCBR1.Pos.stVal.eValue:= E_AcsiDbpos.On;
END_IF
fbIED.IEDLD1.XCBR1.Pos.q.OldData:= NOT fbIED.IEDLD1.XCBR1.Pos.q.OldData;
fbIED.IEDLD1.XCBR1.Pos.q.eValidity:= SEL(fbIED.IEDLD1.XCBR1.Pos.q.OldData, E_AcsiQualityValidity.Good, E_AcsiQualityValidity.Questionable);
END_IF
FUNCTION_BLOCK FB_IEDServerSession IMPLEMENTS I_ScsmAbortIndEventSink, I_ScsmAssociateIndEventSink, I_ScsmReleaseIndEventSink
VAR_INPUT
fbConnection : FB_iec61850ConnectionClass := (ipAbortInd:=THIS^, ipAssociateInd:=THIS^, ipReleaseInd:=THIS^);
END_VAR
VAR
_bAbort : BOOL;
_bDisconnect : BOOL;
state : BYTE;
eState : E_AsyncEnvironmentState;
bBusy : BOOL;
bSuccess : BOOL;
ipResult : I_AsyncServiceResultClass;
sLastErrorResult : T_MaxString;
fbAbortReason : FB_ServiceErrorClass := (stError:=SUCCESS_EVENT);
sLastAbortReason : T_MaxString;
bSimulation : BOOL:=TRUE;
tSimulation : TIME:=T#2S;
fbTimer : TON;
tT : T_UtcTime;
END_VAR
fbConnection.Execute();
eState:= fbConnection.eState;
fbTimer(IN:=bSimulation, PT:=tSimulation);
IF fbTimer.Q THEN(* Simulate server data update *)
fbTimer(IN:=FALSE);
fbTimer(IN:=bSimulation);
fbConnection.GetSystemTime(ipAA:=0, tT=>tT);
(* Simulate "IEDLD1/LLN0.DS1" member value change *)
fbIED.IEDLD1.LEDGGIO1.SPCSO1.t.tValue:=tT;
fbIED.IEDLD1.LEDGGIO1.SPCSO1.stVal.bValue:=NOT fbIED.IEDLD1.LEDGGIO1.SPCSO1.stVal.bValue;
fbIED.IEDLD1.LEDGGIO2.SPCSO1.t.tValue:=tT;
fbIED.IEDLD1.LEDGGIO2.SPCSO1.stVal.bValue:=NOT fbIED.IEDLD1.LEDGGIO2.SPCSO1.stVal.bValue;
fbIED.IEDLD1.LEDGGIO3.SPCSO1.t.tValue:=tT;
fbIED.IEDLD1.LEDGGIO3.SPCSO1.stVal.bValue:=NOT fbIED.IEDLD1.LEDGGIO3.SPCSO1.stVal.bValue;
fbIED.IEDLD1.LEDGGIO4.SPCSO1.t.tValue:=tT;
fbIED.IEDLD1.LEDGGIO4.SPCSO1.stVal.bValue:=NOT fbIED.IEDLD1.LEDGGIO4.SPCSO1.stVal.bValue;
fbIED.IEDLD1.LEDGGIO5.SPCSO1.t.tValue:=tT;
fbIED.IEDLD1.LEDGGIO5.SPCSO1.stVal.bValue:=NOT fbIED.IEDLD1.LEDGGIO5.SPCSO1.stVal.bValue;
fbIED.IEDLD1.LEDGGIO6.SPCSO1.t.tValue:=tT;
fbIED.IEDLD1.LEDGGIO6.SPCSO1.stVal.bValue:=NOT fbIED.IEDLD1.LEDGGIO6.SPCSO1.stVal.bValue;
fbIED.IEDLD1.LEDGGIO7.SPCSO1.t.tValue:=tT;
fbIED.IEDLD1.LEDGGIO7.SPCSO1.stVal.bValue:=NOT fbIED.IEDLD1.LEDGGIO7.SPCSO1.stVal.bValue;
fbIED.IEDLD1.LEDGGIO8.SPCSO1.t.tValue:=tT;
fbIED.IEDLD1.LEDGGIO8.SPCSO1.stVal.bValue:=NOT fbIED.IEDLD1.LEDGGIO8.SPCSO1.stVal.bValue;
(* Simulate "IEDLD1/LLN0.DS2" member value change *)
fbIED.IEDLD1.MMXU1.TotW.t.tValue:=tT;
fbIED.IEDLD1.MMXU1.TotW.mag.f.fValue:=fbIED.IEDLD1.MMXU1.TotW.mag.f.fValue + 0.1;
(* Simulate "IEDLD1/LLN0.DS3" member value change *)
fbIED.IEDLD1.XCBR1.Pos.t.tValue:=tT;
IF fbIED.IEDLD1.XCBR1.Pos.stVal.eValue = E_AcsiDbpos.On THEN
fbIED.IEDLD1.XCBR1.Pos.stVal.eValue:= E_AcsiDbpos.Off;
ELSE
fbIED.IEDLD1.XCBR1.Pos.stVal.eValue:= E_AcsiDbpos.On;
END_IF
fbIED.IEDLD1.XCBR1.Pos.q.OldData:= NOT fbIED.IEDLD1.XCBR1.Pos.q.OldData;
END_IF
(* Execute server control block implementations *)
fbIED.IEDLD1.LLN0.urcb101.Server.Execute();
fbIED.IEDLD1.LLN0.urcb201.Server.Execute();
fbIED.IEDLD1.LLN0.urcb301.Server.Execute();
fbIED.IEDLD1.LLN0.brcb101.Server.Execute();
fbIED.IEDLD1.LLN0.brcb201.Server.Execute();
fbIED.IEDLD1.LLN0.brcb301.Server.Execute();
CASE state OF
0: (* Initial state *)
IF _bAbort THEN (* Abort connection => execute AbortReq() command *)
_bAbort:= FALSE;
bSuccess:= fbConnection.AbortReq(ipReason:=fbAbortReason, ipSink:=0, ipResult=>ipResult);
state:= SEL(bSuccess, 100, 1);
ELSIF eState = E_AsyncEnvironmentState.Established AND _bDisconnect THEN (* Close/release connection => execute ReleaseReq() command *)
_bDisconnect:= FALSE;
bSuccess:= fbConnection.ReleaseReq(ipSink:=0, ipResult=>ipResult);
state:= SEL(bSuccess, 100, 1);
ELSIF eState = E_AsyncEnvironmentState.Established THEN (* Connection established => exchange IED data *)
state:= 10;
END_IF
_bDisconnect:= FALSE;
1: (* Wait for AbortReq() or ReleaseReq() command completion *)
IF ipResult <> 0 THEN
ipResult.Execute();
IF NOT (bBusy:=ipResult.IsBusy()) THEN
state:= SEL(ipResult.IsCompleted(), 100(* failed or aborted *), 0(* succeeded *));
END_IF
END_IF
10: (* connection established *)
state:= 0;
100: (* Error state *)
state:= 0;
IF ipResult <> 0 THEN
sLastErrorResult:= ipResult.Dump();
END_IF
END_CASE