补充总召唤的广播地址,电度召唤+时钟同步业务的实现
CI / L0 静态门禁 (pre-commit) (push) Failing after 44s
CI / L0.5 主机侧单测 (MSVC) (push) Failing after 42s

This commit is contained in:
STF
2026-10-29 03:20:19 -07:00
parent 4c55544f05
commit 7596f3371a
267 changed files with 130894 additions and 1436 deletions
+269 -45
View File
@@ -1,8 +1,8 @@
# IEC104 函数 API 接口汇总(按实现文件)
依据 2026-10-25 当前工作区的协议库源码及对应头文件整理,按实现所在的 `.c` 文件分节,函数按文件内的定义顺序排列。范围不包含示范程序、测试程序及其业务辅助函数。
依据当前工作区的协议库源码及对应头文件整理,按实现所在的 `.c` 文件分节。范围不包含示范程序、测试程序及其业务辅助函数;2026-10-29补充独立Type 103时钟同步模块。
共覆盖 **21 个实现文件、352 个模块接口和 7 个内部跨文件协作函数(Link 5 个、信息对象层 2 个)**。表中标有“内部协作”的函数不属于业务 API;文件内的 `static` 辅助函数、宏和类型定义不作为函数接口列出。
共覆盖 **24 个实现文件**,其中内部跨文件协作函数为Link 5个、信息对象层2个、召唤引擎2个。表中标有“内部协作”的函数不属于业务 API;文件内的 `static` 辅助函数、宏和类型定义不作为函数接口列出,各模块入口以对应小节和声明头文件为准。
模块接口并不都供业务直接调用:子站业务入口使用 `IEC104_Slave.h`,TcpServer 由 Slave 持有,Connection、SendManager 及其底层接口供相应实现层协作。运行顺序见 [完整调用流程](IEC104_完整调用流程.md)。各文件小节先说明使用约定,再列功能表和可展开的完整函数原型;原型中的参数名、类型和返回类型与声明头文件一致。
@@ -13,9 +13,12 @@
| 要完成的操作 | 应使用的接口 | 前提与结果 |
| --- | --- | --- |
| 创建和启动子站 | [Slave_Create / Config_Default / Init](#file-19) | Create 返回不透明指针;Init 成功后可以监听和缓存 NORMAL |
| 共用站点与业务路由 | [Slave_Add_Station / Get_Station](#station-registry) | 调用方提供节点,按CA取得业务context;异步持有用Acquire/Release,固定集合用Lock/Unlock |
| 独立循环驱动 | [Slave_Poll / NextEvent](#file-19) | Poll 推进网络及协议,NextEvent 按 FIFO 交付通知,须逐条读到 EMPTY |
| 接入已有事件循环 | [Slave_Prepare / Process](#file-19) | Prepare 导出关注条件;按原 token 汇集就绪后 Process,再读取事件 |
| 接入站总召/分组总召 | [Interrogation](#file-21) | Handle_Event接收激活/撤销及状态通知,Process推进并检查期限;NextEvent取业务事件,以Reply_Begin/Submit_Batch/Complete_Data显式应答 |
| 接入计数量召唤 | [CounterInterrogation](#file-23) | 独立创建,复用驱动;读取用Reply_Begin和数据接口,冻结/复位用Execute_Begin |
| 接入时钟同步 | [ClockSynchronization](#file-24) | Handle_Event校验保存,Process推进应答及期限,只有显式Execute_Next调用一次应用动作;确认回显原时标 |
| 解析主站请求 | [Asdu_Get_Data / Parse / Get_Element](#file-16) | 先检查 I_FRAME 及 `asdu_result`,再做精确解析与业务条件检查 |
| 回复任意 ASDU | [MasterConnection_Send_ASDU](#file-18) | 使用 `&event.connection`;ASDU 可以是单独构造的对象 |
| 回复激活确认、终止 | [MasterConnection_Send_ACT_CON / Send_ACT_TERM](#file-18) | 修改传入 ASDU 的 COT/P/N 后提交,执行时机由业务决定 |
@@ -37,6 +40,7 @@
| 类型或头文件 | 职责与所有权 |
| --- | --- |
| `IEC104_Slave_t` / `IEC104_Slave.h` | 不透明服务句柄,Create/Destroy 管理静态槽;内部持有私有 TcpServer 指针 |
| `IEC104_Station_t` | 调用方拥有的固定地址节点,Add/Remove管理注册关系;CA、context及引用由Slave统一管理,字段仅库可改写 |
| `IEC104_Interrogation_t` / `IEC104_Interrogation.h` | 不透明总召业务句柄,借用Slave;管理请求副本、分批、撤销、期限和WAIT进度,不拥有网络服务 |
| `IEC104_SlaveEventIdentity_t` | Slave校验后交付的来源身份,包含服务句柄、实例编号和连接代次;可按值保存,不借用事件原文;字段只读 |
| `IEC104_TcpServer_t` / `IEC104_TcpServer.h` | 不透明底层 TCP 服务,持有连接、队列、帧缓冲、记录和事件 FIFO;可独立接入,但应用须自行管理原请求身份 |
@@ -104,7 +108,7 @@
`IEC104_APP_LAYER_PARAMETERS_DEFAULT` 位于 [IEC104_Types.h](../../104/IEC104_Types.h),值为 `{1, 1, 2, 0, 2, 3, IEC104_MAX_ASDU_LEN}`,依次为 Type ID、VSQ、COT 宽度、默认 OA、CA 宽度、IOA 宽度及最大 ASDU 长度。标准 104 为 COT/CA/IOA=2/2/3,ASDU 最长 249 字节,APCI 6 字节,APDU 最长 255 字节。共用 ASDU 模块也支持其他布局,104 发送适配器会拒绝不兼容参数。
`IEC104_CA_BROADCAST`也位于IEC104_Types.h,固定值为`0xFFFFU`,表示104两字节CA的广播地址。它不是配置项;总召的config.common_address仍须使用1..65534的实际站地址。共用Asdu_Set_CA只负责字段编码,广播请求的接收与应答规则由对应业务模块决定。
`IEC104_CA_BROADCAST`也位于IEC104_Types.h,固定值为`0xFFFFU`,表示104两字节CA的广播地址。它不是配置项;站点注册及总召的兼容common_address配置使用1..65534的实际站地址。绑定station时总召从节点取得CA。共用Asdu_Set_CA只负责字段编码,广播请求的接收与应答规则由对应业务模块决定。
`Slave_Config_Default` 填入监听 `0.0.0.0:2404`、k=12、w=8、t1/t2/t3=15000/10000/20000 毫秒、本地发送与 STOP 总期限各 15000 毫秒、HIGH 配额 0(严格优先)。配置要求 k/w 在 1~32767,期限非零且 t2<t1;本地发送与 STOP 期限是独立字段,修改 t1 不会联动更新它们。端口 0 可由系统选择,实际端口从 Get_Status 获取。
@@ -129,7 +133,7 @@ NORMAL 允许覆盖最旧原文;事件 FIFO 不覆盖。HIGH 清除、NORMAL
CONNECTED 先于同代次其他通知,DISCONNECTED 最后;实际启停变化逐条保存,初始 STOPPED、同状态重复 ACT、CON 写完或单纯断线不额外产生启停事件。普通空间不足时回执可能暂留 Manager,晚于随后入队的启停事件交付;FIFO 表示入队顺序,不是所有网络事实的严格时间排序。
`Get_Status` 的 `connected` 表示当前 TCP 是否存在;`connection_status_valid` 为 1 才读取 connection,`asdu_queue_status_valid` 为 1 才读取两级队列。`pending_asdu_count` 只数尚未交接组帧的原文,`stored_asdu_count` 数仍保留的原文;二者都不覆盖全部在途帧。`pending_send_records` 数尚未转为 RECORD 通知的记录,`pending_event_count` 数已入 FIFO 的未读通知。事件中的连接状态则用 `data.connection.status_valid` 判断,不能与当前快照混用。
`Get_Status` 的 `connected` 表示当前 TCP 是否存在;`connection_status_valid` 为 1 才读取 connection,`asdu_queue_status_valid` 为 1 才读取两级队列。`pending_asdu_count` 只数尚未交接组帧的原文,`stored_asdu_count` 数仍保留的原文;二者都不覆盖全部在途帧。`pending_send_records` 数尚未转为 RECORD 通知的记录,`pending_event_count` 数已入 FIFO 的未读通知,`pending_state_event_count` 只数其中未读的 ACTIVATED/DEACTIVATED。后者为0不能证明过去没有发生已消费的STOP/START。事件中的连接状态则用 `data.connection.status_valid` 判断,不能与当前快照混用。
## 文件索引
@@ -151,9 +155,12 @@ CONNECTED 先于同代次其他通知,DISCONNECTED 最后;实际启停变化
- [IEC60870_Asdu.c](#file-16):10 个函数。
- [IEC60870_InformationObjects.c](#file-17):139 个函数(含 2 个内部协作函数)。
- [IEC60870_Slave.c](#file-18):4 个函数。
- [IEC104_Slave.c](#file-19):18 个函数。
- [IEC104_Slave.c](#file-19):41 个函数。
- [IEC60870_Codec.c](#file-20):2 个函数。
- [IEC104_Interrogation.c](#file-21):12 个函数。
- [IEC104_Interrogation.c](#file-21):11 个模块接口及2个内部协作函数;总召Create已移至种类文件。
- [IEC104_GeneralInterrogation.c](#file-22):1 个函数(保留旧Create名称)。
- [IEC104_CounterInterrogation.c](#file-23):2 个函数。
- [IEC104_ClockSynchronization.c](#file-24):8 个函数。
<a id="file-1"></a>
@@ -350,6 +357,7 @@ extern int IEC104_Build_Uframe(uint8_t *buf, int function);
| `IEC104_Connection_Peek_Received_Frame` | 读取已完成链路受理的 I 帧借用视图及 receive_serial;不重复处理序号或 ACK,FAILED 阶段仍可用于回收。 |
| `IEC104_Connection_Release_Received_Frame` | 按连接代次和 receive_serial 释放已受理 I 帧的接收空间;用于上层成功复制事件后,不再次提交链路事件。 |
| `IEC104_Connection_Submit_Response` | 校验当前连接和应答准入,尝试直接准备应答 I 帧,暂不能准备时复制到 HIGH;成功表示原文已接手,不执行实际网络发送。 |
| `IEC104_Connection_Try_Submit_Response` | 同一会话和期限检查下仅尝试直接准备;有待发HIGH/NORMAL、缓冲或窗口背压以及到期协议工作时WAIT,不缓存ASDU。 |
| `IEC104_Connection_Prepare_Next_Send` | 推进保留帧、控制义务及本地期限,再取得当前待写字节;无当前帧时才选择 ASDU,确认 t1 已到期时暂停新 I 帧编号。 |
| `IEC104_Connection_Get_Record_Wait` | 在 EOF 收尾的发送 WAIT 后,只读判断是否仅因可回收的已确认记录占满容量而阻塞,供 TcpServer 决定是否等待事件空间后续发。 |
| `IEC104_Connection_Handle_Send_Progress` | 提交真实写入增量,经 Manager 完成整帧记账,并更新活动、接收确认、TESTFR 或 STOP 计时;随后重试保留帧和控制流程。 |
@@ -365,7 +373,7 @@ extern int IEC104_Build_Uframe(uint8_t *buf, int function);
<!-- library-api-prototypes:4 -->
<details>
<summary>完整函数原型(21 个;参数和返回类型见声明)</summary>
<summary>完整函数原型(22 个;参数和返回类型见声明)</summary>
```c
#include "IEC104_Connection.h"
@@ -404,6 +412,11 @@ extern IEC104_ConnectionResult_t IEC104_Connection_Submit_Response(
const uint8_t *asdu, uint16_t asdu_length, uint64_t now_ms,
IEC104_SendBufferHeadInfo_t *head_info);
extern IEC104_ConnectionResult_t IEC104_Connection_Try_Submit_Response(
IEC104_Connection_t *connection, uint64_t connection_generation,
const uint8_t *asdu, uint16_t asdu_length, uint64_t now_ms,
IEC104_SendBufferHeadInfo_t *head_info);
extern IEC104_ConnectionResult_t IEC104_Connection_Prepare_Next_Send(
IEC104_Connection_t *connection, uint64_t connection_generation, uint64_t now_ms,
IEC104_SendBufferHeadInfo_t *head_info);
@@ -955,6 +968,7 @@ extern void IEC104_SendBuffer_Abort(IEC104_SendBuffer_t *buffer);
| `IEC104_SendManager_Handle_Testfr_Con` | 通过 Manager 统一接收 TESTFR_CON,结束匹配的主动探测;无需预留控制槽,当前未等待 CON 时返回 IGNORED。 |
| `IEC104_SendManager_Prepare_Next_Send` | 优先取得已有帧的剩余字节;无已有帧时按 HIGH/NORMAL 优先级及配额选择 ASDU,构帧、建立发送记录并完成队列交接。 |
| `IEC104_SendManager_Submit_Response` | 只在 STARTED/IDLE 接手应答;条件允许时直接准备 HIGH I 帧,否则缓存到 HIGH 队列,返回已准备、已入队或尚未接手。 |
| `IEC104_SendManager_Try_Submit_Response` | 仅在发送缓冲空闲、HIGH/NORMAL无未提交条目且窗口和记录容量允许时准备I帧;WAIT不入队、不分配N(S)。 |
| `IEC104_SendManager_Handle_Send_Progress` | 登记实际写入增量;整帧写完后按原帧类型提交 Link 完成、记录 I 帧本地完成事实,并释放发送缓冲槽。 |
| `IEC104_SendManager_Handle_Send_Failure` | 对匹配的未完成队首提交最终发送失败或本地发送超时,调用适用的链路失败完成接口并返回 FATAL;保留记录供收尾。 |
| `IEC104_SendManager_Handle_I_S_Frame` | 把收到的 I/S 帧交给 Link 校验序号和累计 ACK,登记全部新确认事实,核减 HIGH 未确认数,并释放 NORMAL 原文或覆盖记账。 |
@@ -973,7 +987,7 @@ extern void IEC104_SendBuffer_Abort(IEC104_SendBuffer_t *buffer);
<!-- library-api-prototypes:12 -->
<details>
<summary>完整函数原型(25 个;参数和返回类型见声明)</summary>
<summary>完整函数原型(26 个;参数和返回类型见声明)</summary>
```c
#include "IEC104_SendManager.h"
@@ -1016,6 +1030,11 @@ extern IEC104_SendManagerResult_t IEC104_SendManager_Submit_Response(
const uint8_t *asdu, uint16_t asdu_length, uint64_t now_ms,
IEC104_SendBufferHeadInfo_t *head_info);
extern IEC104_SendManagerResult_t IEC104_SendManager_Try_Submit_Response(
IEC104_SendManager_t *manager, uint64_t connection_generation,
const uint8_t *asdu, uint16_t asdu_length, uint64_t now_ms,
IEC104_SendBufferHeadInfo_t *head_info);
extern IEC104_SendManagerResult_t IEC104_SendManager_Handle_Send_Progress(
IEC104_SendManager_t *manager, IEC104_SendFrameId_t frame_id,
uint16_t bytes_written_now, uint64_t now_ms, IEC104_LocalSendCompletion_t *completion);
@@ -1101,6 +1120,7 @@ Prepare 的 socket 仅供注册,等待前落实 pending_event_count/need_proce
| `IEC104_TcpServer_NextEvent` | 按 FIFO 复制并取出最早一条建连、启用、停用、I 帧、发送记录或断开事件;副本无需释放,Close 后仍可继续读取。 |
| `IEC104_TcpServer_Enqueue_Event` | 把主动上报 ASDU 复制到服务级 NORMAL 队列,支持首次连接前及断线期间缓存;空间不足覆盖最旧原文,不立即写 socket。 |
| `IEC104_TcpServer_Send_Response` | 按原请求事件的连接代次提交应答;仅启动状态接手,条件允许时直接准备并尝试一次非阻塞发送,否则入 HIGH,成功不表示已写完或已 ACK。 |
| `IEC104_TcpServer_Try_Send_Response` | 按原代次尝试不排队提交;WAIT未接手,OK已固定帧顺序,之后沿原部分写入和ACK路径推进;FATAL不可直接重放。 |
| `IEC104_TcpServer_Get_Remote_Address` | 读取当前主站的缓存 IPv4 地址、源端口及连接代次副本;不查询 socket,无当前连接时拒绝。 |
| `IEC104_TcpServer_Close_Connection` | 立即关闭指定代次的当前主站并交付收尾事件;保留监听和现存 NORMAL,清空 HIGH,不等待 STOPDT、发送完成或 ACK。 |
| `IEC104_TcpServer_Get_Status` | 读取监听端口、当前连接、最近会话、未读事件、HIGH/NORMAL 队列和接入重试诊断等快照;不消费数据或推进协议。 |
@@ -1108,7 +1128,7 @@ Prepare 的 socket 仅供注册,等待前落实 pending_event_count/need_proce
<!-- library-api-prototypes:13 -->
<details>
<summary>完整函数原型(14 个;参数和返回类型见声明)</summary>
<summary>完整函数原型(15 个;参数和返回类型见声明)</summary>
```c
#include "IEC104_TcpServer.h"
@@ -1142,6 +1162,10 @@ extern IEC104_TcpSubmitResult_t IEC104_TcpServer_Send_Response(
IEC104_TcpServer_t *server, uint64_t generation,
const uint8_t *asdu, uint16_t asdu_length);
extern IEC104_TcpSubmitResult_t IEC104_TcpServer_Try_Send_Response(
IEC104_TcpServer_t *server, uint64_t generation,
const uint8_t *asdu, uint16_t asdu_length);
extern IEC104_TcpResult_t IEC104_TcpServer_Get_Remote_Address(
const IEC104_TcpServer_t *server, IEC104_TcpPeerInfo_t *peer, IEC104_TcpError_t *error);
@@ -1961,7 +1985,7 @@ const IEC60870_AppLayerParameters_t * IEC60870_MasterConnection_Get_Application_
实现:[IEC104_Slave.c](../../104/IEC104_Slave.c);声明:[IEC104_Slave.h](../../104/IEC104_Slave.h)。
Slave是业务统一入口,持有静态池TCP服务。NextEvent自动安装共用应答接口;业务仅管理事件存储,不手动绑定TCP句柄或generation。延迟任务用Copy_Event复制,自引用不得直接赋值搬移。确认/终止失败仍保留已修改的ASDU。
Slave是业务统一入口,持有静态池TCP服务。NextEvent自动安装共用应答接口;业务仅管理事件存储,不手动绑定TCP句柄或generation。需要完整事件的延迟任务用Copy_Event复制,自引用不得直接赋值搬移;只需部分请求数据时可使用下述纯值身份入口。确认/终止失败仍保留已修改的ASDU。
调用约定:NextEvent 的 READY 交付原通知,I_FRAME 还需检查 asdu_result;完整对象合法性再由 Asdu_Parse 验证。Copy_Event 返回 true/false,失败不修改目标;它修复自引用、保留历史提交结果,不提交报文或改变请求身份。Get_Last_Submit_Result 在首次提交前、空/无效接口时为 ERROR。
@@ -1971,12 +1995,71 @@ Slave是业务统一入口,持有静态池TCP服务。NextEvent自动安装共
Close_Connection 按事件保存的原服务实例及 generation 定位:其他实例为 INVALID_ARGUMENT,当前无连接为 NOT_CONNECTED,已有新连接时旧代次为 STALE_GENERATION。Get_Application_Layer_Parameters 对有效已创建句柄即可使用,不要求主站在线;返回参数不表示应答准入。
需要精简延迟任务时,可只保存Get_Event_Identity取得的纯值身份及业务需要的请求字节,随后使用Send_Response/Try_Send_Response提交独立ASDU,或Close_Connection_By_Identity关闭原连接。三个身份字段保持原样,self须为原服务;发送拒绝旧实例/旧代次,关闭也不能作用于新连接。Send_Response沿普通HIGH路径,Try_Send_Response保持不排队语义;两者直接返回提交结果,不修改某个事件的最近结果。调用方仍须先处理通知并检查撤销、期限及业务状态,身份本身不表示请求仍在执行。
Init 失败不销毁对象;Create 后、Init 失败后和 Close 后仍可 Get_Status。Destroy 成功才丢弃句柄;运行中返回 INVALID_STATE,未读通知返回 EVENTS_PENDING。旧裸指针不能识别静态地址复用,必须由应用停止使用;旧事件则通过实例编号及原 generation 防止误发到新会话。
<a id="reject-request"></a>
### 同步拒绝当前请求
`IEC104_Slave_Reject_Request(self, event, cause)`用于分派当前I帧时生成原文错误应答。`cause`只接受44..47,分别表示未知类型、COT、CA和IOA;接口不代替业务决定拒绝原因。仅复制当前ASDU并替换COT、置P/N=1,保留TEST、OA、CA、VSQ和全部对象字节。不修改输入事件、ASDU视图或该事件的`Get_Last_Submit_Result`,调用方直接检查返回值。原文副本沿已有普通发送入口直接接手或进入HIGH,不新增待应答队列,不做内容去重,不使用NORMAL。
调用限于当前`NextEvent`交付后的同步分派,并且必须在读取后续事件之前完成。原服务/会话及启停状态仍须有效;未读ACTIVATED/DEACTIVATED会使接口返回ERROR,后续仅有普通I帧或RECORD不要求先读空。`pending_state_event_count`只反映未读启停通知,不能发现应用已经消费的历史STOP/START;**禁止保存旧事件、读完后续通知后再调用本接口**。需要延迟应答时使用`Copy_Event`或保存原身份,自行处理STOP取消、撤销和期限,再经`Check_Request`及普通发送入口提交;最终READY也不能恢复已经取消的旧任务。
| 返回值 | 含义及调用方处理 |
|---|---|
| `IEC104_TCP_SUBMIT_OK` | 已复制接手,不代表写完或收到ACK;不得重复提交 |
| `IEC104_TCP_SUBMIT_WAIT` | HIGH剩余容量不足,本条未接手;示例丢弃新请求并累计overloads、dropped_requests,保留连接及已接手应答 |
| `IEC104_TCP_SUBMIT_ERROR` | 参数/事件/cause非法、原会话失效或存在未读启停通知;不按容量不足计数,继续处理通知及相应失效原因 |
| `IEC104_TCP_SUBMIT_FATAL` | 发送故障,可能已经写出字节;不得重试,继续排空RECORD/DISCONNECTED等收尾通知 |
示例在总召、计数量及后续业务均未接手后显式调用此接口;Slave不会根据类型自动吞掉或拒绝NextEvent交付的请求。HIGH真正满时允许丢弃本条错误应答,因此不承诺所有负载下都有响应;已接手条目不因新兜底请求到达而被覆盖或主动断链。
<a id="station-registry"></a>
### 共用站点表
一个Slave可以注册多个逻辑站点,各业务按CA取得同一节点及应用context。组织方式参考[iec104-python的Server站点管理](https://github.com/Fraunhofer-FIT-DIEN/iec104-python/blob/main/src/Server.cpp):按CA查找并拒绝重复注册。本实现采用调用方节点链表,不使用C堆或为每个站点复制通信资源;没有额外的固定站点数量上限,实际受1..65534地址范围与可用内存约束。
每个`IEC104_Station_t`须零初始化,注册成功后直到`Is_Reusable`返回true均须保持节点及context的地址和寿命稳定,不得赋值搬移、清零或直接改字段。CA及context在注册时指定;context内容、IOA点表、权限和业务状态归应用管理。同一Slave内已注册CA唯一,不同Slave可以注册相同CA,但不能共用同一个节点。查找和注册为线性扫描,适合当前少量站点规模。
`Get_Station`只查具体的已注册站点,0、65535及未知CA返回NULL。地址检查与业务分派分离的思路亦见[lib60870的CA许可检查](https://github.com/mz-automation/lib60870/blob/master/lib60870-C/src/iec60870/cs104/cs104_slave.c)。NextEvent不自动丢弃或应答报文:业务负责对象布局、COT、CA、权限及广播规则。总召开启`use_station_registry`后已接入按CA查找和广播遍历,其他业务可复用同一基础接口。
延迟业务保存节点前调用`Acquire_Station`,清理完成后调用`Release_Station`。Remove立即使节点退出CA查找、计数和普通遍历;已有引用仍可读取原CA/context,最后一个引用和快照归还后才清零节点。Remove返回OK不等于可释放存储,须检查`Is_Reusable`。同CA可立即注册另一个节点;`Get_Id/Get_Station_By_Id`以不复用的注册编号定位原节点,避免旧事务查到替代站点。引用仍不能代替原连接身份检查。
`StationSnapshot_Begin/Next/End`提供跨循环遍历:Begin记录注册编号上界,Next跳过已删除的未来站点,并排除后来新增或同CA重建的节点;不复制或冻结IOA和点值。快照须零初始化、地址稳定、配对End,Next返回NULL也不自动结束。任一快照活动时退役节点延迟回收,因此调用方不得提前释放这些节点。需要跨End保留Next返回值时另行Acquire。`Lock_Stations/Unlock_Stations`仍作为显式冻结集合的兼容接口,锁定期间Add/Remove返回BUSY;总召广播不使用集合锁,合法多目标请求在调用方状态区中持有各节点引用,错误回复遍历使用快照。所有操作在同一事件循环串行执行,不是线程锁。
Close/Init不改变站点表。Slave_Destroy要求站点全部Remove、引用及快照归还、集合锁解除,否则返回INVALID_STATE。Remove不释放应用context,也不撤回已接手报文;已选中的总召可持原引用完成。若业务要求立即停止该事务,应先显式取消并处理FINISHED;不能靠删除再注册同一CA清空旧报文。
`IEC104_StationResult_t`须显式比较:OK成功;INVALID_ARGUMENT参数/配对非法;DUPLICATE_CA已有注册CA;ALREADY_REGISTERED节点仍由某服务持有;NOT_FOUND节点不属于本服务或重复删除;BUSY显式集合锁阻止修改;LIMIT引用/快照/锁计数或注册编号耗尽。失败不改变注册关系;零计数Release/Unlock返回INVALID_ARGUMENT。
Windows x64 GCC测量(默认4任务、4业务事件):节点40字节、快照32字节、Slave40字节、总召1240字节、任务168字节、紧凑请求40字节。多目标广播另需调用方提供每站48字节状态区,13站为624字节;不扩大任务或事件队列。HIGH/NORMAL容量及覆盖策略不变,此数值不是目标板RAM实测。
后续业务的接入顺序为:解析请求 → 按CA查站点 → 检查该站点的业务权限 → 如需延迟则Acquire → 用context内的IOA点表处理 → 沿原连接应答 → 完成清理后Release。主动上报使用所属节点的CA构造ASDU;不同CA下相同IOA属于不同点。
| 函数 API 接口名 | 功能作用 |
|---|---|
| `IEC104_Slave_Add_Station` | 借用零初始化节点并按CA注册到末尾;拒绝重复CA,不分配堆。 |
| `IEC104_Slave_Remove_Station` | 立即退出路由,引用和快照归还后回收;OK后仍须检查Is_Reusable。 |
| `IEC104_Slave_Get_Station` | 按具体CA取得借用节点;未知CA或广播返回NULL。 |
| `IEC104_Slave_Get_Station_Count` | 取得当前注册数量,无效服务返回0。 |
| `IEC104_Slave_Get_Station_By_Id` | 按注册编号查当前或被保留的退役节点,不误取同CA的新站。 |
| `IEC104_Station_Get_Id` | 取得注册编号,NULL或已回收节点返回0。 |
| `IEC104_Station_Is_Registered` | 是否仍参与CA查找;NULL返回false。 |
| `IEC104_Station_Is_Reusable` | 节点是否已回收,可由调用方复用/释放;NULL返回false。 |
| `IEC104_Slave_StationSnapshot_Begin` | 开始一次不阻止增删的遍历,固定注册编号上界。 |
| `IEC104_Slave_StationSnapshot_Next` | 取得下一个未删除的原有站点,借用指针。 |
| `IEC104_Slave_StationSnapshot_End` | 归还快照并尝试回收退役节点。 |
| `IEC104_Slave_Next_Station` | 按注册顺序遍历;current=NULL取首站,末尾/外来节点返回NULL。 |
| `IEC104_Slave_Lock_Stations` | 固定当前集合,阻止增删与服务销毁,不冻结点值。 |
| `IEC104_Slave_Unlock_Stations` | 归还一次集合借用,零计数释放报错。 |
| `IEC104_Slave_Acquire_Station` | 增加节点引用,延迟删除后的存储回收;拒绝外来节点及溢出。 |
| `IEC104_Slave_Release_Station` | 归还一次节点引用,零计数释放报错。 |
| `IEC104_Station_Get_Common_Address` | 取得CA,退役但被保留的节点仍可读;NULL或已回收时返回0。 |
| `IEC104_Station_Get_Context` | 取得应用context,退役但被保留的节点仍可读;NULL或已回收时返回NULL。 |
| `IEC104_Slave_Create` | 从静态池取得子站门面及其私有TCP服务;无malloc、无网络操作。 |
| `IEC104_Slave_Destroy` | 归还已关闭且事件读空的服务;失败保留对象,成功使旧句柄失效。 |
| `IEC104_Slave_Destroy` | 已关闭、事件读空、站点移除且集合锁归还后释放服务;失败保留对象。 |
| `IEC104_Slave_Config_Default` | 设置默认监听及协议配置。 |
| `IEC104_Slave_Init` | 通过内部TCP服务初始化监听和队列;不创建业务回调线程。 |
| `IEC104_Slave_Prepare` | 只读查询socket、定时及本地工作条件,继续使用原token规则。 |
@@ -1987,20 +2070,46 @@ Init 失败不销毁对象;Create 后、Init 失败后和 Close 后仍可 Get_
| `IEC104_Slave_Get_Event_Identity` | 校验事件并取得来源身份;允许同实例历史会话通知,失败保持输出。 |
| `IEC104_Slave_Check_Request` | 统一检查未读通知、原服务实例、连接代次和激活状态,返回业务推进条件。 |
| `IEC104_Slave_Get_Last_Submit_Result` | 按事件connection查询最近发送的OK/WAIT/ERROR/FATAL,仅WAIT可等待容量重试。 |
| `IEC104_Slave_Try_Send_ASDU` | 沿事件原connection尝试不排队提交ASDU,直接返回并记录OK/WAIT/ERROR/FATAL;WAIT不保存原文,应用重读后重试,普通共用发送接口不变。 |
| `IEC104_Slave_Send_Response` | 使用保存的原事件身份提交独立ASDU;保留原实例/代次检查,直接发送或进入HIGH,返回提交结果,不要求保留完整事件。 |
| `IEC104_Slave_Try_Send_Response` | 使用保存的原事件身份尝试不排队提交;WAIT不缓存数据,原队列、窗口和期限规则保持不变。 |
| `IEC104_Slave_Reject_Request` | 同步拒绝当前事件,原文仅改COT=44..47/P/N=1,复用HIGH;直接返回提交结果,输入及最近提交结果不变。 |
| `IEC104_Slave_Get_Application_Layer_Parameters` | 返回标准104静态只读参数,供主动上报构造ASDU。 |
| `IEC104_Slave_Enqueue_Event` | 校验ASDU对象并提交NORMAL,支持离线缓存和原有覆盖策略。 |
| `IEC104_Slave_Get_Remote_Address` | 取得当前主站地址副本,不借用内部连接对象。 |
| `IEC104_Slave_Close_Connection` | 按原事件关闭指定连接,内部提取身份;旧代次不能关闭新连接。 |
| `IEC104_Slave_Close_Connection_By_Identity` | 按保存的原事件身份关闭原连接;与完整事件入口同样检查服务实例/代次,保留监听和NORMAL。 |
| `IEC104_Slave_Get_Status` | 取得服务、连接及两级队列的只读快照。 |
| `IEC104_Slave_Close` | 关闭服务并保留收尾通知;继续NextEvent读空,再Destroy。 |
<!-- library-api-prototypes:19 -->
<details>
<summary>完整函数原型(18 个;参数和返回类型见声明)</summary>
<summary>完整函数原型(41 个;参数和返回类型见声明)</summary>
```c
#include "IEC104_Slave.h"
IEC104_StationResult_t IEC104_Slave_Add_Station(IEC104_Slave_t *self,
IEC104_Station_t *station, uint16_t common_address, void *context);
IEC104_StationResult_t IEC104_Slave_Remove_Station(IEC104_Slave_t *self, IEC104_Station_t *station);
IEC104_Station_t *IEC104_Slave_Get_Station(const IEC104_Slave_t *self, uint16_t common_address);
size_t IEC104_Slave_Get_Station_Count(const IEC104_Slave_t *self);
IEC104_Station_t *IEC104_Slave_Get_Station_By_Id(const IEC104_Slave_t *self, uint64_t station_id);
uint64_t IEC104_Station_Get_Id(const IEC104_Station_t *station);
bool IEC104_Station_Is_Registered(const IEC104_Station_t *station);
bool IEC104_Station_Is_Reusable(const IEC104_Station_t *station);
IEC104_StationResult_t IEC104_Slave_StationSnapshot_Begin(IEC104_Slave_t *self,
IEC104_StationSnapshot_t *snapshot);
IEC104_Station_t *IEC104_Slave_StationSnapshot_Next(IEC104_StationSnapshot_t *snapshot);
IEC104_StationResult_t IEC104_Slave_StationSnapshot_End(IEC104_StationSnapshot_t *snapshot);
IEC104_Station_t *IEC104_Slave_Next_Station(const IEC104_Slave_t *self, const IEC104_Station_t *current);
IEC104_StationResult_t IEC104_Slave_Lock_Stations(IEC104_Slave_t *self);
IEC104_StationResult_t IEC104_Slave_Unlock_Stations(IEC104_Slave_t *self);
IEC104_StationResult_t IEC104_Slave_Acquire_Station(IEC104_Slave_t *self, IEC104_Station_t *station);
IEC104_StationResult_t IEC104_Slave_Release_Station(IEC104_Slave_t *self, IEC104_Station_t *station);
uint16_t IEC104_Station_Get_Common_Address(const IEC104_Station_t *station);
void *IEC104_Station_Get_Context(const IEC104_Station_t *station);
IEC104_Slave_t * IEC104_Slave_Create(void);
IEC104_TcpResult_t IEC104_Slave_Destroy(IEC104_Slave_t *self, IEC104_TcpError_t *error);
@@ -2032,6 +2141,18 @@ IEC104_SlaveRequestState_t IEC104_Slave_Check_Request(const IEC104_Slave_t *self
IEC104_TcpSubmitResult_t IEC104_Slave_Get_Last_Submit_Result(
const IEC60870_MasterConnection_t *connection);
IEC104_TcpSubmitResult_t IEC104_Slave_Try_Send_ASDU(
IEC60870_MasterConnection_t *connection, const IEC60870_Asdu_t *asdu);
IEC104_TcpSubmitResult_t IEC104_Slave_Send_Response(IEC104_Slave_t *self,
const IEC104_SlaveEventIdentity_t *identity, const IEC60870_Asdu_t *asdu);
IEC104_TcpSubmitResult_t IEC104_Slave_Try_Send_Response(IEC104_Slave_t *self,
const IEC104_SlaveEventIdentity_t *identity, const IEC60870_Asdu_t *asdu);
IEC104_TcpSubmitResult_t IEC104_Slave_Reject_Request(IEC104_Slave_t *self,
const IEC104_SlaveEvent_t *event, int cause);
const IEC60870_AppLayerParameters_t * IEC104_Slave_Get_Application_Layer_Parameters(
const IEC104_Slave_t *self);
@@ -2043,6 +2164,9 @@ IEC104_TcpResult_t IEC104_Slave_Get_Remote_Address(const IEC104_Slave_t *self,
IEC104_TcpResult_t IEC104_Slave_Close_Connection(IEC104_Slave_t *self,
const IEC104_SlaveEvent_t *event, IEC104_TcpError_t *error);
IEC104_TcpResult_t IEC104_Slave_Close_Connection_By_Identity(IEC104_Slave_t *self,
const IEC104_SlaveEventIdentity_t *identity, IEC104_TcpError_t *error);
IEC104_TcpResult_t IEC104_Slave_Get_Status(const IEC104_Slave_t *self,
IEC104_TcpServerStatus_t *status, IEC104_TcpError_t *error);
@@ -2088,9 +2212,11 @@ extern int IEC60870_Parse_ASDU_Header(const IEC60870_AppLayerParameters_t *param
## 21. IEC104_Interrogation.c
实现:[IEC104_Interrogation.c](../../104/IEC104_Interrogation.c);声明:[IEC104_Interrogation.h](../../104/IEC104_Interrogation.h)。
实现:[IEC104_Interrogation.c](../../104/business/IEC104_Interrogation.c);声明:[IEC104_Interrogation.h](../../104/business/IEC104_Interrogation.h)。
事件式总召模块:处理Type100、QOI=20..36,沿原connection按ACT_CON → 多类型数据批次 → ACT_TERM提交HIGH;支持主站COT=8撤销及业务超时。Slave负责通信,应用通过NextEvent取得BEGIN/NEED_DATA/FINISHED并显式应答;不注册业务回调。完整顺序见[总召接入流程](IEC104_完整调用流程.md#flow-interrogation)。
本节保留总召原12个接口的接入说明;Create的实现已移至GeneralInterrogation.c。请求追加freeze/data_cause,数据COT应取data_cause。内部头声明Create_With_Rules及Execute_Action,仅供种类文件协作,业务不直接调用。默认实例池为TCP服务容量的两倍,同一Slave允许总召、计数量各一实例,广播数组不得重叠。
事件式总召模块:处理Type100、QOI=20..36,沿原connection按ACT_CON → 多类型数据批次 → ACT_TERM发送;确认和终止可排入HIGH,数据尝试不排队提交。支持主站COT=8撤销及业务超时。Slave负责通信,应用通过NextEvent取得BEGIN/NEED_DATA/FINISHED并显式应答;不注册业务回调。完整顺序见[总召接入流程](IEC104_完整调用流程.md#flow-interrogation)。
### 配置、所有权与事件
@@ -2100,50 +2226,59 @@ Handle_Event通过Slave_Get_Event_Identity统一验证来源并绑定服务身
| IEC104_InterrogationConfig_t字段 | 默认值及范围 | 含义 |
| --- | --- | --- |
| common_address | 1;1..65534 | 本站地址;请求可使用本站地址或65535,广播请求的应答使用本站地址 |
| common_address | 1;1..65534 | 不使用站点表且station=NULL时的兼容站地址;其他模式忽略 |
| max_objects_per_asdu | 127;1..127 | 单批对象数上限,实际还受249字节容量限制 |
| use_station_registry | false | true按请求CA查找共用站点,65535逐站遍历;此时station须NULL |
| broadcast_states / broadcast_capacity | NULL / 0 | 调用方提供的IEC104_InterrogationStationState_t数组及项数,多目标广播须足够容纳本轮目标 |
| begin_timeout_ms | 15000ms;0禁用 | BEGIN入队至应用应答,含未取事件的等待 |
| data_timeout_ms | 15000ms;0禁用 | 每次NEED_DATA入队至Submit_Batch/Complete_Data/Fail_Request,每批重新计时 |
| total_timeout_ms | 60000ms;0禁用 | BEGIN生成至ACT_TERM被发送层接手,包含应用取数和发送等待,不包含生成BEGIN前等事件空位,不等待ACK |
| data_timeout_ms | 15000ms;0禁用 | 每次NEED_DATA入队至首次有效批次尝试/Complete_Data/Fail_Request;有效批次发送WAIT也停止本批计时,每批重新开始 |
| total_timeout_ms | 0(默认禁用) | 启用后从BEGIN生成至ACT_TERM被发送层接手,包含应用取数和发送等待,不包含生成BEGIN前等事件空位,不等待ACK |
| station | NULL | 兼容固定站绑定,Create持有引用至Destroy;删除后不接新请求,已接受任务可完成 |
三项期限是独立于链路t0..t3的可配置业务策略,默认值不是标准规定的总召时限。BEGIN成功生成时启动应用接受及执行总期限;此前START等业务FIFO空位不计业务期限,但已占用总召资格,须及时读取历史FINISHED。执行总期限不因提交新批次或忙应答而延长。用Config_Default初始化,再按业务需要修改。
新应用先Slave_Add_Station,再设置`config.use_station_registry=true`;CA只在注册时指定。多目标广播配置broadcast_states/broadcast_capacity,按“全部确认、逐站数据、全部终止”共用一个任务。数组借用至Destroy,地址稳定、期间不可读写或与其他实例重叠;NULL/0须配对,内部字段无需初始化。容量不足会在任何肯定确认前整轮否定,不部分执行;单目标无需此数组。BEGIN携带`request.station_id`,应用需保留context时按编号Acquire,使用结束后Release。`use_station_registry=false`保留原common_address或固定station模式;true与非NULL station组合拒绝创建。配置布局改变,库与调用方须一起重新编译。
三项期限是独立于链路t0..t3的可配置业务策略,默认值不是标准规定的总召时限。执行总期限默认禁用,BEGIN和每批取数期限仍各为15000ms,底层链路期限不受影响。BEGIN成功生成时启动已启用的应用接受及执行总期限;此前START等业务FIFO空位不计业务期限,但已占用总召资格,须及时读取历史FINISHED。总期限设置为非零后仍为固定整体期限,不因提交新批次或忙应答而延长。用Config_Default初始化,再按业务需要修改。
| 类型或字段 | 约定 |
| --- | --- |
| IEC104_InterrogationRequest_t | CA/OA/QOI/TEST、generation/receive_serial及对象数上限;common_address始终为本站地址,qualifier为20全站或21..36第1..16组,按值包含在业务事件中 |
| IEC104_InterrogationRequest_t | CA/OA/QOI/TEST、generation/receive_serial、station_id及对象数上限;CA为当前目标站,station_id为注册编号(裸CA模式为0),qualifier为20全站或21..36分组 |
| IEC104_InterrogationEvent_t | 纯值,可赋值及跨循环保存;无Slave事件的自引用,不需要Copy_Event |
| instance_id / request_id / batch_index | 实例、请求和批次身份,只读,调用应答接口时原样传回;拒绝重复、迟到和取消后的应答 |
| BEGIN | 应用按qualifier选点表,检查数据源及必要快照资源后接受/拒绝;无需预先编码全部数据 |
| NEED_DATA | 应用构造下一批;数据未就绪时保存事件,稍后提交,不重复生成请求 |
| BEGIN | 按qualifier检查数据源并接受/拒绝;可延至首个NEED_DATA建立游标,不预先采样全部数据 |
| NEED_DATA | 应用选择本批IOA和类型,读取当前值/品质后组包;未就绪时保存事件,稍后提交,不重复生成请求 |
| FINISHED | reason说明终结原因,带已提交批次/对象进度;应用停止对应采样任务并清理资源,不需要应答 |
每个已取出的BEGIN保证且仅保证一次FINISHED;BEGIN未生成或未取出便取消时直接移除工作,不生成FINISHED。参数校验、总召忙和满槽拒绝不交付BEGIN。取消移除未取出的工作事件,但保留所有FINISHED。模块不保存应用session或数据源指针;快照归应用所有,建议按实例和请求编号管理。
每个已取出的BEGIN保证且仅保证一次FINISHED;BEGIN未生成或未取出便取消时直接移除工作,不生成FINISHED。参数校验、总召忙和满槽拒绝不交付BEGIN。取消移除未取出的工作事件,但保留所有FINISHED。模块不保存应用session或数据源指针;遍历状态及业务资源归应用所有,建议按实例和请求编号管理。
编译实现文件时可覆盖IEC104_INTERROGATION_CAPACITY(默认IEC104_TCP_SERVER_CAPACITY)、IEC104_INTERROGATION_TASK_CAPACITY(默认4)和IEC104_INTERROGATION_EVENT_CAPACITY(默认4,最小2)。任务池保存至多一个总召及若干待发单帧应答,没有后续总召等待队列。任务保存在固定槽位中,用索引记录顺序;非队首应答完成后立即释放对应槽位,不搬动含自引用的请求及发送缓冲。生成BEGIN时预留FINISHED位置;业务FIFO满时等待应用读取,不丢弃终结通知,也不阻塞已排队单帧应答的调度。只有协议任务及业务事件均为空才允许Destroy;退出须Cancel → NextEvent读完并清理 → Destroy,再销毁Slave。
IOA及分组成员由调用者决定,协议库不规定点表或强制固定IOA集合。NEED_DATA及每次WAIT重试都读取当前值;Submit_Batch不缓存待发数据,OK才固定帧顺序。点值更新及突发发布须与总召读值及提交串行化,具体排序保证及持续突发可能推迟总召的边界见[总召模块设计](IEC104_Interrogation_设计架构.md#6-数据批次与发送路径)。
编译实现文件时可覆盖IEC104_INTERROGATION_CAPACITY(默认IEC104_TCP_SERVER_CAPACITY)、IEC104_INTERROGATION_TASK_CAPACITY(默认4)和IEC104_INTERROGATION_EVENT_CAPACITY(默认4,最小2)。任务池保存至多一轮总召及短应答,没有后续总召等待队列或数据批次缓冲。任务只保存原事件身份及10字节Type100原文,不保存完整SlaveEvent或自引用视图。单目标生成BEGIN时在FIFO预留FINISHED;多目标由每站状态区保留终结记录,NextEvent随空位逐个交付,2槽FIFO也能无损处理13站。网络流程结束后进入FINISHING,读完终结事件才释放该任务;此时仍忙拒绝新激活。只有任务及事件均为空才允许Destroy;退出须Cancel → NextEvent读完并清理 → Destroy,再销毁Slave。测量见[固定存储](IEC104_Interrogation_设计架构.md#2-对象身份与固定存储)。
### 广播公共地址
Type100激活和撤销接受CA=IEC104_CA_BROADCAST(65535、0xFFFF),按当前实例配置的本站地址处理一次。BEGIN、NEED_DATA和FINISHED的request.common_address均为本站地址;广播不改变QOI分组含义、单总召准入及业务期限。
开启站点表时,多目标Type100广播(CA=65535)先按注册顺序完成全部BEGIN/ACT_CON,再逐站完成数据,最后发送全部ACT_TERM。每站独立BEGIN/FINISHED、request_id和计时。接收时持有目标集合;尚未选入BEGIN便删除的站跳过,新增或同CA替代节点下轮才参加。已选中尤其已确认的站持原引用完成或明确失败,不因删除而静默跳过。只占一个任务槽,另需每站轻量状态区。没有注册站时回复COT=46并保留65535;单目标保持原流程。
广播请求的肯定/否定ACT_CON、ACT_TERM、肯定/否定DEACT_CON和错误应答都使用本站CA。请求入口完整解析Type100后一次确定应答CA;新任务、立即应答及撤销替换统一在Copy_Response_Request建立副本时写入地址。Process及发送重试沿用副本CA,调用方的原Slave事件保持原文。任务仅保留info这一份业务元数据用于事件交付和批次校验,不再维护重复header。其他非本站CA仍按原值回复COT=46,不改写为本站地址;校验优先级仍为COT、CA、IOA、QOI/P/N。有效的其他类型包括Type101仍返回IGNORED,广播规则由其业务处理器决定。
广播每站的确认、数据、终止和错误应答使用该站实际CA,保留OA/TEST/QOI和原连接身份。每次选站时更新紧凑请求的CA,同一站的发送重试沿用它,输入Slave事件不修改。忙/错误广播也用一个遍历任务逐站回复;无空槽保存游标时整体丢弃新广播并计数,不先发送部分站的应答。未知具体CA按原值回COT=46;校验优先级仍为COT、CA、IOA、QOI/P/N。Type101等其他类型继续返回IGNORED。
广播三阶段顺序与[c104的Server.cpp](https://github.com/Fraunhofer-FIT-DIEN/iec104-python/blob/main/src/Server.cpp)一致。对象寿命使用调用方节点及显式引用,保留本库的WAIT重试、业务事件、QOI=20..36及撤销/失败收尾规则;并不复制C++对象模型或动态分配方式。
### 数据批次及返回值
Submit_Batch接受应用构造的非空ASDU,参数须2/2/3,COT=请求qualifier(全站20,分组21..36)、P/N=0,CA必须等于配置的本站地址,OA/TEST与请求一致。业务事件的request.common_address已是本站CA,可直接用于构造;提交65535或其他站地址返回ERROR并保留交互供修正。支持Type 1/3/5/7/9/11/13/20/21,SQ=0/1,批间允许切换类型;完整解析校验数量、布局及类型。最多249字节且不超配置对象数。成功后模块复制原文,应用ASDU、缓冲和参数可立即复用;发送WAIT期间保留内部副本,不重复请求本批。Add返回FULL时提交已有非空批次,未加入对象留给下批;数据耗尽用Complete_Data,不提交空ASDU。
Submit_Batch接受应用构造的非空ASDU,参数须2/2/3,COT=请求qualifier(全站20,分组21..36)、P/N=0,CA必须等于当前任务的request.common_address,OA/TEST与请求一致;提交65535或其他站CA返回ERROR并保留交互。支持Type 1/3/5/7/9/11/13/20/21,SQ=0/1,批间允许切换类型,最多249字节且不超配置对象数。Slave_Try_Send_Response按原身份尝试提交,当前发送缓冲及HIGH/NORMAL未提交条目均为空、链路和窗口允许才接手;否则WAIT,不缓存数据。OK后应用可推进游标;WAIT保留事件和游标,网络推进后重读当前值重试。Add返回FULL时提交已有非空批次,未加入对象留给下批;数据耗尽用Complete_Data。
组号与IOA的对应关系由应用维护,模块没有内置点表,也不校验点是否属于请求的组。未配置组可Reply_Begin(false);接受的空组仍可正常确认并终止。ACT_CON/ACT_TERM的COT保持7/10,回显原QOI。
| 返回类型 | 值及语义 |
| --- | --- |
| IEC104_InterrogationEventResult_t | EVENT_HANDLED=1表示请求/通知已处理,包含拒绝或满槽丢弃(查看dropped_requests);EVENT_IGNORED=0交后续处理器;EVENT_ERROR=-1参数或处理失败 |
| IEC104_InterrogationEventResult_t | EVENT_HANDLED=1表示请求/通知已处理,包含拒绝或畸形/满槽丢弃(查看rejected、dropped_requests);EVENT_IGNORED=0交公共入口或后续处理器;EVENT_ERROR=-1参数或处理失败 |
| IEC104_InterrogationNextResult_t | NEXT_READY=1提供一个业务事件;NEXT_EMPTY=0或NEXT_ERROR=-1保持输出不变 |
| IEC104_InterrogationReplyResult_t | REPLY_OK=1已接手应答;REPLY_WAIT=0先消费Slave通知,再用同一事件/数据重试;REPLY_STALE=-2丢弃旧应答;REPLY_ERROR=-1修正参数/批次或处理查询错误 |
| IEC104_InterrogationReplyResult_t | REPLY_OK=1已接手应答;REPLY_WAIT=0先推进网络、消费Slave通知,保留事件和游标,数据批次重新取值后重试;REPLY_STALE=-2丢弃旧应答;REPLY_ERROR=-1修正参数/批次或处理查询错误 |
应答只接受已经NextEvent取出且尚未回答的BEGIN或NEED_DATA,按实例、请求和批次身份匹配原任务,不依赖任务是否位于队首。REPLY_OK不表示ASDU已提交网络,也不表示已ACK。REPLY_WAIT来自Slave FIFO未读空,和Process遇到发送容量不足不同;二者都不能跳过STOP/START或撤销请求。有效交互还会检查期限,到期即终结当前业务并返回STALE,不能用迟到应答绕过超时;确认前保留TCP,调用方仍须驱动Process发送否定确认。普通REPLY_ERROR不消费交互,可修正批次后重试;NEED_DATA阶段Fail_Request即使关闭连接失败仍取消任务、保留FINISHED,此时不可重试。不要把任意非零结果当作成功。
应答只接受已经NextEvent取出且尚未回答的BEGIN或NEED_DATA,按实例、请求和批次身份匹配原任务,不依赖任务是否位于队首。Submit_Batch的REPLY_OK表示数据已被发送层接手,其余应答仍由Process推进;均不表示写完或ACK。REPLY_WAIT可能来自Slave FIFO未读空,Submit_Batch还可能因发送背压返回WAIT;不能跳过STOP/START或撤销请求。有效交互还会检查期限,到期即终结当前业务并返回STALE,不能用迟到应答绕过超时;确认前保留TCP,调用方仍须驱动Process发送否定确认。参数或批次校验REPLY_ERROR不消费交互,可修正后重试;Submit_Batch的发送ERROR若确认为正常STOP拒绝,先返回WAIT等待通知,随后FINISHED(STOPPED),其他发送ERROR/FATAL仍以SEND_ERROR终结并返回STALE。NEED_DATA阶段Fail_Request即使关闭连接失败仍取消任务、保留FINISHED,此时也不可重试。不要把任意非零结果当作成功。
### 主站撤销
Handle_Event接受COT=6和8。撤销使用与激活相同的Type100、SQ=0、一个对象、P/N=0、IOA=0;本模块按原连接代次、实际站CA、OA、TEST和QOI全部一致匹配唯一未终结总召,包括BEGIN尚未生成的START。激活和撤销中的65535先映射到本站CA,因此允许广播激活配本站撤销、本站激活配广播撤销。撤销在忙拒绝判断之前处理;被忙拒绝的新QOI没有可撤销的任务,不会误取消原总召。
Handle_Event接受COT=6和8。撤销按原连接代次、目标CA、OA、TEST和QOI匹配,包括已选中但BEGIN尚未生成的START,以及已确认但未取数/等终止的站。定向撤销匹配站,其他站继续;广播撤销停止整轮,按各仍活动的站回复肯定COT=9。尚未选入BEGIN的未来站及已结束站不补发确认;已删除但仍执行的原站可匹配。撤销先于忙拒绝处理,被忙拒绝的新QOI没有可撤销任务。
匹配后停止原任务,旧工作事件应答返回STALE,丢弃模块尚未提交的批次;原任务槽改为肯定DEACT_CON(COT=9、P/N=0),将其索引移到单帧应答队尾,条件允许时立即提交,否则由Process重试。不会再提交该任务的ACT_TERM。已交付BEGIN则生成一次FINISHED(DEACTIVATED),应用据此停止采样并清理资源;无需额外撤销回调或应用确认。BEGIN从未交付时只移除工作事件。FINISHED说明业务任务已停止,不说明撤销确认已发出或已ACK。
@@ -2161,51 +2296,64 @@ Handle_Event接受COT=6和8。撤销使用与激活相同的Type100、SQ=0、一
| Reply_Begin(false) | FINISHED(REJECTED),排队否定ACT_CON,连接保持 |
| 需要新增总召但任务池满 | 尝试提交否定ACT_CON;无法安全提交/保存则丢新请求并计数,保留已有任务与TCP |
| 否定/错误应答未接手且任务池满 | 丢弃该新请求,增加overloads和dropped_requests,不越过旧REPLY,不取消已有任务 |
| 非法请求布局 | 关闭原连接;已交付BEGIN的任务终结为INVALID_REQUEST |
| 非100或公共ASDU视图不可用 | 安全读取Type后返回IGNORED;公共入口先过滤不可用视图,底层帧错误由Receiver/连接层处理 |
| Type100唯一对象完整,但SQ=1或有多余尾字节 | 回否定7/9,COT/CA/IOA错误优先45/46/47;应答规范为10字节、VSQ=1,保留OA/TEST/IOA/QOI;不执行激活/撤销 |
| Type100零对象/多对象等无法可靠应答的布局错误 | 计入rejected后HANDLED丢弃;不生成应答,不影响已有总召及其期限 |
| Submit_Batch非法 | REPLY_ERROR,保留NEED_DATA供修正,连接保持 |
| BEGIN阶段Fail_Request | 原槽转否定ACT_CON,保留连接及其他任务,FINISHED(SOURCE_ERROR) |
| START/CONFIRM阶段期限到点 | 原槽转否定ACT_CON,保留连接及其他任务;已交付BEGIN才有FINISHED(BEGIN_TIMEOUT / TOTAL_TIMEOUT) |
| NEED_DATA阶段Fail_Request或DATA/TERMINATE超时 | 关闭原连接、取消本模块任务,FINISHED(SOURCE_ERROR / DATA_TIMEOUT / TOTAL_TIMEOUT),不发成功ACT_TERM |
| STOPDT / 断线 / Cancel | FINISHED(STOPPED / DISCONNECTED / CANCELLED),未交付工作事件移除 |
| Destroy时尚有任务或业务事件 | false,保持状态;必须先取消和读取终结事件 |
| 发送ERROR/FATAL、会话变化、查询失败 | 取消并分别终结为SEND_ERROR、CONNECTION_CHANGED、SERVICE_ERROR |
| 发送ERROR且确认为原连接正常STOP拒绝、通知待处理 | 保留任务并让出,由DEACTIVATED终结为STOPPED;Submit_Batch返回WAIT,不算发送背压 |
| 其他发送ERROR/FATAL、会话变化、查询失败 | 取消并分别终结为SEND_ERROR、CONNECTION_CHANGED、SERVICE_ERROR |
Handle_Event仅在空闲时保存合法总召,忙拒绝保存的只是单帧应答;对单帧应答,只有Slave通知已读空、原会话仍有效且没有更早REPLY时,才沿现有HIGH接口立即尝试提交。使用请求副本,不修改调用方事件;OK后不再入任务池,WAIT或暂不能提交时保存,满槽则只丢新请求并累计dropped_requests。匹配撤销复用原槽,因此即使满池也能保存确认。丢弃的请求没有应答,主站可能等待超时或重试;EVENT_HANDLED不承诺主站已收到响应。
Handle_Event仅在空闲时保存合法总召,忙拒绝及可应答的畸形请求只保存应答;对单帧应答,只有Slave通知已读空、原会话仍有效且没有更早REPLY时,才沿现有HIGH接口立即尝试提交。使用请求副本,不修改调用方事件;OK后不再入任务池,WAIT或暂不能提交时保存,满槽则只丢新请求并累计dropped_requests。布局错误不取消已有总召或刷新其期限;规范化副本只能用于否定应答。合法匹配撤销复用原槽,因此即使满池也能保存确认。丢弃的请求没有应答,主站可能等待超时或重试;EVENT_HANDLED不承诺主站已收到响应。
调用IEC104_Interrogation_Process(self)即可自动推进保存的任务,不需要提供处理步数。单帧REPLY按形成顺序发送;单帧应答与唯一总召都可推进时轮流获得处理机会。总召等待BEGIN/NEED_DATA应答或业务FIFO空位时,继续处理后面的单帧应答。总召保持ACT_CON → 数据批次 → ACT_TERM顺序;ACT_TERM被发送层接手才正常终结,不等待ACK。应用拒绝、确认前失败或匹配撤销立即终结总召并转为REPLY,新请求可重新争取总召资格。此前忙时拒绝的请求不会自动启动,主站须重新发起。
调用IEC104_Interrogation_Process(self)即可推进保存的任务。REPLY按形成顺序发送;正常总召与短应答轮转,总召等应用/事件空位不阻塞短应答。每站保持ACT_CON → 数据 → ACT_TERM,多目标还保证全部确认先于数据、全部数据先于终止。ACT_TERM接手后终结,不等ACK。广播某站拒绝、超时或定向撤销后继续其他站;整轮结束并读完FINISHED才接受新激活。此前忙时拒绝的请求须由主站重新发起。
确认前后以肯定ACT_CON被发送层接手为边界。Reply_Begin(true)只进入CONFIRM;发送WAIT时总超时仍可改为否定确认,原槽复用并排到已有REPLY之后。已接手但尚未写出或ACK的肯定确认也算提交成功,DATA/TERMINATE失败仍关闭原连接。Process检测到确认前超时后重新检查Slave通知,在本轮继续调度否定确认;发送WAIT保留应答,不再累计已终结请求的业务超时。终结原因保留为具体TIMEOUT或SOURCE_ERROR,不统一改成REJECTED;未取出的BEGIN移除且不生成FINISHED。
Process在任务为空、所有剩余任务等待应用/业务事件空位,或发送返回WAIT时结束本轮;同一交互不重复交付。发送WAIT保留当前任务及数据,下次调用优先尝试最早的REPLY。模块不会在Process内部接收新请求或执行应用代码:每条REPLY提交后释放,总召提交当前帧后结束或等待应用,只有主站新发来的请求才会建立下一次总召,因此固定任务池限定了本轮工作量。保存业务事件期间仍须持续驱动Slave与Process。
Process在任务为空、全部等待应用/事件空位、发送WAIT或达到2倍任务容量步数时返回,余下本地工作由need_process提示。确认、终止及短应答WAIT保留当前发送项,下次调用优先尝试最早REPLY;DATA只生成NEED_DATA,应用Submit_Batch直接提交。广播共用任务遍历三个阶段,每站独立业务期限;启用总期限时,等待其他站的已确认目标也受检查,到期关闭原连接。保存业务事件期间须持续推进网络,并重读、重试数据WAIT。
调度只改变模块尚未提交任务的先后,已经被HIGH队列或发送缓冲接手的帧仍按原顺序发送,不能被新应答越过,也不能通过Cancel撤回。COMPLETED仅表示ACT_TERM提交成功。
Process每一步先检查Slave通知,再检查业务期限和选择任务;有未读通知即让出,包含本次推进期间新产生的通知。若已收到撤销/STOP/断线则按对应原因结束,不再重复生成超时事件。单帧应答的调度不会重置活动总召的期限;总期限与阶段期限都到点时优先报告TOTAL_TIMEOUT。
等待前重新Get_Status;timer_active为true时,以timeout_remaining_ms限制Poll/应用等待上限,0表示立即推进。状态查询只计算剩余时间,不触发超时;模块没有自动定时线程。BEGIN尚未生成时没有业务计时器,应继续读业务事件并驱动Process。确认前超时保留连接;肯定确认提交后的超时关闭原连接,会中断该连接其他业务。三项期限可以配置或逐项设0禁用,不增加独立的断链策略开关。
发送返回ERROR时额外复核正常STOP状态,三个提交入口共用此判断;不因任意新通知而忽略错误。OK照常记账,WAIT让出,FATAL仍失败。STOP拒绝的等待不累计waits、不设置data_send_waiting、不停止取数计时;完整条件见[提交结果处理](IEC104_Interrogation_设计架构.md#72-process中的选择规则)。
等待前重新Get_Status;need_process为true时立即推进,不能用!waiting替代。timer_active为true时,以timeout_remaining_ms限制Poll/应用等待上限,0表示立即推进。查询只读取状态和时间,不执行发送或超时处理;模块没有自动定时线程。BEGIN尚未生成时没有业务计时器,应继续读业务事件并驱动Process。确认前超时保留连接;肯定确认提交后的超时关闭原连接,会中断该连接其他业务。三项期限可以配置或逐项设0禁用,不增加独立的断链策略开关。
data_timeout_ms只计算NEED_DATA入队至首次有效批次尝试、结束或失败。有效批次因发送背压WAIT后,data_send_waiting置位,本批取数计时停止且不恢复,下一批NEED_DATA重新计时。无效批次或未读Slave通知导致的提前WAIT不停止计时;显式启用的total_timeout_ms及底层链路期限仍有效。
业务事件释放空位或应答接手后重查need_process;发送WAIT后不因保留任务而持续置位。每次Slave_Poll/Process推进网络并读空Slave通知后仍须调用总召Process重试,即使need_process为false。Get_Status实时检查Slave FIFO,未读通知使events_pending为true、need_process为false,先转交STOP/撤销等通知。外部事件循环同时遵守Slave_Prepare的等待条件;need_process仅表示需要尝试本地推进,不保证发送成功。新增状态字段后调用方须与库一起重新编译。
数据WAIT时,还须在网络推进后重读并重试应用持有的NEED_DATA,不能等待新事件或need_process置位;Process不保存或代为重发本批。
| 状态 | 含义 |
| --- | --- |
| generation / active / gi_pending | 已消费的连接代次、激活状态、是否存在唯一未终结总召(含BEGIN尚未生成) |
| phase | 当前总召的START / CONFIRM / DATA / TERMINATE;仅有应答时REPLY,无任务时IDLE |
| waiting / application_waiting | 等发送容量、应用应答或业务FIFO空位;后者明确存在未应答工作事件。总召等待时仍可调度单帧应答,waiting不表示所有任务都无法推进 |
| events_pending / pending_events | Slave未读通知标志 / 未取出的业务事件数,不能混用两条FIFO |
| phase | 当前站START / CONFIRM / DATA / TERMINATE,收尾FINISHING;仅有应答REPLY,无任务IDLE |
| waiting / application_waiting | 等发送容量、应用应答或业务FIFO空位;后者表示等待应用首次有效应答。总召等待时仍可调度单帧应答,waiting不表示所有任务都无法推进 |
| data_send_waiting | 有效批次曾遇发送WAIT;应用仍持有NEED_DATA,须在网络推进后重新取值提交 |
| need_process | 应立即推进本地任务、到期期限或失效会话;未读Slave通知优先,发送WAIT后等网络推进再重试 |
| events_pending / pending_events | Slave未读通知标志 / 未取出的业务事件数(含状态区待交付FINISHED,可大于FIFO容量) |
| timer_active / timeout_remaining_ms | 是否存在启用的业务期限 / 最近期限距查询时刻的剩余毫秒数 |
| pending_requests | 总召及排队单帧应答数量(含撤销确认) |
| pending_requests | 总召、FINISHING及排队单帧应答数量(含撤销确认);FINISHING的gi_pending虽为false,仍忙拒绝新激活 |
| batch_index / objects_submitted | 优先报告活动总召已提交的数据批次/对象数;仅有单帧应答时报告最早应答保留的进度,无任务时为0;发送WAIT不增加 |
| submitted / gi_submitted | 发送层已接手ASDU数 / 已提交ACT_TERM的总召数 |
| waits / cancelled / rejected / overloads | 发送WAIT、取消任务、拒绝及任务容量超限次数;确认前失败计入rejected,超时同时计入timeouts,不计入cancelled |
| waits / cancelled / rejected / overloads | 发送WAIT、取消任务、拒绝及任务容量超限次数;rejected含无法可靠应答的畸形请求;确认前失败计入rejected,超时同时计入timeouts,不计入cancelled |
| negative_submitted / dropped_requests | 发送层已接手的否定应答数 / 无法安全提交或保存而丢弃的新请求数;前者不等于主站已接收 |
| deactivated / timeouts | 成功匹配的主站撤销数 / 执行超时数 |
| last_finish_reason | 最近合法总召的终结原因,BEGIN尚未交付也会更新 |
旧begin/read_batch/finish和parameter字段已移除;用业务事件与应答接口替代。模块仍提供12个公开函数,Process只接收self。当前只保留一个未终结总召,忙时否定后续激活,不自动执行此前被拒绝的请求。已删除queue_timeout_ms、QUEUED/QUEUE_TIMEOUT、queued_interrogations和queue_timeouts;调用方删除相关字段及分支,使用Config_Default初始化配置并重新编译。negative_submitted和dropped_requests继续记录否定提交与满槽丢弃;FINISH_OVERLOAD保留枚举值,满槽不以此终结已有任务。
旧begin/read_batch/finish和parameter字段已移除;用业务事件与应答接口替代。模块仍提供12个公开函数,Process只接收self。当前只保留一个未终结总召,忙时否定后续激活,不自动执行此前被拒绝的请求。已删除queue_timeout_ms、QUEUED/QUEUE_TIMEOUT、queued_interrogations和queue_timeouts;调用方删除相关字段及分支,使用Config_Default初始化配置并重新编译。negative_submitted和dropped_requests继续记录否定提交与满槽丢弃;FINISH_OVERLOAD及FINISH_INVALID_REQUEST保留枚举值,满槽或畸形新请求不以此终结已有任务。
### 函数功能
| 函数 | 功能与约定 |
| --- | --- |
| `IEC104_Interrogation_Config_Default` | CA=1、每批127对象,接受/取数/执行整体期限15000/15000/60000ms;NULL无操作。 |
| `IEC104_Interrogation_Config_Default` | CA=1、每批127对象,接受/取数期限各15000ms,执行整体期限0(禁用);NULL无操作。 |
| `IEC104_Interrogation_Create` | 复制配置并借用Slave;非法配置、重复绑定、池满或实例编号耗尽返回NULL。 |
| `IEC104_Interrogation_Destroy` | 无协议任务且无业务事件时归还槽;否则false且不修改状态,不关闭Slave。 |
| `IEC104_Interrogation_Cancel` | 取消未提交任务,为已交付BEGIN保留FINISHED(CANCELLED);不补发终止、不撤回已接手帧。 |
@@ -2213,8 +2361,8 @@ Process每一步先检查Slave通知,再检查业务期限和选择任务;
| `IEC104_Interrogation_Process` | 自动推进当前就绪的单帧应答与总召,直到空闲或需等待;每步检查Slave通知与期限。true可为空闲/等待/取消/超时,false为参数、组包、关闭连接或服务查询错误。 |
| `IEC104_Interrogation_NextEvent` | 取最早的业务事件,仅READY写输出;无网络操作。 |
| `IEC104_Interrogation_Reply_Begin` | 接受或拒绝已取出的BEGIN;接受后Process才可提交ACT_CON。 |
| `IEC104_Interrogation_Submit_Batch` | 应答NEED_DATA,验证并复制非空ASDU;OK后可复用输入,错误不消费交互。 |
| `IEC104_Interrogation_Complete_Data` | 应答NEED_DATA声明数据耗尽;随后Process提交ACT_TERM,再交付FINISHED。 |
| `IEC104_Interrogation_Submit_Batch` | 应答NEED_DATA,验证并立即尝试不排队提交非空ASDU;OK推进进度,WAIT保留事件重读再试,参数ERROR可修正,发送故障终结为STALE。 |
| `IEC104_Interrogation_Complete_Data` | 应答NEED_DATA声明本站数据耗尽,可复用取数游标;多目标全部数据结束后才逐站ACT_TERM及FINISHED。 |
| `IEC104_Interrogation_Fail_Request` | 报告数据失败:BEGIN转否定ACT_CON并保留连接,NEED_DATA关闭原连接;均终结为SOURCE_ERROR,关闭失败也交付终结事件。 |
| `IEC104_Interrogation_Get_Status` | 取得状态、累计统计及距最近业务期限的剩余毫秒;失败保持输出,不推进业务。 |
@@ -2259,3 +2407,79 @@ bool IEC104_Interrogation_Get_Status(const IEC104_Interrogation_t *self,
```
</details>
<a id="file-22"></a>
## 22. IEC104_GeneralInterrogation.c
实现:[IEC104_GeneralInterrogation.c](../../104/business/IEC104_GeneralInterrogation.c)。保留原`IEC104_Interrogation_Create(slave, config)`签名和总召行为,内部传入总召规则。原声明继续由Interrogation.h提供;工程必须把这个新的源文件加入构建。
<a id="file-23"></a>
## 23. IEC104_CounterInterrogation.c
实现:[IEC104_CounterInterrogation.c](../../104/business/IEC104_CounterInterrogation.c);声明:[IEC104_CounterInterrogation.h](../../104/business/IEC104_CounterInterrogation.h)。
```c
#include "IEC104_CounterInterrogation.h"
IEC104_Interrogation_t *IEC104_CounterInterrogation_Create(IEC104_Slave_t *slave,
const IEC104_InterrogationConfig_t *config);
typedef bool (*IEC104_CounterInterrogationAction_t)(void *context,
const IEC104_InterrogationRequest_t *request);
IEC104_InterrogationReplyResult_t IEC104_CounterInterrogation_Execute_Begin(
IEC104_Interrogation_t *self, const IEC104_InterrogationEvent_t *event,
IEC104_CounterInterrogationAction_t action, void *context);
```
Create使用共用配置并返回共用驱动句柄,同一Slave可以同时存在总召及计数量实例。读取沿用Reply_Begin、Submit_Batch、Complete_Data;FRZ=1..3通过Execute_Begin先校验再执行一次同步动作,WAIT/STALE/ERROR不执行回调。动作成功后只发ACT_CON即完成,TEST动作拒绝;传输失败不撤销已经执行的动作。回调失败必须保证未改变计数器,不得重入或等待外部I/O。完整约定见[计数量设计](IEC104_CounterInterrogation_设计架构.md)。
<a id="file-24"></a>
## 24. IEC104_ClockSynchronization.c
实现:[IEC104_ClockSynchronization.c](../../104/business/IEC104_ClockSynchronization.c);声明:[IEC104_ClockSynchronization.h](../../104/business/IEC104_ClockSynchronization.h)。独立静态池默认与TCP服务容量相同,每实例4个任务槽,只使用共用站点表。完整校验、时间计算及广播规则见[时钟同步设计](IEC104_ClockSynchronization_设计架构.md)。
Create复制配置、借用Slave,每服务至多一个实例,须在消费通知前创建。`master_utc_offset_minutes`默认480、范围-1439..1439,不含SU;`execute_timeout_ms`默认15000,0禁用执行期限。原事件不修改,可靠的单对象错误应答规范为16字节、VSQ=1,仅改COT/P/N及广播目标CA,保留CP56原文。COT/CA/IOA错误依次优先45/46/47,其余可对应的非法请求否定7,不能对应唯一完整对象时丢弃。
请求结构是回调期间借用的纯值,包含原`master_time`、站点/请求身份、`accepted_ms`、`base_ms`及对应base_ms的`utc_ms/local_time`。先完整校验IV/SU/时区及本地日历上界,再用同一次64位单调采样检查期限和补偿受理后的滞留;网络、Slave通知FIFO及硬件写入耗时不补偿。action只由Execute_Next同步调用,不存储回调或context;不得重入本模块或推进Slave事件循环,只有Get_Status允许在action中只读调用。
action的true/false分别报告成功/未报告成功,false不保证硬件没有改变。调用过action的肯定或否定ACT_CON均走Try;WAIT只重试确认,不重复动作,也不再计算执行期限。校验错误走普通HIGH。应答按请求FIFO提交;Execute_Next可以越过较早REPLY执行最早EXECUTE,其确认仍不插队。广播只执行一次动作,按受理快照逐个目标CA确认;删除的未提交目标跳过,新注册站不参与。满槽丢新并计overloads/dropped_requests,保持原连接和任务。
| 返回类型 | 含义 |
| --- | --- |
| EventResult | ERROR=-1为入口错误;IGNORED=0非本模块请求;HANDLED=1含拒绝/丢弃,不代表已应答 |
| ExecuteResult | ERROR=-1为参数、重入或服务查询错误;EMPTY=0无可执行请求但可能仍有应答(包括刚因期限/时间错误转为否定);WAIT=1先读Slave通知;OK=2调用过一次action,不代表动作成功或确认到达主站 |
| Process / Get_Status / Destroy | bool结果;Process不执行action,Get_Status只读且失败不改输出,Destroy要求先清理任务 |
| 状态 | 含义 |
| --- | --- |
| active / generation / events_pending | 本模块已消费的会话状态及是否须先处理Slave通知 |
| execute_pending / pending_requests | 是否存在待执行动作 / 尚未交接完成的任务数 |
| confirm_pending | 包含正在执行的action,以及已执行但尚未交接完成的ACT_CON;应用据此协调新的带时标发布 |
| need_process / timer_active / timeout_remaining_ms | 是否需立即推进Process,以及最近执行期限的剩余毫秒;查询不触发发送或超时 |
| accepted / executed / synchronized | 合法请求受理数 / 实际action调用数 / action报告成功数;广播动作只计一次 |
| submitted / negative_submitted | 已交接ASDU数 / 其中否定应答数;广播每个实际提交目标分别计数,不代表ACK |
| rejected / waits / cancelled | 请求拒绝、发送WAIT及任务取消统计;取消不能证明设时或发送从未发生 |
| overloads / dropped_requests / timeouts / time_errors | 满槽及丢新、未执行到期、倒退或时间换算错误;0超时配置不禁用范围检查 |
Try等到当前HIGH/NORMAL可提交条件满足,不隔离之后新增的NORMAL;持续发布可能推迟确认。`confirm_pending`供发布方串行协调,不是跨线程锁。STOP/断线/Cancel只取消未交接任务和站点遍历,不回滚时钟、不撤回已编号I帧。同内容的新请求不去重,重发旧时标可能再次回拨。
```c
#include "IEC104_ClockSynchronization.h"
typedef bool (*IEC104_ClockSynchronizationAction_t)(void *context,
const IEC104_ClockSynchronizationRequest_t *request);
void IEC104_ClockSynchronization_Config_Default(IEC104_ClockSynchronizationConfig_t *config);
IEC104_ClockSynchronization_t *IEC104_ClockSynchronization_Create(IEC104_Slave_t *slave,
const IEC104_ClockSynchronizationConfig_t *config);
bool IEC104_ClockSynchronization_Destroy(IEC104_ClockSynchronization_t *self);
void IEC104_ClockSynchronization_Cancel(IEC104_ClockSynchronization_t *self);
IEC104_ClockSynchronizationEventResult_t IEC104_ClockSynchronization_Handle_Event(
IEC104_ClockSynchronization_t *self, const IEC104_SlaveEvent_t *event);
bool IEC104_ClockSynchronization_Process(IEC104_ClockSynchronization_t *self);
IEC104_ClockSynchronizationExecuteResult_t IEC104_ClockSynchronization_Execute_Next(
IEC104_ClockSynchronization_t *self, IEC104_ClockSynchronizationAction_t action, void *context);
bool IEC104_ClockSynchronization_Get_Status(const IEC104_ClockSynchronization_t *self,
IEC104_ClockSynchronizationStatus_t *status);
```
@@ -0,0 +1,94 @@
# IEC104 时钟同步模块设计
`IEC104_ClockSynchronization.c/h`处理Type 103(`C_CS_NA_1`)时钟同步请求。模块负责请求校验、排队、站点路由、执行期限及ACT_CON;实际设时仅由应用显式调用`Execute_Next`时提供的同步action完成。`Handle_Event`和`Process`不会调用应用代码,模块不保存action/context,不直接依赖`IEC60870_Clock`或修改系统时钟。
源码:[IEC104_ClockSynchronization.h](../../104/business/IEC104_ClockSynchronization.h)、[IEC104_ClockSynchronization.c](../../104/business/IEC104_ClockSynchronization.c)。接入流程见[完整调用流程](IEC104_完整调用流程.md#flow-clock-synchronization),接口见[API汇总](IEC104_API接口汇总.md#file-24),运行入口见[子站示例](../examples/IEC104子站示例.md)。
## 1. 业务边界
- 只执行Type 103、COT=6的校时激活,动作结束只回ACT_CON,不发送ACT_TERM。
- ACT_CON回显原请求的16字节ASDU,替换COT、P/N并规范VSQ=1;多站广播另替换为目标站CA。CP56原文、SU、OA、TEST及其余对象字节保持原值,不以本站读时或补偿后的时间改写确认。
- 不支持COT=5读时、COT=8撤销或自定义确认时间;这些COT按45处理。没有NTP客户端、网络时延测量、时间质量老化或周期校时线程。
- 同内容的新请求不合并,也不保证幂等。主站重发旧时标可能再次回拨时钟;权限、允许步进幅度及设备副作用由应用action决定。
原文回显是对请求的确认,不是本次执行时刻的测量值。已有`IEC60870_Time`负责CP56/SU/时区转换,`IEC60870_Clock_Set`可作为应用的设备适配;设置失败也不承诺硬件未部分改变,协议模块不提供回滚。
## 2. 对象与固定存储
ClockSynchronization使用独立静态实例池,不占用总召/计数量的召唤引擎实例。`IEC104_CLOCK_SYNCHRONIZATION_CAPACITY`默认等于TCP服务容量,`IEC104_CLOCK_SYNCHRONIZATION_TASK_CAPACITY`默认4。一个Slave至多绑定一个校时实例;Create复制配置并借用Slave,须在开始消费该Slave的通知之前创建。每实例固定任务槽由合法待执行请求及待发否定/错误应答共用,不使用malloc,也不复制整份SlaveEvent。
任务保存原服务身份、必要请求字段、16字节原文及发送状态。应答严格按受理顺序FIFO提交,不越过等待执行或等待发送的旧任务。Execute_Next选最早EXECUTE,可越过较早的REPLY执行下一动作,但对应确认仍在原位等待,不插队。需要新槽而已满时丢弃新请求并增加overloads和dropped_requests;保留连接与已有任务,不覆盖旧任务、不额外执行动作,也不承诺每个超载请求都有响应。
所有调用在同一事件循环串行执行。action必须同步返回,不得保存请求指针、等待异步完成或重入任何修改本模块的入口;action内允许只读`Get_Status`。不为应用创建线程、定时器或时钟锁。Destroy之前先Cancel并完成清理,业务对象的寿命不得超过借用的Slave。
2026-10-29 Windows x64 GCC测得ClockTask为104字节、默认4槽实例为568字节、Request为80字节、Status为72字节;默认单服务的静态实例数组为568字节。此数值不含既有Slave/TCP/发送队列,不能作为MCU预算。示例main的x64单函数栈帧由768增至800字节,执行入口为240字节;完整单函数测量及产物见[测试说明](../test/IEC104测试文档.md#校时固定存储与栈测量),未将单帧结果当作最坏调用链。
### 2.1 请求校验顺序
非103返回IGNORED;以下规则按表中顺序判断,命中后不执行后续业务动作,输入Slave事件保持不变。
| 检查 | 处理 |
| --- | --- |
| 数量不是1,或不足16字节、无法保留唯一完整对象 | rejected加一,HANDLED丢弃,无应答 |
| COT不是6 | COT=45、P/N=1 |
| 定向CA未注册,或广播没有任何可用站点 | COT=46、P/N=1;无站广播保留65535 |
| IOA不是0 | COT=47、P/N=1 |
| SQ、尾字节、P/N、CP56字段、IV、TEST或本站时间范围非法 | 否定ACT_CON,COT=7、P/N=1 |
| 请求有效且有槽 | 保存EXECUTE,accepted加一,等待应用显式执行 |
| 需要保存而槽满 | 丢新并计overloads/dropped_requests,不断链 |
可应答的畸形单对象规范为16字节、VSQ=1,保留原16字节中的其他内容,仅修改COT/P/N及必要的广播目标CA。COT/CA/IOA错误优先于时标等内容错误;错误请求不刷新已有任务期限。HANDLED包含拒绝和丢弃,不表示已经发送响应。
## 3. 时间基准与执行期限
配置`master_utc_offset_minutes`默认为480,表示源报文标准当地时间减UTC的分钟数,合法范围-1439..1439,不包含夏令时。`master_time`保留原CP56解析值,转换时由`CP56_To_Seconds`检查IV并扣除SU对应的一小时;不得先清SU、先减小时或重复补时区。
`accepted_ms`是Handle_Event受理请求时的64位单调毫秒值,`base_ms`是本次执行检查所用的同一单调时钟采样。请求交给action时,`utc_ms`和固定UTC+8的`local_time`均对应`base_ms`。它不是设备设置成功或硬件真正生效的时刻。
计算使用已通过检查的UTC完整秒数:
```c
base_utc_ms = (uint64_t)utc_seconds * UINT64_C(1000) + master_time.time.ms;
limit_ms = ((uint64_t)IEC60870_TIME_MAX_SECONDS - UINT64_C(480) * 60U)
* UINT64_C(1000) + 999U;
```
受理前检查`base_utc_ms <= limit_ms`,保证该时刻可转换为本站日历;CP56转换成合法UTC秒数本身不保证固定UTC+8仍在2099年内。执行时先检查`base_ms >= accepted_ms`,再检查经过时间不大于`limit_ms - base_utc_ms`,之后才相加。最后以`utc_ms / 1000`和`utc_ms % 1000`调用`Seconds_To_Local`,成功后才调用action。不能先窄化为32位再检查,也不能在32位域中乘1000。
这里只补偿受理至执行检查之间的本站滞留,不补偿网络传输、Slave通知FIFO等待及action内部耗时。两次执行不同请求分别按各自受理时刻计算;重新收到旧时标不会自动补上它在主站重试前经过的时间。
`execute_timeout_ms`默认15000,0表示禁用。期限只覆盖尚未调用action的请求,从accepted_ms起算,`elapsed >= timeout`即到期。过期检查和执行补偿使用同一次单调时间采样,避免刚通过期限后又取时、越过边界仍执行动作。动作一旦调用,无论返回true或false,均不再受此执行期限控制;发送等待继续受链路及单帧期限约束。
到期请求转为否定ACT_CON并计timeouts,不调用action。单调时钟倒退显式判断:开启执行期限时按超时处理,禁用期限时按time_errors处理;补偿越界或日历转换失败也不执行action。0及连续相等的单调读数有效。期限不是标准规定的固定时限,也不替代链路t0..t3。
## 4. 执行、应答与发布顺序
正常任务从EXECUTE转为REPLY:`Execute_Next`先检查当前会话、未读通知和期限,再调用本次传入的action一次。true表示动作报告成功,false表示动作报告失败;二者分别生成肯定、否定ACT_CON。action调用期间`confirm_pending`已为true,调用返回后直到确认被发送层接手或任务取消仍保持该屏障。
执行过action的ACT_CON一律通过`Slave_Try_Send_Response`尝试提交,false结果也使用Try。已有HIGH/NORMAL待发原文、当前帧、窗口或记录背压时返回WAIT,模块保留原确认并在Process中重试,**不得再次调用action**。原请求校验失败产生的错误/否定应答通过普通`Slave_Send_Response`直接提交或进入HIGH。
Try返回OK表示原文已被发送层接手、帧顺序已确定,不表示写完socket或对端ACK。已经编号的I帧继续由发送层处理;业务不能因后续STOP撤回它。ERROR若确认来自原会话正常STOP、且通知尚待消费,则先让出等待通知取消;其余ERROR及FATAL取消尚未交接任务。FATAL可能已经写出字节,不重试、不回滚时钟,并继续排空连接通知。
Try只等待提交时已经存在的待发内容,没有为校时建立发送队列隔离区。持续新增NORMAL可能使确认一直WAIT。需要保证校时确认先于新的带时标上报时,应用在`confirm_pending`期间暂停产生这类新上报,继续推进网络以排空已有内容。取时、执行校时、发布及Try提交必须由应用串行协调;该状态不是跨线程锁。
## 5. 站点与广播
定向请求按Slave共用站点表匹配CA,动作请求保留原注册编号station_id;广播station_id为0。应用根据自己的context和该身份取得设备资源。CA=65535使用共用站点快照:受理时确定目标集合,之后新增站点不加入,同CA替代节点不承接旧请求。
广播只执行一次action,因为同一Slave的这些逻辑站共用本次校时动作;随后按快照顺序对仍有效的目标站分别提交同一结果,各应答CA替换为目标站CA。不按站重复设时。发送WAIT期间当前站被删除时,跳过该目标继续后续站;快照/引用按生命周期归还。
广播没有任何可用站点时不调用action,不建立虚构站点;保留原CA=65535生成一帧错误应答。错误COT优先45,COT合法而没有站点则46。具体校验失败不影响已有任务,也不刷新其执行期限。
## 6. 生命周期与事件循环
连接、启停和断线通知须交付所有业务实例;I帧依次交给总召、计数量、校时,均未接手才调用同步Reject_Request兜底。Slave通知优先处理,WAIT_EVENTS表示先继续读通知,不能绕过STOP/START历史仅凭最终激活状态执行旧任务。
每轮读空通知后调用校时Process处理超时及队首应答,再由应用显式调用Execute_Next执行当前允许的请求,继续Process提交确认。Process每步先检查会话和通知,单次最多推进2倍任务容量的步数;队首EXECUTE、发送WAIT或未读通知时让出,不在内部等待应用。等待前重新查询状态:有本地工作时立即推进,有执行期限时用最近剩余值限制等待;发送WAIT后可以等待网络,但网络推进并读空通知后仍要重试Process。
STOP、断线、服务实例/连接代次失效及Cancel取消尚未交接的任务和广播遍历。已经执行的时钟修改保留,不生成补偿动作,也不通过否定应答伪装成未执行。已交接的确认继续按发送层实际状态收尾。模块没有BEGIN/FINISHED业务事件队列;应用以显式执行结果及只读状态观察进度。
## 7. 构建、示例与验证
在Slave及其依赖之外加入`104/business/IEC104_ClockSynchronization.c`,头文件搜索路径包含`104/`、`104/business/`和`common/`。本模块使用公共时间转换工具和Net单调时钟,不要求链接`IEC60870_Clock.c`;应用需要实际设备读写时再选择Clock或自身平台适配。
示例默认只更新模拟时钟,不修改Windows系统时间。实际系统设时必须通过示例的显式模式选择,并由Windows权限决定是否成功;自动回归及本机TCP测试使用模拟模式。协议测试、内存测量和运行限制统一见[测试说明](../test/IEC104测试文档.md)及[示例说明](../examples/IEC104子站示例.md)。
@@ -0,0 +1,48 @@
# IEC104 计数量召唤
`IEC104_CounterInterrogation.c/h`实现 Type 101(C_CI_NA_1)的规则与创建入口,状态机、事件、广播、期限及发送背压由`IEC104_Interrogation`共用引擎维护。总召和计数量召唤各有实例,忙状态独立;共享连接、HIGH队列和发送窗口,任一业务关闭连接都会影响另一业务。
## 1. 请求与数据
请求为104的2/2/3地址布局、单对象、SQ=0、IOA=0、COT=6。QCC低6位RQT为1..4分组或5总请求,高2位FRZ为0读取、1冻结、2冻结并复位、3仅复位。COT=8不支持,返回未知传送原因45。未知CA和非零IOA分别返回46、47;限定词、P/N、忙状态及可可靠回显的畸形单对象请求回否定ACT_CON。无法对应单对象的请求丢弃并统计。输入原文不修改。
读取数据限Type15(SQ=0/1)和Type37(SQ=0)。数据COT使用`request.data_cause`:RQT=5为37,分组1..4为38..41。CA必须为事件中的实际站地址,OA、TEST保持原请求,P/N=0。249字节上限分别容纳30、48、16个对象,并受配置对象数限制。IOA分组、冻结值、BCR序号和品质由应用负责。
## 2. 两种完成方式
| 请求 | 应用处理 | 线路顺序 |
|---|---|---|
| FRZ=0 | BEGIN接受;NEED_DATA提交冻结值;Complete_Data结束 | ACT_CON → 数据批次 → ACT_TERM |
| FRZ=1..3 | BEGIN中通过Execute_Begin执行动作 | 动作完成 → ACT_CON;随后FINISHED |
冻结及复位不产生NEED_DATA,也不发送ACT_TERM。流程依据IEC60870-5-101:2003 §7.4.8模式C的冻结/冻结并复位流程;读取另按模式B进行。目标主站若要求额外终止帧,应作为单独兼容需求评估。Type16及撤销不在104计数量模块的支持范围内。
因此拿动作的BEGIN去调用Submit_Batch会因事件类型不匹配返回STALE,不会发送数据;ERROR仍用于有效NEED_DATA的非法批次。这与最初“无数据也产生NEED_DATA”的草案不同。
`gi_submitted`在计数量实例中统计成功提交完成响应的事务:读取的ACT_TERM或动作的ACT_CON;不是主站ACK计数。总召实例原含义不变。多站读取仍为全部确认、逐站数据、全部终止;多站动作逐站执行和确认,每站独立FINISHED,不保证同时冻结或失败回滚。
## 3. 动作执行与失败边界
应用用`IEC104_CounterInterrogation_Execute_Begin(self, event, action, context)`处理FRZ=1..3。回调签名为`bool action(void *context, const IEC104_InterrogationRequest_t *request)`。接口先检查实例、事件、会话及期限;WAIT/STALE不会调用动作,事件消费后再次调用不会重复执行。TEST动作请求被否定,不调用动作。
回调必须同步、短时完成,不得重入协议接口或推进网络;返回false表示未改变计数器,库生成否定确认。应用须先完成分组、资源等检查,再整体提交修改,不能部分清零后返回false。成功后确认等待不再因业务期限变成否定确认;传输层的发送期限、STOP和断线仍有效。已执行动作不会因连接断开或本地Cancel而撤销。
这个接口适合内存计数器或已准备完成的本地原子提交,不提供异步外部电表事务。异步冻结需要另行管理设备命令、迟到完成和不可撤销结果,不能简单“先发设备命令,稍后Reply_Begin”。主站重新发送一个新请求仍可能再次冻结;本模块不提供跨请求去重或断电持久化事务。
读取的BEGIN继续使用`Reply_Begin`;拒绝未配置分组也使用该接口。不得在可重试的BEGIN处理分支直接清零,再调用Reply_Begin。
## 4. 数据一致性与接入
一次读取必须固定同一冻结版本,包含值、序号、品质及可选时标。应用可复制快照、持有不可变版本,或阻止读取期间改写相关冻结区。WAIT后重组包仍取同一版本。多个Slave共享数据源时同样需要协调,不能只避让本地定时器。Type37时标由应用给出,本模块不读取墙钟。
创建顺序为Slave、站点、总召实例、计数量实例、Slave_Init。计数量调用`IEC104_CounterInterrogation_Create`,配置沿用Config_Default;使用多站广播时,两个实例必须各有独占状态数组。所有连接/启停通知交给两个实例;I帧依次交总召、计数量,均IGNORED才兜底。
每轮先推进Slave并读空通知,再推进两种业务并读取事件。合并两个实例的need_process、events_pending及最小剩余期限;发送WAIT后网络有进展时必须重试。示例轮换两实例的先后次序,避免固定优先级长期占据小窗口。退出时两实例分别Cancel、读完FINISHED、Destroy,然后释放站点与Slave。
## 5. 构建与边界
引擎及种类模块集中在`104/business/`,头文件搜索路径为`104/`、`104/business/`和`common/`。引擎依赖只读规则,不引用具体种类符号。总召工程加入`104/business/IEC104_Interrogation.c`和`104/business/IEC104_GeneralInterrogation.c`;计数量再加入`104/business/IEC104_CounterInterrogation.c`。只用计数量时不需要总召规则文件。默认实例池为TCP服务容量的两倍;只用一种业务可显式定义`IEC104_INTERROGATION_CAPACITY=1`。
规则表及Create_With_Rules属于内部接口;业务使用两个公开创建入口及共用驱动接口。现有总召Create签名保留,所有调用方重新编译。该引擎仍绑定104的Slave、传输及地址布局,尚不是101串口业务适配层。
可运行验证入口及当前记录见[测试说明](../test/IEC104测试文档.md),模拟计数器操作见[示例说明](../examples/IEC104子站示例.md)。
@@ -1,6 +1,6 @@
# IEC104 总召业务模块
`IEC104_Interrogation.c/h`在`IEC104_Slave`之上实现Type100总召业务。通信服务负责连接、启停、收发和会话身份;总召模块负责请求准入、业务阶段、数据批次检查、应答调度及终结通知。应用通过`BEGIN / NEED_DATA / FINISHED`事件提供点表和数据,没有业务回调。
`104/business/IEC104_Interrogation.c/h`在`IEC104_Slave`之上实现共用召唤引擎,Type100规则和原Create入口位于同目录的`IEC104_GeneralInterrogation.c/h`。通信服务负责连接、启停、收发和会话身份;引擎负责请求准入、业务阶段、数据批次检查、应答调度及终结通知。本文描述总召行为,应用通过`BEGIN / NEED_DATA / FINISHED`事件提供点表和数据;计数量的差异见第12节。
当前采用**每个实例至多一个未终结总召,忙时否定所有新的总召激活**。空闲时支持全站和全部16个分组,忙时不区分相同或不同QOI。任务池仍保留4个槽,用于一个总召及待发送短应答;没有后续总召等待队列,也没有独立排队超时。
@@ -16,17 +16,18 @@
│ └─ TcpServer → Connection → Link / Receiver / SendManager → Net
└─ IEC104_Interrogation
Handle_Event ← 应用逐条转交Slave通知
Process → 沿原请求connection提交HIGH应答
Process → 沿保存的原连接身份提交确认、终止及短应答
Submit_Batch → 沿保存的原连接身份尝试直接提交数据,不入队等待
NextEvent → BEGIN / NEED_DATA / FINISHED
Reply_Begin / Submit_Batch / Complete_Data / Fail_Request ← 应用显式应答
```
| 模块 | 自己维护的规则 | 与总召的关系 |
|---|---|---|
| Slave | 服务生命周期、事件来源、连接代次、启停及提交结果 | 总召通过公共接口校验请求、复制事件和提交应答 |
| Slave | 服务生命周期、事件来源、连接代次、启停及提交结果 | 总召通过公共接口取得原身份、校验会话和提交应答 |
| TcpServer及内部通信模块 | TCP字节流、APCI序号、k/w、t0..t3、HIGH容量和发送完成 | 总召不直接操作socket、链路状态或发送窗口 |
| Interrogation | 一个总召的准入与阶段、短应答、业务事件及业务期限 | 不持有应用点表,不主动读取设备 |
| 应用 | 分组成员、快照或采样方式、数据遍历、资源清理 | 根据业务事件决定接受、交付批次或终结数据 |
| 应用 | IOA、分组成员、数据源、遍历状态、资源清理 | 根据业务事件决定接受、逐批取值交付或终结数据 |
`Slave_Get_Event_Identity`校验事件来源;`Slave_Check_Request`判断是否有未读通知、原会话是否仍可提交。总召不自行拼凑TCP状态快照判断连接是否有效。应用应使用Slave和总召公开接口,不访问不透明对象内部字段。
@@ -34,16 +35,32 @@
`IEC104_Interrogation_t`是不透明的静态池句柄,通过`Create`取得,`Destroy`归还。一个Slave只能绑定一个总召实例;须在开始消费Slave通知前创建。配置按值复制,调用方配置返回后可复用;Slave须存活到总召实例销毁之后。
设置config.use_station_registry=true后,总召按请求CA选取本Slave的已注册站点,65535逐站遍历;config.station须NULL,common_address忽略。当前任务持有节点引用,删除使该站立即退出新请求路由,已有任务继续使用原节点。BEGIN携带station_id,应用跨循环保留context时应立即按编号查节点并Acquire,FINISHED清理后Release,防止同CA替换串站。默认false兼容原单CA调用,可选config.station仍固定绑定一个站点并持有到Destroy。注册、引用、延迟回收及快照契约见[共用站点表](IEC104_API接口汇总.md#station-registry)。
| 存储 | 默认容量 | 保存内容与约束 |
|---|---|---|
| 总召实例池 | 随`IEC104_TCP_SERVER_CAPACITY` | 各实例的会话、配置、任务及业务事件互不混用 |
| 召唤实例池 | `2 * IEC104_TCP_SERVER_CAPACITY` | 同一Slave的总召及计数量各一个;各实例会话、配置、任务及事件独立 |
| 每实例协议任务池 | 4槽 | 至多一个未终结总召;其他槽仅保存单帧应答,也允许4槽全是应答 |
| 每实例业务事件FIFO | 4槽,最小2槽 | 纯值业务事件;生成BEGIN时为FINISHED预留一个位置 |
| 每个总召的数据缓冲 | 最多249字节 | 只缓存当前一个ASDU批次,发送WAIT时保持不变 |
| 每实例业务事件FIFO | 4槽,最小2槽 | 单目标预留FINISHED位置;多目标终结记录保存在调用方状态区,按需交付 |
| 多站广播状态区 | 调用方提供,每目标站一项 | 保存站引用、身份、计时及进度,不保存点值或数据ASDU |
| 总召数据存储 | 不设置私有批次缓冲 | WAIT不接手数据;OK时发送层已复制到当前APDU |
编译实现文件时可覆盖`IEC104_INTERROGATION_CAPACITY`、`IEC104_INTERROGATION_TASK_CAPACITY`、`IEC104_INTERROGATION_EVENT_CAPACITY`。增加任务槽只增加短应答积压容量,不允许第二个总召。任务池设为1仍可工作,但当前总召占槽且短应答不能立即提交时,新请求只能丢弃并计数。
协议任务中的`SlaveEvent`和发送ASDU含自引用指针。任务在固定地址保存,`order[]`只移动槽下标,不能通过结构赋值或`memmove`移动任务本体。非队首短应答完成后立即释放对应槽,其他任务的视图不受影响。复制Slave事件使用`IEC104_Slave_Copy_Event`,不借用调用方事件地址。
协议任务只保存`InterrogationResponse`:由`Slave_Get_Event_Identity`取得的原服务/实例/连接代次,加10字节Type100 ASDU。合法请求复制原文并处理应答CA;可可靠应答的畸形单对象请求只保存规范为10字节、VSQ=1的否定应答副本,不执行激活或撤销。错误IOA、QOI、OA、TEST等字段仍完整保留。每次发送临时建立ASDU视图,调用`Slave_Send_Response`或`Slave_Try_Send_Response`;任务不保存完整SlaveEvent或自引用接口,源事件出队后可立即复用。固定任务槽及`order[]`调度规则保持不变,非队首短应答完成后仍立即释放对应槽。
2026-10-27按默认4任务槽、4业务事件槽测量,Windows x64 GCC的`sizeof`如下;这是结构存储尺寸,不是STM32的链接占用,Keil结果须另行实测。
| 项目 | 精简前 | 精简后 |
|---|---:|---:|
| 每任务请求存储 | 400字节(完整SlaveEvent) | 40字节(原身份及10字节ASDU,含对齐) |
| 每任务 | 480字节 | 120字节 |
| 每实例4个任务合计 | 1920字节 | 480字节 |
| 每个总召实例 | 2400字节 | 960字节 |
每实例减少1440字节(60%)。任务槽数、业务事件FIFO、TCP通知环、HIGH/NORMAL容量、k窗口及事件覆盖策略均保持原样,节省来自请求表示,不减少背压容量。立即应答也不再在栈上复制完整SlaveEvent;GCC `-O2 -fstack-usage`中该函数从464降至96字节,但这不是ARMCC或完整调用链的实测栈水位。
上表保留请求存储精简阶段的比较。当前Windows x64默认配置实测:总召1240字节、每任务168字节、紧凑请求40字节、业务事件80字节、配置40字节。相对逐站完整执行版本的1192字节,管理对象增加48字节;多目标广播另需调用方提供每站48字节的状态区,13站为624字节。单目标不要求状态区。广播共用一个任务及一套应用取数游标,HIGH/NORMAL和任务/事件容量不变。数值不是目标板RAM实测;站点及Slave开销见[共用站点表](IEC104_API接口汇总.md#station-registry)。
业务事件是纯值结构,可以复制和跨循环保存。`instance_id`区分静态池槽的历次使用,`request_id`在实例内递增,`batch_index`区分本次取数交互;编号耗尽时拒绝新建,不回绕复用。TCP的`generation`属于另一层身份,防止旧请求向新连接发送。
@@ -51,14 +68,15 @@
### 3.1 接收范围及校验顺序
总召使用`C_IC_NA_1`,Type=100、SQ=0、一个对象、IOA=0。支持激活COT=6和撤销COT=8,请求P/N须为0,CA可为配置站地址或广播地址65535(0xFFFF)。配置站地址仍限于1..65534。QOI=20为全站,21..36为第1..16组;分组内容由应用维护。
总召使用`C_IC_NA_1`,Type=100、SQ=0、一个对象、IOA=0。支持激活COT=6和撤销COT=8,请求P/N须为0,CA可为已注册站地址或广播65535(0xFFFF);兼容模式只接受其配置站。实际站CA限于1..65534。QOI=20为全站,21..36为第1..16组;分组内容由应用维护。
先校验事件身份及ASDU基础头部,再按类型分派。有效的其他类型返回`IGNORED`,总召不代替其他业务发送UNKNOWN_TYPE。Type100须完整解析布局,然后按`COT → CA → IOA → QOI/P/N → 事务状态`的优先级处理,避免忙状态掩盖地址或传送原因错误。
先校验事件身份,再保护长度并读取类型字节。非100返回`IGNORED`,总召不代替其他业务发送UNKNOWN_TYPE;公共ASDU视图不可用时也返回`IGNORED`,由公共入口处理。正常网络路径的公共头及最低载荷已由Receiver检查,真实截短/帧错误仍沿连接层原策略处理。Type100先判断是否存在唯一完整对象,可靠时按`COT → CA → IOA → 布局/QOI/P/N → 事务状态`处理,避免忙状态掩盖地址或传送原因错误。
| 条件 | 结果 |
|---|---|
| 基础头部无法解析,或Type100布局、SQ、数量、长度非法 | 关闭原连接并取消本模块任务;不交付BEGIN |
| Type不是100且基础视图有效 | `IGNORED`,交后续处理器 |
| Type不是100,或公共视图不可用 | `IGNORED`;公共入口应先过滤不可用视图,再分派其他业务 |
| Type100对象数为0或大于1,无法确定唯一完整IOA/QOI | 计入rejected并返回`HANDLED`,丢弃新请求,不生成应答 |
| Type100单对象完整,但SQ=1或有多余尾字节 | 仅回否定COT=7/9,COT/CA/IOA错误仍优先45/46/47;不执行激活或撤销 |
| COT不是6或8 | 回COT=45,P/N=1 |
| CA既不是本站地址也不是65535 | 回COT=46,P/N=1,保留请求中的错误CA |
| IOA不是0 | 回COT=47,P/N=1 |
@@ -68,13 +86,15 @@
| 合法激活,空闲且有任务槽 | 保存请求、分配request_id,进入START |
| 合法激活,空闲但任务槽被短应答占满 | 尝试否定确认;不能提交或保存时丢弃新请求并计数 |
确认和错误应答调整COT及P/N;请求CA为65535时,应答CA改为本站地址,其他CA原样保留。OA、TEST、IOA和QOI保持原文,包括错误请求中的原值。忙拒绝同样使用否定ACT_CON,没有另设“忙”的专用传送原因。
确认和错误应答调整COT及P/N;请求CA为65535时,应答CA改为本站地址,其他CA原样保留。OA、TEST、IOA和QOI保持原文,包括错误请求中的原值。畸形单对象的否定副本规范为10字节、VSQ=1,原输入事件不修改;该容错应答是本项目策略,不是将畸形命令视作有效命令。忙拒绝同样使用否定ACT_CON,没有另设“忙”的专用传送原因。
上述请求错误不会关闭TCP、取消已有总召、重置原期限或生成其FINISHED(INVALID_REQUEST),其他CA任务继续。否定应答复用原HIGH/FIFO背压与满槽丢弃策略,不扩大任务或缓冲容量。无唯一对象的丢弃只计入rejected;dropped_requests仍专指无法安全提交/保存的容量丢弃。内部服务故障、会话失效及发送失败继续走原失败路径。
### 3.2 忙状态的起止点
忙状态从合法激活被保存为START开始,早于生成BEGIN和提交肯定ACT_CON。因此,同一批网络通知连续包含多个激活时,只有第一个可建立总召,后续请求立即进入否定应答路径。不能等到应用接受后才设置忙,否则等待应用期间会接入多个总召。
忙状态覆盖BEGIN尚未生成、等待应用接受、确认等待发送、等待数据、已缓存批次等待发送及终止等待发送。相同QOI、不同QOI、不同OA或TEST的新激活都不能覆盖原请求。
忙状态覆盖BEGIN尚未生成、等待应用接受、确认等待发送、等待数据、有效批次未获发送机会及终止等待发送。相同QOI、不同QOI、不同OA或TEST的新激活都不能覆盖原请求。
| 终结方式 | 何时释放总召资格 |
|---|---|
@@ -84,13 +104,19 @@
| 主站匹配撤销 | 结束原业务,原任务转为肯定撤销REPLY |
| 本地Cancel、STOP、断线或确认后失败 | 取消总召及本模块待发短应答,保留应有的FINISHED |
已终结并转为REPLY的任务不再占用总召资格。新激活可以在有空槽时保存,但新BEGIN仍受业务事件容量约束。此前被拒绝的请求不会自动启动,主站需要重新发起。
单站终结并转为REPLY后不再占用总召资格;多目标广播中某站拒绝、超时或定向撤销时,仍为其他站保留总召资格。广播网络流程结束后进入FINISHING,gi_pending为false,但仍占用状态区并忙拒绝新激活,直到全部FINISHED出队。此前被拒绝的请求不会自动启动,主站需要重新发起。
### 3.3 广播地址与本站地址
广播CA表示请求面向所有站。`IEC104_Types.h`以`IEC104_CA_BROADCAST`固定定义0xFFFF(65535),它不是可配置的本站地址。本模块每个实例只有一个配置站地址,因此收到65535后只处理本站一次,不遍历其他实例,也不向其他TCP连接转发。CA选择目标站,QOI选择全站点表或分组,两者独立;广播分组请求同样支持QOI=21..36,忙时仍按单总召规则拒绝。
广播CA表示请求面向所有站。`IEC104_Types.h`以`IEC104_CA_BROADCAST`定义0xFFFF(65535),它不是实际站地址。开启共用站点表后,多目标广播采用c104的三阶段顺序:先按注册顺序完成各站BEGIN及ACT_CON,再逐站处理全部数据,最后逐站ACT_TERM及FINISHED。例如两站线上顺序为CON1、CON2、DATA1、DATA2、TERM1、TERM2;每站数据可有多批或为空。各站有独立request_id、进度和期限,但共享一个任务和一个取数位置。兼容单站及仅含一个目标的广播保持单目标流程。
以本站CA=7为例:
接收合法多目标广播时,把目标节点引用保存到`config.broadcast_states`;`broadcast_capacity`须覆盖当时的注册站数。数组由调用方持有至Destroy,地址稳定,不得读写或与其他实例重叠;无需逐项初始化。容量不足在任何肯定确认前整轮拒绝,各站收到否定ACT_CON,并累计overloads,不截取部分站执行。单目标不需要此数组,注册表本身仍无固定站数上限。
目标集合不冻结IOA或点值。后来新增或同CA重建的节点下轮广播才参加;尚未选入BEGIN便被删除的站跳过。已选中站持原引用完成,尤其已确认但尚未取数的站不能因删除而静默消失;应用通过station_id取得原context。无法继续取数时显式Fail_Request。没有注册站时广播回复COT=46并保留65535。忙或错误广播仍使用共用站点快照遍历实际CA,无槽保存遍历时整体丢弃新请求并计数。
每次Process最多推进2倍任务容量步,默认8步;剩余可提交应答通过need_process继续调度。多目标每站状态区保留终结原因,NextEvent按FIFO空位逐个交付,即使13站也只需2槽事件FIFO。发送WAIT不推进当前发送项,仍须重试。确认前拒绝或超时只结束该站并继续确认其他站;确认后取数失败或超时关闭原连接,整轮取消,各已交付BEGIN都有FINISHED。CA选站、QOI选组,两者独立,广播支持20..36。
以仅注册CA=7为例(广播多站时逐站应用同一规则):
| 请求或交互 | CA的处理 |
|---|---|
@@ -101,11 +127,11 @@
| 肯定/否定ACT_CON、ACT_TERM、肯定/否定DEACT_CON、广播请求的错误应答 | CA均为7,发送等待与确认前失败不改变这条规则 |
| 其他CA且前置校验已通过 | COT=46、P/N=1,保留原错误CA,避免伪装成本站请求 |
输入的`const SlaveEvent`始终保持原文。Request完整解析Type100后,通过Resolve_Common_Address计算一次response_ca:广播取本站地址,其他CA保留原值。地址校验、撤销匹配、立即应答及任务保存都使用这个结果,错误原因的COT优先级保持不变。
输入的`const SlaveEvent`始终保持原文。Request完整解析Type100后选择实际目标站,广播从首站开始;未知CA保留原值。错误原因仍按COT、CA、IOA、QOI/P/N优先检查。撤销匹配本轮活动站集合,不依赖注册表首站或当前取数位置。
内部Copy_Response_Request统一执行Slave_Copy_Event和Asdu_Set_CA。新建总召、短应答临时副本及匹配撤销的请求替换均通过它建立副本,复制时保留原服务实例和原连接代次。每份副本只在建立时准备CA,Process发送确认、终止或REPLY时沿用该地址;发送WAIT后不重新解析或转换CA。立即提交未接手而需要保存任务时,新建稳定副本仍使用先前确定的response_ca。
内部Copy_Response_Request保存原身份及10字节ASDU,再用临时视图调用Asdu_Set_CA。广播每次切换站点才更新副本CA,同一站的WAIT重试只重建视图、设置COT/P/N并沿用原CA。副本不借用调用方事件,也不重新绑定当前连接。
任务仅保留info这一份业务元数据,数据COT直接按info.qualifier检查,OA/TEST按info检查,CA按配置的本站地址检查;不再保存重复的header。合法总召的业务事件CA始终为本站地址。错误短应答保留原错误CA且不生成BEGIN,不能据此建立应用总召。数据批次不自动改写地址,非法批次仍返回ERROR供应用修正。
任务仅保留info这一份业务元数据,数据COT、OA、TEST及CA均按当前任务info检查;CA必须等于当前目标站。错误短应答不生成BEGIN。数据批次不自动改写地址,非法批次返回ERROR供应用修正;同一IOA在不同CA下属于不同点。
`IEC60870_Asdu_Set_CA`是共用ASDU层的字段接口,按COT宽度定位CA并按CA宽度写入小端字节,支持构造容器和借用可写原文。1字节CA只允许0..255,2字节允许0..65535;参数或容器无效时返回ERROR且不修改原文。它不负责站地址的业务合法性,总召配置仍拒绝0和65535。
@@ -120,7 +146,8 @@
├─ Reply_Begin(false) → FINISHED(REJECTED) → REPLY(否定ACT_CON)
└─ Reply_Begin(true) → CONFIRM
→ 提交肯定ACT_CON成功 → DATA
→ NEED_DATA → Submit_Batch → 缓存一批 → 提交成功 → 下一个NEED_DATA
→ NEED_DATA → Submit_Batch → OK → 下一个NEED_DATA
→ WAIT → 保留事件和游标,重读当前值后重试
→ NEED_DATA → Complete_Data → TERMINATE
→ 提交ACT_TERM成功 → FINISHED(COMPLETED) → 释放任务
@@ -132,14 +159,15 @@ DATA/TERMINATE超时或NEED_DATA失败:关闭原连接,取消本模块任务
| 阶段 | 等待或动作 | 推进条件 |
|---|---|---|
| START | 等事件空位生成BEGIN,随后等应用决定 | 业务FIFO能容纳BEGIN及终结预留;应用明确接受或拒绝 |
| CONFIRM | 提交肯定ACT_CON | 发送层接手后进入DATA |
| DATA | 请求下一批,或发送已缓存批次 | 应用交付批次;发送层接手后才增加批次和对象计数 |
| START | 等事件空位生成BEGIN,随后等应用决定 | 单目标需要BEGIN及终结预留;多目标终结位置由状态区保证 |
| CONFIRM | 提交肯定ACT_CON | 多目标继续下一站确认,全部确认完成后开始数据 |
| DATA | 生成NEED_DATA,应用读取当前值并尝试直接提交 | Submit_Batch返回OK才消费交互、增加批次和对象计数 |
| TERMINATE | 提交ACT_TERM | 发送层接手后终结;发送WAIT时仍然忙 |
| REPLY | 一条否定确认、错误应答或撤销确认 | 提交成功即释放,不产生新的取数交互 |
| FINISHING | 多目标网络流程结束,等待应用读完FINISHED | 最后一个终结事件出队后释放任务及节点引用,才接受新总召 |
| IDLE | 状态查询时表示没有协议任务 | 不表示业务FIFO一定为空,仍可能有FINISHED待取 |
`Reply_Begin / Submit_Batch / Complete_Data`成功表示应用决定或数据已被模块接手,后续`Process`才提交ASDU。`FINISHED(COMPLETED)`表示ACT_TERM已被发送层接手;它不保证报文写完或被主站确认。发送层可能随后因STOP或连接故障终止传输。
`Reply_Begin / Complete_Data`成功表示应用决定已被模块接手,后续`Process`提交确认或终止。`Submit_Batch`返回OK则表示数据已被发送层接手并确定帧顺序;不再等待Process提交本批。`FINISHED(COMPLETED)`表示ACT_TERM已被发送层接手;这些成功状态均不保证报文写完或被主站确认。发送层可能随后因STOP或连接故障终止传输。
## 5. 应用事件与资源生命周期
@@ -147,8 +175,8 @@ DATA/TERMINATE超时或NEED_DATA失败:关闭原连接,取消本模块任务
| 事件 | 应用需要做什么 | 应答接口 |
|---|---|---|
| BEGIN | 根据QOI选择点表,检查数据源与必要快照资源后决定是否接受 | `Reply_Begin(true/false)`;数据源失败可`Fail_Request` |
| NEED_DATA | 准备一个非空批次;暂未就绪则保存本事件,继续驱动通信 | `Submit_Batch`、`Complete_Data`、`Fail_Request`三者择一 |
| BEGIN | 根据QOI检查数据源并决定是否接受;可延至首个NEED_DATA建立取数游标,不采样整轮数据 | `Reply_Begin(true/false)`;数据源失败可`Fail_Request` |
| NEED_DATA | 应用选择本批IOA与类型,读取当前值和品质后构造非空批次;暂未就绪则保存本事件,继续驱动通信 | `Submit_Batch`、`Complete_Data`、`Fail_Request`三者择一 |
| FINISHED | 停止对应采样工作,按实例/请求编号释放资源 | 无需应答 |
`NextEvent`主动取出事件;事件入队不会执行应用代码。BEGIN或NEED_DATA只有被取出之后才允许应答。工作事件只发出一次,应用暂未就绪时保存事件,不能反复等待模块发出同一事件。
@@ -158,21 +186,33 @@ DATA/TERMINATE超时或NEED_DATA失败:关闭原连接,取消本模块任务
| 应答结果 | 调用方处理 |
|---|---|
| OK=1 | 交互已消费,不要重复应答;输入ASDU可复用 |
| WAIT=0 | Slave还有未读通知;保留事件及数据,先转交通知,再用同一份输入重试 |
| WAIT=0 | 有未读Slave通知,或Submit_Batch暂不能直接发送;保留事件及游标,推进通信、读空通知后重试;数据批次须重新读取当前值组包 |
| STALE=-2 | 事件已失效或不匹配,停止重试;按FINISHED清理资源 |
| ERROR=-1 | 参数或批次不合法等;通常不消费交互,可修正后重试。NEED_DATA阶段Fail_Request关闭失败是例外,任务仍已终结 |
### 5.2 FINISHED预留
生成BEGIN需要两个业务位置:一个用于BEGIN,一个预留给未来FINISHED。NEED_DATA不能占用终结预留,始终保持`实际事件数 + 终结预留数 ≤ 事件容量`。
单目标生成BEGIN需要两个业务位置:一个用于BEGIN,一个预留给未来FINISHED。NEED_DATA不能占用终结预留,始终保持`实际事件数 + 终结预留数 ≤ 事件容量`。多目标广播改由每站状态区保存终结记录,因此BEGIN只需一个FIFO空位,所有站可以先确认而不扩大FIFO。pending_events包含尚在状态区中等待入队的FINISHED,可能大于FIFO容量。
应用取出BEGIN后,无论正常完成、拒绝、撤销、STOP、断线、取消或超时,都恰好产生一次FINISHED。BEGIN尚未生成或尚未取出便取消时,移除工作事件并释放预留,不产生FINISHED,因为应用尚未取得请求。取消同时移除未读NEED_DATA;已经取出的旧事件再应答返回STALE。
历史FINISHED保留到应用读取。业务FIFO容量不足时,新的唯一总召可能停留在START等待空位;它已经占用总召资格,但尚未生成BEGIN,不启动业务期限。应用必须及时读完终结事件,不能只等待新的网络通知。默认容量4时可以同时保留旧FINISHED及下一次BEGIN;测试使用最小容量2验证这个边界。
多目标广播进入FINISHING后,连续NextEvent即可读完全部终结记录,无需靠Process搬运。应用不能假定BEGIN与FINISHED严格交替。示例在首个NEED_DATA建立唯一游标,Complete_Data返回OK后立即归还该站取数资源;失败/取消则按FINISHED清理仍在取数的站。若应用在BEGIN另行建立每站资源,须按request_id管理并等对应FINISHED清理。
## 6. 数据批次与发送路径
应用以`Asdu_Create`及信息对象接口构造批次,再调用`Submit_Batch`。模块只保存当前批次的编码原文,不保存应用点表、对象指针或数据源回调。
应用以`Asdu_Create`及信息对象接口构造批次,再调用`Submit_Batch`。模块立即尝试不排队提交,不保存待发批次、应用点表、对象指针或数据源回调。WAIT时输入存储也可复用,但同一业务事件和遍历位置须保留到成功或终结。
IOA、数据类型、分组成员和遍历方式均由调用者决定。BEGIN用于检查数据源和判断是否接受,遍历状态可延至首个NEED_DATA建立,不向库登记或冻结完整IOA列表。应用自行保证总召或分组点的遍历完整性,包括点表变化时如何避免漏点、重复点;库不强制点表在整轮期间保持不变。
推荐接入方式是在每次NEED_DATA组包时读取本批点的当前值和品质,不默认在BEGIN冻结整轮数值。值与品质的读取一致性由数据源保证;逐批取值不承诺整轮所有点属于同一采样时刻。需要固定时刻快照的应用可以自行实现,但必须另行处理快照与突发上报的顺序,不能把快照当成库提供的一致性保证。
总召数据通过`Slave_Try_Send_Response → TcpServer_Try_Send_Response → Connection_Try_Submit_Response → SendManager_Try_Submit_Response`提交,Slave按保存的原身份校验服务实例及连接代次。只有当前发送缓冲空闲、HIGH和NORMAL均无未提交条目、链路和发送窗口允许时,才直接准备I帧并确定N(S)。否则返回WAIT,不把旧值放入HIGH等待。NORMAL中仅保留已提交、待ACK的原文不单独阻挡本批,仍须满足k窗口;不要求等待全部ACK或整轮总召结束。
应用须把“更新当前点值并发布突发”与“读取本批当前值并调用Submit_Batch”放在同一串行执行上下文,或用等效同步保证二者不交错。WAIT之后只保留事件和游标,每次重试重新取值:已积压的突发先发送,随后总召读取最新值;总召返回OK后,本帧顺序已固定,后来发布的突发不能插入该帧之前。这同时避免旧NORMAL被新总召越过,以及新突发之后重发WAIT期间缓存的旧总召值。
这个保证依赖应用发布顺序:库不比较测点版本,无法识别应用在读取之后才迟到发布的旧采样,也无法修正调用者仍使用BEGIN数值快照的做法。突发发送不因总召被暂停;若HIGH/NORMAL持续有待发条目,总召数据可能持续WAIT,本路径没有强制抢占或最长完成时间。应用应按实际负载安排上报频率和批次,不能通过重试旧数值消除等待。
| 检查项 | 要求 |
|---|---|
@@ -185,11 +225,11 @@ DATA/TERMINATE超时或NEED_DATA失败:关闭原连接,取消本模块任务
| 数量与长度 | 至少1个对象,不超过配置上限及249字节;完整布局有效 |
| SQ及类型切换 | 每批允许SQ=0/1;一个ASDU同一Type,不同批次可切换Type |
成功后将原文复制到固定任务缓冲,以Slave的稳定参数重建发送视图。应用可以立即复用输入缓冲,并推进自己的游标。发送WAIT只重试已缓存的同一批,不重复索取数据;只有发送层接手后才增加`batch_index / objects_submitted`。
返回OK时发送层已复制本批原文,应用可立即复用输入缓冲并推进自己的游标,`batch_index / objects_submitted`也已增加。返回WAIT时不消费NEED_DATA、不推进计数、不保存原文,也不重复生成NEED_DATA;应用先处理通知,交互仍有效时在通信推进后重读同一遍历位置并再次调用Submit_Batch。发送ERROR若确认为正常STOP导致的拒绝,返回WAIT等待通知,随后终结为STOPPED;其他发送ERROR/FATAL以SEND_ERROR终结并返回STALE,不能重试原事件。参数或批次校验ERROR则不消费交互,允许修正。
空数据集或已经遍历完毕使用`Complete_Data`,不能用空ASDU代替结束。未配置分组可以在BEGIN时拒绝;接受的空组仍按ACT_CON → ACT_TERM正常结束。累计量数据不在当前总召批次范围,留给总电度业务。模块不检查IOA是否属于请求的分组,分组筛选和快照一致性由应用负责。
空数据集或已经遍历完毕使用`Complete_Data`,不能用空ASDU代替结束。未配置分组可以在BEGIN时拒绝;接受的空组仍按ACT_CON → ACT_TERM正常结束。累计量数据不在当前总召批次范围,留给总电度业务。模块不检查IOA是否属于请求的分组,分组筛选和取值由应用负责。
确认、数据、终止和短应答均沿原请求connection走HIGH。不能把数据放NORMAL而终止放HIGH,否则发送优先级可能改变事务顺序。模块调度只影响尚未被发送层接手的任务;已接手帧按通信层规则发送,不能被新应答越过,也不能通过Cancel撤回。
确认、终止和短应答沿原请求connection走普通HIGH应答接口;数据沿同一connection走不排队提交接口,不使用NORMAL,也不进入HIGH等待。只有上一批返回OK后才能完成数据阶段,因此终止不会越过尚未接手的数据。已接手帧按通信层规则发送,不能被新应答越过,也不能通过Cancel撤回。
## 7. 短应答调度与满槽处理
@@ -203,6 +243,7 @@ DATA/TERMINATE超时或NEED_DATA失败:关闭原连接,取消本模块任务
→ 可安全提交:Copy_Response_Request复制请求并写入已确定的应答CA,调整COT/P/N,调用原HIGH接口
├─ 已接手:累计submitted,否定时累计negative_submitted,不占新任务槽
├─ WAIT:未接手,保留/保存为REPLY
├─ ERROR且确认为正常STOP拒绝:保留/保存为REPLY,先交付停用通知
└─ 实际提交错误:按SEND_ERROR等失败路径处理
→ 需要保存但池已满:只丢弃新请求,增加overloads和dropped_requests
```
@@ -213,27 +254,42 @@ DATA/TERMINATE超时或NEED_DATA失败:关闭原连接,取消本模块任务
单帧应答按形成顺序FIFO提交。调度器分别查找唯一总召和最早REPLY,不要求总召位于任务索引队首。总召正在等应用或业务事件空位时,直接处理REPLY;双方都可推进时轮转,避免总召一直挡住短应答,也避免短应答持续占据所有推进机会。
每次发送WAIT都结束本轮,保存原字节和阶段,下次优先检查最早REPLY。每一步都重新检查Slave通知,发送过程中产生的新通知也必须先处理。不会跨过未读STOP/START或撤销继续发送旧事务。
Send_Response内部会推进保留的接收帧并交付通知。Process每步检查FIFO;发送返回值还须区分以下情况,Process、立即应答和Submit_Batch共用同一STOP判定:
`Process(gi)`没有预算参数,也不内部等待:REPLY成功就释放;总召成功发送当前批次后会请求应用提供下一批,或者结束;没有就绪任务、发送WAIT或发现未读Slave通知时返回。本函数不接收新请求、不执行应用代码,固定任务池和逐批交互保证单次调用可以自然结束。
| 发送返回值 | 后续处理 |
|---|---|
| OK | 按已接手事实记账/推进,下一次提交前检查通知;不撤回或重发本帧 |
| WAIT | 保留任务并让出;先处理通知,再按原背压规则恢复 |
| ERROR且确认为正常STOP拒绝 | 设置events_pending并让出,保留任务/业务事件,由DEACTIVATED终结为STOPPED |
| 其他ERROR或FATAL | 保留SEND_ERROR失败路径;FATAL可能已写出部分或整帧,不能转成可重试WAIT |
只有ERROR分支额外查询状态:请求须仍属于原服务/实例/代次,TCP仍连接,会话ACTIVE且无故障、队列状态有效,传输已停用并且通知未读,才按正常STOP拒绝让出。不能仅凭FIFO非空忽略错误。成功路径不增加查询;正常STOP拒绝不累计waits、不推进游标、不停止或刷新取数期限。应用先读取Slave通知,旧NEED_DATA随后返回STALE;立即应答保存的短任务也由STOP清理。
Process提交确认、终止或短应答遇到WAIT就结束本轮,保存请求和阶段,下次优先检查最早REPLY。每一步都重新检查Slave通知,发送过程中产生的新通知也必须先处理。不会跨过未读STOP/START或撤销继续发送旧事务。数据WAIT由持有NEED_DATA的应用在通信推进后重读并重试。
`Process(gi)`没有预算参数,也不内部等待:当前站REPLY或终止成功后继续广播或释放任务;DATA只生成NEED_DATA。没有就绪任务、发送WAIT、有未读Slave通知或已推进2倍任务容量步时返回;剩余本地工作由need_process提示续跑。每步发送前重查通知,不执行应用代码。
### 7.3 4个槽的含义
任务池的容量与“同时允许几个总召”是两个独立约束。总召资格上限恒为1,任务池还为无法立即发送的短应答提供存储。忙应答占槽并不代表原激活已被保存,发完否定确认后即释放,不会再生成该请求的BEGIN。
满槽时保留已有任务、应用事件及当前数据,不主动关闭TCP,不挤掉最早应答。只有新的、无法安全提交或保存的请求被丢弃。`EVENT_HANDLED`包括这种丢弃;应用不能把它当成“主站已经收到应答”,也不能再次投递同一Slave通知。`dropped_requests`用于观察这个情况,主站可能等待超时或自行重试。
满槽时保留已有任务、应用事件及进度,不主动关闭TCP,不挤掉最早应答。只有新的、无法安全提交或保存的请求被丢弃。`EVENT_HANDLED`包括这种丢弃;应用不能把它当成“主站已经收到应答”,也不能再次投递同一Slave通知。`dropped_requests`用于观察这个情况,主站可能等待超时或自行重试。
## 8. 业务期限与驱动
内部`Task_Timeout`按同一时刻汇总最近剩余时间、超时原因和已确认广播站的总期限到期标志。`Get_Status`读取该结果安排唤醒,`Expire`据此选择拒绝当前站或关闭原连接,不再二次遍历广播站或重新取时判定。结果只在本次调用中使用,不在任务内保存第二份期限状态。
| 配置 | 默认值 | 起止点 |
|---|---|---|
| `begin_timeout_ms` | 15000ms | BEGIN入业务FIFO至应用接受、拒绝或报告失败,包含未取事件的时间 |
| `data_timeout_ms` | 15000ms | 每次NEED_DATA入队至交付批次、声明结束或报告失败,每批重新计时 |
| `total_timeout_ms` | 60000ms | BEGIN生成至ACT_TERM被发送层接手,覆盖取数、事件容量等待和发送等待,不等待主站ACK |
| `data_timeout_ms` | 15000ms | 每次NEED_DATA入队至首次有效批次尝试、声明结束或报告失败,每批重新计时 |
| `total_timeout_ms` | 0(默认禁用) | 启用后从BEGIN生成至ACT_TERM被发送层接手,覆盖取数、事件容量等待和发送等待,不等待主站ACK |
三项默认值是本项目的可配置业务策略,不是IEC104规定的总召时限,也不代替链路t0..t3。每项0表示禁用。没有`queue_timeout_ms`;START尚未生成BEGIN时不计业务期限,但此时仍拒绝后续总召。及时消费业务事件是应用的驱动义务,模块不会通过后台线程保证进度。
三项默认值是本项目的可配置业务策略,不是IEC104规定的总召时限,也不代替链路t0..t3。每项0表示禁用。执行总期限默认关闭,避免慢链路的累计发送等待触发固定整体截止;BEGIN和每批取数期限仍各为15000ms,底层单帧发送、确认和链路探测期限继续有效。需要限制整次总召耗时时,可显式将`total_timeout_ms`设为非零。没有`queue_timeout_ms`;START尚未生成BEGIN时不计业务期限,但此时仍拒绝后续总召。及时消费业务事件是应用的驱动义务,模块不会通过后台线程保证进度。
所有期限使用Net提供的64位单调毫秒时钟,以经过时间比较,避免系统校时和绝对截止时刻相加溢出。0毫秒时刻有效。业务应答停止对应阶段期限,下一次NEED_DATA重新开始阶段计时;执行整体期限不会因新批次、忙应答或发送重试而延长。同一时刻总期限和阶段期限都到点时优先报告TOTAL_TIMEOUT。
所有期限使用Net提供的64位单调毫秒时钟,以经过时间比较,避免校时和绝对截止相加溢出。0毫秒有效。业务应答停止对应阶段期限,下一次NEED_DATA重新计时;显式总期限在同一站内不因新批次、忙应答或重试而延长。每站从自己的BEGIN独立计时,包含等待其他站确认、取数及终止的时间;已确认但不在当前取数位置的站也会被检查,到期关闭原连接并取消整轮。没有额外跨全部CA的整体定时器,同一时刻两项到点优先报告TOTAL_TIMEOUT。
有效批次因发送背压返回WAIT时置`data_send_waiting=true`,说明应用已响应,本批取数计时停止,之后不再恢复,下一批NEED_DATA才重新计时。无效批次或因未读Slave通知而提前返回WAIT不停止取数计时。发送等待仍受底层链路期限及显式启用的总期限约束;模块不替应用重读或重试数据,调用方必须持续驱动保留的事件。
Process和有效应用应答都会检查期限。先消费Slave通知,再判断超时,使已经到达的STOP、撤销或断线优先终结事务。到期后旧交互失效,迟到应答返回STALE;具体处理按肯定ACT_CON是否已被发送层接手划分:
@@ -251,9 +307,17 @@ Process和有效应用应答都会检查期限。先消费Slave通知,再判
等待前查询`Get_Status`。若`timer_active=true`,以`timeout_remaining_ms`限制下一次Poll或应用等待的上限,0表示立即推进;false表示当前没有启用的业务期限,不代表业务已经结束。状态查询只读,不触发发送、终结或超时。
`need_process=true`表示应立即调用总召Process:例如新请求有空位生成BEGIN、业务应答接手后可提交下一帧、读取旧FINISHED后解除事件容量等待,或业务期限已到期。该值按Process同一选择规则计算,既考虑总召,也考虑排队的短应答;它不是`!waiting`,也不保证发送层一定接手。当前帧提交WAIT后暂停本地续跑,避免空转;每次Slave_Poll/Process推进网络并读空Slave通知后,仍须调用总召Process重试,不能以此标志为false跳过网络进展。
应用持有的数据WAIT也须在上述网络推进后重试,不能只等need_process置位或新的NEED_DATA。`data_send_waiting`表示已交付有效数据但等待发送机会;`application_waiting`只表示仍等待应用首次有效应答,两者都不是发送就绪查询。
Get_Status实时检查Slave FIFO;`events_pending=true`时`need_process=false`,先处理可能存在的STOP、撤销或断线。`pending_events`表示另一条业务FIFO,应及时读取;每次读取事件或调用应答接口后重新查询状态。外部事件循环还须合并Slave_Prepare的等待条件,总召的标志不替代TCP的need_process及链路期限。
## 9. 撤销、取消与连接变化
主站撤销为Type100、COT=8。它在忙拒绝判断之前处理,按原连接代次、实际站CA、OA、TEST和QOI完整匹配当前总召,包括BEGIN尚未生成的START。激活和撤销的65535均映射为本站CA,因此广播激活可由本站地址撤销,本站激活也可由广播撤销;其他身份字段仍须一致。仅QOI相同不足以匹配,收到其他QOI的撤销也不能取消当前任务。
主站撤销为Type100、COT=8,在忙拒绝判断之前按原连接代次、目标CA、OA、TEST和QOI匹配,包括已选中但BEGIN尚未生成的START。定向撤销匹配站,其他站继续;已确认但尚未取数、数据已交完等终止、或已删除的原站均可匹配。尚未选入BEGIN的未来站没有活动事务。广播撤销停止整轮,对每个仍活动的站发送肯定COT=9,不向已结束或尚未启动的未来站补发确认。
撤销确认组复用原任务槽,排在已有短应答之后;即使任务池满也能撤销。当前站待发的否定ACT_CON不会被其他站的撤销确认覆盖。一组撤销确认尚未提交完时,新的重叠撤销按未匹配拒绝;不能安全保存否定回复时按既有满槽策略计数丢弃。已被发送层接手的帧不回滚,不再为被撤销站发送ACT_TERM。
匹配成功后,先通过Copy_Response_Request保存撤销请求并写入应答CA,使用原激活的info和进度生成FINISHED(DEACTIVATED),随后把原槽转换为肯定DEACT_CON。转换不需要额外任务槽,池满仍可撤销;形成的确认排在已有REPLY之后,条件允许时立即提交。已被发送层接手的帧继续在确认前按原顺序发送,本地未提交批次丢弃,不再提交原总召ACT_TERM。
@@ -268,7 +332,7 @@ Process和有效应用应答都会检查期限。先消费Slave通知,再判
COT=8的总召撤销与Slave的DEACTIVATED通知不是同一事件:前者结束一个业务,后者表示STOPDT停用。FINISHED表示应用可以清理资源,不承诺撤销确认已发送完成。
非法批次返回ERROR并保留交互,应用可以修正;数据源确实不能继续则调用`Fail_Request`。BEGIN阶段回否定确认并保留连接,NEED_DATA阶段关闭原连接,两者都终结为SOURCE_ERROR。确认后失败仍可能影响同连接其他业务。接受BEGIN前应检查点表、数据源和必要快照资源,减少确认后才发现无法执行的情况;无需预先编码全部数据,仍可逐批交付。
非法批次返回ERROR并保留交互,应用可以修正;数据源确实不能继续则调用`Fail_Request`。BEGIN阶段回否定确认并保留连接,NEED_DATA阶段关闭原连接,两者都终结为SOURCE_ERROR。确认后失败仍可能影响同连接其他业务。接受BEGIN前应检查数据源和必要业务资源,减少确认后才发现无法执行的情况;无需预先采样或编码全部数据,仍可逐批交付。
## 10. 应用接入与退出顺序
@@ -284,7 +348,7 @@ COT=8的总召撤销与Slave的DEACTIVATED通知不是同一事件:前者结
| Process | 自动推进就绪任务,遇等待条件返回 |
| NextEvent | 主动读取业务事件,只有READY写出结果 |
| Reply_Begin | 应答已取出的BEGIN,明确接受或拒绝 |
| Submit_Batch | 校验并复制当前NEED_DATA对应的非空ASDU |
| Submit_Batch | 校验当前NEED_DATA对应的非空ASDU,立即尝试不排队提交;WAIT须重读数据后重试 |
| Complete_Data | 声明数据耗尽,后续Process提交ACT_TERM |
| Fail_Request | 报告数据源失败;BEGIN转否定确认并保留连接,NEED_DATA关闭原连接,均保留SOURCE_ERROR |
| Get_Status | 查询进度、累计计数及最近业务期限,不产生副作用 |
@@ -298,7 +362,7 @@ Slave_Create / Config_Default / Init
3. Interrogation_Process
4. Interrogation_NextEvent,处理BEGIN / NEED_DATA / FINISHED
5. 应用数据就绪时显式应答,继续推进
6. 查询Slave等待条件及Interrogation业务期限,再安排等待
6. 查询Slave等待条件及Interrogation的need_process、事件和期限,再安排等待
→ Interrogation_Cancel
→ 读完业务事件并清理FINISHED对应资源
→ Slave_Close,读空收尾通知
@@ -315,19 +379,21 @@ Slave_Create / Config_Default / Init
| 字段 | 解释 |
|---|---|
| generation / active | 最近已处理的连接代次和启用状态,不能代替实时会话查询 |
| gi_pending | 当前唯一总召未终结,包含等待生成BEGIN;不是整个Slave的全局忙状态 |
| phase | 优先报告总召阶段;没有总召时报告REPLY或IDLE |
| pending_requests | 总召及短应答占用的任务槽数,不是等待执行的总召数 |
| pending_events | 实际业务事件数,不包含为FINISHED预留的位置 |
| waiting / application_waiting | 存在发送、应用或事件容量等待;后者明确存在未应答工作事件 |
| events_pending | 最近检查发现Slave通知尚未读空,应先处理网络通知 |
| gi_pending | 总召网络流程未终结;FINISHING虽为false,状态区清理前仍忙拒绝新激活 |
| phase | 当前站阶段,收尾为FINISHING;仅有应答为REPLY,无任务为IDLE |
| pending_requests | 总召、FINISHING及短应答占用的任务槽数,不是等待执行的总召数 |
| pending_events | FIFO事件及状态区中待交付的FINISHED,不包含预留位置 |
| waiting / application_waiting | 存在发送、应用或事件容量等待;后者表示仍等待应用首次有效应答 |
| data_send_waiting | 当前批次已有效尝试但发送WAIT;不保存原文,应用须保留事件、重读数据并重试 |
| events_pending | 当前Slave通知尚未读空,应先处理网络通知 |
| need_process | 当前有需立即推进的本地工作、到期期限或失效会话;发送WAIT后等待网络推进再重试 |
| timer_active / timeout_remaining_ms | 是否启用业务期限及距最近期限的剩余毫秒 |
| batch_index / objects_submitted | 当前总召已提交的批次和对象数;仅有REPLY时保留最早应答的历史进度 |
| submitted / gi_submitted | 被发送层接手的ASDU数 / ACT_TERM提交成功的总召次数 |
| waits | 实际提交返回发送WAIT的次数,不统计应用等待循环次数 |
| rejected | 校验、忙、应用决定、确认前失败和容量原因拒绝的请求数 |
| rejected | 校验、忙、应用决定、确认前失败和容量原因拒绝的请求数,含无法可靠应答而丢弃的畸形请求 |
| negative_submitted | 发送层已接手的否定应答数,不等于主站已收到 |
| overloads / dropped_requests | 需要保存但任务池满的次数 / 无法安全提交或保存的新请求丢弃数 |
| overloads / dropped_requests | 任务池满或广播状态区不足的次数 / 无法安全提交或保存的新请求丢弃数 |
| cancelled | 取消的协议任务数,包含待发REPLY;不等于FINISHED数量 |
| deactivated / timeouts | 成功匹配主站撤销次数 / 执行业务超时次数 |
| last_finish_reason | 最近合法总召的终结原因,BEGIN尚未交付时取消也会更新 |
@@ -338,7 +404,9 @@ Slave_Create / Config_Default / Init
## 12. 总电度扩展与设计边界
忙检查放在Type100激活处理内部,Type101总电度请求不会因总召忙被本模块否定。后续总电度模块应维护自己的状态、请求身份、QCC及数据流程,不能复用总召的gi_pending作为全局准入开关。
Type100与Type101分别使用GeneralInterrogation和CounterInterrogation只读规则,共用引擎通过创建时传入的规则执行解析、限定词和数据检查以及完成方式选择,不引用具体种类符号。总召旧Create名称保留;计数量使用CounterInterrogation_Create。同一Slave每种规则至多一个实例;不能复用总召的gi_pending作为全局准入开关。
计数量RQT=1..5、FRZ=0..3;读取接受Type15/37,COT为37..41。冻结/复位通过Execute_Begin先检查请求再执行一次同步动作,肯定ACT_CON提交后完成,不产生NEED_DATA或ACT_TERM。TEST动作拒绝;已执行动作不会因超时转为否定,也不因断线回滚。请求结构追加freeze/data_cause,总召分别为0/QOI。完整约定见[计数量设计](IEC104_CounterInterrogation_设计架构.md)。
接入多个业务处理器时,连接、启停和断线通知应广播给每个相关模块,不能因某个模块返回HANDLED就停止广播;I帧按业务类型分派,未接手的交给后续处理器,最后再由应用兜底。总召模块不实现总电度冻结、复位或累计量点表。
@@ -350,7 +418,7 @@ Slave_Create / Config_Default / Init
## 13. 验证与调用方迁移
可控网络回归共59组,使用真实Slave/TCP提交路径及可控单调时钟;HIGH容量缩到251字节,业务FIFO缩到2槽,验证实际发送背压和终结预留。共用ASDU测试共10组,覆盖Set_CA的宽度、容器形态及失败不修改原文。真实TCP回环使用Windows后端和k=1,逐帧确认并核对完整业务字节。
总召基线回归98组,计数量另有12组,合计110组,使用真实Slave/TCP提交路径及可控单调时钟;HIGH缩到251字节、业务FIFO缩到2槽。覆盖原有突发排序、部分写入、取数期限、k=1窗口及到期ACK;多站覆盖13站三阶段顺序、非空混合批次、状态区不足与跨实例重叠、已确认未来站删除/同CA重建、超出FIFO容量的无损FINISHED、各阶段定向及满槽广播撤销、拒绝与撤销同时等待、非当前站总期限,以及总期限禁用、BEGIN先到期和两项同时到期的处理优先级。畸形请求覆盖否定/丢弃边界、输入不变、基础截短仍被Receiver拒绝、原任务/期限保持、背压满槽和广播撤销隔离。提交期STOP覆盖三个入口、真实通知容量背压、FATAL与STOP并存、非STOP的ERROR、OK/WAIT伴随新回执及START后重新总召。真实TCP回环验证命令行单站,使用Windows后端和k=1逐帧核对业务字节。
| 验证范围 | 应满足的结果 |
|---|---|
@@ -364,6 +432,7 @@ Slave_Create / Config_Default / Init
| 短应答及满槽 | 安全时提前提交,积压保持FIFO;满槽只丢新请求,旧工作事件仍有效 |
| 远方撤销 | 本站/广播激活与本站/广播撤销四种组合均匹配实际站CA,其他身份仍严格检查;满槽仍可复用原槽 |
| 业务FIFO满 | FINISHED保留;BEGIN前不计业务期限,读出旧FINISHED后才能启动当前请求 |
| 本地续跑 | 事件空位恢复及业务应答后need_process置位;发送WAIT时不空转,网络推进后继续发送;未读Slave通知优先 |
| 三项执行期限 | 迟到应答不能绕过期限,忙应答不刷新总期限,先处理已到达的STOP/撤销 |
| 确认前失败 | 未读BEGIN超时不交付FINISHED;已交付时恰好终结一次,否定确认保留QOI/OA/TEST并使用本站CA |
| 满槽及发送等待期间失败 | 原槽转REPLY,不挤掉旧应答;按形成顺序发送,保留TCP,同连接重试可以完成 |
@@ -372,11 +441,17 @@ Slave_Create / Config_Default / Init
| 真实TCP连续分组请求 | 第一个完成,后续得到否定确认;结束后重发的请求分别正常完成 |
| 真实TCP广播流程 | 全站、分组、忙拒绝、非法QOI及撤销均返回本站CA;连接继续工作 |
从先前多QOI排队版本迁移时,删除`queue_timeout_ms`、`queued_interrogations`、`queue_timeouts`字段访问,以及`QUEUED / FINISH_QUEUE_TIMEOUT`分支。配置和状态布局已改变,所有调用方须重新编译并以Config_Default初始化配置。`Process`继续只接收实例,不增加预算参数;公开函数仍为12个。`FINISH_OVERLOAD`仅保留旧枚举值,当前满槽不以此终结已有总召。
从先前多QOI排队版本迁移时,删除`queue_timeout_ms`、`queued_interrogations`、`queue_timeouts`字段访问,以及`QUEUED / FINISH_QUEUE_TIMEOUT`分支。所有调用方须重新编译并以Config_Default初始化配置。`Process`继续只接收实例,不增加预算参数;原12个总召接口保留,其中Create实现迁至GeneralInterrogation.c,工程须加入此源文件。计数量另增2个入口。`FINISH_OVERLOAD`仅保留旧枚举值,当前满槽不以此终结已有总召。
状态新增`need_process`和`data_send_waiting`,所有调用方须与库一起重新编译。原来用`pending_requests != 0 && !waiting`决定是否立即续跑的代码应改为读取need_process,并保留网络推进后调用总召Process的步骤。Submit_Batch现立即尝试提交,OK时进度已推进;WAIT时只保留业务事件和游标,每次重试须重新读取当前值,不能继续复用旧构造结果。Reply_Begin和Complete_Data仍由Process发送后续确认或终止。
请求存储精简只改变内部表示,总召公开函数、业务事件及配置字段不变。Slave新增按身份发送、尝试发送和关闭原连接的接口;总召内部使用,调用方继续按原方式驱动总召即可。完整SlaveEvent及Copy_Event保留原语义,其他业务无需迁移。
确认前失败修复不新增接口、配置字段或枚举值。调用方收到BEGIN阶段Fail_Request的OK或超时后的STALE时,不应再假定TCP已经断开,须继续处理FINISHED并驱动Process发送否定确认。
广播支持新增共用接口`IEC60870_Asdu_Set_CA`,总召的12个公开接口、配置和事件布局不变。应用继续用业务事件的request.common_address构造批次;广播请求交给应用时该字段已是本站CA,不要从原始网络请求取65535填入数据ASDU。站配置无需改成65535,也无需增加广播开关。
早期单站广播支持新增`IEC60870_Asdu_Set_CA`,当时未改变总召配置及事件布局。当前多CA版本新增use_station_registry与request.station_id,所有调用方须重新编译。新应用启用站点表,按事件实际CA构造数据,不把65535填入数据ASDU;BEGIN获取引用,FINISHED归还。Remove的OK已改为立即退出路由,释放内存须另查Is_Reusable,原先“Remove成功即可释放”的调用代码必须迁移。
广播发送顺序与[c104 Server.cpp](https://github.com/Fraunhofer-FIT-DIEN/iec104-python/blob/main/src/Server.cpp)一致:全部站确认、逐站数据、全部站终止。c104使用shared_ptr;这里用调用方节点、显式引用及轻量状态区,并保留可重试WAIT、业务事件和各站明确收尾。仍只允许一轮总召,不引入每站独立发送任务或动态发送队列。新增broadcast_states/broadcast_capacity及FINISHING,调用方须重新编译;启用多目标广播前配置状态区,并适配多个BEGIN先于首个FINISHED的事件顺序。
地址处理重构新增语义常量`IEC104_CA_BROADCAST`,将地址准备集中到内部副本创建,并移除任务中重复的header。公开函数签名和事件字段保持原样,应用无需增加调用。59组可控网络及真实TCP回归继续检查确认/数据/终止WAIT、错误地址、原事件不变、广播/本站交叉撤销和确认前失败,避免内部重构改变协议行为。
@@ -1,14 +1,14 @@
# IEC104 完整调用流程
本文依据 2026-10-25 当前工作区的协议库源码,说明从服务创建到关闭释放的调用顺序,与 [API 接口汇总](IEC104_API接口汇总.md) 配合阅读。范围仅为库本身:不包含示范程序、测试程序或应用业务实现。接口汇总按实现文件查函数,本文按运行过程追踪函数之间的关系。
本文依据 2026-10-29 当前工作区的协议库源码,说明从服务创建到关闭释放的调用顺序,与 [API 接口汇总](IEC104_API接口汇总.md) 配合阅读。范围仅为库本身:不包含示范程序、测试程序或应用业务实现。接口汇总按实现文件查函数,本文按运行过程追踪函数之间的关系。
主线为当前已实现的 **IPv4 TCP 子站**:应用通过 `IEC104_Slave_*` 接入,Slave内部持有并调用 `IEC104_TcpServer_*`,内部使用 Connection、SendManager、Link、Receiver、SendBuffer 和 AsduQueue。当前网络后端为 Windows;主站文件只提供 Link 层启停过程,未接入这一套 TCP 子站调度。
调用树中的 `[内部]` 表示对应 `.c` 内的静态辅助函数,不是应用可直接调用的 API。调用树表示主要步骤;具体的等待、失败和恢复分支在后文说明。
可先读[应用主线](#flow-1)和[对象关系](#layers),需要总召时读[总召接入](#flow-interrogation),再查[事件与 ASDU](#flow-6)、[应答与上报](#flow-7)、[容量等待](#flow-12)、[断线重连](#flow-13)、[自有事件循环](#flow-14)和[关闭释放](#flow-15)。
可先读[应用主线](#flow-1)和[对象关系](#layers),需要业务时读[总召接入](#flow-interrogation)和[时钟同步接入](#flow-clock-synchronization),再查[事件与 ASDU](#flow-6)、[应答与上报](#flow-7)、[容量等待](#flow-12)、[断线重连](#flow-13)、[自有事件循环](#flow-14)和[关闭释放](#flow-15)。
业务调用主线为 `Slave_Create → Slave_Config_Default/Init → Slave_Prepare/Process 或 Poll → Slave_NextEvent → 共用发送接口 → Slave_Close → 读空事件 → Slave_Destroy`。NextEvent 直接交付 ASDU 及原连接接口,延迟处理用 Slave_Copy_Event。以下展开 Slave 调用到的 TCP 内部流程;其中 `server` 和应答所用的 `generation` 由 Slave 维护。
业务调用主线为 `Slave_Create → Slave_Config_Default/Init → Slave_Prepare/Process 或 Poll → Slave_NextEvent → 共用发送接口 → Slave_Close → 读空事件 → Slave_Destroy`。NextEvent 直接交付 ASDU 及原连接接口;延迟处理可用Slave_Copy_Event,也可保存来源身份及必要请求数据,通过Slave身份入口应答。以下展开 Slave 调用到的 TCP 内部流程;其中 `server` 和应答所用的 `generation` 由 Slave 维护。
<a id="flow-1"></a>
@@ -74,7 +74,7 @@ flowchart TD
| Slave_Enqueue_Event、底层 TcpServer_Send_Response | `IEC104_TCP_SUBMIT_OK` | WAIT 尚未接手;ERROR 为参数、代次或准入等错误;FATAL 须结合收尾事实处理 |
| 共用连接的Send_ASDU、Send_ACT_CON、Send_ACT_TERM | `true` | 104侧用Get_Last_Submit_Result区分WAIT/ERROR/FATAL;false不等于可以直接重试 |
`SUBMIT_OK` 表示已经复制接手,应避免重复提交;`SUBMIT_WAIT` 才由业务保留原文后重试。应答返回 FATAL 时可能已经写出部分甚至全部字节,不能直接按“完全未发送”处理。
`SUBMIT_OK` 表示已经复制接手,应避免重复提交;`SUBMIT_WAIT` 表示未接手,由业务稍后重试。普通应答保留原文,总召数据使用不排队接口,WAIT后重读当前值再试。应答返回 FATAL 时可能已经写出部分甚至全部字节,不能直接按“完全未发送”处理。
<a id="layers"></a>
@@ -125,13 +125,15 @@ flowchart TD
### 1.3 接入独立总召模块
[IEC104_Interrogation.h](../../104/IEC104_Interrogation.h) 提供站总召、分组总召、主站撤销及业务超时处理,接口详见[总召API](IEC104_API接口汇总.md#file-21)。配置包括CA、每批对象上限和三项业务期限,不注册业务回调。Slave交付网络通知,总召模块交付业务事件;应用在同一事件循环中主动读取这两条FIFO,再显式接受请求、提交批次或报告结束。
[IEC104_Interrogation.h](../../104/business/IEC104_Interrogation.h) 提供站总召、分组总召、主站撤销及业务超时处理,接口详见[总召API](IEC104_API接口汇总.md#file-21)。配置包括CA、每批对象上限和三项业务期限,不注册业务回调。Slave交付网络通知,总召模块交付业务事件;应用在同一事件循环中主动读取这两条FIFO,再显式接受请求、提交批次或报告结束。
总召内部已经接入上述来源和会话校验:Handle_Event保存经Slave验证的身份,Process及Reply_Begin/Submit_Batch/Complete_Data/Fail_Request在推进前检查请求状态。应用仍按下列流程驱动,无需为总召重复调用这些校验接口;应用只负责业务数据、接受决定及资源清理。
```text
Slave_Create
→ Interrogation_Config_Default,设置CA、每批上限和接受/取数/整体期限
→ Slave_Add_Station,注册应用提供的节点、CA和业务context
→ Interrogation_Config_Default,设置use_station_registry=true、每批上限和业务期限
→ 多目标广播配置broadcast_states/broadcast_capacity(调用方数组借用至Destroy)
→ Interrogation_Create(slave, &config),检查非NULL
→ Slave_Init
→ 循环:
@@ -143,27 +145,30 @@ Slave_Create
→ 连接、启停和断线通知也须交付
Interrogation_Process(gi) 自动推进当前就绪任务
Interrogation_NextEvent,逐条处理业务事件:
BEGIN → 按request.qualifier选全站/分组点表,建立快照/游标 → Reply_Begin(accept)
NEED_DATA → 准备本批 → Submit_Batch;无剩余数据则Complete_Data
BEGIN → 按request.qualifier检查数据源 → Reply_Begin(accept)
NEED_DATA → 应用选择本批IOA,读取当前值/品质并组包 → Submit_Batch;无剩余数据则Complete_Data
FINISHED → 清理对应请求资源,无需应答
数据尚未就绪:保存NEED_DATA,后续循环就绪时再应答
Interrogation_Get_Status,安排下轮调度
→ Interrogation_Cancel
→ Interrogation_NextEvent读空,处理FINISHED并释放应用资源
→ Slave_Close → Slave_NextEvent读空收尾通知
→ Interrogation_Destroy → Slave_Destroy
→ Interrogation_Destroy → Slave_Remove_Station → Slave_Destroy
```
必须在开始消费Slave通知前创建总召模块,一个Slave只绑定一个实例。配置按值复制,Slave须存活到总召Destroy之后;Init失败也要释放已创建实例。所有接口串行调用;异步采样结果须回到所属事件循环再提交。模块不创建线程,也不会调用应用函数。
上面的顺序使用共用站点表,CA只在注册时指定。多目标广播先确认全部站,再逐站取数,最后终止全部站;各站BEGIN可先于首条FINISHED。应用可在首个NEED_DATA建立唯一取数游标,Complete_Data返回OK后复用;异常时按FINISHED清理。需保留context则按station_id查原节点并Acquire,使用结束后Release。删除立即退出路由,已有引用结束后才可释放存储;同CA重建不改变旧数据源。兼容应用可保留use_station_registry=false的单CA或固定station模式。详见[共用站点表](IEC104_API接口汇总.md#station-registry)。
请求范围为Type100、SQ=0、一个对象、COT=6(激活)或8(撤销)、P/N=0、CA为本站地址或65535、IOA=0、QOI=20..36。QOI=20选择全站,21..36选择第1..16组。每个实例至多一个未终结总召:空闲时保存合法激活为START;从保存起到终结,相同和不同QOI的新激活均否定,不覆盖原QOI/OA/TEST及进度。被拒绝的请求不会自动执行,主站须在之后重新发起。总召忙仅约束Type100激活,Type101等其他业务继续分派,匹配的COT=8仍可撤销。详细设计见[总召模块设计](IEC104_Interrogation_设计架构.md)。Process在Slave通知FIFO读空后推进:
```text
空闲收到激活 → START(已占用总召资格)→ 业务事件有空位 → BEGIN(开始执行总期限)
空闲收到激活 → START(已占用总召资格)→ 业务事件有空位 → BEGIN(开始已启用的业务期限)
├─ Reply_Begin(false) → FINISHED(REJECTED);排队否定ACT_CON
└─ Reply_Begin(true) → Process提交ACT_CON(COT=7)
→ NEED_DATA(batch_index=0)
Submit_Batch → 复制数据 → Process提交COT=请求QOI
Submit_Batch → 当场尝试不排队提交COT=请求QOI
→ WAIT时保留事件与游标,重读当前值后重试
→ 提交OK后增加batch_index → 下一个NEED_DATA
Complete_Data → Process提交ACT_TERM(COT=10)
→ FINISHED(COMPLETED)
@@ -176,58 +181,90 @@ START/CONFIRM期限到期 → 否定ACT_CON,保留连接;已交付BEGIN才
DATA/TERMINATE期限到期 → 关闭原连接、取消 → FINISHED(DATA_TIMEOUT/TOTAL_TIMEOUT)
```
分组点表由应用维护。BEGIN中的request.qualifier决定所选组,未配置的组可Reply_Begin(false);接受的空组可在NEED_DATA直接Complete_Data,仍按ACT_CON → ACT_TERM结束。数据ASDU的COT必须等于请求QOI:全站为20,第1组为21,直至第16组为36;确认/终止仍用COT=7/10并回显原QOI。模块验证批次头部,但不判断某个IOA是否属于该组,点表筛选与快照一致性由应用保证。
分组点表及IOA由应用维护,BEGIN不向库登记或冻结完整IOA列表。request.qualifier指定请求的组,未配置的组可Reply_Begin(false);接受的空组可在NEED_DATA直接Complete_Data,仍按ACT_CON → ACT_TERM结束。数据ASDU的COT必须等于请求QOI:全站为20,第1组为21,直至第16组为36;确认/终止仍用COT=7/10并回显原QOI。模块验证批次头部,但不判断某个IOA是否属于该组,点表筛选、遍历及取值由应用负责。
本站配置CA仍为1..65534。请求使用IEC104_CA_BROADCAST(65535、0xFFFF,定义于IEC104_Types.h)时,模块将业务事件request.common_address设为本站CA,应用直接用该字段构造数据;Submit_Batch按配置CA校验,拒绝65535及其他站地址。广播请求的ACT_CON、数据、ACT_TERM、DEACT_CON及错误应答均使用本站CA,OA/TEST/QOI仍保留请求值。请求入口完整解析Type100后确定应答CA,新任务、立即应答及撤销替换通过Copy_Response_Request复制原事件并调用Asdu_Set_CA;每份副本建立时处理一次地址,Process及发送重试沿用副本CA。输入Slave事件和原连接身份保持不变。其他错误CA按原值回复COT=46,校验优先级不变。此规则只处理Type100,不替后续Type101等业务解释广播地址。
实际站CA为1..65534。多目标广播使用一个任务执行“全部ACT_CON → 逐站数据 → 全部ACT_TERM”,各站独立BEGIN/FINISHED及request_id;上图描述其中单站的先后关系。调用方状态区须容纳接收时的注册站数,不足时整轮否定,不部分执行。request.common_address是实际站CA,Submit_Batch拒绝65535及其他站CA。紧凑副本仍只保存原身份及10字节ASDU,原OA/TEST/QOI保持,输入事件不修改。新增/替代节点下轮参加,尚未选入BEGIN便删除的站跳过;已经选中尤其已确认的原站必须完成或明确终结。空站表广播及未知具体CA回复46。详见[广播设计](IEC104_Interrogation_设计架构.md#33-广播地址与本站地址),Type101等业务自行决定广播规则。
业务事件是纯值结构,可直接复制、存入应用队列或跨循环保存;这与含自引用、必须用Copy_Event复制的Slave事件不同。事件中的instance_id标识总召实例,request_id标识请求,batch_index标识当前批次。应答须原样传回取出的事件,不能修改标识。模块按这些身份定位原任务,不依赖任务是否在队首,处理其他请求的单帧应答不会使当前交互失效。重复应答、旧批次、旧请求、取消/撤销/超时后的事件以及静态槽复用前的事件均返回STALE。request还提供CA/OA/QOI/TEST、generation/receive_serial及每批上限,实际发送身份始终取模块保存的原请求。
| 业务事件 | 应用职责 | 对应应答 |
| --- | --- | --- |
| BEGIN | 按QOI选点表,检查数据源及必要快照资源后决定是否接受;无需预先编码全部数据 | Reply_Begin(true/false),数据源失败也可Fail_Request |
| NEED_DATA | 用应用自己的缓冲构造一批ASDU;未就绪时保留事件,继续驱动网络 | Submit_Batch、Complete_Data或Fail_Request,三者只选其一 |
| FINISHED | 停止对应采样任务,按实例/请求编号清理快照及游标,记录reason和已提交进度 | 无需应答 |
| BEGIN | 按QOI检查数据源并决定是否接受;取数游标可延至首个NEED_DATA建立,不预先采样全部数据 | Reply_Begin(true/false),数据源失败也可Fail_Request |
| NEED_DATA | 应用选择本批IOA和类型,读取当前值/品质,用自己的缓冲构造ASDU;未就绪时保留事件,继续驱动网络 | Submit_Batch、Complete_Data或Fail_Request,三者只选其一 |
| FINISHED | 停止对应采样任务,按实例/请求编号清理游标及业务资源,记录reason和已提交进度 | 无需应答 |
每个已经取出的BEGIN恰好配对一个FINISHED。BEGIN尚未生成或未被取出时取消,不产生终结事件,因为应用尚未建立资源;参数校验、总召忙及满槽拒绝也不交付BEGIN。取消会清除尚未取出的NEED_DATA,已经取出的旧事件应答失效。FINISHED始终保留到应用读取;即使没有协议任务,未读终结事件仍会使Destroy返回false。COMPLETED仅说明终止帧已被发送层接手,不说明已写完或已ACK。
应用使用Asdu_Create、具体对象Create及Asdu_Add_InformationObject构造批次。参数为104的2/2/3,数据COT=事件request.qualifier、P/N=0,CA/OA/TEST与事件一致;一个ASDU只能同类型,批间可切换类型。支持Type 1/3/5/7/9/11/13/20/21,SQ=0/1均可,单批最多配置数量且不超过249字节。Add返回FULL时提交已有的非空批次,未加入对象留给下一批;空数据集直接Complete_Data。累计量留给后续总电度模块。
多目标广播的FINISHED先保存在每站状态区,NextEvent按FIFO空位逐个交付;连续读取即可,无需Process。网络流程完成后phase为FINISHING、gi_pending为false,但仍占用任务及状态区,读完全部终结事件之前新激活仍忙拒绝。
Submit_Batch返回OK后模块已复制原文,应用可复用ASDU、缓冲及参数,并推进自己的数据游标。发送层WAIT只重试模块保存的字节,不再次生成NEED_DATA;只有上一批提交发送层OK后才索取下一批。数据源未就绪则由应用保存当前事件,不重复提交空批次,也不需要返回“数据WAIT”。整体一致性由应用快照决定,模块不自动采集全站。
应用使用Asdu_Create、具体对象Create及Asdu_Add_InformationObject构造批次。参数为104的2/2/3,数据COT=事件request.qualifier、P/N=0,CA/OA/TEST与事件一致;一个ASDU只能同类型,批间可切换类型。支持Type 1/3/5/7/9/11/13/20/21,SQ=0/1均可,单批最多配置数量且不超过249字节。Add返回FULL时提交已有的非空批次,未加入对象留给下一批;空数据集直接Complete_Data。累计量由独立CounterInterrogation实例处理。
Submit_Batch返回OK后发送层已复制原文并确定帧顺序,应用可复用ASDU、缓冲及参数,并推进自己的数据游标。WAIT不保存原文、不推进计数,也不再次生成NEED_DATA;应用保留当前事件及游标,网络推进后重新读取当前值组包重试。只有本批OK后才索取下一批;数据源未就绪时也保存事件,不重复提交空批次。点值更新及突发发布须与总召读值及提交串行化;逐批取值不保证整轮同一采样时刻,具体排序保证及持续积压的边界见[总召模块设计](IEC104_Interrogation_设计架构.md#6-数据批次与发送路径)。
| 应答结果(省略IEC104_INTERROGATION_REPLY_) | 处理 |
| --- | --- |
| OK=1 | 本次应答已被接手,不再重复应答;还不代表发帧/ACK |
| WAIT=0 | Slave尚有未读通知;保留同一事件和已构造数据,先按FIFO处理通知,再重试 |
| WAIT=0 | Slave有未读通知,或Submit_Batch暂不能直接发送;保留事件和游标,推进通信并读空通知后重读当前值重试 |
| STALE=-2 | 交互已经失效,丢弃该应答;对应资源按FINISHED清理 |
| ERROR=-1 | 参数、批次或服务查询错误;修正后可使用同一事件重试。NEED_DATA阶段Fail_Request关闭失败是例外:任务已取消,应处理FINISHED |
Handle_Event仅在空闲时保存合法总召,忙拒绝保留的只是单帧应答。单帧应答在Slave通知已读空、原会话有效且没有更早REPLY时,使用请求副本沿原HIGH接口立即尝试提交;成功便不占任务槽。WAIT或暂不能安全提交时保存,满槽无法保存则只丢新请求并计数。立即提交也不会修改调用方的原事件,不会跳过STOP/START或越过旧REPLY。
IEC104_Interrogation_Process(gi)只接收总召实例,在单帧REPLY与唯一总召之间轮转,自动处理保存的就绪任务。总召等待BEGIN/NEED_DATA应答或业务FIFO空位时,后面的单帧应答仍可提交。总召保持ACT_CON → 数据批次 → ACT_TERM顺序,中间允许穿插其他请求的REPLY;ACT_TERM提交成功才正常终结,不等待ACK。应用拒绝、确认前失败或匹配撤销立即终结总召,剩余确认走REPLY路径,不再占用总召资格。新的激活必须由主站重新发起。发送WAIT保留任务和数据,本次返回,下次优先尝试最早的REPLY。已交给HIGH队列或发送缓冲的帧仍保持原顺序。
IEC104_Interrogation_Process(gi)在单帧REPLY与唯一总召之间轮转,DATA只生成NEED_DATA,应用调用Submit_Batch提交。总召等应用或事件空位不阻塞短应答;其他REPLY可穿插三阶段流程。ACT_TERM接手才正常终结,不等待ACK。拒绝、确认前失败或定向撤销只结束该站,多目标广播仍为其他站保留总召资格。Process发送WAIT保留请求和阶段,下次优先尝试最早REPLY;数据WAIT由应用重读后重试。已交给HIGH或发送缓冲的帧保持原顺序。
Process每一步和应答入口都检查Slave通知,避免跨过同一TCP上的STOP/START或撤销请求;推进期间新产生的通知也须先交付处理。全部响应沿原connection走HIGH。waiting包括发送容量、应用应答及业务事件队列空位等待;总召等待时仍可调度单帧应答,因此它不表示所有任务都被阻塞。application_waiting表示存在未应答工作事件;events_pending表示Slave FIFO未读空;pending_events是业务FIFO未取出事件数。phase和批次/对象进度优先反映活动总召;仅有单帧应答时报告REPLY及最早应答保留的进度,无任务时为IDLE和0。waits只统计发送WAIT,批次/对象计数只在发送层接手后增加。
Process每一步和应答入口都检查Slave通知,避免跨过STOP/START或撤销。响应沿原connection发送;确认、终止及短应答可排入HIGH,数据只尝试直接提交。waiting包含发送、应用和事件空位等待;application_waiting表示等首次有效应答,data_send_waiting表示有效批次曾遇发送WAIT。events_pending是Slave通知标志,pending_events包含业务FIFO事件和状态区中的待交付FINISHED。phase和进度优先反映当前站,收尾为FINISHING,仅有短应答为REPLY,无任务为IDLE。waits只统计发送WAIT,批次/对象计数只在接手后增加。
Process在任务为空、所有剩余任务等待应用/业务事件空位、发送WAIT或有未读Slave通知时返回。无需设置步数上限:Process不接收新请求、不执行应用代码,单帧应答提交后释放,总召提交当前帧后结束或等待应用,只有收到新的主站请求才会建立下一次总召,固定任务池限定本轮工作量。应用仍须读取BEGIN/NEED_DATA并显式应答,再继续调用Process;数据暂未就绪时也须持续驱动网络和业务期限。
Send_Response内部也可能受理STOP。发送ERROR时若确认原连接正常停用且通知待处理,模块先让出,由DEACTIVATED终结为STOPPED;Submit_Batch暂返WAIT,不停止取数计时。其他ERROR/FATAL仍按SEND_ERROR处理。成功提交照常记账,下一轮检查通知,不重复发送;判定条件见[提交结果处理](IEC104_Interrogation_设计架构.md#72-process中的选择规则)。
主站撤销按连接代次、实际站CA、OA、TEST和QOI匹配唯一未终结总召,包括BEGIN尚未生成的START。激活和撤销的65535均先映射为本站CA,允许广播与本站地址交叉使用,其他身份必须一致。撤销先于忙拒绝处理,被忙拒绝的新请求不成为撤销对象。匹配后旧BEGIN/NEED_DATA应答立即失效,本地未提交批次丢弃;原槽改为肯定DEACT_CON(COT=9、P/N=0),排在已有REPLY之后,条件允许时立即提交,否则由Process重试。不再提交该总召的ACT_TERM;已取出BEGIN才产生FINISHED(DEACTIVATED)。已被发送层接手的帧仍按原顺序发送,FINISHED不表示撤销确认已被主站接收。满池也可复用原槽撤销。不匹配、重复撤销或ACT_TERM已经提交后再撤销,回否定COT=9。COT=8只撤销匹配业务,STOPDT取消总召及待发短应答;本地Cancel不自动生成COT=9。
Process在任务为空、所有任务等应用/事件空位、发送WAIT或有未读Slave通知时返回;每轮另有2倍任务容量的步数上限,默认8步,余下本地工作由need_process提示。应用无需传入步数,仍须读取BEGIN/NEED_DATA并显式应答;数据未就绪时持续驱动网络和业务期限。
每次业务应答或读取业务事件后重查Get_Status。need_process为true时不休眠:新请求可生成BEGIN、旧FINISHED出队释放空位、下一帧待提交或业务期限到期都可能要求立即推进。它不是waiting的反值;发送WAIT后停止本地续跑,每次Slave_Poll/Process推进网络并读空Slave通知后仍须调用总召Process重试。Get_Status实时更新events_pending,未读Slave通知优先于need_process;外部等待同时遵守Slave_Prepare的网络调度条件。
数据WAIT时,同一网络推进之后还须重试应用保存的NEED_DATA,不能只等待need_process或新业务事件;Process不会替应用再次读取数据。
主站撤销按连接代次、目标CA、OA、TEST和QOI匹配,先于忙拒绝处理。定向撤销可匹配当前站、已确认但尚未取数或等终止的站,其他站继续;广播撤销停止整轮,对各仍活动站回肯定COT=9。尚未选入BEGIN或已结束的站不补发确认。匹配站旧应答STALE,已取BEGIN时有FINISHED(DEACTIVATED),满槽也复用原任务,不补发该站ACT_TERM;已接手帧不撤回。删除不妨碍原活动事务被撤销。详见[撤销与取消](IEC104_Interrogation_设计架构.md#9-撤销取消与连接变化)。
| 配置期限 | 默认值 | 起止点 |
| --- | --- | --- |
| begin_timeout_ms | 15000ms | BEGIN入业务FIFO至应用接受、拒绝或报告失败,包含事件未被取出的时间 |
| data_timeout_ms | 15000ms | 每次NEED_DATA入队至应用交付批次、声明结束或报告失败,每一批单独计时 |
| total_timeout_ms | 60000ms | BEGIN生成至ACT_TERM被发送层接手,包含取数和发送等待,不包含生成BEGIN前等事件空位,不等待ACK |
| data_timeout_ms | 15000ms | 每次NEED_DATA入队至首次有效批次尝试、声明结束或报告失败;有效批次遇发送WAIT也停止本批计时,下一批重新计时 |
| total_timeout_ms | 0(默认禁用) | 启用后从BEGIN生成至ACT_TERM被发送层接手,包含取数和发送等待,不包含生成BEGIN前等事件空位,不等待ACK |
每项配置为0即禁用该项;默认值是可调整的业务策略,不是标准规定的总召时限,也不替代104链路t0..t3。使用Net提供的64位单调时钟,不受校时影响。Process及有效事件的应答接口都会检查期限。START在BEGIN生成前不计业务期限,但已占用总召资格;历史FINISHED挤占业务FIFO时须及时读取,否则当前请求会一直等空位,主站可能先超时。BEGIN生成后开始应用接受和执行总期限。START/CONFIRM超时保留TCP并转为否定ACT_CON;DATA/TERMINATE超时关闭原连接并取消本模块任务,不补发成功ACT_TERM。迟到应答返回STALE。提交新批次或单帧应答不延长执行总期限;总期限与阶段期限同时到点优先报告TOTAL_TIMEOUT。未读Slave通知优先处理,避免把已到达的撤销误算为超时。
每项配置为0即禁用该项;执行总期限默认关闭,接受和每批取数期限仍各为15000ms。默认值是可调整的业务策略,不是标准规定的总召时限,也不替代104链路t0..t3及单帧发送期限。使用Net提供的64位单调时钟,不受校时影响。Process及有效事件的应答接口都会检查期限。START在BEGIN生成前不计业务期限,但已占用总召资格;历史FINISHED挤占业务FIFO时须及时读取,否则当前请求会一直等空位,主站可能先超时。BEGIN生成后开始已启用的应用接受和执行总期限。START/CONFIRM超时保留TCP并转为否定ACT_CON;DATA/TERMINATE超时关闭原连接并取消本模块任务,不补发成功ACT_TERM。迟到应答返回STALE。显式启用后,提交新批次或单帧应答不延长执行总期限;总期限与阶段期限同时到点优先报告TOTAL_TIMEOUT。未读Slave通知优先处理,避免把已到达的撤销误算为超时。
确认边界是肯定ACT_CON被发送层接手,而不是Reply_Begin(true)、TCP写完或收到ACK。CONFIRM提交WAIT时仍可否定激活;已进入HIGH或当前发送缓冲的肯定确认不能改为否定。确认前失败复用原任务槽,保留QOI/OA/TEST并使用本站CA,排在已有REPLY之后,满槽也无需丢弃其他应答。已取出BEGIN才产生一次FINISHED,保留具体TIMEOUT或SOURCE_ERROR;未取出的BEGIN移除。Process转换后重查Slave通知并在本轮继续调度,发送WAIT保留重试,不再计算已终结业务的期限。确认前超时增加timeouts及rejected,BEGIN失败增加rejected,均不增加cancelled。
等待前重新Get_Status;timer_active为true时,Poll或应用事件循环的等待上限取原上限与timeout_remaining_ms的较小值,剩余0则立即驱动Process。Get_Status只查询,不自动执行超时。gi_pending表示唯一总召尚未终结,含BEGIN尚未生成;phase优先报告当前总召阶段,仅有短应答时为REPLY。deactivated记录匹配撤销,timeouts记录执行超时;last_finish_reason也记录BEGIN尚未交付便取消的总召。
COT/CA/IOA错误回45/46/47;激活的非法QOI、P/N、总召忙或应用拒绝回否定ACT_CON,撤销的非法QOI/P/N回否定DEACT_CON。合法新总召满槽时尝试否定ACT_CON;若不能安全提交或保存,就仅丢弃新请求并增加dropped_requests,保留当前任务和TCP。overloads统计需要保存但槽满的次数,negative_submitted统计发送层已接手的否定应答数。丢弃请求没有响应,主站可能超时或重试;Handle_Event返回HANDLED包含这种已处理的丢弃。非法请求布局仍关闭原连接;非法Submit_Batch返回ERROR,保留交互供修正。数据源无法继续时调用Fail_Request:BEGIN阶段保存否定确认并保留连接,NEED_DATA阶段结束原连接;两者均交付FINISHED(SOURCE_ERROR)。BEGIN失败返回OK或超时应答返回STALE后,仍须继续驱动Process,不能假定连接已经断开。
COT/CA/IOA错误回45/46/47;激活的非法QOI、P/N、总召忙或应用拒绝回否定ACT_CON,撤销的非法QOI/P/N回否定DEACT_CON。合法新总召满槽时尝试否定ACT_CON;若不能安全提交或保存,就仅丢弃新请求并增加dropped_requests,保留当前任务和TCP。overloads统计需要保存但槽满的次数,negative_submitted统计发送层已接手的否定应答数。丢弃请求没有响应,主站可能超时或重试;Handle_Event返回HANDLED包含这种已处理的丢弃。
构建在Slave及依赖之外加入IEC104_Interrogation.c。静态容量宏为IEC104_INTERROGATION_CAPACITY(默认随TCP实例容量)、IEC104_INTERROGATION_TASK_CAPACITY(默认4)、IEC104_INTERROGATION_EVENT_CAPACITY(默认4,最小2)。4个任务槽共用于至多一个总召及短应答,不是后续总召队列。任务使用固定槽位和顺序索引,完成的非队首应答可立即释放槽位,其他任务的自引用不受影响。生成BEGIN时同时预留一个终结槽;未读业务事件挤满队列时暂停生成工作事件,已排队单帧应答仍可调度,取消仍可交付FINISHED,不使用malloc。
总召入口先安全读取类型,非100或公共视图不可用返回IGNORED,公共入口先过滤不可用视图再分派业务。Type100唯一对象完整但SQ=1或有尾字节时,仅回否定7/9,COT/CA/IOA错误仍优先45/46/47;副本规范为10字节、VSQ=1,保留OA/TEST/IOA/QOI及原输入事件。零对象/多对象等无法可靠对应请求的情况计入rejected并HANDLED丢弃。两类请求均不执行激活/撤销,不影响原总召及其期限;真实截短/帧错误仍由Receiver和连接层处理。完整策略见[请求校验](IEC104_Interrogation_设计架构.md#31-接收范围及校验顺序)。
本接口替换旧begin/read_batch/finish及parameter配置:应用使用NextEvent和显式应答,快照/游标自行持有。Destroy前须Cancel并读完业务事件。Process只接收实例,旧调用删除第二个预算实参。当前只保留一个未终结总召,忙时否定后续激活,不自动执行此前被拒绝的请求。已删除queue_timeout_ms、QUEUED/QUEUE_TIMEOUT、queued_interrogations和queue_timeouts;调用方删除相关字段及分支,使用Config_Default初始化配置并重新编译。negative_submitted和dropped_requests继续记录否定提交与满槽丢弃;FINISH_OVERLOAD保留枚举值,满槽不以此终结已有任务。
非法Submit_Batch返回ERROR,保留交互供修正。数据源无法继续时调用Fail_Request:BEGIN阶段保存否定确认并保留连接,NEED_DATA阶段结束原连接;两者均交付FINISHED(SOURCE_ERROR)。BEGIN失败返回OK或超时应答返回STALE后,仍须继续驱动Process,不能假定连接已经断开。
构建在Slave及依赖之外加入104/business/IEC104_Interrogation.c及104/business/IEC104_GeneralInterrogation.c,头文件搜索路径包含104/、104/business/和common/。静态容量宏为IEC104_INTERROGATION_CAPACITY(默认TCP实例容量的两倍)、IEC104_INTERROGATION_TASK_CAPACITY(默认4)、IEC104_INTERROGATION_EVENT_CAPACITY(默认4,最小2)。每实例4个任务槽共用于至多一个召唤及短应答,不是后续召唤队列。任务使用固定槽位和顺序索引,完成的非队首应答可立即释放槽位,其他任务的自引用不受影响。生成BEGIN时同时预留一个终结槽;未读业务事件挤满队列时暂停生成工作事件,已排队单帧应答仍可调度,取消仍可交付FINISHED,不使用malloc。
同时接入计数量时另编译104/business/IEC104_CounterInterrogation.c,创建独立实例,配置独占广播状态区。连接及启停通知必须同时交付两边;I帧先总召、再计数量,未接手再交校时等后续模块或兜底。每轮读空Slave通知后推进两个召唤实例,轮换先后顺序,合并need_process、events_pending及最小剩余期限。FRZ=0走确认、数据、终止;FRZ=1..3通过Execute_Begin执行一次本地同步动作,确认后直接FINISHED。两个实例分别Cancel、读取FINISHED、Destroy。动作失败及快照约定见[计数量设计](IEC104_CounterInterrogation_设计架构.md)。
本接口替换旧begin/read_batch/finish及parameter配置:应用使用NextEvent和显式应答,遍历状态自行持有。Destroy前须Cancel并读完业务事件。Process只接收实例,旧调用删除第二个预算实参。当前只保留一个未终结总召,忙时否定后续激活,不自动执行此前被拒绝的请求。已删除queue_timeout_ms、QUEUED/QUEUE_TIMEOUT、queued_interrogations和queue_timeouts;调用方删除相关字段及分支,使用Config_Default初始化配置并重新编译。negative_submitted和dropped_requests继续记录否定提交与满槽丢弃;FINISH_OVERLOAD保留枚举值,满槽不以此终结已有任务。
<a id="flow-clock-synchronization"></a>
### 1.4 接入独立时钟同步模块
编译`104/business/IEC104_ClockSynchronization.c`并包含对应头文件,在Slave创建并初始化后、消费任何Slave通知前创建校时实例。配置只含源标准时区和执行期限,默认480分钟及15000ms;模块使用独立静态池和每实例4个任务槽,不与召唤引擎共享任务。详情见[时钟同步设计](IEC104_ClockSynchronization_设计架构.md)。
1. 将CONNECTED、ACTIVATED、DEACTIVATED及DISCONNECTED按FIFO交付总召、计数量、校时所有实例。I帧按总召→计数量→校时分派,均IGNORED才用Reject_Request兜底;HANDLED也可能表示拒绝或满槽丢弃。
2. `Handle_Event`校验Type 103的唯一对象、COT/CA/IOA、CP56/IV/TEST、源时区及本站日期范围。合法请求保存原文及accepted_ms,不设置时钟;广播在受理时建立目标快照。
3. 读空Slave通知后调用`Process`推进队首应答和未执行请求期限。Process不调用action,不越过等待执行的队首提交后续应答。
4. 应用明确准备执行时调用`Execute_Next(cs, action, context)`。入口检查原会话,使用同一单调采样检查期限及计算滞留补偿,向action交付对应base_ms的utc_ms和固定UTC+8 local_time。只调用最早EXECUTE一次;可以越过较早REPLY执行,但确认保持原FIFO位置。
5. action返回true/false分别保存肯定/否定ACT_CON,均仅回显原CP56并走Try;等待发送不重复动作。应用可把local_time交给Clock_Set,也可更新模拟时钟;不再次处理SU/时区或把local_time当UTC。false和后续取消均不保证硬件没有改变。
6. 重查Get_Status,用execute_pending安排显式执行,用need_process及最近期限安排本地推进和等待。网络推进并读空通知后仍调用Process重试发送WAIT。confirm_pending包含action执行中,需排序的新带时标发布在此期间暂停,既有HIGH/NORMAL继续排空。
7. 退出时校时Cancel,与其他业务分别清理,再Slave_Close、读空并分派通知、校时Destroy,最后移除站点并销毁Slave。STOP取消未交接请求和广播遍历,不回滚时钟,也不撤回已编号确认。
Execute结果须按枚举分支:WAIT先处理Slave通知,EMPTY只表示没有可执行动作、仍可能有待发确认;OK表示调用过action一次,不表示action成功、确认写完或主站ACK。ERROR不应当作发送背压盲目重试。不能在action内推进Slave或重入修改校时对象;Get_Status是唯一允许的本模块只读入口。
时间只补偿Handle_Event受理后的本站滞留;base_ms不是设备生效时刻,不补网络、通知FIFO和驱动耗时。新请求不按内容去重,重发旧时标可能再次回拨。Try不隔离新增NORMAL,持续发布可能使确认延迟;confirm_pending是应用协调依据,不是传输层预留或线程锁。
<a id="flow-2"></a>
@@ -485,8 +522,12 @@ IEC60870_Asdu_Get_Data(&event.asdu, &bytes, &length)
Copy_Event 不发送报文,也不把历史请求重新绑定到当前主站。原连接断开、重新 Init 或服务槽复用之后,旧事件字节仍可用于诊断,旧发送接口会被身份校验拒绝。复制失败保持目标不变;源和目标相同时只执行有效性校验。
延迟任务须逐条处理STOP/START并在STOP时取消原任务,提交前再用`Check_Request`检查当前条件。同一代次恢复START后,最终READY不代表STOP之前的旧任务重新有效。`Reject_Request`只用于当前事件的同步分派,不用于此处的延迟应答;它检查的`pending_state_event_count`不能追溯已经消费的历史启停通知。
READY 会替换传给 NextEvent 的整个输出;EMPTY/ERROR 保持输出原值。把事件地址交给待处理任务后,不应再用同一存储接收下一条事件。多个应答可以使用同一个原请求 connection,待发 ASDU 也可以单独构造,不必都复用 `event.asdu`。
需要精简任务存储时,另一种方式是保存`Slave_Get_Event_Identity`返回的纯值身份,以及业务需要的请求字段/字节。随后先处理通知并检查原请求,再用`Slave_Send_Response / Slave_Try_Send_Response`提交独立ASDU;失败需关闭原连接时用`Slave_Close_Connection_By_Identity`。这些入口保留原服务实例和原连接代次检查。总召采用此方式;完整事件及Copy_Event接口继续保留,容量及排序不因内部存储精简改变。
<a id="flow-7"></a>
## 7. 应答与主动上报如何进入同一发送流程
@@ -495,6 +536,8 @@ READY 会替换传给 NextEvent 的整个输出;EMPTY/ERROR 保持输出原值
### 7.1 请求应答:HIGH 或直接发送缓冲
下图为普通事件应答路径。总召确认/终止经`IEC104_Slave_Send_Response`按保存的原身份进入同一普通HIGH路径;数据经`IEC104_Slave_Try_Send_Response → IEC104_TcpServer_Try_Send_Response → IEC104_Connection_Try_Submit_Response → IEC104_SendManager_Try_Submit_Response`:当前发送缓冲空闲、两级队列均无未提交条目且链路/窗口允许时才直接组帧;否则WAIT,不缓存HIGH。到期协议工作也先返回WAIT,由网络Process处理已到达的ACK。OK后沿同一发送缓冲、记录和非阻塞写入路径推进。
```text
应用:IEC60870_MasterConnection_Send_ASDU / Send_ACT_CON / Send_ACT_TERM
→ 共用层检查连接及函数指针,分派给 Slave 的发送适配器
@@ -530,6 +573,8 @@ WAIT 时保留原 connection、已经准备好的 ASDU 和业务进度,继续
HIGH 是当前连接的应答队列。STOP 受理后尚留在 HIGH 的应答会清空,没有逐条丢弃通知;已经组帧的 I 帧和发送记录继续按实际状态排空。新的应答在停止状态下返回 ERROR。
其他业务均未接手当前I帧时,示例在读取下一事件前同步调用`IEC104_Slave_Reject_Request(slave, &event, IEC60870_ASDU_COT_UNKNOWN_TYPE)`。该入口只将原文COT改为44并置否定位,复用上述普通HIGH路径;输入事件及其最近提交结果保持不变,直接检查返回值。两条连续命令可逐条提交,允许在总召期间应答,不设置示例待应答槽。WAIT仅表示HIGH容量不足,示例丢新并累计overloads/dropped_requests,保留连接与已接手内容;ERROR继续处理失效原因及通知,FATAL可能已写出字节,不重试并排空收尾通知。cause范围、未读启停检查和同步调用限制见[同步拒绝当前请求](IEC104_API接口汇总.md#reject-request)。
### 7.2 主动上报:NORMAL
```text
@@ -937,6 +982,8 @@ ready.service_token = info.service_token;
接入总召模块时,先Interrogation_Cancel,再读取所有业务FINISHED并清理应用资源;最后在Slave_Destroy之前释放总召实例。总召的业务FIFO和Slave的通知FIFO须分别读空,详见[总召接入](#flow-interrogation)。
使用共用站点表时,还须归还业务引用、结束全部快照、解除集合锁,并Remove所有注册节点后才可Slave_Destroy。节点Is_Reusable为true后才可释放;Close/Init保留站点表,不能用网络关闭代替业务清理。
```text
应用停止提交新业务
→ IEC104_Slave_Close() → IEC104_TcpServer_Close()
@@ -988,7 +1035,7 @@ Close 后读空事件,也可以再次 Init 重开;新服务和连接的身
```text
子站入口与身份绑定:IEC104_Slave.c
站总召/分组总召、撤销、业务期限与事件:IEC104_Interrogation.c
站总召/分组总召、撤销、业务期限与事件:104/business/IEC104_Interrogation.c
共用发送接口分派: IEC60870_Slave.c → IEC104_Slave.c 内的发送适配器
ASDU 与信息对象: IEC60870_Asdu.c → IEC60870_InformationObjects.c
监听和网络调度: IEC104_TcpServer.c
@@ -1001,5 +1048,5 @@ ASDU 与信息对象: IEC60870_Asdu.c → IEC60870_InformationObjects.c
平台非阻塞 I/O: IEC104_Net_Windows.c
```
库提供命令、校时、总召、参数和文件等信息对象的编解码,以及响应提交接口。独立IEC104_Interrogation模块已实现QOI=20站总召、QOI=21..36分组总召、多类型批次、发送等待、主站撤销和业务超时;应用通过NextEvent与显式应答管理点表、分组和快照。总电度、命令执行、参数和文件业务尚未接入;业务级权限仍由应用提供。校时时应先解析并验证 CP56Time2a 及时间品质,再按业务策略调用 Clock;Time 的编码转换、Clock 的墙上时间与 Net 的协议单调时钟属于不同用途。
库提供命令、校时、总召、参数和文件等信息对象的编解码,以及响应提交接口。独立召唤引擎已实现站/分组总召和计数量读取/冻结/复位,应用通过NextEvent与显式应答管理数据源。独立ClockSynchronization已实现Type 103校验、补偿、期限及原文确认,仅由Execute_Next调用应用动作。遥控、参数和文件业务尚未接入;业务级权限及硬件操作仍由应用提供。Time的编码转换、Clock的墙上时间与Net的协议单调时钟属于不同用途;默认示例校时只更新模拟值。
@@ -468,7 +468,8 @@ Receiver已实现每连接成帧和完整APDU交付;独立发送队列、业
| STARTDT/STOPDT/TESTFR事件分派 | Connection(已实现) | 按子站角色路由U功能码,由Manager统一提交Link修改;Connection仅作只读Link查询 |
| `t1/t2/t3`、`k`和`w`调用时机 | Connection + TcpServer(已实现) | Poll使用单调时间推进Tick,执行确认、探测和超时收尾;被动监听不发起TCP连接,未实现主站`t0`建连流程 |
| I/S/U实际发送 | TcpServer + Connection(已实现) | Prepare取得待发字节,send后立即回报增量;部分发送下轮续传,业务不再直接调用底层完成接口 |
| 遥信、遥测、遥控、总召、对时等 | ASDU业务层(待接入) | 从NextEvent取出I_FRAME副本后解析公共头及业务对象,携带事件generation调用TcpServer_Send_Response提交应答 |
| 总召、计数量召唤与Type 103校时 | 独立业务模块(已实现) | 通过Slave事件及共用站点表分派,总召/计数量使用业务事件;校时由Execute_Next显式执行,确认回显原时标,见[时钟同步设计](IEC104_ClockSynchronization_设计架构.md) |
| 实际遥信/遥测数据源、遥控及设备设时 | 应用接入层 | 按业务模块请求读取点表或执行设备动作;示例提供模拟点表和模拟时钟,协议库不自动操作硬件 |
### 10.2 统一连接上下文
@@ -522,8 +523,8 @@ I帧完成接口要求快照的 `N(S)==sent_vs`,只推进本地完成边界和
| 顺序 | 实施内容 | 可检查的完成条件 |
|---:|---|---|
| 1(已实现接收基础) | Receiver独立实例、完整帧视图、消费数和显式释放;Link由连接层共享 | 两个接收实例可交错输入;待处理帧不被后续Feed覆盖,旧代次调用被拒绝 |
| 2 | 接入发送前准备、部分写入续传及独立确认记录,补齐实际I/O和收发串行调度 | 发出 `N(S)=0` 后收到 `N(R)=1` 能正确确认并释放原ASDU;发送失败不重复推进状态,旧确认不清除新增待确认数据 |
| 3 | 连接生命周期、U 控制、定时器及窗口策略 | 受控传输与时间事件下可验证启动、停止、心跳、超时和窗口边界;序号错误停止当前批次并关闭连接 |
| 4(对象编解码已实现) | IEC60870_Asdu与InformationObjects提供监视/命令对象编解码和应答构造;继续接入业务分派、点表与执行流程 | 已有精确长度、品质/时标、容量与原文应答测试;地址匹配、总召进度、校时及遥控业务验收仍由后续实现补齐 |
| 2(已实现) | 接入发送前准备、部分写入续传及独立确认记录,补齐实际I/O和收发串行调度 | 发出 `N(S)=0` 后收到 `N(R)=1` 能正确确认并释放原ASDU;发送失败不重复推进状态,旧确认不清除新增待确认数据 |
| 3(已实现) | 连接生命周期、U 控制、定时器及窗口策略 | 受控传输与时间事件下可验证启动、停止、心跳、超时和窗口边界;序号错误停止当前批次并关闭连接 |
| 4(对象及三类业务已实现) | 公共对象编解码、总召/计数量、校时校验及显式执行;继续接入真实数据源和遥控 | 自动回归覆盖地址路由、召唤进度、校时原文确认及时间边界;模拟模式不设系统时间,硬件设时和实际主站联调须另行验收 |
---
+35 -10
View File
@@ -2,7 +2,9 @@
当前子站业务统一包含`IEC104_Slave.h`:Slave内部持有不透明TcpServer,提供生命周期、事件循环、主动上报和状态查询,并在NextEvent交付I帧时自动建立ASDU视图及原连接的共用应答接口。业务不再手动创建TcpServer、绑定generation或安装函数指针。入口和延迟任务示例见[子站统一入口](#子站统一入口)。
总召由独立库模块[IEC104_Interrogation.c/h](../../104/IEC104_Interrogation.h)实现,支持QOI=20站总召、21..36分组总召及多类型无时标监视数据,负责批次校验、发送等待、主站撤销、业务超时及停用/断线取消。应用通过BEGIN/NEED_DATA/FINISHED事件和显式应答选择点表分组、管理快照及终结;[子站示例](../../examples/iec104_slave_example.c)提供模拟点表、事件分派和main,构建运行见[示例说明](../examples/IEC104子站示例.md)。
Slave提供[共用站点表](IEC104_API接口汇总.md#station-registry):应用提供固定地址节点,动态注册多个CA和业务context,重复CA拒绝。删除立即退出路由,已有引用和遍历结束后才回收;Is_Reusable为true后应用才能释放存储。总召设置config.use_station_registry=true后按请求CA选站;多目标广播配置每站状态区,以一个任务执行“全部确认、逐站数据、全部终止”。其他业务可复用相同的查找、引用及快照遍历接口。
总召由独立库模块[IEC104_Interrogation.c/h](../../104/business/IEC104_Interrogation.h)实现,支持QOI=20站总召、21..36分组总召及多类型无时标监视数据,负责批次校验、发送等待、主站撤销、业务超时及停用/断线取消。应用通过BEGIN/NEED_DATA/FINISHED事件和显式应答管理IOA、分组、遍历、逐批取值及终结;[子站示例](../../examples/iec104_slave_example.c)提供模拟点表、事件分派和main,构建运行见[示例说明](../examples/IEC104子站示例.md)。
内部调用关系为`Slave → TcpServer → Connection → SendManager/Receiver/Link`,网络I/O仍经Net执行,当前提供Windows后端。现有单主站、静态内存、HIGH/NORMAL队列、事件FIFO、Prepare/Process或Poll调度策略保持不变。共用ASDU及Slave发送接口继续使用IEC60870前缀;公共层不依赖104传输实现。下文的底层接口说明供维护和独立TCP接入使用,业务默认通过Slave操作服务。
@@ -10,6 +12,8 @@
`NextEvent`区分连接建立(CONNECTED)、数据传输激活(ACTIVATED)、停用(DEACTIVATED)和连接断开(DISCONNECTED),四者共用`event.transport.data.connection`快照及对端地址。启停事件在对应ACT成功受理并提交状态变化后产生,CON可能仍待发送;同状态重复ACT不重复通知,直接断开不补造停用事件。业务可按事件类型处理启停,无需通过查询状态比较变化。
`Get_Status`的`pending_state_event_count`报告尚未消费的ACTIVATED/DEACTIVATED数量,不能追溯已消费的启停历史。当前请求的错误应答可在同步分派中调用`IEC104_Slave_Reject_Request`,支持COT=44..47,复用HIGH且不改输入事件;不新增示例应答队列。调用时机、WAIT丢新策略与FATAL不可重试约定见[同步拒绝当前请求](IEC104_API接口汇总.md#reject-request)。
发送队列NORMAL与`NextEvent`业务通知FIFO是两份独立存储:NORMAL满时可覆盖旧ASDU原文;已入队业务通知逐条保留,不覆盖、不合并启停变化。通知按有效载荷长度存放于字节环,配置宏仍表示保证条数:默认普通30条最长通知、启停4条及收尾32条记录加1个最长I帧和1个断开通知。当前x64 ABI下环占11243字节,接收暂存默认2048字节,两块合计13291字节,比原来减少43.9%;16字节APDU可在普通区缓存185条。普通通知不能占用启停及收尾预留;启停配额满时暂缓受理后续改变状态的ACT,已有CON继续发送。详细公式见[TCP服务设计](IEC104_TcpServer_设计架构.md)。
新I帧无普通空间可交付时保留原帧并暂停续收,`Prepare`及`Get_Status`的`receive_paused`为1;NextEvent腾出空间后安排Process恢复,等待中的I帧先于回执交付。已确认回执因普通空间或本轮预算不足暂留内部时,可能晚于随后发生的启停通知交付,保持不因回执积压而暂缓启停CON的行为。通知积压不直接断连,但保留I帧会阻挡后续S/U;长期不消费仍可能触发原有协议超时。应用应在每轮Process/Poll后及时取出事件。此次存储调整保持公开API及事件结构不变。
@@ -28,8 +32,10 @@
| `IEC104_Codec.c/h` | 104专用APCI检查、I/S/U帧编码和帧类型 |
| `IEC60870_Asdu.c/h` | 通用ASDU容器、布局校验、对象分派与原文应答;使用说明见下文“ASDU信息对象模块” |
| `IEC60870_Slave.c/h` | 101/104共用的主站连接接口,分派ASDU发送、激活确认和激活终止 |
| `IEC104_Slave.c/h` | 子站统一入口:持有TCP服务,自动交付ASDU/应答接口,支持延迟事件复制及原连接隔离 |
| `IEC104_Interrogation.c/h` | 总召独立业务模块:站/分组总召、主站撤销、业务期限、事件与显式应答、多类型批次及发送等待;接入见[总召流程](IEC104_完整调用流程.md#flow-interrogation) |
| `IEC104_Slave.c/h` | 子站统一入口:TCP服务、ASDU/应答接口、原连接隔离及各业务共用的CA站点表 |
| `business/IEC104_Interrogation.c/h` | 共用召唤引擎:任务、广播、期限、事件、取消与发送等待;接入见[总召流程](IEC104_完整调用流程.md#flow-interrogation) |
| `business/IEC104_GeneralInterrogation.c/h` | Type100规则及兼容的Interrogation_Create入口 |
| `business/IEC104_CounterInterrogation.c/h` | Type101规则、创建及一次动作执行入口;见[计数量设计](IEC104_CounterInterrogation_设计架构.md) |
| `IEC104_TcpTypes.h` | Slave与TcpServer共用的配置、调度、事件和状态值类型,无服务操作接口 |
| `IEC60870_InformationObjects.c/h` | 67种独立信息对象,覆盖监视、命令、初始化、参数和文件;编码通过只读函数表分派,支持变长文件段 |
| `IEC60870_Time.c/h` | 101/104共用时间工具:CP16/CP24/CP56Time2a编解码、本地标准时间转换、SU修正、日历/星期和纪元秒数工具;使用约定见[时间工具设计](../common/IEC60870_Time_设计架构.md) |
@@ -59,7 +65,9 @@
| 104 APCI与I/S/U编解码 | `IEC104_Codec.c` |
| ASDU信息对象编解码 | `IEC60870_Asdu.c`、`IEC60870_InformationObjects.c`、`IEC60870_Codec.c`、`IEC60870_Time.c`;C11及IEEE 754 binary32,不依赖TcpServer或Clock |
| 子站统一入口 | 在ASDU及TCP依赖之外增加`IEC60870_Slave.c`、`IEC104_Slave.c`;共用接口本身不依赖TCP或串口实现 |
| 104站/分组总召 | 在Slave及其依赖之外加入`IEC104_Interrogation.c`,共用Net单调时钟;应用按QOI选点表,响应NEED_DATA提交数据 |
| 104站/分组总召 | 在Slave及其依赖之外加入`104/business/IEC104_Interrogation.c`和`104/business/IEC104_GeneralInterrogation.c`,头文件搜索路径增加`104/business/`;应用按QOI选点表,响应NEED_DATA提交数据 |
| 104计数量召唤 | 引擎加`104/business/IEC104_CounterInterrogation.c`,头文件搜索路径增加`104/business/`;与总召分别创建,状态通知交付两边,动作通过Execute_Begin执行 |
| 104时钟同步 | Slave依赖加`104/business/IEC104_ClockSynchronization.c`,独立实例及静态池;只由Execute_Next执行同步action,模块不依赖Clock设备适配 |
| 101/104公共时间工具 | `IEC60870_Time.c`,对已有时间值进行日历校验、时间转换及编解码 |
| 101/104时间源:Windows系统时钟 | `IEC60870_Clock.c`、`IEC60870_Time.c`;`IEC60870_CLOCK_USE_RTC=0`(默认),Clock.c直接使用Windows的Kernel32接口 |
| 101/104时间源:RTC预留接口 | 上述两个源文件,定义`IEC60870_CLOCK_USE_RTC=1`,另需板级驱动实现`IEC60870_Clock_RTC_Read/Set`;具体芯片驱动暂不提供 |
@@ -71,7 +79,7 @@
| 独立流式接收 | `IEC104_Receiver.c`、`IEC104_Codec.c`、`IEC60870_Codec.c`;不依赖Link、SendManager或日志 |
| 旧单路逐字节接收 | `IEC104.c`、`IEC104_Codec.c`、`IEC104_Link.c`、`IEC104_Log.c` |
时间工具以`IEC60870_Time`作为公共入口,供101和104的ASDU业务层及平台接入层按需调用,不在Connection中自动执行校时。`IEC60870_InformationObjects.c`复用CP56Time2a处理带时标信息对象和命令,实际事件采集及校时业务流程仍由应用接入。**业务发送入口负责使用本地标准时间日历,并显式设置SU=0。** 公共编解码继续支持SU=0和1,按输入保留标志,不强制或自动清零。发送前须保证日历本身已是本地标准时间,不能只清除夏令时标志而保留原小时值。
时间工具以`IEC60870_Time`作为公共入口,供101和104的ASDU业务层及平台接入层按需调用,不在Connection中自动执行校时。`IEC60870_InformationObjects.c`复用CP56Time2a处理带时标信息对象和命令;Type 103校验及确认使用独立ClockSynchronization模块,实际设时由应用显式执行。**新生成业务时标使用本地标准时间日历并设置SU=0;Type 103确认回显原CP56及SU。** 公共编解码继续支持SU=0和1,按输入保留标志,不强制或自动清零。生成新时标前须保证日历本身已是本地标准时间,不能只清除夏令时标志而保留原小时值。
时间源提供UTC纪元秒数时,直接调用`IEC60870_Time_Seconds_To_Local(utc_seconds, ms, &local_time)`即可取得固定UTC+8的本地年月日、时分秒、毫秒和星期,调用前后都无需手动加八小时。接口内部统一处理跨日/月/年及范围检查;原始UTC秒数上限为4102415999,超过时返回0并保留输出。RTC已直接提供本地标准时间日历时,可直接构造CP56解析值并编码。
@@ -117,7 +125,7 @@ I/S的启停准入和停止阶段立即确认规则集中在 `IEC104_Link.c` 的
公共层仅需 `IEC60870_Asdu.c`、`IEC60870_InformationObjects.c`、`IEC60870_Codec.c`、`IEC60870_Time.c`;头文件依赖也不包含任何104模块。101接入提供约定的字段宽度和容量,104接入提供标准2/2/3及249字节上限。两种接入共用同一套对象构造和解析接口。
业务包含`IEC104_Slave.h`,取得`Slave_NextEvent`交付的I帧后可直接使用ASDU视图并解析具体对象,或先构造ASDU再交给发送接口。ASDU编解码层不分配内存、不操作连接、不读取或设置系统时钟;共用连接接口可构造并提交确认/终止应答。站/分组总召可交给IEC104_Interrogation管理校验、分批、撤销、期限和重试,应用响应业务事件提交数据,自行维护分组并持有和清理快照;其他命令的选择/执行、校时权限及业务任务由应用接入。
业务包含`IEC104_Slave.h`,取得`Slave_NextEvent`交付的I帧后可直接使用ASDU视图并解析具体对象,或先构造ASDU再交给发送接口。ASDU编解码层不分配内存、不操作连接、不读取或设置系统时钟;共用连接接口可构造并提交确认/终止应答。站/分组总召可交给IEC104_Interrogation管理校验、分批、撤销、期限和重试,应用响应业务事件提交数据,自行维护IOA、分组及遍历状态;其他命令的选择/执行、校时权限及业务任务由应用接入。
| 层次 | 职责 |
|---|---|
@@ -279,19 +287,25 @@ if (event.transport.type == IEC104_TCP_EVENT_I_FRAME &&
事件含有指向自身的ASDU视图和连接上下文,不能用赋值或memcpy搬到另一地址。延迟任务可让NextEvent直接写入自身稳定存储,或调用`IEC104_Slave_Copy_Event(&task.event, &event)`;复制时同时保存字节、重建自引用,并保留原服务实例和原连接代次。源事件随后可被下一次NextEvent覆盖,任务副本仍独立可用;复制不代表需要重发已经OK的报文。多个任务的事件存储由应用提供,库不额外引入待办任务池。
总召内部使用紧凑存储:只保留`Get_Event_Identity`取得的原身份及10字节请求ASDU,按需建立视图,通过`Slave_Send_Response / Slave_Try_Send_Response`应答,关闭原连接用`Slave_Close_Connection_By_Identity`。任务槽、事件队列和发送容量保持不变,内存测量见[固定存储](IEC104_Interrogation_设计架构.md#2-对象身份与固定存储)。其他需要完整事件的业务仍可用Copy_Event。
NextEvent返回EMPTY/ERROR时保持输出不变。Close、重连、重新Init或Destroy后复用服务槽,都不会把旧任务改绑到新连接;发送继续检查原身份。Destroy成功后旧服务句柄失效,但旧事件的字节仍由任务自身保存。所有延迟任务也在所属事件循环串行提交。
多帧总召的数据和终止应经同一事件的connection依次提交,逐帧处理WAIT;把数据放NORMAL而终止放HIGH会造成优先级越序。门面不实现点表、总召状态机、遥控选择/执行或系统校时。
[子站示例](../../examples/iec104_slave_example.c)通过BEGIN/NEED_DATA/FINISHED事件与显式应答接入独立的[总召模块](../../104/IEC104_Interrogation.h)推进业务;Slave和TcpServer继续负责服务、连接及传输。总召接入顺序见[完整调用流程](IEC104_完整调用流程.md#flow-interrogation)。
[子站示例](../../examples/iec104_slave_example.c)通过BEGIN/NEED_DATA/FINISHED事件与显式应答接入独立的[总召模块](../../104/business/IEC104_Interrogation.h)推进业务;Slave和TcpServer继续负责服务、连接及传输。总召接入顺序见[完整调用流程](IEC104_完整调用流程.md#flow-interrogation)。
调用`IEC104_Interrogation_Process(gi)`自动推进当前就绪任务,无需传入预算。每个实例至多一个未终结总召,包含等待生成BEGIN的请求;忙时相同和不同QOI的新激活均否定,不保存后续总召。4个任务槽共用于这个总召和待发短应答。Handle_Event在原会话有效、Slave通知已读空且无更早REPLY时尝试立即提交短应答,未接手再保存。满槽时尝试否定确认;不能安全提交/保存则仅丢新请求并累计dropped_requests,保留当前任务及TCP。Process在空闲、等待应用/事件空位、发送WAIT或Slave通知未读空时返回。对象、状态、调度及接入约定见[总召模块设计](IEC104_Interrogation_设计架构.md)。
BEGIN携带QOI=20..36,应用选择全站或对应分组,数据COT必须等于QOI;未配置组可拒绝,空组可正常完成。本站配置CA限于1..65534,总召请求可使用本站CA或IEC104_CA_BROADCAST(IEC104_Types.h中固定为0xFFFFU)。广播请求的业务事件、确认、数据、终止及错误应答使用本站CA,Submit_Batch拒绝65535;输入Slave事件保持原文,其他错误CA仍按原值回复COT=46。应答CA在请求入口确定、内部副本建立时写入,发送及重试直接沿用;任务只维护info这一份业务元数据。
总召Get_Status的`need_process`用于决定是否立即续跑,不能从`waiting`取反推断。应答或读取业务事件后重新查询;发送WAIT后等待网络推进再重试。每次Slave_Poll/Process及Slave通知处理结束仍须推进总召,即使need_process为false。外部事件循环同时合并Slave_Prepare的调度条件;新增状态字段后调用方须重新编译。
主站COT=8按原连接、实际站CA/OA/TEST/QOI匹配总召,65535先映射到本站CA,因此激活与撤销可交叉使用广播和本站地址。模块停止旧任务,排队COT=9,并以FINISHED(DEACTIVATED)通知资源清理;旧批次应答失效。已被发送层接手的帧仍按顺序发送,撤销不补发成功ACT_TERM。
BEGIN由调用者判断是否接受,遍历状态可延至首个NEED_DATA建立;NEED_DATA由调用者选择本批IOA并读取当前值和品质,协议库不登记或冻结点表。Submit_Batch立即尝试不排队提交,WAIT不保存数据、不推进游标,应用在网络推进后重新取当前值重试;OK才固定帧顺序并推进批次。点值更新及突发发布须与总召读值及提交串行化,持续突发可能推迟总召,详见[数据批次与发送路径](IEC104_Interrogation_设计架构.md#6-数据批次与发送路径)。
业务接受、每批取数和执行整体期限默认15秒、15秒、60秒,各项0可禁用;这些是业务默认策略,不是标准规定的总召时限。BEGIN生成时开始计时,此前等待业务事件空位没有独立超时,但已占用总召资格,应及时读取FINISHED。START/CONFIRM超时及BEGIN阶段Fail_Request复用原槽发送否定ACT_CON,保留TCP及其他短应答;肯定ACT_CON被发送层接手后,取数超时、总超时和数据源失败仍关闭原连接。终结事件保留具体原因,BEGIN未取出则只移除工作事件。接受BEGIN前检查点表、数据源及必要快照资源,仍可逐批编码数据。等待前查询timer_active/timeout_remaining_ms并持续驱动Process。此次失败处理修复不新增接口或配置开关;先前已移除queue_timeout_ms及多总召排队状态/统计,保留negative_submitted和dropped_requests,使用旧字段的调用方须调整并重新编译。
BEGIN携带实际CA、station_id和QOI=20..36;应用检查数据源并决定接受,首个NEED_DATA才需建立取数游标。多目标广播先完成全部BEGIN/ACT_CON,再逐站取数,最后全部ACT_TERM;每站状态区保证FINISHED无损交付。Submit_Batch只接受事件目标CA。新增/替代站下轮参加,尚未选入BEGIN便删除的站跳过;已经选中尤其已确认的原站必须完成或明确终结。各站独立计时,总期限默认关闭;FINISHING的终结事件读完前仍忙拒绝新激活。详见[总召设计](IEC104_Interrogation_设计架构.md#33-广播地址与本站地址)。
主站COT=8按原连接、目标CA/OA/TEST/QOI匹配总召,包括已确认但尚未取数或等终止的站。定向撤销匹配站,其他站继续;广播撤销停止整轮,对各仍活动的站回肯定COT=9。FINISHED(DEACTIVATED)通知应用清理,匹配站旧批次失效。已被发送层接手的帧仍按顺序发送,撤销不补发成功ACT_TERM。
业务接受和每批取数期限默认各15秒,执行整体期限默认0(禁用),各项均可单独配置;这些是业务默认策略,不是标准规定的总召时限。关闭整体期限允许慢链路累计传输超过原60秒上限,底层单帧发送、确认和链路探测期限仍有效。BEGIN生成时启动已启用的业务期限,此前等待业务事件空位没有独立超时,但已占用总召资格,应及时读取FINISHED。总期限显式设为非零后仍按固定整体耗时计算,不因新批次或发送重试而延长。START/CONFIRM超时及BEGIN阶段Fail_Request复用原槽发送否定ACT_CON,保留TCP及其他短应答;肯定ACT_CON被发送层接手后,取数超时、总超时和数据源失败仍关闭原连接。终结事件保留具体原因,BEGIN未取出则只移除工作事件。接受BEGIN前检查数据源及必要业务资源,仍可逐批取值并编码。等待前查询timer_active/timeout_remaining_ms并持续驱动Process。此次失败处理修复不新增接口或配置开关;先前已移除queue_timeout_ms及多总召排队状态/统计,保留negative_submitted和dropped_requests,使用旧字段的调用方须调整并重新编译。
原`IEC104_SlaveConnection_Init`和手动接入对象已移除,调用方改用Slave服务及其事件接口并重新编译。TCP底层接口仍供独立通信接入和维护测试使用,业务的默认入口统一为Slave。
@@ -299,6 +313,14 @@ BEGIN携带QOI=20..36,应用选择全站或对应分组,数据COT必须等
时标保留IV/SU;IV=1可解码但不代表可校时。业务新上报应提供本地标准时间且SU=0,应答回显保留请求时标。验证命令和覆盖范围见[测试说明](../test/IEC104测试文档.md)。
### 时钟同步业务
[ClockSynchronization](../../104/business/IEC104_ClockSynchronization.h)处理Type 103、COT=6、IOA=0,按共用站点表路由;COT=5读时和COT=8撤销回45。独立静态池默认与TCP服务容量相同,每实例4个任务槽,与总召/计数量分别创建、分别取消。Handle_Event校验保存,Process处理期限及FIFO应答,只有应用显式Execute_Next会调用本次传入的同步action,不保存函数指针。
默认源标准时区480分钟、执行期限15000ms,0禁用期限;IV/SU及时区由公共时间工具处理,64位毫秒计算只补偿受理后的站内滞留。action收到的local_time已经是固定UTC+8,base_ms为执行检查时刻。肯定/否定确认保留原CP56字节,不回读时钟或提供自定义确认时标。
调用过action的确认走Try,错误校验应答走普通HIGH;WAIT只重试确认,STOP取消未交接任务但不回滚时钟。广播动作一次,快照目标分别确认;满槽丢新并计数,不断链。同内容的新请求仍执行,可能再次回拨。持续NORMAL发布可能推迟Try确认,应用通过包含回调执行期的confirm_pending串行协调新的带时标上报。默认示例只设模拟时钟,`--clock system`才调用Windows时间源。完整契约见[时钟同步设计](IEC104_ClockSynchronization_设计架构.md)和[调用流程](IEC104_完整调用流程.md#flow-clock-synchronization)。
## 启停主流程
| 角色与过程 | 调用顺序 |
@@ -331,6 +353,7 @@ HIGH在完成交接记账时消费队列原文,SendBuffer中的完整I帧及Ma
```text
主动上报:TcpServer_Enqueue_Event → 服务ASDU队列(首次连接前、断线期间均可缓存)
连接应答:TcpServer_Send_Response → Connection_Submit_Response → Manager.Submit_Response(直接准备或缓存HIGH)
总召数据:TcpServer_Try_Send_Response → Connection_Try_Submit_Response → Manager.Try_Submit_Response(只能直接准备,不能排队则WAIT)
内部发送:Connection经Manager准备字节 → TcpServer经Net写socket → Connection回报Manager进度
内部接收:TcpServer经Net收字节 → Connection经Receiver成帧、由Manager统一提交链路事件
事件交付:TcpServer复制I帧/回执到事件队列并释放内部资源 → 业务通过NextEvent取副本
@@ -342,6 +365,8 @@ HIGH在完成交接记账时消费队列原文,SendBuffer中的完整I帧及Ma
`Prepare_Next_Send` 的成功值是 **OK**;`Submit_Response`以OK表示直接准备、QUEUED表示已缓存HIGH;控制帧入队成功仍为QUEUED,不能按布尔值判断。两条I帧路径共用组帧和发送记录,直接应答不经过ASDU队列,也不绕过旧HIGH、当前帧、窗口或公平配额。TcpServer的`Send_Response`最多尝试一次非阻塞写入;EAGAIN保留当前帧和期限,部分写入不能交错其他APDU。当前I帧已经准备后,即使暂时写出0字节,也要先完成,再发后来排队的控制帧。
`Try_Submit_Response`还要求NORMAL没有未提交条目;无发送机会时只返回WAIT,不降级为HIGH排队。Slave提供事件连接入口`Try_Send_ASDU`和纯值身份入口`Try_Send_Response`;总召使用后者,WAIT后重读数据。它们仍通过原SendBuffer、发送记录、k窗口和链路期限,不要求等待所有已提交帧的ACK。已有普通应答和主动上报接口的排队语义不变。
业务提交统一返回`IEC104_TcpSubmitResult_t`:`IEC104_TCP_SUBMIT_OK`表示已接手ASDU,不保证已写完或已ACK;WAIT表示未接手,业务保留重试;FATAL可能发生在已写出字节后,须处理收尾事件。`Enqueue_Event`只缓存服务事件,`Send_Response`尝试直接发送或缓存连接应答;调用后重新Prepare刷新调度条件。旧Enqueue_Normal/Enqueue_High入口与TCP_ENQUEUE结果名已移除,不保留兼容包装。
子站受理STARTDT_ACT即进入STARTED,STARTDT_CON作为发送任务按FIFO完成。STOPDT受理即停用并停止选取新业务,最多完成一帧此前已编号的当前I帧,等待已发帧确认后返回CON;未提交的NORMAL保留未编号,HIGH按下述规则清理。公共S、t2、TESTFR、t3及子站启停继续由Manager封装。接收接口的WAIT表示请求尚未受理,接入方须有界保留原帧并按接收顺序重试。
+3 -1
View File
@@ -5,7 +5,7 @@
## 推荐阅读顺序
1. [模块说明与接入示例](104/README.md):公共 ASDU、子站统一入口及模块依赖。
2. [子站示例说明](examples/IEC104子站示例.md):构建运行、模拟点表与总召处理。
2. [子站示例说明](examples/IEC104子站示例.md):构建运行、模拟点表、总召、计数量与校时处理。
3. [完整调用流程](104/IEC104_完整调用流程.md):从应用入口到协议收发的调用关系。
4. [API 接口汇总](104/IEC104_API接口汇总.md):按文件查阅接口。
5. [测试说明](test/IEC104测试文档.md):测试入口、构建命令、覆盖范围与验证记录。
@@ -21,6 +21,8 @@
| 104 | [连接层设计](104/IEC104_Connection_设计架构.md) | 单连接收发协调与生命周期 |
| 104 | [TCP 服务设计](104/IEC104_TcpServer_设计架构.md) | 网络适配、事件循环及容量配置 |
| 104 | [总召设计](104/IEC104_Interrogation_设计架构.md) | 站总召、分组、撤销、超时与业务事件 |
| 104 | [计数量设计](104/IEC104_CounterInterrogation_设计架构.md) | 读取、冻结、复位、动作执行及双实例接入 |
| 104 | [时钟同步设计](104/IEC104_ClockSynchronization_设计架构.md) | Type 103、显式设时动作、滞留补偿、期限及广播确认 |
| 104 | [完整调用流程](104/IEC104_完整调用流程.md) | 各层调用顺序与业务流程 |
| 104 | [API 接口汇总](104/IEC104_API接口汇总.md) | 104 及其依赖的公共接口 |
| 公共 | [时间工具设计](common/IEC60870_Time_设计架构.md) | CP16/CP24/CP56、日历转换与时钟接口 |
@@ -1,6 +1,6 @@
# IEC 60870-5-101/104 公共时间工具
`IEC60870_Time.c/h` 为 IEC 60870-5-101 和 IEC 60870-5-104 提供共用的公历计算、CP16/CP24/CP56Time2a 编解码、本地标准时间转换及纪元秒数工具。时间源模块集中在`IEC60870_Clock.c/h`:Clock.c包含公共校验和Windows读写,Clock.h包含业务接口及RTC预留声明。时间源限定为Windows系统时钟或RTC,由`IEC60870_CLOCK_USE_RTC`在编译时二选一;RTC具体芯片驱动暂不实现,也不规划其他操作系统适配。时间相关的 ASDU 业务入口尚未接入;下文约定后续业务使用本地标准时间,并给出调用示例。原始解码保留源日历和标志,业务再调用转换接口取得本地标准时间日历。公共模块不维护连接状态、共享可变状态或动态内存,可独立于协议链路编译和链接;时间戳转日期可选择平台库或保留的纯整数算法。
`IEC60870_Time.c/h` 为 IEC 60870-5-101 和 IEC 60870-5-104 提供共用的公历计算、CP16/CP24/CP56Time2a 编解码、本地标准时间转换及纪元秒数工具。时间源模块集中在`IEC60870_Clock.c/h`:Clock.c包含公共校验和Windows读写,Clock.h包含业务接口及RTC预留声明。时间源限定为Windows系统时钟或RTC,由`IEC60870_CLOCK_USE_RTC`在编译时二选一;RTC具体芯片驱动暂不实现,也不规划其他操作系统适配。Type 103业务已由独立的[ClockSynchronization模块](../104/IEC104_ClockSynchronization_设计架构.md)接入,应用显式执行设时动作;公共Time/Clock仍不自行分派ASDU。原始解码保留源日历和标志,业务再调用转换接口取得本地标准时间日历。公共模块不维护连接状态、共享可变状态或动态内存,可独立于协议链路编译和链接;时间戳转日期可选择平台库或保留的纯整数算法。
## 101/104 的共用边界
@@ -14,7 +14,7 @@
IEC 60870-5-101:2003/AMD1:2015 第7.2.6.18条将SU规定为可选且不推荐使用的功能;如果选用,SU=1与时间值早一小时且SU=0的标签表示同一时刻。第7.4.6条要求不使用夏令时时将SU置0。参见[标准修订单第4至6页](https://preview.sist.si/sist-preview/22451/3a4cae004abe434883eba6133f897d87/IEC-60870-5-101-2003-AMD1-2015.pdf)。
**后续业务发送入口负责保证:使用本地标准时间(固定UTC+8),并显式设置`is_summer_time=0U`。** 公共编解码继续支持SU=0和1,按输入保留合法的SU值;公共编码接口不强制SU=0,也不会自动清除SU=1。该发送约束由后续业务入口落实,目前文档中的发送函数仅为调用示例。
**应用新生成的业务时标使用本地标准时间(固定UTC+8),并显式设置`is_summer_time=0U`。** 公共编解码继续支持SU=0和1,按输入保留合法的SU值;公共编码接口不强制SU=0,也不会自动清除SU=1。Type 103的ACT_CON属于原请求回显,保留原CP56及SU,不套用新生成时标的SU=0规则。本文中的通用发送函数仍为调用示例。
业务发送时,输入日历须已经是本地标准时间。若来源是夏令时日历,应先完成时间转换,再以SU=0编码,不能只把SU清零而保留夏令时的小时值。接收时保留SU=1的兼容处理:原始解码后调用`IEC60870_Time_CP56_To_Local`,取得已处理SU的本地标准时间日历;接收兼容不改变本地发送SU=0的约定。
@@ -30,7 +30,7 @@ IEC 60870-5-101:2003/AMD1:2015 第7.2.6.18条将SU规定为可选且不推荐使
| 业务发送示例 | 调用方提供已是本地标准时间的日历,示例显式设置SU=0,IV由零初始化保持为0 |
| 业务接收示例 | `From_CP56 → CP56_To_Local`,直接取得本地标准时间日历 |
`From_CP56`只保留源日历及SU;`CP56_To_Local`读取完整解析值,输出已经修正为本地标准时间的`IEC60870_WallTime_t`。**调用方不需要再加八小时或再减夏令时的一小时。** 使用独立输出存储时,原始输入不改写;若输入与输出重叠,成功写回会覆盖重叠的存储,调用方须按转换后的时间基准使用结果,再次编码时构造新的CP56值并置SU=0。目前仓库尚未提供实际的校时业务处理器,下文给出本地标准时间收发的调用方式。
`From_CP56`只保留源日历及SU;`CP56_To_Local`读取完整解析值,输出已经修正为本地标准时间的`IEC60870_WallTime_t`。**调用方不需要再加八小时或再减夏令时的一小时。** 使用独立输出存储时,原始输入不改写;若输入与输出重叠,成功写回会覆盖重叠的存储,调用方须按转换后的时间基准使用结果,再次生成时标时构造新的CP56值并置SU=0。ClockSynchronization使用`CP56_To_Seconds`取得UTC秒数,在64位毫秒域补偿受理后的滞留,再调用`Seconds_To_Local`;确认直接回显原请求,不重编码补偿后的日历。
转换接口的`source_standard_utc_offset_minutes`表示“源报文标准当地时间减UTC”的分钟数,向东为正、向西为负。双方约定本地标准时间时传`IEC60870_LOCAL_UTC_OFFSET_MINUTES`(480);源报文为UTC时传0,源标准时区为UTC-5时传-300。-1439..1439是本接口约定的参数范围;输出时区始终是本地标准时间。
@@ -64,8 +64,8 @@ SU本身不携带时区信息;源偏移须来自双方约定。这里的“本
| CP56转本地标准时间,检查IV、处理SU和源时区,输出完整日历 | `IEC60870_Time_CP56_To_Local` |
| 统一读时、设时入口,校验日历、重算星期并检查时间质量标志 | `IEC60870_Clock` |
| Windows系统时钟读写,UTC与固定UTC+8日历互换 | `IEC60870_Clock.c`中的Windows分支 |
| 发送前保证日历已是本地标准时间,并统一设置SU=0 | ASDU 业务层,后续业务发送入口落实 |
| 事件时间戳、校时 ASDU 的业务处理,确定时间约定并调用转换接口 | ASDU 业务层 |
| 新生成时标使用本地标准时间并设置SU=0;请求回显保留原时标 | ASDU业务层,Type 103确认由ClockSynchronization按原文生成 |
| 校时ASDU校验、期限、滞留补偿及确认 | `IEC104_ClockSynchronization`;设备权限、实际设时及事件时间戳仍由应用接入 |
| 实现`IEC60870_Clock_RTC_Read/Set`,处理RTC寄存器、BCD、时间基准、掉电标志及读写一致性 | 板级接入层和具体RTC驱动,暂不实现 |
| t1/t2/t3 及发送期限使用的单调计时 | 平台提供 `now_ms`,Link/Connection 使用 |
@@ -75,14 +75,14 @@ SU本身不携带时区信息;源偏移须来自双方约定。这里的“本
## 当前接入状态与待补齐项
当前已实现日期转换、星期计算、CP56Time2a编解码、统一时间源接口及Windows系统时钟适配。完整的协议时间业务还需补齐以下接入:
当前已实现日期转换、星期计算、CP56Time2a编解码、统一时间源接口、Windows系统时钟适配及Type 103业务模块。公共工具、协议处理及设备接入的边界如下:
| 项目 | 当前状态与待补齐内容 |
|---|---|
| 协议时间源读取及设置 | 已提供直接调用的`Clock_Read/Clock_Set`及Windows读写实现;时间源限定为Windows或RTC,RTC目前仅预留接口。日志读取仍使用原有入口 |
| RTC数据适配 | 接入函数声明已提供,具体芯片驱动暂不实现;后续接入时再明确RTC采用UTC还是本地标准时间、返回整数还是BCD、年份表示和毫秒来源,并按芯片要求取得一致的日期、时分秒及亚秒值,整理为固定UTC+8的公共日历 |
| 时间有效性及失败处理 | `Clock_Read`分别返回操作状态和`is_invalid`,校验日期及0/1标志,失败保留输出;具体驱动负责识别未初始化、掉电或失去可信时间。下文示例传递质量到CP56的IV;实际业务仍须决定是否发送无效时间。IV=1也不能用全零日期占位 |
| ASDU业务收发与校时 | `IEC60870_InformationObjects.c`的带时标类型复用基础对象值处理,并调用CP24/CP56编解码;保护事件和延时命令使用CP16。含103校时命令,保留IV/SU。`IEC60870_Asdu.c`维护容器与原文应答。事件采集、时间源读取、校时权限、平台设时及成功/失败应答的业务决策仍需应用接入 |
| ASDU业务收发与校时 | `IEC60870_InformationObjects.c`复用CP16/CP24/CP56编解码,`IEC60870_Asdu.c`维护容器与原文应答;ClockSynchronization处理Type 103校验、排队、补偿、显式执行及成功/失败确认。事件采集、校时权限及平台设时仍由应用action接入;示例默认模拟,显式system模式调用Clock_Set |
| 日期转换实现 | 保留Windows CRT和纯整数算法两种实现。RTC直接提供日历时无需时间戳展开;需要转换时间戳且没有Windows CRT时使用整数算法,不规划增加其他操作系统时间库 |
| 项目内的时间模块回归 | `iec60870_asdu_test.c`已覆盖CP56已知字节、IV/SU保留、非法日期和保留位,并分别使用两种时间实现编译运行;这不等于完整日期转换一致性测试。独立转换边界及平台读写失败、时间质量用例仍待补齐 |
@@ -181,7 +181,7 @@ if (!IEC60870_Time_CP56_To_Local(&timestamp,
return IEC60870_Clock_Set(&local_time);
```
该例假定源标准时区也是UTC+8;其他源时区应传双方约定的偏移。`Clock_Set`只接收日历,不能自行判断报文IV、SU或校时权限;不能跳过转换直接把解码的源日历交给设时接口。以上函数和片段说明接入顺序,尚未注册为ASDU业务处理器,实际成功/失败的业务应答仍由ASDU层负责。
该例假定源标准时区也是UTC+8;其他源时区应传双方约定的偏移。`Clock_Set`只接收日历,不能自行判断报文IV、SU或校时权限;不能跳过转换直接把解码的源日历交给设时接口。以上片段演示公共接口的直接用法。通过ClockSynchronization接入时,action收到的`request->local_time`已完成校验、时区换算及受理后滞留补偿,可直接交给Clock_Set,不再次换算;肯定/否定确认由业务模块生成。
## 时间戳转日期的实现选择
@@ -237,7 +237,7 @@ Windows显式使用64位版本,避免32位`time_t`限制原有的1970..2099年
- **星期**:`has_weekday=0` 编码为报文星期0;为1时从日期计算1=周一至7=周日。解码允许星期0,此时仍计算内存中的 `time.weekday`;非零星期必须与日期一致,否则失败。这样不依赖调用方传入的过期星期,也不会默默接受相互矛盾的日期和星期。
- **IV**:编解码保留该标志。IV=1表示业务时间无效,不等于报文格式无法解析;调用方必须检查该标志后再决定是否记录或使用时间。即使IV=1,本模块也要求日期和时间字段合法,不接受全零日期等占位数据。
- **SU**:公共编解码支持0和1,复制标志而不调整日历值;本地标准时间转换接口在SU=1时扣除一小时,并处理源标准时区与本地标准时间的差值。编码或转换时SU大于1均失败。后续业务发送入口负责设置SU=0;接收时将完整解析值交给`CP56_To_Local`,不丢弃SU后直接使用原时间。
- **SU**:公共编解码支持0和1,复制标志而不调整日历值;本地标准时间转换接口在SU=1时扣除一小时,并处理源标准时区与本地标准时间的差值。编码或转换时SU大于1均失败。应用新生成时标使用SU=0,Type 103确认保留原SU;接收时将完整解析值交给转换接口,不丢弃SU后直接使用原时间。
- **保留位**:编码统一清零,解码严格拒绝非零值。分钟bit6按本模块采用的布局视为保留位,不接受将它另作扩展标志的报文。这是明确的接入约束,不能把此接口当作任意七字节数据的无损容器。
数值校验成功只说明日期真实存在、各字段符合上述约定。它不保证远端时钟准确,也不决定校时权限或业务接受策略。
@@ -1,14 +1,34 @@
# 104子站示例
[iec104_slave_example.c](../../examples/iec104_slave_example.c) 包含模拟点表、业务事件处理、其他命令兜底、事件循环及main。总召状态机使用独立库模块[IEC104_Interrogation.c/h](../../104/IEC104_Interrogation.h),不在示例内重复实现。
[iec104_slave_example.c](../../examples/iec104_slave_example.c) 包含模拟点表、业务事件处理、其他命令兜底、事件循环及main。总召状态机使用独立库模块[IEC104_Interrogation.c/h](../../104/business/IEC104_Interrogation.h),不在示例内重复实现。
当前实现站总召(`C_IC_NA_1`,QOI=20),返回8个单点遥信和8个标度化遥测值;分组1(QOI=21)只返回遥信,分组2(QOI=22)只返回遥测。库支持全部16组,示例未配置的其余组由应用拒绝。通信通过 `IEC104_Slave`,总召通过 `IEC104_Interrogation` 接入,所有任务使用固定存储、在主循环线程串行推进。模拟点表是只读的;变化上报、实际设备取值、校时及遥控可在这个入口继续接入。
当前实现站总召(`C_IC_NA_1`,QOI=20),返回8个单点遥信和8个标度化遥测值;分组1(QOI=21)只返回遥信,分组2(QOI=22)只返回遥测。库支持全部16组,示例未配置的其余组由应用拒绝。通信通过 `IEC104_Slave`,总召通过 `IEC104_Interrogation` 接入,所有任务使用固定存储、在主循环线程串行推进。示例另含计数量召唤和Type 103校时;默认时钟为模拟值,实际设备数据源及遥控仍由应用接入。
ExampleApp持有一个零初始化的Station节点。Example_Init按`--ca`注册CA及应用context,再以`config.use_station_registry=true`创建总召。命令行仍只注册一个站,状态区参数传NULL/0;扩展多CA时须提供broadcast_states/broadcast_capacity,广播先确认所有站,再逐站数据,最后终止所有站。BEGIN只判断接受;首个NEED_DATA才按station_id查原节点并Acquire,建立唯一取数游标。Complete_Data返回OK后释放游标及引用,异常时按FINISHED清理。处理中删除/同CA重建不改变旧数据源。节点须等Is_Reusable为true后释放,见[共用站点表](../104/IEC104_API接口汇总.md#station-registry)。
例如最多一轮覆盖13站时,由应用提供以下存储并在Create前写入配置(或传给示例内部Example_Init的后两个参数):
```c
static IEC104_InterrogationStationState_t gi_stations[13];
/* config已经通过IEC104_Interrogation_Config_Default初始化。 */
config.use_station_registry = true;
config.broadcast_states = gi_stations;
config.broadcast_capacity = sizeof(gi_stations) / sizeof(gi_stations[0]);
```
数组借用至Destroy,期间不可读写或重叠使用;它保存身份、进度和终结状态,不保存点值。注册表不限制为13站,但若本轮目标数超过状态区容量,会在任何肯定确认前整轮否定。单目标不需要该状态区。
## 构建与运行
命令行示例还创建独立的Type101计数量实例。IOA3001..3004为第1组,初值120000、345600、7890、0;IOA3005..3008为第2组,初值1、65535、2147483647、42。RQT=5读取全部,1/2读取对应组,3/4拒绝。每批4个Type15对象、SQ=0;读取开始固定冻结快照,WAIT不混入新版本。
FRZ=1冻结并将序号模32加一;FRZ=2另将当前值清零;FRZ=3仅清当前值,冻结值和序号不变。动作通过Execute_Begin执行一次;TEST动作拒绝。FRZ=0读冻结值并发送ACT_TERM;FRZ=1..3只回ACT_CON后结束。CY/CA/IV在模拟点表中为0,不模拟真实计量品质。STOP统计原字段保持总召含义,末尾增加counter_submitted和counter_completed。
主循环向总召、计数量、校时实例广播生命周期通知,交替推进召唤业务并显式执行校时;其他类型在三个模块均未接手后兜底。原Example_Init仍可用于仅总召测试,计数量及校时通过各自初始化辅助函数补充,两个召唤实例各需独立广播状态区,校时使用共用站点快照。协议及应用动作限制见[计数量设计](../104/IEC104_CounterInterrogation_设计架构.md)和[时钟同步设计](../104/IEC104_ClockSynchronization_设计架构.md)。
在项目根目录的 PowerShell 中执行,需要 MinGW GCC:
示例通过 `IEC104_Interrogation.h` 引入库声明;构建脚本同时添加 `104/` 和 `common/` 头文件搜索路径,编辑器也应配置这两个目录。构建可执行程序仍使用下面的脚本,它会链接所需库源文件及 Windows 网络库;只编译当前 C 文件会缺少库函数实现。编辑器的 C 语言标准应与构建保持一致,使用 C11,并选择实际的 MinGW GCC 编译器。
示例通过 `IEC104_Interrogation.h`、`IEC104_CounterInterrogation.h` 和 `IEC104_ClockSynchronization.h` 引入业务声明;构建脚本添加 `104/`、`104/business/` 和 `common/` 头文件搜索路径,编辑器也应配置这三个目录。构建可执行程序仍使用下面的脚本,它会链接所需库源文件、Clock适配及Windows网络库;只编译当前C文件会缺少库函数实现。编辑器的C语言标准应与构建保持一致,使用C11,并选择实际的MinGW GCC编译器。
```powershell
./scripts/build.ps1
@@ -27,10 +47,19 @@
| `--port 0..65535` | 默认2404;0由系统分配,启动时输出实际端口 |
| `--ca 1..65534` | 本站公共地址,默认1;总召请求也接受65535,广播请求的应答使用本站地址 |
| `--window 1..32767` | 发送窗口k,默认12;设为1可观察逐帧ACK后续发 |
| `--clock simulated\|system` | 默认simulated,只更新示例模拟时钟;system显式调用Windows系统设时接口 |
| `--run-ms N` | 运行约N毫秒后正常关闭;默认0表示持续运行 |
| `--help` | 打印用法后退出 |
启动输出 `LISTEN 地址:端口 CA=站地址`。退出输出已提交ASDU数、已提交完毕的总召数、WAIT次数、拒绝数、取消任务数、积压超限次数、主站撤销数及业务超时数。`submitted` 和 `gi_submitted` 表示报文已交给发送模块,不表示主站已经ACK。
启动输出 `LISTEN 地址:端口 CA=站地址`。退出保留原有STOP统计字段:`submitted`累计总召与兜底已接手ASDU,`gi_submitted`为已提交完毕的总召数,二者均不表示主站已经ACK。`waits`只统计总召发送WAIT;兜底HIGH满时丢新,计入`overloads`和`dropped_requests`,不记录待重试次数。兜底ERROR/FATAL计入`cancelled`,其中FATAL可能已写出字节,该计数不表示确定未发送。拒绝、主站撤销及业务超时统计继续保留,计数量另见`counter_submitted`和`counter_completed`。末尾校时字段`clock_executed`为实际action调用数、`clock_synchronized`为action返回true次数、`clock_submitted`为已交接校时ASDU数,包含错误及否定应答,广播按各提交目标分别计数;不混入原字段。
### Type 103校时
主站发送Type=103、SQ=0、对象数=1、COT=6、P/N=0、TEST=0、CA匹配或65535、IOA=0及有效CP56。默认按主站UTC+8标准时间解释,SU由时间工具处理;模块补偿受理至执行检查的等待。应用显式Execute_Next后回COT=7,原CP56字节原样回显。TEST否定7且不执行;COT=5读时否定45,不返回本站当前时间。
默认`--clock simulated`把请求的utc_ms/base_ms保存为模拟时钟基准,不改变系统时间。`--clock system`通过`IEC60870_Clock_Set(&request->local_time)`设置Windows时间;local_time已是固定UTC+8且已补偿,示例不再手工换时区或加等待。Clock内部转换为UTC后调用系统接口,权限不足时动作报告失败并回否定ACT_CON。自动回归只运行模拟模式。
确认发送WAIT时只重试原确认,不再次设时;STOP取消未交接确认而不撤销已完成的动作。同内容的新请求会再执行,旧时标重发可能回拨。真实数据发布方应在confirm_pending期间暂停新的带时标上报并继续驱动网络,避免持续新增NORMAL推迟确认。完整范围及期限见[时钟同步设计](../104/IEC104_ClockSynchronization_设计架构.md)。
## 主站操作和模拟点表
@@ -50,9 +79,9 @@
3. 两帧Type=11、COT=20的标度化遥测,每帧4个对象。
4. Type=100,COT=10,激活终止。
分组总召各提交4个ASDU:ACT_CON、两批对应类型数据、ACT_TERM。分组1数据为Type=1/COT=21,分组2数据为Type=11/COT=22;确认和终止保留请求QOI。分组选择由BEGIN分支设置应用游标实现,不把模拟点表写进库。
分组总召各提交4个ASDU:ACT_CON、两批对应类型数据、ACT_TERM。分组1数据为Type=1/COT=21,分组2数据为Type=11/COT=22;确认和终止保留请求QOI。BEGIN校验分组,首个NEED_DATA按该组初始化应用游标,不把模拟点表写进库。
全部应答使用原请求的 `connection`,沿HIGH路径保持提交顺序。总召数据不使用 `Enqueue_Event`,以免低优先级数据排在高优先级终止之后。
全部应答使用原请求的连接身份。总召确认、终止及短应答走普通HIGH路径,总召数据由Submit_Batch尝试不排队提交;校时调用过action的确认也使用Try。未提交HIGH/NORMAL、当前帧或窗口背压时Try返回WAIT;校验错误仍走普通HIGH。总召数据不使用Enqueue_Event,只有本批接手后才推进游标及后续终止。
主站撤销使用相同的实际站CA/OA/TEST/QOI,COT改为8、P/N=0。激活和撤销均接受本站地址或65535,匹配时65535映射为本站地址,允许广播激活配本站撤销及本站激活配广播撤销。尚有匹配总召时模块停止任务、回复肯定COT=9,并通过FINISHED(DEACTIVATED)通知应用;不匹配或已完成则否定COT=9。已提交发送层的帧可能仍在确认前发出,撤销后不再提交旧批次和ACT_TERM。示例点数很少,总召可能在主站发撤销前已全部提交;观察中途撤销和各阶段拥塞可使用下面的可控网络测试。
@@ -62,7 +91,7 @@
| 情况 | 本示例的处理 |
|---|---|
| 不支持的Type ID | 回显原文,COT=44、P/N=1 |
| 不支持的Type ID | 同步调用Reject_Request回显原文,COT=44、P/N=1;HIGH满时丢新并计数,不因连续请求断链 |
| 总召COT不是6或8 | COT=45、P/N=1 |
| 总召CA既不是本站地址也不是65535 | COT=46、P/N=1,保留原错误CA |
| 总召IOA不是0 | COT=47、P/N=1 |
@@ -71,23 +100,30 @@
| 激活QOI为23..36 | 合法请求交给应用,示例因未配置点表而Reply_Begin(false),回否定ACT_CON |
| 撤销不匹配、QOI超范围或P/N非法 | 否定DEACT_CON,COT=9、P/N=1 |
| 匹配的主站撤销 | 取消对应总召,肯定DEACT_CON,COT=9、P/N=0 |
| 总召SQ、数量或完整长度不合法 | 结束原连接,不执行业务 |
| 总召唯一对象完整,但SQ=1或有多余尾字节 | 回否定7/9,COT/CA/IOA错误优先45/46/47;不执行激活或撤销 |
| 总召零对象/多对象等无法可靠应答的布局错误 | 计入rejected并丢弃;保留连接和已有总召 |
| 需要新增总召且4个任务槽已占满 | 尝试否定ACT_CON;无法安全提交/保存则丢新请求并计数,保留连接与原任务;匹配撤销复用原槽 |
| 接受/取数/整体业务期限到点 | 肯定ACT_CON提交前回否定确认并保留连接,提交后关闭原连接;已取出BEGIN才交付对应TIMEOUT终结事件 |
非总召命令先留给其他业务处理器;本示例最终回UNKNOWN_TYPE,单独保存一个待发送请求,若该槽占用时再收到未知命令则关闭原连接。
Example_Handle_Event先过滤asdu_result失败的I帧视图,避免进入业务分派或未知类型兜底;正常线上基础头/载荷截短已由Receiver和连接层处理。当前I帧依次交给总召、计数量、校时及后续业务;都未接手时,在读取下一事件前同步调用`IEC104_Slave_Reject_Request`回UNKNOWN_TYPE。该接口借用当前事件生成原文错误应答,复用库的普通HIGH路径;示例不保存other_request,也没有other_pending、other_waiting或other_waits。未支持类型的连续及相同内容有效新请求逐条尝试应答,允许穿插正在进行的总召。103现在由校时模块接手,102仍回44;模块之间不承诺跨业务应答按接收顺序排列。
校验从类型开始;已知总召先验证完整对象布局,再检查COT、CA、IOA及QOI。错误应答调整COT/P/N,广播总召还将CA改为本站地址,保留其他原文字节。这里只实现上述命令集合,不代表所有类型的业务规则。
OK表示已接手;WAIT只因HIGH容量不足,示例丢弃本条并增加overloads/dropped_requests,保持连接、已有HIGH内容及当前业务。ERROR按失效请求处理并继续读通知;FATAL可能已写出字节,不得重试,继续排空收尾通知。接口不修改输入事件或其最近提交结果,不能通过Get_Last_Submit_Result读取本次返回值。未读启停通知会阻止本次应答;状态计数不能追溯已消费的STOP/START,禁止先保存事件再读完后续通知后调用。需要异步业务应答时另用Copy_Event、Check_Request并自行处理STOP取消,完整契约见[同步拒绝当前请求](../104/IEC104_API接口汇总.md#reject-request)。
`Example_Drain_Business`主动读取总召业务事件:BEGIN按QOI选点表,Reply_Begin接受或拒绝请求;NEED_DATA调用普通辅助函数Example_Build_Batch构造数据,再Submit_Batch,数据耗尽则Complete_Data;FINISHED按实例/请求编号清理对应资源,撤销和超时也走该分支。没有向库注册业务函数指针。数据源暂未就绪时可保存事件,后续就绪再应答;发送WAIT由模块保留原文,不重复索取本批。
校验从类型开始;已知总召先检查是否有唯一完整对象,再按COT、CA、IOA、布局/QOI/P/N及事务状态决定。畸形单对象的否定副本规范为10字节、VSQ=1,保留OA/TEST/IOA/QOI,不修改原输入。广播应答使用实际站CA。错误请求不取消原总召或刷新期限;否定应答沿现有队列重试,满槽只丢新请求。这里只实现上述命令集合,不代表所有类型的业务规则。
Submit_Batch返回OK后输入存储可复用;返回WAIT则先处理Slave通知,保留同一业务事件和构造结果重试;STALE表示原交互已失效(包括取消、撤销、超时),丢弃旧应答并处理FINISHED。示例的两条事件流均在主循环串行处理,新增数据类型只需调整应用的构造分支。
`Example_Drain_Business`主动读取总召业务事件:BEGIN按QOI选点表,Reply_Begin接受或拒绝请求;NEED_DATA调用普通辅助函数Example_Build_Batch构造数据,再Submit_Batch,数据耗尽则Complete_Data;FINISHED按实例/请求编号清理对应资源,撤销和超时也走该分支。没有向库注册业务函数指针。数据源暂未就绪时可保存事件,后续就绪再应答;发送WAIT也保留事件,下一次调用重新组包,模块不缓存本批原文或重复生成NEED_DATA。
本例BEGIN只判断接受,首个NEED_DATA初始化游标并读取这一批;接真实数据源时,在Example_Build_Batch的逐点组包处读取当前值和品质。IOA、类型、分组成员及遍历策略均由调用者决定,协议库不登记或冻结完整IOA列表。示例常量表只用于展示业务接入,不是库规定的地址范围。逐批采样不保证整轮同一时刻,也不自动解决与突发上报的新旧排序;完整边界见[总召模块设计](../104/IEC104_Interrogation_设计架构.md#6-数据批次与发送路径)。
Example_Build_Batch使用局部游标遍历,仅在Submit_Batch返回OK后推进应用游标,此时发送层已复制原文并确定帧顺序,输入存储可复用。返回WAIT时保留同一业务事件及游标,先推进网络、读空Slave通知,再从同一位置重新取当前值和品质,不能重发旧构造结果。STALE表示原交互已失效(包括取消、撤销、超时及提交故障),丢弃旧应答并处理FINISHED。接实际数据源时,须将点值更新及突发发布与总召读值及提交串行化;排序保证及持续突发可能推迟总召的边界见[数据批次与发送路径](../104/IEC104_Interrogation_设计架构.md#6-数据批次与发送路径)。
`IEC104_Interrogation_Process(app->interrogation)`自动推进当前就绪任务,无需预算参数。Handle_Event在原会话有效、Slave通知已读空且没有更早REPLY时立即尝试提交短应答;未接手再保存,无法保存则计入dropped_requests。Process继续处理保存的应答及串行总召;任务为空、全部等待应用/事件空位、发送WAIT或Slave有未读通知时返回。Example_Step随后消费业务事件,下一轮继续推进;不在Process内部执行应用代码。
Example_Init使用默认业务期限:BEGIN应答15秒、每批NEED_DATA应答15秒、执行整体60秒;对应begin_timeout_ms/data_timeout_ms/total_timeout_ms,各项0表示禁用。BEGIN生成前不计业务期限,生成后开始完整执行总期限。START/CONFIRM超时或BEGIN阶段Fail_Request转为否定ACT_CON并保留TCP及已有短应答;肯定ACT_CON提交后发生取数超时、总超时或NEED_DATA失败,仍关闭原连接。失败通知保留具体原因,须继续读取FINISHED并驱动Process,不能根据BEGIN失败假定已断链。业务FIFO被旧FINISHED占用时要及时读取,才能启动当前请求,期间仍拒绝新的总召。Example_Wait_Time在每次Poll前重新查询最近期限,将50ms等待上限进一步缩短。期限使用单调时钟,不代替链路t0..t3,也不等待主站ACK。退出统计包含negative_submitted和dropped_requests;丢弃请求没有响应,主站可能超时或重试。
Example_Wait_Time直接读取总召状态的`need_process`,不再用`pending_requests && !waiting`推断。业务应答后或旧FINISHED出队释放空位时,下轮Poll使用0等待;发送WAIT后允许等待网络。每次Poll并读空Slave通知后,Example_Step仍调用总召Process重试,不能用need_process为false跳过;未读Slave通知始终优先处理。
STOPDT或断线通知取消未提交任务;ERROR/FATAL也取消,不把旧请求交给新连接。事件消费预算耗尽时先续读通知,再推进业务,防止尚未读取的STOP/START被跨过。关闭时执行`Interrogation_Cancel → 读空业务事件并清理FINISHED → Slave_Close → 读空Slave通知 → Interrogation_Destroy → Slave_Destroy`。总召Destroy遇到活动任务或未读业务事件会拒绝释放。
Example_Init使用默认业务期限:BEGIN应答15秒、每批NEED_DATA至首次有效批次尝试15秒、执行整体期限0(禁用);对应begin_timeout_ms/data_timeout_ms/total_timeout_ms,各项0表示禁用。有效批次因发送背压WAIT后,本批取数计时停止,下一批NEED_DATA才重新计时;应用仍须重读并重试。BEGIN生成前不计业务期限;总期限显式设为非零时,从BEGIN生成起计算完整执行耗时,发送等待计入且不因新批次而延长。START/CONFIRM超时或BEGIN阶段Fail_Request转为否定ACT_CON并保留TCP及已有短应答;肯定ACT_CON提交后发生取数超时、总超时或NEED_DATA失败,仍关闭原连接。失败通知保留具体原因,须继续读取FINISHED并驱动Process,不能根据BEGIN失败假定已断链。业务FIFO被旧FINISHED占用时要及时读取,才能启动当前请求,期间仍拒绝新的总召。Example_Wait_Time在每次Poll前重新查询最近期限,将50ms等待上限进一步缩短。期限使用单调时钟,不代替链路t0..t3及单帧发送期限,也不等待主站ACK;默认关闭整体期限不影响这些底层保护。退出统计包含negative_submitted和dropped_requests;丢弃请求没有响应,主站可能超时或重试。
STOPDT或断线通知取消未提交任务。发送内部受理STOP而返回ERROR时,总召确认原连接正常停用后先让出,待通知终结为STOPPED;其他ERROR/FATAL仍失败。Submit_Batch为等待STOP通知而返回WAIT时不停止取数计时,应用继续按FIFO交付通知,旧交互随后STALE。事件消费预算耗尽时先续读通知,再推进业务。关闭时执行`Interrogation_Cancel → 读空业务事件并清理FINISHED/Release → Slave_Close → 读空Slave通知 → Interrogation_Destroy → Slave_Remove_Station → Slave_Destroy`。总召Destroy遇到活动任务或未读业务事件会拒绝释放;应用引用须持有到FINISHED清理,不能用Remove成功代替资源清理。
## 验证
@@ -95,13 +131,27 @@ STOPDT或断线通知取消未提交任务;ERROR/FATAL也取消,不把旧请
./scripts/build.ps1 -Test
```
`-Test` 另外需要 Node.js,只使用其内置模块,无需安装依赖。它先运行59组可控网络测试,再运行真实本机TCP测试:
`-Test` 需要预先安装 Node.js。脚本依次运行32组Slave/站点测试(单服务及双服务配置)、130组总召/计数量/兜底/校时可控网络测试(单服务及双服务)、3组规则单测和真实本机TCP测试:
- `iec104_slave_test.c`:站点注册/移除、延迟回收、重复CA、13站共享服务、CA/context隔离、注册编号防替换、快照增删及嵌套集合锁、关闭重启保留,以及原有应答、事件身份和背压回归。Reject_Request另覆盖原文回显、非法参数、连续请求、窗口背压、HIGH满、启停门禁、旧会话及FATAL。
- `iec104_slave_example_test.c`:完整报文、部分写入、各发送阶段HIGH满与WAIT、请求校验、STOP/重连、非法请求、传输通知预算及API生命周期;业务事件覆盖延迟应答、批次校验、混合类型、容量边界、拒绝/失败/取消、终结预留和实例复用。总召覆盖全部16组、各阶段撤销、三项期限、迟到应答及通知优先。调度覆盖短应答立即提交及顺序保护、非队首槽复用、满槽否定或计数丢弃且当前任务有效、各阶段相同/不同QOI均忙拒绝、结束后重发成功、撤销对象匹配、停止清理、BEGIN前等事件空位及BEGIN后完整执行期限,并验证忙状态不拦截Type101总电度请求。测试设置HIGH=251字节、业务事件FIFO=2槽,使用真实容量背压限制发送。
- `slave_example_loopback_test.cjs`:启动实际可执行程序,使用Windows网络后端和127.0.0.1随机端口,以独立主站字节验证分片请求、k=1时逐帧ACK、站总召、多个不同QOI连续请求的忙拒绝及结束后重发成功、主站撤销与重复撤销、错误应答、STOP/START、重连和正常退出。服务约4秒后自行关闭。
- `slave_example_loopback_test.cjs`:启动实际可执行程序,使用Windows网络后端和127.0.0.1随机端口,以独立主站字节验证分片请求、k=1时逐帧ACK、站总召、忙拒绝及结束后重发、撤销、错误应答、106与102同批分别回44、STOP/START、重连和正常退出。校时核对正常ACT_CON、TEST否定7、读时COT=5否定45、广播实际CA,以及103与102同批时先102回44、再103回7;连接保留。服务约8秒后自行关闭,使用默认模拟时钟。
原114组基线由98组总召、12组计数量及4组兜底回归组成。兜底改用未支持的106/102,覆盖同批及反序、相同内容的有效新请求、k=1与写阻塞排队、HIGH满后丢新且不补发、STOP/STOPSTART取消,以及总召未结束时立即提交错误应答。启用校时的独立回归另外核对103业务。
新增16组校时回归覆盖无站广播45/46、同批及重复103逐条执行、FIFO中间否定、满槽丢新、Try等待不重执行、STOP/重连、Defer/FATAL、同一次采样检查期限及补偿、action内Get_Status与防重入、广播WAIT期间删除当前站及同CA替代不加入。所有action均为模拟,不设置系统时间。
畸形请求回归覆盖SQ/尾字节否定、零/多对象丢弃、基础头及载荷截短仍由Receiver拒绝、原事件不变、错误原因优先级、畸形激活/撤销不影响原BEGIN/NEED_DATA及期限、背压满槽后的FIFO恢复和多CA广播撤销隔离。
确认前失败另覆盖BEGIN尚未取出的超时、满任务池及HIGH等待时复用原槽、QOI/OA/TEST保持且应答使用本站CA、旧应答FIFO顺序、同连接重新总召、STOP/撤销/断线优先。确认已被发送层接手但尚未写出时,取数失败及超时仍断链,避免将已接受请求改发否定确认。
广播回归覆盖QOI=20..36、非1的本站地址、确认/数据/终止WAIT、立即或排队的错误及忙应答、确认前失败、激活/撤销地址的四种组合、源Slave事件不变,以及Submit_Batch拒绝广播或其他站CA。空闲和忙时的广播Type101继续交给后续处理器。真实TCP额外验证广播全站/分组、连续请求的忙拒绝、非法QOI和广播/本站撤销组合,共核对62个ASDU及11次正常完成的总召。
need_process回归覆盖完整业务交互、事件空位释放、总召等待应用时的短应答、CONFIRM/数据/终止/短应答的发送WAIT及恢复、到期立即续跑和未读STOP/START优先,并验证只读查询不提交帧或重复累计waits。
提交期通知回归覆盖真实启停容量背压留下STOP、三个发送入口的STOPPED归因、FATAL与STOP并存时仍为SEND_ERROR、非STOP的ERROR、OK/WAIT顺带交付RECORD时暂停后续提交,以及START后重新总召。
不排队提交回归覆盖旧NORMAL先于新总召、WAIT期间新突发先发后重新取值、HIGH积压、已接手总召部分写入后突发不能越过、背压不计取数超时而下一批重新计时、k=1窗口释放、到期ACK优先处理及发送故障终结。WAIT时验证队列和批次进度不增加,OK后覆盖输入存储验证发送层持有独立副本。
广播回归覆盖QOI=20..36、13个CA全部确认/数据/终止顺序、非空混合批次、状态区不足及跨实例重叠、已确认站删除/同CA替换、三阶段WAIT、超出FIFO容量的FINISHED、各阶段定向撤销及满槽广播撤销、拒绝与撤销同时等待、非当前站总期限和STOP/断线清理。Submit_Batch拒绝广播CA及非当前站CA;Type101继续交后续处理器。真实TCP用命令行注册的单站验证广播全站/分组、忙拒绝、非法QOI、撤销组合及连续兜底,共核对65个总召/兜底ASDU及11次正常完成的总召,计数量另行核对;5个校时应答独立计数,其中3次执行并成功,另2次为TEST及COT拒绝。多CA由可控Net回归覆盖。
这些检查覆盖本示例的模拟点表流程;实际主站设备、现场点表和设备执行接口仍需联调。
+43 -5
View File
@@ -1,6 +1,6 @@
# IEC104 测试说明
> **目录与命令同步日期**:2026-10-26。测试代码位于根目录的 `test/`,本文仅列出当前保留的测试入口及可运行命令。
> **目录与命令同步日期**:2026-10-29。测试代码位于根目录的 `test/`,本文仅列出当前保留的测试入口及可运行命令。
> **配套文档**:[TCP服务设计](../104/IEC104_TcpServer_设计架构.md)、[链路设计](../104/IEC104_Link_设计架构.md)、[发送模块设计](../104/IEC104_发送模块_设计架构.md)。
## 1. 当前测试入口
@@ -13,14 +13,20 @@
`iec104_tcp_scheduler_test.c`复用上述可控Net后端,包含25个调度场景;其中2个明确验证原有严格容量背压策略仍会超时,不能把测试通过解释为消除了队头阻塞。`iec104_net_pending_input_test.c`使用当前Windows后端进行真实本机回环检查。
`iec104_slave_test.c`复用同一可控Net后端,包含15组统一子站入口和共用应答接口测试:12种字段布局的可写ASDU借用、原地字段修改和失败保留、函数指针分派、从I_FRAME事件到确认/终止的完整发送字节、构造容器发送、HIGH满后的WAIT及重试、非法布局/长度/空指针、事件身份及请求状态校验、旧代次拒绝及FATAL诊断;另覆盖延迟事件复制、Close/Init及服务槽复用、两层池生命周期、外部调度和离线主动上报。共用层使用非104参数的测试接入验证分派,不代表已提供101串口传输。
`iec104_slave_test.c`复用可控Net后端,共32组统一入口测试,覆盖ASDU视图、共用发送分派、确认/终止字节、HIGH背压、非法参数、原连接身份、延迟复制、服务复用、生命周期、外部调度及离线上报。站点测试覆盖13节点、CA/context隔离、删除即时退出路由与引用延迟回收、同CA替代节点的注册编号、快照期间增删/嵌套/结束回收、显式集合锁及跨服务误用;新增9组同步拒绝回归。共用层非104参数测试不代表已提供101串口传输。
因接口过期而无法编译的旧协议、发送管理器、TCP模拟后端和Windows回环测试及其专用辅助文件已删除。这里新增的测试均使用当前接口,历史测试未恢复。
`iec104_slave_example_test.c`验证总召模块及单文件示例,包含59组业务流程测试:完整报文、部分写入、各阶段WAIT续发、错误请求、忙时否定确认、停用/断线取消、格式错误、任务积压及通知预算后的STOP/START。`test/slave_example_loopback_test.cjs`启动实际示例进程,通过Windows后端和真实本机TCP验证总召及错误应答。构建及点表见[示例说明](../examples/IEC104子站示例.md)。
`iec104_slave_example_test.c`包含原98组总召基线,覆盖总召报文、部分写入、各阶段WAIT、忙拒绝、撤销、STOP/断线、need_process、突发排序、不排队提交及业务/链路期限。提交期通知覆盖三个入口的STOPPED归因、FATAL与STOP并存、非STOP的ERROR、OK/WAIT伴随新回执及START后恢复。畸形请求覆盖可靠否定/丢弃边界、基础截短仍被Receiver拒绝、输入原文保持、活动总召/期限不变、背压满槽及广播撤销隔离。多站覆盖13站三阶段顺序、非空混合批次、状态区不足/重叠、已确认未来站删除与同CA重建、超出2槽FIFO的FINISHED交付、各阶段定向及满槽广播撤销、拒绝与撤销同时等待、非当前站总期限,以及总期限禁用、BEGIN先到期和两项同时到期的处理优先级。`test/slave_example_loopback_test.cjs`通过Windows后端与真实TCP验证命令行单站,多CA由可控Net回归覆盖。构建及点表见[示例说明](../examples/IEC104子站示例.md)。
同步拒绝接口回归检查COT=44..47的原文回显、TEST/OA/CA/VSQ及对象字节保留、输入事件和最近提交结果不变、非法参数及原服务/会话隔离。未读启停通知拒绝提交;已消费STOP/START后的旧事件不在同步接口允许的调用范围内,不能用状态计数为0证明其仍有效。示例回归覆盖连续及重复内容的新命令、HIGH真满时丢新且保留连接/旧应答、活动总召中的兜底、启停与发送故障收尾;原同批兜底回归继续保留。接入校时后的真实TCP对103与102同批发送分别核对ACT_CON和COT=44,不保证跨业务响应顺序。接口与丢新语义见[同步拒绝当前请求](../104/IEC104_API接口汇总.md#reject-request)。
校时测试使用模拟action及可控单调时钟,不设置Windows时间。覆盖Handle/Process不执行应用、显式执行一次、原CP56回显、COT/CA/IOA校验优先级、TEST/IV/非法CP56拒绝、SU/时区转换、受理起滞留补偿、日期上界和64位运算、期限与倒退、Try背压不重复动作、FIFO/满槽、STOP/断线/旧会话、广播快照及删除目标,以及action内只读状态。真实设备写入耗时、权限及失败后的硬件状态不由这些模拟测试证明,边界见[时钟同步设计](../104/IEC104_ClockSynchronization_设计架构.md)。
## 2. 编译与运行
当前业务测试由98组总召、12组计数量、4组兜底及16组校时组成,共130组,单/双服务配置各跑一次。计数量覆盖独立实例、请求校验、Type15/37批次边界、忙隔离、冻结/复位、动作重复调用和WAIT/STALE/TEST、业务期限、STOP/断线、共享连接失败、发送背压及多站广播;兜底改用未支持的106/102,覆盖同批与反序/重复内容、窗口与写阻塞、HIGH满丢新及恢复、STOP/STOPSTART取消和活动总召中的应答。校时专项检查独立受理与执行、原文确认、补偿/期限、广播及生命周期。`iec104_interrogation_rules_test.c`另有3组规则测试,遍历限定词、数据COT和类型/SQ/FRZ矩阵并检查解析;没有网络交互。
在项目根目录的PowerShell中执行,要求GCC可用。下述TCP事件环和调度测试已包含TcpServer实现且自行实现Net接口,构建时不要再次链接`IEC104_TcpServer.c`或`IEC104_Net_Windows.c`。
```powershell
@@ -126,7 +132,7 @@ if ($LASTEXITCODE -ne 0) { throw 'Slave replies build failed' }
if ($LASTEXITCODE -ne 0) { throw 'Slave replies tests failed' }
```
输出`Slave service: 15 groups passed (automatic replies, deferred copies, lifecycle, request validation)`。默认及`-DIEC104_TCP_SERVER_CAPACITY=2`均运行同一套测试。确认及终止的期望字节直接由独立请求向量和明确COT字节比较,不使用待测应答函数生成预期结果。确认提交失败仍保留已修改的COT/P/N,队列未接手时不增加条目;重连后旧代次无法写出应答。FATAL用测试内注入的队列不可用状态验证结果传递,不能据此声称覆盖了所有发送故障或部分写出组合。
输出`Slave service: 32 groups passed (replies, identities, stations, lifecycle, backpressure, rejection)`。默认及`-DIEC104_TCP_SERVER_CAPACITY=2`均运行同一套测试。确认及终止字节由独立请求向量和明确COT比较,不用待测函数生成期望。失败且未接手时不增加队列条目,旧代次不能写入新连接;FATAL注入验证结果传递,不代表穷尽所有发送故障组合,也不能把取消计数解释为确定没有字节写出。
参数矩阵遍历COT=1/2、CA=1/2、IOA=1/2/3:288组双对象监视报文布局、216组命令编解码报文;应答单独遍历全部30类型、12种配置、7种COT及两种P/N。所有布局检查截短输入及额外尾字节。边界测试检查省略OA、1字节CA溢出、三种IOA连续地址溢出、非法/NULL参数、容量与配置上限分别生效、4字节未知类型应答的重叠复制及5字节读命令。通用ASDU可编码/解析254字节报文,而104 I帧封装仍拒绝该长度。
@@ -140,7 +146,7 @@ if ($LASTEXITCODE -ne 0) { throw 'Slave replies tests failed' }
./scripts/build.ps1 -Test
```
先输出`slave example + event interrogation tests: 59 groups passed`,再输出`Slave example loopback: passed ...`。可控后端使用251字节HIGH和2槽业务事件FIFO,验证WAIT时原文及进度保留;真实回环使用默认存储、k=1、随机本机端口,验证分片请求、逐帧ACK、错误应答、STOP/START、重连及4秒后的正常退出。这里只实现模拟点表总召,不包含实际校时、遥控业务,也不构成完整互操作认证。
脚本输出两次`Slave service: 32 groups passed ...`(单/双服务)、两次`slave example + event interrogation tests: 130 groups passed ...`、`interrogation rules: 3 groups passed ...`和`Slave example loopback: passed ...`。可控后端使用251字节HIGH和2槽召唤业务事件FIFO。真实回环使用默认存储、k=1、随机本机端口,验证总召与计数量交错、冻结/复位及后续读取、分组/广播、TEST拒绝、COT=8返回45、106/102同批回44、STOP/START、重连及8秒后的正常退出。校时另核对正常7、TEST否定7、COT=5否定45、广播实际CA及103/102同批先44再7,全部使用模拟时钟且连接保留;STOP为submitted=65、clock_executed=3、clock_synchronized=3、clock_submitted=5。这里没有真实计量设备、系统设时或遥控,不构成完整互操作认证。
## 3. 覆盖范围
@@ -199,8 +205,40 @@ if ($LASTEXITCODE -ne 0) { throw 'Large configuration tests failed' }
调度专项使用同样的配置参数,将命令中的`@testSources`换为`@schedulerSources`并使用独立输出文件名即可。
### 校时固定存储与栈测量
2026-10-29 Windows x64 GCC默认配置实测:ClockTask为104字节,含4槽的ClockSynchronization实例为568字节,Request为80字节,Status为72字节;默认单服务的校时静态实例数组占568字节,不是完整协议库RAM总量。探针及产物位于`.build/clock-synchronization-20261029/size_probe.c/exe`。
`-O2 -fstack-usage`报告的单函数栈帧:Handle_Event 256、Execute_Next 240、Submit_Head 224、Process 64、Get_Status 80字节;示例main由修改前768增至800字节,Clock_Action为48字节。记录为同目录的`example-before.su`、`example-after.su`及`clock.su`。这些是x64宿主ABI和单函数测量,不是最坏调用链,也不代表ARMCC/MCU的RAM或栈占用;嵌入式接入须按实际工具链重新核算。
## 5. 验证记录
2026-10-29 Type 103时钟同步:新增独立静态池模块、显式Execute_Next动作、64位受理后滞留补偿、执行期限、原文ACT_CON及广播快照确认;示例默认模拟,显式system模式才设置Windows时间。整合`build.ps1 -Test`中GCC `-std=c11 -O2 -Wall -Wextra -Werror`构建无诊断,Slave单/双服务各32组、业务单/双服务各130组(98总召、12计数量、4兜底、16校时)及3组规则测试均通过。脚本内首次Node启动因沙箱`spawnSync EPERM`失败,因此该次脚本整体不是成功退出;随后获准单独重跑相同Node回环命令,退出码0,真实本机TCP通过,核对submitted=65、clock_executed=3、clock_synchronized=3、clock_submitted=5。默认模拟校时的正常/TEST/COT=5/广播及103+102同批均覆盖,未执行system模式或真实设备设时。改前基线、大小探针及栈测量位于`.build/clock-synchronization-20261029/`。
2026-10-28 业务目录整理:共用召唤引擎、内部规则头、总召和计数量共7个源码/头文件移到`104/business/`,迁移前后SHA256逐字节一致;构建加入该目录的头文件搜索路径,同步测试直接包含路径及文档源码链接。严格GCC构建通过,单/双服务各110组业务测试、23组Slave测试及3组规则测试通过,公开头C++检查和真实Windows TCP回环通过。沙箱限制Node启动子进程时,回环使用原授权命令在沙箱外执行。迁移清单及备份位于`.build/business-layout-20261028/`,修改仅限IEC60870-Slave;未执行Keil目标构建或硬件联调。
2026-10-28 计数量召唤:先验证原98组总召、单/双服务各23组Slave及真实TCP基线;拆分规则后原98组不修改通过。新实现用独立规则和实例复用引擎,动作接口校验后执行一次,冻结/复位只确认,不制造取数阶段;总召接口名称和行为保留。严格GCC构建通过,110组业务测试在单/双服务配置均通过,3组规则测试、10组ASDU和7组扩展ASDU测试通过。真实Windows TCP回环通过;沙箱限制Node子进程启动(EPERM)时,原回环命令在获准的环境中运行通过。只链接总召或只链接计数量(实例容量1)的创建/销毁检查通过。修改前字节备份和验证产物位于`.build/counter-interrogation-20261028/`。未执行Keil目标构建或硬件联调;本次仅修改IEC60870-Slave目录。
同日Windows x64 GCC实测:实例1240→1248字节,默认单服务池1240→2496字节,请求32字节、事件80字节、广播每站48字节不变。新增请求字段使用原尾部填充,旧字段偏移保留;此测量不代替ARMCC目标的RAM/栈预算。只用一种业务可显式缩小实例池。
2026-10-27 广播总期限判定收敛:Task_Timeout以同一次取时返回最近期限、原因及已确认站到期标志,Expire不再重复遍历。公开API、固定存储容量及超时处理策略不变。新增总期限禁用、BEGIN先到期和同时到期用例,重构前后均通过。GCC C11严格警告构建、98组总召、单/双服务各23组Slave及真实Windows TCP回环通过;回环在沙箱内因子进程EPERM失败,按原命令在沙箱外重跑通过。修改及验证限本目录,未执行Keil构建或硬件联调。
2026-10-27 提交期STOP归因:总召三个发送入口仅在ERROR时复核原连接正常停用状态,先交付DEACTIVATED,保留STOPPED原因;其他ERROR/FATAL不降级,成功路径不增加查询。目录内严格编译、97组总召、单/双服务各23组Slave及真实Windows TCP回环通过。新增真实通知容量背压复现,以及其他入口的容量/故障注入;验证等待不推进批次、不停止取数计时、不重复提交,STOP伴随FATAL仍为SEND_ERROR,OK/WAIT交付新回执后暂停后续提交。原临时复现程序已迁入正式测试,公开接口和固定存储容量不变。
2026-10-27 总召畸形请求隔离:单对象完整但SQ/尾字节错误时规范化否定应答,零/多对象丢弃;公共视图失败交公共入口,基础截短仍由Receiver处理。目录内生产示例通过严格警告编译,92组总召回归及单/双服务各23组Slave回归通过,真实Windows TCP回环通过(沙箱子进程EPERM后按原命令重跑)。覆盖畸形激活/撤销不取消已有总召、不刷新期限、不修改输入,以及FIFO背压、满槽丢弃和多CA活动广播继续完成。未增加任务槽或发送缓冲,修改及验证均限IEC60870-Slave目录。
2026-10-27 c104广播发送顺序:合法多目标广播改为全部ACT_CON、逐站数据、全部ACT_TERM,调用方提供每站状态区,仍共用一个任务。87组总召及23组Slave回归在单/双服务配置下通过C11严格警告编译及运行;生产示例构建和真实Windows TCP回环通过。新增覆盖状态区不足/重叠、非空多站报文、已确认未来站删除、13条FINISHED通过2槽FIFO无损交付、满槽及跨阶段撤销、拒绝与撤销并存、非当前站总期限。回环因沙箱子进程EPERM按原授权命令重跑。Windows x64默认配置实测管理对象1240B、每站状态48B,通信队列容量和覆盖策略不变。文档197个本地链接及锚点、UTF-8编码和diff空白检查通过;修改及验证均限本目录。
2026-10-27 多CA总召及站点删除:Remove即时退出路由,引用/快照结束后回收;总召按请求CA选站,广播以一个任务逐站推进。23组Slave及78组总召测试均在单/双服务配置下通过`-std=c11 -O2 -Wall -Wextra -Werror`编译和运行,包含13站广播、同CA替换、三阶段WAIT及撤销/超时/STOP。生产示例严格编译和真实TCP回环通过;回环子进程EPERM后以已授权原命令运行。Windows x64默认配置测量:站点40B、快照32B、Slave40B、总召1192B、任务168B;通信队列容量及事件覆盖策略未变。本次修改与验证仅在IEC60870-Slave目录内进行。
2026-10-27 共用站点基础:Slave提供调用方节点注册、按CA查找、context、引用及集合锁,总召可绑定已注册节点,示例改为注册一次CA后复用。22组Slave测试及70组总召测试均在单/双服务配置下通过`-std=c11 -O2 -Wall -Wextra -Werror`构建与运行;生产示例严格编译及真实Windows TCP回环通过。回环因沙箱子进程EPERM使用已授权原命令重跑。当前总召仍只处理一个绑定站点,未验证或宣称多CA总召调度。x64测量每节点32字节,Slave32字节,总召968字节,通信队列及业务槽容量不变。仓库`.venv`和可执行Python缺失,`@build`及RAM/栈报告入口未能启动;未执行ARMCC目标验证或硬件联调。
2026-10-27 总召请求存储精简:任务改为保存原事件身份及10字节Type100原文,发送时临时建立ASDU视图;Slave新增按身份发送/尝试发送/关闭接口,保留原服务实例与连接代次校验。任务、事件及传输队列容量和覆盖策略未调整,内存测量见[固定存储](../104/IEC104_Interrogation_设计架构.md#2-对象身份与固定存储)。68组总召回归、18组Slave回归(默认及双服务配置)和真实Windows TCP回环通过严格警告构建及运行。新增验证源事件复用、输入存储释放、HIGH接手条数不变、Try等待不入队/覆盖及旧身份隔离;原总召回归继续覆盖忙拒绝、广播错误字段、满槽撤销、超时、STOP和新旧数据排序。回环子进程经已授权的原命令运行;未执行Keil目标构建或硬件联调。
2026-10-27 总召与突发发送排序:总召数据改为不排队提交,有未提交HIGH/NORMAL、当前帧或窗口背压时返回WAIT;应用保留事件及游标,重读当前值后重试。有效批次遇发送WAIT后停止本批取数期限,下一批重新计时;示例仅在OK后推进游标。68组总召测试、13个TCP事件环场景、25个调度场景及15组Slave测试通过C11严格警告编译与运行;真实Windows TCP回环通过。回环子进程在沙箱内遇EPERM,经授权使用原命令重跑通过。新增场景覆盖新旧突发及总召顺序、部分写入、窗口和期限边界、提交故障;产物位于`.build/`,未连接实际设备。
2026-10-26 总召本地续跑:状态新增need_process,示例直接据此决定立即续跑;实时检查未读Slave通知,发送WAIT后让出,网络推进后继续重试。62组可控网络测试通过C11严格警告编译与运行,覆盖业务应答、旧FINISHED出队释放空位、短应答、四类发送背压、到期及STOP/START优先;真实Windows TCP回环通过。回环在沙箱内启动子进程遇到EPERM,经授权使用原测试命令重跑通过。产物位于`.build/slave-example/`,未连接实际设备。
2026-10-26 目录整理:104协议、公共模块、示例、测试、文档和构建脚本分别归入 `104/`、`common/`、`examples/`、`test/`、`docs/` 和 `scripts/`,同步头文件搜索路径、示例引用及文档命令。49个生产源码/头文件的SHA256与迁移前一致,21个生产C文件通过C11严格警告检查。按本文命令运行的默认/纯整数时间ASDU测试(各10组)、扩展ASDU测试(各7组)、104封装2组、子站15组、TCP事件环默认/小容量/大容量各13组、调度25组及Windows输入查询回环均通过;从非项目根目录调用构建脚本,示例编译及59组总召测试通过。真实TCP回环核对62个ASDU和11次正常完成的总召通过。当前执行环境拒绝Node默认子进程管道(EPERM);本次回环以临时文件接收真实子进程输出,原测试代码、网络交互和断言未修改。迁移清单、路径校验及临时运行适配保存在 `.build/layout-migration/`,未连接实际设备。
2026-10-26 广播地址处理重构:新增固定协议常量IEC104_CA_BROADCAST,Type100入口一次确定应答CA,统一在内部请求副本建立时写入;Process及发送重试直接沿用该地址。移除重复任务header,批次COT/OA/TEST从info校验,CA继续按本站配置校验。原59组可控网络测试通过C11严格警告编译与运行,覆盖广播及定向总召、错误CA回显、发送WAIT、忙拒绝、撤销及确认前失败。真实Windows TCP回环继续通过,核对62个ASDU和11次正常完成的总召。修改前备份与可控测试产物位于`.build/interrogation-address-copy-20261026-031956/`;公开函数签名、事件及配置字段不变。