Compare commits

..

9 Commits

Author SHA1 Message Date
jake12355
e0e12450c5 jiuchuan-s3修改按键定义取消不对话自动关机修复屏幕显示不全 (#997)
* jiuchuan-s3修改按键定义取消不对话自动关机修复屏幕显示不全

* jiuchuan-s3修改按键定义取消不对话自动关机修复屏幕显示不全
2025-07-29 17:28:40 +08:00
Xiaoxia
e5ac40aac8 fix audio pm (#1004) 2025-07-29 15:25:40 +08:00
Xiaoxia
345c8be467 Add custom wakeword threshold option (#1003) 2025-07-29 10:56:52 +08:00
香草味的纳西妲喵
def6301292 增加其他分辨率表情转换选项 (#987)
* Create requirements.txt

* Update README.md

* add: 增加其他尺寸表情选项
2025-07-26 02:31:09 +08:00
Xiaoxia
df7cbdfcb6 fix esp-hi crashing in esp_codec_dev_close() (#984) 2025-07-25 10:36:56 +08:00
Terrence
d38763d5ef Reduce SRAM usage of audio tasks 2025-07-25 08:27:12 +08:00
LILYGO_L
e90e540933 增强T-CameraPlus-S3麦克风接收音量 (#958)
* Adapt for LilyGO-T-Circle-S3 device

* Adapt for LilyGO-T-Circle-S3 device

* Remove comments and modify the size of the lilygo-t-circle-s3 image

* Modify the code style and format to Google C++

* Modify the code style and format to Google C++

* Fixed bugs in the LILYGO T-Circle-S3 board and added support for two new boards: LILYGO T-Display-S3-Pro-MVSRLora and LILYGO T-Display-S3-Pro-MVSRLora_NO_BATTERY.

* Added support for two new boards: LILYGO T-Display-S3-Pro-MVSRLora and LILYGO T-Display-S3-Pro-MVSRLora_NO_BATTERY.

* Merge branch 'main' of https://github.com/Llgok/xiaozhi-esp32

* Added support for two new boards: LILYGO T-Display-S3-Pro-MVSRLora and LILYGO T-Display-S3-Pro-MVSRLora_NO_BATTERY.

* Added support for two new boards: LILYGO T-Display-S3-Pro-MVSRLora and LILYGO T-Display-S3-Pro-MVSRLora_NO_BATTERY.

* Added support for two new boards: LILYGO T-Display-S3-Pro-MVSRLora and LILYGO T-Display-S3-Pro-MVSRLora_NO_BATTERY.

* Added support for two new boards: LILYGO T-Display-S3-Pro-MVSRLora and LILYGO T-Display-S3-Pro-MVSRLora_NO_BATTERY.

* Fix the color display issue for T-Display-S3-Pro-MVSRLora and LILYGO T-Display-S3-Pro-MVSRLora_NO_BATTERY.

* Update T-CameraPlus-S3_V1.2 Version Xiaozhi Example

* Resolve the issue where the camera on the T-CameraPlus-S3_V1.2 board cannot be used normally.

* Enhance microphone reception volume

* fix the issue where voice wake-up is not working

* fix the issue where voice wake-up is not working
2025-07-23 23:02:45 +08:00
Ky1eYang
656bf3c7fa FIX: 修复双声道声波配网失效, 添加屏幕打印SSID/密码 (#971)
* debug: 添加声波配网的log打印点display

* fix: 修复双声道下声波配网失效的问题

* fix: codec可能为nullptr的问题(需要从单例board获取)

* Update afsk_demod.cc

fix coding style

---------

Co-authored-by: yangkaiyue <yangkaiyue1@tenclass.com>
Co-authored-by: Xiaoxia <terrence@tenclass.com>
2025-07-23 22:59:07 +08:00
Y1hsiaochunnn
ca35b0761b Update README.md (#968)
The README text description is incorrect. It needs to be corrected.
2025-07-23 22:47:19 +08:00
64 changed files with 539 additions and 660 deletions

View File

@@ -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.2")
set(PROJECT_VER "1.8.4")
# Add this line to disable the specific warning
add_compile_options(-Wno-missing-field-initializers)

View File

@@ -257,6 +257,7 @@ file(GLOB COMMON_SOUNDS ${CMAKE_CURRENT_SOURCE_DIR}/assets/common/*.p3)
if(CONFIG_IDF_TARGET_ESP32)
list(REMOVE_ITEM SOURCES "audio/codecs/box_audio_codec.cc"
"audio/codecs/es8388_audio_codec.cc"
"audio/codecs/es8389_audio_codec.cc"
"led/gpio_led.cc"
)
endif()

View File

@@ -421,19 +421,26 @@ config USE_CUSTOM_WAKE_WORD
config CUSTOM_WAKE_WORD
string "Custom Wake Word"
default "ni hao xiao zhi"
default "xiao tu dou"
depends on USE_CUSTOM_WAKE_WORD
help
自定义唤醒词,用汉语拼音表示
自定义唤醒词,中文用拼音表示,每个字之间用空格隔开
config CUSTOM_WAKE_WORD_DISPLAY
string "Custom Wake Word Display"
default "你好小智"
default "小土豆"
depends on USE_CUSTOM_WAKE_WORD
help
自定义唤醒词对应问候语
唤醒后发送给服务器的问候语
config CUSTOM_WAKE_WORD_THRESHOLD
int "Custom Wake Word Threshold (%)"
default 20
range 1 99
depends on USE_CUSTOM_WAKE_WORD
help
自定义唤醒词阈值范围1-99越小越敏感默认10
config USE_AUDIO_PROCESSOR
bool "Enable Audio Noise Reduction"
default y

View File

@@ -97,34 +97,42 @@ void AudioService::Start() {
esp_timer_start_periodic(audio_power_timer_, 1000000);
/* Start the audio input task */
#if CONFIG_USE_AUDIO_PROCESSOR
/* Start the audio input task */
xTaskCreatePinnedToCore([](void* arg) {
AudioService* audio_service = (AudioService*)arg;
audio_service->AudioInputTask();
vTaskDelete(NULL);
}, "audio_input", 2048 * 3, this, 8, &audio_input_task_handle_, 1);
#else
xTaskCreate([](void* arg) {
AudioService* audio_service = (AudioService*)arg;
audio_service->AudioInputTask();
vTaskDelete(NULL);
}, "audio_input", 2048 * 3, this, 8, &audio_input_task_handle_);
#endif
/* Start the audio output task */
xTaskCreate([](void* arg) {
AudioService* audio_service = (AudioService*)arg;
audio_service->AudioOutputTask();
vTaskDelete(NULL);
}, "audio_output", 4096, this, 3, &audio_output_task_handle_);
}, "audio_output", 2048 * 2, this, 3, &audio_output_task_handle_);
#else
/* Start the audio input task */
xTaskCreate([](void* arg) {
AudioService* audio_service = (AudioService*)arg;
audio_service->AudioInputTask();
vTaskDelete(NULL);
}, "audio_input", 2048 * 2, 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, this, 3, &audio_output_task_handle_);
#endif
/* Start the opus codec task */
xTaskCreate([](void* arg) {
AudioService* audio_service = (AudioService*)arg;
audio_service->OpusCodecTask();
vTaskDelete(NULL);
}, "opus_codec", 4096 * 7, this, 2, &opus_codec_task_handle_);
}, "opus_codec", 2048 * 13, this, 2, &opus_codec_task_handle_);
}
void AudioService::Stop() {

View File

@@ -1,9 +1,7 @@
#include "afe_wake_word.h"
#include "application.h"
#include "audio_service.h"
#include <esp_log.h>
#include <model_path.h>
#include <arpa/inet.h>
#include <sstream>
#define DETECTION_RUNNING_EVENT 1
@@ -27,6 +25,14 @@ AfeWakeWord::~AfeWakeWord() {
heap_caps_free(wake_word_encode_task_stack_);
}
if (wake_word_encode_task_buffer_ != nullptr) {
heap_caps_free(wake_word_encode_task_buffer_);
}
if (models_ != nullptr) {
esp_srmodel_deinit(models_);
}
vEventGroupDelete(event_group_);
}
@@ -34,16 +40,16 @@ bool AfeWakeWord::Initialize(AudioCodec* codec) {
codec_ = codec;
int ref_num = codec_->input_reference() ? 1 : 0;
srmodel_list_t *models = esp_srmodel_init("model");
if (models == nullptr || models->num == -1) {
models_ = esp_srmodel_init("model");
if (models_ == nullptr || models_->num == -1) {
ESP_LOGE(TAG, "Failed to initialize wakenet model");
return false;
}
for (int i = 0; i < models->num; i++) {
ESP_LOGI(TAG, "Model %d: %s", i, models->model_name[i]);
if (strstr(models->model_name[i], ESP_WN_PREFIX) != NULL) {
wakenet_model_ = models->model_name[i];
auto words = esp_srmodel_get_wake_words(models, wakenet_model_);
for (int i = 0; i < models_->num; i++) {
ESP_LOGI(TAG, "Model %d: %s", i, models_->model_name[i]);
if (strstr(models_->model_name[i], ESP_WN_PREFIX) != NULL) {
wakenet_model_ = models_->model_name[i];
auto words = esp_srmodel_get_wake_words(models_, wakenet_model_);
// split by ";" to get all wake words
std::stringstream ss(words);
std::string word;
@@ -60,7 +66,7 @@ bool AfeWakeWord::Initialize(AudioCodec* codec) {
for (int i = 0; i < ref_num; i++) {
input_format.push_back('R');
}
afe_config_t* afe_config = afe_config_init(input_format.c_str(), models, AFE_TYPE_SR, AFE_MODE_HIGH_PERF);
afe_config_t* afe_config = afe_config_init(input_format.c_str(), models_, AFE_TYPE_SR, AFE_MODE_HIGH_PERF);
afe_config->aec_init = codec_->input_reference();
afe_config->aec_mode = AEC_MODE_SR_HIGH_PERF;
afe_config->afe_perferred_core = 1;
@@ -146,10 +152,17 @@ void AfeWakeWord::StoreWakeWordData(const int16_t* data, size_t samples) {
}
void AfeWakeWord::EncodeWakeWordData() {
const size_t stack_size = 4096 * 7;
wake_word_opus_.clear();
if (wake_word_encode_task_stack_ == nullptr) {
wake_word_encode_task_stack_ = (StackType_t*)heap_caps_malloc(4096 * 8, MALLOC_CAP_SPIRAM);
wake_word_encode_task_stack_ = (StackType_t*)heap_caps_malloc(stack_size, MALLOC_CAP_SPIRAM);
assert(wake_word_encode_task_stack_ != nullptr);
}
if (wake_word_encode_task_buffer_ == nullptr) {
wake_word_encode_task_buffer_ = (StaticTask_t*)heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL);
assert(wake_word_encode_task_buffer_ != nullptr);
}
wake_word_encode_task_ = xTaskCreateStatic([](void* arg) {
auto this_ = (AfeWakeWord*)arg;
{
@@ -176,7 +189,7 @@ void AfeWakeWord::EncodeWakeWordData() {
this_->wake_word_cv_.notify_all();
}
vTaskDelete(NULL);
}, "encode_detect_packets", 4096 * 8, this, 2, wake_word_encode_task_stack_, &wake_word_encode_task_buffer_);
}, "encode_wake_word", stack_size, this, 2, wake_word_encode_task_stack_, wake_word_encode_task_buffer_);
}
bool AfeWakeWord::GetWakeWordOpus(std::vector<uint8_t>& opus) {

View File

@@ -7,8 +7,9 @@
#include <esp_afe_sr_models.h>
#include <esp_nsn_models.h>
#include <model_path.h>
#include <list>
#include <deque>
#include <string>
#include <vector>
#include <functional>
@@ -34,6 +35,7 @@ public:
const std::string& GetLastDetectedWakeWord() const { return last_detected_wake_word_; }
private:
srmodel_list_t *models_ = nullptr;
esp_afe_sr_iface_t* afe_iface_ = nullptr;
esp_afe_sr_data_t* afe_data_ = nullptr;
char* wakenet_model_ = NULL;
@@ -44,10 +46,10 @@ private:
std::string last_detected_wake_word_;
TaskHandle_t wake_word_encode_task_ = nullptr;
StaticTask_t wake_word_encode_task_buffer_;
StaticTask_t* wake_word_encode_task_buffer_ = nullptr;
StackType_t* wake_word_encode_task_stack_ = nullptr;
std::list<std::vector<int16_t>> wake_word_pcm_;
std::list<std::vector<uint8_t>> wake_word_opus_;
std::deque<std::vector<int16_t>> wake_word_pcm_;
std::deque<std::vector<uint8_t>> wake_word_opus_;
std::mutex wake_word_mutex_;
std::condition_variable wake_word_cv_;

View File

@@ -1,37 +1,21 @@
#include "custom_wake_word.h"
#include "application.h"
#include "audio_service.h"
#include "system_info.h"
#include <esp_log.h>
#include <model_path.h>
#include <arpa/inet.h>
#include "esp_wn_iface.h"
#include "esp_wn_models.h"
#include "esp_afe_sr_iface.h"
#include "esp_afe_sr_models.h"
#include "esp_mn_iface.h"
#include "esp_mn_models.h"
#include "esp_mn_speech_commands.h"
#include <sstream>
#define DETECTION_RUNNING_EVENT 1
#define TAG "CustomWakeWord"
CustomWakeWord::CustomWakeWord()
: afe_data_(nullptr),
wake_word_pcm_(),
wake_word_opus_() {
event_group_ = xEventGroupCreate();
: wake_word_pcm_(), wake_word_opus_() {
}
CustomWakeWord::~CustomWakeWord() {
if (afe_data_ != nullptr) {
afe_iface_->destroy(afe_data_);
}
// 清理 multinet 资源
if (multinet_model_data_ != nullptr && multinet_ != nullptr) {
multinet_->destroy(multinet_model_data_);
multinet_model_data_ = nullptr;
@@ -41,64 +25,41 @@ CustomWakeWord::~CustomWakeWord() {
heap_caps_free(wake_word_encode_task_stack_);
}
vEventGroupDelete(event_group_);
if (wake_word_encode_task_buffer_ != nullptr) {
heap_caps_free(wake_word_encode_task_buffer_);
}
if (models_ != nullptr) {
esp_srmodel_deinit(models_);
}
}
bool CustomWakeWord::Initialize(AudioCodec* codec) {
codec_ = codec;
models = esp_srmodel_init("model");
if (models == nullptr || models->num == -1) {
models_ = esp_srmodel_init("model");
if (models_ == nullptr || models_->num == -1) {
ESP_LOGE(TAG, "Failed to initialize wakenet model");
return false;
}
// 初始化 multinet (命令词识别)
mn_name_ = esp_srmodel_filter(models, ESP_MN_PREFIX, ESP_MN_CHINESE);
mn_name_ = esp_srmodel_filter(models_, ESP_MN_PREFIX, ESP_MN_CHINESE);
if (mn_name_ == nullptr) {
ESP_LOGE(TAG, "Failed to initialize multinet, mn_name is nullptr");
ESP_LOGI(TAG, "Please refer to https://pcn7cs20v8cr.feishu.cn/wiki/CpQjwQsCJiQSWSkYEvrcxcbVnwh to add custom wake word");
return false;
}
ESP_LOGI(TAG, "multinet:%s", mn_name_);
ESP_LOGI(TAG, "multinet: %s", mn_name_);
multinet_ = esp_mn_handle_from_name(mn_name_);
multinet_model_data_ = multinet_->create(mn_name_, 2000); // 2秒超时
multinet_->set_det_threshold(multinet_model_data_, 0.5);
multinet_model_data_ = multinet_->create(mn_name_, 3000); // 3 秒超时
multinet_->set_det_threshold(multinet_model_data_, CONFIG_CUSTOM_WAKE_WORD_THRESHOLD / 100.0f);
esp_mn_commands_clear();
esp_mn_commands_add(1, CONFIG_CUSTOM_WAKE_WORD); // 添加自定义唤醒词作为命令词
esp_mn_commands_add(1, CONFIG_CUSTOM_WAKE_WORD);
esp_mn_commands_update();
// 打印所有的命令词
multinet_->print_active_speech_commands(multinet_model_data_);
ESP_LOGI(TAG, "Custom wake word: %s", CONFIG_CUSTOM_WAKE_WORD);
// 初始化 afe
int ref_num = codec_->input_reference() ? 1 : 0;
std::string input_format;
for (int i = 0; i < codec_->input_channels() - ref_num; i++) {
input_format.push_back('M');
}
for (int i = 0; i < ref_num; i++) {
input_format.push_back('R');
}
afe_config_t* afe_config = afe_config_init(input_format.c_str(), models, AFE_TYPE_SR, AFE_MODE_HIGH_PERF);
afe_config->aec_init = codec_->input_reference();
afe_config->aec_mode = AEC_MODE_SR_HIGH_PERF;
afe_config->afe_perferred_core = 1;
afe_config->afe_perferred_priority = 1;
afe_config->memory_alloc_mode = AFE_MEMORY_ALLOC_MORE_PSRAM;
afe_iface_ = esp_afe_handle_from_config(afe_config);
afe_data_ = afe_iface_->create_from_config(afe_config);
xTaskCreate([](void* arg) {
auto this_ = (CustomWakeWord*)arg;
this_->AudioDetectionTask();
vTaskDelete(NULL);
}, "audio_detection", 16384, this, 3, nullptr);
return true;
}
@@ -107,102 +68,54 @@ void CustomWakeWord::OnWakeWordDetected(std::function<void(const std::string& wa
}
void CustomWakeWord::Start() {
xEventGroupSetBits(event_group_, DETECTION_RUNNING_EVENT);
running_ = true;
}
void CustomWakeWord::Stop() {
xEventGroupClearBits(event_group_, DETECTION_RUNNING_EVENT);
if (afe_data_ != nullptr) {
afe_iface_->reset_buffer(afe_data_);
}
running_ = false;
}
void CustomWakeWord::Feed(const std::vector<int16_t>& data) {
if (afe_data_ == nullptr) {
if (multinet_model_data_ == nullptr || !running_) {
return;
}
afe_iface_->feed(afe_data_, data.data());
StoreWakeWordData(data);
esp_mn_state_t mn_state = multinet_->detect(multinet_model_data_, const_cast<int16_t*>(data.data()));
if (mn_state == ESP_MN_STATE_DETECTING) {
return;
} else if (mn_state == ESP_MN_STATE_DETECTED) {
esp_mn_results_t *mn_result = multinet_->get_results(multinet_model_data_);
ESP_LOGI(TAG, "Custom wake word detected: command_id=%d, string=%s, prob=%f",
mn_result->command_id[0], mn_result->string, mn_result->prob[0]);
if (mn_result->command_id[0] == 1) {
last_detected_wake_word_ = CONFIG_CUSTOM_WAKE_WORD_DISPLAY;
}
running_ = false;
if (wake_word_detected_callback_) {
wake_word_detected_callback_(last_detected_wake_word_);
}
multinet_->clean(multinet_model_data_);
} else if (mn_state == ESP_MN_STATE_TIMEOUT) {
ESP_LOGD(TAG, "Command word detection timeout, cleaning state");
multinet_->clean(multinet_model_data_);
}
}
size_t CustomWakeWord::GetFeedSize() {
if (afe_data_ == nullptr) {
if (multinet_model_data_ == nullptr) {
return 0;
}
return afe_iface_->get_feed_chunksize(afe_data_) * codec_->input_channels();
return multinet_->get_samp_chunksize(multinet_model_data_) * codec_->input_channels();
}
void CustomWakeWord::AudioDetectionTask() {
auto fetch_size = afe_iface_->get_fetch_chunksize(afe_data_);
auto feed_size = afe_iface_->get_feed_chunksize(afe_data_);
// 检查 multinet 是否已正确初始化
if (multinet_ == nullptr || multinet_model_data_ == nullptr) {
ESP_LOGE(TAG, "Multinet not initialized properly");
return;
}
int mu_chunksize = multinet_->get_samp_chunksize(multinet_model_data_);
assert(mu_chunksize == feed_size);
ESP_LOGI(TAG, "Audio detection task started, feed size: %d fetch size: %d", feed_size, fetch_size);
// 禁用wakenet直接使用multinet检测自定义唤醒词
afe_iface_->disable_wakenet(afe_data_);
while (true) {
xEventGroupWaitBits(event_group_, DETECTION_RUNNING_EVENT, pdFALSE, pdTRUE, portMAX_DELAY);
auto res = afe_iface_->fetch_with_delay(afe_data_, portMAX_DELAY);
if (res == nullptr || res->ret_value == ESP_FAIL) {
ESP_LOGW(TAG, "Fetch failed, continue");
continue;
}
// 存储音频数据用于语音识别
StoreWakeWordData(res->data, res->data_size / sizeof(int16_t));
// 直接使用multinet检测自定义唤醒词
esp_mn_state_t mn_state = multinet_->detect(multinet_model_data_, res->data);
if (mn_state == ESP_MN_STATE_DETECTING) {
// 仍在检测中,继续
continue;
} else if (mn_state == ESP_MN_STATE_DETECTED) {
// 检测到自定义唤醒词
esp_mn_results_t *mn_result = multinet_->get_results(multinet_model_data_);
ESP_LOGI(TAG, "Custom wake word detected: command_id=%d, string=%s, prob=%f",
mn_result->command_id[0], mn_result->string, mn_result->prob[0]);
if (mn_result->command_id[0] == 1) { // 自定义唤醒词
ESP_LOGI(TAG, "Custom wake word '%s' detected successfully!", CONFIG_CUSTOM_WAKE_WORD);
// 停止检测
Stop();
last_detected_wake_word_ = CONFIG_CUSTOM_WAKE_WORD_DISPLAY;
// 调用回调
if (wake_word_detected_callback_) {
wake_word_detected_callback_(last_detected_wake_word_);
}
// 清理multinet状态准备下次检测
multinet_->clean(multinet_model_data_);
ESP_LOGI(TAG, "Ready for next detection");
}
} else if (mn_state == ESP_MN_STATE_TIMEOUT) {
// 超时,清理状态继续检测
ESP_LOGD(TAG, "Command word detection timeout, cleaning state");
multinet_->clean(multinet_model_data_);
continue;
}
}
ESP_LOGI(TAG, "Audio detection task ended");
}
void CustomWakeWord::StoreWakeWordData(const int16_t* data, size_t samples) {
void CustomWakeWord::StoreWakeWordData(const std::vector<int16_t>& data) {
// store audio data to wake_word_pcm_
wake_word_pcm_.emplace_back(std::vector<int16_t>(data, data + samples));
wake_word_pcm_.push_back(data);
// keep about 2 seconds of data, detect duration is 30ms (sample_rate == 16000, chunksize == 512)
while (wake_word_pcm_.size() > 2000 / 30) {
wake_word_pcm_.pop_front();
@@ -210,10 +123,17 @@ void CustomWakeWord::StoreWakeWordData(const int16_t* data, size_t samples) {
}
void CustomWakeWord::EncodeWakeWordData() {
const size_t stack_size = 4096 * 7;
wake_word_opus_.clear();
if (wake_word_encode_task_stack_ == nullptr) {
wake_word_encode_task_stack_ = (StackType_t*)heap_caps_malloc(4096 * 8, MALLOC_CAP_SPIRAM);
wake_word_encode_task_stack_ = (StackType_t*)heap_caps_malloc(stack_size, MALLOC_CAP_SPIRAM);
assert(wake_word_encode_task_stack_ != nullptr);
}
if (wake_word_encode_task_buffer_ == nullptr) {
wake_word_encode_task_buffer_ = (StaticTask_t*)heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL);
assert(wake_word_encode_task_buffer_ != nullptr);
}
wake_word_encode_task_ = xTaskCreateStatic([](void* arg) {
auto this_ = (CustomWakeWord*)arg;
{
@@ -240,7 +160,7 @@ void CustomWakeWord::EncodeWakeWordData() {
this_->wake_word_cv_.notify_all();
}
vTaskDelete(NULL);
}, "encode_detect_packets", 4096 * 8, this, 2, wake_word_encode_task_stack_, &wake_word_encode_task_buffer_);
}, "encode_wake_word", stack_size, this, 2, wake_word_encode_task_stack_, wake_word_encode_task_buffer_);
}
bool CustomWakeWord::GetWakeWordOpus(std::vector<uint8_t>& opus) {

View File

@@ -1,24 +1,18 @@
#ifndef CUSTOM_WAKE_WORD_H
#define CUSTOM_WAKE_WORD_H
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/event_groups.h>
#include <esp_afe_sr_models.h>
#include <esp_afe_sr_iface.h>
#include <esp_nsn_models.h>
#include <esp_wn_iface.h>
#include <esp_wn_models.h>
#include <esp_attr.h>
#include <esp_mn_iface.h>
#include <esp_mn_models.h>
#include <model_path.h>
#include <list>
#include <deque>
#include <string>
#include <vector>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include "audio_codec.h"
#include "wake_word.h"
@@ -39,32 +33,26 @@ public:
const std::string& GetLastDetectedWakeWord() const { return last_detected_wake_word_; }
private:
esp_afe_sr_iface_t* afe_iface_ = nullptr;
esp_afe_sr_data_t* afe_data_ = nullptr;
srmodel_list_t *models = nullptr;
// multinet 相关成员变量
esp_mn_iface_t* multinet_ = nullptr;
model_iface_data_t* multinet_model_data_ = nullptr;
srmodel_list_t *models_ = nullptr;
char* mn_name_ = nullptr;
char* wakenet_model_ = NULL;
std::vector<std::string> wake_words_;
EventGroupHandle_t event_group_;
std::function<void(const std::string& wake_word)> wake_word_detected_callback_;
AudioCodec* codec_ = nullptr;
std::string last_detected_wake_word_;
std::atomic<bool> running_ = false;
TaskHandle_t wake_word_encode_task_ = nullptr;
StaticTask_t wake_word_encode_task_buffer_;
StaticTask_t* wake_word_encode_task_buffer_ = nullptr;
StackType_t* wake_word_encode_task_stack_ = nullptr;
std::list<std::vector<int16_t>> wake_word_pcm_;
std::list<std::vector<uint8_t>> wake_word_opus_;
std::deque<std::vector<int16_t>> wake_word_pcm_;
std::deque<std::vector<uint8_t>> wake_word_opus_;
std::mutex wake_word_mutex_;
std::condition_variable wake_word_cv_;
void StoreWakeWordData(const int16_t* data, size_t size);
void AudioDetectionTask();
void StoreWakeWordData(const std::vector<int16_t>& data);
};
#endif

View File

@@ -1,17 +1,10 @@
#include "esp_wake_word.h"
#include "application.h"
#include <esp_log.h>
#include <model_path.h>
#include <arpa/inet.h>
#include <sstream>
#define DETECTION_RUNNING_EVENT 1
#define TAG "EspWakeWord"
EspWakeWord::EspWakeWord() {
event_group_ = xEventGroupCreate();
}
EspWakeWord::~EspWakeWord() {
@@ -19,8 +12,6 @@ EspWakeWord::~EspWakeWord() {
wakenet_iface_->destroy(wakenet_data_);
esp_srmodel_deinit(wakenet_model_);
}
vEventGroupDelete(event_group_);
}
bool EspWakeWord::Initialize(AudioCodec* codec) {
@@ -53,18 +44,22 @@ void EspWakeWord::OnWakeWordDetected(std::function<void(const std::string& wake_
}
void EspWakeWord::Start() {
xEventGroupSetBits(event_group_, DETECTION_RUNNING_EVENT);
running_ = true;
}
void EspWakeWord::Stop() {
xEventGroupClearBits(event_group_, DETECTION_RUNNING_EVENT);
running_ = false;
}
void EspWakeWord::Feed(const std::vector<int16_t>& data) {
if (wakenet_data_ == nullptr || !running_) {
return;
}
int res = wakenet_iface_->detect(wakenet_data_, (int16_t *)data.data());
if (res > 0) {
Stop();
last_detected_wake_word_ = wakenet_iface_->get_word_name(wakenet_data_, res);
running_ = false;
if (wake_word_detected_callback_) {
wake_word_detected_callback_(last_detected_wake_word_);

View File

@@ -1,20 +1,14 @@
#ifndef ESP_WAKE_WORD_H
#define ESP_WAKE_WORD_H
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/event_groups.h>
#include <esp_wn_iface.h>
#include <esp_wn_models.h>
#include <model_path.h>
#include <list>
#include <string>
#include <vector>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include "audio_codec.h"
#include "wake_word.h"
@@ -38,8 +32,8 @@ private:
esp_wn_iface_t *wakenet_iface_ = nullptr;
model_iface_data_t *wakenet_data_ = nullptr;
srmodel_list_t *wakenet_model_ = nullptr;
EventGroupHandle_t event_group_;
AudioCodec* codec_ = nullptr;
std::atomic<bool> running_ = false;
std::function<void(const std::string& wake_word)> wake_word_detected_callback_;
std::string last_detected_wake_word_;

View File

@@ -139,8 +139,7 @@ private:
power_save_timer_->OnEnterSleepMode([this]() {
power_sleep_ = kDeviceNeutralSleep;
XiaozhiStatus_ = kDevice_join_Sleep;
display_->SetChatMessage("system", "");
display_->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
if (LcdStatus_ != kDevicelcdbacklightOff) {
GetBacklight()->SetBrightness(1);
@@ -148,8 +147,7 @@ private:
});
power_save_timer_->OnExitSleepMode([this]() {
power_sleep_ = kDeviceNoSleep;
display_->SetChatMessage("system", "");
display_->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
if (XiaozhiStatus_ != kDevice_Exit_Sleep) {
GetBacklight()->RestoreBrightness();

View File

@@ -99,13 +99,11 @@ private:
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
display_->SetChatMessage("system", "");
display_->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(1);
});
power_save_timer_->OnExitSleepMode([this]() {
display_->SetChatMessage("system", "");
display_->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {

View File

@@ -99,13 +99,11 @@ private:
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
display_->SetChatMessage("system", "");
display_->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(1);
});
power_save_timer_->OnExitSleepMode([this]() {
display_->SetChatMessage("system", "");
display_->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {

View File

@@ -2,6 +2,7 @@
#include <cstring>
#include <algorithm>
#include "esp_log.h"
#include "display.h"
#ifndef M_PI
#define M_PI 3.14159265358979323846
@@ -12,7 +13,10 @@ namespace audio_wifi_config
static const char *kLogTag = "AUDIO_WIFI_CONFIG";
void ReceiveWifiCredentialsFromAudio(Application *app,
WifiConfigurationAp *wifi_ap)
WifiConfigurationAp *wifi_ap,
Display *display,
size_t input_channels
)
{
const int kInputSampleRate = 16000; // Input sampling rate
const float kDownsampleStep = static_cast<float>(kInputSampleRate) / static_cast<float>(kAudioSampleRate); // Downsampling step
@@ -35,29 +39,31 @@ namespace audio_wifi_config
vTaskDelay(pdMS_TO_TICKS(10));
continue;
}
if (input_channels == 2) { // 如果是双声道输入,转换为单声道
auto mono_data = std::vector<int16_t>(audio_data.size() / 2);
for (size_t i = 0, j = 0; i < mono_data.size(); ++i, j += 2) {
mono_data[i] = audio_data[j];
}
audio_data = std::move(mono_data);
}
// Downsample the audio data
std::vector<float> downsampled_data;
size_t last_index = 0;
if (kDownsampleStep > 1.0f)
{
if (kDownsampleStep > 1.0f) {
downsampled_data.reserve(audio_data.size() / static_cast<size_t>(kDownsampleStep));
for (size_t i = 0; i < audio_data.size(); ++i)
{
for (size_t i = 0; i < audio_data.size(); ++i) {
size_t sample_index = static_cast<size_t>(i / kDownsampleStep);
if ((sample_index + 1) > last_index)
{
if ((sample_index + 1) > last_index) {
downsampled_data.push_back(static_cast<float>(audio_data[i]));
last_index = sample_index + 1;
}
}
}
else
{
} else {
downsampled_data.reserve(audio_data.size());
for (int16_t sample : audio_data)
{
for (int16_t sample : audio_data) {
downsampled_data.push_back(static_cast<float>(sample));
}
}
@@ -66,35 +72,28 @@ namespace audio_wifi_config
auto probabilities = signal_processor.ProcessAudioSamples(downsampled_data);
// Feed probability data to the data buffer
if (data_buffer.ProcessProbabilityData(probabilities, 0.5f))
{
if (data_buffer.ProcessProbabilityData(probabilities, 0.5f)) {
// If complete data was received, extract WiFi credentials
if (data_buffer.decoded_text.has_value())
{
if (data_buffer.decoded_text.has_value()) {
ESP_LOGI(kLogTag, "Received text data: %s", data_buffer.decoded_text->c_str());
display->SetChatMessage("system", data_buffer.decoded_text->c_str());
// Split SSID and password by newline character
std::string wifi_ssid, wifi_password;
size_t newline_position = data_buffer.decoded_text->find('\n');
if (newline_position != std::string::npos)
{
if (newline_position != std::string::npos) {
wifi_ssid = data_buffer.decoded_text->substr(0, newline_position);
wifi_password = data_buffer.decoded_text->substr(newline_position + 1);
ESP_LOGI(kLogTag, "WiFi SSID: %s, Password: %s", wifi_ssid.c_str(), wifi_password.c_str());
}
else
{
} else {
ESP_LOGE(kLogTag, "Invalid data format, no newline character found");
continue;
}
if (wifi_ap->ConnectToWifi(wifi_ssid, wifi_password))
{
if (wifi_ap->ConnectToWifi(wifi_ssid, wifi_password)) {
wifi_ap->Save(wifi_ssid, wifi_password); // Save WiFi credentials
esp_restart(); // Restart device to apply new WiFi configuration
}
else
{
} else {
ESP_LOGE(kLogTag, "Failed to connect to WiFi with received credentials");
}
data_buffer.decoded_text.reset(); // Clear processed data
@@ -115,8 +114,7 @@ namespace audio_wifi_config
// FrequencyDetector implementation
FrequencyDetector::FrequencyDetector(float frequency, size_t window_size)
: frequency_(frequency), window_size_(window_size)
{
: frequency_(frequency), window_size_(window_size) {
frequency_bin_ = std::floor(frequency_ * static_cast<float>(window_size_));
angular_frequency_ = 2.0f * M_PI * frequency_;
cos_coefficient_ = std::cos(angular_frequency_);
@@ -128,17 +126,14 @@ namespace audio_wifi_config
state_buffer_.push_back(0.0f);
}
void FrequencyDetector::Reset()
{
void FrequencyDetector::Reset() {
state_buffer_.clear();
state_buffer_.push_back(0.0f);
state_buffer_.push_back(0.0f);
}
void FrequencyDetector::ProcessSample(float sample)
{
if (state_buffer_.size() < 2)
{
void FrequencyDetector::ProcessSample(float sample) {
if (state_buffer_.size() < 2) {
return;
}
@@ -153,10 +148,8 @@ namespace audio_wifi_config
state_buffer_.push_back(s_current); // Add new S[0]
}
float FrequencyDetector::GetAmplitude() const
{
if (state_buffer_.size() < 2)
{
float FrequencyDetector::GetAmplitude() const {
if (state_buffer_.size() < 2) {
return 0.0f;
}
@@ -172,10 +165,8 @@ namespace audio_wifi_config
// AudioSignalProcessor implementation
AudioSignalProcessor::AudioSignalProcessor(size_t sample_rate, size_t mark_frequency, size_t space_frequency,
size_t bit_rate, size_t window_size)
: input_buffer_size_(window_size), output_sample_count_(0)
{
if (sample_rate % bit_rate != 0)
{
: input_buffer_size_(window_size), output_sample_count_(0) {
if (sample_rate % bit_rate != 0) {
// On ESP32 we can continue execution, but log the error
ESP_LOGW(kLogTag, "Sample rate %zu is not divisible by bit rate %zu", sample_rate, bit_rate);
}
@@ -189,28 +180,21 @@ namespace audio_wifi_config
samples_per_bit_ = sample_rate / bit_rate; // Number of samples per bit
}
std::vector<float> AudioSignalProcessor::ProcessAudioSamples(const std::vector<float> &samples)
{
std::vector<float> AudioSignalProcessor::ProcessAudioSamples(const std::vector<float> &samples) {
std::vector<float> result;
for (float sample : samples)
{
if (input_buffer_.size() < input_buffer_size_)
{
for (float sample : samples) {
if (input_buffer_.size() < input_buffer_size_) {
input_buffer_.push_back(sample); // Just add, don't process yet
}
else
{
} else {
// Input buffer is full, process the data
input_buffer_.pop_front(); // Remove oldest sample
input_buffer_.push_back(sample); // Add new sample
output_sample_count_++;
if (output_sample_count_ >= samples_per_bit_)
{
if (output_sample_count_ >= samples_per_bit_) {
// Process all samples in the window using Goertzel algorithm
for (float window_sample : input_buffer_)
{
for (float window_sample : input_buffer_) {
mark_detector_->ProcessSample(window_sample);
space_detector_->ProcessSample(window_sample);
}
@@ -239,8 +223,7 @@ namespace audio_wifi_config
: current_state_(DataReceptionState::kInactive),
start_of_transmission_(kDefaultStartTransmissionPattern),
end_of_transmission_(kDefaultEndTransmissionPattern),
enable_checksum_validation_(true)
{
enable_checksum_validation_(true) {
identifier_buffer_size_ = std::max(start_of_transmission_.size(), end_of_transmission_.size());
max_bit_buffer_size_ = 776; // Preset bit buffer size, 776 bits = (32 + 1 + 63 + 1) * 8 = 776
@@ -252,48 +235,39 @@ namespace audio_wifi_config
: current_state_(DataReceptionState::kInactive),
start_of_transmission_(start_identifier),
end_of_transmission_(end_identifier),
enable_checksum_validation_(enable_checksum)
{
enable_checksum_validation_(enable_checksum) {
identifier_buffer_size_ = std::max(start_of_transmission_.size(), end_of_transmission_.size());
max_bit_buffer_size_ = max_byte_size * 8; // Bit buffer size in bytes
bit_buffer_.reserve(max_bit_buffer_size_);
}
uint8_t AudioDataBuffer::CalculateChecksum(const std::string &text)
{
uint8_t AudioDataBuffer::CalculateChecksum(const std::string &text) {
uint8_t checksum = 0;
for (char character : text)
{
for (char character : text) {
checksum += static_cast<uint8_t>(character);
}
return checksum;
}
void AudioDataBuffer::ClearBuffers()
{
void AudioDataBuffer::ClearBuffers() {
identifier_buffer_.clear();
bit_buffer_.clear();
}
bool AudioDataBuffer::ProcessProbabilityData(const std::vector<float> &probabilities, float threshold)
{
for (float probability : probabilities)
{
bool AudioDataBuffer::ProcessProbabilityData(const std::vector<float> &probabilities, float threshold) {
for (float probability : probabilities) {
uint8_t bit = (probability > threshold) ? 1 : 0;
if (identifier_buffer_.size() >= identifier_buffer_size_)
{
if (identifier_buffer_.size() >= identifier_buffer_size_) {
identifier_buffer_.pop_front(); // Maintain buffer size
}
identifier_buffer_.push_back(bit);
// Process received bit based on state machine
switch (current_state_)
{
switch (current_state_) {
case DataReceptionState::kInactive:
if (identifier_buffer_.size() >= start_of_transmission_.size())
{
if (identifier_buffer_.size() >= start_of_transmission_.size()) {
current_state_ = DataReceptionState::kWaiting; // Enter waiting state
ESP_LOGI(kLogTag, "Entering Waiting state");
}
@@ -301,8 +275,7 @@ namespace audio_wifi_config
case DataReceptionState::kWaiting:
// Waiting state, possibly waiting for transmission end
if (identifier_buffer_.size() >= start_of_transmission_.size())
{
if (identifier_buffer_.size() >= start_of_transmission_.size()) {
std::vector<uint8_t> identifier_snapshot(identifier_buffer_.begin(), identifier_buffer_.end());
if (identifier_snapshot == start_of_transmission_)
{
@@ -315,11 +288,9 @@ namespace audio_wifi_config
case DataReceptionState::kReceiving:
bit_buffer_.push_back(bit);
if (identifier_buffer_.size() >= end_of_transmission_.size())
{
if (identifier_buffer_.size() >= end_of_transmission_.size()) {
std::vector<uint8_t> identifier_snapshot(identifier_buffer_.begin(), identifier_buffer_.end());
if (identifier_snapshot == end_of_transmission_)
{
if (identifier_snapshot == end_of_transmission_) {
current_state_ = DataReceptionState::kInactive; // Enter inactive state
// Convert bits to bytes
@@ -328,22 +299,18 @@ namespace audio_wifi_config
uint8_t received_checksum = 0;
size_t minimum_length = 0;
if (enable_checksum_validation_)
{
if (enable_checksum_validation_) {
// If checksum is required, last byte is checksum
minimum_length = 1 + start_of_transmission_.size() / 8;
if (bytes.size() >= minimum_length)
{
received_checksum = bytes[bytes.size() - start_of_transmission_.size() / 8 - 1];
}
}
else
{
} else {
minimum_length = start_of_transmission_.size() / 8;
}
if (bytes.size() < minimum_length)
{
if (bytes.size() < minimum_length) {
ClearBuffers();
ESP_LOGW(kLogTag, "Data too short, clearing buffer");
return false; // Data too short, return failure
@@ -356,11 +323,9 @@ namespace audio_wifi_config
std::string result(text_bytes.begin(), text_bytes.end());
// Validate checksum if required
if (enable_checksum_validation_)
{
if (enable_checksum_validation_) {
uint8_t calculated_checksum = CalculateChecksum(result);
if (calculated_checksum != received_checksum)
{
if (calculated_checksum != received_checksum) {
// Checksum mismatch
ESP_LOGW(kLogTag, "Checksum mismatch: expected %d, got %d",
received_checksum, calculated_checksum);
@@ -372,9 +337,7 @@ namespace audio_wifi_config
ClearBuffers();
decoded_text = result;
return true; // Return success
}
else if (bit_buffer_.size() >= max_bit_buffer_size_)
{
} else if (bit_buffer_.size() >= max_bit_buffer_size_) {
// If not end identifier and bit buffer is full, reset
ClearBuffers();
ESP_LOGW(kLogTag, "Buffer overflow, clearing buffer");
@@ -388,19 +351,16 @@ namespace audio_wifi_config
return false;
}
std::vector<uint8_t> AudioDataBuffer::ConvertBitsToBytes(const std::vector<uint8_t> &bits) const
{
std::vector<uint8_t> AudioDataBuffer::ConvertBitsToBytes(const std::vector<uint8_t> &bits) const {
std::vector<uint8_t> bytes;
// Ensure number of bits is a multiple of 8
size_t complete_bytes_count = bits.size() / 8;
bytes.reserve(complete_bytes_count);
for (size_t i = 0; i < complete_bytes_count; ++i)
{
for (size_t i = 0; i < complete_bytes_count; ++i) {
uint8_t byte_value = 0;
for (size_t j = 0; j < 8; ++j)
{
for (size_t j = 0; j < 8; ++j) {
byte_value |= bits[i * 8 + j] << (7 - j);
}
bytes.push_back(byte_value);
@@ -408,4 +368,4 @@ namespace audio_wifi_config
return bytes;
}
}
}

View File

@@ -19,7 +19,8 @@ const size_t kWindowSize = 64;
namespace audio_wifi_config
{
// Main function to receive WiFi credentials through audio signal
void ReceiveWifiCredentialsFromAudio(Application *app, WifiConfigurationAp *wifi_ap);
void ReceiveWifiCredentialsFromAudio(Application *app, WifiConfigurationAp *wifi_ap, Display *display,
size_t input_channels = 1);
/**
* Goertzel algorithm implementation for single frequency detection

View File

@@ -62,12 +62,25 @@ void PowerSaveTimer::PowerSaveCheck() {
ticks_++;
if (seconds_to_sleep_ != -1 && ticks_ >= seconds_to_sleep_) {
if (!in_sleep_mode_) {
ESP_LOGI(TAG, "Enabling power save mode");
in_sleep_mode_ = true;
if (on_enter_sleep_mode_) {
on_enter_sleep_mode_();
}
if (cpu_max_freq_ != -1) {
// Disable wake word detection
auto& audio_service = app.GetAudioService();
is_wake_word_running_ = audio_service.IsWakeWordRunning();
if (is_wake_word_running_) {
audio_service.EnableWakeWordDetection(false);
}
// Disable audio input
auto codec = Board::GetInstance().GetAudioCodec();
if (codec) {
codec->EnableInput(false);
}
esp_pm_config_t pm_config = {
.max_freq_mhz = cpu_max_freq_,
.min_freq_mhz = 40,
@@ -85,6 +98,7 @@ void PowerSaveTimer::PowerSaveCheck() {
void PowerSaveTimer::WakeUp() {
ticks_ = 0;
if (in_sleep_mode_) {
ESP_LOGI(TAG, "Exiting power save mode");
in_sleep_mode_ = false;
if (cpu_max_freq_ != -1) {
@@ -94,6 +108,13 @@ void PowerSaveTimer::WakeUp() {
.light_sleep_enable = false,
};
esp_pm_configure(&pm_config);
// Enable wake word detection
auto& app = Application::GetInstance();
auto& audio_service = app.GetAudioService();
if (is_wake_word_running_) {
audio_service.EnableWakeWordDetection(true);
}
}
if (on_exit_sleep_mode_) {

View File

@@ -22,6 +22,7 @@ private:
esp_timer_handle_t power_save_timer_ = nullptr;
bool enabled_ = false;
bool in_sleep_mode_ = false;
bool is_wake_word_running_ = false;
int ticks_ = 0;
int cpu_max_freq_;
int seconds_to_sleep_;

View File

@@ -70,6 +70,12 @@ void SleepTimer::CheckTimer() {
if (on_enter_light_sleep_mode_) {
on_enter_light_sleep_mode_();
}
auto& audio_service = app.GetAudioService();
bool is_wake_word_running = audio_service.IsWakeWordRunning();
if (is_wake_word_running) {
audio_service.EnableWakeWordDetection(false);
}
app.Schedule([this, &app]() {
while (in_light_sleep_mode_) {
@@ -86,12 +92,17 @@ void SleepTimer::CheckTimer() {
lvgl_port_resume();
auto wakeup_reason = esp_sleep_get_wakeup_cause();
ESP_LOGI(TAG, "Wake up from light sleep, wakeup_reason: %d", wakeup_reason);
if (wakeup_reason != ESP_SLEEP_WAKEUP_TIMER) {
break;
}
}
WakeUp();
});
if (is_wake_word_running) {
audio_service.EnableWakeWordDetection(true);
}
}
}
if (seconds_to_deep_sleep_ != -1 && ticks_ >= seconds_to_deep_sleep_) {

View File

@@ -52,7 +52,14 @@ void WifiBoard::EnterWifiConfigMode() {
application.Alert(Lang::Strings::WIFI_CONFIG_MODE, hint.c_str(), "", Lang::Sounds::P3_WIFICONFIG);
#if CONFIG_USE_ACOUSTIC_WIFI_PROVISIONING
audio_wifi_config::ReceiveWifiCredentialsFromAudio(&application, &wifi_ap);
auto display = Board::GetInstance().GetDisplay();
auto codec = Board::GetInstance().GetAudioCodec();
int channel = 1;
if (codec) {
channel = codec->input_channels();
}
ESP_LOGI(TAG, "Start receiving WiFi credentials from audio, input channels: %d", channel);
audio_wifi_config::ReceiveWifiCredentialsFromAudio(&application, &wifi_ap, display, channel);
#endif
// Wait forever until reset after configuration

View File

@@ -47,14 +47,11 @@ private:
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
display_->SetChatMessage("system", "");
display_->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(1);
});
power_save_timer_->OnExitSleepMode([this]() {
display_->SetChatMessage("system", "");
display_->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {

View File

@@ -141,7 +141,8 @@ 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_));
// ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
return;
}
AudioCodec::EnableInput(enable);
}

View File

@@ -69,14 +69,11 @@ void InitializePowerManager() {
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(-1, 60);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
display_->SetChatMessage("system", "");
display_->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(1);
});
power_save_timer_->OnExitSleepMode([this]() {
display_->SetChatMessage("system", "");
display_->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});

View File

@@ -135,16 +135,11 @@ private:
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(20);
});
power_save_timer_->OnExitSleepMode([this]() {
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {

View File

@@ -120,16 +120,11 @@ private:
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(20);
});
power_save_timer_->OnExitSleepMode([this]() {
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {

View File

@@ -69,23 +69,13 @@ private:
}
void InitializePowerSaveTimer() {
// 第一个参数不为 -1 时,进入睡眠会关闭音频输入
power_save_timer_ = new PowerSaveTimer(240, 60);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("sleepy");
auto codec = GetAudioCodec();
codec->EnableInput(false);
GetDisplay()->SetPowerSaveMode(true);
});
power_save_timer_->OnExitSleepMode([this]() {
auto codec = GetAudioCodec();
codec->EnableInput(true);
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
});
power_save_timer_->SetEnabled(true);
}

View File

@@ -22,12 +22,35 @@
#include <driver/rtc_io.h>
#include <esp_sleep.h>
#define TAG "JiuchuanDevBoard"
#define BOARD_TAG "JiuchuanDevBoard"
#define __USER_GPIO_PWRDOWN__
LV_FONT_DECLARE(font_puhui_20_4);
LV_FONT_DECLARE(font_awesome_20_4);
// 自定义LCD显示器类用于圆形屏幕适配
class CustomLcdDisplay : public SpiLcdDisplay
{
public:
CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle,
esp_lcd_panel_handle_t panel_handle,
int width,
int height,
int offset_x,
int offset_y,
bool mirror_x,
bool mirror_y,
bool swap_xy,
DisplayFonts fonts)
: SpiLcdDisplay(io_handle, panel_handle, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy, fonts)
{
DisplayLockGuard lock(this);
lv_obj_set_style_pad_left(status_bar_, LV_HOR_RES * 0.167, 0);
lv_obj_set_style_pad_right(status_bar_, LV_HOR_RES * 0.167, 0);
}
};
class JiuchuanDevBoard : public WifiBoard {
private:
i2c_master_bus_handle_t codec_i2c_bus_;
@@ -40,6 +63,17 @@ private:
PowerManager* power_manager_;
esp_lcd_panel_io_handle_t panel_io = NULL;
esp_lcd_panel_handle_t panel = NULL;
// 音量映射函数:将内部音量(0-80)映射为显示音量(0-100%)
int MapVolumeForDisplay(int internal_volume) {
// 确保输入在有效范围内
if (internal_volume < 0) internal_volume = 0;
if (internal_volume > 80) internal_volume = 80;
// 将0-80映射到0-100
// 公式: 显示音量 = (内部音量 / 80) * 100
return (internal_volume * 100) / 80;
}
void InitializePowerManager() {
power_manager_ = new PowerManager(PWR_ADC_GPIO);
@@ -81,17 +115,14 @@ private:
}
#endif
//一分钟进入浅睡眠5分钟进入深睡眠关机
power_save_timer_ = new PowerSaveTimer(-1, (60*10), (60*30));
power_save_timer_ = new PowerSaveTimer(-1, (60*5), -1);
// power_save_timer_ = new PowerSaveTimer(-1, 6, 10);//test
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
display_->SetChatMessage("system", "");
display_->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(1);
});
power_save_timer_->OnExitSleepMode([this]() {
display_->SetChatMessage("system", "");
display_->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {
@@ -131,6 +162,11 @@ private:
}
void InitializeButtons() {
static bool pwrbutton_unreleased = false;
if (gpio_get_level(GPIO_NUM_3) == 1) {
pwrbutton_unreleased = true;
}
// 配置GPIO
ESP_LOGI(TAG, "Configuring power button GPIO");
GpioManager::Config(GPIO_NUM_3, GpioManager::GpioMode::INPUT_PULLDOWN);
@@ -142,10 +178,19 @@ private:
// 检查电源按钮初始状态
ESP_LOGI(TAG, "Power button initial state: %d", GpioManager::GetLevel(PWR_BUTTON_GPIO));
// 高电平有效长按关机逻辑
pwr_button_.OnLongPress([this]() {
ESP_LOGI(TAG, "Power button long press detected (high-active)");
pwr_button_.OnPressDown([this]() {
pwrbutton_unreleased = false;
});
pwr_button_.OnLongPress([this]()
{
ESP_LOGI(TAG, "Power button long press detected (high-active)");
if (pwrbutton_unreleased){
ESP_LOGI(TAG, "开机后电源键未松开,取消关机");
return;
}
// 高电平有效防抖确认
for (int i = 0; i < 5; i++) {
@@ -160,75 +205,126 @@ private:
}
ESP_LOGI(TAG, "Confirmed power button pressed - initiating shutdown");
power_manager_->SetPowerState(PowerState::SHUTDOWN);
});
power_manager_->SetPowerState(PowerState::SHUTDOWN); });
wifi_button.OnClick([this]() {
ESP_LOGI(TAG, "Wifi button clicked");
power_save_timer_->WakeUp();
//单击切换状态
pwr_button_.OnClick([this]()
{
// 获取当前应用实例和状态
auto &app = Application::GetInstance();
auto current_state = app.GetDeviceState();
ESP_LOGI(TAG, "当前设备状态: %d", current_state);
if (current_state == kDeviceStateIdle) {
// 如果当前是待命状态,切换到聆听状态
ESP_LOGI(TAG, "从待命状态切换到聆听状态");
app.ToggleChatState(); // 切换到聆听状态
} else if (current_state == kDeviceStateListening) {
// 如果当前是聆听状态,切换到待命状态
ESP_LOGI(TAG, "从聆听状态切换到待命状态");
app.ToggleChatState(); // 切换到待命状态
} else if (current_state == kDeviceStateSpeaking) {
// 如果当前是说话状态,终止说话并切换到待命状态
ESP_LOGI(TAG, "从说话状态切换到待命状态");
app.ToggleChatState(); // 终止说话
} else {
// 其他状态下只唤醒设备
ESP_LOGI(TAG, "唤醒设备");
power_save_timer_->WakeUp();
} });
ESP_LOGI(TAG, "Resetting WiFi configuration");
GpioManager::SetLevel(PWR_EN_GPIO, 1);
ResetWifiConfiguration();
});
cmd_button.OnClick([this]() {
ESP_LOGI(TAG, "Command button clicked");
// 电源键三击重置WiFi
pwr_button_.OnMultipleClick([this]()
{
ESP_LOGI(TAG, "Power button triple click: 重置WiFi");
power_save_timer_->WakeUp();
Application::GetInstance().ToggleChatState();
});
ResetWifiConfiguration(); }, 3);
wifi_button.OnPressDown([this]()
{
ESP_LOGI(TAG, "Volume up button pressed");
power_save_timer_->WakeUp();
auto codec = GetAudioCodec();
int current_vol = codec->output_volume(); // 获取实际当前音量
current_vol = (current_vol + 8 > 80) ? 80 : current_vol + 8;
codec->SetOutputVolume(current_vol);
ESP_LOGI(TAG, "Current volume: %d", current_vol);
int display_volume = MapVolumeForDisplay(current_vol);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(display_volume) + "%");});
cmd_button.OnPressDown([this]()
{
ESP_LOGI(TAG, "Volume down button pressed");
power_save_timer_->WakeUp();
auto codec = GetAudioCodec();
int current_vol = codec->output_volume(); // 获取实际当前音量
current_vol = (current_vol - 8 < 0) ? 0 : current_vol - 8;
codec->SetOutputVolume(current_vol);
ESP_LOGI(TAG, "Current volume: %d", current_vol);
if (current_vol == 0) {
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
} else {
int display_volume = MapVolumeForDisplay(current_vol);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(display_volume) + "%");
}});
}
void InitializeGC9301isplay() {
// 液晶屏控制IO初始化
ESP_LOGI(TAG, "test Install panel IO");
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = DISPLAY_SPI_MOSI_PIN;
buscfg.sclk_io_num = DISPLAY_SPI_SCK_PIN;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.quadwp_io_num = GPIO_NUM_NC;
buscfg.quadhd_io_num = GPIO_NUM_NC;
buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t);
ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
// 初始化SPI总线
esp_lcd_panel_io_spi_config_t io_config = {};
io_config.cs_gpio_num = DISPLAY_SPI_CS_PIN;
io_config.dc_gpio_num = DISPLAY_DC_PIN;
io_config.spi_mode = 3;
io_config.pclk_hz = 80 * 1000 * 1000;
io_config.trans_queue_depth = 10;
io_config.lcd_cmd_bits = 8;
io_config.lcd_param_bits = 8;
esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io);
void InitializeGC9301isplay()
{
// 液晶屏控制IO初始化
ESP_LOGI(TAG, "test Install panel IO");
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = DISPLAY_SPI_MOSI_PIN;
buscfg.sclk_io_num = DISPLAY_SPI_SCK_PIN;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.quadwp_io_num = GPIO_NUM_NC;
buscfg.quadhd_io_num = GPIO_NUM_NC;
buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t);
ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
// 初始化液晶屏驱动芯片9309
ESP_LOGI(TAG, "Install LCD driver");
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = GPIO_NUM_NC;
panel_config.rgb_ele_order = LCD_RGB_ENDIAN_BGR;
panel_config.bits_per_pixel = 16;
esp_lcd_new_panel_gc9309na(panel_io, &panel_config, &panel);
// 初始化SPI总线
esp_lcd_panel_io_spi_config_t io_config = {};
io_config.cs_gpio_num = DISPLAY_SPI_CS_PIN;
io_config.dc_gpio_num = DISPLAY_DC_PIN;
io_config.spi_mode = 3;
io_config.pclk_hz = 80 * 1000 * 1000;
io_config.trans_queue_depth = 10;
io_config.lcd_cmd_bits = 8;
io_config.lcd_param_bits = 8;
esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io);
esp_lcd_panel_reset(panel);
// 初始化液晶屏驱动芯片9309
ESP_LOGI(TAG, "Install LCD driver");
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = GPIO_NUM_NC;
panel_config.rgb_ele_order = LCD_RGB_ENDIAN_BGR;
panel_config.bits_per_pixel = 16;
esp_lcd_new_panel_gc9309na(panel_io, &panel_config, &panel);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, false);
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
display_ = new SpiLcdDisplay(panel_io, panel,
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY,
{
.text_font = &font_puhui_20_4,
.icon_font = &font_awesome_20_4,
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, false);
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
display_ = new CustomLcdDisplay(panel_io, panel,
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY,
{
.text_font = &font_puhui_20_4,
.icon_font = &font_awesome_20_4,
#if CONFIG_USE_WECHAT_MESSAGE_STYLE
.emoji_font = font_emoji_32_init(),
.emoji_font = font_emoji_32_init(),
#else
.emoji_font = font_emoji_64_init(),
.emoji_font = font_emoji_64_init(),
#endif
});
});
}
public:
@@ -298,4 +394,4 @@ public:
}
};
DECLARE_BOARD(JiuchuanDevBoard);
DECLARE_BOARD(JiuchuanDevBoard);

View File

@@ -4,15 +4,16 @@
#include <driver/gpio.h>
#include "pin_config.h"
#define AUDIO_INPUT_REFERENCE true
#define AUDIO_INPUT_SAMPLE_RATE 24000
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#ifdef CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_0_V1_1
#define AUDIO_INPUT_REFERENCE true
#define AUDIO_MIC_I2S_GPIO_BCLK static_cast<gpio_num_t>(MSM261_BCLK)
#define AUDIO_MIC_I2S_GPIO_WS static_cast<gpio_num_t>(MSM261_WS)
#define AUDIO_MIC_I2S_GPIO_DATA static_cast<gpio_num_t>(MSM261_DATA)
#elif defined CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_2
#define AUDIO_INPUT_REFERENCE false
#define AUDIO_MIC_I2S_GPIO_BCLK GPIO_NUM_NC
#define AUDIO_MIC_I2S_GPIO_WS static_cast<gpio_num_t>(MP34DT05TR_LRCLK)
#define AUDIO_MIC_I2S_GPIO_DATA static_cast<gpio_num_t>(MP34DT05TR_DATA)

View File

@@ -82,16 +82,11 @@ private:
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(-1, 60, -1);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(10);
});
power_save_timer_->OnExitSleepMode([this]() {
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {

View File

@@ -140,10 +140,17 @@ void Tcamerapluss3AudioCodec::EnableOutput(bool enable) {
AudioCodec::EnableOutput(enable);
}
int Tcamerapluss3AudioCodec::Read(int16_t *dest, int samples){
if (input_enabled_){
int Tcamerapluss3AudioCodec::Read(int16_t *dest, int samples) {
if (input_enabled_) {
size_t bytes_read;
i2s_channel_read(rx_handle_, dest, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY);
// 麦克风接收音量放大20倍限制在 int16_t 范围内防止溢出)
int16_t *ptr = dest;
for (int i = 0; i < samples; i++) {
int32_t amplified = *ptr * 20;
*ptr++ = (amplified > 32767) ? 32767 : (amplified < -32768) ? -32768 : amplified;
}
}
return samples;
}

View File

@@ -64,16 +64,11 @@ private:
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(10);
});
power_save_timer_->OnExitSleepMode([this]() {
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->SetEnabled(true);

View File

@@ -83,16 +83,11 @@ private:
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(10);
});
power_save_timer_->OnExitSleepMode([this]() {
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->SetEnabled(true);

View File

@@ -138,16 +138,11 @@ private:
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(10);
});
power_save_timer_->OnExitSleepMode([this]() {
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {

View File

@@ -87,14 +87,11 @@ private:
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(240, 60, -1);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
display_->SetChatMessage("system", "");
display_->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(1);
});
power_save_timer_->OnExitSleepMode([this]() {
display_->SetChatMessage("system", "");
display_->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});

View File

@@ -102,14 +102,11 @@ private:
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(240, 60, -1);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
display_->SetChatMessage("system", "");
display_->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(1);
});
power_save_timer_->OnExitSleepMode([this]() {
display_->SetChatMessage("system", "");
display_->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});

View File

@@ -102,22 +102,11 @@ private:
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(160);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(10);
auto codec = GetAudioCodec();
codec->EnableInput(false);
});
power_save_timer_->OnExitSleepMode([this]() {
auto codec = GetAudioCodec();
codec->EnableInput(true);
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->SetEnabled(true);

View File

@@ -66,22 +66,11 @@ private:
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(160);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(10);
auto codec = GetAudioCodec();
codec->EnableInput(false);
});
power_save_timer_->OnExitSleepMode([this]() {
auto codec = GetAudioCodec();
codec->EnableInput(true);
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->SetEnabled(true);

View File

@@ -61,14 +61,11 @@ private:
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
display_->SetChatMessage("system", "");
display_->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(1);
});
power_save_timer_->OnExitSleepMode([this]() {
display_->SetChatMessage("system", "");
display_->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {

View File

@@ -102,16 +102,11 @@ private:
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(10);
});
power_save_timer_->OnExitSleepMode([this]() {
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {

View File

@@ -113,14 +113,11 @@ private:
power_save_timer_ = new PowerSaveTimer(-1, 60, 290);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
display_->SetChatMessage("system", "");
display_->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(1);
});
power_save_timer_->OnExitSleepMode([this]() {
display_->SetChatMessage("system", "");
display_->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {

View File

@@ -98,14 +98,11 @@ private:
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
display_->SetChatMessage("system", "");
display_->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(1);
});
power_save_timer_->OnExitSleepMode([this]() {
display_->SetChatMessage("system", "");
display_->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {

View File

@@ -76,14 +76,12 @@ private:
power_save_timer_ = new PowerSaveTimer(-1, 60, -1);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling modem-sleep mode");
display_->SetChatMessage("system", "");
display_->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(1);
esp_wifi_set_ps(WIFI_PS_MIN_MODEM);
});
power_save_timer_->OnExitSleepMode([this]() {
display_->SetChatMessage("system", "");
display_->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
esp_wifi_set_ps(WIFI_PS_NONE); // 关闭Wi-Fi省电恢复正常
// esp_lcd_panel_disp_on_off(panel_, true); // 重新打开显示

View File

@@ -67,20 +67,10 @@ private:
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(240, 60, -1);
power_save_timer_->OnEnterSleepMode([this]() {
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("sleepy");
auto codec = GetAudioCodec();
codec->EnableInput(false);
GetDisplay()->SetPowerSaveMode(true);
});
power_save_timer_->OnExitSleepMode([this]() {
auto codec = GetAudioCodec();
codec->EnableInput(true);
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
});
power_save_timer_->SetEnabled(true);
}

View File

@@ -161,15 +161,10 @@ private:
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(20); });
power_save_timer_->OnExitSleepMode([this]() {
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness(); });
power_save_timer_->OnShutdownRequest([this](){
pmic_->PowerOff(); });

View File

@@ -1,4 +1,4 @@
# Waveshare ESP32-S3-Touch-AMOLED-1.75
# Waveshare ESP32-S3-Touch-AMOLED-2.06
[ESP32-S3-Touch-AMOLED-2.06](https://www.waveshare.com/esp32-s3-touch-amoled-2.06.htm) is a high-performance, wearable watch-style development board designed by Waveshare. Based on the ESP32-S3R8 microcontroller, it integrates a 2.06inch AMOLED capacitive touch display, 6-axis IMU, RTC chip, audio codec chip, power management IC, and so on. Comes with a custom-designed case with a smartwatch-like appearance, making it ideal for prototyping and functional verification of wearable applications.

View File

@@ -156,15 +156,10 @@ private:
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(20); });
power_save_timer_->OnExitSleepMode([this]() {
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness(); });
power_save_timer_->OnShutdownRequest([this](){
pmic_->PowerOff(); });

View File

@@ -122,16 +122,11 @@ private:
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(20);
});
power_save_timer_->OnExitSleepMode([this]() {
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {

View File

@@ -112,14 +112,11 @@ private:
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
display_->SetChatMessage("system", "");
display_->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(1);
});
power_save_timer_->OnExitSleepMode([this]() {
display_->SetChatMessage("system", "");
display_->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {

View File

@@ -112,14 +112,11 @@ private:
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
display_->SetChatMessage("system", "");
display_->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(1);
});
power_save_timer_->OnExitSleepMode([this]() {
display_->SetChatMessage("system", "");
display_->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {

View File

@@ -54,15 +54,10 @@ private:
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
});
power_save_timer_->OnExitSleepMode([this]() {
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
});
power_save_timer_->OnShutdownRequest([this]() {
ESP_LOGI(TAG, "Shutting down");

View File

@@ -55,15 +55,10 @@ private:
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
});
power_save_timer_->OnExitSleepMode([this]() {
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
});
power_save_timer_->OnShutdownRequest([this]() {
ESP_LOGI(TAG, "Shutting down");

View File

@@ -52,14 +52,11 @@ private:
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
display_->SetChatMessage("system", "");
display_->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(1);
});
power_save_timer_->OnExitSleepMode([this]() {
display_->SetChatMessage("system", "");
display_->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {

View File

@@ -52,14 +52,11 @@ private:
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
display_->SetChatMessage("system", "");
display_->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(1);
});
power_save_timer_->OnExitSleepMode([this]() {
display_->SetChatMessage("system", "");
display_->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {

View File

@@ -55,7 +55,7 @@ private:
sleep_timer_->OnEnterLightSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
// Show the standby screen
GetDisplay()->ShowStandbyScreen(true);
GetDisplay()->SetPowerSaveMode(true);
// Enable sleep mode, and sleep in 1 second after DTR is set to high
modem_->SetSleepMode(true, 1);
// Set the DTR pin to high to make the modem enter sleep mode
@@ -65,7 +65,7 @@ private:
// Set the DTR pin to low to make the modem wake up
modem_->GetAtUart()->SetDtrPin(false);
// Hide the standby screen
GetDisplay()->ShowStandbyScreen(false);
GetDisplay()->SetPowerSaveMode(false);
});
sleep_timer_->SetEnabled(true);
}

View File

@@ -7,7 +7,7 @@
#include "mcp_server.h"
#include "settings.h"
#include "config.h"
#include "sleep_timer.h"
#include "power_save_timer.h"
#include "font_awesome_symbols.h"
#include "adc_battery_monitor.h"
@@ -32,44 +32,33 @@ private:
Display* display_ = nullptr;
Button boot_button_;
bool press_to_talk_enabled_ = false;
SleepTimer* sleep_timer_ = nullptr;
PowerSaveTimer* power_save_timer_ = nullptr;
AdcBatteryMonitor* adc_battery_monitor_ = nullptr;
void InitializePowerManager() {
adc_battery_monitor_ = new AdcBatteryMonitor(ADC_UNIT_1, ADC_CHANNEL_3, 100000, 100000, GPIO_NUM_12);
adc_battery_monitor_->OnChargingStatusChanged([this](bool is_charging) {
if (is_charging) {
sleep_timer_->SetEnabled(false);
power_save_timer_->SetEnabled(false);
} else {
sleep_timer_->SetEnabled(true);
power_save_timer_->SetEnabled(true);
}
});
}
void InitializePowerSaveTimer() {
#if CONFIG_USE_ESP_WAKE_WORD
sleep_timer_ = new SleepTimer(600);
power_save_timer_ = new PowerSaveTimer(160, 600);
#else
sleep_timer_ = new SleepTimer(30);
power_save_timer_ = new PowerSaveTimer(160, 60);
#endif
sleep_timer_->OnEnterLightSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("sleepy");
auto codec = GetAudioCodec();
codec->EnableInput(false);
power_save_timer_->OnEnterSleepMode([this]() {
GetDisplay()->SetPowerSaveMode(true);
});
sleep_timer_->OnExitLightSleepMode([this]() {
auto codec = GetAudioCodec();
codec->EnableInput(true);
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("neutral");
power_save_timer_->OnExitSleepMode([this]() {
GetDisplay()->SetPowerSaveMode(false);
});
sleep_timer_->SetEnabled(true);
power_save_timer_->SetEnabled(true);
}
void InitializeCodecI2c() {
@@ -156,8 +145,8 @@ private:
}
});
boot_button_.OnPressDown([this]() {
if (sleep_timer_) {
sleep_timer_->WakeUp();
if (power_save_timer_) {
power_save_timer_->WakeUp();
}
if (press_to_talk_enabled_) {
Application::GetInstance().StartListening();
@@ -241,7 +230,7 @@ public:
virtual void SetPowerSaveMode(bool enabled) override {
if (!enabled) {
sleep_timer_->WakeUp();
power_save_timer_->WakeUp();
}
WifiBoard::SetPowerSaveMode(enabled);
}

View File

@@ -39,21 +39,10 @@ private:
power_save_timer_ = new PowerSaveTimer(160, 60);
#endif
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("sleepy");
auto codec = GetAudioCodec();
codec->EnableInput(false);
GetDisplay()->SetPowerSaveMode(true);
});
power_save_timer_->OnExitSleepMode([this]() {
auto codec = GetAudioCodec();
codec->EnableInput(true);
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
});
power_save_timer_->SetEnabled(true);
}

View File

@@ -55,14 +55,11 @@ private:
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
display_->SetChatMessage("system", "");
display_->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(1);
});
power_save_timer_->OnExitSleepMode([this]() {
display_->SetChatMessage("system", "");
display_->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->SetEnabled(true);

View File

@@ -59,14 +59,11 @@ private:
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
display_->SetChatMessage("system", "");
display_->SetEmotion("sleepy");
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(1);
});
power_save_timer_->OnExitSleepMode([this]() {
display_->SetChatMessage("system", "");
display_->SetEmotion("neutral");
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->SetEnabled(true);

View File

@@ -269,8 +269,8 @@ void Display::SetTheme(const std::string& theme_name) {
settings.SetString("theme", theme_name);
}
void Display::ShowStandbyScreen(bool show) {
if (show) {
void Display::SetPowerSaveMode(bool on) {
if (on) {
SetChatMessage("system", "");
SetEmotion("sleepy");
} else {

View File

@@ -30,7 +30,7 @@ public:
virtual void SetTheme(const std::string& theme_name);
virtual std::string GetTheme() { return current_theme_name_; }
virtual void UpdateStatusBar(bool update_all = false);
virtual void ShowStandbyScreen(bool show);
virtual void SetPowerSaveMode(bool on);
inline int width() const { return width_; }
inline int height() const { return height_; }

View File

@@ -22,6 +22,7 @@ OledDisplay::OledDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handl
ESP_LOGI(TAG, "Initialize LVGL");
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;
lvgl_port_init(&port_cfg);

View File

@@ -15,10 +15,10 @@ dependencies:
78/esp_lcd_nv3023: ~1.0.0
78/esp-wifi-connect: ~2.4.3
78/esp-opus-encoder: ~2.4.0
78/esp-ml307: ~3.2.0
78/esp-ml307: ~3.2.4
78/xiaozhi-fonts: ~1.3.2
espressif/led_strip: ^2.5.5
espressif/esp_codec_dev: ~1.3.2
espressif/esp_codec_dev: ~1.3.6
espressif/esp-sr: ~2.1.1
espressif/button: ~4.1.3
espressif/knob: ^1.0.0

View File

@@ -20,14 +20,26 @@
### 使用方法
安装Pillow
创建虚拟环境
```bash
pip install Pillow # 处理图像需要
# 创建 venv
python -m venv venv
# 激活环境
source venv/bin/activate # Linux/Mac
venv\Scripts\activate # Windows
```
安装依赖
```bash
pip install -r requirements.txt
```
运行转换工具
```bash
# 激活环境
source venv/bin/activate # Linux/Mac
venv\Scripts\activate # Windows
# 运行
python lvgl_tools_gui.py
```

View File

@@ -51,7 +51,7 @@ class ImageConverterApp:
# 分辨率设置
ttk.Label(settings_frame, text="分辨率:").grid(row=0, column=0, padx=2)
ttk.Combobox(settings_frame, textvariable=self.resolution,
values=["128x128", "64x64", "32x32"], width=8).grid(row=0, column=1, padx=2)
values=["512x512", "256x256", "128x128", "64x64", "32x32"], width=8).grid(row=0, column=1, padx=2)
# 颜色格式
ttk.Label(settings_frame, text="颜色格式:").grid(row=0, column=2, padx=2)
@@ -64,7 +64,7 @@ class ImageConverterApp:
values=["NONE", "RLE"], width=8).grid(row=0, column=5, padx=2)
# 文件操作框架
file_frame = ttk.LabelFrame(self.root, text="输入文件")
file_frame = ttk.LabelFrame(self.root, text="选取文件")
file_frame.grid(row=1, column=0, padx=10, pady=5, sticky="nsew")
# 文件操作按钮
@@ -77,7 +77,7 @@ class ImageConverterApp:
# 文件列表Treeview
self.tree = ttk.Treeview(file_frame, columns=("selected", "filename"),
show="headings", height=10)
self.tree.heading("selected", text="", anchor=tk.W)
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)
@@ -250,4 +250,4 @@ class ImageConverterApp:
if __name__ == "__main__":
root = tk.Tk()
app = ImageConverterApp(root)
root.mainloop()
root.mainloop()

View File

@@ -0,0 +1,3 @@
lz4==4.4.4
Pillow==11.3.0
pypng==0.20220715.0