10322208fd
- vendored coreSNTP v2.0.0 序列化器(MIT,仅 serializer+LICENSE,零堆), 48 字节报文编解码在库,节奏与准入判据在本方(RFC 4330 §10 调研口径) - App/sync/time_sntp.c 挂 timeSyncTask 的 8888 事件循环,等待切片 ≤50 ms, 不饿任务狗与 IWDG;懒建 UDP socket,代际变化/禁用/死错误码即关闭归还 - 防伪 = OriginTimestamp 逐位等于发出时 T1(含低 16 位随机小数位); mode/stratum/闰秒指示/KoD 按 RFC 拒收;DENY·RSTR 换服务器、RATE 只退避 - 主备轮换 + 失败指数退避(5 s 起、60 s 封顶)+ 首轮 1~5 分钟随机错峰; 成功恢复 poll_min 周期(1~1440 分,出厂默认 60,默认不启用不发包) - 钟面 = 本地时间:UTC+8 固定偏移落 sys_clock_set_unix(§1.1 时基裁定, 第三个写入口,装置仍不存时区);|offset|<2 s 只记日志不步进,冷启动或 更大偏差步进并回写 SD2506,留痕 [SNTP] step 与 syslog OPER - 首个校验通过的同步置 SYS_EVT_TIMESYNC_READY(就绪位,消费方按需 NoClear) - [sntp] 配置段(enable/server1/server2/poll_min)属主 sntp_cfg.c, 注册在 net_cfg_load();UI 页 31「SNTP 设置」(密码叶):启用开关 + 主/备服务器 octet 编辑 + 周期(分) + 保存,复用 net_cfg_save_request 链 - 测试与门禁:test/host/sntp 新套件(报文/防伪/KoD/退避周期/阶跃域/配置 注册表)、net_cfg 与 time_sync 套件补 fake、黄金参考 66→74 张零漂移、 字符集 228 字、Keil 0 错 0 警(RO +5,848 B 含字体)、覆盖率基线回填 - 文档:墙钟维护说明 §16、网络配置说明 [sntp] 节、配置文件设计说明变更记录、 产品需求 FR-TIME-3 与 8.3/9.1.2 台账、UI 补齐规划/UI补齐 31 叶口径、 登记簿销项并登记「SNTP 校时真机判据」等台架(PC 时间源 192.168.31.161/.13)
432 lines
17 KiB
C
432 lines
17 KiB
C
/*
|
||
* 模块名称:UI Page SNTP (SNTP设置内容页)
|
||
* 模块功能:SNTP设置叶子节点的内容页数据 + 行为——5 行列表(启用/主服务器/
|
||
* 备服务器/周期(分) + 保存行,5 行每页共 1 页);
|
||
* 行槽顶 = 24+14i、选中白带 x=2..156 高 13、名 x=4 / 值 x=54;
|
||
* 值取自 sntp_cfg RAM 副本([sntp] 段,defaultTask 上电自 Flash
|
||
* 加载,net_cfg_load 统一注册);
|
||
* 编辑模式:ENTER 进选中编辑行(地址行 = 字节光标反显,周期行 =
|
||
* 整值反显),↑↓ 调值、←→ 移字节光标 / 粗调周期、ENTER 确认退出;
|
||
* 启用行为开关行:可编辑态 ENTER 直接切换(无字节光标);
|
||
* 权限:edit_capable=1——查看密码 → 只读(编辑/保存键无效),
|
||
* 修改密码 → 可编辑;保存行 ENTER → 投递保存请求
|
||
* (net_cfg_save_request 整档链:属主任务 defaultTask 写盘;
|
||
* [sntp] 消费方每轮直读 RAM 副本 + 网络代次重建自动收口,
|
||
* 无需独立 apply 通道);保存行值随交接状态显示
|
||
* ENTER/WAIT/OK/ERR;
|
||
* 向框架 ui_content_page 提供页面描述符(零 LVGL 对象)
|
||
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
|
||
* 作者:王建锋
|
||
* 创建日期:2026-11-04
|
||
* 修改记录:
|
||
* v1.0 2026-11-04 王建锋 创建初始版本(板端 SNTP 客户端接入,#57 调研结论;
|
||
* 布局/编辑语义复刻 ui_page_net_cfg.c 先例)
|
||
*/
|
||
|
||
/* 头文件包含区 */
|
||
#include <string.h>
|
||
|
||
#include "ui_page_sntp.h"
|
||
|
||
#include "net_cfg.h" /* net_cfg_fmt_ipv4 + 整档保存请求链(save request/state) */
|
||
|
||
#include "sntp_cfg.h"
|
||
|
||
#include "ui_tokens.h"
|
||
|
||
/* 私有宏定义区 */
|
||
|
||
/*
|
||
* 布局取值:行骨架用 ui_tokens.h 的列表页令牌(UI_LIST_TOP / UI_ROW_PITCH /
|
||
* UI_ROW_BAND_H / UI_BUF_STRLEN),本文件只留本页私有的两列 x 与行数
|
||
* (与网口设置页同栅格:值列 ≥54,IPv4 定长 15 字符 90px 止于 143,在
|
||
* 选中白带 x=2..156 内)。
|
||
*/
|
||
#define SN_ROW_COUNT 5u /* 1 开关行 + 2 地址行 + 1 周期行 + 1 保存行 */
|
||
#define SN_EN_ROW 0u /* 启用(开关行) */
|
||
#define SN_ADDR_ROW_MIN 1u /* 地址行范围 1..2(主/备服务器) */
|
||
#define SN_ADDR_ROW_MAX 2u
|
||
#define SN_POLL_ROW 3u /* 周期行(分钟) */
|
||
#define SN_SAVE_ROW 4u /* 保存行下标 */
|
||
#define SN_LEFT_X 4 /* 左列 x(名) */
|
||
#define SN_RIGHT_X 54 /* 右列 x(值) */
|
||
|
||
/* 周期编辑步进与合法带(与 sntp_cfg.h getter 带一致;带外读回 60 自愈) */
|
||
#define SN_POLL_MIN 1u
|
||
#define SN_POLL_MAX 1440u
|
||
#define SN_POLL_BIG_STEP 60u
|
||
|
||
/* 静态变量定义区 */
|
||
|
||
/* 页面运行态(每次开页经 init_cb 重置;约 2 B) */
|
||
static uint8_t s_editing; /* 1 = 编辑中(被编辑行 = 当前选中行) */
|
||
static uint8_t s_cursor; /* 地址行编辑活动字节(0..3) */
|
||
|
||
/* 函数定义区 */
|
||
|
||
/*
|
||
* 函数功能:判断两区域是否相交(与 ui_content_page.c / ui_page_net_cfg.c 同实现)
|
||
* 入口参数:a - 区域 1 const lv_area_t *
|
||
* b - 区域 2 const lv_area_t *
|
||
* 返回值:true - 相交;false - 不相交
|
||
* 限定条件:无
|
||
*/
|
||
static bool sn_areas_intersect(const lv_area_t *a, const lv_area_t *b) {
|
||
int32_t x1 = (a->x1 > b->x1) ? a->x1 : b->x1;
|
||
int32_t y1 = (a->y1 > b->y1) ? a->y1 : b->y1;
|
||
int32_t x2 = (a->x2 < b->x2) ? a->x2 : b->x2;
|
||
int32_t y2 = (a->y2 < b->y2) ? a->y2 : b->y2;
|
||
|
||
return (x1 <= x2 && y1 <= y2);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:生成指定地址行的值串(与 refresh 值列同源,编辑光标定位与行文本一致)
|
||
* 入口参数:row - 地址行下标(1..2) uint16_t
|
||
* p_buf - 输出缓冲(≥ UI_BUF_STRLEN) char *
|
||
* 返回值:无
|
||
* 限定条件:p_buf 非空
|
||
* 函数说明:"192.168.001.100"(每段 3 位补零定长,字节 i 恒在字符 4i 起),
|
||
* 与网口设置页 IP 行同格式(net_cfg_fmt_ipv4)
|
||
*/
|
||
static void sn_fmt_addr(uint16_t row, char *p_buf) {
|
||
const sntp_cfg_t *cfg = sntp_cfg_get();
|
||
const uint8_t *field = (row == SN_ADDR_ROW_MIN) ? cfg->server_pri : cfg->server_bak;
|
||
|
||
net_cfg_fmt_ipv4(field, p_buf, UI_BUF_STRLEN);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:动态行刷新(逐行取名/值)
|
||
* 入口参数:row - 行下标(0..4) uint16_t
|
||
* name - 名缓冲(≥ UI_BUF_STRLEN,框架栈缓冲) char *
|
||
* value - 值缓冲(≥ UI_BUF_STRLEN,框架栈缓冲) char *
|
||
* 返回值:无
|
||
* 限定条件:页面打开(框架绘制链内逐行调用)
|
||
* 函数说明:周期行显示 RAM 副本原值(不做带外回显换算——带外值经
|
||
* sntp_cfg_poll_min() 读回 60 属消费侧自愈口径,UI 忠实显示
|
||
* 用户写入值,避免"保存后数字自己变了");
|
||
* 保存行值随保存交接状态(net_cfg_save_state)显示 ENTER/WAIT/OK/ERR
|
||
*/
|
||
static void sn_refresh(uint16_t row, char *name, char *value) {
|
||
const sntp_cfg_t *cfg = sntp_cfg_get();
|
||
|
||
name[0] = '\0';
|
||
value[0] = '\0';
|
||
|
||
switch (row) {
|
||
case SN_EN_ROW:
|
||
(void)memcpy(name, "启用", sizeof("启用"));
|
||
if (cfg->enable != 0u) {
|
||
(void)memcpy(value, "开启", sizeof("开启"));
|
||
}
|
||
else {
|
||
(void)memcpy(value, "关闭", sizeof("关闭"));
|
||
}
|
||
break;
|
||
case SN_ADDR_ROW_MIN:
|
||
(void)memcpy(name, "主服务器", sizeof("主服务器"));
|
||
sn_fmt_addr(SN_ADDR_ROW_MIN, value);
|
||
break;
|
||
case SN_ADDR_ROW_MAX:
|
||
(void)memcpy(name, "备服务器", sizeof("备服务器"));
|
||
sn_fmt_addr(SN_ADDR_ROW_MAX, value);
|
||
break;
|
||
case SN_POLL_ROW:
|
||
(void)memcpy(name, "周期(分)", sizeof("周期(分)"));
|
||
(void)lv_snprintf(value, UI_BUF_STRLEN, "%u", (unsigned)cfg->poll_min);
|
||
break;
|
||
case SN_SAVE_ROW:
|
||
(void)memcpy(name, "保存", sizeof("保存"));
|
||
switch (net_cfg_save_state()) {
|
||
case NET_CFG_SAVE_ST_WAIT:
|
||
(void)memcpy(value, "WAIT", sizeof("WAIT"));
|
||
break;
|
||
case NET_CFG_SAVE_ST_OK:
|
||
(void)memcpy(value, "OK", sizeof("OK"));
|
||
break;
|
||
case NET_CFG_SAVE_ST_FAIL:
|
||
(void)memcpy(value, "ERR", sizeof("ERR"));
|
||
break;
|
||
default:
|
||
(void)memcpy(value, "ENTER", sizeof("ENTER"));
|
||
break;
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:切换启用开关(只改 RAM 副本)
|
||
* 入口参数:无
|
||
* 返回值:无
|
||
* 限定条件:可编辑权限(key_cb 已把门)
|
||
*/
|
||
static void sn_toggle_enable(void) {
|
||
sntp_cfg_t cfg = *sntp_cfg_get();
|
||
|
||
cfg.enable = (cfg.enable != 0u) ? 0u : 1u;
|
||
sntp_cfg_set(&cfg);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:调整被编辑地址行的活动字节(0..255 环绕,实时落 RAM 副本)
|
||
* 入口参数:row - 地址行下标(1..2) uint16_t
|
||
* delta - 调整方向(>0 = +1,<0 = -1) int8_t
|
||
* 返回值:无
|
||
* 限定条件:编辑模式(s_cursor 有效)
|
||
* 函数说明:只改 RAM 副本;写盘须显式经保存行投递 net_cfg_save_request
|
||
* (net_cfg_adjust_octet 同口径)
|
||
*/
|
||
static void sn_adjust_octet(uint16_t row, int8_t delta) {
|
||
sntp_cfg_t cfg = *sntp_cfg_get();
|
||
uint8_t *field = (row == SN_ADDR_ROW_MIN) ? cfg.server_pri : cfg.server_bak;
|
||
|
||
field[s_cursor] = (uint8_t)((field[s_cursor] + ((delta > 0) ? 1u : 255u)) % 256u);
|
||
sntp_cfg_set(&cfg);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:调整周期(分钟)值(实时落 RAM 副本)
|
||
* 入口参数:step - 步长(有符号;±1 在 1..1440 带内环绕,±60 触带钳位) int16_t
|
||
* 返回值:无
|
||
* 限定条件:编辑模式(选中周期行)
|
||
*/
|
||
static void sn_adjust_poll(int16_t step) {
|
||
sntp_cfg_t cfg = *sntp_cfg_get();
|
||
int32_t v = (int32_t)cfg.poll_min;
|
||
|
||
if (v < (int32_t)SN_POLL_MIN || v > (int32_t)SN_POLL_MAX) {
|
||
v = (int32_t)SNTP_CFG_DEF_POLL_MIN; /* 带外起点先落回默认再步进 */
|
||
}
|
||
if (step == 1) {
|
||
v = (v >= (int32_t)SN_POLL_MAX) ? (int32_t)SN_POLL_MIN : (v + 1);
|
||
}
|
||
else if (step == -1) {
|
||
v = (v <= (int32_t)SN_POLL_MIN) ? (int32_t)SN_POLL_MAX : (v - 1);
|
||
}
|
||
else {
|
||
v += step; /* ±60 粗调:钳带不环绕(大步进环绕会跨带跳变,失序) */
|
||
if (v < (int32_t)SN_POLL_MIN) {
|
||
v = (int32_t)SN_POLL_MIN;
|
||
}
|
||
if (v > (int32_t)SN_POLL_MAX) {
|
||
v = (int32_t)SN_POLL_MAX;
|
||
}
|
||
}
|
||
cfg.poll_min = (uint16_t)v;
|
||
sntp_cfg_set(&cfg);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:页面自绘(编辑模式:选中行值内活动区域反显)
|
||
* 入口参数:layer - 绘制层 lv_layer_t *
|
||
* clip - 重绘区 const lv_area_t *
|
||
* 返回值:无
|
||
* 限定条件:页面打开(框架绘制链末尾调用)
|
||
* 函数说明:非编辑模式无附加绘制(行/带/页脚全由框架);地址行编辑 = 活动
|
||
* 字节反显(同网口设置页),周期行编辑 = 整值反显;
|
||
* 1bpp:black=暗底 white=亮字
|
||
*/
|
||
static void sn_draw_cb(lv_layer_t *layer, const lv_area_t *clip) {
|
||
char value[UI_BUF_STRLEN];
|
||
lv_area_t box;
|
||
uint32_t start;
|
||
uint32_t seg_len;
|
||
int32_t slot_y;
|
||
int32_t x0;
|
||
int32_t w;
|
||
uint16_t sel;
|
||
|
||
if (s_editing == 0u || ui_content_page_is_editable() == false) {
|
||
return;
|
||
}
|
||
|
||
sel = ui_content_page_selected_row();
|
||
if (sel == SN_ADDR_ROW_MIN || sel == SN_ADDR_ROW_MAX) {
|
||
sn_fmt_addr(sel, value);
|
||
start = (uint32_t)s_cursor * 4u; /* 字节 i 字符偏移 4i(%03u + 点) */
|
||
seg_len = 3u;
|
||
}
|
||
else if (sel == SN_POLL_ROW) {
|
||
(void)lv_snprintf(value, UI_BUF_STRLEN, "%u",
|
||
(unsigned)sntp_cfg_get()->poll_min);
|
||
start = 0u;
|
||
seg_len = (uint32_t)strlen(value); /* 周期行:整值反显 */
|
||
}
|
||
else {
|
||
return; /* 防御:开关行/保存行不进编辑态,无光标可画 */
|
||
}
|
||
|
||
slot_y = UI_LIST_TOP + (int32_t)sel * UI_ROW_PITCH;
|
||
x0 = SN_RIGHT_X + (int32_t)lv_text_get_width(value, start, LV_FONT_DEFAULT, 0);
|
||
w = (int32_t)lv_text_get_width(value + start, seg_len, LV_FONT_DEFAULT, 0);
|
||
|
||
box.x1 = x0 - 1;
|
||
box.x2 = x0 + w;
|
||
box.y1 = slot_y;
|
||
box.y2 = slot_y + UI_ROW_BAND_H - 1;
|
||
if (!sn_areas_intersect(&box, clip)) {
|
||
return; /* 编辑光标不在重绘区:跳过(culling) */
|
||
}
|
||
|
||
/* 反显:黑底白字(与选中行白带黑字相反,方能在带内凸显活动段) */
|
||
lv_draw_rect_dsc_t fd;
|
||
|
||
lv_draw_rect_dsc_init(&fd);
|
||
fd.base.layer = layer;
|
||
fd.bg_color = UI_COL_BG;
|
||
fd.bg_opa = LV_OPA_COVER;
|
||
lv_draw_rect(layer, &fd, &box);
|
||
|
||
{
|
||
lv_draw_label_dsc_t ld;
|
||
char seg[8]; /* 段文本:地址 3 字符 / 周期 ≤4 字符,+ '\0' */
|
||
|
||
/* 只绘制"段文本"本身(防 LVGL 按 coords 宽折行溢出,见网口设置页同注释) */
|
||
memcpy(seg, value + start, seg_len);
|
||
seg[seg_len] = '\0';
|
||
|
||
lv_draw_label_dsc_init(&ld);
|
||
ld.base.layer = layer;
|
||
ld.text = seg;
|
||
ld.text_static = 0u; /* 栈缓冲(同帧有效) */
|
||
ld.color = UI_COL_FG;
|
||
ld.ofs_x = 1; /* box.x1 = x0-1,+1 = x0(与框架行文本原点一致) */
|
||
ld.ofs_y = 0; /* 与行文本垂直对齐(text_dy = 0) */
|
||
lv_draw_label(layer, &ld, &box);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:页面按键处理(列表页 + 编辑模式)
|
||
* 入口参数:key - LVGL 键值 lv_key_t
|
||
* 返回值:true - 页已处理(框架跳过内置语义);false - 未处理
|
||
* 限定条件:页面打开;经框架键分发调用(uiTask 上下文)
|
||
* 函数说明: 1. 编辑模式(地址行/周期行):地址行 ↑↓ 调活动字节(0..255
|
||
* 环绕)、←→ 移字节光标(首↔末环绕);周期行 ↑↓ ±1 分带内环绕、
|
||
* ←→ ±60 分钳带;ENTER 确认退出编辑(值已实时落 RAM 副本);
|
||
* ESC 恒关页(框架保证,未保存调整留在 RAM,下次开页重显、
|
||
* 不写盘、重启失效——网口设置页同口径)
|
||
* 2. 正常模式:ENTER 启用行 → 直接切换开关;ENTER 地址行/周期行 →
|
||
* 进编辑模式;ENTER 保存行 → 投递整档保存请求
|
||
* net_cfg_save_request(属主任务 defaultTask 写盘,[sntp] 段
|
||
* 随档落盘;消费方每轮直读 RAM 副本,网络代次重建自动收口);
|
||
* ↑↓ 落框架移选、← 落框架关页;只读态 ENTER 一律忽略
|
||
*/
|
||
static bool sn_key_cb(lv_key_t key) {
|
||
uint16_t sel = ui_content_page_selected_row();
|
||
|
||
if (s_editing != 0u) {
|
||
if (sel < SN_ADDR_ROW_MIN || sel > SN_POLL_ROW) {
|
||
s_editing = 0u; /* 防御:编辑中选中行越界(编辑态移选被拦截,不应发生) */
|
||
return false;
|
||
}
|
||
|
||
switch (key) {
|
||
case LV_KEY_UP:
|
||
if (sel == SN_POLL_ROW) {
|
||
sn_adjust_poll(1);
|
||
}
|
||
else {
|
||
sn_adjust_octet(sel, 1);
|
||
}
|
||
ui_content_page_invalidate();
|
||
return true;
|
||
case LV_KEY_DOWN:
|
||
if (sel == SN_POLL_ROW) {
|
||
sn_adjust_poll(-1);
|
||
}
|
||
else {
|
||
sn_adjust_octet(sel, -1);
|
||
}
|
||
ui_content_page_invalidate();
|
||
return true;
|
||
case LV_KEY_LEFT:
|
||
if (sel == SN_POLL_ROW) {
|
||
sn_adjust_poll(-(int16_t)SN_POLL_BIG_STEP);
|
||
}
|
||
else {
|
||
s_cursor = (uint8_t)((s_cursor + 3u) % 4u); /* 光标左移,首绕末 */
|
||
}
|
||
ui_content_page_invalidate();
|
||
return true;
|
||
case LV_KEY_RIGHT:
|
||
if (sel == SN_POLL_ROW) {
|
||
sn_adjust_poll((int16_t)SN_POLL_BIG_STEP);
|
||
}
|
||
else {
|
||
s_cursor = (uint8_t)((s_cursor + 1u) % 4u); /* 光标右移,末绕首 */
|
||
}
|
||
ui_content_page_invalidate();
|
||
return true;
|
||
case LV_KEY_ENTER:
|
||
s_editing = 0u; /* 确认(值已在 RAM 副本);退出编辑 */
|
||
ui_content_page_invalidate();
|
||
return true;
|
||
default:
|
||
return false; /* ESC 框架恒关页;其余键落内置忽略 */
|
||
}
|
||
}
|
||
|
||
if (key == LV_KEY_ENTER && ui_content_page_is_editable() != false) {
|
||
if (sel == SN_EN_ROW) {
|
||
sn_toggle_enable();
|
||
ui_content_page_invalidate();
|
||
return true;
|
||
}
|
||
if (sel >= SN_ADDR_ROW_MIN && sel <= SN_POLL_ROW) {
|
||
s_editing = 1u;
|
||
s_cursor = 0u;
|
||
ui_content_page_invalidate();
|
||
return true;
|
||
}
|
||
if (sel == SN_SAVE_ROW) {
|
||
(void)net_cfg_save_request(); /* 投递整档保存请求:属主任务写盘 */
|
||
ui_content_page_invalidate();
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:重置页面运行态
|
||
* 入口参数:无
|
||
* 返回值:无
|
||
* 限定条件:框架 ui_content_page_open 内调用(开页后一次)
|
||
* 函数说明:页面模块静态态不随屏/容器建毁自动复位,须自重置(编辑态/光标)
|
||
*/
|
||
static void sn_init_cb(void) {
|
||
s_editing = 0u;
|
||
s_cursor = 0u;
|
||
}
|
||
|
||
/*
|
||
* 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=sntp 绑定引用):
|
||
* 列表页(5 行 1 页,动态值)+ 可编辑(edit_capable,还须修改权限)
|
||
* + ← 拦截(编辑模式移字节光标/粗调周期;ESC 恒不可拦截)
|
||
*/
|
||
const content_page_def_t ui_page_sntp_desc = {
|
||
.id = "sntp",
|
||
.title = "SNTP设置",
|
||
.selectable = 1u,
|
||
.key_intercept = 1u,
|
||
.edit_capable = 1u,
|
||
.top_y = UI_LIST_TOP,
|
||
.pitch = UI_ROW_PITCH,
|
||
.band_h = UI_ROW_BAND_H,
|
||
.text_dy = 0,
|
||
.left_x = SN_LEFT_X,
|
||
.right_x = SN_RIGHT_X,
|
||
.rows_per_page = SN_ROW_COUNT,
|
||
.row_count = SN_ROW_COUNT,
|
||
.refresh = sn_refresh,
|
||
.init_cb = sn_init_cb,
|
||
.draw_cb = sn_draw_cb,
|
||
.key_cb = sn_key_cb,
|
||
};
|