添加遥控业务代码
CI / L0 静态门禁 (pre-commit) (push) Failing after 2m51s
CI / L0.5 主机侧单测 (MSVC) (push) Successful in 18s

This commit is contained in:
STF
2026-10-30 20:03:19 -07:00
parent 7596f3371a
commit cd42b4d61d
126 changed files with 70586 additions and 149 deletions
@@ -2,7 +2,7 @@
[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` 接入,所有任务使用固定存储、在主循环线程串行推进。示例另含计数量召唤和Type 103校时;默认时钟为模拟值,实际设备数据源及遥控仍由应用接入。
当前实现站总召(`C_IC_NA_1`,QOI=20),依次返回8个单点遥信、8个双点遥信和8个标度化遥测值;分组1(QOI=21)返回单点及双点遥信,分组2(QOI=22)只返回遥测。库支持全部16组,示例未配置的其余组由应用拒绝。通信通过 `IEC104_Slave`,总召通过 `IEC104_Interrogation` 接入,所有任务使用固定存储、在主循环线程串行推进。示例另含计数量召唤、Type 103校时及Type 45/46遥控;默认时钟为模拟值,遥控只更新内存点值,实际设备数据源和物理输出仍由应用接入。
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)。
@@ -24,11 +24,11 @@ config.broadcast_capacity = sizeof(gi_stations) / sizeof(gi_stations[0]);
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)。
主循环向总召、计数量、校时、遥控实例广播生命周期通知,读空Slave通知后优先推进遥控,再交替推进召唤业务并显式执行校时;其他类型在四个模块均未接手后兜底。原Example_Init仍可用于仅总召测试,命令行入口分别调用计数量、校时及Example_Init_Command补齐实例,两个召唤实例各需独立广播状态区,校时使用共用站点快照。协议及应用动作限制见[计数量设计](../104/IEC104_CounterInterrogation_设计架构.md)、[时钟同步设计](../104/IEC104_ClockSynchronization_设计架构.md)和[遥控设计](../104/IEC104_Command_设计架构.md)。
在项目根目录的 PowerShell 中执行,需要 MinGW GCC:
示例通过 `IEC104_Interrogation.h`、`IEC104_CounterInterrogation.h` 和 `IEC104_ClockSynchronization.h` 引入业务声明;构建脚本添加 `104/`、`104/business/` 和 `common/` 头文件搜索路径,编辑器也应配置这三个目录。构建可执行程序仍使用下面的脚本,它会链接所需库源文件、Clock适配及Windows网络库;只编译当前C文件会缺少库函数实现。编辑器的C语言标准应与构建保持一致,使用C11,并选择实际的MinGW GCC编译器。
示例通过 `IEC104_Interrogation.h`、`IEC104_CounterInterrogation.h`、`IEC104_ClockSynchronization.h` 和 `IEC104_Command.h` 引入业务声明;构建脚本添加 `104/`、`104/business/` 和 `common/` 头文件搜索路径,编辑器也应配置这三个目录。构建可执行程序仍使用下面的脚本,它会链接所需库源文件、Clock适配及Windows网络库;只编译当前C文件会缺少库函数实现。编辑器的C语言标准应与构建保持一致,使用C11,并选择实际的MinGW GCC编译器。
```powershell
./scripts/build.ps1
@@ -53,6 +53,8 @@ FRZ=1冻结并将序号模32加一;FRZ=2另将当前值清零;FRZ=3仅清当
启动输出 `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数,包含错误及否定应答,广播按各提交目标分别计数;不混入原字段。
遥控统计另追加`command_selected`、`command_executed`、`command_completed`、`command_submitted`和`command_returns`,分别表示已接受的选择、已启动的执行、成功完成事务、已交接应答和返回信息帧数;保留原有统计字段的含义。
### 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,不返回本站当前时间。
@@ -61,27 +63,46 @@ FRZ=1冻结并将序号模32加一;FRZ=2另将当前值清零;FRZ=3仅清当
确认发送WAIT时只重试原确认,不再次设时;STOP取消未交接确认而不撤销已完成的动作。同内容的新请求会再执行,旧时标重发可能回拨。真实数据发布方应在confirm_pending期间暂停新的带时标上报并继续驱动网络,避免持续新增NORMAL推迟确认。完整范围及期限见[时钟同步设计](../104/IEC104_ClockSynchronization_设计架构.md)。
### Type 45/46遥控
`Example_Init_Command`使用默认配置:两种类型均须先选择,决定期限15秒、选择保持30秒、执行期限60秒;成功发送ACT_TERM,失败默认不发。整个实例只允许一个事务,所有站点的新遥控互斥;TEST请求否定,CA=65535回UNKNOWN_CA,其他遥控IOA由应用Reject(UNKNOWN_IOA)。
| 命令 | 受控点及返回信息 | 初值与总召 |
|---|---|---|
| Type45,IOA6001..6008 | Type1,IOA1001..1008,COT=11;状态0分/1合 | 初值见下表,执行后总召读取更新值 |
| Type46,IOA6101..6108 | Type3,IOA1101..1108,COT=11;状态1分/2合 | 初值全部为1;执行后站总召及遥信分组读取更新值 |
表中IOA均为十进制。单点命令6001..6008分别对应遥信1001..1008;双点命令6101..6108分别对应返回位置1101..1108。例如对Type46、IOA6103完成状态2的选择与执行后,后续QOI=20或21读取的Type3、IOA1103为2;FINISHED清理不会把它恢复为初值1。
选择S/E=1、执行S/E=0,均使用COT=6;执行的类型、IOA、OA、状态及QU须与选择一致。主站收到选择的肯定ACT_CON后再执行;执行依次返回ACT_CON、位置返回信息、ACT_TERM。撤销使用COT=8,同类型/IOA/OA匹配,不要求状态相同。47..51及58..64仍由兜底回44。
`Example_Drain_Command`先读取SELECT/EXECUTE/FINISHED;首次保存事务context前Acquire应用站点引用。`Example_Command_Action`模拟返校或修改`ExampleApp.signal_values/double_values`并返回STARTED,不在动作中上报。Accept的OK只表示事件已消费,示例通过Get_Transaction确认EXECUTING后,依次Report_Result(SUCCEEDED)、Example_Build_Command_Return、Submit_Return_Info和Complete;设备结果可以早于确认交接。
每一步WAIT都保留原事件和当前步骤;返回信息使用独立ASDU缓冲,WAIT时保留同一帧,OK后才推进,避免重复提交。STALE停止该事务重试,继续读取FINISHED。FINISHED清除模拟返校/输出标志并Release应用引用,不恢复已修改的点值;真实输出若仍未停止,应用必须继续保持设备忙状态和引用。Example_Wait_Time同时计入遥控期限、模块推进和应用挂起重试,发送WAIT时允许等待网络而不空转。
## 主站操作和模拟点表
主站先建立TCP并发送 `STARTDT_ACT`,随后发送总召:Type=100、SQ=0、对象数=1、COT=6、P/N=0、CA匹配、IOA=0,QOI=20选择全站,21/22选择本例的两个分组。TEST请求同样只读取模拟点表,应答保留TEST和OA。
| IOA | 类型 | 模拟值(按地址顺序) |
| IOA | 类型 | 模拟初值(按地址顺序) |
|---|---|---|
| 1001..1008 | 单点信息 `M_SP_NA_1`,Type=1 | 0、1、0、1、1、0、1、0 |
| 1101..1108 | 双点信息 `M_DP_NA_1`,Type=3 | 1、1、1、1、1、1、1、1 |
| 2001..2008 | 标度化测量值 `M_ME_NB_1`,Type=11 | 2300、2310、2290、5000、-125、0、1234、42 |
所有品质为0。遥测值为原始有符号16位标度值,本示例没有定义物理单位和倍率。
一次成功站总召依次提交6个ASDU:
一次成功站总召依次提交8个ASDU:
1. Type=100,COT=7,肯定激活确认。
2. 两帧Type=1、COT=20的单点数据,每帧4个对象。
3. 两帧Type=11、COT=20的标度化遥测,每帧4个对象。
4. Type=100,COT=10,激活终止。
3. 两帧Type=3、COT=20的双点数据,每帧4个对象。
4. 两帧Type=11、COT=20的标度化遥测,每帧4个对象。
5. Type=100,COT=10,激活终止。
分组总召各提交4个ASDU:ACT_CON、两批对应类型数据、ACT_TERM。分组1数据为Type=1/COT=21,分组2数据为Type=11/COT=22;确认和终止保留请求QOI。BEGIN校验分组,首个NEED_DATA按该组初始化应用游标,不把模拟点表写进库。
分组1(QOI=21)共提交6个ASDU:ACT_CON、两帧Type=1单点数据、两帧Type=3双点数据、ACT_TERM,数据COT均为21,每帧4个对象。分组2(QOI=22)仍共提交4个ASDU:ACT_CON、两帧Type=11/COT=22遥测、ACT_TERM,每帧4个对象。确认和终止保留请求QOI。BEGIN校验分组,首个NEED_DATA按该组初始化应用游标,不把模拟点表写进库。
全部应答使用原请求的连接身份。总召确认、终止及短应答走普通HIGH路径,总召数据由Submit_Batch尝试不排队提交;校时调用过action的确认也使用Try。未提交HIGH/NORMAL、当前帧或窗口背压时Try返回WAIT;校验错误仍走普通HIGH。总召数据不使用Enqueue_Event,只有本批接手后才推进游标及后续终止。
全部应答使用原请求的连接身份。总召确认、终止及短应答走普通HIGH路径,总召数据由Submit_Batch尝试不排队提交;校时调用过action的确认也使用Try。遥控执行动作产生副作用后的确认、返回信息和ACT_TERM均走Try,选择确认及校验否定走普通HIGH。未提交HIGH/NORMAL、当前帧或窗口背压时Try返回WAIT;遥控的模块FIFO还会保留更早应答优先。总召数据和遥控返回信息不使用Enqueue_Event,只有本帧接手后才推进应用步骤。
主站撤销使用相同的实际站CA/OA/TEST/QOI,COT改为8、P/N=0。激活和撤销均接受本站地址或65535,匹配时65535映射为本站地址,允许广播激活配本站撤销及本站激活配广播撤销。尚有匹配总召时模块停止任务、回复肯定COT=9,并通过FINISHED(DEACTIVATED)通知应用;不匹配或已完成则否定COT=9。已提交发送层的帧可能仍在确认前发出,撤销后不再提交旧批次和ACT_TERM。示例点数很少,总召可能在主站发撤销前已全部提交;观察中途撤销和各阶段拥塞可使用下面的可控网络测试。
@@ -105,7 +126,7 @@ FRZ=1冻结并将序号模32加一;FRZ=2另将当前值清零;FRZ=3仅清当
| 需要新增总召且4个任务槽已占满 | 尝试否定ACT_CON;无法安全提交/保存则丢新请求并计数,保留连接与原任务;匹配撤销复用原槽 |
| 接受/取数/整体业务期限到点 | 肯定ACT_CON提交前回否定确认并保留连接,提交后关闭原连接;已取出BEGIN才交付对应TIMEOUT终结事件 |
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;模块之间不承诺跨业务应答按接收顺序排列。
Example_Handle_Event先过滤asdu_result失败的I帧视图,避免进入业务分派或未知类型兜底;正常线上基础头/载荷截短已由Receiver和连接层处理。当前I帧依次交给总召、计数量、校时及遥控;都未接手时,在读取下一事件前同步调用`IEC104_Slave_Reject_Request`回UNKNOWN_TYPE。该接口借用当前事件生成原文错误应答,复用库的普通HIGH路径;示例不保存other_request,也没有other_pending、other_waiting或other_waits。未支持类型的连续及相同内容有效新请求逐条尝试应答,允许穿插正在进行的总召。103由校时接手,45/46由遥控接手,102、47、58仍回44;模块之间不承诺跨业务应答按接收顺序排列。
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)。
@@ -113,7 +134,7 @@ OK表示已接手;WAIT只因HIGH容量不足,示例丢弃本条并增加over
`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-数据批次与发送路径)。
本例BEGIN只判断接受,首个NEED_DATA初始化游标并读取这一批;接真实数据源时,在Example_Build_Batch的逐点组包处读取当前值和品质。IOA、类型、分组成员及遍历策略均由调用者决定,协议库不登记或冻结完整IOA列表。示例单点常量表保存地址、初值及品质,双点初值均为1;单点和双点实际值分别存放在ExampleApp.signal_values和ExampleApp.double_values中,站总召及遥信分组逐批读取这些可写数组的当前值,包括遥控执行后的更新;这些地址不是库规定的范围。逐批采样不保证整轮同一时刻,也不自动解决与突发上报的新旧排序;完整边界见[总召模块设计](../104/IEC104_Interrogation_设计架构.md#6-数据批次与发送路径)。
Example_Build_Batch使用局部游标遍历,仅在Submit_Batch返回OK后推进应用游标,此时发送层已复制原文并确定帧顺序,输入存储可复用。返回WAIT时保留同一业务事件及游标,先推进网络、读空Slave通知,再从同一位置重新取当前值和品质,不能重发旧构造结果。STALE表示原交互已失效(包括取消、撤销、超时及提交故障),丢弃旧应答并处理FINISHED。接实际数据源时,须将点值更新及突发发布与总召读值及提交串行化;排序保证及持续突发可能推迟总召的边界见[数据批次与发送路径](../104/IEC104_Interrogation_设计架构.md#6-数据批次与发送路径)。
@@ -125,18 +146,21 @@ Example_Init使用默认业务期限:BEGIN应答15秒、每批NEED_DATA至首
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成功代替资源清理。
遥控同样执行`Command_Cancel → 读完FINISHED → Slave_Close → 读空并分派通知 → Command_Destroy`。示例先取消全部业务并清理应用引用,再关闭Slave;关闭后的生命周期通知仍交付全部模块。遥控FINISHED可以在离线时读取,事务槽及独立应答清空后才允许Destroy,最后移除站点并销毁Slave。
## 验证
```powershell
./scripts/build.ps1 -Test
```
`-Test` 需要预先安装 Node.js。脚本依次运行32组Slave/站点测试(单服务及双服务配置)、130组总召/计数量/兜底/校时可控网络测试(单服务及双服务)、3组规则单测和真实本机TCP测试:
`-Test` 需要预先安装 Node.js。脚本运行Slave/站点测试、总召/计数量/兜底/校时可控网络测试、规则单测、遥控容量矩阵和真实本机TCP测试;实跑数量及记录见[测试说明](../test/IEC104测试文档.md):
- `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、站总召、忙拒绝及结束后重发、撤销、错误应答、106与102同批分别回44、STOP/START、重连和正常退出。校时核对正常ACT_CON、TEST否定7、读时COT=5否定45、广播实际CA,以及103与102同批时先102回44、再103回7;连接保留。服务约8秒后自行关闭,使用默认模拟时钟。
- `iec104_command_test.c`:独立可控Net/单调时钟测试,TCP服务容量1/2和独立应答槽容量1/4交叉构建,HIGH=251字节;覆盖选择执行、撤销、动作结果、期限、FIFO、返回信息及FINISHED/引用生命周期。
- `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。遥控核对45/46选择执行、返回信息在确认和终止之间、撤销、广播46、未知IOA47、TEST否定、47/58回44,以及单点遥控后总召读取新值。服务约12秒后自行关闭,使用默认模拟时钟。
原114组基线由98组总召、12组计数量及4组兜底回归组成。兜底改用未支持的106/102,覆盖同批及反序、相同内容的有效新请求、k=1与写阻塞排队、HIGH满后丢新且不补发、STOP/STOPSTART取消,以及总召未结束时立即提交错误应答。启用校时的独立回归另外核对103业务。