forked from xiaozhi/xiaozhi-esp32
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38157aa180 | ||
|
|
1bacf40cd4 | ||
|
|
99aa15822b | ||
|
|
1ffc5190b6 | ||
|
|
73dbeb4b9a | ||
|
|
1e94e884b8 | ||
|
|
b35bf0c344 | ||
|
|
5d3f597137 | ||
|
|
3e37551923 | ||
|
|
d09537ed5c | ||
|
|
86921f4862 | ||
|
|
7af366b7b2 | ||
|
|
ddbb24942d | ||
|
|
610a4a0703 | ||
|
|
7cd37427b2 | ||
|
|
2d772dad68 | ||
|
|
156eb15f58 |
@@ -4,7 +4,7 @@
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
set(PROJECT_VER "1.8.8")
|
||||
set(PROJECT_VER "1.9.2")
|
||||
|
||||
# Add this line to disable the specific warning
|
||||
add_compile_options(-Wno-missing-field-initializers)
|
||||
|
||||
@@ -130,7 +130,7 @@
|
||||
|
||||
## 大模型配置
|
||||
|
||||
如果你已经拥有一个的小智 AI 聊天机器人设备,并且已接入官方服务器,可以登录 [xiaozhi.me](https://xiaozhi.me) 控制台进行配置。
|
||||
如果你已经拥有一个小智 AI 聊天机器人设备,并且已接入官方服务器,可以登录 [xiaozhi.me](https://xiaozhi.me) 控制台进行配置。
|
||||
|
||||
👉 [后台操作视频教程(旧版界面)](https://www.bilibili.com/video/BV1jUCUY2EKM/)
|
||||
|
||||
|
||||
@@ -86,6 +86,8 @@ elseif(CONFIG_BOARD_TYPE_ATOMS3R_ECHO_BASE)
|
||||
set(BOARD_TYPE "atoms3r-echo-base")
|
||||
elseif(CONFIG_BOARD_TYPE_ATOMS3R_CAM_M12_ECHO_BASE)
|
||||
set(BOARD_TYPE "atoms3r-cam-m12-echo-base")
|
||||
elseif(CONFIG_BOARD_TYPE_ATOM_ECHOS3R)
|
||||
set(BOARD_TYPE "atom-echos3r")
|
||||
elseif(CONFIG_BOARD_TYPE_ATOMMATRIX_ECHO_BASE)
|
||||
set(BOARD_TYPE "atommatrix-echo-base")
|
||||
elseif(CONFIG_BOARD_TYPE_XMINI_C3_V3)
|
||||
@@ -104,6 +106,8 @@ elseif(CONFIG_BOARD_TYPE_ESP_HI)
|
||||
set(BOARD_TYPE "esp-hi")
|
||||
elseif(CONFIG_BOARD_TYPE_ECHOEAR)
|
||||
set(BOARD_TYPE "echoear")
|
||||
elseif(CONFIG_BOARD_TYPE_ESP32S3_AUDIO_BOARD)
|
||||
set(BOARD_TYPE "waveshare-s3-audio-board")
|
||||
elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_AMOLED_1_8)
|
||||
set(BOARD_TYPE "esp32-s3-touch-amoled-1.8")
|
||||
elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_AMOLED_2_06)
|
||||
@@ -394,4 +398,4 @@ spiffs_create_partition_assets(
|
||||
MMAP_FILE_SUPPORT_FORMAT ".aaf, ttf, bin"
|
||||
IMPORT_INC_PATH ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE}
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -178,9 +178,15 @@ choice BOARD_TYPE
|
||||
config BOARD_TYPE_ATOMS3R_CAM_M12_ECHO_BASE
|
||||
bool "AtomS3R CAM/M12 + Echo Base"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_ATOM_ECHOS3R
|
||||
bool "AtomEchoS3R"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_ATOMMATRIX_ECHO_BASE
|
||||
bool "AtomMatrix + Echo Base"
|
||||
depends on IDF_TARGET_ESP32
|
||||
config BOARD_TYPE_ESP32S3_AUDIO_BOARD
|
||||
bool "Waveshare ESP32-S3-Audio-Board"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_ESP32S3_Touch_AMOLED_1_8
|
||||
bool "Waveshare ESP32-S3-Touch-AMOLED-1.8"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
@@ -422,15 +428,27 @@ endchoice
|
||||
choice DISPLAY_ESP32S3_KORVO2_V3
|
||||
depends on BOARD_TYPE_ESP32S3_KORVO2_V3
|
||||
prompt "ESP32S3_KORVO2_V3 LCD Type"
|
||||
default LCD_ST7789
|
||||
default ESP32S3_KORVO2_V3_LCD_ST7789
|
||||
help
|
||||
屏幕类型选择
|
||||
config LCD_ST7789
|
||||
config ESP32S3_KORVO2_V3_LCD_ST7789
|
||||
bool "ST7789, 分辨率240*280"
|
||||
config LCD_ILI9341
|
||||
config ESP32S3_KORVO2_V3_LCD_ILI9341
|
||||
bool "ILI9341, 分辨率240*320"
|
||||
endchoice
|
||||
|
||||
choice DISPLAY_ESP32S3_AUDIO_BOARD
|
||||
depends on BOARD_TYPE_ESP32S3_AUDIO_BOARD
|
||||
prompt "ESP32S3_AUDIO_BOARD LCD Type"
|
||||
default AUDIO_BOARD_LCD_JD9853
|
||||
help
|
||||
屏幕类型选择
|
||||
config AUDIO_BOARD_LCD_JD9853
|
||||
bool "JD9853, 分辨率320*172"
|
||||
config AUDIO_BOARD_LCD_ST7789
|
||||
bool "ST7789, 分辨率240*320"
|
||||
endchoice
|
||||
|
||||
config USE_WECHAT_MESSAGE_STYLE
|
||||
bool "Enable WeChat Message Style"
|
||||
default n
|
||||
@@ -490,7 +508,10 @@ config USE_AUDIO_PROCESSOR
|
||||
config USE_DEVICE_AEC
|
||||
bool "Enable Device-Side AEC"
|
||||
default n
|
||||
depends on USE_AUDIO_PROCESSOR && (BOARD_TYPE_ESP_BOX_3 || BOARD_TYPE_ESP_BOX || BOARD_TYPE_ESP_BOX_LITE || BOARD_TYPE_LICHUANG_DEV || BOARD_TYPE_ESP32S3_KORVO2_V3 || BOARD_TYPE_ESP32S3_Touch_AMOLED_1_75 || BOARD_TYPE_ESP32S3_Touch_AMOLED_2_06 || BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_4B || BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_XC || BOARD_TYPE_ESP_S3_LCD_EV_Board_2)
|
||||
depends on USE_AUDIO_PROCESSOR && (BOARD_TYPE_ESP_BOX_3 || BOARD_TYPE_ESP_BOX || BOARD_TYPE_ESP_BOX_LITE || BOARD_TYPE_LICHUANG_DEV \
|
||||
|| BOARD_TYPE_ESP32S3_KORVO2_V3 || BOARD_TYPE_ESP32S3_Touch_AMOLED_1_75 || BOARD_TYPE_ESP32S3_Touch_AMOLED_2_06 \
|
||||
|| BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_4B || BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_XC || BOARD_TYPE_ESP_S3_LCD_EV_Board_2 \
|
||||
|| BOARD_TYPE_ECHOEAR)
|
||||
help
|
||||
因为性能不够,不建议和微信聊天界面风格同时开启
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "audio_codec.h"
|
||||
#include "mqtt_protocol.h"
|
||||
#include "websocket_protocol.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
#include "assets/lang_config.h"
|
||||
#include "mcp_server.h"
|
||||
|
||||
@@ -14,6 +13,7 @@
|
||||
#include <cJSON.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <font_awesome.h>
|
||||
|
||||
#define TAG "Application"
|
||||
|
||||
@@ -49,7 +49,7 @@ Application::Application() {
|
||||
esp_timer_create_args_t clock_timer_args = {
|
||||
.callback = [](void* arg) {
|
||||
Application* app = (Application*)arg;
|
||||
app->OnClockTimer();
|
||||
xEventGroupSetBits(app->event_group_, MAIN_EVENT_CLOCK_TICK);
|
||||
},
|
||||
.arg = this,
|
||||
.dispatch_method = ESP_TIMER_TASK,
|
||||
@@ -87,7 +87,7 @@ void Application::CheckNewVersion(Ota& ota) {
|
||||
|
||||
char buffer[256];
|
||||
snprintf(buffer, sizeof(buffer), Lang::Strings::CHECK_NEW_VERSION_FAILED, retry_delay, ota.GetCheckVersionUrl().c_str());
|
||||
Alert(Lang::Strings::ERROR, buffer, "sad", Lang::Sounds::OGG_EXCLAMATION);
|
||||
Alert(Lang::Strings::ERROR, buffer, "cloud_slash", Lang::Sounds::OGG_EXCLAMATION);
|
||||
|
||||
ESP_LOGW(TAG, "Check new version failed, retry in %d seconds (%d/%d)", retry_delay, retry_count, MAX_RETRY);
|
||||
for (int i = 0; i < retry_delay; i++) {
|
||||
@@ -103,13 +103,12 @@ void Application::CheckNewVersion(Ota& ota) {
|
||||
retry_delay = 10; // 重置重试延迟时间
|
||||
|
||||
if (ota.HasNewVersion()) {
|
||||
Alert(Lang::Strings::OTA_UPGRADE, Lang::Strings::UPGRADING, "happy", Lang::Sounds::OGG_UPGRADE);
|
||||
Alert(Lang::Strings::OTA_UPGRADE, Lang::Strings::UPGRADING, "download", Lang::Sounds::OGG_UPGRADE);
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(3000));
|
||||
|
||||
SetDeviceState(kDeviceStateUpgrading);
|
||||
|
||||
display->SetIcon(FONT_AWESOME_DOWNLOAD);
|
||||
std::string message = std::string(Lang::Strings::NEW_VERSION) + ota.GetFirmwareVersion();
|
||||
display->SetChatMessage("system", message.c_str());
|
||||
|
||||
@@ -130,7 +129,7 @@ void Application::CheckNewVersion(Ota& ota) {
|
||||
ESP_LOGE(TAG, "Firmware upgrade failed, restarting audio service and continuing operation...");
|
||||
audio_service_.Start(); // Restart audio service
|
||||
board.SetPowerSaveMode(true); // Restore power save mode
|
||||
Alert(Lang::Strings::ERROR, Lang::Strings::UPGRADE_FAILED, "sad", Lang::Sounds::OGG_EXCLAMATION);
|
||||
Alert(Lang::Strings::ERROR, Lang::Strings::UPGRADE_FAILED, "circle_xmark", Lang::Sounds::OGG_EXCLAMATION);
|
||||
vTaskDelay(pdMS_TO_TICKS(3000));
|
||||
// Continue to normal operation (don't break, just fall through)
|
||||
} else {
|
||||
@@ -195,7 +194,7 @@ void Application::ShowActivationCode(const std::string& code, const std::string&
|
||||
}};
|
||||
|
||||
// This sentence uses 9KB of SRAM, so we need to wait for it to finish
|
||||
Alert(Lang::Strings::ACTIVATION, message.c_str(), "happy", Lang::Sounds::OGG_ACTIVATION);
|
||||
Alert(Lang::Strings::ACTIVATION, message.c_str(), "link", Lang::Sounds::OGG_ACTIVATION);
|
||||
|
||||
for (const auto& digit : code) {
|
||||
auto it = std::find_if(digit_sounds.begin(), digit_sounds.end(),
|
||||
@@ -207,7 +206,7 @@ void Application::ShowActivationCode(const std::string& code, const std::string&
|
||||
}
|
||||
|
||||
void Application::Alert(const char* status, const char* message, const char* emotion, const std::string_view& sound) {
|
||||
ESP_LOGW(TAG, "Alert %s: %s [%s]", status, message, emotion);
|
||||
ESP_LOGW(TAG, "Alert [%s] %s: %s", emotion, status, message);
|
||||
auto display = Board::GetInstance().GetDisplay();
|
||||
display->SetStatus(status);
|
||||
display->SetEmotion(emotion);
|
||||
@@ -333,6 +332,9 @@ void Application::Start() {
|
||||
/* Setup the display */
|
||||
auto display = board.GetDisplay();
|
||||
|
||||
// Print board name/version info
|
||||
display->SetChatMessage("system", SystemInfo::GetUserAgent().c_str());
|
||||
|
||||
/* Setup the audio service */
|
||||
auto codec = board.GetAudioCodec();
|
||||
audio_service_.Initialize(codec);
|
||||
@@ -350,6 +352,12 @@ void Application::Start() {
|
||||
};
|
||||
audio_service_.SetCallbacks(callbacks);
|
||||
|
||||
// Start the main event loop task with priority 3
|
||||
xTaskCreate([](void* arg) {
|
||||
((Application*)arg)->MainEventLoop();
|
||||
vTaskDelete(NULL);
|
||||
}, "main_event_loop", 2048 * 4, this, 3, &main_event_loop_task_handle_);
|
||||
|
||||
/* Start the clock timer to update the status bar */
|
||||
esp_timer_start_periodic(clock_timer_handle_, 1000000);
|
||||
|
||||
@@ -378,6 +386,10 @@ void Application::Start() {
|
||||
protocol_ = std::make_unique<MqttProtocol>();
|
||||
}
|
||||
|
||||
protocol_->OnConnected([this]() {
|
||||
DismissAlert();
|
||||
});
|
||||
|
||||
protocol_->OnNetworkError([this](const std::string& message) {
|
||||
last_error_message_ = message;
|
||||
xEventGroupSetBits(event_group_, MAIN_EVENT_ERROR);
|
||||
@@ -493,6 +505,8 @@ void Application::Start() {
|
||||
});
|
||||
bool protocol_started = protocol_->Start();
|
||||
|
||||
// Print heap stats
|
||||
SystemInfo::PrintHeapStats();
|
||||
SetDeviceState(kDeviceStateIdle);
|
||||
|
||||
has_server_time_ = ota.HasServerTime();
|
||||
@@ -503,23 +517,6 @@ void Application::Start() {
|
||||
// Play the success sound to indicate the device is ready
|
||||
audio_service_.PlaySound(Lang::Sounds::OGG_SUCCESS);
|
||||
}
|
||||
|
||||
// Print heap stats
|
||||
SystemInfo::PrintHeapStats();
|
||||
}
|
||||
|
||||
void Application::OnClockTimer() {
|
||||
clock_ticks_++;
|
||||
|
||||
auto display = Board::GetInstance().GetDisplay();
|
||||
display->UpdateStatusBar();
|
||||
|
||||
// Print the debug info every 10 seconds
|
||||
if (clock_ticks_ % 10 == 0) {
|
||||
// SystemInfo::PrintTaskCpuUsage(pdMS_TO_TICKS(1000));
|
||||
// SystemInfo::PrintTaskList();
|
||||
SystemInfo::PrintHeapStats();
|
||||
}
|
||||
}
|
||||
|
||||
// Add a async task to MainLoop
|
||||
@@ -535,18 +532,17 @@ void Application::Schedule(std::function<void()> callback) {
|
||||
// If other tasks need to access the websocket or chat state,
|
||||
// they should use Schedule to call this function
|
||||
void Application::MainEventLoop() {
|
||||
// Raise the priority of the main event loop to avoid being interrupted by background tasks (which has priority 2)
|
||||
vTaskPrioritySet(NULL, 3);
|
||||
|
||||
while (true) {
|
||||
auto bits = xEventGroupWaitBits(event_group_, MAIN_EVENT_SCHEDULE |
|
||||
MAIN_EVENT_SEND_AUDIO |
|
||||
MAIN_EVENT_WAKE_WORD_DETECTED |
|
||||
MAIN_EVENT_VAD_CHANGE |
|
||||
MAIN_EVENT_CLOCK_TICK |
|
||||
MAIN_EVENT_ERROR, pdTRUE, pdFALSE, portMAX_DELAY);
|
||||
|
||||
if (bits & MAIN_EVENT_ERROR) {
|
||||
SetDeviceState(kDeviceStateIdle);
|
||||
Alert(Lang::Strings::ERROR, last_error_message_.c_str(), "sad", Lang::Sounds::OGG_EXCLAMATION);
|
||||
Alert(Lang::Strings::ERROR, last_error_message_.c_str(), "circle_xmark", Lang::Sounds::OGG_EXCLAMATION);
|
||||
}
|
||||
|
||||
if (bits & MAIN_EVENT_SEND_AUDIO) {
|
||||
@@ -576,6 +572,19 @@ void Application::MainEventLoop() {
|
||||
task();
|
||||
}
|
||||
}
|
||||
|
||||
if (bits & MAIN_EVENT_CLOCK_TICK) {
|
||||
clock_ticks_++;
|
||||
auto display = Board::GetInstance().GetDisplay();
|
||||
display->UpdateStatusBar();
|
||||
|
||||
// Print the debug info every 10 seconds
|
||||
if (clock_ticks_ % 10 == 0) {
|
||||
// SystemInfo::PrintTaskCpuUsage(pdMS_TO_TICKS(1000));
|
||||
// SystemInfo::PrintTaskList();
|
||||
SystemInfo::PrintHeapStats();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -734,11 +743,20 @@ bool Application::CanEnterSleepMode() {
|
||||
}
|
||||
|
||||
void Application::SendMcpMessage(const std::string& payload) {
|
||||
Schedule([this, payload]() {
|
||||
if (protocol_) {
|
||||
if (protocol_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure you are using main thread to send MCP message
|
||||
if (xTaskGetCurrentTaskHandle() == main_event_loop_task_handle_) {
|
||||
ESP_LOGI(TAG, "Send MCP message in main thread");
|
||||
protocol_->SendMcpMessage(payload);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Send MCP message in sub thread");
|
||||
Schedule([this, payload = std::move(payload)]() {
|
||||
protocol_->SendMcpMessage(payload);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Application::SetAecMode(AecMode mode) {
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "protocol.h"
|
||||
@@ -17,12 +16,15 @@
|
||||
#include "audio_service.h"
|
||||
#include "device_state_event.h"
|
||||
|
||||
|
||||
#define MAIN_EVENT_SCHEDULE (1 << 0)
|
||||
#define MAIN_EVENT_SEND_AUDIO (1 << 1)
|
||||
#define MAIN_EVENT_WAKE_WORD_DETECTED (1 << 2)
|
||||
#define MAIN_EVENT_VAD_CHANGE (1 << 3)
|
||||
#define MAIN_EVENT_ERROR (1 << 4)
|
||||
#define MAIN_EVENT_CHECK_NEW_VERSION_DONE (1 << 5)
|
||||
#define MAIN_EVENT_CLOCK_TICK (1 << 6)
|
||||
|
||||
|
||||
enum AecMode {
|
||||
kAecOff,
|
||||
@@ -80,12 +82,27 @@ private:
|
||||
bool aborted_ = false;
|
||||
int clock_ticks_ = 0;
|
||||
TaskHandle_t check_new_version_task_handle_ = nullptr;
|
||||
TaskHandle_t main_event_loop_task_handle_ = nullptr;
|
||||
|
||||
void OnWakeWordDetected();
|
||||
void CheckNewVersion(Ota& ota);
|
||||
void ShowActivationCode(const std::string& code, const std::string& message);
|
||||
void OnClockTimer();
|
||||
void SetListeningMode(ListeningMode mode);
|
||||
};
|
||||
|
||||
|
||||
class TaskPriorityReset {
|
||||
public:
|
||||
TaskPriorityReset(BaseType_t priority) {
|
||||
original_priority_ = uxTaskPriorityGet(NULL);
|
||||
vTaskPrioritySet(NULL, priority);
|
||||
}
|
||||
~TaskPriorityReset() {
|
||||
vTaskPrioritySet(NULL, original_priority_);
|
||||
}
|
||||
|
||||
private:
|
||||
BaseType_t original_priority_;
|
||||
};
|
||||
|
||||
#endif // _APPLICATION_H_
|
||||
|
||||
@@ -100,18 +100,18 @@ void AudioService::Start() {
|
||||
|
||||
#if CONFIG_USE_AUDIO_PROCESSOR
|
||||
/* Start the audio input task */
|
||||
xTaskCreatePinnedToCore([](void* arg) {
|
||||
xTaskCreate([](void* arg) {
|
||||
AudioService* audio_service = (AudioService*)arg;
|
||||
audio_service->AudioInputTask();
|
||||
vTaskDelete(NULL);
|
||||
}, "audio_input", 2048 * 3, this, 8, &audio_input_task_handle_, 1);
|
||||
}, "audio_input", 2048 * 3, this, 8, &audio_input_task_handle_);
|
||||
|
||||
/* Start the audio output task */
|
||||
xTaskCreate([](void* arg) {
|
||||
AudioService* audio_service = (AudioService*)arg;
|
||||
audio_service->AudioOutputTask();
|
||||
vTaskDelete(NULL);
|
||||
}, "audio_output", 2048 * 2, this, 3, &audio_output_task_handle_);
|
||||
}, "audio_output", 2048 * 2, this, 4, &audio_output_task_handle_);
|
||||
#else
|
||||
/* Start the audio input task */
|
||||
xTaskCreate([](void* arg) {
|
||||
@@ -125,7 +125,7 @@ void AudioService::Start() {
|
||||
AudioService* audio_service = (AudioService*)arg;
|
||||
audio_service->AudioOutputTask();
|
||||
vTaskDelete(NULL);
|
||||
}, "audio_output", 2048, this, 3, &audio_output_task_handle_);
|
||||
}, "audio_output", 2048, this, 4, &audio_output_task_handle_);
|
||||
#endif
|
||||
|
||||
/* Start the opus codec task */
|
||||
@@ -540,6 +540,12 @@ void AudioService::SetCallbacks(AudioServiceCallbacks& callbacks) {
|
||||
}
|
||||
|
||||
void AudioService::PlaySound(const std::string_view& ogg) {
|
||||
if (!codec_->output_enabled()) {
|
||||
esp_timer_stop(audio_power_timer_);
|
||||
esp_timer_start_periodic(audio_power_timer_, AUDIO_POWER_CHECK_INTERVAL_MS * 1000);
|
||||
codec_->EnableOutput(true);
|
||||
}
|
||||
|
||||
const uint8_t* buf = reinterpret_cast<const uint8_t*>(ogg.data());
|
||||
size_t size = ogg.size();
|
||||
size_t offset = 0;
|
||||
|
||||
@@ -64,7 +64,7 @@ BoxAudioCodec::BoxAudioCodec(void* i2c_master_handle, int input_sample_rate, int
|
||||
|
||||
es7210_codec_cfg_t es7210_cfg = {};
|
||||
es7210_cfg.ctrl_if = in_ctrl_if_;
|
||||
es7210_cfg.mic_selected = ES7120_SEL_MIC1 | ES7120_SEL_MIC2 | ES7120_SEL_MIC3 | ES7120_SEL_MIC4;
|
||||
es7210_cfg.mic_selected = ES7210_SEL_MIC1 | ES7210_SEL_MIC2 | ES7210_SEL_MIC3 | ES7210_SEL_MIC4;
|
||||
in_codec_if_ = es7210_codec_new(&es7210_cfg);
|
||||
assert(in_codec_if_ != NULL);
|
||||
|
||||
|
||||
@@ -13,11 +13,6 @@ void NoAudioProcessor::Feed(std::vector<int16_t>&& data) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.size() != frame_samples_) {
|
||||
ESP_LOGE(TAG, "Feed data size is not equal to frame size, feed size: %u, frame size: %u", data.size(), frame_samples_);
|
||||
return;
|
||||
}
|
||||
|
||||
if (codec_->input_channels() == 2) {
|
||||
// If input channels is 2, we need to fetch the left channel data
|
||||
auto mono_data = std::vector<int16_t>(data.size() / 2);
|
||||
|
||||
45
main/boards/atom-echos3r/README.md
Normal file
45
main/boards/atom-echos3r/README.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# AtomEchoS3R
|
||||
## 简介
|
||||
|
||||
AtomEchoS3R 是 M5Stack 推出的基于 ESP32-S3-PICO-1-N8R8 的物联网可编程控制器,采用了 ES8311 单声道音频解码器、MEMS 麦克风和 NS4150B 功率放大器的集成方案。
|
||||
|
||||
开发版**不带屏幕、不带额外按键**,需要使用语音唤醒。必要时,需要使用 `idf.py monitor` 查看 log 以确定运行状态。
|
||||
|
||||
## 配置、编译命令
|
||||
|
||||
**配置编译目标为 ESP32S3**
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32s3
|
||||
```
|
||||
|
||||
**打开 menuconfig 并配置**
|
||||
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
分别配置如下选项:
|
||||
|
||||
- `Xiaozhi Assistant` → `Board Type` → 选择 `AtomEchoS3R`
|
||||
- `Partition Table` → `Custom partition CSV file` → 删除原有内容,输入 `partitions/v1/8m.csv`
|
||||
- `Serial flasher config` → `Flash size` → 选择 `8 MB`
|
||||
- `Component config` → `ESP PSRAM` → `Support for external, SPI-connected RAM` → `SPI RAM config` → 选择 `Octal Mode PSRAM`
|
||||
|
||||
按 `S` 保存,按 `Q` 退出。
|
||||
|
||||
**编译**
|
||||
|
||||
```bash
|
||||
idf.py build
|
||||
```
|
||||
|
||||
**烧录**
|
||||
|
||||
将 AtomEchoS3R 连接到电脑,按住侧面 RESET 按键,直到 RESET 按键下方绿灯闪烁。
|
||||
|
||||
```bash
|
||||
idf.py flash
|
||||
```
|
||||
|
||||
烧录完毕后,按一下 RESET 按钮重启设备。
|
||||
92
main/boards/atom-echos3r/atom_echos3r.cc
Normal file
92
main/boards/atom-echos3r/atom_echos3r.cc
Normal file
@@ -0,0 +1,92 @@
|
||||
#include "wifi_board.h"
|
||||
#include "codecs/es8311_audio_codec.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "i2c_device.h"
|
||||
#include "assets/lang_config.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#define TAG "AtomEchoS3R"
|
||||
|
||||
|
||||
class AtomEchoS3rBaseBoard : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
Button boot_button_;
|
||||
void InitializeI2c() {
|
||||
// Initialize I2C peripheral
|
||||
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||
.i2c_port = I2C_NUM_0,
|
||||
.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, &i2c_bus_));
|
||||
}
|
||||
|
||||
void I2cDetect() {
|
||||
uint8_t address;
|
||||
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
|
||||
for (int i = 0; i < 128; i += 16) {
|
||||
printf("%02x: ", i);
|
||||
for (int j = 0; j < 16; j++) {
|
||||
fflush(stdout);
|
||||
address = i + j;
|
||||
esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
|
||||
if (ret == ESP_OK) {
|
||||
printf("%02x ", address);
|
||||
} else if (ret == ESP_ERR_TIMEOUT) {
|
||||
printf("UU ");
|
||||
} else {
|
||||
printf("-- ");
|
||||
}
|
||||
}
|
||||
printf("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
}
|
||||
public:
|
||||
AtomEchoS3rBaseBoard() : boot_button_(USER_BUTTON_GPIO) {
|
||||
InitializeI2c();
|
||||
I2cDetect();
|
||||
InitializeButtons();
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static Es8311AudioCodec audio_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_GPIO_PA,
|
||||
AUDIO_CODEC_ES8311_ADDR,
|
||||
false);
|
||||
return &audio_codec;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(AtomEchoS3rBaseBoard);
|
||||
29
main/boards/atom-echos3r/config.h
Normal file
29
main/boards/atom-echos3r/config.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
// AtomEchoS3R Board configuration
|
||||
|
||||
#include <driver/gpio.h>
|
||||
|
||||
#define AUDIO_INPUT_REFERENCE true
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 24000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
|
||||
|
||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_11
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_3
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_17
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_4
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_48
|
||||
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_45
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_0
|
||||
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
|
||||
#define AUDIO_CODEC_GPIO_PA GPIO_NUM_18
|
||||
|
||||
#define BUILTIN_LED_GPIO GPIO_NUM_NC
|
||||
#define USER_BUTTON_GPIO GPIO_NUM_41
|
||||
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
|
||||
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
|
||||
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
12
main/boards/atom-echos3r/config.json
Normal file
12
main/boards/atom-echos3r/config.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "atom-echos3r",
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
|
||||
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/8m.csv\""
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -111,7 +111,7 @@ private:
|
||||
InitializeButtons();
|
||||
GetBacklight()->SetBrightness(100);
|
||||
display_->SetStatus(Lang::Strings::ERROR);
|
||||
display_->SetEmotion("sad");
|
||||
display_->SetEmotion("triangle_exclamation");
|
||||
display_->SetChatMessage("system", "Echo Base\nnot connected");
|
||||
|
||||
while (1) {
|
||||
|
||||
@@ -177,7 +177,7 @@ private:
|
||||
InitializeButtons();
|
||||
GetBacklight()->SetBrightness(100);
|
||||
display_->SetStatus(Lang::Strings::ERROR);
|
||||
display_->SetEmotion("sad");
|
||||
display_->SetEmotion("triangle_exclamation");
|
||||
display_->SetChatMessage("system", "Echo Base\nnot connected");
|
||||
|
||||
while (1) {
|
||||
|
||||
@@ -28,7 +28,7 @@ Esp32Camera::Esp32Camera(const camera_config_t& config) {
|
||||
memset(&preview_image_, 0, sizeof(preview_image_));
|
||||
preview_image_.header.magic = LV_IMAGE_HEADER_MAGIC;
|
||||
preview_image_.header.cf = LV_COLOR_FORMAT_RGB565;
|
||||
preview_image_.header.flags = LV_IMAGE_FLAGS_ALLOCATED | LV_IMAGE_FLAGS_MODIFIABLE;
|
||||
preview_image_.header.flags = 0;
|
||||
|
||||
switch (config.frame_size) {
|
||||
case FRAMESIZE_SVGA:
|
||||
@@ -89,6 +89,7 @@ bool Esp32Camera::Capture() {
|
||||
encoder_thread_.join();
|
||||
}
|
||||
|
||||
auto start_time = esp_timer_get_time();
|
||||
int frames_to_get = 2;
|
||||
// Try to get a stable frame
|
||||
for (int i = 0; i < frames_to_get; i++) {
|
||||
@@ -101,6 +102,8 @@ bool Esp32Camera::Capture() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
auto end_time = esp_timer_get_time();
|
||||
ESP_LOGI(TAG, "Camera captured %d frames in %d ms", frames_to_get, int((end_time - start_time) / 1000));
|
||||
|
||||
// 如果预览图片 buffer 为空,则跳过预览
|
||||
// 但仍返回 true,因为此时图像可以上传至服务器
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
#include "application.h"
|
||||
#include "display.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
#include "assets/lang_config.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_timer.h>
|
||||
#include <font_awesome.h>
|
||||
#include <opus_encoder.h>
|
||||
|
||||
static const char *TAG = "Ml307Board";
|
||||
@@ -50,9 +50,9 @@ void Ml307Board::StartNetwork() {
|
||||
while (true) {
|
||||
auto result = modem_->WaitForNetworkReady();
|
||||
if (result == NetworkStatus::ErrorInsertPin) {
|
||||
application.Alert(Lang::Strings::ERROR, Lang::Strings::PIN_ERROR, "sad", Lang::Sounds::OGG_ERR_PIN);
|
||||
application.Alert(Lang::Strings::ERROR, Lang::Strings::PIN_ERROR, "triangle_exclamation", Lang::Sounds::OGG_ERR_PIN);
|
||||
} else if (result == NetworkStatus::ErrorRegistrationDenied) {
|
||||
application.Alert(Lang::Strings::ERROR, Lang::Strings::REG_ERROR, "sad", Lang::Sounds::OGG_ERR_REG);
|
||||
application.Alert(Lang::Strings::ERROR, Lang::Strings::REG_ERROR, "triangle_exclamation", Lang::Sounds::OGG_ERR_REG);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -80,13 +80,13 @@ const char* Ml307Board::GetNetworkStateIcon() {
|
||||
if (csq == -1) {
|
||||
return FONT_AWESOME_SIGNAL_OFF;
|
||||
} else if (csq >= 0 && csq <= 14) {
|
||||
return FONT_AWESOME_SIGNAL_1;
|
||||
return FONT_AWESOME_SIGNAL_WEAK;
|
||||
} else if (csq >= 15 && csq <= 19) {
|
||||
return FONT_AWESOME_SIGNAL_2;
|
||||
return FONT_AWESOME_SIGNAL_FAIR;
|
||||
} else if (csq >= 20 && csq <= 24) {
|
||||
return FONT_AWESOME_SIGNAL_3;
|
||||
return FONT_AWESOME_SIGNAL_GOOD;
|
||||
} else if (csq >= 25 && csq <= 31) {
|
||||
return FONT_AWESOME_SIGNAL_4;
|
||||
return FONT_AWESOME_SIGNAL_STRONG;
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG, "Invalid CSQ: %d", csq);
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "display.h"
|
||||
#include "application.h"
|
||||
#include "system_info.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
#include "settings.h"
|
||||
#include "assets/lang_config.h"
|
||||
|
||||
@@ -12,6 +11,7 @@
|
||||
#include <esp_network.h>
|
||||
#include <esp_log.h>
|
||||
|
||||
#include <font_awesome.h>
|
||||
#include <wifi_station.h>
|
||||
#include <wifi_configuration_ap.h>
|
||||
#include <ssid_manager.h>
|
||||
@@ -49,7 +49,7 @@ void WifiBoard::EnterWifiConfigMode() {
|
||||
hint += "\n\n";
|
||||
|
||||
// 播报配置 WiFi 的提示
|
||||
application.Alert(Lang::Strings::WIFI_CONFIG_MODE, hint.c_str(), "", Lang::Sounds::OGG_WIFICONFIG);
|
||||
application.Alert(Lang::Strings::WIFI_CONFIG_MODE, hint.c_str(), "gear", Lang::Sounds::OGG_WIFICONFIG);
|
||||
|
||||
#if CONFIG_USE_ACOUSTIC_WIFI_PROVISIONING
|
||||
auto display = Board::GetInstance().GetDisplay();
|
||||
@@ -124,7 +124,7 @@ const char* WifiBoard::GetNetworkStateIcon() {
|
||||
}
|
||||
auto& wifi_station = WifiStation::GetInstance();
|
||||
if (!wifi_station.IsConnected()) {
|
||||
return FONT_AWESOME_WIFI_OFF;
|
||||
return FONT_AWESOME_WIFI_SLASH;
|
||||
}
|
||||
int8_t rssi = wifi_station.GetRssi();
|
||||
if (rssi >= -60) {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "display/lcd_display.h"
|
||||
#include "esp_lcd_ili9341.h"
|
||||
#include "led_control.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
{
|
||||
"name": "echoear",
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/16m_echoear.csv\""
|
||||
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/16m_echoear.csv\"",
|
||||
"CONFIG_USE_DEVICE_AEC=y"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#include "electron_emoji_display.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <font_awesome.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "font_awesome_symbols.h"
|
||||
|
||||
#define TAG "ElectronEmojiDisplay"
|
||||
|
||||
@@ -141,7 +141,7 @@ void ElectronEmojiDisplay::SetChatMessage(const char* role, const char* content)
|
||||
}
|
||||
|
||||
lv_label_set_text(chat_message_label_, content);
|
||||
lv_obj_clear_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_remove_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
|
||||
ESP_LOGI(TAG, "设置聊天消息 [%s]: %s", role, content);
|
||||
}
|
||||
@@ -163,7 +163,7 @@ void ElectronEmojiDisplay::SetIcon(const char* icon) {
|
||||
}
|
||||
|
||||
lv_label_set_text(chat_message_label_, icon_message.c_str());
|
||||
lv_obj_clear_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_remove_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
|
||||
ESP_LOGI(TAG, "设置图标: %s", icon);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "codecs/box_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "esp_lcd_ili9341.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "box_audio_codec_lite.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "esp_lcd_ili9341.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "codecs/box_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "esp_lcd_ili9341.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
|
||||
@@ -141,8 +141,7 @@ void AdcPdmAudioCodec::EnableInput(bool enable) {
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
|
||||
} else {
|
||||
// ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
|
||||
return;
|
||||
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
|
||||
}
|
||||
AudioCodec::EnableInput(enable);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <esp_log.h>
|
||||
#include "mmap_generate_emoji.h"
|
||||
#include "emoji_display.h"
|
||||
#include "assets/lang_config.h"
|
||||
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
@@ -146,9 +147,9 @@ void EmojiWidget::SetEmotion(const char* emotion)
|
||||
void EmojiWidget::SetStatus(const char* status)
|
||||
{
|
||||
if (player_) {
|
||||
if (strcmp(status, "聆听中...") == 0) {
|
||||
if (strcmp(status, Lang::Strings::LISTENING) == 0) {
|
||||
player_->StartPlayer(MMAP_EMOJI_ASKING_AAF, true, 15);
|
||||
} else if (strcmp(status, "待命") == 0) {
|
||||
} else if (strcmp(status, Lang::Strings::STANDBY) == 0) {
|
||||
player_->StartPlayer(MMAP_EMOJI_WAKE_AAF, true, 15);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "servo_dog_ctrl.h"
|
||||
#include "led_strip.h"
|
||||
#include "driver/rmt_tx.h"
|
||||
#include "device_state_event.h"
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
@@ -284,13 +285,14 @@ private:
|
||||
ESP_LOGI(TAG, "Create emoji widget, panel: %p, panel_io: %p", panel, panel_io);
|
||||
display_ = new anim::EmojiWidget(panel, panel_io);
|
||||
|
||||
#if CONFIG_ESP_CONSOLE_NONE
|
||||
servo_dog_ctrl_config_t config = {
|
||||
.fl_gpio_num = FL_GPIO_NUM,
|
||||
.fr_gpio_num = FR_GPIO_NUM,
|
||||
.bl_gpio_num = BL_GPIO_NUM,
|
||||
.br_gpio_num = BR_GPIO_NUM,
|
||||
};
|
||||
#if CONFIG_ESP_CONSOLE_NONE
|
||||
|
||||
servo_dog_ctrl_init(&config);
|
||||
#endif
|
||||
}
|
||||
@@ -378,7 +380,7 @@ private:
|
||||
int r = properties["r"].value<int>();
|
||||
int g = properties["g"].value<int>();
|
||||
int b = properties["b"].value<int>();
|
||||
|
||||
|
||||
led_on_ = true;
|
||||
SetLedColor(r, g, b);
|
||||
return true;
|
||||
@@ -395,6 +397,11 @@ public:
|
||||
InitializeSpi();
|
||||
InitializeLcdDisplay();
|
||||
InitializeTools();
|
||||
|
||||
DeviceStateEventManager::GetInstance().RegisterStateChangeCallback([this](DeviceState previous_state, DeviceState current_state) {
|
||||
ESP_LOGD(TAG, "Device state changed from %d to %d", previous_state, current_state);
|
||||
this->GetAudioCodec()->EnableOutput(current_state == kDeviceStateSpeaking);
|
||||
});
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "wifi_board.h"
|
||||
#include "codecs/es8311_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "wifi_board.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "esp_lcd_sh8601.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
|
||||
#include "codecs/es8311_audio_codec.h"
|
||||
#include "application.h"
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
|
||||
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
|
||||
|
||||
#ifdef CONFIG_LCD_ST7789
|
||||
#ifdef CONFIG_ESP32S3_KORVO2_V3_LCD_ST7789
|
||||
#define DISPLAY_SDA_PIN GPIO_NUM_NC
|
||||
#define DISPLAY_SCL_PIN GPIO_NUM_NC
|
||||
#define DISPLAY_WIDTH 280
|
||||
@@ -40,7 +40,7 @@
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LCD_ILI9341
|
||||
#ifdef CONFIG_ESP32S3_KORVO2_V3_LCD_ILI9341
|
||||
#define LCD_TYPE_ILI9341_SERIAL
|
||||
#define DISPLAY_SDA_PIN GPIO_NUM_NC
|
||||
#define DISPLAY_SCL_PIN GPIO_NUM_NC
|
||||
@@ -78,4 +78,4 @@
|
||||
#define CAMERA_PIN_PCLK 11
|
||||
|
||||
#define XCLK_FREQ_HZ 20000000
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
@@ -64,7 +64,7 @@ CoreS3AudioCodec::CoreS3AudioCodec(void* i2c_master_handle, int input_sample_rat
|
||||
|
||||
es7210_codec_cfg_t es7210_cfg = {};
|
||||
es7210_cfg.ctrl_if = in_ctrl_if_;
|
||||
es7210_cfg.mic_selected = ES7120_SEL_MIC1 | ES7120_SEL_MIC2 | ES7120_SEL_MIC3;
|
||||
es7210_cfg.mic_selected = ES7210_SEL_MIC1 | ES7210_SEL_MIC2 | ES7210_SEL_MIC3;
|
||||
in_codec_if_ = es7210_codec_new(&es7210_cfg);
|
||||
assert(in_codec_if_ != NULL);
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "tab5_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "esp_lcd_ili9881c.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
#include "font_emoji.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
|
||||
@@ -66,7 +66,7 @@ Tab5AudioCodec::Tab5AudioCodec(void* i2c_master_handle, int input_sample_rate, i
|
||||
|
||||
es7210_codec_cfg_t es7210_cfg = {};
|
||||
es7210_cfg.ctrl_if = in_ctrl_if_;
|
||||
es7210_cfg.mic_selected = ES7120_SEL_MIC1 | ES7120_SEL_MIC2 | ES7120_SEL_MIC3 | ES7120_SEL_MIC4;
|
||||
es7210_cfg.mic_selected = ES7210_SEL_MIC1 | ES7210_SEL_MIC2 | ES7210_SEL_MIC3 | ES7210_SEL_MIC4;
|
||||
in_codec_if_ = es7210_codec_new(&es7210_cfg);
|
||||
assert(in_codec_if_ != NULL);
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "button.h"
|
||||
#include "led/circular_strip.h"
|
||||
#include "config.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
#include "assets/lang_config.h"
|
||||
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "led/circular_strip.h"
|
||||
#include "config.h"
|
||||
#include "assets/lang_config.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "led/single_led.h"
|
||||
#include "config.h"
|
||||
#include "power_save_timer.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "led/single_led.h"
|
||||
#include "config.h"
|
||||
#include "power_save_timer.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
#include "otto_emoji_display.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <font_awesome.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "display/lcd_display.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
|
||||
#define TAG "OttoEmojiDisplay"
|
||||
|
||||
@@ -142,7 +142,7 @@ void OttoEmojiDisplay::SetChatMessage(const char* role, const char* content) {
|
||||
}
|
||||
|
||||
lv_label_set_text(chat_message_label_, content);
|
||||
lv_obj_clear_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_remove_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
|
||||
ESP_LOGI(TAG, "设置聊天消息 [%s]: %s", role, content);
|
||||
}
|
||||
@@ -164,7 +164,7 @@ void OttoEmojiDisplay::SetIcon(const char* icon) {
|
||||
}
|
||||
|
||||
lv_label_set_text(chat_message_label_, icon_message.c_str());
|
||||
lv_obj_clear_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_remove_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
|
||||
ESP_LOGI(TAG, "设置图标: %s", icon);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "wifi_board.h"
|
||||
#include "sensecap_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
#include "application.h"
|
||||
#include "knob.h"
|
||||
#include "config.h"
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
|
||||
#define TAG "SURFERC3114TFT"
|
||||
|
||||
LV_FONT_DECLARE(font_puhui_16_4);
|
||||
LV_FONT_DECLARE(font_awesome_16_4);
|
||||
LV_FONT_DECLARE(font_puhui_20_4);
|
||||
LV_FONT_DECLARE(font_awesome_20_4);
|
||||
|
||||
class SurferC3114TFT : public WifiBoard {
|
||||
private:
|
||||
@@ -148,8 +148,8 @@ private:
|
||||
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_16_4,
|
||||
.icon_font = &font_awesome_16_4,
|
||||
.text_font = &font_puhui_20_4,
|
||||
.icon_font = &font_awesome_20_4,
|
||||
.emoji_font = font_emoji_32_init(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "power_save_timer.h"
|
||||
#include "axp2101.h"
|
||||
#include "assets/lang_config.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <driver/gpio.h>
|
||||
|
||||
3
main/boards/waveshare-s3-audio-board/README.md
Normal file
3
main/boards/waveshare-s3-audio-board/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
新增 微雪 开发板: ESP32-S3-AUDIO-Board
|
||||
产品链接:
|
||||
https://www.waveshare.net/shop/ESP32-S3-AUDIO-Board.htm
|
||||
95
main/boards/waveshare-s3-audio-board/config.h
Normal file
95
main/boards/waveshare-s3-audio-board/config.h
Normal file
@@ -0,0 +1,95 @@
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/spi_master.h>
|
||||
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 24000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
|
||||
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_0
|
||||
#define BUILTIN_LED_GPIO GPIO_NUM_38
|
||||
|
||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_12
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_13
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_14
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_15
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_16
|
||||
#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC
|
||||
#define AUDIO_INPUT_REFERENCE true
|
||||
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
|
||||
#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
|
||||
|
||||
#define I2C_SCL_IO GPIO_NUM_10
|
||||
#define I2C_SDA_IO GPIO_NUM_11
|
||||
|
||||
#define I2C_ADDRESS ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000
|
||||
|
||||
#define DISPLAY_SDA_PIN I2C_SDA_IO
|
||||
#define DISPLAY_SCL_PIN I2C_SCL_IO
|
||||
|
||||
#define DISPLAY_MISO_PIN GPIO_NUM_8
|
||||
#define DISPLAY_MOSI_PIN GPIO_NUM_9
|
||||
#define DISPLAY_SCLK_PIN GPIO_NUM_4
|
||||
#define DISPLAY_CS_PIN GPIO_NUM_3
|
||||
#define DISPLAY_DC_PIN GPIO_NUM_7
|
||||
#define DISPLAY_RESET_PIN GPIO_NUM_NC
|
||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_5
|
||||
|
||||
#define DISPLAY_SPI_SCLK_HZ (20 * 1000 * 1000)
|
||||
|
||||
/* Camera pins */
|
||||
#define CAMERA_PIN_PWDN -1
|
||||
#define CAMERA_PIN_RESET -1
|
||||
#define CAMERA_PIN_XCLK 43
|
||||
#define CAMERA_PIN_SIOD -1
|
||||
#define CAMERA_PIN_SIOC -1
|
||||
|
||||
#define CAMERA_PIN_D7 48
|
||||
#define CAMERA_PIN_D6 47
|
||||
#define CAMERA_PIN_D5 46
|
||||
#define CAMERA_PIN_D4 45
|
||||
#define CAMERA_PIN_D3 39
|
||||
#define CAMERA_PIN_D2 18
|
||||
#define CAMERA_PIN_D1 17
|
||||
#define CAMERA_PIN_D0 2
|
||||
#define CAMERA_PIN_VSYNC 21
|
||||
#define CAMERA_PIN_HREF 1
|
||||
#define CAMERA_PIN_PCLK 44
|
||||
|
||||
#define XCLK_FREQ_HZ 20000000
|
||||
|
||||
|
||||
|
||||
|
||||
#ifdef CONFIG_AUDIO_BOARD_LCD_JD9853
|
||||
#define LCD_TYPE_JD9853_SERIAL
|
||||
#define DISPLAY_WIDTH 320
|
||||
#define DISPLAY_HEIGHT 172
|
||||
|
||||
#define DISPLAY_SWAP_XY true
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y true
|
||||
#define DISPLAY_INVERT_COLOR true
|
||||
#define BACKLIGHT_INVERT false
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_AUDIO_BOARD_LCD_ST7789
|
||||
#define LCD_TYPE_ST7789_SERIAL
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 320
|
||||
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_INVERT_COLOR true
|
||||
#define BACKLIGHT_INVERT false
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
9
main/boards/waveshare-s3-audio-board/config.json
Normal file
9
main/boards/waveshare-s3-audio-board/config.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "waveshare-s3-audio-board",
|
||||
"sdkconfig_append": []
|
||||
}
|
||||
]
|
||||
}
|
||||
247
main/boards/waveshare-s3-audio-board/esp32-s3-audio_board.cc
Normal file
247
main/boards/waveshare-s3-audio-board/esp32-s3-audio_board.cc
Normal file
@@ -0,0 +1,247 @@
|
||||
#include "wifi_board.h"
|
||||
#include "codecs/box_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "system_reset.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include "i2c_device.h"
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/ledc.h>
|
||||
#include <wifi_station.h>
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
#include <esp_lcd_st77916.h>
|
||||
#include <esp_timer.h>
|
||||
#include "esp_io_expander_tca95xx_16bit.h"
|
||||
#include "esp32_camera.h"
|
||||
#include "led/circular_strip.h"
|
||||
#include "esp_lcd_jd9853.h"
|
||||
|
||||
#define TAG "waveshare_lcd_1_85c"
|
||||
|
||||
#define LCD_OPCODE_WRITE_CMD (0x02ULL)
|
||||
#define LCD_OPCODE_READ_CMD (0x0BULL)
|
||||
#define LCD_OPCODE_WRITE_COLOR (0x32ULL)
|
||||
|
||||
LV_FONT_DECLARE(font_puhui_14_1);
|
||||
LV_FONT_DECLARE(font_awesome_14_1);
|
||||
|
||||
|
||||
|
||||
|
||||
class CustomBoard : public WifiBoard {
|
||||
private:
|
||||
Button boot_button_;
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
esp_io_expander_handle_t io_expander = NULL;
|
||||
LcdDisplay* display_;
|
||||
Esp32Camera* camera_;
|
||||
|
||||
void InitializeI2c() {
|
||||
// Initialize I2C peripheral
|
||||
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||
.i2c_port = (i2c_port_t)0,
|
||||
.sda_io_num = I2C_SDA_IO,
|
||||
.scl_io_num = I2C_SCL_IO,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
|
||||
}
|
||||
|
||||
void InitializeTca9555(void)
|
||||
{
|
||||
esp_err_t ret = esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus_, I2C_ADDRESS, &io_expander);
|
||||
if(ret != ESP_OK)
|
||||
ESP_LOGE(TAG, "TCA9554 create returned error"); // 打印引脚状态
|
||||
|
||||
ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 | IO_EXPANDER_PIN_NUM_8|IO_EXPANDER_PIN_NUM_5|IO_EXPANDER_PIN_NUM_6, IO_EXPANDER_OUTPUT); // 设置引脚 EXIO0 和 EXIO1 模式为输出
|
||||
ESP_ERROR_CHECK(ret);
|
||||
ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 复位 LCD 与 TouchPad
|
||||
ESP_ERROR_CHECK(ret);
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 0); // 复位 LCD 与 TouchPad
|
||||
ESP_ERROR_CHECK(ret);
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 复位 LCD 与 TouchPad
|
||||
ESP_ERROR_CHECK(ret);
|
||||
ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_8, 1); // 启用喇叭功放
|
||||
ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_5, false); // 复位摄像头
|
||||
vTaskDelay(pdMS_TO_TICKS(5));
|
||||
ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_6, true);
|
||||
vTaskDelay(pdMS_TO_TICKS(5));
|
||||
ESP_ERROR_CHECK(ret);
|
||||
}
|
||||
|
||||
void InitializeSpi() {
|
||||
spi_bus_config_t buscfg = {};
|
||||
buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
|
||||
buscfg.miso_io_num = GPIO_NUM_NC;
|
||||
buscfg.sclk_io_num = DISPLAY_SCLK_PIN;
|
||||
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(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
||||
}
|
||||
|
||||
void InitializeSt7789Display() {
|
||||
esp_lcd_panel_io_handle_t panel_io = nullptr;
|
||||
esp_lcd_panel_handle_t panel = nullptr;
|
||||
// 液晶屏控制IO初始化
|
||||
ESP_LOGD(TAG, "Install panel IO");
|
||||
esp_lcd_panel_io_spi_config_t io_config = {};
|
||||
io_config.cs_gpio_num = DISPLAY_CS_PIN;
|
||||
io_config.dc_gpio_num = DISPLAY_DC_PIN;
|
||||
io_config.spi_mode = 0;
|
||||
io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ;
|
||||
io_config.trans_queue_depth = 10;
|
||||
io_config.lcd_cmd_bits = 8;
|
||||
io_config.lcd_param_bits = 8;
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io));
|
||||
|
||||
// 初始化液晶屏驱动芯片ST7789
|
||||
ESP_LOGD(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_ELEMENT_ORDER_RGB;
|
||||
panel_config.bits_per_pixel = 16;
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR));
|
||||
|
||||
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_14_1,
|
||||
.icon_font = &font_awesome_14_1,
|
||||
.emoji_font = font_emoji_32_init(),
|
||||
});
|
||||
}
|
||||
|
||||
void InitializeJd9853Display() {
|
||||
esp_lcd_panel_io_handle_t panel_io = nullptr;
|
||||
esp_lcd_panel_handle_t panel = nullptr;
|
||||
// 液晶屏控制IO初始化
|
||||
ESP_LOGD(TAG, "Install panel IO");
|
||||
esp_lcd_panel_io_spi_config_t io_config = {};
|
||||
io_config.cs_gpio_num = DISPLAY_CS_PIN;
|
||||
io_config.dc_gpio_num = DISPLAY_DC_PIN;
|
||||
io_config.spi_mode = 0;
|
||||
io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ;
|
||||
io_config.trans_queue_depth = 10;
|
||||
io_config.lcd_cmd_bits = 8;
|
||||
io_config.lcd_param_bits = 8;
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io));
|
||||
|
||||
// 初始化液晶屏驱动芯片JD9853
|
||||
ESP_LOGD(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_ELEMENT_ORDER_RGB;
|
||||
panel_config.bits_per_pixel = 16;
|
||||
//ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_jd9853(panel_io, &panel_config, &panel));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel, 0, 34));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, true, false));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, true));
|
||||
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_14_1,
|
||||
.icon_font = &font_awesome_14_1,
|
||||
.emoji_font = font_emoji_32_init(),
|
||||
});
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
}
|
||||
|
||||
void InitializeCamera() {
|
||||
camera_config_t config = {};
|
||||
config.ledc_channel = LEDC_CHANNEL_2; // LEDC通道选择 用于生成XCLK时钟 但是S3不用
|
||||
config.ledc_timer = LEDC_TIMER_2; // LEDC timer选择 用于生成XCLK时钟 但是S3不用
|
||||
config.pin_d0 = CAMERA_PIN_D0;
|
||||
config.pin_d1 = CAMERA_PIN_D1;
|
||||
config.pin_d2 = CAMERA_PIN_D2;
|
||||
config.pin_d3 = CAMERA_PIN_D3;
|
||||
config.pin_d4 = CAMERA_PIN_D4;
|
||||
config.pin_d5 = CAMERA_PIN_D5;
|
||||
config.pin_d6 = CAMERA_PIN_D6;
|
||||
config.pin_d7 = CAMERA_PIN_D7;
|
||||
config.pin_xclk = CAMERA_PIN_XCLK;
|
||||
config.pin_pclk = CAMERA_PIN_PCLK;
|
||||
config.pin_vsync = CAMERA_PIN_VSYNC;
|
||||
config.pin_href = CAMERA_PIN_HREF;
|
||||
config.pin_sccb_sda = CAMERA_PIN_SIOD; // 这里如果写-1 表示使用已经初始化的I2C接口
|
||||
config.pin_sccb_scl = CAMERA_PIN_SIOC;
|
||||
config.sccb_i2c_port = 0; // 这里如果写1 默认使用I2C1
|
||||
config.pin_pwdn = CAMERA_PIN_PWDN;
|
||||
config.pin_reset = CAMERA_PIN_RESET;
|
||||
config.xclk_freq_hz = XCLK_FREQ_HZ;
|
||||
config.pixel_format = PIXFORMAT_RGB565;
|
||||
config.frame_size = FRAMESIZE_QVGA;
|
||||
config.jpeg_quality = 12;
|
||||
config.fb_count = 1;
|
||||
config.fb_location = CAMERA_FB_IN_PSRAM;
|
||||
config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
|
||||
|
||||
camera_ = new Esp32Camera(config);
|
||||
camera_->SetVFlip(1);
|
||||
}
|
||||
public:
|
||||
CustomBoard() :
|
||||
boot_button_(BOOT_BUTTON_GPIO) {
|
||||
InitializeI2c();
|
||||
InitializeTca9555();
|
||||
InitializeSpi();
|
||||
InitializeButtons();
|
||||
#ifdef LCD_TYPE_JD9853_SERIAL
|
||||
InitializeJd9853Display();
|
||||
#else
|
||||
InitializeSt7789Display();
|
||||
#endif
|
||||
InitializeCamera();
|
||||
GetBacklight()->RestoreBrightness();
|
||||
}
|
||||
|
||||
virtual Led* GetLed() override {
|
||||
static CircularStrip led(BUILTIN_LED_GPIO, 6);
|
||||
return &led;
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static BoxAudioCodec audio_codec(i2c_bus_, 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, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
return display_;
|
||||
}
|
||||
|
||||
virtual Backlight* GetBacklight() override {
|
||||
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, BACKLIGHT_INVERT);
|
||||
return &backlight;
|
||||
}
|
||||
|
||||
virtual Camera* GetCamera() override {
|
||||
return camera_;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(CustomBoard);
|
||||
460
main/boards/waveshare-s3-audio-board/esp_lcd_jd9853.c
Normal file
460
main/boards/waveshare-s3-audio-board/esp_lcd_jd9853.c
Normal file
@@ -0,0 +1,460 @@
|
||||
#include <stdio.h>
|
||||
#include "esp_lcd_jd9853.h"
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_lcd_panel_interface.h"
|
||||
#include "esp_lcd_panel_io.h"
|
||||
#include "esp_lcd_panel_vendor.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
#include "esp_lcd_panel_commands.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
|
||||
static const char *TAG = "JD9853";
|
||||
|
||||
static esp_err_t panel_jd9853_del(esp_lcd_panel_t *panel);
|
||||
static esp_err_t panel_jd9853_reset(esp_lcd_panel_t *panel);
|
||||
static esp_err_t panel_jd9853_init(esp_lcd_panel_t *panel);
|
||||
static esp_err_t panel_jd9853_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data);
|
||||
static esp_err_t panel_jd9853_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
|
||||
static esp_err_t panel_jd9853_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
|
||||
static esp_err_t panel_jd9853_swap_xy(esp_lcd_panel_t *panel, bool swap_axes);
|
||||
static esp_err_t panel_jd9853_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap);
|
||||
static esp_err_t panel_jd9853_disp_on_off(esp_lcd_panel_t *panel, bool off);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
esp_lcd_panel_t base;
|
||||
esp_lcd_panel_io_handle_t io;
|
||||
int reset_gpio_num;
|
||||
bool reset_level;
|
||||
int x_gap;
|
||||
int y_gap;
|
||||
uint8_t fb_bits_per_pixel;
|
||||
uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register
|
||||
uint8_t colmod_val; // save current value of LCD_CMD_COLMOD register
|
||||
const jd9853_lcd_init_cmd_t *init_cmds;
|
||||
uint16_t init_cmds_size;
|
||||
} jd9853_panel_t;
|
||||
|
||||
esp_err_t esp_lcd_new_panel_jd9853(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
jd9853_panel_t *jd9853 = NULL;
|
||||
gpio_config_t io_conf = {0};
|
||||
|
||||
ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
||||
jd9853 = (jd9853_panel_t *)calloc(1, sizeof(jd9853_panel_t));
|
||||
ESP_GOTO_ON_FALSE(jd9853, ESP_ERR_NO_MEM, err, TAG, "no mem for jd9853 panel");
|
||||
|
||||
if (panel_dev_config->reset_gpio_num >= 0)
|
||||
{
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
io_conf.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num;
|
||||
ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed");
|
||||
}
|
||||
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
switch (panel_dev_config->color_space)
|
||||
{
|
||||
case ESP_LCD_COLOR_SPACE_RGB:
|
||||
jd9853->madctl_val = 0;
|
||||
break;
|
||||
case ESP_LCD_COLOR_SPACE_BGR:
|
||||
jd9853->madctl_val |= LCD_CMD_BGR_BIT;
|
||||
break;
|
||||
default:
|
||||
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color space");
|
||||
break;
|
||||
}
|
||||
#else
|
||||
switch (panel_dev_config->rgb_endian)
|
||||
{
|
||||
case LCD_RGB_ENDIAN_RGB:
|
||||
jd9853->madctl_val = 0;
|
||||
break;
|
||||
case LCD_RGB_ENDIAN_BGR:
|
||||
jd9853->madctl_val |= LCD_CMD_BGR_BIT;
|
||||
break;
|
||||
default:
|
||||
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported rgb endian");
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
switch (panel_dev_config->bits_per_pixel)
|
||||
{
|
||||
case 16: // RGB565
|
||||
jd9853->colmod_val = 0x55;
|
||||
jd9853->fb_bits_per_pixel = 16;
|
||||
break;
|
||||
case 18: // RGB666
|
||||
jd9853->colmod_val = 0x66;
|
||||
// each color component (R/G/B) should occupy the 6 high bits of a byte, which means 3 full bytes are required for a pixel
|
||||
jd9853->fb_bits_per_pixel = 24;
|
||||
break;
|
||||
default:
|
||||
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width");
|
||||
break;
|
||||
}
|
||||
|
||||
jd9853->io = io;
|
||||
jd9853->reset_gpio_num = panel_dev_config->reset_gpio_num;
|
||||
jd9853->reset_level = panel_dev_config->flags.reset_active_high;
|
||||
if (panel_dev_config->vendor_config)
|
||||
{
|
||||
jd9853->init_cmds = ((jd9853_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds;
|
||||
jd9853->init_cmds_size = ((jd9853_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds_size;
|
||||
}
|
||||
jd9853->base.del = panel_jd9853_del;
|
||||
jd9853->base.reset = panel_jd9853_reset;
|
||||
jd9853->base.init = panel_jd9853_init;
|
||||
jd9853->base.draw_bitmap = panel_jd9853_draw_bitmap;
|
||||
jd9853->base.invert_color = panel_jd9853_invert_color;
|
||||
jd9853->base.set_gap = panel_jd9853_set_gap;
|
||||
jd9853->base.mirror = panel_jd9853_mirror;
|
||||
jd9853->base.swap_xy = panel_jd9853_swap_xy;
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
jd9853->base.disp_off = panel_jd9853_disp_on_off;
|
||||
#else
|
||||
jd9853->base.disp_on_off = panel_jd9853_disp_on_off;
|
||||
#endif
|
||||
*ret_panel = &(jd9853->base);
|
||||
ESP_LOGD(TAG, "new jd9853 panel @%p", jd9853);
|
||||
|
||||
// ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_jd9853_VER_MAJOR, ESP_LCD_jd9853_VER_MINOR,
|
||||
// ESP_LCD_jd9853_VER_PATCH);
|
||||
|
||||
return ESP_OK;
|
||||
|
||||
err:
|
||||
if (jd9853)
|
||||
{
|
||||
if (panel_dev_config->reset_gpio_num >= 0)
|
||||
{
|
||||
gpio_reset_pin(panel_dev_config->reset_gpio_num);
|
||||
}
|
||||
free(jd9853);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t panel_jd9853_del(esp_lcd_panel_t *panel)
|
||||
{
|
||||
jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base);
|
||||
|
||||
if (jd9853->reset_gpio_num >= 0)
|
||||
{
|
||||
gpio_reset_pin(jd9853->reset_gpio_num);
|
||||
}
|
||||
ESP_LOGD(TAG, "del jd9853 panel @%p", jd9853);
|
||||
free(jd9853);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t panel_jd9853_reset(esp_lcd_panel_t *panel)
|
||||
{
|
||||
jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base);
|
||||
esp_lcd_panel_io_handle_t io = jd9853->io;
|
||||
|
||||
// perform hardware reset
|
||||
if (jd9853->reset_gpio_num >= 0)
|
||||
{
|
||||
gpio_set_level(jd9853->reset_gpio_num, jd9853->reset_level);
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
gpio_set_level(jd9853->reset_gpio_num, !jd9853->reset_level);
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
else
|
||||
{ // perform software reset
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed");
|
||||
vTaskDelay(pdMS_TO_TICKS(20)); // spec, wait at least 5ms before sending new command
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t cmd;
|
||||
uint8_t data[16];
|
||||
uint8_t data_bytes; // Length of data in above data array; 0xFF = end of cmds.
|
||||
} lcd_init_cmd_t;
|
||||
|
||||
// static const jd9853_lcd_init_cmd_t vendor_specific_init_default[] = {
|
||||
// // {cmd, { data }, data_size, delay_ms}
|
||||
// /* Power contorl B, power control = 0, DC_ENA = 1 */
|
||||
// {0xCF, (uint8_t []){0x00, 0xAA, 0XE0}, 3, 0},
|
||||
// /* Power on sequence control,
|
||||
// * cp1 keeps 1 frame, 1st frame enable
|
||||
// * vcl = 0, ddvdh=3, vgh=1, vgl=2
|
||||
// * DDVDH_ENH=1
|
||||
// */
|
||||
// {0xED, (uint8_t []){0x67, 0x03, 0X12, 0X81}, 4, 0},
|
||||
// /* Driver timing control A,
|
||||
// * non-overlap=default +1
|
||||
// * EQ=default - 1, CR=default
|
||||
// * pre-charge=default - 1
|
||||
// */
|
||||
// {0xE8, (uint8_t []){0x8A, 0x01, 0x78}, 3, 0},
|
||||
// /* Power control A, Vcore=1.6V, DDVDH=5.6V */
|
||||
// {0xCB, (uint8_t []){0x39, 0x2C, 0x00, 0x34, 0x02}, 5, 0},
|
||||
// /* Pump ratio control, DDVDH=2xVCl */
|
||||
// {0xF7, (uint8_t []){0x20}, 1, 0},
|
||||
|
||||
// {0xF7, (uint8_t []){0x20}, 1, 0},
|
||||
// /* Driver timing control, all=0 unit */
|
||||
// {0xEA, (uint8_t []){0x00, 0x00}, 2, 0},
|
||||
// /* Power control 1, GVDD=4.75V */
|
||||
// {0xC0, (uint8_t []){0x23}, 1, 0},
|
||||
// /* Power control 2, DDVDH=VCl*2, VGH=VCl*7, VGL=-VCl*3 */
|
||||
// {0xC1, (uint8_t []){0x11}, 1, 0},
|
||||
// /* VCOM control 1, VCOMH=4.025V, VCOML=-0.950V */
|
||||
// {0xC5, (uint8_t []){0x43, 0x4C}, 2, 0},
|
||||
// /* VCOM control 2, VCOMH=VMH-2, VCOML=VML-2 */
|
||||
// {0xC7, (uint8_t []){0xA0}, 1, 0},
|
||||
// /* Frame rate control, f=fosc, 70Hz fps */
|
||||
// {0xB1, (uint8_t []){0x00, 0x1B}, 2, 0},
|
||||
// /* Enable 3G, disabled */
|
||||
// {0xF2, (uint8_t []){0x00}, 1, 0},
|
||||
// /* Gamma set, curve 1 */
|
||||
// {0x26, (uint8_t []){0x01}, 1, 0},
|
||||
// /* Positive gamma correction */
|
||||
// {0xE0, (uint8_t []){0x1F, 0x36, 0x36, 0x3A, 0x0C, 0x05, 0x4F, 0X87, 0x3C, 0x08, 0x11, 0x35, 0x19, 0x13, 0x00}, 15, 0},
|
||||
// /* Negative gamma correction */
|
||||
// {0xE1, (uint8_t []){0x00, 0x09, 0x09, 0x05, 0x13, 0x0A, 0x30, 0x78, 0x43, 0x07, 0x0E, 0x0A, 0x26, 0x2C, 0x1F}, 15, 0},
|
||||
// /* Entry mode set, Low vol detect disabled, normal display */
|
||||
// {0xB7, (uint8_t []){0x07}, 1, 0},
|
||||
// /* Display function control */
|
||||
// {0xB6, (uint8_t []){0x08, 0x82, 0x27}, 3, 0},
|
||||
// };
|
||||
|
||||
static const jd9853_lcd_init_cmd_t vendor_specific_init_default[] = {
|
||||
{0x11, (uint8_t []){ 0x00 }, 0, 120},
|
||||
{0xDF, (uint8_t[]){0x98, 0x53}, 2, 0},
|
||||
{0xDF, (uint8_t[]){0x98, 0x53}, 2, 0},
|
||||
{0xB2, (uint8_t[]){0x23}, 1, 0},
|
||||
{0xB7, (uint8_t[]){0x00, 0x47, 0x00, 0x6F}, 4, 0},
|
||||
{0xBB, (uint8_t[]){0x1C, 0x1A, 0x55, 0x73, 0x63, 0xF0}, 6, 0},
|
||||
{0xC0, (uint8_t[]){0x44, 0xA4}, 2, 0},
|
||||
{0xC1, (uint8_t[]){0x16}, 1, 0},
|
||||
{0xC3, (uint8_t[]){0x7D, 0x07, 0x14, 0x06, 0xCF, 0x71, 0x72, 0x77}, 8, 0},
|
||||
{0xC4, (uint8_t[]){0x00, 0x00, 0xA0, 0x79, 0x0B, 0x0A, 0x16, 0x79, 0x0B, 0x0A, 0x16, 0x82}, 12, 0}, // 00=60Hz 06=57Hz 08=51Hz, LN=320 Line
|
||||
{0xC8, (uint8_t[]){0x3F, 0x32, 0x29, 0x29, 0x27, 0x2B, 0x27, 0x28, 0x28, 0x26, 0x25, 0x17, 0x12, 0x0D, 0x04, 0x00, 0x3F, 0x32, 0x29, 0x29, 0x27, 0x2B, 0x27, 0x28, 0x28, 0x26, 0x25, 0x17, 0x12, 0x0D, 0x04, 0x00}, 32, 0}, // SET_R_GAMMA
|
||||
{0xD0, (uint8_t[]){0x04, 0x06, 0x6B, 0x0F, 0x00}, 5, 0},
|
||||
{0xD7, (uint8_t[]){0x00, 0x30}, 2, 0},
|
||||
{0xE6, (uint8_t[]){0x14}, 1, 0},
|
||||
{0xDE, (uint8_t[]){0x01}, 1, 0},
|
||||
{0xB7, (uint8_t[]){0x03, 0x13, 0xEF, 0x35, 0x35}, 5, 0},
|
||||
{0xC1, (uint8_t[]){0x14, 0x15, 0xC0}, 3, 0},
|
||||
{0xC2, (uint8_t[]){0x06, 0x3A}, 2, 0},
|
||||
{0xC4, (uint8_t[]){0x72, 0x12}, 2, 0},
|
||||
{0xBE, (uint8_t[]){0x00}, 1, 0},
|
||||
{0xDE, (uint8_t[]){0x02}, 1, 0},
|
||||
{0xE5, (uint8_t[]){0x00, 0x02, 0x00}, 3, 0},
|
||||
{0xE5, (uint8_t[]){0x01, 0x02, 0x00}, 3, 0},
|
||||
{0xDE, (uint8_t[]){0x00}, 1, 0},
|
||||
{0x35, (uint8_t[]){0x00}, 1, 0},
|
||||
{0x3A, (uint8_t[]){0x05}, 1, 0}, // 06=RGB666;05=RGB565
|
||||
{0x2A, (uint8_t[]){0x00, 0x22, 0x00, 0xCD}, 4, 0}, // Start_X=34, End_X=205
|
||||
{0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0x3F}, 4, 0}, // Start_Y=0, End_Y=319
|
||||
{0xDE, (uint8_t[]){0x02}, 1, 0},
|
||||
{0xE5, (uint8_t[]){0x00, 0x02, 0x00}, 3, 0},
|
||||
{0xDE, (uint8_t[]){0x00}, 1, 0},
|
||||
{0x29, (uint8_t []){ 0x00 }, 0, 0},
|
||||
};
|
||||
|
||||
static esp_err_t panel_jd9853_init(esp_lcd_panel_t *panel)
|
||||
{
|
||||
jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base);
|
||||
esp_lcd_panel_io_handle_t io = jd9853->io;
|
||||
|
||||
// LCD goes into sleep mode and display will be turned off after power on reset, exit sleep mode first
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SLPOUT, NULL, 0), TAG, "send command failed");
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){
|
||||
jd9853->madctl_val,
|
||||
},
|
||||
1),
|
||||
TAG, "send command failed");
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]){
|
||||
jd9853->colmod_val,
|
||||
},
|
||||
1),
|
||||
TAG, "send command failed");
|
||||
|
||||
const jd9853_lcd_init_cmd_t *init_cmds = NULL;
|
||||
uint16_t init_cmds_size = 0;
|
||||
if (jd9853->init_cmds)
|
||||
{
|
||||
init_cmds = jd9853->init_cmds;
|
||||
init_cmds_size = jd9853->init_cmds_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
init_cmds = vendor_specific_init_default;
|
||||
init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(jd9853_lcd_init_cmd_t);
|
||||
}
|
||||
|
||||
bool is_cmd_overwritten = false;
|
||||
for (int i = 0; i < init_cmds_size; i++)
|
||||
{
|
||||
// Check if the command has been used or conflicts with the internal
|
||||
switch (init_cmds[i].cmd)
|
||||
{
|
||||
case LCD_CMD_MADCTL:
|
||||
is_cmd_overwritten = true;
|
||||
jd9853->madctl_val = ((uint8_t *)init_cmds[i].data)[0];
|
||||
break;
|
||||
case LCD_CMD_COLMOD:
|
||||
is_cmd_overwritten = true;
|
||||
jd9853->colmod_val = ((uint8_t *)init_cmds[i].data)[0];
|
||||
break;
|
||||
default:
|
||||
is_cmd_overwritten = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (is_cmd_overwritten)
|
||||
{
|
||||
ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", init_cmds[i].cmd);
|
||||
}
|
||||
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed");
|
||||
vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms));
|
||||
}
|
||||
ESP_LOGD(TAG, "send init commands success");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t panel_jd9853_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data)
|
||||
{
|
||||
jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base);
|
||||
assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position");
|
||||
esp_lcd_panel_io_handle_t io = jd9853->io;
|
||||
|
||||
x_start += jd9853->x_gap;
|
||||
x_end += jd9853->x_gap;
|
||||
y_start += jd9853->y_gap;
|
||||
y_end += jd9853->y_gap;
|
||||
|
||||
// define an area of frame memory where MCU can access
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_CASET, (uint8_t[]){
|
||||
(x_start >> 8) & 0xFF,
|
||||
x_start & 0xFF,
|
||||
((x_end - 1) >> 8) & 0xFF,
|
||||
(x_end - 1) & 0xFF,
|
||||
},
|
||||
4),
|
||||
TAG, "send command failed");
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_RASET, (uint8_t[]){
|
||||
(y_start >> 8) & 0xFF,
|
||||
y_start & 0xFF,
|
||||
((y_end - 1) >> 8) & 0xFF,
|
||||
(y_end - 1) & 0xFF,
|
||||
},
|
||||
4),
|
||||
TAG, "send command failed");
|
||||
// transfer frame buffer
|
||||
size_t len = (x_end - x_start) * (y_end - y_start) * jd9853->fb_bits_per_pixel / 8;
|
||||
esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR, color_data, len);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t panel_jd9853_invert_color(esp_lcd_panel_t *panel, bool invert_color_data)
|
||||
{
|
||||
jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base);
|
||||
esp_lcd_panel_io_handle_t io = jd9853->io;
|
||||
int command = 0;
|
||||
if (invert_color_data)
|
||||
{
|
||||
command = LCD_CMD_INVON;
|
||||
}
|
||||
else
|
||||
{
|
||||
command = LCD_CMD_INVOFF;
|
||||
}
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t panel_jd9853_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
|
||||
{
|
||||
jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base);
|
||||
esp_lcd_panel_io_handle_t io = jd9853->io;
|
||||
if (mirror_x)
|
||||
{
|
||||
jd9853->madctl_val |= LCD_CMD_MX_BIT;
|
||||
}
|
||||
else
|
||||
{
|
||||
jd9853->madctl_val &= ~LCD_CMD_MX_BIT;
|
||||
}
|
||||
if (mirror_y)
|
||||
{
|
||||
jd9853->madctl_val |= LCD_CMD_MY_BIT;
|
||||
}
|
||||
else
|
||||
{
|
||||
jd9853->madctl_val &= ~LCD_CMD_MY_BIT;
|
||||
}
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){jd9853->madctl_val}, 1), TAG, "send command failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t panel_jd9853_swap_xy(esp_lcd_panel_t *panel, bool swap_axes)
|
||||
{
|
||||
jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base);
|
||||
esp_lcd_panel_io_handle_t io = jd9853->io;
|
||||
if (swap_axes)
|
||||
{
|
||||
jd9853->madctl_val |= LCD_CMD_MV_BIT;
|
||||
}
|
||||
else
|
||||
{
|
||||
jd9853->madctl_val &= ~LCD_CMD_MV_BIT;
|
||||
}
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){jd9853->madctl_val}, 1), TAG, "send command failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t panel_jd9853_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap)
|
||||
{
|
||||
jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base);
|
||||
jd9853->x_gap = x_gap;
|
||||
jd9853->y_gap = y_gap;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t panel_jd9853_disp_on_off(esp_lcd_panel_t *panel, bool on_off)
|
||||
{
|
||||
jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base);
|
||||
esp_lcd_panel_io_handle_t io = jd9853->io;
|
||||
int command = 0;
|
||||
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
on_off = !on_off;
|
||||
#endif
|
||||
|
||||
if (on_off)
|
||||
{
|
||||
command = LCD_CMD_DISPON;
|
||||
}
|
||||
else
|
||||
{
|
||||
command = LCD_CMD_DISPOFF;
|
||||
}
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
102
main/boards/waveshare-s3-audio-board/esp_lcd_jd9853.h
Normal file
102
main/boards/waveshare-s3-audio-board/esp_lcd_jd9853.h
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
/**
|
||||
* @file
|
||||
* @brief ESP LCD: jd9853
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "hal/spi_ll.h"
|
||||
#include "esp_lcd_panel_vendor.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief LCD panel initialization commands.
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
int cmd; /*<! The specific LCD command */
|
||||
const void *data; /*<! Buffer that holds the command specific data */
|
||||
size_t data_bytes; /*<! Size of `data` in memory, in bytes */
|
||||
unsigned int delay_ms; /*<! Delay in milliseconds after this command */
|
||||
} jd9853_lcd_init_cmd_t;
|
||||
|
||||
/**
|
||||
* @brief LCD panel vendor configuration.
|
||||
*
|
||||
* @note This structure needs to be passed to the `vendor_config` field in `esp_lcd_panel_dev_config_t`.
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
const jd9853_lcd_init_cmd_t *init_cmds; /*!< Pointer to initialization commands array. Set to NULL if using default commands.
|
||||
* The array should be declared as `static const` and positioned outside the function.
|
||||
* Please refer to `vendor_specific_init_default` in source file.
|
||||
*/
|
||||
uint16_t init_cmds_size; /*<! Number of commands in above array */
|
||||
} jd9853_vendor_config_t;
|
||||
|
||||
/**
|
||||
* @brief Create LCD panel for model jd9853
|
||||
*
|
||||
* @note Vendor specific initialization can be different between manufacturers, should consult the LCD supplier for initialization sequence code.
|
||||
*
|
||||
* @param[in] io LCD panel IO handle
|
||||
* @param[in] panel_dev_config general panel device configuration
|
||||
* @param[out] ret_panel Returned LCD panel handle
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG if parameter is invalid
|
||||
* - ESP_ERR_NO_MEM if out of memory
|
||||
* - ESP_OK on success
|
||||
*/
|
||||
esp_err_t esp_lcd_new_panel_jd9853(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel);
|
||||
|
||||
/**
|
||||
* @brief LCD panel bus configuration structure
|
||||
*
|
||||
* @param[in] sclk SPI clock pin number
|
||||
* @param[in] mosi SPI MOSI pin number
|
||||
* @param[in] max_trans_sz Maximum transfer size in bytes
|
||||
*
|
||||
*/
|
||||
#define JD9853_PANEL_BUS_SPI_CONFIG(sclk, mosi, max_trans_sz) \
|
||||
{ \
|
||||
.sclk_io_num = sclk, \
|
||||
.mosi_io_num = mosi, \
|
||||
.miso_io_num = -1, \
|
||||
.quadhd_io_num = -1, \
|
||||
.quadwp_io_num = -1, \
|
||||
.max_transfer_sz = max_trans_sz, \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief LCD panel IO configuration structure
|
||||
*
|
||||
* @param[in] cs SPI chip select pin number
|
||||
* @param[in] dc SPI data/command pin number
|
||||
* @param[in] cb Callback function when SPI transfer is done
|
||||
* @param[in] cb_ctx Callback function context
|
||||
*
|
||||
*/
|
||||
#define JD9853_PANEL_IO_SPI_CONFIG(cs, dc, callback, callback_ctx) \
|
||||
{ \
|
||||
.cs_gpio_num = cs, \
|
||||
.dc_gpio_num = dc, \
|
||||
.spi_mode = 0, \
|
||||
.pclk_hz = 40 * 1000 * 1000, \
|
||||
.trans_queue_depth = 10, \
|
||||
.on_color_trans_done = callback, \
|
||||
.user_ctx = callback_ctx, \
|
||||
.lcd_cmd_bits = 8, \
|
||||
.lcd_param_bits = 8, \
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "wifi_board.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "esp_lcd_sh8601.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
|
||||
#include "codecs/box_audio_codec.h"
|
||||
#include "application.h"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "wifi_board.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "esp_lcd_sh8601.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
|
||||
#include "codecs/box_audio_codec.h"
|
||||
#include "application.h"
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "lcd_display.h"
|
||||
|
||||
#include <vector>
|
||||
#include <font_awesome_symbols.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_err.h>
|
||||
#include <esp_lvgl_port.h>
|
||||
@@ -350,4 +349,4 @@ CustomLcdDisplay::CustomLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_p
|
||||
}
|
||||
|
||||
SetupUI();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "settings.h"
|
||||
#include "config.h"
|
||||
#include "sleep_timer.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
#include "adc_battery_monitor.h"
|
||||
#include "press_to_talk_mcp_tool.h"
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "settings.h"
|
||||
#include "config.h"
|
||||
#include "power_save_timer.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
#include "adc_battery_monitor.h"
|
||||
#include "press_to_talk_mcp_tool.h"
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "settings.h"
|
||||
#include "config.h"
|
||||
#include "power_save_timer.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
#include "press_to_talk_mcp_tool.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
|
||||
@@ -15,7 +15,7 @@ public:
|
||||
|
||||
void SetupHighTempWarningPopup() {
|
||||
// 创建高温警告弹窗
|
||||
high_temp_popup_ = lv_obj_create(lv_scr_act()); // 使用当前屏幕
|
||||
high_temp_popup_ = lv_obj_create(lv_screen_active()); // 使用当前屏幕
|
||||
lv_obj_set_scrollbar_mode(high_temp_popup_, LV_SCROLLBAR_MODE_OFF);
|
||||
lv_obj_set_size(high_temp_popup_, LV_HOR_RES * 0.9, fonts_.text_font->line_height * 2);
|
||||
lv_obj_align(high_temp_popup_, LV_ALIGN_BOTTOM_MID, 0, 0);
|
||||
@@ -47,7 +47,7 @@ public:
|
||||
|
||||
void ShowHighTempWarning() {
|
||||
if (high_temp_popup_ && lv_obj_has_flag(high_temp_popup_, LV_OBJ_FLAG_HIDDEN)) {
|
||||
lv_obj_clear_flag(high_temp_popup_, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_remove_flag(high_temp_popup_, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
#include <string>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <font_awesome.h>
|
||||
|
||||
#include "display.h"
|
||||
#include "board.h"
|
||||
#include "application.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
#include "audio_codec.h"
|
||||
#include "settings.h"
|
||||
#include "assets/lang_config.h"
|
||||
@@ -21,7 +21,7 @@ Display::Display() {
|
||||
Display *display = static_cast<Display*>(arg);
|
||||
DisplayLockGuard lock(display);
|
||||
lv_obj_add_flag(display->notification_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_clear_flag(display->status_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_remove_flag(display->status_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
},
|
||||
.arg = this,
|
||||
.dispatch_method = ESP_TIMER_TASK,
|
||||
@@ -67,7 +67,7 @@ void Display::SetStatus(const char* status) {
|
||||
return;
|
||||
}
|
||||
lv_label_set_text(status_label_, status);
|
||||
lv_obj_clear_flag(status_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_remove_flag(status_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
|
||||
last_status_update_time_ = std::chrono::system_clock::now();
|
||||
@@ -83,7 +83,7 @@ void Display::ShowNotification(const char* notification, int duration_ms) {
|
||||
return;
|
||||
}
|
||||
lv_label_set_text(notification_label_, notification);
|
||||
lv_obj_clear_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_remove_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_add_flag(status_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
|
||||
esp_timer_stop(notification_timer_);
|
||||
@@ -105,7 +105,7 @@ void Display::UpdateStatusBar(bool update_all) {
|
||||
// 如果静音状态改变,则更新图标
|
||||
if (codec->output_volume() == 0 && !muted_) {
|
||||
muted_ = true;
|
||||
lv_label_set_text(mute_label_, FONT_AWESOME_VOLUME_MUTE);
|
||||
lv_label_set_text(mute_label_, FONT_AWESOME_VOLUME_XMARK);
|
||||
} else if (codec->output_volume() > 0 && muted_) {
|
||||
muted_ = false;
|
||||
lv_label_set_text(mute_label_, "");
|
||||
@@ -136,13 +136,13 @@ void Display::UpdateStatusBar(bool update_all) {
|
||||
const char* icon = nullptr;
|
||||
if (board.GetBatteryLevel(battery_level, charging, discharging)) {
|
||||
if (charging) {
|
||||
icon = FONT_AWESOME_BATTERY_CHARGING;
|
||||
icon = FONT_AWESOME_BATTERY_BOLT;
|
||||
} else {
|
||||
const char* levels[] = {
|
||||
FONT_AWESOME_BATTERY_EMPTY, // 0-19%
|
||||
FONT_AWESOME_BATTERY_1, // 20-39%
|
||||
FONT_AWESOME_BATTERY_2, // 40-59%
|
||||
FONT_AWESOME_BATTERY_3, // 60-79%
|
||||
FONT_AWESOME_BATTERY_QUARTER, // 20-39%
|
||||
FONT_AWESOME_BATTERY_HALF, // 40-59%
|
||||
FONT_AWESOME_BATTERY_THREE_QUARTERS, // 60-79%
|
||||
FONT_AWESOME_BATTERY_FULL, // 80-99%
|
||||
FONT_AWESOME_BATTERY_FULL, // 100%
|
||||
};
|
||||
@@ -157,7 +157,7 @@ void Display::UpdateStatusBar(bool update_all) {
|
||||
if (low_battery_popup_ != nullptr) {
|
||||
if (strcmp(icon, FONT_AWESOME_BATTERY_EMPTY) == 0 && discharging) {
|
||||
if (lv_obj_has_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN)) { // 如果低电量提示框隐藏,则显示
|
||||
lv_obj_clear_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_remove_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
|
||||
app.PlaySound(Lang::Sounds::OGG_LOW_BATTERY);
|
||||
}
|
||||
} else {
|
||||
@@ -196,50 +196,11 @@ void Display::UpdateStatusBar(bool update_all) {
|
||||
|
||||
|
||||
void Display::SetEmotion(const char* emotion) {
|
||||
struct Emotion {
|
||||
const char* icon;
|
||||
const char* text;
|
||||
};
|
||||
|
||||
static const std::vector<Emotion> emotions = {
|
||||
{FONT_AWESOME_EMOJI_NEUTRAL, "neutral"},
|
||||
{FONT_AWESOME_EMOJI_HAPPY, "happy"},
|
||||
{FONT_AWESOME_EMOJI_LAUGHING, "laughing"},
|
||||
{FONT_AWESOME_EMOJI_FUNNY, "funny"},
|
||||
{FONT_AWESOME_EMOJI_SAD, "sad"},
|
||||
{FONT_AWESOME_EMOJI_ANGRY, "angry"},
|
||||
{FONT_AWESOME_EMOJI_CRYING, "crying"},
|
||||
{FONT_AWESOME_EMOJI_LOVING, "loving"},
|
||||
{FONT_AWESOME_EMOJI_EMBARRASSED, "embarrassed"},
|
||||
{FONT_AWESOME_EMOJI_SURPRISED, "surprised"},
|
||||
{FONT_AWESOME_EMOJI_SHOCKED, "shocked"},
|
||||
{FONT_AWESOME_EMOJI_THINKING, "thinking"},
|
||||
{FONT_AWESOME_EMOJI_WINKING, "winking"},
|
||||
{FONT_AWESOME_EMOJI_COOL, "cool"},
|
||||
{FONT_AWESOME_EMOJI_RELAXED, "relaxed"},
|
||||
{FONT_AWESOME_EMOJI_DELICIOUS, "delicious"},
|
||||
{FONT_AWESOME_EMOJI_KISSY, "kissy"},
|
||||
{FONT_AWESOME_EMOJI_CONFIDENT, "confident"},
|
||||
{FONT_AWESOME_EMOJI_SLEEPY, "sleepy"},
|
||||
{FONT_AWESOME_EMOJI_SILLY, "silly"},
|
||||
{FONT_AWESOME_EMOJI_CONFUSED, "confused"}
|
||||
};
|
||||
|
||||
// 查找匹配的表情
|
||||
std::string_view emotion_view(emotion);
|
||||
auto it = std::find_if(emotions.begin(), emotions.end(),
|
||||
[&emotion_view](const Emotion& e) { return e.text == emotion_view; });
|
||||
|
||||
DisplayLockGuard lock(this);
|
||||
if (emotion_label_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果找到匹配的表情就显示对应图标,否则显示默认的neutral表情
|
||||
if (it != emotions.end()) {
|
||||
lv_label_set_text(emotion_label_, it->icon);
|
||||
const char* utf8 = font_awesome_get_utf8(emotion);
|
||||
if (utf8 != nullptr) {
|
||||
SetIcon(utf8);
|
||||
} else {
|
||||
lv_label_set_text(emotion_label_, FONT_AWESOME_EMOJI_NEUTRAL);
|
||||
SetIcon(FONT_AWESOME_NEUTRAL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
#include "lcd_display.h"
|
||||
#include "assets/lang_config.h"
|
||||
#include "settings.h"
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <font_awesome_symbols.h>
|
||||
#include <font_awesome.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_err.h>
|
||||
#include <esp_lvgl_port.h>
|
||||
#include <esp_heap_caps.h>
|
||||
#include "assets/lang_config.h"
|
||||
#include <esp_psram.h>
|
||||
#include <cstring>
|
||||
#include "settings.h"
|
||||
|
||||
#include "board.h"
|
||||
|
||||
@@ -102,10 +102,24 @@ SpiLcdDisplay::SpiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_h
|
||||
ESP_LOGI(TAG, "Initialize LVGL library");
|
||||
lv_init();
|
||||
|
||||
#if CONFIG_SPIRAM
|
||||
// lv image cache, currently only PNG is supported
|
||||
size_t psram_size_mb = esp_psram_get_size() / 1024 / 1024;
|
||||
if (psram_size_mb >= 8) {
|
||||
lv_image_cache_resize(2 * 1024 * 1024, true);
|
||||
ESP_LOGI(TAG, "Use 2MB of PSRAM for image cache");
|
||||
} else if (psram_size_mb >= 2) {
|
||||
lv_image_cache_resize(512 * 1024, true);
|
||||
ESP_LOGI(TAG, "Use 512KB of PSRAM for image cache");
|
||||
}
|
||||
#endif
|
||||
|
||||
ESP_LOGI(TAG, "Initialize LVGL port");
|
||||
lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
|
||||
port_cfg.task_priority = 1;
|
||||
port_cfg.timer_period_ms = 50;
|
||||
#if CONFIG_SOC_CPU_CORES_NUM > 1
|
||||
port_cfg.task_affinity = 1;
|
||||
#endif
|
||||
lvgl_port_init(&port_cfg);
|
||||
|
||||
ESP_LOGI(TAG, "Adding LCD display");
|
||||
@@ -167,7 +181,9 @@ RgbLcdDisplay::RgbLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_h
|
||||
ESP_LOGI(TAG, "Initialize LVGL port");
|
||||
lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
|
||||
port_cfg.task_priority = 1;
|
||||
port_cfg.timer_period_ms = 50;
|
||||
#if CONFIG_SOC_CPU_CORES_NUM > 1
|
||||
port_cfg.task_affinity = 1;
|
||||
#endif
|
||||
lvgl_port_init(&port_cfg);
|
||||
|
||||
ESP_LOGI(TAG, "Adding LCD display");
|
||||
@@ -226,6 +242,10 @@ MipiLcdDisplay::MipiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel
|
||||
|
||||
ESP_LOGI(TAG, "Initialize LVGL port");
|
||||
lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
|
||||
port_cfg.task_priority = 1;
|
||||
#if CONFIG_SOC_CPU_CORES_NUM > 1
|
||||
port_cfg.task_affinity = 1;
|
||||
#endif
|
||||
lvgl_port_init(&port_cfg);
|
||||
|
||||
ESP_LOGI(TAG, "Adding LCD display");
|
||||
@@ -367,7 +387,7 @@ void LcdDisplay::SetupUI() {
|
||||
emotion_label_ = lv_label_create(status_bar_);
|
||||
lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_4, 0);
|
||||
lv_obj_set_style_text_color(emotion_label_, current_theme_.text, 0);
|
||||
lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP);
|
||||
lv_label_set_text(emotion_label_, FONT_AWESOME_MICROCHIP_AI);
|
||||
lv_obj_set_style_margin_right(emotion_label_, 5, 0); // 添加右边距,与后面的元素分隔
|
||||
|
||||
notification_label_ = lv_label_create(status_bar_);
|
||||
@@ -621,7 +641,7 @@ void LcdDisplay::SetPreviewImage(const lv_img_dsc_t* img_dsc) {
|
||||
|
||||
// 设置自定义属性标记气泡类型
|
||||
lv_obj_set_user_data(img_bubble, (void*)"image");
|
||||
|
||||
|
||||
// Create the image object inside the bubble
|
||||
lv_obj_t* preview_image = lv_image_create(img_bubble);
|
||||
|
||||
@@ -744,7 +764,7 @@ void LcdDisplay::SetupUI() {
|
||||
emotion_label_ = lv_label_create(content_);
|
||||
lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_4, 0);
|
||||
lv_obj_set_style_text_color(emotion_label_, current_theme_.text, 0);
|
||||
lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP);
|
||||
lv_label_set_text(emotion_label_, FONT_AWESOME_MICROCHIP_AI);
|
||||
|
||||
preview_image_ = lv_image_create(content_);
|
||||
lv_obj_set_size(preview_image_, width_ * 0.5, height_ * 0.5);
|
||||
@@ -814,11 +834,13 @@ void LcdDisplay::SetPreviewImage(const lv_img_dsc_t* img_dsc) {
|
||||
}
|
||||
|
||||
if (img_dsc != nullptr) {
|
||||
// zoom factor 0.5
|
||||
lv_image_set_scale(preview_image_, 128 * width_ / img_dsc->header.w);
|
||||
// 设置图片源并显示预览图片
|
||||
lv_image_set_src(preview_image_, img_dsc);
|
||||
lv_obj_clear_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
|
||||
if (img_dsc->header.w > 0) {
|
||||
// zoom factor 0.5
|
||||
lv_image_set_scale(preview_image_, 128 * width_ / img_dsc->header.w);
|
||||
}
|
||||
lv_obj_remove_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
|
||||
// 隐藏emotion_label_
|
||||
if (emotion_label_ != nullptr) {
|
||||
lv_obj_add_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
@@ -827,7 +849,7 @@ void LcdDisplay::SetPreviewImage(const lv_img_dsc_t* img_dsc) {
|
||||
// 隐藏预览图片并显示emotion_label_
|
||||
lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
|
||||
if (emotion_label_ != nullptr) {
|
||||
lv_obj_clear_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_remove_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -867,6 +889,13 @@ void LcdDisplay::SetEmotion(const char* emotion) {
|
||||
std::string_view emotion_view(emotion);
|
||||
auto it = std::find_if(emotions.begin(), emotions.end(),
|
||||
[&emotion_view](const Emotion& e) { return e.text == emotion_view; });
|
||||
if (fonts_.emoji_font == nullptr || it == emotions.end()) {
|
||||
const char* utf8 = font_awesome_get_utf8(emotion);
|
||||
if (utf8 != nullptr) {
|
||||
SetIcon(utf8);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
DisplayLockGuard lock(this);
|
||||
if (emotion_label_ == nullptr) {
|
||||
@@ -883,7 +912,7 @@ void LcdDisplay::SetEmotion(const char* emotion) {
|
||||
|
||||
#if !CONFIG_USE_WECHAT_MESSAGE_STYLE
|
||||
// 显示emotion_label_,隐藏preview_image_
|
||||
lv_obj_clear_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_remove_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
if (preview_image_ != nullptr) {
|
||||
lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
@@ -900,7 +929,7 @@ void LcdDisplay::SetIcon(const char* icon) {
|
||||
|
||||
#if !CONFIG_USE_WECHAT_MESSAGE_STYLE
|
||||
// 显示emotion_label_,隐藏preview_image_
|
||||
lv_obj_clear_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_remove_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
if (preview_image_ != nullptr) {
|
||||
lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "oled_display.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
#include "assets/lang_config.h"
|
||||
|
||||
#include <string>
|
||||
@@ -8,6 +7,7 @@
|
||||
#include <esp_log.h>
|
||||
#include <esp_err.h>
|
||||
#include <esp_lvgl_port.h>
|
||||
#include <font_awesome.h>
|
||||
|
||||
#define TAG "OledDisplay"
|
||||
|
||||
@@ -23,7 +23,9 @@ OledDisplay::OledDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handl
|
||||
lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
|
||||
port_cfg.task_priority = 1;
|
||||
port_cfg.task_stack = 6144;
|
||||
port_cfg.timer_period_ms = 50;
|
||||
#if CONFIG_SOC_CPU_CORES_NUM > 1
|
||||
port_cfg.task_affinity = 1;
|
||||
#endif
|
||||
lvgl_port_init(&port_cfg);
|
||||
|
||||
ESP_LOGI(TAG, "Adding OLED display");
|
||||
@@ -112,7 +114,7 @@ void OledDisplay::SetChatMessage(const char* role, const char* content) {
|
||||
lv_obj_add_flag(content_right_, LV_OBJ_FLAG_HIDDEN);
|
||||
} else {
|
||||
lv_label_set_text(chat_message_label_, content_str.c_str());
|
||||
lv_obj_clear_flag(content_right_, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_remove_flag(content_right_, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,7 +159,7 @@ void OledDisplay::SetupUI_128x64() {
|
||||
|
||||
emotion_label_ = lv_label_create(content_left_);
|
||||
lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_1, 0);
|
||||
lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP);
|
||||
lv_label_set_text(emotion_label_, FONT_AWESOME_MICROCHIP_AI);
|
||||
lv_obj_center(emotion_label_);
|
||||
lv_obj_set_style_pad_top(emotion_label_, 8, 0);
|
||||
|
||||
@@ -249,7 +251,7 @@ void OledDisplay::SetupUI_128x32() {
|
||||
|
||||
emotion_label_ = lv_label_create(content_);
|
||||
lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_1, 0);
|
||||
lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP);
|
||||
lv_label_set_text(emotion_label_, FONT_AWESOME_MICROCHIP_AI);
|
||||
lv_obj_center(emotion_label_);
|
||||
|
||||
/* Right side */
|
||||
|
||||
@@ -13,27 +13,27 @@ dependencies:
|
||||
espressif/esp_io_expander_tca9554: ==2.0.0
|
||||
espressif/esp_lcd_panel_io_additions: ^1.0.1
|
||||
78/esp_lcd_nv3023: ~1.0.0
|
||||
78/esp-wifi-connect: ~2.5.0
|
||||
78/esp-wifi-connect: ~2.5.2
|
||||
78/esp-opus-encoder: ~2.4.1
|
||||
78/esp-ml307: ~3.2.6
|
||||
78/xiaozhi-fonts: ~1.4.0
|
||||
espressif/led_strip: ^2.5.5
|
||||
espressif/esp_codec_dev: ~1.3.6
|
||||
espressif/esp-sr: ==2.1.4
|
||||
78/esp-ml307: ~3.3.5
|
||||
78/xiaozhi-fonts: ~1.5.2
|
||||
espressif/led_strip: ~3.0.1
|
||||
espressif/esp_codec_dev: ~1.4.0
|
||||
espressif/esp-sr: ~2.1.5
|
||||
espressif/button: ~4.1.3
|
||||
espressif/knob: ^1.0.0
|
||||
espressif/esp32-camera: ^2.0.15
|
||||
espressif/esp32-camera: ~2.1.2
|
||||
espressif/esp_lcd_touch_ft5x06: ~1.0.7
|
||||
espressif/esp_lcd_touch_gt911: ^1
|
||||
espressif/esp_lcd_touch_gt1151: ^1
|
||||
waveshare/esp_lcd_touch_cst9217: ^1.0.3
|
||||
espressif/esp_lcd_touch_cst816s: ^1.0.6
|
||||
lvgl/lvgl: ~9.2.2
|
||||
lvgl/lvgl: ~9.3.0
|
||||
esp_lvgl_port: ~2.6.0
|
||||
espressif/esp_io_expander_tca95xx_16bit: ^2.0.0
|
||||
espressif2022/image_player: ==1.1.0~1
|
||||
espressif2022/esp_emote_gfx: ^1.0.0
|
||||
espressif/adc_mic: ^0.2.0
|
||||
espressif2022/esp_emote_gfx: ==1.0.0~2
|
||||
espressif/adc_mic: ^0.2.1
|
||||
espressif/esp_mmap_assets: '>=1.2'
|
||||
txp666/otto-emoji-gif-component: ~1.0.2
|
||||
espressif/adc_battery_estimation: ^0.2.0
|
||||
|
||||
@@ -15,7 +15,7 @@ CircularStrip::CircularStrip(gpio_num_t gpio, uint8_t max_leds) : max_leds_(max_
|
||||
led_strip_config_t strip_config = {};
|
||||
strip_config.strip_gpio_num = gpio;
|
||||
strip_config.max_leds = max_leds_;
|
||||
strip_config.led_pixel_format = LED_PIXEL_FORMAT_GRB;
|
||||
strip_config.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB;
|
||||
strip_config.led_model = LED_MODEL_WS2812;
|
||||
|
||||
led_strip_rmt_config_t rmt_config = {};
|
||||
|
||||
@@ -18,7 +18,7 @@ SingleLed::SingleLed(gpio_num_t gpio) {
|
||||
led_strip_config_t strip_config = {};
|
||||
strip_config.strip_gpio_num = gpio;
|
||||
strip_config.max_leds = 1;
|
||||
strip_config.led_pixel_format = LED_PIXEL_FORMAT_GRB;
|
||||
strip_config.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB;
|
||||
strip_config.led_model = LED_MODEL_WS2812;
|
||||
|
||||
led_strip_rmt_config_t rmt_config = {};
|
||||
|
||||
@@ -27,5 +27,4 @@ extern "C" void app_main(void)
|
||||
// Launch the application
|
||||
auto& app = Application::GetInstance();
|
||||
app.Start();
|
||||
app.MainEventLoop();
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
#define TAG "MCP"
|
||||
|
||||
#define DEFAULT_TOOLCALL_STACK_SIZE 6144
|
||||
|
||||
McpServer::McpServer() {
|
||||
}
|
||||
|
||||
@@ -29,12 +27,17 @@ McpServer::~McpServer() {
|
||||
}
|
||||
|
||||
void McpServer::AddCommonTools() {
|
||||
// To speed up the response time, we add the common tools to the beginning of
|
||||
// *Important* To speed up the response time, we add the common tools to the beginning of
|
||||
// the tools list to utilize the prompt cache.
|
||||
// **重要** 为了提升响应速度,我们把常用的工具放在前面,利用 prompt cache 的特性。
|
||||
|
||||
// Backup the original tools list and restore it after adding the common tools.
|
||||
auto original_tools = std::move(tools_);
|
||||
auto& board = Board::GetInstance();
|
||||
|
||||
// Do not add custom tools here.
|
||||
// Custom tools must be added in the board's InitializeTools function.
|
||||
|
||||
AddTool("self.get_device_status",
|
||||
"Provides the real-time information of the device, including the current status of the audio speaker, screen, battery, network, etc.\n"
|
||||
"Use this tool for: \n"
|
||||
@@ -95,6 +98,9 @@ void McpServer::AddCommonTools() {
|
||||
Property("question", kPropertyTypeString)
|
||||
}),
|
||||
[camera](const PropertyList& properties) -> ReturnValue {
|
||||
// Lower the priority to do the camera capture
|
||||
TaskPriorityReset priority_reset(1);
|
||||
|
||||
if (!camera->Capture()) {
|
||||
return "{\"success\": false, \"message\": \"Failed to capture photo\"}";
|
||||
}
|
||||
@@ -122,6 +128,12 @@ void McpServer::AddTool(const std::string& name, const std::string& description,
|
||||
AddTool(new McpTool(name, description, properties, callback));
|
||||
}
|
||||
|
||||
void McpServer::AddUserOnlyTool(const std::string& name, const std::string& description, const PropertyList& properties, std::function<ReturnValue(const PropertyList&)> callback) {
|
||||
auto tool = new McpTool(name, description, properties, callback);
|
||||
tool->set_user_only(true);
|
||||
AddTool(tool);
|
||||
}
|
||||
|
||||
void McpServer::ParseMessage(const std::string& message) {
|
||||
cJSON* json = cJSON_Parse(message.c_str());
|
||||
if (json == nullptr) {
|
||||
@@ -224,13 +236,7 @@ void McpServer::ParseMessage(const cJSON* json) {
|
||||
ReplyError(id_int, "Invalid arguments");
|
||||
return;
|
||||
}
|
||||
auto stack_size = cJSON_GetObjectItem(params, "stackSize");
|
||||
if (stack_size != nullptr && !cJSON_IsNumber(stack_size)) {
|
||||
ESP_LOGE(TAG, "tools/call: Invalid stackSize");
|
||||
ReplyError(id_int, "Invalid stackSize");
|
||||
return;
|
||||
}
|
||||
DoToolCall(id_int, std::string(tool_name->valuestring), tool_arguments, stack_size ? stack_size->valueint : DEFAULT_TOOLCALL_STACK_SIZE);
|
||||
DoToolCall(id_int, std::string(tool_name->valuestring), tool_arguments);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Method not implemented: %s", method_str.c_str());
|
||||
ReplyError(id_int, "Method not implemented: " + method_str);
|
||||
@@ -305,7 +311,7 @@ void McpServer::GetToolsList(int id, const std::string& cursor) {
|
||||
ReplyResult(id, json);
|
||||
}
|
||||
|
||||
void McpServer::DoToolCall(int id, const std::string& tool_name, const cJSON* tool_arguments, int stack_size) {
|
||||
void McpServer::DoToolCall(int id, const std::string& tool_name, const cJSON* tool_arguments) {
|
||||
auto tool_iter = std::find_if(tools_.begin(), tools_.end(),
|
||||
[&tool_name](const McpTool* tool) {
|
||||
return tool->name() == tool_name;
|
||||
@@ -347,15 +353,9 @@ void McpServer::DoToolCall(int id, const std::string& tool_name, const cJSON* to
|
||||
return;
|
||||
}
|
||||
|
||||
// Start a task to receive data with stack size
|
||||
esp_pthread_cfg_t cfg = esp_pthread_get_default_config();
|
||||
cfg.thread_name = "tool_call";
|
||||
cfg.stack_size = stack_size;
|
||||
cfg.prio = 1;
|
||||
esp_pthread_set_cfg(&cfg);
|
||||
|
||||
// Use a thread to call the tool to avoid blocking the main thread
|
||||
tool_call_thread_ = std::thread([this, id, tool_iter, arguments = std::move(arguments)]() {
|
||||
// Use main thread to call the tool
|
||||
auto& app = Application::GetInstance();
|
||||
app.Schedule([this, id, tool_iter, arguments = std::move(arguments)]() {
|
||||
try {
|
||||
ReplyResult(id, (*tool_iter)->Call(arguments));
|
||||
} catch (const std::exception& e) {
|
||||
@@ -363,5 +363,4 @@ void McpServer::DoToolCall(int id, const std::string& tool_name, const cJSON* to
|
||||
ReplyError(id, e.what());
|
||||
}
|
||||
});
|
||||
tool_call_thread_.detach();
|
||||
}
|
||||
@@ -177,6 +177,7 @@ private:
|
||||
std::string description_;
|
||||
PropertyList properties_;
|
||||
std::function<ReturnValue(const PropertyList&)> callback_;
|
||||
bool user_only_ = false;
|
||||
|
||||
public:
|
||||
McpTool(const std::string& name,
|
||||
@@ -188,9 +189,11 @@ public:
|
||||
properties_(properties),
|
||||
callback_(callback) {}
|
||||
|
||||
void set_user_only(bool user_only) { user_only_ = user_only; }
|
||||
inline const std::string& name() const { return name_; }
|
||||
inline const std::string& description() const { return description_; }
|
||||
inline const PropertyList& properties() const { return properties_; }
|
||||
inline bool user_only() const { return user_only_; }
|
||||
|
||||
std::string to_json() const {
|
||||
std::vector<std::string> required = properties_.GetRequired();
|
||||
@@ -214,6 +217,15 @@ public:
|
||||
}
|
||||
|
||||
cJSON_AddItemToObject(json, "inputSchema", input_schema);
|
||||
|
||||
// Add audience annotation if the tool is user only (invisible to AI)
|
||||
if (user_only_) {
|
||||
cJSON *annotations = cJSON_CreateObject();
|
||||
cJSON *audience = cJSON_CreateArray();
|
||||
cJSON_AddItemToArray(audience, cJSON_CreateString("user"));
|
||||
cJSON_AddItemToObject(annotations, "audience", audience);
|
||||
cJSON_AddItemToObject(json, "annotations", annotations);
|
||||
}
|
||||
|
||||
char *json_str = cJSON_PrintUnformatted(json);
|
||||
std::string result(json_str);
|
||||
@@ -259,6 +271,7 @@ public:
|
||||
void AddCommonTools();
|
||||
void AddTool(McpTool* tool);
|
||||
void AddTool(const std::string& name, const std::string& description, const PropertyList& properties, std::function<ReturnValue(const PropertyList&)> callback);
|
||||
void AddUserOnlyTool(const std::string& name, const std::string& description, const PropertyList& properties, std::function<ReturnValue(const PropertyList&)> callback);
|
||||
void ParseMessage(const cJSON* json);
|
||||
void ParseMessage(const std::string& message);
|
||||
|
||||
@@ -272,10 +285,9 @@ private:
|
||||
void ReplyError(int id, const std::string& message);
|
||||
|
||||
void GetToolsList(int id, const std::string& cursor);
|
||||
void DoToolCall(int id, const std::string& tool_name, const cJSON* tool_arguments, int stack_size);
|
||||
void DoToolCall(int id, const std::string& tool_name, const cJSON* tool_arguments);
|
||||
|
||||
std::vector<McpTool*> tools_;
|
||||
std::thread tool_call_thread_;
|
||||
};
|
||||
|
||||
#endif // MCP_SERVER_H
|
||||
|
||||
@@ -51,17 +51,17 @@ std::string Ota::GetCheckVersionUrl() {
|
||||
|
||||
std::unique_ptr<Http> Ota::SetupHttp() {
|
||||
auto& board = Board::GetInstance();
|
||||
auto app_desc = esp_app_get_description();
|
||||
|
||||
auto network = board.GetNetwork();
|
||||
auto http = network->CreateHttp(0);
|
||||
auto user_agent = SystemInfo::GetUserAgent();
|
||||
http->SetHeader("Activation-Version", has_serial_number_ ? "2" : "1");
|
||||
http->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str());
|
||||
http->SetHeader("Client-Id", board.GetUuid());
|
||||
if (has_serial_number_) {
|
||||
http->SetHeader("Serial-Number", serial_number_.c_str());
|
||||
ESP_LOGI(TAG, "Setup HTTP, User-Agent: %s, Serial-Number: %s", user_agent.c_str(), serial_number_.c_str());
|
||||
}
|
||||
http->SetHeader("User-Agent", std::string(BOARD_NAME "/") + app_desc->version);
|
||||
http->SetHeader("User-Agent", user_agent);
|
||||
http->SetHeader("Accept-Language", Lang::CODE);
|
||||
http->SetHeader("Content-Type", "application/json");
|
||||
|
||||
|
||||
@@ -12,11 +12,33 @@
|
||||
|
||||
MqttProtocol::MqttProtocol() {
|
||||
event_group_handle_ = xEventGroupCreate();
|
||||
|
||||
// Initialize reconnect timer
|
||||
esp_timer_create_args_t reconnect_timer_args = {
|
||||
.callback = [](void* arg) {
|
||||
MqttProtocol* protocol = (MqttProtocol*)arg;
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateIdle) {
|
||||
ESP_LOGI(TAG, "Reconnecting to MQTT server");
|
||||
app.Schedule([protocol]() {
|
||||
protocol->StartMqttClient(false);
|
||||
});
|
||||
}
|
||||
},
|
||||
.arg = this,
|
||||
};
|
||||
esp_timer_create(&reconnect_timer_args, &reconnect_timer_);
|
||||
}
|
||||
|
||||
MqttProtocol::~MqttProtocol() {
|
||||
ESP_LOGI(TAG, "MqttProtocol deinit");
|
||||
vEventGroupDelete(event_group_handle_);
|
||||
if (reconnect_timer_ != nullptr) {
|
||||
esp_timer_stop(reconnect_timer_);
|
||||
esp_timer_delete(reconnect_timer_);
|
||||
}
|
||||
if (event_group_handle_ != nullptr) {
|
||||
vEventGroupDelete(event_group_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool MqttProtocol::Start() {
|
||||
@@ -50,7 +72,18 @@ bool MqttProtocol::StartMqttClient(bool report_error) {
|
||||
mqtt_->SetKeepAlive(keepalive_interval);
|
||||
|
||||
mqtt_->OnDisconnected([this]() {
|
||||
ESP_LOGI(TAG, "Disconnected from endpoint");
|
||||
if (on_disconnected_ != nullptr) {
|
||||
on_disconnected_();
|
||||
}
|
||||
ESP_LOGI(TAG, "MQTT disconnected, schedule reconnect in %d seconds", MQTT_RECONNECT_INTERVAL_MS / 1000);
|
||||
esp_timer_start_once(reconnect_timer_, MQTT_RECONNECT_INTERVAL_MS * 1000);
|
||||
});
|
||||
|
||||
mqtt_->OnConnected([this]() {
|
||||
if (on_connected_ != nullptr) {
|
||||
on_connected_();
|
||||
}
|
||||
esp_timer_stop(reconnect_timer_);
|
||||
});
|
||||
|
||||
mqtt_->OnMessage([this](const std::string& topic, const std::string& payload) {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <mbedtls/aes.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <esp_timer.h>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
@@ -16,7 +17,7 @@
|
||||
#include <mutex>
|
||||
|
||||
#define MQTT_PING_INTERVAL_SECONDS 90
|
||||
#define MQTT_RECONNECT_INTERVAL_MS 10000
|
||||
#define MQTT_RECONNECT_INTERVAL_MS 60000
|
||||
|
||||
#define MQTT_PROTOCOL_SERVER_HELLO_EVENT (1 << 0)
|
||||
|
||||
@@ -45,6 +46,7 @@ private:
|
||||
int udp_port_;
|
||||
uint32_t local_sequence_;
|
||||
uint32_t remote_sequence_;
|
||||
esp_timer_handle_t reconnect_timer_;
|
||||
|
||||
bool StartMqttClient(bool report_error=false);
|
||||
void ParseServerHello(const cJSON* root);
|
||||
|
||||
@@ -24,6 +24,14 @@ void Protocol::OnNetworkError(std::function<void(const std::string& message)> ca
|
||||
on_network_error_ = callback;
|
||||
}
|
||||
|
||||
void Protocol::OnConnected(std::function<void()> callback) {
|
||||
on_connected_ = callback;
|
||||
}
|
||||
|
||||
void Protocol::OnDisconnected(std::function<void()> callback) {
|
||||
on_disconnected_ = callback;
|
||||
}
|
||||
|
||||
void Protocol::SetError(const std::string& message) {
|
||||
error_occurred_ = true;
|
||||
if (on_network_error_ != nullptr) {
|
||||
|
||||
@@ -60,6 +60,8 @@ public:
|
||||
void OnAudioChannelOpened(std::function<void()> callback);
|
||||
void OnAudioChannelClosed(std::function<void()> callback);
|
||||
void OnNetworkError(std::function<void(const std::string& message)> callback);
|
||||
void OnConnected(std::function<void()> callback);
|
||||
void OnDisconnected(std::function<void()> callback);
|
||||
|
||||
virtual bool Start() = 0;
|
||||
virtual bool OpenAudioChannel() = 0;
|
||||
@@ -78,6 +80,8 @@ protected:
|
||||
std::function<void()> on_audio_channel_opened_;
|
||||
std::function<void()> on_audio_channel_closed_;
|
||||
std::function<void(const std::string& message)> on_network_error_;
|
||||
std::function<void()> on_connected_;
|
||||
std::function<void()> on_disconnected_;
|
||||
|
||||
int server_sample_rate_ = 24000;
|
||||
int server_frame_duration_ = 60;
|
||||
|
||||
@@ -47,6 +47,12 @@ std::string SystemInfo::GetChipModelName() {
|
||||
return std::string(CONFIG_IDF_TARGET);
|
||||
}
|
||||
|
||||
std::string SystemInfo::GetUserAgent() {
|
||||
auto app_desc = esp_app_get_description();
|
||||
auto user_agent = std::string(BOARD_NAME "/") + app_desc->version;
|
||||
return user_agent;
|
||||
}
|
||||
|
||||
esp_err_t SystemInfo::PrintTaskCpuUsage(TickType_t xTicksToWait) {
|
||||
#define ARRAY_SIZE_OFFSET 5
|
||||
TaskStatus_t *start_array = NULL, *end_array = NULL;
|
||||
|
||||
@@ -13,6 +13,7 @@ public:
|
||||
static size_t GetFreeHeapSize();
|
||||
static std::string GetMacAddress();
|
||||
static std::string GetChipModelName();
|
||||
static std::string GetUserAgent();
|
||||
static esp_err_t PrintTaskCpuUsage(TickType_t xTicksToWait);
|
||||
static void PrintTaskList();
|
||||
static void PrintHeapStats();
|
||||
|
||||
9
partitions/v2/16m.csv
Normal file
9
partitions/v2/16m.csv
Normal file
@@ -0,0 +1,9 @@
|
||||
# ESP-IDF Partition Table
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x4000,
|
||||
otadata, data, ota, 0xd000, 0x2000,
|
||||
phy_init, data, phy, 0xf000, 0x1000,
|
||||
model, data, spiffs, 0x10000, 0xF0000,
|
||||
ota_0, app, ota_0, 0x100000, 4M,
|
||||
ota_1, app, ota_1, 0x500000, 4M,
|
||||
assets, data, spiffs, 0x900000, 7M
|
||||
|
9
partitions/v2/16m_c3.csv
Normal file
9
partitions/v2/16m_c3.csv
Normal file
@@ -0,0 +1,9 @@
|
||||
# ESP-IDF Partition Table
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x4000,
|
||||
otadata, data, ota, 0xd000, 0x2000,
|
||||
phy_init, data, phy, 0xf000, 0x1000,
|
||||
model, data, spiffs, 0x10000, 0xF0000,
|
||||
ota_0, app, ota_0, 0x100000, 4M,
|
||||
ota_1, app, ota_1, 0x500000, 4M,
|
||||
assets, data, spiffs, 0x900000, 4000K
|
||||
|
10
partitions/v2/32m.csv
Normal file
10
partitions/v2/32m.csv
Normal file
@@ -0,0 +1,10 @@
|
||||
# ESP-IDF Partition Table
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvsfactory, data, nvs, , 200K,
|
||||
nvs, data, nvs, , 840K,
|
||||
otadata, data, ota, , 0x2000,
|
||||
phy_init, data, phy, , 0x1000,
|
||||
model, data, spiffs, , 0xF0000,
|
||||
ota_0, app, ota_0, 0x200000, 4M,
|
||||
ota_1, app, ota_1, 0x600000, 4M,
|
||||
assets, data, spiffs, 0xA00000, 16M
|
||||
|
9
partitions/v2/8m.csv
Normal file
9
partitions/v2/8m.csv
Normal file
@@ -0,0 +1,9 @@
|
||||
# ESP-IDF Partition Table
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x4000,
|
||||
otadata, data, ota, 0xd000, 0x2000,
|
||||
phy_init, data, phy, 0xf000, 0x1000,
|
||||
model, data, spiffs, 0x10000, 0xF0000,
|
||||
ota_0, app, ota_0, 0x100000, 3M,
|
||||
ota_1, app, ota_1, 0x400000, 3M,
|
||||
assets, data, spiffs, 0x700000, 1M
|
||||
|
107
partitions/v2/README.md
Normal file
107
partitions/v2/README.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# Version 2 Partition Table
|
||||
|
||||
This version introduces significant improvements over v1 by adding an `assets` partition to support network-loadable content and optimizing partition layouts for different flash sizes.
|
||||
|
||||
## Key Changes from v1
|
||||
|
||||
### Major Improvements
|
||||
1. **Added Assets Partition**: New `assets` partition for network-loadable content
|
||||
2. **Replaced Model Partition**: The old `model` partition (960KB) is replaced with a larger `assets` partition
|
||||
3. **Optimized App Partitions**: Reduced application partition sizes to accommodate assets
|
||||
4. **Enhanced Flexibility**: Support for dynamic content updates without reflashing
|
||||
|
||||
### Assets Partition Features
|
||||
The `assets` partition stores:
|
||||
- **Wake word models**: Customizable wake word models that can be loaded from the network
|
||||
- **Theme files**: Complete theming system including:
|
||||
- Fonts (text and icon fonts)
|
||||
- Audio effects and sound files
|
||||
- Background images and UI elements
|
||||
- Custom emoji packs
|
||||
- Language configuration files
|
||||
- **Dynamic Content**: All content can be updated over-the-air via HTTP downloads
|
||||
|
||||
## Partition Layout Comparison
|
||||
|
||||
### v1 Layout (16MB)
|
||||
- `nvs`: 16KB (non-volatile storage)
|
||||
- `otadata`: 8KB (OTA data)
|
||||
- `phy_init`: 4KB (PHY initialization data)
|
||||
- `model`: 960KB (model storage - fixed content)
|
||||
- `ota_0`: 6MB (application partition 0)
|
||||
- `ota_1`: 6MB (application partition 1)
|
||||
|
||||
### v2 Layout (16MB)
|
||||
- `nvs`: 16KB (non-volatile storage)
|
||||
- `otadata`: 8KB (OTA data)
|
||||
- `phy_init`: 4KB (PHY initialization data)
|
||||
- `ota_0`: 4MB (application partition 0)
|
||||
- `ota_1`: 4MB (application partition 1)
|
||||
- `assets`: 8MB (network-loadable assets)
|
||||
|
||||
## Available Configurations
|
||||
|
||||
### 8MB Flash Devices (`8m.csv`)
|
||||
- `nvs`: 16KB
|
||||
- `otadata`: 8KB
|
||||
- `phy_init`: 4KB
|
||||
- `ota_0`: 3MB
|
||||
- `ota_1`: 3MB
|
||||
- `assets`: 2MB
|
||||
|
||||
### 16MB Flash Devices (`16m.csv`) - Standard
|
||||
- `nvs`: 16KB
|
||||
- `otadata`: 8KB
|
||||
- `phy_init`: 4KB
|
||||
- `ota_0`: 4MB
|
||||
- `ota_1`: 4MB
|
||||
- `assets`: 8MB
|
||||
|
||||
### 16MB Flash Devices (`16m_c3.csv`) - ESP32-C3 Optimized
|
||||
- `nvs`: 16KB
|
||||
- `otadata`: 8KB
|
||||
- `phy_init`: 4KB
|
||||
- `ota_0`: 4MB
|
||||
- `ota_1`: 4MB
|
||||
- `assets`: 4MB (4000K - limited by available mmap pages)
|
||||
|
||||
### 32MB Flash Devices (`32m.csv`)
|
||||
- `nvsfactory`: 200KB
|
||||
- `nvs`: 840KB
|
||||
- `otadata`: 8KB
|
||||
- `phy_init`: 4KB
|
||||
- `ota_0`: 4MB
|
||||
- `ota_1`: 4MB
|
||||
- `assets`: 16MB
|
||||
|
||||
## Benefits
|
||||
|
||||
1. **Dynamic Content Management**: Users can download and update wake word models, themes, and other assets without reflashing the device
|
||||
2. **Reduced App Size**: Application partitions are optimized, allowing more space for dynamic content
|
||||
3. **Enhanced Customization**: Support for custom themes, wake words, and language packs enhances user experience
|
||||
4. **Network Flexibility**: Assets can be updated independently of the main application firmware
|
||||
5. **Better Resource Utilization**: Efficient use of flash memory with configurable asset storage
|
||||
6. **OTA Asset Updates**: Assets can be updated over-the-air via HTTP downloads
|
||||
|
||||
## Technical Details
|
||||
|
||||
- **Partition Type**: Assets partition uses `spiffs` subtype for SPIFFS filesystem compatibility
|
||||
- **Memory Mapping**: Assets are memory-mapped for efficient access during runtime
|
||||
- **Checksum Validation**: Built-in integrity checking ensures asset data validity
|
||||
- **Progressive Download**: Assets can be downloaded progressively with progress tracking
|
||||
- **Fallback Support**: Graceful fallback to default assets if network updates fail
|
||||
|
||||
## Migration from v1
|
||||
|
||||
When upgrading from v1 to v2:
|
||||
1. **Backup Important Data**: Ensure any important data in the old `model` partition is backed up
|
||||
2. **Flash New Partition Table**: Use the appropriate v2 partition table for your flash size
|
||||
3. **Download Assets**: The device will automatically download required assets on first boot
|
||||
4. **Verify Functionality**: Ensure all features work correctly with the new partition layout
|
||||
|
||||
## Usage Notes
|
||||
|
||||
- The `assets` partition size varies by configuration to optimize for different flash sizes
|
||||
- ESP32-C3 devices use a smaller assets partition (4MB) due to limited available mmap pages in the system
|
||||
- 32MB devices get the largest assets partition (16MB) for maximum content storage
|
||||
- All partition tables maintain proper alignment for optimal flash performance
|
||||
29
scripts/ogg_converter/README.md
Normal file
29
scripts/ogg_converter/README.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# ogg_covertor 小智AI OGG 批量转换器
|
||||
|
||||
本脚本为OGG批量转换工具,支持将输入的音频文件转换为小智可使用的OGG格式
|
||||
基于Python第三方库`ffmpeg-python`实现
|
||||
支持OGG和音频之间的互转,响度调节等功能
|
||||
|
||||
# 创建并激活虚拟环境
|
||||
|
||||
```bash
|
||||
# 创建虚拟环境
|
||||
python -m venv venv
|
||||
# 激活虚拟环境
|
||||
source venv/bin/activate # Mac/Linux
|
||||
venv\Scripts\activate # Windows
|
||||
```
|
||||
|
||||
# 安装依赖
|
||||
|
||||
请在虚拟环境中执行
|
||||
|
||||
```bash
|
||||
pip install ffmpeg-python
|
||||
```
|
||||
|
||||
# 运行脚本
|
||||
```bash
|
||||
python ogg_covertor.py
|
||||
```
|
||||
|
||||
230
scripts/ogg_converter/xiaozhi_ogg_converter.py
Normal file
230
scripts/ogg_converter/xiaozhi_ogg_converter.py
Normal file
@@ -0,0 +1,230 @@
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, filedialog, messagebox
|
||||
import os
|
||||
import threading
|
||||
import sys
|
||||
import ffmpeg
|
||||
|
||||
class AudioConverterApp:
|
||||
def __init__(self, master):
|
||||
self.master = master
|
||||
master.title("小智AI OGG音频批量转换工具")
|
||||
master.geometry("680x600") # 调整窗口高度
|
||||
|
||||
# 初始化变量
|
||||
self.mode = tk.StringVar(value="audio_to_ogg")
|
||||
self.output_dir = tk.StringVar()
|
||||
self.output_dir.set(os.path.abspath("output"))
|
||||
self.enable_loudnorm = tk.BooleanVar(value=True)
|
||||
self.target_lufs = tk.DoubleVar(value=-16.0)
|
||||
|
||||
# 创建UI组件
|
||||
self.create_widgets()
|
||||
self.redirect_output()
|
||||
|
||||
def create_widgets(self):
|
||||
# 模式选择
|
||||
mode_frame = ttk.LabelFrame(self.master, text="转换模式")
|
||||
mode_frame.grid(row=0, column=0, padx=10, pady=5, sticky="ew")
|
||||
|
||||
ttk.Radiobutton(mode_frame, text="音频转到OGG", variable=self.mode,
|
||||
value="audio_to_ogg", command=self.toggle_settings,
|
||||
width=12).grid(row=0, column=0, padx=5)
|
||||
ttk.Radiobutton(mode_frame, text="OGG转回音频", variable=self.mode,
|
||||
value="ogg_to_audio", command=self.toggle_settings,
|
||||
width=12).grid(row=0, column=1, padx=5)
|
||||
|
||||
# 响度设置
|
||||
self.loudnorm_frame = ttk.Frame(self.master)
|
||||
self.loudnorm_frame.grid(row=1, column=0, padx=10, pady=5, sticky="ew")
|
||||
|
||||
ttk.Checkbutton(self.loudnorm_frame, text="启用响度调整",
|
||||
variable=self.enable_loudnorm, width=15
|
||||
).grid(row=0, column=0, padx=2)
|
||||
ttk.Entry(self.loudnorm_frame, textvariable=self.target_lufs,
|
||||
width=6).grid(row=0, column=1, padx=2)
|
||||
ttk.Label(self.loudnorm_frame, text="LUFS").grid(row=0, column=2, padx=2)
|
||||
|
||||
# 文件选择
|
||||
file_frame = ttk.LabelFrame(self.master, text="输入文件")
|
||||
file_frame.grid(row=2, column=0, padx=10, pady=5, sticky="nsew")
|
||||
|
||||
# 文件操作按钮
|
||||
ttk.Button(file_frame, text="选择文件", command=self.select_files,
|
||||
width=12).grid(row=0, column=0, padx=5, pady=2)
|
||||
ttk.Button(file_frame, text="移除选中", command=self.remove_selected,
|
||||
width=12).grid(row=0, column=1, padx=5, pady=2)
|
||||
ttk.Button(file_frame, text="清空列表", command=self.clear_files,
|
||||
width=12).grid(row=0, column=2, padx=5, pady=2)
|
||||
|
||||
# 文件列表(使用Treeview)
|
||||
self.tree = ttk.Treeview(file_frame, columns=("selected", "filename"),
|
||||
show="headings", height=8)
|
||||
self.tree.heading("selected", text="选中", anchor=tk.W)
|
||||
self.tree.heading("filename", text="文件名", anchor=tk.W)
|
||||
self.tree.column("selected", width=60, anchor=tk.W)
|
||||
self.tree.column("filename", width=600, anchor=tk.W)
|
||||
self.tree.grid(row=1, column=0, columnspan=3, sticky="nsew", padx=5, pady=2)
|
||||
self.tree.bind("<ButtonRelease-1>", self.on_tree_click)
|
||||
|
||||
# 输出目录
|
||||
output_frame = ttk.LabelFrame(self.master, text="输出目录")
|
||||
output_frame.grid(row=3, column=0, padx=10, pady=5, sticky="ew")
|
||||
|
||||
ttk.Entry(output_frame, textvariable=self.output_dir, width=60
|
||||
).grid(row=0, column=0, padx=5, sticky="ew")
|
||||
ttk.Button(output_frame, text="浏览", command=self.select_output_dir,
|
||||
width=8).grid(row=0, column=1, padx=5)
|
||||
|
||||
# 转换按钮区域
|
||||
button_frame = ttk.Frame(self.master)
|
||||
button_frame.grid(row=4, column=0, padx=10, pady=10, sticky="ew")
|
||||
|
||||
ttk.Button(button_frame, text="转换全部文件", command=lambda: self.start_conversion(True),
|
||||
width=15).pack(side=tk.LEFT, padx=5)
|
||||
ttk.Button(button_frame, text="转换选中文件", command=lambda: self.start_conversion(False),
|
||||
width=15).pack(side=tk.LEFT, padx=5)
|
||||
|
||||
# 日志区域
|
||||
log_frame = ttk.LabelFrame(self.master, text="日志")
|
||||
log_frame.grid(row=5, column=0, padx=10, pady=5, sticky="nsew")
|
||||
|
||||
self.log_text = tk.Text(log_frame, height=14, width=80)
|
||||
self.log_text.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# 配置布局权重
|
||||
self.master.columnconfigure(0, weight=1)
|
||||
self.master.rowconfigure(2, weight=1)
|
||||
self.master.rowconfigure(5, weight=3)
|
||||
file_frame.columnconfigure(0, weight=1)
|
||||
file_frame.rowconfigure(1, weight=1)
|
||||
|
||||
def toggle_settings(self):
|
||||
if self.mode.get() == "audio_to_ogg":
|
||||
self.loudnorm_frame.grid()
|
||||
else:
|
||||
self.loudnorm_frame.grid_remove()
|
||||
|
||||
def select_files(self):
|
||||
file_types = [
|
||||
("音频文件", "*.wav *.mogg *.ogg *.flac") if self.mode.get() == "audio_to_ogg"
|
||||
else ("ogg文件", "*.ogg")
|
||||
]
|
||||
|
||||
files = filedialog.askopenfilenames(filetypes=file_types)
|
||||
for f in files:
|
||||
self.tree.insert("", tk.END, values=("[ ]", os.path.basename(f)), tags=(f,))
|
||||
|
||||
def on_tree_click(self, event):
|
||||
"""处理复选框点击事件"""
|
||||
region = self.tree.identify("region", event.x, event.y)
|
||||
if region == "cell":
|
||||
col = self.tree.identify_column(event.x)
|
||||
item = self.tree.identify_row(event.y)
|
||||
if col == "#1": # 点击的是选中列
|
||||
current_val = self.tree.item(item, "values")[0]
|
||||
new_val = "[√]" if current_val == "[ ]" else "[ ]"
|
||||
self.tree.item(item, values=(new_val, self.tree.item(item, "values")[1]))
|
||||
|
||||
def remove_selected(self):
|
||||
"""移除选中的文件"""
|
||||
to_remove = []
|
||||
for item in self.tree.get_children():
|
||||
if self.tree.item(item, "values")[0] == "[√]":
|
||||
to_remove.append(item)
|
||||
for item in reversed(to_remove):
|
||||
self.tree.delete(item)
|
||||
|
||||
def clear_files(self):
|
||||
"""清空所有文件"""
|
||||
for item in self.tree.get_children():
|
||||
self.tree.delete(item)
|
||||
|
||||
def select_output_dir(self):
|
||||
path = filedialog.askdirectory()
|
||||
if path:
|
||||
self.output_dir.set(path)
|
||||
|
||||
def redirect_output(self):
|
||||
class StdoutRedirector:
|
||||
def __init__(self, text_widget):
|
||||
self.text_widget = text_widget
|
||||
self.original_stdout = sys.stdout
|
||||
|
||||
def write(self, message):
|
||||
self.text_widget.insert(tk.END, message)
|
||||
self.text_widget.see(tk.END)
|
||||
self.original_stdout.write(message)
|
||||
|
||||
def flush(self):
|
||||
self.original_stdout.flush()
|
||||
|
||||
sys.stdout = StdoutRedirector(self.log_text)
|
||||
|
||||
def start_conversion(self, convert_all):
|
||||
"""开始转换"""
|
||||
input_files = []
|
||||
for item in self.tree.get_children():
|
||||
if convert_all or self.tree.item(item, "values")[0] == "[√]":
|
||||
input_files.append(self.tree.item(item, "tags")[0])
|
||||
|
||||
if not input_files:
|
||||
msg = "没有找到可转换的文件" if convert_all else "没有选中任何文件"
|
||||
messagebox.showwarning("警告", msg)
|
||||
return
|
||||
|
||||
os.makedirs(self.output_dir.get(), exist_ok=True)
|
||||
|
||||
try:
|
||||
if self.mode.get() == "audio_to_ogg":
|
||||
target_lufs = self.target_lufs.get() if self.enable_loudnorm.get() else None
|
||||
thread = threading.Thread(target=self.convert_audio_to_ogg, args=(target_lufs, input_files))
|
||||
else:
|
||||
thread = threading.Thread(target=self.convert_ogg_to_audio, args=(input_files,))
|
||||
|
||||
thread.start()
|
||||
except Exception as e:
|
||||
print(f"转换初始化失败: {str(e)}")
|
||||
|
||||
def convert_audio_to_ogg(self, target_lufs, input_files):
|
||||
"""音频转到ogg转换逻辑"""
|
||||
for input_path in input_files:
|
||||
try:
|
||||
filename = os.path.basename(input_path)
|
||||
base_name = os.path.splitext(filename)[0]
|
||||
output_path = os.path.join(self.output_dir.get(), f"{base_name}.ogg")
|
||||
|
||||
print(f"正在转换: {filename}")
|
||||
(
|
||||
ffmpeg
|
||||
.input(input_path)
|
||||
.output(output_path, acodec='libopus', audio_bitrate='16k', ac=1, ar=16000, frame_duration=60)
|
||||
.run(overwrite_output=True)
|
||||
)
|
||||
print(f"转换成功: {filename}\n")
|
||||
except Exception as e:
|
||||
print(f"转换失败: {str(e)}\n")
|
||||
|
||||
def convert_ogg_to_audio(self, input_files):
|
||||
"""ogg转回音频转换逻辑"""
|
||||
for input_path in input_files:
|
||||
try:
|
||||
filename = os.path.basename(input_path)
|
||||
base_name = os.path.splitext(filename)[0]
|
||||
output_path = os.path.join(self.output_dir.get(), f"{base_name}.ogg")
|
||||
|
||||
print(f"正在转换: {filename}")
|
||||
(
|
||||
ffmpeg
|
||||
.input(input_path)
|
||||
.output(output_path, acodec='libopus', audio_bitrate='16k', ac=1, ar=16000, frame_duration=60)
|
||||
.run(overwrite_output=True)
|
||||
)
|
||||
print(f"转换成功: {filename}\n")
|
||||
except Exception as e:
|
||||
print(f"转换失败: {str(e)}\n")
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = tk.Tk()
|
||||
app = AudioConverterApp(root)
|
||||
root.mainloop()
|
||||
@@ -39,6 +39,10 @@ CONFIG_UART_ISR_IN_IRAM=y
|
||||
# Fix ESP_SSL error
|
||||
CONFIG_MBEDTLS_SSL_RENEGOTIATION=n
|
||||
|
||||
# ESP32 Camera
|
||||
CONFIG_CAMERA_NO_AFFINITY=y
|
||||
CONFIG_CAMERA_DMA_BUFFER_SIZE_MAX=8192
|
||||
|
||||
# LVGL 9.2.2
|
||||
|
||||
CONFIG_LV_OS_NONE=y
|
||||
@@ -47,6 +51,9 @@ CONFIG_LV_USE_CLIB_MALLOC=y
|
||||
CONFIG_LV_USE_CLIB_STRING=y
|
||||
CONFIG_LV_USE_CLIB_SPRINTF=y
|
||||
CONFIG_LV_USE_IMGFONT=y
|
||||
CONFIG_LV_USE_ASSERT_STYLE=y
|
||||
CONFIG_LV_USE_GIF=y
|
||||
CONFIG_LV_USE_LODEPNG=y
|
||||
|
||||
# Use compressed font
|
||||
CONFIG_LV_FONT_FMT_TXT_LARGE=y
|
||||
|
||||
@@ -13,7 +13,6 @@ CONFIG_SPIRAM_MEMTEST=n
|
||||
CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
|
||||
|
||||
CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB=y
|
||||
CONFIG_ESP32S3_DATA_CACHE_64KB=y
|
||||
CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y
|
||||
|
||||
CONFIG_SR_WN_WN9_NIHAOXIAOZHI_TTS=y
|
||||
|
||||
Reference in New Issue
Block a user