Upgrade Playlist Features

This commit is contained in:
2025-12-09 17:20:01 +08:00
parent 577990de69
commit 8bd2780688
683 changed files with 91812 additions and 81260 deletions

View File

@@ -1,51 +1,51 @@
# ESP-Hi
## 简介
<div align="center">
<a href="https://oshwhub.com/esp-college/esp-hi"><b> 立创开源平台 </b></a>
|
<a href="https://www.bilibili.com/video/BV1BHJtz6E2S"><b> Bilibili </b></a>
</div>
ESP-Hi 是 ESP Friends 开源的一款基于 ESP32C3 的超**低成本** AI 对话机器人。ESP-Hi 集成了一个0.96寸的彩屏,用于显示表情,**机器狗已实现数十种动作**。通过对 ESP32-C3 外设的充分挖掘,仅需最少的板级硬件即可实现拾音和发声,同步优化了软件,降低内存与 Flash 占用,在资源受限的情况下同时实现了**唤醒词检测**与多种外设驱动。硬件详情等可查看[立创开源项目](https://oshwhub.com/esp-college/esp-hi)。
## WebUI
ESP-Hi x 小智内置了一个控制身体运动的 WebUI请将手机与 ESP-Hi 连接到同一个 Wi-Fi 下,手机访问 `http://esp-hi.local/` 以使用。
如需禁用,请取消 `ESP_HI_WEB_CONTROL_ENABLED`,即取消勾选 `Component config``Servo Dog Configuration``Web Control``Enable ESP-HI Web Control`
## 配置、编译命令
由于 ESP-Hi 需要配置较多的 sdkconfig 选项,推荐使用编译脚本编译。
**编译**
```bash
python ./scripts/release.py esp-hi
```
如需手动编译,请参考 `esp-hi/config.json` 修改 menuconfig 对应选项。
**烧录**
```bash
idf.py flash
```
> [!TIP]
>
> **舵机控制会占用 ESP-Hi 的 USB Type-C 接口**,导致无法连接电脑(无法烧录/查看运行日志)。如遇此情况,请按以下提示操作:
>
> **烧录**
>
> 1. 断开 ESP-Hi 的电源,只留头部,不要连接身体。
> 2. 按住 ESP-Hi 的按钮并连接电脑。
>
> 此时ESP-Hi (ESP32C3) 应当处于烧录模式,可以使用电脑烧录程序。烧录完成后,可能需要重新插拔电源。
>
> **查看 log**
>
> 请设置 `CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y`,即 `Component config` → `ESP System Settings` → `Channel for console output` 选择 `USB Serial/JTAG Controller`。这同时会禁用舵机控制功能。
# ESP-Hi
## 简介
<div align="center">
<a href="https://oshwhub.com/esp-college/esp-hi"><b> 立创开源平台 </b></a>
|
<a href="https://www.bilibili.com/video/BV1BHJtz6E2S"><b> Bilibili </b></a>
</div>
ESP-Hi 是 ESP Friends 开源的一款基于 ESP32C3 的超**低成本** AI 对话机器人。ESP-Hi 集成了一个0.96寸的彩屏,用于显示表情,**机器狗已实现数十种动作**。通过对 ESP32-C3 外设的充分挖掘,仅需最少的板级硬件即可实现拾音和发声,同步优化了软件,降低内存与 Flash 占用,在资源受限的情况下同时实现了**唤醒词检测**与多种外设驱动。硬件详情等可查看[立创开源项目](https://oshwhub.com/esp-college/esp-hi)。
## WebUI
ESP-Hi x 小智内置了一个控制身体运动的 WebUI请将手机与 ESP-Hi 连接到同一个 Wi-Fi 下,手机访问 `http://esp-hi.local/` 以使用。
如需禁用,请取消 `ESP_HI_WEB_CONTROL_ENABLED`,即取消勾选 `Component config``Servo Dog Configuration``Web Control``Enable ESP-HI Web Control`
## 配置、编译命令
由于 ESP-Hi 需要配置较多的 sdkconfig 选项,推荐使用编译脚本编译。
**编译**
```bash
python ./scripts/release.py esp-hi
```
如需手动编译,请参考 `esp-hi/config.json` 修改 menuconfig 对应选项。
**烧录**
```bash
idf.py flash
```
> [!TIP]
>
> **舵机控制会占用 ESP-Hi 的 USB Type-C 接口**,导致无法连接电脑(无法烧录/查看运行日志)。如遇此情况,请按以下提示操作:
>
> **烧录**
>
> 1. 断开 ESP-Hi 的电源,只留头部,不要连接身体。
> 2. 按住 ESP-Hi 的按钮并连接电脑。
>
> 此时ESP-Hi (ESP32C3) 应当处于烧录模式,可以使用电脑烧录程序。烧录完成后,可能需要重新插拔电源。
>
> **查看 log**
>
> 请设置 `CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y`,即 `Component config` → `ESP System Settings` → `Channel for console output` 选择 `USB Serial/JTAG Controller`。这同时会禁用舵机控制功能。

View File

@@ -1,203 +1,249 @@
#include "adc_pdm_audio_codec.h"
#include <esp_log.h>
#include <driver/i2c.h>
#include <driver/i2c_master.h>
#include <driver/i2s_tdm.h>
#include "adc_mic.h"
#include "driver/i2s_pdm.h"
#include "soc/gpio_sig_map.h"
#include "soc/io_mux_reg.h"
#include "hal/rtc_io_hal.h"
#include "hal/gpio_ll.h"
#include "settings.h"
static const char TAG[] = "AdcPdmAudioCodec";
#define BSP_I2S_GPIO_CFG(_dout) \
{ \
.clk = GPIO_NUM_NC, \
.dout = _dout, \
.invert_flags = { \
.clk_inv = false, \
}, \
}
/**
* @brief Mono Duplex I2S configuration structure
*
* This configuration is used by default in bsp_audio_init()
*/
#define BSP_I2S_DUPLEX_MONO_CFG(_sample_rate, _dout) \
{ \
.clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(_sample_rate), \
.slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), \
.gpio_cfg = BSP_I2S_GPIO_CFG(_dout), \
}
AdcPdmAudioCodec::AdcPdmAudioCodec(int input_sample_rate, int output_sample_rate,
uint32_t adc_mic_channel, gpio_num_t pdm_speak_p,gpio_num_t pdm_speak_n, gpio_num_t pa_ctl) {
input_reference_ = false;
input_sample_rate_ = input_sample_rate;
output_sample_rate_ = output_sample_rate;
uint8_t adc_channel[1] = {0};
adc_channel[0] = adc_mic_channel;
audio_codec_adc_cfg_t cfg = {
.handle = NULL,
.max_store_buf_size = 1024 * 2,
.conv_frame_size = 1024,
.unit_id = ADC_UNIT_1,
.adc_channel_list = adc_channel,
.adc_channel_num = sizeof(adc_channel) / sizeof(adc_channel[0]),
.sample_rate_hz = (uint32_t)input_sample_rate,
};
const audio_codec_data_if_t *adc_if = audio_codec_new_adc_data(&cfg);
esp_codec_dev_cfg_t codec_dev_cfg = {
.dev_type = ESP_CODEC_DEV_TYPE_IN,
.data_if = adc_if,
};
input_dev_ = esp_codec_dev_new(&codec_dev_cfg);
if (!input_dev_) {
ESP_LOGE(TAG, "Failed to create codec device");
return;
}
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
chan_cfg.auto_clear = true; // Auto clear the legacy data in the DMA buffer
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, NULL));
i2s_pdm_tx_config_t pdm_cfg_default = BSP_I2S_DUPLEX_MONO_CFG((uint32_t)output_sample_rate, pdm_speak_p);
pdm_cfg_default.clk_cfg.up_sample_fs = output_sample_rate / 100;
pdm_cfg_default.slot_cfg.sd_scale = I2S_PDM_SIG_SCALING_MUL_4;
pdm_cfg_default.slot_cfg.hp_scale = I2S_PDM_SIG_SCALING_MUL_4;
pdm_cfg_default.slot_cfg.lp_scale = I2S_PDM_SIG_SCALING_MUL_4;
pdm_cfg_default.slot_cfg.sinc_scale = I2S_PDM_SIG_SCALING_MUL_4;
const i2s_pdm_tx_config_t *p_i2s_cfg = &pdm_cfg_default;
ESP_ERROR_CHECK(i2s_channel_init_pdm_tx_mode(tx_handle_, p_i2s_cfg));
audio_codec_i2s_cfg_t i2s_cfg = {
.port = I2S_NUM_0,
.rx_handle = NULL,
.tx_handle = tx_handle_,
};
const audio_codec_data_if_t *i2s_data_if = audio_codec_new_i2s_data(&i2s_cfg);
codec_dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_OUT;
codec_dev_cfg.codec_if = NULL;
codec_dev_cfg.data_if = i2s_data_if;
output_dev_ = esp_codec_dev_new(&codec_dev_cfg);
output_volume_ = 100;
if(pa_ctl != GPIO_NUM_NC) {
pa_ctrl_pin_ = pa_ctl;
gpio_config_t io_conf = {};
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = (1ULL << pa_ctrl_pin_);
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
gpio_config(&io_conf);
}
gpio_set_drive_capability(pdm_speak_p, GPIO_DRIVE_CAP_0);
if(pdm_speak_n != GPIO_NUM_NC){
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[pdm_speak_n], PIN_FUNC_GPIO);
gpio_set_direction(pdm_speak_n, GPIO_MODE_OUTPUT);
esp_rom_gpio_connect_out_signal(pdm_speak_n, I2SO_SD_OUT_IDX, 1, 0); //反转输出 SD OUT 信号
gpio_set_drive_capability(pdm_speak_n, GPIO_DRIVE_CAP_0);
}
ESP_LOGI(TAG, "AdcPdmAudioCodec initialized");
}
AdcPdmAudioCodec::~AdcPdmAudioCodec() {
ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
esp_codec_dev_delete(output_dev_);
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
esp_codec_dev_delete(input_dev_);
}
void AdcPdmAudioCodec::SetOutputVolume(int volume) {
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
AudioCodec::SetOutputVolume(volume);
}
void AdcPdmAudioCodec::EnableInput(bool enable) {
if (enable == input_enabled_) {
return;
}
if (enable) {
esp_codec_dev_sample_info_t fs = {
.bits_per_sample = 16,
.channel = 1,
.channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0),
.sample_rate = (uint32_t)input_sample_rate_,
.mclk_multiple = 0,
};
ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
} else {
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
}
AudioCodec::EnableInput(enable);
}
void AdcPdmAudioCodec::EnableOutput(bool enable) {
if (enable == output_enabled_) {
return;
}
if (enable) {
// Play 16bit 1 channel
esp_codec_dev_sample_info_t fs = {
.bits_per_sample = 16,
.channel = 1,
.channel_mask = 0,
.sample_rate = (uint32_t)output_sample_rate_,
.mclk_multiple = 0,
};
ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs));
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_));
if(pa_ctrl_pin_ != GPIO_NUM_NC){
gpio_set_level(pa_ctrl_pin_, 1);
}
} else {
if(pa_ctrl_pin_ != GPIO_NUM_NC){
gpio_set_level(pa_ctrl_pin_, 0);
}
ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
}
AudioCodec::EnableOutput(enable);
}
int AdcPdmAudioCodec::Read(int16_t* dest, int samples) {
if (input_enabled_) {
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t)));
}
return samples;
}
int AdcPdmAudioCodec::Write(const int16_t* data, int samples) {
if (output_enabled_) {
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t)));
}
return samples;
}
void AdcPdmAudioCodec::Start() {
Settings settings("audio", false);
output_volume_ = settings.GetInt("output_volume", output_volume_);
if (output_volume_ <= 0) {
ESP_LOGW(TAG, "Output volume value (%d) is too small, setting to default (10)", output_volume_);
output_volume_ = 10;
}
ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_));
EnableInput(true);
EnableOutput(true);
ESP_LOGI(TAG, "Audio codec started");
}
#include "adc_pdm_audio_codec.h"
#include <esp_log.h>
#include <esp_timer.h>
#include <driver/i2c.h>
#include <driver/i2c_master.h>
#include <driver/i2s_tdm.h>
#include "adc_mic.h"
#include "driver/i2s_pdm.h"
#include "soc/gpio_sig_map.h"
#include "soc/io_mux_reg.h"
#include "hal/rtc_io_hal.h"
#include "hal/gpio_ll.h"
#include "settings.h"
#include "config.h"
static const char TAG[] = "AdcPdmAudioCodec";
#define BSP_I2S_GPIO_CFG(_dout) \
{ \
.clk = GPIO_NUM_NC, \
.dout = _dout, \
.invert_flags = { \
.clk_inv = false, \
}, \
}
/**
* @brief Mono Duplex I2S configuration structure
*
* This configuration is used by default in bsp_audio_init()
*/
#define BSP_I2S_DUPLEX_MONO_CFG(_sample_rate, _dout) \
{ \
.clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(_sample_rate), \
.slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), \
.gpio_cfg = BSP_I2S_GPIO_CFG(_dout), \
}
AdcPdmAudioCodec::AdcPdmAudioCodec(int input_sample_rate, int output_sample_rate,
uint32_t adc_mic_channel, gpio_num_t pdm_speak_p,gpio_num_t pdm_speak_n, gpio_num_t pa_ctl) {
input_reference_ = false;
input_sample_rate_ = input_sample_rate;
output_sample_rate_ = output_sample_rate;
uint8_t adc_channel[1] = {0};
adc_channel[0] = adc_mic_channel;
audio_codec_adc_cfg_t cfg = {
.handle = NULL,
.max_store_buf_size = 1024 * 2,
.conv_frame_size = 1024,
.unit_id = ADC_UNIT_1,
.adc_channel_list = adc_channel,
.adc_channel_num = sizeof(adc_channel) / sizeof(adc_channel[0]),
.sample_rate_hz = (uint32_t)input_sample_rate,
};
const audio_codec_data_if_t *adc_if = audio_codec_new_adc_data(&cfg);
esp_codec_dev_cfg_t codec_dev_cfg = {
.dev_type = ESP_CODEC_DEV_TYPE_IN,
.data_if = adc_if,
};
input_dev_ = esp_codec_dev_new(&codec_dev_cfg);
if (!input_dev_) {
ESP_LOGE(TAG, "Failed to create codec device");
return;
}
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
chan_cfg.auto_clear = true; // Auto clear the legacy data in the DMA buffer
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, NULL));
i2s_pdm_tx_config_t pdm_cfg_default = BSP_I2S_DUPLEX_MONO_CFG((uint32_t)output_sample_rate, pdm_speak_p);
pdm_cfg_default.clk_cfg.up_sample_fs = AUDIO_PDM_UPSAMPLE_FS;
pdm_cfg_default.slot_cfg.sd_scale = I2S_PDM_SIG_SCALING_MUL_4;
pdm_cfg_default.slot_cfg.hp_scale = I2S_PDM_SIG_SCALING_MUL_4;
pdm_cfg_default.slot_cfg.lp_scale = I2S_PDM_SIG_SCALING_MUL_4;
pdm_cfg_default.slot_cfg.sinc_scale = I2S_PDM_SIG_SCALING_MUL_4;
const i2s_pdm_tx_config_t *p_i2s_cfg = &pdm_cfg_default;
ESP_ERROR_CHECK(i2s_channel_init_pdm_tx_mode(tx_handle_, p_i2s_cfg));
audio_codec_i2s_cfg_t i2s_cfg = {
.port = I2S_NUM_0,
.rx_handle = NULL,
.tx_handle = tx_handle_,
};
const audio_codec_data_if_t *i2s_data_if = audio_codec_new_i2s_data(&i2s_cfg);
codec_dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_OUT;
codec_dev_cfg.codec_if = NULL;
codec_dev_cfg.data_if = i2s_data_if;
output_dev_ = esp_codec_dev_new(&codec_dev_cfg);
output_volume_ = 100;
if(pa_ctl != GPIO_NUM_NC) {
pa_ctrl_pin_ = pa_ctl;
gpio_config_t io_conf = {};
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = (1ULL << pa_ctrl_pin_);
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
gpio_config(&io_conf);
}
gpio_set_drive_capability(pdm_speak_p, GPIO_DRIVE_CAP_0);
if(pdm_speak_n != GPIO_NUM_NC){
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[pdm_speak_n], PIN_FUNC_GPIO);
gpio_set_direction(pdm_speak_n, GPIO_MODE_OUTPUT);
esp_rom_gpio_connect_out_signal(pdm_speak_n, I2SO_SD_OUT_IDX, 1, 0); //反转输出 SD OUT 信号
gpio_set_drive_capability(pdm_speak_n, GPIO_DRIVE_CAP_0);
}
// 初始化输出定时器
esp_timer_create_args_t output_timer_args = {
.callback = &AdcPdmAudioCodec::OutputTimerCallback,
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "output_timer"
};
ESP_ERROR_CHECK(esp_timer_create(&output_timer_args, &output_timer_));
ESP_LOGI(TAG, "AdcPdmAudioCodec initialized");
}
AdcPdmAudioCodec::~AdcPdmAudioCodec() {
// 删除定时器
if (output_timer_) {
esp_timer_stop(output_timer_);
esp_timer_delete(output_timer_);
output_timer_ = nullptr;
}
ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
esp_codec_dev_delete(output_dev_);
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
esp_codec_dev_delete(input_dev_);
}
void AdcPdmAudioCodec::SetOutputVolume(int volume) {
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
AudioCodec::SetOutputVolume(volume);
}
void AdcPdmAudioCodec::EnableInput(bool enable) {
if (enable == input_enabled_) {
return;
}
if (enable) {
esp_codec_dev_sample_info_t fs = {
.bits_per_sample = 16,
.channel = 1,
.channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0),
.sample_rate = (uint32_t)input_sample_rate_,
.mclk_multiple = 0,
};
ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
} else {
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
}
AudioCodec::EnableInput(enable);
}
void AdcPdmAudioCodec::EnableOutput(bool enable) {
if (enable == output_enabled_) {
return;
}
if (enable) {
// Play 16bit 1 channel
esp_codec_dev_sample_info_t fs = {
.bits_per_sample = 16,
.channel = 1,
.channel_mask = 0,
.sample_rate = (uint32_t)output_sample_rate_,
.mclk_multiple = 0,
};
ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs));
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_));
// 强制按板卡配置重配PDM TX时钟覆盖第三方库在set_fmt中的默认up_sample_fs
// 若通道已启用,先禁用再重配,最后再启用
ESP_ERROR_CHECK_WITHOUT_ABORT(i2s_channel_disable(tx_handle_));
i2s_pdm_tx_clk_config_t clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG((uint32_t)output_sample_rate_);
clk_cfg.up_sample_fs = AUDIO_PDM_UPSAMPLE_FS;
ESP_ERROR_CHECK(i2s_channel_reconfig_pdm_tx_clock(tx_handle_, &clk_cfg));
ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_));
if(pa_ctrl_pin_ != GPIO_NUM_NC){
gpio_set_level(pa_ctrl_pin_, 1);
}
// 启用输出时启动定时器
if (output_timer_) {
esp_timer_start_once(output_timer_, TIMER_TIMEOUT_US);
}
} else {
// 禁用输出时停止定时器
if (output_timer_) {
esp_timer_stop(output_timer_);
}
if(pa_ctrl_pin_ != GPIO_NUM_NC){
gpio_set_level(pa_ctrl_pin_, 0);
}
ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
}
AudioCodec::EnableOutput(enable);
}
int AdcPdmAudioCodec::Read(int16_t* dest, int samples) {
if (input_enabled_) {
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t)));
}
return samples;
}
int AdcPdmAudioCodec::Write(const int16_t* data, int samples) {
if (output_enabled_) {
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t)));
// 重置输出定时器
if (output_timer_) {
esp_timer_stop(output_timer_);
esp_timer_start_once(output_timer_, TIMER_TIMEOUT_US);
}
}
return samples;
}
void AdcPdmAudioCodec::Start() {
Settings settings("audio", false);
output_volume_ = settings.GetInt("output_volume", output_volume_);
if (output_volume_ <= 0) {
ESP_LOGW(TAG, "Output volume value (%d) is too small, setting to default (10)", output_volume_);
output_volume_ = 10;
}
EnableInput(true);
EnableOutput(true);
ESP_LOGI(TAG, "Audio codec started");
}
// 定时器回调函数实现
void AdcPdmAudioCodec::OutputTimerCallback(void* arg) {
AdcPdmAudioCodec* codec = static_cast<AdcPdmAudioCodec*>(arg);
if (codec && codec->output_enabled_) {
codec->EnableOutput(false);
}
}

View File

@@ -1,29 +1,37 @@
#ifndef _BOX_AUDIO_CODEC_H
#define _BOX_AUDIO_CODEC_H
#include "audio_codec.h"
#include <esp_codec_dev.h>
#include <esp_codec_dev_defaults.h>
class AdcPdmAudioCodec : public AudioCodec {
private:
esp_codec_dev_handle_t output_dev_ = nullptr;
esp_codec_dev_handle_t input_dev_ = nullptr;
gpio_num_t pa_ctrl_pin_ = GPIO_NUM_NC;
virtual int Read(int16_t* dest, int samples) override;
virtual int Write(const int16_t* data, int samples) override;
public:
AdcPdmAudioCodec(int input_sample_rate, int output_sample_rate,
uint32_t adc_mic_channel, gpio_num_t pdm_speak_p, gpio_num_t pdm_speak_n, gpio_num_t pa_ctl);
virtual ~AdcPdmAudioCodec();
virtual void SetOutputVolume(int volume) override;
virtual void EnableInput(bool enable) override;
virtual void EnableOutput(bool enable) override;
void Start();
};
#endif // _BOX_AUDIO_CODEC_H
#ifndef _BOX_AUDIO_CODEC_H
#define _BOX_AUDIO_CODEC_H
#include "audio_codec.h"
#include <esp_codec_dev.h>
#include <esp_codec_dev_defaults.h>
#include <esp_timer.h>
class AdcPdmAudioCodec : public AudioCodec {
private:
esp_codec_dev_handle_t output_dev_ = nullptr;
esp_codec_dev_handle_t input_dev_ = nullptr;
gpio_num_t pa_ctrl_pin_ = GPIO_NUM_NC;
// 定时器相关成员变量
esp_timer_handle_t output_timer_ = nullptr;
static constexpr uint64_t TIMER_TIMEOUT_US = 120000; // 120ms = 120000us
// 定时器回调函数
static void OutputTimerCallback(void* arg);
virtual int Read(int16_t* dest, int samples) override;
virtual int Write(const int16_t* data, int samples) override;
public:
AdcPdmAudioCodec(int input_sample_rate, int output_sample_rate,
uint32_t adc_mic_channel, gpio_num_t pdm_speak_p, gpio_num_t pdm_speak_n, gpio_num_t pa_ctl);
virtual ~AdcPdmAudioCodec();
virtual void SetOutputVolume(int volume) override;
virtual void EnableInput(bool enable) override;
virtual void EnableOutput(bool enable) override;
void Start();
};
#endif // _BOX_AUDIO_CODEC_H

View File

@@ -1,44 +1,47 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_ADC_MIC_CHANNEL 2
#define AUDIO_PDM_SPEAK_P_GPIO GPIO_NUM_6
#define AUDIO_PDM_SPEAK_N_GPIO GPIO_NUM_7
#define AUDIO_PA_CTL_GPIO GPIO_NUM_3
#define BUILTIN_LED_GPIO GPIO_NUM_NC
#define BOOT_BUTTON_GPIO GPIO_NUM_9
#define MOVE_WAKE_BUTTON_GPIO GPIO_NUM_0
#define AUDIO_WAKE_BUTTON_GPIO GPIO_NUM_1
#define DISPLAY_MOSI_PIN GPIO_NUM_4
#define DISPLAY_CLK_PIN GPIO_NUM_5
#define DISPLAY_DC_PIN GPIO_NUM_10
#define DISPLAY_RST_PIN GPIO_NUM_NC
#define DISPLAY_CS_PIN GPIO_NUM_NC
#define FL_GPIO_NUM GPIO_NUM_21
#define FR_GPIO_NUM GPIO_NUM_19
#define BL_GPIO_NUM GPIO_NUM_20
#define BR_GPIO_NUM GPIO_NUM_18
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 160
#define DISPLAY_HEIGHT 80
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y true
#define DISPLAY_SWAP_XY true
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif // _BOARD_CONFIG_H_
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
// 配置PDM上采样fs参数取值范围<=480。部分设备在441时表现更稳定
#define AUDIO_PDM_UPSAMPLE_FS 441
#define AUDIO_ADC_MIC_CHANNEL 2
#define AUDIO_PDM_SPEAK_P_GPIO GPIO_NUM_6
#define AUDIO_PDM_SPEAK_N_GPIO GPIO_NUM_7
#define AUDIO_PA_CTL_GPIO GPIO_NUM_3
#define BUILTIN_LED_GPIO GPIO_NUM_NC
#define BOOT_BUTTON_GPIO GPIO_NUM_9
#define MOVE_WAKE_BUTTON_GPIO GPIO_NUM_0
#define AUDIO_WAKE_BUTTON_GPIO GPIO_NUM_1
#define DISPLAY_MOSI_PIN GPIO_NUM_4
#define DISPLAY_CLK_PIN GPIO_NUM_5
#define DISPLAY_DC_PIN GPIO_NUM_10
#define DISPLAY_RST_PIN GPIO_NUM_NC
#define DISPLAY_CS_PIN GPIO_NUM_NC
#define FL_GPIO_NUM GPIO_NUM_21
#define FR_GPIO_NUM GPIO_NUM_19
#define BL_GPIO_NUM GPIO_NUM_20
#define BR_GPIO_NUM GPIO_NUM_18
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 160
#define DISPLAY_HEIGHT 80
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y true
#define DISPLAY_SWAP_XY true
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif // _BOARD_CONFIG_H_

View File

@@ -1,35 +1,34 @@
{
"target": "esp32c3",
"builds": [
{
"name": "esp-hi",
"sdkconfig_append": [
"CONFIG_IDF_TARGET=\"esp32c3\"",
"CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y",
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/4m_esp-hi.csv\"",
"CONFIG_BOARD_TYPE_ESP_HI=y",
"CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=3",
"CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=4",
"CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=n",
"CONFIG_ESP_WIFI_RX_BA_WIN=4",
"CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=n",
"CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=0",
"CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=n",
"CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y",
"CONFIG_ESP_MAIN_TASK_STACK_SIZE=7168",
"CONFIG_FREERTOS_HZ=1000",
"CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=768",
"CONFIG_LWIP_MAX_SOCKETS=10",
"CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=16",
"CONFIG_LWIP_IPV6=n",
"CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=2048",
"CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y",
"CONFIG_NEWLIB_NANO_FORMAT=y",
"CONFIG_MMAP_FILE_NAME_LENGTH=25",
"CONFIG_ESP_CONSOLE_NONE=y",
"CONFIG_USE_ESP_WAKE_WORD=y",
"CONFIG_COMPILER_OPTIMIZATION_SIZE=y"
]
}
]
}
{
"target": "esp32c3",
"builds": [
{
"name": "esp-hi",
"sdkconfig_append": [
"CONFIG_IDF_TARGET=\"esp32c3\"",
"CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y",
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/4m.csv\"",
"CONFIG_BOARD_TYPE_ESP_HI=y",
"CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=3",
"CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=4",
"CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=n",
"CONFIG_ESP_WIFI_RX_BA_WIN=4",
"CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=n",
"CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=0",
"CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=n",
"CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y",
"CONFIG_ESP_MAIN_TASK_STACK_SIZE=7168",
"CONFIG_FREERTOS_HZ=1000",
"CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=768",
"CONFIG_LWIP_MAX_SOCKETS=10",
"CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=16",
"CONFIG_LWIP_IPV6=n",
"CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=2048",
"CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y",
"CONFIG_NEWLIB_NANO_FORMAT=y",
"CONFIG_ESP_CONSOLE_NONE=y",
"CONFIG_USE_ESP_WAKE_WORD=y",
"CONFIG_COMPILER_OPTIMIZATION_SIZE=y"
]
}
]
}

View File

@@ -1,172 +1,178 @@
#include <cstring>
#include "display/lcd_display.h"
#include <esp_log.h>
#include "mmap_generate_emoji.h"
#include "emoji_display.h"
#include "assets/lang_config.h"
#include <esp_lcd_panel_io.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/queue.h>
#include <freertos/event_groups.h>
static const char *TAG = "emoji";
namespace anim {
bool EmojiPlayer::OnFlushIoReady(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
auto* disp_drv = static_cast<anim_player_handle_t*>(user_ctx);
anim_player_flush_ready(disp_drv);
return true;
}
void EmojiPlayer::OnFlush(anim_player_handle_t handle, int x_start, int y_start, int x_end, int y_end, const void *color_data)
{
auto* panel = static_cast<esp_lcd_panel_handle_t>(anim_player_get_user_data(handle));
esp_lcd_panel_draw_bitmap(panel, x_start, y_start, x_end, y_end, color_data);
}
EmojiPlayer::EmojiPlayer(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io)
{
ESP_LOGI(TAG, "Create EmojiPlayer, panel: %p, panel_io: %p", panel, panel_io);
const mmap_assets_config_t assets_cfg = {
.partition_label = "assets_A",
.max_files = MMAP_EMOJI_FILES,
.checksum = MMAP_EMOJI_CHECKSUM,
.flags = {.mmap_enable = true, .full_check = true}
};
mmap_assets_new(&assets_cfg, &assets_handle_);
anim_player_config_t player_cfg = {
.flush_cb = OnFlush,
.update_cb = NULL,
.user_data = panel,
.flags = {.swap = true},
.task = ANIM_PLAYER_INIT_CONFIG()
};
player_handle_ = anim_player_init(&player_cfg);
const esp_lcd_panel_io_callbacks_t cbs = {
.on_color_trans_done = OnFlushIoReady,
};
esp_lcd_panel_io_register_event_callbacks(panel_io, &cbs, player_handle_);
StartPlayer(MMAP_EMOJI_CONNECTING_AAF, true, 15);
}
EmojiPlayer::~EmojiPlayer()
{
if (player_handle_) {
anim_player_update(player_handle_, PLAYER_ACTION_STOP);
anim_player_deinit(player_handle_);
player_handle_ = nullptr;
}
if (assets_handle_) {
mmap_assets_del(assets_handle_);
assets_handle_ = NULL;
}
}
void EmojiPlayer::StartPlayer(int aaf, bool repeat, int fps)
{
if (player_handle_) {
uint32_t start, end;
const void *src_data;
size_t src_len;
src_data = mmap_assets_get_mem(assets_handle_, aaf);
src_len = mmap_assets_get_size(assets_handle_, aaf);
anim_player_set_src_data(player_handle_, src_data, src_len);
anim_player_get_segment(player_handle_, &start, &end);
if(MMAP_EMOJI_WAKE_AAF == aaf){
start = 7;
}
anim_player_set_segment(player_handle_, start, end, fps, true);
anim_player_update(player_handle_, PLAYER_ACTION_START);
}
}
void EmojiPlayer::StopPlayer()
{
if (player_handle_) {
anim_player_update(player_handle_, PLAYER_ACTION_STOP);
}
}
EmojiWidget::EmojiWidget(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io)
{
InitializePlayer(panel, panel_io);
}
EmojiWidget::~EmojiWidget()
{
}
void EmojiWidget::SetEmotion(const char* emotion)
{
if (!player_) {
return;
}
using Param = std::tuple<int, bool, int>;
static const std::unordered_map<std::string, Param> emotion_map = {
{"happy", {MMAP_EMOJI_HAPPY_LOOP_AAF, true, 25}},
{"laughing", {MMAP_EMOJI_HAPPY_LOOP_AAF, true, 25}},
{"funny", {MMAP_EMOJI_HAPPY_LOOP_AAF, true, 25}},
{"loving", {MMAP_EMOJI_HAPPY_LOOP_AAF, true, 25}},
{"embarrassed", {MMAP_EMOJI_HAPPY_LOOP_AAF, true, 25}},
{"confident", {MMAP_EMOJI_HAPPY_LOOP_AAF, true, 25}},
{"delicious", {MMAP_EMOJI_HAPPY_LOOP_AAF, true, 25}},
{"sad", {MMAP_EMOJI_SAD_LOOP_AAF, true, 25}},
{"crying", {MMAP_EMOJI_SAD_LOOP_AAF, true, 25}},
{"sleepy", {MMAP_EMOJI_SAD_LOOP_AAF, true, 25}},
{"silly", {MMAP_EMOJI_SAD_LOOP_AAF, true, 25}},
{"angry", {MMAP_EMOJI_ANGER_LOOP_AAF, true, 25}},
{"surprised", {MMAP_EMOJI_PANIC_LOOP_AAF, true, 25}},
{"shocked", {MMAP_EMOJI_PANIC_LOOP_AAF, true, 25}},
{"thinking", {MMAP_EMOJI_HAPPY_LOOP_AAF, true, 25}},
{"winking", {MMAP_EMOJI_BLINK_QUICK_AAF, true, 5}},
{"relaxed", {MMAP_EMOJI_SCORN_LOOP_AAF, true, 25}},
{"confused", {MMAP_EMOJI_SCORN_LOOP_AAF, true, 25}},
};
auto it = emotion_map.find(emotion);
if (it != emotion_map.end()) {
const auto& [aaf, repeat, fps] = it->second;
player_->StartPlayer(aaf, repeat, fps);
} else if (strcmp(emotion, "neutral") == 0) {
}
}
void EmojiWidget::SetStatus(const char* status)
{
if (player_) {
if (strcmp(status, Lang::Strings::LISTENING) == 0) {
player_->StartPlayer(MMAP_EMOJI_ASKING_AAF, true, 15);
} else if (strcmp(status, Lang::Strings::STANDBY) == 0) {
player_->StartPlayer(MMAP_EMOJI_WAKE_AAF, true, 15);
}
}
}
void EmojiWidget::InitializePlayer(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io)
{
player_ = std::make_unique<EmojiPlayer>(panel, panel_io);
}
bool EmojiWidget::Lock(int timeout_ms)
{
return true;
}
void EmojiWidget::Unlock()
{
}
} // namespace anim
#include <cstring>
#include "display/lcd_display.h"
#include <esp_log.h>
#include "emoji_display.h"
#include "assets/lang_config.h"
#include "assets.h"
#include <esp_lcd_panel_io.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/queue.h>
#include <freertos/event_groups.h>
static const char *TAG = "emoji";
namespace anim {
// Emoji asset name mapping based on usage pattern
static const std::unordered_map<std::string, std::string> emoji_asset_name_map = {
{"connecting", "connecting.aaf"},
{"wake", "wake.aaf"},
{"asking", "asking.aaf"},
{"happy_loop", "happy_loop.aaf"},
{"sad_loop", "sad_loop.aaf"},
{"anger_loop", "anger_loop.aaf"},
{"panic_loop", "panic_loop.aaf"},
{"blink_quick", "blink_quick.aaf"},
{"scorn_loop", "scorn_loop.aaf"}
};
bool EmojiPlayer::OnFlushIoReady(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
auto* disp_drv = static_cast<anim_player_handle_t*>(user_ctx);
anim_player_flush_ready(disp_drv);
return true;
}
void EmojiPlayer::OnFlush(anim_player_handle_t handle, int x_start, int y_start, int x_end, int y_end, const void *color_data)
{
auto* panel = static_cast<esp_lcd_panel_handle_t>(anim_player_get_user_data(handle));
esp_lcd_panel_draw_bitmap(panel, x_start, y_start, x_end, y_end, color_data);
}
EmojiPlayer::EmojiPlayer(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io)
{
ESP_LOGI(TAG, "Create EmojiPlayer, panel: %p, panel_io: %p", panel, panel_io);
anim_player_config_t player_cfg = {
.flush_cb = OnFlush,
.update_cb = NULL,
.user_data = panel,
.flags = {.swap = true},
.task = ANIM_PLAYER_INIT_CONFIG()
};
player_cfg.task.task_priority = 1;
player_cfg.task.task_stack = 4096;
player_handle_ = anim_player_init(&player_cfg);
const esp_lcd_panel_io_callbacks_t cbs = {
.on_color_trans_done = OnFlushIoReady,
};
esp_lcd_panel_io_register_event_callbacks(panel_io, &cbs, player_handle_);
StartPlayer("connecting", true, 15);
}
EmojiPlayer::~EmojiPlayer()
{
if (player_handle_) {
anim_player_update(player_handle_, PLAYER_ACTION_STOP);
anim_player_deinit(player_handle_);
player_handle_ = nullptr;
}
}
void EmojiPlayer::StartPlayer(const std::string& asset_name, bool repeat, int fps)
{
if (player_handle_) {
uint32_t start, end;
void *src_data = nullptr;
size_t src_len = 0;
auto& assets = Assets::GetInstance();
std::string filename = emoji_asset_name_map.at(asset_name);
if (!assets.GetAssetData(filename, src_data, src_len)) {
ESP_LOGE(TAG, "Failed to get asset data for %s", asset_name.c_str());
return;
}
anim_player_set_src_data(player_handle_, src_data, src_len);
anim_player_get_segment(player_handle_, &start, &end);
if(asset_name == "wake"){
start = 7;
}
anim_player_set_segment(player_handle_, start, end, fps, true);
anim_player_update(player_handle_, PLAYER_ACTION_START);
}
}
void EmojiPlayer::StopPlayer()
{
if (player_handle_) {
anim_player_update(player_handle_, PLAYER_ACTION_STOP);
}
}
EmojiWidget::EmojiWidget(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io)
{
InitializePlayer(panel, panel_io);
}
EmojiWidget::~EmojiWidget()
{
}
void EmojiWidget::SetEmotion(const char* emotion)
{
if (!player_) {
return;
}
using Param = std::tuple<std::string, bool, int>;
static const std::unordered_map<std::string, Param> emotion_map = {
{"happy", {"happy_loop", true, 25}},
{"laughing", {"happy_loop", true, 25}},
{"funny", {"happy_loop", true, 25}},
{"loving", {"happy_loop", true, 25}},
{"embarrassed", {"happy_loop", true, 25}},
{"confident", {"happy_loop", true, 25}},
{"delicious", {"happy_loop", true, 25}},
{"sad", {"sad_loop", true, 25}},
{"crying", {"sad_loop", true, 25}},
{"sleepy", {"sad_loop", true, 25}},
{"silly", {"sad_loop", true, 25}},
{"angry", {"anger_loop", true, 25}},
{"surprised", {"panic_loop", true, 25}},
{"shocked", {"panic_loop", true, 25}},
{"thinking", {"happy_loop", true, 25}},
{"winking", {"blink_quick", true, 5}},
{"relaxed", {"scorn_loop", true, 25}},
{"confused", {"scorn_loop", true, 25}},
};
auto it = emotion_map.find(emotion);
if (it != emotion_map.end()) {
const auto& [aaf, repeat, fps] = it->second;
player_->StartPlayer(aaf, repeat, fps);
} else if (strcmp(emotion, "neutral") == 0) {
}
}
void EmojiWidget::SetStatus(const char* status)
{
if (player_) {
if (strcmp(status, Lang::Strings::LISTENING) == 0) {
player_->StartPlayer("asking", true, 15);
} else if (strcmp(status, Lang::Strings::STANDBY) == 0) {
player_->StartPlayer("wake", true, 15);
}
}
}
void EmojiWidget::InitializePlayer(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io)
{
player_ = std::make_unique<EmojiPlayer>(panel, panel_io);
}
bool EmojiWidget::Lock(int timeout_ms)
{
return true;
}
void EmojiWidget::Unlock()
{
}
} // namespace anim

View File

@@ -1,54 +1,55 @@
#pragma once
#include "display/lcd_display.h"
#include <memory>
#include <functional>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include "anim_player.h"
#include "mmap_generate_emoji.h"
namespace anim {
class EmojiPlayer;
using FlushIoReadyCallback = std::function<bool(esp_lcd_panel_io_handle_t, esp_lcd_panel_io_event_data_t*, void*)>;
using FlushCallback = std::function<void(anim_player_handle_t, int, int, int, int, const void*)>;
class EmojiPlayer {
public:
EmojiPlayer(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io);
~EmojiPlayer();
void StartPlayer(int aaf, bool repeat, int fps);
void StopPlayer();
private:
static bool OnFlushIoReady(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx);
static void OnFlush(anim_player_handle_t handle, int x_start, int y_start, int x_end, int y_end, const void *color_data);
anim_player_handle_t player_handle_;
mmap_assets_handle_t assets_handle_;
};
class EmojiWidget : public Display {
public:
EmojiWidget(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io);
virtual ~EmojiWidget();
virtual void SetEmotion(const char* emotion) override;
virtual void SetStatus(const char* status) override;
anim::EmojiPlayer* GetPlayer()
{
return player_.get();
}
private:
void InitializePlayer(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io);
virtual bool Lock(int timeout_ms = 0) override;
virtual void Unlock() override;
std::unique_ptr<anim::EmojiPlayer> player_;
};
} // namespace anim
#pragma once
#include "display/lcd_display.h"
#include <memory>
#include <functional>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include "anim_player.h"
#include "assets.h"
namespace anim {
class EmojiPlayer;
using FlushIoReadyCallback = std::function<bool(esp_lcd_panel_io_handle_t, esp_lcd_panel_io_event_data_t*, void*)>;
using FlushCallback = std::function<void(anim_player_handle_t, int, int, int, int, const void*)>;
class EmojiPlayer {
public:
EmojiPlayer(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io);
~EmojiPlayer();
void StartPlayer(const std::string& asset_name, bool repeat, int fps);
void StopPlayer();
private:
static bool OnFlushIoReady(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx);
static void OnFlush(anim_player_handle_t handle, int x_start, int y_start, int x_end, int y_end, const void *color_data);
anim_player_handle_t player_handle_;
};
class EmojiWidget : public Display {
public:
EmojiWidget(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io);
virtual ~EmojiWidget();
virtual void SetEmotion(const char* emotion) override;
virtual void SetStatus(const char* status) override;
virtual void SetChatMessage(const char* role, const char* content) override {}
anim::EmojiPlayer* GetPlayer()
{
return player_.get();
}
private:
void InitializePlayer(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io);
virtual bool Lock(int timeout_ms = 0) override;
virtual void Unlock() override;
std::unique_ptr<anim::EmojiPlayer> player_;
};
} // namespace anim

View File

@@ -1,425 +1,420 @@
#include "wifi_board.h"
#include "adc_pdm_audio_codec.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "mcp_server.h"
#include <wifi_station.h>
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <driver/spi_common.h>
#include <esp_wifi.h>
#include <esp_event.h>
#include "display/lcd_display.h"
#include <esp_lcd_panel_vendor.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include "esp_lcd_ili9341.h"
#include "assets/lang_config.h"
#include "anim_player.h"
#include "emoji_display.h"
#include "servo_dog_ctrl.h"
#include "led_strip.h"
#include "driver/rmt_tx.h"
#include "device_state_event.h"
#include "sdkconfig.h"
#ifdef CONFIG_ESP_HI_WEB_CONTROL_ENABLED
#include "esp_hi_web_control.h"
#endif //CONFIG_ESP_HI_WEB_CONTROL_ENABLED
#define TAG "ESP_HI"
static const ili9341_lcd_init_cmd_t vendor_specific_init[] = {
{0x11, NULL, 0, 120}, // Sleep out, Delay 120ms
{0xB1, (uint8_t []){0x05, 0x3A, 0x3A}, 3, 0},
{0xB2, (uint8_t []){0x05, 0x3A, 0x3A}, 3, 0},
{0xB3, (uint8_t []){0x05, 0x3A, 0x3A, 0x05, 0x3A, 0x3A}, 6, 0},
{0xB4, (uint8_t []){0x03}, 1, 0}, // Dot inversion
{0xC0, (uint8_t []){0x44, 0x04, 0x04}, 3, 0},
{0xC1, (uint8_t []){0xC0}, 1, 0},
{0xC2, (uint8_t []){0x0D, 0x00}, 2, 0},
{0xC3, (uint8_t []){0x8D, 0x6A}, 2, 0},
{0xC4, (uint8_t []){0x8D, 0xEE}, 2, 0},
{0xC5, (uint8_t []){0x08}, 1, 0},
{0xE0, (uint8_t []){0x0F, 0x10, 0x03, 0x03, 0x07, 0x02, 0x00, 0x02, 0x07, 0x0C, 0x13, 0x38, 0x0A, 0x0E, 0x03, 0x10}, 16, 0},
{0xE1, (uint8_t []){0x10, 0x0B, 0x04, 0x04, 0x10, 0x03, 0x00, 0x03, 0x03, 0x09, 0x17, 0x33, 0x0B, 0x0C, 0x06, 0x10}, 16, 0},
{0x35, (uint8_t []){0x00}, 1, 0},
{0x3A, (uint8_t []){0x05}, 1, 0},
{0x36, (uint8_t []){0xC8}, 1, 0},
{0x29, NULL, 0, 0}, // Display on
{0x2C, NULL, 0, 0}, // Memory write
};
static const led_strip_config_t bsp_strip_config = {
.strip_gpio_num = GPIO_NUM_8,
.max_leds = 4,
.led_model = LED_MODEL_WS2812,
.flags = {
.invert_out = false
}
};
static const led_strip_rmt_config_t bsp_rmt_config = {
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = 10 * 1000 * 1000,
.flags = {
.with_dma = false
}
};
class EspHi : public WifiBoard {
private:
Button boot_button_;
Button audio_wake_button_;
Button move_wake_button_;
anim::EmojiWidget* display_ = nullptr;
bool web_server_initialized_ = false;
led_strip_handle_t led_strip_;
bool led_on_ = false;
#ifdef CONFIG_ESP_HI_WEB_CONTROL_ENABLED
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) {
xTaskCreate(
[](void* arg) {
EspHi* instance = static_cast<EspHi*>(arg);
vTaskDelay(5000 / portTICK_PERIOD_MS);
if (!instance->web_server_initialized_) {
ESP_LOGI(TAG, "WiFi connected, init web control server");
esp_err_t err = esp_hi_web_control_server_init();
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize web control server: %d", err);
} else {
ESP_LOGI(TAG, "Web control server initialized");
instance->web_server_initialized_ = true;
}
}
vTaskDelete(NULL);
},
"web_server_init",
1024 * 10, arg, 5, nullptr);
}
}
#endif //CONFIG_ESP_HI_WEB_CONTROL_ENABLED
void HandleMoveWakePressDown(int64_t current_time, int64_t &last_trigger_time, int &gesture_state)
{
int64_t interval = last_trigger_time == 0 ? 0 : current_time - last_trigger_time;
last_trigger_time = current_time;
if (interval > 1000) {
gesture_state = 0;
} else {
switch (gesture_state) {
case 0:
break;
case 1:
if (interval > 300) {
gesture_state = 2;
}
break;
case 2:
if (interval > 100) {
gesture_state = 0;
}
break;
}
}
}
void HandleMoveWakePressUp(int64_t current_time, int64_t &last_trigger_time, int &gesture_state)
{
int64_t interval = current_time - last_trigger_time;
if (interval > 1000) {
gesture_state = 0;
} else {
switch (gesture_state) {
case 0:
if (interval > 300) {
gesture_state = 1;
}
break;
case 1:
break;
case 2:
if (interval < 100) {
ESP_LOGI(TAG, "gesture detected");
gesture_state = 0;
auto &app = Application::GetInstance();
app.ToggleChatState();
}
break;
}
}
}
void InitializeButtons()
{
static int64_t last_trigger_time = 0;
static int gesture_state = 0; // 0: init, 1: wait second long interval, 2: wait oscillation
boot_button_.OnClick([this]() {
auto &app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
audio_wake_button_.OnPressDown([this]() {
});
audio_wake_button_.OnPressUp([this]() {
});
move_wake_button_.OnPressDown([this]() {
int64_t current_time = esp_timer_get_time() / 1000;
HandleMoveWakePressDown(current_time, last_trigger_time, gesture_state);
});
move_wake_button_.OnPressUp([this]() {
int64_t current_time = esp_timer_get_time() / 1000;
HandleMoveWakePressUp(current_time, last_trigger_time, gesture_state);
});
}
void InitializeLed() {
ESP_LOGI(TAG, "BLINK_GPIO setting %d", bsp_strip_config.strip_gpio_num);
ESP_ERROR_CHECK(led_strip_new_rmt_device(&bsp_strip_config, &bsp_rmt_config, &led_strip_));
led_strip_set_pixel(led_strip_, 0, 0x00, 0x00, 0x00);
led_strip_set_pixel(led_strip_, 1, 0x00, 0x00, 0x00);
led_strip_set_pixel(led_strip_, 2, 0x00, 0x00, 0x00);
led_strip_set_pixel(led_strip_, 3, 0x00, 0x00, 0x00);
led_strip_refresh(led_strip_);
}
esp_err_t SetLedColor(uint8_t r, uint8_t g, uint8_t b) {
esp_err_t ret = ESP_OK;
ret |= led_strip_set_pixel(led_strip_, 0, r, g, b);
ret |= led_strip_set_pixel(led_strip_, 1, r, g, b);
ret |= led_strip_set_pixel(led_strip_, 2, r, g, b);
ret |= led_strip_set_pixel(led_strip_, 3, r, g, b);
ret |= led_strip_refresh(led_strip_);
return ret;
}
void InitializeIot()
{
ESP_LOGI(TAG, "Initialize Iot");
InitializeLed();
SetLedColor(0x00, 0x00, 0x00);
#ifdef CONFIG_ESP_HI_WEB_CONTROL_ENABLED
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_CONNECTED,
&wifi_event_handler, this));
#endif //CONFIG_ESP_HI_WEB_CONTROL_ENABLED
}
void InitializeSpi()
{
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.sclk_io_num = DISPLAY_CLK_PIN;
buscfg.quadwp_io_num = GPIO_NUM_NC;
buscfg.quadhd_io_num = GPIO_NUM_NC;
buscfg.max_transfer_sz = DISPLAY_WIDTH * 10 * sizeof(uint16_t);
ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
}
void InitializeLcdDisplay()
{
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
// 液晶屏控制IO初始化
ESP_LOGD(TAG, "Install panel IO");
esp_lcd_panel_io_spi_config_t io_config = {};
io_config.cs_gpio_num = DISPLAY_CS_PIN;
io_config.dc_gpio_num = DISPLAY_DC_PIN;
io_config.spi_mode = DISPLAY_SPI_MODE;
io_config.pclk_hz = 40 * 1000 * 1000;
io_config.trans_queue_depth = 10;
io_config.lcd_cmd_bits = 8;
io_config.lcd_param_bits = 8;
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io));
// 初始化液晶屏驱动芯片
ESP_LOGD(TAG, "Install LCD driver");
const ili9341_vendor_config_t vendor_config = {
.init_cmds = &vendor_specific_init[0],
.init_cmds_size = sizeof(vendor_specific_init) / sizeof(ili9341_lcd_init_cmd_t),
};
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = DISPLAY_RST_PIN;
panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
panel_config.bits_per_pixel = 16;
panel_config.vendor_config = (void *) &vendor_config;
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(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_invert_color(panel, false);
esp_lcd_panel_set_gap(panel, 0, 24);
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
ESP_LOGI(TAG, "LCD panel create success, %p", panel);
esp_lcd_panel_disp_on_off(panel, true);
ESP_LOGI(TAG, "Create emoji widget, panel: %p, panel_io: %p", panel, panel_io);
display_ = new anim::EmojiWidget(panel, panel_io);
#if CONFIG_ESP_CONSOLE_NONE
servo_dog_ctrl_config_t config = {
.fl_gpio_num = FL_GPIO_NUM,
.fr_gpio_num = FR_GPIO_NUM,
.bl_gpio_num = BL_GPIO_NUM,
.br_gpio_num = BR_GPIO_NUM,
};
servo_dog_ctrl_init(&config);
#endif
}
void InitializeTools()
{
auto& mcp_server = McpServer::GetInstance();
// 基础动作控制
mcp_server.AddTool("self.dog.basic_control", "机器人的基础动作。机器人可以做以下基础动作:\n"
"forward: 向前移动\nbackward: 向后移动\nturn_left: 向左转\nturn_right: 向右转\nstop: 立即停止当前动作",
PropertyList({
Property("action", kPropertyTypeString),
}), [this](const PropertyList& properties) -> ReturnValue {
const std::string& action = properties["action"].value<std::string>();
if (action == "forward") {
servo_dog_ctrl_send(DOG_STATE_FORWARD, NULL);
} else if (action == "backward") {
servo_dog_ctrl_send(DOG_STATE_BACKWARD, NULL);
} else if (action == "turn_left") {
servo_dog_ctrl_send(DOG_STATE_TURN_LEFT, NULL);
} else if (action == "turn_right") {
servo_dog_ctrl_send(DOG_STATE_TURN_RIGHT, NULL);
} else if (action == "stop") {
servo_dog_ctrl_send(DOG_STATE_IDLE, NULL);
} else {
return false;
}
return true;
});
// 扩展动作控制
mcp_server.AddTool("self.dog.advanced_control", "机器人的扩展动作。机器人可以做以下扩展动作:\n"
"sway_back_forth: 前后摇摆\nlay_down: 趴下\nsway: 左右摇摆\nretract_legs: 收回腿部\n"
"shake_hand: 握手\nshake_back_legs: 伸懒腰\njump_forward: 向前跳跃",
PropertyList({
Property("action", kPropertyTypeString),
}), [this](const PropertyList& properties) -> ReturnValue {
const std::string& action = properties["action"].value<std::string>();
if (action == "sway_back_forth") {
servo_dog_ctrl_send(DOG_STATE_SWAY_BACK_FORTH, NULL);
} else if (action == "lay_down") {
servo_dog_ctrl_send(DOG_STATE_LAY_DOWN, NULL);
} else if (action == "sway") {
dog_action_args_t args = {
.repeat_count = 4,
};
servo_dog_ctrl_send(DOG_STATE_SWAY, &args);
} else if (action == "retract_legs") {
servo_dog_ctrl_send(DOG_STATE_RETRACT_LEGS, NULL);
} else if (action == "shake_hand") {
servo_dog_ctrl_send(DOG_STATE_SHAKE_HAND, NULL);
} else if (action == "shake_back_legs") {
servo_dog_ctrl_send(DOG_STATE_SHAKE_BACK_LEGS, NULL);
} else if (action == "jump_forward") {
servo_dog_ctrl_send(DOG_STATE_JUMP_FORWARD, NULL);
} else {
return false;
}
return true;
});
// 灯光控制
mcp_server.AddTool("self.light.get_power", "获取灯是否打开", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
return led_on_;
});
mcp_server.AddTool("self.light.turn_on", "打开灯", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
SetLedColor(0xFF, 0xFF, 0xFF);
led_on_ = true;
return true;
});
mcp_server.AddTool("self.light.turn_off", "关闭灯", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
SetLedColor(0x00, 0x00, 0x00);
led_on_ = false;
return true;
});
mcp_server.AddTool("self.light.set_rgb", "设置RGB颜色", PropertyList({
Property("r", kPropertyTypeInteger, 0, 255),
Property("g", kPropertyTypeInteger, 0, 255),
Property("b", kPropertyTypeInteger, 0, 255)
}), [this](const PropertyList& properties) -> ReturnValue {
int r = properties["r"].value<int>();
int g = properties["g"].value<int>();
int b = properties["b"].value<int>();
led_on_ = true;
SetLedColor(r, g, b);
return true;
});
}
public:
EspHi() : boot_button_(BOOT_BUTTON_GPIO),
audio_wake_button_(AUDIO_WAKE_BUTTON_GPIO),
move_wake_button_(MOVE_WAKE_BUTTON_GPIO)
{
InitializeButtons();
InitializeIot();
InitializeSpi();
InitializeLcdDisplay();
InitializeTools();
DeviceStateEventManager::GetInstance().RegisterStateChangeCallback([this](DeviceState previous_state, DeviceState current_state) {
ESP_LOGD(TAG, "Device state changed from %d to %d", previous_state, current_state);
this->GetAudioCodec()->EnableOutput(current_state == kDeviceStateSpeaking);
});
}
virtual AudioCodec* GetAudioCodec() override
{
static AdcPdmAudioCodec audio_codec(
AUDIO_INPUT_SAMPLE_RATE,
AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_ADC_MIC_CHANNEL,
AUDIO_PDM_SPEAK_P_GPIO,
AUDIO_PDM_SPEAK_N_GPIO,
AUDIO_PA_CTL_GPIO);
return &audio_codec;
}
virtual Display* GetDisplay() override
{
return display_;
}
};
DECLARE_BOARD(EspHi);
#include "wifi_board.h"
#include "adc_pdm_audio_codec.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "mcp_server.h"
#include <wifi_station.h>
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <driver/spi_common.h>
#include <esp_wifi.h>
#include <esp_event.h>
#include "display/lcd_display.h"
#include <esp_lcd_panel_vendor.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include "esp_lcd_ili9341.h"
#include "assets/lang_config.h"
#include "anim_player.h"
#include "emoji_display.h"
#include "servo_dog_ctrl.h"
#include "led_strip.h"
#include "driver/rmt_tx.h"
#include "device_state_event.h"
#include "sdkconfig.h"
#ifdef CONFIG_ESP_HI_WEB_CONTROL_ENABLED
#include "esp_hi_web_control.h"
#endif //CONFIG_ESP_HI_WEB_CONTROL_ENABLED
#define TAG "ESP_HI"
static const ili9341_lcd_init_cmd_t vendor_specific_init[] = {
{0x11, NULL, 0, 120}, // Sleep out, Delay 120ms
{0xB1, (uint8_t []){0x05, 0x3A, 0x3A}, 3, 0},
{0xB2, (uint8_t []){0x05, 0x3A, 0x3A}, 3, 0},
{0xB3, (uint8_t []){0x05, 0x3A, 0x3A, 0x05, 0x3A, 0x3A}, 6, 0},
{0xB4, (uint8_t []){0x03}, 1, 0}, // Dot inversion
{0xC0, (uint8_t []){0x44, 0x04, 0x04}, 3, 0},
{0xC1, (uint8_t []){0xC0}, 1, 0},
{0xC2, (uint8_t []){0x0D, 0x00}, 2, 0},
{0xC3, (uint8_t []){0x8D, 0x6A}, 2, 0},
{0xC4, (uint8_t []){0x8D, 0xEE}, 2, 0},
{0xC5, (uint8_t []){0x08}, 1, 0},
{0xE0, (uint8_t []){0x0F, 0x10, 0x03, 0x03, 0x07, 0x02, 0x00, 0x02, 0x07, 0x0C, 0x13, 0x38, 0x0A, 0x0E, 0x03, 0x10}, 16, 0},
{0xE1, (uint8_t []){0x10, 0x0B, 0x04, 0x04, 0x10, 0x03, 0x00, 0x03, 0x03, 0x09, 0x17, 0x33, 0x0B, 0x0C, 0x06, 0x10}, 16, 0},
{0x35, (uint8_t []){0x00}, 1, 0},
{0x3A, (uint8_t []){0x05}, 1, 0},
{0x36, (uint8_t []){0xC8}, 1, 0},
{0x29, NULL, 0, 0}, // Display on
{0x2C, NULL, 0, 0}, // Memory write
};
static const led_strip_config_t bsp_strip_config = {
.strip_gpio_num = GPIO_NUM_8,
.max_leds = 4,
.led_model = LED_MODEL_WS2812,
.flags = {
.invert_out = false
}
};
static const led_strip_rmt_config_t bsp_rmt_config = {
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = 10 * 1000 * 1000,
.flags = {
.with_dma = false
}
};
class EspHi : public WifiBoard {
private:
Button boot_button_;
Button audio_wake_button_;
Button move_wake_button_;
anim::EmojiWidget* display_ = nullptr;
bool web_server_initialized_ = false;
led_strip_handle_t led_strip_;
bool led_on_ = false;
#ifdef CONFIG_ESP_HI_WEB_CONTROL_ENABLED
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) {
xTaskCreate(
[](void* arg) {
EspHi* instance = static_cast<EspHi*>(arg);
vTaskDelay(5000 / portTICK_PERIOD_MS);
if (!instance->web_server_initialized_) {
ESP_LOGI(TAG, "WiFi connected, init web control server");
esp_err_t err = esp_hi_web_control_server_init();
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize web control server: %d", err);
} else {
ESP_LOGI(TAG, "Web control server initialized");
instance->web_server_initialized_ = true;
}
}
vTaskDelete(NULL);
},
"web_server_init",
1024 * 10, arg, 5, nullptr);
}
}
#endif //CONFIG_ESP_HI_WEB_CONTROL_ENABLED
void HandleMoveWakePressDown(int64_t current_time, int64_t &last_trigger_time, int &gesture_state)
{
int64_t interval = last_trigger_time == 0 ? 0 : current_time - last_trigger_time;
last_trigger_time = current_time;
if (interval > 1000) {
gesture_state = 0;
} else {
switch (gesture_state) {
case 0:
break;
case 1:
if (interval > 300) {
gesture_state = 2;
}
break;
case 2:
if (interval > 100) {
gesture_state = 0;
}
break;
}
}
}
void HandleMoveWakePressUp(int64_t current_time, int64_t &last_trigger_time, int &gesture_state)
{
int64_t interval = current_time - last_trigger_time;
if (interval > 1000) {
gesture_state = 0;
} else {
switch (gesture_state) {
case 0:
if (interval > 300) {
gesture_state = 1;
}
break;
case 1:
break;
case 2:
if (interval < 100) {
ESP_LOGI(TAG, "gesture detected");
gesture_state = 0;
auto &app = Application::GetInstance();
app.ToggleChatState();
}
break;
}
}
}
void InitializeButtons()
{
static int64_t last_trigger_time = 0;
static int gesture_state = 0; // 0: init, 1: wait second long interval, 2: wait oscillation
boot_button_.OnClick([this]() {
auto &app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
audio_wake_button_.OnPressDown([this]() {
});
audio_wake_button_.OnPressUp([this]() {
});
move_wake_button_.OnPressDown([this]() {
int64_t current_time = esp_timer_get_time() / 1000;
HandleMoveWakePressDown(current_time, last_trigger_time, gesture_state);
});
move_wake_button_.OnPressUp([this]() {
int64_t current_time = esp_timer_get_time() / 1000;
HandleMoveWakePressUp(current_time, last_trigger_time, gesture_state);
});
}
void InitializeLed() {
ESP_LOGI(TAG, "BLINK_GPIO setting %d", bsp_strip_config.strip_gpio_num);
ESP_ERROR_CHECK(led_strip_new_rmt_device(&bsp_strip_config, &bsp_rmt_config, &led_strip_));
led_strip_set_pixel(led_strip_, 0, 0x00, 0x00, 0x00);
led_strip_set_pixel(led_strip_, 1, 0x00, 0x00, 0x00);
led_strip_set_pixel(led_strip_, 2, 0x00, 0x00, 0x00);
led_strip_set_pixel(led_strip_, 3, 0x00, 0x00, 0x00);
led_strip_refresh(led_strip_);
}
esp_err_t SetLedColor(uint8_t r, uint8_t g, uint8_t b) {
esp_err_t ret = ESP_OK;
ret |= led_strip_set_pixel(led_strip_, 0, r, g, b);
ret |= led_strip_set_pixel(led_strip_, 1, r, g, b);
ret |= led_strip_set_pixel(led_strip_, 2, r, g, b);
ret |= led_strip_set_pixel(led_strip_, 3, r, g, b);
ret |= led_strip_refresh(led_strip_);
return ret;
}
void InitializeIot()
{
ESP_LOGI(TAG, "Initialize Iot");
InitializeLed();
SetLedColor(0x00, 0x00, 0x00);
#ifdef CONFIG_ESP_HI_WEB_CONTROL_ENABLED
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_CONNECTED,
&wifi_event_handler, this));
#endif //CONFIG_ESP_HI_WEB_CONTROL_ENABLED
}
void InitializeSpi()
{
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.sclk_io_num = DISPLAY_CLK_PIN;
buscfg.quadwp_io_num = GPIO_NUM_NC;
buscfg.quadhd_io_num = GPIO_NUM_NC;
buscfg.max_transfer_sz = DISPLAY_WIDTH * 10 * sizeof(uint16_t);
ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
}
void InitializeLcdDisplay()
{
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
// 液晶屏控制IO初始化
ESP_LOGD(TAG, "Install panel IO");
esp_lcd_panel_io_spi_config_t io_config = {};
io_config.cs_gpio_num = DISPLAY_CS_PIN;
io_config.dc_gpio_num = DISPLAY_DC_PIN;
io_config.spi_mode = DISPLAY_SPI_MODE;
io_config.pclk_hz = 40 * 1000 * 1000;
io_config.trans_queue_depth = 10;
io_config.lcd_cmd_bits = 8;
io_config.lcd_param_bits = 8;
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io));
// 初始化液晶屏驱动芯片
ESP_LOGD(TAG, "Install LCD driver");
const ili9341_vendor_config_t vendor_config = {
.init_cmds = &vendor_specific_init[0],
.init_cmds_size = sizeof(vendor_specific_init) / sizeof(ili9341_lcd_init_cmd_t),
};
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = DISPLAY_RST_PIN;
panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
panel_config.bits_per_pixel = 16;
panel_config.vendor_config = (void *) &vendor_config;
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(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_invert_color(panel, false);
esp_lcd_panel_set_gap(panel, 0, 24);
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
ESP_LOGI(TAG, "LCD panel create success, %p", panel);
esp_lcd_panel_disp_on_off(panel, true);
ESP_LOGI(TAG, "Create emoji widget, panel: %p, panel_io: %p", panel, panel_io);
display_ = new anim::EmojiWidget(panel, panel_io);
#if CONFIG_ESP_CONSOLE_NONE
servo_dog_ctrl_config_t config = {
.fl_gpio_num = FL_GPIO_NUM,
.fr_gpio_num = FR_GPIO_NUM,
.bl_gpio_num = BL_GPIO_NUM,
.br_gpio_num = BR_GPIO_NUM,
};
servo_dog_ctrl_init(&config);
#endif
}
void InitializeTools()
{
auto& mcp_server = McpServer::GetInstance();
// 基础动作控制
mcp_server.AddTool("self.dog.basic_control", "机器人的基础动作。机器人可以做以下基础动作:\n"
"forward: 向前移动\nbackward: 向后移动\nturn_left: 向左转\nturn_right: 向右转\nstop: 立即停止当前动作",
PropertyList({
Property("action", kPropertyTypeString),
}), [this](const PropertyList& properties) -> ReturnValue {
const std::string& action = properties["action"].value<std::string>();
if (action == "forward") {
servo_dog_ctrl_send(DOG_STATE_FORWARD, NULL);
} else if (action == "backward") {
servo_dog_ctrl_send(DOG_STATE_BACKWARD, NULL);
} else if (action == "turn_left") {
servo_dog_ctrl_send(DOG_STATE_TURN_LEFT, NULL);
} else if (action == "turn_right") {
servo_dog_ctrl_send(DOG_STATE_TURN_RIGHT, NULL);
} else if (action == "stop") {
servo_dog_ctrl_send(DOG_STATE_IDLE, NULL);
} else {
return false;
}
return true;
});
// 扩展动作控制
mcp_server.AddTool("self.dog.advanced_control", "机器人的扩展动作。机器人可以做以下扩展动作:\n"
"sway_back_forth: 前后摇摆\nlay_down: 趴下\nsway: 左右摇摆\nretract_legs: 收回腿部\n"
"shake_hand: 握手\nshake_back_legs: 伸懒腰\njump_forward: 向前跳跃",
PropertyList({
Property("action", kPropertyTypeString),
}), [this](const PropertyList& properties) -> ReturnValue {
const std::string& action = properties["action"].value<std::string>();
if (action == "sway_back_forth") {
servo_dog_ctrl_send(DOG_STATE_SWAY_BACK_FORTH, NULL);
} else if (action == "lay_down") {
servo_dog_ctrl_send(DOG_STATE_LAY_DOWN, NULL);
} else if (action == "sway") {
dog_action_args_t args = {
.repeat_count = 4,
};
servo_dog_ctrl_send(DOG_STATE_SWAY, &args);
} else if (action == "retract_legs") {
servo_dog_ctrl_send(DOG_STATE_RETRACT_LEGS, NULL);
} else if (action == "shake_hand") {
servo_dog_ctrl_send(DOG_STATE_SHAKE_HAND, NULL);
} else if (action == "shake_back_legs") {
servo_dog_ctrl_send(DOG_STATE_SHAKE_BACK_LEGS, NULL);
} else if (action == "jump_forward") {
servo_dog_ctrl_send(DOG_STATE_JUMP_FORWARD, NULL);
} else {
return false;
}
return true;
});
// 灯光控制
mcp_server.AddTool("self.light.get_power", "获取灯是否打开", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
return led_on_;
});
mcp_server.AddTool("self.light.turn_on", "打开灯", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
SetLedColor(0xFF, 0xFF, 0xFF);
led_on_ = true;
return true;
});
mcp_server.AddTool("self.light.turn_off", "关闭灯", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
SetLedColor(0x00, 0x00, 0x00);
led_on_ = false;
return true;
});
mcp_server.AddTool("self.light.set_rgb", "设置RGB颜色", PropertyList({
Property("r", kPropertyTypeInteger, 0, 255),
Property("g", kPropertyTypeInteger, 0, 255),
Property("b", kPropertyTypeInteger, 0, 255)
}), [this](const PropertyList& properties) -> ReturnValue {
int r = properties["r"].value<int>();
int g = properties["g"].value<int>();
int b = properties["b"].value<int>();
led_on_ = true;
SetLedColor(r, g, b);
return true;
});
}
public:
EspHi() : boot_button_(BOOT_BUTTON_GPIO),
audio_wake_button_(AUDIO_WAKE_BUTTON_GPIO),
move_wake_button_(MOVE_WAKE_BUTTON_GPIO)
{
InitializeButtons();
InitializeIot();
InitializeSpi();
InitializeLcdDisplay();
InitializeTools();
}
virtual AudioCodec* GetAudioCodec() override
{
static AdcPdmAudioCodec audio_codec(
AUDIO_INPUT_SAMPLE_RATE,
AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_ADC_MIC_CHANNEL,
AUDIO_PDM_SPEAK_P_GPIO,
AUDIO_PDM_SPEAK_N_GPIO,
AUDIO_PA_CTL_GPIO);
return &audio_codec;
}
virtual Display* GetDisplay() override
{
return display_;
}
};
DECLARE_BOARD(EspHi);