forked from xiaozhi/xiaozhi-esp32
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0e12450c5 | ||
|
|
e5ac40aac8 | ||
|
|
345c8be467 | ||
|
|
def6301292 | ||
|
|
df7cbdfcb6 | ||
|
|
d38763d5ef | ||
|
|
e90e540933 | ||
|
|
656bf3c7fa | ||
|
|
ca35b0761b |
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_);
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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]() {
|
||||
|
||||
@@ -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]() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_) {
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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_) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
|
||||
@@ -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]() {
|
||||
|
||||
@@ -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]() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]() {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]() {
|
||||
|
||||
@@ -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]() {
|
||||
|
||||
@@ -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]() {
|
||||
|
||||
@@ -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]() {
|
||||
|
||||
@@ -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); // 重新打开显示
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(); });
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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(); });
|
||||
|
||||
@@ -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]() {
|
||||
|
||||
@@ -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]() {
|
||||
|
||||
@@ -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]() {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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]() {
|
||||
|
||||
@@ -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]() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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_; }
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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()
|
||||
|
||||
3
scripts/Image_Converter/requirements.txt
Normal file
3
scripts/Image_Converter/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
lz4==4.4.4
|
||||
Pillow==11.3.0
|
||||
pypng==0.20220715.0
|
||||
Reference in New Issue
Block a user