lib60870.NET is an implementation of the IEC 60870-5 protocol for client (master station) and server (controlled station). The library implements all data types of the IEC 60870-5-101/104 specifications. lib60870.NET is a pure C#/.NET implementation. It is designed to be as easy to use as possible. Since it is using only standardized .NET features it is compatible with Windows systems and the Microsoft .NET implementation as well as on Linux and other operating systems running with Mono.
The client/server API is strictly asynchronous. You send the requests with non-blocking functions and will have to handle the response and other events in callback functions.
The list of supported features:
-
CS 101 (IEC 60870-5-101) balanced and unbalanced serial modes
-
CS 104 (IEC 60870-5-104) client and server TCP/IP communication
-
CS 104 supports encrypted and authenticated TLS communication
-
CS 104 uses the CS 101 application layer
-
CS 104 slave supports multiple redundancy groups
-
Master/Client supports sending system commands, process commands, parameter commands, and data messages in reverse direction.
-
Slave/Server supports sending data messages in monitoring direction and commands in reverse direction
-
The list of supported ASDU types can be found in the annex
-
The library supports user defined private ASDU types
-
transmission of transparent files
-
CS 101 protocol over TCP/IP (client/server)
-
Support for .NET Standard, Mono, and .NET core 2.0
NOTE: CS stands for "companion standard" and specifies variants of the communication protocols and services defined in the IEC 60870-5 standard series.
For master side programming the following API classes can be used:
-
CS101Master for a CS 101 compliant balanced mode or unbalanced mode serial connection.
-
Connection for a CS 104 compliant TCP/IP connection
All of these classes implement the abstract base class Master that provides a set of methods to send commands and messages to the slave.
Since an IEC 60870-5-104 (CS 104) connection is based on a TCP client/server connection the connection will be established by the client(master). The server(slave or outstation) is passively waiting for connections.
In C# a connection is simply created by calling a Constructor of the Connection class:
Connection con = new Connection ("127.0.0.1");
This creates a new Connection object that is ready to connect to the server. If you can go with the default connection parameters (TCP port 2404 and a common set of the IEC 60870-5-104 parameters) you can now simply call the Connect method:
con.Connect ();
When the connection has been established correctly you can use the connection object to send commands and receive data.
To use the Connection class you have to import the lib60870.CS104 namespace into your application:
using lib60870.CS104;
CS101 provides two kind of serial connection modes for master/slave communication.
Balanced mode supports communication between a single master and a single slave using a dedicated serial line. Both ends can spontaneously send messages at any time.
Unbalanced mode supports communication between a single master and multiple slaves on a serial bus. Each slave is addressed by its unique link layer address. Slaves do not send spontaneously. They only respond after a request form the master. The master can address multiple slaves by using a broadcast address.
For both mode first the serial port has to be configured and initialized. The following code shows an example how to prepare the serial port for using with the library:
SerialPort port = new SerialPort ();
port.PortName = "/dev/ttyUSB1";
port.BaudRate = 9600;
port.Parity = Parity.Even;
port.Handshake = Handshake.None;
port.Open ();
Before passing the SerialPort object to the library the port has to be opened.
Setting the link layer parameters is an optional step. When not explicitly set a default set of parameters will be used for the new master instance.
LinkLayerParameters llParameters = new LinkLayerParameters();
llParameters.AddressLength = 2;
llParameters.UseSingleCharACK = false;
In the example the length of the link layer address will be set to two bytes and the library will be instructed not to send single char ACK messages but use short messages instead. The new LinkLayerParameters object will be passed to the master constructor in the next step.
For balanced and unbalanced communication mode the CS101Master class has to be used.
The following code create a new unbalanced master instance and provides the previously set link layer parameters. The SetASDUReceivedHandler method provides a callback function for received ASDUs. The AddSlave function will create a new slave specific state machine to handle all communication with the slave with link layer address 1.
CS101Master master = new CS101Master(port, LinkLayerMode.UNBALANCED, llParameters);
master.SetASDUReceivedHandler (asduReceivedHandler, null);
master.AddSlave (1);
The link layer parameters and application layer parameters are optional parameters. If not set default instances of the parameter classes are created and used.
Before sending a command or other request to a specific slave the SlaveAddress property has to be set.
master.SlaveAddress = 1;
master.GetFile (1, 30000, NameOfFile.TRANSPARENT_FILE, new Receiver ());
The balanced master is created the same way. Just the link layer mode parameter is different.
CS101Master master = new CS101Master (port, LinkLayerMode.BALANCED, llParameters, alParameters);
master.OwnAddress = 1;
master.SlaveAddress = 2;
master.SetASDUReceivedHandler (asduReceivedHandler, null);
master.SetLinkLayerStateChangedHandler (linkLayerStateChanged, null);
In balanced mode the slave address has only to be set one time, because there is only one client.
The LinkLayerStateChangedHandler can be used to track changes of the link layer state. This way it can be detected when there is an error or the other side’s link layer is no longer available.
The IEC 60870 documents don’t recommend this service (cyclical data requests or polling) but it is an easy way to get the required data. You just need to know the common address (CA) and the information object address (IOA) to create the proper request.
con.SendReadCommand(1 /* CA */, 2001 /* IOA */);
The call is non-blocking. You have to evaluate the response in the ASDUReceivedHandler callback function.
Typically it is expected that the server response contains only the basic data type without timestamps (that is using the message types for a specific data type that does not contain the timestamps)!
You can also request a group of data items from a slave with a single request. On the master (client) side you can simply use the SendInterrogationCommand method of the Connection object:
con.SendInterrogationCommand (CauseOfTransmission.ACTIVATION, 1, 20);
The client/master side method signature looks like this:
public void SendInterrogationCommand(CauseOfTransmission cot, int ca, byte qoi)
The parameter ca is the common address (CA) as in the other methods. The parameter qoi is the "Qualifier of interrogation" (QOI). The value "20" (indicating "station interrogation") for the QOI indicates that it is an request for all data points. Other values for QOI will indicate that the client (master) only wants to receive data from a specific interrogation group.
For the clock synchronization procedure the controlling station (master) sends a C_CS_NA_1 ACT message to the controlled station (slave) containing the current valid time information as a CP56Time2a typed time value. The controlled station has to update its internal time and respond with a C_CS_NA_1 ACT_CON message after all queued time-tagged PDUs have been sent.
Clock synchronization of the controlled station can be done with a with the SendClockSyncCommand method of the Connection class.
CP56Time2a currentTime = new CP56Time2a (DateTime.Now); con.SendClockSyncCommand (1 /* CA */, currentTime);
Commands are used to set set points, parameters or trigger some actions at the outstation.
The following command types (data types are available for commands):
-
C_SC (single command) - to control binary data (switch…)
-
C_DC (double command) - to control binary data with transition state (moving switch…)
-
S_RC (step position command) - to control a step position
-
S_SE (setpoint command) - to control a set point (scaled value, normalized value, floating point values) - may also be used to set parameters, alarm limits etc.
These command types are also available in a version with a time tag (CP56TIme2a).
There are two different command procedures available. The direct operate command procedure and the select and operate command procedure.
To send a command for the direct operate command procedure you have to send an ACTIVATION APDU to the outstation.
Connection con = new Connection ("127.0.0.1");
con.SendControlCommand (TypeID.C_SC_NA_1, CauseOfTransmission.ACTIVATION, 1, new SingleCommand (5000, true, false, 0));
To issue a single command you have to provide the proper TypeID (C_SC_NA_1) and pass a SingleCommand instance to the SendControlCommand method.
The constructor of SingleCommand has the following signature:
public SingleCommand (int ioa, bool command, bool selectCommand, int qu)
In order to send a direct operate command the selectCommand parameter should be false. The qualifier (qu) should in general be set to 0.
If the command has been successful the outstation will answer with an ACT_CON response message with the negative flag not set. In case the outstation cannot execute the command it will also answer with an ACT_CON response but with the negative flag set. You can check if this flag is set with the IsNegative property of the received ASDU instance.
To configure and setup an IEC 60870-5-104 server/slave an instance of the Server class is required.
Server server = new Server ();
After the server instance is created it can be configured
The server provides three different modes.
The default mode (SINGLE_REDUNDANCY_GROUP) allows only a single active client connection. An active client connection is a connection where ASDUs (application data units) are sent. All other connections are only standby connections that don’t send application layer data. There is a single queue for events. Events are also stored when no client is connected or when no connection is active.
The second mode (CONNECTION_IS_REDUNDANCY_GROUP) allows multiple active client connections. Every connection has its own event queue. The event queue will be deleted when the client connection is closed. This mode can be used when more than one client has to access the application data. This mode is easy to use. But the drawback of this mode is that events are lost when no client is connected.
The third mode (MULTIPLE_REDUNDANCY_GROUPS) allows multiple active client connections while preserving events when no client is connected. In this mode clients can be assigned to specific redundancy groups. The assignment is based on the IP address of the client. A redundancy group can have multiple simultaneous connections but only one of these connections can be active. The number of activated connections is restricted by the number of redundancy groups. Each redundancy group has a dedicated event queue.
The server mode can be set with the ServerMode property of the Server class.
server.ServerMode = ServerMode.CONNECTION_IS_REDUNDANCY_GROUP;
Redundancy groups only have to be created explicitly when using the servermode MULTIPLE_REDUNDANCY_GROUPS. You can assign multiple IP addresses to a redundancy group. Incoming connections from one of these IP addresses will then automatically be assigned to this redundancy group.
When a redundancy group has no assigned IP address it works as a "catch all" group. This means that all incoming connections that are not assigned to one of the other groups will end up in this group.
/* Configure a server with three redundancy groups */
server.ServerMode = ServerMode.MULTIPLE_REDUNDANCY_GROUPS;
RedundancyGroup redGroup1 = new RedundancyGroup("red-group-1");
redGroup1.AddAllowedClient("192.168.2.9");
RedundancyGroup redGroup2 = new RedundancyGroup("red-group-2");
redGroup2.AddAllowedClient("192.168.2.223");
redGroup2.AddAllowedClient("192.168.2.222");
/* add a "catch all" redundancy groups - all other connections are handled by this group */
RedundancyGroup redGroup3 = new RedundancyGroup("catch all");
server.AddRedundancyGroup(redGroup1);
server.AddRedundancyGroup(redGroup2);
server.AddRedundancyGroup(redGroup3);
The number of clients can be restricted with the MaxOpenConnections property of the Server class.
server.MaxOpenConnections = 2;
In this case the server will only allow two concurrent client connections.
The default TCP port for IEC 60870-5-104 is 2404. The port can be changed with the SetLocalPort method of the Server class.
server.SetLocalPort(2405);
Per default the server listens to all local IP addresses. With the SetLocalAddress method of the Server class it is possible to restrict the server to a single local IP address.
server.SetLocalAddress("192.168.1.50");
The maximum size of the event queue(s) can be set with the MaxQueueSize property of the Server class. The default size is 1000. Each queue entry needs approximately 260 bytes.
server.MaxQueueSize = 10;
The ConnectionRequestHandler can be used to restrict the access to the server. With the return value the application can allow or deny the connection attempt of a client.
A ConnectionRequestHandler can be set with the SetConnectionRequestHandler method of the Server class. The second parameter is an arbitrary user provided object that will be passed to the handler when it is called. If not needed it can be set to null.
server.SetConnectionRequestHandler (connectionRequestHandler, null);
static bool connectionRequestHandler(object parameter, IPAddress ipAddress)
{
// Allow only known IP addresses!
// You can implement your allowed client whitelist here
if (ipAddress.ToString ().Equals ("127.0.0.1"))
return true;
else
return false;
}
In the handler you can optionally check the client IP address against a whitelist of allowed clients or implement a blacklist.
After the server is configured it can be started with the Start method
server.Start ();
To deactivate the IEC 60870-5-104 service the server can be stopped with the Stop method.
server.Stop ();
On the server side you should use the InterrogationHandler delegate to handle the Interrogation request. Depending on the QOI value your should return different information objects. For a simple system it is enough to only handle station interrogation requests (QOI = 20).
According to the specification the server has to respond the ACTIVATION request from the client with the ACT_CON response followed by ASDUs containing the information objects with the COT = INTERROGATED_BY_STATION. After sending all information objects the server has to send the initial interrogation command message with COT = ACT_TERM to indicate that the transmission of the interrogation data is finished.
private static bool interrogationHandler(object parameter, ServerConnection connection, ASDU asdu, byte qoi)
{
Console.WriteLine ("Interrogation for group " + qoi);
// send ACT_CON
connection.SendACT_CON (asdu, false);
// send information objects
newAsdu.AddInformationObject (new MeasuredValueScaled (100, -1, new QualityDescriptor ()));
newAsdu.AddInformationObject (new MeasuredValueScaled (101, 23, new QualityDescriptor ()));
newAsdu.AddInformationObject (new MeasuredValueScaled (102, 2300, new QualityDescriptor ()));
connection.SendASDU (newAsdu);
newAsdu = new ASDU (TypeID.M_ME_TE_1, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 3, 1, false);
newAsdu.AddInformationObject(new MeasuredValueScaledWithCP56Time2a(103, 3456, new QualityDescriptor (), new CP56Time2a(DateTime.Now)));
connection.SendASDU (newAsdu);
newAsdu = new ASDU (TypeID.M_SP_TB_1, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, false);
newAsdu.AddInformationObject (new SinglePointWithCP56Time2a (104, true, new QualityDescriptor (), new CP56Time2a (DateTime.Now)));
connection.SendASDU (newAsdu);
// send ACT_TERM
connection.SendACT_TERM (asdu);
return true;
}
For spontaneous message transmission on the server side the API user has to allocate an ASDU object, add Information Objects to the ASDU and put the ASDU into the transmission queue. The transmission queue is a FIFO (first in first out) list. If the queue is full the oldest message will be deleted and replaced by the newly added message. Messages will only be sent if the there is an active client connection. Otherwise the messages will remain in the queue until a connection is activated.
CS104 The size of the queue is controlled by the property MaxQueueSize of the Server object..
These are the required steps:
-
Step: Create a new ASDU instance
ASDU newAsdu = new ASDU (TypeID.M_ME_NB_1, CauseOfTransmission.PERIODIC, false, false, 2, 1, false);
-
Step: Add an information object to the ASDU
newAsdu.AddInformationObject (new MeasuredValueScaled (110, -1, new QualityDescriptor ()));
-
Step: Add the ASDU to the transmission queue
server.EnqueueASDU (newAsdu);
Multiple information objects can be included in the same ASDU when the information objects are all of the same type.
The return value of the AddInformationObject method indicates if the information object has been added successfully. When the ASDU capacity is already reached the return value is false and the InformationObject instance has not been added.
For lib60870.NET you should derive a new class from the InformationObject class.
The CS 101 master or slave protocols can also be used over a TCP/IP connection with the TcpServerVirtualSerialPort and TcpClientVirtualSerialPort classes. These classes can be used to configure the CS 101 master or slave as a TCP/IP client or server.
NOTE: This is a non-standard extension! According to the IEC 60870-5 standard TCP/IP has to be used according to IEC 60870-5-104 (CS 104).
To use TCP/IP instead of a serial connection you have to create an instance of the TcpServerVirtualSerialPort or TcpClientVirtualSerialPort class and handle the instance to the constructor of the CS101Master or CS101Slave class.
TcpServerVirtualSerialPort port = new TcpServerVirtualSerialPort ();
port.Start ();
CS101Slave slave = new CS101Slave (port, llParameters);
...
port.Stop ();
The Start and Stop methods are used to start and stop the TCP/IP client and server. The server class allows only a single client connection.
The library supports the following ASDU (application service data unit) types.
Message type | Description | C | C# |
---|---|---|---|
M_SP_NA_1(1) |
Single point information (BOOLEAN) |
+ |
+ |
M_SP_TA_1(2) |
Single point information (BOOLEAN) with CP24Time2a |
+ |
+ |
M_DP_NA_1(3) |
Double point information (ON/OFF/transient) |
+ |
+ |
M_DP_TA_1(4) |
Double point information (ON/OFF/transient) with CP24Time2a |
+ |
+ |
M_ST_NA_1(5) |
Step position information (-64 … 63, is transient) |
+ |
+ |
M_ST_TA_1(6) |
Step position information (-64 … 63, is transient) with CP24Time2a |
+ |
+ |
M_BO_NA_1(7) |
Bitstring32 (32 bit bitstring) |
+ |
+ |
M_BO_TA_1(8) |
Bitstring32 (32 bit bitstring) with CP24Time2a |
+ |
+ |
M_ME_NA_1(9) |
Normalized measured value (-1.0 … +1.0) |
+ |
+ |
M_ME_TA_1(10) |
Normalized measured value (-1.0 … +1.0) with CP24Time2a |
+ |
+ |
M_ME_NB_1(11) |
Scaled measured value (-32768 … +32767) |
+ |
+ |
M_ME_TB_1(12) |
Scaled measured value (-32768 … +32767) with CP24Time2a |
+ |
+ |
M_ME_NC_1(13) |
Short measured value (FLOAT32) |
+ |
+ |
M_ME_TC_1(14) |
Short measured value (FLOAT32) with CP24Time2a |
+ |
+ |
M_IT_NA_1(15) |
Integrated totals (INT32 with quality indicators) |
+ |
+ |
M_IT_TA_1(16) |
Integrated totals (INT32 with quality indicators) with CP24Time2a |
+ |
+ |
M_EP_TA_1(17) |
Event of protection equipment |
+ |
+ |
M_EP_TB_1(18) |
Packed start events of protection equipment |
+ |
+ |
M_EP_TC_1(19) |
Packed output circuit info |
+ |
+ |
M_PS_NA_1(20) |
Packed single point with SCD |
+ |
+ |
M_ME_ND_1(21) |
Normalized measured value (-1.0 … +1.0) without quality |
+ |
+ |
M_SP_TB_1(30) |
Single point information (BOOLEAN) with CP56Time2a |
+ |
+ |
M_DP_TB_1(31) |
Double point information (ON/OFF/transient) with CP56Time2a |
+ |
+ |
M_ST_TB_1(32) |
Step position information (-64 … 63, is transient) with CP56Time2a |
+ |
+ |
M_BO_TB_1(33) |
Bitstring32 (32 bit bitstring) with CP56Time2a |
+ |
+ |
M_ME_TD_1(34) |
Normalized measured value (-1.0 … +1.0) with CP56Time2a |
+ |
+ |
M_ME_TE_1(35) |
Scaled measured value (-32768 … +32767) with CP56Time2a |
+ |
+ |
M_ME_TF_1(36) |
Short measured value (FLOAT32) with CP56Time2a |
+ |
+ |
M_IT_TB_1(37) |
Integrated totals (INT32 with quality indicators) with CP56Time2a |
+ |
+ |
M_EP_TD_1(38) |
Event of protection equipment with CP56Time2a |
+ |
+ |
M_EP_TE_1(39) |
Packed start events of protection equipment with CP56Time2a |
+ |
+ |
M_EP_TF_1(40) |
Packed output circuit info with CP56Time2a |
+ |
+ |
C_SC_NA_1(45) |
Single command (BOOLEAN) |
+ |
+ |
C_DC_NA_1(46) |
Double command (ON/OFF/transient) |
+ |
+ |
C_RC_NA_1(47) |
Step command |
+ |
+ |
C_SE_NA_1(48) |
Setpoint command, normalized value (-1.0 … +1.0) |
+ |
+ |
C_SE_NB_1(49) |
Setpoint command, scaled value (-32768 … +32767) |
+ |
+ |
C_SE_NC_1(50) |
Setpoint command, short value (FLOAT32) |
+ |
+ |
C_BO_NA_1(51) |
Bitstring command (32 bit bitstring) |
+ |
+ |
C_SC_TA_1(58) |
Single command (BOOLEAN) with CP56Time2a |
+ |
+ |
C_DC_TA_1(59) |
Double command (ON/OFF/transient) with CP56Time2a |
+ |
+ |
C_RC_TA_1(60) |
Step command with CP56Time2a |
+ |
+ |
C_SE_TA_1(61) |
Setpoint command, normalized value (-1.0 … +1.0) with CP56Time2a |
+ |
+ |
C_SE_TB_1(62) |
Setpoint command, scaled value (-32768 … +32767) with CP56Time2a |
+ |
+ |
C_SE_TC_1(63) |
Setpoint command, short value (FLOAT32) with CP56Time2a |
+ |
+ |
C_BO_TA_1(64) |
Bitstring command (32 bit bitstring) with CP56Time2a |
+ |
+ |
C_IC_NA_1(100) |
Interrogation command |
+ |
+ |
C_CI_NA_1(101) |
Counter interrogation command |
+ |
+ |
C_RD_NA_1(102) |
Read command |
+ |
+ |
C_CS_NA_1(103) |
Clock synchronization command |
+ |
+ |
C_RP_NA_1(105) |
Reset process command |
+ |
+ |
C_CD_NA_1(106) |
Delay acquisition command |
+ |
+ |
P_ME_NA_1(110) |
Parameter of measured values, normalized value |
+ |
+ |
P_ME_NB_1(111) |
Parameter of measured values, scaled value |
+ |
+ |
P_ME_NC_1(112) |
Parameter of measured values, short floating point number |
+ |
+ |
P_AC_NA_1(113) |
Parameter for activation |
+ |
+ |
The following parameters are for the CS101/CS104 application layer and are stored in the lib60870.CS101.ApplicationLayerParameters class.
Parameter | Description |
---|---|
SizeOfCOT |
Size of the COT field of the ASDU. Can be 1 or 2 (default). When the size is 2 the COT field contains the originator address (OA). |
OA |
Originator address |
SizeOfCA |
Size of the common address (CA) field of the ASDU. Can be 1 or 2 (default). |
SizeOfIOA |
Size of the information object addresses (IOA). Can be 1, 2, or 3 (default). |
MaxAsduLength |
Maximum allowed length of the ASDU (default and maximum is 249). Should not be changed. |
The LinkLayerParameters class stored the configuration parameters for the CS 101 link layer.
Parameter | Description |
---|---|
AddressLength |
Size of the link layer address field of the LPCI. Can be 0, 1 (default), or 2. |
TimeoutForACK |
Timeout for ACK of the link layer message |
TimeoutRepeat |
Timeout for repeated transmission of link layer messages. |
UseSingleCharACK |
Indicates if the secondary link layer will use single char ACK (E5) |
The following parameters are stored in APCIParameters objects.
Parameter | Description |
---|---|
k |
Number of unconfirmed APDUs in I format. Sender will stop transmission after k unconfirmed I messages. |
w |
Number of unconfirmed APDUs in I format. Receiver will confirm latest after w messages |
t0 |
Timeout for connection establishment (in s) |
t1 |
Timeout for transmitted APDUs in I/U format (in s) when timeout elapsed without confirmation the connection will be closed. This is used by the sender to determine if the receiver has failed to confirm a message. |
t2 |
Timeout to confirm messages (in s). This timeout is used by the receiver to determine the time when the message confirmation has to be sent. |
t3 |
time until test telegrams will be sent in case of an idle connection |