Rdzleo 3dced23cfd feat: 新增图片删除功能 + 修复开机/唤醒闪烁问题
## 新增功能
- 图片删除:支持从 SPIFFS 彻底删除图片(物理删除 + 内存管理)
- ContainerDle 管理:新增半透明圆形删除容器(含删除和返回按钮)
- 状态管理:标志位模式管理 UI 状态,离开界面自动清理

## 解决的关键问题
1. 开机闪烁 (️)
   - 原因:LCD GRAM 保留旧数据
   - 方案:背光使能前清空 GRAM(DMA 分批填充黑色,34ms)

2. 低功耗唤醒闪烁 (️)
   - 原因:先恢复亮度后切换界面,看到旧界面
   - 方案:先切换界面(背光=0)→ 延时 100ms → 恢复亮度

3. ContainerDle 状态保留 (️)
   - 原因:LVGL 对象不销毁,状态被保留
   - 方案:离开界面时主动清理(手势/按键回调中调用隐藏函数)

4. 按键回调冲突 (️)
   - 原因:按键系统单回调限制
   - 方案:统一在 main.c 管理 BOOT 按键,其他模块通过接口调用

5. 开机加载图片闪烁 (️)
   - 原因:screen_init() 中 JPEG 解码触发渲染
   - 方案:延迟到 LV_EVENT_SCREEN_LOADED 事件加载

## 功能改动
- BOOT 按键增强:唤醒 + 退出手电筒 + 隐藏容器 + 返回 Home
- 图片界面优化:支持删除当前图片并自动显示下一张
- 休眠管理优化:移除 BOOT 注册,避免回调冲突

## 技术优化
- 资源节约:分批处理大数据(LCD GRAM 清除)
- 时序优化:根据屏幕状态智能调整唤醒时序
- 模块化设计:按键集中管理 + 接口清晰 + 状态标志模式

## 文件变更
- lcd/lcd.c: LCD GRAM 清除逻辑
- main.c: BOOT 按键统一管理
- pages/pages.c: 图片删除功能实现
- ui/screens/ui_ScreenImg.c: ContainerDle 管理 + 状态控制
- sleep_mgr/sleep_mgr.c: 按键回调优化
- ui/images: 新增删除和返回按钮图标 (s13.png, s14.png)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-11 16:51:51 +08:00

880 lines
31 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "lvgl.h"
#include "fatfs.h"
#include "driver/ledc.h"
#include "gpio.h"
#include "wifi.h"
#include "temp.h"
#include "jpeg_decoder.h"
#include "../ui/screens/ui_ScreenImg.h"
#include <inttypes.h>
#include <dirent.h>
#include <sys/stat.h>
#include <string.h>
#include <strings.h>
#include <stdbool.h>
char img_path[40];
char *img_filename;
lv_obj_t *app_img;
lv_obj_t *act_mainscreen;
uint8_t *app_img_data = 0;
esp_jpeg_image_output_t outdata;
lv_img_dsc_t image;
#define MAX_IMAGE_FILES 10
#define MAX_FILENAME_LEN 32
static char spiffs_image_files[MAX_IMAGE_FILES][MAX_FILENAME_LEN];
static int spiffs_image_count = 0;
static int current_image_index = 0;
static bool image_list_initialized = false;
// 当前亮度值(用于休眠恢复)
static uint8_t current_brightness = 50;
// 获取当前亮度值
uint8_t pwm_get_brightness(void) {
return current_brightness;
}
// 设置屏幕亮度percent范围0-100
// 0=完全关闭背光10~100为正常亮度范围
// 显示10%~100%映射到实际亮度20%~100%,背光低电平有效需反转占空比
void pwm_set_brightness(uint8_t percent) {
if (percent == 0) {
// 完全关闭背光低电平有效占空比100%=全高=关闭)
ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 8191);
ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);
return;
}
if (percent < 10) percent = 10;
if (percent > 100) percent = 100;
current_brightness = percent;
uint32_t actual = 20 + (uint32_t)(percent - 10) * 80 / 90;
uint32_t duty = 8191 - (8191 * actual) / 100;
ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, duty);
ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);
}
// 初始化PWM
void pwm_init(){
ledc_timer_config_t ledc_timer = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.timer_num = LEDC_TIMER_0,
.duty_resolution = LEDC_TIMER_13_BIT,
.freq_hz = 5000,
.clk_cfg = LEDC_AUTO_CLK
};
ledc_timer_config(&ledc_timer);// 配置PWM定时器
ledc_channel_config_t ledc_channel = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.channel = LEDC_CHANNEL_0,
.timer_sel = LEDC_TIMER_0,
.intr_type = LEDC_INTR_DISABLE,
.gpio_num = PIN_LCD_EN,
.duty = 0,
.hpoint = 0
};
ledc_channel_config(&ledc_channel);// 配置PWM通道
pwm_set_brightness(50);// 初始亮度50%
// ledc_timer_config_t motor_timer = {
// .speed_mode = LEDC_LOW_SPEED_MODE,
// .timer_num = LEDC_TIMER_1,
// .duty_resolution = LEDC_TIMER_13_BIT,
// .freq_hz = 5000,
// .clk_cfg = LEDC_AUTO_CLK
// };
// ledc_timer_config(&motor_timer);
// ledc_channel_config_t motor_channel = {
// .speed_mode = LEDC_LOW_SPEED_MODE,
// .channel = LEDC_CHANNEL_1,
// .timer_sel = LEDC_TIMER_0,
// .intr_type = LEDC_INTR_DISABLE,
// .gpio_num = PIN_MOTOR_EN,
// .duty = 4095,
// .hpoint = 0
// };
// ledc_channel_config(&motor_channel);
// ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_1, 0);
// ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_1);
}
// 测试扫描WiFi列表
void wifi_scan_list_test(){
wifi_ap_record_t* wifi_list = NULL;// 定义WiFi列表指针
uint16_t num = 0;// 定义WiFi数量变量
esp_err_t err = wifi_scan_list(&num,&wifi_list);// 扫描WiFi列表
if(err == ESP_OK){
ESP_LOGI("WIFI","列表获取成功,数量:%d",num);// 打印WiFi数量
for (int i = 0; i < num; i++) {
ESP_LOGI("WIFI", "AP %d - SSID: %s, RSSI: %d", i + 1, wifi_list[i].ssid, wifi_list[i].rssi);// 打印每个WiFi的SSID和RSSI
}
}
}
// 测试连接WiFi
void wifi_connect_test(){
wifi_config_t wifi_config = {
.sta = {
.password = "12345678",
.ssid = "LDL的iPhone"
}
};
esp_wifi_set_config(ESP_IF_WIFI_STA,&wifi_config);// 设置WiFi配置
esp_err_t err = esp_wifi_connect();// 连接WiFi
if(err == ESP_OK){
ESP_LOGI("WIFI","WIFI连接成功");
}
}
// 测试断开WiFi连接
void wifi_disconnect_test(){
esp_err_t err = esp_wifi_disconnect();// 断开WiFi连接
if(err == ESP_OK){
ESP_LOGI("WIFI","已断开WiFi连接");
}
}
// 测试开始扫描WiFi
void wifi_scan_start_test(){
wifi_scan_start();// 开始扫描WiFi
}
// 测试获取可用堆内存
void free_heap_test(){
ESP_LOGI("HEAP","可用堆内存:%d",(int)heap_caps_get_total_size(MALLOC_CAP_SPIRAM));
}
lv_obj_t *act_wifiscreen;// 当前WiFi屏幕对象
lv_obj_t *act_testscreen;// 当前测试屏幕对象
lv_obj_t *act_mainscreen;// 当前主屏幕对象
void app_wifi_display(){
}
// 测试获取温度和湿度
void temp_test(){
float temp,humi;// 定义温度和湿度变量
get_temp(&temp);// 获取温度
get_humi(&humi);// 获取湿度
ESP_LOGI("TEMP","temp:%.2f humi:%.2f",temp,humi);// 打印温度和湿度
}
// 从NVS中读取图片路径
esp_err_t nvs_read_img(void) {
nvs_handle_t nvs_handle; // NVS 句柄
esp_err_t err; // NVS 错误码
err = nvs_open("config", NVS_READONLY, &nvs_handle);// 打开 NVS 句柄
if (err != ESP_OK) return err; // 如果打开失败,返回错误码
size_t imgname_len;
err = nvs_get_str(nvs_handle, "img_filename", NULL, &imgname_len);// 获取图片路径长度
if (err == ESP_OK) {
img_filename = malloc(imgname_len);// 分配内存
err = nvs_get_str(nvs_handle, "img_filename", img_filename, &imgname_len);// 获取图片路径
if (err != ESP_OK) {
nvs_close(nvs_handle);// 关闭 NVS 句柄
return err; // 如果获取失败,返回错误码
}
ESP_LOGI("NVS", "img_filename: %s", img_filename);// 打印图片路径
}
nvs_close(nvs_handle);// 关闭 NVS 句柄
return err;
}
// 测试改变NVS中的图片路径
esp_err_t nvs_change_img(char *imgname) {
nvs_handle_t nvs_handle;// NVS 句柄
esp_err_t err;
err = nvs_open("config", NVS_READWRITE, &nvs_handle);// 打开 NVS 句柄
if (err != ESP_OK) goto close_handle;
err = nvs_set_str(nvs_handle, "img_filename", imgname);// 设置图片路径
if (err != ESP_OK) goto close_handle;
err = nvs_commit(nvs_handle);// 提交更改
if (err != ESP_OK) goto close_handle;// 如果提交失败,关闭句柄并返回错误码
close_handle:
nvs_close(nvs_handle); // 关闭 NVS 句柄
return err;
}
// 仅更新现有图片,显示其他图片
// img_name: 图片文件名为NULL时从NVS读取
void app_img_change(const char *img_name){
// 释放之前的图片数据
if(app_img_data){
free(app_img_data);
app_img_data = NULL;
ESP_LOGI("IMG", "释放之前显示的图片数据缓存");
}
const char *current_img_name = img_name;
// 如果没有指定图片名从NVS读取
if(!current_img_name) {
esp_err_t ret_nvs = nvs_read_img();// 从NVS中读取图片路径
if(ret_nvs != ESP_OK){
ESP_LOGE("NVS","图片路径获取失败2");
return;
}
current_img_name = img_filename;
}
// 构建图片路径
snprintf(img_path, sizeof(img_path), "/spiflash/%s", current_img_name);// 格式化图片路径
ESP_LOGI("IMG", "准备显示图片: %s, 路径: %s", current_img_name, img_path);
// 检查文件是否存在
struct stat file_stat;
if(stat(img_path, &file_stat) != 0) {
ESP_LOGE("IMG", "文件不存在: %s", img_path);
return;
}
ESP_LOGI("IMG", "文件大小: %ld 字节", file_stat.st_size);
// 解码图片
esp_err_t ret = DecodeImg(img_path,&app_img_data,&outdata);// 解码图片
if(ret == ESP_OK){
ESP_LOGI("IMG", "图片解码成功,数据地址: %p, 宽度: %d, 高度: %d",
app_img_data, outdata.width, outdata.height);
// 检查解码后的数据
if(app_img_data == NULL) {
ESP_LOGE("IMG", "解码数据为空");
return;
}
// 配置图片数据
image.header.cf = LV_IMG_CF_TRUE_COLOR;
image.header.always_zero = 0;
image.header.reserved = 0;
image.header.w = outdata.width;
image.header.h = outdata.height;
image.data_size = outdata.output_len;
image.data = app_img_data;
// 获取屏幕对象
act_mainscreen = lv_scr_act();
if(act_mainscreen == NULL) {
ESP_LOGE("IMG", "获取屏幕对象失败");
return;
}
// 如果图片对象不存在,创建它
if(app_img == NULL) {
app_img = lv_img_create(act_mainscreen);
if(app_img == NULL) {
ESP_LOGE("IMG", "创建图片对象失败");
return;
}
lv_obj_center(app_img);
ESP_LOGI("IMG", "创建图片对象成功");
}
// 更新图片显示
lvgl_port_lock(0);// 锁定LVGL端口
lv_img_set_src(app_img, &image);// 设置图片源
lv_scr_load(act_mainscreen);// 加载主屏幕
lvgl_port_unlock();// 解锁LVGL端口
ESP_LOGI("IMG", "图片显示成功: %s", current_img_name);
} else {
ESP_LOGE("IMG", "图片解码失败,错误码: %d", ret);
}
}
// 完整的图片显示初始化
void app_img_display(){
ESP_LOGI("IMG", "开始显示图片");
esp_err_t ret_nvs = nvs_read_img();// 从NVS中读取图片路径
if(ret_nvs != ESP_OK){
ESP_LOGE("NVS","图片路径获取失败1");
return;
}
ESP_LOGI("IMG", "图片路径: %s", img_filename);
snprintf(img_path, sizeof(img_path), "/spiflash/%s",img_filename);// 格式化图片路径
ESP_LOGI("IMG", "完整路径: %s", img_path);
// 检查文件是否存在
struct stat file_stat;
if(stat(img_path, &file_stat) != 0){
ESP_LOGE("IMG", "文件不存在");
return;
}
ESP_LOGI("IMG", "文件大小: %ld 字节", file_stat.st_size);
esp_err_t ret = DecodeImg(img_path,&app_img_data,&outdata);// 解码图片
if(ret == ESP_OK){
ESP_LOGI("IMG", "图片解码成功,数据地址: %p", app_img_data);
// 检查解码后的数据
if(app_img_data == NULL){
ESP_LOGE("IMG", "解码数据为空");
return;
}
image.header.cf = LV_IMG_CF_TRUE_COLOR;
image.header.always_zero = 0;
image.header.reserved = 0;
image.header.w = outdata.width;
image.header.h = outdata.height;
image.data_size = outdata.output_len;
image.data = app_img_data;
ESP_LOGI("IMG", "LV_IMG_CF_RGB565 值: %d", LV_IMG_CF_RGB565);
ESP_LOGI("IMG", "设置图片数据: 宽度=%lu, 高度=%lu, 数据大小=%lu", (unsigned long)image.header.w, (unsigned long)image.header.h, (unsigned long)image.data_size);
act_mainscreen = lv_scr_act();// 获取当前主屏幕对象
if(act_mainscreen == NULL){
ESP_LOGE("IMG", "获取屏幕对象失败");
return;
}
ESP_LOGI("IMG", "获取屏幕对象成功");
app_img = lv_img_create(act_mainscreen);// 创建图片对象
if(app_img == NULL){
ESP_LOGE("IMG", "创建图片对象失败");
return;
}
ESP_LOGI("IMG", "创建图片对象成功");
lvgl_port_lock(0);// 锁定LVGL端口
ESP_LOGI("IMG", "设置图片源前");
lv_img_set_src(app_img, &image);// 设置图片源
ESP_LOGI("IMG", "设置图片源后");
lv_obj_center(app_img);// 居中显示图片
ESP_LOGI("IMG", "居中显示图片后");
lv_scr_load(act_mainscreen);// 加载主屏幕
ESP_LOGI("IMG", "加载主屏幕后");
lvgl_port_unlock();// 解锁LVGL端口
vTaskDelay(50);// 延时50ms
pwm_init();// 初始化PWM
ESP_LOGI("IMG", "图片显示完成");
} else {
ESP_LOGE("IMG", "图片解码失败,错误码: %d", ret);
}
}
// // 图片切换任务
// void img_switch_task(void *pvParameters) {
// char *image_files[] = {"default.jpg", "02.jpg"};
// int file_count = 2;
// int current_index = 0;
// while(1) {
// // 释放之前的图片数据
// if(app_img_data) {
// free(app_img_data);
// app_img_data = NULL;
// }
// // 获取当前要显示的图片文件名
// const char *current_image = image_files[current_index];
// ESP_LOGI("IMG_SWITCH", "切换到图片: %s", current_image);
// // 构建图片路径
// snprintf(img_path, sizeof(img_path), "/spiflash/%s", current_image);
// ESP_LOGI("IMG_SWITCH", "图片路径: %s", img_path);
// // 检查文件是否存在
// struct stat file_stat;
// if(stat(img_path, &file_stat) != 0) {
// ESP_LOGE("IMG_SWITCH", "文件不存在");
// vTaskDelay(pdMS_TO_TICKS(2000));
// continue;
// }
// ESP_LOGI("IMG_SWITCH", "文件大小: %ld 字节", file_stat.st_size);
// // 解码图片
// esp_err_t ret = DecodeImg(img_path, &app_img_data, &outdata);
// if(ret == ESP_OK) {
// ESP_LOGI("IMG_SWITCH", "图片解码成功,数据地址: %p", app_img_data);
// // 检查解码后的数据
// if(app_img_data == NULL) {
// ESP_LOGE("IMG_SWITCH", "解码数据为空");
// vTaskDelay(pdMS_TO_TICKS(2000));
// continue;
// }
// // 配置图片数据
// image.header.cf = LV_IMG_CF_TRUE_COLOR;
// image.header.always_zero = 0;
// image.header.reserved = 0;
// image.header.w = outdata.width;
// image.header.h = outdata.height;
// image.data_size = outdata.output_len;
// image.data = app_img_data;
// // 获取屏幕对象
// act_mainscreen = lv_scr_act();
// if(act_mainscreen == NULL) {
// ESP_LOGE("IMG_SWITCH", "获取屏幕对象失败");
// vTaskDelay(pdMS_TO_TICKS(2000));
// continue;
// }
// // 如果图片对象不存在,创建它
// if(app_img == NULL) {
// app_img = lv_img_create(act_mainscreen);
// if(app_img == NULL) {
// ESP_LOGE("IMG_SWITCH", "创建图片对象失败");
// vTaskDelay(pdMS_TO_TICKS(2000));
// continue;
// }
// lv_obj_center(app_img);
// }
// // 更新图片显示
// lvgl_port_lock(0);
// lv_img_set_src(app_img, &image);
// lv_scr_load(act_mainscreen);
// lvgl_port_unlock();
// ESP_LOGI("IMG_SWITCH", "图片显示成功");
// } else {
// ESP_LOGE("IMG_SWITCH", "图片解码失败,错误码: %d", ret);
// }
// // 切换到下一张图片
// current_index = (current_index + 1) % file_count;
// // 等待2秒
// vTaskDelay(pdMS_TO_TICKS(2000));
// }
// }
// 新的显示测试屏幕函数
void app_test_display(){
lvgl_port_lock(0);// 锁定LVGL端口
// 获取或创建屏幕对象
lv_obj_t *screen = lv_scr_act();
if(screen == NULL) {
screen = lv_obj_create(NULL);// 创建屏幕对象
if(screen == NULL) {
ESP_LOGE("TEST", "Failed to create screen object");// 创建屏幕对象失败
lvgl_port_unlock();// 解锁LVGL端口
return;
}
}
// 清空屏幕
lv_obj_clean(screen);
// 创建标签
lv_obj_t *label = lv_label_create(screen);
if(label) {
lv_label_set_text(label, "Test Screen\nLCD is working!");// 设置标签文本
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
// 创建基本样式
static lv_style_t style;// 基本样式
lv_style_init(&style);// 初始化基本样式
lv_style_set_text_font(&style, &lv_font_montserrat_14);// 设置字体
lv_style_set_text_color(&style, lv_color_hex(0xf9076a));// 设置文本颜色
lv_style_set_bg_color(&style, lv_color_hex(0x000000));// 设置背景颜色
lv_obj_add_style(label, &style, 0);// 添加样式到标签
}
lv_scr_load(screen);// 加载屏幕
lvgl_port_unlock();// 解锁LVGL端口
}
// 图片循环显示任务 - 显示spiffs中的所有图片
void img_loop_task(void *pvParameters) {
// 存储SPIFFS中的图片文件名
char *image_files[10]; // 最多支持10张图片
int file_count = 0;
int current_index = 0;
static bool backlight_initialized = false;
// 初始化背光(只执行一次)
if(!backlight_initialized) {
pwm_init();
backlight_initialized = true;// 初始化背光
ESP_LOGI("IMG_LOOP", "背光初始化完成");
}
while(1) {
// 重新扫描SPIFFS中的图片文件
ESP_LOGI("IMG_LOOP", "开始扫描SPIFFS中的图片文件");
// 打开SPIFFS目录
DIR *dir = opendir("/spiflash");
if(!dir) {
ESP_LOGE("IMG_LOOP", "无法打开SPIFFS目录");
vTaskDelay(pdMS_TO_TICKS(3000));
continue;
}
// 重置文件计数
file_count = 0;
// 遍历目录
struct dirent *entry;
while((entry = readdir(dir)) != NULL && file_count < 10) {
// 检查是否是图片文件(.jpg, .jpeg, .png等
const char *name = entry->d_name;
int len = strlen(name);
if(len > 4) {
const char *ext = name + len - 4;
if(strcasecmp(ext, ".jpg") == 0 || strcasecmp(ext, ".jpeg") == 0 ||
strcasecmp(ext, ".png") == 0 || strcasecmp(ext, ".bmp") == 0) {
// 存储图片文件名
image_files[file_count] = strdup(name);
ESP_LOGI("IMG_LOOP", "发现图片文件: %s", name);
file_count++;
}
}
}
closedir(dir);
// 检查是否找到图片
if(file_count == 0) {
ESP_LOGE("IMG_LOOP", "未找到图片文件");
vTaskDelay(pdMS_TO_TICKS(3000));
continue;
}
ESP_LOGI("IMG_LOOP", "共发现 %d 张图片,开始循环显示", file_count);
// 循环显示所有图片
for(current_index = 0; current_index < file_count; current_index++) {
const char *current_image = image_files[current_index];
ESP_LOGI("IMG_LOOP", "显示图片 %d/%d: %s", current_index + 1, file_count, current_image);
// 使用修改后的app_img_change函数显示图片
app_img_change(current_image);
// 等待3秒
vTaskDelay(pdMS_TO_TICKS(3000));
}
// 释放文件名内存
for(int i = 0; i < file_count; i++) {
free(image_files[i]);
}
// 再次扫描前短暂延时
vTaskDelay(pdMS_TO_TICKS(500));
}
}
// 图片切换任务 - 显示指定的两张图片
void img_switch_task(void *pvParameters) {
char *image_files[] = {"default.jpg", "02.jpg"};
int file_count = 2;
int current_index = 0;
while(1) {
// 使用修改后的app_img_change函数显示图片
const char *current_image = image_files[current_index];
ESP_LOGI("IMG_SWITCH", "切换到图片: %s", current_image);
app_img_change(current_image);
// 切换到下一张图片
current_index = (current_index + 1) % file_count;
// 等待2秒
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
// 初始化SPIFFS图片列表
void init_spiffs_image_list(void) {
if(image_list_initialized) {
ESP_LOGI("IMG_LIST", "图片列表已初始化,跳过");
return;
}
ESP_LOGI("IMG_LIST", "开始扫描SPIFFS中的图片文件");
// 打开SPIFFS目录
DIR *dir = opendir("/spiflash");
if(!dir) {
ESP_LOGE("IMG_LIST", "无法打开SPIFFS目录");
return;
}
// 重置文件计数
spiffs_image_count = 0;
// 遍历目录
struct dirent *entry;
while((entry = readdir(dir)) != NULL && spiffs_image_count < MAX_IMAGE_FILES) {
// 检查是否是图片文件(.jpg, .jpeg, .png等
const char *name = entry->d_name;
int len = strlen(name);
if(len > 4 && len < MAX_FILENAME_LEN) {
const char *ext = name + len - 4;
if(strcasecmp(ext, ".jpg") == 0 || strcasecmp(ext, ".jpeg") == 0 ||
strcasecmp(ext, ".png") == 0 || strcasecmp(ext, ".bmp") == 0) {
// 存储图片文件名到静态缓冲区
strncpy(spiffs_image_files[spiffs_image_count], name, MAX_FILENAME_LEN - 1);
spiffs_image_files[spiffs_image_count][MAX_FILENAME_LEN - 1] = '\0';
ESP_LOGI("IMG_LIST", "发现图片文件: %s", name);
spiffs_image_count++;
}
}
}
closedir(dir);
// 检查是否找到图片
if(spiffs_image_count == 0) {
ESP_LOGE("IMG_LIST", "未找到图片文件");
return;
}
image_list_initialized = true;
ESP_LOGI("IMG_LIST", "图片列表初始化完成,共发现 %d 张图片", spiffs_image_count);
// 查找default.jpg并设置为当前索引
for(int i = 0; i < spiffs_image_count; i++) {
if(strcmp(spiffs_image_files[i], "default.jpg") == 0) {
current_image_index = i;
ESP_LOGI("IMG_LIST", "设置默认图片索引: %d", current_image_index);
break;
}
}
}
// 获取下一张图片
const char* get_next_image(void) {
if(!image_list_initialized || spiffs_image_count == 0) {
ESP_LOGE("IMG_LIST", "图片列表未初始化或为空");
return NULL;
}
current_image_index = (current_image_index + 1) % spiffs_image_count;
ESP_LOGI("IMG_LIST", "切换到下一张图片,索引: %d/%d", current_image_index + 1, spiffs_image_count);
return spiffs_image_files[current_image_index];
}
// 获取上一张图片
const char* get_prev_image(void) {
if(!image_list_initialized || spiffs_image_count == 0) {
ESP_LOGE("IMG_LIST", "图片列表未初始化或为空");
return NULL;
}
current_image_index = (current_image_index - 1 + spiffs_image_count) % spiffs_image_count;
ESP_LOGI("IMG_LIST", "切换到上一张图片,索引: %d/%d", current_image_index + 1, spiffs_image_count);
return spiffs_image_files[current_image_index];
}
// 重置图片列表
void free_spiffs_image_list(void) {
if(!image_list_initialized) {
return;
}
spiffs_image_count = 0;
current_image_index = 0;
image_list_initialized = false;
ESP_LOGI("IMG_LIST", "图片列表已重置");
}
// 获取当前图片文件名
const char* get_current_image(void) {
if(!image_list_initialized || spiffs_image_count == 0) {
ESP_LOGE("IMG_LIST", "图片列表未初始化或为空");
return NULL;
}
return spiffs_image_files[current_image_index];
}
// 删除当前图片并从列表中移除
bool delete_current_image(void) {
if(!image_list_initialized || spiffs_image_count == 0) {
ESP_LOGE("IMG_DEL", "图片列表未初始化或为空");
return false;
}
const char *current_img = spiffs_image_files[current_image_index];
// 构建完整路径
char full_path[64];
snprintf(full_path, sizeof(full_path), "/spiflash/%s", current_img);
ESP_LOGI("IMG_DEL", "准备删除图片: %s", full_path);
// 从SPIFFS文件系统中删除文件
if(unlink(full_path) != 0) {
ESP_LOGE("IMG_DEL", "删除文件失败: %s", full_path);
return false;
}
ESP_LOGI("IMG_DEL", "文件删除成功: %s", current_img);
// 从列表中移除该图片
for(int i = current_image_index; i < spiffs_image_count - 1; i++) {
strncpy(spiffs_image_files[i], spiffs_image_files[i + 1], MAX_FILENAME_LEN);
}
// 更新计数
spiffs_image_count--;
// 如果列表为空,重置状态
if(spiffs_image_count == 0) {
ESP_LOGI("IMG_DEL", "所有图片已删除");
image_list_initialized = false;
current_image_index = 0;
return true;
}
// 调整当前索引(如果删除的是最后一张,回到第一张)
if(current_image_index >= spiffs_image_count) {
current_image_index = 0;
}
ESP_LOGI("IMG_DEL", "图片列表已更新,剩余 %d 张图片,当前索引: %d",
spiffs_image_count, current_image_index);
return true;
}
// 更新ui_ImgBle控件的图片
void update_ui_ImgBle(const char *img_name) {
if(!img_name) {
ESP_LOGE("IMG_UI", "图片名为空");
return;
}
if(!ui_ImgBle) {
ESP_LOGE("IMG_UI", "ui_ImgBle控件不存在");
return;
}
static uint8_t *ui_img_data = NULL;
static lv_img_dsc_t ui_image;
// 释放之前的图片数据
if(ui_img_data) {
free(ui_img_data);
ui_img_data = NULL;
ESP_LOGI("IMG_UI", "释放之前的图片数据");
}
// 构建图片路径
snprintf(img_path, sizeof(img_path), "/spiflash/%s", img_name);
ESP_LOGI("IMG_UI", "准备显示图片: %s, 路径: %s", img_name, img_path);
// 检查文件是否存在
struct stat file_stat;
if(stat(img_path, &file_stat) != 0) {
ESP_LOGE("IMG_UI", "文件不存在: %s", img_path);
return;
}
ESP_LOGI("IMG_UI", "文件大小: %ld 字节", file_stat.st_size);
// 解码图片
esp_jpeg_image_output_t ui_outdata;
esp_err_t ret = DecodeImg(img_path, &ui_img_data, &ui_outdata);
if(ret == ESP_OK) {
ESP_LOGI("IMG_UI", "图片解码成功,宽度: %d, 高度: %d", ui_outdata.width, ui_outdata.height);
// 检查解码后的数据
if(ui_img_data == NULL) {
ESP_LOGE("IMG_UI", "解码数据为空");
return;
}
// 配置图片数据
ui_image.header.cf = LV_IMG_CF_TRUE_COLOR;
ui_image.header.always_zero = 0;
ui_image.header.reserved = 0;
ui_image.header.w = ui_outdata.width;
ui_image.header.h = ui_outdata.height;
ui_image.data_size = ui_outdata.output_len;
ui_image.data = ui_img_data;
// 更新ui_ImgBle控件的图片
lvgl_port_lock(0);
lv_img_set_src(ui_ImgBle, &ui_image);
lvgl_port_unlock();
ESP_LOGI("IMG_UI", "ui_ImgBle图片更新成功: %s", img_name);
} else {
ESP_LOGE("IMG_UI", "图片解码失败,错误码: %d", ret);
}
}
// // 原本显示测试屏幕 代码
// void app_test_display(){
// lvgl_port_lock(0);// 锁定LVGL端口
// act_testscreen = lv_scr_act();// 获取当前测试屏幕对象
// // 创建列表样式
// static lv_style_t list_style;
// lv_style_init(&list_style);
// lv_style_set_bg_color(&list_style, lv_color_hex(0x1E1E1E));
// lv_style_set_border_width(&list_style, 0);
// lv_style_set_radius(&list_style, 0);
// lv_style_set_text_align(&list_style, LV_TEXT_ALIGN_CENTER);
// // 创建列表项样式
// static lv_style_t list_item_style;
// lv_style_init(&list_item_style);
// lv_style_set_bg_color(&list_item_style, lv_color_hex(0x2E2E2E));
// lv_style_set_bg_opa(&list_item_style, LV_OPA_100);
// lv_style_set_text_color(&list_item_style, lv_color_hex(0xFFFFFF));
// lv_style_set_pad_all(&list_item_style, 10);
// lv_style_set_min_height(&list_item_style, 80); // 设置最小高度为60px
// // 创建选中项样式
// static lv_style_t list_item_selected_style;
// lv_style_init(&list_item_selected_style);
// lv_style_set_bg_color(&list_item_selected_style, lv_color_hex(0x4A90E2));
// lv_style_set_text_color(&list_item_selected_style, lv_color_hex(0xFFFFFF));
// lv_obj_t* list = lv_list_create(act_testscreen);
// lv_obj_add_style(list, &list_style, 0);
// lv_obj_set_size(list, lv_pct(100), lv_pct(100));
// lv_obj_t* list_item1 = lv_list_add_btn(list, LV_SYMBOL_FILE, "FLASH TEST");
// lv_obj_t* list_item2 = lv_list_add_btn(list, LV_SYMBOL_FILE, "WIFI SCAN START TEST");
// lv_obj_t* list_item3 = lv_list_add_btn(list, LV_SYMBOL_FILE, "WIFI SCAN LIST TEST");
// lv_obj_t* list_item5 = lv_list_add_btn(list, LV_SYMBOL_FILE, "WIFI CONNECT TEST");
// lv_obj_t* list_item6 = lv_list_add_btn(list, LV_SYMBOL_FILE, "WIFI DISCONNECT TEST");
// lv_obj_t* list_item4 = lv_list_add_btn(list, LV_SYMBOL_FILE, "FREE HEAP");
// lv_obj_t* list_item7 = lv_list_add_btn(list, LV_SYMBOL_FILE, "TEMP TEST");
// lv_obj_add_event_cb(list_item1, fs_test, LV_EVENT_CLICKED, NULL);
// lv_obj_add_event_cb(list_item2, wifi_scan_start_test, LV_EVENT_CLICKED, NULL);
// lv_obj_add_event_cb(list_item3, wifi_scan_list_test, LV_EVENT_CLICKED, NULL);
// lv_obj_add_event_cb(list_item4, free_heap_test, LV_EVENT_CLICKED, NULL);
// lv_obj_add_event_cb(list_item5, wifi_connect_test, LV_EVENT_CLICKED, NULL);
// lv_obj_add_event_cb(list_item6, wifi_disconnect_test, LV_EVENT_CLICKED, NULL);
// lv_obj_add_event_cb(list_item7, temp_test, LV_EVENT_CLICKED, NULL);
// lv_obj_t *parent = lv_obj_create(act_mainscreen);
// lv_obj_set_size(parent, 150, 150);
// lv_obj_center(parent); // 居中显示
// static lv_style_t style_bg_black;
// lv_style_init(&style_bg_black);
// lv_style_set_bg_color(&style_bg_black, lv_color_hex(0x000000));
// lv_obj_add_style(act_mainscreen, &style_bg_black, 0);
// static lv_style_t style_img;
// lv_style_init(&style_img);
// lv_style_set_radius(&style_img,75);
// lv_style_set_clip_corner(&style_img, true);
// lv_style_set_border_width(&style_img,75);
// lv_obj_add_style(parent,&style_img,0);
// // lv_obj_t *img = lv_img_create(act_mainscreen);
// // lv_img_set_src(img,"C:spiflash/face_1759919044875.jpg");
// lv_scr_load(act_testscreen);
// lvgl_port_unlock();
// vTaskDelay(50);
// pwm_init();
// }