c936441c83
本轮把"自研代码落在门禁之外"的口子收掉,同时落地 LVGL 特性裁剪与 L3 保护定值/元件框架。 门禁(.pre-commit-config.yaml,26 个钩子) - 新增 scripts/check_lv_conf_sync.py + 钩子 lv-conf-sync:比对两份 lv_conf.h 的**生效值**(未定义则取 lv_conf_internal.h 兜底默认), 抓 MISMATCH / LVGL 不认的宏名 / 同文件重复定义 - 顶层 exclude 删掉 simulator/:其下 21 个 tracked 文件全是本项目自研 代码,生成物(build/、SDL2/、tests/ref/、shot*)已在 .gitignore; doc-links 的 exclude 同步删除 - lv-conf-sync 的 files: 覆盖两端配置 → 只改 simulator/lv_conf.h 也触发 (pre-commit 4.x 已移除 always: true,实测只告警并跳过,无兜底开关) - 新增 prot-set-unit-test / prot-elm-unit-test 两个主机侧单测钩子 - S11(2.3.5) 判定细化:禁的是"用路径找文件",尖括号系统头带子目录 (如 <sys/stat.h>)不再误报;全仓库复核仅 2 处项目代码判定变化 模拟器代码整改(纳入钩子后暴露,全部修代码而非豁免) - cppcheck 4 处:sim_syslog.c 判空分支内解引用 p_epoch(真实 bug)、 2 处 struct tm 未初始化、1 处指针可加 const - coding-standard 61 处:44 处函数左大括号并到右括号同行、8 处补函数 注释、3 处补文件头注释块、2 处 __ 前缀保护宏改 XXX_H、2 处 sprintf 改 snprintf(写入字节数与原来完全一致) LVGL 特性裁剪(F-1/F-2/F-3) - 两端 lv_conf.h 对齐同一特性子集:删 26 个 LVGL 9.3 不存在的选项名、 3 处重复定义,10 项靠默认值隐式生效的改为显式固化 - 实测对镜像体积中性;价值在防漂移,且模拟器 golden 回归跑的正是固件 实际编译的配置 L3 软件侧 - App/algo/prot_set.* Q10 定值模型(范围校验 / 整表 all-or-nothing / 校验和封存-自检-闭锁)+ prot_elm.* 通用"滞回+定时限"元件框架 7 实例 - test/host/prot_set、test/host/prot_elm 主机侧单测;uvprojx 挂载两处 文档同步 - pre-commit使用说明:§2.8 S11 口径、§2.15 lv-conf-sync 详解、新增 §5.5 复盘(教训:exclude 只写真正不该进钩子的路径) - AGENTS.md、UI_设计架构 §1.4 第 10 条、RAM 分析、审查报告刷新与销项 验证 - @build BUILD_OK 0/0(Code=194276 RO=15872 RW=876 ZI=139892) - 模拟器重建:菜单 8 状态像素逐字节一致(池 5368 B)、11 内容页黄金参考 全过(往返 5448 B)、remo sink 0/25600 位差异、--logtest/--cfgtest PASS - 全仓库 cppcheck / coding-standard / no-mojibake --all-files 全过 - check_doc_links --all 129/0;check_mem_numbers --check 内存数字一致 - 端到端:往 simulator/lv_conf.h 注入一行漂移 → 钩子 FAIL(exit 1)
212 lines
6.7 KiB
C
212 lines
6.7 KiB
C
/*
|
||
* 模块名称:System Log (模拟器后端) — 运行/自检记录落盘
|
||
* 模块功能:宿主文件 sim_run.log / sim_selftest.log(工作目录),与 MCU 端同格式
|
||
* (定长 64B 记录);单线程直写,无队列
|
||
* 适用平台:PC 模拟器
|
||
* 作者:王建锋
|
||
* 创建日期:2026-09-14
|
||
* 修改记录:
|
||
* v1.0 2026-09-14 王建锋 创建初始版本(设计见 docs/子系统设计/运行与自检记录设计说明.md §4)
|
||
*/
|
||
|
||
/* 头文件包含区 */
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include <time.h>
|
||
#include <sys/stat.h>
|
||
|
||
#include "sys_log.h"
|
||
|
||
/* 静态变量定义区 */
|
||
|
||
static const char s_paths[SYSLOG_COUNT][16] = { "sim_run.log", "sim_selftest.log" };
|
||
static const uint32_t s_caps[SYSLOG_COUNT] = { SYSLOG_RUN_MAX, SYSLOG_SELFTEST_MAX };
|
||
|
||
/* 函数定义区 */
|
||
|
||
/*
|
||
* 函数功能:取当前宿主本地时间(ISO 8601 空格变体 + epoch)
|
||
* 入口参数:p_ts - 输出缓冲(≥SYSLOG_TS_LEN+1 字节,不可为 NULL) char *
|
||
* p_epoch- 输出 epoch 秒(不可为 NULL) uint32_t *
|
||
* 返回值:无
|
||
*/
|
||
static void s_now(char *p_ts, uint32_t *p_epoch) {
|
||
time_t t = time(NULL);
|
||
struct tm tmv = {0}; /* 显式清零:localtime_s/r 是"输出型"参数,静态分析按未初始化报 */
|
||
|
||
#if defined(_WIN32)
|
||
localtime_s(&tmv, &t);
|
||
#else
|
||
localtime_r(&t, &tmv);
|
||
#endif
|
||
(void)snprintf(p_ts, SYSLOG_TS_LEN + 1u, "%04d-%02d-%02d %02d:%02d:%02d",
|
||
tmv.tm_year + 1900, tmv.tm_mon + 1, tmv.tm_mday,
|
||
tmv.tm_hour, tmv.tm_min, tmv.tm_sec);
|
||
*p_epoch = (uint32_t)t;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:追加一条记录(格式化 64B;容量满先删除重建)
|
||
* 入口参数:id - 日志 ID syslog_id_t
|
||
* desc - 描述串(UTF-8,超长截断至 SYSLOG_DESC_MAX) const char *
|
||
* 返回值:0 - 成功;-1 - 写失败 int
|
||
*/
|
||
static int s_append(syslog_id_t id, const char *desc) {
|
||
FILE *fp;
|
||
uint8_t rec[SYSLOG_REC_SIZE];
|
||
char ts[SYSLOG_TS_LEN + 1u];
|
||
uint32_t epoch = 0u;
|
||
size_t dl;
|
||
|
||
if ((uint32_t)id >= (uint32_t)SYSLOG_COUNT || desc == NULL) {
|
||
return -1;
|
||
}
|
||
if (syslog_count(id) >= s_caps[id]) {
|
||
(void)remove(s_paths[id]);
|
||
}
|
||
memset(rec, 0u, sizeof(rec));
|
||
s_now(ts, &epoch);
|
||
memcpy(rec, ts, SYSLOG_TS_LEN);
|
||
rec[SYSLOG_TS_LEN] = ' ';
|
||
/* +1 = 容纳 NUL;字段本身固定 10 位数字,与旧 sprintf 写入字节数完全一致 */
|
||
(void)snprintf((char *)(rec + SYSLOG_EPOCH_OFFS), SYSLOG_EPOCH_LEN + 1u,
|
||
"%010u", (unsigned int)epoch);
|
||
rec[SYSLOG_DESC_OFFS - 1u] = ' ';
|
||
dl = strlen(desc);
|
||
if (dl > SYSLOG_DESC_MAX) {
|
||
dl = SYSLOG_DESC_MAX;
|
||
}
|
||
memcpy(rec + SYSLOG_DESC_OFFS, desc, dl);
|
||
|
||
fp = fopen(s_paths[id], "ab");
|
||
if (fp == NULL) {
|
||
return -1;
|
||
}
|
||
if (fwrite(rec, 1u, sizeof(rec), fp) != sizeof(rec)) {
|
||
(void)fclose(fp);
|
||
return -1;
|
||
}
|
||
(void)fclose(fp);
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读取第 idx 条原始记录(64B)
|
||
* 入口参数:id - 日志 ID syslog_id_t
|
||
* idx - 记录序号(0 起) uint32_t
|
||
* p_rec- 输出缓冲(SYSLOG_REC_SIZE 字节,不可为 NULL) uint8_t *
|
||
* 返回值:0 - 成功;-1 - 越界/读失败 int
|
||
*/
|
||
static int s_read_raw(syslog_id_t id, uint32_t idx, uint8_t *p_rec) {
|
||
FILE *fp;
|
||
|
||
if (p_rec == NULL || idx >= syslog_count(id)) {
|
||
return -1;
|
||
}
|
||
fp = fopen(s_paths[id], "rb");
|
||
if (fp == NULL) {
|
||
return -1;
|
||
}
|
||
if (fseek(fp, (long)idx * SYSLOG_REC_SIZE, SEEK_SET) != 0 ||
|
||
fread(p_rec, 1u, sizeof(uint8_t) * SYSLOG_REC_SIZE, fp) != SYSLOG_REC_SIZE) {
|
||
(void)fclose(fp);
|
||
return -1;
|
||
}
|
||
(void)fclose(fp);
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:投递一条日志(单线程直写宿主文件)
|
||
* 入口参数:id - 日志 ID syslog_id_t
|
||
* desc - 描述串(UTF-8,超长截断) const char *
|
||
* 返回值:无
|
||
*/
|
||
void syslog_post(syslog_id_t id, const char *desc) {
|
||
(void)s_append(id, desc);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:获取记录条数(= filesize / SYSLOG_REC_SIZE,O(1))
|
||
* 入口参数:id - 日志 ID syslog_id_t
|
||
* 返回值:条数;文件缺失返回 0 uint32_t
|
||
*/
|
||
uint32_t syslog_count(syslog_id_t id) {
|
||
struct stat st;
|
||
|
||
if ((uint32_t)id >= (uint32_t)SYSLOG_COUNT) {
|
||
return 0u;
|
||
}
|
||
if (stat(s_paths[id], &st) != 0) {
|
||
return 0u;
|
||
}
|
||
return (uint32_t)st.st_size / SYSLOG_REC_SIZE;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读取第 idx 条记录(时间戳 + 描述,均 NUL 结尾)
|
||
* 入口参数:id - 日志 ID syslog_id_t
|
||
* idx - 记录序号(0 起) uint32_t
|
||
* p_ts - 时间戳输出缓冲(≥SYSLOG_TS_LEN+1 字节,不可为 NULL) char *
|
||
* ts_len - p_ts 容量 size_t
|
||
* p_desc - 描述输出缓冲(≥SYSLOG_DESC_MAX+1 字节,不可为 NULL) char *
|
||
* desc_len - p_desc 容量 size_t
|
||
* 返回值:0 - 成功;-1 - 越界/读失败 int
|
||
*/
|
||
int syslog_get_record(syslog_id_t id, uint32_t idx, char *p_ts, size_t ts_len, char *p_desc, size_t desc_len) {
|
||
uint8_t rec[SYSLOG_REC_SIZE];
|
||
size_t dl = 0u;
|
||
|
||
if (p_ts == NULL || p_desc == NULL || ts_len <= SYSLOG_TS_LEN || desc_len <= SYSLOG_DESC_MAX) {
|
||
return -1;
|
||
}
|
||
if (s_read_raw(id, idx, rec) != 0) {
|
||
return -1;
|
||
}
|
||
memcpy(p_ts, rec, SYSLOG_TS_LEN);
|
||
p_ts[SYSLOG_TS_LEN] = '\0';
|
||
while (dl < SYSLOG_DESC_MAX && rec[SYSLOG_DESC_OFFS + dl] != '\0') {
|
||
dl++;
|
||
}
|
||
if (dl >= desc_len) {
|
||
dl = desc_len - 1u;
|
||
}
|
||
memcpy(p_desc, rec + SYSLOG_DESC_OFFS, dl);
|
||
p_desc[dl] = '\0';
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读取末条记录的 epoch 秒(上电/重启分类用)
|
||
* 入口参数:id - 日志 ID syslog_id_t
|
||
* p_epoch- 输出缓冲(不可为 NULL;无记录时置 0) uint32_t *
|
||
* 返回值:0 - 成功读到末条;-1 - 文件缺失/空 int
|
||
*/
|
||
int syslog_last_epoch(syslog_id_t id, uint32_t *p_epoch) {
|
||
uint8_t rec[SYSLOG_REC_SIZE];
|
||
uint32_t v = 0u;
|
||
uint32_t i;
|
||
|
||
/* 先判空再写:旧写法 `if (p_epoch == NULL || ...)` 之后立刻 *p_epoch = 0,
|
||
* 一旦调用方真传 NULL 就是空指针解引用(cppcheck nullPointerRedundantCheck 实报) */
|
||
if (p_epoch == NULL) {
|
||
return -1;
|
||
}
|
||
if (syslog_count(id) == 0u) {
|
||
*p_epoch = 0u;
|
||
return -1;
|
||
}
|
||
if (s_read_raw(id, syslog_count(id) - 1u, rec) != 0) {
|
||
*p_epoch = 0u;
|
||
return -1;
|
||
}
|
||
for (i = 0u; i < SYSLOG_EPOCH_LEN; i++) {
|
||
char c = (char)rec[SYSLOG_EPOCH_OFFS + i];
|
||
if (c >= '0' && c <= '9') {
|
||
v = v * 10u + (uint32_t)(c - '0');
|
||
}
|
||
}
|
||
*p_epoch = v;
|
||
return 0;
|
||
}
|