/* * 模块名称:UI Page Net Cfg (网口设置内容页) * 模块功能:网口设置叶子节点的内容页数据 + 行为——5 行地址列表(IP 地址/子网 * 掩码/默认网关/MAC 地址 + 保存行,5 行每页共 1 页); * 行槽顶 = 24+14i、选中白带 x=2..156 高 13、名 x=4 / 值 x=54; * 值取自 net_cfg RAM 副本(netTask 上电自 Flash 加载并回写实际 MAC); * 编辑模式:ENTER 进选中地址行(字节光标 = 活动字节,反显标记), * ↑↓调字节 0..255(环绕、值列实时刷新)、←→移字节光标(首↔末环绕)、 * ENTER 确认退出编辑; * 权限:edit_capable=1——查看密码 → 只读(编辑/保存键无效), * 修改密码 → 可编辑;保存行 ENTER → net_cfg_save() 写盘 + * net_cfg_request_apply() 通知 netTask 立即重建网络(无需重启); * 向框架 ui_content_page 提供页面描述符(零 LVGL 对象) * 适用平台:STM32F407ZGTx / PC 模拟器(两端共用) * 作者:王建锋 * 创建日期:2026-09-10 * 修改记录: * v1.0 2026-09-10 王建锋 创建初始版本 */ /* 头文件包含区 */ #include #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 重置;约 3 B) */ static uint8_t s_editing; /* 1 = 编辑中(被编辑行 = 当前选中行) */ static uint8_t s_cursor; /* 编辑模式活动字节(IP/掩码/网关 0..3,MAC 0..5) */ static uint8_t s_saved; /* 保存行值:0 = "ENTER",1 = "OK",2 = "ERR" */ /* 函数定义区 */ /* * 函数功能:判断两区域是否相交(与 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 影子); * 保存行值随 s_saved 态显示 ENTER/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("保存")); if (s_saved == 0u) { (void)memcpy(value, "ENTER", sizeof("ENTER")); } else if (s_saved == 1u) { (void)memcpy(value, "OK", sizeof("OK")); } else { (void)memcpy(value, "ERR", sizeof("ERR")); } break; default: break; } } /* * 函数功能:调整被编辑地址行的活动字节(0..255 环绕,实时落 RAM 副本) * 入口参数:row - 地址行下标(0..3) uint16_t * delta - 调整方向(>0 = +1,<0 = -1) int8_t * 返回值:无 * 限定条件:编辑模式(s_cursor 有效) * 函数说明:只改 RAM 副本;写盘须显式经保存行 net_cfg_save */ 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(需可编辑权限);↑↓ 落框架移选、 * ← 落框架关页;只读态 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) { s_saved = (net_cfg_save() == 0) ? 1u : 2u; if (s_saved == 1u) { net_cfg_request_apply(); /* 立即应用:通知 netTask 重建网络(无需重启) */ } 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_saved = 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, };