Files
STM32F4-Base/simulator/sim/sim_syslog.c
T
wandering1 c936441c83 门禁扩容:新增 lv-conf-sync、simulator/ 纳入全部钩子;含 L3 定值/元件框架落地
本轮把"自研代码落在门禁之外"的口子收掉,同时落地 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)
2026-10-11 08:48:49 +08:00

212 lines
6.7 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.
/*
* 模块名称: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;
}