1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847
|
= lib60870 - IEC 60870-5-101/104 C Source Code Library User Guide - Version 2.3.6
Copyright 2020 MZ Automation GmbH
== Introduction
lib60870 is a feature rich and field proven implementation of the IEC 60870-5-101/104 protocol for client (master station) and server (slave or controlled station). The library implements all data types of the IEC 60870-5-101/104 specifications. *lib60870* is implemented in standard C and is compatible with the C99 standard. It is designed to be as easy to use as possible.
The client/server API is strictly asynchronous. You send the requests with non-blocking functions and will have to handle the responses and other events in callback functions.
Here is a 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: support for 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
* Plugin interface for library extensions
*NOTE:* CS stands for "companion standard" and specifies variants of the communication protocols and services defined in the IEC 60870-5 standard series.
The library uses an "object-oriented" programming style. It is based on abstract data types (ADT) and functions
that operate on these data types. In general the actual implementations of the data types (the data structures that hold the data) is hidden from the API user. In almost all cases it is not required (and also not recommended) that the API user accesses these data structures directly.
== Application layer messages
This programming style will be explained by the example of the *_CS101_ASDU_* ADT that is a central part of the
library API. This data type represents an application layer message of the CS101/CS104 protocols. The abbreviation ASDU stands for "Application Service Data Unit". To create a new ASDU object the _CS101_ASDU_create_ function has to be used as a constructor of the object.
CS101_ASDU newAsdu = CS101_ASDU_create(alParams, false, CS101_COT_INTERROGATED_BY_STATION,
0, 1, false, false);
This function has various parameters to reflect the different properties of an ASDU. Here is the signature of the _CS101_ASDU_create_ function:
[[app-listing]]
[source, c]
----
CS101_ASDU
CS101_ASDU_create(CS101_AppLayerParameters parameters, bool isSequence, CS101_CauseOfTransmission cot,
int oa, int ca, bool isTest, bool isNegative);
----
The first parameter if another object of type _CS101_AppLayerParameters_ that represents the application layer parameters that are shared
between master and slave on agreement. If both side don't have the identical parameters they will not
be able to understand the ASDUs from the other side.
The second parameter _isSequence_ indicates that the ASDU contains a sequence of consecutive information objects (if _true_) or one or more independent information objects (if _false_). A sequence of consecutive information objects means that the ASDU only contains a single information object address (IOA). The consecutive information objects then have addresses IOA, IOA + 1, IOA + 2, ...
The third parameters indicates the cause of transmission (COT). It is to tell the other side the reason for sending the messages. Possible values could be _CS101_COT_PERIODIC_ for periodic messages, _CS101_COT_SPONTANEOUS_ for spontaneous messages.
The other parameters are _oa_ for the originator address, _ca_ for the common address of the ASDU, _isTest_ to indicate that the message is a test message, and _isNegative_ to indicate that the message is a negative confirmation of another message.
With the handle of the new ASDU object (in our case _newAsdu_) we can call one of the various functions to
get or set data of the ASDU. For example you can get or set the test flag values with the _CS101_ASDU_isTest_ or _CS101_ASDU_setTest_ functions. The first parameter of these functions is always the handle of the ASDU object.
bool isTest = CS101_ASDU_isTest(newAsdu);
An important function to create usable ASDU objects is the _CS101_ASDU_addInformationObject_ functions. With this function you can add information object instances to the ASDU.
[[app-listing]]
[source, c]
----
InformationObject io = (InformationObject) MeasuredValueScaled_create(NULL, 100, -1,
IEC60870_QUALITY_GOOD);
CS101_ASDU_addInformationObject(newAsdu, io);
----
This function can be called multiple times to add more information objects to the ASDU object. It has a boolean return values that indicates if the information object has been added successfully (return value _true_). Adding an information object can fail when the ASDU payload is already full. In this case the function will return _false_.
Finally, when the application created an ASDU object and it is no longer needed it has to be released with
with the _CS101_ASDU_destroy_ function.
CS101_ASDU_destroy(newAsdu);
== Master (client) side programming
For master side programming the following abstract data types and APIs can be used:
* *CS101_Master* for CS 101 compliant _balanced mode_ and _unbalanced mode_ serial connections.
* *CS104_Connection* for a CS 104 compliant TCP/IP connection.
=== Create a connection to a CS 104 server
Since an IEC 60870-5-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 usually passively waiting for connections.
A new connection is simple created by calling a the _CS104_Connection_create_ function of the CS104_Connection type:
CS104_Connection con = CS104_Connection_create("127.0.0.1", 2404);
This creates a new CS104_Connection object that is ready to connect to the server. The parameters are the hostname or IP address of the server and the TCP/IP port (usually 2404). For the port parameter you can also
set -1 to use the default port.
After the connection object is created you can now simply call the _CS104_Connection_connect_ function to connect to the server:
CS104_Connection_connect(con);
The parameter _con_ is the reference to the connection object created above.
When the connection has been established correctly you can use the connection object to send commands and receive data.
When you finished using the connection object you have to call
CS104_Connection_destroy(con);
To release all resources allocated by the object. After using the _destroy_ function you cannot use any
functions with the _con_ reference!
=== Preparing a CS 101 connection to one or more slaves
CS 101 provides two link layer modes for master/slave connections.
*Balanced mode* supports communication between a single master and a single slave using a
dedicated serial line. This mode is "balanced" in the sense that 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 are not allowed
to send messages spontaneously. They only respond following a request from the master.
The master can address multiple slaves at once by using a broadcast address.
==== Configuring the serial port
For both modes first the serial port has to be configured and initialized. The following
code shows an example how to prepare the serial port for usage with the library:
[[app-listing]]
[source, c]
----
SerialPort port = SerialPort_create("/dev/ttsS0", 9600, 8, 'E', 1);
----
==== Create and use a new unbalanced master instances
For balanced and unbalanced communication modes the *CS101_Master* type has to be used.
The following code creates a new unbalanced master instance using the serial port
defined above. The _CS101_Master_setASDUReceivedHandler_ function provides a callback handler for received ASDUs. The _CS101_Master_addSlave_ function will create a new slave specific state machine to handle all communication with the slave with link layer address 1.
[[app-listing]]
[source, c]
----
CS101_Master master = CS101_Master_create(port, NULL, NULL, IEC60870_LINK_LAYER_UNBALANCED);
CS101_Master_setASDUReceivedHandler(master, asduReceivedHandler, NULL);
CS101_Master_addSlave(master, 1);
----
The link layer parameters and application layer parameters are optional parameters. If not set default instances of the parameter objects are created and used. The
parameters can also be modified later.
Before sending any command or other request to a specific slave the slave address has to be set with the _CS101_Master_useSlaveAddress_ function.
[[app-listing]]
[source, c]
----
CS101_Master_useSlaveAddress(master, 1);
CS101_Master_sendProcessCommand(master, CS101_COT_ACTIVATION, 1, sc);
----
==== Balanced master
The balanced master is created the same way. Just the link layer mode parameter is different. The _CS101_Master_useSlaveAddress_ is used to set the slave address. In
the balanced master case it has only to be set one time, as there exists only
[[app-listing]]
[source, c]
----
CS101_Master master = CS101_Master_create(port, NULL, NULL, IEC60870_LINK_LAYER_BALANCED);
CS101_Master_useSlaveAddress(master, 3);
CS101_Master_setASDUReceivedHandler(master, asduReceivedHandler, NULL);
----
==== Setting the link layer parameters
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. The parameters
can be given with the constructor _CS101_Master_create_ or modified later.
[[app-listing]]
[source, c]
.Example: Disable usage of single char ACKs
----
LinkLayerParameters llParams = CS101_Master_getLinkLayerParameters(master);
llParams->useSingleCharACK = false;
----
=== Sending requests and receiving responses from the slave
In general an application is concerned with sending application layer messages (ASDUs) to the slave. The master side API supports generic and specialized functions to send messages to the slave. When sending system commands or process commands it is recommended to use the specialized functions because they help to
create ASDUs that comply to the standards. These specialized functions are explained in the following sections. They exist generally in two variants for CS101 and CS104.
For the general case it is possible to send arbitrary ASDUs by using the _CS101_Master_sendASDU_ or _CS104_Connection_sendASDU_ functions.
For receiving application layer messages the application has to implement the _CS101_ASDUReceivedHandler_ callback.
[[app-listing]]
[source, c]
.Example for processing received ASDUs in the CS101_ASDUReceivedHandler
----
static bool
asduReceivedHandler (void* parameter, int address, CS101_ASDU asdu)
{
printf("RECVD ASDU type: %s(%i) elements: %i\n",
TypeID_toString(CS101_ASDU_getTypeID(asdu)),
CS101_ASDU_getTypeID(asdu),
CS101_ASDU_getNumberOfElements(asdu));
if (CS101_ASDU_getTypeID(asdu) == M_ME_TE_1) {
printf(" measured scaled values with CP56Time2a timestamp:\n");
int i;
for (i = 0; i < CS101_ASDU_getNumberOfElements(asdu); i++) {
MeasuredValueScaledWithCP56Time2a io =
(MeasuredValueScaledWithCP56Time2a) CS101_ASDU_getElement(asdu, i);
printf(" IOA: %i value: %i\n",
InformationObject_getObjectAddress((InformationObject) io),
MeasuredValueScaled_getValue((MeasuredValueScaled) io)
);
MeasuredValueScaledWithCP56Time2a_destroy(io);
}
}
else if (CS101_ASDU_getTypeID(asdu) == M_SP_NA_1) {
printf(" single point information:\n");
int i;
for (i = 0; i < CS101_ASDU_getNumberOfElements(asdu); i++) {
SinglePointInformation io =
(SinglePointInformation) CS101_ASDU_getElement(asdu, i);
printf(" IOA: %i value: %i\n",
InformationObject_getObjectAddress((InformationObject) io),
SinglePointInformation_getValue((SinglePointInformation) io)
);
SinglePointInformation_destroy(io);
}
}
return true;
}
----
This callback handler has to be installed with the _CS104_Connection_setASDUReceivedHandler_ or _CS101_Master_setASDUReceivedHandler_ function.
CS101_Master_setASDUReceivedHandler(master, asduReceivedHandler, NULL);
All callback handler have a generic reference parameter with the name "parameter" in its function signatures. This parameter can be used by the user to provide application specific context information to the callback
handler. This parameter will be set with the install function of the callback handler (like _CS101_Master_setASDUReceivedHandler_ in the example above). If not used this parameter can be set to _NULL_.
.Master side callback handler types
[width="90%",cols="n,10,1,1",frame="topbot",options="header"]
|==========================
| callback type | event | CS 101 | CS 104
| CS101_ASDUReceivedHandler | ASDU received but not handled by one of the other callback handlers | + | +
| IEC60870_LinkLayerStateChangedHandler | link layer state changed event | + | -
| CS104_ConnectionHandler | CS104 APCI event | - | +
|==========================
=== Sending a read request
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.
CS104_Connection_sendReadCommand(con, 1 /* CA */, 2001 /* IOA */);
This call is non-blocking. You have to evaluate the response in the _CS101_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)!
=== Interrogation
It is also possible to request a group of data items from a slave with a single request. On the master (client) side you can simply use the _sendInterrogationCommand_ function of the Connection object:
CS104_Connection_sendInterrogationCommand (con, CS101_COT_ACTIVATION, /* CA */ 1, /* QOI */ 20);
The client/master side method signature looks like this:
bool
CS104_Connection_sendInterrogationCommand(CS104_Connection self, CS101_CauseOfTransmission cot, int ca, QualifierOfInterrogation 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.
=== Clock synchronization procedure
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 the _CS104_Connection_sendClockSyncCommand_ function for CS104 or the _CS101_Master_sendClockSyncCommand_ for CS101.
First a CP56Time2a timestamp has to be created and initialized:
struct sCP56Time2a currentTime;
CP56Time2a_createFromMsTimestamp(¤tTime, Hal_getTimeInMs());
CS104_Connection_sendClockSyncCommand(con, 1 /* CA */, ¤tTime);
Or when using dynamic memory allocation and CS 101:
CP56Time2a currentTime = CP56Time2a_createFromMsTimestamp(NULL, Hal_getTimeInMs());
CS101_Master_sendClockSyncCommand(master, 1 /* CA */, currentTime);
*NOTE*: The _Hal_getTimeInMs_ function is platform independent way to get the current time
as milliseconds since 00:00:00 1. January 1970 UTC. You can also use your own function to get
the time.
=== Command procedures
Commands are used to set set points, parameters or trigger some actions at the controlled station.
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 before operate* command procedure.
To send a command for the direct operate command procedure you have to send an ACTIVATION APDU to the controlled station.
[[app-listing]]
[source, c]
.Send a process command to the controlled station
----
InformationObject sc = (InformationObject)
SingleCommand_create(NULL, 5000, true, false, 0);
CS101_Master_sendProcessCommand(master, CS101_COT_ACTIVATION, 1, sc);
InformationObject_destroy(sc);
----
The constructor of SingleCommand data type has the following signature:
[[app-listing]]
[source, c]
----
SingleCommand
SingleCommand_create(SingleCommand self, 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.
For *select before operate* the command has to be sent with the _selectCommand_ parameter set to true to select the control output. In the next step an additional command with _selectCommand_ set to false has to be sent to cause the actual command execution.
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 _CS101_ASDU_isNegative_ function used with the received _CS101_ASDU_ instance.
For a CS 104 master a command can be sent the same way by using the _CS104_Master_sendProcessCommandEx_ function.
== Slave (server) side programming
=== CS104 (TCP/IP) Server configuration and setup
To configure and setup an IEC 60870-5-104 server/slave an instance of the _CS104_Slave_ data type is required.
CS104_Slave slave = CS104_Slave_create(100, 100);
After the server instance is created it can be configured
=== CS104 Server mode
The server provides three different modes concerning the support of redundant connections and event queue handling:
The default mode (_CS104_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 (_CS104_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 (_CS104_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 _CS104_Slave_setServerMode_ function:
CS104_Slave_setServerMode(slave, CS104_MODE_MULTIPLE_REDUNDANCY_GROUPS);
=== CS104: Defining multiple redundancy groups
Redundancy groups only have to be created explicitly when using the servermode _CS104_MODE_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 specific
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.
[[app-listing]]
[source, c]
.Example how to define multipe redundancy groups
----
CS104_Slave_setServerMode(slave, CS104_MODE_MULTIPLE_REDUNDANCY_GROUPS);
CS104_RedundancyGroup redGroup1 = CS104_RedundancyGroup_create("red-group-1");
CS104_RedundancyGroup_addAllowedClient(redGroup1, "192.168.2.9");
CS104_RedundancyGroup redGroup2 = CS104_RedundancyGroup_create("red-group-2");
CS104_RedundancyGroup_addAllowedClient(redGroup2, "192.168.2.223");
CS104_RedundancyGroup_addAllowedClient(redGroup2, "192.168.2.222");
CS104_RedundancyGroup redGroup3 = CS104_RedundancyGroup_create("catch-all");
CS104_Slave_addRedundancyGroup(slave, redGroup1);
CS104_Slave_addRedundancyGroup(slave, redGroup2);
CS104_Slave_addRedundancyGroup(slave, redGroup3);
----
=== CS101 (serial) slave configuration and setup
Similar to the master side the CS101 slave side can also be configured for one of the two link layer modes (_balanced_ or _unbalanced_). A CS101 slave is represented by a _CS101_SLave_ object.
Before a _CS101_Slave_ object can be created a _SerialPort_ object is required. The _SerialPort_ object
represents the serial interface and its configuration.
SerialPort port = SerialPort_create(serialPort, 9600, 8, 'E', 1);
The created _SerialPort_ object is required for the _CS101_Slave_create_ function:
CS101_Slave slave = CS101_Slave_create(port, NULL, NULL, IEC60870_LINK_LAYER_UNBALANCED);
This function has the following signature:
[[app-listing]]
[source, c]
----
CS101_Slave
CS101_Slave_create(SerialPort serialPort, LinkLayerParameters llParameters, CS101_AppLayerParameters alParameters, IEC60870_LinkLayerMode linkLayerMode)
----
Optionally the link layer parameters and application layer parameters can be specified. If the
default values should be used these parameters can be skipped (set to _NULL_). The last parameter specifies
if the _balanced_ or _unbalanced_ mode is used.
For the serial slave it is also required to set a link layer address:
CS101_Slave_setLinkLayerAddress(slave, 1);
=== Setting the callback handler functions
Before starting or running the server it is recommended to set the callback functions to
handle slave events. The following callback handler types are available (please the the API
reference manual for function signature details). Some of them are only available for CS 104 servers and some only for CS101 slaves.
.Slave side callback handler types
[width="90%",cols="n,10,1,1",frame="topbot",options="header"]
|==========================
| callback type | event | CS 101 | CS 104
| CS101_InterrogationHandler | interrogation requests | + | +
| CS101_CounterInterrogationHandler | counter interrogation requests | + | +
| CS101_ReadHandler | read requests for single information objects | + | +
| CS101_ClockSynchronizationHandler | clock synchronization message received | + | +
| CS101_ResetProcessHandler | reset process request received | + | +
| CS101_DelayAcquisitionHandler | delay acquisition request received | + | -
| CS101_ASDUHandler | ASDU received but not handled by one of the other callback handlers | + | +
| CS101_ResetCUHandler | a link layer message of type reset CU (communication unit) has been received | + | -
| CS104_ConnectionRequestHandler | a new TCP/IP client tries to connect | - | +
|==========================
[[app-listing]]
[source, c]
.Setting some callback functions for the CS101 slave
----
/* set the callback handler for the clock synchronization command */
CS101_Slave_setClockSyncHandler(slave, clockSyncHandler, NULL);
/* set the callback handler for the interrogation command */
CS101_Slave_setInterrogationHandler(slave, interrogationHandler, NULL);
/* set handler for other message types */
CS101_Slave_setASDUHandler(slave, asduHandler, NULL);
/* set handler for reset CU (reset communication unit) message */
CS101_Slave_setResetCUHandler(slave, resetCUHandler, (void*) slave);
----
=== CS104 Starting/Stopping the server
After the server is configured it can be started with the _CS104_Slave_start_ function. This function
starts a new background thread that is listening for incoming client connections.
CS104_Slave_start(slave);
To deactivate the IEC 60870-5-104 service the server can be stopped with the _CS104_Slave_stop_ function.
CS104_Slave_stop(slave);
=== Spontaneous or periodic transmission of messages
For spontaneous or periodic message transmission on the server/slave side the API user has to allocate a _CS101_ASDU_ object that represents a single ASDU, add Information Objects to the ASDU, and finally 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 or working link layer connection. Otherwise the messages will remain in the queue until a connection is activated.
*CS 104:* In the CS 104 slave the queue size is determined by the *maxLowPrioQueueSize* parameter of the *CS104_Slave_create* function. If the _maxLowPrioQueueSize_ parameter is set to zero the queue will always have the size defined with by _CONFIG_SLAVE_MESSAGE_QUEUE_SIZE_. The second parameter *maxHighPrioQueueSize* determines the size of the high priority data queue. Messages that are put into this queue bypass the messages of the low priority queue. The high priority queue is used for request responses in library callback handlers.
The following steps have to be done to send spontaneous or periodic messages:
1. Step: Create a new _CS101_ASDU_ instance (use _CS101_COT_PERIODIC_ for periodic data and _CS101_COT_SPONTANEOUS_ for spontaneous data)
CS101_ASDU newAsdu = CS101_ASDU_create(alParameters, false, CS101_COT_PERIODIC, 0, 1, false, false);
2. Step: Create a new information object instance containing the data to send
InformationObject io = (InformationObject) MeasuredValueScaled_create(NULL, 110, scaledValue, IEC60870_QUALITY_GOOD);
3. Step: Add the new information object to the ASDU
CS101_ASDU_addInformationObject(newAsdu, io);
4. Step: Release the information object memory
InformationObject_destroy(io);
5. Step: Put the ASDU into the class 2 data queue for transmission
CS101_Slave_enqueueUserDataClass2(slave, newAsdu);
6. Step: Release the ASDU memory
CS101_ASDU_destroy(newAsdu);
*NOTE:* For _CS 104_ you have to use the _CS104_Slave_enqueueASDU_ function in step 5:
CS104_Slave_enqueueASDU(slave, newAsdu);
=== Handling of interrogation requests
On the server side you should use the InterrogationHandler callback function to handle the Interrogation request. Depending on the QOI (_Qualifier of interrogation_) value you can return different information objects. For a simple system it is enough to only handle station interrogation requests (QOI = 20). The QOI values 21-36 are used for the interrogation groups (1-16). It is up to the slave implementer to assign information objects to interrogation groups.
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 _CS101_COT_INTERROGATED_BY_STATION_ for a station interrogation or COT that represent the respective interrogation group (e.g. _CS101_COT_INTERROGATED_BY_GROUP_1_ for interrogation group 1). After sending all information objects the server has to send the initial interrogation command message with COT = _CS101_COT_ACTIVATION_TERMINATION_ to indicate that the transmission of the interrogation data is finished.
[[app-listing]]
[source, c]
.Example how to implement an interrogation handler
----
static bool
interrogationHandler(void* parameter, IMasterConnection connection, CS101_ASDU asdu, uint8_t qoi)
{
if (qoi == 20) { /* only handle station interrogation */
CS101_AppLayerParameters alParams = IMasterConnection_getApplicationLayerParameters(connection);
IMasterConnection_sendACT_CON(connection, asdu, false);
CS101_ASDU newAsdu = CS101_ASDU_create(alParams, false, CS101_COT_INTERROGATED_BY_STATION,
0, 1, false, false);
InformationObject io = (InformationObject) MeasuredValueScaled_create(NULL, 100, -1, IEC60870_QUALITY_GOOD);
CS101_ASDU_addInformationObject(newAsdu, io);
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)
MeasuredValueScaled_create((MeasuredValueScaled) io, 101, 23, IEC60870_QUALITY_GOOD));
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)
MeasuredValueScaled_create((MeasuredValueScaled) io, 102, 2300, IEC60870_QUALITY_GOOD));
InformationObject_destroy(io);
IMasterConnection_sendASDU(connection, newAsdu);
CS101_ASDU_destroy(newAsdu);
IMasterConnection_sendACT_TERM(connection, asdu);
}
else {
IMasterConnection_sendACT_CON(connection, asdu, true);
}
return true;
}
----
Inside of the interrogation handler the IMasterConnection interface can be used to send the interrogated data
back to the client/master. The _CS101_ASDU_ and _InformationObject_ instances created inside the interrogation handler are in the responsibility of the user and have to be released with the appropriate functions (_CS101_ASDU_destroy_ and _InformationObject_destroy_) when they have been allocated dynamically before.
=== Handling of read commands (C_RD_NA_1) ===
The read command C_RD_NA_1(102) can be used by the client/master to read the value of a particular data point in monitoring direction.
The most convenient way to handle read commands at the server/slave side is to implement the callback function type _CS101_ReadHandler_. The read handler can be installed by the _CS104_Slave_setReadHandler_ or _CS101_Slave_setReadHandler_ functions for a CS 104 server or CS 101 slave.
In the read handler you have either to send the same read command but with a COT that indicates an error. Or you have to create the ASDU of the proper type for the data point and send this back to the client/master. When doing the latter you have to use the COT _CS101_COT_REQUEST_ to indicate that the message was caused by a read request.
[[app-listing]]
[source, c]
.Simple implementation of a read handler (pseudo code)
----
static bool
readHandler(void* parameter, IMasterConnection connection, CS101_ASDU asdu, int ioa)
{
if (request failed) {
/* send error reponse- e.g. unknown */
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_CA);
CS101_ASDU_setNegative(asdu, true);
IMasterConnection_sendASDU(connection, asdu);
}
else {
CS101_AppLayerParameters alParams = CS104_Slave_getAppLayerParameters(cs104Slave);
sCS101_StaticASDU _asdu;
CS101_ADSU newAsdu = CS101_ASDU_initializeStatic(_asdu, alParams, false, CS101_COT_REQUEST,
0, 1, false, false);
CS101_ASDU_addInformationObject(newAsdu, io);
IMasterConnection_sendASDU(connection, newAsdu);
}
/* return true to indicate that the request ASDU is handled here */
return true;
}
----
=== CS104 (TCP/IP) specific issues
==== Server mode
The server provides three different modes:
The default mode (_CS104_MODE_SINGLE_REDUNDANCY_GROUP_) allows only a *single active client connection*. An active client connection is a connection where ASDUs are sent. All other connections are standby connections. 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 (_CS104_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 has to be used when more then one client has to access the application data.
The third mode (_CS104_MODE_MULTIPLE_REDUNDANCY_GROUPS_) is the most flexible mode and allows to define specific _redundancy groups_. These redundany groups are groups of clients that share the same event queue. For each redundancy group there is a seperate event queue instance.
The server mode can be set with the _CS104_Slave_setServerMode_ function.
CS104_Slave_setServerMode(slave, CS104_MODE_CONNECTION_IS_REDUNDANCY_GROUP);
==== Restrict the number of client connections
The number of clients can be restricted with the _CS104_Slave_setMaxOpenConnections_ function.
CS104_Slave_setMaxOpenConnections(slave, 2);
In this case the server will only allow two concurrent client connections.
==== Setting local port and IP address
The default TCP port for IEC 60870-5-104 is 2404. The port can be changed with the _CS104_Slave_setLocalPort_ function.
CS104_Slave_setLocalPort(slave, 2405);
By default the server listens to all local IP addresses. With the _CS104_Slave_setLocalAddress_ function it is possible to restrict the server to listen to a single local IP address.
CS104_Slave_setLocalAddress(slave, "192.168.1.50");
With this setting the CS104 server will only listen on the local interface with the assigned IP address 192.168.1.50.
==== Set a connection request handler to restrict the access and track connections
The _CS104_ConnectionRequestHandler_ can be used to restrict the access to the server. With the return value the application can allow or deny the connection attempts of a client.
A _CS104_ConnectionRequestHandler_ can be set with the _CS104_Slave_setConnectionRequestHandler_ function. 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_.
CS104_Slave_setConnectionRequestHandler(slave, connectionRequestHandler, NULL);
In the handler you can optionally check the client IP address against a whitelist of allowed clients or implement a blacklist.
[[app-listing]]
[source, c]
.Example how to implement a ConnectionRequestHandler
----
static bool connectionRequestHandler(void* parameter, const char* ipAddress)
{
/* Allow only known IP addresses! */
/* You can implement your allowed client whitelist here */
if (strcmp(ipAddress, "127.0.0.1") == 0) {
return true;
else
return false;
}
----
==== Create a secure connection with TLS
The CS 104 standard can also be used with TLS to realize secure and authenticated connections.
In order to use TLS, the related parameters, certificates, and private keys have to be configured.
The configuration is stored in a _TLSConfiguration_ object. A new configuration object can be created with the _TLSConfiguration_create_ function.
[[app-listing]]
[source, c]
.Example how to create a CS 104 slave with TLS support
----
TLSConfiguration tlsConfig = TLSConfiguration_create();
TLSConfiguration_setChainValidation(tlsConfig, false);
TLSConfiguration_setAllowOnlyKnownCertificates(tlsConfig, true);
TLSConfiguration_setOwnKeyFromFile(tlsConfig, "server-key.pem", NULL);
TLSConfiguration_setOwnCertificateFromFile(tlsConfig, "server.cer");
TLSConfiguration_addCACertificateFromFile(tlsConfig, "root.cer");
TLSConfiguration_addAllowedCertificateFromFile(tlsConfig, "client1.cer");
/* create a new slave/server instance */
CS104_Slave slave = CS104_Slave_createSecure(100, 100, tlsConfig);
----
== lib60870-C specific topics
=== Debug output
The debug output to the console can be enabled by setting _CONFIG_DEBUG_OUTPUT_ to 1. This will enable the debug output by default. The debug output can be disabled my using the function *Lib60870_enableDebugOutput*. The default implementation of the debug output function will print to the console (using printf). If you need to redirect the output the most easy way would be to change the implementation of the debug output *lib60870_debug_print* function in _lib60870_common.c_.
=== Big endian platforms
The library contains a C header file to determine the platform byte order (_src/inc/internal/platform_endian.h_) when using the GCC compiler. This depends on defines that are provided by the C compiler. On some older big endian platforms like PowerPC or Coldfire depending on the compiler this may fail. You may have to define
PLATFORM_IS_BIGENDIAN 1
when compiling the library code.
E.g. put
-DPLATFORM_IS_BIGENDIAN=1
on the GCC command line when the platform byte order is big endian.
=== Configuration options at library compile time
Some configuration options are fixed at compile time of the library code. These options can be found in the file *lib60870_config.h*.
Compile time options include the support for specific CS 104 redundancy modes, support for threads and semaphores (required when the library uses threads), maximum number of TCP connections for CS 104 slave, and others.
== Reference information
=== Supported message types
The library supports the following ASDU (application service data unit) types.
.IEC 60870-5-101/104 message types
[width="90%",cols="n,10,1,1",frame="topbot",options="header"]
|===
| 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 | + | +
| M_EI_NA_1(70) | End of initialization | + | +
| 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_TS_NA_1(104) | Test command | + | +
| C_RP_NA_1(105) | Reset process command | + | +
| C_CD_NA_1(106) | Delay acquisition command | + | +
| C_TS_TA_1(107) | Test command with CP56Time2a | + | +
| 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 | + | +
| F_FR_NA_1(120) | File ready | + | +
| F_SR_NA_1(121) | Section ready | + | +
| F_SC_NA_1(122) | Call/Select directory/file/section | + | +
| F_LS_NA_1(123) | Last segment/section | + | +
| F_AF_NA_1(124) | ACK file/section | + | +
| F_SG_NA_1(125) | File segment | + | +
| F_DR_TA_1(126) | File directory | + | +
| F_SC_NB_1(127) | Query log | + | +
|===
=== CS 104 specific parameters
The following parameters are stored in *CS104_ConnectionParameters* objects.
.IEC 60870-5-104 parameters
[width="90%",cols="n,10",frame="topbot",options="header"]
|===
|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 |Timeout to send test telegrams in case of an idle connection
|===
|