8c72698666
新增 App/algo/run_par.c/h(「数据可信」卡 10,lead 四裁落地): - [run_par] cfg 段:遥测死区 9 键 int32 毫单位(db_ua…db_freq),工厂默认 = 卡 6 额定 0.5% 档(500mV×4 / 5mA×4 / 500mHz);注册走 adc_cal 同款内核前 契约(app_main_init),不挂回调 = 引擎副本即生效值;键数 9≤16 不拆段 - 真源收编(裁③):pts 层 s_yc_db 运行时副本删除,COV 每拍实时读 run_par (协议→算法向下依赖,同 meas 先例;无投递无缓存 = 顺序/幂等平凡成立); iec104_pts_yc_set_deadband()/deadband() 保留为转发壳,壳内保留"成功清 半截确认计数"语义并由 TC-IEC104-027 追加断言钉住(裁⑤) - 104 地址映射(裁①):0x8030~0x8038 死区 9 点 / 0x8040~0x8047 k 覆盖 / 0x8048~0x804F b 覆盖(逐项地址本仓自定——复现规划 §5.4 权威表不在仓, 送检点表到位后替换 run_par.c 顶部常量段一处;已在 IEC104 §3.4 表头挂账); 路由顺序 = run_par 表先、未命中落 prot_set——0x8027~0x802A 属 prot_set 告警段永不覆盖,TC-IEC104-029 双向往返钉死;空隙地址自然 COT14/PN - 通道系数覆盖 = adc_cal_set 代理(裁②a):合法带/越带 [ERR] 留痕全复用, run_par 不存第二份系数(防双真源);单侧写 RMW 用另一侧当前生效值; 回显 = 生效值视图(文件坏值被哨兵回退 ROM 时主站看到的就是下游吃到的); adc_cal 配套:+adc_cal_b_codes() 读取口(回显/RMW 用)+ set() 授权写者 注记扩至 iec104Task 直通路径(单字原子契约同「数据可信」§4.4) - 持久化(裁②b):新事件位 SYS_EVT_RUN_PAR_SAVE(1<<8);iec104Task 主循环 peek 投递 → defaultTask run_par_store_save() 一条事件一趟 cfg_file_save 整档外科(覆盖 [run_par] 9 键 + 联动 [adc_cal] 两段);失败脏保留 = 天然 重试(B7 prot_set 同款)。C_SE 现行无"暂停/测试态"门控——文档现行如此 不造新语义,送检若要求接线点已写进 §3.4 注(裁④) 测试:新建 test/host/run_par(TC-RUNPAR-001~008,67 断言,真实 cfg 引擎 + 真实 adc_cal + 内存虚拟盘同 adc_cal 套件先例;[run_par] 注册契约/落盘往返/ 外改文件即副本生效/两段式不提交不置脏/合法带拒+RMW+生效值回显/init 幂等 重注册留痕);iec104 套扩真链 TU(cfg_file+adc_cal+run_par 非桩,141 断言: +TC-IEC104-029 路由 + 壳清确认计数 3 断言);meas 套静态更名 s_snap*→ s_ms_snap*(与 cfg_file.c s_snap 同 TU 相撞,D1 同款改名法,226 断言不变); iec104_serv 套补 run_par_dirty_peek 假件。id_registry 建族 TC-HOST-RUNPAR (游标 008)+ TC-HOST-IEC104 游标 028→029;新钩子 run-par-unit-test + iec104 钩子触发面随真链扩;uvprojx +run_par.c(合法入库)+ SIM_EXEMPT 登记 (模拟器不编 104 协议,已核实 CMake 面)。 文档:IEC104 §3.4 运行参数段表(含送检替换账/门控未来条件/代理声明)、§7、 §8 第 4 条、变更记录;网络配置说明新增 §3.5 [run_par] 键表;复现规划 §5.4 补运行参数段落实进度。**遗留(交 lead)**:数据可信 §4.4 "引擎 RAM 副本" 一节当时的措辞对象含 pts 侧副本,s_yc_db 删除后该句过时(裁③b 点名,§4.4 在写域外未动)。 gate:@build 0/0(Code=219620 RO=22036 RW=1280 ZI=132864);vs 0cbae7d: Flash +1912B、RAM(RW+ZI) +8B(37B 新静态被链接器并洞吸收);iec104Task 栈 1456B/margin 43%、defaultTask 3032B 不变;id-check/doc-links/cppcheck/ coding-standard 实跑过;--all-files 全量 45 钩子实跑(43 绿 + 仅 mem-budget / mem-numbers 两红 = 台账受管区 vs .map 漂移,回填属 lead 单写者区,批 1/道D-1 /D2 同口径):gate: only mem-budget/mem-numbers pending lead ledger backfill BOM/CR/U+FFFD/字面量 ASCII 逐文件自查通过。
1305 lines
49 KiB
C
1305 lines
49 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-IEC104-001 CP56Time2a 往返 / 闰年 / IV / 钳位
|
||
* TC-IEC104-002 IOA 3B 与短浮点小端往返
|
||
* TC-IEC104-003 APCI 重组:半帧 / 粘帧 / 坏起始符 / 坏长度
|
||
* TC-IEC104-004 STARTDT 握手 → RUN
|
||
* TC-IEC104-005 OPEN 态 I 帧忽略(序号不消费)
|
||
* TC-IEC104-006 总召全流程:actcon→4 分片(首召 COT=4)→actterm;二召 COT=2;RBT→COT=4
|
||
* TC-IEC104-007 遥信变位 COV(COT=3);无效位不触发
|
||
* TC-IEC104-008 遥测死区 COV(卡 9 起 = 2 点确认):死区内不发 / 首越只挂起 /
|
||
* 连续第 2 越点上送
|
||
* TC-IEC104-009 k 窗:12 未确认停发;S 确认后恢复
|
||
* TC-IEC104-010 w 阈:收满 8 发 S
|
||
* TC-IEC104-011 t2 到期发 S
|
||
* TC-IEC104-012 t3 空闲发 TESTFR act;未答 t1 超时 → IDLE;con 清挂起不误杀
|
||
* TC-IEC104-013 遥控两段式:选择/执行成功;失配 PN;拒绝类选择段即 PN
|
||
* TC-IEC104-014 定值两段写(真实 prot_set):越界 PN;执行后 get_f + selfcheck
|
||
* TC-IEC104-015 C_RD:YC 命中 / 定值命中 / 未知 IOA COT14+EVI
|
||
* TC-IEC104-016 C_CS:clock_sync 收正确 epoch;IV → PN;clock_sync 失败 → PN
|
||
* TC-IEC104-017 未知类型 COT12+EVI;错公共地址 COT13+EVI
|
||
* TC-IEC104-018 STOPDT:数据停发,TESTFR 仍应答
|
||
* TC-IEC104-019 序号错 → IDLE(链路故障)
|
||
* TC-IEC104-020 pts:无效通道 IV / 未就绪 NT / 无效遥信 IV / is_dp / 表尺寸
|
||
* TC-IEC104-021 C_TS / C_RP actcon
|
||
* TC-IEC104-022 周期上送 COT=1(YC 组 + 频率)
|
||
* TC-IEC104-023 k 窗满 COV 不丢事件(pend 暂存,窗开后补发)
|
||
* —— 以下为「数据可信」卡 9/卡 11(道D,2026-10-25;板端默认源 = 真实
|
||
L2 链 ring→phasor→freq→meas,非桩)——
|
||
* TC-IEC104-025 板端源读快照(024 号被存量 B7 用例的裸标签占用):未发布
|
||
* IV|NT / 值=基波有效值 / 削顶 CLIPPED
|
||
* →线上 IV 清零 / 干净路品质 0 / 频率未新鲜 IV
|
||
* TC-IEC104-026 0x401D 绑频:收敛后 ±0.02 Hz / freq_valid==0 → IV 清零
|
||
* (卡 11 DoD 原句)
|
||
* TC-IEC104-027 死区运行时化(卡 10 后 = run_par 真源 + 转发壳):set/get
|
||
* + 非法拒 + 运行档压旧档 + 2 点确认/回摆撤销 + 壳清半截确认计数
|
||
* TC-IEC104-028 品质翻转直报(假源)+ 板端 seq 去重(同帧不计两点)+
|
||
* 换帧只变品质时按集合直报
|
||
* TC-IEC104-029 卡 10 路由:0x8030/0x8038 命中 / 0x8027 仍归 prot_set /
|
||
* 空隙 COT14 / C_SE 两段式 / k 带拒 / b 单侧 RMW 保 k
|
||
*
|
||
* 通过:全部 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 "meas.c" /* 卡 9:板端默认 YC 源 = L2 遥测快照(真实链,非桩) */
|
||
|
||
/* ===================== 卡 10 真链桩(同 adc_cal 套件先例) =====================
|
||
* run_par 依赖真实 cfg 引擎与 adc_cal:dbg_log 走 stub/ 计数宏,文件/堆走
|
||
* 内存虚拟盘(cfg_store_* 强符号),adc_input_desc 按 V0.1 权威映射给桩。
|
||
* 顺序敏感:桩头与强符号先于被纳入的 .c。 */
|
||
#include "dbg_log.h" /* stub/ 优先解析:DBG_* → 计数 */
|
||
#include <stdlib.h>
|
||
#include "cfg_file_store.h"
|
||
|
||
#define RP_VFS_CAP 8192u
|
||
|
||
static uint8_t rp_s_vfs[RP_VFS_CAP];
|
||
static size_t rp_s_vfs_len = 0u;
|
||
static uint32_t rp_s_vfs_mtime = 100u;
|
||
static int rp_s_vfs_exists = 0;
|
||
|
||
void *cfg_store_alloc(size_t size) { return malloc(size); }
|
||
void cfg_store_free(void *p_buf) { free(p_buf); }
|
||
uint32_t cfg_store_heap_free(void) { return 0u; }
|
||
|
||
int cfg_store_read(const char *p_path, uint8_t *p_buf, size_t cap, size_t *p_nread) {
|
||
size_t n;
|
||
|
||
(void)p_path;
|
||
if (!rp_s_vfs_exists) {
|
||
return -1;
|
||
}
|
||
n = rp_s_vfs_len;
|
||
if (n > cap) {
|
||
n = cap;
|
||
}
|
||
(void)memcpy(p_buf, rp_s_vfs, n);
|
||
*p_nread = n;
|
||
return 0;
|
||
}
|
||
|
||
int cfg_store_write_atomic(const char *p_path, const uint8_t *p_buf, size_t len) {
|
||
(void)p_path;
|
||
if (len > RP_VFS_CAP) {
|
||
return -1;
|
||
}
|
||
(void)memcpy(rp_s_vfs, p_buf, len);
|
||
rp_s_vfs_len = len;
|
||
rp_s_vfs_exists = 1;
|
||
rp_s_vfs_mtime++;
|
||
return 0;
|
||
}
|
||
|
||
int cfg_store_stat(const char *p_path, cfg_file_stat_t *p_st) {
|
||
(void)p_path;
|
||
if (!rp_s_vfs_exists) {
|
||
return -1;
|
||
}
|
||
p_st->size = (uint32_t)rp_s_vfs_len;
|
||
p_st->mtime = rp_s_vfs_mtime;
|
||
return 0;
|
||
}
|
||
|
||
int cfg_store_remove(const char *p_path) {
|
||
(void)p_path;
|
||
rp_s_vfs_exists = 0;
|
||
rp_s_vfs_len = 0u;
|
||
return 0;
|
||
}
|
||
|
||
const char *cfg_store_config_path(void) { return "/config.ini"; }
|
||
const char *cfg_store_tmp_path(void) { return "/config.tmp"; }
|
||
|
||
/* ---- sys_log 桩:只计数自检留痕 ---- */
|
||
#include "sys_log.h"
|
||
|
||
static int rp_s_syslog_count = 0;
|
||
|
||
void syslog_post(syslog_id_t id, const char *desc) {
|
||
(void)id;
|
||
(void)desc;
|
||
rp_s_syslog_count++;
|
||
}
|
||
|
||
/* ---- adc_input_desc 桩:V0.1 权威映射(docs/硬件平台/ADC通道映射说明.md) ---- */
|
||
#include "adc_input.h"
|
||
|
||
static const adc_channel_desc_t rp_s_desc[ADC_SEM_COUNT] = {
|
||
{ ADC_SRC_TPAFE, 6u }, /* UA = buf[6] */
|
||
{ ADC_SRC_STM32, 14u }, /* UB = CH14(PC4) */
|
||
{ ADC_SRC_STM32, 3u }, /* UC = CH3 */
|
||
{ ADC_SRC_TPAFE, 5u }, /* UN = buf[5] */
|
||
{ ADC_SRC_TPAFE, 2u }, /* IA = buf[2] */
|
||
{ ADC_SRC_STM32, 6u }, /* IB = CH6 */
|
||
{ ADC_SRC_STM32, 1u }, /* IC = CH1 */
|
||
{ ADC_SRC_STM32, 2u }, /* IN = CH2 */
|
||
{ ADC_SRC_STM32, 5u }, /* TEMP = CH5 */
|
||
};
|
||
|
||
const adc_channel_desc_t *adc_input_desc(adc_semantic_t ch) {
|
||
if ((int)ch < 0 || (unsigned)ch >= (unsigned)ADC_SEM_COUNT) {
|
||
return NULL;
|
||
}
|
||
return &rp_s_desc[(unsigned)ch];
|
||
}
|
||
|
||
/* dbg 计数定义(stub/dbg_log.h 声明 extern) */
|
||
int g_dbgstub104_err_count = 0;
|
||
int g_dbgstub104_info_count = 0;
|
||
int g_dbgstub104_debug_count = 0;
|
||
|
||
#include "cfg_file.c"
|
||
#include "adc_cal.c"
|
||
#include "run_par.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;
|
||
}
|
||
/* B9 时标源假回调:s_epoch_val = UINT64_MAX → 时钟无效(CP56 IV) */
|
||
static uint64_t s_epoch_val;
|
||
static uint64_t f_epoch(void *ctx)
|
||
{
|
||
(void)ctx;
|
||
return s_epoch_val;
|
||
}
|
||
|
||
static iec104_link_t g_lk;
|
||
static const iec104_link_cfg_t g_cfg = { 1u, cap_send, f_clock, f_epoch };
|
||
|
||
/* ---------------- 主站侧模拟(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();
|
||
run_par_init(); /* 卡 10:死区/脏复位到工厂默认(重复注册 -1 走计数桩,无害) */
|
||
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;
|
||
s_epoch_val = 1760234567000ULL; /* B9:固定时标(2025-10-12 UTC) */
|
||
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 round trip on 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 round trip on the leap day 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 a value before 2000 is clamped");
|
||
}
|
||
|
||
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 is 3 bytes little endian");
|
||
iec104_put_float(b, 1.0f);
|
||
CHECK((b[0] == 0x00u) && (b[1] == 0x00u) && (b[2] == 0x80u)
|
||
&& (b[3] == 0x3Fu), "TC-002 float in IEEE754 little endian");
|
||
iec104_put_float(b, -12.5f);
|
||
CHECK(iec104_get_float(b) == -12.5f, "TC-002 float round trip");
|
||
}
|
||
|
||
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 a half frame triggers nothing");
|
||
iec104_link_on_rx(&g_lk, &b[3], 3u, now);
|
||
CHECK(cap_u_count(0x0Bu) == 1, "TC-003 a half frame is reassembled then 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 a coalesced frame is handled one by one and STOPDT takes effect");
|
||
}
|
||
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 a bad start byte -> 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 a bad length -> 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 an I frame in OPEN state is ignored");
|
||
handshake(now);
|
||
m_ns = 0u; /* OPEN 忽略不消费序号 → 主站仍从 0 起 */
|
||
feed_i(a, n, now); /* ns=0 仍有效(序号未被消费) */
|
||
CHECK(cap_i_count() == 1, "TC-005 after RUN the same ns is answered normally");
|
||
}
|
||
|
||
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 is sent right away, COT7");
|
||
iec104_link_poll(&g_lk, now);
|
||
CHECK(cap_i_count() == 5, "TC-006 the first poll uses the whole budget of 4 frames");
|
||
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 double-point group, first call 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 single-point group with 29 points");
|
||
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 group of 8 points, 40 bytes");
|
||
if (cap_i(3, &c) == 0) {
|
||
CHECK((iec104_get_float(c.obj) == 111.0f) && (c.obj[4] == 0u),
|
||
"TC-006 YC value 111.0 with QDS=0");
|
||
CHECK(c.obj[9] == IEC104_QDS_IV, "TC-006 an invalid channel sets QDS=IV");
|
||
}
|
||
CHECK((cap_i(4, &c) == 0) && (c.ioa == 0x401Du) && (c.vsq == 1u),
|
||
"TC-006 frequency as a single point with 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 closes the sequence");
|
||
/* 二召: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 second call 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;
|
||
uint64_t back;
|
||
|
||
reset_all(now);
|
||
handshake(now);
|
||
iec104_link_poll(&g_lk, now); /* 建快照 */
|
||
CHECK(cap_i_count() == 0, "TC-007 the first poll sends nothing spontaneous");
|
||
s_f_bits |= (1uLL << 15); /* YX16(idx15)动作 */
|
||
iec104_link_poll(&g_lk, now + 100u);
|
||
/* B9:单点 YX 自发 = M_SP_TB_1(30)带 CP56Time2a 时标(SOE 上送路径) */
|
||
CHECK((cap_i_count() == 1) && (cap_i(0, &c) == 0) && (c.type == 30u)
|
||
&& (c.cot == IEC104_COT_SPONT) && (c.ioa == 16u)
|
||
&& (c.obj_len == 8u) && (c.obj[0] == 1u), "TC-007 a YX16 change of state sends COT3 with TB");
|
||
if ((cap_i(0, &c) == 0) && (c.obj_len == 8u)) {
|
||
CHECK(iec104_cp56_to_epoch_ms(&c.obj[1], &back) == 0,
|
||
"TC-007 the CP56 time tag decodes");
|
||
CHECK(back == s_epoch_val, "TC-007 the time tag equals the epoch_ms source");
|
||
}
|
||
s_f_bits &= ~(1uLL << 15); /* 复归 */
|
||
iec104_link_poll(&g_lk, now + 200u);
|
||
CHECK((cap_i_count() == 2) && (cap_i(1, &c) == 0) && (c.type == 30u)
|
||
&& (c.ioa == 16u) && (c.obj[0] == 0u), "TC-007 YX16 is reset and changes again");
|
||
/* B9 IV 路径:时标源无效 → CP56 IV 位如实置位 */
|
||
s_epoch_val = UINT64_MAX;
|
||
s_f_bits |= (1uLL << 16); /* YX17(idx16)动作 */
|
||
iec104_link_poll(&g_lk, now + 300u);
|
||
CHECK((cap_i_count() == 3) && (cap_i(2, &c) == 0) && (c.type == 30u)
|
||
&& (c.ioa == 17u) && ((c.obj[3] & 0x80u) != 0u),
|
||
"TC-007 an invalid clock sets the CP56 IV bit");
|
||
s_epoch_val = 1760234567000ULL;
|
||
s_f_bits |= (1uLL << 0); /* idx0 无效位 → 不触发 */
|
||
iec104_link_poll(&g_lk, now + 400u);
|
||
CHECK(cap_i_count() == 3, "TC-007 a change while the IV bit is set triggers nothing");
|
||
}
|
||
|
||
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] = 100.4f; /* 死区 0.5 V 内(卡 6 二次值口径:0.5 %×100 V,
|
||
* 旧拟定码值死区 10 已由「数据可信」卡 6 重定) */
|
||
iec104_link_poll(&g_lk, now + 100u);
|
||
CHECK(cap_i_count() == 0, "TC-008 inside the deadband nothing is uploaded");
|
||
s_f_yc[0] = 101.25f; /* 101.25-100=1.25 > 0.5,第 1 越点 */
|
||
iec104_link_poll(&g_lk, now + 200u);
|
||
CHECK(cap_i_count() == 0, "TC-IEC104-008 first deadband crossing only arms (2-point confirm)");
|
||
iec104_link_poll(&g_lk, now + 300u); /* 第 2 个连续测点仍越 -> 确认上送 */
|
||
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) == 101.25f),
|
||
"TC-IEC104-008 second consecutive crossing uploads (Card 9 confirm)");
|
||
}
|
||
|
||
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 the k=12 window caps the outstanding frames");
|
||
feed_rd(now);
|
||
CHECK(cap_i_count() == 12, "TC-009 the 13th frame is suppressed by the k window");
|
||
feed_s(12u, now); /* 全确认 → 窗开 */
|
||
feed_rd(now);
|
||
CHECK(cap_i_count() == 13, "TC-009 sending resumes after the S acknowledgement");
|
||
}
|
||
|
||
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 after 7 received frames no S has been sent yet");
|
||
feed_rd(now); /* 第 8 个 → w 阈 */
|
||
CHECK((cap_has_s(&nr) == 1) && (nr == 8u), "TC-010 w=8 -> S with 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 no S before t2 expires");
|
||
iec104_link_poll(&g_lk, now + 10001u);
|
||
CHECK((cap_has_s(&nr) == 1) && (nr == 1u), "TC-011 S is sent once t2 expires");
|
||
}
|
||
|
||
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 no TESTFR before t3 expires");
|
||
iec104_link_poll(&g_lk, now + 20001u);
|
||
CHECK(cap_u_count(0x43u) == 1, "TC-012 TESTFR act is sent once t3 expires");
|
||
iec104_link_poll(&g_lk, now + 20001u + 15001u);
|
||
CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-012 an unanswered TESTFR hits t1 -> IDLE");
|
||
/* con 清挂起 → 不误杀 */
|
||
reset_all(now);
|
||
handshake(now);
|
||
iec104_link_poll(&g_lk, now + 20001u);
|
||
CHECK(cap_u_count(0x43u) == 1, "TC-012 after reconnect t3 fires again");
|
||
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 once con clears the pending frame t1 does not kill it by mistake");
|
||
}
|
||
|
||
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 the select phase answers actcon");
|
||
CHECK((s_f_yk_sel == 1u) && (s_f_yk_ioa == 0x600Au), "TC-013 the select phase forwards the source");
|
||
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 the execute phase answers 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 a mismatching execute phase returns 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 a YK without output hardware returns PN in the select phase");
|
||
}
|
||
|
||
/* 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 soft plates are engaged by default");
|
||
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 the YK12 select phase answers actcon");
|
||
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 1u, "TC-024 the select phase performs no action");
|
||
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 the YK12 execute phase answers actcon");
|
||
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 0u, "TC-024 the execute phase drives the soft plate to the released state");
|
||
CHECK(prot_set_selfcheck() == 0, "TC-024 set_sealed seals atomically and selfcheck passes");
|
||
CHECK(prot_set_dirty_peek() == 1u, "TC-024 the dirty flag is set for persistence");
|
||
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 the engage command executes with actcon");
|
||
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 1u, "TC-024 the soft plate reaches the engaged state");
|
||
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 an invalid DPC returns PN in the select phase");
|
||
}
|
||
|
||
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 the OC1 current setting communication address");
|
||
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 selecting a setting answers 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 executing a setting answers actcon");
|
||
CHECK(prot_set_get_f(PS_OC1_CUR) == 5.0f, "TC-014 the setting really was written (real prot_set)");
|
||
CHECK(prot_set_selfcheck() == 0, "TC-014 sealing after execute keeps selfcheck passing");
|
||
CHECK(prot_set_dirty_peek() == 1u, "TC-014 the execute phase sets dirty (B7, waiting to be persisted)");
|
||
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 an out-of-range select phase returns 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 hits a YC group");
|
||
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 hits a setting");
|
||
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 an unknown IOA answers COT14 plus 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 a local setting item (comm_addr=0) is not addressable (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 syncs the clock with an exact 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 a clock_sync failure -> 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 an unknown type answers COT12 plus 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 a wrong common address answers COT13 plus 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 in STOPPED state the data service is off");
|
||
feed_u(0x43u, now);
|
||
CHECK(cap_u_count(0x83u) == 1, "TC-018 in STOPPED state TESTFR is still answered");
|
||
}
|
||
|
||
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 a sequence error -> 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 table size and IOA layout");
|
||
s_f_yc[0] = 7.5f;
|
||
CHECK((iec104_pts_yc_read(0u, &v, &q) == 0) && (v == 7.5f) && (q == 0u),
|
||
"TC-020 a valid 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 an invalid channel gives IV");
|
||
s_f_yc_rc[2] = -2;
|
||
(void)iec104_pts_yc_read(2u, &v, &q);
|
||
CHECK(q == IEC104_QDS_NT, "TC-020 not ready gives NT");
|
||
iec104_pts_yx_read(0u, &d);
|
||
CHECK(d == IEC104_DIQ_IV, "TC-020 an invalid telemetry point gives IV");
|
||
CHECK((iec104_pts_yx_is_dp(0u) == 1u) && (iec104_pts_yx_is_dp(4u) == 0u),
|
||
"TC-020 the is_dp boundary");
|
||
s_f_bits = (1uLL << 15);
|
||
iec104_pts_yx_read(15u, &d);
|
||
CHECK(d == 1u, "TC-020 a valid telemetry point gives 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 the cyclic upload takes 2 frames");
|
||
CHECK((cap_i(0, &c) == 0) && (c.type == 13u) && (c.cot == IEC104_COT_PERIODIC)
|
||
&& (c.ioa == 0x4011u), "TC-022 the cyclic YC group uses COT=1");
|
||
CHECK((cap_i(1, &c) == 0) && (c.ioa == 0x401Du), "TC-022 the cyclic frequency point");
|
||
}
|
||
|
||
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 with k full nothing spontaneous can go out");
|
||
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 once the window opens the backlogged event is sent, none lost");
|
||
}
|
||
|
||
/* ========== 卡 9/卡 11:板端默认源(真实 L2 链:ring→phasor→freq→meas) ========== */
|
||
|
||
/* 与 App/task/adc_task.c 同型的单拍编排:压环 → phasor_run →(可选 freq_run)。
|
||
* 9 路同幅错相正弦(整周波 → 各路基波有效值 = A/√2,与相位无关,期望好算) */
|
||
static double s_ph2 = 0.0;
|
||
static void snap_beats(int n, double amp, int run_freq)
|
||
{
|
||
float f[ADC_SEM_COUNT];
|
||
int i;
|
||
int s;
|
||
|
||
for (i = 0; i < n; i++) {
|
||
for (s = 0; s < (int)ADC_SEM_TEMP; s++) {
|
||
f[s] = (float)(amp * sin(s_ph2 + (double)s * 0.35));
|
||
}
|
||
f[ADC_SEM_TEMP] = 1234.0f;
|
||
s_ph2 += 2.0 * 3.14159265358979323846 * 50.0 / 1000.0; /* 50 Hz */
|
||
(void)sample_ring_push_frame(f, (uint8_t)ADC_SEM_COUNT);
|
||
phasor_run();
|
||
if (run_freq != 0) {
|
||
freq_run();
|
||
}
|
||
}
|
||
}
|
||
/* 200 ms 发布拍(读同拍 freq 状态,adc_task v3.2 编排的镜像) */
|
||
static void snap_pub(uint32_t ms, uint32_t clip_mask)
|
||
{
|
||
meas_tick(ms, freq_valid(), freq_last(), clip_mask);
|
||
}
|
||
|
||
static void tc_pts_snap(void)
|
||
{
|
||
float v;
|
||
uint8_t q;
|
||
|
||
reset_all(1000u);
|
||
iec104_pts_bind_src(NULL); /* 板端默认源 = 真实快照链 */
|
||
(void)phasor_init(); /* 环 + 相量双区清零 */
|
||
(void)meas_init();
|
||
CHECK((iec104_pts_yc_read(0u, &v, &q) == -1)
|
||
&& (q == (uint8_t)(IEC104_QDS_IV | IEC104_QDS_NT)) && (v == 0.0f),
|
||
"TC-IEC104-025 before first publish -> IV|NT, value zero");
|
||
snap_beats(20, 100.0, 0);
|
||
snap_pub(200u, 0xE6u); /* 削顶位图同板端默认构建(cl=0xE6) */
|
||
CHECK((iec104_pts_yc_read(0u, &v, &q) == 0) && (q == 0u)
|
||
&& (fabsf(v - 70.711f) < 0.1f),
|
||
"TC-IEC104-025 UA = snapshot fundamental RMS (engineering domain)");
|
||
CHECK((iec104_pts_yc_read(3u, &v, &q) == 0) && (q == 0u),
|
||
"TC-IEC104-025 UN (bit3 not in 0xE6) stays valid");
|
||
CHECK((iec104_pts_yc_read(4u, &v, &q) == 0) && (q == 0u),
|
||
"TC-IEC104-025 IA valid");
|
||
CHECK((iec104_pts_yc_read(1u, &v, &q) == -1) && (q == IEC104_QDS_IV)
|
||
&& (v == 0.0f), "TC-IEC104-025 clipped UB: CLIPPED maps to wire IV, value zero");
|
||
CHECK((iec104_pts_yc_read(2u, &v, &q) == -1) && (q == IEC104_QDS_IV),
|
||
"TC-IEC104-025 clipped UC IV");
|
||
CHECK((iec104_pts_yc_read(5u, &v, &q) == -1) && (q == IEC104_QDS_IV),
|
||
"TC-IEC104-025 clipped IB IV");
|
||
CHECK((iec104_pts_yc_read(8u, &v, &q) == -1) && (q == IEC104_QDS_IV),
|
||
"TC-IEC104-025 freq point published but not fresh -> IV (Card 11 rule)");
|
||
}
|
||
|
||
static void tc_pts_freq(void)
|
||
{
|
||
float v;
|
||
uint8_t q;
|
||
int k;
|
||
|
||
reset_all(1000u);
|
||
iec104_pts_bind_src(NULL);
|
||
(void)phasor_init();
|
||
(void)freq_init();
|
||
(void)meas_init();
|
||
for (k = 0; k < 5; k++) { /* 100 拍:测频启动收敛需 20 拍 */
|
||
snap_beats(20, 100.0, 1);
|
||
snap_pub((uint32_t)(k + 1) * 200u, 0u);
|
||
}
|
||
CHECK(freq_valid() == 1u, "TC-IEC104-026 drive precondition: freq converged");
|
||
CHECK((iec104_pts_yc_read(8u, &v, &q) == 0) && (q == 0u)
|
||
&& (fabsf(v - 50.0f) <= 0.02f),
|
||
"TC-IEC104-026 0x401D carries freq reading within 0.02 Hz of synthetic 50.000");
|
||
for (k = 0; k < 3; k++) { /* 浮空:幅值门落下 */
|
||
snap_beats(20, 0.0, 1);
|
||
snap_pub((uint32_t)(6 + k) * 200u, 0u);
|
||
}
|
||
CHECK((freq_valid() == 0u) && (iec104_pts_yc_read(8u, &v, &q) == -1)
|
||
&& (q == IEC104_QDS_IV) && (v == 0.0f),
|
||
"TC-IEC104-026 freq_valid==0 -> IV with zeroed value (Card 11 DoD)");
|
||
}
|
||
|
||
static void tc_db_runtime(void)
|
||
{
|
||
iec104_pts_cov_t cov;
|
||
|
||
reset_all(1000u); /* 假源在场 = 每次 cov_check 一测点 */
|
||
CHECK((iec104_pts_yc_set_deadband(0u, 2.0f) == 0)
|
||
&& (iec104_pts_yc_deadband(0u) == 2.0f),
|
||
"TC-IEC104-027 runtime deadband write + readback");
|
||
CHECK((iec104_pts_yc_set_deadband(9u, 1.0f) == -1)
|
||
&& (iec104_pts_yc_set_deadband(0u, -0.1f) == -1)
|
||
&& (iec104_pts_yc_deadband(0u) == 2.0f),
|
||
"TC-IEC104-027 bad idx / negative rejected, RAM untouched");
|
||
CHECK(iec104_pts_yc_deadband(8u) == 0.5f, "TC-IEC104-027 freq point ROM default kept");
|
||
s_f_yc[0] = 100.0f;
|
||
iec104_pts_cov_check(&cov); /* 建线 rep=100(档 2.0) */
|
||
s_f_yc[0] = 101.0f; /* 越旧档 0.5、未越运行档 2.0 */
|
||
iec104_pts_cov_check(&cov);
|
||
CHECK((cov.yc_changed & 0x0001u) == 0u, "TC-IEC104-027 runtime db gates the old step");
|
||
iec104_pts_cov_check(&cov);
|
||
CHECK((cov.yc_changed & 0x0001u) == 0u, "TC-IEC104-027 2nd point under 2.0 still silent");
|
||
|
||
run_par_init(); /* 恢复工厂默认档(0.5)——卡 10 复位入口 */
|
||
iec104_pts_init();
|
||
iec104_pts_bind_src(&g_fake);
|
||
s_f_yc[0] = 100.0f;
|
||
iec104_pts_cov_check(&cov); /* 建线 */
|
||
s_f_yc[0] = 100.6f; /* d=0.6 > 0.5,第 1 越 */
|
||
iec104_pts_cov_check(&cov);
|
||
CHECK((cov.yc_changed & 0x0001u) == 0u, "TC-IEC104-027 crossing arms only (2-point)");
|
||
iec104_pts_cov_check(&cov);
|
||
CHECK((cov.yc_changed & 0x0001u) != 0u, "TC-IEC104-027 second consecutive crossing confirms");
|
||
s_f_yc[0] = 101.4f; /* 新基准 100.6 起 d=0.8 第 1 越 */
|
||
iec104_pts_cov_check(&cov);
|
||
CHECK((cov.yc_changed & 0x0001u) == 0u, "TC-IEC104-027 pullback test step 1 armed");
|
||
s_f_yc[0] = 100.8f; /* 回摆入死区 -> 撤销候选 */
|
||
iec104_pts_cov_check(&cov);
|
||
s_f_yc[0] = 101.4f; /* 重新起算:单点不报 */
|
||
iec104_pts_cov_check(&cov);
|
||
CHECK((cov.yc_changed & 0x0001u) == 0u, "TC-IEC104-027 pullback cleared pending count");
|
||
iec104_pts_cov_check(&cov);
|
||
CHECK((cov.yc_changed & 0x0001u) != 0u, "TC-IEC104-027 two fresh crossings confirm again");
|
||
|
||
/* 壳语义钉(lead 裁⑤):iec104_pts_yc_set_deadband 成功须清零该点半截确认
|
||
* 计数——若不清,下面第一拍就凑满 2 点直发,与"跨档位重新起算"矛盾 */
|
||
s_f_yc[0] = 100.0f; /* rep=101.4 起 d=1.4 > 0.5:第 1 越挂起 */
|
||
iec104_pts_cov_check(&cov);
|
||
CHECK((cov.yc_changed & 0x0001u) == 0u, "TC-IEC104-027 shell pin arms first");
|
||
iec104_pts_yc_set_deadband(0u, 0.4f);
|
||
s_f_yc[0] = 100.5f; /* d=0.9 > 0.4:计数已清则仍只是第 1 越 */
|
||
iec104_pts_cov_check(&cov);
|
||
CHECK((cov.yc_changed & 0x0001u) == 0u, "TC-IEC104-027 shell clears pending count");
|
||
iec104_pts_cov_check(&cov); /* 新档连续第 2 越 → 确认 */
|
||
CHECK((cov.yc_changed & 0x0001u) != 0u, "TC-IEC104-027 reconfirms after shell clear");
|
||
}
|
||
|
||
static void tc_cov_qds_seq(void)
|
||
{
|
||
iec104_pts_cov_t cov;
|
||
|
||
reset_all(1000u); /* 假源段:品质翻转 */
|
||
s_f_yc[1] = 50.0f;
|
||
iec104_pts_cov_check(&cov);
|
||
s_f_yc_rc[1] = -1;
|
||
iec104_pts_cov_check(&cov);
|
||
CHECK((cov.yc_changed & 0x0002u) != 0u, "TC-IEC104-028 valid->IV reported at once");
|
||
iec104_pts_cov_check(&cov);
|
||
CHECK((cov.yc_changed & 0x0002u) == 0u, "TC-IEC104-028 IV steady state not repeated");
|
||
s_f_yc_rc[1] = 0;
|
||
iec104_pts_cov_check(&cov);
|
||
CHECK((cov.yc_changed & 0x0002u) != 0u, "TC-IEC104-028 IV->valid recovery reported at once");
|
||
|
||
iec104_pts_init(); /* 板端段:seq 去重 */
|
||
iec104_pts_bind_src(NULL);
|
||
(void)phasor_init();
|
||
(void)meas_init();
|
||
snap_beats(20, 100.0, 0);
|
||
snap_pub(200u, 0u); /* seq=1 */
|
||
iec104_pts_cov_check(&cov); /* 建线帧 */
|
||
CHECK(cov.yc_changed == 0u, "TC-IEC104-028 baseline frame silent");
|
||
snap_beats(20, 200.0, 0);
|
||
snap_pub(400u, 0u); /* seq=2:第 1 越点 */
|
||
iec104_pts_cov_check(&cov);
|
||
CHECK((cov.yc_changed & 0x0001u) == 0u, "TC-IEC104-028 one new frame arms only");
|
||
iec104_pts_cov_check(&cov);
|
||
iec104_pts_cov_check(&cov); /* 无换帧:不得计为第 2 点 */
|
||
CHECK((cov.yc_changed & 0x0001u) == 0u, "TC-IEC104-028 same frame never counts twice");
|
||
snap_beats(20, 200.0, 0);
|
||
snap_pub(600u, 0u); /* seq=3:连续第 2 越点 */
|
||
iec104_pts_cov_check(&cov);
|
||
CHECK((cov.yc_changed & 0x0001u) != 0u, "TC-IEC104-028 second new frame confirms");
|
||
snap_pub(800u, 0xE6u); /* 换帧仅变品质(削顶注入) */
|
||
iec104_pts_cov_check(&cov);
|
||
CHECK((cov.yc_changed & 0x00E6u) == 0x00E6u,
|
||
"TC-IEC104-028 clipped flip per frame reported as a set");
|
||
}
|
||
|
||
/* 卡 10:0x8020~0x8058 读写路由(板端默认源,run_par 表先 / prot_set 后) */
|
||
static void tc_pts_rpar_route(void)
|
||
{
|
||
float v;
|
||
|
||
reset_all(1000u);
|
||
iec104_pts_bind_src(NULL);
|
||
CHECK((iec104_pts_set_read(0x8030u, &v) == 0) && (fabsf(v - 0.5f) < 1e-4f),
|
||
"TC-IEC104-029 0x8030 deadband UA reads factory default");
|
||
CHECK((iec104_pts_set_read(0x8038u, &v) == 0) && (fabsf(v - 0.5f) < 1e-4f),
|
||
"TC-IEC104-029 0x8038 deadband freq reads factory 0.5 Hz");
|
||
/* 0x8027~0x802A = prot_set 告警段(欠压默认 20 V)——run_par 精确匹配
|
||
* 永不覆盖;路由顺序的直接证据 */
|
||
CHECK((iec104_pts_set_read(0x8027u, &v) == 0) && (fabsf(v - 20.0f) < 0.01f),
|
||
"TC-IEC104-029 0x8027 still served by prot_set table");
|
||
CHECK(iec104_pts_set_read(0x8039u, &v) == -1, "TC-IEC104-029 gap address unknown");
|
||
/* C_SE 两段式:选择段校验不提交,执行段落真源 */
|
||
CHECK((iec104_pts_set_write(0x8031u, 0.7f, 1u) == 0)
|
||
&& (fabsf(iec104_pts_yc_deadband(1u) - 0.5f) < 1e-4f),
|
||
"TC-IEC104-029 select phase validates without commit");
|
||
CHECK((iec104_pts_set_write(0x8031u, 0.7f, 0u) == 0)
|
||
&& (fabsf(iec104_pts_yc_deadband(1u) - 0.7f) < 1e-4f)
|
||
&& (run_par_dirty_peek() != 0u),
|
||
"TC-IEC104-029 execute commits and marks dirty");
|
||
CHECK(iec104_pts_set_write(0x8031u, 51.0f, 1u) == -1,
|
||
"TC-IEC104-029 over-range rejected in select");
|
||
/* k/b 覆盖直通 adc_cal(UA 合法带 = ROM x[0.5,2.0] = [76.29,305.17]) */
|
||
CHECK((iec104_pts_set_write(0x8040u, 300.0f, 0u) == 0)
|
||
&& (fabsf(adc_cal_k_uv(ADC_SEM_UA) - 300.0f) < 0.01f),
|
||
"TC-IEC104-029 k override lands in adc_cal");
|
||
CHECK(iec104_pts_set_write(0x8040u, 400.0f, 0u) == -1,
|
||
"TC-IEC104-029 k band reject (via adc_cal)");
|
||
CHECK((iec104_pts_set_write(0x8048u, -5.0f, 0u) == 0)
|
||
&& (fabsf(adc_cal_b_codes(ADC_SEM_UA) + 5.0f) < 1e-3f)
|
||
&& (fabsf(adc_cal_k_uv(ADC_SEM_UA) - 300.0f) < 0.01f),
|
||
"TC-IEC104-029 b single-side write keeps k (RMW)");
|
||
CHECK((iec104_pts_set_read(0x8048u, &v) == 0) && (fabsf(v + 5.0f) < 1e-3f),
|
||
"TC-IEC104-029 b echo via run_par read path");
|
||
run_par_init(); /* 收尾:把 k/b 脏与副本复位留给后续 TC 的干净出厂态 */
|
||
}
|
||
|
||
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();
|
||
tc_pts_snap();
|
||
tc_pts_freq();
|
||
tc_db_runtime();
|
||
tc_cov_qds_seq();
|
||
tc_pts_rpar_route();
|
||
printf("=== IEC104 Tests: %d/%d PASSED (failed=%d) ===\n",
|
||
s_passed, s_total, s_failed);
|
||
return (s_failed == 0) ? 0 : 1;
|
||
}
|