You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

224 lines
7.4 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "wifi_board.h"
#include "audio_codecs/es8311_audio_codec.h"
#include "display/oled_display.h"
#include "application.h"
#include "button.h"
#include "led/single_led.h"
#include "iot/thing_manager.h"
#include "settings.h"
#include "config.h"
#include "power_save_timer.h"
#include "font_awesome_symbols.h"
#include <wifi_station.h>
#include <esp_log.h>
#include <esp_efuse_table.h>
#include <driver/i2c_master.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lcd_panel_vendor.h>
#define TAG "XminiC3Board"
LV_FONT_DECLARE(font_puhui_14_1);
LV_FONT_DECLARE(font_awesome_14_1);
class XminiC3Board : public WifiBoard {
private:
i2c_master_bus_handle_t codec_i2c_bus_;
esp_lcd_panel_io_handle_t panel_io_ = nullptr;
esp_lcd_panel_handle_t panel_ = nullptr;
Display* display_ = nullptr;
Button boot_button_;
bool press_to_talk_enabled_ = false;
PowerSaveTimer* power_save_timer_;
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(160, 60);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("sleepy");
auto codec = GetAudioCodec();
codec->EnableInput(false);
});
power_save_timer_->OnExitSleepMode([this]() {
auto codec = GetAudioCodec();
codec->EnableInput(true);
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("neutral");
});
power_save_timer_->SetEnabled(true);
}
void InitializeCodecI2c() {
// 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 InitializeSsd1306Display() {
// SSD1306 config
esp_lcd_panel_io_i2c_config_t io_config = {
.dev_addr = 0x3C,
.on_color_trans_done = nullptr,
.user_ctx = nullptr,
.control_phase_bytes = 1,
.dc_bit_offset = 6,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.flags = {
.dc_low_on_data = 0,
.disable_control_phase = 0,
},
.scl_speed_hz = 400 * 1000,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(codec_i2c_bus_, &io_config, &panel_io_));
ESP_LOGI(TAG, "Install SSD1306 driver");
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = -1;
panel_config.bits_per_pixel = 1;
esp_lcd_panel_ssd1306_config_t ssd1306_config = {
.height = static_cast<uint8_t>(DISPLAY_HEIGHT),
};
panel_config.vendor_config = &ssd1306_config;
ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_));
ESP_LOGI(TAG, "SSD1306 driver installed");
// Reset the display
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_));
if (esp_lcd_panel_init(panel_) != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize display");
display_ = new NoDisplay();
return;
}
// Set the display to on
ESP_LOGI(TAG, "Turning display on");
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y,
{&font_puhui_14_1, &font_awesome_14_1});
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
if (!press_to_talk_enabled_) {
app.ToggleChatState();
}
});
boot_button_.OnPressDown([this]() {
power_save_timer_->WakeUp();
if (press_to_talk_enabled_) {
Application::GetInstance().StartListening();
}
});
boot_button_.OnPressUp([this]() {
if (press_to_talk_enabled_) {
Application::GetInstance().StopListening();
}
});
}
// 物联网初始化,添加对 AI 可见设备
void InitializeIot() {
Settings settings("vendor");
press_to_talk_enabled_ = settings.GetInt("press_to_talk", 0) != 0;
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("PressToTalk"));
}
public:
XminiC3Board() : boot_button_(BOOT_BUTTON_GPIO) {
// 把 ESP32C3 的 VDD SPI 引脚作为普通 GPIO 口使用
esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO);
InitializeCodecI2c();
InitializeSsd1306Display();
InitializeButtons();
InitializePowerSaveTimer();
InitializeIot();
}
virtual Led* GetLed() override {
static SingleLed led(BUILTIN_LED_GPIO);
return &led;
}
virtual Display* GetDisplay() override {
return display_;
}
virtual AudioCodec* GetAudioCodec() override {
static Es8311AudioCodec 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_ES8311_ADDR);
return &audio_codec;
}
void SetPressToTalkEnabled(bool enabled) {
press_to_talk_enabled_ = enabled;
Settings settings("vendor", true);
settings.SetInt("press_to_talk", enabled ? 1 : 0);
ESP_LOGI(TAG, "Press to talk enabled: %d", enabled);
}
bool IsPressToTalkEnabled() {
return press_to_talk_enabled_;
}
};
DECLARE_BOARD(XminiC3Board);
namespace iot {
class PressToTalk : public Thing {
public:
PressToTalk() : Thing("PressToTalk", "控制对话模式,一种是长按对话,一种是单击后连续对话。") {
// 定义设备的属性
properties_.AddBooleanProperty("enabled", "true 表示长按说话模式false 表示单击说话模式", []() -> bool {
auto board = static_cast<XminiC3Board*>(&Board::GetInstance());
return board->IsPressToTalkEnabled();
});
// 定义设备可以被远程执行的指令
methods_.AddMethod("SetEnabled", "启用或禁用长按说话模式,调用前需要经过用户确认", ParameterList({
Parameter("enabled", "true 表示长按说话模式false 表示单击说话模式", kValueTypeBoolean, true)
}), [](const ParameterList& parameters) {
bool enabled = parameters["enabled"].boolean();
auto board = static_cast<XminiC3Board*>(&Board::GetInstance());
board->SetPressToTalkEnabled(enabled);
});
}
};
} // namespace iot
DECLARE_THING(PressToTalk);