增加网络配置界面

This commit is contained in:
2026-09-10 11:36:33 +08:00
parent 856b6f6426
commit 2b80d34e74
44 changed files with 2488 additions and 332 deletions
+10 -8
View File
@@ -6,6 +6,8 @@
#include <stdbool.h>
#include <ctype.h>
#include "FreeRTOS.h"
#include "lftpd.h"
#include "private/lftpd_status.h"
#include "private/lftpd_inet.h"
@@ -222,14 +224,14 @@ static int cmd_cwd(lftpd_client_t *client, const char *arg)
int is_dir;
if (lftpd_io_stat(path, &size, &is_dir) != 0 || !is_dir) {
send_simple_response(client->socket, 550, STATUS_550);
free(path);
vPortFree(path);
return -1;
}
}
strncpy(client->directory, path, sizeof(client->directory) - 1);
client->directory[sizeof(client->directory) - 1] = '\0';
free(path);
vPortFree(path);
send_simple_response(client->socket, 250, STATUS_250);
return 0;
@@ -248,18 +250,18 @@ static int cmd_dele(lftpd_client_t *client, const char *arg)
int is_dir;
if (lftpd_io_stat(path, &size, &is_dir) != 0) {
send_simple_response(client->socket, 550, STATUS_550);
free(path);
vPortFree(path);
return -1;
}
if (is_dir) {
send_simple_response(client->socket, 550, STATUS_550);
free(path);
vPortFree(path);
return -1;
}
lftpd_io_unlink(path);
free(path);
vPortFree(path);
send_simple_response(client->socket, 250, STATUS_250);
return 0;
}
@@ -459,7 +461,7 @@ static int cmd_retr(lftpd_client_t *client, const char *arg)
lftpd_log_error("DOWNLOAD '%s' FAILED", path);
send_simple_response(client->socket, 450, STATUS_450);
}
free(path);
vPortFree(path);
return 0;
}
@@ -481,7 +483,7 @@ static int cmd_size(lftpd_client_t *client, const char *arg)
send_simple_response(client->socket, 550, STATUS_550);
}
free(path);
vPortFree(path);
return 0;
}
@@ -518,7 +520,7 @@ static int cmd_stor(lftpd_client_t *client, const char *arg)
lftpd_log_error("UPLOAD '%s' FAILED", path);
send_simple_response(client->socket, 450, STATUS_450);
}
free(path);
vPortFree(path);
return 0;
}
+10 -8
View File
@@ -3,6 +3,8 @@
#include <string.h>
#include <ctype.h>
#include "FreeRTOS.h"
#include "ff.h"
#include "private/lftpd_io.h"
@@ -41,19 +43,19 @@ char *lftpd_io_canonicalize_path(const char *base, const char *name)
char *path;
if (name[0] == '/') {
path = (char *)malloc(strlen(name) + 1);
path = (char *)pvPortMalloc(strlen(name) + 1);
if (path == NULL) return NULL;
strcpy(path, name);
} else {
size_t len = strlen(base) + 1 + strlen(name) + 1;
path = (char *)malloc(len);
path = (char *)pvPortMalloc(len);
if (path == NULL) return NULL;
snprintf(path, len, "%s/%s", base, name);
}
char *abs_path = (char *)malloc(strlen(path) + 2);
char *abs_path = (char *)pvPortMalloc(strlen(path) + 2);
if (abs_path == NULL) {
free(path);
vPortFree(path);
return NULL;
}
abs_path[0] = '\0';
@@ -83,7 +85,7 @@ char *lftpd_io_canonicalize_path(const char *base, const char *name)
*p = saved;
}
free(path);
vPortFree(path);
if (strlen(abs_path) == 0) {
strcpy(abs_path, "/");
@@ -186,13 +188,13 @@ int lftpd_io_unlink(const char *path)
void *lftpd_io_opendir(const char *path)
{
DIR *dp = (DIR *)malloc(sizeof(DIR));
DIR *dp = (DIR *)pvPortMalloc(sizeof(DIR));
if (dp == NULL) {
return NULL;
}
const char *fpath = to_fatfs_path(path);
if (f_opendir(dp, fpath) != FR_OK) {
free(dp);
vPortFree(dp);
return NULL;
}
return (void *)dp;
@@ -230,6 +232,6 @@ void lftpd_io_closedir(void *dp)
{
if (dp != NULL) {
f_closedir((DIR *)dp);
free(dp);
vPortFree(dp);
}
}
+30 -4
View File
@@ -11,6 +11,7 @@
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#include "FreeRTOS.h"
#include "task.h"
@@ -69,11 +70,27 @@ static int net_relisten_locked(net_sock_t *p_sock);
static int net_accept_locked(int sockfd, struct net_sockaddr *addr, int *addrlen);
static int net_connect_locked(int sockfd, const struct net_sockaddr *addr, int addrlen);
/*
* 函数功能:判断 6 字节 MAC 是否全零
* 入口参数:p_mac - MAC 指针(不可为 NULL) const uint8_t *
* 返回值:true - 全零(= 使用芯片内置 MAC);false - 非全零
* 限定条件:p_mac 非空
*/
static bool s_mac_is_zero(const uint8_t *p_mac) {
for (int i = 0; i < 6; i++) {
if (p_mac[i] != 0u) {
return false;
}
}
return true;
}
/*
* 函数功能:网络子系统初始化(配置 CH395F + 启用多连接模式)
* 入口参数:ip - IP 地址字符串 "192.168.1.100",NULL 使用 DHCP 自动获取 const char *
* mask - 子网掩码字符串 "255.255.255.0",NULL 使用默认值 const char *
* gateway - 网关地址字符串 "192.168.1.1",NULL 使用默认值 const char *
* mac - MAC 地址(6 字节,低字节在前),NULL/全零使用芯片内置 const uint8_t *
* 返回值:0 - 初始化成功 int
* -1 - 初始化失败(芯片检测/复位/协议栈初始化失败) int
* 限定条件:SPI2 已正确初始化;netTask 尚未启动
@@ -81,8 +98,10 @@ static int net_connect_locked(int sockfd, const struct net_sockaddr *addr, int a
* 2. 启用 TCP Server 多连接模式(bit1=1),设置 TCP MSS、重传参数、KeepAlive 参数
* 3. 配置 IP/网关/子网掩码,或启用 DHCP
* 4. 初始化 CH395F 协议栈并使能 PING 响应
* 5. mac 非空且非全零:初始化成功后设置 MAC(写芯片 EEPROM,
* 每上电重放同值幂等)
*/
int net_init(const char *ip, const char *mask, const char *gateway) {
int net_init(const char *ip, const char *mask, const char *gateway, const uint8_t *mac) {
ch395f_status_t ret;
DBG_INFO("net_init: start");
@@ -155,6 +174,13 @@ int net_init(const char *ip, const char *mask, const char *gateway) {
return -1;
}
/* 设置 MAC(须在协议栈初始化之后,限定条件"芯片已初始化";写芯片 EEPROM,
* 每上电重放同值幂等);NULL 或全零 = 保持芯片内置 MAC */
if (mac != NULL && s_mac_is_zero(mac) == false) {
ch395f_set_mac_addr((uint8_t *)mac);
DBG_INFO("net_init: set MAC");
}
/* 多 Socket 模式:为 Socket 0~7 各分配独立收发缓冲(必须在 OPEN_SOCKET 之前)
* 手册(§5.45):CH395F 内部缓冲共 48 块 × 512B = 24KB;芯片上电默认仅给 Socket 0~3
* 分配(每 Socket 接收 8 块/发送 4 块),Socket 4~7 为 NULL,故用到 8 个 Socket 必须重分配。
@@ -520,7 +546,7 @@ int net_listen_locked(int sockfd) {
break; /* 打开成功,退出重试 */
}
ch395f_close_socket(sockfd); /* 打开失败先关闭,再延时后重试 */
HAL_Delay(500);
osDelay(500);
}
/* 3 次仍失败,返回错误 */
if (status != CH395F_ERR_SUCCESS) {
@@ -795,7 +821,7 @@ static int net_connect_locked(int sockfd, const struct net_sockaddr *addr, int a
break; /* 打开成功,退出重试 */
}
ch395f_close_socket(sockfd); /* 打开失败先关闭,再延时后重试 */
HAL_Delay(500);
osDelay(500);
}
if (status != CH395F_ERR_SUCCESS) {
@@ -1020,7 +1046,7 @@ static int net_relisten_locked(net_sock_t *p_sock) {
break;
}
ch395f_close_socket((uint8_t)sockfd); /* 打开失败先关闭,再延时后重试 */
HAL_Delay(500);
osDelay(500);
}
if (status != CH395F_ERR_SUCCESS) {
s_net_errno = NET_ERR_BUSY;
+5 -2
View File
@@ -53,11 +53,14 @@ typedef struct {
* 入口参数:ip - 本地 IP 地址字符串 "192.168.1.100",NULL 使用 DHCP
* mask - 子网掩码字符串 "255.255.255.0",NULL 使用默认
* gateway - 网关地址字符串 "192.168.1.1",NULL 使用默认
* mac - MAC 地址(6 字节,低字节在前),NULL 或全零使用芯片内置
* 返回值:0 成功,-1 失败
* 限定条件:SPI2 已正确初始化
* 函数说明:必须在所有网络操作之前调用
* 函数说明:必须在所有网络操作之前调用;mac 非空且非全零时于协议栈
* 初始化成功后设置(ch395f_set_mac_addr 写芯片 EEPROM,
* 每上电重放同值幂等)
*/
int net_init(const char *ip, const char *mask, const char *gateway);
int net_init(const char *ip, const char *mask, const char *gateway, const uint8_t *mac);
/*
* 函数功能:轮询所有 Socket 状态(必须在主循环中调用)
+260
View File
@@ -0,0 +1,260 @@
/*
* 模块名称:Network Configuration Core (配置核心)
* 模块功能:RAM 配置副本 + 工厂默认 + 加载/保存编排 + 实际 MAC 影子 +
* IPv4 字符串转换;磁盘格式 magic 'NCFG' + ver + CRC-16(MODBUS)
* + 18B 字段(两端小端一致,sizeof 含尾部对齐填充)
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-09-10
* 修改记录:
* v1.0 2026-09-10 王建锋 创建初始版本
*/
/* 头文件包含区 */
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#include "net_cfg.h"
#include "net_cfg_store.h"
#define DBG_TAG "[NET_CFG]"
#include "dbg_log.h"
/* 私有宏定义区 */
#define NET_CFG_MAGIC 0x4647434Eu /* 'NCFG'(文件字节序 N,C,G,F;小端装载) */
#define NET_CFG_VER 1u /* 磁盘格式版本 */
/* 工厂默认值(单一来源:s_cfg 初值与 s_cfg_defaults 共用,避免两处漂移) */
#define NET_CFG_DEF_IP { 192u, 168u, 1u, 100u }
#define NET_CFG_DEF_MASK { 255u, 255u, 255u, 0u }
#define NET_CFG_DEF_GW { 192u, 168u, 1u, 1u }
#define NET_CFG_DEF_MAC { 0u, 0u, 0u, 0u, 0u, 0u }
/* 磁盘格式(两端布局一致:u32 4 对齐;字段区 18B 连续,CRC 覆盖之) */
typedef struct {
uint32_t magic; /* NET_CFG_MAGIC */
uint16_t ver; /* 格式版本 */
uint16_t crc; /* CRC-16/MODBUS(覆盖 ip..mac 共 18B) */
uint8_t ip[4];
uint8_t mask[4];
uint8_t gw[4];
uint8_t mac[6];
} net_cfg_blob_t;
/* 静态变量定义区 */
/* RAM 配置副本:以工厂默认值静态初始化(落 .data),自启动即有效——
* 不依赖 net_cfg_load 是否被调用(load 仅在文件有效时覆盖) */
static net_cfg_t s_cfg = {
.ip = NET_CFG_DEF_IP,
.mask = NET_CFG_DEF_MASK,
.gw = NET_CFG_DEF_GW,
.mac = NET_CFG_DEF_MAC,
};
static uint8_t s_active_mac[6]; /* 芯片实际 MAC 影子(netTask 回写) */
static uint8_t s_active_valid; /* 影子有效标志(0 = 尚未回写) */
/* 函数定义区 */
/*
* 函数功能:CRC-16/MODBUS(无查表逐位算法)
* 入口参数:p_data - 数据指针 const uint8_t *
* len - 数据长度 size_t
* 返回值:CRC-16 值 uint16_t
* 限定条件:p_data 非空(len 可为 0,返回初始值 0xFFFF)
* 函数说明:文件私有(双端共享、避免跨模块依赖:模拟器不编译 App/util;
* 数据量 ≤32B,性能无关)
*/
static uint16_t s_crc16_modbus(const uint8_t *p_data, size_t len) {
uint16_t crc = 0xFFFFu;
for (size_t i = 0u; i < len; i++) {
crc = (uint16_t)(crc ^ (uint16_t)p_data[i]);
for (int b = 0; b < 8; b++) {
if (crc & 0x0001u) {
crc = (uint16_t)((crc >> 1) ^ 0xA001u);
}
else {
crc = (uint16_t)(crc >> 1);
}
}
}
return crc;
}
/*
* 函数功能:填充工厂默认配置
* 入口参数:p_cfg - 输出结构指针(不可为 NULL) net_cfg_t *
* 返回值:无
* 限定条件:p_cfg 非空
* 函数说明:与既有硬编码组网值一致(192.168.1.100/24,gw .1,MAC 全零
* = 芯片内置),保证首启/存储异常时行为与旧版本相同
*/
static void s_cfg_defaults(net_cfg_t *p_cfg) {
const net_cfg_t def = {
.ip = NET_CFG_DEF_IP,
.mask = NET_CFG_DEF_MASK,
.gw = NET_CFG_DEF_GW,
.mac = NET_CFG_DEF_MAC,
};
*p_cfg = def;
}
/*
* 函数功能:判断 6 字节 MAC 是否全零
* 入口参数:p_mac - MAC 指针(不可为 NULL) const uint8_t *
* 返回值:true - 全零;false - 非全零
* 限定条件:p_mac 非空
*/
static bool s_mac_is_zero(const uint8_t *p_mac) {
for (int i = 0; i < 6; i++) {
if (p_mac[i] != 0u) {
return false;
}
}
return true;
}
/*
* 函数功能:从存储加载网络配置到 RAM 副本
* 入口参数:无
* 返回值:恒 0(RAM 恒有效:文件缺失/CRC 错回退工厂默认) int
* 限定条件:MCU 端应在存储就绪后调用;未就绪时文件读失败自动回退
* 函数说明:见 net_cfg.h 同名函数说明
*/
int net_cfg_load(void) {
net_cfg_blob_t blob;
if (net_cfg_store_read((uint8_t *)&blob, sizeof(blob)) != 0) {
/* 文件缺失/不可读(首启、存储未就绪):工厂默认 */
s_cfg_defaults(&s_cfg);
DBG_INFO("load: no valid file, factory defaults");
return 0;
}
if (blob.magic != NET_CFG_MAGIC || blob.ver != NET_CFG_VER
|| s_crc16_modbus(blob.ip, 18u) != blob.crc) {
/* 格式损坏:工厂默认并留痕(不让坏配置带崩网络) */
s_cfg_defaults(&s_cfg);
DBG_ERROR("load: blob invalid (magic/ver/crc), factory defaults");
return 0;
}
memcpy(s_cfg.ip, blob.ip, 4u);
memcpy(s_cfg.mask, blob.mask, 4u);
memcpy(s_cfg.gw, blob.gw, 4u);
memcpy(s_cfg.mac, blob.mac, 6u);
DBG_INFO("load: OK ip=%u.%u.%u.%u",
(unsigned)s_cfg.ip[0], (unsigned)s_cfg.ip[1],
(unsigned)s_cfg.ip[2], (unsigned)s_cfg.ip[3]);
return 0;
}
/*
* 函数功能:保存当前 RAM 配置到存储
* 入口参数:无
* 返回值:0 - 成功;-1 - 写失败 int
* 限定条件:MCU 端应在存储就绪后调用
* 函数说明:组装磁盘格式经后端写盘;下次上电生效
*/
int net_cfg_save(void) {
net_cfg_blob_t blob;
memcpy(blob.ip, s_cfg.ip, 4u);
memcpy(blob.mask, s_cfg.mask, 4u);
memcpy(blob.gw, s_cfg.gw, 4u);
memcpy(blob.mac, s_cfg.mac, 6u);
blob.magic = NET_CFG_MAGIC;
blob.ver = NET_CFG_VER;
blob.crc = s_crc16_modbus(blob.ip, 18u);
if (net_cfg_store_write((const uint8_t *)&blob, sizeof(blob)) != 0) {
DBG_ERROR("save: store write FAILED");
return -1;
}
DBG_INFO("save: OK (effective at next power-on)");
return 0;
}
/*
* 函数功能:取 RAM 配置副本(只读指针)
* 入口参数:无
* 返回值:RAM 配置副本指针(恒非空) const net_cfg_t *
* 限定条件:无
*/
const net_cfg_t *net_cfg_get(void) {
return &s_cfg;
}
/*
* 函数功能:更新 RAM 配置副本(不写盘)
* 入口参数:p_cfg - 新配置指针(不可为 NULL) const net_cfg_t *
* 返回值:无
* 限定条件:p_cfg 非空
*/
void net_cfg_set(const net_cfg_t *p_cfg) {
if (p_cfg == NULL) {
return;
}
s_cfg = *p_cfg;
}
/*
* 函数功能:回写芯片实际 MAC(影子区)
* 入口参数:p_mac - 芯片读回的实际 MAC(不可为 NULL) const uint8_t *
* 返回值:无
* 限定条件:net_init 成功后由 netTask 上下文调用
*/
void net_cfg_set_active_mac(const uint8_t *p_mac) {
if (p_mac == NULL) {
return;
}
memcpy(s_active_mac, p_mac, 6u);
s_active_valid = 1u;
}
/*
* 函数功能:取显示用 MAC
* 入口参数:p_out - 6 字节输出缓冲(不可为 NULL) uint8_t *
* 返回值:无
* 限定条件:p_out 非空
* 函数说明:存储非全零 → 存储值;否则芯片实际(已回写);否则全零
*/
void net_cfg_get_mac(uint8_t *p_out) {
if (p_out == NULL) {
return;
}
if (!s_mac_is_zero(s_cfg.mac)) {
memcpy(p_out, s_cfg.mac, 6u);
}
else if (s_active_valid != 0u) {
memcpy(p_out, s_active_mac, 6u);
}
else {
memset(p_out, 0, 6u);
}
}
/*
* 函数功能:4 字节 IPv4 格式化为点分十进制字符串(每段 3 位补零)
* 入口参数:p_ip - 4 字节 IP(不可为 NULL) const uint8_t *
* p_buf - 输出缓冲(不可为 NULL) char *
* len - 缓冲长度 size_t
* 返回值:无
* 限定条件:p_ip/p_buf 非空;len ≥ 16
* 函数说明:"192.168.001.100"(定长 11 字符,UI 编辑光标按固定偏移定位字节)
*/
void net_cfg_fmt_ipv4(const uint8_t *p_ip, char *p_buf, size_t len) {
if (p_ip == NULL || p_buf == NULL || len == 0u) {
return;
}
(void)snprintf(p_buf, len, "%03u.%03u.%03u.%03u",
(unsigned)p_ip[0], (unsigned)p_ip[1],
(unsigned)p_ip[2], (unsigned)p_ip[3]);
}
+116
View File
@@ -0,0 +1,116 @@
/*
* 模块名称:Network Configuration (NET CFG)
* 模块功能:网络参数配置(IP/掩码/网关/MAC)——双端共享核心:RAM 配置副本 +
* 工厂默认 + 加载/保存编排 + 实际 MAC 影子;存储后端经 net_cfg_store.h
* 抽象(MCU = NAND 上 FatFS 文件 /cfg/netcfg.bin,模拟器 = 本地文件
* sim_netcfg.bin);磁盘格式 = magic + ver + CRC-16 + 18B 字段;
* MAC 全零哨兵 = 不覆盖芯片内置 MAC;保存后下次上电生效
* (每次上电 netTask 在 net_init 前加载)
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-09-10
* 修改记录:
* v1.0 2026-09-10 王建锋 创建初始版本
*/
#ifndef NET_CFG_H
#define NET_CFG_H
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/*
* 网络参数配置(RAM 副本)
* mac 全零哨兵:不设置芯片 MAC(用芯片内置值,存于芯片 EEPROM)
*/
typedef struct {
uint8_t ip[4]; /* IP 地址(工厂默认 192.168.1.100) */
uint8_t mask[4]; /* 子网掩码(工厂默认 255.255.255.0) */
uint8_t gw[4]; /* 默认网关(工厂默认 192.168.1.1) */
uint8_t mac[6]; /* MAC 地址(全零 = 芯片内置 MAC) */
} net_cfg_t;
/*
* 函数功能:从存储加载网络配置到 RAM 副本
* 入口参数:无
* 返回值:恒 0(RAM 恒有效:文件缺失/损坏回退工厂默认) int
* 限定条件:MCU 端应在存储就绪(SYS_EVT_STORAGE_READY)后调用;
* 未就绪时文件读失败,自动回退工厂默认(不阻塞网络启动)
* 函数说明: 1. 文件缺失(首启/未保存过)→ 工厂默认
* 2. magic/ver/CRC 错误 → 工厂默认并留痕(不让坏配置带崩网络)
* 3. 校验通过 → 装入 RAM 副本
*/
int net_cfg_load(void);
/*
* 函数功能:保存当前 RAM 配置到存储(Flash 持久化)
* 入口参数:无
* 返回值:0 - 成功;-1 - 写失败 int
* 限定条件:MCU 端应在存储就绪后调用
* 函数说明:组装磁盘格式(magic+ver+CRC-16+18B 字段)经后端写盘;
* 下次上电生效(netTask 在 net_init 前加载)
*/
int net_cfg_save(void);
/*
* 函数功能:取 RAM 配置副本(只读指针)
* 入口参数:无
* 返回值:RAM 配置副本指针(恒非空) const net_cfg_t *
* 限定条件:无
* 函数说明:netTask(启动期读取组网)与 UI 页(显示/编辑)共用
*/
const net_cfg_t *net_cfg_get(void);
/*
* 函数功能:更新 RAM 配置副本(不写盘)
* 入口参数:p_cfg - 新配置指针(不可为 NULL) const net_cfg_t *
* 返回值:无
* 限定条件:p_cfg 非空;写盘须另行调 net_cfg_save
* 函数说明:UI 编辑页调整字段后经此落 RAM 副本
*/
void net_cfg_set(const net_cfg_t *p_cfg);
/*
* 函数功能:回写芯片实际 MAC(影子区,供 UI 显示)
* 入口参数:p_mac - 芯片读回的实际 MAC(不可为 NULL) const uint8_t *
* 返回值:无
* 限定条件:net_init 成功后由 netTask 上下文调用
* 函数说明:CH395F SPI 归 netTask 独占(中断/轮询集中处理);UI 页不可直接
* 调 SPI 读命令,故 netTask 初始化后读回实际 MAC 存于此供
* "网口设置"页显示(存储 MAC 全零时显示芯片内置 MAC)
*/
void net_cfg_set_active_mac(const uint8_t *p_mac);
/*
* 函数功能:取显示用 MAC
* 入口参数:p_out - 6 字节输出缓冲(不可为 NULL) uint8_t *
* 返回值:无
* 限定条件:p_out 非空
* 函数说明:存储 MAC 非全零 → 存储值;否则芯片实际(已回写时);
* 否则全零(网络未初始化/芯片内置未知)
*/
void net_cfg_get_mac(uint8_t *p_out);
/*
* 函数功能:4 字节 IPv4 格式化为点分十进制字符串(每段 3 位补零)
* 入口参数:p_ip - 4 字节 IP(不可为 NULL) const uint8_t *
* p_buf - 输出缓冲(不可为 NULL) char *
* len - 缓冲长度 size_t
* 返回值:无
* 限定条件:p_ip/p_buf 非空;len ≥ 16(输出 11 字符 + '\0')
* 函数说明:"192.168.001.100"——每段 3 位补零使字符串定长,UI 编辑光标
* 可按固定偏移(4i/3i 起始)定位字节;net_init 的 parse_ip_string
* 对前导零兼容(十进制逐段解析)
*/
void net_cfg_fmt_ipv4(const uint8_t *p_ip, char *p_buf, size_t len);
#ifdef __cplusplus
}
#endif
#endif /* NET_CFG_H */
+46
View File
@@ -0,0 +1,46 @@
/*
* 模块名称:Network Configuration Store (存储后端接口)
* 模块功能:net_cfg 加载/保存的磁盘读写抽象——每平台一份实现(互斥编译):
* MCU = NAND 上 FatFS 文件 /cfg/netcfg.bin(net_cfg_store_mcu.c);
* 模拟器 = 工作目录本地文件 sim_netcfg.bin(net_cfg_store_sim.c)
* 适用平台:STM32F407ZGTx / PC 模拟器(接口共用;两端各编一份实现)
* 作者:王建锋
* 创建日期:2026-09-10
* 修改记录:
* v1.0 2026-09-10 王建锋 创建初始版本
*/
#ifndef NET_CFG_STORE_H
#define NET_CFG_STORE_H
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/*
* 函数功能:读取配置文件
* 入口参数:p_buf - 接收缓冲(不可为 NULL) uint8_t *
* len - 文件长度(须与磁盘格式一致,net_cfg.c 的 net_cfg_blob_t) size_t
* 返回值:0 - 成功;-1 - 文件缺失/读失败 int
* 限定条件:后端存储可用(MCU:FatFS 已挂载;未挂载时实现直接返回 -1 即可)
*/
int net_cfg_store_read(uint8_t *p_buf, size_t len);
/*
* 函数功能:写配置文件(不存在则创建,存在则覆盖)
* 入口参数:p_buf - 待写数据(不可为 NULL) const uint8_t *
* len - 数据长度(须与磁盘格式一致) size_t
* 返回值:0 - 成功;-1 - 写失败 int
* 限定条件:后端存储可用
*/
int net_cfg_store_write(const uint8_t *p_buf, size_t len);
#ifdef __cplusplus
}
#endif
#endif /* NET_CFG_STORE_H */
+71
View File
@@ -0,0 +1,71 @@
/*
* 模块名称:Network Configuration Store (MCU 后端)
* 模块功能:net_cfg 存储后端——NAND 上 FatFS 文件 /cfg/netcfg.bin
* (目录 /cfg 首次写入时自动创建;卷由 defaultTask 挂载并广播
* SYS_EVT_STORAGE_READY 后方可读写)
* 适用平台:STM32F407ZGTx
* 作者:王建锋
* 创建日期:2026-09-10
* 修改记录:
* v1.0 2026-09-10 王建锋 创建初始版本
*/
/* 头文件包含区 */
#include "net_cfg_store.h"
#include "ff.h"
/* 静态变量定义区 */
static const char s_path[] = "/cfg/netcfg.bin";
/* 函数定义区 */
/*
* 函数功能:读取配置文件
* 入口参数:p_buf - 接收缓冲(不可为 NULL) uint8_t *
* len - 文件长度 size_t
* 返回值:0 - 成功;-1 - 文件缺失/读失败 int
* 限定条件:FatFS 已挂载(未挂载时 f_open 返回 FR_NO_SYSTEM,转 -1)
*/
int net_cfg_store_read(uint8_t *p_buf, size_t len) {
FIL f;
FRESULT fr;
UINT br = 0u;
if (p_buf == NULL) {
return -1;
}
fr = f_open(&f, s_path, FA_READ);
if (fr != FR_OK) {
return -1;
}
fr = f_read(&f, p_buf, (UINT)len, &br);
(void)f_close(&f);
return (fr == FR_OK && br == (UINT)len) ? 0 : -1;
}
/*
* 函数功能:写配置文件(目录不存在则创建;文件覆盖写)
* 入口参数:p_buf - 待写数据(不可为 NULL) const uint8_t *
* len - 数据长度 size_t
* 返回值:0 - 成功;-1 - 写失败 int
* 限定条件:FatFS 已挂载
*/
int net_cfg_store_write(const uint8_t *p_buf, size_t len) {
FIL f;
FRESULT fr;
UINT bw = 0u;
if (p_buf == NULL) {
return -1;
}
(void)f_mkdir("/cfg"); /* 目录已存在(FR_EXIST)不影响后续建文件 */
fr = f_open(&f, s_path, FA_WRITE | FA_CREATE_ALWAYS);
if (fr != FR_OK) {
return -1;
}
fr = f_write(&f, (const void *)p_buf, (UINT)len, &bw);
(void)f_close(&f);
return (fr == FR_OK && bw == (UINT)len) ? 0 : -1;
}
+67
View File
@@ -0,0 +1,67 @@
/*
* 模块名称:Network Configuration Store (模拟器后端)
* 模块功能:net_cfg 存储后端——模拟器工作目录本地文件 sim_netcfg.bin
* (磁盘格式与 MCU 端一致,两端可互读)
* 适用平台:PC 模拟器
* 作者:王建锋
* 创建日期:2026-09-10
* 修改记录:
* v1.0 2026-09-10 王建锋 创建初始版本
*/
/* 头文件包含区 */
#include <stdio.h>
#include "net_cfg_store.h"
/* 静态变量定义区 */
static const char s_path[] = "sim_netcfg.bin";
/* 函数定义区 */
/*
* 函数功能:读取配置文件
* 入口参数:p_buf - 接收缓冲(不可为 NULL) uint8_t *
* len - 文件长度 size_t
* 返回值:0 - 成功;-1 - 文件缺失/读失败 int
* 限定条件:无
*/
int net_cfg_store_read(uint8_t *p_buf, size_t len) {
FILE *fp;
size_t n;
if (p_buf == NULL) {
return -1;
}
fp = fopen(s_path, "rb");
if (fp == NULL) {
return -1;
}
n = fread(p_buf, 1u, len, fp);
fclose(fp);
return (n == len) ? 0 : -1;
}
/*
* 函数功能:写配置文件(不存在则创建,存在则覆盖)
* 入口参数:p_buf - 待写数据(不可为 NULL) const uint8_t *
* len - 数据长度 size_t
* 返回值:0 - 成功;-1 - 写失败 int
* 限定条件:无
*/
int net_cfg_store_write(const uint8_t *p_buf, size_t len) {
FILE *fp;
size_t n;
if (p_buf == NULL) {
return -1;
}
fp = fopen(s_path, "wb");
if (fp == NULL) {
return -1;
}
n = fwrite(p_buf, 1u, len, fp);
fclose(fp);
return (n == len) ? 0 : -1;
}
+100 -16
View File
@@ -16,6 +16,14 @@
* 2026-09-09 王建锋 拆分为"框架 + 页面插件"模型(内容页架构.md):三页数据移入
* ui_page_xxx 模块,本文件只留机制 + 描述符表;绘制/按键改
* 按描述符驱动(静态表/refresh + 4 钩子),行为零变化
* 2026-09-09 王建锋 密码页(用户密钥):绘制链支持每页 title_y/"浅 V"/页脚开关;
* 按键分发支持每页 ← 拦截(ESC 恒不可拦截);新 API
* ui_content_page_invalidate(表单页触发整页重绘)
* 2026-09-09 王建锋 权限模型:open(id, perm) 记录授予权限(页面经
* ui_content_page_is_editable 查询:edit_capable 且修改权限)
* 2026-09-10 王建锋 网口设置页(node 30):新增 CONTENT_PAGE_NET_CFG 入描述符表;
* 新 API ui_content_page_selected_row(页面钩子感知选中行)+
* 描述符 init_cb(开页后调一次,页面模块重置运行态)
*/
/* 头文件包含区 */
@@ -29,6 +37,12 @@
#include "ui_page_dc.h"
#include "ui_page_password.h"
#include "ui_page_net_cfg.h"
#include "ui_pwd_def.h"
#include "ui_menu_view.h"
#include "ui_deco.h"
@@ -63,10 +77,13 @@ static const content_page_def_t *const s_defs[CONTENT_PAGE_COUNT] = {
[CONTENT_PAGE_DEVICE_INFO] = &ui_page_device_info_desc,
[CONTENT_PAGE_AC] = &ui_page_ac_desc,
[CONTENT_PAGE_DC] = &ui_page_dc_desc,
[CONTENT_PAGE_PASSWORD] = &ui_page_password_desc,
[CONTENT_PAGE_NET_CFG] = &ui_page_net_cfg_desc,
};
static content_page_id_t s_open = CONTENT_PAGE_COUNT; /* 当前打开的页(COUNT = 未打开) */
static uint16_t s_sel; /* 列表页选中行(全局下标,跨页连续) */
static uint8_t s_perm; /* 本次授予权限(ui_perm_t;开页时装载,关页重置) */
static lv_obj_t *s_scr; /* 页面屏幕(进入时建、返回时毁) */
static lv_obj_t *s_cont; /* 页面全屏容器(焦点/键入口 + 自绘载体) */
@@ -121,6 +138,19 @@ static uint16_t cp_row_total(const content_page_def_t *d) {
return d->row_count;
}
/*
* 函数功能:失效当前打开的页面容器(触发整页重绘)
* 入口参数:无
* 返回值:无
* 限定条件:页面打开时(s_cont 非空)
* 函数说明:供页面模块(表单/操作页)自身状态变化后调用;无页打开时空操作
*/
void ui_content_page_invalidate(void) {
if (s_cont != NULL) {
lv_obj_invalidate(s_cont);
}
}
/*
* 函数功能:绘制居中单行文本(标题/副标题)
* 入口参数:layer - 绘制层 lv_layer_t *
@@ -168,19 +198,22 @@ static void cp_draw_cb(lv_event_t *e) {
const content_page_def_t *d = s_defs[s_open];
lv_layer_t *layer = lv_event_get_layer(e);
/* "浅 V"装饰:共享 ui_deco 模块(与菜单根框同几何) */
ui_deco_draw(layer, &layer->_clip_area);
/* "浅 V"装饰:共享 ui_deco 模块(与菜单根框同几何);每页可关(密码页跟随实机:无装饰) */
if (d->hide_deco == 0u) {
ui_deco_draw(layer, &layer->_clip_area);
}
/* 标题(居中) */
/* 标题(居中;放置 y 每页可配,0 = 框架默认) */
{
int32_t ty = (d->title_y != 0) ? d->title_y : CP_TITLE_Y;
lv_area_t t;
t.x1 = 0;
t.x2 = CP_SCREEN_W - 1;
t.y1 = CP_TITLE_Y;
t.y2 = CP_TITLE_Y + 15;
t.y1 = ty;
t.y2 = ty + 15;
if (cp_areas_intersect(&t, &layer->_clip_area)) {
cp_draw_centered_text(layer, d->title, CP_TITLE_Y);
cp_draw_centered_text(layer, d->title, ty);
}
}
@@ -284,7 +317,7 @@ static void cp_draw_cb(lv_event_t *e) {
}
}
/* 页脚"第 X 页,共 Y 页":列表页随选中行现算,信息页按参考图固定值 */
/* 页脚"第 X 页,共 Y 页":列表页随选中行现算,信息页按参考图固定值;每页可关(密码页跟随实机) */
{
char buf[32];
uint8_t cur;
@@ -304,7 +337,7 @@ static void cp_draw_cb(lv_event_t *e) {
t.x2 = CP_SCREEN_W - 1;
t.y1 = CP_PAGE_LINE_Y;
t.y2 = CP_PAGE_LINE_Y + 15;
if (cp_areas_intersect(&t, &layer->_clip_area)) {
if (d->hide_footer == 0u && cp_areas_intersect(&t, &layer->_clip_area)) {
/* lv_snprintf 而非 snprintf:MSVC 对 printf 族做格式串扫描,中文字面量会误报 C4819 */
lv_snprintf(buf, sizeof(buf), "第 %u 页,共 %u 页", (unsigned)cur, (unsigned)pages);
@@ -332,22 +365,33 @@ static void cp_draw_cb(lv_event_t *e) {
* 入口参数:e - 事件指针 lv_event_t *
* 返回值:无
* 限定条件:容器已加入默认输入组并获焦
* 函数说明: 1. ESC/←:恒关页返回菜单(框架保证,页面钩子不可拦截)
* 2. 其余键:key_cb 钩子先调(返回 true = 页已处理,框架跳过内置)
* 3. 内置:↑/↓ 列表页移选(首尾环绕,跨页连续),窗口随页切换后重绘;
* 函数说明: 1. ESC:恒关页返回菜单(框架保证,页面钩子不可拦截)
* 2. ←:页面置拦截标志(key_intercept)时先交 key_cb(true = 页已处理不关页),
* 否则关页返回(密码页光标 ←/→ 即由此处理)
* 3. 其余键:key_cb 钩子先调(返回 true = 页已处理,框架跳过内置)
* 4. 内置:↑/↓ 列表页移选(首尾环绕,跨页连续),窗口随页切换后重绘;
* 信息页不响应;动态行数可能缩小,选中行越界时重置回首行
*/
static void cp_key_cb(lv_event_t *e) {
const content_page_def_t *d = s_defs[s_open];
lv_key_t key = (lv_key_t)lv_event_get_key(e);
/* ESC/←:关闭页面返回菜单屏(焦点/导航状态还原) */
if (key == LV_KEY_ESC || key == LV_KEY_LEFT) {
/* ESC:关闭页面返回菜单屏(焦点/导航状态还原;恒不可拦截) */
if (key == LV_KEY_ESC) {
ui_content_page_close();
return;
}
/* 页面钩子先调:true = 页已处理,框架跳过内置语义 */
/* ←:页面置拦截标志且钩子已处理则不关页(密码页光标 ←/→);否则照旧返回 */
if (key == LV_KEY_LEFT) {
if (d->key_intercept != 0u && d->key_cb != NULL && d->key_cb(key)) {
return;
}
ui_content_page_close();
return;
}
/* 其余键:页面钩子先调:true = 页已处理,框架跳过内置语义 */
if (d->key_cb != NULL && d->key_cb(key)) {
return;
}
@@ -385,14 +429,16 @@ static void cp_key_cb(lv_event_t *e) {
/*
* 函数功能:打开指定内容页(建屏 + 切屏 + 焦点置页面容器)
* 入口参数:id - 内容页 ID content_page_id_t
* 入口参数:id - 内容页 ID content_page_id_t
* perm - 本次进入授予的权限 ui_perm_t(UI_PERM_VIEW/MODIFY)
* 返回值:true - 成功;false - 失败(ID 无效/已有页面打开/无描述符/对象创建失败)
* 限定条件:ui_menu_init() 已完成(默认输入组与菜单屏存在)
* 函数说明:1. lv_obj_create(NULL) 新建独立屏幕 + 全屏容器(自绘载体,无子对象)
* 2. 容器入默认输入组并获焦后再切屏——按键立即由本模块接管
* 3. 导航状态不压层:菜单 s_depth/s_sel 原样保留,ESC 返回即还原
* 4. 记录授予权限(页面经 ui_content_page_is_editable 查询)
*/
bool ui_content_page_open(content_page_id_t id) {
bool ui_content_page_open(content_page_id_t id, uint8_t perm) {
if (id >= CONTENT_PAGE_COUNT || s_open != CONTENT_PAGE_COUNT) {
return false; /* ID 无效或已有页面打开(防御) */
}
@@ -403,6 +449,8 @@ bool ui_content_page_open(content_page_id_t id) {
return false; /* 枚举/描述符表漂移:拒开并留痕,不崩溃 */
}
s_perm = perm; /* 本次授予权限(页面可编辑性 = edit_capable 且修改权限) */
s_scr = lv_obj_create(NULL); /* parent=NULL → 新建屏幕 */
if (s_scr == NULL) {
return false; /* LVGL 池耗尽 */
@@ -424,6 +472,10 @@ bool ui_content_page_open(content_page_id_t id) {
s_open = id;
s_sel = 0u; /* 每次进入从首行开始(页面按需建毁,不跨次保留) */
if (d->init_cb != NULL) {
d->init_cb(); /* 页面模块重置运行态(编辑模式/光标/保存标志等) */
}
/* 焦点/键入口:入默认输入组并获焦(先于切屏,保证按键立即由本模块接管) */
lv_group_t *g = lv_group_get_default();
if (g != NULL) {
@@ -464,6 +516,7 @@ void ui_content_page_close(void) {
s_cont = NULL;
s_open = CONTENT_PAGE_COUNT;
s_sel = 0u;
s_perm = (uint8_t)UI_PERM_VIEW; /* 权限重置(卫生:不留上次进入的授权) */
}
/*
@@ -476,3 +529,34 @@ void ui_content_page_close(void) {
bool ui_content_page_is_open(void) {
return (s_open != CONTENT_PAGE_COUNT);
}
/*
* 函数功能:查询当前打开的页面是否可编辑
* 入口参数:无
* 返回值:true - 页面支持编辑模式(edit_capable)且被授予修改权限;false - 其余
* 限定条件:无
* 函数说明:供页面钩子(key_cb/draw_cb)决定编辑行为;权限由 open 的 perm 实参授予
*/
bool ui_content_page_is_editable(void) {
if (s_open == CONTENT_PAGE_COUNT || s_defs[s_open] == NULL) {
return false; /* 无页打开或无描述符(防御) */
}
return (s_defs[s_open]->edit_capable != 0u && s_perm == (uint8_t)UI_PERM_MODIFY);
}
/*
* 函数功能:查询当前列表页的选中行(全局下标,跨页连续)
* 入口参数:无
* 返回值:选中行下标;无页打开/非列表页 = 0 uint16_t
* 限定条件:无
* 函数说明:供页面钩子(key_cb/draw_cb)感知当前选中位置
*/
uint16_t ui_content_page_selected_row(void) {
if (s_open == CONTENT_PAGE_COUNT || s_defs[s_open] == NULL) {
return 0u;
}
if (s_defs[s_open]->selectable == 0u) {
return 0u; /* 信息页/表单页无行选中概念 */
}
return s_sel;
}
+54 -6
View File
@@ -16,6 +16,13 @@
* 2026-09-09 王建锋 拆分为"框架 + 页面插件"模型(内容页架构.md):三页数据移入
* ui_page_xxx 模块;本文件只留描述类型与 API;描述符新增
* 4 个扩展钩子(refresh/count_cb/draw_cb/key_cb)
* 2026-09-09 王建锋 密码页(用户密钥):描述符新增 title_y/hide_deco/hide_footer/key_intercept
* 4 字段 + 新 API ui_content_page_invalidate;← 键可每页拦截
* (ESC 恒不可拦截)
* 2026-09-09 王建锋 权限模型:open() 增授予权限实参(ui_perm_t,逻辑层按
* 是否过密码页/所输密码决定);描述符新增 edit_capable(页面
* 是否支持编辑模式)+ 新 API ui_content_page_is_editable
* (页面钩子据以决定编辑行为:edit_capable 且修改权限)
*/
#ifndef UI_CONTENT_PAGE_H
#define UI_CONTENT_PAGE_H
@@ -33,6 +40,8 @@ typedef enum {
CONTENT_PAGE_DEVICE_INFO = 0u, /* 装置信息(node 1):静态键值行 + 副标题,单页 */
CONTENT_PAGE_AC, /* 交流量(node 3):数据列表,24 项 / 8 项每页 */
CONTENT_PAGE_DC, /* 直流量(node 4):数据列表,3 项单页 */
CONTENT_PAGE_PASSWORD, /* 用户密钥(密码页):3 位密码,表单/操作页首个实例 */
CONTENT_PAGE_NET_CFG, /* 网口设置(node 30):地址列表 + 保存行,可编辑(修改权限) */
CONTENT_PAGE_COUNT /* 哨兵:无效 ID / 未打开 */
} content_page_id_t;
@@ -50,7 +59,12 @@ typedef struct {
const char *title; /* 标题(浅 V 区居中) */
const char *subtitle; /* 副标题;NULL = 无(仅信息页有) */
int32_t subtitle_y; /* 副标题放置 y(仅 subtitle 非空时有效) */
int32_t title_y; /* 标题放置 y;0 = 框架默认(CP_TITLE_Y) */
uint8_t hide_deco; /* 1 = 不绘"浅 V"装饰(密码页跟随实机) */
uint8_t hide_footer; /* 1 = 不绘页脚页码(密码页跟随实机:无页脚) */
uint8_t selectable; /* 1 = 列表页(↑↓移选+分页);0 = 信息页(静态行) */
uint8_t key_intercept; /* 1 = LV_KEY_LEFT 先交 key_cb(true = 页已处理不关页);ESC 恒不可拦截 */
uint8_t edit_capable; /* 1 = 本页支持编辑模式(可编辑还须 UI_PERM_MODIFY 权限) */
int32_t top_y; /* 首行槽顶 y */
int32_t pitch; /* 行距(相邻行槽顶间距) */
int32_t band_h; /* 选中白带高(0 = 无选中带) */
@@ -63,22 +77,26 @@ typedef struct {
uint8_t fixed_cur_page; /* 信息页页脚"第 X 页"固定显示值 */
uint8_t fixed_total_pages; /* 信息页页脚"共 Y 页"固定显示值 */
/* —— 扩展钩子(全可选;NULL = 纯内置行为;均运行在 uiTask 上下文)—— */
void (*refresh)(uint16_t row, char *name, char *value); /* 动态行:逐行取数(名/值) */
uint16_t (*count_cb)(void); /* 动态行数(记录页) */
void (*draw_cb)(lv_layer_t *layer, const lv_area_t *clip); /* 表单/操作页:自定义内容 */
bool (*key_cb)(lv_key_t key); /* 页面自有键;true = 页已处理 */
void (*init_cb)(void); /* 开页后调一次(页面模块重置运行态) */
void (*refresh)(uint16_t row, char *name, char *value); /* 动态行:逐行取数(名/值) */
uint16_t (*count_cb)(void); /* 动态行数(记录页) */
void (*draw_cb)(lv_layer_t *layer, const lv_area_t *clip); /* 表单/操作页:自定义内容 */
bool (*key_cb)(lv_key_t key); /* 页面自有键;true = 页已处理 */
} content_page_def_t;
/*
* 函数功能:打开指定内容页(建屏 + 切屏 + 焦点置页面容器)
* 入口参数:id - 内容页 ID content_page_id_t
* 入口参数:id - 内容页 ID content_page_id_t
* perm - 本次进入授予的权限 ui_perm_t(UI_PERM_VIEW/MODIFY;
* 由逻辑层按是否过密码页/所输密码决定,页面经
* ui_content_page_is_editable 查询)
* 返回值:true - 成功;false - 失败(ID 无效/已有页面打开/无描述符/对象创建失败)
* 限定条件:ui_menu_init() 已完成(默认输入组与菜单屏存在)
* 函数说明:1. 新建独立屏幕,全屏容器自绘全部内容(无子对象,池开销极小)
* 2. 容器入默认输入组并获焦——按键由本模块处理(↑↓移选/ESC返回/页面钩子)
* 3. 导航状态不压层:菜单 s_depth/s_sel 原样保留,ESC 返回即还原
*/
bool ui_content_page_open(content_page_id_t id);
bool ui_content_page_open(content_page_id_t id, uint8_t perm);
/*
* 函数功能:关闭当前内容页(焦点回菜单容器 + 切回菜单屏 + 销毁页面)
@@ -89,6 +107,15 @@ bool ui_content_page_open(content_page_id_t id);
*/
void ui_content_page_close(void);
/*
* 函数功能:失效当前打开的页面容器(触发整页重绘)
* 入口参数:无
* 返回值:无
* 限定条件:有页面打开时生效
* 函数说明:供页面模块(表单/操作页)自身状态变化后调用;无页打开时空操作
*/
void ui_content_page_invalidate(void);
/*
* 函数功能:查询是否有内容页打开
* 入口参数:无
@@ -98,4 +125,25 @@ void ui_content_page_close(void);
*/
bool ui_content_page_is_open(void);
/*
* 函数功能:查询当前打开的页面是否可编辑
* 入口参数:无
* 返回值:true - 页面支持编辑模式且被授予修改权限;false - 其余(无页打开/
* 页面不支持编辑/仅只读权限)
* 限定条件:无
* 函数说明:供页面钩子(key_cb/draw_cb)决定编辑行为;权限由 ui_content_page_open
* 的 perm 实参授予(逻辑层按密码页结果/无密码默认只读传入)
*/
bool ui_content_page_is_editable(void);
/*
* 函数功能:查询当前列表页的选中行(全局下标,跨页连续)
* 入口参数:无
* 返回值:选中行下标;无页打开/非列表页 = 0 uint16_t
* 限定条件:无
* 函数说明:供页面钩子(key_cb/draw_cb)感知当前选中位置(编辑模式以
* 选中行作用、绘制钩子定位选中行带)
*/
uint16_t ui_content_page_selected_row(void);
#endif /* UI_CONTENT_PAGE_H */
+176 -84
View File
@@ -1,7 +1,7 @@
/*******************************************************************************
* Size: 12 px
* Bpp: 1
* Opts: --font C:\Users\ADMINI~1\AppData\Local\Temp\simsun_regular.ttf --size 12 --bpp 1 -r 0x20-0x7E --symbols ▶三主事交作保信值制前动区压参双口启告型复字定实式强归当录息手抖护报据控操故数整无时板查检模死波流测清点界直看类系维网置自行装警讯记设询软运通遥量钟防除面:馈线化终端号件版硬校验程序日期第页共, --no-compress --format lvgl --lv-font-name ui_font_cn_12 -o D:\Code\DTU Code\STM32F4-Base\Drivers\BSP\UI\ui_font_cn_12.c
* Opts: --font C:\Users\ADMINI~1\AppData\Local\Temp\simsun_regular.ttf --size 12 --bpp 1 -r 0x20-0x7E --symbols ▶三主事交件作保信值共关制前动化区压参双口号启告地址型复子字存定实密序式强归当录息户手抖护报据控掩操故数整无日时期本板查校检模死波流测清点版用界直看码硬程端第类系线终维网置自行装警认讯记设询软运通遥量钟钥防除面页馈验默,: --no-compress --format lvgl --lv-font-name ui_font_cn_12 -o D:\Code\DTU Code\STM32F4-Base\Drivers\BSP\UI\ui_font_cn_12.c
******************************************************************************/
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
@@ -357,6 +357,11 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
0x4, 0x47, 0xff, 0x0, 0x2, 0x20, 0x82, 0x20,
0x20,
/* U+5173 "关" */
0x20, 0x82, 0x20, 0x0, 0x3f, 0xe0, 0x40, 0x8,
0x3f, 0xf8, 0x20, 0xa, 0x2, 0x20, 0x82, 0x60,
0x30,
/* U+5236 "制" */
0x10, 0x2a, 0x5, 0xf4, 0xc8, 0x9f, 0xf2, 0x22,
0x5f, 0x4a, 0xa9, 0x55, 0x2a, 0x85, 0x70, 0x88,
@@ -413,6 +418,16 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
0xc0, 0x3, 0xfe, 0x40, 0x48, 0x9, 0xff, 0x20,
0x20,
/* U+5730 "地" */
0x21, 0x4, 0x20, 0x95, 0x92, 0xdf, 0x72, 0x5a,
0x49, 0x49, 0x2b, 0x25, 0x6, 0x87, 0x10, 0x81,
0xf0,
/* U+5740 "址" */
0x20, 0x84, 0x10, 0x82, 0x12, 0x4f, 0x4e, 0x49,
0x9, 0x21, 0x24, 0x24, 0x86, 0x93, 0x12, 0x7,
0xf0,
/* U+578B "型" */
0x0, 0x2f, 0xd4, 0x92, 0x92, 0x5f, 0xea, 0x49,
0x51, 0xc, 0x3, 0x4, 0xf, 0xf8, 0x10, 0x7f,
@@ -423,11 +438,20 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
0x8f, 0xf0, 0x80, 0x3f, 0xca, 0x10, 0x3c, 0x38,
0x70,
/* U+5B50 "子" */
0x3f, 0x80, 0x20, 0x8, 0x2, 0x0, 0x41, 0xff,
0xc1, 0x0, 0x20, 0x4, 0x0, 0x80, 0x70, 0x0,
/* U+5B57 "字" */
0x8, 0x0, 0x83, 0xff, 0xc0, 0x10, 0x0, 0x7f,
0x0, 0x40, 0x10, 0xff, 0xe0, 0x80, 0x10, 0xe,
0x0,
/* U+5B58 "存" */
0x8, 0x1, 0x3, 0xff, 0x88, 0x2, 0x7c, 0x41,
0x18, 0x45, 0x7f, 0x21, 0x4, 0x20, 0x84, 0x11,
0x80,
/* U+5B9A "定" */
0x8, 0x0, 0x83, 0xff, 0xc0, 0x10, 0x1, 0xff,
0xc1, 0x1, 0x20, 0x27, 0xc4, 0x81, 0x50, 0x47,
@@ -438,6 +462,11 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
0x4, 0x87, 0xff, 0x4, 0x1, 0x30, 0xc1, 0x60,
0x10,
/* U+5BC6 "密" */
0x4, 0x1f, 0xfe, 0x20, 0x82, 0x85, 0x24, 0xa9,
0x63, 0xe9, 0x80, 0x4, 0x8, 0x89, 0x11, 0x3f,
0xe0,
/* U+5E8F "序" */
0x2, 0xf, 0xfd, 0x0, 0x2f, 0xe4, 0x8, 0x9a,
0x10, 0x83, 0xff, 0x42, 0x28, 0x49, 0x8, 0x43,
@@ -472,6 +501,11 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
0x90, 0x13, 0xfe, 0x4, 0xa, 0x49, 0x42, 0xc7,
0xd0,
/* U+6237 "户" */
0x4, 0x0, 0x40, 0xff, 0x90, 0x12, 0x2, 0x40,
0x4f, 0xf9, 0x1, 0x20, 0x8, 0x1, 0x0, 0x40,
0x0,
/* U+624B "手" */
0x3, 0xcf, 0x80, 0x10, 0x2, 0x7, 0xfc, 0x8,
0x1, 0x7, 0xff, 0x4, 0x0, 0x80, 0x10, 0xe,
@@ -502,6 +536,11 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
0x9a, 0xe, 0x3e, 0x41, 0x8, 0x21, 0x4, 0x67,
0xf0,
/* U+63A9 "掩" */
0x21, 0x5, 0xfc, 0x8a, 0x7a, 0x22, 0x92, 0x4f,
0x8d, 0x57, 0x3e, 0x25, 0x44, 0xf8, 0x94, 0xb0,
0x70,
/* U+64CD "操" */
0x27, 0xc4, 0x88, 0x9f, 0x78, 0x2, 0xee, 0x55,
0x4f, 0xbf, 0x8, 0x2f, 0xe4, 0x70, 0x95, 0x34,
@@ -540,6 +579,11 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
0x5e, 0x4a, 0x4f, 0xfd, 0x20, 0x25, 0x24, 0xc3,
0x30,
/* U+672C "本" */
0x4, 0x0, 0x83, 0xff, 0x82, 0x0, 0xe0, 0x2a,
0x5, 0x41, 0x24, 0x44, 0x57, 0xf4, 0x10, 0x2,
0x0,
/* U+677F "板" */
0x20, 0x64, 0xf0, 0x90, 0x7a, 0x2, 0x7e, 0xea,
0x5b, 0x4d, 0x2a, 0x25, 0x44, 0x90, 0xa5, 0x19,
@@ -599,6 +643,10 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
0x51, 0x4b, 0xaa, 0x55, 0x4a, 0x91, 0x65, 0x49,
0x10,
/* U+7528 "用" */
0x7f, 0xd1, 0x14, 0x45, 0xff, 0x44, 0x51, 0x17,
0xfd, 0x11, 0x44, 0x51, 0x18, 0x1c,
/* U+754C "界" */
0x7f, 0xc8, 0x89, 0xff, 0x22, 0x27, 0xfc, 0x8,
0x6, 0xc7, 0x7, 0x11, 0x2, 0x20, 0x84, 0x20,
@@ -613,6 +661,11 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
0xc8, 0x13, 0xfe, 0xa0, 0x47, 0xf8, 0x81, 0x1f,
0xe0,
/* U+7801 "码" */
0xf, 0xde, 0x8, 0x91, 0x12, 0x24, 0x44, 0xef,
0xf4, 0xa, 0x81, 0x5f, 0xae, 0x5, 0x40, 0x80,
0x60,
/* U+786C "硬" */
0xf7, 0xe4, 0x20, 0x9f, 0x22, 0xa7, 0x7d, 0xaa,
0x95, 0xf2, 0xc8, 0x55, 0xe, 0x61, 0x53, 0x80,
@@ -686,6 +739,11 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
0x82, 0x8f, 0xff, 0x0, 0xf, 0xf8, 0x82, 0x1f,
0xc0,
/* U+8BA4 "认" */
0x41, 0x4, 0x20, 0x4, 0x0, 0x8e, 0x10, 0x42,
0x8, 0xa1, 0x14, 0x2a, 0x86, 0x88, 0x91, 0x4,
0x10,
/* U+8BAF "讯" */
0x40, 0x5, 0xf0, 0x12, 0x2, 0x4e, 0x48, 0x5f,
0x9, 0x21, 0x24, 0x24, 0x86, 0x94, 0x91, 0x82,
@@ -736,6 +794,11 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
0x48, 0xaf, 0xdf, 0x22, 0xa5, 0x10, 0xc2, 0x10,
0x40,
/* U+94A5 "钥" */
0x40, 0x8, 0x7d, 0xe8, 0xc1, 0x17, 0xbe, 0x44,
0x48, 0x8f, 0xdf, 0x22, 0x25, 0x44, 0xc8, 0x92,
0x70,
/* U+9632 "防" */
0x2, 0x1e, 0x22, 0x7f, 0xc9, 0xa, 0x21, 0x47,
0xe4, 0x8c, 0x91, 0x92, 0x3c, 0x86, 0x10, 0xc4,
@@ -765,6 +828,11 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
0xbc, 0x0, 0x92, 0x39, 0x5a, 0xa8, 0x52, 0x37,
0xf0,
/* U+9ED8 "默" */
0xfc, 0x92, 0x9b, 0x72, 0xca, 0x4f, 0xde, 0x21,
0x3f, 0x20, 0x84, 0x1d, 0x5c, 0x29, 0x55, 0x55,
0x10,
/* U+FF0C "," */
0xf6,
@@ -772,7 +840,10 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
0xf0, 0xf0,
/* U+25B6 "▶"(来源:Segoe UI Symbol;宋体无此字形) */
0x80, 0xe0, 0xf0, 0xfc, 0xff, 0xfe, 0xf8, 0xf0, 0xc0, 0x00
0x80, 0xe0, 0xf0, 0xfc, 0xff, 0xfe, 0xf8, 0xf0, 0xc0, 0x00,
/* U+FF0C ','(来源:宋体迷你字体并入;lv_font_conv 丢低码位符号) */
0xf6
};
@@ -891,12 +962,12 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
{.bitmap_index = 672, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 689, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 706, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 723, .adv_w = 192, .box_w = 10, .box_h = 11, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 737, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 753, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 770, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 786, .adv_w = 192, .box_w = 9, .box_h = 10, .ofs_x = 1, .ofs_y = -1},
{.bitmap_index = 798, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 723, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 740, .adv_w = 192, .box_w = 10, .box_h = 11, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 754, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 770, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 787, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 803, .adv_w = 192, .box_w = 9, .box_h = 10, .ofs_x = 1, .ofs_y = -1},
{.bitmap_index = 815, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 832, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 849, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
@@ -904,33 +975,33 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
{.bitmap_index = 883, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 900, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 917, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 934, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 951, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 968, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 985, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1002, .adv_w = 192, .box_w = 9, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 1016, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1033, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1050, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1067, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1084, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1101, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1118, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1135, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1152, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1169, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1186, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1203, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1220, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1236, .adv_w = 192, .box_w = 9, .box_h = 11, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 1249, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1266, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1283, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1300, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1317, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1334, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1351, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1368, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 934, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 950, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 967, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 984, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1001, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1018, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1035, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1052, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1069, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1086, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1103, .adv_w = 192, .box_w = 9, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 1117, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1134, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1151, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1168, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1185, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1202, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1219, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1236, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1253, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1270, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1287, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1304, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1321, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1338, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1355, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1371, .adv_w = 192, .box_w = 9, .box_h = 11, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 1384, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1401, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1418, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
@@ -938,42 +1009,57 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
{.bitmap_index = 1452, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1469, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1486, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1503, .adv_w = 192, .box_w = 10, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 1518, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1535, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1551, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1568, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1585, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1602, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1619, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1636, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1653, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1670, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1687, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1704, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1721, .adv_w = 192, .box_w = 9, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 1735, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1752, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1769, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1786, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1803, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1820, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1837, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1854, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1871, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1888, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1905, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1922, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1939, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1956, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1973, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1990, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2006, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2023, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2040, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2057, .adv_w = 192, .box_w = 2, .box_h = 4, .ofs_x = 2, .ofs_y = 0},
{.bitmap_index = 2058, .adv_w = 192, .box_w = 2, .box_h = 6, .ofs_x = 2, .ofs_y = 0},
{.bitmap_index = 2060, .adv_w = 165, .box_w = 8, .box_h = 10, .ofs_x = 1, .ofs_y = -1} /* U+25B6 */
{.bitmap_index = 1503, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1520, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1536, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1553, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1570, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1587, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1604, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1621, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1638, .adv_w = 192, .box_w = 10, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1652, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1669, .adv_w = 192, .box_w = 10, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 1684, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1701, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1718, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1734, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1751, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1768, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1785, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1802, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1819, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1836, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1853, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1870, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1887, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1904, .adv_w = 192, .box_w = 9, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 1918, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1935, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1952, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1969, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1986, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2003, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2020, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2037, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2054, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2071, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2088, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2105, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2122, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2139, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2156, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2173, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2190, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2207, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2223, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2240, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2257, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2274, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2291, .adv_w = 192, .box_w = 2, .box_h = 4, .ofs_x = 2, .ofs_y = 0},
{.bitmap_index = 2292, .adv_w = 192, .box_w = 2, .box_h = 6, .ofs_x = 2, .ofs_y = 0},
{.bitmap_index = 2294, .adv_w = 165, .box_w = 8, .box_h = 10, .ofs_x = 1, .ofs_y = -1} /* U+25B6 */,
{.bitmap_index = 2304, .adv_w = 192, .box_w = 2, .box_h = 4, .ofs_x = 2, .ofs_y = 0} /* U+FF0C */
};
/*---------------------
@@ -982,17 +1068,19 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
static const uint16_t unicode_list_1[] = {
0x0, 0x32, 0x82, 0x9b, 0xed, 0x153, 0x1d4, 0x1d8,
0x233, 0x368, 0x42d, 0x444, 0x49f, 0x50d, 0x531, 0x582,
0x5b9, 0x5c3, 0x5da, 0x5ee, 0x626, 0x641, 0x982, 0xb04,
0xd4e, 0xd91, 0xd95, 0x1086, 0x1106, 0x1131, 0x1149, 0x114a,
0x114c, 0x1266, 0x1442, 0x148d, 0x149b, 0x149c, 0x1565, 0x159e,
0x233, 0x368, 0x36a, 0x42d, 0x444, 0x49f, 0x50d, 0x531,
0x582, 0x5b9, 0x5c3, 0x5da, 0x5ee, 0x626, 0x641, 0x927,
0x937, 0x982, 0xb04, 0xd47, 0xd4e, 0xd4f, 0xd91, 0xd95,
0xdbd, 0x1086, 0x1106, 0x1131, 0x1149, 0x114a, 0x114c, 0x1266,
0x142e, 0x1442, 0x148d, 0x149b, 0x149c, 0x1565, 0x159e, 0x15a0,
0x16c4, 0x173c, 0x1767, 0x176b, 0x17d7, 0x17dc, 0x17ed, 0x1916,
0x1976, 0x19dc, 0x1a18, 0x1ab7, 0x1c18, 0x1d72, 0x1ed9, 0x1f38,
0x1f42, 0x1ffc, 0x22b0, 0x243f, 0x2743, 0x28eb, 0x2902, 0x2a63,
0x2c02, 0x2ce6, 0x2d23, 0x2e72, 0x2ef2, 0x30b6, 0x30bf, 0x30eb,
0x3148, 0x3165, 0x33e1, 0x3a43, 0x3abc, 0x3d5d, 0x3da6, 0x3da7,
0x3db5, 0x3dd9, 0x4166, 0x41c7, 0x4211, 0x425c, 0x43c6, 0x4696,
0x4829, 0x485b, 0x4959, 0x4a6c, 0x4b7f, 0x4c83, 0xb103, 0xb111
0x1923, 0x1976, 0x19dc, 0x1a18, 0x1ab7, 0x1c18, 0x1d72, 0x1ed9,
0x1f38, 0x1f42, 0x1ffc, 0x22b0, 0x243f, 0x271f, 0x2743, 0x28eb,
0x2902, 0x29f8, 0x2a63, 0x2c02, 0x2ce6, 0x2d23, 0x2e72, 0x2ef2,
0x30b6, 0x30bf, 0x30eb, 0x3148, 0x3165, 0x33e1, 0x3a43, 0x3abc,
0x3d5d, 0x3d9b, 0x3da6, 0x3da7, 0x3db5, 0x3dd9, 0x4166, 0x41c7,
0x4211, 0x425c, 0x43c6, 0x4696, 0x469c, 0x4829, 0x485b, 0x4959,
0x4a6c, 0x4b7f, 0x4c83, 0x50cf, 0xb103, 0xb111
};
/*Collect the unicode lists and glyph_id offsets*/
@@ -1004,10 +1092,14 @@ static const lv_font_fmt_txt_cmap_t cmaps[] =
},
{
.range_start = 19977, .range_length = 45330, .glyph_id_start = 96,
.unicode_list = unicode_list_1, .glyph_id_ofs_list = NULL, .list_length = 96, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
.unicode_list = unicode_list_1, .glyph_id_ofs_list = NULL, .list_length = 110, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
},
{
.range_start = 9654, .range_length = 1, .glyph_id_start = 192,
.range_start = 9654, .range_length = 1, .glyph_id_start = 206,
.unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY
},
{
.range_start = 65292, .range_length = 1, .glyph_id_start = 207,
.unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY
}
};
@@ -1033,7 +1125,7 @@ static lv_font_fmt_txt_dsc_t font_dsc = {
.cmaps = cmaps,
.kern_dsc = NULL,
.kern_scale = 0,
.cmap_num = 3,
.cmap_num = 4,
.bpp = 1,
.kern_classes = 0,
.bitmap_format = 0,
+73 -12
View File
@@ -12,6 +12,14 @@
* 2026-09-08 王建锋 按代码规范 V1.0 补全分区注释与关键行注释;函数定义括号改 K&R 风格(同行)
* 2026-09-08 王建锋 4 层容器→单一持久容器(焦点/键唯一入口;键回调深度改取
* s_depth,去掉 user_data=depth;container/focus 调用去参)
* 2026-09-09 王建锋 密码页路由:需密码叶子(ui_menu_tab_has_pwd)ENTER
* → 先密码页;确认一致由 menu_password_ok 续做原"进入"动作
* (内容屏/子框)
* 2026-09-09 王建锋 权限模型:密码检查提到内容页判断之前(带内容页的受保护
* 叶子也先经密码页,修复"内容页叶子绕过密码"路由缺口);表 pwd
* 列改布尔"需密码"(行尾 #P);密码页按所输密码授权(查看→只读
* / 修改→可修改),授权经回调随开页传给内容页(页面经
* is_editable 判编辑);无密码叶子直接进、默认只读
*/
/* 头文件包含区 */
@@ -23,8 +31,12 @@
#include "ui_content_page.h"
#include "ui_page_password.h"
#include "ui_menu_tab.h"
#include "ui_pwd_def.h"
#include "lvgl.h"
#define DBG_TAG "[UI_MENU]"
@@ -95,15 +107,54 @@ static void menu_back(void) {
* 入口参数:node - 菜单节点下标 uint8_t
* 返回值:内容页 ID content_page_id_t;无内容页 = CONTENT_PAGE_COUNT
* 限定条件:无
* 函数说明:仅部分叶子有内容页(参考图 10/20/21);下标对应 ui_menu_tab.c
* node 1=装置信息、3=交流量、4=直流量(表序变更时须同步本映射)
* 函数说明:仅部分叶子有内容页(参考图 10/20/21 + 网口设置);下标对应
* ui_menu_tab.c
* node 1=装置信息、3=交流量、4=直流量、30=网口设置(表序变更时须同步本映射)
*/
static content_page_id_t menu_content_page_for(uint8_t node) {
switch (node) {
case 1u: return CONTENT_PAGE_DEVICE_INFO; /* 装置信息(L1 叶子) */
case 3u: return CONTENT_PAGE_AC; /* 交流量(实时数据子项) */
case 4u: return CONTENT_PAGE_DC; /* 直流量(实时数据子项) */
default: return (content_page_id_t)CONTENT_PAGE_COUNT; /* 无内容页 */
case 1u: return CONTENT_PAGE_DEVICE_INFO; /* 装置信息(L1 叶子) */
case 3u: return CONTENT_PAGE_AC; /* 交流量(实时数据子项) */
case 4u: return CONTENT_PAGE_DC; /* 直流量(实时数据子项) */
case 30u: return CONTENT_PAGE_NET_CFG; /* 网口设置(通讯参数子项,需密码) */
default: return (content_page_id_t)CONTENT_PAGE_COUNT; /* 无内容页 */
}
}
/*
* 函数功能:密码页确认一致回调(预装进密码页模块;uiTask 上下文调用)
* 入口参数:perm - 密码页按所输密码授予的权限 ui_perm_t(UI_PERM_MODIFY/UI_PERM_VIEW)
* 返回值:无
* 限定条件:密码页打开中;导航状态未变(选中条目仍为该受保护叶子)
* 函数说明: 1. 先关密码页(焦点回菜单容器、切回菜单屏;与 ESC 路径同模式,
* 事件内删对象由 LVGL e->deleted 机制保证安全)
* 2. 再沿未变的导航状态续做"进入"动作:有内容页 → 开内容页(携授予的
* 权限;页面经 is_editable 判只读/可编辑);否则压层进子框
* (叶子 → 空白终点框,权限不涉及)
* 3. 防御:状态异常 / 最深层无子层可压时仅停在菜单屏(页已关)
*/
static void menu_password_ok(uint8_t perm) {
ui_content_page_close(); /* 先关密码页回菜单屏,后续动作才不会落在被盖住的菜单屏上 */
uint8_t depth = s_depth;
uint8_t kids[MENU_ITEM_MAX];
uint8_t cnt = ui_menu_tab_children(s_path[depth], kids, MENU_ITEM_MAX);
if (cnt == 0u || s_sel[depth] >= cnt) {
return; /* 防御:状态异常,页已关,停在菜单屏 */
}
{
uint8_t child = kids[s_sel[depth]];
content_page_id_t pid = menu_content_page_for(child);
if (pid < CONTENT_PAGE_COUNT) {
ui_content_page_open(pid, perm); /* 页已关,守卫可过:开目标内容页(携权限) */
}
else if (depth < MENU_PAGE_MAX - 1u) {
menu_open(child); /* 压层:建新框 + 焦点移到新框(叶子 → 空白终点框) */
}
/* 最深层无子层可压:页已关,停在菜单屏 */
}
}
@@ -114,7 +165,10 @@ static content_page_id_t menu_content_page_for(uint8_t node) {
* 返回值:无
* 限定条件:ui_menu_init() 已完成
* 函数说明: 1. UP/DOWN:移选(首尾环绕);选中越出可见区由显示层跟随滚动
* 2. ENTER/RIGHT:叶子有内容页 → 切内容屏(不压层);否则进子框(叶子 → 空白终点框)
* 2. ENTER/RIGHT:需密码叶子(含带内容页者)→ 密码页(授权按所输
* 密码:查看→只读 / 修改→可修改,menu_password_ok 携权续进入);
* 否则叶子有内容页 → 切内容屏(不压层,默认只读);
* 再否则进子框(叶子 → 空白终点框)
* 3. ESC/LEFT:关最右框(根框不响应)
* 4. 空框(终点页 0 条目)只响应返回
*/
@@ -141,14 +195,21 @@ static void menu_key_handle(uint8_t depth, lv_key_t key) {
case LV_KEY_RIGHT: {
/* 进子框:空框无条目可进;最深层(depth=MAX-1)无子层可再开 */
if (cnt > 0u) {
content_page_id_t pid = menu_content_page_for(kids[s_sel[depth]]);
uint8_t child = kids[s_sel[depth]];
content_page_id_t pid = menu_content_page_for(child);
if (pid < CONTENT_PAGE_COUNT) {
/* 叶子有内容页:切内容屏(不压层,导航状态原样保留;ESC 返回即还原) */
ui_content_page_open(pid);
if (ui_menu_tab_has_pwd(child)) {
/* 需密码叶子(含带内容页者):先经密码页;授权按所输密码
(查看→只读 / 修改→可修改),回调携权续做"进入"动作 */
ui_page_password_arm(menu_password_ok);
ui_content_page_open(CONTENT_PAGE_PASSWORD, (uint8_t)UI_PERM_VIEW);
}
else if (pid < CONTENT_PAGE_COUNT) {
/* 叶子有内容页:无密码直接进(默认只读权限) */
ui_content_page_open(pid, (uint8_t)UI_PERM_VIEW);
}
else if (depth < MENU_PAGE_MAX - 1u) {
menu_open(kids[s_sel[depth]]); /* 压层:建新框 + 焦点移到新框(叶子 → 空白终点框) */
menu_open(child); /* 压层:建新框 + 焦点移到新框(叶子 → 空白终点框) */
}
}
break;
+57 -42
View File
@@ -4,48 +4,48 @@
#include "ui_menu_tab.h"
const ui_menu_node_t g_menu_tab[UI_MENU_TAB_COUNT] = {
{ MENU_ROOT, "主界面" }, /* 0 L0 */
{ 0, "装置信息" }, /* 1 L1 */
{ 0, "实时数据" }, /* 2 L1 */
{ 2, "交流量" }, /* 3 L2 */
{ 2, "直流量" }, /* 4 L2 */
{ 2, "遥信量" }, /* 5 L2 */
{ 0, "参数定值" }, /* 6 L1 */
{ 6, "整定" }, /* 7 L2 */
{ 7, "参数" }, /* 8 L3 */
{ 7, "定值" }, /* 9 L3 */
{ 7, "控制字" }, /* 10 L3 */
{ 7, "软压板" }, /* 11 L3 */
{ 6, "查看" }, /* 12 L2 */
{ 12, "参数" }, /* 13 L3 */
{ 12, "定值" }, /* 14 L3 */
{ 12, "控制字" }, /* 15 L3 */
{ 12, "软压板" }, /* 16 L3 */
{ 0, "三遥设置" }, /* 17 L1 */
{ 17, "遥测死区" }, /* 18 L2 */
{ 17, "遥测系数" }, /* 19 L2 */
{ 17, "遥信类型" }, /* 20 L2 */
{ 17, "遥信防抖" }, /* 21 L2 */
{ 17, "双点遥信" }, /* 22 L2 */
{ 0, "装置维护" }, /* 23 L1 */
{ 23, "时钟设置" }, /* 24 L2 */
{ 23, "强制复归" }, /* 25 L2 */
{ 23, "手动录波" }, /* 26 L2 */
{ 23, "清除记录" }, /* 27 L2 */
{ 0, "通讯参数" }, /* 28 L1 */
{ 28, "通讯设置" }, /* 29 L2 */
{ 28, "网口设置" }, /* 30 L2 */
{ 28, "SNTP设置" }, /* 31 L2 */
{ 0, "记录查询" }, /* 32 L1 */
{ 32, "SOE记录" }, /* 33 L2 */
{ 32, "事故记录" }, /* 34 L2 */
{ 32, "操作记录" }, /* 35 L2 */
{ 32, "保护告警" }, /* 36 L2 */
{ 32, "保护启动" }, /* 37 L2 */
{ 32, "遥控记录" }, /* 38 L2 */
{ 32, "自检记录" }, /* 39 L2 */
{ 32, "运行记录" }, /* 40 L2 */
{ 32, "运行报告" }, /* 41 L2 */
{ MENU_ROOT, 0u, "主界面" }, /* 0 L0 */
{ 0, 0u, "装置信息" }, /* 1 L1 */
{ 0, 0u, "实时数据" }, /* 2 L1 */
{ 2, 0u, "交流量" }, /* 3 L2 */
{ 2, 0u, "直流量" }, /* 4 L2 */
{ 2, 0u, "遥信量" }, /* 5 L2 */
{ 0, 0u, "参数定值" }, /* 6 L1 */
{ 6, 0u, "整定" }, /* 7 L2 */
{ 7, 1u, "参数" }, /* 8 L3 */
{ 7, 1u, "定值" }, /* 9 L3 */
{ 7, 1u, "控制字" }, /* 10 L3 */
{ 7, 1u, "软压板" }, /* 11 L3 */
{ 6, 0u, "查看" }, /* 12 L2 */
{ 12, 0u, "参数" }, /* 13 L3 */
{ 12, 0u, "定值" }, /* 14 L3 */
{ 12, 0u, "控制字" }, /* 15 L3 */
{ 12, 0u, "软压板" }, /* 16 L3 */
{ 0, 0u, "三遥设置" }, /* 17 L1 */
{ 17, 1u, "遥测死区" }, /* 18 L2 */
{ 17, 1u, "遥测系数" }, /* 19 L2 */
{ 17, 1u, "遥信类型" }, /* 20 L2 */
{ 17, 1u, "遥信防抖" }, /* 21 L2 */
{ 17, 1u, "双点遥信" }, /* 22 L2 */
{ 0, 0u, "装置维护" }, /* 23 L1 */
{ 23, 1u, "时钟设置" }, /* 24 L2 */
{ 23, 1u, "强制复归" }, /* 25 L2 */
{ 23, 1u, "手动录波" }, /* 26 L2 */
{ 23, 1u, "清除记录" }, /* 27 L2 */
{ 0, 0u, "通讯参数" }, /* 28 L1 */
{ 28, 1u, "通讯设置" }, /* 29 L2 */
{ 28, 1u, "网口设置" }, /* 30 L2 */
{ 28, 1u, "SNTP设置" }, /* 31 L2 */
{ 0, 0u, "记录查询" }, /* 32 L1 */
{ 32, 0u, "SOE记录" }, /* 33 L2 */
{ 32, 0u, "事故记录" }, /* 34 L2 */
{ 32, 0u, "操作记录" }, /* 35 L2 */
{ 32, 0u, "保护告警" }, /* 36 L2 */
{ 32, 0u, "保护启动" }, /* 37 L2 */
{ 32, 0u, "遥控记录" }, /* 38 L2 */
{ 32, 0u, "自检记录" }, /* 39 L2 */
{ 32, 0u, "运行记录" }, /* 40 L2 */
{ 32, 0u, "运行报告" }, /* 41 L2 */
};
/*
@@ -97,6 +97,18 @@ const char *ui_menu_tab_name(uint8_t node)
return g_menu_tab[node].name;
}
/*
* 函数功能:判断节点是否需密码(叶子密码保护)
* 入口参数:node - 节点下标 uint8_t
* 返回值:true - 需密码页(按所输密码授权查看/修改权限);false - 直接进(只读)
* 限定条件:无
* 说明:表 pwd 列取自源 ui_menu_tree.txt 行尾 #P 注记;导航逻辑层据此决定是否先经密码页
*/
bool ui_menu_tab_has_pwd(uint8_t node)
{
return (g_menu_tab[node].pwd != 0u);
}
/*
* 函数功能:菜单表启动自检
* 入口参数:无
@@ -114,6 +126,9 @@ bool ui_menu_tab_valid(void)
if (p != MENU_ROOT && p >= UI_MENU_TAB_COUNT) {
return false;
}
if (g_menu_tab[i].pwd > 1u) {
return false; /* pwd 只取 0/1(行尾注记笔误捕获) */
}
}
return true;
}
+4 -1
View File
@@ -10,8 +10,10 @@
#define MENU_ROOT 0xFFu /* 根哨兵:仅根节点(主界面)的 parent 取此值;8 位,上限 255 项 */
/* 菜单节点:子项 = parent 等于本下标的所有行(按表序);有子项=菜单,无=叶子 */
/* 字段序 parent/pwd 在前:32 位下两项 8 位合并 + 4 位指针 = 8 B/项 */
typedef struct {
uint8_t parent; /* 父节点下标(0..254);根为 MENU_ROOT(0xFF) */
uint8_t pwd; /* 1 = 需密码(密码页按所输密码授权);0 = 直接进(只读) */
const char *name; /* 显示文本(字面量,存 Flash) */
} ui_menu_node_t;
@@ -29,6 +31,7 @@ extern const ui_menu_node_t g_menu_tab[];
uint8_t ui_menu_tab_children(uint8_t node, uint8_t out[], uint8_t max); /* 取 node 直接子项(按表序),最多 max 个;返回实际个数 */
bool ui_menu_tab_has_children(uint8_t node); /* 有子项=菜单(带▶);无=叶子 */
const char *ui_menu_tab_name(uint8_t node); /* 显示名 */
bool ui_menu_tab_valid(void); /* 启动自检:非根 parent 下标均有效 */
bool ui_menu_tab_has_pwd(uint8_t node); /* 需密码(密码页按所输密码授权);false = 直接进(只读) */
bool ui_menu_tab_valid(void); /* 启动自检:非根 parent 下标均有效、pwd 为 0/1 */
#endif
+342
View File
@@ -0,0 +1,342 @@
/*
* 模块名称:UI Page Net Cfg (网口设置内容页)
* 模块功能:网口设置叶子节点的内容页数据 + 行为——5 行地址列表(IP 地址/子网
* 掩码/默认网关/MAC 地址 + 保存行,5 行每页共 1 页);
* 行槽顶 = 24+14i、选中白带 x=2..156 高 13、名 x=6 / 值 x=60;
* 值取自 net_cfg RAM 副本(netTask 上电自 Flash 加载并回写实际 MAC);
* 编辑模式:ENTER 进选中地址行(字节光标 = 活动字节,反显标记),
* ↑↓调字节 0..255(环绕、值列实时刷新)、←→移字节光标(首↔末环绕)、
* ENTER 确认退出编辑;
* 权限:edit_capable=1——查看密码 → 只读(编辑/保存键无效),
* 修改密码 → 可编辑;保存行 ENTER → net_cfg_save()(下次上电生效);
* 向框架 ui_content_page 提供页面描述符(零 LVGL 对象)
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-09-10
* 修改记录:
* v1.0 2026-09-10 王建锋 创建初始版本
*/
/* 头文件包含区 */
#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=6 / 值 x=60(值列须 ≥60:4 汉字名占 48px + 间隙;值列宽——
* IP/掩码/网关 11 ASCII 字符 = 66px 止于 126,MAC 12 hex 字符 = 72px 止于 132)
*/
#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 6 /* 左列 x(名) */
#define NC_RIGHT_X 60 /* 右列 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 = "A494B6123456"(无分隔 hex,字节 i 恒在字符 2i 起)
*/
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)strcpy(name, "IP地址");
net_cfg_fmt_ipv4(cfg->ip, value, NC_ROW_BUF_LEN);
break;
case 1u:
(void)strcpy(name, "子网掩码");
net_cfg_fmt_ipv4(cfg->mask, value, NC_ROW_BUF_LEN);
break;
case 2u:
(void)strcpy(name, "默认网关");
net_cfg_fmt_ipv4(cfg->gw, value, NC_ROW_BUF_LEN);
break;
case 3u:
(void)strcpy(name, "MAC地址");
nc_fmt_value(row, value);
break;
case 4u:
(void)strcpy(name, "保存");
(void)strcpy(value, (s_saved == 0u) ? "ENTER" : ((s_saved == 1u) ? "OK" : "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) ? 2u : 4u);
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;
lv_draw_label_dsc_init(&ld);
ld.base.layer = layer;
ld.text = value + start;
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;
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,
};
+29
View File
@@ -0,0 +1,29 @@
/*
* 模块名称:UI Page Net Cfg (网口设置内容页)
* 模块功能:网口设置叶子节点的内容页描述符(5 行地址列表 + 保存行;
* 可编辑页:修改权限下 ENTER 进编辑、↑↓ 调字节、←→ 移字节光标、
* 保存行 ENTER 写 Flash 下次上电生效)
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-09-10
* 修改记录:
* v1.0 2026-09-10 王建锋 创建初始版本
*/
#ifndef UI_PAGE_NET_CFG_H
#define UI_PAGE_NET_CFG_H
#include "ui_content_page.h"
#ifdef __cplusplus
extern "C" {
#endif
/* 页面描述符(框架描述符表 s_defs[] 引用) */
extern const content_page_def_t ui_page_net_cfg_desc;
#ifdef __cplusplus
}
#endif
#endif /* UI_PAGE_NET_CFG_H */
+231
View File
@@ -0,0 +1,231 @@
/*
* 模块名称:UI Page Password (用户密钥密码页)
* 模块功能:3 位密码输入页(表单/操作页首个实例)——标题由框架自绘(无"浅 V"、
* 无页脚,跟随实机),本模块自绘 3 个数字框:预填 0、首框反显 = 光标位;
* ↑↓调值(9↔0 环绕)、←→移光标(首↔末环绕)、ENTER 确认(修改密码→完成
* 回调授修改权限 / 查看密码→完成回调授只读权限 / 不一致→清空重输)、
* ESC 返回菜单(框架保证);
* 向框架 ui_content_page 提供页面描述符(零 LVGL 对象)
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-09-09
* 修改记录:
* 2026-09-09 王建锋 创建:表单/操作页首个实例(内容页架构.md §4.1;
* 实机参考 mockups/密码页面实际机器的示例图片.jpg)
* 2026-09-09 王建锋 改权限模型:arm 只载回调(不再载期望密码);页面比对
* ui_pwd_def.h 两个编译期密码(先查修改),一致时回调携
* 授予的权限(UI_PERM_MODIFY / UI_PERM_VIEW)
*/
/* 头文件包含区 */
#include "ui_page_password.h"
#include "ui_pwd_def.h"
/* 私有宏定义区 */
/*
* 布局常量(设备像素;实机参考 mockups/密码页面实际机器的示例图片.jpg 实测):
* 3 个数字框单行居中于屏中下部(框 14x14、间距 6、行顶 y=78、首框 x=53 →
* 3 框 + 2 距 = 54px,屏中心 80);数字墨迹框内水平居中、垂直贴框顶
* (ASCII 字形顶距 +2 已计入字体度量)
*/
#define PW_DIGITS 3 /* 密码位数 */
#define PW_BOX_W 14 /* 数字框宽(px) */
#define PW_BOX_H 14 /* 数字框高(px) */
#define PW_BOX_GAP 6 /* 数字框间距(px) */
#define PW_BOX_Y 78 /* 数字框行顶 y */
#define PW_BOX_X0 53 /* 首个数字框左 x(居中) */
#define PW_TITLE_Y 44 /* 标题放置 y(中上;0 = 框架默认,实机明显低于其余各页的 y=2) */
/* 静态变量定义区 */
/* 单位数字文本(Flash):数字 0..9 各 1 字符(text_static,不拷内存) */
static const char *const s_digit_str[10] = {
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
};
/* 页面运行状态(RAM,约 9 B):3 位值 + 光标位 + 完成回调 */
static uint8_t s_digits[PW_DIGITS]; /* 3 位密码值(0..9) */
static uint8_t s_cur; /* 光标位 0..2(反显框) */
static void (*s_on_ok)(uint8_t); /* 完成回调(逻辑层预装,实参=授予权限;NULL = 一致时仅关页) */
/* 函数定义区 */
/*
* 函数功能:判断两区域是否相交(等价 lv_area_intersect 非空判断;避免依赖 LVGL 私有头)
* 入口参数:a - 区域 1 const lv_area_t *
* b - 区域 2 const lv_area_t *
* 返回值:true - 相交;false - 不相交
* 限定条件:无
* 函数说明:四边取 min/max 成交集,任一边为空即不相交(与 ui_content_page.c 同实现)
*/
static bool pw_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);
}
/*
* 函数功能:绘制数字框内数字(单字符,水平居中)
* 入口参数:layer - 绘制层 lv_layer_t *
* box - 数字框区域 const lv_area_t *
* digit - 数字值(0..9) uint32_t
* color - 文本颜色 lv_color_t
* 返回值:无
* 限定条件:数字框在重绘区内(调用方 culling)
* 函数说明:文本为 Flash 字面量(不拷内存);ofs 相对区域原点
*/
static void pw_draw_digit(lv_layer_t *layer, const lv_area_t *box, uint32_t digit, lv_color_t color) {
lv_draw_label_dsc_t ld;
lv_draw_label_dsc_init(&ld);
ld.base.layer = layer;
ld.text = s_digit_str[digit];
ld.text_static = 1;
ld.color = color;
ld.ofs_x = (PW_BOX_W - lv_text_get_width(s_digit_str[digit], 1, LV_FONT_DEFAULT, 0)) / 2;
ld.ofs_y = 0;
lv_draw_label(layer, &ld, box);
}
/*
* 函数功能:页面自绘(3 个数字框:光标框反显,其余白底黑框)
* 入口参数:layer - 绘制层 lv_layer_t *
* clip - 重绘区 const lv_area_t *
* 返回值:无
* 限定条件:页面打开(框架绘制链末尾调用)
* 函数说明:标题/页脚由框架处理(每页 title_y、"浅 V"/页脚已关);本模块只绘数字框;
* 1bpp 极性:white=暗、black=亮——光标框反显(white 底 black 字),
* 其余框亮底暗字 + 1px 暗边
*/
static void pw_draw_cb(lv_layer_t *layer, const lv_area_t *clip) {
for (uint32_t i = 0u; i < PW_DIGITS; i++) {
lv_area_t box;
lv_draw_rect_dsc_t fd;
box.x1 = (int32_t)(PW_BOX_X0 + i * (PW_BOX_W + PW_BOX_GAP));
box.x2 = (int32_t)(box.x1 + PW_BOX_W - 1u);
box.y1 = PW_BOX_Y;
box.y2 = (int32_t)(PW_BOX_Y + PW_BOX_H - 1u);
if (!pw_areas_intersect(&box, clip)) {
continue; /* 数字框不在重绘区:跳过(culling) */
}
lv_draw_rect_dsc_init(&fd);
fd.base.layer = layer;
fd.bg_opa = LV_OPA_COVER;
if (i == s_cur) {
/* 光标框:反显(1bpp:white=暗底,black=亮字) */
fd.bg_color = lv_color_white();
}
else {
/* 非光标框:亮底暗字 + 1px 暗边(1bpp:black=亮底,white=暗) */
fd.bg_color = lv_color_black();
fd.border_color = lv_color_white();
fd.border_width = 1;
}
lv_draw_rect(layer, &fd, &box);
pw_draw_digit(layer, &box, s_digits[i], (i == s_cur) ? lv_color_black() : lv_color_white());
}
}
/*
* 函数功能:页面按键处理(表单页:键全由页面处理)
* 入口参数:key - LVGL 键值 lv_key_t
* 返回值:true - 页已处理(框架跳过内置语义);false - 未处理
* 限定条件:页面打开;经框架键分发调用(uiTask 上下文)
* 函数说明: 1. ↑/↓:调整光标位数字(+1/-1,9↔0 环绕)
* 2. ←/→:移动光标(首↔末环绕;← 键经框架拦截标志转入本函数)
* 3. ENTER:修改密码 → 完成回调(授修改权限);查看密码 → 完成回调
* (授只读权限);不一致 → 清空重输
* 4. ESC 不经本函数(框架最优先恒关页);其余键值未处理
*/
static bool pw_key_cb(lv_key_t key) {
switch (key) {
case LV_KEY_UP:
s_digits[s_cur] = (uint8_t)((s_digits[s_cur] + 1u) % 10u); /* 值 +1,9 环绕 0 */
break;
case LV_KEY_DOWN:
s_digits[s_cur] = (uint8_t)((s_digits[s_cur] + 9u) % 10u); /* 值 -1,0 环绕 9 */
break;
case LV_KEY_LEFT:
s_cur = (uint8_t)((s_cur + PW_DIGITS - 1u) % PW_DIGITS); /* 光标左移,首绕末 */
break;
case LV_KEY_RIGHT:
s_cur = (uint8_t)((s_cur + 1u) % PW_DIGITS); /* 光标右移,末绕首 */
break;
case LV_KEY_ENTER: {
uint16_t input = (uint16_t)((uint32_t)s_digits[0] * 100u
+ (uint32_t)s_digits[1] * 10u
+ (uint32_t)s_digits[2]);
uint8_t perm;
if (input == (uint16_t)UI_PWD_VALUE_MODIFY) {
perm = (uint8_t)UI_PERM_MODIFY; /* 修改密码:授修改权限(先查,两值同设时授高权) */
}
else if (input == (uint16_t)UI_PWD_VALUE_VIEW) {
perm = (uint8_t)UI_PERM_VIEW; /* 查看密码:授只读权限 */
}
else {
/* 不一致:清空重输(3 框回 0、光标回首框) */
s_digits[0] = 0u;
s_digits[1] = 0u;
s_digits[2] = 0u;
s_cur = 0u;
break;
}
/* 一致:逻辑层接管"关页 + 按权限续进入"(回调内自行关页) */
if (s_on_ok != NULL) {
s_on_ok(perm);
}
else {
ui_content_page_close(); /* 防御:无回调 → 仅关页 */
}
return true; /* 页面已关(或已切走),无需重绘 */
}
default:
return false; /* 未处理:落回框架内置语义 */
}
ui_content_page_invalidate(); /* 状态已变:触发整页重绘 */
return true;
}
/*
* 函数功能:装载完成回调(须在开页前调用)
* 入口参数:on_ok - 完成回调(NULL = 一致时仅关页);一致后以授予的权限为实参
* 返回值:无
* 限定条件:由逻辑层在 uiTask 上下文、开页前调用
* 函数说明:密码页比对 ui_pwd_def.h 的两个编译期密码值(无需载值);
* 每次进入重置为初始态(全 0、光标首框,与实机初始显示一致)
*/
void ui_page_password_arm(void (*on_ok)(uint8_t perm)) {
s_on_ok = on_ok;
s_digits[0] = 0u;
s_digits[1] = 0u;
s_digits[2] = 0u;
s_cur = 0u;
}
/*
* 页面描述符(外部链接:框架描述符表 s_defs[] 直接引用):
* 表单/操作页——selectable=0、无行数据,自绘内容 + 自有键;
* 标题/装饰/页脚跟随实机(无"浅 V"、标题中上、无页脚);
* key_intercept=1:← 键先由 key_cb 处理(移光标),ESC 恒不可拦截
*/
const content_page_def_t ui_page_password_desc = {
.title = "用户密钥",
.title_y = PW_TITLE_Y,
.hide_deco = 1u,
.hide_footer = 1u,
.selectable = 0u,
.key_intercept = 1u,
.draw_cb = pw_draw_cb,
.key_cb = pw_key_cb,
};
+40
View File
@@ -0,0 +1,40 @@
/*
* 模块名称:UI Page Password (用户密钥密码页)
* 模块功能:3 位密码输入页(表单/操作页首个实例)——3 个数字框 + 反显光标,
* ↑↓调值(9↔0 环绕)、←→移光标(首↔末环绕)、ENTER 确认、ESC 返回菜单;
* 确认一致时按所输密码授予权限(查看密码→只读 / 修改密码→可修改),
* 由逻辑层预装的完成回调(携权限)接管"关页 + 转目标";
* 向框架 ui_content_page 提供页面描述符(零 LVGL 对象)
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-09-09
* 修改记录:
* 2026-09-09 王建锋 创建:表单/操作页首个实例(内容页架构.md §4.1;
* 实机参考 mockups/密码页面实际机器的示例图片.jpg)
*/
#ifndef UI_PAGE_PASSWORD_H
#define UI_PAGE_PASSWORD_H
#include "ui_content_page.h"
/*
* 页面描述符(Flash const):用户密钥页 = CONTENT_PAGE_PASSWORD;
* 接入框架描述符表 s_defs[](定义于 ui_page_password.c)
*/
extern const content_page_def_t ui_page_password_desc;
/*
* 函数功能:装载完成回调(须在开页前调用)
* 入口参数:on_ok - 完成回调(ENTER 确认一致后调用,实参为授予的权限:
* UI_PERM_MODIFY = 输了修改密码 / UI_PERM_VIEW = 输了查看密码;
* 由逻辑层实现"关页 + 按权限续进入";
* NULL = 一致时仅关页)
* 返回值:无
* 限定条件:由逻辑层在 uiTask 上下文、ui_content_page_open(CONTENT_PAGE_PASSWORD)
* 之前调用
* 函数说明:页面比对 ui_pwd_def.h 的两个编译期密码(先查修改:两值同设时授高权);
* 每次进入重置为初始态(全 0、光标首框,与实机一致);页面模块不感知菜单
*/
void ui_page_password_arm(void (*on_ok)(uint8_t perm));
#endif /* UI_PAGE_PASSWORD_H */
+40
View File
@@ -0,0 +1,40 @@
/*
* 模块名称:UI PWD Def (密码与权限配置)
* 模块功能:查看/修改两个密码的编译期取值(3 位 0..999)+ 权限枚举定义——
* 密码值唯一配置点;改值后重新编译生效,不占 RAM、不落 Flash;
* 菜单表(ui_menu_tree.txt)叶子行尾 #P 注记标"需密码",
* 进叶子时按所输密码授权(查看密码→只读 / 修改密码→可修改)
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-09-09
* 修改记录:
* 2026-09-09 王建锋 创建:密码值编译期配置(用户定案:密码仅编译期
* 写入,不做运行期设置页与 Flash 持久化)
* 2026-09-09 王建锋 改权限模型:密码不绑定特定叶子,同一叶子输不同密码
* 获不同权限(查看密码→只读 / 修改密码→可修改);
* 新增 ui_perm_t 权限枚举;树注记改 #P(需密码)
*/
#ifndef UI_PWD_DEF_H
#define UI_PWD_DEF_H
#include <stdint.h>
/*
* 权限:进入受保护叶子时获得的权限级别(由所输密码决定):
* 同一叶子输查看密码 → 只读;输修改密码 → 可修改(含查看);
* 无密码叶子直接进,默认只读
*/
typedef enum {
UI_PERM_VIEW = 0u, /* 只读权限:查看密码授予(无密码叶子的默认权限) */
UI_PERM_MODIFY, /* 修改权限:修改密码授予(含查看) */
UI_PERM_COUNT /* 哨兵:非法权限(自检) */
} ui_perm_t;
#define UI_PWD_VALUE_VIEW 0u /* 查看密码:输入授予只读权限 */
#define UI_PWD_VALUE_MODIFY 123u /* 修改密码:输入授予修改权限 */
/* 护栏:密码值域 0..999(3 位),越界编译失败(C99 写法,兼容 ARMCC v5) */
typedef char ui_pwd_value_view_check[(UI_PWD_VALUE_VIEW <= 999u) ? 1 : -1];
typedef char ui_pwd_value_modify_check[(UI_PWD_VALUE_MODIFY <= 999u) ? 1 : -1];
#endif /* UI_PWD_DEF_H */