feat(UI): 装置维护 3 页落地(时钟 / 强制复归 / 清除记录),内容页黄金 27→40
三个薄页面模块 ui_page_clock / ui_page_reset / ui_page_clear,页面注册由生成器 自动写双端(裁定 A-1:「三叶各绑一页」与「零新增 .c」不可兼得,选薄页面加生成器, 不写手写 node switch——那种分发在增删叶子时会静默换功能,且没有任何门禁照得到)。 数据侧是注入式函数表:UI 不 include App 层的头。两条硬理由都实测过——分层方向 (layer-deps)之外,模拟器根本不链 sys_clock,ALLOW 豁免挡得住 layer-deps, 挡不住模拟器链接期。面板对时的唯一真判据是回读 RTC:sys_clock_set() 恒返 0 (RTC 写失败只打日志),所以真机侧写 1 后回读 1,最坏约 200 ms 落在一次「保存」 按键上,IWDG 800 ms 窗有余;上界与依据都写进注释。 「无时钟」徽标位置有两个像素级实测坑:放标题带右侧会被 4 字标题连成「时钟设置 无时钟」一句话;放页脚带左侧 x=7 会压住页脚——页脚从 x=6 起排,不是居中。 最终落在清单末行与页脚之间的空带 y=124。clear_done 那张顺带给出像素证据: 清 run.log 之后「操作记录」那一行从 2 变 3,页面上没缓存、没撒谎。 ui_w_records 的 UI_W_REC_LOG_* 顺序必须等于 syslog_id_t 顺序(7 类,只准尾追加), 已加编译期顺序守卫;执行侧 7 条映射表同样带编译期守卫。 测试与真值:内容页黄金 27 → 40 页(+13),文本断言 +68、反向断言 ABSENT +19; 27 张旧参考 git diff 为空 = 逐字节未变。往返池 5448 B 与菜单常驻堆 5368 B 均未变; 字库未扩(源码在用 171 并 KEEP_EXTRA 39 = 清单 183),font-charset 与 cn-chars、 cn-font 字节一致。@build 0 错误 0 警告;uiTask 静态最坏链 1,336 B / 16,384 (余 91%)与基线相同,页面钩子没进最坏链。 一条测试学结论单独留痕:反向断言不能当「位置不重叠」的证据——本脚本的字形匹配 把 y 带判得比字面宽(徽标在 125..136、页脚在 144..155,仍被 (140,160) 判命中)。 该防线改由正向断言「页脚未被徽标压住」承担(压字那一版实测过不了它)。 两条自我更正一并记档(作者自报,按真值而非原口径): ① 先前报「文案零新增汉字」是错的——观测行拟用「动作元件」,「元」不在子集, cn-chars 与 font-charset 如实红;最终改「保护动作 N」才让「零新增」成为事实。 顺序是:先按机器红,再按真值写结论。 ② 先前报「6 个文件 BOM 逐个核过」被 bom-guard 证伪:丢 BOM 发生在上一次编译之后, 正是 AGENTS 那句「历史 0 警告不代表安全」的活例;此后一律以退出码为准。 未覆盖项不假装:台架「N>0 → N=0」在产品构建的未标定闭锁期拿不到(闭锁期 N 恒 0), 判据显式退化为「页面受理 + 落一条操作记录」,写在 rs_watch 注释里。 生成物入库例外照用:uvprojx 只加 3 个 ui_page_ 的 FilePath(真增删源文件), CMakeLists 同批;两处都由 gen_menu.py 自己写,没手写。 本笔先走了一次真钩子:20 条钩子实跑通过(ui-golden 菜单 8 状态加内容页 40 页、 generated-fresh、registration-check、font-charset、ui-data 单测、cppcheck、 coding-standard、bom、mojibake、id-check、doc-links 等),只有 mem-numbers 与 mem-budget 两条红——磁盘上的 .map 出自 20:39 那次构建、含三条在飞道的代码, 与 HEAD 台账必然不同码。按仓库既有约定(这两条等台账回填时允许 --no-verify 并 正文留证)落这一笔;回填由我统一在收尾一笔做。 三条在飞道(task-26 崩溃证据路径、task-16 与 task-17 谓词收敛和卷锁观测、 task-36 台架工具)的未暂存改动被 autostash 暂时收走,钩子看到的是 HEAD 加本笔; 过程中曾把 comm 道未跟踪的 iec104_comm.c 临时移出(它不在 stash 覆盖面内), 那次移出反而让 ui-golden 撞见 CMake 缓存里的悬空引用而红——已改用「先落 comm 那笔、再落本笔」的顺序消掉这个中间态。ui_data_maint_ready() 待删(只有测试在读 的自检口),排在本笔之后单独一笔。
This commit is contained in:
+475
-4
@@ -14,10 +14,16 @@
|
||||
* App 侧节拍里的 ui_data_param_drain(),两者同在 uiTask → 环形队列照旧
|
||||
* 无锁;若将来出现第二个投递上下文,唯一要做的是给 request/drain 两侧
|
||||
* 各加一把互斥(队列本身是单字 head/cnt 推进,页面侧不会看到半成品)
|
||||
* 模块功能补充:除三通道快照外,本模块还持有定值写路径的请求队列与状态字
|
||||
* (ui_data_param_request/state/drain)——本层只做"排队 + 状态机",
|
||||
* 不判断值域、不碰 prot_set、不碰文件系统(值域与写入事实经
|
||||
* ui_data_param_ops_t 由 App 侧注入,理由见 ui_data.h 写路径节)
|
||||
* 模块功能补充:除三通道快照外,本模块还持有三条与页面相关的东西:
|
||||
* ① 定值写路径的请求队列与状态字(ui_data_param_request/state/drain)
|
||||
* ——只做"排队 + 状态机",不判断值域、不碰 prot_set、不碰文件系统
|
||||
* (值域与写入事实经 ui_data_param_ops_t 由 App 侧注入);
|
||||
* ② 装置维护写通道(ui_data_maint_*:墙钟设置 / 强制复归 / 清除记录)
|
||||
* ——常驻注入的 ui_data_maint_ops_t 转发,本层不解释业务含义;
|
||||
* 为什么这里常驻注入而 ① 每次传表,见 ui_data.h 通道四的函数说明;
|
||||
* ③ 时钟字段换算(ui_data_clock_*:本地域、无时区偏移、纯整数零依赖)
|
||||
* ——「无效值拒收」的唯一判据处,也是唯一能在主机单测里跑全表的
|
||||
* 日历判据(页面 .c 依赖 lvgl.h,编不进主机测试单元)
|
||||
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-10-25
|
||||
@@ -27,6 +33,15 @@
|
||||
* 与状态机(IDLE/WAIT/OK/ERR_RANGE/ERR_LOCKED/ERR_BUSY),
|
||||
* 执行经注入函数表回调 App 侧;参数帧新增 cal_block
|
||||
* 字段随 publish 原样入存(本层不解释含义)
|
||||
* v1.2 2026-10-26 DTU 项目组 道F task-3(装置维护 3 页的前置通道):新增维护
|
||||
* 写通道 ui_data_maint_*(常驻注入 ui_data_maint_ops_t:
|
||||
* clock_set / reset_req / active_count / oper_note /
|
||||
* clear_req / clear_state 六口,未注入一律安全返回)与
|
||||
* 时钟字段换算 ui_data_clock_*(本地域无时区偏移;
|
||||
* to_epoch 同时是"无效值拒收"的唯一判据处)。
|
||||
* 三通道快照结构一个字节都没动 ⇒ UI_DATA_VER 保持 2
|
||||
* (版本号是页面与写侧唯一的对齐凭据,无字段变更却升版
|
||||
* = 让老页面以为新帧换了布局)
|
||||
*/
|
||||
|
||||
/* 头文件包含区 */
|
||||
@@ -420,3 +435,459 @@ int ui_data_param_drain(const ui_data_param_ops_t *a_ops) {
|
||||
s_pq_state = (s_pq_cnt != 0u) ? UI_DATA_PARAM_ST_WAIT : settled;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ── 通道四:装置维护写请求(时钟 / 强制复归 / 清除记录)──────────────────── */
|
||||
|
||||
/*
|
||||
* 注入的维护执行侧函数表(NULL = 未注入)。
|
||||
* 只存指针不拷内容:注入方(App/task/ui_task.c 与 simulator/main.c)的两端实现
|
||||
* 都是文件级 static const 表,生命周期 = 整个运行期,与 ui_w_records_bind() 存的
|
||||
* reader 指针同一条约定。撤销只可能出现在宿主测试里。
|
||||
*/
|
||||
static const ui_data_maint_ops_t *s_maint;
|
||||
|
||||
/*
|
||||
* 函数功能:注入装置维护执行侧函数表(启动期一次)
|
||||
* 入口参数:ops - 函数表;六个成员指针都不得为 NULL;NULL = 撤销注入
|
||||
* const ui_data_maint_ops_t *
|
||||
* 返回值:0 - 已注入(或已撤销);-1 - ops 非空但某成员为 NULL(存量不动) int
|
||||
* 限定条件:uiTask 启动期,先于 ui_menu_init()(与 ui_w_records_bind 同一处);
|
||||
* uiTask 之外的上下文不得调用(本层无锁,与快照层同一条并发前提)
|
||||
* 函数说明:成员逐条校验而不只看表指针——半途接错一条(例如忘了注 clear_state)
|
||||
* 会让清除记录页在"按下才崩"的位置崩,生成期/启动期就拒掉整表才是省事
|
||||
*/
|
||||
int ui_data_maint_bind(const ui_data_maint_ops_t *a_ops) {
|
||||
if (a_ops == NULL) {
|
||||
s_maint = NULL; /* 撤销注入:页面随即按"能力不可用"渲染 */
|
||||
return 0;
|
||||
}
|
||||
if ((a_ops->clock_set == NULL) || (a_ops->reset_req == NULL) ||
|
||||
(a_ops->active_count == NULL) || (a_ops->oper_note == NULL) ||
|
||||
(a_ops->clear_req == NULL) || (a_ops->clear_state == NULL)) {
|
||||
return -1;
|
||||
}
|
||||
s_maint = a_ops;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:查询维护通道是否已注入
|
||||
* 入口参数:无
|
||||
* 返回值:1 - 已注入;0 - 未注入 uint8_t
|
||||
* 限定条件:无(只读单字,任意上下文安全)
|
||||
* 函数说明:页面用它把"没接线"和"操作被拒"分开显示——前者是装配缺陷,
|
||||
* 现场再按一百次也不会好,必须看得见
|
||||
*/
|
||||
uint8_t ui_data_maint_ready(void) {
|
||||
return (s_maint != NULL) ? 1u : 0u;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:请求设置墙钟(时钟设置页"保存"行的唯一出口)
|
||||
* 入口参数:unix_epoch - 已整组校验过的秒(见 ui_data_clock_to_epoch) uint32_t
|
||||
* 返回值:0 - 执行侧受理;-1 - 未注入或执行侧失败;-2 - 参数无效(0 = 本层保留的
|
||||
* "未初始化"哨兵,与 sys_clock 的 UINT32_MAX 哨兵不同族,
|
||||
* 这里只挡 0 这一个明显不该写进 RTC 的值) int
|
||||
* 限定条件:uiTask 上下文(页面按键钩子)
|
||||
* 函数说明:本层不判日期合法性——判据只有一份,在 ui_data_clock_to_epoch();
|
||||
* 也不做回读,回读属执行侧(它才知道 sys_clock_get() 的真值)
|
||||
*/
|
||||
int ui_data_maint_clock_set(uint32_t a_unix_epoch) {
|
||||
if (s_maint == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (a_unix_epoch == 0u) {
|
||||
return -2;
|
||||
}
|
||||
return s_maint->clock_set(a_unix_epoch);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:请求复归全部保护元件(强制复归页的执行出口)
|
||||
* 入口参数:无
|
||||
* 返回值:0 - 已受理;-1 - 未注入或执行侧拒收 int
|
||||
* 限定条件:uiTask 上下文
|
||||
* 函数说明:本函数**不投**操作记录,页面在返 0 之后自己投("本地面板发起"这个
|
||||
* 事实只有页面知道;远端 104 那条另有投递点,两边各一条、不得互替);
|
||||
* 返 -1 时页面必须一条都不投——给一次没发生的操作留审计记录,比少一条
|
||||
* 记录坏得多
|
||||
*/
|
||||
int ui_data_maint_reset_req(void) {
|
||||
if (s_maint == NULL) {
|
||||
return -1;
|
||||
}
|
||||
return s_maint->reset_req();
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:取当前动作元件数(复归页"保护动作 N"行的数据源)
|
||||
* 入口参数:无
|
||||
* 返回值:元件数;未注入返回 0 uint32_t
|
||||
* 限定条件:页面绘制路径可直调(一次函数指针转发,零 I/O、零锁)
|
||||
* 函数说明:返回类型取 uint32_t 与执行侧位图同宽,不在本层压成 uint8_t——
|
||||
* 元件数将来超 8 时,窄化会在页面里静默截成 0("明明动作了屏上说没有")
|
||||
*/
|
||||
uint32_t ui_data_maint_active_count(void) {
|
||||
if (s_maint == NULL) {
|
||||
return 0u;
|
||||
}
|
||||
return s_maint->active_count();
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:投一条操作记录留痕
|
||||
* 入口参数:desc - 描述串(ASCII 常量;NULL = 什么都不做) const char *
|
||||
* 返回值:0 - 已投递;-1 - 未注入或 desc 为 NULL int
|
||||
* 限定条件:uiTask 上下文
|
||||
* 函数说明:投递可能被队列丢弃(执行侧口径)——本层不因此把屏上的"操作成功"
|
||||
* 翻成失败:记录通道故障不等于操作没发生,混起来只会让现场误判装置
|
||||
*/
|
||||
int ui_data_maint_oper_note(const char *a_desc) {
|
||||
if ((s_maint == NULL) || (a_desc == NULL)) {
|
||||
return -1;
|
||||
}
|
||||
s_maint->oper_note(a_desc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:请求清除某类记录(清除记录页的执行出口)
|
||||
* 入口参数:log_id - UI 侧记录源编号(UI_W_REC_LOG_*) uint8_t
|
||||
* 返回值:0 - 已受理;-1 - 未注入或执行侧拒绝 int
|
||||
* 限定条件:uiTask 上下文;"受理"≠"已清除",终态读 ui_data_maint_clear_state()
|
||||
* 函数说明:不在本层判 log_id 上界——上界的真源是记录侧有几个文件(映射表在
|
||||
* 适配层),这里再抄一份长度就成了第二真源,记录加一类时必然漏改
|
||||
*/
|
||||
int ui_data_maint_clear_req(uint8_t a_log_id) {
|
||||
if (s_maint == NULL) {
|
||||
return -1;
|
||||
}
|
||||
return s_maint->clear_req(a_log_id);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:查某类记录的清除状态
|
||||
* 入口参数:log_id - 记录源编号 uint8_t
|
||||
* 返回值:UI_DATA_CLEAR_ST_NONE / _OK / _WAIT / _FAIL;未注入返回 NONE int
|
||||
* 限定条件:页面绘制路径可直调
|
||||
* 函数说明:返回值直通执行侧、不做二次加工(与 ui_data_param_state() 同理:
|
||||
* 两处解释同一件事迟早漂移);WAIT 是正常态,页面不得画成失败
|
||||
*/
|
||||
int ui_data_maint_clear_state(uint8_t a_log_id) {
|
||||
if (s_maint == NULL) {
|
||||
return UI_DATA_CLEAR_ST_NONE;
|
||||
}
|
||||
return s_maint->clear_state(a_log_id);
|
||||
}
|
||||
|
||||
/* ── 时钟字段换算(本地域,无时区偏移)──────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* 私有宏定义区(本小节自带,不与其它小节共用):
|
||||
* 算法 = Howard Hinnant 的 days_from_civil / civil_from_days(以 0000-03-01
|
||||
* 为纪元、400 年一周期),与 App/protocol/iec104/iec104_asdu.c 的正反两套实现
|
||||
* 同族;本工程年域 2000..2038,全程非负,用 uint32 就够,不引 int64 也不引 time.h
|
||||
*/
|
||||
#define CL_SECONDS_PER_DAY 86400u
|
||||
#define CL_SECONDS_OF_DAY_MAX 11647u /* 0x7FFFFFFF % 86400 = 2038-01-19 的 03:14:07 */
|
||||
#define CL_DAYS_EPOCH_MAX 24855u /* 0x7FFFFFFF / 86400 = 2038-01-19(距 1970-01-01 天数) */
|
||||
#define CL_DAYS_TO_0000 719468u /* 1970-01-01 距 0000-03-01 纪元的天数(Hinnant 偏移量) */
|
||||
#define CL_DAYS_PER_ERA 146097u /* 400 年一周期的天数 */
|
||||
|
||||
/*
|
||||
* 函数功能:公历日期 → 距 1970-01-01 的天数(调用方保证字段已判过合法性)
|
||||
* 入口参数:year - 年(>= 1) uint16_t month - 月 1..12 uint8_t day - 日 1..31 uint8_t
|
||||
* 返回值:天数(本工程年域内恒非负) uint32_t
|
||||
* 限定条件:月/日未做合法性判定——**合法性判定在 to_epoch(),不在这里**,
|
||||
* 换算函数保持"给什么算什么"才能与反向函数严格互逆
|
||||
* 函数说明:先做 1/2 月归到上一年的 11/12 月(y 相应减一),使 3 月成为岁首、
|
||||
* 闰日落在周期末端,之后每月天数变成 31 天/30.6 天的规整式子
|
||||
*/
|
||||
static uint32_t ui_data_clock_days_from_civil(uint16_t a_year, uint8_t a_month, uint8_t a_day) {
|
||||
uint32_t y = (a_month <= 2u) ? ((uint32_t)a_year - 1u) : (uint32_t)a_year;
|
||||
uint32_t era = y / 400u;
|
||||
uint32_t yoe = y - (era * 400u); /* [0, 399] */
|
||||
uint32_t mp = (a_month > 2u) ? ((uint32_t)a_month - 3u) : ((uint32_t)a_month + 9u);
|
||||
uint32_t doy = ((153u * mp + 2u) / 5u) + (uint32_t)a_day - 1u; /* [0, 365] */
|
||||
uint32_t doe = (yoe * 365u) + (yoe / 4u) - (yoe / 100u) + doy; /* [0, 146096] */
|
||||
|
||||
return (era * CL_DAYS_PER_ERA) + doe - CL_DAYS_TO_0000;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:判断是否闰年(完整格里历规则)
|
||||
* 入口参数:year - 年 uint16_t
|
||||
* 返回值:1 - 闰年;0 - 平年 uint8_t
|
||||
* 限定条件:与 App/util/sys_clock.c 的 unix_to_sd2506() 内联判据逐字同一条规则
|
||||
* (4 年 && 非 100 年 || 400 年);2100 不在允许年域内,无需讨论该例外
|
||||
* 函数说明:取 uint8_t 而不是 bool——本工程布尔判定允许 bool,但本函数参与
|
||||
* 天数计算,返回宽度明确的整型省掉一次隐式转换(同 ui_w_editor_key()
|
||||
* 把"分发标志"写成 uint8_t 的口径)
|
||||
*/
|
||||
static uint8_t ui_data_clock_is_leap(uint16_t a_year) {
|
||||
if ((((uint32_t)a_year % 4u) == 0u) && (((uint32_t)a_year % 100u) != 0u)) {
|
||||
return 1u;
|
||||
}
|
||||
if (((uint32_t)a_year % 400u) == 0u) {
|
||||
return 1u;
|
||||
}
|
||||
return 0u;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:取某年某月的天数(含闰年 2 月)
|
||||
* 入口参数:year - 年 uint16_t month - 月 1..12 uint8_t
|
||||
* 返回值:28 / 29 / 30 / 31;月份为 0 或 > 12 时返回 0(调用方以 0 判非法,
|
||||
* 不开第二条错误码) uint8_t
|
||||
* 限定条件:无(纯查表 + 闰年判定,零依赖)
|
||||
* 函数说明:表序 = 1 月起(下标 0 是 1 月),与 sys_clock.c:145 那份 mdays[]
|
||||
* 的"0 月起"排法**故意不同**——本表按屏上月的直觉排(下标 = 月 - 1),
|
||||
* 少一次心算 -1 就少一次错位;两侧同一张表的等价性由主机单测逐月核对
|
||||
*/
|
||||
uint8_t ui_data_clock_days_in_month(uint16_t a_year, uint8_t a_month) {
|
||||
static const uint8_t k_mdays[12] = {
|
||||
31u, 28u, 31u, 30u, 31u, 30u, 31u, 31u, 30u, 31u, 30u, 31u
|
||||
};
|
||||
|
||||
if ((a_month < 1u) || (a_month > 12u)) {
|
||||
return 0u;
|
||||
}
|
||||
if ((a_month == 2u) && (ui_data_clock_is_leap(a_year) != 0u)) {
|
||||
return 29u;
|
||||
}
|
||||
return k_mdays[a_month - 1u];
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:日历字段 → 本地域 epoch 秒,同时充当这组字段的合法性判据
|
||||
* 入口参数:p_in - 六个字段 const ui_data_clock_t *
|
||||
* p_out - 秒 uint32_t *
|
||||
* 返回值:0 - 合法并已输出;-1 - 参数为 NULL;-2 - 字段组合非法(任一段越界,
|
||||
* 含 2 月 30 日、非闰年 2 月 29 日、月 13、时 24、分/秒 60);
|
||||
* -3 - 字段各自合法但换算结果越界(> UI_DATA_CLOCK_EPOCH_MAX) int
|
||||
* 限定条件:年段先判(2000..2038),因此天数计算全程在非负 uint32 域内
|
||||
* 函数说明:三段边界分开返码,是为了让页面能给出现场听得懂的话:"2 月 30 日"
|
||||
* 是输入错(-2,光标回到出问题那一行),而"2038 年之后"是能力边界
|
||||
* (-3,说这一版装置的墙钟就到这儿)——两种情况混成一个 ERR,
|
||||
* 用户会反复试一个永远试不出的日期
|
||||
* ⚠ 全程不含时区偏移(口径见 ui_data.h 本节注释与
|
||||
* App/task/ui_task.c 的 SOE 时标注记):加 28800 的后果是"屏上输
|
||||
* 08:00、存进去 16:00",且模拟器里往返照样自洽、照不出来
|
||||
*/
|
||||
int ui_data_clock_to_epoch(const ui_data_clock_t *p_in, uint32_t *p_out) {
|
||||
uint32_t days;
|
||||
uint32_t sod;
|
||||
uint32_t dmax;
|
||||
|
||||
if ((p_in == NULL) || (p_out == NULL)) {
|
||||
return -1;
|
||||
}
|
||||
if ((p_in->year < (uint16_t)UI_DATA_CLOCK_YEAR_MIN) ||
|
||||
(p_in->year > (uint16_t)UI_DATA_CLOCK_YEAR_MAX)) {
|
||||
return -2;
|
||||
}
|
||||
if ((p_in->month < 1u) || (p_in->month > 12u)) {
|
||||
return -2;
|
||||
}
|
||||
dmax = (uint32_t)ui_data_clock_days_in_month(p_in->year, p_in->month);
|
||||
if ((p_in->day < 1u) || ((uint32_t)p_in->day > dmax)) {
|
||||
return -2;
|
||||
}
|
||||
if (p_in->hour > 23u) {
|
||||
return -2;
|
||||
}
|
||||
if ((p_in->minute > 59u) || (p_in->second > 59u)) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
days = ui_data_clock_days_from_civil(p_in->year, p_in->month, p_in->day);
|
||||
sod = ((uint32_t)p_in->hour * 3600u) + ((uint32_t)p_in->minute * 60u) +
|
||||
(uint32_t)p_in->second;
|
||||
if ((days > CL_DAYS_EPOCH_MAX) || ((days == CL_DAYS_EPOCH_MAX) && (sod > CL_SECONDS_OF_DAY_MAX))) {
|
||||
return -3; /* 2038-01-19 03:14:07 之后:uint32 秒域到头,不是用户输错 */
|
||||
}
|
||||
|
||||
*p_out = (days * CL_SECONDS_PER_DAY) + sod;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:本地域 epoch 秒 → 日历字段(ui_data_clock_to_epoch 的反函数)
|
||||
* 入口参数:unix_epoch - 秒 uint32_t p_out - 输出 ui_data_clock_t *
|
||||
* 返回值:0 - 已输出;-1 - p_out 为 NULL;-2 - 超出允许域(0 哨兵、年 < 2000、
|
||||
* 或 > UI_DATA_CLOCK_EPOCH_MAX) int
|
||||
* 限定条件:与 to_epoch 严格互逆(主机单测按往返相等钉,见 test/host/ui_data);
|
||||
* 刻意把 0 也判越界:0 = 1970-01-01,落在 RTC 年域(2000..2099)之外,
|
||||
* 而它又恰好是"墙钟无效"在记录侧的占位起点,绝不允许被当真实时间显示
|
||||
*/
|
||||
int ui_data_clock_from_epoch(uint32_t a_unix_epoch, ui_data_clock_t *p_out) {
|
||||
uint32_t days;
|
||||
uint32_t sod;
|
||||
uint32_t z;
|
||||
uint32_t era;
|
||||
uint32_t doe;
|
||||
uint32_t yoe;
|
||||
uint32_t doy;
|
||||
uint32_t mp;
|
||||
uint32_t y;
|
||||
uint32_t m;
|
||||
|
||||
if (p_out == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if ((a_unix_epoch == 0u) || (a_unix_epoch > UI_DATA_CLOCK_EPOCH_MAX)) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
days = a_unix_epoch / CL_SECONDS_PER_DAY;
|
||||
sod = a_unix_epoch % CL_SECONDS_PER_DAY;
|
||||
z = days + CL_DAYS_TO_0000; /* 移到 0000-03-01 纪元 */
|
||||
era = z / CL_DAYS_PER_ERA;
|
||||
doe = z - (era * CL_DAYS_PER_ERA); /* [0, 146096] */
|
||||
yoe = (doe - (doe / 1460u) + (doe / 36524u) - (doe / 146096u)) / 365u; /* [0, 399] */
|
||||
doy = doe - ((365u * yoe) + (yoe / 4u) - (yoe / 100u));
|
||||
mp = (5u * doy + 2u) / 153u;
|
||||
|
||||
/* 3 月纪元月序转公历月序:mp < 10 落在 3..12 月,否则落在 1/2 月并归上一年 */
|
||||
y = yoe + (era * 400u);
|
||||
if (mp < 10u) {
|
||||
m = mp + 3u;
|
||||
} else {
|
||||
m = mp - 9u;
|
||||
y += 1u; /* 1/2 月属于上一年 */
|
||||
}
|
||||
|
||||
p_out->year = (uint16_t)y;
|
||||
p_out->month = (uint8_t)m;
|
||||
p_out->day = (uint8_t)(doy - ((153u * mp + 2u) / 5u) + 1u);
|
||||
p_out->hour = (uint8_t)(sod / 3600u);
|
||||
p_out->minute = (uint8_t)((sod / 60u) % 60u);
|
||||
p_out->second = (uint8_t)(sod % 60u);
|
||||
|
||||
if ((p_out->year < (uint16_t)UI_DATA_CLOCK_YEAR_MIN) ||
|
||||
(p_out->year > (uint16_t)UI_DATA_CLOCK_YEAR_MAX)) {
|
||||
return -2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:解析定长时标串为日历字段
|
||||
* 入口参数:ts - "YYYY-MM-DD HH:MM:SS"(快照 rt.clock 的形状) const char *
|
||||
* p_out - 输出 ui_data_clock_t *
|
||||
* 返回值:0 - 成功;-1 - 参数为 NULL;-2 - 串型不对(不是 19 字符、分隔位不是
|
||||
* '-'/' '/':'、出现非数字)**或**型对但值非法
|
||||
* (组合再过 ui_data_clock_to_epoch() 把关) int
|
||||
* 限定条件:空串(墙钟无效时快照里的取值,ui_data.h:149)走 -2;
|
||||
* 本函数**不猜**:"1970-01-01 08:00:00"这类记录侧占位串在这里同样返 -2
|
||||
* (年 1970 出域),页面据此显示"无时钟"而不是把占位串画成真实时间
|
||||
* 函数说明:手写十进制累加而不用 sscanf——本模块是零 LVGL、零 App 的纯内存叶子,
|
||||
* 拉进 stdio 就等于给主机单测加依赖、给 MCU 端加一份 printf 家族
|
||||
*/
|
||||
int ui_data_clock_parse(const char *a_ts, ui_data_clock_t *p_out) {
|
||||
ui_data_clock_t t;
|
||||
uint32_t i;
|
||||
uint32_t acc;
|
||||
const char *p;
|
||||
|
||||
if ((a_ts == NULL) || (p_out == NULL)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
(void)memset(&t, 0, sizeof(t));
|
||||
|
||||
/* 五段数字:年 4 位、月/日/时/分/秒各 2 位;段间分隔位逐位核对 */
|
||||
p = a_ts;
|
||||
acc = 0u;
|
||||
for (i = 0u; i < 4u; i++) {
|
||||
if ((p[i] < '0') || (p[i] > '9')) {
|
||||
return -2;
|
||||
}
|
||||
acc = (acc * 10u) + (uint32_t)(p[i] - '0');
|
||||
}
|
||||
t.year = (uint16_t)acc;
|
||||
if (p[4] != '-') {
|
||||
return -2;
|
||||
}
|
||||
p += 5u;
|
||||
|
||||
{
|
||||
uint8_t *const fields[5] = { &t.month, &t.day, &t.hour, &t.minute, &t.second };
|
||||
const char seps[4] = { '-', ' ', ':', ':' };
|
||||
|
||||
for (i = 0u; i < 5u; i++) {
|
||||
if (((p[0] < '0') || (p[0] > '9')) || ((p[1] < '0') || (p[1] > '9'))) {
|
||||
return -2;
|
||||
}
|
||||
*fields[i] = (uint8_t)(((uint32_t)(p[0] - '0') * 10u) + (uint32_t)(p[1] - '0'));
|
||||
p += 2u;
|
||||
if (i < 4u) {
|
||||
if (*p != seps[i]) {
|
||||
return -2;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (*p != '\0') {
|
||||
return -2; /* 型对但尾部还有字符:不是本工程认的时标串 */
|
||||
}
|
||||
{
|
||||
uint32_t probe;
|
||||
if (ui_data_clock_to_epoch(&t, &probe) != 0) {
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
|
||||
*p_out = t;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:日历字段 → 定长时标串(与 ui_data_clock_parse 同串型)
|
||||
* 入口参数:p_in - 字段 const ui_data_clock_t *
|
||||
* buf - 输出缓冲 char *
|
||||
* len - 缓冲容量(含 NUL,至少 UI_DATA_CLOCK_TS_LEN + 1 = 20) uint32_t
|
||||
* 返回值:0 - 已写出;-1 - 参数无效或容量不足(**不写半个串**,避免调用方
|
||||
* 把截断串当完整时标显示) int
|
||||
* 限定条件:不判字段合法性(调用方刚从 parse/to_epoch 那条链出来,再判一遍
|
||||
* 就是第二条判据)
|
||||
*/
|
||||
int ui_data_clock_format(const ui_data_clock_t *p_in, char *a_buf, uint32_t a_len) {
|
||||
uint8_t i;
|
||||
char *p;
|
||||
uint32_t v;
|
||||
|
||||
if ((p_in == NULL) || (a_buf == NULL) ||
|
||||
(a_len < (UI_DATA_CLOCK_TS_LEN + 1u))) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
p = a_buf;
|
||||
v = (uint32_t)p_in->year;
|
||||
for (i = 0u; i < 4u; i++) { /* 年份 4 位补零 */
|
||||
p[3u - i] = (char)('0' + (uint32_t)(v % 10u));
|
||||
v /= 10u;
|
||||
}
|
||||
p[4] = '-';
|
||||
p += 5;
|
||||
|
||||
{
|
||||
const uint8_t fields[5] = { p_in->month, p_in->day, p_in->hour,
|
||||
p_in->minute, p_in->second };
|
||||
const char seps[4] = { '-', ' ', ':', ':' };
|
||||
|
||||
for (i = 0u; i < 5u; i++) {
|
||||
p[0] = (char)('0' + ((uint32_t)fields[i] / 10u));
|
||||
p[1] = (char)('0' + ((uint32_t)fields[i] % 10u));
|
||||
p += 2;
|
||||
if (i < 4u) {
|
||||
*p = seps[i];
|
||||
p++;
|
||||
}
|
||||
}
|
||||
}
|
||||
*p = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user