Files
STM32F4-Base/Drivers/BSP/UI/ui_page_net_cfg.c
T

371 lines
14 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.
/*
* 模块名称:UI Page Net Cfg (网口设置内容页)
* 模块功能:网口设置叶子节点的内容页数据 + 行为——5 行地址列表(IP 地址/子网
* 掩码/默认网关/MAC 地址 + 保存行,5 行每页共 1 页);
* 行槽顶 = 24+14i、选中白带 x=2..156 高 13、名 x=4 / 值 x=54;
* 值取自 net_cfg RAM 副本(defaultTask 上电自 Flash 加载;
* netTask 回写实际 MAC 影子);
* 编辑模式:ENTER 进选中地址行(字节光标 = 活动字节,反显标记),
* ↑↓调字节 0..255(环绕、值列实时刷新)、←→移字节光标(首↔末环绕)、
* ENTER 确认退出编辑;
* 权限:edit_capable=1——查看密码 → 只读(编辑/保存键无效),
* 修改密码 → 可编辑;保存行 ENTER → 投递保存请求
* (net_cfg_save_request;属主任务 defaultTask 写盘并立即应用
* 重建网络,无需重启);保存行值随交接状态显示
* ENTER/WAIT/OK/ERR;
* 向框架 ui_content_page 提供页面描述符(零 LVGL 对象)
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-09-10
* 修改记录:
* v1.0 2026-09-10 王建锋 创建初始版本
* v1.1 2026-09-15 王建锋 保存改异步交接:投递请求 → 属主任务写盘/应用;
* 保存行随交接状态显示 ENTER/WAIT/OK/ERR
*/
/* 头文件包含区 */
#include <string.h>
#include "ui_page_net_cfg.h"
#include "net_cfg.h"
/* 私有宏定义区 */
/*
* 布局常量(设备像素;1bpp 屏 160x160):
* 5 行共 1 页(行槽顶 y = 24+14i,末行止于 y=93,与页脚 y=143 无冲突);
* 名 x=4 / 值 x=54(值列须 ≥54:4 汉字名占 48px + 间隙;值列宽——
* IP/掩码/网关 11 ASCII 字符 = 66px 止于 119,MAC 17 字符(冒号分隔)= 102px 止于 155,
* 均在选中白带 x=2..156 内)
*/
#define NC_ROW_COUNT 5u /* 4 地址行 + 1 保存行 */
#define NC_ADDR_ROW_MAX 4u /* 地址行范围 0..3(IP/掩码/网关/MAC) */
#define NC_SAVE_ROW 4u /* 保存行下标 */
#define NC_TOP_Y 24 /* 首行槽顶 y */
#define NC_PITCH 14 /* 行距 */
#define NC_BAND_H 13 /* 选中白带高 */
#define NC_LEFT_X 4 /* 左列 x(名) */
#define NC_RIGHT_X 54 /* 右列 x(值) */
#define NC_ROW_BUF_LEN 32u /* 行缓冲长度(与框架 CP_ROW_BUF_LEN 一致) */
/* 静态变量定义区 */
/* 页面运行态(每次开页经 init_cb 重置;约 2 B) */
static uint8_t s_editing; /* 1 = 编辑中(被编辑行 = 当前选中行) */
static uint8_t s_cursor; /* 编辑模式活动字节(IP/掩码/网关 0..3,MAC 0..5) */
/* 函数定义区 */
/*
* 函数功能:判断两区域是否相交(与 ui_content_page.c / ui_page_password.c 同实现)
* 入口参数:a - 区域 1 const lv_area_t *
* b - 区域 2 const lv_area_t *
* 返回值:true - 相交;false - 不相交
* 限定条件:无
*/
static bool nc_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);
}
/*
* 函数功能:取地址行字节数
* 入口参数:row - 地址行下标(0..3) uint16_t
* 返回值:字节数(IP/掩码/网关 = 4,MAC = 6) uint8_t
* 限定条件:row < NC_ADDR_ROW_MAX
*/
static uint8_t nc_octet_count(uint16_t row) {
return (row == 3u) ? 6u : 4u;
}
/*
* 函数功能:生成指定地址行的值串(与 refresh 值列同源,编辑光标定位与行文本一致)
* 入口参数:row - 地址行下标(0..3) uint16_t
* p_buf - 输出缓冲(≥ NC_ROW_BUF_LEN) char *
* 返回值:无
* 限定条件:p_buf 非空
* 函数说明:IP/掩码/网关 = "192.168.001.100"(每段 3 位补零定长,字节 i 恒在
* 字符 4i 起);MAC = "A4:94:B6:12:34:56"(冒号分隔,字节 i 恒在字符 3i 起)
*/
static void nc_fmt_value(uint16_t row, char *p_buf) {
const net_cfg_t *cfg = net_cfg_get();
if (row == 3u) {
uint8_t mac[6];
net_cfg_get_mac(mac);
(void)lv_snprintf(p_buf, NC_ROW_BUF_LEN, "%02X:%02X:%02X:%02X:%02X:%02X",
(unsigned)mac[0], (unsigned)mac[1], (unsigned)mac[2],
(unsigned)mac[3], (unsigned)mac[4], (unsigned)mac[5]);
return;
}
{
const uint8_t *field = (row == 0u) ? cfg->ip
: ((row == 1u) ? cfg->mask : cfg->gw);
net_cfg_fmt_ipv4(field, p_buf, NC_ROW_BUF_LEN);
}
}
/*
* 函数功能:动态行刷新(逐行取名/值)
* 入口参数:row - 行下标(0..4) uint16_t
* name - 名缓冲(≥ NC_ROW_BUF_LEN,框架栈缓冲) char *
* value - 值缓冲(≥ NC_ROW_BUF_LEN,框架栈缓冲) char *
* 返回值:无
* 限定条件:页面打开(框架绘制链内逐行调用)
* 函数说明:地址行值取 net_cfg RAM 副本(MAC 全零时显示芯片实际 MAC 影子);
* 保存行值随保存交接状态(net_cfg_save_state)显示
* ENTER/WAIT/OK/ERR
*/
static void nc_refresh(uint16_t row, char *name, char *value) {
const net_cfg_t *cfg = net_cfg_get();
name[0] = '\0';
value[0] = '\0';
switch (row) {
case 0u:
(void)memcpy(name, "IP地址", sizeof("IP地址"));
net_cfg_fmt_ipv4(cfg->ip, value, NC_ROW_BUF_LEN);
break;
case 1u:
(void)memcpy(name, "子网掩码", sizeof("子网掩码"));
net_cfg_fmt_ipv4(cfg->mask, value, NC_ROW_BUF_LEN);
break;
case 2u:
(void)memcpy(name, "默认网关", sizeof("默认网关"));
net_cfg_fmt_ipv4(cfg->gw, value, NC_ROW_BUF_LEN);
break;
case 3u:
(void)memcpy(name, "MAC地址", sizeof("MAC地址"));
nc_fmt_value(row, value);
break;
case 4u:
(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;
}
}
/*
* 函数功能:调整被编辑地址行的活动字节(0..255 环绕,实时落 RAM 副本)
* 入口参数:row - 地址行下标(0..3) uint16_t
* delta - 调整方向(>0 = +1,<0 = -1) int8_t
* 返回值:无
* 限定条件:编辑模式(s_cursor 有效)
* 函数说明:只改 RAM 副本;写盘须显式经保存行投递 net_cfg_save_request
*/
static void nc_adjust_octet(uint16_t row, int8_t delta) {
net_cfg_t cfg = *net_cfg_get();
uint8_t *field = (row == 0u) ? cfg.ip
: ((row == 1u) ? cfg.mask : ((row == 2u) ? cfg.gw : cfg.mac));
field[s_cursor] = (uint8_t)((field[s_cursor] + ((delta > 0) ? 1u : 255u)) % 256u);
net_cfg_set(&cfg);
}
/*
* 函数功能:页面自绘(编辑模式:选中行值内活动字节反显)
* 入口参数:layer - 绘制层 lv_layer_t *
* clip - 重绘区 const lv_area_t *
* 返回值:无
* 限定条件:页面打开(框架绘制链末尾调用)
* 函数说明:非编辑模式无附加绘制(行/带/页脚全由框架);编辑模式在选中行白带
* 内对活动字节区域反显(1bpp:white=暗底 black=亮字),位置按文本
* 步进宽实测定位(ASCII 步进 6px/字符,与行文本布局一致)
*/
static void nc_draw_cb(lv_layer_t *layer, const lv_area_t *clip) {
char value[NC_ROW_BUF_LEN];
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 >= NC_ADDR_ROW_MAX) {
return; /* 防御:保存行无编辑模式 */
}
nc_fmt_value(sel, value);
start = (uint32_t)s_cursor * ((sel == 3u) ? 3u : 4u); /* 字节 i 字符偏移:IP/掩码/网关 4i,MAC 3i(冒号分隔) */
seg_len = (sel == 3u) ? 2u : 3u;
slot_y = NC_TOP_Y + (int32_t)sel * NC_PITCH;
x0 = NC_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 + NC_BAND_H - 1;
if (!nc_areas_intersect(&box, clip)) {
return; /* 编辑光标不在重绘区:跳过(culling) */
}
/* 反显:黑底白字(与选中行白带黑字相反,方能在带内凸显活动字节;
* 1bpp:black=暗底 white=亮字) */
lv_draw_rect_dsc_t fd;
lv_draw_rect_dsc_init(&fd);
fd.base.layer = layer;
fd.bg_color = lv_color_black();
fd.bg_opa = LV_OPA_COVER;
lv_draw_rect(layer, &fd, &box);
{
lv_draw_label_dsc_t ld;
char seg[4]; /* 段文本:IP/掩码/网关 3 字符、MAC 2 字符,+ '\0' */
/* 只绘制"段文本"本身:若传整串(value + start),LVGL 在未置 EXPAND 标志时
* 按 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 = lv_color_white();
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 环绕),←→移字节光标(首↔末
* 环绕;← 键经框架拦截标志转入本函数),ENTER 确认退出编辑
* (值已实时落 RAM 副本);ESC 恒关页(框架保证,未保存调整留在
* RAM,下次开页重显、不写盘、重启失效)
* 2. 正常模式:ENTER 地址行 → 进编辑模式(需可编辑权限);ENTER
* 保存行 → 投递保存请求 net_cfg_save_request(需可编辑权限;
* 写盘与"立即应用"由属主任务 defaultTask 执行,保存行随交接
* 状态显示 WAIT/OK/ERR);↑↓ 落框架移选、← 落框架关页;
* 只读态 ENTER 一律忽略
*/
static bool nc_key_cb(lv_key_t key) {
uint16_t sel = ui_content_page_selected_row();
if (s_editing != 0u) {
uint8_t n;
if (sel >= NC_ADDR_ROW_MAX) {
s_editing = 0u; /* 防御:编辑中选中行越界(编辑态移选被拦截,不应发生) */
return false;
}
n = nc_octet_count(sel);
switch (key) {
case LV_KEY_UP:
nc_adjust_octet(sel, 1);
ui_content_page_invalidate();
return true;
case LV_KEY_DOWN:
nc_adjust_octet(sel, -1);
ui_content_page_invalidate();
return true;
case LV_KEY_LEFT:
s_cursor = (uint8_t)((s_cursor + n - 1u) % n); /* 光标左移,首绕末 */
ui_content_page_invalidate();
return true;
case LV_KEY_RIGHT:
s_cursor = (uint8_t)((s_cursor + 1u) % n); /* 光标右移,末绕首 */
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 < NC_ADDR_ROW_MAX) {
s_editing = 1u;
s_cursor = 0u;
ui_content_page_invalidate();
return true;
}
if (sel == NC_SAVE_ROW) {
(void)net_cfg_save_request(); /* 投递保存请求:属主任务写盘并立即应用 */
ui_content_page_invalidate();
return true;
}
}
return false;
}
/*
* 函数功能:重置页面运行态
* 入口参数:无
* 返回值:无
* 限定条件:框架 ui_content_page_open 内调用(开页后一次)
* 函数说明:页面模块静态态不随屏/容器建毁自动复位,须自重置
* (编辑态/光标/保存提示)
*/
static void nc_init_cb(void) {
s_editing = 0u;
s_cursor = 0u;
}
/*
* 页面描述符(外部链接:框架描述符表 s_defs[] 直接引用):
* 列表页(5 行 1 页,动态值)+ 可编辑(edit_capable,还须修改权限)
* + ← 拦截(编辑模式移字节光标;ESC 恒不可拦截)
*/
const content_page_def_t ui_page_net_cfg_desc = {
.title = "网口设置",
.selectable = 1u,
.key_intercept = 1u,
.edit_capable = 1u,
.top_y = NC_TOP_Y,
.pitch = NC_PITCH,
.band_h = NC_BAND_H,
.text_dy = 0,
.left_x = NC_LEFT_X,
.right_x = NC_RIGHT_X,
.rows_per_page = NC_ROW_COUNT,
.row_count = NC_ROW_COUNT,
.refresh = nc_refresh,
.init_cb = nc_init_cb,
.draw_cb = nc_draw_cb,
.key_cb = nc_key_cb,
};