优化整定一版

This commit is contained in:
冯佳
2025-12-18 23:56:36 +08:00
parent e5eaf2172f
commit e36b4e0e27
16 changed files with 2619 additions and 504 deletions

View File

@ -2,21 +2,59 @@
* @file menu_core.c
* @brief 菜单核心逻辑:导航、栈管理、主循环(工业级:状态机驱动)
*/
#include "../api/menu.h"
#include "menu_core.h"
#include "menu_data.h"
#include <stddef.h>
/************************** 全局变量定义 **************************/
MenuCoreCtx s_menu_core_ctx;
/************************** 内部函数声明 **************************/
static MenuErrCode menu_core_stack_push(MenuGlobalCtx* global_ctx, MenuNodeId node_id);
static MenuErrCode menu_core_stack_pop(MenuGlobalCtx* global_ctx, MenuNodeId* node_id);
static MenuErrCode menu_core_navigate_enter(MenuGlobalCtx* global_ctx, MenuNodeId node_id);
static MenuErrCode menu_core_navigate_back(MenuGlobalCtx* global_ctx);
static MenuErrCode menu_core_navigate_up(MenuGlobalCtx* global_ctx);
static MenuErrCode menu_core_navigate_down(MenuGlobalCtx* global_ctx);
static bool menu_core_get_event(MenuGlobalCtx* global_ctx, MenuEvent* event);
/************************** 状态机相关函数声明 **************************/
static MenuErrCode menu_state_action_normal_navigate(MenuGlobalCtx* global_ctx, MenuEventType event);
static MenuErrCode menu_state_action_param_edit(MenuGlobalCtx* global_ctx, MenuEventType event);
static MenuErrCode menu_state_action_confirm(MenuGlobalCtx* global_ctx, MenuEventType event);
static MenuErrCode menu_state_action_error(MenuGlobalCtx* global_ctx, MenuEventType event);
static MenuErrCode menu_state_transition(MenuGlobalCtx* global_ctx, MenuEventType event);
/************************** 状态转换表定义 **************************/
static const MenuStateTransition menu_state_transitions[] = {
// 当前状态 触发事件 下一个状态 转换动作
{ MENU_STATE_INIT, MENU_EVENT_NONE, MENU_STATE_NORMAL, NULL },
{ MENU_STATE_NORMAL, MENU_EVENT_KEY_UP, MENU_STATE_NORMAL, NULL },
{ MENU_STATE_NORMAL, MENU_EVENT_KEY_DOWN, MENU_STATE_NORMAL, NULL },
{ MENU_STATE_NORMAL, MENU_EVENT_KEY_ENTER, MENU_STATE_PARAM_EDIT, NULL },
{ MENU_STATE_NORMAL, MENU_EVENT_KEY_BACK, MENU_STATE_NORMAL, NULL },
{ MENU_STATE_PARAM_EDIT, MENU_EVENT_KEY_UP, MENU_STATE_PARAM_EDIT, NULL },
{ MENU_STATE_PARAM_EDIT, MENU_EVENT_KEY_DOWN, MENU_STATE_PARAM_EDIT, NULL },
{ MENU_STATE_PARAM_EDIT, MENU_EVENT_KEY_ENTER, MENU_STATE_NORMAL, NULL },
{ MENU_STATE_PARAM_EDIT, MENU_EVENT_KEY_BACK, MENU_STATE_NORMAL, NULL },
{ MENU_STATE_CONFIRM, MENU_EVENT_KEY_UP, MENU_STATE_CONFIRM, NULL },
{ MENU_STATE_CONFIRM, MENU_EVENT_KEY_DOWN, MENU_STATE_CONFIRM, NULL },
{ MENU_STATE_CONFIRM, MENU_EVENT_KEY_ENTER, MENU_STATE_NORMAL, NULL },
{ MENU_STATE_CONFIRM, MENU_EVENT_KEY_BACK, MENU_STATE_NORMAL, NULL },
{ MENU_STATE_ERROR, MENU_EVENT_KEY_ENTER, MENU_STATE_NORMAL, NULL },
{ MENU_STATE_ERROR, MENU_EVENT_KEY_BACK, MENU_STATE_NORMAL, NULL },
};
static const uint8_t menu_state_transition_count = sizeof(menu_state_transitions) / sizeof(menu_state_transitions[0]);
/**
* @brief 查找菜单节点通过ID
* @param global_ctx 全局上下文指针
* @param node_id 节点ID
* @return 节点指针NULL表示未找到
*/
MenuNode* menu_core_find_node(MenuNodeId node_id)
MenuNode* menu_core_find_node(MenuGlobalCtx* global_ctx, MenuNodeId node_id)
{
MenuCoreCtx* ctx = menu_core_get_ctx();
MenuCoreCtx* ctx = &global_ctx->core;
for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES; i++)
{
if (ctx->nodes[i].flags.is_registered && ctx->nodes[i].id == node_id)
@ -24,17 +62,19 @@ MenuNode* menu_core_find_node(MenuNodeId node_id)
return &ctx->nodes[i];
}
}
return NULL;
}
/**
* @brief 菜单栈压入节点
* @param global_ctx 全局上下文指针
* @param node_id 节点ID
* @return 错误码
*/
static MenuErrCode menu_core_stack_push(MenuNodeId node_id)
static MenuErrCode menu_core_stack_push(MenuGlobalCtx* global_ctx, MenuNodeId node_id)
{
MenuCoreCtx* ctx = menu_core_get_ctx();
MenuCoreCtx* ctx = &global_ctx->core;
MENU_ASSERT(ctx != NULL);
if (ctx->stack.top >= MENU_CONFIG_STACK_DEPTH)
@ -50,12 +90,13 @@ static MenuErrCode menu_core_stack_push(MenuNodeId node_id)
/**
* @brief 菜单栈弹出节点
* @param global_ctx 全局上下文指针
* @param node_id 输出参数弹出的节点ID
* @return 错误码
*/
static MenuErrCode menu_core_stack_pop(MenuNodeId* node_id)
static MenuErrCode menu_core_stack_pop(MenuGlobalCtx* global_ctx, MenuNodeId* node_id)
{
MenuCoreCtx* ctx = menu_core_get_ctx();
MenuCoreCtx* ctx = &global_ctx->core;
MENU_ASSERT(ctx != NULL && node_id != NULL);
if (ctx->stack.top == 0)
@ -71,12 +112,13 @@ static MenuErrCode menu_core_stack_pop(MenuNodeId* node_id)
/**
* @brief 菜单导航到子节点(确认键)
* @param global_ctx 全局上下文指针
* @param node_id 当前节点ID
* @return 错误码
*/
static MenuErrCode menu_core_navigate_enter(MenuNodeId node_id)
static MenuErrCode menu_core_navigate_enter(MenuGlobalCtx* global_ctx, MenuNodeId node_id)
{
MenuNode* node = menu_core_find_node(node_id);
MenuNode* node = menu_core_find_node(global_ctx, node_id);
if (node == NULL)
{
return MENU_ERR_NODE_NOT_FOUND;
@ -95,9 +137,9 @@ static MenuErrCode menu_core_navigate_enter(MenuNodeId node_id)
// 如果有子节点,压入栈并选中第一个子节点
if (node->first_child != NULL)
{
MenuCoreCtx* ctx = menu_core_get_ctx();
MenuCoreCtx* ctx = &global_ctx->core;
ctx->current_node_id = node->first_child->id;
return menu_core_stack_push(node->first_child->id);
return menu_core_stack_push(global_ctx, node->first_child->id);
}
return MENU_OK;
@ -105,13 +147,14 @@ static MenuErrCode menu_core_navigate_enter(MenuNodeId node_id)
/**
* @brief 菜单导航返回父节点(返回键)
* @param global_ctx 全局上下文指针
* @return 错误码
*/
static MenuErrCode menu_core_navigate_back(void)
static MenuErrCode menu_core_navigate_back(MenuGlobalCtx* global_ctx)
{
MenuCoreCtx* ctx = menu_core_get_ctx();
MenuCoreCtx* ctx = &global_ctx->core;
MenuNodeId current_id = ctx->current_node_id;
MenuNode* current_node = menu_core_find_node(current_id);
MenuNode* current_node = menu_core_find_node(global_ctx, current_id);
if (current_node == NULL)
{
return MENU_ERR_NODE_NOT_FOUND;
@ -136,7 +179,7 @@ static MenuErrCode menu_core_navigate_back(void)
}
MenuNodeId pop_id;
MenuErrCode err = menu_core_stack_pop(&pop_id);
MenuErrCode err = menu_core_stack_pop(global_ctx, &pop_id);
if (err != MENU_OK)
{
return err;
@ -148,12 +191,13 @@ static MenuErrCode menu_core_navigate_back(void)
/**
* @brief 菜单导航上选(上键)
* @param global_ctx 全局上下文指针
* @return 错误码
*/
static MenuErrCode menu_core_navigate_up(void)
static MenuErrCode menu_core_navigate_up(MenuGlobalCtx* global_ctx)
{
MenuCoreCtx* ctx = menu_core_get_ctx();
MenuNode* current_node = menu_core_find_node(ctx->current_node_id);
MenuCoreCtx* ctx = &global_ctx->core;
MenuNode* current_node = menu_core_find_node(global_ctx, ctx->current_node_id);
if (current_node == NULL)
{
return MENU_ERR_NODE_NOT_FOUND;
@ -181,12 +225,13 @@ static MenuErrCode menu_core_navigate_up(void)
/**
* @brief 菜单导航下选(下键)
* @param global_ctx 全局上下文指针
* @return 错误码
*/
static MenuErrCode menu_core_navigate_down(void)
static MenuErrCode menu_core_navigate_down(MenuGlobalCtx* global_ctx)
{
MenuCoreCtx* ctx = menu_core_get_ctx();
MenuNode* current_node = menu_core_find_node(ctx->current_node_id);
MenuCoreCtx* ctx = &global_ctx->core;
MenuNode* current_node = menu_core_find_node(global_ctx, ctx->current_node_id);
if (current_node == NULL)
{
return MENU_ERR_NODE_NOT_FOUND;
@ -200,7 +245,7 @@ static MenuErrCode menu_core_navigate_down(void)
else
{
// 循环到第一个兄弟节点
MenuNode* parent_node = menu_core_find_node(current_node->parent_id);
MenuNode* parent_node = menu_core_find_node(global_ctx, current_node->parent_id);
if (parent_node != NULL && parent_node->first_child != NULL)
{
ctx->current_node_id = parent_node->first_child->id;
@ -212,69 +257,173 @@ static MenuErrCode menu_core_navigate_down(void)
}
/**
* @brief 初始化菜单核心上下文
* @return 错误码
*/
MenuErrCode menu_core_ctx_init(void)
{
MenuCoreCtx* ctx = menu_core_get_ctx();
MENU_MEM_SET_ZERO(ctx, sizeof(MenuCoreCtx));
ctx->is_initialized = true;
return MENU_OK;
}
/**
* @brief 获取菜单核心上下文(内部唯一访问入口)
* @return 菜单核心上下文指针
*/
MenuCoreCtx* menu_core_get_ctx(void)
{
return &s_menu_core_ctx;
}
/**
* @brief 处理单个菜单事件
* @brief 处理单个菜单事件(使用状态机)
* @param global_ctx 全局上下文指针
* @param event 事件指针
* @return 错误码
*/
MenuErrCode menu_core_handle_event(const MenuEvent* event)
MenuErrCode menu_core_handle_event(MenuGlobalCtx* global_ctx, const MenuEvent* event)
{
MENU_ASSERT(event != NULL);
MenuErrCode err = MENU_OK;
switch (event->type)
// 使用状态机处理事件
err = menu_state_transition(global_ctx, event->type);
return err;
}
/**
* @brief 状态机转换处理函数
* @param global_ctx 全局上下文指针
* @param event 触发事件
* @return 错误码
*/
static MenuErrCode menu_state_transition(MenuGlobalCtx* global_ctx, MenuEventType event)
{
MenuCoreCtx* ctx = &global_ctx->core;
MenuState current_state = ctx->current_state;
MenuErrCode err = MENU_OK;
// 查找状态转换
for (uint8_t i = 0; i < menu_state_transition_count; i++)
{
if (menu_state_transitions[i].current_state == current_state &&
menu_state_transitions[i].event == event)
{
// 执行状态转换动作
switch (current_state)
{
case MENU_STATE_NORMAL:
err = menu_state_action_normal_navigate(global_ctx, event);
break;
case MENU_STATE_PARAM_EDIT:
err = menu_state_action_param_edit(global_ctx, event);
break;
case MENU_STATE_CONFIRM:
err = menu_state_action_confirm(global_ctx, event);
break;
case MENU_STATE_ERROR:
err = menu_state_action_error(global_ctx, event);
break;
default:
break;
}
// 更新状态
ctx->current_state = menu_state_transitions[i].next_state;
MENU_DEBUG("State transition: %d -> %d, event: %d",
current_state, ctx->current_state, event);
break;
}
}
return err;
}
/**
* @brief 正常导航状态的动作处理
* @param global_ctx 全局上下文指针
* @param event 触发事件
* @return 错误码
*/
static MenuErrCode menu_state_action_normal_navigate(MenuGlobalCtx* global_ctx, MenuEventType event)
{
MenuErrCode err = MENU_OK;
switch (event)
{
case MENU_EVENT_KEY_UP:
err = menu_core_navigate_up();
err = menu_core_navigate_up(global_ctx);
break;
case MENU_EVENT_KEY_DOWN:
err = menu_core_navigate_down();
err = menu_core_navigate_down(global_ctx);
break;
case MENU_EVENT_KEY_ENTER:
err = menu_core_navigate_enter(menu_core_get_ctx()->current_node_id);
err = menu_core_navigate_enter(global_ctx, global_ctx->core.current_node_id);
break;
case MENU_EVENT_KEY_BACK:
err = menu_core_navigate_back();
break;
case MENU_EVENT_NONE:
// 无事件,不处理
err = menu_core_navigate_back(global_ctx);
break;
default:
// 自定义事件(用户可扩展)
MENU_DEBUG("Custom event received: type %d, param %lu", event->type, event->param);
break;
}
return err;
}
/**
* @brief 参数编辑状态的动作处理
* @param global_ctx 全局上下文指针
* @param event 触发事件
* @return 错误码
*/
static MenuErrCode menu_state_action_param_edit(MenuGlobalCtx* global_ctx, MenuEventType event)
{
MenuErrCode err = MENU_OK;
// Suppress unused parameter warnings
(void)global_ctx;
(void)event;
// 这里可以添加参数编辑的具体逻辑
// 例如:根据上下键调整参数值,确认键保存,返回键取消编辑
MENU_DEBUG("Param edit state, event: %d", event);
return err;
}
/**
* @brief 确认状态的动作处理
* @param global_ctx 全局上下文指针
* @param event 触发事件
* @return 错误码
*/
static MenuErrCode menu_state_action_confirm(MenuGlobalCtx* global_ctx, MenuEventType event)
{
MenuErrCode err = MENU_OK;
// Suppress unused parameter warnings
(void)global_ctx;
(void)event;
// 这里可以添加确认状态的具体逻辑
// 例如:确认保存设置,确认退出等
MENU_DEBUG("Confirm state, event: %d", event);
return err;
}
/**
* @brief 错误状态的动作处理
* @param global_ctx 全局上下文指针
* @param event 触发事件
* @return 错误码
*/
static MenuErrCode menu_state_action_error(MenuGlobalCtx* global_ctx, MenuEventType event)
{
MenuErrCode err = MENU_OK;
// Suppress unused parameter warnings
(void)global_ctx;
(void)event;
// 这里可以添加错误状态的具体逻辑
// 例如:显示错误信息,等待用户确认
MENU_DEBUG("Error state, event: %d", event);
return err;
}
/**
* @brief 刷新菜单显示内部调用对接port层显示接口
* @param global_ctx 全局上下文指针
*/
void menu_core_refresh_display(void)
void menu_core_refresh_display(MenuGlobalCtx* global_ctx)
{
MenuCoreCtx* ctx = menu_core_get_ctx();
MenuNode* current_node = menu_core_find_node(ctx->current_node_id);
MenuCoreCtx* ctx = &global_ctx->core;
MenuNode* current_node = menu_core_find_node(global_ctx, ctx->current_node_id);
if (current_node == NULL)
{
return;
@ -287,12 +436,28 @@ void menu_core_refresh_display(void)
/**
* @brief 从事件队列获取事件(非阻塞)
* @param global_ctx 全局上下文指针
* @param event 输出参数,获取到的事件
* @return 是否获取到事件
*/
static bool menu_core_get_event(MenuEvent* event)
/**
* @brief 获取菜单组件上下文所需的内存大小
* @return 上下文内存大小(字节)
*/
uint32_t menu_get_ctx_size(void)
{
MenuCoreCtx* ctx = menu_core_get_ctx();
return sizeof(MenuGlobalCtx);
}
/**
* @brief 从事件队列获取事件(非阻塞,优先级队列:高优先级事件先处理)
* @param global_ctx 全局上下文指针
* @param event 输出参数,获取到的事件
* @return 是否获取到事件
*/
static bool menu_core_get_event(MenuGlobalCtx* global_ctx, MenuEvent* event)
{
MenuCoreCtx* ctx = &global_ctx->core;
MENU_ASSERT(ctx != NULL && event != NULL);
if (ctx->event_queue.count == 0)
@ -300,9 +465,28 @@ static bool menu_core_get_event(MenuEvent* event)
return false;
}
// 从队列头取出事件
*event = ctx->event_queue.buffer[ctx->event_queue.head];
ctx->event_queue.head = (ctx->event_queue.head + 1) % MENU_CONFIG_EVENT_QUEUE_LEN;
// 查找优先级最高的事件
uint8_t highest_priority_idx = ctx->event_queue.head;
for (uint8_t i = 0; i < ctx->event_queue.count; i++)
{
uint8_t idx = (ctx->event_queue.head + i) % MENU_CONFIG_EVENT_QUEUE_LEN;
if (ctx->event_queue.buffer[idx].priority > ctx->event_queue.buffer[highest_priority_idx].priority)
{
highest_priority_idx = idx;
}
}
// 复制优先级最高的事件
*event = ctx->event_queue.buffer[highest_priority_idx];
// 将最后一个事件移到被取出的位置
uint8_t last_idx = (ctx->event_queue.head + ctx->event_queue.count - 1) % MENU_CONFIG_EVENT_QUEUE_LEN;
if (highest_priority_idx != last_idx)
{
ctx->event_queue.buffer[highest_priority_idx] = ctx->event_queue.buffer[last_idx];
}
// 更新队列计数
ctx->event_queue.count--;
return true;
@ -310,28 +494,30 @@ static bool menu_core_get_event(MenuEvent* event)
/**
* @brief 菜单主循环(需在用户主循环中调用,处理事件和刷新显示)
* @param global_ctx 全局上下文指针
*/
void menu_main_loop(void)
void menu_main_loop(MenuGlobalCtx* global_ctx)
{
MenuCoreCtx* ctx = menu_core_get_ctx();
if (!ctx->is_initialized)
MENU_ASSERT(global_ctx != NULL);
MenuCoreCtx* ctx = &global_ctx->core;
if (!global_ctx->is_initialized)
{
return;
}
// 处理事件队列
MenuEvent event;
while (menu_core_get_event(&event))
while (menu_core_get_event(global_ctx, &event))
{
// 检查事件是否超时
if (menu_utils_get_tick() - event.timestamp > MENU_CONFIG_EVENT_TIMEOUT)
{
MENU_DEBUG("Event timeout: type %d", event.type);
MENU_DEBUG("Event timeout: type %d, priority %d", event.type, event.priority);
continue;
}
// 处理事件
MenuErrCode err = menu_core_handle_event(&event);
MenuErrCode err = menu_core_handle_event(global_ctx, &event);
if (err != MENU_OK)
{
MENU_DEBUG("Event handle error: %d", err);
@ -341,44 +527,101 @@ void menu_main_loop(void)
// 定时刷新显示
if (menu_utils_get_tick() - ctx->last_refresh_tick >= MENU_CONFIG_REFRESH_INTERVAL)
{
menu_core_refresh_display();
menu_core_refresh_display(global_ctx);
ctx->last_refresh_tick = menu_utils_get_tick();
}
}
/**
* @brief 菜单组件初始化(必须首先调用)
* @param global_ctx 全局上下文指针
* @return 错误码
*/
MenuErrCode menu_init(void)
MenuErrCode menu_init(MenuGlobalCtx* global_ctx)
{
MenuErrCode err = menu_core_ctx_init();
if (err != MENU_OK)
if (global_ctx == NULL)
{
return err;
return MENU_ERR_INVALID_PARAM;
}
// 初始化全局上下文
MENU_MEM_SET_ZERO(global_ctx, sizeof(MenuGlobalCtx));
// 初始化核心上下文
MenuCoreCtx* ctx = &global_ctx->core;
MENU_MEM_SET_ZERO(ctx, sizeof(MenuCoreCtx));
ctx->is_initialized = true;
ctx->current_state = MENU_STATE_INIT; // 设置初始状态
// 初始化事件队列
MenuCoreCtx* ctx = menu_core_get_ctx();
ctx->last_refresh_tick = menu_utils_get_tick();
// 初始化其他功能模块(如果启用)
#if MENU_CONFIG_ENABLE_PARAM
global_ctx->param.count = 0;
#endif
#if MENU_CONFIG_ENABLE_LANG
global_ctx->lang.count = 0;
global_ctx->lang.current_lang_id = 0; // 默认语言ID
#endif
#if MENU_CONFIG_ENABLE_MODBUS_MAP
global_ctx->modbus.count = 0;
#endif
global_ctx->is_initialized = true;
// 初始状态转换从INIT到NORMAL
MenuEvent init_event = {
.type = MENU_EVENT_NONE,
.param = 0,
.timestamp = menu_utils_get_tick(),
.priority = MENU_EVENT_PRIORITY_NORMAL
};
menu_state_transition(global_ctx, init_event.type);
return MENU_OK;
}
/**
* @brief 菜单组件反初始化(释放资源)
* @param global_ctx 全局上下文指针
* @return 错误码
*/
MenuErrCode menu_deinit(MenuGlobalCtx* global_ctx)
{
if (global_ctx == NULL || !global_ctx->is_initialized)
{
return MENU_ERR_INVALID_PARAM;
}
// 反初始化各功能模块
// 目前都是静态内存,无需释放,只需重置状态
MENU_MEM_SET_ZERO(global_ctx, sizeof(MenuGlobalCtx));
return MENU_OK;
}
/**
* @brief 发送事件到菜单事件队列(如按键事件、自定义事件)
* @param global_ctx 全局上下文指针
* @param type 事件类型
* @param param 事件附加参数(可选,如按键长按时间)
* @param priority 事件优先级
* @return 错误码
*/
MenuErrCode menu_post_event(MenuEventType type, uint32_t param)
// Undefine the macro to avoid conflict with the function definition
#undef menu_post_event
MenuErrCode menu_post_event(MenuGlobalCtx* global_ctx, MenuEventType type, uint32_t param, MenuEventPriority priority)
{
MenuCoreCtx* ctx = menu_core_get_ctx();
if (!ctx->is_initialized)
if (global_ctx == NULL || !global_ctx->is_initialized)
{
return MENU_ERR_INVALID_PARAM;
return MENU_ERR_NOT_INITIALIZED;
}
MenuCoreCtx* ctx = &global_ctx->core;
// 检查事件队列是否已满
if (ctx->event_queue.count >= MENU_CONFIG_EVENT_QUEUE_LEN)
{
@ -386,17 +629,22 @@ MenuErrCode menu_post_event(MenuEventType type, uint32_t param)
}
// 入队事件
ctx->event_queue.buffer[ctx->event_queue.tail].type = type;
ctx->event_queue.buffer[ctx->event_queue.tail].param = param;
ctx->event_queue.buffer[ctx->event_queue.tail].timestamp = menu_utils_get_tick();
ctx->event_queue.tail = (ctx->event_queue.tail + 1) % MENU_CONFIG_EVENT_QUEUE_LEN;
uint8_t idx = (ctx->event_queue.head + ctx->event_queue.count) % MENU_CONFIG_EVENT_QUEUE_LEN;
ctx->event_queue.buffer[idx].type = type;
ctx->event_queue.buffer[idx].param = param;
ctx->event_queue.buffer[idx].timestamp = menu_utils_get_tick();
ctx->event_queue.buffer[idx].priority = priority;
ctx->event_queue.count++;
MENU_DEBUG("Event posted: type %d, param %lu, priority %d, count %d",
type, param, priority, ctx->event_queue.count);
return MENU_OK;
}
/**
* @brief 注册菜单节点(构建菜单树)
* @param global_ctx 全局上下文指针
* @param parent_id 父节点ID根节点填0
* @param node_id 当前节点ID唯一不可重复
* @param name_str 菜单名称字符串(或多语言索引)
@ -404,14 +652,14 @@ MenuErrCode menu_post_event(MenuEventType type, uint32_t param)
* @param exit_cb 退出该菜单的回调函数NULL表示无
* @return 错误码
*/
MenuErrCode menu_register_node(MenuNodeId parent_id, MenuNodeId node_id, const char* name_str, MenuCallback enter_cb, MenuCallback exit_cb)
MenuErrCode menu_register_node(MenuGlobalCtx* global_ctx, MenuNodeId parent_id, MenuNodeId node_id, const char* name_str, MenuCallback enter_cb, MenuCallback exit_cb)
{
MenuCoreCtx* ctx = menu_core_get_ctx();
if (!ctx->is_initialized || node_id == 0)
if (global_ctx == NULL || !global_ctx->is_initialized || node_id == 0)
{
return MENU_ERR_INVALID_PARAM;
}
MenuCoreCtx* ctx = &global_ctx->core;
// 查找空闲节点
MenuNode* new_node = NULL;
for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES; i++)
@ -429,7 +677,7 @@ MenuErrCode menu_register_node(MenuNodeId parent_id, MenuNodeId node_id, const c
}
// 检查节点ID是否已存在
if (menu_core_find_node(node_id) != NULL)
if (menu_core_find_node(global_ctx, node_id) != NULL)
{
return MENU_ERR_INVALID_PARAM;
}
@ -451,7 +699,7 @@ MenuErrCode menu_register_node(MenuNodeId parent_id, MenuNodeId node_id, const c
if (ctx->current_node_id != 0)
{
// 已有根节点,将新节点作为兄弟节点添加到根节点之后
MenuNode* root_node = menu_core_find_node(ctx->current_node_id);
MenuNode* root_node = menu_core_find_node(global_ctx, ctx->current_node_id);
while (root_node->next_sibling != NULL)
{
root_node = root_node->next_sibling;
@ -463,13 +711,13 @@ MenuErrCode menu_register_node(MenuNodeId parent_id, MenuNodeId node_id, const c
{
// 第一个根节点,设置为当前节点
ctx->current_node_id = node_id;
menu_core_stack_push(node_id);
menu_core_stack_push(global_ctx, node_id);
}
}
else
{
// 查找父节点
MenuNode* parent_node = menu_core_find_node(parent_id);
MenuNode* parent_node = menu_core_find_node(global_ctx, parent_id);
if (parent_node == NULL)
{
return MENU_ERR_NODE_NOT_FOUND;
@ -499,17 +747,123 @@ MenuErrCode menu_register_node(MenuNodeId parent_id, MenuNodeId node_id, const c
/**
* @brief 获取当前选中的菜单节点ID
* @param global_ctx 全局上下文指针
* @param node_id 输出参数当前节点ID
* @return 错误码
*/
MenuErrCode menu_get_current_node(MenuNodeId* node_id)
MenuErrCode menu_get_current_node(MenuGlobalCtx* global_ctx, MenuNodeId* node_id)
{
MenuCoreCtx* ctx = menu_core_get_ctx();
if (!ctx->is_initialized || node_id == NULL)
if (global_ctx == NULL || !global_ctx->is_initialized || node_id == NULL)
{
return MENU_ERR_INVALID_PARAM;
}
MenuCoreCtx* ctx = &global_ctx->core;
*node_id = ctx->current_node_id;
return MENU_OK;
}
}
/**
* @brief 批量注册菜单节点
* @param global_ctx 全局上下文指针
* @param configs 菜单节点配置数组
* @param config_count 配置数量
* @return 错误码
*/
MenuErrCode menu_register_nodes(MenuGlobalCtx* global_ctx, const MenuNodeConfig* configs, uint16_t config_count)
{
if (global_ctx == NULL || !global_ctx->is_initialized || configs == NULL || config_count == 0)
{
return MENU_ERR_INVALID_PARAM;
}
MenuErrCode err = MENU_OK;
for (uint16_t i = 0; i < config_count; i++)
{
err = menu_register_node(global_ctx, configs[i].parent_id, configs[i].node_id,
configs[i].name, configs[i].enter_cb, configs[i].exit_cb);
if (err != MENU_OK)
{
MENU_ERROR("Failed to register menu node %d at index %d: %s",
configs[i].node_id, i, menu_err_code_to_str(err));
return err;
}
}
return MENU_OK;
}
/**
* @brief 检查菜单组件是否已初始化
* @param global_ctx 全局上下文指针
* @return 是否已初始化
*/
bool menu_is_initialized(MenuGlobalCtx* global_ctx)
{
if (global_ctx == NULL)
{
return false;
}
return global_ctx->is_initialized;
}
#if MENU_CONFIG_ENABLE_MEM_MONITOR
/**
* @brief 获取菜单内存使用统计信息
* @param global_ctx 全局上下文指针
* @param stats 输出参数,内存使用统计信息
* @return 错误码
*/
MenuErrCode menu_get_mem_stats(MenuGlobalCtx* global_ctx, MenuMemStats* stats)
{
if (global_ctx == NULL || !global_ctx->is_initialized || stats == NULL)
{
return MENU_ERR_INVALID_PARAM;
}
MenuCoreCtx* ctx = &global_ctx->core;
// 初始化统计信息
MENU_MEM_SET_ZERO(stats, sizeof(MenuMemStats));
// 统计菜单节点使用情况
stats->total_nodes = MENU_CONFIG_MAX_NODES;
for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES; i++)
{
if (ctx->nodes[i].flags.is_registered)
{
stats->used_nodes++;
}
}
// 统计参数使用情况
stats->total_params = MENU_CONFIG_MAX_PARAMS;
#if MENU_CONFIG_ENABLE_PARAM
for (uint16_t i = 0; i < MENU_CONFIG_MAX_PARAMS; i++)
{
if (global_ctx->param.params[i].is_registered)
{
stats->used_params++;
}
}
#endif
// 统计Modbus映射使用情况
stats->total_modbus_maps = MENU_CONFIG_MAX_MODBUS_MAPS;
#if MENU_CONFIG_ENABLE_MODBUS_MAP
stats->used_modbus_maps = global_ctx->modbus.count;
#endif
// 统计事件队列使用情况
stats->event_queue_len = MENU_CONFIG_EVENT_QUEUE_LEN;
stats->event_queue_used = ctx->event_queue.count;
MENU_DEBUG("Memory stats: nodes %d/%d, params %d/%d, modbus %d/%d, events %d/%d",
stats->used_nodes, stats->total_nodes,
stats->used_params, stats->total_params,
stats->used_modbus_maps, stats->total_modbus_maps,
stats->event_queue_used, stats->event_queue_len);
return MENU_OK;
}
#endif // MENU_CONFIG_ENABLE_MEM_MONITOR

View File

@ -2,7 +2,7 @@
* @file menu_modbus.c
* @brief 菜单参数-Modbus寄存器映射注册、查询、数据双向转换工业级类型安全、字节序适配
*/
#include "menu.h"
#include "../api/menu.h"
#include "menu_core.h"
#include "menu_data.h"
#include "menu_def.h"
@ -169,16 +169,23 @@ static void menu_modbus_byte_order_convert(uint16_t* data, uint8_t len, uint8_t
/**
* @brief 查找Modbus映射通过参数ID
* @param ctx 菜单全局上下文
* @param param_id 参数ID
* @return 映射内部结构体指针NULL表示未找到
*/
static ModbusMapInternal* menu_modbus_find_by_param(uint16_t param_id)
static ModbusMapInternal* menu_modbus_find_by_param(MenuGlobalCtx* ctx, uint16_t param_id)
{
if (ctx == NULL)
{
return NULL;
}
MenuModbusCtx* modbus_ctx = &ctx->modbus;
for (uint8_t i = 0; i < MENU_CONFIG_MAX_MODBUS_MAPS; i++)
{
if (s_menu_modbus_maps[i].is_registered && s_menu_modbus_maps[i].pub.param_id == param_id)
if (modbus_ctx->maps[i].is_registered && modbus_ctx->maps[i].pub.param_id == param_id)
{
return &s_menu_modbus_maps[i];
return &modbus_ctx->maps[i];
}
}
return NULL;
@ -186,30 +193,37 @@ static ModbusMapInternal* menu_modbus_find_by_param(uint16_t param_id)
/**
* @brief 查找Modbus映射通过寄存器类型和地址
* @param ctx 菜单全局上下文
* @param reg_type 寄存器类型
* @param reg_addr 寄存器地址(协议地址)
* @return 映射内部结构体指针NULL表示未找到
*/
static ModbusMapInternal* menu_modbus_find_by_reg(ModbusRegType reg_type, uint16_t reg_addr)
static ModbusMapInternal* menu_modbus_find_by_reg(MenuGlobalCtx* ctx, ModbusRegType reg_type, uint16_t reg_addr)
{
if (ctx == NULL)
{
return NULL;
}
MenuModbusCtx* modbus_ctx = &ctx->modbus;
for (uint8_t i = 0; i < MENU_CONFIG_MAX_MODBUS_MAPS; i++)
{
if (s_menu_modbus_maps[i].is_registered &&
s_menu_modbus_maps[i].pub.reg_type == reg_type &&
s_menu_modbus_maps[i].pub.reg_addr == reg_addr)
if (modbus_ctx->maps[i].is_registered &&
modbus_ctx->maps[i].pub.reg_type == reg_type &&
modbus_ctx->maps[i].pub.reg_addr == reg_addr)
{
return &s_menu_modbus_maps[i];
return &modbus_ctx->maps[i];
}
}
return NULL;
}
/************************** 对外接口实现 **************************/
MenuErrCode menu_modbus_map_register(uint16_t param_id, ModbusRegType reg_type, uint16_t reg_addr, uint8_t reg_count, ModbusPerm perm, uint8_t byte_order)
MenuErrCode menu_modbus_map_register(MenuGlobalCtx* ctx, uint16_t param_id, ModbusRegType reg_type, uint16_t reg_addr, uint8_t reg_count, ModbusPerm perm, uint8_t byte_order)
{
// 1. 校验参数是否已注册(通过获取参数值间接检查)
float dummy_val;
MenuErrCode err = menu_param_get_value(param_id, &dummy_val);
MenuErrCode err = menu_param_get_value(ctx, param_id, &dummy_val);
if (err != MENU_OK)
{
MENU_DEBUG("Modbus map param %d not found", param_id);
@ -254,19 +268,20 @@ MenuErrCode menu_modbus_map_register(uint16_t param_id, ModbusRegType reg_type,
#endif
// 6. 检查映射是否已存在
if (menu_modbus_find_by_param(param_id) != NULL || menu_modbus_find_by_reg(reg_type, reg_addr) != NULL)
if (menu_modbus_find_by_param(ctx, param_id) != NULL || menu_modbus_find_by_reg(ctx, reg_type, reg_addr) != NULL)
{
MENU_DEBUG("Modbus map already exists: param %d, reg %d:%d", param_id, reg_type, reg_addr);
return MENU_ERR_INVALID_PARAM;
}
// 7. 查找空闲的映射位置(静态数组
// 7. 查找空闲的映射位置(从全局上下文获取
MenuModbusCtx* modbus_ctx = &ctx->modbus;
ModbusMapInternal* map = NULL;
for (uint8_t i = 0; i < MENU_CONFIG_MAX_MODBUS_MAPS; i++)
{
if (!s_menu_modbus_maps[i].is_registered)
if (!modbus_ctx->maps[i].is_registered)
{
map = &s_menu_modbus_maps[i];
map = &modbus_ctx->maps[i];
break;
}
}
@ -279,7 +294,7 @@ MenuErrCode menu_modbus_map_register(uint16_t param_id, ModbusRegType reg_type,
// 8. 获取参数类型
MenuParamType param_type;
err = menu_param_get_type(param_id, &param_type);
err = menu_param_get_type(ctx, param_id, &param_type);
if (err != MENU_OK)
{
return err;
@ -309,14 +324,14 @@ MenuErrCode menu_modbus_map_register(uint16_t param_id, ModbusRegType reg_type,
return MENU_OK;
}
MenuErrCode menu_modbus_map_query_by_param(uint16_t param_id, ModbusMap* map)
MenuErrCode menu_modbus_map_query_by_param(MenuGlobalCtx* ctx, uint16_t param_id, ModbusMap* map)
{
if (map == NULL)
if (ctx == NULL || map == NULL)
{
return MENU_ERR_INVALID_PARAM;
}
ModbusMapInternal* internal_map = menu_modbus_find_by_param(param_id);
ModbusMapInternal* internal_map = menu_modbus_find_by_param(ctx, param_id);
if (internal_map == NULL)
{
MENU_DEBUG("Modbus map param %d not found", param_id);
@ -328,14 +343,14 @@ MenuErrCode menu_modbus_map_query_by_param(uint16_t param_id, ModbusMap* map)
return MENU_OK;
}
MenuErrCode menu_modbus_map_query_by_reg(ModbusRegType reg_type, uint16_t reg_addr, ModbusMap* map)
MenuErrCode menu_modbus_map_query_by_reg(MenuGlobalCtx* ctx, ModbusRegType reg_type, uint16_t reg_addr, ModbusMap* map)
{
if (map == NULL)
if (ctx == NULL || map == NULL)
{
return MENU_ERR_INVALID_PARAM;
}
ModbusMapInternal* internal_map = menu_modbus_find_by_reg(reg_type, reg_addr);
ModbusMapInternal* internal_map = menu_modbus_find_by_reg(ctx, reg_type, reg_addr);
if (internal_map == NULL)
{
MENU_DEBUG("Modbus map reg %d:%d not found", reg_type, reg_addr);
@ -347,15 +362,15 @@ MenuErrCode menu_modbus_map_query_by_reg(ModbusRegType reg_type, uint16_t reg_ad
return MENU_OK;
}
MenuErrCode menu_modbus_map_param_to_reg(uint16_t param_id, uint8_t* reg_buf, uint8_t* buf_len)
MenuErrCode menu_modbus_map_param_to_reg(MenuGlobalCtx* ctx, uint16_t param_id, uint8_t* reg_buf, uint8_t* buf_len)
{
if (reg_buf == NULL || buf_len == NULL || *buf_len == 0)
if (ctx == NULL || reg_buf == NULL || buf_len == NULL || *buf_len == 0)
{
return MENU_ERR_INVALID_PARAM;
}
// 1. 查找映射关系
ModbusMapInternal* map = menu_modbus_find_by_param(param_id);
ModbusMapInternal* map = menu_modbus_find_by_param(ctx, param_id);
if (map == NULL)
{
return MENU_ERR_NODE_NOT_FOUND;
@ -372,7 +387,7 @@ MenuErrCode menu_modbus_map_param_to_reg(uint16_t param_id, uint8_t* reg_buf, ui
// 3. 获取参数值
float param_val;
MenuErrCode err = menu_param_get_value(param_id, &param_val);
MenuErrCode err = menu_param_get_value(ctx, param_id, &param_val);
if (err != MENU_OK)
{
MENU_DEBUG("Failed to get param value: %d", err);
@ -471,15 +486,15 @@ MenuErrCode menu_modbus_map_param_to_reg(uint16_t param_id, uint8_t* reg_buf, ui
return MENU_OK;
}
MenuErrCode menu_modbus_map_reg_to_param(uint16_t param_id, const uint8_t* reg_buf, uint8_t buf_len)
MenuErrCode menu_modbus_map_reg_to_param(MenuGlobalCtx* ctx, uint16_t param_id, const uint8_t* reg_buf, uint8_t buf_len)
{
if (reg_buf == NULL || buf_len == 0)
if (ctx == NULL || reg_buf == NULL || buf_len == 0)
{
return MENU_ERR_INVALID_PARAM;
}
// 1. 查找映射关系
ModbusMapInternal* map = menu_modbus_find_by_param(param_id);
ModbusMapInternal* map = menu_modbus_find_by_param(ctx, param_id);
if (map == NULL)
{
return MENU_ERR_NODE_NOT_FOUND;
@ -505,13 +520,6 @@ MenuErrCode menu_modbus_map_reg_to_param(uint16_t param_id, const uint8_t* reg_b
// 4. 复制寄存器数据并进行字节序转换(读取寄存器后)
uint16_t reg_data[MENU_CONFIG_MAX_MODBUS_MAPS * 2] = {0}; // 足够大的临时缓冲区
// 检查缓冲区长度是否足够
if (buf_len < req_len)
{
MENU_DEBUG("Modbus reg buf too small: need %d bytes, got %d bytes", req_len, buf_len);
return MENU_ERR_INVALID_PARAM;
}
// 复制数据到临时缓冲区
memcpy(reg_data, reg_buf, req_len);
@ -613,7 +621,7 @@ MenuErrCode menu_modbus_map_reg_to_param(uint16_t param_id, const uint8_t* reg_b
}
// 6. 设置参数值(自动进行范围检查)
MenuErrCode err = menu_param_set_value(param_id, param_val);
MenuErrCode err = menu_param_set_value(ctx, param_id, param_val);
if (err != MENU_OK)
{
return err;
@ -623,7 +631,6 @@ MenuErrCode menu_modbus_map_reg_to_param(uint16_t param_id, const uint8_t* reg_b
return MENU_OK;
}
// 定义Modbus映射静态数组
ModbusMapInternal s_menu_modbus_maps[MENU_CONFIG_MAX_MODBUS_MAPS];
#endif // MENU_CONFIG_ENABLE_MODBUS_MAP

View File

@ -7,23 +7,24 @@
#if MENU_CONFIG_ENABLE_LANG
/************************** 全局变量定义 **************************/
MenuLangStr s_menu_lang_strs[MENU_CONFIG_MAX_NODES * MENU_CONFIG_MAX_LANGS];
uint8_t s_current_lang_id = 0;
/************************** 内部函数声明 **************************/
static const char* menu_lang_find_str(MenuGlobalCtx* global_ctx, uint16_t str_id, uint8_t lang_id);
/**
* @brief 查找语言字符串通过字符串ID和语言ID
* @param global_ctx 全局上下文指针
* @param str_id 字符串ID
* @param lang_id 语言ID
* @return 语言字符串NULL表示未找到
*/
static const char* menu_lang_find_str(uint16_t str_id, uint8_t lang_id)
static const char* menu_lang_find_str(MenuGlobalCtx* global_ctx, uint16_t str_id, uint8_t lang_id)
{
MenuLangCtx* lang_ctx = &global_ctx->lang;
for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES * MENU_CONFIG_MAX_LANGS; i++)
{
if (s_menu_lang_strs[i].str_id == str_id && s_menu_lang_strs[i].lang_id == lang_id)
if (lang_ctx->strs[i].str_id == str_id && lang_ctx->strs[i].lang_id == lang_id)
{
return s_menu_lang_strs[i].str;
return lang_ctx->strs[i].str;
}
}
return NULL;
@ -31,26 +32,33 @@ static const char* menu_lang_find_str(uint16_t str_id, uint8_t lang_id)
/**
* @brief 注册语言字符串
* @param global_ctx 全局上下文指针
* @param str_id 字符串ID
* @param lang_id 语言ID
* @param str 字符串内容
* @return 错误码
*/
MenuErrCode menu_lang_register_str(uint16_t str_id, uint8_t lang_id, const char* str)
MenuErrCode menu_lang_register_str(MenuGlobalCtx* global_ctx, uint16_t str_id, uint8_t lang_id, const char* str)
{
if (global_ctx == NULL || !global_ctx->is_initialized)
{
return MENU_ERR_NOT_INITIALIZED;
}
if (lang_id >= MENU_CONFIG_MAX_LANGS || str == NULL)
{
return MENU_ERR_INVALID_PARAM;
}
MenuLangCtx* lang_ctx = &global_ctx->lang;
// 查找空闲位置或已存在的字符串条目
for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES * MENU_CONFIG_MAX_LANGS; i++)
{
if (!s_menu_lang_strs[i].str || (s_menu_lang_strs[i].str_id == str_id && s_menu_lang_strs[i].lang_id == lang_id))
if (!lang_ctx->strs[i].str || (lang_ctx->strs[i].str_id == str_id && lang_ctx->strs[i].lang_id == lang_id))
{
s_menu_lang_strs[i].str_id = str_id;
s_menu_lang_strs[i].lang_id = lang_id;
s_menu_lang_strs[i].str = str;
lang_ctx->strs[i].str_id = str_id;
lang_ctx->strs[i].lang_id = lang_id;
lang_ctx->strs[i].str = str;
return MENU_OK;
}
}
@ -60,54 +68,70 @@ MenuErrCode menu_lang_register_str(uint16_t str_id, uint8_t lang_id, const char*
/**
* @brief 获取当前语言的字符串
* @param global_ctx 全局上下文指针
* @param str_id 字符串ID
* @return 字符串内容NULL表示未找到
*/
const char* menu_lang_get_str(uint16_t str_id)
const char* menu_lang_get_str(MenuGlobalCtx* global_ctx, uint16_t str_id)
{
const char* str = menu_lang_find_str(str_id, s_current_lang_id);
if (global_ctx == NULL || !global_ctx->is_initialized)
{
return NULL;
}
MenuLangCtx* lang_ctx = &global_ctx->lang;
const char* str = menu_lang_find_str(global_ctx, str_id, lang_ctx->current_lang_id);
if (str == NULL)
{
// 如果当前语言未找到尝试使用默认语言0
str = menu_lang_find_str(str_id, 0);
str = menu_lang_find_str(global_ctx, str_id, 0);
}
return str;
}
/**
* @brief 设置当前语言
* @param global_ctx 全局上下文指针
* @param lang_id 语言ID如0-中文1-英文)
* @return 错误码
*/
MenuErrCode menu_lang_set_current(uint8_t lang_id)
MenuErrCode menu_lang_set_current(MenuGlobalCtx* global_ctx, uint8_t lang_id)
{
if (global_ctx == NULL || !global_ctx->is_initialized)
{
return MENU_ERR_NOT_INITIALIZED;
}
if (lang_id >= MENU_CONFIG_MAX_LANGS)
{
return MENU_ERR_INVALID_PARAM;
}
s_current_lang_id = lang_id;
MenuLangCtx* lang_ctx = &global_ctx->lang;
lang_ctx->current_lang_id = lang_id;
MENU_DEBUG("Language changed to: %d", lang_id);
// 语言切换后,刷新菜单显示
menu_core_refresh_display();
menu_core_refresh_display(global_ctx);
return MENU_OK;
}
/**
* @brief 获取当前语言
* @param global_ctx 全局上下文指针
* @param lang_id 输出参数当前语言ID
* @return 错误码
*/
MenuErrCode menu_lang_get_current(uint8_t* lang_id)
MenuErrCode menu_lang_get_current(MenuGlobalCtx* global_ctx, uint8_t* lang_id)
{
if (lang_id == NULL)
if (global_ctx == NULL || !global_ctx->is_initialized || lang_id == NULL)
{
return MENU_ERR_INVALID_PARAM;
}
*lang_id = s_current_lang_id;
MenuLangCtx* lang_ctx = &global_ctx->lang;
*lang_id = lang_ctx->current_lang_id;
return MENU_OK;
}
@ -120,4 +144,64 @@ uint8_t menu_lang_get_max_langs(void)
return MENU_CONFIG_MAX_LANGS;
}
/**
* @brief 批量注册语言字符串
* @param global_ctx 全局上下文指针
* @param strs 语言字符串数组
* @param count 字符串数量
* @return 错误码
*/
MenuErrCode menu_lang_register_strs(MenuGlobalCtx* global_ctx, const MenuLangStr* strs, uint16_t count)
{
if (global_ctx == NULL || !global_ctx->is_initialized || strs == NULL || count == 0)
{
return MENU_ERR_INVALID_PARAM;
}
MenuErrCode err = MENU_OK;
for (uint16_t i = 0; i < count; i++)
{
err = menu_lang_register_str(global_ctx, strs[i].str_id, strs[i].lang_id, strs[i].str);
if (err != MENU_OK)
{
MENU_ERROR("Failed to register lang str %d at index %d: %s",
strs[i].str_id, i, menu_err_code_to_str(err));
return err;
}
}
return MENU_OK;
}
/**
* @brief 加载语言包(动态加载语言字符串)
* @param global_ctx 全局上下文指针
* @param lang_id 语言ID
* @param strs 语言字符串数组
* @param count 字符串数量
* @return 错误码
*/
MenuErrCode menu_lang_load_pack(MenuGlobalCtx* global_ctx, uint8_t lang_id, const MenuLangStr* strs, uint16_t count)
{
if (global_ctx == NULL || !global_ctx->is_initialized || strs == NULL || count == 0)
{
return MENU_ERR_INVALID_PARAM;
}
// 先清除该语言的所有字符串
MenuLangCtx* lang_ctx = &global_ctx->lang;
for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES * MENU_CONFIG_MAX_LANGS; i++)
{
if (lang_ctx->strs[i].lang_id == lang_id)
{
lang_ctx->strs[i].str_id = 0;
lang_ctx->strs[i].lang_id = 0;
lang_ctx->strs[i].str = NULL;
}
}
// 再注册新的字符串
return menu_lang_register_strs(global_ctx, strs, count);
}
#endif // MENU_CONFIG_ENABLE_LANG

View File

@ -7,21 +7,30 @@
#if MENU_CONFIG_ENABLE_PARAM
/************************** 全局变量定义 **************************/
MenuParam s_menu_params[MENU_CONFIG_MAX_PARAMS];
/************************** 内部函数声明 **************************/
static MenuParam* menu_param_find(MenuGlobalCtx* global_ctx, uint16_t param_id);
static void menu_param_float_to_internal(MenuParam* param, float value);
static float menu_param_internal_to_float(const MenuParam* param);
/**
* @brief 查找参数通过参数ID内部使用
* @param global_ctx 全局上下文指针
* @param param_id 参数ID
* @return 参数指针NULL表示未找到
*/
static MenuParam* menu_param_find(uint16_t param_id)
static MenuParam* menu_param_find(MenuGlobalCtx* global_ctx, uint16_t param_id)
{
if (global_ctx == NULL)
{
return NULL;
}
MenuParamCtx* param_ctx = &global_ctx->param;
for (uint16_t i = 0; i < MENU_CONFIG_MAX_PARAMS; i++)
{
if (s_menu_params[i].is_registered && s_menu_params[i].id == param_id)
if (param_ctx->params[i].is_registered && param_ctx->params[i].id == param_id)
{
return &s_menu_params[i];
return &param_ctx->params[i];
}
}
return NULL;
@ -124,6 +133,7 @@ static float menu_param_internal_to_float(const MenuParam* param)
/**
* @brief 注册参数到菜单节点(参数管理功能)
* @param global_ctx 全局上下文指针
* @param node_id 菜单节点ID参数与菜单绑定
* @param param_id 参数ID唯一
* @param type 参数类型
@ -133,33 +143,39 @@ static float menu_param_internal_to_float(const MenuParam* param)
* @param scale 缩放因子如0.1表示保留1位小数
* @return 错误码
*/
MenuErrCode menu_param_register(MenuNodeId node_id, uint16_t param_id, MenuParamType type, float min_val, float max_val, float default_val, float scale)
MenuErrCode menu_param_register(MenuGlobalCtx* global_ctx, MenuNodeId node_id, uint16_t param_id, MenuParamType type, float min_val, float max_val, float default_val, float scale)
{
if (global_ctx == NULL || !global_ctx->is_initialized)
{
return MENU_ERR_NOT_INITIALIZED;
}
if (scale <= 0.0f)
{
return MENU_ERR_INVALID_PARAM;
}
// 查找菜单节点
MenuNode* node = menu_core_find_node(node_id);
MenuNode* node = menu_core_find_node(global_ctx, node_id);
if (node == NULL)
{
return MENU_ERR_NODE_NOT_FOUND;
}
// 检查参数ID是否已存在
if (menu_param_find(param_id) != NULL)
if (menu_param_find(global_ctx, param_id) != NULL)
{
return MENU_ERR_INVALID_PARAM;
}
// 查找空闲参数位置
MenuParamCtx* param_ctx = &global_ctx->param;
MenuParam* param = NULL;
for (uint16_t i = 0; i < MENU_CONFIG_MAX_PARAMS; i++)
{
if (!s_menu_params[i].is_registered)
if (!param_ctx->params[i].is_registered)
{
param = &s_menu_params[i];
param = &param_ctx->params[i];
break;
}
}
@ -194,14 +210,20 @@ MenuErrCode menu_param_register(MenuNodeId node_id, uint16_t param_id, MenuParam
/**
* @brief 设置参数值
* @param global_ctx 全局上下文指针
* @param param_id 参数ID
* @param value 新值(浮点型,内部自动转换)
* @return 错误码
*/
MenuErrCode menu_param_set_value(uint16_t param_id, float value)
MenuErrCode menu_param_set_value(MenuGlobalCtx* global_ctx, uint16_t param_id, float value)
{
if (global_ctx == NULL || !global_ctx->is_initialized)
{
return MENU_ERR_NOT_INITIALIZED;
}
// 查找参数
MenuParam* param = menu_param_find(param_id);
MenuParam* param = menu_param_find(global_ctx, param_id);
if (param == NULL)
{
return MENU_ERR_NODE_NOT_FOUND;
@ -218,19 +240,20 @@ MenuErrCode menu_param_set_value(uint16_t param_id, float value)
/**
* @brief 获取参数值
* @param global_ctx 全局上下文指针
* @param param_id 参数ID
* @param value 输出参数,当前值(浮点型)
* @return 错误码
*/
MenuErrCode menu_param_get_value(uint16_t param_id, float* value)
MenuErrCode menu_param_get_value(MenuGlobalCtx* global_ctx, uint16_t param_id, float* value)
{
if (value == NULL)
if (global_ctx == NULL || !global_ctx->is_initialized || value == NULL)
{
return MENU_ERR_INVALID_PARAM;
}
// 查找参数
MenuParam* param = menu_param_find(param_id);
MenuParam* param = menu_param_find(global_ctx, param_id);
if (param == NULL)
{
return MENU_ERR_NODE_NOT_FOUND;
@ -246,13 +269,19 @@ MenuErrCode menu_param_get_value(uint16_t param_id, float* value)
/**
* @brief 将参数恢复为默认值
* @param global_ctx 全局上下文指针
* @param param_id 参数ID
* @return 错误码
*/
MenuErrCode menu_param_restore_default(uint16_t param_id)
MenuErrCode menu_param_restore_default(MenuGlobalCtx* global_ctx, uint16_t param_id)
{
if (global_ctx == NULL || !global_ctx->is_initialized)
{
return MENU_ERR_NOT_INITIALIZED;
}
// 查找参数
MenuParam* param = menu_param_find(param_id);
MenuParam* param = menu_param_find(global_ctx, param_id);
if (param == NULL)
{
return MENU_ERR_NODE_NOT_FOUND;
@ -260,60 +289,72 @@ MenuErrCode menu_param_restore_default(uint16_t param_id)
// 恢复默认值
float default_val = menu_param_internal_to_float((const MenuParam*)&param->default_val);
return menu_param_set_value(param_id, default_val);
return menu_param_set_value(global_ctx, param_id, default_val);
}
/**
* @brief 增加参数值(用于菜单上下键调整)
* @param global_ctx 全局上下文指针
* @param param_id 参数ID
* @param step 步长(浮点型)
* @return 错误码
*/
MenuErrCode menu_param_increase(uint16_t param_id, float step)
MenuErrCode menu_param_increase(MenuGlobalCtx* global_ctx, uint16_t param_id, float step)
{
if (global_ctx == NULL || !global_ctx->is_initialized)
{
return MENU_ERR_NOT_INITIALIZED;
}
float current_val = 0.0f;
MenuErrCode err = menu_param_get_value(param_id, &current_val);
MenuErrCode err = menu_param_get_value(global_ctx, param_id, &current_val);
if (err != MENU_OK)
{
return err;
}
return menu_param_set_value(param_id, current_val + step);
return menu_param_set_value(global_ctx, param_id, current_val + step);
}
/**
* @brief 减少参数值(用于菜单上下键调整)
* @param global_ctx 全局上下文指针
* @param param_id 参数ID
* @param step 步长(浮点型)
* @return 错误码
*/
MenuErrCode menu_param_decrease(uint16_t param_id, float step)
MenuErrCode menu_param_decrease(MenuGlobalCtx* global_ctx, uint16_t param_id, float step)
{
if (global_ctx == NULL || !global_ctx->is_initialized)
{
return MENU_ERR_NOT_INITIALIZED;
}
float current_val = 0.0f;
MenuErrCode err = menu_param_get_value(param_id, &current_val);
MenuErrCode err = menu_param_get_value(global_ctx, param_id, &current_val);
if (err != MENU_OK)
{
return err;
}
return menu_param_set_value(param_id, current_val - step);
return menu_param_set_value(global_ctx, param_id, current_val - step);
}
/**
* @brief 获取参数类型
* @param global_ctx 全局上下文指针
* @param param_id 参数ID
* @param type 输出参数,参数类型
* @return 错误码
*/
MenuErrCode menu_param_get_type(uint16_t param_id, MenuParamType* type)
MenuErrCode menu_param_get_type(MenuGlobalCtx* global_ctx, uint16_t param_id, MenuParamType* type)
{
if (type == NULL)
if (global_ctx == NULL || !global_ctx->is_initialized || type == NULL)
{
return MENU_ERR_INVALID_PARAM;
}
// 查找参数
MenuParam* param = menu_param_find(param_id);
MenuParam* param = menu_param_find(global_ctx, param_id);
if (param == NULL)
{
return MENU_ERR_NODE_NOT_FOUND;
@ -324,4 +365,35 @@ MenuErrCode menu_param_get_type(uint16_t param_id, MenuParamType* type)
return MENU_OK;
}
/**
* @brief 批量注册参数到菜单节点
* @param global_ctx 全局上下文指针
* @param configs 参数配置数组
* @param config_count 配置数量
* @return 错误码
*/
MenuErrCode menu_param_register_batch(MenuGlobalCtx* global_ctx, const MenuParamConfig* configs, uint16_t config_count)
{
if (global_ctx == NULL || !global_ctx->is_initialized || configs == NULL || config_count == 0)
{
return MENU_ERR_INVALID_PARAM;
}
MenuErrCode err = MENU_OK;
for (uint16_t i = 0; i < config_count; i++)
{
err = menu_param_register(global_ctx, configs[i].node_id, configs[i].param_id,
configs[i].type, configs[i].min_val, configs[i].max_val,
configs[i].default_val, configs[i].scale);
if (err != MENU_OK)
{
MENU_ERROR("Failed to register param %d at index %d: %s",
configs[i].param_id, i, menu_err_code_to_str(err));
return err;
}
}
return MENU_OK;
}
#endif // MENU_CONFIG_ENABLE_PARAM

39
src/param/menu_param.h Normal file
View File

@ -0,0 +1,39 @@
/**
* @file menu_param.h
* @brief 菜单参数管理内部类型定义(用户无需关心)
*/
#ifndef MENU_PARAM_H
#define MENU_PARAM_H
#include "menu_core.h"
#include <stdint.h>
#include <stdbool.h>
/**
* @brief 参数值共用体(支持多种数据类型)
*/
typedef union {
int8_t i8; ///< 8位有符号整数
uint8_t u8; ///< 8位无符号整数
int16_t i16; ///< 16位有符号整数
uint16_t u16; ///< 16位无符号整数
int32_t i32; ///< 32位有符号整数
uint32_t u32; ///< 32位无符号整数
float f; ///< 浮点数
} MenuParamValue;
/**
* @brief 参数结构体(参数管理的核心单元)
*/
typedef struct {
uint16_t id; ///< 参数ID唯一
MenuParamType type; ///< 参数类型
MenuParamValue value; ///< 当前值
MenuParamValue default_val;///< 默认值
float min_val; ///< 最小值(浮点表示)
float max_val; ///< 最大值(浮点表示)
float scale; ///< 缩放因子
bool is_registered : 1; ///< 是否已注册
} MenuParam;
#endif // MENU_PARAM_H

View File

@ -5,6 +5,24 @@
#include "menu_def.h"
#include "menu_port.h"
#include <stdarg.h>
#include <stdio.h>
/**
* @brief 错误码字符串映射
*/
static const char* menu_err_str[] = {
"OK", ///< MENU_OK
"Invalid parameter", ///< MENU_ERR_INVALID_PARAM
"Out of memory", ///< MENU_ERR_OUT_OF_MEMORY
"Node not found", ///< MENU_ERR_NODE_NOT_FOUND
"Stack overflow", ///< MENU_ERR_STACK_OVERFLOW
"Stack underflow", ///< MENU_ERR_STACK_UNDERFLOW
"Event queue full", ///< MENU_ERR_EVENT_QUEUE_FULL
"Feature not supported", ///< MENU_ERR_NOT_SUPPORTED
"Hardware port error", ///< MENU_ERR_HW_PORT_ERROR
"Component not initialized", ///< MENU_ERR_NOT_INITIALIZED
"Invalid context pointer" ///< MENU_ERR_INVALID_CONTEXT
};
/**
* @brief 断言失败处理函数
@ -14,14 +32,57 @@
void menu_utils_assert_failed(const char* file, uint32_t line)
{
// 调用port层的打印接口输出断言信息
menu_utils_printf("[MENU ASSERT] %s:%lu\r\n", file, line);
menu_utils_log(MENU_LOG_LEVEL_CRITICAL, file, line, "Assertion failed");
// 可以在这里添加其他处理,如触发硬件异常、保存上下文等
// 例如menu_port_trigger_exception();
}
/**
* @brief 调试打印函数对接port层的硬件打印接口
* @brief 通用日志函数对接port层的硬件打印接口
* @param level 日志级别
* @param file 文件名
* @param line 行号
* @param fmt 格式化字符串
* @param ... 可变参数
*/
void menu_utils_log(MenuLogLevel level, const char* file, uint32_t line, const char* fmt, ...)
{
// Explicitly cast unused parameters to void to suppress warnings
(void)level;
(void)file;
(void)line;
va_list args;
va_start(args, fmt);
// Get the driver and use its printf directly
const MenuPortDriver* driver = menu_port_get_driver();
if (driver != NULL && driver->printf != NULL)
{
// Output the log message
driver->printf(fmt, args);
}
va_end(args);
}
/**
* @brief 错误码转换为字符串
* @param err_code 错误码
* @return 错误信息字符串
*/
const char* menu_err_code_to_str(MenuErrCode err_code)
{
if (err_code < sizeof(menu_err_str) / sizeof(menu_err_str[0])) {
return menu_err_str[err_code];
} else {
return "Unknown error";
}
}
/**
* @brief 调试打印函数兼容旧接口对接port层的硬件打印接口
* @param fmt 格式化字符串
* @param ... 可变参数
*/
@ -31,7 +92,6 @@ void menu_utils_printf(const char* fmt, ...)
va_start(args, fmt);
// 调用port层的可变参数打印接口
// 注意这里需要用户在port层实现menu_port_printf函数
menu_port_printf(fmt, args);
va_end(args);