/* * 模块名称:SOE Store — 事件顺序记录落盘(MCU 胶水,单写者) * 模块功能:protectTask 动作/复归边沿 → soe_store_post() 投递(纯 RAM: * sys_clock 取时 + soe_rec_build + 消息队列,**零文件系统**); * defaultTask(/log 唯一写者,经 syslog_drain 尾部挂接)消费落盘 * /log/soe.log(定长 64B,容量满删除重建,照 run.log 先例)。 * 记录格式/类型:见 soe_rec.h(纯编解码在 soe_rec.c,主机单测覆盖)。 * 线程约定:post = protectTask(唯一生产者;内核启动后任意任务上下文亦可, * 队列未建时走静态待发数组,照 syslog_post 先例); * drain = 仅 defaultTask(syslog_drain 尾部调用,继承其卷就绪门)。 * seq 口径:RAM 计数器,上电内单调;容量满 unlink 重建**不重置**(计数继续), * 仅设备复位后重置(主机解码断言单调性须按"同一 boot 段"分段看)。 * 创建日期:2026-10-12(B9) */ #ifndef SOE_STORE_H #define SOE_STORE_H #include #include "soe_rec.h" /* 记录类型/标志/SOE_ELM_NONE 常量(post 入参口径) */ #ifdef __cplusplus extern "C" { #endif /* 容量上限(条数;满时删除重建,照 SYSLOG_RUN_MAX 先例):1024 × 64B = 64KB */ #define SOE_STORE_MAX 1024u /* * 函数功能:投递一条 SOE 记录(构建 + 入队;非阻塞,队列满则丢弃并计数) * 入口参数:type - 记录类型 soe_type_t uint8_t * elm_id - 元件号(非元件事件传 SOE_ELM_NONE) uint8_t * value - 类型相关值(见 soe_rec.h 类型表) uint32_t * 返回值:无 * 限定条件:内核启动后调用(protectTask 上下文);**绝不触碰文件系统**—— * 取时标(sys_clock 纯 RAM)+ 组记录 + osMessageQueuePut(0 超时)。 * 队列未创建(defaultTask 首次 drain 前)走静态待发数组;数组满 * = 丢弃 + 计数 + 首次留痕日志(绝不阻塞、绝不崩)。 */ void soe_store_post(uint8_t type, uint8_t elm_id, uint32_t value); /* * 函数功能:排空 SOE 待发(静态数组 + 队列,FIFO)并落 /log/soe.log * 入口参数:无 * 返回值:无 * 限定条件:仅 defaultTask(唯一写者)调用,且由 syslog_drain 在"卷就绪"门 * 之后挂接(继承 s_fs_ready 语义,勿单独调用——/log 目录可能未建) * 函数说明:写路径照 sys_log_mcu.c s_append 全套实证纪律:vol_lock try(busy * = -2 顺延)、FA_WRITE|FA_OPEN_APPEND(本移植 OPEN_APPEND 不含 * WRITE)、校验写回字节数(FR_OK+短写=失败)、容量满 unlink 重建。 * unlink/名字类错误码**严禁**归入任何"卷损坏"触发集(存储架构 * §10.1 教训)——本模块无任何重格式化路径,故障只留痕不升级。 */ void soe_store_drain(void); /* * 函数功能:累计丢弃计数(队列满/待发数组满;可观测性) * 返回值:丢弃条数 uint32_t */ uint32_t soe_store_dropped(void); /* ---------------- 只读遍历(UI 的 SOE 记录页用;写侧零影响) ---------------- * 读者不得看到半成品记录,三道保障缺一不可(实现见 soe_store.c 同号函数): * 1) count 只数**整条 64 B 槽**(filesize / SOE_REC_SIZE 整除),写者留下的 * 半条尾槽天然不计入; * 2) 窗口代数:写者每次成功落盘 / 容量重建,在**卷锁区间内**自增内部代数, * 读者比对填窗时记下的代数,不符即重填。对齐的 32 位读写在 ARM 上原子, * 跨任务不需要临界区;最坏多读一轮,不会读出错数据; * 3) 单条硬门槛:soe_rec_parse() 校版本 + 校验和,撕裂/半写一律拒收(-3)。 * 容忍的竞态只有一个:count 与 read 之间新落一条 → 本次遍历少看到一条,下一轮 * 重绘(uiTask 200 ms 节拍)自然追上。**不容忍**的是读到半条,所以边界一律以 * 卷锁内重新取到的条数为准,不用调用方传进来的旧值。 * 与 App/log/sys_log_mcu.c 的 syslog_count/syslog_get_record 同构(同一套窗口 + * 代数 + try 锁纪律),刻意不另起第二套读盘路径。 */ /* * 函数功能:已落盘的 SOE 记录条数(= filesize / SOE_REC_SIZE,O(1)) * 入口参数:无 * 返回值:条数;文件缺失、未挂载或 f_stat 失败返回 0 uint32_t * 限定条件:任意任务上下文可调(uiTask 记录页用);只 f_stat,不取卷锁 * 函数说明:见上方"只读遍历"总说明第 1 道保障 */ uint32_t soe_store_count(void); /* * 函数功能:读取第 idx 条 SOE 记录(解析后的结构体,idx 0 = 最早一条) * 入口参数:idx - 记录序号 uint32_t * p_out - 输出(不可为 NULL) soe_rec_t * * 返回值:0 - 成功;-1 - p_out 为空 / 越界 / 卷忙 / I/O 失败 / 版本或校验和不过 int * 限定条件:uiTask 等读上下文;**不阻塞**——卷被占用立即返回 -1(调用方这行退化 * 显示"读失败",下轮重绘再试),排障页不得拖住主界面节拍 * 函数说明:soe_rec_parse 的 -1/-2/-3 在此折成一个 -1:调用方对三者的处置完全相同 * (这行读不到),把内部码外溢只会诱导页面为区分不出来的差异写分支。 * 时间字段取记录内的 CP56(事件自己的时标),不用"现在的时间"换算 */ int soe_store_read(uint32_t idx, soe_rec_t *p_out); #ifdef __cplusplus } #endif #endif /* SOE_STORE_H */