/* * 模块名称: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 #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; /* 未知地址 */ }