通信协议代码
CI / L0 静态门禁 (pre-commit) (push) Failing after 45s
CI / L0.5 主机侧单测 (MSVC) (push) Failing after 42s

This commit is contained in:
STF
2026-10-26 08:31:48 -04:00
parent 81f06178a7
commit 4c55544f05
79 changed files with 41064 additions and 0 deletions
@@ -0,0 +1,107 @@
# 104子站示例
[iec104_slave_example.c](../../examples/iec104_slave_example.c) 包含模拟点表、业务事件处理、其他命令兜底、事件循环及main。总召状态机使用独立库模块[IEC104_Interrogation.c/h](../../104/IEC104_Interrogation.h),不在示例内重复实现。
当前实现站总召(`C_IC_NA_1`,QOI=20),返回8个单点遥信和8个标度化遥测值;分组1(QOI=21)只返回遥信,分组2(QOI=22)只返回遥测。库支持全部16组,示例未配置的其余组由应用拒绝。通信通过 `IEC104_Slave`,总召通过 `IEC104_Interrogation` 接入,所有任务使用固定存储、在主循环线程串行推进。模拟点表是只读的;变化上报、实际设备取值、校时及遥控可在这个入口继续接入。
## 构建与运行
在项目根目录的 PowerShell 中执行,需要 MinGW GCC:
示例通过 `IEC104_Interrogation.h` 引入库声明;构建脚本同时添加 `104/` 和 `common/` 头文件搜索路径,编辑器也应配置这两个目录。构建可执行程序仍使用下面的脚本,它会链接所需库源文件及 Windows 网络库;只编译当前 C 文件会缺少库函数实现。编辑器的 C 语言标准应与构建保持一致,使用 C11,并选择实际的 MinGW GCC 编译器。
```powershell
./scripts/build.ps1
./.build/slave-example/iec104_slave_example.exe
```
默认监听 `127.0.0.1:2404`,公共地址 CA=1;按 Ctrl+C 退出并关闭服务。也可以指定监听地址、端口和公共地址:
```powershell
./.build/slave-example/iec104_slave_example.exe --bind 0.0.0.0 --port 2404 --ca 1
```
| 参数 | 含义 |
|---|---|
| `--bind IPv4` | 数字IPv4监听地址,默认127.0.0.1 |
| `--port 0..65535` | 默认2404;0由系统分配,启动时输出实际端口 |
| `--ca 1..65534` | 本站公共地址,默认1;总召请求也接受65535,广播请求的应答使用本站地址 |
| `--window 1..32767` | 发送窗口k,默认12;设为1可观察逐帧ACK后续发 |
| `--run-ms N` | 运行约N毫秒后正常关闭;默认0表示持续运行 |
| `--help` | 打印用法后退出 |
启动输出 `LISTEN 地址:端口 CA=站地址`。退出输出已提交ASDU数、已提交完毕的总召数、WAIT次数、拒绝数、取消任务数、积压超限次数、主站撤销数及业务超时数。`submitted` 和 `gi_submitted` 表示报文已交给发送模块,不表示主站已经ACK。
## 主站操作和模拟点表
主站先建立TCP并发送 `STARTDT_ACT`,随后发送总召:Type=100、SQ=0、对象数=1、COT=6、P/N=0、CA匹配、IOA=0,QOI=20选择全站,21/22选择本例的两个分组。TEST请求同样只读取模拟点表,应答保留TEST和OA。
| IOA | 类型 | 模拟值(按地址顺序) |
|---|---|---|
| 1001..1008 | 单点信息 `M_SP_NA_1`,Type=1 | 0、1、0、1、1、0、1、0 |
| 2001..2008 | 标度化测量值 `M_ME_NB_1`,Type=11 | 2300、2310、2290、5000、-125、0、1234、42 |
所有品质为0。遥测值为原始有符号16位标度值,本示例没有定义物理单位和倍率。
一次成功站总召依次提交6个ASDU:
1. Type=100,COT=7,肯定激活确认。
2. 两帧Type=1、COT=20的单点数据,每帧4个对象。
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分支设置应用游标实现,不把模拟点表写进库。
全部应答使用原请求的 `connection`,沿HIGH路径保持提交顺序。总召数据不使用 `Enqueue_Event`,以免低优先级数据排在高优先级终止之后。
主站撤销使用相同的实际站CA/OA/TEST/QOI,COT改为8、P/N=0。激活和撤销均接受本站地址或65535,匹配时65535映射为本站地址,允许广播激活配本站撤销及本站激活配广播撤销。尚有匹配总召时模块停止任务、回复肯定COT=9,并通过FINISHED(DEACTIVATED)通知应用;不匹配或已完成则否定COT=9。已提交发送层的帧可能仍在确认前发出,撤销后不再提交旧批次和ACT_TERM。示例点数很少,总召可能在主站发撤销前已全部提交;观察中途撤销和各阶段拥塞可使用下面的可控网络测试。
广播总召的BEGIN/NEED_DATA/FINISHED携带本站CA,示例直接用该字段构造数据。确认、数据、终止、否定及错误应答的CA都为本站地址,OA/TEST/QOI保留原请求值;原始Slave事件不被修改。该支持属于Type100总召处理器,其他类型仍按各自业务规则分派。
## 请求校验及任务处理
| 情况 | 本示例的处理 |
|---|---|
| 不支持的Type ID | 回显原文,COT=44、P/N=1 |
| 总召COT不是6或8 | COT=45、P/N=1 |
| 总召CA既不是本站地址也不是65535 | COT=46、P/N=1,保留原错误CA |
| 总召IOA不是0 | COT=47、P/N=1 |
| 激活QOI超出20..36、请求P/N非法或当前已有未终结总召 | 否定ACT_CON,COT=7、P/N=1 |
| 空闲时收到合法激活请求 | 有空槽则建立唯一总召;BEGIN后应用接受才发肯定确认,忙时不同QOI也拒绝 |
| 激活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、数量或完整长度不合法 | 结束原连接,不执行业务 |
| 需要新增总召且4个任务槽已占满 | 尝试否定ACT_CON;无法安全提交/保存则丢新请求并计数,保留连接与原任务;匹配撤销复用原槽 |
| 接受/取数/整体业务期限到点 | 肯定ACT_CON提交前回否定确认并保留连接,提交后关闭原连接;已取出BEGIN才交付对应TIMEOUT终结事件 |
非总召命令先留给其他业务处理器;本示例最终回UNKNOWN_TYPE,单独保存一个待发送请求,若该槽占用时再收到未知命令则关闭原连接。
校验从类型开始;已知总召先验证完整对象布局,再检查COT、CA、IOA及QOI。错误应答调整COT/P/N,广播总召还将CA改为本站地址,保留其他原文字节。这里只实现上述命令集合,不代表所有类型的业务规则。
`Example_Drain_Business`主动读取总召业务事件:BEGIN按QOI选点表,Reply_Begin接受或拒绝请求;NEED_DATA调用普通辅助函数Example_Build_Batch构造数据,再Submit_Batch,数据耗尽则Complete_Data;FINISHED按实例/请求编号清理对应资源,撤销和超时也走该分支。没有向库注册业务函数指针。数据源暂未就绪时可保存事件,后续就绪再应答;发送WAIT由模块保留原文,不重复索取本批。
Submit_Batch返回OK后输入存储可复用;返回WAIT则先处理Slave通知,保留同一业务事件和构造结果重试;STALE表示原交互已失效(包括取消、撤销、超时),丢弃旧应答并处理FINISHED。示例的两条事件流均在主循环串行处理,新增数据类型只需调整应用的构造分支。
`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;丢弃请求没有响应,主站可能超时或重试。
STOPDT或断线通知取消未提交任务;ERROR/FATAL也取消,不把旧请求交给新连接。事件消费预算耗尽时先续读通知,再推进业务,防止尚未读取的STOP/START被跨过。关闭时执行`Interrogation_Cancel → 读空业务事件并清理FINISHED → Slave_Close → 读空Slave通知 → Interrogation_Destroy → Slave_Destroy`。总召Destroy遇到活动任务或未读业务事件会拒绝释放。
## 验证
```powershell
./scripts/build.ps1 -Test
```
`-Test` 另外需要 Node.js,只使用其内置模块,无需安装依赖。它先运行59组可控网络测试,再运行真实本机TCP测试:
- `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秒后自行关闭。
确认前失败另覆盖BEGIN尚未取出的超时、满任务池及HIGH等待时复用原槽、QOI/OA/TEST保持且应答使用本站CA、旧应答FIFO顺序、同连接重新总召、STOP/撤销/断线优先。确认已被发送层接手但尚未写出时,取数失败及超时仍断链,避免将已接受请求改发否定确认。
广播回归覆盖QOI=20..36、非1的本站地址、确认/数据/终止WAIT、立即或排队的错误及忙应答、确认前失败、激活/撤销地址的四种组合、源Slave事件不变,以及Submit_Batch拒绝广播或其他站CA。空闲和忙时的广播Type101继续交给后续处理器。真实TCP额外验证广播全站/分组、连续请求的忙拒绝、非法QOI和广播/本站撤销组合,共核对62个ASDU及11次正常完成的总召。
这些检查覆盖本示例的模拟点表流程;实际主站设备、现场点表和设备执行接口仍需联调。