Files
STM32F4-Base/App/protocol/iec104/iec104_pts.c
T
wandering1 058e7c061c docs(数据可信): §8 风险表改按行名引用,修掉两处指错行
- IEC104上行设计说明.md 与 iec104_pts.c 的 s_q_map 注释原写「§8 风险
  表末行」,末行其实是「加压烧板」;含单测要求的是「削顶通道被当成可
  测」行。两处都改成行名,行名完整落在一行内
- ADC前端注入验证流程.md 同源错引用改成「削顶负向用例被当成固件 bug」行
- 数据可信.md §8 表头加一条引用规矩:写行名,别写「末行 / 第 N 行」,
  本表会插行
- doc-links 只校验章节编号存在性,看不见「末行」这类行内指向,也从不扫
  .c/.h —— 这两处此前没有门能拦

@build 0 errors 0 warnings(只改注释,Program Size 未变)
2026-10-30 00:18:53 +08:00

683 lines
30 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称:IEC 60870-5-104 点表与数据源适配(实现)
* 模块功能:实现 iec104_pts.h——YC/YX/YK/定值点表(ROM)、板端默认源
* (**测频与遥测快照**(104 改读快照)/ prot_elm / prot_set)、COV 比对(遥信变位 +
* 遥测死区 2 点确认 + 品质变化)。点表口径见 iec104_pts.h 文件头与
* 设计文档 §3。
* 线程约定:init/bind 建服务前;其余仅 104 服务任务上下文(快照非原子)。
* 创建日期:2026-10-12(B4)
* 修改记录:
* 2026-10-25 DTU 项目组 下游常数按工程量重定(「数据可信」):YC 死区由拟定码值域重定为
* 二次值工程量域(额定 0.5 % 规则),判据依据见
* k_yc 表头注释;数值随 104 改读快照迁运行参数,本表将退化为
* ROM 默认载体。
* 2026-10-25 DTU 项目组 104 改读快照与频率点绑定(「数据可信」,道D):YC 板端源改读
* 测频与遥测快照(App/algo/meas.h,meas_get 无锁双区);
* 削顶五路由快照品质 CLIPPED 驱动线上 IV(不再绑死 -1,
* V0.2 消除双转换器无共同采样触发后零改动自动启用);0x401d 绑频率源(频率点绑定);
* 死区改运行时副本 + set/get 入口;COV 遥测腿 = 死区
* 连续 2 点确认(板端按快照 seq 去重)+ 品质翻转即报。
* 单测 = test/host/iec104 TC-IEC104-025~028。
* 运行参数段 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)初版落本文件尾部;
* s_src_yk_exec() 等遥控/记录投递路径零改动。
* 2026-10-26 DTU 项目组 同日晚纠偏:[comm] 实现整体迁出至配对模块
* iec104_comm.c(coding-standard CODE-RULE-17 (2.2)
* 按文件名配对判定,本文件的公开符号必须声明于
* iec104_pts.h——[comm] 不属点表,拆出正解;契约
* = iec104_comm.h,本文件不再引用 cfg_file)。
*/
#include "iec104_pts.h"
#include <string.h>
#include "iec104_asdu.h" /* QDS/DIQ 品质位常量 */
#include "meas.h" /* 板端源:遥测 = 测频与遥测快照(104 改读快照;品质域位同源) */
#include "run_par.h" /* 运行参数段:死区真源 + 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 默认值) ------------ */
/* 源类型(「数据可信」频率点绑定:YC 表加 src 类型区分相量/频率) */
#define PTS_SRC_MEAS 0u /* 全周傅氏相量快照槽 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] 段 = 运行参数段真源) */
uint8_t src; /* PTS_SRC_* 源类型 */
} pts_yc_def_t;
/* 死区规则 = 额定二次值的 0.5 %(「数据可信」§2.3 表第 2 行建议规则,随下游常数按工程量重定采纳;
* 额定基准 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 并随
* 运行参数段可调)。自 104 改读快照起本表**只作工厂默认载体**:生效死区 =
* App/algo/run_par.c 的 [run_par] 引擎副本(运行参数段真源收编,本层不再存副本);削顶五路不再 sem=-1 绑死,而是绑快照槽、由品质 CLIPPED
* 驱动线上 IV——V0.2 削顶消失后点位零改动自动启用(docs/任务规划/
* 数据可信.md §10 的「双转换器无共同采样触发」行)。
* ⚠ 过渡期(eng=0 引脚电压域,两点标定完成前):上送值本身非二次值,死区
* 数字按本域偏保守(迟报不漏报);标定落地后复核(「数据可信」§2.3 表
* 第 2 行)。判定 = 死区连续 2 点确认(104 改读快照),见 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:频率点绑定——绑频率源,
* freq_valid=0 → IV */
};
#define YC_N ((uint8_t)(sizeof(k_yc) / sizeof(k_yc[0])))
/* ===== 遥测 COV 运行时状态(104 改读快照;仅 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 服务启动前,调一次
* 函数说明:104 改读快照起不再扫 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,104 改读快照)
* 入口参数: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→无位)——旧注入源
* 行为与 104 改读快照前**完全一致**(单测假源零改动)。判定顺序 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;
}
/*
* 函数功能:写死区(104 改读快照的 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(运行参数段持久化链)。
*/
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(104 改读快照):测点推进 = 板端源按快照换帧(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 进出,频率点绑定) */
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);
}
/* ============================ 板端默认源 ================================= */
/*
* 函数功能:板端默认源 - 遥测 = 测频与遥测快照直读(「数据可信」104 改读快照与频率点绑定)
* 入口参数: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 处理),"新不新、能不能信"全在发布侧一处决定(遥测快照发布的设计意图,
* 两处判断必打架)。频率路(PTS_SRC_FREQ):snap.freq_valid==0 →
* IV(频率点绑定 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; /* 运行参数段命中 */
}
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; /* 未知地址 */
}