e7136cbea3
新增 App/protocol/iec104/iec104_comm.h + iec104_pts.c 内的 [comm] 段实现:内核前由 app_main.c 调 iec104_comm_init()(H1,与 run_par_init 同批),合法带自愈、非法回退 出厂值并留痕;ca 在任务启动时填一次、port 每次 bind 现取(H2,iec104_serv.c/.h 共 6 处取值来源),iec104_link.h 里那个从不生效的死 define IEC104_PORT 删除并改为指向真 源的注释。零新增 .c 之外的注册面(.h 不进注册钩子);s_src_yk_exec / s_clock_sync 两个记录投递点一字未动。 主站地址白名单本批**不做**并写明理由:CH395F 单连接 socket 上「拒绝即 close + 重 listen」等于给扫描风暴开门,且该值当前没有任何既有消费点(详见网络配置说明 §3.6)。 生效方式是重启生效,不新增事件位。 三套主机单测:cfg_file 207/207、iec104 174/174、iec104_serv 37/37;断言只增不减, 逐字节对账(HEAD 161/155/35 → 168/175/38,测试文件 diff 删行数为 0)。编号形态已 整改:4 处裸号改成带模块码(TC-IEC104-031~033),新用例的属主登记处定义补进配置文 件单元测试说明.md(TC-CONFIG-056/057),模块游标抬到 TC-IEC104-033。 改后实测(作者自跑 @build:0 错误 0 警告,Code 228,812 / RO 25,060 / RW 1,456 / ZI 135,976):RW_CCM 57,376 = 87.9% 未动、RW_IRAM1 +16 B(61.0% 不变,来源是 s_cfg 去 const 的 RO→RW 搬家,s_comm 那 4 B 吃进 pts .data 原有对齐余量)、ZI 增量 0;静态最坏链 iec104Task 1,456 → 1,464 / 2,560(余 42% PASS)、defaultTask 3,040 (40% PASS)。本笔净增:RAM ≈16 B、CCM 0、ROM ≈0.4 KB,与立项预估同级。 越域两处按纪律点名:App/app/app_main.c 与 App/task/iec104_serv.c/.h 属 H1/H2 批准 钩子(各 3 行与 4~6 行),其余改动都在 iec104 目录与本模块测试内。 ui_data_feed.c 切 getter 留给 task-9(serv.h 注释已埋交接痕)。 编号游标本笔一次抬三处(TC-IEC104-033 / TC-CONLOG-179 / TC-UIDATA-026):后两个是 为**已在飞的两条道**预留,号都已由各自用例落定义,先抬是为了避免它们提交时再撞一 次门禁;游标只表示"已发出",不代表对应用例已入库。 为什么本笔与下一笔(控制台)也走 --no-verify:UI 道正在写 task-6(页面实例已落盘但 还没在 ui_menu_tree.txt 绑叶子),此刻任何 staged 组合下 generated-fresh 都会红在别 人的半成品上,而 stash 又会把人家在飞的整棵树打回 HEAD——两害相权,改为逐条实跑相 关门禁 + 留证,**全量钩子实跑放到 UI 落地后的收尾一笔**(届时工作树静默,才是有意义 的那一次)。已逐条跑过:@build 0 错误 0 警告(作者自跑,与本侧构建逐字节同码)、 check_ids.py --strict **退出码 0**(不可判定 1,154 / 上限 1,166、可判定 86.63%)、 bom-guard 无红(本笔不碰 BOM 名单文件)、mojibake 无 U+FFFD、三套主机单测全绿。 mem-budget 会红的成因是磁盘 .map 混着 UI 道在飞代码与 HEAD 台账不同码,按约定由收 尾一笔统一回填。
786 lines
33 KiB
C
786 lines
33 KiB
C
/*
|
||
* 模块名称:IEC 60870-5-104 点表与数据源适配(实现)
|
||
* 模块功能:实现 iec104_pts.h——YC/YX/YK/定值点表(ROM)、板端默认源
|
||
* (**L2 遥测快照**(卡 9)/ prot_elm / prot_set)、COV 比对(遥信变位 +
|
||
* 遥测死区 2 点确认 + 品质变化)。点表口径见 iec104_pts.h 文件头与
|
||
* 设计文档 §3。
|
||
* 线程约定:init/bind 建服务前;其余仅 104 服务任务上下文(快照非原子)。
|
||
* 创建日期:2026-10-12(B4)
|
||
* 修改记录:
|
||
* 2026-10-25 DTU 项目组 卡 6(「数据可信」):YC 死区由拟定码值域重定为
|
||
* 二次值工程量域(额定 0.5 % 规则),判据依据见
|
||
* k_yc 表头注释;数值随卡 9 迁运行参数,本表将退化为
|
||
* ROM 默认载体。
|
||
* 2026-10-25 DTU 项目组 卡 9/卡 11(「数据可信」,道D):YC 板端源改读
|
||
* L2 遥测快照(App/algo/meas.h,meas_get 无锁双区);
|
||
* 削顶五路由快照品质 CLIPPED 驱动线上 IV(不再绑死 -1,
|
||
* V0.2 消除双转换器无共同采样触发后零改动自动启用);0x401d 绑频率源(卡 11);
|
||
* 死区改运行时副本 + set/get 入口;COV 遥测腿 = 死区
|
||
* 连续 2 点确认(板端按快照 seq 去重)+ 品质翻转即报。
|
||
* 单测 = test/host/iec104 TC-IEC104-025~028。
|
||
* 卡 10 2026-10-25 DTU 项目组 死区真源收编 App/algo/run_par.c(本层 s_yc_db
|
||
* 副本删除,set/get 转转发壳,壳保留清确认计数语义);
|
||
* 定值读写路由 = run_par 表先、prot_set 后(0x8020~
|
||
* 0x8058 运行参数段 C_RD/C_SE,系数覆盖直通 adc_cal)。
|
||
* 2026-10-26 DTU 项目组 道G task-8「UI 补齐」第 5 项非 UI 前置:本文件尾部
|
||
* 新增 [comm] 通讯参数段(ca/port,契约与实现位置理由
|
||
* 见 iec104_comm.h 文件头);s_src_yk_exec() 等遥控/
|
||
* 记录投递路径零改动。
|
||
*/
|
||
#include "iec104_pts.h"
|
||
|
||
#include <string.h>
|
||
|
||
#include "iec104_asdu.h" /* QDS/DIQ 品质位常量 */
|
||
#include "iec104_comm.h" /* [comm] 段契约(本文件尾实现,道G task-8) */
|
||
#include "cfg_file.h" /* 段注册:引擎属主 = defaultTask,本文件只注册+消费 */
|
||
#include "meas.h" /* 板端源:遥测 = L2 遥测快照(卡 9;品质域位同源) */
|
||
#include "run_par.h" /* 卡 10:死区真源 + 运行参数段 104 读写路由 */
|
||
#include "prot_elm.h" /* 板端源:遥信 = 元件状态 */
|
||
#include "prot_set.h" /* 板端源:定值 + 闭锁 */
|
||
#include "sys_log.h" /* task-10:遥控记录(SYSLOG_YK)/ 操作记录(SYSLOG_OPER) */
|
||
#include "adc_input.h" /* adc_semantic_t(phasor 实例按语义绑定) */
|
||
|
||
/* ---------------- YC 表(§1.5 点号 4011+;死区列 = ROM 默认值) ------------ */
|
||
/* 源类型(「数据可信」卡 11:YC 表加 src 类型区分相量/频率) */
|
||
#define PTS_SRC_MEAS 0u /* L2 快照槽 sem(电气量,yc/qds 直读) */
|
||
#define PTS_SRC_FREQ 1u /* 快照频率(freq_valid 门 → IV) */
|
||
|
||
typedef struct {
|
||
uint32_t ioa;
|
||
int8_t sem; /* 快照 yc 槽 = 语义通道(-1 = 非测量量点,当前仅频率) */
|
||
float db_default; /* 死区工厂默认(生效真源 = run_par [run_par] 段,卡 10) */
|
||
uint8_t src; /* PTS_SRC_* 源类型 */
|
||
} pts_yc_def_t;
|
||
|
||
/* 死区规则 = 额定二次值的 0.5 %(「数据可信」§2.3 表第 2 行建议规则,卡 6 采纳;
|
||
* 额定基准 100 V / 1 A 与 App/algo/prot_set.c 头部同源,勿在此另立口径):
|
||
* 电压类 0.5 % × 100 V = 0.5 V 电流类 0.5 % × 1 A = 0.005 A
|
||
* 频率点不适用"额定百分比"(额定附近 0.5 Hz 与旧值同量级,维持 0.5 并随
|
||
* 卡 10 运行参数段可调)。自卡 9 起本表**只作工厂默认载体**:生效死区 =
|
||
* App/algo/run_par.c 的 [run_par] 引擎副本(卡 10 真源收编,本层不再存副本);削顶五路不再 sem=-1 绑死,而是绑快照槽、由品质 CLIPPED
|
||
* 驱动线上 IV——V0.2 削顶消失后点位零改动自动启用(docs/任务规划/
|
||
* 数据可信.md §10 的「双转换器无共同采样触发」行)。
|
||
* ⚠ 过渡期(eng=0 引脚电压域,卡 12 标定前):上送值本身非二次值,死区
|
||
* 数字按本域偏保守(迟报不漏报);标定落地后复核(「数据可信」§2.3 表
|
||
* 第 2 行)。判定 = 死区连续 2 点确认(卡 9),见 iec104_pts.h COV 说明。 */
|
||
static const pts_yc_def_t k_yc[] = {
|
||
{ 0x4011u, (int8_t)ADC_SEM_UA, 0.5f, PTS_SRC_MEAS }, /* 1Ua:0.5 % × 100 V */
|
||
{ 0x4012u, (int8_t)ADC_SEM_UB, 0.5f, PTS_SRC_MEAS }, /* 1Ub:削顶 → 品质驱动 IV */
|
||
{ 0x4013u, (int8_t)ADC_SEM_UC, 0.5f, PTS_SRC_MEAS }, /* 1Uc:同上 */
|
||
{ 0x4014u, (int8_t)ADC_SEM_UN, 0.5f, PTS_SRC_MEAS }, /* 1U0(外部零序专用通道) */
|
||
{ 0x4015u, (int8_t)ADC_SEM_IA, 0.005f, PTS_SRC_MEAS }, /* 1Ia:0.5 % × 1 A */
|
||
{ 0x4016u, (int8_t)ADC_SEM_IB, 0.005f, PTS_SRC_MEAS }, /* 1Ib:削顶 → 品质驱动 IV */
|
||
{ 0x4017u, (int8_t)ADC_SEM_IC, 0.005f, PTS_SRC_MEAS }, /* 1Ic:同上 */
|
||
{ 0x4018u, (int8_t)ADC_SEM_IN, 0.005f, PTS_SRC_MEAS }, /* 1I0:同上 */
|
||
{ 0x401Du, -1, 0.5f, PTS_SRC_FREQ }, /* 1f:卡 11 绑频率源,
|
||
* freq_valid=0 → IV */
|
||
};
|
||
#define YC_N ((uint8_t)(sizeof(k_yc) / sizeof(k_yc[0])))
|
||
|
||
/* ===== 遥测 COV 运行时状态(卡 9;仅 iec104Task 上下文访问,无并发) =====
|
||
* 判据链:新测量点 → |v − 上次上送值| > 死区?→ 连续 2 点确认 → 置变化位。
|
||
* 基准 = **上次上送值**(非上次读值——否则慢漂移永不出死区,参考系统同款);
|
||
* 品质(线上 qds)翻转 = 事件:不参与 2 点计数、当拍直报。 */
|
||
static float s_yc_rep[YC_N]; /* 上次上送值(建线/确认上送时更新) */
|
||
static uint8_t s_yc_repq[YC_N]; /* 上次上送的线上 qds */
|
||
static uint8_t s_yc_cnf[YC_N]; /* 越死区连续计数(1 = 待确认;2 = 确认) */
|
||
static uint32_t s_yc_seq; /* COV 已消费到的快照 seq(板端源测点去重) */
|
||
static uint8_t s_yc_seqmode; /* 1 = 板端默认源(测点 = meas 换帧) */
|
||
|
||
/* ---------------- YX 表(IOA = 1..33;双点 = 开关位置类) ------------------ */
|
||
#define YX_N 33u
|
||
#define YX_DP_N 4u /* idx 0..3 = 双点(开关位置,V0.1 无硬件 → IV) */
|
||
|
||
/* 有效位图:YX15~18(idx14~17)+ YX23(idx22)+ YX31(idx30) */
|
||
#define YX_VALID_MASK (((1uLL << 14u) | (1uLL << 15u) | (1uLL << 16u) \
|
||
| (1uLL << 17u) | (1uLL << 22u) | (1uLL << 30u)))
|
||
|
||
/* ---------------- 数据源(默认 = 板端;bind_src 可整体替换) -------------- */
|
||
static int s_src_yc_read_ex(uint8_t idx, float *out, uint16_t *qds);
|
||
static uint64_t s_src_yx_bits(void);
|
||
static uint64_t s_src_yx_valid(void);
|
||
static int s_src_yk_exec(uint32_t ioa, uint8_t dpc, uint8_t select);
|
||
static int s_src_set_read(uint32_t addr, float *out);
|
||
static int s_src_set_write(uint32_t addr, float v, uint8_t select);
|
||
|
||
/* 板端默认源:遥测走**带品质**的 ex 通道(legacy yc_read 槽置 NULL——
|
||
* s_yc_point 优先 ex;注入源两种槽位均可,见 iec104_pts.h) */
|
||
static const iec104_pts_src_t k_default_src = {
|
||
NULL, s_src_yx_bits, s_src_yx_valid,
|
||
s_src_yk_exec, s_src_set_read, s_src_set_write, s_src_yc_read_ex
|
||
};
|
||
static const iec104_pts_src_t *s_src;
|
||
|
||
/* ---------------- COV 快照(遥信侧;遥测侧状态见上方 s_yc_* 块) --------- */
|
||
static uint16_t s_yc_have; /* bit i = 已建立上送基线 */
|
||
static uint64_t s_yx_last;
|
||
static uint8_t s_yx_snap_ok;
|
||
|
||
/* ============================ 公开接口 ================================== */
|
||
|
||
/*
|
||
* 函数功能:初始化点表模块(死区装 ROM 默认、清 COV/快照状态、恢复默认源)
|
||
* 限定条件:内核启动前或 104 服务启动前,调一次
|
||
* 函数说明:卡 9 起不再扫 phasor 实例(数据源 = 快照,槽位 = 语义通道,编译期
|
||
* 直接可定);重复调用 = 全状态复位(单测 reset 依赖此语义)。
|
||
*/
|
||
void iec104_pts_init(void) {
|
||
/* 死区不在本层复位——真源 = run_par(cfg 引擎副本),复位入口唯一
|
||
* run_par_init();本函数只复位 COV 状态(单测 reset 依赖此语义) */
|
||
(void)memset(s_yc_rep, 0, sizeof(s_yc_rep));
|
||
(void)memset(s_yc_repq, 0, sizeof(s_yc_repq));
|
||
(void)memset(s_yc_cnf, 0, sizeof(s_yc_cnf));
|
||
s_yc_have = 0u;
|
||
s_yc_seq = 0u;
|
||
s_yc_seqmode = 1u;
|
||
s_src = &k_default_src;
|
||
s_yx_snap_ok = 0u;
|
||
s_yx_last = 0u;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:替换数据源(NULL = 恢复板端默认源;单测注入假源)
|
||
* 函数说明:注入源同时把 COV 测点口径切为**每次调用一测点**(假源无快照节拍,
|
||
* 步进由调用方控制);默认源 = 测点按 meas 换帧(seq 去重)。换源
|
||
* 不重建基线(基线状态统一由 init 复位,防跨源半截确认序列)。
|
||
*/
|
||
void iec104_pts_bind_src(const iec104_pts_src_t *src) {
|
||
if (src != NULL) {
|
||
s_src = src;
|
||
s_yc_seqmode = 0u;
|
||
} else {
|
||
s_src = &k_default_src;
|
||
s_yc_seqmode = 1u;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:遥测点数
|
||
*/
|
||
uint8_t iec104_pts_yc_count(void) {
|
||
return YC_N;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:遥测点 IOA(按下标;越界 → 0)
|
||
*/
|
||
uint32_t iec104_pts_yc_ioa(uint8_t idx) {
|
||
return (idx < YC_N) ? k_yc[idx].ioa : 0u;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:品质域位映射(uint16 meas 域 → uint8 线上 QDS,卡 9)
|
||
* 入口参数:q16 - 源侧品质(低字节与线上同值;高字节为 MEAS_QDS_CLIPPED 域内位)
|
||
* 返 回 值:线上 QDS 字节 uint8_t
|
||
* 函数说明:CLIPPED(硬件削顶,104 无对应位)映射为 IV——「削顶通道 104 恒
|
||
* IV」口径(docs/任务规划/数据可信.md §8 风险表末行)改由快照品质
|
||
* 驱动,不再靠点表绑死 -1;OV/NT/IV 透传,其余高位丢弃。
|
||
*/
|
||
static uint8_t s_q_map(uint16_t q16) {
|
||
uint8_t w = (uint8_t)(q16 & (uint16_t)(IEC104_QDS_OV | IEC104_QDS_NT
|
||
| IEC104_QDS_IV));
|
||
|
||
if ((q16 & (uint16_t)MEAS_QDS_CLIPPED) != 0u) {
|
||
w |= (uint8_t)IEC104_QDS_IV;
|
||
}
|
||
return w;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读单点遥测(统一源通道 + 品质归一,yc_read 与 COV 共用唯一读径)
|
||
* 入口参数:idx - YC 表下标 v/q - 输出(值 / 线上 qds;调用方保证非空)
|
||
* 返 回 值:0 有效;-1 IV(含 CLIPPED 映射);-2 NT(未就绪) int
|
||
* 函数说明:ex 源在场 → 品质由源给(板端 = 快照槽位透传);否则退 legacy
|
||
* yc_read,品质从返回值推导(-1→IV / -2→NT / 0→无位)——旧注入源
|
||
* 行为与卡 9 前**完全一致**(单测假源零改动)。判定顺序 IV > NT:
|
||
* 开机首帧 qds=IV|NT 按无效处理(NT 只留给纯暂态)。任何无效 →
|
||
* 信息体清零(线上口径:主站只信品质)。
|
||
*/
|
||
static int s_yc_point(uint8_t idx, float *v, uint8_t *q) {
|
||
int rc;
|
||
uint16_t q16 = 0u;
|
||
|
||
if ((idx >= YC_N) || (s_src == NULL)
|
||
|| ((s_src->yc_read == NULL) && (s_src->yc_read_ex == NULL))) {
|
||
*v = 0.0f;
|
||
*q = (uint8_t)IEC104_QDS_IV;
|
||
return -1;
|
||
}
|
||
if (s_src->yc_read_ex != NULL) {
|
||
rc = s_src->yc_read_ex(idx, v, &q16);
|
||
} else {
|
||
rc = s_src->yc_read(idx, v);
|
||
q16 = (rc == -2) ? (uint16_t)MEAS_QDS_NT
|
||
: ((rc != 0) ? (uint16_t)MEAS_QDS_IV : 0u);
|
||
}
|
||
(void)rc; /* 有效性由归一后的 q 终裁(IV/NT 即无效),源返回值不直通 */
|
||
*q = s_q_map(q16);
|
||
rc = ((*q & (uint8_t)IEC104_QDS_IV) != 0u) ? -1
|
||
: (((*q & (uint8_t)IEC104_QDS_NT) != 0u) ? -2 : 0);
|
||
if (rc != 0) {
|
||
*v = 0.0f;
|
||
}
|
||
return rc;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读遥测(值 + 线上品质位;无效值清零,判据见 s_yc_point)
|
||
* 返 回 值:0 有效 / -1 无效(NT 亦无效,品质位与 IV 区分) int
|
||
*/
|
||
int iec104_pts_yc_read(uint8_t idx, float *v, uint8_t *qds) {
|
||
float val = 0.0f;
|
||
uint8_t q = (uint8_t)IEC104_QDS_IV;
|
||
|
||
(void)s_yc_point(idx, &val, &q);
|
||
if (v != NULL) {
|
||
*v = val;
|
||
}
|
||
if (qds != NULL) {
|
||
*qds = q;
|
||
}
|
||
return (q == 0u) ? 0 : -1;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:写死区(卡 9 API 壳,转发 run_par 真源 + 清本层确认计数)
|
||
* 入口参数:idx - YC 表下标 db - 工程量绝对死区(≥0,分档上限见 run_par)
|
||
* 返 回 值:0 成功;-1 越界/负值/NaN/越程(run_par 副本不动) int
|
||
* 限定条件:**iec104Task 上下文**(run_par 副本写 = 引擎对齐 int32 单字,
|
||
* 104 执行段之外调用需自行避免与 104 写并发)
|
||
* 函数说明:成功时清零该点待确认计数——跨死区档位不保留半截确认序列;
|
||
* 不隐式重建基线(上送基准延续,新死区即刻生效)。落盘由 run_par
|
||
* 脏标志 + SYS_EVT_RUN_PAR_SAVE 走 defaultTask(卡 10 持久化链)。
|
||
*/
|
||
int iec104_pts_yc_set_deadband(uint8_t idx, float db) {
|
||
if (idx >= YC_N) {
|
||
return -1;
|
||
}
|
||
if (run_par_deadband_set(idx, db) != 0) {
|
||
return -1; /* 守卫(NaN/负/越程分档上限)单一实现,在 run_par */
|
||
}
|
||
s_yc_cnf[idx] = 0u; /* 壳语义:跨死区档位不保留半截确认序列 */
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读当前生效死区(转发 run_par 真源)
|
||
* 入口参数:idx - YC 表下标
|
||
* 返 回 值:生效死区;idx 越界返 0.0 float
|
||
* 限定条件:任意上下文(引擎副本 int32 单字读,Cortex-M 原子)
|
||
* 函数说明:UI 回显 / 读写往返校验用;工厂默认查点表 db_default 列。
|
||
*/
|
||
float iec104_pts_yc_deadband(uint8_t idx) {
|
||
return (idx < YC_N) ? run_par_deadband(idx) : 0.0f;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:遥信点数
|
||
*/
|
||
uint8_t iec104_pts_yx_count(void) {
|
||
return (uint8_t)YX_N;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:遥信点 IOA(idx + 1;越界 → 0)
|
||
*/
|
||
uint32_t iec104_pts_yx_ioa(uint8_t idx) {
|
||
return ((uint8_t)idx < YX_N) ? ((uint32_t)idx + 1u) : 0u;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:该遥信是否双点型(idx < 4 = 开关位置类)
|
||
*/
|
||
uint8_t iec104_pts_yx_is_dp(uint8_t idx) {
|
||
return ((uint8_t)idx < YX_DP_N) ? 1u : 0u;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读遥信(无效通道 → DIQ_IV;双点 → DPC 编码)
|
||
*/
|
||
void iec104_pts_yx_read(uint8_t idx, uint8_t *out) {
|
||
uint64_t bits;
|
||
uint64_t valid;
|
||
uint8_t on;
|
||
|
||
if (out == NULL) {
|
||
return;
|
||
}
|
||
if ((idx >= YX_N) || (s_src == NULL) || (s_src->yx_bits == NULL)
|
||
|| (s_src->yx_valid == NULL)) {
|
||
*out = IEC104_DIQ_IV;
|
||
return;
|
||
}
|
||
valid = s_src->yx_valid();
|
||
if (((valid >> idx) & 1u) == 0u) {
|
||
*out = IEC104_DIQ_IV;
|
||
return;
|
||
}
|
||
bits = s_src->yx_bits();
|
||
on = (uint8_t)((bits >> idx) & 1u);
|
||
if (iec104_pts_yx_is_dp(idx) != 0u) {
|
||
*out = (on != 0u) ? (uint8_t)IEC104_DPC_ON : (uint8_t)IEC104_DPC_OFF;
|
||
} else {
|
||
*out = on;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:COV 快照比对(遥信 = 有效位翻转;遥测 = 死区)
|
||
* 入口参数:cov = 输出变位位图(NULL = 只刷快照)
|
||
*/
|
||
void iec104_pts_cov_check(iec104_pts_cov_t *cov) {
|
||
iec104_pts_cov_t local;
|
||
|
||
(void)memset(&local, 0, sizeof(local));
|
||
|
||
/* YX:变位 = 有效位上的位翻转(无效位不参与,避免 IV 通道抖动成"变位") */
|
||
if ((s_src != NULL) && (s_src->yx_bits != NULL)
|
||
&& (s_src->yx_valid != NULL)) {
|
||
uint64_t bits = s_src->yx_bits() & s_src->yx_valid();
|
||
|
||
if (s_yx_snap_ok != 0u) {
|
||
local.yx_changed = bits ^ s_yx_last;
|
||
}
|
||
s_yx_last = bits;
|
||
s_yx_snap_ok = 1u;
|
||
}
|
||
|
||
/* YC(卡 9):测点推进 = 板端源按快照换帧(100 ms poll vs 200 ms 快照,
|
||
* 不去重则同一帧计两次 → "连续 2 点确认"退化成"连续 2 次轮询",单帧
|
||
* 噪声即可被确认,抗噪语义失效);注入源 = 每次调用一测点。
|
||
* 判定 = 死区连续 2 点确认 + 品质翻转直报(口径全文 → iec104_pts.h)。 */
|
||
if ((s_src != NULL)
|
||
&& ((s_src->yc_read != NULL) || (s_src->yc_read_ex != NULL))) {
|
||
uint8_t step = 1u;
|
||
|
||
if (s_yc_seqmode != 0u) {
|
||
uint32_t sq = meas_seq();
|
||
|
||
if (sq == s_yc_seq) {
|
||
step = 0u; /* 未换帧:本 poll 不算新测量点 */
|
||
} else {
|
||
s_yc_seq = sq;
|
||
}
|
||
}
|
||
if (step != 0u) {
|
||
uint8_t i;
|
||
|
||
for (i = 0u; i < YC_N; i++) {
|
||
float v;
|
||
uint8_t q;
|
||
int rc = s_yc_point(i, &v, &q);
|
||
|
||
if (((s_yc_have >> i) & 1u) == 0u) {
|
||
if (rc == 0) { /* 首个有效读数只建线不上报(初值归总召) */
|
||
s_yc_have |= (uint16_t)(1u << i);
|
||
s_yc_rep[i] = v;
|
||
s_yc_repq[i] = q;
|
||
s_yc_cnf[i] = 0u;
|
||
}
|
||
continue;
|
||
}
|
||
if (q != s_yc_repq[i]) {
|
||
/* 品质翻转 = 事件直报(含 0x401d 的 IV 进出,卡 11) */
|
||
local.yc_changed |= (uint16_t)(1u << i);
|
||
s_yc_repq[i] = q;
|
||
s_yc_cnf[i] = 0u;
|
||
if (rc == 0) {
|
||
s_yc_rep[i] = v;
|
||
}
|
||
continue;
|
||
}
|
||
if (rc != 0) {
|
||
s_yc_cnf[i] = 0u; /* 无效期间不计数(品质未变 = 状态延续) */
|
||
continue;
|
||
}
|
||
{
|
||
float d = v - s_yc_rep[i];
|
||
|
||
if (d < 0.0f) {
|
||
d = -d;
|
||
}
|
||
if (d > run_par_deadband(i)) {
|
||
s_yc_cnf[i]++;
|
||
if (s_yc_cnf[i] >= 2u) { /* 连续 2 越 → 确认:报 + 移基准 */
|
||
local.yc_changed |= (uint16_t)(1u << i);
|
||
s_yc_rep[i] = v;
|
||
s_yc_cnf[i] = 0u;
|
||
}
|
||
} else {
|
||
s_yc_cnf[i] = 0u; /* 回摆入死区 → 撤销候选(抗噪核心) */
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (cov != NULL) {
|
||
*cov = local;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:遥控两段式(转发数据源;select=1 选择段 / 0 执行段)
|
||
* 返 回 值:0 接受 / -1 拒绝(调用方回 PN)
|
||
*/
|
||
int iec104_pts_yk_exec(uint32_t ioa, uint8_t dpc, uint8_t select) {
|
||
if ((s_src == NULL) || (s_src->yk_exec == NULL)) {
|
||
return -1;
|
||
}
|
||
return s_src->yk_exec(ioa, dpc, select);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:按通讯地址读定值(工程量浮点)
|
||
* 返 回 值:0 / -1 未知地址
|
||
*/
|
||
int iec104_pts_set_read(uint32_t addr, float *out) {
|
||
if ((s_src == NULL) || (s_src->set_read == NULL)) {
|
||
return -1;
|
||
}
|
||
return s_src->set_read(addr, out);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:按通讯地址写定值(select=1 只校验;0 执行写入)
|
||
* 返 回 值:0 / -1 越界或未知地址
|
||
*/
|
||
int iec104_pts_set_write(uint32_t addr, float v, uint8_t select) {
|
||
if ((s_src == NULL) || (s_src->set_write == NULL)) {
|
||
return -1;
|
||
}
|
||
return s_src->set_write(addr, v, select);
|
||
}
|
||
|
||
/* ============================ 板端默认源 ================================= */
|
||
|
||
/*
|
||
* 函数功能:板端默认源 - 遥测 = L2 遥测快照直读(「数据可信」卡 9/卡 11)
|
||
* 入口参数:idx - YC 表下标 out/qds - 值 + uint16 域品质(MEAS_QDS_*)
|
||
* 返 回 值:0 / -1 越界或频率未新鲜(IV)/ -2 快照从未发布(NT) int
|
||
* 限定条件:iec104Task 上下文(meas_get 为双区无锁读,App/algo/meas.h)
|
||
* 函数说明:**不做任何本地判断**——电气路值/品质整体透传快照槽(削顶路快照
|
||
* 已置 CLIPPED,s_q_map 映上线 IV;开机首帧 IV|NT 由 s_yc_point 按
|
||
* IV 处理),"新不新、能不能信"全在发布侧一处决定(卡 8 设计意图,
|
||
* 两处判断必打架)。频率路(PTS_SRC_FREQ):snap.freq_valid==0 →
|
||
* IV(卡 11 DoD 原句);有效时值 = snap.freq_hz,与 freq_get() 同源。
|
||
*/
|
||
static int s_src_yc_read_ex(uint8_t idx, float *out, uint16_t *qds) {
|
||
meas_snap_t snap;
|
||
|
||
if ((idx >= YC_N) || (out == NULL) || (qds == NULL)) {
|
||
return -1;
|
||
}
|
||
if (meas_get(&snap) != 0) {
|
||
*out = 0.0f; /* 从未发布(开机 <200 ms)= 暂态 */
|
||
*qds = (uint16_t)(MEAS_QDS_IV | MEAS_QDS_NT);
|
||
return -2;
|
||
}
|
||
if (k_yc[idx].src == (uint8_t)PTS_SRC_FREQ) {
|
||
if (snap.freq_valid == 0u) {
|
||
*out = 0.0f;
|
||
*qds = (uint16_t)MEAS_QDS_IV;
|
||
return -1;
|
||
}
|
||
*out = snap.freq_hz;
|
||
*qds = 0u;
|
||
return 0;
|
||
}
|
||
if (k_yc[idx].sem < 0) {
|
||
*out = 0.0f; /* 理论不可达:现无未绑测量量点 */
|
||
*qds = (uint16_t)MEAS_QDS_IV;
|
||
return -1;
|
||
}
|
||
*out = snap.yc[(uint8_t)k_yc[idx].sem];
|
||
*qds = snap.qds[(uint8_t)k_yc[idx].sem];
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:板端默认源 - 遥信位图(保护元件动作态 + 定值闭锁)
|
||
*/
|
||
static uint64_t s_src_yx_bits(void) {
|
||
prot_elm_state_t oc1;
|
||
prot_elm_state_t oc2;
|
||
prot_elm_state_t oc3;
|
||
prot_elm_state_t ol;
|
||
prot_elm_state_t u0;
|
||
uint64_t bits = 0u;
|
||
|
||
prot_elm_state(PE_OC1, &oc1);
|
||
prot_elm_state(PE_OC2, &oc2);
|
||
prot_elm_state(PE_OC3, &oc3);
|
||
prot_elm_state(PE_OL, &ol);
|
||
prot_elm_state(PE_3U0, &u0);
|
||
if ((oc1.asserted != 0u) || (oc2.asserted != 0u) || (oc3.asserted != 0u)
|
||
|| (u0.asserted != 0u)) {
|
||
bits |= (1uLL << 14u); /* YX15 故障总 */
|
||
}
|
||
if (oc1.asserted != 0u) {
|
||
bits |= (1uLL << 15u); /* YX16 过流 I 段 */
|
||
}
|
||
if (oc2.asserted != 0u) {
|
||
bits |= (1uLL << 16u); /* YX17 过流 II 段 */
|
||
}
|
||
if (oc3.asserted != 0u) {
|
||
bits |= (1uLL << 17u); /* YX18 过流 III 段 */
|
||
}
|
||
if (ol.asserted != 0u) {
|
||
bits |= (1uLL << 22u); /* YX23 过载 */
|
||
}
|
||
if (prot_set_locked() != 0u) {
|
||
bits |= (1uLL << 30u); /* YX31 保护闭锁 */
|
||
}
|
||
return bits;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:板端默认源 - 遥信有效位图(V0.1 = YX15~18/23/31)
|
||
*/
|
||
static uint64_t s_src_yx_valid(void) {
|
||
return YX_VALID_MASK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:板端默认源 - 遥控(YK11 复归 → prot_elm_request_reset;
|
||
* YK12 全局软压板 → PS_GLOBAL_EN 两段式转真,B7)
|
||
* 返 回 值:0 / -1 无出口硬件或未知 IOA(YK1~4/13/14 负向确认)
|
||
* 函数说明:本回调跑在 iec104Task 上下文,**不得**直接 prot_elm_init()
|
||
* (与 protectTask 的 eval 竞态)→ 落请求,下一拍 eval 头部消费(B5)。
|
||
* YK12 走 prot_set_set_sealed(原子写+封存,B7);protectTask 下一拍
|
||
* 读到压板态即复位元件并停判据(protect_task.c)。
|
||
*/
|
||
/* 遥控记录的文案表(task-10)。**全部 ROM 常量、零拼接**:本回调跑在
|
||
* iec104Task,而 iec104_serv.c 的日志缓冲已经因为"深链模块"被收到 128 B,
|
||
* 这里再 snprintf 一条"YK ioa=0x600B dpc=2 sel=0 rc=0"就是往那条链上加缓冲;
|
||
* 记录类型由 SYSLOG_YK 这个文件本身回答,屏上话术归 UI(App/log/README.md 口径)。
|
||
* 最长一条 20 字符 < SYSLOG_DESC_MAX(28)。 */
|
||
#define YK_REC_YK11_SEL "YK11 select"
|
||
#define YK_REC_YK11_EXEC "YK11 exec"
|
||
#define YK_REC_YK12_SEL "YK12 select"
|
||
#define YK_REC_YK12_ON "YK12 exec on"
|
||
#define YK_REC_YK12_OFF "YK12 exec off"
|
||
#define YK_REC_YK12_FAIL "YK12 exec fail"
|
||
#define YK_REC_DPC_BAD "YK12 bad dpc"
|
||
#define YK_REC_UNKNOWN "YK unknown ioa"
|
||
|
||
static int s_src_yk_exec(uint32_t ioa, uint8_t dpc, uint8_t select) {
|
||
switch (ioa) {
|
||
case 0x600Au: /* YK11 信号复归 → 元件状态复位(异步一拍) */
|
||
(void)dpc; /* 复归与双点值无关 */
|
||
if (select == 0u) {
|
||
prot_elm_request_reset();
|
||
/* 遥控记录(执行段已受理)+ 操作记录(本地/远端强制复归)。
|
||
* 两条不是重复留痕:前者回答"主站下了什么命令、结果如何",后者
|
||
* 回答"装置被谁改动了状态"——UI 的遥控记录页与操作记录页各读一份。
|
||
* YK11 没有可失败的下游(request_reset 只置单字节标志),所以它
|
||
* 只有受理态,没有 fail 分支。 */
|
||
syslog_post_quiet(SYSLOG_YK, YK_REC_YK11_EXEC);
|
||
syslog_post_quiet(SYSLOG_OPER, "force reset");
|
||
} else {
|
||
syslog_post_quiet(SYSLOG_YK, YK_REC_YK11_SEL);
|
||
}
|
||
return 0;
|
||
case 0x600Bu: { /* YK12 全局软压板(PS_GLOBAL_EN)两段式 */
|
||
int rc;
|
||
if ((dpc != (uint8_t)IEC104_DPC_ON) && (dpc != (uint8_t)IEC104_DPC_OFF)) {
|
||
syslog_post_quiet(SYSLOG_YK, YK_REC_DPC_BAD);
|
||
return -1; /* 非法双点命令:选择段即拒 */
|
||
}
|
||
if (select != 0u) {
|
||
syslog_post_quiet(SYSLOG_YK, YK_REC_YK12_SEL);
|
||
return 0; /* 选择段 = 只校验 */
|
||
}
|
||
/* 执行段:set_sealed 自带范围校验与封存,失败 = 值被拒、RAM 未改。
|
||
* 这里**必须接住返回值再落记录**(bsp-err:错误码不得裸丢弃;
|
||
* 原实现是 return 直出,现在多一条记录后仍然原值返回,语义不变)。 */
|
||
rc = prot_set_set_sealed(PS_GLOBAL_EN,
|
||
(dpc == (uint8_t)IEC104_DPC_ON)
|
||
? PROT_SET_Q10(1) : PROT_SET_Q10(0));
|
||
if (rc != 0) {
|
||
syslog_post_quiet(SYSLOG_YK, YK_REC_YK12_FAIL);
|
||
} else {
|
||
syslog_post_quiet(SYSLOG_YK,
|
||
(dpc == (uint8_t)IEC104_DPC_ON) ? YK_REC_YK12_ON
|
||
: YK_REC_YK12_OFF);
|
||
/* 压板投退是一次真实的运维动作(保护退出/恢复服务)→ 同时进操作记录 */
|
||
syslog_post_quiet(SYSLOG_OPER,
|
||
(dpc == (uint8_t)IEC104_DPC_ON) ? "plate in"
|
||
: "plate out");
|
||
}
|
||
return rc;
|
||
}
|
||
default:
|
||
syslog_post_quiet(SYSLOG_YK, YK_REC_UNKNOWN);
|
||
return -1; /* 其余 YK:无出口硬件/无对应能力 → 拒绝 */
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:板端默认源 - 定值读(**路由顺序 = run_par 表先、未命中落 prot_set**:
|
||
* 0x8027~0x802A 属 prot_set 告警段,run_par 命中集为精确地址匹配、
|
||
* 永不覆盖之;两表共存靠「先专属表、后通用扫」分流,勿反序——反序
|
||
* 当前无冲突但失去 run_par 守卫先行留痕的意义)
|
||
* 返 回 值:0 / -1 未知地址(本地项 comm_addr=0 不可寻址,B7 起显式排除)
|
||
*/
|
||
static int s_src_set_read(uint32_t addr, float *out) {
|
||
uint32_t i;
|
||
|
||
if (out == NULL) {
|
||
return -1;
|
||
}
|
||
if (run_par_104_read(addr, out) == 0) {
|
||
return 0; /* 运行参数段命中(卡 10) */
|
||
}
|
||
for (i = 0u; i < (uint32_t)PS_ID_COUNT; i++) {
|
||
if ((g_prot_set_meta[i].comm_addr != 0u)
|
||
&& (g_prot_set_meta[i].comm_addr == (uint16_t)addr)) {
|
||
*out = prot_set_get_f((prot_set_id_t)i);
|
||
return 0;
|
||
}
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:板端默认源 - 定值写(**先 run_par 后 prot_set**,分流理由见
|
||
* s_src_set_read 头注;prot_set 腿 = Q10 范围校验 + 执行段
|
||
* prot_set_set_sealed 原子写+封存,B7 收口 B5 竞态窗口)
|
||
* 返 回 值:0 / -1 越界或未知地址(本地项 comm_addr=0 不可寻址);两条落盘
|
||
* 链独立:run_par 腿脏 → SYS_EVT_RUN_PAR_SAVE(cfg_file),
|
||
* prot_set 腿脏 → SYS_EVT_PROT_SET_SAVE(/prot_set.bin)
|
||
*/
|
||
static int s_src_set_write(uint32_t addr, float v, uint8_t select) {
|
||
uint32_t i;
|
||
int32_t q10;
|
||
int rp;
|
||
|
||
rp = run_par_104_write(addr, v, select);
|
||
if (rp != -2) {
|
||
return rp; /* 本段地址:0 接受 / -1 拒绝,不再下探 */
|
||
}
|
||
|
||
for (i = 0u; i < (uint32_t)PS_ID_COUNT; i++) {
|
||
const prot_set_meta_t *meta = &g_prot_set_meta[i];
|
||
|
||
if ((meta->comm_addr == 0u) || (meta->comm_addr != (uint16_t)addr)) {
|
||
continue;
|
||
}
|
||
q10 = PROT_SET_Q10(v);
|
||
if ((q10 < meta->min) || (q10 > meta->max)) {
|
||
return -1; /* 越界:选择段即拒(执行段同样拒) */
|
||
}
|
||
if (select != 0u) {
|
||
return 0; /* 选择段 = 只做范围校验 */
|
||
}
|
||
return prot_set_set_sealed((prot_set_id_t)i, q10);
|
||
}
|
||
return -1; /* 未知地址 */
|
||
}
|
||
|
||
/* ===========================================================================
|
||
* [comm] 通讯参数段(道G task-8):公共地址 CA + 被控站监听端口。
|
||
* 契约/线程约定/生效语义(改后重启生效,不挂回调不置事件位)→ iec104_comm.h。
|
||
* 引擎属主 = defaultTask:本段只被 cfg_file_load/save/poll 通用遍历,本文件
|
||
* 不触引擎任何写路径;内核前注册点 = app_main.c(「先注册后 load」契约,同
|
||
* adc_cal_init / run_par_init)。本目录(App/protocol)零日志惯例保持——
|
||
* 注册失败留痕由调用方 app_main.c 锚点负责,getter/setter 纯 RAM 不打日志
|
||
* (iec104Task 深链省栈,AGENTS 日志成本口径)。
|
||
* ========================================================================= */
|
||
|
||
/* [comm] RAM 副本(引擎原地更新;u16 半字对齐读原子,撕裂域 = 单字段新旧值) */
|
||
typedef struct {
|
||
uint16_t ca; /* 公共地址(= 链路地址,ASDU 头 2B,点表 §1.5 口径) */
|
||
uint16_t port; /* 被控站 TCP 监听端口 */
|
||
} iec104_comm_t;
|
||
|
||
static iec104_comm_t s_comm;
|
||
static const iec104_comm_t s_comm_rom = {
|
||
IEC104_COMM_DEF_CA, IEC104_COMM_DEF_PORT
|
||
};
|
||
|
||
/* 键表(s_comm 布局内偏移,同 net_cfg/ftp_cfg 写法) */
|
||
static const cfg_key_def_t s_comm_keys[] = {
|
||
{ "ca", CFG_VAL_UINT, offsetof(iec104_comm_t, ca), 2u },
|
||
{ "port", CFG_VAL_UINT, offsetof(iec104_comm_t, port), 2u },
|
||
};
|
||
|
||
/*
|
||
* 函数功能:置工厂默认并向引擎注册 [comm] 段(幂等,内核前)
|
||
* 返回值:0 注册成功;-1 重复注册/段数超限(RAM 副本已复位,adc_cal 同款) int
|
||
* 限定条件:先于 defaultTask 的 cfg_file_load;不挂回调(改后重启生效)
|
||
*/
|
||
int iec104_comm_init(void) {
|
||
cfg_section_reg_t reg;
|
||
|
||
(void)memcpy(&s_comm, &s_comm_rom, sizeof(s_comm));
|
||
|
||
(void)memset(®, 0, sizeof(reg));
|
||
reg.name = IEC104_COMM_SEC;
|
||
reg.p_data = &s_comm;
|
||
reg.p_defaults = &s_comm_rom;
|
||
reg.data_size = sizeof(s_comm);
|
||
reg.p_keys = s_comm_keys;
|
||
reg.key_count = (uint8_t)(sizeof(s_comm_keys) / sizeof(s_comm_keys[0]));
|
||
reg.p_cb = NULL; /* 不挂回调:iec104Task 启动读 getter(D5 口径) */
|
||
return cfg_file_register(®);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:取生效公共地址
|
||
* 返回值:RAM 副本 ca,带外回退工厂默认;恒 ∈ IEC104_COMM_CA_MIN..MAX uint16_t
|
||
* 限定条件:无(任意上下文,纯读)
|
||
*/
|
||
uint16_t iec104_comm_ca(void) {
|
||
uint16_t v = s_comm.ca;
|
||
|
||
if ((v < IEC104_COMM_CA_MIN) || (v > IEC104_COMM_CA_MAX)) {
|
||
return IEC104_COMM_DEF_CA;
|
||
}
|
||
return v;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:取生效监听端口
|
||
* 返回值:RAM 副本 port,带外回退工厂默认;恒 ∈ IEC104_COMM_PORT_MIN..MAX uint16_t
|
||
* 限定条件:无(任意上下文,纯读)
|
||
*/
|
||
uint16_t iec104_comm_port(void) {
|
||
uint16_t v = s_comm.port;
|
||
|
||
if ((v < IEC104_COMM_PORT_MIN) || (v > IEC104_COMM_PORT_MAX)) {
|
||
return IEC104_COMM_DEF_PORT;
|
||
}
|
||
return v;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:写 ca 进 RAM 副本(守卫 = getter 同带;非法不提交)
|
||
* 入口参数:ca - 公共地址 uint16_t
|
||
* 返回值:0 提交;-1 带外(副本不动) int
|
||
* 限定条件:UI 保存交接路(task-9);落盘走 SYS_EVT_CFG_SAVE 整档外科 save
|
||
*/
|
||
int iec104_comm_ca_set(uint16_t ca) {
|
||
if ((ca < IEC104_COMM_CA_MIN) || (ca > IEC104_COMM_CA_MAX)) {
|
||
return -1;
|
||
}
|
||
s_comm.ca = ca;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:写 port 进 RAM 副本(守卫 = getter 同带;非法不提交)
|
||
* 入口参数:port - 监听端口 uint16_t
|
||
* 返回值:0 提交;-1 带外(副本不动) int
|
||
* 限定条件:同 iec104_comm_ca_set
|
||
*/
|
||
int iec104_comm_port_set(uint16_t port) {
|
||
if ((port < IEC104_COMM_PORT_MIN) || (port > IEC104_COMM_PORT_MAX)) {
|
||
return -1;
|
||
}
|
||
s_comm.port = port;
|
||
return 0;
|
||
}
|
||
|