0264b49489
新增 App/algo/freq.c/h:由 L2 基波相量导出系统频率。相邻两拍共轭 乘积 P=Z(m)·conj(Z(m-1)) 取角 = 每拍相位增量(免显式解缠绕),再按 自适应窗长 K_eff=round(8·Fs/(2·f_hat))(45~55 Hz 段 73~89 拍)做滑动 平均。定长窗受 20 点矩形窗负序泄漏纹波限制(实测扫频最坏 0.317 Hz; 60 拍 Kay 复数和 0.197 Hz,交叉项和角不按 1/K^2 衰减),自适应窗使 窗长恰为纹波周期 fs/(2f) 整数倍 -> 最坏 0.0101 Hz。推导与两轮弯路 写在 freq.c 文件头。 假值钳制带取 35~65 Hz,刻意宽于系统合理带 40.5~60.5:单拍相位差本身 带 ±5 Hz 泄漏纹波,贴边钳制会非对称丢弃纹波半周 -> 均值偏置(实测 45 Hz 点 +0.16 Hz)。系统合理带由 OF/UF 定值范围在消费侧把关。 幅值门 FREQ_AMP_MIN=50 码(拟定,待 L1-5 标定复核):低于门判无有效 波形,停更新、valid 清零、读数保持 -> 浮空/PT 断线不发散。源优先级 UA(C02)->IA(C01),按名前缀解析、每拍动态回退,切换即废相位基准。 prot_set:尾追加 8 项(PS_ID_COUNT 32->40,编号不回收) OF_EN/OF_HZ/OF_DLY/UF_EN/UF_HZ/UF_DLY/RET_OF/RET_UF。OF/UF 逐项通讯 地址不在本仓库 -> 全部 comm_addr=0 本地项(104 C_SE 不可寻址,待对照 参考表补)。频率返回系数独立(0.99/1.01),勿与电气量 0.95/1.05 共用。 bad_mask 随项数扩为 uint64_t。PROT_SET_BLOB_VER 1->2。 prot_set_store:换代与损坏分流。load 返回 2 = 版本/项数换代(本文件 打 INFO "prot set blob version change, defaults in use"),-1 = 真损坏 (defaultTask 按 CORRUPT 报 ERROR + 自检留痕)。B7 旧档(ver=1/32 项) 升级首启属预期路径,不得报成损坏。语义分流收在属主文件内, default_task.c 不动。 prot_elm:实例表加 src 字段(0=phasor rms,1=freq),PE_OF/PE_UF 尾 追加(PE_COUNT 7->9)。freq 无有效读数按方向填"必不启动"值(过量 0 / 欠量 1e30 -> 滞回比较恒假 -> 元件复位),不整拍跳过——频率源缺失不得 饿死电流/电压类元件(B5 行为不回归)。OF/UF 暂未映射独立 104 遥信点, 动作只经 YX15 保护总聚合可见(送检点表 = B10)。 板端接线:adcTask 在 phasor_run() 之后同拍调 freq_run();心跳/首拍日志 追加 freq(<tag>): f=%ld valid=%u (x0.01)(MicroLIB 无 %f)。app_main 内核前 freq_init()。 主机单测:freq 46 用例(J1 扫频 45~55 Hz <=0.02 Hz 实测最坏 0.0101; J2 噪声 1% <=0.10 实测 0.0160;J3 启动收敛 <=40 ms 实测 20;J4 阶跃 50->51 <=120 ms 实测 98;J5 浮空 valid=0 不发散;J6 跳变 60 ms 内回 ±0.05;另直流 8000 码 / 3-5 次谐波 20%/10% / UA->IA 回退)。 prot_set 90(+TC-PS-011 OF/UF 行 + 64 位 bad_mask bit39 + 旧档拒绝)、 prot_elm 47(+OF/UF 滞回与方向隔离 + run() 测频接线)。回归:phasor 57、iec104 94 全绿。 J4=120 ms 是"精度优先于响应"的显式取舍(Lead 批准):4 周波窗换 0.317->0.0101 Hz,测频响应 98 ms << OF/UF 定时限默认 1000 ms(占 总动作时间 <10%),暂态另有 J6 <=60 ms 兜底。 注册:freq.c 入 uvprojx algo 组 + check_registration.py SIM_EXEMPT (纯算法、模拟器无消费路径,照 prot_set.c 先例)。 @build 0 错误 0 警告(Code=207468 RO=18304 RW=1068 ZI=153084)。 RAM:RW_IRAM1 96096->96728 B(+632 B,73.3%->73.8%)、RW_CCM 不变 57376 B(87.9%)、Flash 224596->226840 B(+2244 B,21.4%->21.6%); 台账随合并由 Lead 回填。stack_report:adcTask 静态最坏链 1320 B / 余量 1240 B(48%)改前后完全不变(freq_run 帧未进最坏链,无需抬栈); protectTask 链 872 B / 余 1176 B(57%)同样不变。 gate: only mem-budget/mem-numbers pending lead ledger backfill -------------------------------------------------------------- 门禁实跑(pre-commit run --all-files,Lead 批准 --no-verify 提交): 28 Passed / 2 Failed,失败仅 mem-numbers 与 mem-budget 两条——均为 docs/内存管理 受管快照与本 .map 不一致(台账由 Lead 每次合并后 --write 回填,避免多分支同改受管表冲突)。其余含 doc-links / coding-standard / no-mojibake / cppcheck / registration-check / 13 条主机单测钩子全 Passed(gitlint 属 commit-msg 阶段,不在该次 --all-files 内)。ci_local L0.5 全绿(freq 46/46 等)。 字节卫生自查(--no-verify 跳过卫生钩子,故手工核):17 个暂存文件 全部 BOM=无 / CR=0(纯 LF)/ U+FFFD=0;自研 .c/.h 去注释后字符串 字面量 100% ASCII(唯一非 ASCII 字面量在 test/host/iec104/iec104_test.c 的既有用例名,非本次改动行;test/ 不在 coding-standard 钩子范围)。
922 lines
31 KiB
C
922 lines
31 KiB
C
/*
|
||
* 模块名称:IEC 60870-5-104 被控站协议栈主机侧单元测试
|
||
* 模块功能:在 PC 上对 App/protocol/iec104 全栈(asdu + pts + link)做离线验证。
|
||
*
|
||
* 被测对象:真实源文件(#include 原样纳入):
|
||
* iec104_asdu.c + iec104_pts.c + iec104_link.c(B4 协议核心)
|
||
* prot_set.c + prot_elm.c + phasor.c + sample_ring.c(板端默认源
|
||
* 的链接依赖;定值两段写走**真实 prot_set**)
|
||
* 数据源经 iec104_pts_bind_src 注入假源(YC/YX/YK 可控),
|
||
* 发送经 send_apdu 回调全量捕获,时基由测试注入。
|
||
*
|
||
* 覆盖点(docs/子系统设计/IEC104上行设计说明.md §7):
|
||
* TC-104-001 CP56Time2a 往返 / 闰年 / IV / 钳位
|
||
* TC-104-002 IOA 3B 与短浮点小端往返
|
||
* TC-104-003 APCI 重组:半帧 / 粘帧 / 坏起始符 / 坏长度
|
||
* TC-104-004 STARTDT 握手 → RUN
|
||
* TC-104-005 OPEN 态 I 帧忽略(序号不消费)
|
||
* TC-104-006 总召全流程:actcon→4 分片(首召 COT=4)→actterm;二召 COT=2;RBT→COT=4
|
||
* TC-104-007 遥信变位 COV(COT=3);无效位不触发
|
||
* TC-104-008 遥测死区 COV:死区内不发 / 越死区发
|
||
* TC-104-009 k 窗:12 未确认停发;S 确认后恢复
|
||
* TC-104-010 w 阈:收满 8 发 S
|
||
* TC-104-011 t2 到期发 S
|
||
* TC-104-012 t3 空闲发 TESTFR act;未答 t1 超时 → IDLE;con 清挂起不误杀
|
||
* TC-104-013 遥控两段式:选择/执行成功;失配 PN;拒绝类选择段即 PN
|
||
* TC-104-014 定值两段写(真实 prot_set):越界 PN;执行后 get_f + selfcheck
|
||
* TC-104-015 C_RD:YC 命中 / 定值命中 / 未知 IOA COT14+EVI
|
||
* TC-104-016 C_CS:clock_sync 收正确 epoch;IV → PN;clock_sync 失败 → PN
|
||
* TC-104-017 未知类型 COT12+EVI;错公共地址 COT13+EVI
|
||
* TC-104-018 STOPDT:数据停发,TESTFR 仍应答
|
||
* TC-104-019 序号错 → IDLE(链路故障)
|
||
* TC-104-020 pts:无效通道 IV / 未就绪 NT / 无效遥信 IV / is_dp / 表尺寸
|
||
* TC-104-021 C_TS / C_RP actcon
|
||
* TC-104-022 周期上送 COT=1(YC 组 + 频率)
|
||
* TC-104-023 k 窗满 COV 不丢事件(pend 暂存,窗开后补发)
|
||
*
|
||
* 通过:全部 TC PASS(main 返回 0)。
|
||
* 创建日期:2026-10-12(B4)
|
||
*/
|
||
#include <stdint.h>
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
|
||
static int s_total = 0, s_passed = 0, s_failed = 0;
|
||
|
||
#define CHECK(cond, name) do { \
|
||
s_total++; \
|
||
if (cond) { s_passed++; printf(" [PASS] %s\n", name); } \
|
||
else { s_failed++; printf(" [FAIL] %s (line %d)\n", name, __LINE__); } \
|
||
} while (0)
|
||
|
||
#include "iec104_asdu.c"
|
||
#include "prot_set.c"
|
||
#include "sample_ring.c"
|
||
#include "phasor.c"
|
||
#include "freq.c"
|
||
#include "prot_elm.c"
|
||
#include "iec104_pts.c"
|
||
#include "iec104_link.c"
|
||
|
||
/* ---------------- 假数据源(定值转发真实 prot_set) ---------------- */
|
||
static float s_f_yc[9];
|
||
static int s_f_yc_rc[9];
|
||
static uint64_t s_f_bits;
|
||
static uint64_t s_f_valid;
|
||
static uint32_t s_f_yk_ioa;
|
||
static uint8_t s_f_yk_dpc;
|
||
static uint8_t s_f_yk_sel;
|
||
|
||
static int f_yc(uint8_t idx, float *out)
|
||
{
|
||
if (idx >= 9u) {
|
||
return -1;
|
||
}
|
||
if (s_f_yc_rc[idx] != 0) {
|
||
return s_f_yc_rc[idx];
|
||
}
|
||
*out = s_f_yc[idx];
|
||
return 0;
|
||
}
|
||
static uint64_t f_bits(void) { return s_f_bits; }
|
||
static uint64_t f_valid(void) { return s_f_valid; }
|
||
static int f_yk(uint32_t ioa, uint8_t dpc, uint8_t sel)
|
||
{
|
||
s_f_yk_ioa = ioa;
|
||
s_f_yk_dpc = dpc;
|
||
s_f_yk_sel = sel;
|
||
if (ioa == 0x600Bu) {
|
||
/* YK12 全局软压板(B7):转真实板端源(prot_set_set_sealed 原子路径) */
|
||
return s_src_yk_exec(ioa, dpc, sel);
|
||
}
|
||
return (ioa == 0x600Au) ? 0 : -1;
|
||
}
|
||
static int f_set_read(uint32_t addr, float *out)
|
||
{
|
||
return s_src_set_read(addr, out);
|
||
}
|
||
static int f_set_write(uint32_t addr, float v, uint8_t sel)
|
||
{
|
||
return s_src_set_write(addr, v, sel);
|
||
}
|
||
static const iec104_pts_src_t g_fake = {
|
||
f_yc, f_bits, f_valid, f_yk, f_set_read, f_set_write
|
||
};
|
||
|
||
/* ---------------- 发送捕获 + 对时假回调 ---------------- */
|
||
#define CAP_MAX 48
|
||
#define CAP_SZ 260
|
||
static uint8_t s_cap[CAP_MAX][CAP_SZ];
|
||
static int s_cap_n;
|
||
static uint64_t s_clock_ms;
|
||
static int s_clock_rc;
|
||
|
||
static void cap_send(void *ctx, const uint8_t *apdu, uint16_t len)
|
||
{
|
||
(void)ctx;
|
||
if ((s_cap_n < CAP_MAX) && (len <= CAP_SZ)) {
|
||
(void)memcpy(s_cap[s_cap_n], apdu, len);
|
||
s_cap_n++;
|
||
}
|
||
}
|
||
static int f_clock(void *ctx, uint64_t ms)
|
||
{
|
||
(void)ctx;
|
||
s_clock_ms = ms;
|
||
return s_clock_rc;
|
||
}
|
||
|
||
static iec104_link_t g_lk;
|
||
static const iec104_link_cfg_t g_cfg = { 1u, cap_send, f_clock };
|
||
|
||
/* ---------------- 主站侧模拟(N(R) 单调,照真实主站) ---------------- */
|
||
static uint16_t m_ns;
|
||
static uint16_t m_nr;
|
||
|
||
static void m_reset(void)
|
||
{
|
||
m_ns = 0u;
|
||
m_nr = 0u;
|
||
s_cap_n = 0;
|
||
}
|
||
static void feed_u(uint8_t q, uint32_t now)
|
||
{
|
||
uint8_t b[6];
|
||
|
||
b[0] = 0x68u; b[1] = 4u; b[2] = q; b[3] = 0u; b[4] = 0u; b[5] = 0u;
|
||
iec104_link_on_rx(&g_lk, b, 6u, now);
|
||
}
|
||
static void feed_i(const uint8_t *asdu, uint16_t len, uint32_t now)
|
||
{
|
||
uint8_t b[6u + 250u];
|
||
uint16_t fs = (uint16_t)(m_ns << 1);
|
||
uint16_t fr = (uint16_t)(m_nr << 1); /* N(R) 单调(feed_s 推进) */
|
||
|
||
b[0] = 0x68u;
|
||
b[1] = (uint8_t)(4u + len);
|
||
b[2] = (uint8_t)(fs & 0xFFu);
|
||
b[3] = (uint8_t)(fs >> 8);
|
||
b[4] = (uint8_t)(fr & 0xFFu);
|
||
b[5] = (uint8_t)(fr >> 8);
|
||
(void)memcpy(&b[6], asdu, len);
|
||
iec104_link_on_rx(&g_lk, b, (uint16_t)(6u + len), now);
|
||
m_ns = (uint16_t)((m_ns + 1u) % 32768u);
|
||
}
|
||
static void feed_s(uint16_t nr, uint32_t now)
|
||
{
|
||
uint8_t b[6];
|
||
uint16_t f = (uint16_t)(nr << 1);
|
||
|
||
b[0] = 0x68u; b[1] = 4u; b[2] = 1u; b[3] = 0u;
|
||
b[4] = (uint8_t)(f & 0xFFu);
|
||
b[5] = (uint8_t)(f >> 8);
|
||
m_nr = nr; /* 主站确认推进(单调) */
|
||
iec104_link_on_rx(&g_lk, b, 6u, now);
|
||
}
|
||
static uint16_t mk_asdu(uint8_t *b, uint8_t type, uint8_t vsq, uint8_t cot,
|
||
uint8_t cot_hi, uint8_t with_ioa, uint32_t ioa,
|
||
const uint8_t *body, uint16_t blen)
|
||
{
|
||
uint16_t n = iec104_asdu_put_hdr(b, type, vsq, cot, cot_hi, 1u);
|
||
|
||
if (with_ioa != 0u) {
|
||
iec104_put_ioa(&b[n], ioa);
|
||
n += 3u;
|
||
}
|
||
if (blen > 0u) {
|
||
(void)memcpy(&b[n], body, blen);
|
||
n += blen;
|
||
}
|
||
return n;
|
||
}
|
||
|
||
/* ---------------- 复位 / 握手 ---------------- */
|
||
static void reset_all(uint32_t now)
|
||
{
|
||
prot_set_init();
|
||
prot_elm_init();
|
||
iec104_pts_init();
|
||
iec104_pts_bind_src(&g_fake);
|
||
(void)memset(s_f_yc, 0, sizeof(s_f_yc));
|
||
(void)memset(s_f_yc_rc, 0, sizeof(s_f_yc_rc));
|
||
s_f_bits = 0u;
|
||
s_f_valid = (1uLL << 14) | (1uLL << 15) | (1uLL << 16) | (1uLL << 17)
|
||
| (1uLL << 22) | (1uLL << 30);
|
||
s_clock_rc = 0;
|
||
s_clock_ms = 0u;
|
||
iec104_link_init(&g_lk, &g_cfg, NULL);
|
||
iec104_link_open(&g_lk, now);
|
||
m_reset();
|
||
}
|
||
static void handshake(uint32_t now)
|
||
{
|
||
feed_u(0x07u, now); /* STARTDT act */
|
||
}
|
||
|
||
/* ---------------- 捕获检查 ---------------- */
|
||
typedef struct {
|
||
uint8_t type, vsq, cot, cot_hi, oa;
|
||
uint32_t ca, ioa;
|
||
const uint8_t *obj; /* ioa 之后的信息体 */
|
||
uint16_t obj_len;
|
||
} cap_t;
|
||
|
||
static int cap_i_count(void)
|
||
{
|
||
int k, n = 0;
|
||
|
||
for (k = 0; k < s_cap_n; k++) {
|
||
if ((s_cap[k][2] & 0x01u) == 0u) {
|
||
n++;
|
||
}
|
||
}
|
||
return n;
|
||
}
|
||
static int cap_u_count(uint8_t q)
|
||
{
|
||
int k, n = 0;
|
||
|
||
for (k = 0; k < s_cap_n; k++) {
|
||
if ((s_cap[k][2] & 0x03u) == 0x03u && s_cap[k][2] == q) {
|
||
n++;
|
||
}
|
||
}
|
||
return n;
|
||
}
|
||
static int cap_has_s(uint16_t *nr_out)
|
||
{
|
||
int k;
|
||
|
||
for (k = s_cap_n - 1; k >= 0; k--) {
|
||
const uint8_t *p = s_cap[k];
|
||
|
||
if ((p[2] & 0x03u) == 0x01u) {
|
||
*nr_out = (uint16_t)(((uint16_t)p[4] | ((uint16_t)p[5] << 8)) >> 1);
|
||
return 1;
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
static int cap_i(int n, cap_t *o)
|
||
{
|
||
int k, seen = 0;
|
||
|
||
for (k = 0; k < s_cap_n; k++) {
|
||
const uint8_t *p = s_cap[k];
|
||
uint16_t lf = p[1];
|
||
iec104_asdu_hdr_t h;
|
||
|
||
if ((p[2] & 0x01u) != 0u) {
|
||
continue;
|
||
}
|
||
if (seen == n) {
|
||
if (iec104_asdu_parse_hdr(&p[6], (uint16_t)(lf - 4u), &h) != 0) {
|
||
return -1;
|
||
}
|
||
o->type = h.type;
|
||
o->vsq = h.vsq;
|
||
o->cot = h.cot;
|
||
o->cot_hi = h.cot_hi;
|
||
o->oa = h.oa;
|
||
o->ca = h.ca;
|
||
if (h.obj_len >= 3u) {
|
||
o->ioa = iec104_get_ioa(h.obj);
|
||
o->obj = h.obj + 3u;
|
||
o->obj_len = (uint16_t)(h.obj_len - 3u);
|
||
} else {
|
||
o->ioa = 0u;
|
||
o->obj = h.obj;
|
||
o->obj_len = 0u;
|
||
}
|
||
return 0;
|
||
}
|
||
seen++;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
/* ============================ 测试用例 ============================ */
|
||
|
||
static void tc_cp56(void)
|
||
{
|
||
uint8_t b[7];
|
||
uint64_t e1 = 946684800000ULL; /* 2000-01-01 00:00:00.000 */
|
||
uint64_t e2 = 1709188496789ULL; /* 2024-02-29 06:34:56.789(闰日) */
|
||
uint64_t back = 0u;
|
||
int rc;
|
||
|
||
iec104_epoch_ms_to_cp56(e1, b);
|
||
rc = iec104_cp56_to_epoch_ms(b, &back);
|
||
CHECK((rc == 0) && (back == e1), "TC-001 CP56 往返 2000-01-01");
|
||
iec104_epoch_ms_to_cp56(e2, b);
|
||
back = 0u;
|
||
rc = iec104_cp56_to_epoch_ms(b, &back);
|
||
CHECK((rc == 0) && (back == e2), "TC-001 CP56 往返闰日 2024-02-29");
|
||
b[2] |= 0x80u;
|
||
CHECK(iec104_cp56_to_epoch_ms(b, &back) == -2, "TC-001 IV → -2");
|
||
iec104_epoch_ms_to_cp56(0u, b); /* 早于 2000 → 钳位 */
|
||
back = 0u;
|
||
(void)iec104_cp56_to_epoch_ms(b, &back);
|
||
CHECK(back == e1, "TC-001 早于 2000 钳位");
|
||
}
|
||
|
||
static void tc_ioa_float(void)
|
||
{
|
||
uint8_t b[8];
|
||
|
||
iec104_put_ioa(b, 0x123456u);
|
||
CHECK((iec104_get_ioa(b) == 0x123456u) && (b[0] == 0x56u) && (b[1] == 0x34u)
|
||
&& (b[2] == 0x12u), "TC-002 IOA 3B 小端");
|
||
iec104_put_float(b, 1.0f);
|
||
CHECK((b[0] == 0x00u) && (b[1] == 0x00u) && (b[2] == 0x80u)
|
||
&& (b[3] == 0x3Fu), "TC-002 float IEEE754 小端");
|
||
iec104_put_float(b, -12.5f);
|
||
CHECK(iec104_get_float(b) == -12.5f, "TC-002 float 往返");
|
||
}
|
||
|
||
static void tc_apci(void)
|
||
{
|
||
uint32_t now = 1000u;
|
||
|
||
reset_all(now);
|
||
{
|
||
static const uint8_t b[6] = { 0x68u, 4u, 0x07u, 0u, 0u, 0u };
|
||
|
||
iec104_link_on_rx(&g_lk, b, 3u, now);
|
||
CHECK(cap_u_count(0x0Bu) == 0, "TC-003 半帧不触发");
|
||
iec104_link_on_rx(&g_lk, &b[3], 3u, now);
|
||
CHECK(cap_u_count(0x0Bu) == 1, "TC-003 半帧重组 → con");
|
||
}
|
||
{
|
||
static const uint8_t b[12] = {
|
||
0x68u, 4u, 0x43u, 0u, 0u, 0u, /* TESTFR act */
|
||
0x68u, 4u, 0x13u, 0u, 0u, 0u /* STOPDT act */
|
||
};
|
||
|
||
iec104_link_on_rx(&g_lk, b, 12u, now);
|
||
CHECK((cap_u_count(0x83u) == 1)
|
||
&& (iec104_link_state(&g_lk) == IEC104_ST_STOPPED),
|
||
"TC-003 粘帧逐处 + STOPDT 生效");
|
||
}
|
||
reset_all(now);
|
||
{
|
||
static const uint8_t b[6] = { 0x69u, 4u, 0x07u, 0u, 0u, 0u };
|
||
|
||
iec104_link_on_rx(&g_lk, b, 6u, now);
|
||
CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-003 坏起始符 → IDLE");
|
||
}
|
||
reset_all(now);
|
||
{
|
||
static const uint8_t b[6] = { 0x68u, 3u, 0x07u, 0u, 0u, 0u };
|
||
|
||
iec104_link_on_rx(&g_lk, b, 6u, now);
|
||
CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-003 坏长度 → IDLE");
|
||
}
|
||
}
|
||
|
||
static void tc_startdt(void)
|
||
{
|
||
uint32_t now = 1000u;
|
||
|
||
reset_all(now);
|
||
CHECK(iec104_link_state(&g_lk) == IEC104_ST_OPEN, "TC-004 open → OPEN");
|
||
handshake(now);
|
||
CHECK((cap_u_count(0x0Bu) == 1)
|
||
&& (iec104_link_state(&g_lk) == IEC104_ST_RUN), "TC-004 STARTDT → RUN");
|
||
}
|
||
|
||
static void tc_open_ignore(void)
|
||
{
|
||
uint32_t now = 1000u;
|
||
uint8_t a[16];
|
||
uint16_t n;
|
||
|
||
reset_all(now);
|
||
n = mk_asdu(a, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_REQ, 0u, 1u, 0x4011u, NULL, 0u);
|
||
feed_i(a, n, now);
|
||
CHECK((cap_i_count() == 0) && (iec104_link_state(&g_lk) == IEC104_ST_OPEN),
|
||
"TC-005 OPEN 态 I 帧忽略");
|
||
handshake(now);
|
||
m_ns = 0u; /* OPEN 忽略不消费序号 → 主站仍从 0 起 */
|
||
feed_i(a, n, now); /* ns=0 仍有效(序号未被消费) */
|
||
CHECK(cap_i_count() == 1, "TC-005 RUN 后同 ns 正常应答");
|
||
}
|
||
|
||
static void tc_interrogation(void)
|
||
{
|
||
uint32_t now = 5000u;
|
||
uint8_t a[32];
|
||
uint8_t body[1];
|
||
uint16_t n;
|
||
cap_t c;
|
||
|
||
reset_all(now);
|
||
s_f_yc[0] = 111.0f;
|
||
s_f_yc_rc[1] = -1; /* idx1(1Ub)无效通道 */
|
||
handshake(now);
|
||
body[0] = IEC104_RQT_GROUP1;
|
||
n = mk_asdu(a, IEC104_T_C_IC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, body, 1u);
|
||
feed_i(a, n, now);
|
||
CHECK((cap_i_count() == 1) && (cap_i(0, &c) == 0) && (c.type == 100u)
|
||
&& (c.cot == IEC104_COT_ACTCON), "TC-006 actcon 即发 COT7");
|
||
iec104_link_poll(&g_lk, now);
|
||
CHECK(cap_i_count() == 5, "TC-006 首 poll 发满预算 4 分片");
|
||
CHECK((cap_i(1, &c) == 0) && (c.type == 3u) && (c.cot == IEC104_COT_INIT)
|
||
&& (c.vsq == (uint8_t)(IEC104_VSQ_SQ | 4u)) && (c.ioa == 1u),
|
||
"TC-006 YX 双点组 首召 COT=4");
|
||
CHECK((cap_i(2, &c) == 0) && (c.type == 1u)
|
||
&& (c.vsq == (uint8_t)(IEC104_VSQ_SQ | 29u)) && (c.ioa == 5u),
|
||
"TC-006 YX 单点组 29 点");
|
||
CHECK((cap_i(3, &c) == 0) && (c.type == 13u)
|
||
&& (c.vsq == (uint8_t)(IEC104_VSQ_SQ | 8u)) && (c.ioa == 0x4011u)
|
||
&& (c.obj_len == 40u), "TC-006 YC 组 8 点 40B");
|
||
if (cap_i(3, &c) == 0) {
|
||
CHECK((iec104_get_float(c.obj) == 111.0f) && (c.obj[4] == 0u),
|
||
"TC-006 YC 值 111.0 QDS=0");
|
||
CHECK(c.obj[9] == IEC104_QDS_IV, "TC-006 无效通道 QDS=IV");
|
||
}
|
||
CHECK((cap_i(4, &c) == 0) && (c.ioa == 0x401Du) && (c.vsq == 1u),
|
||
"TC-006 频率单点 SQ=0");
|
||
iec104_link_poll(&g_lk, now + 100u);
|
||
CHECK((cap_i_count() == 6) && (cap_i(5, &c) == 0) && (c.type == 100u)
|
||
&& (c.cot == IEC104_COT_ACTTERM), "TC-006 actterm 收尾");
|
||
/* 二召:init 已清 → COT=2 */
|
||
n = mk_asdu(a, IEC104_T_C_IC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, body, 1u);
|
||
feed_i(a, n, now + 200u);
|
||
iec104_link_poll(&g_lk, now + 200u);
|
||
CHECK((cap_i(7, &c) == 0) && (c.type == 3u) && (c.cot == IEC104_COT_BG_SCAN),
|
||
"TC-006 二召 COT=2");
|
||
/* RBT 位 → COT=4(actcon3 是第 12 个未确认帧 → 先 S 确认窗才开) */
|
||
n = mk_asdu(a, IEC104_T_C_IC_NA_1, 1u, IEC104_COT_ACT, IEC104_COT_BIT_RBT,
|
||
1u, 0u, body, 1u);
|
||
feed_i(a, n, now + 300u);
|
||
feed_s(12u, now + 300u);
|
||
iec104_link_poll(&g_lk, now + 300u);
|
||
CHECK((cap_i(12, &c) == 0) && (c.type == 3u) && (c.cot == IEC104_COT_INIT),
|
||
"TC-006 RBT → COT=4");
|
||
}
|
||
|
||
static void tc_cov_yx(void)
|
||
{
|
||
uint32_t now = 1000u;
|
||
cap_t c;
|
||
|
||
reset_all(now);
|
||
handshake(now);
|
||
iec104_link_poll(&g_lk, now); /* 建快照 */
|
||
CHECK(cap_i_count() == 0, "TC-007 首 poll 无自发");
|
||
s_f_bits |= (1uLL << 15); /* YX16(idx15)动作 */
|
||
iec104_link_poll(&g_lk, now + 100u);
|
||
CHECK((cap_i_count() == 1) && (cap_i(0, &c) == 0) && (c.type == 1u)
|
||
&& (c.cot == IEC104_COT_SPONT) && (c.ioa == 16u)
|
||
&& (c.obj_len == 1u) && (c.obj[0] == 1u), "TC-007 YX16 变位 COT3");
|
||
s_f_bits &= ~(1uLL << 15); /* 复归 */
|
||
iec104_link_poll(&g_lk, now + 200u);
|
||
CHECK((cap_i_count() == 2) && (cap_i(1, &c) == 0) && (c.ioa == 16u)
|
||
&& (c.obj[0] == 0u), "TC-007 YX16 复归再变");
|
||
s_f_bits |= (1uLL << 0); /* idx0 无效位 → 不触发 */
|
||
iec104_link_poll(&g_lk, now + 300u);
|
||
CHECK(cap_i_count() == 2, "TC-007 无效位变位不触发");
|
||
}
|
||
|
||
static void tc_cov_yc(void)
|
||
{
|
||
uint32_t now = 1000u;
|
||
cap_t c;
|
||
|
||
reset_all(now);
|
||
handshake(now);
|
||
s_f_yc[0] = 100.0f;
|
||
iec104_link_poll(&g_lk, now); /* 快照 = 100 */
|
||
s_f_yc[0] = 105.0f; /* 死区 10 内 */
|
||
iec104_link_poll(&g_lk, now + 100u);
|
||
CHECK(cap_i_count() == 0, "TC-008 死区内不上送");
|
||
s_f_yc[0] = 130.0f; /* 130-105=25 > 10 */
|
||
iec104_link_poll(&g_lk, now + 200u);
|
||
CHECK((cap_i_count() == 1) && (cap_i(0, &c) == 0) && (c.type == 13u)
|
||
&& (c.cot == IEC104_COT_SPONT) && (c.ioa == 0x4011u)
|
||
&& (iec104_get_float(c.obj) == 130.0f), "TC-008 越死区上送");
|
||
}
|
||
|
||
static void feed_rd(uint32_t now)
|
||
{
|
||
uint8_t a[16];
|
||
uint16_t n = mk_asdu(a, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_REQ, 0u,
|
||
1u, 0x4011u, NULL, 0u);
|
||
|
||
feed_i(a, n, now);
|
||
}
|
||
|
||
static void tc_k_window(void)
|
||
{
|
||
uint32_t now = 1000u;
|
||
int i;
|
||
|
||
reset_all(now);
|
||
handshake(now);
|
||
for (i = 0; i < 12; i++) {
|
||
feed_rd(now);
|
||
}
|
||
CHECK(cap_i_count() == 12, "TC-009 k=12 封顶");
|
||
feed_rd(now);
|
||
CHECK(cap_i_count() == 12, "TC-009 第 13 个被 k 窗抑制");
|
||
feed_s(12u, now); /* 全确认 → 窗开 */
|
||
feed_rd(now);
|
||
CHECK(cap_i_count() == 13, "TC-009 S 确认后恢复发送");
|
||
}
|
||
|
||
static void tc_w_thresh(void)
|
||
{
|
||
uint32_t now = 1000u;
|
||
uint16_t nr = 0xFFFFu;
|
||
int i;
|
||
|
||
reset_all(now);
|
||
handshake(now);
|
||
for (i = 0; i < 7; i++) {
|
||
feed_rd(now);
|
||
}
|
||
CHECK(cap_has_s(&nr) == 0, "TC-010 收 7 未发 S");
|
||
feed_rd(now); /* 第 8 个 → w 阈 */
|
||
CHECK((cap_has_s(&nr) == 1) && (nr == 8u), "TC-010 w=8 → S nr=8");
|
||
}
|
||
|
||
static void tc_t2(void)
|
||
{
|
||
uint32_t now = 1000u;
|
||
uint16_t nr = 0xFFFFu;
|
||
|
||
reset_all(now);
|
||
handshake(now);
|
||
feed_rd(now); /* rx_unacked=1,t2 起爆 */
|
||
iec104_link_poll(&g_lk, now + 9999u);
|
||
CHECK(cap_has_s(&nr) == 0, "TC-011 t2 未到期不发 S");
|
||
iec104_link_poll(&g_lk, now + 10001u);
|
||
CHECK((cap_has_s(&nr) == 1) && (nr == 1u), "TC-011 t2 到期发 S");
|
||
}
|
||
|
||
static void tc_t3_t1(void)
|
||
{
|
||
uint32_t now = 1000u;
|
||
|
||
reset_all(now);
|
||
handshake(now);
|
||
iec104_link_poll(&g_lk, now + 19999u);
|
||
CHECK(cap_u_count(0x43u) == 0, "TC-012 t3 未到期无 TESTFR");
|
||
iec104_link_poll(&g_lk, now + 20001u);
|
||
CHECK(cap_u_count(0x43u) == 1, "TC-012 t3 到期发 TESTFR act");
|
||
iec104_link_poll(&g_lk, now + 20001u + 15001u);
|
||
CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-012 TESTFR 未答 t1 → IDLE");
|
||
/* con 清挂起 → 不误杀 */
|
||
reset_all(now);
|
||
handshake(now);
|
||
iec104_link_poll(&g_lk, now + 20001u);
|
||
CHECK(cap_u_count(0x43u) == 1, "TC-012 重连 t3 再触发");
|
||
feed_u(0x83u, now + 20100u); /* TESTFR con */
|
||
iec104_link_poll(&g_lk, now + 35000u);
|
||
CHECK(iec104_link_state(&g_lk) == IEC104_ST_RUN, "TC-012 con 清挂起后 t1 不误杀");
|
||
}
|
||
|
||
static void tc_dc(void)
|
||
{
|
||
uint32_t now = 1000u;
|
||
uint8_t a[16];
|
||
uint8_t body[1];
|
||
uint16_t n;
|
||
cap_t c;
|
||
|
||
reset_all(now);
|
||
handshake(now);
|
||
body[0] = 0x82u; /* S=1, DPC=ON */
|
||
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Au, body, 1u);
|
||
feed_i(a, n, now);
|
||
CHECK((cap_i(0, &c) == 0) && (c.type == 52u) && (c.cot == IEC104_COT_ACTCON)
|
||
&& ((c.cot_hi & IEC104_COT_BIT_PN) == 0u), "TC-013 选择段 actcon");
|
||
CHECK((s_f_yk_sel == 1u) && (s_f_yk_ioa == 0x600Au), "TC-013 选择段转发源");
|
||
body[0] = 0x02u; /* 执行 ON */
|
||
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Au, body, 1u);
|
||
feed_i(a, n, now);
|
||
CHECK((cap_i(1, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) == 0u)
|
||
&& (s_f_yk_sel == 0u), "TC-013 执行段 actcon");
|
||
/* 失配:选 ON 执 OFF */
|
||
body[0] = 0x82u;
|
||
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Au, body, 1u);
|
||
feed_i(a, n, now);
|
||
body[0] = 0x01u;
|
||
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Au, body, 1u);
|
||
feed_i(a, n, now);
|
||
CHECK((cap_i(3, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) != 0u),
|
||
"TC-013 执行失配 PN");
|
||
/* 拒绝类(YK1):选择段即 PN */
|
||
body[0] = 0x82u;
|
||
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x6000u, body, 1u);
|
||
feed_i(a, n, now);
|
||
CHECK((cap_i(4, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) != 0u),
|
||
"TC-013 无出口硬件 YK 选择段 PN");
|
||
}
|
||
|
||
/* TC-024(B7):YK12 全局软压板两段式转真(经真实板端源 + 原子封存) */
|
||
static void tc_dc_yk12(void)
|
||
{
|
||
uint32_t now = 1000u;
|
||
uint8_t a[16];
|
||
uint8_t body[1];
|
||
uint16_t n;
|
||
cap_t c;
|
||
|
||
reset_all(now);
|
||
handshake(now);
|
||
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 1u, "TC-024 软压板默认投入");
|
||
body[0] = 0x81u; /* S=1, DPC=OFF:选择段 */
|
||
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Bu, body, 1u);
|
||
feed_i(a, n, now);
|
||
CHECK((cap_i(0, &c) == 0) && (c.cot == IEC104_COT_ACTCON)
|
||
&& ((c.cot_hi & IEC104_COT_BIT_PN) == 0u), "TC-024 YK12 选择段 actcon");
|
||
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 1u, "TC-024 选择段不动作");
|
||
body[0] = 0x01u; /* 执行 OFF */
|
||
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Bu, body, 1u);
|
||
feed_i(a, n, now);
|
||
CHECK((cap_i(1, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) == 0u),
|
||
"TC-024 YK12 执行段 actcon");
|
||
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 0u, "TC-024 执行段软压板转真退出");
|
||
CHECK(prot_set_selfcheck() == 0, "TC-024 set_sealed 原子封存 → selfcheck 通过");
|
||
CHECK(prot_set_dirty_peek() == 1u, "TC-024 置脏待持久化");
|
||
body[0] = 0x82u; /* 再投入:选择 ON */
|
||
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Bu, body, 1u);
|
||
feed_i(a, n, now);
|
||
body[0] = 0x02u; /* 执行 ON */
|
||
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Bu, body, 1u);
|
||
feed_i(a, n, now);
|
||
CHECK((cap_i(3, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) == 0u),
|
||
"TC-024 投入执行 actcon");
|
||
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 1u, "TC-024 软压板转真投入");
|
||
body[0] = 0x80u; /* DPC=00 非法:选择段即 PN */
|
||
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Bu, body, 1u);
|
||
feed_i(a, n, now);
|
||
CHECK((cap_i(4, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) != 0u),
|
||
"TC-024 非法 DPC 选择段 PN");
|
||
}
|
||
|
||
static void tc_se(void)
|
||
{
|
||
uint32_t now = 1000u;
|
||
uint8_t a[24];
|
||
uint8_t body[5];
|
||
uint16_t n;
|
||
uint32_t addr = g_prot_set_meta[PS_OC1_CUR].comm_addr; /* 0x8858 */
|
||
cap_t c;
|
||
|
||
reset_all(now);
|
||
handshake(now);
|
||
CHECK(addr == 0x8858u, "TC-014 OC1 电流定值通讯地址");
|
||
iec104_put_float(body, 5.0f);
|
||
body[4] = IEC104_QDC_S; /* 选择段 */
|
||
n = mk_asdu(a, IEC104_T_C_SE_NC_1, 1u, IEC104_COT_ACT, 0u, 1u, addr, body, 5u);
|
||
feed_i(a, n, now);
|
||
CHECK((cap_i(0, &c) == 0) && (c.type == 50u)
|
||
&& ((c.cot_hi & IEC104_COT_BIT_PN) == 0u), "TC-014 定值选择 actcon");
|
||
body[4] = 0u; /* 执行段 */
|
||
n = mk_asdu(a, IEC104_T_C_SE_NC_1, 1u, IEC104_COT_ACT, 0u, 1u, addr, body, 5u);
|
||
feed_i(a, n, now);
|
||
CHECK((cap_i(1, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) == 0u),
|
||
"TC-014 定值执行 actcon");
|
||
CHECK(prot_set_get_f(PS_OC1_CUR) == 5.0f, "TC-014 定值已写入(真实 prot_set)");
|
||
CHECK(prot_set_selfcheck() == 0, "TC-014 执行后 seal → selfcheck 通过");
|
||
CHECK(prot_set_dirty_peek() == 1u, "TC-014 执行段置脏(B7 待落盘)");
|
||
iec104_put_float(body, 100000.0f); /* 越上限 */
|
||
body[4] = IEC104_QDC_S;
|
||
n = mk_asdu(a, IEC104_T_C_SE_NC_1, 1u, IEC104_COT_ACT, 0u, 1u, addr, body, 5u);
|
||
feed_i(a, n, now);
|
||
CHECK((cap_i(2, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) != 0u),
|
||
"TC-014 越界选择段 PN");
|
||
}
|
||
|
||
static void tc_rd(void)
|
||
{
|
||
uint32_t now = 1000u;
|
||
uint8_t a[16];
|
||
uint16_t n;
|
||
cap_t c;
|
||
|
||
reset_all(now);
|
||
handshake(now);
|
||
s_f_yc[0] = 222.0f;
|
||
n = mk_asdu(a, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_REQ, 0u, 1u, 0x4011u, NULL, 0u);
|
||
feed_i(a, n, now);
|
||
CHECK((cap_i(0, &c) == 0) && (c.type == 13u) && (c.cot == IEC104_COT_REQ)
|
||
&& (iec104_get_float(c.obj) == 222.0f), "TC-015 C_RD YC 命中");
|
||
n = mk_asdu(a, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_REQ, 0u, 1u, 0x8858u, NULL, 0u);
|
||
feed_i(a, n, now);
|
||
CHECK((cap_i(1, &c) == 0) && (c.type == 13u) && (c.ioa == 0x8858u),
|
||
"TC-015 C_RD 定值命中");
|
||
n = mk_asdu(a, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_REQ, 0u, 1u, 0x9999u, NULL, 0u);
|
||
feed_i(a, n, now);
|
||
CHECK((cap_i(2, &c) == 0) && (c.type == 102u)
|
||
&& (c.cot == IEC104_COT_UNK_IOA) && ((c.oa & 0x80u) != 0u),
|
||
"TC-015 未知 IOA COT14+EVI");
|
||
n = mk_asdu(a, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_REQ, 0u, 1u, 0u, NULL, 0u);
|
||
feed_i(a, n, now);
|
||
CHECK((cap_i(3, &c) == 0) && (c.cot == IEC104_COT_UNK_IOA),
|
||
"TC-015 本地定值项(comm_addr=0)不可寻址(B7)");
|
||
}
|
||
|
||
static void tc_cs(void)
|
||
{
|
||
uint32_t now = 1000u;
|
||
uint8_t a[24];
|
||
uint8_t cp[7];
|
||
uint16_t n;
|
||
cap_t c;
|
||
uint64_t e = 1709188496789ULL;
|
||
|
||
reset_all(now);
|
||
handshake(now);
|
||
(void)iec104_epoch_ms_to_cp56(e, cp);
|
||
n = mk_asdu(a, IEC104_T_C_CS_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, cp, 7u);
|
||
feed_i(a, n, now);
|
||
CHECK((cap_i(0, &c) == 0) && (c.type == 103u)
|
||
&& ((c.cot_hi & IEC104_COT_BIT_PN) == 0u) && (s_clock_ms == e),
|
||
"TC-016 C_CS 对时成功 epoch 精确");
|
||
cp[2] |= 0x80u; /* IV */
|
||
n = mk_asdu(a, IEC104_T_C_CS_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, cp, 7u);
|
||
feed_i(a, n, now);
|
||
CHECK((cap_i(1, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) != 0u),
|
||
"TC-016 CP56 IV → PN");
|
||
(void)iec104_epoch_ms_to_cp56(e, cp);
|
||
s_clock_rc = -1; /* 系统侧拒绝 */
|
||
n = mk_asdu(a, IEC104_T_C_CS_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, cp, 7u);
|
||
feed_i(a, n, now);
|
||
CHECK((cap_i(2, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) != 0u),
|
||
"TC-016 clock_sync 失败 → PN");
|
||
}
|
||
|
||
static void tc_err_resp(void)
|
||
{
|
||
uint32_t now = 1000u;
|
||
uint8_t a[16];
|
||
uint16_t n, m;
|
||
cap_t c;
|
||
|
||
reset_all(now);
|
||
handshake(now);
|
||
n = mk_asdu(a, 200u, 1u, IEC104_COT_ACT, 0u, 1u, 0u, NULL, 0u);
|
||
feed_i(a, n, now);
|
||
CHECK((cap_i(0, &c) == 0) && (c.type == 200u) && (c.cot == IEC104_COT_UNK_COT)
|
||
&& ((c.oa & 0x80u) != 0u), "TC-017 未知类型 COT12+EVI");
|
||
m = iec104_asdu_put_hdr(a, IEC104_T_C_IC_NA_1, 1u, IEC104_COT_ACT, 0u, 2u);
|
||
a[m] = IEC104_RQT_GROUP1;
|
||
m++;
|
||
feed_i(a, m, now); /* ca=2 ≠ 1 */
|
||
CHECK((cap_i(1, &c) == 0) && (c.cot == IEC104_COT_UNK_CA)
|
||
&& ((c.oa & 0x80u) != 0u), "TC-017 错公共地址 COT13+EVI");
|
||
}
|
||
|
||
static void tc_stopdt(void)
|
||
{
|
||
uint32_t now = 1000u;
|
||
uint8_t a[16];
|
||
uint8_t body[1];
|
||
uint16_t n;
|
||
|
||
reset_all(now);
|
||
handshake(now);
|
||
feed_u(0x13u, now); /* STOPDT act */
|
||
CHECK((cap_u_count(0x23u) == 1)
|
||
&& (iec104_link_state(&g_lk) == IEC104_ST_STOPPED), "TC-018 STOPDT con");
|
||
body[0] = IEC104_RQT_GROUP1;
|
||
n = mk_asdu(a, IEC104_T_C_IC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, body, 1u);
|
||
feed_i(a, n, now);
|
||
CHECK(cap_i_count() == 0, "TC-018 STOPPED 态数据服务关");
|
||
feed_u(0x43u, now);
|
||
CHECK(cap_u_count(0x83u) == 1, "TC-018 STOPPED 态 TESTFR 仍应答");
|
||
}
|
||
|
||
static void tc_seq_err(void)
|
||
{
|
||
uint32_t now = 1000u;
|
||
uint8_t a[16];
|
||
uint16_t n;
|
||
|
||
reset_all(now);
|
||
handshake(now);
|
||
m_ns = 5u; /* 期望 0,投 5 */
|
||
n = mk_asdu(a, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_REQ, 0u, 1u, 0x4011u, NULL, 0u);
|
||
feed_i(a, n, now);
|
||
CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-019 序号错 → IDLE");
|
||
}
|
||
|
||
static void tc_pts(void)
|
||
{
|
||
float v;
|
||
uint8_t q;
|
||
uint8_t d;
|
||
|
||
reset_all(1000u);
|
||
CHECK((iec104_pts_yc_count() == 9u) && (iec104_pts_yx_count() == 33u)
|
||
&& (iec104_pts_yc_ioa(8u) == 0x401Du), "TC-020 表尺寸/IOA");
|
||
s_f_yc[0] = 7.5f;
|
||
CHECK((iec104_pts_yc_read(0u, &v, &q) == 0) && (v == 7.5f) && (q == 0u),
|
||
"TC-020 有效 YC");
|
||
s_f_yc_rc[1] = -1;
|
||
(void)iec104_pts_yc_read(1u, &v, &q);
|
||
CHECK((v == 0.0f) && (q == IEC104_QDS_IV), "TC-020 无效通道 IV");
|
||
s_f_yc_rc[2] = -2;
|
||
(void)iec104_pts_yc_read(2u, &v, &q);
|
||
CHECK(q == IEC104_QDS_NT, "TC-020 未就绪 NT");
|
||
iec104_pts_yx_read(0u, &d);
|
||
CHECK(d == IEC104_DIQ_IV, "TC-020 无效遥信 IV");
|
||
CHECK((iec104_pts_yx_is_dp(0u) == 1u) && (iec104_pts_yx_is_dp(4u) == 0u),
|
||
"TC-020 is_dp 分界");
|
||
s_f_bits = (1uLL << 15);
|
||
iec104_pts_yx_read(15u, &d);
|
||
CHECK(d == 1u, "TC-020 有效遥信 ON");
|
||
}
|
||
|
||
static void tc_ts_rp(void)
|
||
{
|
||
uint32_t now = 1000u;
|
||
uint8_t a[16];
|
||
uint8_t body[2];
|
||
uint16_t n;
|
||
cap_t c;
|
||
|
||
reset_all(now);
|
||
handshake(now);
|
||
body[0] = 0u;
|
||
n = mk_asdu(a, IEC104_T_C_TS_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, body, 1u);
|
||
feed_i(a, n, now);
|
||
CHECK((cap_i(0, &c) == 0) && (c.type == 104u) && (c.cot == IEC104_COT_ACTCON),
|
||
"TC-021 C_TS actcon");
|
||
n = mk_asdu(a, IEC104_T_C_RP_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, body, 1u);
|
||
feed_i(a, n, now);
|
||
CHECK((cap_i(1, &c) == 0) && (c.type == 105u) && (c.cot == IEC104_COT_ACTCON),
|
||
"TC-021 C_RP actcon");
|
||
}
|
||
|
||
static void tc_periodic(void)
|
||
{
|
||
uint32_t now = 1000u;
|
||
cap_t c;
|
||
|
||
reset_all(now);
|
||
handshake(now);
|
||
iec104_link_poll(&g_lk, now + 60001u);
|
||
CHECK(cap_i_count() == 2, "TC-022 周期上送 2 分片");
|
||
CHECK((cap_i(0, &c) == 0) && (c.type == 13u) && (c.cot == IEC104_COT_PERIODIC)
|
||
&& (c.ioa == 0x4011u), "TC-022 周期 YC 组 COT=1");
|
||
CHECK((cap_i(1, &c) == 0) && (c.ioa == 0x401Du), "TC-022 周期频率点");
|
||
}
|
||
|
||
static void tc_cov_kfull(void)
|
||
{
|
||
uint32_t now = 1000u;
|
||
int i;
|
||
cap_t c;
|
||
|
||
reset_all(now);
|
||
handshake(now);
|
||
iec104_link_poll(&g_lk, now); /* 先建快照(首 poll 只建不算变位) */
|
||
for (i = 0; i < 12; i++) {
|
||
feed_rd(now); /* k 窗占满 */
|
||
}
|
||
s_f_bits |= (1uLL << 14); /* YX15 变位 */
|
||
iec104_link_poll(&g_lk, now + 100u);
|
||
CHECK(cap_i_count() == 12, "TC-023 k 满时自发发不出");
|
||
feed_s(12u, now + 200u); /* 窗开 */
|
||
iec104_link_poll(&g_lk, now + 300u);
|
||
CHECK((cap_i_count() == 13) && (cap_i(12, &c) == 0) && (c.ioa == 15u)
|
||
&& (c.cot == IEC104_COT_SPONT), "TC-023 窗开后补发不丢事件");
|
||
}
|
||
|
||
int main(void)
|
||
{
|
||
printf("=== IEC104 Protocol Stack Host Tests ===\n");
|
||
tc_cp56();
|
||
tc_ioa_float();
|
||
tc_apci();
|
||
tc_startdt();
|
||
tc_open_ignore();
|
||
tc_interrogation();
|
||
tc_cov_yx();
|
||
tc_cov_yc();
|
||
tc_k_window();
|
||
tc_w_thresh();
|
||
tc_t2();
|
||
tc_t3_t1();
|
||
tc_dc();
|
||
tc_dc_yk12();
|
||
tc_se();
|
||
tc_rd();
|
||
tc_cs();
|
||
tc_err_resp();
|
||
tc_stopdt();
|
||
tc_seq_err();
|
||
tc_pts();
|
||
tc_ts_rp();
|
||
tc_periodic();
|
||
tc_cov_kfull();
|
||
printf("=== IEC104 Tests: %d/%d PASSED (failed=%d) ===\n",
|
||
s_passed, s_total, s_failed);
|
||
return (s_failed == 0) ? 0 : 1;
|
||
}
|