/* * 模块名称:SOE Record — 事件顺序记录编解码(实现) * 模块功能:实现 soe_rec.h——64B 定长记录的构建(epoch→CP56Time2a,Hinnant * civil 日历算法,与 iec104_asdu.c 同一算法族但本模块零依赖)与 * 解析(版本 + sum8 校验和硬门槛)。 * 线程约定:全部纯函数,无状态 → 任意上下文可调(生产者 = protectTask, * 落盘/读取 = defaultTask / 主机工具)。 * 创建日期:2026-10-12(B9) */ #include "soe_rec.h" #include /* 2000-01-01 00:00:00 UTC 的 epoch 秒(CP56 年字段下限,钳位用) */ #define EPOCH_2000_S 946684800uL /* 2127-12-31 23:59:59 UTC(年字段 7bit 上限 2000+127,钳位用; * = 2128-01-01(4986057600) - 1,含 2000..2124 共 32 个闰日) */ #define EPOCH_2127_END_S 4986057599uLL /* * 函数功能:epoch 秒 → 小端 32 位写入 */ static void s_soe_put_u32(uint8_t *buf, uint32_t v) { buf[0] = (uint8_t)(v & 0xFFu); buf[1] = (uint8_t)((v >> 8) & 0xFFu); buf[2] = (uint8_t)((v >> 16) & 0xFFu); buf[3] = (uint8_t)((v >> 24) & 0xFFu); } /* * 函数功能:小端 32 位读取 */ static uint32_t s_soe_get_u32(const uint8_t *buf) { return (uint32_t)buf[0] | ((uint32_t)buf[1] << 8) | ((uint32_t)buf[2] << 16) | ((uint32_t)buf[3] << 24); } /* * 函数功能:距 1970-01-01 天数 → 公历(Hinnant civil_from_days,与 * iec104_asdu.c 同一算法;纯整数,ARMCC/MSVC 双端可移植) * 入口参数:z_in = 天数;y/m/d = 输出 */ static void s_soe_civil_from_days(uint32_t z_in, uint32_t *y, uint32_t *m, uint32_t *d) { uint32_t z = z_in + 719468u; uint32_t era = z / 146097u; uint32_t doe = z - (era * 146097u); uint32_t yoe = (doe - (doe / 1460u) + (doe / 36524u) - (doe / 146096u)) / 365u; uint32_t yy = yoe + (era * 400u); uint32_t doy = doe - ((365u * yoe) + (yoe / 4u) - (yoe / 100u)); uint32_t mp = ((5u * doy) + 2u) / 153u; uint32_t mon = mp + ((mp < 10u) ? 3u : 0u); *d = doy - (((153u * mp) + 2u) / 5u) + 1u; if (mp >= 10u) { mon = mp - 9u; } *m = mon; *y = yy + ((mon <= 2u) ? 1u : 0u); } /* * 函数功能:epoch 秒 → CP56Time2a(7B,UTC 分解) * 入口参数:epoch_s - Unix 秒;buf - 目标 7B;iv - 1 = 置 IV 位(时间无效) * 返回值:无 * 函数说明:钳位 [2000-01-01, 2127-12-31](CP56 年字段 7bit 域;epoch_s 为 * uint32 → 上界钳位是防御性死分支,2106 后域扩展时生效); * ms 域 = 秒×1000(秒域时基如实编码,亚秒恒 0,不造假)。 */ static void s_soe_epoch_to_cp56(uint32_t epoch_s, uint8_t *buf, uint8_t iv) { uint64_t clamped; uint32_t days, rem, y, m, d, ms_min; clamped = epoch_s; if (clamped < (uint64_t)EPOCH_2000_S) { clamped = (uint64_t)EPOCH_2000_S; } if (clamped > EPOCH_2127_END_S) { clamped = EPOCH_2127_END_S; } days = (uint32_t)(clamped / 86400uLL); rem = (uint32_t)(clamped % 86400uLL); s_soe_civil_from_days(days, &y, &m, &d); if (y > 2127u) { /* 钳位上界的日历化(防 epoch 上界跨日) */ y = 2127u; m = 12u; d = 31u; rem = 86399u; } ms_min = (rem % 60u) * 1000u; /* 秒域时基:秒如实入 ms 域(×1000), * 亚秒部分恒 0——不造假(B9 口径) */ buf[0] = (uint8_t)(ms_min & 0xFFu); buf[1] = (uint8_t)(ms_min >> 8); buf[2] = (uint8_t)(((rem / 60u) % 60u) & 0x3Fu); /* min;bit7 IV 下方置 */ buf[3] = (uint8_t)((rem / 3600u) % 24u); /* hour */ buf[4] = (uint8_t)(d & 0x1Fu); /* day */ buf[5] = (uint8_t)(m & 0x0Fu); /* month */ buf[6] = (uint8_t)((y - 2000u) & 0x7Fu); /* year */ if (iv != 0u) { buf[2] |= 0x80u; /* IV:时间无效 */ } } /* * 函数功能:sum8([0..62]) 校验和 */ static uint8_t s_soe_sum8(const uint8_t *rec) { uint8_t s = 0u; uint32_t i; for (i = 0u; i < SOE_OFF_SUM; i++) { s = (uint8_t)(s + rec[i]); } return s; } /* * 函数功能:构建一条 64B SOE 记录(见 soe_rec.h 声明注释) */ void soe_rec_build(uint8_t *rec, uint8_t type, uint8_t elm_id, uint8_t flags, uint32_t seq, uint32_t epoch_s, uint32_t value) { if (rec == NULL) { return; } (void)memset(rec, 0, SOE_REC_SIZE); rec[SOE_OFF_VER] = (uint8_t)SOE_REC_VER; rec[SOE_OFF_TYPE] = type; rec[SOE_OFF_ELM] = elm_id; rec[SOE_OFF_FLAGS] = flags; s_soe_put_u32(rec + SOE_OFF_SEQ, seq); s_soe_epoch_to_cp56(epoch_s, rec + SOE_OFF_CP56, ((flags & SOE_FLG_IV) != 0u) ? 1u : 0u); s_soe_put_u32(rec + SOE_OFF_EPOCH, ((flags & SOE_FLG_IV) != 0u) ? 0u : epoch_s); s_soe_put_u32(rec + SOE_OFF_VALUE, value); rec[SOE_OFF_SUM] = s_soe_sum8(rec); } /* * 函数功能:解析一条 64B SOE 记录(见 soe_rec.h 声明注释) */ int soe_rec_parse(const uint8_t *rec, soe_rec_t *out) { if ((rec == NULL) || (out == NULL)) { return -1; } if (rec[SOE_OFF_VER] != (uint8_t)SOE_REC_VER) { return -2; } if (rec[SOE_OFF_SUM] != s_soe_sum8(rec)) { return -3; } out->ver = rec[SOE_OFF_VER]; out->type = rec[SOE_OFF_TYPE]; out->elm_id = rec[SOE_OFF_ELM]; out->flags = rec[SOE_OFF_FLAGS]; out->seq = s_soe_get_u32(rec + SOE_OFF_SEQ); (void)memcpy(out->cp56, rec + SOE_OFF_CP56, sizeof(out->cp56)); out->epoch_s = s_soe_get_u32(rec + SOE_OFF_EPOCH); out->value = s_soe_get_u32(rec + SOE_OFF_VALUE); return 0; }