toy-hardware/main/boards/jiuchuang-s3/jiuchuang_dev_board.cc

1853 lines
68 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 "wifi_board.h"
#include "audio_codecs/es8311_audio_codec.h"
#include "display/lcd_display.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "i2c_device.h"
#include "iot/thing_manager.h"
#include "../../components/gbk_encoding/include/gbk_encoding.h" // 使用完整路径
#include <esp_log.h>
#include <esp_lcd_panel_vendor.h>
#include <driver/i2c_master.h>
#include <driver/spi_common.h>
#include <wifi_station.h>
#include "led/single_led.h"
#include "assets/lang_config.h"
#include "esp_lcd_panel_gc9301.h"
#include "power_save_timer.h"
#include "power_manager.h"
#include <driver/rtc_io.h>
#include <esp_sleep.h>
#include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h>
#include "esp_vfs_fat.h"
#include "sdmmc_cmd.h"
#include "driver/sdmmc_host.h"
#include <dirent.h>
#include <nvs_flash.h> // 添加NVS头文件
#include <nvs.h> // 添加NVS头文件
#include "audio_player.h" // 音频播放器
#define TAG "JiuchuangDevBoard"
#define __USER_GPIO_PWRDOWN__
LV_FONT_DECLARE(font_puhui_20_4);
LV_FONT_DECLARE(font_awesome_20_4);
// 前向声明
class JiuchuangDevBoard;
// 音频播放器必须的回调函数
static esp_err_t audio_mute_callback(AUDIO_PLAYER_MUTE_SETTING setting);
static esp_err_t audio_clk_set_callback(uint32_t rate, uint32_t bits_cfg, i2s_slot_mode_t ch);
static esp_err_t audio_write_callback(void *audio_buffer, size_t len, size_t *bytes_written, uint32_t timeout_ms);
static void audio_event_callback(audio_player_cb_ctx_t *ctx);
static esp_err_t restore_device_status();
static void sd_card_detect_task(void *arg);
static bool is_supported_audio_file(const char *filename)
{
int len = strlen(filename);
if (len <= 4)
return false;
const char *ext = filename + len - 4;
return (strcasecmp(ext, ".mp3") == 0 ||
strcasecmp(ext, ".wav") == 0);
}
// 添加一个辅助函数用于将二进制数据转换成十六进制字符串,方便调试
static std::string bytes_to_hex(const uint8_t* data, size_t len) {
std::string result;
result.reserve(len * 3);
for (size_t i = 0; i < len; i++) {
char buf[4];
snprintf(buf, sizeof(buf), "%02X ", data[i]);
result += buf;
}
return result;
}
// 添加一个简单的GBK到UTF-8转换映射表覆盖常用的中文字符
struct GbkUtf8Mapping {
uint16_t gbk_code;
const char *utf8_str;
};
// 常用的中文字符GBK到UTF-8的映射扩充更多常用字符
static const GbkUtf8Mapping gbk_utf8_map[] = {
// 您的音乐文件名中出现的字符
{0xB6AA, ""}, {0xCAD6, ""}, {0xBEEE, ""}, // 丢手绢
{0xD0A1, ""}, {0xCFBC, ""}, // 小霞
{0xD7F9, ""}, {0xCEBB, ""}, // 座位
{0xB8E6, ""}, {0xB0D7, ""}, {0xC6F8, ""}, {0xC7F2, ""}, // 告白气球
{0xB0AE, ""}, {0xB4ED, ""}, // 爱错
// 扩充更多常用汉字
// 数字相关
{0xD2BB, ""}, {0xB6FE, ""}, {0xC8FD, ""}, {0xCBC4, ""}, {0xCEE5, ""},
{0xC1F9, ""}, {0xC6DF, ""}, {0xB0CB, ""}, {0xBEC5, ""}, {0xCAE5, ""},
{0xB0D9, ""}, {0xC7A7, ""}, {0xCDF2, ""}, {0xD2DA, "亿"},
// 常用形容词
{0xBAC3, ""}, {0xBDD6, ""}, {0xC2A5, ""}, {0xD0C2, ""}, {0xC0CF, ""},
{0xD0A1, ""}, {0xB4F3, ""}, {0xB8DF, ""}, {0xB5CD, ""}, {0xD1D5, ""},
{0xC9AB, ""}, {0xBADA, ""}, {0xB3C1, ""}, {0xCFE0, ""}, {0xB5E7, ""},
// 常用名词
{0xC4EA, ""}, {0xD4C2, ""}, {0xC8D5, ""}, {0xCEC4, ""}, {0xC6DA, ""},
{0xCAB1, ""}, {0xBFE4, ""}, {0xB7D6, ""}, {0xC3EB, ""}, {0xC4DA, ""},
{0xBEA9, ""}, {0xC9CF, ""}, {0xBAA3, ""}, {0xB9E3, "广"}, {0xD6DD, ""},
{0xC7ED, ""}, {0xB5BA, ""}, {0xCED2, ""}, {0xC4E3, ""}, {0xCBFB, ""},
// 常用动词
{0xBFB4, ""}, {0xCFB7, ""}, {0xCFDF, ""}, {0xD7F7, ""}, {0xCEC2, ""},
{0xCBB5, ""}, {0xCFD6, ""}, {0xCFC2, ""}, {0xC9CF, ""}, {0xD7F8, ""},
{0xD3D2, ""}, {0xC7B0, ""}, {0xBBA7, ""}, {0xCDE2, ""}, {0xCBF7, ""},
// 音乐相关
{0xD2F4, ""}, {0xC0D6, ""}, {0xB8E8, ""}, {0xB3CC, ""}, {0xB5C6, ""},
{0xB9E2, ""}, {0xCAD3, ""}, {0xC6C1, ""}, {0xBDA1, ""}, {0xC7FA, ""},
{0xC4DA, ""}, {0xCDA8, ""}, {0xBCA3, ""}, {0xB7D2, ""}, {0xBBAA, ""},
// 音乐人名
{0xCEB2, ""}, {0xD6A3, ""}, {0xC9A1, ""}, {0xCEB0, ""}, {0xD6EC, ""},
{0xCFC9, ""}, {0xB8B7, ""}, {0xD2AF, ""}, {0xC1D5, ""}, {0xD4AA, ""},
{0xBAA3, ""}, {0xC0BC, ""}, {0xDEB9, ""}, {0xB4EF, ""},
// 常用标点符号
{0xA3BA, ""}, {0xA3BB, ""}, {0xA1A4, ""}, {0xA3AC, ""}, {0xA1A2, ""},
{0xA3BF, ""}, {0xA3A1, ""}, {0xA1B0, ""}, {0xA1B1, ""}, {0xA1F1, "·"},
// 更多可能的中文字符映射可以根据需要添加
};
// 更优化的字符编码转换函数连续检测和转换GBK编码
static std::string gbk_to_utf8(const char* gbk_str) {
std::string utf8_result;
const unsigned char* p = (const unsigned char*)gbk_str;
while (*p) {
if (*p < 0x80) {
// ASCII字符直接复制
utf8_result += *p;
p++;
} else if (*p >= 0x81 && *p <= 0xFE && *(p+1) >= 0x40 && *(p+1) <= 0xFE) {
// 可能是GBK编码的中文字符
uint16_t gbk_code = (*p << 8) | *(p + 1);
bool found = false;
// 查找映射表
for (const auto& mapping : gbk_utf8_map) {
if (mapping.gbk_code == gbk_code) {
utf8_result += mapping.utf8_str;
found = true;
break;
}
}
if (!found) {
// 如果找不到映射,使用占位符并记录未识别的编码
ESP_LOGW(TAG, "未识别的GBK编码: 0x%04X", gbk_code);
utf8_result += "?";
}
p += 2; // GBK编码是双字节
} else {
// 不是有效的GBK编码跳过
ESP_LOGW(TAG, "无效的GBK编码字节: 0x%02X", *p);
p++;
}
}
return utf8_result;
}
// 增强的自定义映射函数,先尝试使用硬编码映射,再尝试通用转换
static std::string map_filename_by_hex(const char* filename) {
// 创建一个十六进制字符串用于比较
std::string hex_str = bytes_to_hex((const uint8_t*)filename, strlen(filename));
// 移除十六进制字符串中的空格
std::string clean_hex;
for (char c : hex_str) {
if (c != ' ') {
clean_hex += c;
}
}
// 特定文件的硬编码映射
if (clean_hex.find("B6AACAD6BEEE2E4D5033") != std::string::npos) {
return "丢手绢.MP3";
} else if (clean_hex.find("D0A1CFBC2E4D5033") != std::string::npos) {
return "小霞.MP3";
} else if (clean_hex.find("D7F9CEBB2E4D5033") != std::string::npos) {
return "座位.MP3";
} else if (clean_hex.find("B8E6B0D7C6F8C7F22E4D5033") != std::string::npos) {
return "告白气球.MP3";
} else if (clean_hex.find("B0AEB4ED2E4D5033") != std::string::npos) {
return "爱错.MP3";
}
// 添加日志中显示的特定文件名映射
else if (clean_hex.find("B1F0C8C3B0AE7E312E4D5033") != std::string::npos) {
return "别让爱~1.MP3";
} else if (clean_hex.find("D7DFD4DAC0E47E312E4D5033") != std::string::npos) {
return "走在冷~1.MP3";
} else if (clean_hex.find("B4BAB7E7D0ED7E312E4D5033") != std::string::npos) {
return "春风许~1.MP3";
}
// 添加新发现的特定文件名映射
else if (clean_hex.find("D0A6CBC0CED2C1CB2E4D5033") != std::string::npos) {
return "笑死我了.MP3";
} else if (clean_hex.find("C4E3CAC7CBAD2E4D5033") != std::string::npos) {
return "你是谁.MP3";
} else if (clean_hex.find("D4F5C3B4CBB52E4D5033") != std::string::npos) {
return "怎么说.MP3";
}
// 添加最新发现的文件名映射
else if (clean_hex.find("CDA6BAC3B5C4B0A12E4D5033") != std::string::npos) {
return "哈哈好的啊.MP3";
} else if (clean_hex.find("BECDD5E2D1F9B0C92E4D5033") != std::string::npos) {
return "就这样吧.MP3";
} else if (clean_hex.find("D7EEBDFCD4F57E312E4D5033") != std::string::npos) {
return "最近怎~1.MP3";
}
// 记录未硬编码映射的文件的十六进制表示,便于后续添加
ESP_LOGI(TAG, "未硬编码的文件十六进制表示: %s", clean_hex.c_str());
// 如果找不到硬编码映射,尝试通用转换,但已知这部分有问题
return gbk_to_utf8(filename);
}
// 添加辅助函数用于显示文件名的原始字节和显示形式
static void debug_filename(const char* filename) {
size_t len = strlen(filename);
ESP_LOGI(TAG, "文件名: [%s], 长度: %d", filename, len);
ESP_LOGI(TAG, "十六进制: %s", bytes_to_hex((const uint8_t*)filename, len).c_str());
}
// 专门用于处理SD卡文件名的函数包含更详细的调试信息
static std::string process_sd_filename(const char* original_filename) {
if (!original_filename || strlen(original_filename) == 0) {
return "";
}
// 打印原始文件名
ESP_LOGI(TAG, "处理SD卡文件名: [%s]", original_filename);
// 获取十六进制表示
std::string hex_string = bytes_to_hex((const uint8_t*)original_filename, strlen(original_filename));
ESP_LOGI(TAG, "文件名十六进制: %s", hex_string.c_str());
// 检查文件名是否已经是UTF-8编码
bool is_utf8 = true;
const uint8_t* str = (const uint8_t*)original_filename;
size_t len = strlen(original_filename);
for (size_t i = 0; i < len; i++) {
if (str[i] < 0x80) {
// ASCII字符继续
continue;
} else if ((str[i] & 0xE0) == 0xC0) {
// 2字节UTF-8序列
if (i + 1 >= len || (str[i+1] & 0xC0) != 0x80) {
is_utf8 = false;
break;
}
i += 1;
} else if ((str[i] & 0xF0) == 0xE0) {
// 3字节UTF-8序列
if (i + 2 >= len || (str[i+1] & 0xC0) != 0x80 || (str[i+2] & 0xC0) != 0x80) {
is_utf8 = false;
break;
}
i += 2;
} else if ((str[i] & 0xF8) == 0xF0) {
// 4字节UTF-8序列
if (i + 3 >= len || (str[i+1] & 0xC0) != 0x80 || (str[i+2] & 0xC0) != 0x80 || (str[i+3] & 0xC0) != 0x80) {
is_utf8 = false;
break;
}
i += 3;
} else {
// 不是有效的UTF-8序列
is_utf8 = false;
break;
}
}
if (is_utf8) {
ESP_LOGI(TAG, "文件名已经是UTF-8编码无需转换: [%s]", original_filename);
return original_filename;
}
// 如果不是UTF-8则尝试从GBK转换
ESP_LOGI(TAG, "文件名不是UTF-8编码尝试从GBK转换");
// 直接使用组件提供的GBK转换函数
char* output_buffer = (char*)malloc(strlen(original_filename) * 4 + 1);
if (!output_buffer) {
ESP_LOGE(TAG, "内存分配失败");
return original_filename;
}
char* temp_ptr = output_buffer;
int out_len = gbk_to_utf8((void**)&temp_ptr, (void*)original_filename, strlen(original_filename));
if (out_len > 0) {
std::string result = std::string(output_buffer);
free(output_buffer);
if (strcmp(result.c_str(), original_filename) != 0) {
ESP_LOGI(TAG, "文件名转换结果(GBK库): [%s] -> [%s]", original_filename, result.c_str());
return result;
}
} else {
free(output_buffer);
}
// 如果组件转换失败,尝试硬编码映射(作为备选)
std::string mapped_name = map_filename_by_hex(original_filename);
// 如果转换后的结果不同于原始文件名,显示转换结果
if (strcmp(mapped_name.c_str(), original_filename) != 0) {
ESP_LOGI(TAG, "文件名转换结果(硬编码): [%s] -> [%s]", original_filename, mapped_name.c_str());
} else {
ESP_LOGI(TAG, "文件名未发生变化: [%s]", original_filename);
}
return mapped_name;
}
class JiuchuangDevBoard : public WifiBoard
{
private:
// 声明为友元函数,允许访问私有成员
friend void sd_card_detect_task(void *arg);
friend void audio_event_callback(audio_player_cb_ctx_t *ctx);
i2c_master_bus_handle_t codec_i2c_bus_;
Button boot_button_;
Button pwr_button_;
Button wifi_button;
Button cmd_button;
LcdDisplay *display_;
PowerSaveTimer *power_save_timer_;
PowerManager *power_manager_;
esp_lcd_panel_io_handle_t panel_io = NULL;
esp_lcd_panel_handle_t panel = NULL;
sdmmc_card_t *card = NULL;
sdmmc_host_t host;
sdmmc_slot_config_t slot_config;
std::vector<std::string> audio_files;
bool card_mounted = false;
bool audio_player_initialized = false;
int current_volume = 80; // 添加当前音量存储变量初始值设为80
bool is_playing = false; // 当前是否处于音乐播放状态
TaskHandle_t sd_card_detect_task_handle = NULL;
// 音量映射函数:将内部音量(0-80)映射为显示音量(0-100%)
int MapVolumeForDisplay(int internal_volume) {
// 确保输入在有效范围内
if (internal_volume < 0) internal_volume = 0;
if (internal_volume > 80) internal_volume = 80;
// 将0-80映射到0-100
// 公式: 显示音量 = (内部音量 / 80) * 100
return (internal_volume * 100) / 80;
}
public:
bool is_switching = false; // 防止快速连续切换音乐 - 移至公有部分供回调访问
// 保存音量到NVS
void SaveVolumeToNVS(int volume) {
nvs_handle_t nvs_handle;
esp_err_t err = nvs_open("storage", NVS_READWRITE, &nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error opening NVS handle: %s", esp_err_to_name(err));
return;
}
err = nvs_set_i32(nvs_handle, "volume", volume);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error writing volume to NVS: %s", esp_err_to_name(err));
}
err = nvs_commit(nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error committing NVS: %s", esp_err_to_name(err));
}
nvs_close(nvs_handle);
}
// 从NVS获取音量
int LoadVolumeFromNVS() {
nvs_handle_t nvs_handle;
esp_err_t err = nvs_open("storage", NVS_READONLY, &nvs_handle);
if (err != ESP_OK) {
ESP_LOGI(TAG, "NVS不存在使用默认音量");
return 60; // 默认音量改为60原来是80的75%
}
int32_t volume = 60; // 默认音量改为60
err = nvs_get_i32(nvs_handle, "volume", &volume);
if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) {
ESP_LOGE(TAG, "Error reading volume from NVS: %s", esp_err_to_name(err));
}
nvs_close(nvs_handle);
// 确保音量在有效范围内
if (volume < 0) volume = 0;
if (volume > 80) volume = 80; // 最大音量限制为80
return volume;
}
// 公共初始化方法
public:
static JiuchuangDevBoard *audio_board_instance;
std::string current_file; // 当前播放的文件路径 - 移至公有部分以便audio_event_callback访问
// 获取所有音频文件列表
std::vector<std::string> GetAudioFiles(const char *mount_point)
{
std::vector<std::string> files;
DIR *dir = opendir(mount_point);
if (!dir)
{
ESP_LOGE(TAG, "无法打开目录: %s", mount_point);
return files;
}
ESP_LOGI(TAG, "扫描目录 %s 中的音频文件", mount_point);
struct dirent *entry;
while ((entry = readdir(dir)) != NULL)
{
// 使用d_name可能会有编码问题尝试打印原始字节值
const char *filename = entry->d_name;
if (filename[0] == 0) continue; // 跳过空文件名
// 使用辅助函数显示文件名
debug_filename(filename);
// 尝试使用映射函数转换文件名
std::string mapped_name = process_sd_filename(filename);
if (mapped_name != filename) {
ESP_LOGI(TAG, "文件名映射: [%s] -> [%s]", filename, mapped_name.c_str());
}
if (is_supported_audio_file(filename))
{
char filepath[512];
snprintf(filepath, sizeof(filepath), "%s/%s", mount_point, filename);
files.push_back(filepath);
ESP_LOGI(TAG, "添加音频文件: %s", filepath);
}
}
closedir(dir);
// 按名称排序
std::sort(files.begin(), files.end());
// 打印找到的所有文件
ESP_LOGI(TAG, "找到 %d 个音频文件:", files.size());
for (size_t i = 0; i < files.size(); i++) {
size_t pos = files[i].find_last_of("/\\");
std::string filename = (pos != std::string::npos) ? files[i].substr(pos + 1) : files[i];
std::string mapped_name = process_sd_filename(filename.c_str());
ESP_LOGI(TAG, "[%d] %s -> %s", i, files[i].c_str(), mapped_name.c_str());
}
return files;
}
// 播放指定文件
bool PlayFile(const std::string &filepath)
{
// 首先检查是否处于播放模式
if (!IsPlaying()) {
ESP_LOGI(TAG, "当前不处于音乐播放模式,不开始播放");
return false;
}
ESP_LOGI(TAG, "尝试播放: %s", filepath.c_str());
FILE *file = fopen(filepath.c_str(), "rb");
if (file)
{
// 确保应用状态正确
auto &app = Application::GetInstance();
auto current_state = app.GetDeviceState();
// 检查当前状态,如果不是音乐播放状态,更新状态
if (current_state != DeviceState::kDeviceStateMusicPlaying) {
ESP_LOGI(TAG, "设置应用状态为音乐播放");
app.SetDeviceState(DeviceState::kDeviceStateMusicPlaying);
// 确保已禁用语音功能
app.DisableVoiceFeatures();
}
// 记录当前文件
current_file = filepath;
// 更新用户界面
auto display = GetDisplay();
if (display) {
// 提取文件名(不含路径)
size_t pos = filepath.find_last_of("/\\");
std::string filename = (pos != std::string::npos) ? filepath.substr(pos + 1) : filepath;
// 打印文件名的十六进制值,用于调试
std::string hex_str = "";
for (int i = 0; i < filename.length(); i++) {
char buf[8];
snprintf(buf, sizeof(buf), "%02X ", (unsigned char)filename[i]);
hex_str += buf;
}
ESP_LOGI(TAG, "显示文件名: [%s], 十六进制: %s", filename.c_str(), hex_str.c_str());
// 使用映射函数转换文件名
std::string displayName = process_sd_filename(filename.c_str());
ESP_LOGI(TAG, "转换后的文件名: [%s]", displayName.c_str());
std::string status_text = "正在播放: " + displayName;
display->SetStatus(status_text.c_str());
display->SetEmotion("happy");
// 在聊天消息中也显示当前播放的文件名
display->SetChatMessage("system", status_text.c_str());
}
// 开始播放
ESP_LOGI(TAG, "开始播放: %s", filepath.c_str());
audio_player_play(file);
return true;
}
ESP_LOGE(TAG, "无法打开文件: %s", filepath.c_str());
return false;
}
// 播放下一首歌
bool PlayNextSong()
{
const char mount_point[] = "/sdcard";
ESP_LOGI(TAG, "=== 开始播放下一首歌曲 ===");
ESP_LOGI(TAG, "当前文件: %s", current_file.c_str());
// 检查SD卡状态
if (!card_mounted) {
ESP_LOGE(TAG, "SD卡未挂载无法播放下一首");
return false;
}
// 获取音频文件列表
audio_files = GetAudioFiles(mount_point);
if (audio_files.empty())
{
ESP_LOGE(TAG, "未找到音频文件");
return false;
}
ESP_LOGI(TAG, "找到 %d 个音频文件", audio_files.size());
// 找到当前文件的下一个文件
auto it = std::find(audio_files.begin(), audio_files.end(), current_file);
std::string next_file;
int current_index = -1;
int next_index = 0;
if (it == audio_files.end())
{
// 当前文件未找到,播放第一个
next_file = audio_files.front();
next_index = 0;
ESP_LOGW(TAG, "当前文件未找到,播放第一个文件 (索引: %d)", next_index);
}
else
{
// 找到当前文件的索引
current_index = std::distance(audio_files.begin(), it);
ESP_LOGI(TAG, "当前文件索引: %d", current_index);
// 计算下一个文件的索引
next_index = (current_index + 1) % audio_files.size();
next_file = audio_files[next_index];
if (next_index == 0) {
ESP_LOGI(TAG, "已到最后一首,循环到第一首 (索引: %d)", next_index);
} else {
ESP_LOGI(TAG, "播放下一首 (索引: %d)", next_index);
}
}
ESP_LOGI(TAG, "下一首文件: %s", next_file.c_str());
// 检查文件是否存在
FILE* test_file = fopen(next_file.c_str(), "rb");
if (!test_file) {
ESP_LOGE(TAG, "下一首文件不存在: %s", next_file.c_str());
return false;
}
fclose(test_file);
// 播放文件
ESP_LOGI(TAG, "开始播放下一首文件");
bool success = PlayFile(next_file);
if (!success) {
ESP_LOGE(TAG, "播放下一首失败: %s", next_file.c_str());
} else {
ESP_LOGI(TAG, "成功开始播放下一首: %s", next_file.c_str());
}
ESP_LOGI(TAG, "=== 播放下一首歌曲完成 ===");
return success;
}
// 播放上一首歌
bool PlayPreviousSong()
{
const char mount_point[] = "/sdcard";
ESP_LOGI(TAG, "=== 开始播放上一首歌曲 ===");
ESP_LOGI(TAG, "当前文件: %s", current_file.c_str());
// 检查SD卡状态
if (!card_mounted) {
ESP_LOGE(TAG, "SD卡未挂载无法播放上一首");
return false;
}
// 获取音频文件列表
audio_files = GetAudioFiles(mount_point);
if (audio_files.empty())
{
ESP_LOGE(TAG, "未找到音频文件");
return false;
}
ESP_LOGI(TAG, "找到 %d 个音频文件", audio_files.size());
// 找到当前文件的前一个文件
auto it = std::find(audio_files.begin(), audio_files.end(), current_file);
std::string prev_file;
int current_index = -1;
int prev_index = 0;
if (it == audio_files.end())
{
// 当前文件未找到,播放最后一个
prev_index = audio_files.size() - 1;
prev_file = audio_files.back();
ESP_LOGW(TAG, "当前文件未找到,播放最后一个文件 (索引: %d)", prev_index);
}
else
{
// 找到当前文件的索引
current_index = std::distance(audio_files.begin(), it);
ESP_LOGI(TAG, "当前文件索引: %d", current_index);
// 计算上一个文件的索引
prev_index = (current_index - 1 + audio_files.size()) % audio_files.size();
prev_file = audio_files[prev_index];
if (current_index == 0) {
ESP_LOGI(TAG, "已到第一首,循环到最后一首 (索引: %d)", prev_index);
} else {
ESP_LOGI(TAG, "播放上一首 (索引: %d)", prev_index);
}
}
ESP_LOGI(TAG, "上一首文件: %s", prev_file.c_str());
// 检查文件是否存在
FILE* test_file = fopen(prev_file.c_str(), "rb");
if (!test_file) {
ESP_LOGE(TAG, "上一首文件不存在: %s", prev_file.c_str());
return false;
}
fclose(test_file);
// 播放文件
ESP_LOGI(TAG, "开始播放上一首文件");
bool success = PlayFile(prev_file);
if (!success) {
ESP_LOGE(TAG, "播放上一首失败: %s", prev_file.c_str());
} else {
ESP_LOGI(TAG, "成功开始播放上一首: %s", prev_file.c_str());
}
ESP_LOGI(TAG, "=== 播放上一首歌曲完成 ===");
return success;
}
// 安全的切换到上一首
bool SwitchToPreviousSong() {
ESP_LOGI(TAG, "*** 开始切换到上一首 ***");
ESP_LOGI(TAG, "当前播放状态: %s", is_playing ? "播放中" : "未播放");
ESP_LOGI(TAG, "当前切换状态: %s", is_switching ? "切换中" : "空闲");
if (!is_playing) {
ESP_LOGW(TAG, "当前未在播放音乐,无法切换");
return false;
}
// 防抖:检查是否正在切换
if (is_switching) {
ESP_LOGI(TAG, "正在切换中,忽略操作");
return false;
}
is_switching = true;
ESP_LOGI(TAG, "设置切换状态为true");
// 显示切换状态
auto display = GetDisplay();
if (display) {
display->ShowNotification("切换到上一首...");
ESP_LOGI(TAG, "显示切换通知");
}
// 停止当前播放
ESP_LOGI(TAG, "停止当前播放,准备切换到上一首");
audio_player_state_t current_state = audio_player_get_state();
ESP_LOGI(TAG, "当前播放器状态: %d", current_state);
audio_player_stop();
ESP_LOGI(TAG, "已调用audio_player_stop()");
// 等待播放器真正停止
int timeout = 50; // 5秒超时
ESP_LOGI(TAG, "等待播放器停止,超时时间: %d * 100ms", timeout);
while (audio_player_get_state() != AUDIO_PLAYER_STATE_IDLE && timeout > 0) {
current_state = audio_player_get_state();
ESP_LOGD(TAG, "等待停止中,当前状态: %d, 剩余超时: %d", current_state, timeout);
vTaskDelay(pdMS_TO_TICKS(100));
timeout--;
}
current_state = audio_player_get_state();
ESP_LOGI(TAG, "等待结束,最终状态: %d, 剩余超时: %d", current_state, timeout);
if (timeout <= 0) {
ESP_LOGE(TAG, "停止播放超时,当前状态: %d", current_state);
is_switching = false;
if (display) {
display->ShowNotification("切换超时");
}
return false;
}
ESP_LOGI(TAG, "播放器已停止,开始播放上一首");
// 播放上一首
bool success = PlayPreviousSong();
// 如果播放成功,等待一小段时间确保播放稳定启动
if (success) {
ESP_LOGI(TAG, "等待播放稳定启动...");
vTaskDelay(pdMS_TO_TICKS(200)); // 等待200ms
}
is_switching = false;
ESP_LOGI(TAG, "重置切换状态为false");
if (!success) {
ESP_LOGE(TAG, "播放上一首失败");
if (display) {
display->ShowNotification("切换失败");
}
} else {
ESP_LOGI(TAG, "成功切换到上一首");
if (display) {
display->ShowNotification("已切换到上一首");
}
}
ESP_LOGI(TAG, "*** 切换到上一首完成,结果: %s ***", success ? "成功" : "失败");
return success;
}
// 安全的切换到下一首
bool SwitchToNextSong() {
ESP_LOGI(TAG, "*** 开始切换到下一首 ***");
ESP_LOGI(TAG, "当前播放状态: %s", is_playing ? "播放中" : "未播放");
ESP_LOGI(TAG, "当前切换状态: %s", is_switching ? "切换中" : "空闲");
if (!is_playing) {
ESP_LOGW(TAG, "当前未在播放音乐,无法切换");
return false;
}
// 防抖:检查是否正在切换
if (is_switching) {
ESP_LOGI(TAG, "正在切换中,忽略操作");
return false;
}
is_switching = true;
ESP_LOGI(TAG, "设置切换状态为true");
// 显示切换状态
auto display = GetDisplay();
if (display) {
display->ShowNotification("切换到下一首...");
ESP_LOGI(TAG, "显示切换通知");
}
// 停止当前播放
ESP_LOGI(TAG, "停止当前播放,准备切换到下一首");
audio_player_state_t current_state = audio_player_get_state();
ESP_LOGI(TAG, "当前播放器状态: %d", current_state);
audio_player_stop();
ESP_LOGI(TAG, "已调用audio_player_stop()");
// 等待播放器真正停止
int timeout = 50; // 5秒超时
ESP_LOGI(TAG, "等待播放器停止,超时时间: %d * 100ms", timeout);
while (audio_player_get_state() != AUDIO_PLAYER_STATE_IDLE && timeout > 0) {
current_state = audio_player_get_state();
ESP_LOGD(TAG, "等待停止中,当前状态: %d, 剩余超时: %d", current_state, timeout);
vTaskDelay(pdMS_TO_TICKS(100));
timeout--;
}
current_state = audio_player_get_state();
ESP_LOGI(TAG, "等待结束,最终状态: %d, 剩余超时: %d", current_state, timeout);
if (timeout <= 0) {
ESP_LOGE(TAG, "停止播放超时,当前状态: %d", current_state);
is_switching = false;
if (display) {
display->ShowNotification("切换超时");
}
return false;
}
ESP_LOGI(TAG, "播放器已停止,开始播放下一首");
// 播放下一首
bool success = PlayNextSong();
// 如果播放成功,等待一小段时间确保播放稳定启动
if (success) {
ESP_LOGI(TAG, "等待播放稳定启动...");
vTaskDelay(pdMS_TO_TICKS(200)); // 等待200ms
}
is_switching = false;
ESP_LOGI(TAG, "重置切换状态为false");
if (!success) {
ESP_LOGE(TAG, "播放下一首失败");
if (display) {
display->ShowNotification("切换失败");
}
} else {
ESP_LOGI(TAG, "成功切换到下一首");
if (display) {
display->ShowNotification("已切换到下一首");
}
}
ESP_LOGI(TAG, "*** 切换到下一首完成,结果: %s ***", success ? "成功" : "失败");
return success;
}
void InitializePowerManager()
{
power_manager_ = new PowerManager(PWR_ADC_GPIO);
power_manager_->OnChargingStatusChanged([this](bool is_charging)
{
if (is_charging) {
power_save_timer_->SetEnabled(false);
} else {
power_save_timer_->SetEnabled(true);
} });
}
void InitializeSdCard()
{
// 配置SD卡主机
host = SDMMC_HOST_DEFAULT();
// 配置SD卡插槽
slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
slot_config.width = 1; // 1位模式
slot_config.clk = SD_CARD_CLK_PIN;
slot_config.cmd = SD_CARD_CMD_PIN;
slot_config.d0 = SD_CARD_DAT0_PIN;
slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP;
}
void InitializePowerSaveTimer()
{
#ifndef __USER_GPIO_PWRDOWN__
RTC_DATA_ATTR static bool long_press_occurred = false;
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
if (cause == ESP_SLEEP_WAKEUP_EXT0)
{
ESP_LOGI(TAG, "Wake up by EXT0");
const int64_t start = esp_timer_get_time();
ESP_LOGI(TAG, "esp_sleep_get_wakeup_cause");
while (gpio_get_level(PWR_BUTTON_GPIO) == 0)
{
if (esp_timer_get_time() - start > 3000000)
{
long_press_occurred = true;
break;
}
vTaskDelay(100 / portTICK_PERIOD_MS);
}
if (long_press_occurred)
{
ESP_LOGI(TAG, "Long press wakeup");
long_press_occurred = false;
}
else
{
ESP_LOGI(TAG, "Short press, return to sleep");
ESP_ERROR_CHECK(esp_sleep_enable_ext0_wakeup(PWR_BUTTON_GPIO, 0));
ESP_ERROR_CHECK(rtc_gpio_pullup_en(PWR_BUTTON_GPIO)); // 内部上拉
ESP_ERROR_CHECK(rtc_gpio_pulldown_dis(PWR_BUTTON_GPIO));
esp_deep_sleep_start();
}
}
#endif
// 一分钟进入浅睡眠5分钟进入深睡眠关机
power_save_timer_ = new PowerSaveTimer(-1, (60 * 10), (60 * 30));
// power_save_timer_ = new PowerSaveTimer(-1, 6, 10);//test
power_save_timer_->OnEnterSleepMode([this]()
{
ESP_LOGI(TAG, "Enabling sleep mode");
display_->SetChatMessage("system", "");
display_->SetEmotion("sleepy");
GetBacklight()->SetBrightness(1); });
power_save_timer_->OnExitSleepMode([this]()
{
display_->SetChatMessage("system", "");
display_->SetEmotion("neutral");
GetBacklight()->RestoreBrightness(); });
power_save_timer_->OnShutdownRequest([this]()
{
ESP_LOGI(TAG, "Shutting down");
#ifndef __USER_GPIO_PWRDOWN__
ESP_ERROR_CHECK(esp_sleep_enable_ext0_wakeup(PWR_BUTTON_GPIO, 0));
ESP_ERROR_CHECK(rtc_gpio_pullup_en(PWR_BUTTON_GPIO)); // 内部上拉
ESP_ERROR_CHECK(rtc_gpio_pulldown_dis(PWR_BUTTON_GPIO));
esp_lcd_panel_disp_on_off(panel, false); // 关闭显示
esp_deep_sleep_start();
#else
rtc_gpio_set_level(PWR_EN_GPIO, 0);
rtc_gpio_hold_dis(PWR_EN_GPIO);
#endif
});
power_save_timer_->SetEnabled(true);
}
void InitializeI2c()
{
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = (i2c_port_t)1,
.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN,
.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN,
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.intr_priority = 0,
.trans_queue_depth = 0,
.flags = {
.enable_internal_pullup = 1,
},
};
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_));
}
void InitializeSpi()
{
}
void InitializeButtons()
{
static bool pwrbutton_unreleased = false;
static int power_button_click_count = 0;
static int64_t last_power_button_press_time = 0;
if (gpio_get_level(GPIO_NUM_3) == 1)
{
pwrbutton_unreleased = true;
}
// 电源按钮按下和松开事件处理
pwr_button_.OnPressUp([this]()
{
ESP_LOGI(TAG, "电源按钮按下: %s %d", __FUNCTION__, __LINE__);
pwrbutton_unreleased = false;
int64_t current_time = esp_timer_get_time();
if (current_time - last_power_button_press_time < 1000000) { // 1秒内
power_button_click_count++;
// 三击重置WiFi
if (power_button_click_count >= 3) {
ESP_LOGI(TAG, "三击重置WiFi");
rtc_gpio_set_level(PWR_EN_GPIO, 1);
rtc_gpio_hold_en(PWR_EN_GPIO);
ResetWifiConfiguration();
power_button_click_count = 0;
return;
}
} else {
power_button_click_count = 1;
}
last_power_button_press_time = current_time;
// 获取当前应用实例和状态
auto &app = Application::GetInstance();
auto current_state = app.GetDeviceState();
// 如果正在播放音乐,退出音乐播放模式
if (IsPlaying() || current_state == kDeviceStateMusicPlaying)
{
ESP_LOGI(TAG, "检测到音乐播放状态,正在退出...");
// 先停止所有音频播放
ESP_LOGI(TAG, "停止音频播放");
audio_player_stop();
// 等待一小段时间确保停止处理完成
vTaskDelay(pdMS_TO_TICKS(100));
// 强制重置播放状态标志
ESP_LOGI(TAG, "强制重置播放状态标志");
SetPlaying(false);
// 获取显示对象和音频编解码器
auto display = GetDisplay();
auto codec = GetAudioCodec();
// 重新配置音频编解码器
ESP_LOGI(TAG, "重新配置音频编解码器");
codec->SetSampleRate(24000, 1);
codec->EnableOutput(false);
codec->EnableInput(true);
// 重新启用语音功能
ESP_LOGI(TAG, "重新启用语音功能");
app.EnableVoiceFeatures();
// 强制设置为待机状态
ESP_LOGI(TAG, "强制设置为待机状态");
app.SetDeviceState(DeviceState::kDeviceStateIdle);
// 更新显示
ESP_LOGI(TAG, "更新显示状态");
display->SetStatus(Lang::Strings::STANDBY);
display->SetEmotion("neutral");
ESP_LOGI(TAG, "成功退出音乐播放模式,切换到小智模式");
// 唤醒设备,防止立即进入睡眠
power_save_timer_->WakeUp();
}
else
{
ESP_LOGI(TAG, "当前设备状态: %d", current_state);
if (current_state == kDeviceStateIdle) {
// 如果当前是待命状态,切换到聆听状态
ESP_LOGI(TAG, "从待命状态切换到聆听状态");
app.ToggleChatState(); // 切换到聆听状态
} else if (current_state == kDeviceStateListening) {
// 如果当前是聆听状态,切换到待命状态
ESP_LOGI(TAG, "从聆听状态切换到待命状态");
app.ToggleChatState(); // 切换到待命状态
} else if (current_state == kDeviceStateSpeaking) {
// 如果当前是说话状态,终止说话并切换到待命状态
ESP_LOGI(TAG, "从说话状态切换到待命状态");
app.ToggleChatState(); // 终止说话
} else {
// 其他状态下只唤醒设备
ESP_LOGI(TAG, "唤醒设备");
power_save_timer_->WakeUp();
}
}
});
// 电源按钮长按事件
pwr_button_.OnLongPress([this]()
{
ESP_LOGI(TAG, "电源按钮长按");
if (pwrbutton_unreleased)
return;
// 如果在音乐播放模式,先停止播放
if (IsPlaying()) {
ESP_LOGI(TAG, "从音乐播放模式退出并关机");
audio_player_stop();
SetPlaying(false);
}
// 长按前保存当前音量
ESP_LOGI(TAG, "保存音量设置: %d", current_volume);
SaveVolumeToNVS(current_volume);
// 长按进入深度睡眠模式(关机)
#ifndef __USER_GPIO_PWRDOWN__
ESP_LOGI(TAG, "准备进入深度睡眠模式");
ESP_ERROR_CHECK(esp_sleep_enable_ext0_wakeup(PWR_BUTTON_GPIO, 0));
ESP_ERROR_CHECK(rtc_gpio_pullup_en(PWR_BUTTON_GPIO)); // 内部上拉
ESP_ERROR_CHECK(rtc_gpio_pulldown_dis(PWR_BUTTON_GPIO));
ESP_LOGI(TAG, "Enter deep sleep");
esp_deep_sleep_start();
#else
ESP_LOGI(TAG, "准备进入深度睡眠模式");
ESP_ERROR_CHECK(esp_sleep_enable_ext0_wakeup(PWR_BUTTON_GPIO, 0));
ESP_ERROR_CHECK(rtc_gpio_pulldown_en(PWR_BUTTON_GPIO)); // 内部上拉
ESP_ERROR_CHECK(rtc_gpio_pullup_dis(PWR_BUTTON_GPIO));
ESP_LOGI(TAG, "Enter deep sleep");
rtc_gpio_set_level(PWR_EN_GPIO, 0);
rtc_gpio_hold_dis(PWR_EN_GPIO);
esp_deep_sleep_start();
#endif
});
// WIFI按钮功能
wifi_button.OnClick([this]()
{
if (is_playing) {
// 在音乐模式下:播放上一首
ESP_LOGI(TAG, "播放上一首");
// 使用安全的切换方法
SwitchToPreviousSong();
} else {
// 在小智状态下:加音量
ESP_LOGI(TAG, "WIFI按钮增加音量");
// 调整音量每次增加8个内部音量单位对应显示10%
current_volume = (current_volume + 8 > 80) ? 80 : current_volume + 8;
auto codec = GetAudioCodec();
// 将0-80的音量映射到完整音量范围
int actual_volume = current_volume;
codec->SetOutputVolume(actual_volume);
ESP_LOGI(TAG, "当前音量: %d, 实际音量: %d", current_volume, actual_volume);
// 保存新的音量设置
SaveVolumeToNVS(current_volume);
power_save_timer_->WakeUp();
// 在屏幕上显示当前音量(使用映射后的显示音量)
auto display = GetDisplay();
if (display) {
int display_volume = MapVolumeForDisplay(current_volume);
char volume_text[20];
snprintf(volume_text, sizeof(volume_text), "音量: %d%%", display_volume);
display->ShowNotification(volume_text);
ESP_LOGI(TAG, "显示音量: %d%% (内部音量: %d)", display_volume, current_volume);
}
}
});
// CMD按钮功能
cmd_button.OnClick([this]()
{
if (is_playing) {
// 在音乐模式下:播放下一首
ESP_LOGI(TAG, "播放下一首");
// 使用安全的切换方法
SwitchToNextSong();
} else {
// 在小智状态下:减音量
ESP_LOGI(TAG, "CMD按钮减少音量");
// 调整音量每次减少8个内部音量单位对应显示10%
current_volume = (current_volume - 8 < 0) ? 0 : current_volume - 8;
auto codec = GetAudioCodec();
// 将0-80的音量映射到完整音量范围
int actual_volume = current_volume;
codec->SetOutputVolume(actual_volume);
ESP_LOGI(TAG, "当前音量: %d, 实际音量: %d", current_volume, actual_volume);
// 保存新的音量设置
SaveVolumeToNVS(current_volume);
power_save_timer_->WakeUp();
// 在屏幕上显示当前音量(使用映射后的显示音量)
auto display = GetDisplay();
if (display) {
int display_volume = MapVolumeForDisplay(current_volume);
char volume_text[20];
snprintf(volume_text, sizeof(volume_text), "音量: %d%%", display_volume);
display->ShowNotification(volume_text);
ESP_LOGI(TAG, "显示音量: %d%% (内部音量: %d)", display_volume, current_volume);
}
}
});
// BOOT按钮功能不变仅保留唤醒设备的功能
boot_button_.OnClick([this]()
{
// 仅唤醒设备
power_save_timer_->WakeUp();
});
}
void InitializeGC9301isplay()
{
// 液晶屏控制IO初始化
ESP_LOGI(TAG, "test Install panel IO");
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = DISPLAY_SPI_MOSI_PIN;
buscfg.sclk_io_num = DISPLAY_SPI_SCK_PIN;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.quadwp_io_num = GPIO_NUM_NC;
buscfg.quadhd_io_num = GPIO_NUM_NC;
buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t);
ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
// 初始化SPI总线
esp_lcd_panel_io_spi_config_t io_config = {};
io_config.cs_gpio_num = DISPLAY_SPI_CS_PIN;
io_config.dc_gpio_num = DISPLAY_DC_PIN;
io_config.spi_mode = 3;
io_config.pclk_hz = 80 * 1000 * 1000;
io_config.trans_queue_depth = 10;
io_config.lcd_cmd_bits = 8;
io_config.lcd_param_bits = 8;
esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io);
// 初始化液晶屏驱动芯片9309
ESP_LOGI(TAG, "Install LCD driver");
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = GPIO_NUM_NC;
panel_config.rgb_ele_order = LCD_RGB_ENDIAN_BGR;
panel_config.bits_per_pixel = 16;
esp_lcd_new_panel_gc9309na(panel_io, &panel_config, &panel);
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, false);
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
display_ = new SpiLcdDisplay(panel_io, panel,
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY,
{
.text_font = &font_puhui_20_4,
.icon_font = &font_awesome_20_4,
#if CONFIG_USE_WECHAT_MESSAGE_STYLE
.emoji_font = font_emoji_32_init(),
#else
.emoji_font = font_emoji_64_init(),
#endif
});
}
// 物联网初始化,添加对 AI 可见设备
void InitializeIot()
{
auto &thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Screen"));
thing_manager.AddThing(iot::CreateThing("Battery"));
}
// 初始化mp3和wav播放器
void InitializeAudioPlayer()
{
if (!audio_player_initialized)
{
// 存储实例指针供全局回调使用
audio_board_instance = this;
// 使用外部定义的回调函数
audio_player_config_t config = {
.mute_fn = audio_mute_callback,
.clk_set_fn = audio_clk_set_callback,
.write_fn = audio_write_callback,
.priority = 7,
.coreID = 1};
esp_err_t ret = audio_player_new(config);
if (ret == ESP_OK)
{
// 注册播放器状态变化回调
audio_player_callback_register(audio_event_callback, NULL);
audio_player_initialized = true;
ESP_LOGI(TAG, "音频播放器初始化成功");
}
else
{
ESP_LOGE(TAG, "音频播放器初始化失败: %d", ret);
}
}
}
// 通过语音命令启动音乐播放
virtual bool StartMusicPlayback(const std::string& music_name = "") override
{
ESP_LOGI(TAG, "准备启动音乐播放...");
if (!card_mounted) {
ESP_LOGE(TAG, "SD卡未挂载无法播放音乐");
return false;
}
// 初始化音频播放器(如果尚未初始化)
if (!audio_player_initialized) {
ESP_LOGI(TAG, "初始化音频播放器");
InitializeAudioPlayer();
}
// 停止任何当前播放
if (audio_player_get_state() != AUDIO_PLAYER_STATE_IDLE) {
ESP_LOGI(TAG, "停止当前播放");
audio_player_stop();
vTaskDelay(pdMS_TO_TICKS(100)); // 等待停止完成
}
// 设置音乐播放状态
ESP_LOGI(TAG, "设置音乐播放状态标志");
SetPlaying(true);
// 禁用语音功能
ESP_LOGI(TAG, "禁用语音功能");
auto &app = Application::GetInstance();
app.DisableVoiceFeatures();
// 设置应用状态
ESP_LOGI(TAG, "设置应用状态为音乐播放");
app.SetDeviceState(DeviceState::kDeviceStateMusicPlaying);
// 配置音频编解码器
ESP_LOGI(TAG, "配置音频编解码器");
auto codec = GetAudioCodec();
codec->EnableInput(false);
codec->EnableOutput(true);
// 更新显示状态
ESP_LOGI(TAG, "更新显示状态");
auto display = GetDisplay();
display->SetStatus("音乐播放模式");
display->SetEmotion("happy");
// 列出所有SD卡中的音频文件
ESP_LOGI(TAG, "扫描SD卡音频文件");
const char mount_point[] = "/sdcard";
audio_files = GetAudioFiles(mount_point);
if (audio_files.empty()) {
ESP_LOGI(TAG, "未找到音频文件");
SetPlaying(false);
restore_device_status();
return false;
}
ESP_LOGI(TAG, "找到 %d 个音频文件", audio_files.size());
// 如果提供了音乐名称,尝试查找匹配的文件
if (!music_name.empty()) {
ESP_LOGI(TAG, "尝试查找匹配音乐: %s", music_name.c_str());
for (const auto& file : audio_files) {
// 提取文件名部分(不含路径)
size_t pos = file.find_last_of("/\\");
std::string filename = (pos != std::string::npos) ? file.substr(pos + 1) : file;
// 打印文件名的十六进制值和转换后的文件名
std::string hex_str = bytes_to_hex((const uint8_t*)filename.c_str(), filename.length());
std::string displayName = process_sd_filename(filename.c_str());
ESP_LOGI(TAG, "检查文件: [%s], 转换后: [%s]", filename.c_str(), displayName.c_str());
// 匹配逻辑:检查转换后的文件名是否包含要搜索的音乐名称
if (displayName.find(music_name) != std::string::npos) {
ESP_LOGI(TAG, "找到匹配的音乐: %s -> %s", file.c_str(), displayName.c_str());
current_file = file; // 设置当前文件
FILE *fp = fopen(file.c_str(), "rb");
if (fp) {
ESP_LOGI(TAG, "开始播放匹配的音乐");
audio_player_play(fp);
return true;
}
ESP_LOGE(TAG, "无法打开文件: %s", file.c_str());
break;
}
}
ESP_LOGI(TAG, "未找到匹配的音乐,将播放第一首");
}
// 如果没有找到匹配的文件或没有提供音乐名称,播放第一首
ESP_LOGI(TAG, "播放第一首音乐");
if (!audio_files.empty()) {
current_file = audio_files[0]; // 设置为第一首歌
FILE *fp = fopen(audio_files[0].c_str(), "rb");
if (fp) {
ESP_LOGI(TAG, "开始播放第一首音乐: %s", audio_files[0].c_str());
audio_player_play(fp);
return true;
}
ESP_LOGE(TAG, "无法打开文件: %s", audio_files[0].c_str());
}
// 如果到这里,说明播放失败
ESP_LOGE(TAG, "播放失败,恢复设备状态");
SetPlaying(false);
restore_device_status();
return false;
}
// 公共接口方法
public:
JiuchuangDevBoard() : boot_button_(BOOT_BUTTON_GPIO),
pwr_button_(PWR_BUTTON_GPIO, true),
wifi_button(WIFI_BUTTON_GPIO),
cmd_button(CMD_BUTTON_GPIO)
{
// 初始化NVS
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
// NVS分区已满或版本不匹配擦除并重新初始化
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
ESP_ERROR_CHECK(err);
// 初始化GBK编码表
ESP_LOGI(TAG, "初始化GBK编码表");
init_gbk_encoding();
// 从NVS加载保存的音量
current_volume = LoadVolumeFromNVS();
ESP_LOGI(TAG, "从NVS加载音量: %d", current_volume);
InitializeI2c();
InitializePowerManager();
InitializePowerSaveTimer();
InitializeSpi();
InitializeButtons();
InitializeGC9301isplay();
InitializeIot();
InitializeSdCard();
InitializeAudioPlayer();
// 设置加载的音量
auto codec = GetAudioCodec();
// 应用音量值
int actual_volume = current_volume;
codec->SetOutputVolume(actual_volume);
ESP_LOGI(TAG, "设置初始音量: %d, 实际音量: %d", current_volume, actual_volume);
GetBacklight()->RestoreBrightness();
}
// 获取音乐播放状态
bool IsPlaying() const {
return is_playing;
}
// 设置音乐播放状态
void SetPlaying(bool playing) {
is_playing = playing;
}
// virtual Led* GetLed() override {
// static SingleLed led(BUILTIN_LED_GPIO);
// return &led;
// }
virtual AudioCodec *GetAudioCodec() override
{
static Es8311AudioCodec audio_codec(
codec_i2c_bus_,
I2C_NUM_0,
AUDIO_INPUT_SAMPLE_RATE,
AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_MCLK,
AUDIO_I2S_GPIO_BCLK,
AUDIO_I2S_GPIO_WS,
AUDIO_I2S_GPIO_DOUT,
AUDIO_I2S_GPIO_DIN,
AUDIO_CODEC_PA_PIN,
AUDIO_CODEC_ES8311_ADDR);
return &audio_codec;
}
virtual Display *GetDisplay() override
{
return display_;
}
virtual Backlight *GetBacklight() override
{
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
return &backlight;
}
virtual bool GetBatteryLevel(int &level, bool &charging, bool &discharging) override
{
static bool last_discharging = false;
charging = power_manager_->IsCharging();
discharging = power_manager_->IsDischarging();
if (discharging != last_discharging)
{
power_save_timer_->SetEnabled(discharging);
last_discharging = discharging;
}
level = power_manager_->GetBatteryLevel();
return true;
}
virtual void SetPowerSaveMode(bool enabled) override
{
if (!enabled)
{
power_save_timer_->WakeUp();
}
WifiBoard::SetPowerSaveMode(enabled);
}
virtual void StartSdCardDetection()
{
if (sd_card_detect_task_handle == NULL)
{
xTaskCreate(
sd_card_detect_task,
"sd_card_detect",
8192,
this,
6,
&sd_card_detect_task_handle);
}
}
};
// 在类定义外部初始化静态成员变量
JiuchuangDevBoard *JiuchuangDevBoard::audio_board_instance = nullptr;
/**
* @brief 列出SD卡中的文件
*
* @param mount_point 挂载点路径
* @return esp_err_t ESP_OK表示成功
*/
static esp_err_t list_sd_card_files(const char *mount_point)
{
char tmp_file_path[128];
snprintf(tmp_file_path, sizeof(tmp_file_path), "%s/d.tmp", mount_point);
FILE *tmp_file = fopen(tmp_file_path, "w");
if (fclose(tmp_file) != 0)
{
ESP_LOGE(TAG, "SD卡已拔出");
return ESP_FAIL;
};
unlink(tmp_file_path);
// 继续列出SD卡中的文件
ESP_LOGI(TAG, "SD卡文件列表:");
DIR *dir = opendir(mount_point);
if (!dir)
{
ESP_LOGE(TAG, "无法打开目录: %s", mount_point);
return ESP_FAIL;
}
struct dirent *entry;
int file_count = 0;
int audio_file_count = 0;
// 遍历并打印文件名
while ((entry = readdir(dir)) != NULL)
{
// 使用辅助函数调试文件名
const char* filename = entry->d_name;
std::string mapped_name = process_sd_filename(filename);
file_count++;
// 检查是否是MP3文件
if (is_supported_audio_file(filename)) {
ESP_LOGI(TAG, "找到音频文件: %s -> %s", filename, mapped_name.c_str());
audio_file_count++;
}
}
closedir(dir);
if (file_count == 0)
{
ESP_LOGI(TAG, "SD卡为空");
}
else
{
ESP_LOGI(TAG, "共有 %d 个文件,其中 %d 个音频文件", file_count, audio_file_count);
}
return ESP_OK;
}
// 静音/取消静音控制回调
static esp_err_t audio_mute_callback(AUDIO_PLAYER_MUTE_SETTING setting)
{
ESP_LOGI(TAG, "mute setting %d", setting);
return ESP_OK;
}
// 音频时钟设置回调
static esp_err_t audio_clk_set_callback(uint32_t rate, uint32_t bits_cfg, i2s_slot_mode_t ch)
{
if (!JiuchuangDevBoard::audio_board_instance)
return ESP_ERR_INVALID_STATE;
auto &app = Application::GetInstance();
app.DisableVoiceFeatures();
app.SetDeviceState(DeviceState::kDeviceStateMusicPlaying);
auto codec = JiuchuangDevBoard::audio_board_instance->GetAudioCodec();
codec->EnableInput(false);
codec->SetSampleRate(rate, (int)ch);
ESP_LOGI(TAG, "音频时钟设置: rate=%lu, bits_cfg=%lu, ch=%d", rate, bits_cfg, (int)ch);
return ESP_OK;
}
// 音频数据写入回调
static esp_err_t audio_write_callback(void *audio_buffer, size_t len, size_t *bytes_written, uint32_t timeout_ms)
{
if (!JiuchuangDevBoard::audio_board_instance)
return ESP_ERR_INVALID_STATE;
auto codec = JiuchuangDevBoard::audio_board_instance->GetAudioCodec();
int16_t *samples = (int16_t *)audio_buffer;
int sample_count = len / sizeof(int16_t);
// 调用我们的AudioCodec类的PlayAudio方法播放音频数据
int samples_played = codec->PlayAudio(samples, sample_count);
*bytes_written = samples_played * sizeof(int16_t);
return ESP_OK;
}
// 音频播放器状态变化回调
static void audio_event_callback(audio_player_cb_ctx_t *ctx)
{
if (!JiuchuangDevBoard::audio_board_instance)
return;
auto board = JiuchuangDevBoard::audio_board_instance;
auto display = board->GetDisplay();
switch (ctx->audio_event)
{
case AUDIO_PLAYER_CALLBACK_EVENT_PLAYING:
ESP_LOGI(TAG, "音频播放中...");
// 获取当前播放的文件名
if (!board->current_file.empty()) {
size_t pos = board->current_file.find_last_of("/\\");
std::string filename = (pos != std::string::npos) ? board->current_file.substr(pos + 1) : board->current_file;
std::string displayName = process_sd_filename(filename.c_str());
std::string status_text = "正在播放: " + displayName;
display->SetStatus(status_text.c_str());
display->SetChatMessage("system", status_text.c_str());
} else {
display->SetStatus("正在播放音乐");
}
display->SetEmotion("happy");
break;
case AUDIO_PLAYER_CALLBACK_EVENT_PAUSE:
ESP_LOGI(TAG, "音频已暂停");
display->SetStatus("音乐已暂停");
break;
case AUDIO_PLAYER_CALLBACK_EVENT_IDLE:
ESP_LOGI(TAG, "音频播放结束");
// 检查是否正在切换中,如果是则不自动播放下一首
if (board->is_switching) {
ESP_LOGI(TAG, "正在切换中,不自动播放下一首");
break;
}
// 只有在用户仍处于音乐播放模式时才自动播放下一首
if (board->IsPlaying()) {
// 延迟一小段时间后自动播放下一首
vTaskDelay(pdMS_TO_TICKS(100));
// 再次检查是否仍处于播放模式和切换状态
if (board->IsPlaying() && !board->is_switching) {
ESP_LOGI(TAG, "自动播放下一首歌曲");
board->PlayNextSong();
} else {
ESP_LOGI(TAG, "播放模式已退出或正在切换,不自动播放下一首");
}
} else {
ESP_LOGI(TAG, "当前不处于音乐播放模式,不自动播放下一首");
}
break;
case AUDIO_PLAYER_CALLBACK_EVENT_COMPLETED_PLAYING_NEXT:
break;
case AUDIO_PLAYER_CALLBACK_EVENT_UNKNOWN_FILE_TYPE:
ESP_LOGE(TAG, "未知文件类型");
display->SetStatus("未知文件类型");
break;
default:
ESP_LOGI(TAG, "音频事件: %d", ctx->audio_event);
break;
}
}
static esp_err_t restore_device_status()
{
if (!JiuchuangDevBoard::audio_board_instance)
return ESP_ERR_INVALID_STATE;
ESP_LOGI(TAG, "开始恢复设备状态");
// 停止正在播放的音频
ESP_LOGI(TAG, "停止音频播放");
audio_player_stop();
// 等待确保音频停止完成
vTaskDelay(pdMS_TO_TICKS(100));
auto& board = *JiuchuangDevBoard::audio_board_instance;
// 强制重置播放状态标志
ESP_LOGI(TAG, "强制重置播放状态标志");
board.SetPlaying(false);
auto &app = Application::GetInstance();
// 重新启用语音功能
ESP_LOGI(TAG, "重新启用语音功能");
app.EnableVoiceFeatures();
// 强制设置为待机状态
ESP_LOGI(TAG, "强制设置应用状态为待机");
app.SetDeviceState(DeviceState::kDeviceStateIdle);
// 重新配置音频编解码器
ESP_LOGI(TAG, "重新配置音频编解码器");
auto codec = board.GetAudioCodec();
codec->SetSampleRate(24000, (int)1);
codec->EnableOutput(false);
codec->EnableInput(true);
// 更新用户界面
ESP_LOGI(TAG, "更新用户界面");
auto display = board.GetDisplay();
if (display) {
display->SetStatus(Lang::Strings::STANDBY);
display->SetEmotion("neutral");
}
ESP_LOGI(TAG, "设备状态已完全恢复到小智模式");
return ESP_OK;
}
static void sd_card_detect_task(void *arg)
{
esp_log_level_set("sdmmc_common", ESP_LOG_NONE); // 完全禁用SDMMC公共日志
esp_log_level_set("vfs_fat_sdmmc", ESP_LOG_NONE);
const char mount_point[] = "/sdcard";
esp_err_t ret;
JiuchuangDevBoard *board = (JiuchuangDevBoard *)arg;
ESP_LOGI(TAG, "SD卡检测任务启动");
// 配置挂载设置
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = false,
.max_files = 16, // 增加最大文件数以支持更多文件
.allocation_unit_size = 16 * 1024,
};
// 设置文件系统字符编码为UTF-8
// 注意: ESP32上的文件系统API可能没有直接支持设置UTF-8编码的方法
// 我们尝试通过增加日志和调试来解决中文文件名问题
while (1)
{
// 检查SD卡是否已挂载
if (!board->card_mounted)
{
ESP_LOGI(TAG, "尝试挂载SD卡...");
ret = esp_vfs_fat_sdmmc_mount(mount_point, &board->host, &board->slot_config, &mount_config, &board->card);
if (ret == ESP_OK)
{
ESP_LOGI(TAG, "SD卡挂载成功");
board->card_mounted = true;
// 打印SD卡信息
sdmmc_card_print_info(stdout, board->card);
// 列出SD卡文件
list_sd_card_files(mount_point);
}
else
{
ESP_LOGE(TAG, "SD卡挂载失败: %s", esp_err_to_name(ret));
}
}
else
{
// 仅检查SD卡是否存在不进行任何自动播放操作
if (list_sd_card_files(mount_point) != ESP_OK)
{
ESP_LOGI(TAG, "SD卡已移除执行卸载操作");
esp_vfs_fat_sdcard_unmount(mount_point, board->card);
board->card_mounted = false;
// 如果当前正在播放,停止播放
if (board->IsPlaying())
{
ESP_LOGI(TAG, "SD卡已移除且正在播放停止播放");
audio_player_stop();
board->SetPlaying(false);
// 恢复正常模式
auto &app = Application::GetInstance();
app.EnableVoiceFeatures();
app.SetDeviceState(DeviceState::kDeviceStateIdle);
// 更新显示
auto display = board->GetDisplay();
if (display) {
display->SetStatus(Lang::Strings::STANDBY);
display->SetEmotion("neutral");
}
}
}
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
DECLARE_BOARD(JiuchuangDevBoard);