General Client - Project structure
Each of the client sample projects follows a general structure, which this document aims to explain. This structure includes the nesting of the function blocks in the Solution Explorer and the structure of the state machine in the client function block, which establishes the connection to the server and implements the data exchange. The basic structure of the TwinCAT IEC 61850 sample projects is based on the PLC project structure automatically generated by the TwinCAT Telecontrol Configurator. The TwinCAT Telecontrol Configurator in turn uses the "TwinCAT XAE Project (XML format)" as a template when generating the solution. The data models communicated in the samples reflect different IEC 61850 servers. They differ from sample to sample and are enclosed with the source code as icd files. The ICD files can also be used by third-party software for the simulation of a server.
First of all, a brief examination of the structure of a TwinCAT IEC 61850 PLC client project:

The generated solution name (unless specified otherwise) corresponds to the TwinCAT Telecontrol Configurator project name. The automatically generated TwinCAT PLC project name (unless specified otherwise) on the other hand has the following structure: "[Project name]_[IEDName]_Client".
As standard, every sample project has a "DUTs", a "GVLs" and a "POUs" folder. A Global Variable List (GVL) with the name "TcTelecontrol" is stored in the "GVLs" folder. The following function blocks are instantiated and initialized in this Global Variable List (see source code below):
- A Client function block instance of the type: FB_[IEDName]Client (connection management and data exchange).
- An IED data model function block instance of the type: FB_IED_[IEDName].
- Optional (depending on the GOOSE Subscriber configuration): one or more function block instances of the type: "FB_[IEDName]Gse" for GOOSE communication and GSE management.
In addition, the code version used during code generation and the version of the TwinCAT Telecontrol Configurator used are also stored there.
Namespace: TcTelecontrol
Type: Global Variable List (GVL)
VAR_GLOBAL
ipCreator : I_AcsiCodeCreatorClass := GVL_AcsiVars.Creator.SetCodeRev(codeRev:=2).SetGuiVer(major:=1, minor:=0, build:=93, revision:=10);
fb[IEDName] : FB_IED_[IEDName];
fb[IEDName]Client : FB_[IEDName]Client := (fbConnection:=(ipIED:=fb[IEDName],settings:=(sRemoteHost:='127.0.0.1')));
fb[IEDName]Gse : FB_[IEDName]Gse := (fbAdapter:=(ipIED:=fb[IEDName], settings:=(sMulticastAddr:='01-0C-CD-01-00-00', eDispatchMode:=E_GseDispatchMode.NonPromiscuous)));
END_VAR
In the "POUs" folder there is a further folder: "[IEDName]", which contains the entire hierarchical structure of the IED data model as function blocks. This folder also contains the IED function block that is instanced in the global variable list and has already been mentioned. The Client function block: "FB_[IEDName]Client", which implements the establishment of the connection and the data exchange with an IEC 61850 Server, is located on the same level. The TwinCAT PLC project contains a "MAIN" program as standard. This is called cyclically by a TwinCAT task and in turn calls the program "P_IEC61850MAIN". The program "P_IEC61850MAIN" encapsulates the call of the client function block and of the optional GSE function block, separates the IEC 61850 communication from the remainder of the PLC machine program and helps, for example, with the implementation of further clients.
PROGRAM MAIN
VAR
END_VAR
P_IEC61850MAIN();
PROGRAM P_IEC61850MAIN
VAR
END_VAR
fb[IEDName]Client();
fb[IEDName]Gse();
In the FB_[IEDName]Client function block, there is a state machine, the basic states of which are used in every client sample. These states are graphically illustrated in the following diagram:

State 0 (Init state): the state machine is in this state as soon as the PLC program has been started. Commands for the management of the client/server connection are handled here (and in State 1). This is mainly controlled via four Boolean variables. When set, these variables then activate the corresponding commands (in this case these are once-only method calls at the client function block).
- _bAbort: calls the method "AbortReq", which activates the command to abort the client connection to the server.
- _bConnect: calls the method "AssociateReq", which activates the command to establish a new client connection to the server.
- _bDisconnect: calls the method "ReleaseReq", which activates the command for the controlled release of an existing client connection to the server.
- _bReconnect: also calls the method "AssociateReq" if the Client connection to the Server has been aborted/disconnected, but is to be restored automatically.
The methods listed above, which are called once only in this state, require longer than one PLC cycle for their execution. For this reason the state machine switches to a wait state (State 1), in which the termination of the activated command is awaited.
If the client connection to the server has already been established beforehand, the state machine will switch to the data transmission state (State 10).
State 1 (Wait State): in this state, the client waits until the command processing for the management of the client/server connection is no longer busy. As long as the connection is established, terminated or aborted, the state machine is in state 1. As soon as the command has been successfully processed, the state machine is returned to state 0 (Init state).
State 10 (Data exchange): if the state machine is in this state, then the client connection to the server has already been successfully established. The client is ready for the data transmission to the server. During the data transmission, commands are activated for the transmission or reception of the data.
In this state the sample projects differ from one another. Different methods or auxiliary function blocks are called here, depending on the desired functionality or logic in the application. In addition, the client function block can be extended by further states.
All method calls that activate commands for data transmission require several PLC cycles for successful execution, therefore the state machine must be set to State 11 (Wait state) after calling such a method.
In the case of an active connection and no data transmission, the state machine switches between States 0 and 10. The state machine is reset to State 0 in order to react to changes in the client-server connection status and to handle them in State 0.
State 11 (Wait state): this state is a further wait state. As soon as a data transmission command (activated in State 10) has been executed, the state machine is set to State 11 and waits until the command execution is no longer busy. The state machine then switches to State 0.
State 100 (Error state): as soon as an error occurs during the activation or processing of a command, the state machine is set to State 100. The error is logged here and the state machine reset to State 0.
FUNCTION_BLOCK FB_[IEDName]Client
VAR_INPUT
fbConnection : FB_iec61850ClientClass;
END_VAR
VAR
_bAbort : BOOL;
_bDisconnect : BOOL;
_bConnect : BOOL;
_bReconnect : BOOL := TRUE;
_bReadAllData : BOOL := TRUE;
state : BYTE;
eState : E_AsyncEnvironmentState;
bBusy : BOOL;
bSuccess : BOOL;
ipResult : I_AsyncServiceResultClass;
sLastErrorResult : T_MaxString;
fbAbortReason : FB_ServiceErrorClass := (stError:=SUCCESS_EVENT);
sLastAbortReason : T_MaxString;
nInvokeID : UDINT;
eServiceError : E_AcsiServiceError;
nServiceError : UDINT;
nCmdError : UDINT;
sObjReference : T_AcsiObjectReference;
sCtrlReference : T_AcsiObjectReference;
bGetServerDirectory : BOOL := TRUE;
bGetLogicalDeviceDirectory: BOOL := TRUE;
bGetLogicalNodeDirectory : BOOL := TRUE;
bGetAllServerValues : BOOL := TRUE;
bGetAllServerValues : BOOL := TRUE;
bGetAllDataValues_LLN0_ST : BOOL := TRUE;
bGetDataValues_LLN0_ST_Beh: BOOL := TRUE;
END_VAR
fbConnection.Execute();
eState:= fbConnection.eState;
CASE state OF
0:
IF _bAbort THEN
_bAbort:= FALSE;
bSuccess:= fbConnection.AbortReq(ipReason:=fbAbortReason, ipSink:=0, ipResult=>ipResult);
state:= SEL(bSuccess, 100, 1);
ELSIF eState = E_AsyncEnvironmentState.Idle AND (_bConnect OR _bReconnect) THEN
_bConnect:= FALSE;
bGetAllServerValues:= SEL(_bReadAllData, bGetAllServerValues, TRUE);
bSuccess:= fbConnection.AssociateReq(ipSink:=0, ipResult=>ipResult);
state:= SEL(bSuccess, 100, 1);
ELSIF eState = E_AsyncEnvironmentState.Established AND _bDisconnect THEN
_bDisconnect:= FALSE;
bSuccess:= fbConnection.ReleaseReq(ipSink:=0, ipResult=>ipResult);
state:= SEL(bSuccess, 100, 1);
ELSIF eState = E_AsyncEnvironmentState.Established THEN
state:= 10;
END_IF
_bConnect:= FALSE;
_bDisconnect:= FALSE;
1:
IF ipResult <> 0 THEN
ipResult.Execute();
IF NOT (bBusy:=ipResult.IsBusy()) THEN
state:= SEL(ipResult.IsCompleted(), 100, 0);
END_IF
END_IF
10:
IF bGetServerDirectory THEN
bGetServerDirectory:= FALSE;
bSuccess:= fbConnection.GetServerDirectoryReq(ipServer:=fb[IEDName], eClass:=E_AcsiServerDirectoryClass.LogicalDevice, hUser:=0, ipSink:=0, nInvokeID=>nInvokeID, ipResult=>ipResult);
state:= SEL(bSuccess, 100, 11);
ELSIF bGetLogicalDeviceDirectory THEN
bGetLogicalDeviceDirectory:= FALSE;
bSuccess:= fbConnection.GetLogicalDeviceDirectoryReq(ipLogicalDevice:=fb[IEDName].IEDLD1, hUser:=0, ipSink:=0, nInvokeID=>nInvokeID, ipResult=>ipResult);
state:= SEL(bSuccess, 100, 11);
ELSIF bGetLogicalNodeDirectory THEN
bGetLogicalNodeDirectory:= FALSE;
bSuccess:= fbConnection.GetLogicalNodeDirectoryReq(ipLogicalNode:=fb[IEDName].IEDLD1.LLN0, eClass:=E_AcsiLogicalNodeClass.DataSet, hUser:=0, ipSink:=0, nInvokeID=>nInvokeID, ipResult=>ipResult);
state:= SEL(bSuccess, 100, 11);
ELSIF bGetAllServerValues THEN
bGetAllServerValues:= FALSE;
bSuccess:= fbConnection.GetAllServerValuesReq(ipServer:=fb[IEDName], hUser:=0, ipSink:=0, nInvokeID=>nInvokeID, ipResult=>ipResult);
state:= SEL(bSuccess, 100, 11);
ELSIF bGetAllDataValues_LLN0_ST THEN
bGetAllDataValues_LLN0_ST:= FALSE;
bSuccess:= fbConnection.GetAllDataValuesReq(ipLogicalNode:=fb[IEDName].IEDLD1.LLN0, eFc:=E_AcsiFc.ST_, hUser:=0, ipSink:=0, nInvokeID=>nInvokeID, ipResult=>ipResult);
state:= SEL(bSuccess, 100, 11);
ELSIF bGetDataValues_LLN0_ST_Beh THEN
bGetDataValues_LLN0_ST_Beh:= FALSE;
bSuccess:= fbConnection.GetDataValuesReq(ipData:=fb[IEDName].IEDLD1.LLN0.Beh, eFc:=E_AcsiFc.ST_, hUser:=0, ipSink:=0, nInvokeID=>nInvokeID, ipResult=>ipResult);
state:= SEL(bSuccess, 100, 11);
ELSE
state:= 0;
END_IF
11:
IF ipResult <> 0 THEN
ipResult.Execute();
IF NOT (bBusy:=ipResult.IsBusy()) THEN
state:= SEL(ipResult.IsCompleted(), 100, 0);
END_IF
END_IF
100:
state:= 0;
IF ipResult <> 0 THEN
nCmdError:= nCmdError + 1;
sLastErrorResult:= ipResult.Dump();
END_IF
END_CASE
GOOSE Subscriber (optional)
TwinCAT Telecontrol Configurator can also generate the PLC code for a GOOSE subscriber in a client project during PLC code generation (see code sample below). This requires that the user has previously created the GOOSE components such as GoCBs (goose control blocks) in the TwinCAT Telecontrol Configurator or imported them from an SCL file (e.g. ICD file).
By default, a function block with the name: "FB_[IEDName]Gse" is instantiated during code generation and added to the Global Variable List "TcTelecontrol". This function block establishes the connection between a network adapter of the TwinCAT control computer, the IED data model and the GOOSE configuration in the GoCBs. The GoCBs are instantiated in the IED data model (usually in LLN0). Each GoCB has a function block subelement with the name: "Subscriber". The subscription can be enabled or disabled from the PLC code via the "Subscriber" method calls. By default, the subscription is enabled for all GoCBs when the PLC program is started. This is controlled by the "bSubscriber" variable initialized with "TRUE". A rising edge at the "bUnsubscribe" variable can be used to disable the subscription for all GoCBs. Subscriber commands issued through these methods are executed immediately, without wait cycles or further states required to complete command processing.
The "Subscriber" described here reads the configuration and updates the status of the GoCB (attribute "GoEna" is set to "TRUE" or "FALSE", for example), but it does not use the client-server services such as "SetGoCBValues" or "GetGoCBValues" to enable or disable the "Publisher" on the server side. This means that the generated code already implements a subscriber that can be enabled or disabled, for example, in the first PLC cycle or from the PLC code at any time. The required GoCB configuration settings (GoCB attribute values) can be made via initialization values. However, the GoCBs can already be configured in the TwinCAT Telecontrol Configurator. The initialization values are automatically generated and assigned during code generation. If the Subscriber has been activated and the configuration of the GoCB and the network adapter matches the receiving GOOSE frame, then the GOOSE data is copied (mapped) into the TwinCAT IED data model. The "Execute" method must be called cyclically the rest of the time. Among other things, it is responsible for updating the status information in the GoCB.
FUNCTION_BLOCK FB_[IEDName]Gse IMPLEMENTS I_GseLinkStatusEventSink
VAR_INPUT
fbAdapter : FB_GseAdapterClass := (ipLinkStatus:=THIS^);
END_VAR
VAR
eLinkStatus : E_GseLinkStatus;
bSuccess : BOOL;
ipError : I_ServiceErrorClass;
bSubscribe : BOOL := TRUE;
bUnsubscribe : BOOL;
END_VAR
bSuccess:= fbAdapter.Execute(ipError=>ipError);
IF bSubscribe THEN
bSubscribe:= FALSE;
bSuccess:= fb[IEDName].IEDLD1.LLN0.gocb01.Subscriber.Enable(ipAdapter:=fbAdapter, ipError=>ipError);
bSuccess:= fb[IEDName].IEDLD1.LLN0.gocb02.Subscriber.Enable(ipAdapter:=fbAdapter, ipError=>ipError);
bSuccess:= fb[IEDName].IEDLD1.LLN0.gocb03.Subscriber.Enable(ipAdapter:=fbAdapter, ipError=>ipError);
ELSIF bUnsubscribe THEN
bUnsubscribe:= FALSE;
bSuccess:= fb[IEDName].IEDLD1.LLN0.gocb01.Subscriber.Disable(ipError=>ipError);
bSuccess:= fb[IEDName].IEDLD1.LLN0.gocb02.Subscriber.Disable(ipError=>ipError);
bSuccess:= fb[IEDName].IEDLD1.LLN0.gocb03.Subscriber.Disable(ipError=>ipError);
ELSE
bSuccess:= fb[IEDName].IEDLD1.LLN0.gocb01.Subscriber.Execute(ipError=>ipError);
bSuccess:= fb[IEDName].IEDLD1.LLN0.gocb02.Subscriber.Execute(ipError=>ipError);
bSuccess:= fb[IEDName].IEDLD1.LLN0.gocb03.Subscriber.Execute(ipError=>ipError);
END_IF
In the project tree under the I/O-Device branch you will find a network adapter instance named "GSE (RT Ethernet adapter)". This adapter instance must be configured accordingly, i.e. the I/O configuration must be adapted to the existing hardware and to the target platform on which the project is to run.
A new I/O configuration is also necessary if you change the target platform. This configuration must be done manually in TwinCAT XAE. In addition to the I/O configuration of the network adapter, a link must be established between the network adapter and the PLC function blocks for Goose communication. The link can be used to forward the data received from the network adapter to the instance of the function block: "FB_[IEDName]Gse". In the opposite direction the instance of the function block "FB_[IEDName]Gse" can forward the data to be sent to the network adapter.
Here you can find more information: RT Ethernet adapter Configuration.