lib60870 - IEC 60870-5-101/104 C 源代码库用户指南 - 版本 2.3.0

版权 2020 MZ Automation GmbH

介绍

lib60870 是针对客户端(主站)和服务器(从站或受控站)的 IEC 60870-5-101/104 协议的功能丰富且经过现场验证的实现。该库实现了 IEC 60870-5-101/104 规范的所有数据类型。lib60870 以标准 C 实现,并与 C99 标准兼容。它被设计成尽可能易于使用。

客户端/服务器 API 是严格异步的。使用非阻塞函数发送请求,并且必须在回调函数中处理响应和其他事件。

以下是支持的功能列表:

  • CS 101 (IEC 60870-5-101) 平衡和非平衡串行模式
  • CS 104 (IEC 60870-5-104) 客户端和服务端 TCP/IP 通信
  • CS 104 支持加密和授权的 TLS 通信
  • CS 104 使用 CS 101 应用层
  • CS 104 从机:支持冗余组
  • 主站/客户端支持反向发送系统命令、过程命令、参数命令和数据消息。
  • 从机/服务器支持在监控方向发送数据消息,并支持在相反方向发送命令
  • 支持的 ASDU 类型列表可在附件中找到
  • 库支持用户定义的专用 ASDU 类型
  • 库扩展插件接口

注: CS 代表“配套标准”,规定了 IEC 60870-5 标准系列中定义的通信协议和服务的变体。

该库使用“面向对象”的编程风格。它基于抽象数据类型(ADT)和操作这些数据类型的函数。通常,数据类型(保存数据的数据结构)的实际实现对 API 用户是隐藏的。在几乎所有情况下,API 用户不需要(也不建议)直接访问这些数据结构。

应用层消息

此编程风格将通过 CS101_ASDU ADT 的示例进行解释,后者是库 API 的最重要部分。该数据类型表示 CS101/C104 协议的应用层消息。缩写 ASDU 代表“应用服务数据单元”。要创建新的 ASDU 对象,必须使用 CS101_ASDU_create 函数作为对象的构造函数。

CS101_ASDU newAsdu = CS101_ASDU_create(alParams, false, CS101_COT_INTERROGATED_BY_STATION,
        0, 1, false, false);

此函数具有多个参数,以反映 ASDU 的不同属性。以下是 CS101_ASDU_create 函数的签名:

CS101_ASDU
CS101_ASDU_create(CS101_AppLayerParameters parameters, bool isSequence, CS101_CauseOfTransmission cot,
                  int oa, int ca, bool isTest, bool isNegative);

第一个参数是另一个 CS101_AppLayerParameters 类型的对象,该对象表示主设备和从设备之间根据协议共享的应用层参数。如果双方没有相同的参数,他们将无法理解对方的 ASDU。

第二个参数 isSequence 指示 ASDU 包含连续信息对象序列(如果 true)或一个或多个独立信息对象(如果 false)。连续信息对象的序列意味着 ASDU 仅包含单个信息对象地址(IOA)。然后,连续的信息对象具有地址 IOA、IOA+1、IOA+2。。。

第三个参数表示传输原因(COT)。这是为了告诉对方发送信息的原因。对于周期性消息,可能的值为 CS101_COT_PERIODIC,对于自发消息,可能为 CS101_COT_SPONTANEOUS

其他参数为,oa 表示始发方地址,ca 表示 ASDU 的公共地址,isTest 表示消息是测试消息,isNegative 表示消息为另一消息的否定确认。

有了新的 ASDU 对象的句柄(在我们的例子中为 newAsdu),我们可以调用多种函数来获取或设置 ASDU 数据。例如,您可以使用 CS101_ASDU_isTestCS101_ASDU_setTest 函数获取或设置测试标志值。这些函数的第一个参数始终是 ASDU 对象的句柄。

bool isTest = CS101_ASDU_isTest(newAsdu);

创建可用 ASDU 对象的一个重要函数是 CS101_ASDU_addInformation Object 函数。使用函数可以将信息对象实例添加到 ASDU 中。

InformationObject io = (InformationObject) MeasuredValueScaled_create(NULL, 100, -1,
                       IEC60870_QUALITY_GOOD);

CS101_ASDU_addInformationObject(newAsdu, io);

可以多次调用此函数,以便向 ASDU 对象添加更多信息对象。它有一个布尔返回值,指示信息对象是否已成功添加(返回值 true)。当 ASDU 有效负载已满时,添加信息对象可能会失败。在这种情况下,函数将返回 false

最后,当应用程序创建了一个 ASDU 对象,并且不再需要它时,必须使用 CS101_ASDU_destroy 函数来释放它。

CS101_ASDU_destroy(newAsdu);

主站 (客户端) 侧编程

对于主站端编程,可以使用以下抽象数据类型和 API:

  • CS101_Master 适用于符合 CS 101 的平衡模式和非平衡模式串行连接。
  • CS104_Connection 用于符合 CS 104 的 TCP/IP 连接。

创建一个到 CS 104 服务端的连接

由于 IEC 60870-5-104 连接基于 TCP 客户端/服务器连接,因此该连接将由客户端(主机)建立。服务器(从站或分站)通常被动等待连接。

通过调用 CS104_connection 类型的 CS104_connection_create 函数,可以简单地创建新连接:

CS104_Connection con = CS104_Connection_create("127.0.0.1", 2404);

这将创建一个新的 CS104_Connection 对象,该对象已准备好连接到服务器。参数是服务器的主机名或 IP 地址以及 TCP/IP 端口(通常为 2404)。对于端口参数,您也可以设置 -1 以使用默认端口。

创建连接对象后,现在只需调用 CS104_connection_connect 函数即可连接到服务器:

CS104_Connection_connect(con);

参数 con 是对上面创建的连接对象的引用。

正确建立连接后,可以使用连接对象发送命令和接收数据。

当您使用完连接对象时,您必须调用

CS104_Connection_destroy(con);

释放对象分配的所有资源。使用 destroy 函数后,不能对 con 引用使用任何函数!

准备连接到一个或多个从机的 CS 101

CS 101 为主/从连接提供了两种链路层模式。

平衡模式支持使用专用串行线在单个主设备和单个从设备之间进行通信。这种模式是“平衡的”,因为两端可以随时自发地发送消息。

不平衡模式支持串行总线上单个主设备和多个从设备之间的通信。每个从设备都通过其唯一的链路层地址进行寻址。从机是不允许自发发送信息的。他们只会根据主站的请求做出回应。主设备可以通过使用广播地址同时寻址多个从设备。

配置串口

对于这两种模式,首先必须配置和初始化串行端口。以下代码显示了如何准备串行端口以便与库一起使用的示例:

SerialPort port = SerialPort_create("/dev/ttsS0", 9600, 8, 'E', 1);

创建并使用新的非平衡主实例

对于平衡和不平衡通信模式,必须使用 CS101_Master 类型。

以下代码使用上面定义的串行端口创建一个新的不平衡主站实例。CS101_Master_setASDUReceivedHandler 函数为接收到的 ASDU 提供回调处理程序。CS101_Master_addSlave 函数将创建一个新的从机专用的状态机,以处理与链路层地址为 1 的从机的所有通信。

CS101_Master master = CS101_Master_create(port, NULL, NULL, IEC60870_LINK_LAYER_UNBALANCED);

CS101_Master_setASDUReceivedHandler(master, asduReceivedHandler, NULL);

CS101_Master_addSlave(master, 1);

链路层参数和应用层参数是可选参数。如果未设置,则会创建和使用参数对象的默认实例。以后也可以修改这些参数。

在向特定从机发送任何命令或其他请求之前,必须使用 CS101_Master_useSlaveAddress 函数设置从机地址。

CS101_Master_useSlaveAddress(master, 1);
CS101_Master_sendProcessCommand(master, CS101_COT_ACTIVATION, 1, sc);

平衡主站

平衡主站也是以同样的方式创建的。只是链接层模式参数不同。CS101_Master_useSlaveAddress 用于设置从机地址。在平衡主站情况下,只需设置一次,因为只有

CS101_Master master = CS101_Master_create(port, NULL, NULL, IEC60870_LINK_LAYER_BALANCED);

CS101_Master_useSlaveAddress(master, 3);
CS101_Master_setASDUReceivedHandler(master, asduReceivedHandler, NULL);

设置链路层参数

设置链接层参数是一个可选步骤。如果未明确设置,则将为新的主站实例使用一组默认参数。参数可以通过构造函数 CS101_Master_create 给出,也可以稍后修改。

示例:禁用单字符 ACK 的用法

LinkLayerParameters llParams = CS101_Master_getLinkLayerParameters(master);
llParams->useSingleCharACK = false;

向从机发送请求并接收响应

一般来说,应用程序专注于向从机发送应用层消息(ASDU)。主机 API 包含向从机发送消息的通用和专用函数。发送系统命令或过程命令时,建议使用专用函数,因为它们有助于创建符合标准的 ASDU。以下各节将对这些专门函数进行解释。它们通常存在于 CS101 和 CS104 的两种变体中。

对于一般情况,可以使用 CS101_Master_sendASDUCS104_Connection_sendASDU 函数发送任意 ASDU。

为了接收应用层消息,应用程序必须实现 CS101_ASDUReceivedHandler 回调。

在 CS101_ASDUReceivedHandler 中处理接收到的 ASDU 的示例

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;
}

此回调处理程序必须使用 CS104_Connection_setASDUReceivedHandler 或 _CS101_Master_setASDURLeceivedHandler 函数安装。

CS101_Master_setASDUReceivedHandler(master, asduReceivedHandler, NULL);

所有回调处理程序的函数签名中都有一个名为 “parameter” 的通用引用参数。用户可以使用此参数向回调处理程序提供特定于应用程序的上下文信息。此参数通过回调处理程序的安装函数进行设置(如上例中的 CS101_Master_setASDUReceivedHandler)。如果不使用此参数,则可以将其设置为 NULL

主站侧回调处理程序类型

回调类型 事件 CS 101 CS 104
CS101_ASDUReceivedHandler ASDU已接收,但未由其他回调处理程序处理 + +
IEC60870_LinkLayerStateChangedHandler 链路层状态更改事件 + -
CS104_ConnectionHandler CS104 APCI 事件 - +

发送读请求

IEC 60870 文档不建议使用此服务(周期性数据请求或轮询),但这是获取所需数据的一种简单方法。您只需要知道公共地址(CA)和信息对象地址(IOA)就可以创建正确的请求。

  CS104_Connection_sendReadCommand(con, 1 /* CA */, 2001 /* IOA */);

此调用是非阻塞的。您必须在 CS101_ASDUReceivedHandler 回调函数中评估响应。

通常情况下,服务器响应只包含没有时间戳的基本数据类型(即使用不包含时间戳的特定数据类型的消息类型)!

召唤

也可以通过单个请求向从机请求一组数据项。在主站(客户端)端,您可以简单地使用 Connection 对象的 sendInterrogationCommand 函数:

CS104_Connection_sendInterrogationCommand (con, CS101_COT_ACTIVATION, /* CA */ 1, /* QOI */ 20);

客户端/主机端方法签名如下所示:

bool
CS104_Connection_sendInterrogationCommand(CS104_Connection self, CS101_CauseOfTransmission cot, int ca, QualifierOfInterrogation qoi)

与其他方法一样,参数 ca 是公共地址(ca)。参数 qoi 是“询问限定符”(qoi)。QOI 的值 “20”(表示“站询问”)表示它是对所有数据点的请求。QOI 的其他值将指示客户端(主机)只想从特定询问组接收数据。

时钟同步过程

对于时钟同步过程,控制站(主站)向受控站(从站)发送 C_CS_NA_1 ACT 消息,该消息包含作为 CP56Time2a 类型的时间值的当前有效时间信息。受控站必须更新其内部时间,并在发送了所有排队的时间标记 PDU 之后用 C_CS_NAME_1 ACT_CON 消息进行响应。

受控站的时钟同步可以通过 CS104 的 CS104_Connection_sendClockSyncCommand 函数或 CS101 的 CS101_Master_sendClockSyncCommand 来完成。

首先,必须创建并初始化 CP56Time2a 时间戳:

struct sCP56Time2a currentTime;
CP56Time2a_createFromMsTimestamp(&currentTime, Hal_getTimeInMs());
CS104_Connection_sendClockSyncCommand(con, 1 /* CA */, &currentTime);

或者当使用动态内存分配和 CS 101 时:

CP56Time2a currentTime = CP56Time2a_createFromMsTimestamp(NULL, Hal_getTimeInMs());
CS101_Master_sendClockSyncCommand(master, 1 /* CA */, currentTime);

Hal_getTimeInMs 函数是一种独立于平台的方法,用于获取自 1970 年 1 月 UTC 00:00:00 1 以来的当前时间(以毫秒为单位)。您也可以使用自己的函数来获取时间。

指令执行过程

命令用于在受控站设置设定点、参数或触发一些动作。

以下命令类型(数据类型可用于命令):

  • C_SC (单点命令) - 控制二进制数据(开关…)
  • C_DC (双点命令) - 用转换状态控制二进制数据(移动开关…)
  • S_RC (步进位置命令) - 控制步进位置
  • S_SE (设定值命令) - 控制设定值(缩放值、归一化值、浮点值)-也可用于设置参数、报警限值等。

这些命令类型同样有带时间标签的版本(CP56TIme2a)。

有两种不同的命令过程可用。直接操作命令程序和操作前选择命令程序。

要发送直接操作命令过程的命令,你必须向受控站发送 ACTIVATION APDU。

向受控站发送处理命令

InformationObject sc = (InformationObject)
      SingleCommand_create(NULL, 5000, true, false, 0);

CS101_Master_sendProcessCommand(master, CS101_COT_ACTIVATION, 1, sc);

InformationObject_destroy(sc);

SingleCommand 数据类型的构造器具有以下签名:

SingleCommand
SingleCommand_create(SingleCommand self, int ioa, bool command, bool selectCommand, int qu);

要发送直接操作命令,selectCommand 参数应为 false。限定符(qu)通常应设置为 0。

对于操作前选择,发送命令时必须将 selectCommand 参数设置为 true,以选择控制输出。在下一步中,必须发送一个 selectCommand 设置为 false 的附加命令,才能执行实际的命令。

如果命令成功,则分站将以未设置 negative flag 的 ACT_CON 响应消息进行应答。如果分站无法执行该命令,它也将使用 ACT_CON 响应进行应答,但设置 negative flag 。您可以对接收到的 CS101_ASDU 实例使用 CS101_ASDU_isNegative 函数,检查此标志是否设置。

对于 CS 104 主机,可以使用 CS104_master_sendProcessCommandEx 函数以相同的方式发送命令。

从机 (服务端) 侧编程

CS104(TCP/IP)服务器配置和设置

要配置和设置 IEC 60870-5-104 服务器/从机,需要一个 CS104_slave 数据类型的实例。

CS104_Slave slave = CS104_Slave_create(100, 100);

创建服务器实例后,可以对其进行配置

CS104 服务端模式

服务器提供了三种不同的模式,涉及对冗余连接和事件队列处理的支持:

默认模式(CS104_mode_SINGLE_REDUNDANCY_GROUP)仅允许单个活动客户端连接。活动客户端连接是发送 ASDU(应用程序数据单元)的连接。所有其他连接都只是不发送应用层数据的备用连接。
事件只有一个队列。当没有客户端连接或没有连接处于活动状态时,也会存储事件。

第二种模式(CS104_mode_CONNECTION_IS_REDUNDANCY_GROUP)允许多个活动客户端连接。每个连接都有自己的事件队列。
当客户端连接关闭时,事件队列将被删除。当有多个客户端需要访问应用程序数据时,可以使用此模式。这种模式很容易使用,但它的缺点是,当没有客户端连接时,事件会丢失。

第三种模式(CS104_mode_MULTIPLE_REDUNDANCY_GROUPS)允许多个活动客户端连接,同时在没有客户端连接时保留事件。在这种模式下,客户端可以被分配到特定的冗余组。分配基于客户端的 IP 地址。一个冗余组可以同时具有多个连接,但这些连接中只有一个可以处于活动状态。激活的连接数量受冗余组数量的限制。每个冗余组都有一个专用的事件队列。

可以使用 CS104_Slave_setServerMode 功能设置服务器模式:

CS104_Slave_setServerMode(slave, CS104_MODE_MULTIPLE_REDUNDANCY_GROUPS);

CS104:定义多个冗余组

只有在使用服务器模式 CS104_MODE_MULTIPLE_Redundancy_groups 时,才需要显式创建冗余组。您可以将多个 IP 地址分配给一个冗余组。来自其中一个 IP 地址的传入连接将自动分配给该特定的冗余组。

当一个冗余组没有分配 IP 地址时,它作为一个“包罗万象”的组工作。这意味着所有未分配给其他组的传入连接都将最终在此组中。

如何定义多个冗余组的示例

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(串行)从机配置和设置

与主站侧类似,CS101 从站侧也可以被配置用于两种链路层模式之一(平衡非平衡)。CS101 从机由 CS101_slave 对象表示。

在创建 CS101_Slave 对象之前,需要一个 SerialPort 对象。SerialPort 对象表示串行接口及其配置。

SerialPort port = SerialPort_create(serialPort, 9600, 8, 'E', 1);

创建的 SerialPort 对象是 CS101_Slave_create 函数所必需的:

CS101_Slave slave = CS101_Slave_create(port, NULL, NULL, IEC60870_LINK_LAYER_UNBALANCED);

此函数具有以下签名:

CS101_Slave
CS101_Slave_create(SerialPort serialPort, LinkLayerParameters llParameters, CS101_AppLayerParameters alParameters, IEC60870_LinkLayerMode linkLayerMode)

可以选择指定链路层参数和应用层参数。如果应该使用默认值,则可以跳过这些参数(设置为 NULL)。最后一个参数指定是使用 平衡 模式还是使用 非平衡 模式。

对于串行从机,还需要设置链路层地址:

CS101_Slave_setLinkLayerAddress(slave, 1);

设置回调处理函数

在启动或运行服务器前,建议设置回调函数来处理从站事件。以下回调处理程序类型可用(有关函数签名的详细信息,请参阅 API 参考手册)。其中一些仅适用于 CS 104 服务器,而另一些则仅适用于 CS101 从站。

从机侧回调函数类型

回调类型 事件 CS 101 CS 104
CS101_InterrogationHandler 召唤请求 + +
CS101_CounterInterrogationHandler 逆向召唤请求 + +
CS101_ReadHandler 对单个信息对象的读取请求 + +
CS101_ClockSynchronizationHandler 收到时钟同步消息 + +
CS101_ResetProcessHandler 收到重置进程请求 + +
CS101_DelayAcquisitionHandler 收到延迟获取请求 + -
CS101_ASDUHandler ASDU 已接收,但未由其他回调处理程序处理 + +
CS101_ResetCUHandler 已接收到类型为重置 CU(通信单元)的链路层消息 + -
CS104_ConnectionRequestHandler 一个新的 TCP/IP 客户端尝试连接 - +

为 CS101 从机设置一些回调函数

/* 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 启动/停止服务端

配置服务器后,可以使用 CS104_Slave_start 函数启动服务器。此函数启动一个新的后台线程,以侦听传入的客户端连接。

CS104_Slave_start(slave);

要停用 IEC 60870-5-104 服务,可以使用 CS104_Slave_stop 函数停止服务器。

CS104_Slave_stop(slave);

自发或周期性的信息传输

对于服务器/从站的自发或周期性消息传输,API 用户必须分配一个代表单个 ASDU 的 CS101_ASDU 对象,将信息对象添加到 ASDU,并最终将 ASDU 放入传输队列。传输队列是一个 FIFO(先进先出)列表。如果队列已满,则最旧的消息将被删除,并由新添加的消息替换。只有当存在活动的客户端连接或工作的链接层连接时,才会发送消息。否则,消息将保留在队列中,直到连接被激活。

CS 104: 在 CS 104 从机中,队列大小由 CS104_slave_create 函数的 maxLowPrioQueueSize 参数确定。如果 maxLowPrioQueueSize 参数设置为零,则队列的大小将始终由 CONFIG_SLAVE_MESSAGE_queue_size 定义。第二个参数 maxHighPrioQueueSize 确定高优先级数据队列的大小。放入此队列的消息会绕过低优先级队列的消息。高优先级队列用于库回调处理程序中的请求响应。

要发送自发或周期性消息,必须执行以下步骤:

  1. Step: 创建一个新的 CS101_ASDU 实例(对周期性数据使用 CS101_COT_PERIODIC,对自发数据使用 CS101_COT_SPONTANEOUS
CS101_ASDU newAsdu = CS101_ASDU_create(alParameters, false, CS101_COT_PERIODIC, 0, 1, false, false);
  1. Step: 创建包含要发送的数据的新信息对象实例
InformationObject io = (InformationObject) MeasuredValueScaled_create(NULL, 110, scaledValue, IEC60870_QUALITY_GOOD);
  1. Step: 将新的信息对象添加到 ASDU
CS101_ASDU_addInformationObject(newAsdu, io);
  1. Step: 释放信息对象内存
InformationObject_destroy(io);
  1. Step: 将 ASDU 放入 2 类数据队列进行传输
CS101_Slave_enqueueUserDataClass2(slave, newAsdu);
  1. Step: 释放 ASDU 内存
CS101_ASDU_destroy(newAsdu);

注: 对于 CS 104,您必须在步骤 5 中使用 CS104_Slave_enqueueASDU 函数:

CS104_Slave_enqueueASDU(slave, newAsdu);

召唤请求处理

在服务器端,您应该使用 InterrogationHandler 回调函数来处理召唤请求。根据 QOI(Qualifier of question 问题限定符)值,您可以返回不同的信息对象。对于一个简单的系统来说,只处理站召唤请求(QOI=20)就足够了。QOI 值 21-36 用于召唤组(1-16)。由从机实现者将信息对象分配给召唤组。

根据该规范,服务器必须用 ACT_CON 响应来响应来自客户端的 ACTIVATION 请求,然后是包含信息对象的 ASDU,该信息对象具有用于站召唤的 CS101_COT_INTERROGATED_by_STATION 或表示相应召唤组的 COT(例如,用于召唤组 1 的 CS101-COT_INTERROGATED_by_group_1)。在发送所有信息对象之后,服务器必须发送具有 COT=_CS101_COT_ACTIVATION_TERMINATION_ 的初始召唤命令消息,以指示召唤数据的传输完成。

如何实现召唤处理程序的示例

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;
}

在召唤处理程序内部,IMasterConnection 接口可用于将召唤的数据发送回客户端/主机。在召唤处理程序中创建的 CS101_ASDUInformationObject 实例由用户负责,如果之前动态分配,则必须使用适当的函数(CS101_ASDU_destroyInformationObject_destroy)进行释放。

读数据指令 (C_RD_NA_1) 处理

客户端/主机可以使用读取命令 C_RD_NAME_1(102)来读取监控方向上的特定数据点的值。

在服务器/从端处理读取命令最方便的方法是实现回调函数类型 CS101_ReadHandler。读取处理程序可以通过用于 CS 104 服务器或 CS 101 从机的 CS104_Slave_setReadHandlerCS101_Slave_setReadHandler 函数安装。

在读取处理程序中,您必须发送相同的读取命令,但带有指示错误的 COT。或者,您必须为数据点创建正确类型的 ASDU,并将其发送回客户端/主机。当执行后者时,您必须使用 COT_CS101_COT_REQUEST 来指示消息是由读取请求引起的。

读取处理程序的简单实现(伪代码)

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)特定问题

服务端模式

服务器提供三种不同的模式:

默认模式(CS104_mode_SINGLE_REDUNDANCY_GROUP)仅允许单个活动客户端连接。活动客户端连接是发送 ASDU 的连接。所有其他连接都是备用连接。事件只有一个队列。当没有客户端连接或没有连接处于活动状态时,也会存储事件。

第二种模式(CS104_mode_CONNECTION_IS_REDUNDANCY_GROUP)允许多个活动客户端连接。每个连接都有自己的事件队列。当客户端连接关闭时,事件队列将被删除。当多个客户端访问应用程序数据时,必须使用此模式。

第三种模式(CS104_mode_MULTIPLE_REDUNDANCY_GROUPS)是最灵活的模式,允许定义特定的 REDUNDANCY 组。这些冗余组是共享同一事件队列的客户端组。对于每个冗余组,都有一个单独的事件队列实例。

可以使用 CS104_Slave_setServerMode 函数设置服务器模式。

CS104_Slave_setServerMode(slave, CS104_MODE_CONNECTION_IS_REDUNDANCY_GROUP);

限制客户端连接数量

可以使用 CS104_Slave_setMaxOpenConnections 函数限制客户端的数量。

CS104_Slave_setMaxOpenConnections(slave, 2);

在这种情况下,服务器将只允许两个并发的客户端连接。

设置本地端口和 IP 地址

IEC 60870-5-104 的默认 TCP 端口为 2404。可以使用 CS104_Slave_setLocalPort 函数更改端口。

CS104_Slave_setLocalPort(slave, 2405);

默认情况下,服务器侦听所有本地IP地址。使用 CS104_Slave_setLocalAddress 函数可以限制服务器监听单个本地 IP 地址。

CS104_Slave_setLocalAddress(slave, "192.168.1.50");

通过此设置,CS104 服务器将仅在指定 IP 地址为 192.168.1.50 的本地接口上侦听。

设置连接请求处理程序以限制访问并跟踪连接

CS104_ConnectionRequestHandler 可用于限制对服务器的访问。通过返回值,应用程序可以允许或拒绝客户端的连接尝试。

可以使用 CS104_Slave_setConnectionRequestHandler 函数设置 CS104_ConnectionRequestHandler。第二个参数是用户提供的任意对象,当处理程序被调用时,该对象将被传递给处理程序。如果不需要,可以将其设置为 NULL

CS104_Slave_setConnectionRequestHandler(slave, connectionRequestHandler, NULL);

在处理程序中,您可以根据允许的客户端白名单检查客户端 IP 地址,或者实现黑名单。

示例如何实现 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;
}

使用 TLS 创建安全连接

CS 104 标准也可以与 TLS 一起使用,以实现安全和经过验证的连接。

为了使用 TLS,必须配置相关的参数、证书和私钥。

配置存储在 TLSConfiguration 对象中。可以使用 TLSConfiguration_create 函数创建一个新的配置对象。

示例如何创建支持 TLS 的 CS 104 从机

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 特定主题

调试输出

控制台的调试输出可以通过将 CONFIG_debug_output 设置为 1 来启用。默认情况下启用调试输出。可以使用函数 Lib60870_enableDebugOutput 禁用调试输出。调试输出函数的默认实现将打印到控制台(使用 printf)。如果需要重定向输出,最简单的方法是更改 lib60870_common.c 中调试输出函数 lib60870_debug_print 的实现。

大端平台

该库包含一个 C 头文件,用于在使用 GCC 编译器时确定平台字节顺序(src/inc/internal/platform_endian.h)。这取决于 C 编译器提供的定义。在一些较旧的大端平台上,如 PowerPC 或 Coldfire,对不同的编译器可能会失败。您可能需要在编译库代码时定义

PLATFORM_IS_BIGENDIAN 1

例如,当平台字节序是大端时,在 GCC 命令行中加入

-DPLATFORM_IS_BIGENDIAN=1

库编译时的配置选项

一些配置选项在库代码的编译时是固定的。这些选项可以在文件 lib60870_config.h 中找到。

编译时选项包括对特定 CS 104 冗余模式的支持、对线程和信号量的支持(当库使用线程时需要)、CS 104 从机的最大 TCP 连接数等。

参考信息

支持的消息类型

此库支持以下 ASDU (应用服务数据单元) 类型。

IEC 60870-5-101/104 消息类型

消息类型 描述 C C#
M_SP_NA_1(1) 单点信息 (BOOLEAN) + +
M_SP_TA_1(2) 带 CP24Time2a 时标的单点信息 (BOOLEAN) + +
M_DP_NA_1(3) 双点信息 (ON/OFF/瞬态) + +
M_DP_TA_1(4) 带 CP24Time2a 时标的双点信息 (ON/OFF/瞬态) + +
M_ST_NA_1(5) 步长位置信息 (-64 ... 63, 瞬态) + +
M_ST_TA_1(6) 带 CP24Time2a 时标的步长位置信息 (-64 ... 63, 瞬态) + +
M_BO_NA_1(7) 32 位位串 (32 位位串) + +
M_BO_TA_1(8) 带 CP24Time2a 时标的 32 位位串 (32 位位串) + +
M_ME_NA_1(9) 归一化值 (-1.0 ... +1.0) + +
M_ME_TA_1(10) 带 CP24Time2a 时标的归一化值 (-1.0 ... +1.0) + +
M_ME_NB_1(11) 标度化值 (-32768 ... +32767) + +
M_ME_TB_1(12) 带 CP24Time2A 时标的标度化值 (-32768 ... +32767) + +
M_ME_NC_1(13) 短测量值 (FLOAT32) + +
M_ME_TC_1(14) 带 CP24Time2a 时标的短测量值 (FLOAT32) + +
M_IT_NA_1(15) 综合累计值 (带质量指标的 INT32) + +
M_IT_TA_1(16) 带 CP24Time2a 时标的综合累计值 (带质量指标的 INT32) + +
M_EP_TA_1(17) 继电保护装置事件 + +
M_EP_TB_1(18) 继电保护装置成组启动事件 + +
M_EP_TC_1(19) 继电保护装置承租输出电路信息 + +
M_PS_NA_1(20) 具有状态变位检出的成组单点信息 + +
M_ME_ND_1(21) 不带品质描述词的归一化测量值 (-1.0 ... +1.0) + +
M_SP_TB_1(30) 带 CP56Time2a 时标的单点信息 (BOOLEAN) + +
M_DP_TB_1(31) 带 CP56Time2a 时标的双点信息 (ON/OFF/瞬态) + +
M_ST_TB_1(32) 带 CP56Time2a 时标的步位置信息 (-64 ... 63, 瞬态) + +
M_BO_TB_1(33) 带 CP56Time2a 时标的 32 位位串 (32 位位串) + +
M_ME_TD_1(34) 带 CP56Time2a 时标的归一化值 (-1.0 ... +1.0) + +
M_ME_TE_1(35) 带 CP56Time2a 时标的标度化值 (-32768 ... +32767) + +
M_ME_TF_1(36) 带 CP56Time2a 时标的短测量值 (FLOAT32) + +
M_IT_TB_1(37) 带 CP56Time2a 时标的综合累积量 (带质量指标的 INT32) + +
M_EP_TD_1(38) 带 CP56Time2a 时标的继电保护装置 + +
M_EP_TE_1(39) 带 CP56Time2a 时标的继电保护装置成组启动事件 + +
M_EP_TF_1(40) 带 CP56Time2a 时标的继电保护装置成组输出电路信息 + +
C_SC_NA_1(45) 单点命令 (BOOLEAN) + +
C_DC_NA_1(46) 双点命令 (ON/OFF/瞬态) + +
C_RC_NA_1(47) 步调节命令 + +
C_SE_NA_1(48) 设点命令,归一化值 (-1.0 ... +1.0) + +
C_SE_NB_1(49) 设点命令,标度化值 (-32768 ... +32767) + +
C_SE_NC_1(50) 设点命令,短浮点值 (FLOAT32) + +
C_BO_NA_1(51) 位串命令 (32位位串) + +
C_SC_TA_1(58) 带 CP56Time2a 时标的单点命令 (BOOLEAN) + +
C_DC_TA_1(59) 带 CP56Time2a 时标的双点命令 (ON/OFF/瞬态) + +
C_RC_TA_1(60) 带 CP56Time2a 时标的步调节命令 + +
C_SE_TA_1(61) 带 CP56Time2a 时标的设点命令,归一化值 (-1.0 ... +1.0) + +
C_SE_TB_1(62) 带 CP56Time2a 时标的设点命令,标度化值 (-32768 ... +32767) + +
C_SE_TC_1(63) 带 CP56Time2a 时标的设点命令,短浮点值 (FLOAT32) + +
C_BO_TA_1(64) 带 CP56Time2a 时标的位串命令 (32位位串) + +
M_EI_NA_1(70) 初始化结束 + +
C_IC_NA_1(100) 召唤命令 + +
C_CI_NA_1(101) 反向召唤命令 + +
C_RD_NA_1(102) 读数据命令 + +
C_CS_NA_1(103) 时钟同步命令 + +
C_TS_NA_1(104) 测试命令 + +
C_RP_NA_1(105) 重置进程命令 + +
C_CD_NA_1(106) 延迟采集命令 + +
C_TS_TA_1(107) 带 CP56Time2a 时标的测试命令 + +
P_ME_NA_1(110) 测量值参数,归一化值 + +
P_ME_NB_1(111) 测量值参数,标度化值 + +
P_ME_NC_1(112) 测量值参数,短浮点数 + +
P_AC_NA_1(113) 激活参数 + +
F_FR_NA_1(120) 文件准备就绪 + +
F_SR_NA_1(121) 节准备就绪 + +
F_SC_NA_1(122) 召唤/选择目录/文件/节 + +
F_LS_NA_1(123) 最后段/节 + +
F_AF_NA_1(124) 确认文件/节 + +
F_SG_NA_1(125) 文件段 + +
F_DR_TA_1(126) 文件目录 + +
F_SC_NB_1(127) 查询日志 + +

CS 104 特定参数

以下参数存储在 CS104_ConnectionParameters 对象中.

IEC 60870-5-104 参数

参数 描述
k I 格式的未确认 APDU 的数量。发件人将在收到 k 条未经确认的 I 消息后停止传输。
w I 格式的未确认 APDU 的数量。接收方将在 w 条消息后确认最新消息
t0 建立连接超时(秒)
t1 以 I/U 格式传输的 APDU 超时时间(以秒为单位)。当超时时间到达前报文未经确认,连接将被关闭。发送者使用此项来确定接收器是否未能确认消息。
t2 确认消息超时时间(秒)。接收器使用它来确定必须发送消息确认的时间。
t3 在连接空闲的情况下开始发送测试报文的时间

英文原文:https://github.com/mz-automation/lib60870
API参考:https://support.mz-automation.de/doc/lib60870/latest/index.html