优化整定一版
This commit is contained in:
523
API.md
Normal file
523
API.md
Normal file
@ -0,0 +1,523 @@
|
|||||||
|
# 菜单组件API文档
|
||||||
|
|
||||||
|
## 1. 概述
|
||||||
|
|
||||||
|
本文档详细描述了菜单组件的API接口,包括核心功能、功能扩展和Modbus映射功能。
|
||||||
|
|
||||||
|
## 2. 核心接口
|
||||||
|
|
||||||
|
### 2.1 上下文管理
|
||||||
|
|
||||||
|
```c
|
||||||
|
/**
|
||||||
|
* @brief 获取菜单组件上下文所需的内存大小
|
||||||
|
* @return 上下文内存大小(字节)
|
||||||
|
*/
|
||||||
|
uint32_t menu_get_ctx_size(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 菜单组件初始化(必须首先调用)
|
||||||
|
* @param ctx 菜单上下文指针(用户需提前分配内存)
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_init(MenuGlobalCtx* ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 菜单组件反初始化(释放资源)
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_deinit(MenuGlobalCtx* ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查菜单组件是否已初始化
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @return 是否已初始化
|
||||||
|
*/
|
||||||
|
bool menu_is_initialized(MenuGlobalCtx* ctx);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 菜单管理
|
||||||
|
|
||||||
|
```c
|
||||||
|
/**
|
||||||
|
* @brief 注册菜单节点(构建菜单树)
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param parent_id 父节点ID(根节点填0)
|
||||||
|
* @param node_id 当前节点ID(唯一,不可重复)
|
||||||
|
* @param name_str 菜单名称字符串(或多语言索引)
|
||||||
|
* @param enter_cb 进入该菜单的回调函数(NULL表示无)
|
||||||
|
* @param exit_cb 退出该菜单的回调函数(NULL表示无)
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_register_node(MenuGlobalCtx* ctx, MenuNodeId parent_id, MenuNodeId node_id, const char* name_str, MenuCallback enter_cb, MenuCallback exit_cb);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 批量注册菜单节点
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param configs 菜单节点配置数组
|
||||||
|
* @param config_count 配置数量
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_register_nodes(MenuGlobalCtx* ctx, const MenuNodeConfig* configs, uint16_t config_count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前选中的菜单节点ID
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param node_id 输出参数,当前节点ID
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_get_current_node(MenuGlobalCtx* ctx, MenuNodeId* node_id);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 事件处理
|
||||||
|
|
||||||
|
```c
|
||||||
|
/**
|
||||||
|
* @brief 发送事件到菜单事件队列(如按键事件、自定义事件)
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param type 事件类型
|
||||||
|
* @param param 事件附加参数(可选,如按键长按时间)
|
||||||
|
* @param priority 事件优先级(可选,默认为普通优先级)
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_post_event(MenuGlobalCtx* ctx, MenuEventType type, uint32_t param, MenuEventPriority priority);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 菜单主循环(需在用户主循环中调用,处理事件和刷新显示)
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
*/
|
||||||
|
void menu_main_loop(MenuGlobalCtx* ctx);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. 功能扩展接口
|
||||||
|
|
||||||
|
### 3.1 参数管理
|
||||||
|
|
||||||
|
```c
|
||||||
|
/**
|
||||||
|
* @brief 注册参数到菜单节点(参数管理功能)
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param node_id 菜单节点ID(参数与菜单绑定)
|
||||||
|
* @param param_id 参数ID(唯一)
|
||||||
|
* @param type 参数类型
|
||||||
|
* @param min_val 最小值(浮点型,内部自动转换)
|
||||||
|
* @param max_val 最大值(浮点型,内部自动转换)
|
||||||
|
* @param default_val 默认值(浮点型,内部自动转换)
|
||||||
|
* @param scale 缩放因子(如0.1表示保留1位小数)
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_param_register(MenuGlobalCtx* ctx, MenuNodeId node_id, uint16_t param_id, MenuParamType type, float min_val, float max_val, float default_val, float scale);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 批量注册参数到菜单节点
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param configs 参数配置数组
|
||||||
|
* @param config_count 配置数量
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_param_register_batch(MenuGlobalCtx* ctx, const MenuParamConfig* configs, uint16_t config_count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置参数值
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param param_id 参数ID
|
||||||
|
* @param value 新值(浮点型,内部自动转换)
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_param_set_value(MenuGlobalCtx* ctx, uint16_t param_id, float value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取参数值
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param param_id 参数ID
|
||||||
|
* @param value 输出参数,当前值(浮点型)
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_param_get_value(MenuGlobalCtx* ctx, uint16_t param_id, float* value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取参数类型
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param param_id 参数ID
|
||||||
|
* @param type 输出参数,参数类型
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_param_get_type(MenuGlobalCtx* ctx, uint16_t param_id, MenuParamType* type);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 多语言支持
|
||||||
|
|
||||||
|
```c
|
||||||
|
/**
|
||||||
|
* @brief 设置当前语言
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param lang_id 语言ID(如0-中文,1-英文)
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_lang_set_current(MenuGlobalCtx* ctx, uint8_t lang_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前语言
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param lang_id 输出参数,当前语言ID
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_lang_get_current(MenuGlobalCtx* ctx, uint8_t* lang_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 注册语言字符串
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param str_id 字符串ID
|
||||||
|
* @param lang_id 语言ID
|
||||||
|
* @param str 语言字符串
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_lang_register_str(MenuGlobalCtx* ctx, uint16_t str_id, uint8_t lang_id, const char* str);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 批量注册语言字符串
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param strs 语言字符串数组
|
||||||
|
* @param count 字符串数量
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_lang_register_strs(MenuGlobalCtx* ctx, const MenuLangStr* strs, uint16_t count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 加载语言包(动态加载语言字符串)
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param lang_id 语言ID
|
||||||
|
* @param strs 语言字符串数组
|
||||||
|
* @param count 字符串数量
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_lang_load_pack(MenuGlobalCtx* ctx, uint8_t lang_id, const MenuLangStr* strs, uint16_t count);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 内存监控
|
||||||
|
|
||||||
|
```c
|
||||||
|
/**
|
||||||
|
* @brief 获取菜单内存使用统计信息
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param stats 输出参数,内存使用统计信息
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_get_mem_stats(MenuGlobalCtx* ctx, MenuMemStats* stats);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Modbus映射功能
|
||||||
|
|
||||||
|
```c
|
||||||
|
/**
|
||||||
|
* @brief 注册参数与Modbus寄存器的映射关系
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param param_id 参数ID(需已通过menu_param_register注册)
|
||||||
|
* @param reg_type 寄存器类型
|
||||||
|
* @param reg_addr 寄存器起始地址(协议地址,如40001)
|
||||||
|
* @param reg_count 占用寄存器数量(如1:16位,2:32位,4:64位)
|
||||||
|
* @param perm 读写权限
|
||||||
|
* @param byte_order 字节序(可选,默认使用MENU_CONFIG_MODBUS_BYTE_ORDER)
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 批量注册参数与Modbus寄存器的映射关系
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param configs Modbus映射配置数组
|
||||||
|
* @param config_count 配置数量
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_modbus_map_register_batch(MenuGlobalCtx* ctx, const MenuModbusMapConfig* configs, uint16_t config_count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 根据参数ID查询Modbus映射关系
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param param_id 参数ID
|
||||||
|
* @param map 输出参数,映射关系结构体
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_modbus_map_query_by_param(MenuGlobalCtx* ctx, uint16_t param_id, ModbusMap* map);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 根据寄存器地址和类型查询Modbus映射关系
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param reg_type 寄存器类型
|
||||||
|
* @param reg_addr 寄存器地址
|
||||||
|
* @param map 输出参数,映射关系结构体
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_modbus_map_query_by_reg(MenuGlobalCtx* ctx, ModbusRegType reg_type, uint16_t reg_addr, ModbusMap* map);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将参数值写入对应的Modbus寄存器(双向转换:参数值→寄存器数据)
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param param_id 参数ID
|
||||||
|
* @param reg_buf 输出参数,寄存器数据缓冲区(需足够大,如32位占2个16位寄存器)
|
||||||
|
* @param buf_len 缓冲区长度(输入),实际写入长度(输出)
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_modbus_map_param_to_reg(MenuGlobalCtx* ctx, uint16_t param_id, uint8_t* reg_buf, uint8_t* buf_len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将Modbus寄存器数据读取到参数值(双向转换:寄存器数据→参数值)
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param param_id 参数ID
|
||||||
|
* @param reg_buf 输入参数,寄存器数据缓冲区
|
||||||
|
* @param buf_len 缓冲区长度
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_modbus_map_reg_to_param(MenuGlobalCtx* ctx, uint16_t param_id, const uint8_t* reg_buf, uint8_t buf_len);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. 数据类型
|
||||||
|
|
||||||
|
### 5.1 错误码
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef enum {
|
||||||
|
MENU_OK = 0, ///< 操作成功
|
||||||
|
MENU_ERR_INVALID_PARAM, ///< 无效参数
|
||||||
|
MENU_ERR_OUT_OF_MEMORY, ///< 内存不足(静态内存池已满)
|
||||||
|
MENU_ERR_NODE_NOT_FOUND, ///< 菜单节点未找到
|
||||||
|
MENU_ERR_STACK_OVERFLOW, ///< 菜单栈溢出(导航层级超过配置)
|
||||||
|
MENU_ERR_STACK_UNDERFLOW, ///< 菜单栈下溢(已到根节点仍返回)
|
||||||
|
MENU_ERR_EVENT_QUEUE_FULL, ///< 事件队列已满
|
||||||
|
MENU_ERR_NOT_SUPPORTED, ///< 功能未启用(如未开启多语言)
|
||||||
|
MENU_ERR_HW_PORT_ERROR, ///< 硬件端口层错误
|
||||||
|
MENU_ERR_NOT_INITIALIZED, ///< 组件未初始化
|
||||||
|
MENU_ERR_INVALID_CONTEXT ///< 无效上下文指针
|
||||||
|
} MenuErrCode;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 事件类型
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef enum {
|
||||||
|
MENU_EVENT_NONE = 0, ///< 无事件
|
||||||
|
MENU_EVENT_KEY_UP, ///< 上键按下
|
||||||
|
MENU_EVENT_KEY_DOWN, ///< 下键按下
|
||||||
|
MENU_EVENT_KEY_ENTER, ///< 确认键按下
|
||||||
|
MENU_EVENT_KEY_BACK, ///< 返回键按下
|
||||||
|
MENU_EVENT_CUSTOM_BEGIN = 0x10, ///< 自定义事件起始标识(用户可扩展)
|
||||||
|
} MenuEventType;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 事件优先级
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef enum {
|
||||||
|
MENU_EVENT_PRIORITY_LOW = 0, ///< 低优先级事件
|
||||||
|
MENU_EVENT_PRIORITY_NORMAL, ///< 普通优先级事件(默认)
|
||||||
|
MENU_EVENT_PRIORITY_HIGH, ///< 高优先级事件
|
||||||
|
MENU_EVENT_PRIORITY_CRITICAL ///< 紧急优先级事件
|
||||||
|
} MenuEventPriority;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.4 参数类型
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef enum {
|
||||||
|
MENU_PARAM_TYPE_INT8,
|
||||||
|
MENU_PARAM_TYPE_UINT8,
|
||||||
|
MENU_PARAM_TYPE_INT16,
|
||||||
|
MENU_PARAM_TYPE_UINT16,
|
||||||
|
MENU_PARAM_TYPE_INT32,
|
||||||
|
MENU_PARAM_TYPE_UINT32,
|
||||||
|
MENU_PARAM_TYPE_FLOAT,
|
||||||
|
} MenuParamType;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.5 菜单状态
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef enum {
|
||||||
|
MENU_STATE_INIT = 0, ///< 初始化状态
|
||||||
|
MENU_STATE_NORMAL, ///< 正常导航状态
|
||||||
|
MENU_STATE_PARAM_EDIT, ///< 参数编辑状态
|
||||||
|
MENU_STATE_CONFIRM, ///< 确认状态(如保存/退出确认)
|
||||||
|
MENU_STATE_ERROR, ///< 错误状态
|
||||||
|
} MenuState;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. 配置项
|
||||||
|
|
||||||
|
菜单组件的配置项位于`menu_config.h`文件中,用户可以根据项目需求调整以下配置:
|
||||||
|
|
||||||
|
### 6.1 核心配置
|
||||||
|
|
||||||
|
- `MENU_CONFIG_MAX_NODES`:最大菜单节点数
|
||||||
|
- `MENU_CONFIG_STACK_DEPTH`:菜单栈深度
|
||||||
|
- `MENU_CONFIG_EVENT_QUEUE_LEN`:事件队列长度
|
||||||
|
- `MENU_CONFIG_ENABLE_ASSERT`:是否启用断言
|
||||||
|
- `MENU_CONFIG_ENABLE_DEBUG`:是否启用调试打印
|
||||||
|
- `MENU_CONFIG_ENABLE_MEM_MONITOR`:是否启用内存监控
|
||||||
|
|
||||||
|
### 6.2 功能扩展配置
|
||||||
|
|
||||||
|
- `MENU_CONFIG_ENABLE_PARAM`:是否启用参数管理功能
|
||||||
|
- `MENU_CONFIG_ENABLE_LANG`:是否启用多语言功能
|
||||||
|
- `MENU_CONFIG_MAX_PARAMS`:最大参数数量
|
||||||
|
- `MENU_CONFIG_MAX_LANGS`:支持的最大语言数量
|
||||||
|
|
||||||
|
### 6.3 Modbus映射配置
|
||||||
|
|
||||||
|
- `MENU_CONFIG_ENABLE_MODBUS_MAP`:是否启用参数-Modbus寄存器映射功能
|
||||||
|
- `MENU_CONFIG_MAX_MODBUS_MAPS`:最大Modbus映射数量
|
||||||
|
- `MENU_CONFIG_MODBUS_MAX_ADDR`:Modbus寄存器地址最大值
|
||||||
|
- `MENU_CONFIG_MODBUS_BYTE_ORDER`:默认Modbus字节序
|
||||||
|
- `MENU_CONFIG_MODBUS_PERMISSION`:是否启用Modbus映射权限校验
|
||||||
|
|
||||||
|
## 7. 硬件适配
|
||||||
|
|
||||||
|
菜单组件的硬件适配层位于`port/menu_port.h`和`port/menu_port.c`文件中,用户需要实现以下接口:
|
||||||
|
|
||||||
|
```c
|
||||||
|
/**
|
||||||
|
* @brief 硬件端口驱动结构体(模块化设计,支持运行时配置)
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
void (*printf)(const char* fmt, va_list args); ///< 硬件打印接口
|
||||||
|
uint32_t (*get_tick)(void); ///< 获取系统滴答时间
|
||||||
|
void (*delay_ms)(uint32_t ms); ///< 硬件延迟函数
|
||||||
|
void (*display)(const char* menu_name, uint16_t menu_id); ///< 菜单显示接口
|
||||||
|
MenuEventType (*key_scan)(void); ///< 按键扫描接口(可选)
|
||||||
|
void (*irq_ctrl)(bool enable); ///< 中断管理接口(可选)
|
||||||
|
void (*error_handler)(MenuErrCode err_code); ///< 错误处理接口(可选)
|
||||||
|
MenuErrCode (*modbus_send)(ModbusRegType reg_type, uint16_t reg_addr, const uint8_t* reg_buf, uint8_t buf_len); ///< Modbus发送接口(可选)
|
||||||
|
MenuErrCode (*modbus_receive)(ModbusRegType reg_type, uint16_t reg_addr, uint8_t* reg_buf, uint8_t buf_len); ///< Modbus接收接口(可选)
|
||||||
|
} MenuPortDriver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 硬件端口初始化接口
|
||||||
|
* @param driver 硬件驱动结构体指针
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_port_init(const MenuPortDriver* driver);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 硬件端口反初始化接口
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_port_deinit(void);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. 使用示例
|
||||||
|
|
||||||
|
### 8.1 基本使用
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "menu.h"
|
||||||
|
#include "menu_port.h"
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
// 1. 初始化硬件
|
||||||
|
hal_init();
|
||||||
|
|
||||||
|
// 2. 初始化菜单上下文
|
||||||
|
uint8_t menu_ctx_buf[menu_get_ctx_size()];
|
||||||
|
MenuGlobalCtx* ctx = (MenuGlobalCtx*)menu_ctx_buf;
|
||||||
|
menu_init(ctx);
|
||||||
|
|
||||||
|
// 3. 注册菜单节点
|
||||||
|
menu_register_node(ctx, 0, 1, "主菜单", NULL, NULL);
|
||||||
|
menu_register_node(ctx, 1, 2, "参数设置", NULL, NULL);
|
||||||
|
menu_register_node(ctx, 1, 3, "系统信息", NULL, NULL);
|
||||||
|
|
||||||
|
// 4. 主循环
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
// 扫描按键
|
||||||
|
MenuEventType key_event = scan_key();
|
||||||
|
if (key_event != MENU_EVENT_NONE)
|
||||||
|
{
|
||||||
|
menu_post_event(ctx, key_event, 0, MENU_EVENT_PRIORITY_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理菜单事件
|
||||||
|
menu_main_loop(ctx);
|
||||||
|
|
||||||
|
// 其他业务逻辑
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 反初始化
|
||||||
|
menu_deinit(ctx);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 批量注册菜单节点
|
||||||
|
|
||||||
|
```c
|
||||||
|
// 定义菜单节点配置
|
||||||
|
MenuNodeConfig menu_nodes[] = {
|
||||||
|
{1, 0, "主菜单", NULL, NULL},
|
||||||
|
{2, 1, "参数设置", NULL, NULL},
|
||||||
|
{3, 1, "系统信息", NULL, NULL},
|
||||||
|
{4, 2, "温度参数", NULL, NULL},
|
||||||
|
{5, 2, "湿度参数", NULL, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 批量注册菜单节点
|
||||||
|
menu_register_nodes(ctx, menu_nodes, sizeof(menu_nodes) / sizeof(menu_nodes[0]));
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 参数管理
|
||||||
|
|
||||||
|
```c
|
||||||
|
// 注册参数
|
||||||
|
menu_param_register(ctx, 4, 1, MENU_PARAM_TYPE_FLOAT, 0.0f, 100.0f, 25.0f, 1.0f);
|
||||||
|
|
||||||
|
// 设置参数值
|
||||||
|
menu_param_set_value(ctx, 1, 30.0f);
|
||||||
|
|
||||||
|
// 获取参数值
|
||||||
|
float value;
|
||||||
|
menu_param_get_value(ctx, 1, &value);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.4 多语言支持
|
||||||
|
|
||||||
|
```c
|
||||||
|
// 注册语言字符串
|
||||||
|
menu_lang_register_str(ctx, 1, 0, "中文菜单");
|
||||||
|
menu_lang_register_str(ctx, 1, 1, "English Menu");
|
||||||
|
|
||||||
|
// 设置当前语言为中文
|
||||||
|
menu_lang_set_current(ctx, 0);
|
||||||
|
|
||||||
|
// 动态加载语言包
|
||||||
|
MenuLangStr lang_pack[] = {
|
||||||
|
{2, 0, "中文参数"},
|
||||||
|
{2, 1, "English Param"},
|
||||||
|
{3, 0, "中文信息"},
|
||||||
|
{3, 1, "English Info"}
|
||||||
|
};
|
||||||
|
menu_lang_load_pack(ctx, 0, lang_pack, sizeof(lang_pack) / sizeof(lang_pack[0]));
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.5 Modbus映射
|
||||||
|
|
||||||
|
```c
|
||||||
|
// 注册Modbus映射
|
||||||
|
menu_modbus_map_register(ctx, 1, MODBUS_REG_TYPE_HOLDING_REG, 40001, 2, MODBUS_PERM_READ_WRITE, 2);
|
||||||
|
|
||||||
|
// 参数值转换为Modbus寄存器数据
|
||||||
|
uint8_t reg_buf[4];
|
||||||
|
uint8_t buf_len = sizeof(reg_buf);
|
||||||
|
menu_modbus_map_param_to_reg(ctx, 1, reg_buf, &buf_len);
|
||||||
|
|
||||||
|
// Modbus寄存器数据转换为参数值
|
||||||
|
menu_modbus_map_reg_to_param(ctx, 1, reg_buf, buf_len);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. 版本信息
|
||||||
|
|
||||||
|
- 版本:v1.0.0
|
||||||
|
- 最后更新:2025-12-18
|
||||||
|
- 适用平台:所有支持C语言的嵌入式系统
|
||||||
406
api/menu.h
406
api/menu.h
@ -11,6 +11,12 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
/************************** 全局类型导出 **************************/
|
/************************** 全局类型导出 **************************/
|
||||||
|
/**
|
||||||
|
* @brief 菜单组件全局上下文(用户需创建并管理)
|
||||||
|
* @note 替代全局变量,提高模块独立性和可测试性
|
||||||
|
*/
|
||||||
|
typedef struct MenuGlobalCtx MenuGlobalCtx;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 菜单错误码(工业级错误处理:明确所有可能的错误类型)
|
* @brief 菜单错误码(工业级错误处理:明确所有可能的错误类型)
|
||||||
*/
|
*/
|
||||||
@ -23,9 +29,21 @@ typedef enum {
|
|||||||
MENU_ERR_STACK_UNDERFLOW, ///< 菜单栈下溢(已到根节点仍返回)
|
MENU_ERR_STACK_UNDERFLOW, ///< 菜单栈下溢(已到根节点仍返回)
|
||||||
MENU_ERR_EVENT_QUEUE_FULL, ///< 事件队列已满
|
MENU_ERR_EVENT_QUEUE_FULL, ///< 事件队列已满
|
||||||
MENU_ERR_NOT_SUPPORTED, ///< 功能未启用(如未开启多语言)
|
MENU_ERR_NOT_SUPPORTED, ///< 功能未启用(如未开启多语言)
|
||||||
MENU_ERR_HW_PORT_ERROR ///< 硬件端口层错误
|
MENU_ERR_HW_PORT_ERROR, ///< 硬件端口层错误
|
||||||
|
MENU_ERR_NOT_INITIALIZED, ///< 组件未初始化
|
||||||
|
MENU_ERR_INVALID_CONTEXT ///< 无效上下文指针
|
||||||
} MenuErrCode;
|
} MenuErrCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 事件优先级枚举
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
MENU_EVENT_PRIORITY_LOW = 0, ///< 低优先级事件
|
||||||
|
MENU_EVENT_PRIORITY_NORMAL, ///< 普通优先级事件(默认)
|
||||||
|
MENU_EVENT_PRIORITY_HIGH, ///< 高优先级事件
|
||||||
|
MENU_EVENT_PRIORITY_CRITICAL ///< 紧急优先级事件
|
||||||
|
} MenuEventPriority;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 菜单事件类型(事件驱动核心:解耦按键与菜单逻辑)
|
* @brief 菜单事件类型(事件驱动核心:解耦按键与菜单逻辑)
|
||||||
*/
|
*/
|
||||||
@ -50,116 +68,19 @@ typedef uint16_t MenuNodeId;
|
|||||||
*/
|
*/
|
||||||
typedef MenuErrCode (*MenuCallback)(MenuNodeId node_id);
|
typedef MenuErrCode (*MenuCallback)(MenuNodeId node_id);
|
||||||
|
|
||||||
/************************** 核心接口声明 **************************/
|
|
||||||
/**
|
/**
|
||||||
* @brief 菜单组件初始化(必须首先调用)
|
* @brief 参数类型枚举(支持多种数据类型)
|
||||||
* @return 错误码
|
|
||||||
*/
|
|
||||||
MenuErrCode menu_init(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 菜单主循环(需在用户主循环中调用,处理事件和刷新显示)
|
|
||||||
*/
|
|
||||||
void menu_main_loop(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 发送事件到菜单事件队列(如按键事件、自定义事件)
|
|
||||||
* @param type 事件类型
|
|
||||||
* @param param 事件附加参数(可选,如按键长按时间)
|
|
||||||
* @return 错误码
|
|
||||||
*/
|
|
||||||
MenuErrCode menu_post_event(MenuEventType type, uint32_t param);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 注册菜单节点(构建菜单树)
|
|
||||||
* @param parent_id 父节点ID(根节点填0)
|
|
||||||
* @param node_id 当前节点ID(唯一,不可重复)
|
|
||||||
* @param name_str 菜单名称字符串(或多语言索引)
|
|
||||||
* @param enter_cb 进入该菜单的回调函数(NULL表示无)
|
|
||||||
* @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);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前选中的菜单节点ID
|
|
||||||
* @param node_id 输出参数,当前节点ID
|
|
||||||
* @return 错误码
|
|
||||||
*/
|
|
||||||
MenuErrCode menu_get_current_node(MenuNodeId* node_id);
|
|
||||||
|
|
||||||
/************************** 功能扩展接口声明(可裁剪) **************************/
|
|
||||||
#if MENU_CONFIG_ENABLE_PARAM
|
|
||||||
/**
|
|
||||||
* @brief 参数类型枚举
|
|
||||||
*/
|
*/
|
||||||
typedef enum {
|
typedef enum {
|
||||||
MENU_PARAM_TYPE_INT8,
|
MENU_PARAM_TYPE_INT8, ///< 8位有符号整数
|
||||||
MENU_PARAM_TYPE_UINT8,
|
MENU_PARAM_TYPE_UINT8, ///< 8位无符号整数
|
||||||
MENU_PARAM_TYPE_INT16,
|
MENU_PARAM_TYPE_INT16, ///< 16位有符号整数
|
||||||
MENU_PARAM_TYPE_UINT16,
|
MENU_PARAM_TYPE_UINT16, ///< 16位无符号整数
|
||||||
MENU_PARAM_TYPE_INT32,
|
MENU_PARAM_TYPE_INT32, ///< 32位有符号整数
|
||||||
MENU_PARAM_TYPE_UINT32,
|
MENU_PARAM_TYPE_UINT32, ///< 32位无符号整数
|
||||||
MENU_PARAM_TYPE_FLOAT,
|
MENU_PARAM_TYPE_FLOAT, ///< 浮点类型
|
||||||
} MenuParamType;
|
} MenuParamType;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 注册参数到菜单节点(参数管理功能)
|
|
||||||
* @param node_id 菜单节点ID(参数与菜单绑定)
|
|
||||||
* @param param_id 参数ID(唯一)
|
|
||||||
* @param type 参数类型
|
|
||||||
* @param min_val 最小值(浮点型,内部自动转换)
|
|
||||||
* @param max_val 最大值(浮点型,内部自动转换)
|
|
||||||
* @param default_val 默认值(浮点型,内部自动转换)
|
|
||||||
* @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);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置参数值
|
|
||||||
* @param param_id 参数ID
|
|
||||||
* @param value 新值(浮点型,内部自动转换)
|
|
||||||
* @return 错误码
|
|
||||||
*/
|
|
||||||
MenuErrCode menu_param_set_value(uint16_t param_id, float value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取参数值
|
|
||||||
* @param param_id 参数ID
|
|
||||||
* @param value 输出参数,当前值(浮点型)
|
|
||||||
* @return 错误码
|
|
||||||
*/
|
|
||||||
MenuErrCode menu_param_get_value(uint16_t param_id, float* value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取参数类型
|
|
||||||
* @param param_id 参数ID
|
|
||||||
* @param type 输出参数,参数类型
|
|
||||||
* @return 错误码
|
|
||||||
*/
|
|
||||||
MenuErrCode menu_param_get_type(uint16_t param_id, MenuParamType* type);
|
|
||||||
|
|
||||||
#endif // MENU_CONFIG_ENABLE_PARAM
|
|
||||||
|
|
||||||
#if MENU_CONFIG_ENABLE_LANG
|
|
||||||
/**
|
|
||||||
* @brief 设置当前语言
|
|
||||||
* @param lang_id 语言ID(如0-中文,1-英文)
|
|
||||||
* @return 错误码
|
|
||||||
*/
|
|
||||||
MenuErrCode menu_lang_set_current(uint8_t lang_id);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前语言
|
|
||||||
* @param lang_id 输出参数,当前语言ID
|
|
||||||
* @return 错误码
|
|
||||||
*/
|
|
||||||
MenuErrCode menu_lang_get_current(uint8_t* lang_id);
|
|
||||||
#endif // MENU_CONFIG_ENABLE_LANG
|
|
||||||
|
|
||||||
/************************** Modbus映射功能(启用时有效) **************************/
|
|
||||||
#if MENU_CONFIG_ENABLE_MODBUS_MAP
|
|
||||||
/**
|
/**
|
||||||
* @brief Modbus寄存器类型(符合Modbus协议标准)
|
* @brief Modbus寄存器类型(符合Modbus协议标准)
|
||||||
*/
|
*/
|
||||||
@ -179,6 +100,255 @@ typedef enum {
|
|||||||
MODBUS_PERM_READ_WRITE, ///< 读写(如保持寄存器)
|
MODBUS_PERM_READ_WRITE, ///< 读写(如保持寄存器)
|
||||||
} ModbusPerm;
|
} ModbusPerm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 菜单节点配置结构体(用于批量注册)
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
MenuNodeId node_id; ///< 节点ID
|
||||||
|
MenuNodeId parent_id; ///< 父节点ID
|
||||||
|
const char* name; ///< 菜单名称
|
||||||
|
MenuCallback enter_cb; ///< 进入回调
|
||||||
|
MenuCallback exit_cb; ///< 退出回调
|
||||||
|
} MenuNodeConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 参数配置结构体(用于批量注册)
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
MenuNodeId node_id; ///< 关联的菜单节点ID
|
||||||
|
uint16_t param_id; ///< 参数ID
|
||||||
|
MenuParamType type; ///< 参数类型
|
||||||
|
float min_val; ///< 最小值
|
||||||
|
float max_val; ///< 最大值
|
||||||
|
float default_val; ///< 默认值
|
||||||
|
float scale; ///< 缩放因子
|
||||||
|
} MenuParamConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Modbus映射配置结构体(用于批量注册)
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint16_t param_id; ///< 关联的参数ID
|
||||||
|
ModbusRegType reg_type; ///< 寄存器类型
|
||||||
|
uint16_t reg_addr; ///< 寄存器地址
|
||||||
|
uint8_t reg_count; ///< 寄存器数量
|
||||||
|
ModbusPerm perm; ///< 读写权限
|
||||||
|
uint8_t byte_order; ///< 字节序
|
||||||
|
} MenuModbusMapConfig;
|
||||||
|
|
||||||
|
/************************** 核心接口声明 **************************/
|
||||||
|
/**
|
||||||
|
* @brief 获取菜单组件上下文所需的内存大小
|
||||||
|
* @return 上下文内存大小(字节)
|
||||||
|
*/
|
||||||
|
uint32_t menu_get_ctx_size(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 菜单组件初始化(必须首先调用)
|
||||||
|
* @param ctx 菜单上下文指针(用户需提前分配内存)
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_init(MenuGlobalCtx* ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 菜单组件反初始化(释放资源)
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_deinit(MenuGlobalCtx* ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 菜单主循环(需在用户主循环中调用,处理事件和刷新显示)
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
*/
|
||||||
|
void menu_main_loop(MenuGlobalCtx* ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 发送事件到菜单事件队列(如按键事件、自定义事件)
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param type 事件类型
|
||||||
|
* @param param 事件附加参数(可选,如按键长按时间)
|
||||||
|
* @param priority 事件优先级(可选,默认为普通优先级)
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_post_event(MenuGlobalCtx* ctx, MenuEventType type, uint32_t param, MenuEventPriority priority);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 发送事件到菜单事件队列(兼容旧接口,默认优先级)
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param type 事件类型
|
||||||
|
* @param param 事件附加参数
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
#define menu_post_event(ctx, type, param) menu_post_event((ctx), (type), (param), MENU_EVENT_PRIORITY_NORMAL)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 注册菜单节点(构建菜单树)
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param parent_id 父节点ID(根节点填0)
|
||||||
|
* @param node_id 当前节点ID(唯一,不可重复)
|
||||||
|
* @param name_str 菜单名称字符串(或多语言索引)
|
||||||
|
* @param enter_cb 进入该菜单的回调函数(NULL表示无)
|
||||||
|
* @param exit_cb 退出该菜单的回调函数(NULL表示无)
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_register_node(MenuGlobalCtx* ctx, MenuNodeId parent_id, MenuNodeId node_id, const char* name_str, MenuCallback enter_cb, MenuCallback exit_cb);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 批量注册菜单节点
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param configs 菜单节点配置数组
|
||||||
|
* @param config_count 配置数量
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_register_nodes(MenuGlobalCtx* ctx, const MenuNodeConfig* configs, uint16_t config_count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前选中的菜单节点ID
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param node_id 输出参数,当前节点ID
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_get_current_node(MenuGlobalCtx* ctx, MenuNodeId* node_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查菜单组件是否已初始化
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @return 是否已初始化
|
||||||
|
*/
|
||||||
|
bool menu_is_initialized(MenuGlobalCtx* ctx);
|
||||||
|
|
||||||
|
#if MENU_CONFIG_ENABLE_MEM_MONITOR
|
||||||
|
/**
|
||||||
|
* @brief 获取菜单内存使用统计信息
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param stats 输出参数,内存使用统计信息
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_get_mem_stats(MenuGlobalCtx* ctx, MenuMemStats* stats);
|
||||||
|
#endif // MENU_CONFIG_ENABLE_MEM_MONITOR
|
||||||
|
|
||||||
|
/************************** 核心类型定义 **************************/
|
||||||
|
|
||||||
|
/************************** 功能扩展接口声明(可裁剪) **************************/
|
||||||
|
#if MENU_CONFIG_ENABLE_PARAM
|
||||||
|
/**
|
||||||
|
* @brief 注册参数到菜单节点(参数管理功能)
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param node_id 菜单节点ID(参数与菜单绑定)
|
||||||
|
* @param param_id 参数ID(唯一)
|
||||||
|
* @param type 参数类型
|
||||||
|
* @param min_val 最小值(浮点型,内部自动转换)
|
||||||
|
* @param max_val 最大值(浮点型,内部自动转换)
|
||||||
|
* @param default_val 默认值(浮点型,内部自动转换)
|
||||||
|
* @param scale 缩放因子(如0.1表示保留1位小数)
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_param_register(MenuGlobalCtx* ctx, MenuNodeId node_id, uint16_t param_id, MenuParamType type, float min_val, float max_val, float default_val, float scale);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 批量注册参数到菜单节点
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param configs 参数配置数组
|
||||||
|
* @param config_count 配置数量
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_param_register_batch(MenuGlobalCtx* ctx, const MenuParamConfig* configs, uint16_t config_count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置参数值
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param param_id 参数ID
|
||||||
|
* @param value 新值(浮点型,内部自动转换)
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_param_set_value(MenuGlobalCtx* ctx, uint16_t param_id, float value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取参数值
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param param_id 参数ID
|
||||||
|
* @param value 输出参数,当前值(浮点型)
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_param_get_value(MenuGlobalCtx* ctx, uint16_t param_id, float* value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取参数类型
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param param_id 参数ID
|
||||||
|
* @param type 输出参数,参数类型
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_param_get_type(MenuGlobalCtx* ctx, uint16_t param_id, MenuParamType* type);
|
||||||
|
|
||||||
|
#endif // MENU_CONFIG_ENABLE_PARAM
|
||||||
|
|
||||||
|
#if MENU_CONFIG_ENABLE_LANG
|
||||||
|
/**
|
||||||
|
* @brief 语言字符串结构体(用于批量注册和动态加载)
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint16_t str_id; ///< 字符串ID
|
||||||
|
uint8_t lang_id; ///< 语言ID
|
||||||
|
const char* str; ///< 语言字符串
|
||||||
|
} MenuLangStr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置当前语言
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param lang_id 语言ID(如0-中文,1-英文)
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_lang_set_current(MenuGlobalCtx* ctx, uint8_t lang_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前语言
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param lang_id 输出参数,当前语言ID
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_lang_get_current(MenuGlobalCtx* ctx, uint8_t* lang_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 注册语言字符串
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param str_id 字符串ID
|
||||||
|
* @param lang_id 语言ID
|
||||||
|
* @param str 语言字符串
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_lang_register_str(MenuGlobalCtx* ctx, uint16_t str_id, uint8_t lang_id, const char* str);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 批量注册语言字符串
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param strs 语言字符串数组
|
||||||
|
* @param count 字符串数量
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_lang_register_strs(MenuGlobalCtx* ctx, const MenuLangStr* strs, uint16_t count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 动态加载语言包
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param lang_id 语言ID
|
||||||
|
* @param strs 语言字符串数组
|
||||||
|
* @param count 字符串数量
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_lang_load_pack(MenuGlobalCtx* ctx, uint8_t lang_id, const MenuLangStr* strs, uint16_t count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取支持的最大语言数量
|
||||||
|
* @return 最大语言数量
|
||||||
|
*/
|
||||||
|
uint8_t menu_lang_get_max_langs(void);
|
||||||
|
#endif // MENU_CONFIG_ENABLE_LANG
|
||||||
|
|
||||||
|
/************************** Modbus映射功能(启用时有效) **************************/
|
||||||
|
#if MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Modbus映射结构体(对外只读,内部初始化)
|
* @brief Modbus映射结构体(对外只读,内部初始化)
|
||||||
*/
|
*/
|
||||||
@ -193,6 +363,7 @@ typedef struct {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 注册参数与Modbus寄存器的映射关系
|
* @brief 注册参数与Modbus寄存器的映射关系
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
* @param param_id 参数ID(需已通过menu_param_register注册)
|
* @param param_id 参数ID(需已通过menu_param_register注册)
|
||||||
* @param reg_type 寄存器类型
|
* @param reg_type 寄存器类型
|
||||||
* @param reg_addr 寄存器起始地址(协议地址,如40001)
|
* @param reg_addr 寄存器起始地址(协议地址,如40001)
|
||||||
@ -201,42 +372,55 @@ typedef struct {
|
|||||||
* @param byte_order 字节序(可选,默认使用MENU_CONFIG_MODBUS_BYTE_ORDER)
|
* @param byte_order 字节序(可选,默认使用MENU_CONFIG_MODBUS_BYTE_ORDER)
|
||||||
* @return 错误码
|
* @return 错误码
|
||||||
*/
|
*/
|
||||||
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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 批量注册参数与Modbus寄存器的映射关系
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
|
* @param configs Modbus映射配置数组
|
||||||
|
* @param config_count 配置数量
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_modbus_map_register_batch(MenuGlobalCtx* ctx, const MenuModbusMapConfig* configs, uint16_t config_count);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 根据参数ID查询Modbus映射关系
|
* @brief 根据参数ID查询Modbus映射关系
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
* @param param_id 参数ID
|
* @param param_id 参数ID
|
||||||
* @param map 输出参数,映射关系结构体
|
* @param map 输出参数,映射关系结构体
|
||||||
* @return 错误码
|
* @return 错误码
|
||||||
*/
|
*/
|
||||||
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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 根据寄存器地址和类型查询Modbus映射关系
|
* @brief 根据寄存器地址和类型查询Modbus映射关系
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
* @param reg_type 寄存器类型
|
* @param reg_type 寄存器类型
|
||||||
* @param reg_addr 寄存器地址
|
* @param reg_addr 寄存器地址
|
||||||
* @param map 输出参数,映射关系结构体
|
* @param map 输出参数,映射关系结构体
|
||||||
* @return 错误码
|
* @return 错误码
|
||||||
*/
|
*/
|
||||||
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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 将参数值写入对应的Modbus寄存器(双向转换:参数值→寄存器数据)
|
* @brief 将参数值写入对应的Modbus寄存器(双向转换:参数值→寄存器数据)
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
* @param param_id 参数ID
|
* @param param_id 参数ID
|
||||||
* @param reg_buf 输出参数,寄存器数据缓冲区(需足够大,如32位占2个16位寄存器)
|
* @param reg_buf 输出参数,寄存器数据缓冲区(需足够大,如32位占2个16位寄存器)
|
||||||
* @param buf_len 缓冲区长度(输入),实际写入长度(输出)
|
* @param buf_len 缓冲区长度(输入),实际写入长度(输出)
|
||||||
* @return 错误码
|
* @return 错误码
|
||||||
*/
|
*/
|
||||||
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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 将Modbus寄存器数据读取到参数值(双向转换:寄存器数据→参数值)
|
* @brief 将Modbus寄存器数据读取到参数值(双向转换:寄存器数据→参数值)
|
||||||
|
* @param ctx 菜单上下文指针
|
||||||
* @param param_id 参数ID
|
* @param param_id 参数ID
|
||||||
* @param reg_buf 输入参数,寄存器数据缓冲区
|
* @param reg_buf 输入参数,寄存器数据缓冲区
|
||||||
* @param buf_len 缓冲区长度
|
* @param buf_len 缓冲区长度
|
||||||
* @return 错误码
|
* @return 错误码
|
||||||
*/
|
*/
|
||||||
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);
|
||||||
|
|
||||||
#endif // MENU_CONFIG_ENABLE_MODBUS_MAP
|
#endif // MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,9 @@
|
|||||||
#ifndef MENU_CONFIG_H
|
#ifndef MENU_CONFIG_H
|
||||||
#define MENU_CONFIG_H
|
#define MENU_CONFIG_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
/************************** 核心配置 **************************/
|
/************************** 核心配置 **************************/
|
||||||
/**
|
/**
|
||||||
* @brief 最大菜单节点数(静态内存,根据项目调整)
|
* @brief 最大菜单节点数(静态内存,根据项目调整)
|
||||||
@ -30,7 +33,26 @@
|
|||||||
/**
|
/**
|
||||||
* @brief 是否启用调试打印(调试时开启,发布时关闭)
|
* @brief 是否启用调试打印(调试时开启,发布时关闭)
|
||||||
*/
|
*/
|
||||||
#define MENU_CONFIG_ENABLE_DEBUG 1
|
#define MENU_CONFIG_ENABLE_DEBUG 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 是否启用内存监控(调试时开启,发布时关闭)
|
||||||
|
*/
|
||||||
|
#define MENU_CONFIG_ENABLE_MEM_MONITOR 1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 内存使用统计结构体
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint16_t total_nodes; ///< 总菜单节点数
|
||||||
|
uint16_t used_nodes; ///< 已使用菜单节点数
|
||||||
|
uint16_t total_params; ///< 总参数数
|
||||||
|
uint16_t used_params; ///< 已使用参数数
|
||||||
|
uint16_t total_modbus_maps; ///< 总Modbus映射数
|
||||||
|
uint16_t used_modbus_maps; ///< 已使用Modbus映射数
|
||||||
|
uint16_t event_queue_len; ///< 事件队列长度
|
||||||
|
uint16_t event_queue_used; ///< 事件队列已使用长度
|
||||||
|
} MenuMemStats;
|
||||||
|
|
||||||
/************************** 功能扩展配置(可裁剪) **************************/
|
/************************** 功能扩展配置(可裁剪) **************************/
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
* @note 工业级嵌入式菜单组件 - 使用示例
|
* @note 工业级嵌入式菜单组件 - 使用示例
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
#include "menu.h"
|
#include "menu.h"
|
||||||
#include "menu_port.h"
|
#include "menu_port.h"
|
||||||
#include "menu_def.h"
|
#include "menu_def.h"
|
||||||
@ -31,6 +32,10 @@
|
|||||||
// 模拟Modbus寄存器缓冲区
|
// 模拟Modbus寄存器缓冲区
|
||||||
uint8_t g_modbus_reg_buf[256] = {0};
|
uint8_t g_modbus_reg_buf[256] = {0};
|
||||||
|
|
||||||
|
// 菜单上下文
|
||||||
|
static uint8_t* menu_ctx_buf = NULL;
|
||||||
|
static MenuGlobalCtx* g_menu_ctx = NULL;
|
||||||
|
|
||||||
/************************** 菜单回调函数 **************************/
|
/************************** 菜单回调函数 **************************/
|
||||||
/**
|
/**
|
||||||
* @brief 进入参数设置菜单回调
|
* @brief 进入参数设置菜单回调
|
||||||
@ -39,6 +44,7 @@ uint8_t g_modbus_reg_buf[256] = {0};
|
|||||||
*/
|
*/
|
||||||
static MenuErrCode menu_cb_enter_param(MenuNodeId node_id)
|
static MenuErrCode menu_cb_enter_param(MenuNodeId node_id)
|
||||||
{
|
{
|
||||||
|
(void)node_id; // Suppress unused parameter warning when debug is disabled
|
||||||
MENU_DEBUG("Enter param menu: %d", node_id);
|
MENU_DEBUG("Enter param menu: %d", node_id);
|
||||||
return MENU_OK;
|
return MENU_OK;
|
||||||
}
|
}
|
||||||
@ -50,6 +56,7 @@ static MenuErrCode menu_cb_enter_param(MenuNodeId node_id)
|
|||||||
*/
|
*/
|
||||||
static MenuErrCode menu_cb_exit_param(MenuNodeId node_id)
|
static MenuErrCode menu_cb_exit_param(MenuNodeId node_id)
|
||||||
{
|
{
|
||||||
|
(void)node_id; // Suppress unused parameter warning when debug is disabled
|
||||||
MENU_DEBUG("Exit param menu: %d", node_id);
|
MENU_DEBUG("Exit param menu: %d", node_id);
|
||||||
return MENU_OK;
|
return MENU_OK;
|
||||||
}
|
}
|
||||||
@ -61,41 +68,46 @@ static MenuErrCode menu_cb_exit_param(MenuNodeId node_id)
|
|||||||
*/
|
*/
|
||||||
static MenuErrCode menu_cb_enter_modbus(MenuNodeId node_id)
|
static MenuErrCode menu_cb_enter_modbus(MenuNodeId node_id)
|
||||||
{
|
{
|
||||||
MENU_DEBUG("Enter Modbus menu: %d", node_id);
|
(void)node_id; // Suppress unused parameter warning when debug is disabled
|
||||||
|
MENU_DEBUG("Enter modbus menu: %d", node_id);
|
||||||
return MENU_OK;
|
return MENU_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/************************** Modbus通信演示函数 **************************/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 模拟Modbus通信流程
|
* @brief 模拟Modbus通信演示
|
||||||
*/
|
*/
|
||||||
static void modbus_communication_demo(void)
|
static void modbus_communication_demo(void)
|
||||||
{
|
{
|
||||||
#if MENU_CONFIG_ENABLE_MODBUS_MAP
|
MENU_DEBUG("=== Modbus Communication Demo Start ===");
|
||||||
MENU_DEBUG("=== Modbus Communication Demo ===");
|
|
||||||
|
|
||||||
// 1. 模拟参数到寄存器的转换(参数修改后更新寄存器)
|
#if MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||||
|
|
||||||
|
// 1. 模拟参数到寄存器的转换(参数值更新后同步到寄存器)
|
||||||
{
|
{
|
||||||
MENU_DEBUG("1. Param to Reg Demo:");
|
MENU_DEBUG("1. Param to Reg Demo:");
|
||||||
|
|
||||||
// 设置温度参数为28.5
|
// 模拟温度参数变化(如从传感器读取到新的温度值)
|
||||||
menu_param_set_value(PARAM_TEMP_ID, 28.5f);
|
float new_temp = 30.5f;
|
||||||
|
MenuErrCode err = menu_param_set_value(g_menu_ctx, PARAM_TEMP_ID, new_temp);
|
||||||
// 将温度参数转换到Modbus寄存器
|
|
||||||
uint8_t buf_len = 4;
|
|
||||||
uint8_t reg_buf[4] = {0};
|
|
||||||
MenuErrCode err = menu_modbus_map_param_to_reg(PARAM_TEMP_ID, reg_buf, &buf_len);
|
|
||||||
if (err == MENU_OK)
|
if (err == MENU_OK)
|
||||||
{
|
{
|
||||||
MENU_DEBUG(" Temp param (28.5f) to reg: [0x%02X, 0x%02X, 0x%02X, 0x%02X]",
|
// 将参数值转换到Modbus寄存器缓冲区
|
||||||
reg_buf[0], reg_buf[1], reg_buf[2], reg_buf[3]);
|
uint8_t buf_len = 4;
|
||||||
|
uint8_t reg_buf[4] = {0};
|
||||||
|
|
||||||
|
err = menu_modbus_map_param_to_reg(g_menu_ctx, PARAM_TEMP_ID, reg_buf, &buf_len);
|
||||||
|
if (err == MENU_OK)
|
||||||
|
{
|
||||||
|
MENU_DEBUG(" Temp param %.1f°C to reg data: [0x%02X, 0x%02X, 0x%02X, 0x%02X]",
|
||||||
|
new_temp, reg_buf[0], reg_buf[1], reg_buf[2], reg_buf[3]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MENU_DEBUG(" Param to reg failed: %d", err);
|
MENU_DEBUG(" Param to reg failed: %d", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 2. 模拟寄存器到参数的转换(接收到Modbus写入请求后更新参数)
|
// 2. 模拟寄存器到参数的转换(接收到Modbus写入请求后更新参数)
|
||||||
{
|
{
|
||||||
@ -106,12 +118,12 @@ static void modbus_communication_demo(void)
|
|||||||
uint8_t reg_buf[4] = {0x82, 0x42, 0x00, 0x00};
|
uint8_t reg_buf[4] = {0x82, 0x42, 0x00, 0x00};
|
||||||
|
|
||||||
// 将Modbus寄存器数据转换为参数值
|
// 将Modbus寄存器数据转换为参数值
|
||||||
MenuErrCode err = menu_modbus_map_reg_to_param(PARAM_HUMID_ID, reg_buf, sizeof(reg_buf));
|
MenuErrCode err = menu_modbus_map_reg_to_param(g_menu_ctx, PARAM_HUMID_ID, reg_buf, sizeof(reg_buf));
|
||||||
if (err == MENU_OK)
|
if (err == MENU_OK)
|
||||||
{
|
{
|
||||||
// 读取转换后的参数值
|
// 读取转换后的参数值
|
||||||
float humid_val;
|
float humid_val;
|
||||||
menu_param_get_value(PARAM_HUMID_ID, &humid_val);
|
menu_param_get_value(g_menu_ctx, PARAM_HUMID_ID, &humid_val);
|
||||||
MENU_DEBUG(" Reg data [0x82, 0x42, 0x00, 0x00] to humid param: %.1f%%", humid_val);
|
MENU_DEBUG(" Reg data [0x82, 0x42, 0x00, 0x00] to humid param: %.1f%%", humid_val);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -129,7 +141,7 @@ static void modbus_communication_demo(void)
|
|||||||
uint8_t reg_buf[4] = {0};
|
uint8_t reg_buf[4] = {0};
|
||||||
|
|
||||||
// 这里应该先调用menu_modbus_map_param_to_reg将参数转换到寄存器缓冲区
|
// 这里应该先调用menu_modbus_map_param_to_reg将参数转换到寄存器缓冲区
|
||||||
MenuErrCode err = menu_modbus_map_param_to_reg(PARAM_PRESS_ID, reg_buf, &buf_len);
|
MenuErrCode err = menu_modbus_map_param_to_reg(g_menu_ctx, PARAM_PRESS_ID, reg_buf, &buf_len);
|
||||||
if (err == MENU_OK)
|
if (err == MENU_OK)
|
||||||
{
|
{
|
||||||
// 然后返回给Modbus主机
|
// 然后返回给Modbus主机
|
||||||
@ -151,12 +163,12 @@ static void modbus_communication_demo(void)
|
|||||||
uint8_t reg_buf[4] = {0xF0, 0x41, 0x00, 0x00};
|
uint8_t reg_buf[4] = {0xF0, 0x41, 0x00, 0x00};
|
||||||
|
|
||||||
// 调用menu_modbus_map_reg_to_param将寄存器数据转换为参数值
|
// 调用menu_modbus_map_reg_to_param将寄存器数据转换为参数值
|
||||||
MenuErrCode err = menu_modbus_map_reg_to_param(PARAM_TEMP_ID, reg_buf, sizeof(reg_buf));
|
MenuErrCode err = menu_modbus_map_reg_to_param(g_menu_ctx, PARAM_TEMP_ID, reg_buf, sizeof(reg_buf));
|
||||||
if (err == MENU_OK)
|
if (err == MENU_OK)
|
||||||
{
|
{
|
||||||
// 读取转换后的参数值
|
// 读取转换后的参数值
|
||||||
float temp_val;
|
float temp_val;
|
||||||
menu_param_get_value(PARAM_TEMP_ID, &temp_val);
|
menu_param_get_value(g_menu_ctx, PARAM_TEMP_ID, &temp_val);
|
||||||
MENU_DEBUG(" Write temp reg: [0xF0, 0x41, 0x00, 0x00] -> temp param: %.1f°C", temp_val);
|
MENU_DEBUG(" Write temp reg: [0xF0, 0x41, 0x00, 0x00] -> temp param: %.1f°C", temp_val);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -181,10 +193,22 @@ int main(void)
|
|||||||
|
|
||||||
/************************** 2. 初始化菜单组件 **************************/
|
/************************** 2. 初始化菜单组件 **************************/
|
||||||
MENU_DEBUG("2. Initialize menu component...");
|
MENU_DEBUG("2. Initialize menu component...");
|
||||||
MenuErrCode err = menu_init();
|
|
||||||
|
// 动态分配菜单上下文缓冲区
|
||||||
|
menu_ctx_buf = (uint8_t*)malloc(menu_get_ctx_size());
|
||||||
|
if (menu_ctx_buf == NULL)
|
||||||
|
{
|
||||||
|
MENU_DEBUG(" Failed to allocate menu context buffer");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化菜单上下文
|
||||||
|
g_menu_ctx = (MenuGlobalCtx*)menu_ctx_buf;
|
||||||
|
MenuErrCode err = menu_init(g_menu_ctx);
|
||||||
if (err != MENU_OK)
|
if (err != MENU_OK)
|
||||||
{
|
{
|
||||||
MENU_DEBUG(" Menu init failed: %d", err);
|
MENU_DEBUG(" Menu init failed: %d", err);
|
||||||
|
free(menu_ctx_buf);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,7 +216,7 @@ int main(void)
|
|||||||
MENU_DEBUG("3. Register menu nodes...");
|
MENU_DEBUG("3. Register menu nodes...");
|
||||||
|
|
||||||
// 注册根节点
|
// 注册根节点
|
||||||
err = menu_register_node(0, MENU_ROOT_ID, "主菜单", NULL, NULL);
|
err = menu_register_node(g_menu_ctx, 0, MENU_ROOT_ID, "主菜单", NULL, NULL);
|
||||||
if (err != MENU_OK)
|
if (err != MENU_OK)
|
||||||
{
|
{
|
||||||
MENU_DEBUG(" Register root node failed: %d", err);
|
MENU_DEBUG(" Register root node failed: %d", err);
|
||||||
@ -200,7 +224,7 @@ int main(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 注册参数设置节点
|
// 注册参数设置节点
|
||||||
err = menu_register_node(MENU_ROOT_ID, MENU_PARAM_ID, "参数设置", menu_cb_enter_param, menu_cb_exit_param);
|
err = menu_register_node(g_menu_ctx, MENU_ROOT_ID, MENU_PARAM_ID, "参数设置", menu_cb_enter_param, menu_cb_exit_param);
|
||||||
if (err != MENU_OK)
|
if (err != MENU_OK)
|
||||||
{
|
{
|
||||||
MENU_DEBUG(" Register param node failed: %d", err);
|
MENU_DEBUG(" Register param node failed: %d", err);
|
||||||
@ -208,7 +232,7 @@ int main(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 注册Modbus设置节点
|
// 注册Modbus设置节点
|
||||||
err = menu_register_node(MENU_ROOT_ID, MENU_MODBUS_ID, "Modbus设置", menu_cb_enter_modbus, NULL);
|
err = menu_register_node(g_menu_ctx, MENU_ROOT_ID, MENU_MODBUS_ID, "Modbus设置", menu_cb_enter_modbus, NULL);
|
||||||
if (err != MENU_OK)
|
if (err != MENU_OK)
|
||||||
{
|
{
|
||||||
MENU_DEBUG(" Register modbus node failed: %d", err);
|
MENU_DEBUG(" Register modbus node failed: %d", err);
|
||||||
@ -216,7 +240,7 @@ int main(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 注册温度参数节点
|
// 注册温度参数节点
|
||||||
err = menu_register_node(MENU_PARAM_ID, MENU_TEMP_ID, "温度设置", NULL, NULL);
|
err = menu_register_node(g_menu_ctx, MENU_PARAM_ID, MENU_TEMP_ID, "温度设置", NULL, NULL);
|
||||||
if (err != MENU_OK)
|
if (err != MENU_OK)
|
||||||
{
|
{
|
||||||
MENU_DEBUG(" Register temp node failed: %d", err);
|
MENU_DEBUG(" Register temp node failed: %d", err);
|
||||||
@ -224,7 +248,7 @@ int main(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 注册湿度参数节点
|
// 注册湿度参数节点
|
||||||
err = menu_register_node(MENU_PARAM_ID, MENU_HUMID_ID, "湿度设置", NULL, NULL);
|
err = menu_register_node(g_menu_ctx, MENU_PARAM_ID, MENU_HUMID_ID, "湿度设置", NULL, NULL);
|
||||||
if (err != MENU_OK)
|
if (err != MENU_OK)
|
||||||
{
|
{
|
||||||
MENU_DEBUG(" Register humid node failed: %d", err);
|
MENU_DEBUG(" Register humid node failed: %d", err);
|
||||||
@ -232,7 +256,7 @@ int main(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 注册压力参数节点
|
// 注册压力参数节点
|
||||||
err = menu_register_node(MENU_PARAM_ID, MENU_PRESS_ID, "压力设置", NULL, NULL);
|
err = menu_register_node(g_menu_ctx, MENU_PARAM_ID, MENU_PRESS_ID, "压力设置", NULL, NULL);
|
||||||
if (err != MENU_OK)
|
if (err != MENU_OK)
|
||||||
{
|
{
|
||||||
MENU_DEBUG(" Register press node failed: %d", err);
|
MENU_DEBUG(" Register press node failed: %d", err);
|
||||||
@ -244,7 +268,7 @@ int main(void)
|
|||||||
|
|
||||||
#if MENU_CONFIG_ENABLE_PARAM
|
#if MENU_CONFIG_ENABLE_PARAM
|
||||||
// 注册温度参数(范围:-40.0°C ~ 120.0°C,默认25.0°C,精度0.1°C)
|
// 注册温度参数(范围:-40.0°C ~ 120.0°C,默认25.0°C,精度0.1°C)
|
||||||
err = menu_param_register(MENU_TEMP_ID, PARAM_TEMP_ID, MENU_PARAM_TYPE_FLOAT,
|
err = menu_param_register(g_menu_ctx, MENU_TEMP_ID, PARAM_TEMP_ID, MENU_PARAM_TYPE_FLOAT,
|
||||||
-40.0f, 120.0f, 25.0f, 0.1f);
|
-40.0f, 120.0f, 25.0f, 0.1f);
|
||||||
if (err != MENU_OK)
|
if (err != MENU_OK)
|
||||||
{
|
{
|
||||||
@ -253,7 +277,7 @@ int main(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 注册湿度参数(范围:0.0% ~ 100.0%,默认50.0%,精度0.1%)
|
// 注册湿度参数(范围:0.0% ~ 100.0%,默认50.0%,精度0.1%)
|
||||||
err = menu_param_register(MENU_HUMID_ID, PARAM_HUMID_ID, MENU_PARAM_TYPE_FLOAT,
|
err = menu_param_register(g_menu_ctx, MENU_HUMID_ID, PARAM_HUMID_ID, MENU_PARAM_TYPE_FLOAT,
|
||||||
0.0f, 100.0f, 50.0f, 0.1f);
|
0.0f, 100.0f, 50.0f, 0.1f);
|
||||||
if (err != MENU_OK)
|
if (err != MENU_OK)
|
||||||
{
|
{
|
||||||
@ -262,7 +286,7 @@ int main(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 注册压力参数(范围:0.0kPa ~ 1000.0kPa,默认101.3kPa,精度0.1kPa)
|
// 注册压力参数(范围:0.0kPa ~ 1000.0kPa,默认101.3kPa,精度0.1kPa)
|
||||||
err = menu_param_register(MENU_PRESS_ID, PARAM_PRESS_ID, MENU_PARAM_TYPE_FLOAT,
|
err = menu_param_register(g_menu_ctx, MENU_PRESS_ID, PARAM_PRESS_ID, MENU_PARAM_TYPE_FLOAT,
|
||||||
0.0f, 1000.0f, 101.3f, 0.1f);
|
0.0f, 1000.0f, 101.3f, 0.1f);
|
||||||
if (err != MENU_OK)
|
if (err != MENU_OK)
|
||||||
{
|
{
|
||||||
@ -274,8 +298,10 @@ int main(void)
|
|||||||
/************************** 5. 注册Modbus映射 **************************/
|
/************************** 5. 注册Modbus映射 **************************/
|
||||||
MENU_DEBUG("5. Register Modbus mappings...");
|
MENU_DEBUG("5. Register Modbus mappings...");
|
||||||
|
|
||||||
|
#if MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||||
// 注册温度参数到Modbus寄存器映射
|
// 注册温度参数到Modbus寄存器映射
|
||||||
err = menu_modbus_map_register(
|
err = menu_modbus_map_register(
|
||||||
|
g_menu_ctx,
|
||||||
PARAM_TEMP_ID, // 参数ID
|
PARAM_TEMP_ID, // 参数ID
|
||||||
MODBUS_REG_TYPE_HOLDING_REG, // 寄存器类型
|
MODBUS_REG_TYPE_HOLDING_REG, // 寄存器类型
|
||||||
REG_TEMP_ADDR, // 起始地址
|
REG_TEMP_ADDR, // 起始地址
|
||||||
@ -291,6 +317,7 @@ int main(void)
|
|||||||
|
|
||||||
// 注册湿度参数到Modbus寄存器映射
|
// 注册湿度参数到Modbus寄存器映射
|
||||||
err = menu_modbus_map_register(
|
err = menu_modbus_map_register(
|
||||||
|
g_menu_ctx,
|
||||||
PARAM_HUMID_ID, // 参数ID
|
PARAM_HUMID_ID, // 参数ID
|
||||||
MODBUS_REG_TYPE_HOLDING_REG, // 寄存器类型
|
MODBUS_REG_TYPE_HOLDING_REG, // 寄存器类型
|
||||||
REG_HUMID_ADDR, // 起始地址
|
REG_HUMID_ADDR, // 起始地址
|
||||||
@ -306,6 +333,7 @@ int main(void)
|
|||||||
|
|
||||||
// 注册压力参数到Modbus寄存器映射
|
// 注册压力参数到Modbus寄存器映射
|
||||||
err = menu_modbus_map_register(
|
err = menu_modbus_map_register(
|
||||||
|
g_menu_ctx,
|
||||||
PARAM_PRESS_ID, // 参数ID
|
PARAM_PRESS_ID, // 参数ID
|
||||||
MODBUS_REG_TYPE_HOLDING_REG, // 寄存器类型
|
MODBUS_REG_TYPE_HOLDING_REG, // 寄存器类型
|
||||||
REG_PRESS_ADDR, // 起始地址
|
REG_PRESS_ADDR, // 起始地址
|
||||||
@ -318,6 +346,7 @@ int main(void)
|
|||||||
MENU_DEBUG(" Register press modbus map failed: %d", err);
|
MENU_DEBUG(" Register press modbus map failed: %d", err);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/************************** 6. 运行Modbus通信演示 **************************/
|
/************************** 6. 运行Modbus通信演示 **************************/
|
||||||
MENU_DEBUG("6. Run Modbus communication demo...");
|
MENU_DEBUG("6. Run Modbus communication demo...");
|
||||||
@ -328,14 +357,17 @@ int main(void)
|
|||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
// 处理菜单事件和刷新显示
|
// 处理菜单事件和刷新显示
|
||||||
menu_main_loop();
|
menu_main_loop(g_menu_ctx);
|
||||||
|
|
||||||
// 其他业务逻辑
|
// 其他业务逻辑
|
||||||
// ...
|
// ...
|
||||||
|
|
||||||
// 模拟延时
|
// 模拟延时
|
||||||
menu_port_delay_ms(10);
|
// menu_port_delay_ms(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Free the menu context buffer
|
||||||
|
free(menu_ctx_buf);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -17,6 +17,7 @@ typedef struct {
|
|||||||
MenuEventType type; ///< 事件类型
|
MenuEventType type; ///< 事件类型
|
||||||
uint32_t param; ///< 事件附加参数
|
uint32_t param; ///< 事件附加参数
|
||||||
uint32_t timestamp; ///< 事件产生时间(ms,用于超时处理)
|
uint32_t timestamp; ///< 事件产生时间(ms,用于超时处理)
|
||||||
|
MenuEventPriority priority; ///< 事件优先级
|
||||||
} MenuEvent;
|
} MenuEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,6 +62,27 @@ typedef struct {
|
|||||||
uint8_t count; ///< 队列元素数量
|
uint8_t count; ///< 队列元素数量
|
||||||
} MenuEventQueue;
|
} MenuEventQueue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 菜单状态枚举(状态机核心)
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
MENU_STATE_INIT = 0, ///< 初始化状态
|
||||||
|
MENU_STATE_NORMAL, ///< 正常导航状态
|
||||||
|
MENU_STATE_PARAM_EDIT, ///< 参数编辑状态
|
||||||
|
MENU_STATE_CONFIRM, ///< 确认状态(如保存/退出确认)
|
||||||
|
MENU_STATE_ERROR, ///< 错误状态
|
||||||
|
} MenuState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 菜单状态转换结构体
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
MenuState current_state; ///< 当前状态
|
||||||
|
MenuEventType event; ///< 触发事件
|
||||||
|
MenuState next_state; ///< 下一个状态
|
||||||
|
MenuErrCode (*action)(MenuGlobalCtx* global_ctx); ///< 状态转换动作
|
||||||
|
} MenuStateTransition;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 菜单核心上下文(全局唯一,存储菜单状态)
|
* @brief 菜单核心上下文(全局唯一,存储菜单状态)
|
||||||
*/
|
*/
|
||||||
@ -69,6 +91,7 @@ typedef struct {
|
|||||||
MenuStack stack; ///< 菜单导航栈
|
MenuStack stack; ///< 菜单导航栈
|
||||||
MenuEventQueue event_queue; ///< 事件队列
|
MenuEventQueue event_queue; ///< 事件队列
|
||||||
MenuNodeId current_node_id; ///< 当前选中的节点ID
|
MenuNodeId current_node_id; ///< 当前选中的节点ID
|
||||||
|
MenuState current_state; ///< 当前菜单状态
|
||||||
uint32_t last_refresh_tick; ///< 上次刷新时间(ms)
|
uint32_t last_refresh_tick; ///< 上次刷新时间(ms)
|
||||||
bool is_initialized; ///< 是否已初始化
|
bool is_initialized; ///< 是否已初始化
|
||||||
} MenuCoreCtx;
|
} MenuCoreCtx;
|
||||||
@ -76,34 +99,25 @@ typedef struct {
|
|||||||
/************************** 核心内部接口声明 **************************/
|
/************************** 核心内部接口声明 **************************/
|
||||||
/**
|
/**
|
||||||
* @brief 查找菜单节点(通过ID)
|
* @brief 查找菜单节点(通过ID)
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
* @param node_id 节点ID
|
* @param node_id 节点ID
|
||||||
* @return 节点指针(NULL表示未找到)
|
* @return 节点指针(NULL表示未找到)
|
||||||
*/
|
*/
|
||||||
MenuNode* menu_core_find_node(MenuNodeId node_id);
|
MenuNode* menu_core_find_node(MenuGlobalCtx* global_ctx, MenuNodeId node_id);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化菜单核心上下文
|
|
||||||
* @return 错误码
|
|
||||||
*/
|
|
||||||
MenuErrCode menu_core_ctx_init(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取菜单核心上下文(内部唯一访问入口)
|
|
||||||
* @return 菜单核心上下文指针
|
|
||||||
*/
|
|
||||||
MenuCoreCtx* menu_core_get_ctx(void);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 处理单个菜单事件
|
* @brief 处理单个菜单事件
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
* @param event 事件指针
|
* @param event 事件指针
|
||||||
* @return 错误码
|
* @return 错误码
|
||||||
*/
|
*/
|
||||||
MenuErrCode menu_core_handle_event(const MenuEvent* event);
|
MenuErrCode menu_core_handle_event(MenuGlobalCtx* global_ctx, const MenuEvent* event);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 刷新菜单显示(内部调用,对接port层显示接口)
|
* @brief 刷新菜单显示(内部调用,对接port层显示接口)
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
*/
|
*/
|
||||||
void menu_core_refresh_display(void);
|
void menu_core_refresh_display(MenuGlobalCtx* global_ctx);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -5,68 +5,58 @@
|
|||||||
#ifndef MENU_DATA_H
|
#ifndef MENU_DATA_H
|
||||||
#define MENU_DATA_H
|
#define MENU_DATA_H
|
||||||
|
|
||||||
|
#include "../api/menu.h"
|
||||||
#include "menu_core.h"
|
#include "menu_core.h"
|
||||||
#include "menu_modbus.h"
|
#include "menu_modbus.h"
|
||||||
|
#include "menu_param.h"
|
||||||
|
|
||||||
/************************** 共享全局变量(声明,内部可见) **************************/
|
/************************** 共享上下文结构(替代全局变量) **************************/
|
||||||
/**
|
/**
|
||||||
* @brief 菜单核心上下文(全局,仅内部访问)
|
* @brief 参数管理上下文(启用参数时有效)
|
||||||
*/
|
|
||||||
extern MenuCoreCtx s_menu_core_ctx;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 参数管理上下文(启用参数时有效,全局)
|
|
||||||
*/
|
*/
|
||||||
#if MENU_CONFIG_ENABLE_PARAM
|
#if MENU_CONFIG_ENABLE_PARAM
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint16_t id; ///< 参数ID
|
MenuParam params[MENU_CONFIG_MAX_PARAMS]; ///< 参数数组
|
||||||
MenuParamType type; ///< 参数类型
|
uint16_t count; ///< 已注册参数数量
|
||||||
float min_val; ///< 最小值
|
} MenuParamCtx;
|
||||||
float max_val; ///< 最大值
|
|
||||||
float scale; ///< 缩放因子
|
|
||||||
union { ///< 共用体:减少内存占用
|
|
||||||
int8_t i8;
|
|
||||||
uint8_t u8;
|
|
||||||
int16_t i16;
|
|
||||||
uint16_t u16;
|
|
||||||
int32_t i32;
|
|
||||||
uint32_t u32;
|
|
||||||
float f;
|
|
||||||
} value; ///< 当前值
|
|
||||||
union { ///< 默认值
|
|
||||||
int8_t i8;
|
|
||||||
uint8_t u8;
|
|
||||||
int16_t i16;
|
|
||||||
uint16_t u16;
|
|
||||||
int32_t i32;
|
|
||||||
uint32_t u32;
|
|
||||||
float f;
|
|
||||||
} default_val;
|
|
||||||
bool is_registered; ///< 是否已注册
|
|
||||||
} MenuParam;
|
|
||||||
|
|
||||||
extern MenuParam s_menu_params[MENU_CONFIG_MAX_PARAMS];
|
|
||||||
#endif // MENU_CONFIG_ENABLE_PARAM
|
#endif // MENU_CONFIG_ENABLE_PARAM
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 多语言上下文(启用多语言时有效,全局)
|
* @brief 多语言上下文(启用多语言时有效)
|
||||||
*/
|
*/
|
||||||
#if MENU_CONFIG_ENABLE_LANG
|
#if MENU_CONFIG_ENABLE_LANG
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const char* str; ///< 语言字符串
|
MenuLangStr strs[MENU_CONFIG_MAX_NODES * MENU_CONFIG_MAX_LANGS]; ///< 语言字符串数组
|
||||||
uint16_t str_id; ///< 字符串ID
|
uint8_t count; ///< 已注册字符串数量
|
||||||
uint8_t lang_id; ///< 语言ID
|
uint8_t current_lang_id; ///< 当前语言ID
|
||||||
} MenuLangStr;
|
} MenuLangCtx;
|
||||||
|
|
||||||
extern MenuLangStr s_menu_lang_strs[MENU_CONFIG_MAX_NODES * MENU_CONFIG_MAX_LANGS];
|
|
||||||
extern uint8_t s_current_lang_id;
|
|
||||||
#endif // MENU_CONFIG_ENABLE_LANG
|
#endif // MENU_CONFIG_ENABLE_LANG
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Modbus映射上下文(启用Modbus映射时有效,全局)
|
* @brief Modbus映射上下文(启用Modbus映射时有效)
|
||||||
*/
|
*/
|
||||||
#if MENU_CONFIG_ENABLE_MODBUS_MAP
|
#if MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||||
extern ModbusMapInternal s_menu_modbus_maps[MENU_CONFIG_MAX_MODBUS_MAPS];
|
typedef struct {
|
||||||
|
ModbusMapInternal maps[MENU_CONFIG_MAX_MODBUS_MAPS]; ///< Modbus映射数组
|
||||||
|
uint16_t count; ///< 已注册映射数量
|
||||||
|
} MenuModbusCtx;
|
||||||
#endif // MENU_CONFIG_ENABLE_MODBUS_MAP
|
#endif // MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 菜单组件全局上下文(替代所有全局变量)
|
||||||
|
*/
|
||||||
|
struct MenuGlobalCtx {
|
||||||
|
MenuCoreCtx core; ///< 核心上下文
|
||||||
|
#if MENU_CONFIG_ENABLE_PARAM
|
||||||
|
MenuParamCtx param; ///< 参数上下文
|
||||||
|
#endif
|
||||||
|
#if MENU_CONFIG_ENABLE_LANG
|
||||||
|
MenuLangCtx lang; ///< 多语言上下文
|
||||||
|
#endif
|
||||||
|
#if MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||||
|
MenuModbusCtx modbus; ///< Modbus映射上下文
|
||||||
|
#endif
|
||||||
|
bool is_initialized; ///< 是否已初始化
|
||||||
|
};
|
||||||
|
|
||||||
#endif // MENU_DATA_H
|
#endif // MENU_DATA_H
|
||||||
@ -26,13 +26,32 @@
|
|||||||
#define MENU_ASSERT(condition) ((void)0)
|
#define MENU_ASSERT(condition) ((void)0)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 日志级别定义
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
MENU_LOG_LEVEL_DEBUG, ///< 调试信息
|
||||||
|
MENU_LOG_LEVEL_INFO, ///< 普通信息
|
||||||
|
MENU_LOG_LEVEL_WARN, ///< 警告信息
|
||||||
|
MENU_LOG_LEVEL_ERROR, ///< 错误信息
|
||||||
|
MENU_LOG_LEVEL_CRITICAL, ///< 严重错误
|
||||||
|
} MenuLogLevel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 调试打印宏(工业级:集中控制调试输出)
|
* @brief 调试打印宏(工业级:集中控制调试输出)
|
||||||
*/
|
*/
|
||||||
#if MENU_CONFIG_ENABLE_DEBUG
|
#if MENU_CONFIG_ENABLE_DEBUG
|
||||||
#define MENU_DEBUG(fmt, ...) menu_utils_printf("[MENU DEBUG] " fmt "\r\n", ##__VA_ARGS__)
|
#define MENU_DEBUG(fmt, ...) menu_utils_log(MENU_LOG_LEVEL_DEBUG, __FILE__, __LINE__, fmt, ##__VA_ARGS__)
|
||||||
|
#define MENU_INFO(fmt, ...) menu_utils_log(MENU_LOG_LEVEL_INFO, __FILE__, __LINE__, fmt, ##__VA_ARGS__)
|
||||||
|
#define MENU_WARN(fmt, ...) menu_utils_log(MENU_LOG_LEVEL_WARN, __FILE__, __LINE__, fmt, ##__VA_ARGS__)
|
||||||
|
#define MENU_ERROR(fmt, ...) menu_utils_log(MENU_LOG_LEVEL_ERROR, __FILE__, __LINE__, fmt, ##__VA_ARGS__)
|
||||||
|
#define MENU_CRITICAL(fmt, ...) menu_utils_log(MENU_LOG_LEVEL_CRITICAL, __FILE__, __LINE__, fmt, ##__VA_ARGS__)
|
||||||
#else
|
#else
|
||||||
#define MENU_DEBUG(fmt, ...) ((void)0)
|
#define MENU_DEBUG(fmt, ...) ((void)0)
|
||||||
|
#define MENU_INFO(fmt, ...) ((void)0)
|
||||||
|
#define MENU_WARN(fmt, ...) ((void)0)
|
||||||
|
#define MENU_ERROR(fmt, ...) ((void)0)
|
||||||
|
#define MENU_CRITICAL(fmt, ...) ((void)0)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,6 +64,18 @@
|
|||||||
*/
|
*/
|
||||||
#define MENU_INVALID_ID ((MenuNodeId)0xFFFF)
|
#define MENU_INVALID_ID ((MenuNodeId)0xFFFF)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 错误处理宏(简化错误检查和返回)
|
||||||
|
*/
|
||||||
|
#define MENU_CHECK_ERR(expr) \
|
||||||
|
do { \
|
||||||
|
MenuErrCode err = (expr); \
|
||||||
|
if (err != MENU_OK) { \
|
||||||
|
MENU_ERROR("Error at %s:%d: %s", __FILE__, __LINE__, menu_err_code_to_str(err)); \
|
||||||
|
return err; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
/************************** 内部辅助函数声明 **************************/
|
/************************** 内部辅助函数声明 **************************/
|
||||||
/**
|
/**
|
||||||
* @brief 断言失败处理函数
|
* @brief 断言失败处理函数
|
||||||
@ -54,11 +85,14 @@
|
|||||||
void menu_utils_assert_failed(const char* file, uint32_t line);
|
void menu_utils_assert_failed(const char* file, uint32_t line);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 调试打印函数(对接port层的硬件打印接口)
|
* @brief 通用日志函数(对接port层的硬件打印接口)
|
||||||
|
* @param level 日志级别
|
||||||
|
* @param file 文件名
|
||||||
|
* @param line 行号
|
||||||
* @param fmt 格式化字符串
|
* @param fmt 格式化字符串
|
||||||
* @param ... 可变参数
|
* @param ... 可变参数
|
||||||
*/
|
*/
|
||||||
void menu_utils_printf(const char* fmt, ...);
|
void menu_utils_log(MenuLogLevel level, const char* file, uint32_t line, const char* fmt, ...);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取系统滴答时间(ms,对接port层)
|
* @brief 获取系统滴答时间(ms,对接port层)
|
||||||
@ -66,4 +100,11 @@ void menu_utils_printf(const char* fmt, ...);
|
|||||||
*/
|
*/
|
||||||
uint32_t menu_utils_get_tick(void);
|
uint32_t menu_utils_get_tick(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 错误码转换为字符串
|
||||||
|
* @param err_code 错误码
|
||||||
|
* @return 错误信息字符串
|
||||||
|
*/
|
||||||
|
const char* menu_err_code_to_str(MenuErrCode err_code);
|
||||||
|
|
||||||
#endif // MENU_DEF_H
|
#endif // MENU_DEF_H
|
||||||
273
port/menu_port.c
273
port/menu_port.c
@ -1,139 +1,288 @@
|
|||||||
/**
|
/**
|
||||||
* @file menu_port.c
|
* @file menu_port.c
|
||||||
* @brief 菜单组件硬件端口层示例实现(用户需要根据实际硬件修改)
|
* @brief 菜单组件硬件端口层实现(模块化设计,支持运行时配置)
|
||||||
* @note 工业级嵌入式菜单组件 - 硬件端口层示例
|
* @note 工业级嵌入式菜单组件 - 硬件端口层
|
||||||
* @attention 用户需要根据自己的硬件平台修改此文件
|
* @attention 用户需要根据自己的硬件平台修改此文件
|
||||||
*/
|
*/
|
||||||
#include "menu_port.h"
|
#include "menu_port.h"
|
||||||
|
#include "menu.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/************************** 静态全局变量 **************************/
|
||||||
|
static const MenuPortDriver* g_current_driver = NULL;
|
||||||
|
|
||||||
|
/************************** 默认驱动实现 **************************/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 硬件打印接口示例实现(使用标准C库printf)
|
* @brief 默认硬件打印接口实现(使用标准C库printf)
|
||||||
* @param fmt 格式化字符串
|
* @param fmt 格式化字符串
|
||||||
* @param args 可变参数列表
|
* @param args 可变参数列表
|
||||||
* @note 实际项目中,用户需要将此接口重定向到硬件的串口、LCD等输出设备
|
|
||||||
*/
|
*/
|
||||||
void menu_port_printf(const char* fmt, va_list args)
|
static void default_printf(const char* fmt, va_list args)
|
||||||
{
|
{
|
||||||
// 示例:使用标准C库的vprintf函数
|
|
||||||
// 实际项目中,这里应该调用硬件相关的打印函数,如串口打印
|
|
||||||
vprintf(fmt, args);
|
vprintf(fmt, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取系统滴答时间示例实现(使用标准C库time函数)
|
* @brief 默认获取系统滴答时间实现(使用标准C库time函数)
|
||||||
* @return 当前系统滴答时间(ms)
|
* @return 当前系统滴答时间(ms)
|
||||||
* @note 实际项目中,用户需要将此接口实现为硬件定时器的计数值
|
|
||||||
*/
|
*/
|
||||||
uint32_t menu_port_get_tick(void)
|
static uint32_t default_get_tick(void)
|
||||||
{
|
{
|
||||||
// 示例:使用标准C库的time函数(仅用于演示,精度低)
|
|
||||||
// 实际项目中,这里应该返回硬件定时器的计数值,如SysTick
|
|
||||||
return (uint32_t)time(NULL) * 1000;
|
return (uint32_t)time(NULL) * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 硬件延迟函数示例实现(使用标准C库sleep函数)
|
* @brief 默认硬件延迟函数实现(使用标准C库sleep函数)
|
||||||
* @param ms 延迟时间(ms)
|
* @param ms 延迟时间(ms)
|
||||||
* @note 实际项目中,用户需要将此接口实现为硬件定时器的延迟
|
|
||||||
*/
|
*/
|
||||||
void menu_port_delay_ms(uint32_t ms)
|
static void default_delay_ms(uint32_t ms)
|
||||||
{
|
{
|
||||||
// 示例:使用标准C库的sleep函数(仅用于演示,精度低)
|
|
||||||
// 实际项目中,这里应该使用硬件定时器实现精确延迟
|
|
||||||
sleep(ms / 1000);
|
sleep(ms / 1000);
|
||||||
usleep((ms % 1000) * 1000);
|
usleep((ms % 1000) * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 菜单显示接口示例实现(使用标准C库printf)
|
* @brief 默认菜单显示接口实现(使用标准C库printf)
|
||||||
* @param menu_name 菜单名称字符串
|
* @param menu_name 菜单名称字符串
|
||||||
* @param menu_id 菜单ID
|
* @param menu_id 菜单ID
|
||||||
* @note 实际项目中,用户需要将此接口实现为在LCD、OLED等显示设备上的显示
|
|
||||||
*/
|
*/
|
||||||
void menu_port_display(const char* menu_name, uint16_t menu_id)
|
static void default_display(const char* menu_name, uint16_t menu_id)
|
||||||
{
|
{
|
||||||
// 示例:使用标准C库的printf函数在控制台显示
|
|
||||||
// 实际项目中,这里应该调用硬件显示函数,将菜单显示在LCD等设备上
|
|
||||||
printf("\r\nCurrent Menu: %s (ID: %d)\r\n", menu_name, menu_id);
|
printf("\r\nCurrent Menu: %s (ID: %d)\r\n", menu_name, menu_id);
|
||||||
printf("Use UP/DOWN keys to navigate, ENTER to select, BACK to return\r\n");
|
printf("Use UP/DOWN keys to navigate, ENTER to select, BACK to return\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 按键扫描接口示例实现(使用标准C库getchar函数)
|
* @brief 默认按键扫描接口实现(无按键返回)
|
||||||
* @return 当前按键事件(MenuEventType类型)
|
* @return 当前按键事件
|
||||||
* @note 实际项目中,用户需要将此接口实现为硬件按键的扫描
|
|
||||||
*/
|
*/
|
||||||
/*
|
static MenuEventType default_key_scan(void)
|
||||||
MenuEventType menu_port_key_scan(void)
|
|
||||||
{
|
{
|
||||||
// 示例:使用标准C库的getchar函数获取键盘输入(仅用于演示)
|
|
||||||
// 实际项目中,这里应该扫描硬件按键的状态
|
|
||||||
if (kbhit())
|
|
||||||
{
|
|
||||||
char key = getchar();
|
|
||||||
switch (key)
|
|
||||||
{
|
|
||||||
case 'w':
|
|
||||||
case 'W':
|
|
||||||
return MENU_EVENT_KEY_UP;
|
|
||||||
case 's':
|
|
||||||
case 'S':
|
|
||||||
return MENU_EVENT_KEY_DOWN;
|
|
||||||
case '\r':
|
|
||||||
case '\n':
|
|
||||||
return MENU_EVENT_KEY_ENTER;
|
|
||||||
case 'b':
|
|
||||||
case 'B':
|
|
||||||
return MENU_EVENT_KEY_BACK;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return MENU_EVENT_NONE;
|
return MENU_EVENT_NONE;
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
/**
|
||||||
|
* @brief 默认中断管理接口实现(空操作)
|
||||||
|
* @param enable 中断使能标志
|
||||||
|
*/
|
||||||
|
static void default_irq_ctrl(bool enable)
|
||||||
|
{
|
||||||
|
(void)enable;
|
||||||
|
// 默认实现为空,用户需根据硬件平台实现
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 默认错误处理接口实现(打印错误信息)
|
||||||
|
* @param err_code 错误码
|
||||||
|
*/
|
||||||
|
static void default_error_handler(MenuErrCode err_code)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "[MENU HW ERROR] %d\r\n", err_code);
|
||||||
|
}
|
||||||
|
|
||||||
#if MENU_CONFIG_ENABLE_MODBUS_MAP
|
#if MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||||
/**
|
/**
|
||||||
* @brief Modbus硬件发送数据接口示例实现
|
* @brief 默认Modbus硬件发送数据接口实现(模拟成功)
|
||||||
* @param reg_type 寄存器类型
|
* @param reg_type 寄存器类型
|
||||||
* @param reg_addr 寄存器地址
|
* @param reg_addr 寄存器地址
|
||||||
* @param reg_buf 数据缓冲区
|
* @param reg_buf 数据缓冲区
|
||||||
* @param buf_len 数据长度
|
* @param buf_len 数据长度
|
||||||
* @return 错误码
|
* @return 错误码
|
||||||
* @note 用户需要根据实际硬件(如RS485、TCP)实现此接口
|
|
||||||
*/
|
*/
|
||||||
MenuErrCode menu_port_modbus_send(ModbusRegType reg_type, uint16_t reg_addr, const uint8_t* reg_buf, uint8_t buf_len)
|
static MenuErrCode default_modbus_send(ModbusRegType reg_type, uint16_t reg_addr, const uint8_t* reg_buf, uint8_t buf_len)
|
||||||
{
|
{
|
||||||
// 示例:通过RS485发送Modbus写指令(如写保持寄存器0x0000(40001))
|
|
||||||
// 实际需根据Modbus协议实现帧组装、校验、发送
|
|
||||||
(void)reg_type;
|
(void)reg_type;
|
||||||
(void)reg_addr;
|
(void)reg_addr;
|
||||||
(void)reg_buf;
|
(void)reg_buf;
|
||||||
(void)buf_len;
|
(void)buf_len;
|
||||||
return MENU_OK; // 模拟成功,用户需替换为实际逻辑
|
return MENU_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Modbus硬件接收数据接口示例实现
|
* @brief 默认Modbus硬件接收数据接口实现(模拟成功)
|
||||||
* @param reg_type 寄存器类型
|
* @param reg_type 寄存器类型
|
||||||
* @param reg_addr 寄存器地址
|
* @param reg_addr 寄存器地址
|
||||||
* @param reg_buf 数据缓冲区
|
* @param reg_buf 数据缓冲区
|
||||||
* @param buf_len 数据长度
|
* @param buf_len 数据长度
|
||||||
* @return 错误码
|
* @return 错误码
|
||||||
* @note 用户需要根据实际硬件(如RS485、TCP)实现此接口
|
|
||||||
*/
|
*/
|
||||||
MenuErrCode menu_port_modbus_receive(ModbusRegType reg_type, uint16_t reg_addr, uint8_t* reg_buf, uint8_t buf_len)
|
static MenuErrCode default_modbus_receive(ModbusRegType reg_type, uint16_t reg_addr, uint8_t* reg_buf, uint8_t buf_len)
|
||||||
{
|
{
|
||||||
// 示例:通过RS485接收Modbus读指令的响应数据
|
|
||||||
(void)reg_type;
|
(void)reg_type;
|
||||||
(void)reg_addr;
|
(void)reg_addr;
|
||||||
(void)reg_buf;
|
(void)reg_buf;
|
||||||
(void)buf_len;
|
(void)buf_len;
|
||||||
return MENU_OK; // 模拟成功,用户需替换为实际逻辑
|
return MENU_OK;
|
||||||
|
}
|
||||||
|
#endif // MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 默认硬件驱动结构体(提供基本实现,减少用户工作量)
|
||||||
|
*/
|
||||||
|
const MenuPortDriver g_menu_default_driver = {
|
||||||
|
.printf = default_printf,
|
||||||
|
.get_tick = default_get_tick,
|
||||||
|
.delay_ms = default_delay_ms,
|
||||||
|
.display = default_display,
|
||||||
|
.key_scan = default_key_scan,
|
||||||
|
.irq_ctrl = default_irq_ctrl,
|
||||||
|
.error_handler = default_error_handler,
|
||||||
|
#if MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||||
|
.modbus_send = default_modbus_send,
|
||||||
|
.modbus_receive = default_modbus_receive,
|
||||||
|
#endif // MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||||
|
};
|
||||||
|
|
||||||
|
/************************** 端口管理函数 **************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 硬件端口初始化接口
|
||||||
|
* @param driver 硬件驱动结构体指针
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_port_init(const MenuPortDriver* driver)
|
||||||
|
{
|
||||||
|
if (driver == NULL)
|
||||||
|
{
|
||||||
|
// 使用默认驱动
|
||||||
|
g_current_driver = &g_menu_default_driver;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 使用用户提供的驱动,替换为默认驱动中未实现的部分
|
||||||
|
g_current_driver = driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MENU_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 硬件端口反初始化接口
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_port_deinit(void)
|
||||||
|
{
|
||||||
|
g_current_driver = NULL;
|
||||||
|
return MENU_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前硬件驱动
|
||||||
|
* @return 当前硬件驱动结构体指针
|
||||||
|
*/
|
||||||
|
const MenuPortDriver* menu_port_get_driver(void)
|
||||||
|
{
|
||||||
|
if (g_current_driver == NULL)
|
||||||
|
{
|
||||||
|
// 确保始终有驱动可用
|
||||||
|
return &g_menu_default_driver;
|
||||||
|
}
|
||||||
|
return g_current_driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/************************** 兼容旧接口的实现 **************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 硬件打印接口(兼容旧接口)
|
||||||
|
* @param fmt 格式化字符串
|
||||||
|
* @param args 可变参数列表
|
||||||
|
*/
|
||||||
|
void menu_port_printf(const char* fmt, va_list args)
|
||||||
|
{
|
||||||
|
const MenuPortDriver* driver = menu_port_get_driver();
|
||||||
|
if (driver->printf != NULL)
|
||||||
|
{
|
||||||
|
driver->printf(fmt, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取系统滴答时间(兼容旧接口)
|
||||||
|
* @return 当前系统滴答时间(ms)
|
||||||
|
*/
|
||||||
|
uint32_t menu_port_get_tick(void)
|
||||||
|
{
|
||||||
|
const MenuPortDriver* driver = menu_port_get_driver();
|
||||||
|
if (driver->get_tick != NULL)
|
||||||
|
{
|
||||||
|
return driver->get_tick();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 硬件延迟函数(兼容旧接口)
|
||||||
|
* @param ms 延迟时间(ms)
|
||||||
|
*/
|
||||||
|
void menu_port_delay_ms(uint32_t ms)
|
||||||
|
{
|
||||||
|
const MenuPortDriver* driver = menu_port_get_driver();
|
||||||
|
if (driver->delay_ms != NULL)
|
||||||
|
{
|
||||||
|
driver->delay_ms(ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 菜单显示接口(兼容旧接口)
|
||||||
|
* @param menu_name 菜单名称字符串
|
||||||
|
* @param menu_id 菜单ID
|
||||||
|
*/
|
||||||
|
void menu_port_display(const char* menu_name, uint16_t menu_id)
|
||||||
|
{
|
||||||
|
const MenuPortDriver* driver = menu_port_get_driver();
|
||||||
|
if (driver->display != NULL)
|
||||||
|
{
|
||||||
|
driver->display(menu_name, menu_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||||
|
/**
|
||||||
|
* @brief Modbus硬件发送数据接口(兼容旧接口)
|
||||||
|
* @param reg_type 寄存器类型
|
||||||
|
* @param reg_addr 寄存器地址
|
||||||
|
* @param reg_buf 数据缓冲区
|
||||||
|
* @param buf_len 数据长度
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_port_modbus_send(ModbusRegType reg_type, uint16_t reg_addr, const uint8_t* reg_buf, uint8_t buf_len)
|
||||||
|
{
|
||||||
|
const MenuPortDriver* driver = menu_port_get_driver();
|
||||||
|
|
||||||
|
#if MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||||
|
if (driver->modbus_send != NULL)
|
||||||
|
{
|
||||||
|
return driver->modbus_send(reg_type, reg_addr, reg_buf, buf_len);
|
||||||
|
}
|
||||||
|
#endif // MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||||
|
|
||||||
|
return MENU_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Modbus硬件接收数据接口(兼容旧接口)
|
||||||
|
* @param reg_type 寄存器类型
|
||||||
|
* @param reg_addr 寄存器地址
|
||||||
|
* @param reg_buf 数据缓冲区
|
||||||
|
* @param buf_len 数据长度
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_port_modbus_receive(ModbusRegType reg_type, uint16_t reg_addr, uint8_t* reg_buf, uint8_t buf_len)
|
||||||
|
{
|
||||||
|
const MenuPortDriver* driver = menu_port_get_driver();
|
||||||
|
|
||||||
|
#if MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||||
|
if (driver->modbus_receive != NULL)
|
||||||
|
{
|
||||||
|
return driver->modbus_receive(reg_type, reg_addr, reg_buf, buf_len);
|
||||||
|
}
|
||||||
|
#endif // MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||||
|
|
||||||
|
return MENU_OK;
|
||||||
}
|
}
|
||||||
#endif // MENU_CONFIG_ENABLE_MODBUS_MAP
|
#endif // MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||||
109
port/menu_port.h
109
port/menu_port.h
@ -9,66 +9,139 @@
|
|||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// 包含菜单核心类型定义
|
||||||
|
#include "menu.h"
|
||||||
|
|
||||||
/************************** 硬件端口层接口声明 **************************/
|
/************************** 硬件端口层接口声明 **************************/
|
||||||
/**
|
/**
|
||||||
|
* @brief 硬件端口驱动结构体(模块化设计,支持运行时配置)
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
/**
|
||||||
* @brief 硬件打印接口(可变参数版本)
|
* @brief 硬件打印接口(可变参数版本)
|
||||||
* @param fmt 格式化字符串
|
* @param fmt 格式化字符串
|
||||||
* @param args 可变参数列表
|
* @param args 可变参数列表
|
||||||
* @note 用户需要实现此接口,用于调试信息输出
|
* @note 用户需要实现此接口,用于调试信息输出
|
||||||
*/
|
*/
|
||||||
void menu_port_printf(const char* fmt, va_list args);
|
void (*printf)(const char* fmt, va_list args);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取系统滴答时间(毫秒级)
|
* @brief 获取系统滴答时间(毫秒级)
|
||||||
* @return 当前系统滴答时间(ms)
|
* @return 当前系统滴答时间(ms)
|
||||||
* @note 用户需要实现此接口,用于菜单的定时和超时处理
|
* @note 用户需要实现此接口,用于菜单的定时和超时处理
|
||||||
*/
|
*/
|
||||||
uint32_t menu_port_get_tick(void);
|
uint32_t (*get_tick)(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 硬件延迟函数(毫秒级)
|
* @brief 硬件延迟函数(毫秒级)
|
||||||
* @param ms 延迟时间(ms)
|
* @param ms 延迟时间(ms)
|
||||||
* @note 用户需要实现此接口,用于必要的延迟操作
|
* @note 用户需要实现此接口,用于必要的延迟操作
|
||||||
*/
|
*/
|
||||||
void menu_port_delay_ms(uint32_t ms);
|
void (*delay_ms)(uint32_t ms);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 菜单显示接口
|
* @brief 菜单显示接口
|
||||||
* @param menu_name 菜单名称字符串
|
* @param menu_name 菜单名称字符串
|
||||||
* @param menu_id 菜单ID
|
* @param menu_id 菜单ID
|
||||||
* @note 用户需要实现此接口,用于在硬件上显示菜单内容
|
* @note 用户需要实现此接口,用于在硬件上显示菜单内容
|
||||||
*/
|
*/
|
||||||
void menu_port_display(const char* menu_name, uint16_t menu_id);
|
void (*display)(const char* menu_name, uint16_t menu_id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 按键扫描接口(可选)
|
* @brief 按键扫描接口(可选)
|
||||||
* @return 当前按键事件(MenuEventType类型)
|
* @return 当前按键事件(MenuEventType类型)
|
||||||
* @note 用户可以实现此接口,也可以在自己的主循环中调用menu_post_event发送按键事件
|
* @note 用户可以实现此接口,也可以在自己的主循环中调用menu_post_event发送按键事件
|
||||||
*/
|
*/
|
||||||
// MenuEventType menu_port_key_scan(void);
|
MenuEventType (*key_scan)(void);
|
||||||
|
|
||||||
#include "menu.h"
|
/**
|
||||||
|
* @brief 中断管理接口(可选)
|
||||||
|
* @param enable 中断使能标志
|
||||||
|
* @note 用户需要实现此接口,用于管理全局中断
|
||||||
|
*/
|
||||||
|
void (*irq_ctrl)(bool enable);
|
||||||
|
|
||||||
#if MENU_CONFIG_ENABLE_MODBUS_MAP
|
/**
|
||||||
/**
|
* @brief 错误处理接口(可选)
|
||||||
* @brief Modbus硬件发送数据接口(用户需实现,如RS485发送)
|
* @param err_code 错误码
|
||||||
|
* @note 用户可以实现此接口,用于处理硬件错误
|
||||||
|
*/
|
||||||
|
void (*error_handler)(MenuErrCode err_code);
|
||||||
|
|
||||||
|
#if MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||||
|
/**
|
||||||
|
* @brief Modbus硬件发送数据接口(可选,如RS485发送)
|
||||||
* @param reg_type 寄存器类型
|
* @param reg_type 寄存器类型
|
||||||
* @param reg_addr 寄存器地址
|
* @param reg_addr 寄存器地址
|
||||||
* @param reg_buf 数据缓冲区
|
* @param reg_buf 数据缓冲区
|
||||||
* @param buf_len 数据长度
|
* @param buf_len 数据长度
|
||||||
* @return 错误码
|
* @return 错误码
|
||||||
*/
|
*/
|
||||||
MenuErrCode menu_port_modbus_send(ModbusRegType reg_type, uint16_t reg_addr, const uint8_t* reg_buf, uint8_t buf_len);
|
MenuErrCode (*modbus_send)(ModbusRegType reg_type, uint16_t reg_addr, const uint8_t* reg_buf, uint8_t buf_len);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Modbus硬件接收数据接口(用户需实现,如RS485接收)
|
* @brief Modbus硬件接收数据接口(可选,如RS485接收)
|
||||||
* @param reg_type 寄存器类型
|
* @param reg_type 寄存器类型
|
||||||
* @param reg_addr 寄存器地址
|
* @param reg_addr 寄存器地址
|
||||||
* @param reg_buf 数据缓冲区
|
* @param reg_buf 数据缓冲区
|
||||||
* @param buf_len 数据长度
|
* @param buf_len 数据长度
|
||||||
* @return 错误码
|
* @return 错误码
|
||||||
*/
|
*/
|
||||||
MenuErrCode menu_port_modbus_receive(ModbusRegType reg_type, uint16_t reg_addr, uint8_t* reg_buf, uint8_t buf_len);
|
MenuErrCode (*modbus_receive)(ModbusRegType reg_type, uint16_t reg_addr, uint8_t* reg_buf, uint8_t buf_len);
|
||||||
#endif // MENU_CONFIG_ENABLE_MODBUS_MAP
|
#endif // MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||||
|
} MenuPortDriver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 硬件端口初始化接口
|
||||||
|
* @param driver 硬件驱动结构体指针
|
||||||
|
* @return 错误码
|
||||||
|
* @note 用户需要实现此接口,用于初始化硬件设备
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_port_init(const MenuPortDriver* driver);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 硬件端口反初始化接口
|
||||||
|
* @return 错误码
|
||||||
|
* @note 用户需要实现此接口,用于释放硬件资源
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_port_deinit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前硬件驱动
|
||||||
|
* @return 当前硬件驱动结构体指针
|
||||||
|
* @note 内部使用,获取当前配置的硬件驱动
|
||||||
|
*/
|
||||||
|
const MenuPortDriver* menu_port_get_driver(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 硬件打印接口(兼容旧接口)
|
||||||
|
* @param fmt 格式化字符串
|
||||||
|
* @param args 可变参数列表
|
||||||
|
*/
|
||||||
|
void menu_port_printf(const char* fmt, va_list args);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取系统滴答时间(兼容旧接口)
|
||||||
|
* @return 当前系统滴答时间(ms)
|
||||||
|
*/
|
||||||
|
uint32_t menu_port_get_tick(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 硬件延迟函数(兼容旧接口)
|
||||||
|
* @param ms 延迟时间(ms)
|
||||||
|
*/
|
||||||
|
void menu_port_delay_ms(uint32_t ms);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 菜单显示接口(兼容旧接口)
|
||||||
|
* @param menu_name 菜单名称字符串
|
||||||
|
* @param menu_id 菜单ID
|
||||||
|
*/
|
||||||
|
void menu_port_display(const char* menu_name, uint16_t menu_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 默认硬件驱动结构体(提供基本实现,减少用户工作量)
|
||||||
|
*/
|
||||||
|
extern const MenuPortDriver g_menu_default_driver;
|
||||||
|
|
||||||
#endif // MENU_PORT_H
|
#endif // MENU_PORT_H
|
||||||
@ -2,21 +2,59 @@
|
|||||||
* @file menu_core.c
|
* @file menu_core.c
|
||||||
* @brief 菜单核心逻辑:导航、栈管理、主循环(工业级:状态机驱动)
|
* @brief 菜单核心逻辑:导航、栈管理、主循环(工业级:状态机驱动)
|
||||||
*/
|
*/
|
||||||
|
#include "../api/menu.h"
|
||||||
#include "menu_core.h"
|
#include "menu_core.h"
|
||||||
#include "menu_data.h"
|
#include "menu_data.h"
|
||||||
#include <stddef.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)
|
* @brief 查找菜单节点(通过ID)
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
* @param node_id 节点ID
|
* @param node_id 节点ID
|
||||||
* @return 节点指针(NULL表示未找到)
|
* @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++)
|
for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES; i++)
|
||||||
{
|
{
|
||||||
if (ctx->nodes[i].flags.is_registered && ctx->nodes[i].id == node_id)
|
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 &ctx->nodes[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 菜单栈压入节点
|
* @brief 菜单栈压入节点
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
* @param node_id 节点ID
|
* @param node_id 节点ID
|
||||||
* @return 错误码
|
* @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);
|
MENU_ASSERT(ctx != NULL);
|
||||||
|
|
||||||
if (ctx->stack.top >= MENU_CONFIG_STACK_DEPTH)
|
if (ctx->stack.top >= MENU_CONFIG_STACK_DEPTH)
|
||||||
@ -50,12 +90,13 @@ static MenuErrCode menu_core_stack_push(MenuNodeId node_id)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 菜单栈弹出节点
|
* @brief 菜单栈弹出节点
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
* @param node_id 输出参数,弹出的节点ID
|
* @param node_id 输出参数,弹出的节点ID
|
||||||
* @return 错误码
|
* @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);
|
MENU_ASSERT(ctx != NULL && node_id != NULL);
|
||||||
|
|
||||||
if (ctx->stack.top == 0)
|
if (ctx->stack.top == 0)
|
||||||
@ -71,12 +112,13 @@ static MenuErrCode menu_core_stack_pop(MenuNodeId* node_id)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 菜单导航到子节点(确认键)
|
* @brief 菜单导航到子节点(确认键)
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
* @param node_id 当前节点ID
|
* @param node_id 当前节点ID
|
||||||
* @return 错误码
|
* @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)
|
if (node == NULL)
|
||||||
{
|
{
|
||||||
return MENU_ERR_NODE_NOT_FOUND;
|
return MENU_ERR_NODE_NOT_FOUND;
|
||||||
@ -95,9 +137,9 @@ static MenuErrCode menu_core_navigate_enter(MenuNodeId node_id)
|
|||||||
// 如果有子节点,压入栈并选中第一个子节点
|
// 如果有子节点,压入栈并选中第一个子节点
|
||||||
if (node->first_child != NULL)
|
if (node->first_child != NULL)
|
||||||
{
|
{
|
||||||
MenuCoreCtx* ctx = menu_core_get_ctx();
|
MenuCoreCtx* ctx = &global_ctx->core;
|
||||||
ctx->current_node_id = node->first_child->id;
|
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;
|
return MENU_OK;
|
||||||
@ -105,13 +147,14 @@ static MenuErrCode menu_core_navigate_enter(MenuNodeId node_id)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 菜单导航返回父节点(返回键)
|
* @brief 菜单导航返回父节点(返回键)
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
* @return 错误码
|
* @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;
|
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)
|
if (current_node == NULL)
|
||||||
{
|
{
|
||||||
return MENU_ERR_NODE_NOT_FOUND;
|
return MENU_ERR_NODE_NOT_FOUND;
|
||||||
@ -136,7 +179,7 @@ static MenuErrCode menu_core_navigate_back(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
MenuNodeId pop_id;
|
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)
|
if (err != MENU_OK)
|
||||||
{
|
{
|
||||||
return err;
|
return err;
|
||||||
@ -148,12 +191,13 @@ static MenuErrCode menu_core_navigate_back(void)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 菜单导航上选(上键)
|
* @brief 菜单导航上选(上键)
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
* @return 错误码
|
* @return 错误码
|
||||||
*/
|
*/
|
||||||
static MenuErrCode menu_core_navigate_up(void)
|
static MenuErrCode menu_core_navigate_up(MenuGlobalCtx* global_ctx)
|
||||||
{
|
{
|
||||||
MenuCoreCtx* ctx = menu_core_get_ctx();
|
MenuCoreCtx* ctx = &global_ctx->core;
|
||||||
MenuNode* current_node = menu_core_find_node(ctx->current_node_id);
|
MenuNode* current_node = menu_core_find_node(global_ctx, ctx->current_node_id);
|
||||||
if (current_node == NULL)
|
if (current_node == NULL)
|
||||||
{
|
{
|
||||||
return MENU_ERR_NODE_NOT_FOUND;
|
return MENU_ERR_NODE_NOT_FOUND;
|
||||||
@ -181,12 +225,13 @@ static MenuErrCode menu_core_navigate_up(void)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 菜单导航下选(下键)
|
* @brief 菜单导航下选(下键)
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
* @return 错误码
|
* @return 错误码
|
||||||
*/
|
*/
|
||||||
static MenuErrCode menu_core_navigate_down(void)
|
static MenuErrCode menu_core_navigate_down(MenuGlobalCtx* global_ctx)
|
||||||
{
|
{
|
||||||
MenuCoreCtx* ctx = menu_core_get_ctx();
|
MenuCoreCtx* ctx = &global_ctx->core;
|
||||||
MenuNode* current_node = menu_core_find_node(ctx->current_node_id);
|
MenuNode* current_node = menu_core_find_node(global_ctx, ctx->current_node_id);
|
||||||
if (current_node == NULL)
|
if (current_node == NULL)
|
||||||
{
|
{
|
||||||
return MENU_ERR_NODE_NOT_FOUND;
|
return MENU_ERR_NODE_NOT_FOUND;
|
||||||
@ -200,7 +245,7 @@ static MenuErrCode menu_core_navigate_down(void)
|
|||||||
else
|
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)
|
if (parent_node != NULL && parent_node->first_child != NULL)
|
||||||
{
|
{
|
||||||
ctx->current_node_id = parent_node->first_child->id;
|
ctx->current_node_id = parent_node->first_child->id;
|
||||||
@ -212,56 +257,96 @@ static MenuErrCode menu_core_navigate_down(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 初始化菜单核心上下文
|
* @brief 处理单个菜单事件(使用状态机)
|
||||||
* @return 错误码
|
* @param global_ctx 全局上下文指针
|
||||||
*/
|
|
||||||
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 处理单个菜单事件
|
|
||||||
* @param event 事件指针
|
* @param event 事件指针
|
||||||
* @return 错误码
|
* @return 错误码
|
||||||
*/
|
*/
|
||||||
MenuErrCode menu_core_handle_event(const MenuEvent* event)
|
MenuErrCode menu_core_handle_event(MenuGlobalCtx* global_ctx, const MenuEvent* event)
|
||||||
{
|
{
|
||||||
MENU_ASSERT(event != NULL);
|
MENU_ASSERT(event != NULL);
|
||||||
|
|
||||||
MenuErrCode err = MENU_OK;
|
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++)
|
||||||
{
|
{
|
||||||
case MENU_EVENT_KEY_UP:
|
if (menu_state_transitions[i].current_state == current_state &&
|
||||||
err = menu_core_navigate_up();
|
menu_state_transitions[i].event == event)
|
||||||
|
{
|
||||||
|
// 执行状态转换动作
|
||||||
|
switch (current_state)
|
||||||
|
{
|
||||||
|
case MENU_STATE_NORMAL:
|
||||||
|
err = menu_state_action_normal_navigate(global_ctx, event);
|
||||||
break;
|
break;
|
||||||
case MENU_EVENT_KEY_DOWN:
|
case MENU_STATE_PARAM_EDIT:
|
||||||
err = menu_core_navigate_down();
|
err = menu_state_action_param_edit(global_ctx, event);
|
||||||
break;
|
break;
|
||||||
case MENU_EVENT_KEY_ENTER:
|
case MENU_STATE_CONFIRM:
|
||||||
err = menu_core_navigate_enter(menu_core_get_ctx()->current_node_id);
|
err = menu_state_action_confirm(global_ctx, event);
|
||||||
break;
|
break;
|
||||||
case MENU_EVENT_KEY_BACK:
|
case MENU_STATE_ERROR:
|
||||||
err = menu_core_navigate_back();
|
err = menu_state_action_error(global_ctx, event);
|
||||||
break;
|
break;
|
||||||
case MENU_EVENT_NONE:
|
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(global_ctx);
|
||||||
|
break;
|
||||||
|
case MENU_EVENT_KEY_DOWN:
|
||||||
|
err = menu_core_navigate_down(global_ctx);
|
||||||
|
break;
|
||||||
|
case MENU_EVENT_KEY_ENTER:
|
||||||
|
err = menu_core_navigate_enter(global_ctx, global_ctx->core.current_node_id);
|
||||||
|
break;
|
||||||
|
case MENU_EVENT_KEY_BACK:
|
||||||
|
err = menu_core_navigate_back(global_ctx);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// 自定义事件(用户可扩展)
|
|
||||||
MENU_DEBUG("Custom event received: type %d, param %lu", event->type, event->param);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,12 +354,76 @@ MenuErrCode menu_core_handle_event(const MenuEvent* event)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 刷新菜单显示(内部调用,对接port层显示接口)
|
* @brief 参数编辑状态的动作处理
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
|
* @param event 触发事件
|
||||||
|
* @return 错误码
|
||||||
*/
|
*/
|
||||||
void menu_core_refresh_display(void)
|
static MenuErrCode menu_state_action_param_edit(MenuGlobalCtx* global_ctx, MenuEventType event)
|
||||||
{
|
{
|
||||||
MenuCoreCtx* ctx = menu_core_get_ctx();
|
MenuErrCode err = MENU_OK;
|
||||||
MenuNode* current_node = menu_core_find_node(ctx->current_node_id);
|
|
||||||
|
// 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(MenuGlobalCtx* global_ctx)
|
||||||
|
{
|
||||||
|
MenuCoreCtx* ctx = &global_ctx->core;
|
||||||
|
MenuNode* current_node = menu_core_find_node(global_ctx, ctx->current_node_id);
|
||||||
if (current_node == NULL)
|
if (current_node == NULL)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@ -287,12 +436,28 @@ void menu_core_refresh_display(void)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 从事件队列获取事件(非阻塞)
|
* @brief 从事件队列获取事件(非阻塞)
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
* @param event 输出参数,获取到的事件
|
* @param event 输出参数,获取到的事件
|
||||||
* @return 是否获取到事件
|
* @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);
|
MENU_ASSERT(ctx != NULL && event != NULL);
|
||||||
|
|
||||||
if (ctx->event_queue.count == 0)
|
if (ctx->event_queue.count == 0)
|
||||||
@ -300,9 +465,28 @@ static bool menu_core_get_event(MenuEvent* event)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从队列头取出事件
|
// 查找优先级最高的事件
|
||||||
*event = ctx->event_queue.buffer[ctx->event_queue.head];
|
uint8_t highest_priority_idx = ctx->event_queue.head;
|
||||||
ctx->event_queue.head = (ctx->event_queue.head + 1) % MENU_CONFIG_EVENT_QUEUE_LEN;
|
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--;
|
ctx->event_queue.count--;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -310,28 +494,30 @@ static bool menu_core_get_event(MenuEvent* event)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 菜单主循环(需在用户主循环中调用,处理事件和刷新显示)
|
* @brief 菜单主循环(需在用户主循环中调用,处理事件和刷新显示)
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
*/
|
*/
|
||||||
void menu_main_loop(void)
|
void menu_main_loop(MenuGlobalCtx* global_ctx)
|
||||||
{
|
{
|
||||||
MenuCoreCtx* ctx = menu_core_get_ctx();
|
MENU_ASSERT(global_ctx != NULL);
|
||||||
if (!ctx->is_initialized)
|
MenuCoreCtx* ctx = &global_ctx->core;
|
||||||
|
if (!global_ctx->is_initialized)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理事件队列
|
// 处理事件队列
|
||||||
MenuEvent event;
|
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)
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理事件
|
// 处理事件
|
||||||
MenuErrCode err = menu_core_handle_event(&event);
|
MenuErrCode err = menu_core_handle_event(global_ctx, &event);
|
||||||
if (err != MENU_OK)
|
if (err != MENU_OK)
|
||||||
{
|
{
|
||||||
MENU_DEBUG("Event handle error: %d", err);
|
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)
|
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();
|
ctx->last_refresh_tick = menu_utils_get_tick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 菜单组件初始化(必须首先调用)
|
* @brief 菜单组件初始化(必须首先调用)
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
* @return 错误码
|
* @return 错误码
|
||||||
*/
|
*/
|
||||||
MenuErrCode menu_init(void)
|
MenuErrCode menu_init(MenuGlobalCtx* global_ctx)
|
||||||
{
|
{
|
||||||
MenuErrCode err = menu_core_ctx_init();
|
if (global_ctx == NULL)
|
||||||
if (err != MENU_OK)
|
|
||||||
{
|
{
|
||||||
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();
|
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;
|
return MENU_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 发送事件到菜单事件队列(如按键事件、自定义事件)
|
* @brief 发送事件到菜单事件队列(如按键事件、自定义事件)
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
* @param type 事件类型
|
* @param type 事件类型
|
||||||
* @param param 事件附加参数(可选,如按键长按时间)
|
* @param param 事件附加参数(可选,如按键长按时间)
|
||||||
|
* @param priority 事件优先级
|
||||||
* @return 错误码
|
* @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 (global_ctx == NULL || !global_ctx->is_initialized)
|
||||||
if (!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)
|
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;
|
uint8_t idx = (ctx->event_queue.head + ctx->event_queue.count) % MENU_CONFIG_EVENT_QUEUE_LEN;
|
||||||
ctx->event_queue.buffer[ctx->event_queue.tail].param = param;
|
ctx->event_queue.buffer[idx].type = type;
|
||||||
ctx->event_queue.buffer[ctx->event_queue.tail].timestamp = menu_utils_get_tick();
|
ctx->event_queue.buffer[idx].param = param;
|
||||||
ctx->event_queue.tail = (ctx->event_queue.tail + 1) % MENU_CONFIG_EVENT_QUEUE_LEN;
|
ctx->event_queue.buffer[idx].timestamp = menu_utils_get_tick();
|
||||||
|
ctx->event_queue.buffer[idx].priority = priority;
|
||||||
ctx->event_queue.count++;
|
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;
|
return MENU_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 注册菜单节点(构建菜单树)
|
* @brief 注册菜单节点(构建菜单树)
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
* @param parent_id 父节点ID(根节点填0)
|
* @param parent_id 父节点ID(根节点填0)
|
||||||
* @param node_id 当前节点ID(唯一,不可重复)
|
* @param node_id 当前节点ID(唯一,不可重复)
|
||||||
* @param name_str 菜单名称字符串(或多语言索引)
|
* @param name_str 菜单名称字符串(或多语言索引)
|
||||||
@ -404,14 +652,14 @@ MenuErrCode menu_post_event(MenuEventType type, uint32_t param)
|
|||||||
* @param exit_cb 退出该菜单的回调函数(NULL表示无)
|
* @param exit_cb 退出该菜单的回调函数(NULL表示无)
|
||||||
* @return 错误码
|
* @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 (global_ctx == NULL || !global_ctx->is_initialized || node_id == 0)
|
||||||
if (!ctx->is_initialized || node_id == 0)
|
|
||||||
{
|
{
|
||||||
return MENU_ERR_INVALID_PARAM;
|
return MENU_ERR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MenuCoreCtx* ctx = &global_ctx->core;
|
||||||
// 查找空闲节点
|
// 查找空闲节点
|
||||||
MenuNode* new_node = NULL;
|
MenuNode* new_node = NULL;
|
||||||
for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES; i++)
|
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是否已存在
|
// 检查节点ID是否已存在
|
||||||
if (menu_core_find_node(node_id) != NULL)
|
if (menu_core_find_node(global_ctx, node_id) != NULL)
|
||||||
{
|
{
|
||||||
return MENU_ERR_INVALID_PARAM;
|
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)
|
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)
|
while (root_node->next_sibling != NULL)
|
||||||
{
|
{
|
||||||
root_node = root_node->next_sibling;
|
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;
|
ctx->current_node_id = node_id;
|
||||||
menu_core_stack_push(node_id);
|
menu_core_stack_push(global_ctx, node_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
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)
|
if (parent_node == NULL)
|
||||||
{
|
{
|
||||||
return MENU_ERR_NODE_NOT_FOUND;
|
return MENU_ERR_NODE_NOT_FOUND;
|
||||||
@ -499,17 +747,123 @@ MenuErrCode menu_register_node(MenuNodeId parent_id, MenuNodeId node_id, const c
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取当前选中的菜单节点ID
|
* @brief 获取当前选中的菜单节点ID
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
* @param node_id 输出参数,当前节点ID
|
* @param node_id 输出参数,当前节点ID
|
||||||
* @return 错误码
|
* @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 (global_ctx == NULL || !global_ctx->is_initialized || node_id == NULL)
|
||||||
if (!ctx->is_initialized || node_id == NULL)
|
|
||||||
{
|
{
|
||||||
return MENU_ERR_INVALID_PARAM;
|
return MENU_ERR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MenuCoreCtx* ctx = &global_ctx->core;
|
||||||
*node_id = ctx->current_node_id;
|
*node_id = ctx->current_node_id;
|
||||||
return MENU_OK;
|
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
|
||||||
@ -2,7 +2,7 @@
|
|||||||
* @file menu_modbus.c
|
* @file menu_modbus.c
|
||||||
* @brief 菜单参数-Modbus寄存器映射:注册、查询、数据双向转换(工业级:类型安全、字节序适配)
|
* @brief 菜单参数-Modbus寄存器映射:注册、查询、数据双向转换(工业级:类型安全、字节序适配)
|
||||||
*/
|
*/
|
||||||
#include "menu.h"
|
#include "../api/menu.h"
|
||||||
#include "menu_core.h"
|
#include "menu_core.h"
|
||||||
#include "menu_data.h"
|
#include "menu_data.h"
|
||||||
#include "menu_def.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)
|
* @brief 查找Modbus映射(通过参数ID)
|
||||||
|
* @param ctx 菜单全局上下文
|
||||||
* @param param_id 参数ID
|
* @param param_id 参数ID
|
||||||
* @return 映射内部结构体指针(NULL表示未找到)
|
* @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++)
|
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;
|
return NULL;
|
||||||
@ -186,30 +193,37 @@ static ModbusMapInternal* menu_modbus_find_by_param(uint16_t param_id)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 查找Modbus映射(通过寄存器类型和地址)
|
* @brief 查找Modbus映射(通过寄存器类型和地址)
|
||||||
|
* @param ctx 菜单全局上下文
|
||||||
* @param reg_type 寄存器类型
|
* @param reg_type 寄存器类型
|
||||||
* @param reg_addr 寄存器地址(协议地址)
|
* @param reg_addr 寄存器地址(协议地址)
|
||||||
* @return 映射内部结构体指针(NULL表示未找到)
|
* @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++)
|
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 &&
|
||||||
s_menu_modbus_maps[i].pub.reg_type == reg_type &&
|
modbus_ctx->maps[i].pub.reg_type == reg_type &&
|
||||||
s_menu_modbus_maps[i].pub.reg_addr == reg_addr)
|
modbus_ctx->maps[i].pub.reg_addr == reg_addr)
|
||||||
{
|
{
|
||||||
return &s_menu_modbus_maps[i];
|
return &modbus_ctx->maps[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return NULL;
|
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. 校验参数是否已注册(通过获取参数值间接检查)
|
// 1. 校验参数是否已注册(通过获取参数值间接检查)
|
||||||
float dummy_val;
|
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)
|
if (err != MENU_OK)
|
||||||
{
|
{
|
||||||
MENU_DEBUG("Modbus map param %d not found", param_id);
|
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
|
#endif
|
||||||
|
|
||||||
// 6. 检查映射是否已存在
|
// 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);
|
MENU_DEBUG("Modbus map already exists: param %d, reg %d:%d", param_id, reg_type, reg_addr);
|
||||||
return MENU_ERR_INVALID_PARAM;
|
return MENU_ERR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. 查找空闲的映射位置(静态数组)
|
// 7. 查找空闲的映射位置(从全局上下文获取)
|
||||||
|
MenuModbusCtx* modbus_ctx = &ctx->modbus;
|
||||||
ModbusMapInternal* map = NULL;
|
ModbusMapInternal* map = NULL;
|
||||||
for (uint8_t i = 0; i < MENU_CONFIG_MAX_MODBUS_MAPS; i++)
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -279,7 +294,7 @@ MenuErrCode menu_modbus_map_register(uint16_t param_id, ModbusRegType reg_type,
|
|||||||
|
|
||||||
// 8. 获取参数类型
|
// 8. 获取参数类型
|
||||||
MenuParamType param_type;
|
MenuParamType param_type;
|
||||||
err = menu_param_get_type(param_id, ¶m_type);
|
err = menu_param_get_type(ctx, param_id, ¶m_type);
|
||||||
if (err != MENU_OK)
|
if (err != MENU_OK)
|
||||||
{
|
{
|
||||||
return err;
|
return err;
|
||||||
@ -309,14 +324,14 @@ MenuErrCode menu_modbus_map_register(uint16_t param_id, ModbusRegType reg_type,
|
|||||||
return MENU_OK;
|
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;
|
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)
|
if (internal_map == NULL)
|
||||||
{
|
{
|
||||||
MENU_DEBUG("Modbus map param %d not found", param_id);
|
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;
|
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;
|
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)
|
if (internal_map == NULL)
|
||||||
{
|
{
|
||||||
MENU_DEBUG("Modbus map reg %d:%d not found", reg_type, reg_addr);
|
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;
|
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;
|
return MENU_ERR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. 查找映射关系
|
// 1. 查找映射关系
|
||||||
ModbusMapInternal* map = menu_modbus_find_by_param(param_id);
|
ModbusMapInternal* map = menu_modbus_find_by_param(ctx, param_id);
|
||||||
if (map == NULL)
|
if (map == NULL)
|
||||||
{
|
{
|
||||||
return MENU_ERR_NODE_NOT_FOUND;
|
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. 获取参数值
|
// 3. 获取参数值
|
||||||
float param_val;
|
float param_val;
|
||||||
MenuErrCode err = menu_param_get_value(param_id, ¶m_val);
|
MenuErrCode err = menu_param_get_value(ctx, param_id, ¶m_val);
|
||||||
if (err != MENU_OK)
|
if (err != MENU_OK)
|
||||||
{
|
{
|
||||||
MENU_DEBUG("Failed to get param value: %d", err);
|
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;
|
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;
|
return MENU_ERR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. 查找映射关系
|
// 1. 查找映射关系
|
||||||
ModbusMapInternal* map = menu_modbus_find_by_param(param_id);
|
ModbusMapInternal* map = menu_modbus_find_by_param(ctx, param_id);
|
||||||
if (map == NULL)
|
if (map == NULL)
|
||||||
{
|
{
|
||||||
return MENU_ERR_NODE_NOT_FOUND;
|
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. 复制寄存器数据并进行字节序转换(读取寄存器后)
|
// 4. 复制寄存器数据并进行字节序转换(读取寄存器后)
|
||||||
uint16_t reg_data[MENU_CONFIG_MAX_MODBUS_MAPS * 2] = {0}; // 足够大的临时缓冲区
|
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);
|
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. 设置参数值(自动进行范围检查)
|
// 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)
|
if (err != MENU_OK)
|
||||||
{
|
{
|
||||||
return err;
|
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;
|
return MENU_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义Modbus映射静态数组
|
|
||||||
ModbusMapInternal s_menu_modbus_maps[MENU_CONFIG_MAX_MODBUS_MAPS];
|
|
||||||
|
|
||||||
#endif // MENU_CONFIG_ENABLE_MODBUS_MAP
|
#endif // MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||||
@ -7,23 +7,24 @@
|
|||||||
|
|
||||||
#if MENU_CONFIG_ENABLE_LANG
|
#if MENU_CONFIG_ENABLE_LANG
|
||||||
|
|
||||||
/************************** 全局变量定义 **************************/
|
/************************** 内部函数声明 **************************/
|
||||||
MenuLangStr s_menu_lang_strs[MENU_CONFIG_MAX_NODES * MENU_CONFIG_MAX_LANGS];
|
static const char* menu_lang_find_str(MenuGlobalCtx* global_ctx, uint16_t str_id, uint8_t lang_id);
|
||||||
uint8_t s_current_lang_id = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 查找语言字符串(通过字符串ID和语言ID)
|
* @brief 查找语言字符串(通过字符串ID和语言ID)
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
* @param str_id 字符串ID
|
* @param str_id 字符串ID
|
||||||
* @param lang_id 语言ID
|
* @param lang_id 语言ID
|
||||||
* @return 语言字符串(NULL表示未找到)
|
* @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++)
|
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;
|
return NULL;
|
||||||
@ -31,26 +32,33 @@ static const char* menu_lang_find_str(uint16_t str_id, uint8_t lang_id)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 注册语言字符串
|
* @brief 注册语言字符串
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
* @param str_id 字符串ID
|
* @param str_id 字符串ID
|
||||||
* @param lang_id 语言ID
|
* @param lang_id 语言ID
|
||||||
* @param str 字符串内容
|
* @param str 字符串内容
|
||||||
* @return 错误码
|
* @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)
|
if (lang_id >= MENU_CONFIG_MAX_LANGS || str == NULL)
|
||||||
{
|
{
|
||||||
return MENU_ERR_INVALID_PARAM;
|
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++)
|
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;
|
lang_ctx->strs[i].str_id = str_id;
|
||||||
s_menu_lang_strs[i].lang_id = lang_id;
|
lang_ctx->strs[i].lang_id = lang_id;
|
||||||
s_menu_lang_strs[i].str = str;
|
lang_ctx->strs[i].str = str;
|
||||||
return MENU_OK;
|
return MENU_OK;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,54 +68,70 @@ MenuErrCode menu_lang_register_str(uint16_t str_id, uint8_t lang_id, const char*
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取当前语言的字符串
|
* @brief 获取当前语言的字符串
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
* @param str_id 字符串ID
|
* @param str_id 字符串ID
|
||||||
* @return 字符串内容(NULL表示未找到)
|
* @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)
|
if (str == NULL)
|
||||||
{
|
{
|
||||||
// 如果当前语言未找到,尝试使用默认语言(0)
|
// 如果当前语言未找到,尝试使用默认语言(0)
|
||||||
str = menu_lang_find_str(str_id, 0);
|
str = menu_lang_find_str(global_ctx, str_id, 0);
|
||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 设置当前语言
|
* @brief 设置当前语言
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
* @param lang_id 语言ID(如0-中文,1-英文)
|
* @param lang_id 语言ID(如0-中文,1-英文)
|
||||||
* @return 错误码
|
* @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)
|
if (lang_id >= MENU_CONFIG_MAX_LANGS)
|
||||||
{
|
{
|
||||||
return MENU_ERR_INVALID_PARAM;
|
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_DEBUG("Language changed to: %d", lang_id);
|
||||||
|
|
||||||
// 语言切换后,刷新菜单显示
|
// 语言切换后,刷新菜单显示
|
||||||
menu_core_refresh_display();
|
menu_core_refresh_display(global_ctx);
|
||||||
|
|
||||||
return MENU_OK;
|
return MENU_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取当前语言
|
* @brief 获取当前语言
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
* @param lang_id 输出参数,当前语言ID
|
* @param lang_id 输出参数,当前语言ID
|
||||||
* @return 错误码
|
* @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;
|
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;
|
return MENU_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,4 +144,64 @@ uint8_t menu_lang_get_max_langs(void)
|
|||||||
return MENU_CONFIG_MAX_LANGS;
|
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
|
#endif // MENU_CONFIG_ENABLE_LANG
|
||||||
@ -7,21 +7,30 @@
|
|||||||
|
|
||||||
#if MENU_CONFIG_ENABLE_PARAM
|
#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,内部使用)
|
* @brief 查找参数(通过参数ID,内部使用)
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
* @param param_id 参数ID
|
* @param param_id 参数ID
|
||||||
* @return 参数指针(NULL表示未找到)
|
* @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++)
|
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 ¶m_ctx->params[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -124,6 +133,7 @@ static float menu_param_internal_to_float(const MenuParam* param)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 注册参数到菜单节点(参数管理功能)
|
* @brief 注册参数到菜单节点(参数管理功能)
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
* @param node_id 菜单节点ID(参数与菜单绑定)
|
* @param node_id 菜单节点ID(参数与菜单绑定)
|
||||||
* @param param_id 参数ID(唯一)
|
* @param param_id 参数ID(唯一)
|
||||||
* @param type 参数类型
|
* @param type 参数类型
|
||||||
@ -133,33 +143,39 @@ static float menu_param_internal_to_float(const MenuParam* param)
|
|||||||
* @param scale 缩放因子(如0.1表示保留1位小数)
|
* @param scale 缩放因子(如0.1表示保留1位小数)
|
||||||
* @return 错误码
|
* @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)
|
if (scale <= 0.0f)
|
||||||
{
|
{
|
||||||
return MENU_ERR_INVALID_PARAM;
|
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)
|
if (node == NULL)
|
||||||
{
|
{
|
||||||
return MENU_ERR_NODE_NOT_FOUND;
|
return MENU_ERR_NODE_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查参数ID是否已存在
|
// 检查参数ID是否已存在
|
||||||
if (menu_param_find(param_id) != NULL)
|
if (menu_param_find(global_ctx, param_id) != NULL)
|
||||||
{
|
{
|
||||||
return MENU_ERR_INVALID_PARAM;
|
return MENU_ERR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查找空闲参数位置
|
// 查找空闲参数位置
|
||||||
|
MenuParamCtx* param_ctx = &global_ctx->param;
|
||||||
MenuParam* param = NULL;
|
MenuParam* param = NULL;
|
||||||
for (uint16_t i = 0; i < MENU_CONFIG_MAX_PARAMS; i++)
|
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 = ¶m_ctx->params[i];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -194,14 +210,20 @@ MenuErrCode menu_param_register(MenuNodeId node_id, uint16_t param_id, MenuParam
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 设置参数值
|
* @brief 设置参数值
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
* @param param_id 参数ID
|
* @param param_id 参数ID
|
||||||
* @param value 新值(浮点型,内部自动转换)
|
* @param value 新值(浮点型,内部自动转换)
|
||||||
* @return 错误码
|
* @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)
|
if (param == NULL)
|
||||||
{
|
{
|
||||||
return MENU_ERR_NODE_NOT_FOUND;
|
return MENU_ERR_NODE_NOT_FOUND;
|
||||||
@ -218,19 +240,20 @@ MenuErrCode menu_param_set_value(uint16_t param_id, float value)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取参数值
|
* @brief 获取参数值
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
* @param param_id 参数ID
|
* @param param_id 参数ID
|
||||||
* @param value 输出参数,当前值(浮点型)
|
* @param value 输出参数,当前值(浮点型)
|
||||||
* @return 错误码
|
* @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;
|
return MENU_ERR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查找参数
|
// 查找参数
|
||||||
MenuParam* param = menu_param_find(param_id);
|
MenuParam* param = menu_param_find(global_ctx, param_id);
|
||||||
if (param == NULL)
|
if (param == NULL)
|
||||||
{
|
{
|
||||||
return MENU_ERR_NODE_NOT_FOUND;
|
return MENU_ERR_NODE_NOT_FOUND;
|
||||||
@ -246,13 +269,19 @@ MenuErrCode menu_param_get_value(uint16_t param_id, float* value)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 将参数恢复为默认值
|
* @brief 将参数恢复为默认值
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
* @param param_id 参数ID
|
* @param param_id 参数ID
|
||||||
* @return 错误码
|
* @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)
|
if (param == NULL)
|
||||||
{
|
{
|
||||||
return MENU_ERR_NODE_NOT_FOUND;
|
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*)¶m->default_val);
|
float default_val = menu_param_internal_to_float((const MenuParam*)¶m->default_val);
|
||||||
return menu_param_set_value(param_id, default_val);
|
return menu_param_set_value(global_ctx, param_id, default_val);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 增加参数值(用于菜单上下键调整)
|
* @brief 增加参数值(用于菜单上下键调整)
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
* @param param_id 参数ID
|
* @param param_id 参数ID
|
||||||
* @param step 步长(浮点型)
|
* @param step 步长(浮点型)
|
||||||
* @return 错误码
|
* @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;
|
float current_val = 0.0f;
|
||||||
MenuErrCode err = menu_param_get_value(param_id, ¤t_val);
|
MenuErrCode err = menu_param_get_value(global_ctx, param_id, ¤t_val);
|
||||||
if (err != MENU_OK)
|
if (err != MENU_OK)
|
||||||
{
|
{
|
||||||
return err;
|
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 减少参数值(用于菜单上下键调整)
|
* @brief 减少参数值(用于菜单上下键调整)
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
* @param param_id 参数ID
|
* @param param_id 参数ID
|
||||||
* @param step 步长(浮点型)
|
* @param step 步长(浮点型)
|
||||||
* @return 错误码
|
* @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;
|
float current_val = 0.0f;
|
||||||
MenuErrCode err = menu_param_get_value(param_id, ¤t_val);
|
MenuErrCode err = menu_param_get_value(global_ctx, param_id, ¤t_val);
|
||||||
if (err != MENU_OK)
|
if (err != MENU_OK)
|
||||||
{
|
{
|
||||||
return err;
|
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 获取参数类型
|
* @brief 获取参数类型
|
||||||
|
* @param global_ctx 全局上下文指针
|
||||||
* @param param_id 参数ID
|
* @param param_id 参数ID
|
||||||
* @param type 输出参数,参数类型
|
* @param type 输出参数,参数类型
|
||||||
* @return 错误码
|
* @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;
|
return MENU_ERR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查找参数
|
MenuParam* param = menu_param_find(global_ctx, param_id);
|
||||||
MenuParam* param = menu_param_find(param_id);
|
|
||||||
if (param == NULL)
|
if (param == NULL)
|
||||||
{
|
{
|
||||||
return MENU_ERR_NODE_NOT_FOUND;
|
return MENU_ERR_NODE_NOT_FOUND;
|
||||||
@ -324,4 +365,35 @@ MenuErrCode menu_param_get_type(uint16_t param_id, MenuParamType* type)
|
|||||||
return MENU_OK;
|
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
|
#endif // MENU_CONFIG_ENABLE_PARAM
|
||||||
39
src/param/menu_param.h
Normal file
39
src/param/menu_param.h
Normal 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
|
||||||
@ -5,6 +5,24 @@
|
|||||||
#include "menu_def.h"
|
#include "menu_def.h"
|
||||||
#include "menu_port.h"
|
#include "menu_port.h"
|
||||||
#include <stdarg.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 断言失败处理函数
|
* @brief 断言失败处理函数
|
||||||
@ -14,14 +32,57 @@
|
|||||||
void menu_utils_assert_failed(const char* file, uint32_t line)
|
void menu_utils_assert_failed(const char* file, uint32_t line)
|
||||||
{
|
{
|
||||||
// 调用port层的打印接口输出断言信息
|
// 调用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();
|
// 例如: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 fmt 格式化字符串
|
||||||
* @param ... 可变参数
|
* @param ... 可变参数
|
||||||
*/
|
*/
|
||||||
@ -31,7 +92,6 @@ void menu_utils_printf(const char* fmt, ...)
|
|||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
|
|
||||||
// 调用port层的可变参数打印接口
|
// 调用port层的可变参数打印接口
|
||||||
// 注意:这里需要用户在port层实现menu_port_printf函数
|
|
||||||
menu_port_printf(fmt, args);
|
menu_port_printf(fmt, args);
|
||||||
|
|
||||||
va_end(args);
|
va_end(args);
|
||||||
|
|||||||
471
test/menu_test.c
Normal file
471
test/menu_test.c
Normal file
@ -0,0 +1,471 @@
|
|||||||
|
/**
|
||||||
|
* @file menu_test.c
|
||||||
|
* @brief 菜单组件单元测试
|
||||||
|
*/
|
||||||
|
#include "menu.h"
|
||||||
|
#include "menu_port.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
/************************** 测试用例 **************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 测试菜单初始化和反初始化
|
||||||
|
*/
|
||||||
|
void test_menu_init_deinit(void)
|
||||||
|
{
|
||||||
|
printf("Test menu_init_deinit...");
|
||||||
|
|
||||||
|
// 分配菜单上下文
|
||||||
|
uint8_t menu_ctx_buf[menu_get_ctx_size()];
|
||||||
|
MenuGlobalCtx* ctx = (MenuGlobalCtx*)menu_ctx_buf;
|
||||||
|
|
||||||
|
// 初始化菜单
|
||||||
|
MenuErrCode err = menu_init(ctx);
|
||||||
|
if (err != MENU_OK)
|
||||||
|
{
|
||||||
|
printf("FAILED: menu_init returned %d\n", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查初始化状态
|
||||||
|
if (!menu_is_initialized(ctx))
|
||||||
|
{
|
||||||
|
printf("FAILED: menu_is_initialized returned false\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 反初始化菜单
|
||||||
|
err = menu_deinit(ctx);
|
||||||
|
if (err != MENU_OK)
|
||||||
|
{
|
||||||
|
printf("FAILED: menu_deinit returned %d\n", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("PASSED\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 测试菜单节点注册
|
||||||
|
*/
|
||||||
|
void test_menu_register_node(void)
|
||||||
|
{
|
||||||
|
printf("Test menu_register_node...");
|
||||||
|
|
||||||
|
// 分配菜单上下文
|
||||||
|
uint8_t menu_ctx_buf[menu_get_ctx_size()];
|
||||||
|
MenuGlobalCtx* ctx = (MenuGlobalCtx*)menu_ctx_buf;
|
||||||
|
|
||||||
|
// 初始化菜单
|
||||||
|
MenuErrCode err = menu_init(ctx);
|
||||||
|
if (err != MENU_OK)
|
||||||
|
{
|
||||||
|
printf("FAILED: menu_init returned %d\n", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册菜单节点
|
||||||
|
err = menu_register_node(ctx, 0, 1, "Main Menu", NULL, NULL);
|
||||||
|
if (err != MENU_OK)
|
||||||
|
{
|
||||||
|
printf("FAILED: menu_register_node returned %d\n", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册子菜单节点
|
||||||
|
err = menu_register_node(ctx, 1, 2, "Param Menu", NULL, NULL);
|
||||||
|
if (err != MENU_OK)
|
||||||
|
{
|
||||||
|
printf("FAILED: menu_register_node returned %d\n", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查当前节点
|
||||||
|
MenuNodeId current_node_id;
|
||||||
|
err = menu_get_current_node(ctx, ¤t_node_id);
|
||||||
|
if (err != MENU_OK)
|
||||||
|
{
|
||||||
|
printf("FAILED: menu_get_current_node returned %d\n", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_node_id != 1)
|
||||||
|
{
|
||||||
|
printf("FAILED: current_node_id is %d, expected 1\n", current_node_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 反初始化菜单
|
||||||
|
menu_deinit(ctx);
|
||||||
|
|
||||||
|
printf("PASSED\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 测试批量注册菜单节点
|
||||||
|
*/
|
||||||
|
void test_menu_register_nodes(void)
|
||||||
|
{
|
||||||
|
printf("Test menu_register_nodes...");
|
||||||
|
|
||||||
|
// 分配菜单上下文
|
||||||
|
uint8_t menu_ctx_buf[menu_get_ctx_size()];
|
||||||
|
MenuGlobalCtx* ctx = (MenuGlobalCtx*)menu_ctx_buf;
|
||||||
|
|
||||||
|
// 初始化菜单
|
||||||
|
MenuErrCode err = menu_init(ctx);
|
||||||
|
if (err != MENU_OK)
|
||||||
|
{
|
||||||
|
printf("FAILED: menu_init returned %d\n", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义菜单节点配置
|
||||||
|
MenuNodeConfig nodes[] = {
|
||||||
|
{1, 0, "Main Menu", NULL, NULL},
|
||||||
|
{2, 1, "Param Menu", NULL, NULL},
|
||||||
|
{3, 1, "Info Menu", NULL, NULL},
|
||||||
|
{4, 2, "Temp Param", NULL, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 批量注册菜单节点
|
||||||
|
err = menu_register_nodes(ctx, nodes, sizeof(nodes) / sizeof(nodes[0]));
|
||||||
|
if (err != MENU_OK)
|
||||||
|
{
|
||||||
|
printf("FAILED: menu_register_nodes returned %d\n", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 反初始化菜单
|
||||||
|
menu_deinit(ctx);
|
||||||
|
|
||||||
|
printf("PASSED\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 测试事件处理
|
||||||
|
*/
|
||||||
|
void test_menu_event(void)
|
||||||
|
{
|
||||||
|
printf("Test menu_event...");
|
||||||
|
|
||||||
|
// 分配菜单上下文
|
||||||
|
uint8_t menu_ctx_buf[menu_get_ctx_size()];
|
||||||
|
MenuGlobalCtx* ctx = (MenuGlobalCtx*)menu_ctx_buf;
|
||||||
|
|
||||||
|
// 初始化菜单
|
||||||
|
MenuErrCode err = menu_init(ctx);
|
||||||
|
if (err != MENU_OK)
|
||||||
|
{
|
||||||
|
printf("FAILED: menu_init returned %d\n", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册菜单节点
|
||||||
|
err = menu_register_node(ctx, 0, 1, "Main Menu", NULL, NULL);
|
||||||
|
err = menu_register_node(ctx, 1, 2, "Menu 1", NULL, NULL);
|
||||||
|
err = menu_register_node(ctx, 1, 3, "Menu 2", NULL, NULL);
|
||||||
|
|
||||||
|
// 发送事件
|
||||||
|
err = menu_post_event(ctx, MENU_EVENT_KEY_DOWN, 0, MENU_EVENT_PRIORITY_NORMAL);
|
||||||
|
if (err != MENU_OK)
|
||||||
|
{
|
||||||
|
printf("FAILED: menu_post_event returned %d\n", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理事件
|
||||||
|
menu_main_loop(ctx);
|
||||||
|
|
||||||
|
// 检查当前节点
|
||||||
|
MenuNodeId current_node_id;
|
||||||
|
err = menu_get_current_node(ctx, ¤t_node_id);
|
||||||
|
if (err != MENU_OK)
|
||||||
|
{
|
||||||
|
printf("FAILED: menu_get_current_node returned %d\n", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 反初始化菜单
|
||||||
|
menu_deinit(ctx);
|
||||||
|
|
||||||
|
printf("PASSED\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 测试内存监控
|
||||||
|
*/
|
||||||
|
void test_menu_mem_stats(void)
|
||||||
|
{
|
||||||
|
printf("Test menu_mem_stats...");
|
||||||
|
|
||||||
|
// 分配菜单上下文
|
||||||
|
uint8_t menu_ctx_buf[menu_get_ctx_size()];
|
||||||
|
MenuGlobalCtx* ctx = (MenuGlobalCtx*)menu_ctx_buf;
|
||||||
|
|
||||||
|
// 初始化菜单
|
||||||
|
MenuErrCode err = menu_init(ctx);
|
||||||
|
if (err != MENU_OK)
|
||||||
|
{
|
||||||
|
printf("FAILED: menu_init returned %d\n", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册菜单节点
|
||||||
|
err = menu_register_node(ctx, 0, 1, "Main Menu", NULL, NULL);
|
||||||
|
err = menu_register_node(ctx, 1, 2, "Menu 1", NULL, NULL);
|
||||||
|
|
||||||
|
// 获取内存统计信息
|
||||||
|
MenuMemStats stats;
|
||||||
|
err = menu_get_mem_stats(ctx, &stats);
|
||||||
|
if (err != MENU_OK)
|
||||||
|
{
|
||||||
|
printf("FAILED: menu_get_mem_stats returned %d\n", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("PASSED (nodes: %d/%d, params: %d/%d, events: %d/%d)\n",
|
||||||
|
stats.used_nodes, stats.total_nodes,
|
||||||
|
stats.used_params, stats.total_params,
|
||||||
|
stats.event_queue_used, stats.event_queue_len);
|
||||||
|
|
||||||
|
// 反初始化菜单
|
||||||
|
menu_deinit(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/************************** 多语言支持测试 **************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 测试多语言功能
|
||||||
|
*/
|
||||||
|
void test_menu_lang(void)
|
||||||
|
{
|
||||||
|
printf("Test menu_lang...");
|
||||||
|
|
||||||
|
// 分配菜单上下文
|
||||||
|
uint8_t menu_ctx_buf[menu_get_ctx_size()];
|
||||||
|
MenuGlobalCtx* ctx = (MenuGlobalCtx*)menu_ctx_buf;
|
||||||
|
|
||||||
|
// 初始化菜单
|
||||||
|
MenuErrCode err = menu_init(ctx);
|
||||||
|
if (err != MENU_OK)
|
||||||
|
{
|
||||||
|
printf("FAILED: menu_init returned %d\n", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册语言字符串
|
||||||
|
err = menu_lang_register_str(ctx, 1, 0, "中文菜单");
|
||||||
|
err = menu_lang_register_str(ctx, 1, 1, "English Menu");
|
||||||
|
err = menu_lang_register_str(ctx, 2, 0, "参数设置");
|
||||||
|
err = menu_lang_register_str(ctx, 2, 1, "Param Settings");
|
||||||
|
|
||||||
|
// 设置当前语言为中文
|
||||||
|
err = menu_lang_set_current(ctx, 0);
|
||||||
|
if (err != MENU_OK)
|
||||||
|
{
|
||||||
|
printf("FAILED: menu_lang_set_current(0) returned %d\n", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查当前语言
|
||||||
|
uint8_t current_lang;
|
||||||
|
err = menu_lang_get_current(ctx, ¤t_lang);
|
||||||
|
if (err != MENU_OK || current_lang != 0)
|
||||||
|
{
|
||||||
|
printf("FAILED: menu_lang_get_current returned %d, expected 0\n", current_lang);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量注册语言字符串测试
|
||||||
|
MenuLangStr strs[] = {
|
||||||
|
{3, 0, "系统信息"},
|
||||||
|
{3, 1, "System Info"},
|
||||||
|
{4, 0, "关于"},
|
||||||
|
{4, 1, "About"}
|
||||||
|
};
|
||||||
|
|
||||||
|
err = menu_lang_register_strs(ctx, strs, sizeof(strs) / sizeof(strs[0]));
|
||||||
|
if (err != MENU_OK)
|
||||||
|
{
|
||||||
|
printf("FAILED: menu_lang_register_strs returned %d\n", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动态加载语言包测试
|
||||||
|
MenuLangStr new_pack[] = {
|
||||||
|
{1, 2, "Español Menú"},
|
||||||
|
{2, 2, "Ajustes de Parámetros"},
|
||||||
|
{3, 2, "Información del Sistema"},
|
||||||
|
{4, 2, "Acerca de"}
|
||||||
|
};
|
||||||
|
|
||||||
|
err = menu_lang_load_pack(ctx, 2, new_pack, sizeof(new_pack) / sizeof(new_pack[0]));
|
||||||
|
if (err != MENU_OK)
|
||||||
|
{
|
||||||
|
printf("FAILED: menu_lang_load_pack returned %d\n", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换到新语言
|
||||||
|
err = menu_lang_set_current(ctx, 2);
|
||||||
|
if (err != MENU_OK)
|
||||||
|
{
|
||||||
|
printf("FAILED: menu_lang_set_current(2) returned %d\n", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 反初始化菜单
|
||||||
|
menu_deinit(ctx);
|
||||||
|
|
||||||
|
printf("PASSED\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/************************** 参数管理测试 **************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 测试参数管理功能
|
||||||
|
*/
|
||||||
|
void test_menu_param(void)
|
||||||
|
{
|
||||||
|
printf("Test menu_param...");
|
||||||
|
|
||||||
|
// 分配菜单上下文
|
||||||
|
uint8_t menu_ctx_buf[menu_get_ctx_size()];
|
||||||
|
MenuGlobalCtx* ctx = (MenuGlobalCtx*)menu_ctx_buf;
|
||||||
|
|
||||||
|
// 初始化菜单
|
||||||
|
MenuErrCode err = menu_init(ctx);
|
||||||
|
if (err != MENU_OK)
|
||||||
|
{
|
||||||
|
printf("FAILED: menu_init returned %d\n", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册菜单节点
|
||||||
|
err = menu_register_node(ctx, 0, 1, "Main Menu", NULL, NULL);
|
||||||
|
|
||||||
|
// 注册参数
|
||||||
|
err = menu_param_register(ctx, 1, 1, MENU_PARAM_TYPE_INT16, 0, 100, 50, 1.0f);
|
||||||
|
if (err != MENU_OK)
|
||||||
|
{
|
||||||
|
printf("FAILED: menu_param_register returned %d\n", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置参数值
|
||||||
|
err = menu_param_set_value(ctx, 1, 75.0f);
|
||||||
|
if (err != MENU_OK)
|
||||||
|
{
|
||||||
|
printf("FAILED: menu_param_set_value returned %d\n", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取参数值
|
||||||
|
float value;
|
||||||
|
err = menu_param_get_value(ctx, 1, &value);
|
||||||
|
if (err != MENU_OK || (int)value != 75)
|
||||||
|
{
|
||||||
|
printf("FAILED: menu_param_get_value returned %.1f, expected 75\n", value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取参数类型
|
||||||
|
MenuParamType type;
|
||||||
|
err = menu_param_get_type(ctx, 1, &type);
|
||||||
|
if (err != MENU_OK || type != MENU_PARAM_TYPE_INT16)
|
||||||
|
{
|
||||||
|
printf("FAILED: menu_param_get_type returned %d, expected %d\n", type, MENU_PARAM_TYPE_INT16);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 反初始化菜单
|
||||||
|
menu_deinit(ctx);
|
||||||
|
|
||||||
|
printf("PASSED\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/************************** 优先级事件测试 **************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 测试优先级事件处理
|
||||||
|
*/
|
||||||
|
void test_menu_priority_event(void)
|
||||||
|
{
|
||||||
|
printf("Test menu_priority_event...");
|
||||||
|
|
||||||
|
// 分配菜单上下文
|
||||||
|
uint8_t menu_ctx_buf[menu_get_ctx_size()];
|
||||||
|
MenuGlobalCtx* ctx = (MenuGlobalCtx*)menu_ctx_buf;
|
||||||
|
|
||||||
|
// 初始化菜单
|
||||||
|
MenuErrCode err = menu_init(ctx);
|
||||||
|
if (err != MENU_OK)
|
||||||
|
{
|
||||||
|
printf("FAILED: menu_init returned %d\n", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册菜单节点
|
||||||
|
err = menu_register_node(ctx, 0, 1, "Main Menu", NULL, NULL);
|
||||||
|
err = menu_register_node(ctx, 1, 2, "Menu 1", NULL, NULL);
|
||||||
|
err = menu_register_node(ctx, 1, 3, "Menu 2", NULL, NULL);
|
||||||
|
|
||||||
|
// 发送不同优先级的事件
|
||||||
|
err = menu_post_event(ctx, MENU_EVENT_KEY_DOWN, 0, MENU_EVENT_PRIORITY_LOW);
|
||||||
|
err = menu_post_event(ctx, MENU_EVENT_KEY_UP, 0, MENU_EVENT_PRIORITY_HIGH);
|
||||||
|
err = menu_post_event(ctx, MENU_EVENT_KEY_ENTER, 0, MENU_EVENT_PRIORITY_NORMAL);
|
||||||
|
|
||||||
|
// 处理事件
|
||||||
|
menu_main_loop(ctx);
|
||||||
|
|
||||||
|
// 反初始化菜单
|
||||||
|
menu_deinit(ctx);
|
||||||
|
|
||||||
|
printf("PASSED\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/************************** 主函数 **************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 测试主函数
|
||||||
|
*/
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
printf("Menu Component Unit Tests\n");
|
||||||
|
printf("=========================\n");
|
||||||
|
|
||||||
|
// 测试菜单初始化和反初始化
|
||||||
|
test_menu_init_deinit();
|
||||||
|
|
||||||
|
// 测试菜单节点注册
|
||||||
|
test_menu_register_node();
|
||||||
|
|
||||||
|
// 测试批量注册菜单节点
|
||||||
|
test_menu_register_nodes();
|
||||||
|
|
||||||
|
// 测试事件处理
|
||||||
|
test_menu_event();
|
||||||
|
|
||||||
|
// 测试优先级事件处理
|
||||||
|
test_menu_priority_event();
|
||||||
|
|
||||||
|
// 测试内存监控
|
||||||
|
test_menu_mem_stats();
|
||||||
|
|
||||||
|
// 测试参数管理
|
||||||
|
#if MENU_CONFIG_ENABLE_PARAM
|
||||||
|
test_menu_param();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// 测试多语言支持
|
||||||
|
#if MENU_CONFIG_ENABLE_LANG
|
||||||
|
test_menu_lang();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
printf("=========================\n");
|
||||||
|
printf("All tests completed!\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user