Update to 2.0.1

This commit is contained in:
2025-09-15 22:04:01 +08:00
parent 5c43129024
commit 7d7f5eae3d
74 changed files with 5253 additions and 439 deletions

View File

@@ -2,19 +2,21 @@
#include "system_info.h"
#include "settings.h"
#include "display/display.h"
#include "display/oled_display.h"
#include "assets/lang_config.h"
#include "esp32_music.h"
#include <esp_log.h>
#include <esp_ota_ops.h>
#include <esp_chip_info.h>
#include <esp_random.h>
#include "esp32_music.h"
#define TAG "Board"
Board::Board() {
music_ = nullptr; // 先初始化为空指针
Settings settings("board", true);
uuid_ = settings.GetString("uuid");
if (uuid_.empty()) {
@@ -22,10 +24,6 @@ Board::Board() {
settings.SetString("uuid", uuid_);
}
ESP_LOGI(TAG, "UUID=%s SKU=%s", uuid_.c_str(), BOARD_NAME);
// 初始化音乐播放器
music_ = new Esp32Music();
ESP_LOGI(TAG, "Music player initialized for all boards");
}
Board::~Board() {
@@ -173,6 +171,21 @@ std::string Board::GetSystemInfoJson() {
json += R"("label":")" + std::string(ota_partition->label) + R"(")";
json += R"(},)";
// Append display info
auto display = GetDisplay();
if (display) {
json += R"("display":{)";
if (dynamic_cast<OledDisplay*>(display)) {
json += R"("monochrome":)" + std::string("true") + R"(,)";
} else {
json += R"("monochrome":)" + std::string("false") + R"(,)";
}
json += R"("width":)" + std::to_string(display->width()) + R"(,)";
json += R"("height":)" + std::to_string(display->height()) + R"(,)";
json.pop_back(); // Remove the last comma
}
json += R"(},)";
json += R"("board":)" + GetBoardJson();
// Close the JSON object

View File

@@ -11,9 +11,10 @@
#include "led/led.h"
#include "backlight.h"
#include "camera.h"
#include "music.h"
#include "assets.h"
#include "music.h"
void* create_board();
class AudioCodec;
@@ -32,6 +33,7 @@ protected:
// 音乐播放器实例
Music* music_;
public:
static Board& GetInstance() {
static Board* instance = static_cast<Board*>(create_board());
@@ -64,4 +66,4 @@ void* create_board() { \
return new BOARD_CLASS_NAME(); \
}
#endif // BOARD_H
#endif // BOARD_H

View File

@@ -63,33 +63,26 @@ bool Esp32Camera::Capture() {
// 显示预览图片
auto display = dynamic_cast<LvglDisplay*>(Board::GetInstance().GetDisplay());
if (display != nullptr) {
// Create a new preview image
auto img_dsc = (lv_img_dsc_t*)heap_caps_calloc(1, sizeof(lv_img_dsc_t), MALLOC_CAP_8BIT);
img_dsc->header.magic = LV_IMAGE_HEADER_MAGIC;
img_dsc->header.cf = LV_COLOR_FORMAT_RGB565;
img_dsc->header.flags = 0;
img_dsc->header.w = fb_->width;
img_dsc->header.h = fb_->height;
img_dsc->header.stride = fb_->width * 2;
img_dsc->data_size = fb_->width * fb_->height * 2;
img_dsc->data = (uint8_t*)heap_caps_malloc(img_dsc->data_size, MALLOC_CAP_SPIRAM);
if (img_dsc->data == nullptr) {
auto data = (uint8_t*)heap_caps_malloc(fb_->len, MALLOC_CAP_SPIRAM);
if (data == nullptr) {
ESP_LOGE(TAG, "Failed to allocate memory for preview image");
heap_caps_free(img_dsc);
return false;
}
auto src = (uint16_t*)fb_->buf;
auto dst = (uint16_t*)img_dsc->data;
auto dst = (uint16_t*)data;
size_t pixel_count = fb_->len / 2;
for (size_t i = 0; i < pixel_count; i++) {
// 交换每个16位字内的字节
dst[i] = __builtin_bswap16(src[i]);
}
display->SetPreviewImage(img_dsc);
auto image = std::make_unique<LvglAllocatedImage>(data, fb_->len, fb_->width, fb_->height, fb_->width * 2, LV_COLOR_FORMAT_RGB565);
display->SetPreviewImage(std::move(image));
}
return true;
}
bool Esp32Camera::SetHMirror(bool enabled) {
sensor_t *s = esp_camera_sensor_get();
if (s == nullptr) {

View File

@@ -166,11 +166,12 @@ Esp32Music::Esp32Music() : last_downloaded_data_(), current_music_url_(), curren
song_name_displayed_(false), current_lyric_url_(), lyrics_(),
current_lyric_index_(-1), lyric_thread_(), is_lyric_running_(false),
display_mode_(DISPLAY_MODE_LYRICS), is_playing_(false), is_downloading_(false),
play_thread_(), download_thread_(), audio_buffer_(), buffer_mutex_(),
is_paused_(false), play_thread_(), download_thread_(), audio_buffer_(), buffer_mutex_(),
buffer_cv_(), buffer_size_(0), mp3_decoder_(nullptr), mp3_frame_info_(),
mp3_decoder_initialized_(false) {
ESP_LOGI(TAG, "Music player initialized with default spectrum display mode");
InitializeMp3Decoder();
// 延迟MP3解码器初始化避免在构造函数中初始化导致的问题
// InitializeMp3Decoder();
}
Esp32Music::~Esp32Music() {
@@ -282,9 +283,9 @@ Esp32Music::~Esp32Music() {
}
bool Esp32Music::Download(const std::string& song_name, const std::string& artist_name) {
ESP_LOGI(TAG, "Starting to get music details for: %s", song_name.c_str());
ESP_LOGI(TAG, "云端由MeowEmbeddedMusicServer喵波音律嵌入式提供");
ESP_LOGI(TAG, "喵波音律QQ交流群:865754861");
ESP_LOGI(TAG, "Starting to get music details for: %s", song_name.c_str());
// 清空之前的下载数据
last_downloaded_data_.clear();
@@ -357,21 +358,17 @@ bool Esp32Music::Download(const std::string& song_name, const std::string& artis
if (cJSON_IsString(audio_url) && audio_url->valuestring && strlen(audio_url->valuestring) > 0) {
ESP_LOGI(TAG, "Audio URL path: %s", audio_url->valuestring);
// 第二步:直接使用audio_url播放音乐
std::string audio_path = audio_url->valuestring;
current_music_url_ = audio_path;
// 第二步:直接使用音频URL开始流式播放
std::string current_music_url_ = audio_url->valuestring;
ESP_LOGI(TAG, "云端由MeowEmbeddedMusicServer喵波音律嵌入式提供");
ESP_LOGI(TAG, "喵波音律QQ交流群:865754861");
ESP_LOGI(TAG, "Starting streaming playback for: %s", song_name.c_str());
song_name_displayed_ = false; // 重置歌名显示标志
StartStreaming(current_music_url_);
// 处理歌词URL - 只有在歌词显示模式下才启动歌词
if (cJSON_IsString(lyric_url) && lyric_url->valuestring && strlen(lyric_url->valuestring) > 0) {
// 直接使用歌词URL
std::string lyric_path = lyric_url->valuestring;
current_lyric_url_ = lyric_path;
// 使用歌词URL获取歌词
std::string current_lyric_url_ = lyric_url->valuestring;
// 根据显示模式决定是否启动歌词
if (display_mode_ == DISPLAY_MODE_LYRICS) {
@@ -431,6 +428,14 @@ bool Esp32Music::StartStreaming(const std::string& music_url) {
ESP_LOGD(TAG, "Starting streaming for URL: %s", music_url.c_str());
// 确保MP3解码器已初始化
if (!mp3_decoder_initialized_) {
if (!InitializeMp3Decoder()) {
ESP_LOGE(TAG, "Failed to initialize MP3 decoder");
return false;
}
}
// 停止之前的播放和下载
is_downloading_ = false;
is_playing_ = false;
@@ -491,6 +496,7 @@ bool Esp32Music::StopStreaming() {
// 停止下载和播放标志
is_downloading_ = false;
is_playing_ = false;
is_paused_ = false; // 重置暂停状态
// 清空歌名显示
auto& board = Board::GetInstance();
@@ -723,8 +729,6 @@ void Esp32Music::PlayAudioStream() {
});
}
ESP_LOGI(TAG, "云端由MeowEmbeddedMusicServer喵波音律嵌入式提供");
ESP_LOGI(TAG, "喵波音律QQ交流群:865754861");
ESP_LOGI(TAG, "Starting playback with buffer size: %d", buffer_size_);
size_t total_played = 0;
@@ -744,18 +748,20 @@ void Esp32Music::PlayAudioStream() {
bool id3_processed = false;
while (is_playing_) {
// 检查是否被暂停
if (is_paused_) {
ESP_LOGD(TAG, "Music playback paused, waiting...");
vTaskDelay(pdMS_TO_TICKS(100));
continue;
}
// 检查设备状态,只有在空闲状态才播放音乐
auto& app = Application::GetInstance();
DeviceState current_state = app.GetDeviceState();
// 状态转换:说话中-》聆听中-》待机状态-》播放音乐
if (current_state == kDeviceStateListening || current_state == kDeviceStateSpeaking) {
if (current_state == kDeviceStateSpeaking) {
ESP_LOGI(TAG, "Device is in speaking state, switching to listening state for music playback");
}
if (current_state == kDeviceStateListening) {
ESP_LOGI(TAG, "Device is in listening state, switching to idle state for music playback");
}
// 等小智把话说完了,变成聆听状态之后,马上转成待机状态,进入音乐播放
if (current_state == kDeviceStateListening) {
ESP_LOGI(TAG, "Device is in listening state, switching to idle state for music playback");
// 切换状态
app.ToggleChatState(); // 变成待机状态
vTaskDelay(pdMS_TO_TICKS(300));
@@ -1133,8 +1139,6 @@ bool Esp32Music::DownloadLyrics(const std::string& lyric_url) {
add_auth_headers(http.get());
// 打开GET连接
ESP_LOGI(TAG, "云端由MeowEmbeddedMusicServer喵波音律嵌入式提供");
ESP_LOGI(TAG, "喵波音律QQ交流群:865754861");
if (!http->Open("GET", current_url)) {
ESP_LOGE(TAG, "Failed to open HTTP connection for lyrics");
// 移除delete http; 因为unique_ptr会自动管理内存
@@ -1426,4 +1430,99 @@ void Esp32Music::SetDisplayMode(DisplayMode mode) {
ESP_LOGI(TAG, "Display mode changed from %s to %s",
(old_mode == DISPLAY_MODE_SPECTRUM) ? "SPECTRUM" : "LYRICS",
(mode == DISPLAY_MODE_SPECTRUM) ? "SPECTRUM" : "LYRICS");
}
// MCP工具需要的方法实现
bool Esp32Music::SetVolume(int volume) {
ESP_LOGI(TAG, "SetVolume called with volume: %d", volume);
// 验证音量范围
if (volume < 0 || volume > 100) {
ESP_LOGW(TAG, "Invalid volume level: %d, must be between 0-100", volume);
return false;
}
// 通过Board获取AudioCodec并设置音量
auto& board = Board::GetInstance();
auto codec = board.GetAudioCodec();
if (codec) {
codec->SetOutputVolume(volume);
ESP_LOGI(TAG, "Volume set to %d%%", volume);
return true;
} else {
ESP_LOGE(TAG, "No audio codec available");
return false;
}
}
bool Esp32Music::PlaySong() {
ESP_LOGI(TAG, "PlaySong called");
return false;
}
bool Esp32Music::StopSong() {
ESP_LOGI(TAG, "StopSong called");
return StopStreaming();
}
bool Esp32Music::PauseSong() {
ESP_LOGI(TAG, "PauseSong called");
// 检查是否正在播放
if (!is_playing_) {
ESP_LOGW(TAG, "No music is currently playing");
return false;
}
// 检查是否已经暂停
if (is_paused_) {
ESP_LOGW(TAG, "Music is already paused");
return true;
}
// 设置暂停标志
is_paused_ = true;
ESP_LOGI(TAG, "Music playback paused");
// 更新显示状态
auto& board = Board::GetInstance();
auto display = board.GetDisplay();
if (display && !current_song_name_.empty()) {
std::string formatted_song_name = "" + current_song_name_ + "》已暂停";
display->SetMusicInfo(formatted_song_name.c_str());
ESP_LOGI(TAG, "Updated display: %s", formatted_song_name.c_str());
}
return true;
}
bool Esp32Music::ResumeSong() {
ESP_LOGI(TAG, "ResumeSong called");
// 检查是否正在播放
if (!is_playing_) {
ESP_LOGW(TAG, "No music is currently playing");
return false;
}
// 检查是否已经恢复
if (!is_paused_) {
ESP_LOGW(TAG, "Music is not paused");
return true;
}
// 清除暂停标志
is_paused_ = false;
ESP_LOGI(TAG, "Music playback resumed");
// 更新显示状态
auto& board = Board::GetInstance();
auto display = board.GetDisplay();
if (display && !current_song_name_.empty()) {
std::string formatted_song_name = "" + current_song_name_ + "》播放中...";
display->SetMusicInfo(formatted_song_name.c_str());
ESP_LOGI(TAG, "Updated display: %s", formatted_song_name.c_str());
}
return true;
}

View File

@@ -38,7 +38,6 @@ private:
std::string current_music_url_;
std::string current_song_name_;
bool song_name_displayed_;
std::atomic<bool> stop_flag_{false}; // 停止播放标志位
// 歌词相关
std::string current_lyric_url_;
@@ -51,6 +50,7 @@ private:
std::atomic<DisplayMode> display_mode_;
std::atomic<bool> is_playing_;
std::atomic<bool> is_downloading_;
std::atomic<bool> is_paused_;
std::thread play_thread_;
std::thread download_thread_;
int64_t current_play_time_ms_; // 当前播放时间(毫秒)
@@ -102,11 +102,20 @@ public:
virtual bool StopStreaming() override; // 停止流式播放
virtual size_t GetBufferSize() const override { return buffer_size_; }
virtual bool IsDownloading() const override { return is_downloading_; }
virtual bool IsPlaying() const override { return is_playing_; }
virtual bool IsPaused() const override { return is_paused_; }
virtual int16_t* GetAudioData() override { return final_pcm_data_fft; }
// 显示模式控制方法
void SetDisplayMode(DisplayMode mode);
DisplayMode GetDisplayMode() const { return display_mode_.load(); }
// MCP工具需要的方法
virtual bool PlaySong() override;
virtual bool SetVolume(int volume) override;
virtual bool StopSong() override;
virtual bool PauseSong() override;
virtual bool ResumeSong() override;
};
#endif // ESP32_MUSIC_H

View File

@@ -15,7 +15,16 @@ public:
virtual bool StopStreaming() = 0; // 停止流式播放
virtual size_t GetBufferSize() const = 0;
virtual bool IsDownloading() const = 0;
virtual bool IsPlaying() const = 0;
virtual bool IsPaused() const = 0;
virtual int16_t* GetAudioData() = 0;
// MCP工具需要的方法
virtual bool PlaySong() = 0;
virtual bool SetVolume(int volume) = 0;
virtual bool StopSong() = 0;
virtual bool PauseSong() = 0;
virtual bool ResumeSong() = 0;
};
#endif // MUSIC_H

View File

@@ -0,0 +1,211 @@
# MPU6050传感器集成说明
## 概述
本项目为ESP32-S3智能音箱开发板集成了MPU6050六轴传感器支持提供了现代化的C++封装接口。
## 文件结构
- `mpu6050_sensor.h` - MPU6050传感器封装类的头文件
- `mpu6050_sensor.cc` - MPU6050传感器封装类的实现文件
- `mpu6050_test.cc` - MPU6050传感器测试程序可选
- `esp32s3_smart_speaker.cc` - 已集成MPU6050支持的板子实现
## 功能特性
### 1. 传感器支持
- **加速度计**: 支持±2g, ±4g, ±8g, ±16g量程
- **陀螺仪**: 支持±250°/s, ±500°/s, ±1000°/s, ±2000°/s量程
- **温度传感器**: 内置温度传感器
- **姿态角计算**: 使用互补滤波算法计算俯仰角、横滚角和偏航角
### 2. 技术特点
- 使用ESP-IDF的现代I2C Master API
- 支持多量程配置
- 内置数字低通滤波器
- 可配置采样率
- 互补滤波姿态解算
- 完整的错误处理机制
## 硬件连接
根据`config.h`中的定义:
```c
// IMU传感器 (I2C接口)
#define IMU_I2C_SDA_PIN GPIO_NUM_21
#define IMU_I2C_SCL_PIN GPIO_NUM_20
#define IMU_INT_PIN GPIO_NUM_19
```
### 连接方式
- **VCC**: 3.3V
- **GND**: 地
- **SDA**: GPIO21
- **SCL**: GPIO20
- **INT**: GPIO19中断引脚可选
## 使用方法
### 1. 基本使用
```cpp
#include "mpu6050_sensor.h"
// 创建传感器实例
auto sensor = std::make_unique<Mpu6050Sensor>(i2c_bus_handle);
// 初始化传感器
if (sensor->Initialize(ACCE_FS_4G, GYRO_FS_500DPS)) {
// 唤醒传感器
if (sensor->WakeUp()) {
// 验证设备ID
uint8_t device_id;
if (sensor->GetDeviceId(&device_id)) {
ESP_LOGI(TAG, "MPU6050 initialized, ID: 0x%02X", device_id);
}
}
}
```
### 2. 读取传感器数据
```cpp
mpu6050_acce_value_t acce;
mpu6050_gyro_value_t gyro;
mpu6050_temp_value_t temp;
complimentary_angle_t angle;
// 读取加速度计数据
if (sensor->GetAccelerometer(&acce)) {
ESP_LOGI(TAG, "Accelerometer - X:%.2f, Y:%.2f, Z:%.2f",
acce.acce_x, acce.acce_y, acce.acce_z);
}
// 读取陀螺仪数据
if (sensor->GetGyroscope(&gyro)) {
ESP_LOGI(TAG, "Gyroscope - X:%.2f, Y:%.2f, Z:%.2f",
gyro.gyro_x, gyro.gyro_y, gyro.gyro_z);
}
// 读取温度数据
if (sensor->GetTemperature(&temp)) {
ESP_LOGI(TAG, "Temperature: %.2f°C", temp.temp);
}
// 计算姿态角
if (sensor->ComplimentaryFilter(&acce, &gyro, &angle)) {
ESP_LOGI(TAG, "Attitude - Pitch:%.2f°, Roll:%.2f°, Yaw:%.2f°",
angle.pitch, angle.roll, angle.yaw);
}
```
### 3. 获取传感器状态
```cpp
// 检查是否已初始化
if (sensor->IsInitialized()) {
// 获取状态信息
std::string status = sensor->GetStatusJson();
ESP_LOGI(TAG, "Sensor status: %s", status.c_str());
}
```
## 配置参数
### 加速度计量程
- `ACCE_FS_2G`: ±2g (16384 LSB/g)
- `ACCE_FS_4G`: ±4g (8192 LSB/g)
- `ACCE_FS_8G`: ±8g (4096 LSB/g)
- `ACCE_FS_16G`: ±16g (2048 LSB/g)
### 陀螺仪量程
- `GYRO_FS_250DPS`: ±250°/s (131 LSB/°/s)
- `GYRO_FS_500DPS`: ±500°/s (65.5 LSB/°/s)
- `GYRO_FS_1000DPS`: ±1000°/s (32.8 LSB/°/s)
- `GYRO_FS_2000DPS`: ±2000°/s (16.4 LSB/°/s)
### 互补滤波参数
- **alpha**: 0.98 (默认值,表示更信任陀螺仪)
- **采样率**: 125Hz
- **数字低通滤波器**: 5Hz
## 集成到板子
MPU6050已经集成到`Esp32s3SmartSpeaker`类中:
1. **自动初始化**: 在板子构造函数中自动初始化MPU6050
2. **后台任务**: 自动创建后台任务持续读取传感器数据
3. **状态报告**: 在`GetBoardJson()`中报告传感器状态
## 日志输出
传感器会输出以下日志信息:
```
I (1234) SmartSpeaker: MPU6050 sensor initialized successfully (ID: 0x68)
I (1235) SmartSpeaker: IMU data task created successfully
I (1236) SmartSpeaker: IMU data task started
I (1237) SmartSpeaker: Accelerometer - X:0.12, Y:-0.05, Z:0.98
I (1238) SmartSpeaker: Gyroscope - X:0.15, Y:-0.02, Z:0.08
I (1239) SmartSpeaker: Temperature: 25.3°C
I (1240) SmartSpeaker: Attitude - Pitch:2.1°, Roll:-1.5°, Yaw:0.3°
```
## 故障排除
### 常见问题
1. **设备ID不匹配**
- 检查I2C连接
- 确认设备地址是否正确
- 检查电源供应
2. **初始化失败**
- 检查I2C总线配置
- 确认GPIO引脚配置正确
- 检查上拉电阻
3. **数据读取失败**
- 检查I2C通信
- 确认传感器已唤醒
- 检查采样率配置
### 调试建议
1. 启用I2C调试日志
2. 检查硬件连接
3. 使用示波器检查I2C信号
4. 验证电源电压稳定性
## 技术细节
### I2C配置
- **时钟频率**: 100kHz
- **地址**: 0x68 (7位地址)
- **上拉电阻**: 内部使能
### 寄存器配置
- **加速度计量程**: 寄存器0x1C
- **陀螺仪量程**: 寄存器0x1B
- **数字低通滤波器**: 寄存器0x1A
- **采样率**: 寄存器0x19
- **电源管理**: 寄存器0x6B
### 数据格式
- **加速度计**: 16位有符号整数转换为g值
- **陀螺仪**: 16位有符号整数转换为度/秒
- **温度**: 16位有符号整数转换为摄氏度
## 扩展功能
### 可扩展的功能
1. **中断支持**: 可以配置数据就绪中断
2. **运动检测**: 可以配置运动检测中断
3. **自由落体检测**: 可以配置自由落体检测
4. **FIFO支持**: 可以使用FIFO缓冲区
5. **DMP支持**: 可以使用数字运动处理器
### 性能优化
1. **降低采样率**: 减少功耗
2. **使用中断**: 避免轮询
3. **FIFO缓冲**: 批量读取数据
4. **休眠模式**: 不使用时进入低功耗模式
## 许可证
本代码遵循项目的许可证要求。

View File

@@ -0,0 +1,376 @@
#include "adc_manager.h"
#include "application.h"
#include <board.h>
#include <cmath>
#include <cstring>
#include <esp_adc/adc_cali.h>
#include <esp_adc/adc_oneshot.h>
#include <esp_log.h>
#include <esp_timer.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#define TAG "AdcManager"
// 检测时间定义(毫秒)
#define PRESSURE_DETECTION_TIME_MS 2000 // 压力检测持续时间2秒
#define LOW_VALUE_DETECTION_TIME_MS 2000 // 低值检测持续时间2秒
AdcManager &AdcManager::GetInstance() {
static AdcManager instance;
return instance;
}
bool AdcManager::Initialize() {
if (initialized_) {
ESP_LOGW(TAG, "AdcManager already initialized");
return true;
}
ESP_LOGI(TAG, "Initializing AdcManager...");
// 初始化ADC数组
memset(pressure_adc_values_, 0, sizeof(pressure_adc_values_));
InitializeAdc();
// InitializeDigitalOutput(); // 暂时注释掉DO初始化
// 先设置初始化状态,再启动任务
initialized_ = true;
// 初始化后立刻读取一次,便于快速确认链路
int init_read_raw = -1;
esp_err_t init_read_ret = adc_oneshot_read(
adc1_handle_, PRESSURE_SENSOR_ADC_CHANNEL, &init_read_raw);
if (init_read_ret != ESP_OK) {
ESP_LOGE(TAG, "Initial ADC read failed: %s",
esp_err_to_name(init_read_ret));
} else {
ESP_LOGI(TAG, "Initial ADC read ok: Raw=%d", init_read_raw);
}
// 启动ADC任务
StartAdcTask();
ESP_LOGI(TAG, "AdcManager initialized successfully");
return true;
}
void AdcManager::InitializeAdc() {
ESP_LOGI(TAG, "Initializing ADC for pressure sensor on GPIO4 (ADC1_CH3)...");
// 初始化ADC驱动
adc_oneshot_unit_init_cfg_t init_config1 = {
.unit_id = ADC_UNIT_1,
};
esp_err_t ret = adc_oneshot_new_unit(&init_config1, &adc1_handle_);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize ADC unit: %s", esp_err_to_name(ret));
return;
}
ESP_LOGI(TAG, "ADC unit initialized successfully");
// 配置ADC通道
adc_oneshot_chan_cfg_t chan_config = {
.atten = ADC_ATTEN_DB_12,
.bitwidth = ADC_BITWIDTH_12,
};
ret = adc_oneshot_config_channel(adc1_handle_, PRESSURE_SENSOR_ADC_CHANNEL,
&chan_config);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to configure ADC channel %d: %s",
PRESSURE_SENSOR_ADC_CHANNEL, esp_err_to_name(ret));
return;
}
ESP_LOGI(TAG, "ADC channel %d configured successfully",
PRESSURE_SENSOR_ADC_CHANNEL);
// 初始化ADC校准
adc_cali_curve_fitting_config_t cali_config = {
.unit_id = ADC_UNIT_1,
.atten = ADC_ATTEN_DB_12,
.bitwidth = ADC_BITWIDTH_12,
};
ret = adc_cali_create_scheme_curve_fitting(&cali_config, &adc1_cali_handle_);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "ADC calibration not available: %s", esp_err_to_name(ret));
adc1_cali_handle_ = NULL;
} else {
ESP_LOGI(TAG, "ADC calibration initialized successfully");
}
ESP_LOGI(TAG, "ADC initialized for pressure sensor monitoring on GPIO4");
}
void AdcManager::ReadPressureSensorData() {
if (!initialized_) {
return;
}
int adc_value;
esp_err_t ret =
adc_oneshot_read(adc1_handle_, PRESSURE_SENSOR_ADC_CHANNEL, &adc_value);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to read pressure sensor ADC: %s",
esp_err_to_name(ret));
return;
}
// 直接使用原始ADC值不进行电压转换
current_pressure_value_ = adc_value;
// 压力检测触发音乐播放
static bool last_pressure_state = false;
static int64_t pressure_start_time = 0;
static bool pressure_triggered = false;
if (last_pressure_state) {
// 长时间不动检测逻辑
CheckLongTimeNoMovement(current_pressure_value_);
}
// 每隔5次打印一次详细日志便于定位问题
static int adc_log_counter = 0;
adc_log_counter++;
if (adc_log_counter >= 10) {
ESP_LOGI(TAG, "ADC read: Raw=%d", adc_value);
adc_log_counter = 0;
}
bool current_pressure_state = IsPressureDetected();
if (current_pressure_state && !last_pressure_state) {
// 压力开始检测,记录开始时间
pressure_start_time = esp_timer_get_time();
pressure_triggered = false;
ESP_LOGI(TAG, "Pressure detection started");
} else if (current_pressure_state && last_pressure_state) {
// 压力持续检测中检查是否超过2秒
int64_t current_time = esp_timer_get_time();
int64_t duration_ms =
(current_time - pressure_start_time) / 1000; // 转换为毫秒
if (duration_ms >= PRESSURE_DETECTION_TIME_MS && !pressure_triggered) {
ESP_LOGI(TAG,
"Pressure detected for %ld ms! Triggering music playback...",
(long)duration_ms);
// 触发音乐播放
TriggerMusicPlayback();
pressure_triggered = true; // 防止重复触发
}
} else if (!current_pressure_state && last_pressure_state) {
// 压力结束检测
ESP_LOGI(TAG, "Pressure detection ended");
pressure_triggered = false;
}
last_pressure_state = current_pressure_state;
// ADC值小于100时的暂停检测也需要持续2秒
static int64_t low_value_start_time = 0;
static bool low_value_triggered = false;
if (adc_value < 100) {
if (low_value_start_time == 0) {
// 第一次检测到小于100的值记录开始时间
low_value_start_time = esp_timer_get_time();
low_value_triggered = false;
ESP_LOGI(TAG, "ADC low value detection started (value: %d)", adc_value);
} else {
// 持续检测小于100的值检查是否超过2秒
int64_t current_time = esp_timer_get_time();
int64_t duration_ms =
(current_time - low_value_start_time) / 1000; // 转换为毫秒
if (duration_ms >= LOW_VALUE_DETECTION_TIME_MS && !low_value_triggered) {
ESP_LOGI(TAG,
"ADC low value detected for %ld ms! (value: %d) Triggering music pause...",
(long)duration_ms, adc_value);
TriggerMusicPauseback();
low_value_triggered = true; // 防止重复触发
}
}
} else {
// ADC值大于等于100重置低值检测
if (low_value_start_time != 0) {
ESP_LOGI(TAG, "ADC low value detection ended (value: %d)", adc_value);
low_value_start_time = 0;
low_value_triggered = false;
}
}
}
void AdcManager::StartAdcTask() {
if (!initialized_) {
ESP_LOGE(TAG, "AdcManager not initialized");
return;
}
BaseType_t ret =
xTaskCreate(AdcTask, "adc_task", 4096, this, 2, &adc_task_handle_);
if (ret != pdPASS) {
ESP_LOGE(TAG, "Failed to create ADC task");
} else {
ESP_LOGI(TAG, "ADC task created successfully");
}
}
void AdcManager::StopAdcTask() {
if (adc_task_handle_) {
vTaskDelete(adc_task_handle_);
adc_task_handle_ = nullptr;
}
}
void AdcManager::AdcTask(void *pvParameters) {
AdcManager *manager = static_cast<AdcManager *>(pvParameters);
ESP_LOGI(TAG, "ADC task started");
while (true) {
if (manager->initialized_) {
manager->ReadPressureSensorData();
}
vTaskDelay(pdMS_TO_TICKS(100)); // 100ms间隔
}
}
int AdcManager::GetCurrentPressureValue() const {
return current_pressure_value_;
}
const int *AdcManager::GetPressureAdcValues() const {
return pressure_adc_values_;
}
size_t AdcManager::GetPressureSampleCount() const {
return (pressure_data_index_ == 0) ? kPressureAdcDataCount
: pressure_data_index_;
}
bool AdcManager::IsPressureDetected() const {
if (!initialized_) {
return false;
}
return current_pressure_value_ > 1000; // 压力阈值100
}
bool AdcManager::IsLightPressure() const {
if (!initialized_) {
return false;
}
return current_pressure_value_ > 500; // 轻压阈值500
}
void AdcManager::CheckLongTimeNoMovement(int adc_value) {
uint32_t current_time = esp_timer_get_time() / 1000000; // 转换为秒
// 计算ADC值变化
int adc_change = abs(adc_value - last_stable_value_);
if (adc_change > kMovementThreshold) {
// 有显著变化,重置不动检测
last_stable_value_ = adc_value;
no_movement_start_time_ = current_time;
is_no_movement_detected_ = false;
} else {
// 变化很小,检查是否长时间不动
if (no_movement_start_time_ == 0) {
no_movement_start_time_ = current_time;
}
uint32_t no_movement_duration = current_time - no_movement_start_time_;
if (no_movement_duration >= kLongTimeThreshold &&
!is_no_movement_detected_) {
is_no_movement_detected_ = true;
ESP_LOGW(TAG,
"Long time no movement detected! Duration: %lu seconds, ADC: %d",
no_movement_duration, adc_value);
// 停止音乐播放和语音交互
TriggerMusicPauseback();
}
}
}
bool AdcManager::IsLongTimeNoMovement() const {
if (!initialized_) {
return false;
}
return is_no_movement_detected_;
}
uint32_t AdcManager::GetNoMovementDuration() const {
if (!initialized_ || no_movement_start_time_ == 0) {
return 0;
}
uint32_t current_time = esp_timer_get_time() / 1000000;
return current_time - no_movement_start_time_;
}
void AdcManager::TriggerMusicPauseback() {
ESP_LOGI(TAG, "Triggering music pauseback");
auto music = Board::GetInstance().GetMusic();
if (!music) {
ESP_LOGI(TAG, "No music player found");
return;
}
// 检查音乐状态,避免重复操作
if (!music->IsPlaying() && !music->IsPaused()) {
ESP_LOGI(TAG, "Music is not playing or paused, skipping pause operation");
return;
}
music->PauseSong();
// 停止语音交互
auto &app = Application::GetInstance();
app.GetAudioService().EnableWakeWordDetection(false);
ESP_LOGI(TAG, "Stopped wake word detection due to long time no movement");
}
void AdcManager::TriggerMusicPlayback() {
ESP_LOGI(TAG, "Triggering music playback");
// 确保音频输出已启用
auto& board = Board::GetInstance();
auto codec = board.GetAudioCodec();
if (!codec) {
ESP_LOGE(TAG, "Audio codec not available");
return;
}
codec->EnableOutput(true);
ESP_LOGI(TAG, "Audio output enabled");
// 通过Board接口获取音乐播放器并触发播放
auto music = board.GetMusic();
if (!music) {
ESP_LOGI(TAG, "No music player found");
return;
}
if (music->IsPlaying()) {
ESP_LOGI(TAG, "Music is already playing");
return;
}
if (music->IsDownloading()) {
ESP_LOGI(TAG, "Music is already downloading");
return;
}
if (music->IsPaused()) {
ESP_LOGI(TAG, "Music is already paused");
music->ResumeSong();
return;
}
auto song_name = "稻香";
auto artist_name = "";
if (!music->Download(song_name, artist_name)) {
ESP_LOGI(TAG, "获取音乐资源失败");
return;
}
auto download_result = music->GetDownloadResult();
ESP_LOGI(TAG, "Music details result: %s", download_result.c_str());
}

View File

@@ -0,0 +1,80 @@
#ifndef ADC_MANAGER_H
#define ADC_MANAGER_H
#include "config.h"
#include <esp_adc/adc_oneshot.h>
#include <esp_adc/adc_cali.h>
#include <esp_adc/adc_cali_scheme.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
class AdcManager {
public:
static AdcManager& GetInstance();
// 初始化ADC系统
bool Initialize();
// 读取压感传感器数据
void ReadPressureSensorData();
// 获取当前压感值
int GetCurrentPressureValue() const;
// 获取ADC原始值数组
const int* GetPressureAdcValues() const;
// 获取有效样本数量
size_t GetPressureSampleCount() const;
// 启动/停止ADC任务
void StartAdcTask();
void StopAdcTask();
// 检查是否已初始化
bool IsInitialized() const { return initialized_; }
// 基于ADC值判断压力状态
bool IsPressureDetected() const;
bool IsLightPressure() const;
// 长时间不动检测
bool IsLongTimeNoMovement() const;
uint32_t GetNoMovementDuration() const; // 返回不动持续时间(秒)
// 压力检测触发音乐播放
void TriggerMusicPlayback();
void TriggerMusicPauseback();
private:
AdcManager() = default;
~AdcManager() = default;
AdcManager(const AdcManager&) = delete;
AdcManager& operator=(const AdcManager&) = delete;
void InitializeAdc();
void CheckLongTimeNoMovement(int adc_value);
static void AdcTask(void *pvParameters);
bool initialized_ = false;
adc_oneshot_unit_handle_t adc1_handle_;
adc_cali_handle_t adc1_cali_handle_;
// 压感传感器数据
static constexpr size_t kPressureAdcDataCount = 10;
int pressure_adc_values_[kPressureAdcDataCount];
size_t pressure_data_index_ = 0;
int current_pressure_value_ = 0;
// 长时间不动检测相关变量
int last_stable_value_ = 0;
uint32_t no_movement_start_time_ = 0;
bool is_no_movement_detected_ = false;
static constexpr int kMovementThreshold = 50; // ADC变化阈值
static constexpr uint32_t kLongTimeThreshold = 30; // 长时间阈值(秒)
// 任务句柄
TaskHandle_t adc_task_handle_ = nullptr;
};
#endif // ADC_MANAGER_H

View File

@@ -0,0 +1,131 @@
#include "button_manager.h"
#include "application.h"
#include <esp_log.h>
#define TAG "ButtonManager"
ButtonManager& ButtonManager::GetInstance() {
static ButtonManager instance;
return instance;
}
ButtonManager::ButtonManager()
: boot_button_(BOOT_BUTTON_GPIO),
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
}
bool ButtonManager::Initialize() {
if (initialized_) {
ESP_LOGW(TAG, "ButtonManager already initialized");
return true;
}
ESP_LOGI(TAG, "Initializing ButtonManager...");
// 设置按钮回调
SetupButtonCallbacks();
initialized_ = true;
ESP_LOGI(TAG, "ButtonManager initialized successfully");
return true;
}
void ButtonManager::SetupButtonCallbacks() {
ESP_LOGI(TAG, "Setting up button callbacks...");
// BOOT按钮回调
boot_button_.OnClick([]() {
ESP_LOGI(TAG, "Boot button clicked");
});
boot_button_.OnLongPress([]() {
ESP_LOGI(TAG, "BOOT long pressed: play boot tone");
// 确保音频输出已启用
auto& board = Board::GetInstance();
auto codec = board.GetAudioCodec();
if (!codec) {
ESP_LOGE(TAG, "Audio codec not available");
return;
}
codec->EnableOutput(true);
codec->SetOutputVolume(10);
auto music = Board::GetInstance().GetMusic();
if (!music) {
ESP_LOGE(TAG, "Music player not available");
return;
}
auto song_name = "稻香";
auto artist_name = "";
if (!music->Download(song_name, artist_name)) {
ESP_LOGI(TAG, "获取音乐资源失败");
return;
}
auto download_result = music->GetDownloadResult();
ESP_LOGI(TAG, "Music details result: %s", download_result.c_str());
});
// 音量上按钮回调
volume_up_button_.OnClick([]() {
ESP_LOGI(TAG, "Volume up button clicked");
// 通过AudioService间接控制音量
auto& board = Board::GetInstance();
auto codec = board.GetAudioCodec();
codec->SetOutputVolume(codec->output_volume() + 10);
ESP_LOGI(TAG, "Volume up requested");
});
volume_up_button_.OnLongPress([]() {
ESP_LOGI(TAG, "Volume up long pressed: switching to voice interaction mode");
// 播放进入语音交互模式的提示音
auto& app = Application::GetInstance();
app.PlaySound("success"); // 播放成功提示音
// 暂停音乐播放
auto music = Board::GetInstance().GetMusic();
if (music && music->IsPlaying()) {
music->PauseSong();
ESP_LOGI(TAG, "Music paused for voice interaction");
}
// 切换到语音交互模式
app.GetAudioService().EnableWakeWordDetection(true);
app.GetAudioService().EnableVoiceProcessing(true);
ESP_LOGI(TAG, "Switched to voice interaction mode - waiting for user voice input");
});
// 音量下按钮回调
volume_down_button_.OnClick([]() {
ESP_LOGI(TAG, "Volume down button clicked");
auto& board = Board::GetInstance();
auto codec = board.GetAudioCodec();
codec->SetOutputVolume(codec->output_volume() - 10);
ESP_LOGI(TAG, "Volume down requested");
});
volume_down_button_.OnLongPress([]() {
ESP_LOGI(TAG, "Volume down long pressed: stopping audio playback and voice interaction");
// 播放停止提示音
auto& app = Application::GetInstance();
app.PlaySound("exclamation"); // 播放感叹号提示音
// 停止音乐播放
auto music = Board::GetInstance().GetMusic();
if (music && music->IsPlaying()) {
music->PauseSong();
ESP_LOGI(TAG, "Music playback stopped");
}
// 停止语音交互
app.GetAudioService().EnableWakeWordDetection(false);
app.GetAudioService().EnableVoiceProcessing(false);
ESP_LOGI(TAG, "Voice interaction stopped");
});
}

View File

@@ -0,0 +1,31 @@
#ifndef BUTTON_MANAGER_H
#define BUTTON_MANAGER_H
#include "button.h"
#include "config.h"
class ButtonManager {
public:
static ButtonManager& GetInstance();
// 初始化按钮系统
bool Initialize();
// 检查是否已初始化
bool IsInitialized() const { return initialized_; }
private:
ButtonManager();
~ButtonManager() = default;
ButtonManager(const ButtonManager&) = delete;
ButtonManager& operator=(const ButtonManager&) = delete;
void SetupButtonCallbacks();
bool initialized_ = false;
Button boot_button_;
Button volume_up_button_;
Button volume_down_button_;
};
#endif // BUTTON_MANAGER_H

View File

@@ -0,0 +1,77 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
// ESP32-S3 智能音箱开发板配置
// 音频配置
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 16000
// 扬声器I2S配置
#define AUDIO_I2S_GPIO_WS GPIO_NUM_47 // 扬声器Word Select
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_17 // 扬声器Bit Clock
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15 // 扬声器数据输出
// INMP441 I2S麦克风配置 (BCLK/WS/DIN)
// 说明: INMP441 是 I2S 数字麦,需要标准 I2S 三线
// 建议映射: BCLK=GPIO14, WS=GPIO38, DIN=GPIO16可按需要调整
#define AUDIO_MIC_I2S_BCLK GPIO_NUM_14
#define AUDIO_MIC_I2S_WS GPIO_NUM_38
#define AUDIO_MIC_I2S_DIN GPIO_NUM_16
// 用户交互
#define BUILTIN_LED_GPIO GPIO_NUM_48
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_41
// IMU传感器 (I2C接口)
#define IMU_I2C_SDA_PIN GPIO_NUM_21
#define IMU_I2C_SCL_PIN GPIO_NUM_20
#define IMU_INT_PIN GPIO_NUM_13
// 压感传感器 (ADC接口)
#define PRESSURE_SENSOR_ADC_CHANNEL ADC_CHANNEL_3 // GPIO4 (ADC1_CHANNEL_3)
// 功能IO定义
// LED控制
#define LED_RING_GPIO GPIO_NUM_6 // LED灯环控制
#define STATUS_LED_GPIO GPIO_NUM_10 // 状态指示灯
// 无显示配置
#define DISPLAY_SDA_PIN GPIO_NUM_NC
#define DISPLAY_SCL_PIN GPIO_NUM_NC
#define DISPLAY_WIDTH 0
#define DISPLAY_HEIGHT 0
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
// 无摄像头配置
#define CAMERA_PIN_PWDN GPIO_NUM_NC
#define CAMERA_PIN_RESET GPIO_NUM_NC
#define CAMERA_PIN_XCLK GPIO_NUM_NC
#define CAMERA_PIN_SIOD GPIO_NUM_NC
#define CAMERA_PIN_SIOC GPIO_NUM_NC
#define CAMERA_PIN_D0 GPIO_NUM_NC
#define CAMERA_PIN_D1 GPIO_NUM_NC
#define CAMERA_PIN_D2 GPIO_NUM_NC
#define CAMERA_PIN_D3 GPIO_NUM_NC
#define CAMERA_PIN_D4 GPIO_NUM_NC
#define CAMERA_PIN_D5 GPIO_NUM_NC
#define CAMERA_PIN_D6 GPIO_NUM_NC
#define CAMERA_PIN_D7 GPIO_NUM_NC
#define CAMERA_PIN_VSYNC GPIO_NUM_NC
#define CAMERA_PIN_HREF GPIO_NUM_NC
#define CAMERA_PIN_PCLK GPIO_NUM_NC
// 开发板版本
#define SMART_SPEAKER_VERSION "1.0.0"
#endif // _BOARD_CONFIG_H_

View File

@@ -0,0 +1,15 @@
{
"target": "esp32s3",
"builds": [
{
"name": "esp32s3-smart-speaker",
"sdkconfig_append": [
"CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=n",
"CONFIG_LWIP_IPV6=n",
"CONFIG_USE_ESP_WAKE_WORD=y",
"CONFIG_USE_AUDIO_DEBUGGER=y",
"CONFIG_AUDIO_DEBUG_UDP_SERVER=\"192.168.122.143:8000\""
]
}
]
}

View File

@@ -0,0 +1,151 @@
#include "assets.h"
#include "adc_manager.h"
#include "button_manager.h"
#include "codecs/no_audio_codec.h"
#include "config.h"
#include "esplog_display.h"
#include "esp32_music.h"
#include "gpio_manager.h"
#include "imu_manager.h"
#include "led/single_led.h"
#include "tools_manager.h"
#include "wifi_board.h"
#include "wifi_manager.h"
#include <driver/i2c_master.h>
#include <esp_log.h>
#include <string>
#define TAG "SmartSpeaker"
class Esp32s3SmartSpeaker : public WifiBoard {
private:
// I2C总线句柄
i2c_master_bus_handle_t codec_i2c_bus_;
void InitializeManagers() {
ESP_LOGI(TAG, "Initializing managers...");
// 初始化各个管理器Initialize内部会自动启动任务
AdcManager::GetInstance().Initialize();
ImuManager::GetInstance().Initialize();
ButtonManager::GetInstance().Initialize();
GpioManager::GetInstance().Initialize();
ToolsManager::GetInstance().Initialize();
WifiManager::GetInstance().Initialize();
ESP_LOGI(TAG, "All managers initialized successfully");
}
void InitializeCodecI2c() {
return;
}
public:
Esp32s3SmartSpeaker() {
ESP_LOGI(TAG, "Initializing ESP32-S3 Smart Speaker");
// 初始化音乐播放器
music_ = new Esp32Music();
ESP_LOGI(TAG, "Music player initialized");
// 初始化I2C总线
InitializeCodecI2c();
// 初始化各个管理器
InitializeManagers();
ESP_LOGI(TAG, "ESP32-S3 Smart Speaker initialized successfully");
}
virtual ~Esp32s3SmartSpeaker() = default;
virtual std::string GetBoardType() override {
return std::string("esp32s3-smart-speaker");
}
virtual AudioCodec *GetAudioCodec() override {
static NoAudioCodecSimplex audio_codec(
AUDIO_INPUT_SAMPLE_RATE,
AUDIO_OUTPUT_SAMPLE_RATE,
// 扬声器(标准 I2S 输出)
AUDIO_I2S_GPIO_BCLK,
AUDIO_I2S_GPIO_WS,
AUDIO_I2S_GPIO_DOUT,
// 麦克风(标准 I2S 输入,单声道)
AUDIO_MIC_I2S_BCLK,
AUDIO_MIC_I2S_WS,
AUDIO_MIC_I2S_DIN);
return &audio_codec;
}
virtual Display *GetDisplay() override {
static EspLogDisplay display;
return &display;
}
virtual Led *GetLed() override {
static SingleLed led(BUILTIN_LED_GPIO);
return &led;
}
virtual std::string GetBoardJson() override {
char json_buffer[2048];
// 安全地获取管理器状态,避免在初始化过程中访问
bool imu_initialized = false;
bool adc_initialized = false;
int pressure_value = 0;
size_t pressure_sample_count = 0;
bool imu_sensor_initialized = false;
try {
auto& imu_manager = ImuManager::GetInstance();
imu_initialized = imu_manager.IsInitialized();
auto& adc_manager = AdcManager::GetInstance();
adc_initialized = adc_manager.IsInitialized();
pressure_value = adc_manager.GetCurrentPressureValue();
pressure_sample_count = adc_manager.GetPressureSampleCount();
auto imu_sensor = imu_manager.GetImuSensor();
imu_sensor_initialized = imu_sensor && imu_sensor->IsInitialized();
} catch (...) {
ESP_LOGW(TAG, "Error accessing managers in GetBoardJson, using default values");
}
snprintf(json_buffer, sizeof(json_buffer),
"{"
"\"board_type\":\"esp32s3-smart-speaker\","
"\"version\":\"%s\","
"\"features\":[\"audio\",\"imu\",\"pressure\",\"led_ring\",\"fan\",\"relay\",\"status_led\"],"
"\"audio_codec\":\"NoAudioCodecSimplex\","
"\"audio_method\":\"i2s_standard\","
"\"microphone\":\"INMP441_I2S\","
"\"speaker\":\"NoAudioCodec\","
"\"imu_initialized\":%s,"
"\"pressure_sensor_initialized\":%s,"
"\"pressure_sensor\":{\"current_value\":%d,\"adc_channel\":%d,\"sample_count\":%u},"
"\"imu_sensor\":{\"type\":\"MPU6050\",\"initialized\":%s,\"status\":\"unknown\"}"
"}",
SMART_SPEAKER_VERSION,
imu_initialized ? "true" : "false",
adc_initialized ? "true" : "false",
pressure_value,
PRESSURE_SENSOR_ADC_CHANNEL,
(unsigned int)pressure_sample_count,
imu_sensor_initialized ? "true" : "false"
);
ESP_LOGI(TAG, "GetBoardJson completed successfully");
return std::string(json_buffer);
}
virtual Assets *GetAssets() override {
static Assets assets(std::string(ASSETS_XIAOZHI_WAKENET_SMALL));
return &assets;
}
};
DECLARE_BOARD(Esp32s3SmartSpeaker);

View File

@@ -0,0 +1,53 @@
#include "gpio_manager.h"
#include <esp_log.h>
#define TAG "GpioManager"
GpioManager& GpioManager::GetInstance() {
static GpioManager instance;
return instance;
}
bool GpioManager::Initialize() {
if (initialized_) {
ESP_LOGW(TAG, "GpioManager already initialized");
return true;
}
ESP_LOGI(TAG, "Initializing GpioManager...");
// 初始化GPIO输出
gpio_config_t io_conf = {};
// LED灯环控制
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = (1ULL << LED_RING_GPIO);
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
gpio_config(&io_conf);
// 状态指示灯
io_conf.pin_bit_mask = (1ULL << STATUS_LED_GPIO);
gpio_config(&io_conf);
initialized_ = true;
ESP_LOGI(TAG, "GpioManager initialized successfully");
return true;
}
void GpioManager::SetLedRing(bool state) {
if (!initialized_) {
ESP_LOGE(TAG, "GpioManager not initialized");
return;
}
gpio_set_level(LED_RING_GPIO, state ? 1 : 0);
}
void GpioManager::SetStatusLed(bool state) {
if (!initialized_) {
ESP_LOGE(TAG, "GpioManager not initialized");
return;
}
gpio_set_level(STATUS_LED_GPIO, state ? 1 : 0);
}

View File

@@ -0,0 +1,30 @@
#ifndef GPIO_MANAGER_H
#define GPIO_MANAGER_H
#include "config.h"
#include <driver/gpio.h>
class GpioManager {
public:
static GpioManager& GetInstance();
// 初始化GPIO系统
bool Initialize();
// GPIO控制方法
void SetLedRing(bool state);
void SetStatusLed(bool state);
// 检查是否已初始化
bool IsInitialized() const { return initialized_; }
private:
GpioManager() = default;
~GpioManager() = default;
GpioManager(const GpioManager&) = delete;
GpioManager& operator=(const GpioManager&) = delete;
bool initialized_ = false;
};
#endif // GPIO_MANAGER_H

View File

@@ -0,0 +1,139 @@
#include "imu_manager.h"
#include <esp_log.h>
#define TAG "ImuManager"
ImuManager& ImuManager::GetInstance() {
static ImuManager instance;
return instance;
}
bool ImuManager::Initialize() {
if (initialized_) {
ESP_LOGW(TAG, "ImuManager already initialized");
return true;
}
ESP_LOGI(TAG, "Initializing ImuManager...");
InitializeImu();
// 启动IMU任务
StartImuTask();
initialized_ = true;
ESP_LOGI(TAG, "ImuManager initialized successfully");
return true;
}
void ImuManager::InitializeImu() {
ESP_LOGI(TAG, "Initializing MPU6050 IMU sensor...");
// IMU传感器I2C总线
i2c_master_bus_config_t imu_i2c_cfg = {
.i2c_port = I2C_NUM_1,
.sda_io_num = IMU_I2C_SDA_PIN,
.scl_io_num = IMU_I2C_SCL_PIN,
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.intr_priority = 0,
.trans_queue_depth = 0,
.flags = {
.enable_internal_pullup = 1,
.allow_pd = false,
},
};
ESP_ERROR_CHECK(i2c_new_master_bus(&imu_i2c_cfg, &imu_i2c_bus_));
// 初始化MPU6050传感器
mpu6050_sensor_ = std::make_unique<Mpu6050Sensor>(imu_i2c_bus_);
if (mpu6050_sensor_) {
uint8_t device_id;
if (mpu6050_sensor_->GetDeviceId(&device_id)) {
ESP_LOGI(TAG, "MPU6050 device ID: 0x%02X", device_id);
if (device_id == MPU6050_WHO_AM_I_VAL) {
if (mpu6050_sensor_->Initialize(ACCE_FS_4G, GYRO_FS_500DPS)) {
if (mpu6050_sensor_->WakeUp()) {
initialized_ = true;
ESP_LOGI(TAG, "MPU6050 sensor initialized successfully");
} else {
ESP_LOGE(TAG, "Failed to wake up MPU6050");
}
} else {
ESP_LOGE(TAG, "Failed to initialize MPU6050");
}
} else {
ESP_LOGE(TAG, "MPU6050 device ID mismatch: expected 0x%02X, got 0x%02X",
MPU6050_WHO_AM_I_VAL, device_id);
}
} else {
ESP_LOGE(TAG, "Failed to read MPU6050 device ID");
}
} else {
ESP_LOGE(TAG, "Failed to create MPU6050 sensor instance");
}
if (!initialized_) {
ESP_LOGW(TAG, "IMU sensor initialization failed - continuing without IMU");
}
}
void ImuManager::StartImuTask() {
if (!initialized_) {
ESP_LOGW(TAG, "ImuManager not initialized, skipping IMU task creation");
return;
}
BaseType_t ret = xTaskCreate(ImuDataTask, "imu_data_task", 4096, this, 5, &imu_task_handle_);
if (ret != pdPASS) {
ESP_LOGE(TAG, "Failed to create IMU data task");
} else {
ESP_LOGI(TAG, "IMU data task created successfully");
}
}
void ImuManager::StopImuTask() {
if (imu_task_handle_) {
vTaskDelete(imu_task_handle_);
imu_task_handle_ = nullptr;
}
}
void ImuManager::ImuDataTask(void *pvParameters) {
ImuManager *manager = static_cast<ImuManager *>(pvParameters);
ESP_LOGI(TAG, "IMU data task started");
mpu6050_acce_value_t acce;
mpu6050_gyro_value_t gyro;
mpu6050_temp_value_t temp;
complimentary_angle_t angle;
while (true) {
if (manager->mpu6050_sensor_ && manager->initialized_) {
// 读取加速度计数据
if (manager->mpu6050_sensor_->GetAccelerometer(&acce)) {
ESP_LOGI(TAG, "Accelerometer - X:%.2f, Y:%.2f, Z:%.2f", acce.acce_x,
acce.acce_y, acce.acce_z);
}
// 读取陀螺仪数据
if (manager->mpu6050_sensor_->GetGyroscope(&gyro)) {
ESP_LOGI(TAG, "Gyroscope - X:%.2f, Y:%.2f, Z:%.2f", gyro.gyro_x,
gyro.gyro_y, gyro.gyro_z);
}
// 读取温度数据
if (manager->mpu6050_sensor_->GetTemperature(&temp)) {
ESP_LOGI(TAG, "Temperature: %.2f°C", temp.temp);
}
// 计算姿态角
if (manager->mpu6050_sensor_->ComplimentaryFilter(&acce, &gyro, &angle)) {
ESP_LOGI(TAG, "Attitude - Pitch:%.2f°, Roll:%.2f°, Yaw:%.2f°",
angle.pitch, angle.roll, angle.yaw);
}
}
vTaskDelay(pdMS_TO_TICKS(50));
}
}

View File

@@ -0,0 +1,43 @@
#ifndef IMU_MANAGER_H
#define IMU_MANAGER_H
#include "config.h"
#include "mpu6050_sensor.h"
#include <driver/i2c_master.h>
#include <memory>
class ImuManager {
public:
static ImuManager& GetInstance();
// 初始化IMU系统
bool Initialize();
// 启动/停止IMU任务
void StartImuTask();
void StopImuTask();
// 获取IMU传感器实例
Mpu6050Sensor* GetImuSensor() const { return mpu6050_sensor_.get(); }
// 检查是否已初始化
bool IsInitialized() const { return initialized_; }
private:
ImuManager() = default;
~ImuManager() = default;
ImuManager(const ImuManager&) = delete;
ImuManager& operator=(const ImuManager&) = delete;
void InitializeImu();
static void ImuDataTask(void *pvParameters);
bool initialized_ = false;
i2c_master_bus_handle_t imu_i2c_bus_;
std::unique_ptr<Mpu6050Sensor> mpu6050_sensor_;
// 任务句柄
TaskHandle_t imu_task_handle_ = nullptr;
};
#endif // IMU_MANAGER_H

View File

@@ -0,0 +1,280 @@
#include "mpu6050_sensor.h"
#include <esp_timer.h>
#include <cmath>
const char* Mpu6050Sensor::TAG = "MPU6050";
Mpu6050Sensor::Mpu6050Sensor(i2c_master_bus_handle_t i2c_bus, uint8_t device_addr)
: i2c_bus_(i2c_bus), device_addr_(device_addr), initialized_(false),
acce_fs_(ACCE_FS_4G), gyro_fs_(GYRO_FS_500DPS), dt_(0.0f), alpha_(0.98f) {
// 初始化设备句柄
i2c_device_config_t dev_cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = device_addr_,
.scl_speed_hz = 100000,
};
esp_err_t ret = i2c_master_bus_add_device(i2c_bus_, &dev_cfg, &device_handle_);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to add MPU6050 device to I2C bus: %s", esp_err_to_name(ret));
device_handle_ = nullptr;
}
// 初始化互补滤波
InitializeComplimentaryFilter();
}
Mpu6050Sensor::~Mpu6050Sensor() {
if (device_handle_) {
i2c_master_bus_rm_device(device_handle_);
}
}
bool Mpu6050Sensor::Initialize(mpu6050_acce_fs_t acce_fs, mpu6050_gyro_fs_t gyro_fs) {
if (!device_handle_) {
ESP_LOGE(TAG, "Device handle is null");
return false;
}
acce_fs_ = acce_fs;
gyro_fs_ = gyro_fs;
// 配置加速度计量程
uint8_t acce_config = (acce_fs << 3) & 0x18;
if (!WriteRegister(0x1C, acce_config)) {
ESP_LOGE(TAG, "Failed to configure accelerometer");
return false;
}
// 配置陀螺仪量程
uint8_t gyro_config = (gyro_fs << 3) & 0x18;
if (!WriteRegister(0x1B, gyro_config)) {
ESP_LOGE(TAG, "Failed to configure gyroscope");
return false;
}
// 配置数字低通滤波器
if (!WriteRegister(0x1A, 0x06)) { // DLPF_CFG = 6 (5Hz)
ESP_LOGE(TAG, "Failed to configure DLPF");
return false;
}
// 配置采样率 (1kHz / (1 + 7) = 125Hz)
if (!WriteRegister(0x19, 0x07)) {
ESP_LOGE(TAG, "Failed to configure sample rate");
return false;
}
initialized_ = true;
ESP_LOGI(TAG, "MPU6050 initialized successfully");
ESP_LOGI(TAG, "Accelerometer range: %d, Gyroscope range: %d", acce_fs, gyro_fs);
return true;
}
bool Mpu6050Sensor::WakeUp() {
if (!device_handle_) {
ESP_LOGE(TAG, "Device handle is null");
return false;
}
// 清除睡眠模式位
if (!WriteRegister(0x6B, 0x00)) {
ESP_LOGE(TAG, "Failed to wake up MPU6050");
return false;
}
// 等待传感器稳定
vTaskDelay(pdMS_TO_TICKS(100));
ESP_LOGI(TAG, "MPU6050 woken up");
return true;
}
bool Mpu6050Sensor::GetDeviceId(uint8_t* device_id) {
if (!device_id) {
ESP_LOGE(TAG, "Device ID pointer is null");
return false;
}
return ReadRegister(MPU6050_WHO_AM_I_REG, device_id, 1);
}
bool Mpu6050Sensor::GetAccelerometer(mpu6050_acce_value_t* acce) {
if (!acce) {
ESP_LOGE(TAG, "Accelerometer data pointer is null");
return false;
}
uint8_t data[6];
if (!ReadRegister(0x3B, data, 6)) {
ESP_LOGE(TAG, "Failed to read accelerometer data");
return false;
}
// 组合16位数据
int16_t raw_x = (data[0] << 8) | data[1];
int16_t raw_y = (data[2] << 8) | data[3];
int16_t raw_z = (data[4] << 8) | data[5];
// 根据量程转换为g值
float scale_factor;
switch (acce_fs_) {
case ACCE_FS_2G: scale_factor = 16384.0f; break;
case ACCE_FS_4G: scale_factor = 8192.0f; break;
case ACCE_FS_8G: scale_factor = 4096.0f; break;
case ACCE_FS_16G: scale_factor = 2048.0f; break;
default: scale_factor = 8192.0f; break;
}
acce->acce_x = raw_x / scale_factor;
acce->acce_y = raw_y / scale_factor;
acce->acce_z = raw_z / scale_factor;
return true;
}
bool Mpu6050Sensor::GetGyroscope(mpu6050_gyro_value_t* gyro) {
if (!gyro) {
ESP_LOGE(TAG, "Gyroscope data pointer is null");
return false;
}
uint8_t data[6];
if (!ReadRegister(0x43, data, 6)) {
ESP_LOGE(TAG, "Failed to read gyroscope data");
return false;
}
// 组合16位数据
int16_t raw_x = (data[0] << 8) | data[1];
int16_t raw_y = (data[2] << 8) | data[3];
int16_t raw_z = (data[4] << 8) | data[5];
// 根据量程转换为度/秒
float scale_factor;
switch (gyro_fs_) {
case GYRO_FS_250DPS: scale_factor = 131.0f; break;
case GYRO_FS_500DPS: scale_factor = 65.5f; break;
case GYRO_FS_1000DPS: scale_factor = 32.8f; break;
case GYRO_FS_2000DPS: scale_factor = 16.4f; break;
default: scale_factor = 65.5f; break;
}
gyro->gyro_x = raw_x / scale_factor;
gyro->gyro_y = raw_y / scale_factor;
gyro->gyro_z = raw_z / scale_factor;
return true;
}
bool Mpu6050Sensor::GetTemperature(mpu6050_temp_value_t* temp) {
if (!temp) {
ESP_LOGE(TAG, "Temperature data pointer is null");
return false;
}
uint8_t data[2];
if (!ReadRegister(0x41, data, 2)) {
ESP_LOGE(TAG, "Failed to read temperature data");
return false;
}
// 组合16位数据
int16_t raw_temp = (data[0] << 8) | data[1];
// 转换为摄氏度: T = (TEMP_OUT / 340) + 36.53
temp->temp = raw_temp / 340.0f + 36.53f;
return true;
}
bool Mpu6050Sensor::ComplimentaryFilter(const mpu6050_acce_value_t* acce,
const mpu6050_gyro_value_t* gyro,
complimentary_angle_t* angle) {
if (!acce || !gyro || !angle) {
ESP_LOGE(TAG, "Input pointers are null");
return false;
}
uint64_t current_time = GetCurrentTimeUs();
// 计算时间间隔
if (last_time_ > 0) {
dt_ = (current_time - last_time_) / 1000000.0f; // 转换为秒
} else {
dt_ = 0.01f; // 默认10ms
}
// 从加速度计计算俯仰角和横滚角
float accel_pitch = atan2f(acce->acce_y, sqrtf(acce->acce_x * acce->acce_x + acce->acce_z * acce->acce_z)) * 180.0f / M_PI;
float accel_roll = atan2f(-acce->acce_x, acce->acce_z) * 180.0f / M_PI;
// 互补滤波
angle->pitch = alpha_ * (last_angle_.pitch + gyro->gyro_x * dt_) + (1.0f - alpha_) * accel_pitch;
angle->roll = alpha_ * (last_angle_.roll + gyro->gyro_y * dt_) + (1.0f - alpha_) * accel_roll;
angle->yaw = last_angle_.yaw + gyro->gyro_z * dt_; // 偏航角只能通过陀螺仪积分
// 更新上次的角度和时间
last_angle_ = *angle;
last_time_ = current_time;
return true;
}
std::string Mpu6050Sensor::GetStatusJson() const {
std::string json = "{";
json += "\"initialized\":" + std::string(initialized_ ? "true" : "false") + ",";
json += "\"device_address\":" + std::to_string(device_addr_) + ",";
json += "\"accelerometer_range\":" + std::to_string(static_cast<int>(acce_fs_)) + ",";
json += "\"gyroscope_range\":" + std::to_string(static_cast<int>(gyro_fs_)) + ",";
json += "\"filter_alpha\":" + std::to_string(alpha_) + ",";
json += "\"sample_rate\":125";
json += "}";
return json;
}
bool Mpu6050Sensor::WriteRegister(uint8_t reg_addr, uint8_t data) {
if (!device_handle_) {
return false;
}
uint8_t write_buf[2] = {reg_addr, data};
esp_err_t ret = i2c_master_transmit(device_handle_, write_buf, 2, 1000);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to write register 0x%02X: %s", reg_addr, esp_err_to_name(ret));
return false;
}
return true;
}
bool Mpu6050Sensor::ReadRegister(uint8_t reg_addr, uint8_t* data, size_t len) {
if (!device_handle_ || !data) {
return false;
}
esp_err_t ret = i2c_master_transmit_receive(device_handle_, &reg_addr, 1, data, len, 1000);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to read register 0x%02X: %s", reg_addr, esp_err_to_name(ret));
return false;
}
return true;
}
uint64_t Mpu6050Sensor::GetCurrentTimeUs() {
return esp_timer_get_time();
}
void Mpu6050Sensor::InitializeComplimentaryFilter() {
last_angle_.pitch = 0.0f;
last_angle_.roll = 0.0f;
last_angle_.yaw = 0.0f;
last_time_ = 0;
dt_ = 0.01f;
alpha_ = 0.98f; // 互补滤波系数0.98表示更信任陀螺仪
}

View File

@@ -0,0 +1,184 @@
#ifndef MPU6050_SENSOR_H
#define MPU6050_SENSOR_H
#include <driver/i2c_master.h>
#include <esp_log.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <string>
// MPU6050相关定义
#define MPU6050_I2C_ADDRESS 0x68
#define MPU6050_WHO_AM_I_REG 0x75
#define MPU6050_WHO_AM_I_VAL 0x68
// 加速度计量程
typedef enum {
ACCE_FS_2G = 0, // ±2g
ACCE_FS_4G = 1, // ±4g
ACCE_FS_8G = 2, // ±8g
ACCE_FS_16G = 3 // ±16g
} mpu6050_acce_fs_t;
// 陀螺仪量程
typedef enum {
GYRO_FS_250DPS = 0, // ±250°/s
GYRO_FS_500DPS = 1, // ±500°/s
GYRO_FS_1000DPS = 2, // ±1000°/s
GYRO_FS_2000DPS = 3 // ±2000°/s
} mpu6050_gyro_fs_t;
// 传感器数据结构
typedef struct {
float acce_x;
float acce_y;
float acce_z;
} mpu6050_acce_value_t;
typedef struct {
float gyro_x;
float gyro_y;
float gyro_z;
} mpu6050_gyro_value_t;
typedef struct {
float temp;
} mpu6050_temp_value_t;
typedef struct {
float pitch;
float roll;
float yaw;
} complimentary_angle_t;
/**
* @brief MPU6050传感器封装类
*
* 提供现代化的C++接口来操作MPU6050六轴传感器
* 支持加速度计、陀螺仪、温度传感器和互补滤波
*/
class Mpu6050Sensor {
public:
/**
* @brief 构造函数
* @param i2c_bus I2C总线句柄
* @param device_addr 设备地址默认为0x68
*/
explicit Mpu6050Sensor(i2c_master_bus_handle_t i2c_bus, uint8_t device_addr = MPU6050_I2C_ADDRESS);
/**
* @brief 析构函数
*/
~Mpu6050Sensor();
/**
* @brief 初始化传感器
* @param acce_fs 加速度计量程
* @param gyro_fs 陀螺仪量程
* @return true表示初始化成功false表示失败
*/
bool Initialize(mpu6050_acce_fs_t acce_fs = ACCE_FS_4G, mpu6050_gyro_fs_t gyro_fs = GYRO_FS_500DPS);
/**
* @brief 唤醒传感器
* @return true表示成功false表示失败
*/
bool WakeUp();
/**
* @brief 获取设备ID
* @param device_id 输出设备ID
* @return true表示成功false表示失败
*/
bool GetDeviceId(uint8_t* device_id);
/**
* @brief 获取加速度计数据
* @param acce 输出加速度计数据
* @return true表示成功false表示失败
*/
bool GetAccelerometer(mpu6050_acce_value_t* acce);
/**
* @brief 获取陀螺仪数据
* @param gyro 输出陀螺仪数据
* @return true表示成功false表示失败
*/
bool GetGyroscope(mpu6050_gyro_value_t* gyro);
/**
* @brief 获取温度数据
* @param temp 输出温度数据
* @return true表示成功false表示失败
*/
bool GetTemperature(mpu6050_temp_value_t* temp);
/**
* @brief 互补滤波计算姿态角
* @param acce 加速度计数据
* @param gyro 陀螺仪数据
* @param angle 输出姿态角
* @return true表示成功false表示失败
*/
bool ComplimentaryFilter(const mpu6050_acce_value_t* acce,
const mpu6050_gyro_value_t* gyro,
complimentary_angle_t* angle);
/**
* @brief 检查传感器是否已初始化
* @return true表示已初始化false表示未初始化
*/
bool IsInitialized() const { return initialized_; }
/**
* @brief 获取传感器状态信息
* @return JSON格式的状态信息
*/
std::string GetStatusJson() const;
private:
i2c_master_bus_handle_t i2c_bus_;
i2c_master_dev_handle_t device_handle_;
uint8_t device_addr_;
bool initialized_;
mpu6050_acce_fs_t acce_fs_;
mpu6050_gyro_fs_t gyro_fs_;
// 互补滤波相关
float dt_;
float alpha_;
complimentary_angle_t last_angle_;
uint64_t last_time_;
static const char* TAG;
/**
* @brief 写入寄存器
* @param reg_addr 寄存器地址
* @param data 数据
* @return true表示成功false表示失败
*/
bool WriteRegister(uint8_t reg_addr, uint8_t data);
/**
* @brief 读取寄存器
* @param reg_addr 寄存器地址
* @param data 输出数据
* @param len 数据长度
* @return true表示成功false表示失败
*/
bool ReadRegister(uint8_t reg_addr, uint8_t* data, size_t len);
/**
* @brief 获取当前时间戳(微秒)
* @return 时间戳
*/
uint64_t GetCurrentTimeUs();
/**
* @brief 初始化互补滤波
*/
void InitializeComplimentaryFilter();
};
#endif // MPU6050_SENSOR_H

View File

@@ -0,0 +1,199 @@
#include "tools_manager.h"
#include "application.h"
#include "board.h"
#include <esp_log.h>
#define TAG "ToolsManager"
ToolsManager& ToolsManager::GetInstance() {
static ToolsManager instance;
return instance;
}
bool ToolsManager::Initialize() {
if (initialized_) {
ESP_LOGW(TAG, "ToolsManager already initialized");
return true;
}
ESP_LOGI(TAG, "Initializing ToolsManager...");
// 注册各种工具
RegisterMcpTools();
RegisterSystemTools();
RegisterAudioTools();
RegisterSensorTools();
initialized_ = true;
ESP_LOGI(TAG, "ToolsManager initialized successfully");
return true;
}
void ToolsManager::RegisterMcpTools() {
ESP_LOGI(TAG, "Registering MCP tools...");
auto& mcp_server = McpServer::GetInstance();
// 系统信息查询工具
mcp_server.AddTool(
"self.smart_speaker.get_system_info",
"获取智能音箱系统信息,包括板卡类型、版本、功能特性等",
PropertyList(),
[](const PropertyList& properties) -> ReturnValue {
auto& board = Board::GetInstance();
return board.GetBoardJson();
}
);
// 设备状态查询工具
mcp_server.AddTool(
"self.smart_speaker.get_device_state",
"获取设备当前状态,包括启动状态、连接状态等",
PropertyList(),
[](const PropertyList& properties) -> ReturnValue {
auto& app = Application::GetInstance();
DeviceState state = app.GetDeviceState();
const char* state_str = "unknown";
switch (state) {
case kDeviceStateStarting: state_str = "starting"; break;
case kDeviceStateWifiConfiguring: state_str = "configuring"; break;
case kDeviceStateIdle: state_str = "idle"; break;
case kDeviceStateConnecting: state_str = "connecting"; break;
case kDeviceStateListening: state_str = "listening"; break;
case kDeviceStateSpeaking: state_str = "speaking"; break;
case kDeviceStateUpgrading: state_str = "upgrading"; break;
case kDeviceStateActivating: state_str = "activating"; break;
case kDeviceStateAudioTesting: state_str = "audio_testing"; break;
case kDeviceStateFatalError: state_str = "fatal_error"; break;
default: state_str = "unknown"; break;
}
return std::string("{\"state\":\"") + state_str + "\"}";
}
);
ESP_LOGI(TAG, "MCP tools registered successfully");
}
void ToolsManager::RegisterSystemTools() {
ESP_LOGI(TAG, "Registering system tools...");
auto& mcp_server = McpServer::GetInstance();
// 系统重启工具
mcp_server.AddTool(
"self.smart_speaker.reboot",
"重启智能音箱系统",
PropertyList(),
[](const PropertyList& properties) -> ReturnValue {
auto& app = Application::GetInstance();
app.Reboot();
return "{\"message\":\"System reboot initiated\"}";
}
);
// 设备控制工具
mcp_server.AddTool(
"self.smart_speaker.start_listening",
"开始语音监听",
PropertyList(),
[](const PropertyList& properties) -> ReturnValue {
auto& app = Application::GetInstance();
app.StartListening();
return "{\"message\":\"Started listening\"}";
}
);
mcp_server.AddTool(
"self.smart_speaker.stop_listening",
"停止语音监听",
PropertyList(),
[](const PropertyList& properties) -> ReturnValue {
auto& app = Application::GetInstance();
app.StopListening();
return "{\"message\":\"Stopped listening\"}";
}
);
ESP_LOGI(TAG, "System tools registered successfully");
}
void ToolsManager::RegisterAudioTools() {
ESP_LOGI(TAG, "Registering audio tools...");
auto& mcp_server = McpServer::GetInstance();
// 音频播放工具
mcp_server.AddTool(
"self.smart_speaker.play_sound",
"播放指定音效。sound: 音效名称(activation, welcome, upgrade, wificonfig等)",
PropertyList({Property("sound", kPropertyTypeString, "activation")}),
[](const PropertyList& properties) -> ReturnValue {
auto& app = Application::GetInstance();
std::string sound = properties["sound"].value<std::string>();
app.PlaySound(sound);
return "{\"message\":\"Playing sound: " + sound + "\"}";
}
);
// 语音检测状态工具
mcp_server.AddTool(
"self.smart_speaker.is_voice_detected",
"检查是否检测到语音",
PropertyList(),
[](const PropertyList& properties) -> ReturnValue {
auto& app = Application::GetInstance();
bool voice_detected = app.IsVoiceDetected();
return "{\"voice_detected\":" + std::string(voice_detected ? "true" : "false") + "}";
}
);
ESP_LOGI(TAG, "Audio tools registered successfully");
}
void ToolsManager::RegisterSensorTools() {
ESP_LOGI(TAG, "Registering sensor tools...");
auto& mcp_server = McpServer::GetInstance();
// 压感传感器读取工具
mcp_server.AddTool(
"self.smart_speaker.get_pressure_sensor",
"获取压感传感器数据包括当前值、ADC通道、样本数量等",
PropertyList(),
[](const PropertyList& properties) -> ReturnValue {
auto& board = Board::GetInstance();
std::string board_json = board.GetBoardJson();
// 从board JSON中提取压感传感器信息
// 这里简化处理直接返回board信息中包含的传感器数据
return board_json;
}
);
// IMU传感器状态工具
mcp_server.AddTool(
"self.smart_speaker.get_imu_status",
"获取IMU传感器状态信息",
PropertyList(),
[](const PropertyList& properties) -> ReturnValue {
auto& board = Board::GetInstance();
std::string board_json = board.GetBoardJson();
// 从board JSON中提取IMU信息
return board_json;
}
);
// 传感器数据重置工具
mcp_server.AddTool(
"self.smart_speaker.reset_sensor_data",
"重置传感器数据缓冲区",
PropertyList(),
[](const PropertyList& properties) -> ReturnValue {
// TODO: 实现传感器数据重置
return "{\"message\":\"Sensor data reset requested\"}";
}
);
ESP_LOGI(TAG, "Sensor tools registered successfully");
}

View File

@@ -0,0 +1,32 @@
#ifndef TOOLS_MANAGER_H
#define TOOLS_MANAGER_H
#include <string>
#include "mcp_server.h"
class ToolsManager {
public:
static ToolsManager& GetInstance();
// 初始化工具系统
bool Initialize();
// 工具注册方法
void RegisterMcpTools();
void RegisterSystemTools();
void RegisterAudioTools();
void RegisterSensorTools();
// 检查是否已初始化
bool IsInitialized() const { return initialized_; }
private:
ToolsManager() = default;
~ToolsManager() = default;
ToolsManager(const ToolsManager&) = delete;
ToolsManager& operator=(const ToolsManager&) = delete;
bool initialized_ = false;
};
#endif // TOOLS_MANAGER_H

View File

@@ -0,0 +1,55 @@
#include "wifi_manager.h"
#include "settings.h"
#include "wifi_station.h"
#include <esp_log.h>
#define TAG "WifiManager"
WifiManager& WifiManager::GetInstance() {
static WifiManager instance;
return instance;
}
bool WifiManager::Initialize() {
if (initialized_) {
ESP_LOGW(TAG, "WifiManager already initialized");
return true;
}
ESP_LOGI(TAG, "Initializing WifiManager...");
// 配置WiFi设置
ConfigureWifiSettings();
// 设置默认凭据
SetDefaultCredentials();
initialized_ = true;
ESP_LOGI(TAG, "WifiManager initialized successfully");
return true;
}
void WifiManager::ConfigureWifiSettings() {
ESP_LOGI(TAG, "Configuring WiFi settings...");
// 配置WiFi参数到NVS
Settings wifi_settings("wifi", true);
// 设置不记住BSSID (不区分MAC地址)
wifi_settings.SetInt("remember_bssid", 0);
// 设置最大发射功率
wifi_settings.SetInt("max_tx_power", 0);
ESP_LOGI(TAG, "WiFi settings configured");
}
void WifiManager::SetDefaultCredentials() {
ESP_LOGI(TAG, "Setting default WiFi credentials...");
// 添加默认WiFi配置
auto &wifi_station = WifiStation::GetInstance();
wifi_station.AddAuth("xoxo", "12340000");
ESP_LOGI(TAG, "Default WiFi credentials added: SSID=xoxo, Password=12340000");
}

View File

@@ -0,0 +1,30 @@
#ifndef WIFI_MANAGER_H
#define WIFI_MANAGER_H
#include <string>
class WifiManager {
public:
static WifiManager& GetInstance();
// 初始化WiFi系统
bool Initialize();
// WiFi配置方法
void SetDefaultCredentials();
void ConfigureWifiSettings();
// 检查是否已初始化
bool IsInitialized() const { return initialized_; }
private:
WifiManager() = default;
~WifiManager() = default;
WifiManager(const WifiManager&) = delete;
WifiManager& operator=(const WifiManager&) = delete;
bool initialized_ = false;
};
#endif // WIFI_MANAGER_H

View File

@@ -1,6 +1,7 @@
#include "sscma_camera.h"
#include "mcp_server.h"
#include "lvgl_display.h"
#include "lvgl_image.h"
#include "board.h"
#include "system_info.h"
#include "config.h"
@@ -245,7 +246,8 @@ bool SscmaCamera::Capture() {
// 显示预览图片
auto display = dynamic_cast<LvglDisplay*>(Board::GetInstance().GetDisplay());
if (display != nullptr) {
display->SetPreviewImage(&preview_image_);
auto image = std::make_unique<LvglSourceImage>(&preview_image_);
display->SetPreviewImage(std::move(image));
}
return true;
}

View File

@@ -1,5 +1,5 @@
# 由于原来的麦克风型号停产2025年7月之后的太极派JC3636W518更换了麦克风并且更换了屏幕玻璃所以在产品标签上批次号大于2528的用户请选择I2S Type PDM,
# 新增双声道配置
# 编译配置命令
**配置编译目标为 ESP32S3**
@@ -19,7 +19,12 @@ idf.py menuconfig
```
Xiaozhi Assistant -> Board Type -> 太极小派esp32s3
Xiaozhi Assistant -> taiji-pi-S3 I2S Type -> I2S Type PDM
Xiaozhi Assistant -> TAIJIPAI_S3_CONFIG -> taiji-pi-S3 I2S Type -> I2S Type PDM
```
**如果需要选择双声道:**
```
Xiaozhi Assistant -> TAIJIPAI_S3_CONFIG -> Enabel use 2 slot
```
**修改PSRAM配置**

View File

@@ -618,9 +618,17 @@ public:
AUDIO_I2S_GPIO_BCLK,
AUDIO_I2S_GPIO_WS,
AUDIO_I2S_GPIO_DOUT,
#ifdef CONFIG_I2S_USE_2SLOT
I2S_STD_SLOT_BOTH,
#endif
AUDIO_MIC_SCK_PIN,
AUDIO_MIC_WS_PIN,
#ifdef CONFIG_I2S_USE_2SLOT
AUDIO_MIC_SD_PIN,
I2S_STD_SLOT_LEFT
#else
AUDIO_MIC_SD_PIN
#endif
);
#else
static NoAudioCodecSimplexPdm audio_codec(
@@ -629,6 +637,9 @@ public:
AUDIO_I2S_GPIO_BCLK,
AUDIO_I2S_GPIO_WS,
AUDIO_I2S_GPIO_DOUT,
#ifdef CONFIG_I2S_USE_2SLOT
I2S_STD_SLOT_BOTH,
#endif
AUDIO_MIC_WS_PIN,
AUDIO_MIC_SD_PIN
);

View File

@@ -0,0 +1,88 @@
# 小智云聊S3
## 简介
小智云聊S3是小智AI的魔改项目是首个2.8寸护眼大屏+大字体+2000mah大电池的量产成品做了大量创新和优化。
## 合并版
合并版代码在小智AI主项目中维护跟随主项目的一起版本更新便于用户自行扩展和第三方固件扩展。支持语音唤醒、语音打断、OTA、4G自由切换等功能。
>### 按键操作
>- **开机**: 关机状态长按1秒后释放按键自动开机
>- **关机**: 开机状态长按1秒后释放按键标题栏会显示'请稍候'再等2秒自动关机
>- **唤醒/打断**: 正常通话环境下,单击按键
>- **切换4G/Wifi**: 启动过程或者配网界面1秒钟内双击按键需安装4G模块
>- **重新配网**: 开机状态1秒钟内三击按键会自动重启并进入配网界面
## 魔改版
魔改版由于底层改动太大,代码单独维护,定期合并主项目代码。
>### 为什么是魔改
>- 首个实现微信二维码配网。
>- 首个支持单手机配网。
>- 首个支持扫二维码访问控制台。
>- 首发支持繁体、日文、英文版界面
>- 首个全语音操控模式
>- 独家提供一键刷机脚本等多种刷机方式
## 版本区别
>| 特性 | 合并版 | 魔改版 |
>| --- | --- | --- |
>| 语音打断 | ✓ | ✓ |
>| 4G功能 | ✓ | ✓ |
>| 自动更新固件 | ✓ | X |
>| 第三方固件支持 | ✓ | X |
>| 天气待机界面 | X | ✓ |
>| 闹钟提醒 | X | ✓ |
>| 网络音乐播放 | X | ✓ |
>| 微信扫码配网 | X | ✓ |
>| 单手机配网 | X | ✓ |
>| 扫码访问控制台 | X | ✓ |
>| 繁日英文界面 | X | ✓ |
>| 多语言支持 | X | ✓ |
>| 外接蓝牙音箱 | X | ✓ |
# 编译配置命令
**克隆工程**
```bash
git clone https://github.com/78/xiaozhi-esp32.git
```
**进入工程**
```bash
cd xiaozhi-esp32
```
**配置编译目标为 ESP32S3**
```bash
idf.py set-target esp32s3
```
**打开 menuconfig**
```bash
idf.py menuconfig
```
**选择板子**
```bash
- `Xiaozhi Assistant``Board Type` → 选择 `小智云聊-S3` → 选择 `Enable Device-Side AEC`
```
**编译**
```ba
idf.py build
```
**下载并打开串口终端**
```bash
idf.py build flash monitor
```

View File

@@ -0,0 +1,59 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_REFERENCE true
#define AUDIO_INPUT_SAMPLE_RATE 24000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_DEFAULT_OUTPUT_VOLUME 70
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_14
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_13
#define AUDIO_I2S_GPIO_WS GPIO_NUM_11
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_12
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10
#define AUDIO_CODEC_PA_PIN GPIO_NUM_17
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_21
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_18
#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR
#define BOOT_BUTTON_PIN GPIO_NUM_2
#define BOOT_5V_PIN GPIO_NUM_3 //5V升压输出
#define BOOT_4G_PIN GPIO_NUM_5 //4G模块使能
#define MON_BATT_PIN GPIO_NUM_43 //检测PMU电池指示
#define MON_BATT_CNT 70 //检测PMU电池秒数
#define MON_USB_PIN GPIO_NUM_47 //检测USB插入
#define ML307_RX_PIN GPIO_NUM_16
#define ML307_TX_PIN GPIO_NUM_15
#define DISPLAY_SPI_LCD_HOST SPI2_HOST
#define DISPLAY_SPI_CLOCK_HZ (40 * 1000 * 1000)
#define DISPLAY_SPI_PIN_SCLK 42
#define DISPLAY_SPI_PIN_MOSI 40
#define DISPLAY_SPI_PIN_MISO -1
#define DISPLAY_SPI_PIN_LCD_DC 41
#define DISPLAY_SPI_PIN_LCD_RST 45
#define DISPLAY_SPI_PIN_LCD_CS -1
#define DISPLAY_PIN_TOUCH_CS -1
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_46
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 240
#define DISPLAY_SWAP_XY true
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y true
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER_COLOR LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define KEY_EXPIRE_MS 800
#endif // _BOARD_CONFIG_H_

View File

@@ -0,0 +1,11 @@
{
"target": "esp32s3",
"builds": [
{
"name": "yunliao-s3",
"sdkconfig_append": [
"CONFIG_USE_DEVICE_AEC=y"
]
}
]
}

View File

@@ -0,0 +1,203 @@
#include "power_manager.h"
#include "esp_sleep.h"
#include "driver/rtc_io.h"
#include "esp_log.h"
#include "config.h"
#include <esp_sleep.h>
#include "esp_log.h"
#include "settings.h"
#define TAG "PowerManager"
static QueueHandle_t gpio_evt_queue = NULL;
uint16_t battCnt;//闪灯次数
int battLife = -1; //电量
// 中断服务程序
static void IRAM_ATTR batt_mon_isr_handler(void* arg) {
uint32_t gpio_num = (uint32_t) arg;
xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);
}
// 添加任务处理函数
static void batt_mon_task(void* arg) {
uint32_t io_num;
while(1) {
if(xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) {
battCnt++;
}
}
}
static void calBattLife() {
// 计算电量
battLife = battCnt;
if (battLife > 100){
battLife = 100;
}
// ESP_LOGI(TAG, "Battery life:%d", (int)battLife);
// 重置计数器
battCnt = 0;
}
PowerManager::PowerManager(){
}
void PowerManager::Initialize(){
// 初始化5V控制引脚
gpio_config_t io_conf_5v = {
.pin_bit_mask = 1<<BOOT_5V_PIN,
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&io_conf_5v));
// 初始化4G控制引脚
gpio_config_t io_conf_4g = {
.pin_bit_mask = 1<<BOOT_4G_PIN,
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_ENABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&io_conf_4g));
// 电池电量监测引脚配置
gpio_config_t io_conf_batt_mon = {
.pin_bit_mask = 1ull<<MON_BATT_PIN,
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_POSEDGE,
};
ESP_ERROR_CHECK(gpio_config(&io_conf_batt_mon));
// 创建电量GPIO事件队列
gpio_evt_queue = xQueueCreate(2, sizeof(uint32_t));
// 安装电量GPIO ISR服务
ESP_ERROR_CHECK(gpio_install_isr_service(0));
// 添加中断处理
ESP_ERROR_CHECK(gpio_isr_handler_add(MON_BATT_PIN, batt_mon_isr_handler, (void*)MON_BATT_PIN));
// 创建监控任务
xTaskCreate(&batt_mon_task, "batt_mon_task", 1024, NULL, 10, NULL);
// 初始化监测引脚
gpio_config_t mon_conf = {};
mon_conf.pin_bit_mask = 1ULL << MON_USB_PIN;
mon_conf.mode = GPIO_MODE_INPUT;
mon_conf.pull_up_en = GPIO_PULLUP_DISABLE;
mon_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_config(&mon_conf);
// 创建电池电量检查定时器
esp_timer_create_args_t timer_args = {
.callback = [](void* arg) {
PowerManager* self = static_cast<PowerManager*>(arg);
self->CheckBatteryStatus();
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "battery_check_timer",
.skip_unhandled_events = true,
};
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
}
void PowerManager::CheckBatteryStatus(){
call_count_++;
if(call_count_ >= MON_BATT_CNT) {
calBattLife();
call_count_ = 0;
}
bool new_charging_status = IsCharging();
if (new_charging_status != is_charging_) {
is_charging_ = new_charging_status;
if (charging_callback_) {
charging_callback_(is_charging_);
}
}
bool new_discharging_status = IsDischarging();
if (new_discharging_status != is_discharging_) {
is_discharging_ = new_discharging_status;
if (discharging_callback_) {
discharging_callback_(is_discharging_);
}
}
}
bool PowerManager::IsCharging() {
return gpio_get_level(MON_USB_PIN) == 1 && !IsChargingDone();
}
bool PowerManager::IsDischarging() {
return gpio_get_level(MON_USB_PIN) == 0;
}
bool PowerManager::IsChargingDone() {
return battLife >= 95;
}
int PowerManager::GetBatteryLevel() {
return battLife;
}
void PowerManager::OnChargingStatusChanged(std::function<void(bool)> callback) {
charging_callback_ = callback;
}
void PowerManager::OnChargingStatusDisChanged(std::function<void(bool)> callback) {
discharging_callback_ = callback;
}
void PowerManager::CheckStartup() {
Settings settings1("board", true);
if(settings1.GetInt("sleep_flag", 0) > 0){
vTaskDelay(pdMS_TO_TICKS(1000));
if( gpio_get_level(BOOT_BUTTON_PIN) == 1) {
Sleep(); //进入休眠模式
}else{
settings1.SetInt("sleep_flag", 0);
}
}
}
void PowerManager::Start5V() {
gpio_set_level(BOOT_5V_PIN, 1);
}
void PowerManager::Shutdown5V() {
gpio_set_level(BOOT_5V_PIN, 0);
}
void PowerManager::Start4G() {
gpio_set_level(BOOT_4G_PIN, 1);
}
void PowerManager::Shutdown4G() {
gpio_set_level(BOOT_4G_PIN, 0);
gpio_set_level(ML307_RX_PIN,1);
gpio_set_level(ML307_TX_PIN,1);
}
void PowerManager::Sleep() {
ESP_LOGI(TAG, "Entering deep sleep");
Settings settings("board", true);
settings.SetInt("sleep_flag", 1);
Shutdown4G();
Shutdown5V();
if(gpio_evt_queue) {
vQueueDelete(gpio_evt_queue);
gpio_evt_queue = NULL;
}
ESP_ERROR_CHECK(gpio_isr_handler_remove(BOOT_BUTTON_PIN));
ESP_ERROR_CHECK(esp_sleep_enable_ext0_wakeup(BOOT_BUTTON_PIN, 0));
ESP_ERROR_CHECK(rtc_gpio_pulldown_dis(BOOT_BUTTON_PIN));
ESP_ERROR_CHECK(rtc_gpio_pullup_en(BOOT_BUTTON_PIN));
esp_deep_sleep_start();
}

View File

@@ -0,0 +1,37 @@
#ifndef __POWERMANAGER_H__
#define __POWERMANAGER_H__
#include <functional>
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/timers.h"
class PowerManager{
public:
PowerManager();
void Initialize();
bool IsCharging();
bool IsDischarging();
bool IsChargingDone();
int GetBatteryLevel();
void CheckStartup();
void Start5V();
void Shutdown5V();
void Start4G();
void Shutdown4G();
void Sleep();
void CheckBatteryStatus();
void OnChargingStatusChanged(std::function<void(bool)> callback);
void OnChargingStatusDisChanged(std::function<void(bool)> callback);
private:
esp_timer_handle_t timer_handle_;
std::function<void(bool)> charging_callback_;
std::function<void(bool)> discharging_callback_;
int is_charging_ = -1;
int is_discharging_ = -1;
int call_count_ = 0;
};
#endif

View File

@@ -0,0 +1,207 @@
#include "lvgl_theme.h"
#include "dual_network_board.h"
#include "codecs/es8388_audio_codec.h"
#include "display/lcd_display.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "power_save_timer.h"
#include "power_manager.h"
#include "assets/lang_config.h"
#include <esp_log.h>
#include <esp_lcd_panel_vendor.h>
#include <wifi_station.h>
#define TAG "YunliaoS3"
class YunliaoS3 : public DualNetworkBoard {
private:
i2c_master_bus_handle_t codec_i2c_bus_;
Button boot_button_;
SpiLcdDisplay* display_;
PowerSaveTimer* power_save_timer_;
PowerManager* power_manager_;
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(-1, 60, 600);
power_save_timer_->OnEnterSleepMode([this]() {
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(10);
});
power_save_timer_->OnExitSleepMode([this]() {
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {
ESP_LOGI(TAG, "Shutting down");
power_manager_->Sleep();
});
power_save_timer_->SetEnabled(true);
}
void InitializeI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = I2C_NUM_0,
.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN,
.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN,
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.intr_priority = 0,
.trans_queue_depth = 0,
.flags = {
.enable_internal_pullup = 1,
},
};
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_));
}
void InitializeSpi() {
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = DISPLAY_SPI_PIN_MOSI;
buscfg.miso_io_num = DISPLAY_SPI_PIN_MISO;
buscfg.sclk_io_num = DISPLAY_SPI_PIN_SCLK;
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(DISPLAY_SPI_LCD_HOST, &buscfg, SPI_DMA_CH_AUTO));
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
power_save_timer_->WakeUp();
auto& app = Application::GetInstance();
app.ToggleChatState();
});
boot_button_.OnDoubleClick([this]() {
ESP_LOGI(TAG, "Button OnDoubleClick");
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
SwitchNetworkType();
}
});
boot_button_.OnMultipleClick([this]() {
ESP_LOGI(TAG, "Button OnThreeClick");
if (GetNetworkType() == NetworkType::WIFI) {
auto& wifi_board = static_cast<WifiBoard&>(GetCurrentBoard());
wifi_board.ResetWifiConfiguration();
}
},3);
boot_button_.OnLongPress([this]() {
ESP_LOGI(TAG, "Button LongPress to Sleep");
display_->SetStatus(Lang::Strings::PLEASE_WAIT);
vTaskDelay(pdMS_TO_TICKS(2000));
power_manager_->Sleep();
});
}
void InitializeSt7789Display() {
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
// 液晶屏控制IO初始化
ESP_LOGD(TAG, "Install panel IO");
esp_lcd_panel_io_spi_config_t io_config = {};
io_config.cs_gpio_num = DISPLAY_SPI_PIN_LCD_CS;
io_config.dc_gpio_num = DISPLAY_SPI_PIN_LCD_DC;
io_config.spi_mode = 3;
io_config.pclk_hz = DISPLAY_SPI_CLOCK_HZ;
io_config.trans_queue_depth = 10;
io_config.lcd_cmd_bits = 8;
io_config.lcd_param_bits = 8;
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(DISPLAY_SPI_LCD_HOST, &io_config, &panel_io));
// 初始化液晶屏驱动芯片ST7789
ESP_LOGD(TAG, "Install LCD driver");
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = DISPLAY_SPI_PIN_LCD_RST;
panel_config.rgb_ele_order = DISPLAY_RGB_ORDER_COLOR;
panel_config.bits_per_pixel = 16;
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR);
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);
auto& theme_manager = LvglThemeManager::GetInstance();
auto theme = theme_manager.GetTheme("dark");
if (theme != nullptr) {
display_->SetTheme(theme);
}
}
public:
YunliaoS3() :
DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN, GPIO_NUM_NC, 0),
boot_button_(BOOT_BUTTON_PIN),
power_manager_(new PowerManager()){
power_manager_->Start5V();
power_manager_->Initialize();
InitializeI2c();
power_manager_->CheckStartup();
InitializePowerSaveTimer();
InitializeSpi();
InitializeButtons();
InitializeSt7789Display();
power_manager_->OnChargingStatusDisChanged([this](bool is_discharging) {
if(power_save_timer_){
if (is_discharging) {
power_save_timer_->SetEnabled(true);
} else {
power_save_timer_->SetEnabled(false);
}
}
});
if(GetNetworkType() == NetworkType::WIFI){
power_manager_->Shutdown4G();
}
GetBacklight()->RestoreBrightness();
}
virtual AudioCodec* GetAudioCodec() override {
static Es8388AudioCodec audio_codec(
codec_i2c_bus_,
I2C_NUM_0,
AUDIO_INPUT_SAMPLE_RATE,
AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_MCLK,
AUDIO_I2S_GPIO_BCLK,
AUDIO_I2S_GPIO_WS,
AUDIO_I2S_GPIO_DOUT,
AUDIO_I2S_GPIO_DIN,
AUDIO_CODEC_PA_PIN,
AUDIO_CODEC_ES8388_ADDR,
AUDIO_INPUT_REFERENCE
);
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
virtual Backlight* GetBacklight() override {
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
return &backlight;
}
virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
level = power_manager_->GetBatteryLevel();
charging = power_manager_->IsCharging();
discharging = power_manager_->IsDischarging();
return true;
}
virtual void SetPowerSaveMode(bool enabled) override {
if (!enabled) {
power_save_timer_->WakeUp();
}
DualNetworkBoard::SetPowerSaveMode(enabled);
}
};
DECLARE_BOARD(YunliaoS3);