Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions main/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,11 @@ elseif(CONFIG_BOARD_TYPE_WIRELESS_TAG_WTP4C5MP07S)
set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
set(BUILTIN_ICON_FONT font_awesome_30_4)
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_AIPI_LITE)
set(BOARD_TYPE "aipi-lite")
set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
set(BUILTIN_ICON_FONT font_awesome_14_1)
set(DEFAULT_EMOJI_COLLECTION twemoji_32)
endif()

file(GLOB BOARD_SOURCES
Expand Down
3 changes: 3 additions & 0 deletions main/Kconfig.projbuild
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,9 @@ choice BOARD_TYPE
config BOARD_TYPE_WIRELESS_TAG_WTP4C5MP07S
bool "Wireless-Tag WTP4C5MP07S"
depends on IDF_TARGET_ESP32P4
config BOARD_TYPE_AIPI_LITE
bool "AIPI-Lite"
depends on IDF_TARGET_ESP32S3
endchoice

choice
Expand Down
41 changes: 41 additions & 0 deletions main/boards/aipi-lite/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# 编译命令

## 一键编译

```bash
python scripts/release.py aipi-lite
```

## 手动配置编译

```bash
idf.py set-target esp32s3
```

**配置**

```bash
idf.py menuconfig
```

选择板子

```
Xiaozhi Assistant -> Board Type -> AIPI-Lite
```

## 编译烧入

```bash
idf.py -DBOARD_NAME=aipi-lite build flash
```

注意: 如果当前设备出货之前是AiPi-Lite 固件(非小智版本),请特别小心处理闪存固件分区地址,以避免错误擦除 AiPi-Lite 的自身设备信息(EUI 等),否则设备即使恢复成Xorigin固件也无法正确连接到 服务器!所以在刷写固件之前,请务必记录设备的相关必要信息,以确保有恢复的方法!

您可以使用以下命令备份生产信息

```bash
# firstly backup the factory information partition which contains the credentials for connecting the SenseCraft server
esptool.py --chip esp32s3 --baud 2000000 --before default_reset --after hard_reset --no-stub read_flash 0x9000 16384 nvsfactory.bin

```
40 changes: 40 additions & 0 deletions main/boards/aipi-lite/README_en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Build Instructions

## One-click Build

```bash
python scripts/release.py aipi-lite -c config_en.json
```

## Manual Configuration and Build

```bash
idf.py set-target esp32s3
```

**Configuration**

```bash
idf.py menuconfig
```

Select the board:

```
Xiaozhi Assistant -> Board Type -> AiPi-Lite
```

## Build and Flash

```bash
idf.py -DBOARD_NAME=aipi-lite build flash
```

Note: If your device was previously shipped with the AiPi-Lite firmware (not the Xiaozhi version), please be very careful with the flash partition addresses to avoid accidentally erasing the device information (such as EUI) of the AiPi-Lite. Otherwise, even if you restore the AiPi-Lite firmware, the device may not be able to connect to the Xorigin server correctly! Therefore, before flashing the firmware, be sure to record the necessary device information to ensure you have a way to recover it!

You can use the following command to back up the factory information:

```bash
# Firstly backup the factory information partition which contains the credentials for connecting the SenseCraft server
esptool.py --chip esp32s3 --baud 2000000 --before default_reset --after hard_reset --no-stub read_flash 0x9000 16384 nvsfactory.bin
```
247 changes: 247 additions & 0 deletions main/boards/aipi-lite/aipi-lite.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
#include <driver/gpio.h>
#include <driver/i2c_master.h>
#include <driver/rtc_io.h>
#include <driver/spi_common.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lcd_panel_vendor.h>
#include <esp_log.h>
#include <esp_sleep.h>
#include <wifi_station.h>

#include "application.h"
#include "button.h"
#include "codecs/es8311_audio_codec.h"
#include "config.h"
#include "display/lcd_display.h"
#include "lamp_controller.h"
#include "led/single_led.h"
#include "mcp_server.h"
#include "power_manager.h"
#include "power_save_timer.h"
#include "system_reset.h"
#include "wifi_board.h"

#define TAG "AIPI-Lite"

class AIPILite : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
Button boot_button_;
Button power_button_;
LcdDisplay* display_;
PowerManager* power_manager_;
PowerSaveTimer* power_save_timer_;
esp_lcd_panel_handle_t panel_ = nullptr;

void InitializePowerManager() {
power_manager_ = new PowerManager(POWER_CHARGE_DETECT_PIN);
power_manager_->OnChargingStatusChanged([this](bool is_charging) {
if (is_charging) {
power_save_timer_->SetEnabled(false);
} else {
power_save_timer_->SetEnabled(true);
}
});
}

void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(1);
});
power_save_timer_->OnExitSleepMode([this]() {
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {
ESP_LOGI(TAG, "Shutting down");
esp_lcd_panel_disp_on_off(panel_, false); // 关闭显示
rtc_gpio_set_level(POWER_CONTROL_PIN, 0);
rtc_gpio_hold_dis(POWER_CONTROL_PIN);
esp_deep_sleep_start();
});
power_save_timer_->SetEnabled(true);
}

void InitializeI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = (i2c_port_t)1,
.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, &i2c_bus_));
}

void InitializeSpi() {
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = DISPLAY_SPI_MOSI_PIN;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.sclk_io_num = DISPLAY_SPI_SCLK_PIN;
buscfg.quadwp_io_num = GPIO_NUM_NC;
buscfg.quadhd_io_num = GPIO_NUM_NC;
buscfg.max_transfer_sz =
DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t);
ESP_ERROR_CHECK(
spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
}

void InitializeLcdDisplay() {
esp_lcd_panel_io_handle_t panel_io = nullptr;
// 液晶屏控制IO初始化
ESP_LOGD(TAG, "Install panel IO");
esp_lcd_panel_io_spi_config_t io_config = {};
io_config.cs_gpio_num = DISPLAY_SPI_CS_PIN;
io_config.dc_gpio_num = DISPLAY_SPI_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(SPI3_HOST, &io_config, &panel_io));

// 初始化液晶屏驱动芯片
ESP_LOGD(TAG, "Install LCD driver");
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = DISPLAY_SPI_RESET_PIN;
panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
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);
}

void InitializeButtons() {
boot_button_.OnClick([this]() {
power_save_timer_->WakeUp();
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting &&
!WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});

// 设置开机按钮的长按事件(直接进入配网模式)
boot_button_.OnLongPress([this]() {
// 唤醒电源保存定时器
power_save_timer_->WakeUp();
// 获取应用程序实例
auto& app = Application::GetInstance();

// 进入配网模式
app.SetDeviceState(kDeviceStateWifiConfiguring);

// 重置WiFi配置以确保进入配网模式
ResetWifiConfiguration();
});

power_button_.OnClick([this]() { power_save_timer_->WakeUp(); });
power_button_.OnLongPress([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() != kDeviceStateStarting &&
!(power_manager_->IsCharging() &&
power_manager_->GetBatteryLevel() < 100)) {
ESP_LOGI(TAG, "Power button long pressed, shutting down");
esp_lcd_panel_disp_on_off(panel_, false); // 关闭显示
rtc_gpio_set_level(POWER_CONTROL_PIN, 0);
rtc_gpio_hold_dis(POWER_CONTROL_PIN);
esp_deep_sleep_start();
}
});
}

void InitializePowerCtl() {
ESP_LOGI(TAG, "Initialize Power Control GPIO");
rtc_gpio_init(POWER_CONTROL_PIN);
rtc_gpio_set_direction(POWER_CONTROL_PIN, RTC_GPIO_MODE_OUTPUT_ONLY);
rtc_gpio_set_level(POWER_CONTROL_PIN, 1);
}

// 物联网初始化,添加对 AI 可见设备
void InitializeTools() {}

public:
AIPILite()
: boot_button_(BOOT_BUTTON_GPIO), power_button_(POWER_BUTTON_GPIO) {
InitializePowerCtl();
InitializePowerManager();
InitializePowerSaveTimer();
InitializeI2c();
InitializeSpi();
InitializeLcdDisplay();
InitializeButtons();
InitializeTools();
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
GetBacklight()->RestoreBrightness();
}
}

virtual Led* GetLed() override {
static SingleLed led(BUILTIN_LED_GPIO);
return &led;
}

virtual AudioCodec* GetAudioCodec() override {
static Es8311AudioCodec audio_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, false);
return &audio_codec;
}

virtual Display* GetDisplay() override { return display_; }

virtual Backlight* GetBacklight() override {
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN,
DISPLAY_BACKLIGHT_OUTPUT_INVERT);
return &backlight;
}
return nullptr;
}

virtual bool GetBatteryLevel(int& level, bool& charging,
bool& discharging) override {
static bool last_discharging = false;
charging = power_manager_->IsCharging();
discharging = power_manager_->IsDischarging();
if (discharging != last_discharging) {
power_save_timer_->SetEnabled(discharging);
last_discharging = discharging;
}
level = power_manager_->GetBatteryLevel();
return true;
}

virtual void SetPowerSaveMode(bool enabled) override {
if (!enabled) {
power_save_timer_->WakeUp();
}
WifiBoard::SetPowerSaveMode(enabled);
}
};

DECLARE_BOARD(AIPILite);
Loading