Server - Buffered Reporting
This sample shows the buffered reporting implementation in a TwinCAT IEC61850 server project. The BRCBs (buffered report control blocks) and datasets required for reporting can be configured in the TwinCAT Telecontrol Configurator or imported from an existing SCL file (e.g. ICD file). The instances of the BRCBs and the Datasets are created automatically during code generation (usually below LLN0). The initial values of the BRCB attributes can already be configured in the TwinCAT Telecontrol Configurator.
Download TwinCAT XAE Project (*.zip): Sample28.zip
The example described here uses the state machine that is described in the "General Server project structure" chapter. The States 0, 1, 10 and 100 are identical to the state machine described there. Other states were modified for the example or new states were also added.
The implementation of the buffered reporting functionality in a TwinCAT IEC 61850 server project is practically a "black box". This means that the complete functionality is already encapsulated in a function block and the application only has to call this function block. Each BRCB has a function block subelement with the name: "Server". The PLC application only has to call the method: "Execute" at the "Server" block cyclically. This method is responsible for sending the reports and updating the status information in the associated BRCB (see code below).
The reports can be configured either on the server side directly on the BRCB or on the client side via client-server services (GetBRCBDataValues and SetBRCBDataValues). The IED data to be communicated via reporting are defined with the help of the datasets. Multiple data references (Dataset Members) can be configured in a dataset.
Reports that cannot be sent are stored in the buffer. When the maximum number of entries in the buffer is reached, the oldest report is deleted. When declaring a BRCB, the maximum number of entries can be set via the property "nMaxBufferEntries". In the sample this can be seen in the declaration of "brcb101":
brcb101: FB_ScsmBrCBImplClass := (Server:=(nMaxBufferEntries:=15), RptID:=(sValue:='IEDLD1/LLN0.brcb101'), DatSet:=(sValue:='IEDLD1/LLN0.DS1'), ConfRev:=(nValue:=1), OptFlds:=(SequenceNumber:=TRUE, ReportTimeStamp:=TRUE, ReasonForInclusion:=TRUE, DataSetName:=TRUE, DataReference:=TRUE, BufferOverflow:=TRUE, EntryID:=TRUE, ConfRevision:=TRUE), TrgOps:=(DataChange:=TRUE, QualityChange:=TRUE, DataUpdate:=TRUE, Integrity:=TRUE, GeneralInterrogation:=TRUE), IntgPd:=(nValue:=5000), bLinkResult:=THIS^.AddBufferedReportControlBlockToContainer(ipBufferedReportControlBlock:=brcb101));
The sample contains a simple simulation of some dataset member values. If the variable "bSimulation" is set to TRUE, then the values are modified every 2 seconds.
How does the BRCB Server implementation recognize that a value has been modified and a report is to be generated? This is done through write access to the attribute value (for example: bValue, tValue, iValue, eValue, nValue properties). However, a new report is not generated immediately for each individual write access to an attribute value. This would be undesirable. The "Server" implementation marks the modified dataset members internally as modified. A report is not generated and sent until the "Execute" method is called. The data sent in the report correspond to the data at the time of the dataset member's value change. The values are buffered when changed and coded into a report and sent after the time configured in the "BufTm" attribute has elapsed. If a new write access occurs within the buffer time, the buffered data are overwritten. This process is shown in the following figure:

A different behavior for buffering is defined in IEC 61850-7-2 for data of the functional group "ST". If the value is written again before "BufTm" expires, the buffered data are immediately coded to a report and sent. The new data are then written into the buffer and the monitoring of the buffer time is restarted. The following graphic shows the described behavior:

For data of the functional group "MX" it can be selected whether the BRCB should handle them separately, like data of the functional group "ST". This can be set via the property "bReplaceBufferedMX". In the sample, the special treatment is enabled at "brcb201":
brcb201: FB_ScsmBrCBImplClass := (Server:=(bReplaceBufferedMX:=TRUE), RptID:=(sValue:='IEDLD1/LLN0.brcb201'), DatSet:=(sValue:='IEDLD1/LLN0.DS2'), ConfRev:=(nValue:=1), OptFlds:=(SequenceNumber:=TRUE, ReportTimeStamp:=TRUE, ReasonForInclusion:=TRUE, DataSetName:=TRUE, DataReference:=TRUE, BufferOverflow:=TRUE, EntryID:=TRUE, ConfRevision:=TRUE), BufTm:=(nValue:=5000), TrgOps:=(DataChange:=TRUE, QualityChange:=TRUE, DataUpdate:=TRUE, Integrity:=TRUE, GeneralInterrogation:=TRUE), IntgPd:=(nValue:=5000), bLinkResult:=THIS^.AddBufferedReportControlBlockToContainer(ipBufferedReportControlBlock:=brcb201));
How "MX" data are handled depending on the setting of "bReplaceBufferedMX" is shown in the following figure:

Sending reports immediately when a value of the assigned dataset is changed repeatedly can result in more reports being generated than desired. This is the case, for example, when a data object of the functional group "ST" is referenced in a dataset and then several attributes of the data object are written individually. Since each change in the value of a data attribute within a data object also represents a change in the value of the data object, multiple reports are generated. If the property "bOverwriteSameCycleChanges" is set to TRUE, multiple value changes within a PLC cycle are treated as a single value change. The recognition of which values were written in the current cycle is reset by calling the "Execute" method. A value change before and after the call are therefore interpreted as not being a change in the same cycle.
![]() | Please note that no reports are generated when writing or forcing values in TwinCAT online mode. In this way, the "Server" implementation cannot detect when a value has been modified. |
Integrity reports and General Interrogation reports are automatically generated and sent in accordance with the configuration of the BRCB. The sending of these reports does not have to be triggered in a special way in the PLC application. It is also taken from the cyclic call to the server "Execute" method.
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 brcb201 *)
bOverwriteSameCycleChanges : BOOL; (* Toggles bOverwriteSameCycleChanges property of brcb301 *)
END_VAR
P_IEC61850MAIN();
fbIED.IEDLD1.LLN0.brcb201.Server.bReplaceBufferedMX:= bReplaceBufferedMX;
fbIED.IEDLD1.LLN0.brcb301.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; (* Rising edge activates AbortReq() command execution *)
_bDisconnect : BOOL; (* Rising edge activates ReleaseReq() command execution *)
state : BYTE;
eState : E_AsyncEnvironmentState; (* Environment state *)
bBusy : BOOL; (* If TRUE => command execution is busy *)
bSuccess : BOOL;
ipResult : I_AsyncServiceResultClass;
sLastErrorResult : T_MaxString;
fbAbortReason : FB_ServiceErrorClass := (stError:=SUCCESS_EVENT);
sLastAbortReason : T_MaxString;
END_VAR
fbConnection.Execute();
eState:= fbConnection.eState;
(* Execute server control block implementations *)
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