forked from xiaozhi/xiaozhi-esp32
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e440aa725a | ||
|
|
26e47427b9 | ||
|
|
f76502cb59 | ||
|
|
6bb1ab7583 | ||
|
|
71799ed85c | ||
|
|
f553459121 | ||
|
|
39e9d49288 | ||
|
|
d84e27aae7 | ||
|
|
4884122cc1 | ||
|
|
d0ae468fac | ||
|
|
61cc1a236b | ||
|
|
13fd170a89 | ||
|
|
b2bcb96942 | ||
|
|
2c8f2f7d42 | ||
|
|
349267ef23 | ||
|
|
423ddcb287 | ||
|
|
0e28cd8a54 | ||
|
|
c165d6f7e1 | ||
|
|
a7ed9fd0cf | ||
|
|
4a7ef0b995 | ||
|
|
a1d150a0b4 | ||
|
|
e777287463 | ||
|
|
e29fde6fa3 | ||
|
|
d235ac4f52 | ||
|
|
0ef913635d | ||
|
|
ff7f396f9d | ||
|
|
7eb710d404 | ||
|
|
5a2e9ac87f | ||
|
|
45fa2ca389 | ||
|
|
d60446bc53 | ||
|
|
e23f6cf6d8 | ||
|
|
2fdcda30ea | ||
|
|
4b46e4890d | ||
|
|
236e21ffc2 | ||
|
|
9bc8be2f25 | ||
|
|
dd1139b169 |
@@ -4,7 +4,7 @@
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
set(PROJECT_VER "1.4.6")
|
||||
set(PROJECT_VER "1.5.0")
|
||||
|
||||
# Add this line to disable the specific warning
|
||||
add_compile_options(-Wno-missing-field-initializers)
|
||||
|
||||
@@ -60,6 +60,7 @@ Breadboard demonstration:
|
||||
- <a href="https://github.com/Xinyuan-LilyGO/T-Circle-S3" target="_blank" title="LILYGO T-Circle-S3">LILYGO T-Circle-S3</a>
|
||||
- <a href="https://oshwhub.com/tenclass01/xmini_c3" target="_blank" title="XiaGe Mini C3">XiaGe Mini C3</a>
|
||||
- <a href="https://oshwhub.com/movecall/moji-xiaozhi-ai-derivative-editi" target="_blank" title="Movecall Moji ESP32S3">Moji XiaoZhi AI Derivative Version</a>
|
||||
- <a href="https://www.seeedstudio.com/SenseCAP-Watcher-W1-A-p-5979.html" target="_blank" title="SenseCAP Watcher">SenseCAP Watcher</a>
|
||||
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<a href="docs/v1/lichuang-s3.jpg" target="_blank" title="LiChuang ESP32-S3 Development Board">
|
||||
@@ -92,6 +93,9 @@ Breadboard demonstration:
|
||||
<a href="docs/v1/movecall-moji-esp32s3.jpg" target="_blank" title="Moji">
|
||||
<img src="docs/v1/movecall-moji-esp32s3.jpg" width="240" />
|
||||
</a>
|
||||
<a href="docs/v1/sensecap_watcher.jpg" target="_blank" title="SenseCAP Watcher">
|
||||
<img src="docs/v1/sensecap_watcher.jpg" width="240" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## Firmware Section
|
||||
|
||||
@@ -60,6 +60,7 @@ Feishu ドキュメントチュートリアルをご覧ください:
|
||||
- <a href="https://github.com/Xinyuan-LilyGO/T-Circle-S3" target="_blank" title="LILYGO T-Circle-S3">LILYGO T-Circle-S3</a>
|
||||
- <a href="https://oshwhub.com/tenclass01/xmini_c3" target="_blank" title="XiaGe Mini C3">XiaGe Mini C3</a>
|
||||
- <a href="https://oshwhub.com/movecall/moji-xiaozhi-ai-derivative-editi" target="_blank" title="Movecall Moji ESP32S3">Moji シャオジー AI 派生版</a>
|
||||
- <a href="https://www.seeedstudio.com/SenseCAP-Watcher-W1-A-p-5979.html" target="_blank" title="SenseCAP Watcher">SenseCAP Watcher</a>
|
||||
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<a href="docs/v1/lichuang-s3.jpg" target="_blank" title="LiChuang ESP32-S3 開発ボード">
|
||||
@@ -89,6 +90,9 @@ Feishu ドキュメントチュートリアルをご覧ください:
|
||||
<a href="docs/v1/movecall-moji-esp32s3.jpg" target="_blank" title="Moji">
|
||||
<img src="docs/v1/movecall-moji-esp32s3.jpg" width="240" />
|
||||
</a>
|
||||
<a href="docs/v1/sensecap_watcher.jpg" target="_blank" title="SenseCAP Watcher">
|
||||
<img src="docs/v1/sensecap_watcher.jpg" width="240" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## ファームウェア部分
|
||||
|
||||
@@ -38,6 +38,8 @@ elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ML307)
|
||||
set(BOARD_TYPE "bread-compact-ml307")
|
||||
elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ESP32)
|
||||
set(BOARD_TYPE "bread-compact-esp32")
|
||||
elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ESP32_LCD)
|
||||
set(BOARD_TYPE "bread-compact-esp32-lcd")
|
||||
elseif(CONFIG_BOARD_TYPE_DF_K10)
|
||||
set(BOARD_TYPE "df-k10")
|
||||
elseif(CONFIG_BOARD_TYPE_ESP_BOX_3)
|
||||
@@ -76,6 +78,8 @@ elseif(CONFIG_BOARD_TYPE_ATOMS3_ECHO_BASE)
|
||||
set(BOARD_TYPE "atoms3-echo-base")
|
||||
elseif(CONFIG_BOARD_TYPE_ATOMS3R_ECHO_BASE)
|
||||
set(BOARD_TYPE "atoms3r-echo-base")
|
||||
elseif(CONFIG_BOARD_TYPE_ATOMS3R_CAM_M12_ECHO_BASE)
|
||||
set(BOARD_TYPE "atoms3r-cam-m12-echo-base")
|
||||
elseif(CONFIG_BOARD_TYPE_ATOMMATRIX_ECHO_BASE)
|
||||
set(BOARD_TYPE "atommatrix-echo-base")
|
||||
elseif(CONFIG_BOARD_TYPE_XMINI_C3)
|
||||
@@ -120,6 +124,8 @@ elseif(CONFIG_BOARD_TYPE_XINGZHI_Cube_1_54TFT_ML307)
|
||||
set(BOARD_TYPE "xingzhi-cube-1.54tft-ml307")
|
||||
elseif(CONFIG_BOARD_TYPE_SENSECAP_WATCHER)
|
||||
set(BOARD_TYPE "sensecap-watcher")
|
||||
elseif(CONFIG_BOARD_TYPE_ESP32_CGC)
|
||||
set(BOARD_TYPE "esp32-cgc")
|
||||
endif()
|
||||
file(GLOB BOARD_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE}/*.cc
|
||||
|
||||
@@ -61,7 +61,11 @@ choice BOARD_TYPE
|
||||
config BOARD_TYPE_BREAD_COMPACT_ML307
|
||||
bool "面包板新版接线(ML307 AT)"
|
||||
config BOARD_TYPE_BREAD_COMPACT_ESP32
|
||||
bool "面包板 ESP32 DevKit"
|
||||
bool "面包板(WiFi) ESP32 DevKit"
|
||||
config BOARD_TYPE_BREAD_COMPACT_ESP32_LCD
|
||||
bool "面包板(WiFi+ LCD) ESP32 DevKit"
|
||||
config BOARD_TYPE_ESP32_CGC
|
||||
bool "ESP32 CGC"
|
||||
config BOARD_TYPE_ESP_BOX_3
|
||||
bool "ESP BOX 3"
|
||||
config BOARD_TYPE_ESP_BOX
|
||||
@@ -100,6 +104,8 @@ choice BOARD_TYPE
|
||||
bool "AtomS3 + Echo Base"
|
||||
config BOARD_TYPE_ATOMS3R_ECHO_BASE
|
||||
bool "AtomS3R + Echo Base"
|
||||
config BOARD_TYPE_ATOMS3R_CAM_M12_ECHO_BASE
|
||||
bool "AtomS3R CAM/M12 + Echo Base"
|
||||
config BOARD_TYPE_ATOMMATRIX_ECHO_BASE
|
||||
bool "AtomMatrix + Echo Base"
|
||||
config BOARD_TYPE_XMINI_C3
|
||||
@@ -157,7 +163,7 @@ choice DISPLAY_OLED_TYPE
|
||||
endchoice
|
||||
|
||||
choice DISPLAY_LCD_TYPE
|
||||
depends on BOARD_TYPE_BREAD_COMPACT_WIFI_LCD
|
||||
depends on BOARD_TYPE_BREAD_COMPACT_WIFI_LCD || BOARD_TYPE_BREAD_COMPACT_ESP32_LCD || BOARD_TYPE_ESP32_CGC
|
||||
prompt "LCD Type"
|
||||
default LCD_ST7789_240X320
|
||||
help
|
||||
@@ -183,7 +189,9 @@ choice DISPLAY_LCD_TYPE
|
||||
config LCD_ST7735_128X128
|
||||
bool "ST7735, 分辨率128*128"
|
||||
config LCD_ST7796_320X480
|
||||
bool "ST7796, 分辨率320*480"
|
||||
bool "ST7796, 分辨率320*480 IPS"
|
||||
config LCD_ST7796_320X480_NO_IPS
|
||||
bool "ST7796, 分辨率320*480, 非IPS"
|
||||
config LCD_ILI9341_240X320
|
||||
bool "ILI9341, 分辨率240*320"
|
||||
config LCD_ILI9341_240X320_NO_IPS
|
||||
@@ -194,6 +202,24 @@ choice DISPLAY_LCD_TYPE
|
||||
bool "自定义屏幕参数"
|
||||
endchoice
|
||||
|
||||
choice DISPLAY_ESP32S3_KORVO2_V3
|
||||
depends on BOARD_TYPE_ESP32S3_KORVO2_V3
|
||||
prompt "ESP32S3_KORVO2_V3 LCD Type"
|
||||
default LCD_ST7789
|
||||
help
|
||||
屏幕类型选择
|
||||
config LCD_ST7789
|
||||
bool "ST7789, 分辨率240*280"
|
||||
config LCD_ILI9341
|
||||
bool "ILI9341, 分辨率240*320"
|
||||
endchoice
|
||||
|
||||
config USE_WECHAT_MESSAGE_STYLE
|
||||
bool "使用微信聊天界面风格"
|
||||
default n
|
||||
help
|
||||
使用微信聊天界面风格
|
||||
|
||||
config USE_AUDIO_PROCESSOR
|
||||
bool "启用音频降噪、增益处理"
|
||||
default y
|
||||
|
||||
@@ -354,7 +354,7 @@ void Application::Start() {
|
||||
Application* app = (Application*)arg;
|
||||
app->MainLoop();
|
||||
vTaskDelete(NULL);
|
||||
}, "main_loop", 4096 * 2, this, 3, nullptr);
|
||||
}, "main_loop", 4096 * 2, this, 4, nullptr);
|
||||
|
||||
/* Wait for the network to be ready */
|
||||
board.StartNetwork();
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
main/assets/ja-JP/0.p3
Normal file
BIN
main/assets/ja-JP/0.p3
Normal file
Binary file not shown.
BIN
main/assets/ja-JP/1.p3
Normal file
BIN
main/assets/ja-JP/1.p3
Normal file
Binary file not shown.
BIN
main/assets/ja-JP/2.p3
Normal file
BIN
main/assets/ja-JP/2.p3
Normal file
Binary file not shown.
BIN
main/assets/ja-JP/3.p3
Normal file
BIN
main/assets/ja-JP/3.p3
Normal file
Binary file not shown.
BIN
main/assets/ja-JP/4.p3
Normal file
BIN
main/assets/ja-JP/4.p3
Normal file
Binary file not shown.
BIN
main/assets/ja-JP/5.p3
Normal file
BIN
main/assets/ja-JP/5.p3
Normal file
Binary file not shown.
BIN
main/assets/ja-JP/6.p3
Normal file
BIN
main/assets/ja-JP/6.p3
Normal file
Binary file not shown.
BIN
main/assets/ja-JP/7.p3
Normal file
BIN
main/assets/ja-JP/7.p3
Normal file
Binary file not shown.
BIN
main/assets/ja-JP/8.p3
Normal file
BIN
main/assets/ja-JP/8.p3
Normal file
Binary file not shown.
BIN
main/assets/ja-JP/9.p3
Normal file
BIN
main/assets/ja-JP/9.p3
Normal file
Binary file not shown.
BIN
main/assets/ja-JP/activation.p3
Normal file
BIN
main/assets/ja-JP/activation.p3
Normal file
Binary file not shown.
BIN
main/assets/ja-JP/err_pin.p3
Normal file
BIN
main/assets/ja-JP/err_pin.p3
Normal file
Binary file not shown.
BIN
main/assets/ja-JP/err_reg.p3
Normal file
BIN
main/assets/ja-JP/err_reg.p3
Normal file
Binary file not shown.
BIN
main/assets/ja-JP/welcome.p3
Normal file
BIN
main/assets/ja-JP/welcome.p3
Normal file
Binary file not shown.
BIN
main/assets/ja-JP/wificonfig.p3
Normal file
BIN
main/assets/ja-JP/wificonfig.p3
Normal file
Binary file not shown.
@@ -53,7 +53,7 @@ void AudioProcessor::Initialize(int channels, bool reference) {
|
||||
auto this_ = (AudioProcessor*)arg;
|
||||
this_->AudioProcessorTask();
|
||||
vTaskDelete(NULL);
|
||||
}, "audio_communication", 4096 * 2, this, 2, NULL);
|
||||
}, "audio_communication", 4096 * 2, this, 3, NULL);
|
||||
}
|
||||
|
||||
AudioProcessor::~AudioProcessor() {
|
||||
|
||||
@@ -88,7 +88,7 @@ void WakeWordDetect::Initialize(int channels, bool reference) {
|
||||
auto this_ = (WakeWordDetect*)arg;
|
||||
this_->AudioDetectionTask();
|
||||
vTaskDelete(NULL);
|
||||
}, "audio_detection", 4096 * 2, this, 2, nullptr);
|
||||
}, "audio_detection", 4096 * 2, this, 3, nullptr);
|
||||
}
|
||||
|
||||
void WakeWordDetect::OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback) {
|
||||
|
||||
@@ -142,7 +142,7 @@ private:
|
||||
|
||||
esp_lcd_panel_reset(panel);
|
||||
esp_lcd_panel_init(panel);
|
||||
esp_lcd_panel_invert_color(panel, true);
|
||||
esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
|
||||
esp_lcd_panel_set_gap(panel, 0, 0);
|
||||
uint8_t data0[] = {0x00};
|
||||
uint8_t data1[] = {0x65};
|
||||
@@ -157,7 +157,11 @@ private:
|
||||
{
|
||||
.text_font = &font_puhui_20_4,
|
||||
.icon_font = &font_awesome_20_4,
|
||||
.emoji_font = font_emoji_64_init(),
|
||||
#if CONFIG_USE_WECHAT_MESSAGE_STYLE
|
||||
.emoji_font = font_emoji_32_init(),
|
||||
#else
|
||||
.emoji_font = DISPLAY_HEIGHT >= 240 ? font_emoji_64_init() : font_emoji_32_init(),
|
||||
#endif
|
||||
});
|
||||
}
|
||||
|
||||
@@ -180,6 +184,7 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -129,7 +129,7 @@ private:
|
||||
xl9555_->SetOutputState(2, 0);
|
||||
|
||||
esp_lcd_panel_init(panel);
|
||||
esp_lcd_panel_invert_color(panel, true);
|
||||
esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
|
||||
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,
|
||||
@@ -137,7 +137,11 @@ private:
|
||||
{
|
||||
.text_font = &font_puhui_20_4,
|
||||
.icon_font = &font_awesome_20_4,
|
||||
.emoji_font = font_emoji_64_init(),
|
||||
#if CONFIG_USE_WECHAT_MESSAGE_STYLE
|
||||
.emoji_font = font_emoji_32_init(),
|
||||
#else
|
||||
.emoji_font = DISPLAY_HEIGHT >= 240 ? font_emoji_64_init() : font_emoji_32_init(),
|
||||
#endif
|
||||
});
|
||||
}
|
||||
|
||||
@@ -145,6 +149,7 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
|
||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
|
||||
|
||||
@@ -201,6 +201,7 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
53
main/boards/atoms3r-cam-m12-echo-base/README.md
Normal file
53
main/boards/atoms3r-cam-m12-echo-base/README.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# AtomS3R CAM/M12 + Echo Base
|
||||
|
||||
## 简介
|
||||
|
||||
<div align="center">
|
||||
<a href="https://docs.m5stack.com/zh_CN/core/AtomS3R%20Cam"><b> AtomS3R CAM 产品主页 </b></a>
|
||||
|
|
||||
<a href="https://docs.m5stack.com/zh_CN/core/AtomS3R-M12"><b> AtomS3R M12 产品主页 </b></a>
|
||||
|
|
||||
<a href="https://docs.m5stack.com/zh_CN/atom/Atomic%20Echo%20Base"><b> Echo Base 产品主页 </b></a>
|
||||
</div>
|
||||
|
||||
AtomS3R CAM、AtomS3R M12 是 M5Stack 推出的基于 ESP32-S3-PICO-1-N8R8 的物联网可编程控制器,搭载了摄像头。Atomic Echo Base 是一款专为 M5 Atom 系列主机设计的语音识别底座,采用了 ES8311 单声道音频解码器、MEMS 麦克风和 NS4150B 功率放大器的集成方案。
|
||||
|
||||
两款开发版均**不带屏幕、不带额外按键**,需要使用语音唤醒。必要时,需要使用 `idf.py monitor` 查看 log 以确定运行状态。
|
||||
|
||||
## 配置、编译命令
|
||||
|
||||
**配置编译目标为 ESP32S3**
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32s3
|
||||
```
|
||||
|
||||
**打开 menuconfig 并配置**
|
||||
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
分别配置如下选项:
|
||||
|
||||
- `Xiaozhi Assistant` → `Board Type` → 选择 `AtomS3R CAM/M12 + Echo Base`
|
||||
- `Partition Table` → `Custom partition CSV file` → 删除原有内容,输入 `partitions_8M.csv`
|
||||
- `Serial flasher config` → `Flash size` → 选择 `8 MB`
|
||||
|
||||
按 `S` 保存,按 `Q` 退出。
|
||||
|
||||
**编译**
|
||||
|
||||
```bash
|
||||
idf.py build
|
||||
```
|
||||
|
||||
**烧录**
|
||||
|
||||
将 AtomS3R CAM/M12 连接到电脑,按住侧面 RESET 按键,直到 RESET 按键下方绿灯闪烁。
|
||||
|
||||
```bash
|
||||
idf.py flash
|
||||
```
|
||||
|
||||
烧录完毕后,按一下 RESET 按钮重启。
|
||||
@@ -0,0 +1,162 @@
|
||||
#include "wifi_board.h"
|
||||
#include "audio_codecs/es8311_audio_codec.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "i2c_device.h"
|
||||
#include "iot/thing_manager.h"
|
||||
#include "assets/lang_config.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#define TAG "AtomS3R M12+EchoBase"
|
||||
|
||||
#define PI4IOE_ADDR 0x43
|
||||
#define PI4IOE_REG_CTRL 0x00
|
||||
#define PI4IOE_REG_IO_PP 0x07
|
||||
#define PI4IOE_REG_IO_DIR 0x03
|
||||
#define PI4IOE_REG_IO_OUT 0x05
|
||||
#define PI4IOE_REG_IO_PULLUP 0x0D
|
||||
|
||||
class Pi4ioe : public I2cDevice {
|
||||
public:
|
||||
Pi4ioe(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
|
||||
WriteReg(PI4IOE_REG_IO_PP, 0x00); // Set to high-impedance
|
||||
WriteReg(PI4IOE_REG_IO_PULLUP, 0xFF); // Enable pull-up
|
||||
WriteReg(PI4IOE_REG_IO_DIR, 0x6E); // Set input=0, output=1
|
||||
WriteReg(PI4IOE_REG_IO_OUT, 0xFF); // Set outputs to 1
|
||||
}
|
||||
|
||||
void SetSpeakerMute(bool mute) {
|
||||
WriteReg(PI4IOE_REG_IO_OUT, mute ? 0x00 : 0xFF);
|
||||
}
|
||||
};
|
||||
|
||||
class AtomS3rCamM12EchoBaseBoard : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
Pi4ioe* pi4ioe_ = nullptr;
|
||||
bool is_echo_base_connected_ = false;
|
||||
|
||||
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, &i2c_bus_));
|
||||
}
|
||||
|
||||
void I2cDetect() {
|
||||
is_echo_base_connected_ = false;
|
||||
uint8_t echo_base_connected_flag = 0x00;
|
||||
uint8_t address;
|
||||
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
|
||||
for (int i = 0; i < 128; i += 16) {
|
||||
printf("%02x: ", i);
|
||||
for (int j = 0; j < 16; j++) {
|
||||
fflush(stdout);
|
||||
address = i + j;
|
||||
esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
|
||||
if (ret == ESP_OK) {
|
||||
printf("%02x ", address);
|
||||
if (address == 0x18) {
|
||||
echo_base_connected_flag |= 0xF0;
|
||||
} else if (address == 0x43) {
|
||||
echo_base_connected_flag |= 0x0F;
|
||||
}
|
||||
} else if (ret == ESP_ERR_TIMEOUT) {
|
||||
printf("UU ");
|
||||
} else {
|
||||
printf("-- ");
|
||||
}
|
||||
}
|
||||
printf("\r\n");
|
||||
}
|
||||
is_echo_base_connected_ = (echo_base_connected_flag == 0xFF);
|
||||
}
|
||||
|
||||
void CheckEchoBaseConnection() {
|
||||
if (is_echo_base_connected_) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
ESP_LOGE(TAG, "Atomic Echo Base is disconnected");
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
|
||||
// Rerun detection
|
||||
I2cDetect();
|
||||
if (is_echo_base_connected_) {
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
I2cDetect();
|
||||
if (is_echo_base_connected_) {
|
||||
ESP_LOGI(TAG, "Atomic Echo Base is reconnected");
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
esp_restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InitializePi4ioe() {
|
||||
ESP_LOGI(TAG, "Init PI4IOE");
|
||||
pi4ioe_ = new Pi4ioe(i2c_bus_, PI4IOE_ADDR);
|
||||
pi4ioe_->SetSpeakerMute(false);
|
||||
}
|
||||
|
||||
void EnableCameraPower() {
|
||||
gpio_reset_pin((gpio_num_t)18);
|
||||
gpio_set_direction((gpio_num_t)18, GPIO_MODE_OUTPUT);
|
||||
gpio_set_pull_mode((gpio_num_t)18, GPIO_PULLDOWN_ONLY);
|
||||
|
||||
ESP_LOGI(TAG, "Camera Power Enabled");
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
}
|
||||
|
||||
// 物联网初始化,添加对 AI 可见设备
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
}
|
||||
|
||||
public:
|
||||
AtomS3rCamM12EchoBaseBoard() {
|
||||
EnableCameraPower(); // IO18 还会控制指示灯
|
||||
InitializeI2c();
|
||||
I2cDetect();
|
||||
CheckEchoBaseConnection();
|
||||
InitializePi4ioe();
|
||||
InitializeIot();
|
||||
}
|
||||
|
||||
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_GPIO_PA,
|
||||
AUDIO_CODEC_ES8311_ADDR,
|
||||
false);
|
||||
return &audio_codec;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(AtomS3rCamM12EchoBaseBoard);
|
||||
51
main/boards/atoms3r-cam-m12-echo-base/config.h
Normal file
51
main/boards/atoms3r-cam-m12-echo-base/config.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
// AtomS3R M12+EchoBase Board configuration
|
||||
|
||||
#include <driver/gpio.h>
|
||||
|
||||
#define AUDIO_INPUT_REFERENCE true
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 24000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
|
||||
|
||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_6
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_5
|
||||
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_38
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_39
|
||||
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
|
||||
#define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC
|
||||
|
||||
#define BUILTIN_LED_GPIO GPIO_NUM_NC
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_41
|
||||
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
|
||||
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
|
||||
|
||||
#define CAMERA_PIN_PWDN (-1)
|
||||
#define CAMERA_PIN_RESET (-1)
|
||||
|
||||
#define CAMERA_PIN_VSYNC (10)
|
||||
#define CAMERA_PIN_HREF (14)
|
||||
#define CAMERA_PIN_PCLK (40)
|
||||
#define CAMERA_PIN_XCLK (21)
|
||||
|
||||
#define CAMERA_PIN_SIOD (12)
|
||||
#define CAMERA_PIN_SIOC ( 9)
|
||||
|
||||
#define CAMERA_PIN_D0 ( 3)
|
||||
#define CAMERA_PIN_D1 (42)
|
||||
#define CAMERA_PIN_D2 (46)
|
||||
#define CAMERA_PIN_D3 (48)
|
||||
#define CAMERA_PIN_D4 ( 4)
|
||||
#define CAMERA_PIN_D5 (17)
|
||||
#define CAMERA_PIN_D6 (11)
|
||||
#define CAMERA_PIN_D7 (13)
|
||||
|
||||
#define CAMERA_XCLK_FREQ (20000000)
|
||||
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
12
main/boards/atoms3r-cam-m12-echo-base/config.json
Normal file
12
main/boards/atoms3r-cam-m12-echo-base/config.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "atoms3r-cam-m12-echo-base",
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
|
||||
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions_8M.csv\""
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -277,7 +277,7 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
276
main/boards/bread-compact-esp32-lcd/config.h
Normal file
276
main/boards/bread-compact-esp32-lcd/config.h
Normal file
@@ -0,0 +1,276 @@
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
#include <driver/gpio.h>
|
||||
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 16000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
|
||||
|
||||
// 如果使用 Duplex I2S 模式,请注释下面一行
|
||||
#define AUDIO_I2S_METHOD_SIMPLEX
|
||||
|
||||
#ifdef AUDIO_I2S_METHOD_SIMPLEX
|
||||
|
||||
#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_25
|
||||
#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_26
|
||||
#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_32
|
||||
|
||||
#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_33
|
||||
#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_14
|
||||
#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_27
|
||||
|
||||
#else
|
||||
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_0
|
||||
#define TOUCH_BUTTON_GPIO GPIO_NUM_5
|
||||
#define ASR_BUTTON_GPIO GPIO_NUM_19
|
||||
#define BUILTIN_LED_GPIO GPIO_NUM_2
|
||||
|
||||
|
||||
#ifdef CONFIG_LCD_ST7789_240X240_7PIN
|
||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_22
|
||||
#define DISPLAY_CS_PIN GPIO_NUM_NC
|
||||
#else
|
||||
#define DISPLAY_CS_PIN GPIO_NUM_22
|
||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_23
|
||||
#endif
|
||||
|
||||
#define DISPLAY_MOSI_PIN GPIO_NUM_4
|
||||
#define DISPLAY_CLK_PIN GPIO_NUM_15
|
||||
#define DISPLAY_DC_PIN GPIO_NUM_21
|
||||
#define DISPLAY_RST_PIN GPIO_NUM_18
|
||||
|
||||
|
||||
#ifdef CONFIG_LCD_ST7789_240X320
|
||||
#define LCD_TYPE_ST7789_SERIAL
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 320
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#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
|
||||
|
||||
#ifdef CONFIG_LCD_ST7789_240X320_NO_IPS
|
||||
#define LCD_TYPE_ST7789_SERIAL
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 320
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#define DISPLAY_INVERT_COLOR false
|
||||
#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
|
||||
|
||||
#ifdef CONFIG_LCD_ST7789_170X320
|
||||
#define LCD_TYPE_ST7789_SERIAL
|
||||
#define DISPLAY_WIDTH 170
|
||||
#define DISPLAY_HEIGHT 320
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#define DISPLAY_INVERT_COLOR true
|
||||
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
|
||||
#define DISPLAY_OFFSET_X 35
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
#define DISPLAY_SPI_MODE 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LCD_ST7789_172X320
|
||||
#define LCD_TYPE_ST7789_SERIAL
|
||||
#define DISPLAY_WIDTH 172
|
||||
#define DISPLAY_HEIGHT 320
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#define DISPLAY_INVERT_COLOR true
|
||||
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
|
||||
#define DISPLAY_OFFSET_X 34
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
#define DISPLAY_SPI_MODE 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LCD_ST7789_240X280
|
||||
#define LCD_TYPE_ST7789_SERIAL
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 280
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#define DISPLAY_INVERT_COLOR true
|
||||
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 20
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
#define DISPLAY_SPI_MODE 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LCD_ST7789_240X240
|
||||
#define LCD_TYPE_ST7789_SERIAL
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 240
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#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
|
||||
|
||||
#ifdef CONFIG_LCD_ST7789_240X240_7PIN
|
||||
#define LCD_TYPE_ST7789_SERIAL
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 240
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#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 2
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LCD_ST7789_240X135
|
||||
#define LCD_TYPE_ST7789_SERIAL
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 135
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY true
|
||||
#define DISPLAY_INVERT_COLOR true
|
||||
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
|
||||
#define DISPLAY_OFFSET_X 40
|
||||
#define DISPLAY_OFFSET_Y 53
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
#define DISPLAY_SPI_MODE 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LCD_ST7735_128X160
|
||||
#define LCD_TYPE_ST7789_SERIAL
|
||||
#define DISPLAY_WIDTH 128
|
||||
#define DISPLAY_HEIGHT 160
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y true
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#define DISPLAY_INVERT_COLOR false
|
||||
#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
|
||||
|
||||
#ifdef CONFIG_LCD_ST7735_128X128
|
||||
#define LCD_TYPE_ST7789_SERIAL
|
||||
#define DISPLAY_WIDTH 128
|
||||
#define DISPLAY_HEIGHT 128
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y true
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#define DISPLAY_INVERT_COLOR false
|
||||
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 32
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
#define DISPLAY_SPI_MODE 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LCD_ST7796_320X480
|
||||
#define LCD_TYPE_ST7789_SERIAL
|
||||
#define DISPLAY_WIDTH 320
|
||||
#define DISPLAY_HEIGHT 480
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#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
|
||||
|
||||
#ifdef CONFIG_LCD_ILI9341_240X320
|
||||
#define LCD_TYPE_ILI9341_SERIAL
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 320
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#define DISPLAY_INVERT_COLOR true
|
||||
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
#define DISPLAY_SPI_MODE 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LCD_ILI9341_240X320_NO_IPS
|
||||
#define LCD_TYPE_ILI9341_SERIAL
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 320
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#define DISPLAY_INVERT_COLOR false
|
||||
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
#define DISPLAY_SPI_MODE 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LCD_GC9A01_240X240
|
||||
#define LCD_TYPE_GC9A01_SERIAL
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 240
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#define DISPLAY_INVERT_COLOR true
|
||||
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
#define DISPLAY_SPI_MODE 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LCD_CUSTOM
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 320
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#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
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
13
main/boards/bread-compact-esp32-lcd/config.json
Normal file
13
main/boards/bread-compact-esp32-lcd/config.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"target": "esp32",
|
||||
"builds": [
|
||||
{
|
||||
"name": "bread-compact-esp32-lcd",
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y",
|
||||
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions_4M.csv\"",
|
||||
"LCD_ST7789_240X240_7PIN=y"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
227
main/boards/bread-compact-esp32-lcd/esp32_bread_board_lcd.cc
Normal file
227
main/boards/bread-compact-esp32-lcd/esp32_bread_board_lcd.cc
Normal file
@@ -0,0 +1,227 @@
|
||||
#include "wifi_board.h"
|
||||
#include "audio_codecs/no_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "system_reset.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "iot/thing_manager.h"
|
||||
#include "led/single_led.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
#include <driver/spi_common.h>
|
||||
|
||||
#if defined(LCD_TYPE_ILI9341_SERIAL)
|
||||
#include "esp_lcd_ili9341.h"
|
||||
#endif
|
||||
|
||||
#if defined(LCD_TYPE_GC9A01_SERIAL)
|
||||
#include "esp_lcd_gc9a01.h"
|
||||
static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = {
|
||||
// {cmd, { data }, data_size, delay_ms}
|
||||
{0xfe, (uint8_t[]){0x00}, 0, 0},
|
||||
{0xef, (uint8_t[]){0x00}, 0, 0},
|
||||
{0xb0, (uint8_t[]){0xc0}, 1, 0},
|
||||
{0xb1, (uint8_t[]){0x80}, 1, 0},
|
||||
{0xb2, (uint8_t[]){0x27}, 1, 0},
|
||||
{0xb3, (uint8_t[]){0x13}, 1, 0},
|
||||
{0xb6, (uint8_t[]){0x19}, 1, 0},
|
||||
{0xb7, (uint8_t[]){0x05}, 1, 0},
|
||||
{0xac, (uint8_t[]){0xc8}, 1, 0},
|
||||
{0xab, (uint8_t[]){0x0f}, 1, 0},
|
||||
{0x3a, (uint8_t[]){0x05}, 1, 0},
|
||||
{0xb4, (uint8_t[]){0x04}, 1, 0},
|
||||
{0xa8, (uint8_t[]){0x08}, 1, 0},
|
||||
{0xb8, (uint8_t[]){0x08}, 1, 0},
|
||||
{0xea, (uint8_t[]){0x02}, 1, 0},
|
||||
{0xe8, (uint8_t[]){0x2A}, 1, 0},
|
||||
{0xe9, (uint8_t[]){0x47}, 1, 0},
|
||||
{0xe7, (uint8_t[]){0x5f}, 1, 0},
|
||||
{0xc6, (uint8_t[]){0x21}, 1, 0},
|
||||
{0xc7, (uint8_t[]){0x15}, 1, 0},
|
||||
{0xf0,
|
||||
(uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C,
|
||||
0x04, 0x12, 0x14, 0x1f},
|
||||
14, 0},
|
||||
{0xf1,
|
||||
(uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D,
|
||||
0x0C, 0x1A, 0x14, 0x1E},
|
||||
14, 0},
|
||||
{0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0},
|
||||
{0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0},
|
||||
};
|
||||
#endif
|
||||
|
||||
#define TAG "ESP32-LCD-MarsbearSupport"
|
||||
|
||||
LV_FONT_DECLARE(font_puhui_14_1);
|
||||
LV_FONT_DECLARE(font_awesome_14_1);
|
||||
|
||||
class CompactWifiBoardLCD : public WifiBoard {
|
||||
private:
|
||||
Button boot_button_;
|
||||
Button touch_button_;
|
||||
Button asr_button_;
|
||||
|
||||
LcdDisplay* display_;
|
||||
|
||||
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 * 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;
|
||||
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(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_RST_PIN;
|
||||
panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
|
||||
panel_config.bits_per_pixel = 16;
|
||||
#if defined(LCD_TYPE_ILI9341_SERIAL)
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
|
||||
#elif defined(LCD_TYPE_GC9A01_SERIAL)
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel));
|
||||
gc9a01_vendor_config_t gc9107_vendor_config = {
|
||||
.init_cmds = gc9107_lcd_init_cmds,
|
||||
.init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t),
|
||||
};
|
||||
#else
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
|
||||
#endif
|
||||
|
||||
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);
|
||||
#ifdef LCD_TYPE_GC9A01_SERIAL
|
||||
panel_config.vendor_config = &gc9107_vendor_config;
|
||||
#endif
|
||||
display_ = new SpiLcdDisplay(panel_io, panel,
|
||||
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY,
|
||||
{
|
||||
.text_font = &font_puhui_14_1,
|
||||
.icon_font = &font_awesome_14_1,
|
||||
.emoji_font = font_emoji_32_init(),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
void InitializeButtons() {
|
||||
|
||||
// 配置 GPIO
|
||||
gpio_config_t io_conf = {
|
||||
.pin_bit_mask = 1ULL << BUILTIN_LED_GPIO, // 设置需要配置的 GPIO 引脚
|
||||
.mode = GPIO_MODE_OUTPUT, // 设置为输出模式
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE, // 禁用上拉
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE, // 禁用下拉
|
||||
.intr_type = GPIO_INTR_DISABLE // 禁用中断
|
||||
};
|
||||
gpio_config(&io_conf); // 应用配置
|
||||
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
}
|
||||
gpio_set_level(BUILTIN_LED_GPIO, 1);
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
asr_button_.OnClick([this]() {
|
||||
std::string wake_word="你好小智";
|
||||
Application::GetInstance().WakeWordInvoke(wake_word);
|
||||
});
|
||||
|
||||
touch_button_.OnPressDown([this]() {
|
||||
gpio_set_level(BUILTIN_LED_GPIO, 1);
|
||||
Application::GetInstance().StartListening();
|
||||
});
|
||||
|
||||
touch_button_.OnPressUp([this]() {
|
||||
gpio_set_level(BUILTIN_LED_GPIO, 0);
|
||||
Application::GetInstance().StopListening();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// 物联网初始化,添加对 AI 可见设备
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
CompactWifiBoardLCD() :
|
||||
boot_button_(BOOT_BUTTON_GPIO), touch_button_(TOUCH_BUTTON_GPIO), asr_button_(ASR_BUTTON_GPIO) {
|
||||
InitializeSpi();
|
||||
InitializeLcdDisplay();
|
||||
InitializeButtons();
|
||||
InitializeIot();
|
||||
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 {
|
||||
#ifdef AUDIO_I2S_METHOD_SIMPLEX
|
||||
static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
|
||||
AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
|
||||
#else
|
||||
static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
|
||||
AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
|
||||
#endif
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(CompactWifiBoardLCD);
|
||||
@@ -127,7 +127,11 @@ private:
|
||||
{
|
||||
.text_font = &font_puhui_16_4,
|
||||
.icon_font = &font_awesome_16_4,
|
||||
#if CONFIG_USE_WECHAT_MESSAGE_STYLE
|
||||
.emoji_font = font_emoji_32_init(),
|
||||
#else
|
||||
.emoji_font = DISPLAY_HEIGHT >= 240 ? font_emoji_64_init() : font_emoji_32_init(),
|
||||
#endif
|
||||
});
|
||||
}
|
||||
|
||||
@@ -147,10 +151,8 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
thing_manager.AddThing(iot::CreateThing("Lamp"));
|
||||
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -197,11 +197,26 @@
|
||||
#define LCD_TYPE_ST7789_SERIAL
|
||||
#define DISPLAY_WIDTH 320
|
||||
#define DISPLAY_HEIGHT 480
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#define DISPLAY_INVERT_COLOR true
|
||||
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
|
||||
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
#define DISPLAY_SPI_MODE 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LCD_ST7796_320X480_NO_IPS
|
||||
#define LCD_TYPE_ST7789_SERIAL
|
||||
#define DISPLAY_WIDTH 320
|
||||
#define DISPLAY_HEIGHT 480
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#define DISPLAY_INVERT_COLOR false
|
||||
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
|
||||
@@ -78,7 +78,7 @@ PwmBacklight::PwmBacklight(gpio_num_t pin, bool output_invert) : Backlight() {
|
||||
.speed_mode = LEDC_LOW_SPEED_MODE,
|
||||
.duty_resolution = LEDC_TIMER_10_BIT,
|
||||
.timer_num = LEDC_TIMER_0,
|
||||
.freq_hz = 20000, //背光pwm频率需要高一点,防止电感啸叫
|
||||
.freq_hz = 25000, //背光pwm频率需要高一点,防止电感啸叫
|
||||
.clk_cfg = LEDC_AUTO_CLK,
|
||||
.deconfigure = false
|
||||
};
|
||||
|
||||
@@ -44,7 +44,7 @@ std::string Board::GenerateUuid() {
|
||||
return std::string(uuid_str);
|
||||
}
|
||||
|
||||
bool Board::GetBatteryLevel(int &level, bool& charging) {
|
||||
bool Board::GetBatteryLevel(int &level, bool& charging, bool& discharging) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ public:
|
||||
virtual Udp* CreateUdp() = 0;
|
||||
virtual void StartNetwork() = 0;
|
||||
virtual const char* GetNetworkStateIcon() = 0;
|
||||
virtual bool GetBatteryLevel(int &level, bool& charging);
|
||||
virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging);
|
||||
virtual std::string GetJson();
|
||||
virtual void SetPowerSaveMode(bool enabled) = 0;
|
||||
};
|
||||
|
||||
@@ -195,7 +195,7 @@ private:
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, false));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true));
|
||||
@@ -252,4 +252,4 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(Df_K10Board);
|
||||
DECLARE_BOARD(Df_K10Board);
|
||||
|
||||
@@ -176,7 +176,7 @@ void K10AudioCodec::EnableInput(bool enable) {
|
||||
fs.channel_mask |= ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1);
|
||||
}
|
||||
ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
|
||||
// ESP_ERROR_CHECK(esp_codec_dev_set_in_channel_gain(input_dev_, ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), 40.0));
|
||||
ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, 37.5)); //麦克风增益解决收音太小的问题
|
||||
} else {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
|
||||
}
|
||||
@@ -223,4 +223,4 @@ int K10AudioCodec::Write(const int16_t* data, int samples) {
|
||||
return bytes_written / sizeof(int32_t);
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,17 +7,17 @@
|
||||
#include "config.h"
|
||||
#include "iot/thing_manager.h"
|
||||
#include "led/single_led.h"
|
||||
#include "power_manager.h"
|
||||
#include "power_save_timer.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <driver/spi_common.h>
|
||||
#include <driver/rtc_io.h>
|
||||
#include <esp_sleep.h>
|
||||
|
||||
#if defined(LCD_ILI9341_240X320) || defined(LCD_ILI9341_240X320_NO_IPS)
|
||||
#include "esp_lcd_ili9341.h"
|
||||
#endif
|
||||
|
||||
#define TAG "DuChatX"
|
||||
|
||||
LV_FONT_DECLARE(font_puhui_16_4);
|
||||
@@ -25,10 +25,49 @@ LV_FONT_DECLARE(font_awesome_16_4);
|
||||
|
||||
class DuChatX : public WifiBoard {
|
||||
private:
|
||||
|
||||
Button boot_button_;
|
||||
LcdDisplay* display_;
|
||||
LcdDisplay *display_;
|
||||
PowerManager *power_manager_;
|
||||
PowerSaveTimer *power_save_timer_;
|
||||
esp_lcd_panel_handle_t panel_ = nullptr;
|
||||
|
||||
void InitializePowerManager() {
|
||||
power_manager_ = new PowerManager(GPIO_NUM_6);
|
||||
power_manager_->OnChargingStatusChanged([this](bool is_charging) {
|
||||
if (is_charging) {
|
||||
power_save_timer_->SetEnabled(false);
|
||||
} else {
|
||||
power_save_timer_->SetEnabled(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
void InitializePowerSaveTimer() {
|
||||
rtc_gpio_init(GPIO_NUM_1);
|
||||
rtc_gpio_set_direction(GPIO_NUM_1, RTC_GPIO_MODE_OUTPUT_ONLY);
|
||||
rtc_gpio_set_level(GPIO_NUM_1, 1);
|
||||
|
||||
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
|
||||
power_save_timer_->OnEnterSleepMode([this]() {
|
||||
ESP_LOGI(TAG, "Enabling sleep mode");
|
||||
display_->SetChatMessage("system", "");
|
||||
display_->SetEmotion("sleepy");
|
||||
GetBacklight()->SetBrightness(1);
|
||||
});
|
||||
power_save_timer_->OnExitSleepMode([this]() {
|
||||
display_->SetChatMessage("system", "");
|
||||
display_->SetEmotion("neutral");
|
||||
GetBacklight()->RestoreBrightness();
|
||||
});
|
||||
power_save_timer_->OnShutdownRequest([this]() {
|
||||
ESP_LOGI(TAG, "Shutting down");
|
||||
rtc_gpio_set_level(GPIO_NUM_1, 0);
|
||||
// 启用保持功能,确保睡眠期间电平不变
|
||||
rtc_gpio_hold_en(GPIO_NUM_1);
|
||||
esp_lcd_panel_disp_on_off(panel_, false); //关闭显示
|
||||
esp_deep_sleep_start();
|
||||
});
|
||||
power_save_timer_->SetEnabled(true);
|
||||
}
|
||||
void InitializeSpi() {
|
||||
spi_bus_config_t buscfg = {};
|
||||
buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
|
||||
@@ -42,9 +81,8 @@ private:
|
||||
|
||||
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_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;
|
||||
@@ -61,76 +99,87 @@ private:
|
||||
panel_config.reset_gpio_num = DISPLAY_RST_PIN;
|
||||
panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
|
||||
panel_config.bits_per_pixel = 16;
|
||||
#if defined(LCD_ILI9341_240X320) || defined(LCD_ILI9341_240X320_NO_IPS)
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
|
||||
#else
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
|
||||
#endif
|
||||
|
||||
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,
|
||||
{
|
||||
.text_font = &font_puhui_16_4,
|
||||
.icon_font = &font_awesome_16_4,
|
||||
.emoji_font = DISPLAY_HEIGHT >= 240 ? font_emoji_64_init() : font_emoji_32_init(),
|
||||
});
|
||||
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,
|
||||
{
|
||||
.text_font = &font_puhui_16_4,
|
||||
.icon_font = &font_awesome_16_4,
|
||||
.emoji_font = DISPLAY_HEIGHT >= 240 ? font_emoji_64_init() : font_emoji_32_init(),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
power_save_timer_->WakeUp();
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
}
|
||||
app.ToggleChatState();
|
||||
app.ToggleChatState();
|
||||
});
|
||||
}
|
||||
|
||||
// 物联网初始化,添加对 AI 可见设备
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
auto &thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
thing_manager.AddThing(iot::CreateThing("Battery"));
|
||||
}
|
||||
|
||||
public:
|
||||
DuChatX() :
|
||||
boot_button_(BOOT_BUTTON_GPIO) {
|
||||
DuChatX() : boot_button_(BOOT_BUTTON_GPIO) {
|
||||
InitializeSpi();
|
||||
InitializeLcdDisplay();
|
||||
InitializeButtons();
|
||||
InitializeIot();
|
||||
GetBacklight()->RestoreBrightness();
|
||||
InitializePowerSaveTimer();
|
||||
InitializePowerManager();
|
||||
}
|
||||
|
||||
virtual Led* GetLed() override {
|
||||
virtual Led *GetLed() override {
|
||||
static SingleLed led(BUILTIN_LED_GPIO);
|
||||
return &led;
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
virtual AudioCodec *GetAudioCodec() override {
|
||||
static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
|
||||
AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
virtual Display *GetDisplay() override {
|
||||
return display_;
|
||||
}
|
||||
|
||||
virtual Backlight* GetBacklight() override {
|
||||
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 {
|
||||
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(DuChatX);
|
||||
|
||||
186
main/boards/du-chatx/power_manager.h
Normal file
186
main/boards/du-chatx/power_manager.h
Normal file
@@ -0,0 +1,186 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include <esp_timer.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <esp_adc/adc_oneshot.h>
|
||||
|
||||
|
||||
class PowerManager {
|
||||
private:
|
||||
esp_timer_handle_t timer_handle_;
|
||||
std::function<void(bool)> on_charging_status_changed_;
|
||||
std::function<void(bool)> on_low_battery_status_changed_;
|
||||
|
||||
gpio_num_t charging_pin_ = GPIO_NUM_NC;
|
||||
std::vector<uint16_t> adc_values_;
|
||||
uint32_t battery_level_ = 0;
|
||||
bool is_charging_ = false;
|
||||
bool is_low_battery_ = false;
|
||||
int ticks_ = 0;
|
||||
const int kBatteryAdcInterval = 60;
|
||||
const int kBatteryAdcDataCount = 3;
|
||||
const int kLowBatteryLevel = 20;
|
||||
|
||||
adc_oneshot_unit_handle_t adc_handle_;
|
||||
|
||||
void CheckBatteryStatus() {
|
||||
// Get charging status
|
||||
bool new_charging_status = gpio_get_level(charging_pin_) == 1;
|
||||
if (new_charging_status != is_charging_) {
|
||||
is_charging_ = new_charging_status;
|
||||
if (on_charging_status_changed_) {
|
||||
on_charging_status_changed_(is_charging_);
|
||||
}
|
||||
ReadBatteryAdcData();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果电池电量数据不足,则读取电池电量数据
|
||||
if (adc_values_.size() < kBatteryAdcDataCount) {
|
||||
ReadBatteryAdcData();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据
|
||||
ticks_++;
|
||||
if (ticks_ % kBatteryAdcInterval == 0) {
|
||||
ReadBatteryAdcData();
|
||||
}
|
||||
}
|
||||
|
||||
void ReadBatteryAdcData() {
|
||||
int adc_value;
|
||||
ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_5, &adc_value));
|
||||
|
||||
// 将 ADC 值添加到队列中
|
||||
adc_values_.push_back(adc_value);
|
||||
if (adc_values_.size() > kBatteryAdcDataCount) {
|
||||
adc_values_.erase(adc_values_.begin());
|
||||
}
|
||||
uint32_t average_adc = 0;
|
||||
for (auto value : adc_values_) {
|
||||
average_adc += value;
|
||||
}
|
||||
average_adc /= adc_values_.size();
|
||||
|
||||
// 定义电池电量区间
|
||||
const struct {
|
||||
uint16_t adc;
|
||||
uint8_t level;
|
||||
} levels[] = {
|
||||
{1120, 0},
|
||||
{1140, 20},
|
||||
{1160, 40},
|
||||
{1170, 60},
|
||||
{1190, 80},
|
||||
{1217, 100}
|
||||
};
|
||||
|
||||
// 低于最低值时
|
||||
if (average_adc < levels[0].adc) {
|
||||
battery_level_ = 0;
|
||||
}
|
||||
// 高于最高值时
|
||||
else if (average_adc >= levels[5].adc) {
|
||||
battery_level_ = 100;
|
||||
} else {
|
||||
// 线性插值计算中间值
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
|
||||
float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
|
||||
battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check low battery status
|
||||
if (adc_values_.size() >= kBatteryAdcDataCount) {
|
||||
bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
|
||||
if (new_low_battery_status != is_low_battery_) {
|
||||
is_low_battery_ = new_low_battery_status;
|
||||
if (on_low_battery_status_changed_) {
|
||||
on_low_battery_status_changed_(is_low_battery_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
|
||||
}
|
||||
|
||||
public:
|
||||
PowerManager(gpio_num_t pin) : charging_pin_(pin) {
|
||||
// 初始化充电引脚
|
||||
gpio_config_t io_conf = {};
|
||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||
io_conf.mode = GPIO_MODE_INPUT;
|
||||
io_conf.pin_bit_mask = (1ULL << charging_pin_);
|
||||
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
|
||||
gpio_config(&io_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));
|
||||
|
||||
// 初始化 ADC
|
||||
adc_oneshot_unit_init_cfg_t init_config = {
|
||||
.unit_id = ADC_UNIT_1,
|
||||
.ulp_mode = ADC_ULP_MODE_DISABLE,
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
|
||||
|
||||
adc_oneshot_chan_cfg_t chan_config = {
|
||||
.atten = ADC_ATTEN_DB_12,
|
||||
.bitwidth = ADC_BITWIDTH_12,
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_5, &chan_config));
|
||||
}
|
||||
|
||||
~PowerManager() {
|
||||
if (timer_handle_) {
|
||||
esp_timer_stop(timer_handle_);
|
||||
esp_timer_delete(timer_handle_);
|
||||
}
|
||||
if (adc_handle_) {
|
||||
adc_oneshot_del_unit(adc_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsCharging() {
|
||||
// 如果电量已经满了,则不再显示充电中
|
||||
if (battery_level_ == 100) {
|
||||
return false;
|
||||
}
|
||||
return is_charging_;
|
||||
}
|
||||
|
||||
bool IsDischarging() {
|
||||
// 没有区分充电和放电,所以直接返回相反状态
|
||||
return !is_charging_;
|
||||
}
|
||||
|
||||
uint8_t GetBatteryLevel() {
|
||||
return battery_level_;
|
||||
}
|
||||
|
||||
void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
|
||||
on_low_battery_status_changed_ = callback;
|
||||
}
|
||||
|
||||
void OnChargingStatusChanged(std::function<void(bool)> callback) {
|
||||
on_charging_status_changed_ = callback;
|
||||
}
|
||||
};
|
||||
@@ -117,7 +117,7 @@ private:
|
||||
|
||||
esp_lcd_panel_reset(panel);
|
||||
esp_lcd_panel_init(panel);
|
||||
esp_lcd_panel_invert_color(panel, false);
|
||||
esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
|
||||
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
|
||||
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
esp_lcd_panel_disp_on_off(panel, true);
|
||||
@@ -134,7 +134,7 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -196,8 +196,8 @@ void BoxAudioCodecLite::EnableInput(bool enable) {
|
||||
fs.channel_mask |= ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1);
|
||||
}
|
||||
ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
|
||||
// 不支持设置gain,暂时注释
|
||||
// ESP_ERROR_CHECK(esp_codec_dev_set_in_channel_gain(input_dev_, ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), 40.0));
|
||||
// 麦克风增益解决收音太小的问题
|
||||
ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, 37.5));
|
||||
} else {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "wifi_board.h"
|
||||
#include "audio_codecs/box_audio_codec_lite.h"
|
||||
#include "box_audio_codec_lite.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "esp_lcd_ili9341.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
@@ -203,7 +203,7 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -117,7 +117,7 @@ private:
|
||||
|
||||
esp_lcd_panel_reset(panel);
|
||||
esp_lcd_panel_init(panel);
|
||||
esp_lcd_panel_invert_color(panel, false);
|
||||
esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
|
||||
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
|
||||
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
esp_lcd_panel_disp_on_off(panel, true);
|
||||
@@ -134,7 +134,7 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -126,7 +126,7 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
thing_manager.AddThing(iot::CreateThing("Chassis"));
|
||||
}
|
||||
|
||||
|
||||
46
main/boards/esp32-cgc/README.md
Normal file
46
main/boards/esp32-cgc/README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# 主板开源地址:
|
||||
[https://oshwhub.com/wdmomo/esp32-xiaozhi-kidpcb](https://oshwhub.com/wdmomo/esp32-xiaozhi-kidpcb)
|
||||
|
||||
# 编译配置命令
|
||||
|
||||
**配置编译目标为 ESP32:**
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32
|
||||
```
|
||||
|
||||
**打开 menuconfig:**
|
||||
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
**选择板子:**
|
||||
|
||||
```
|
||||
Xiaozhi Assistant -> Board Type -> ESP32 CGC
|
||||
```
|
||||
|
||||
**选择屏幕类型:**
|
||||
|
||||
```
|
||||
Xiaozhi Assistant -> LCD Type -> "ST7735, 分辨率128*128"
|
||||
```
|
||||
|
||||
**修改 flash 大小:**
|
||||
|
||||
```
|
||||
Serial flasher config -> Flash size -> 4 MB
|
||||
```
|
||||
|
||||
**修改分区表:**
|
||||
|
||||
```
|
||||
Partition Table -> Custom partition CSV file -> partitions_4M.csv
|
||||
```
|
||||
|
||||
**编译:**
|
||||
|
||||
```bash
|
||||
idf.py build
|
||||
```
|
||||
268
main/boards/esp32-cgc/config.h
Normal file
268
main/boards/esp32-cgc/config.h
Normal file
@@ -0,0 +1,268 @@
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
#include <driver/gpio.h>
|
||||
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 16000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
|
||||
|
||||
// 如果使用 Duplex I2S 模式,请注释下面一行
|
||||
#define AUDIO_I2S_METHOD_SIMPLEX
|
||||
|
||||
#ifdef AUDIO_I2S_METHOD_SIMPLEX
|
||||
|
||||
#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_25
|
||||
#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_26
|
||||
#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_32
|
||||
|
||||
#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_33
|
||||
#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_14
|
||||
#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_27
|
||||
|
||||
#else
|
||||
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
|
||||
|
||||
#endif
|
||||
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_0
|
||||
#define ASR_BUTTON_GPIO GPIO_NUM_13
|
||||
|
||||
#define DISPLAY_SDA_PIN GPIO_NUM_NC
|
||||
#define DISPLAY_SCL_PIN GPIO_NUM_NC
|
||||
|
||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_4
|
||||
#define DISPLAY_SCLK_PIN GPIO_NUM_18
|
||||
#define DISPLAY_MOSI_PIN GPIO_NUM_23
|
||||
#define DISPLAY_CS_PIN GPIO_NUM_5
|
||||
#define DISPLAY_DC_PIN GPIO_NUM_2
|
||||
#define DISPLAY_RESET_PIN GPIO_NUM_NC
|
||||
|
||||
#define DISPLAY_SPI_SCLK_HZ (20 * 1000 * 1000)
|
||||
|
||||
#ifdef CONFIG_LCD_ST7789_240X320
|
||||
#define LCD_TYPE_ST7789_SERIAL
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 320
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#define DISPLAY_INVERT_COLOR true
|
||||
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LCD_ST7789_240X320_NO_IPS
|
||||
#define LCD_TYPE_ST7789_SERIAL
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 320
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#define DISPLAY_INVERT_COLOR false
|
||||
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
#define DISPLAY_SPI_MODE 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LCD_ST7789_170X320
|
||||
#define LCD_TYPE_ST7789_SERIAL
|
||||
#define DISPLAY_WIDTH 170
|
||||
#define DISPLAY_HEIGHT 320
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#define DISPLAY_INVERT_COLOR true
|
||||
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
|
||||
#define DISPLAY_OFFSET_X 35
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
#define DISPLAY_SPI_MODE 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LCD_ST7789_172X320
|
||||
#define LCD_TYPE_ST7789_SERIAL
|
||||
#define DISPLAY_WIDTH 172
|
||||
#define DISPLAY_HEIGHT 320
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#define DISPLAY_INVERT_COLOR true
|
||||
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
|
||||
#define DISPLAY_OFFSET_X 34
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
#define DISPLAY_SPI_MODE 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LCD_ST7789_240X280
|
||||
#define LCD_TYPE_ST7789_SERIAL
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 280
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#define DISPLAY_INVERT_COLOR true
|
||||
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 20
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
#define DISPLAY_SPI_MODE 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LCD_ST7789_240X240
|
||||
#define LCD_TYPE_ST7789_SERIAL
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 240
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#define DISPLAY_INVERT_COLOR true
|
||||
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
#define DISPLAY_SPI_MODE 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LCD_ST7789_240X240_7PIN
|
||||
#define LCD_TYPE_ST7789_SERIAL
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 240
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#define DISPLAY_INVERT_COLOR true
|
||||
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
#define DISPLAY_SPI_MODE 2
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LCD_ST7789_240X135
|
||||
#define LCD_TYPE_ST7789_SERIAL
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 135
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY true
|
||||
#define DISPLAY_INVERT_COLOR true
|
||||
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
|
||||
#define DISPLAY_OFFSET_X 40
|
||||
#define DISPLAY_OFFSET_Y 53
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
#define DISPLAY_SPI_MODE 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LCD_ST7735_128X160
|
||||
#define LCD_TYPE_ST7789_SERIAL
|
||||
#define DISPLAY_WIDTH 128
|
||||
#define DISPLAY_HEIGHT 160
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y true
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#define DISPLAY_INVERT_COLOR false
|
||||
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LCD_ST7735_128X128
|
||||
#define LCD_TYPE_ST7789_SERIAL
|
||||
#define DISPLAY_WIDTH 128
|
||||
#define DISPLAY_HEIGHT 128
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y true
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#define DISPLAY_INVERT_COLOR false
|
||||
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
|
||||
#define DISPLAY_OFFSET_X 2
|
||||
#define DISPLAY_OFFSET_Y 3
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
#define DISPLAY_SPI_MODE 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LCD_ST7796_320X480
|
||||
#define LCD_TYPE_ST7789_SERIAL
|
||||
#define DISPLAY_WIDTH 320
|
||||
#define DISPLAY_HEIGHT 480
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#define DISPLAY_INVERT_COLOR true
|
||||
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
#define DISPLAY_SPI_MODE 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LCD_ILI9341_240X320
|
||||
#define LCD_TYPE_ILI9341_SERIAL
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 320
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#define DISPLAY_INVERT_COLOR true
|
||||
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
#define DISPLAY_SPI_MODE 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LCD_ILI9341_240X320_NO_IPS
|
||||
#define LCD_TYPE_ILI9341_SERIAL
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 320
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#define DISPLAY_INVERT_COLOR false
|
||||
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
#define DISPLAY_SPI_MODE 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LCD_GC9A01_240X240
|
||||
#define LCD_TYPE_GC9A01_SERIAL
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 240
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#define DISPLAY_INVERT_COLOR true
|
||||
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
#define DISPLAY_SPI_MODE 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LCD_CUSTOM
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 320
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#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
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
11
main/boards/esp32-cgc/config.json
Normal file
11
main/boards/esp32-cgc/config.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"target": "esp32",
|
||||
"builds": [
|
||||
{
|
||||
"name": "esp32-cgc",
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y",
|
||||
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions_4M.csv\"",
|
||||
"CONFIG_LCD_ST7735_128X128=y"
|
||||
]
|
||||
}
|
||||
192
main/boards/esp32-cgc/esp32_cgc_board.cc
Normal file
192
main/boards/esp32-cgc/esp32_cgc_board.cc
Normal file
@@ -0,0 +1,192 @@
|
||||
#include "wifi_board.h"
|
||||
#include "audio_codecs/no_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "system_reset.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "iot/thing_manager.h"
|
||||
#include "led/single_led.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
#include <driver/spi_common.h>
|
||||
|
||||
#if defined(LCD_TYPE_ILI9341_SERIAL)
|
||||
#include <esp_lcd_ili9341.h>
|
||||
#endif
|
||||
|
||||
#if defined(LCD_TYPE_GC9A01_SERIAL)
|
||||
#include <esp_lcd_gc9a01.h>
|
||||
static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = {
|
||||
// {cmd, { data }, data_size, delay_ms}
|
||||
{0xfe, (uint8_t[]){0x00}, 0, 0},
|
||||
{0xef, (uint8_t[]){0x00}, 0, 0},
|
||||
{0xb0, (uint8_t[]){0xc0}, 1, 0},
|
||||
{0xb1, (uint8_t[]){0x80}, 1, 0},
|
||||
{0xb2, (uint8_t[]){0x27}, 1, 0},
|
||||
{0xb3, (uint8_t[]){0x13}, 1, 0},
|
||||
{0xb6, (uint8_t[]){0x19}, 1, 0},
|
||||
{0xb7, (uint8_t[]){0x05}, 1, 0},
|
||||
{0xac, (uint8_t[]){0xc8}, 1, 0},
|
||||
{0xab, (uint8_t[]){0x0f}, 1, 0},
|
||||
{0x3a, (uint8_t[]){0x05}, 1, 0},
|
||||
{0xb4, (uint8_t[]){0x04}, 1, 0},
|
||||
{0xa8, (uint8_t[]){0x08}, 1, 0},
|
||||
{0xb8, (uint8_t[]){0x08}, 1, 0},
|
||||
{0xea, (uint8_t[]){0x02}, 1, 0},
|
||||
{0xe8, (uint8_t[]){0x2A}, 1, 0},
|
||||
{0xe9, (uint8_t[]){0x47}, 1, 0},
|
||||
{0xe7, (uint8_t[]){0x5f}, 1, 0},
|
||||
{0xc6, (uint8_t[]){0x21}, 1, 0},
|
||||
{0xc7, (uint8_t[]){0x15}, 1, 0},
|
||||
{0xf0,
|
||||
(uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C,
|
||||
0x04, 0x12, 0x14, 0x1f},
|
||||
14, 0},
|
||||
{0xf1,
|
||||
(uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D,
|
||||
0x0C, 0x1A, 0x14, 0x1E},
|
||||
14, 0},
|
||||
{0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0},
|
||||
{0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0},
|
||||
};
|
||||
#endif
|
||||
|
||||
#define TAG "ESP32_CGC"
|
||||
|
||||
LV_FONT_DECLARE(font_puhui_14_1);
|
||||
LV_FONT_DECLARE(font_awesome_14_1);
|
||||
|
||||
class ESP32_CGC : public WifiBoard {
|
||||
private:
|
||||
Button boot_button_;
|
||||
LcdDisplay* display_;
|
||||
Button asr_button_;
|
||||
|
||||
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_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;
|
||||
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 = DISPLAY_SPI_SCLK_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(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_RESET_PIN;
|
||||
panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
|
||||
panel_config.bits_per_pixel = 16;
|
||||
#if defined(LCD_TYPE_ILI9341_SERIAL)
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
|
||||
#elif defined(LCD_TYPE_GC9A01_SERIAL)
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel));
|
||||
gc9a01_vendor_config_t gc9107_vendor_config = {
|
||||
.init_cmds = gc9107_lcd_init_cmds,
|
||||
.init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t),
|
||||
};
|
||||
#else
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
|
||||
#endif
|
||||
|
||||
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);
|
||||
#ifdef LCD_TYPE_GC9A01_SERIAL
|
||||
panel_config.vendor_config = &gc9107_vendor_config;
|
||||
#endif
|
||||
display_ = new SpiLcdDisplay(panel_io, panel,
|
||||
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY,
|
||||
{
|
||||
.text_font = &font_puhui_14_1,
|
||||
.icon_font = &font_awesome_14_1,
|
||||
.emoji_font = font_emoji_32_init(),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
void InitializeButtons() {
|
||||
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
asr_button_.OnClick([this]() {
|
||||
std::string wake_word="你好小智";
|
||||
Application::GetInstance().WakeWordInvoke(wake_word);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// 物联网初始化,添加对 AI 可见设备
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
}
|
||||
|
||||
public:
|
||||
ESP32_CGC() :
|
||||
boot_button_(BOOT_BUTTON_GPIO), asr_button_(ASR_BUTTON_GPIO) {
|
||||
InitializeSpi();
|
||||
InitializeLcdDisplay();
|
||||
InitializeButtons();
|
||||
InitializeIot();
|
||||
GetBacklight()->RestoreBrightness();
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override
|
||||
{
|
||||
#ifdef AUDIO_I2S_METHOD_SIMPLEX
|
||||
static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
|
||||
AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
|
||||
#else
|
||||
static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
|
||||
AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
|
||||
#endif
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(ESP32_CGC);
|
||||
@@ -230,9 +230,9 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("BoardControl"));
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
thing_manager.AddThing(iot::CreateThing("Battery"));
|
||||
thing_manager.AddThing(iot::CreateThing("BoardControl"));
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -263,21 +263,16 @@ public:
|
||||
return backlight_;
|
||||
}
|
||||
|
||||
virtual bool GetBatteryLevel(int &level, bool& charging) override {
|
||||
static bool last_charging = false;
|
||||
virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override {
|
||||
static bool last_discharging = false;
|
||||
charging = pmic_->IsCharging();
|
||||
if (charging != last_charging) {
|
||||
power_save_timer_->WakeUp();
|
||||
last_charging = charging;
|
||||
discharging = pmic_->IsDischarging();
|
||||
if (discharging != last_discharging) {
|
||||
power_save_timer_->SetEnabled(discharging);
|
||||
last_discharging = discharging;
|
||||
}
|
||||
|
||||
level = pmic_->GetBatteryLevel();
|
||||
|
||||
if (pmic_->IsDischarging()) {
|
||||
power_save_timer_->SetEnabled(true);
|
||||
} else {
|
||||
power_save_timer_->SetEnabled(false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
esp_io_expander_handle_t io_expander = NULL;
|
||||
LcdDisplay* display_;
|
||||
button_handle_t boot_btn,pwr_btn;
|
||||
button_handle_t boot_btn, pwr_btn;
|
||||
|
||||
void InitializeI2c() {
|
||||
// Initialize I2C peripheral
|
||||
@@ -216,7 +216,7 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -219,7 +219,7 @@ private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
esp_io_expander_handle_t io_expander = NULL;
|
||||
LcdDisplay* display_;
|
||||
button_handle_t boot_btn,pwr_btn;
|
||||
button_handle_t boot_btn, pwr_btn;
|
||||
|
||||
void InitializeI2c() {
|
||||
// Initialize I2C peripheral
|
||||
@@ -433,7 +433,7 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -378,7 +378,7 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
|
||||
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
|
||||
|
||||
#ifdef CONFIG_LCD_ST7789
|
||||
#define DISPLAY_SDA_PIN GPIO_NUM_NC
|
||||
#define DISPLAY_SCL_PIN GPIO_NUM_NC
|
||||
#define DISPLAY_WIDTH 280
|
||||
@@ -37,6 +38,23 @@
|
||||
|
||||
#define DISPLAY_OFFSET_X 20
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LCD_ILI9341
|
||||
#define LCD_TYPE_ILI9341_SERIAL
|
||||
#define DISPLAY_SDA_PIN GPIO_NUM_NC
|
||||
#define DISPLAY_SCL_PIN GPIO_NUM_NC
|
||||
#define DISPLAY_WIDTH 320
|
||||
#define DISPLAY_HEIGHT 240
|
||||
|
||||
#define DISPLAY_SWAP_XY false
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y true
|
||||
#define BACKLIGHT_INVERT false
|
||||
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
#endif
|
||||
|
||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "display/lcd_display.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "i2c_device.h"
|
||||
#include "iot/thing_manager.h"
|
||||
@@ -11,6 +10,7 @@
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <esp_io_expander_tca9554.h>
|
||||
#include <esp_lcd_ili9341.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/spi_common.h>
|
||||
#include <wifi_station.h>
|
||||
@@ -20,6 +20,28 @@
|
||||
LV_FONT_DECLARE(font_puhui_20_4);
|
||||
LV_FONT_DECLARE(font_awesome_20_4);
|
||||
|
||||
// Init ili9341 by custom cmd
|
||||
static const ili9341_lcd_init_cmd_t vendor_specific_init[] = {
|
||||
{0xC8, (uint8_t []){0xFF, 0x93, 0x42}, 3, 0},
|
||||
{0xC0, (uint8_t []){0x0E, 0x0E}, 2, 0},
|
||||
{0xC5, (uint8_t []){0xD0}, 1, 0},
|
||||
{0xC1, (uint8_t []){0x02}, 1, 0},
|
||||
{0xB4, (uint8_t []){0x02}, 1, 0},
|
||||
{0xE0, (uint8_t []){0x00, 0x03, 0x08, 0x06, 0x13, 0x09, 0x39, 0x39, 0x48, 0x02, 0x0a, 0x08, 0x17, 0x17, 0x0F}, 15, 0},
|
||||
{0xE1, (uint8_t []){0x00, 0x28, 0x29, 0x01, 0x0d, 0x03, 0x3f, 0x33, 0x52, 0x04, 0x0f, 0x0e, 0x37, 0x38, 0x0F}, 15, 0},
|
||||
|
||||
{0xB1, (uint8_t []){00, 0x1B}, 2, 0},
|
||||
{0x36, (uint8_t []){0x08}, 1, 0},
|
||||
{0x3A, (uint8_t []){0x55}, 1, 0},
|
||||
{0xB7, (uint8_t []){0x06}, 1, 0},
|
||||
|
||||
{0x11, (uint8_t []){0}, 0x80, 0},
|
||||
{0x29, (uint8_t []){0}, 0x80, 0},
|
||||
|
||||
{0, (uint8_t []){0}, 0xff, 0},
|
||||
};
|
||||
|
||||
|
||||
class Esp32S3Korvo2V3Board : public WifiBoard {
|
||||
private:
|
||||
Button boot_button_;
|
||||
@@ -123,6 +145,53 @@ private:
|
||||
});
|
||||
}
|
||||
|
||||
void InitializeIli9341Display() {
|
||||
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 = GPIO_NUM_NC;
|
||||
io_config.dc_gpio_num = GPIO_NUM_2;
|
||||
io_config.spi_mode = 0;
|
||||
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");
|
||||
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 = GPIO_NUM_NC;
|
||||
// panel_config.flags.reset_active_high = 0,
|
||||
panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
|
||||
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_ERROR_CHECK(esp_lcd_panel_reset(panel));
|
||||
EnableLcdCs();
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, false));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true));
|
||||
display_ = new SpiLcdDisplay(panel_io, panel,
|
||||
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY,
|
||||
{
|
||||
.text_font = &font_puhui_20_4,
|
||||
.icon_font = &font_awesome_20_4,
|
||||
.emoji_font = font_emoji_64_init(),
|
||||
});
|
||||
}
|
||||
|
||||
void InitializeSt7789Display() {
|
||||
esp_lcd_panel_io_handle_t panel_io = nullptr;
|
||||
esp_lcd_panel_handle_t panel = nullptr;
|
||||
@@ -176,7 +245,11 @@ public:
|
||||
InitializeTca9554();
|
||||
InitializeSpi();
|
||||
InitializeButtons();
|
||||
InitializeSt7789Display();
|
||||
#ifdef LCD_TYPE_ILI9341_SERIAL
|
||||
InitializeIli9341Display();
|
||||
#else
|
||||
InitializeSt7789Display();
|
||||
#endif
|
||||
InitializeIot();
|
||||
}
|
||||
|
||||
|
||||
@@ -250,21 +250,16 @@ public:
|
||||
return display_;
|
||||
}
|
||||
|
||||
virtual bool GetBatteryLevel(int &level, bool& charging) override {
|
||||
static bool last_charging = false;
|
||||
virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override {
|
||||
static bool last_discharging = false;
|
||||
charging = pmic_->IsCharging();
|
||||
if (charging != last_charging) {
|
||||
power_save_timer_->WakeUp();
|
||||
last_charging = charging;
|
||||
discharging = pmic_->IsDischarging();
|
||||
if (discharging != last_discharging) {
|
||||
power_save_timer_->SetEnabled(discharging);
|
||||
last_discharging = discharging;
|
||||
}
|
||||
|
||||
level = pmic_->GetBatteryLevel();
|
||||
|
||||
if (pmic_->IsDischarging()) {
|
||||
power_save_timer_->SetEnabled(true);
|
||||
} else {
|
||||
power_save_timer_->SetEnabled(false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "config.h"
|
||||
#include "iot/thing_manager.h"
|
||||
#include "led/circular_strip.h"
|
||||
#include "led_strip_control.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
@@ -17,6 +18,7 @@ class KevinBoxBoard : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t codec_i2c_bus_;
|
||||
Button boot_button_;
|
||||
CircularStrip* led_strip_;
|
||||
|
||||
void InitializeCodecI2c() {
|
||||
// Initialize I2C peripheral
|
||||
@@ -54,6 +56,10 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
|
||||
led_strip_ = new CircularStrip(BUILTIN_LED_GPIO, 8);
|
||||
auto led_strip_control = new LedStripControl(led_strip_);
|
||||
thing_manager.AddThing(led_strip_control);
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -67,8 +73,7 @@ public:
|
||||
}
|
||||
|
||||
virtual Led* GetLed() override {
|
||||
static CircularStrip led(BUILTIN_LED_GPIO, 8);
|
||||
return &led;
|
||||
return led_strip_;
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
|
||||
123
main/boards/kevin-c3/led_strip_control.cc
Normal file
123
main/boards/kevin-c3/led_strip_control.cc
Normal file
@@ -0,0 +1,123 @@
|
||||
#include "led_strip_control.h"
|
||||
#include "settings.h"
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "LedStripControl"
|
||||
|
||||
|
||||
int LedStripControl::LevelToBrightness(int level) const {
|
||||
if (level < 0) level = 0;
|
||||
if (level > 8) level = 8;
|
||||
return (1 << level) - 1; // 2^n - 1
|
||||
}
|
||||
|
||||
StripColor LedStripControl::RGBToColor(int red, int green, int blue) {
|
||||
if (red < 0) red = 0;
|
||||
if (red > 255) red = 255;
|
||||
if (green < 0) green = 0;
|
||||
if (green > 255) green = 255;
|
||||
if (blue < 0) blue = 0;
|
||||
if (blue > 255) blue = 255;
|
||||
return {static_cast<uint8_t>(red), static_cast<uint8_t>(green), static_cast<uint8_t>(blue)};
|
||||
}
|
||||
|
||||
LedStripControl::LedStripControl(CircularStrip* led_strip)
|
||||
: Thing("LedStripControl", "LED 灯带控制,一共有8个灯珠"), led_strip_(led_strip) {
|
||||
// 从设置中读取亮度等级
|
||||
Settings settings("led_strip");
|
||||
brightness_level_ = settings.GetInt("brightness", 4); // 默认等级4
|
||||
led_strip_->SetBrightness(LevelToBrightness(brightness_level_), 4);
|
||||
|
||||
// 定义设备的属性
|
||||
properties_.AddNumberProperty("brightness", "对话时的亮度等级(0-8)", [this]() -> int {
|
||||
return brightness_level_;
|
||||
});
|
||||
|
||||
// 定义设备可以被远程执行的指令
|
||||
methods_.AddMethod("SetBrightness", "设置对话时的亮度等级", ParameterList({
|
||||
Parameter("level", "亮度等级(0-8)", kValueTypeNumber, true)
|
||||
}), [this](const ParameterList& parameters) {
|
||||
int level = static_cast<int>(parameters["level"].number());
|
||||
ESP_LOGI(TAG, "Set LedStrip brightness level to %d", level);
|
||||
|
||||
if (level < 0) level = 0;
|
||||
if (level > 8) level = 8;
|
||||
|
||||
brightness_level_ = level;
|
||||
led_strip_->SetBrightness(LevelToBrightness(brightness_level_), 4);
|
||||
|
||||
// 保存设置
|
||||
Settings settings("led_strip", true);
|
||||
settings.SetInt("brightness", brightness_level_);
|
||||
});
|
||||
|
||||
methods_.AddMethod("SetSingleColor", "设置单个灯颜色", ParameterList({
|
||||
Parameter("index", "灯珠索引(0-7)", kValueTypeNumber, true),
|
||||
Parameter("red", "红色(0-255)", kValueTypeNumber, true),
|
||||
Parameter("green", "绿色(0-255)", kValueTypeNumber, true),
|
||||
Parameter("blue", "蓝色(0-255)", kValueTypeNumber, true)
|
||||
}), [this](const ParameterList& parameters) {
|
||||
int index = parameters["index"].number();
|
||||
StripColor color = RGBToColor(
|
||||
parameters["red"].number(),
|
||||
parameters["green"].number(),
|
||||
parameters["blue"].number()
|
||||
);
|
||||
ESP_LOGI(TAG, "Set led strip single color %d to %d, %d, %d",
|
||||
index, color.red, color.green, color.blue);
|
||||
led_strip_->SetSingleColor(index, color);
|
||||
});
|
||||
|
||||
methods_.AddMethod("SetAllColor", "设置所有灯颜色", ParameterList({
|
||||
Parameter("red", "红色(0-255)", kValueTypeNumber, true),
|
||||
Parameter("green", "绿色(0-255)", kValueTypeNumber, true),
|
||||
Parameter("blue", "蓝色(0-255)", kValueTypeNumber, true)
|
||||
}), [this](const ParameterList& parameters) {
|
||||
StripColor color = RGBToColor(
|
||||
parameters["red"].number(),
|
||||
parameters["green"].number(),
|
||||
parameters["blue"].number()
|
||||
);
|
||||
ESP_LOGI(TAG, "Set led strip color to %d, %d, %d",
|
||||
color.red, color.green, color.blue
|
||||
);
|
||||
led_strip_->SetAllColor(color);
|
||||
});
|
||||
|
||||
methods_.AddMethod("Blink", "闪烁动画", ParameterList({
|
||||
Parameter("red", "红色(0-255)", kValueTypeNumber, true),
|
||||
Parameter("green", "绿色(0-255)", kValueTypeNumber, true),
|
||||
Parameter("blue", "蓝色(0-255)", kValueTypeNumber, true),
|
||||
Parameter("interval", "间隔(ms)", kValueTypeNumber, true)
|
||||
}), [this](const ParameterList& parameters) {
|
||||
int interval = parameters["interval"].number();
|
||||
StripColor color = RGBToColor(
|
||||
parameters["red"].number(),
|
||||
parameters["green"].number(),
|
||||
parameters["blue"].number()
|
||||
);
|
||||
ESP_LOGI(TAG, "Blink led strip with color %d, %d, %d, interval %dms",
|
||||
color.red, color.green, color.blue, interval);
|
||||
led_strip_->Blink(color, interval);
|
||||
});
|
||||
|
||||
methods_.AddMethod("Scroll", "跑马灯动画", ParameterList({
|
||||
Parameter("red", "红色(0-255)", kValueTypeNumber, true),
|
||||
Parameter("green", "绿色(0-255)", kValueTypeNumber, true),
|
||||
Parameter("blue", "蓝色(0-255)", kValueTypeNumber, true),
|
||||
Parameter("length", "滚动条长度(1-7)", kValueTypeNumber, true),
|
||||
Parameter("interval", "间隔(ms)", kValueTypeNumber, true)
|
||||
}), [this](const ParameterList& parameters) {
|
||||
int interval = parameters["interval"].number();
|
||||
int length = parameters["length"].number();
|
||||
StripColor low = RGBToColor(4, 4, 4);
|
||||
StripColor high = RGBToColor(
|
||||
parameters["red"].number(),
|
||||
parameters["green"].number(),
|
||||
parameters["blue"].number()
|
||||
);
|
||||
ESP_LOGI(TAG, "Scroll led strip with color %d, %d, %d, length %d, interval %dms",
|
||||
high.red, high.green, high.blue, length, interval);
|
||||
led_strip_->Scroll(low, high, length, interval);
|
||||
});
|
||||
}
|
||||
21
main/boards/kevin-c3/led_strip_control.h
Normal file
21
main/boards/kevin-c3/led_strip_control.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef LED_STRIP_CONTROL_H
|
||||
#define LED_STRIP_CONTROL_H
|
||||
|
||||
#include "iot/thing.h"
|
||||
#include "led/circular_strip.h"
|
||||
|
||||
using namespace iot;
|
||||
|
||||
class LedStripControl : public Thing {
|
||||
private:
|
||||
CircularStrip* led_strip_;
|
||||
int brightness_level_; // 亮度等级 (0-8)
|
||||
|
||||
int LevelToBrightness(int level) const; // 将等级转换为实际亮度值
|
||||
StripColor RGBToColor(int red, int green, int blue);
|
||||
|
||||
public:
|
||||
explicit LedStripControl(CircularStrip* led_strip);
|
||||
};
|
||||
|
||||
#endif // LED_STRIP_CONTROL_H
|
||||
@@ -95,8 +95,8 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
thing_manager.AddThing(iot::CreateThing("Lamp"));
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -108,8 +108,8 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
thing_manager.AddThing(iot::CreateThing("Lamp"));
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -122,7 +122,7 @@ esp_err_t esp_lcd_new_panel_gc9503(const esp_lcd_panel_io_handle_t io, const esp
|
||||
*/
|
||||
#define GC9503_376_960_PANEL_60HZ_RGB_TIMING() \
|
||||
{ \
|
||||
.pclk_hz = 20 * 1000 * 1000, \
|
||||
.pclk_hz = 16 * 1000 * 1000, \
|
||||
.h_res = 376, \
|
||||
.v_res = 960, \
|
||||
.hsync_pulse_width = 8, \
|
||||
|
||||
@@ -147,7 +147,7 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -104,7 +104,7 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -122,7 +122,11 @@ private:
|
||||
{
|
||||
.text_font = &font_puhui_20_4,
|
||||
.icon_font = &font_awesome_20_4,
|
||||
#if CONFIG_USE_WECHAT_MESSAGE_STYLE
|
||||
.emoji_font = font_emoji_32_init(),
|
||||
#else
|
||||
.emoji_font = font_emoji_64_init(),
|
||||
#endif
|
||||
});
|
||||
}
|
||||
|
||||
@@ -130,7 +134,7 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -208,7 +208,7 @@ private:
|
||||
void InitializeIot() {
|
||||
auto &thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -226,7 +226,7 @@ private:
|
||||
void InitializeIot() {
|
||||
auto &thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -309,7 +309,7 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
thing_manager.AddThing(iot::CreateThing("Battery"));
|
||||
}
|
||||
|
||||
@@ -346,21 +346,16 @@ public:
|
||||
return display_;
|
||||
}
|
||||
|
||||
virtual bool GetBatteryLevel(int &level, bool& charging) override {
|
||||
static bool last_charging = false;
|
||||
virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override {
|
||||
static bool last_discharging = false;
|
||||
charging = pmic_->IsCharging();
|
||||
if (charging != last_charging) {
|
||||
power_save_timer_->WakeUp();
|
||||
last_charging = charging;
|
||||
discharging = pmic_->IsDischarging();
|
||||
if (discharging != last_discharging) {
|
||||
power_save_timer_->SetEnabled(discharging);
|
||||
last_discharging = discharging;
|
||||
}
|
||||
|
||||
level = pmic_->GetBatteryLevel();
|
||||
|
||||
if (pmic_->IsDischarging()) {
|
||||
power_save_timer_->SetEnabled(true);
|
||||
} else {
|
||||
power_save_timer_->SetEnabled(false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -180,7 +180,7 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -224,7 +224,7 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -211,7 +211,7 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -169,7 +169,7 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -128,7 +128,7 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
|
||||
|
||||
#define BUILTIN_LED_GPIO GPIO_NUM_NC
|
||||
#define BUILTIN_LED_GPIO GPIO_NUM_40
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_0
|
||||
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
|
||||
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
"name": "sensecap-watcher",
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_ESPTOOLPY_FLASHSIZE_32MB=y",
|
||||
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME='partitions_32M_sensecap.csv'"
|
||||
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions_32M_sensecap.csv\"",
|
||||
"CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH=y",
|
||||
"CONFIG_ESPTOOLPY_FLASH_MODE_AUTO_DETECT=n",
|
||||
"CONFIG_IDF_EXPERIMENTAL_FEATURES=y"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#include "display/lv_display.h"
|
||||
#include "misc/lv_event.h"
|
||||
#include "wifi_board.h"
|
||||
#include "sensecap_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
@@ -5,6 +7,7 @@
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "led/single_led.h"
|
||||
#include "iot/thing_manager.h"
|
||||
#include "power_save_timer.h"
|
||||
|
||||
@@ -54,8 +57,13 @@ private:
|
||||
});
|
||||
power_save_timer_->OnShutdownRequest([this]() {
|
||||
ESP_LOGI(TAG, "Shutting down");
|
||||
IoExpanderSetLevel(BSP_PWR_LCD, 0);
|
||||
IoExpanderSetLevel(BSP_PWR_SYSTEM, 0);
|
||||
bool is_charging = (IoExpanderGetLevel(BSP_PWR_VBUS_IN_DET) == 0);
|
||||
if (is_charging) {
|
||||
ESP_LOGI(TAG, "charging");
|
||||
GetBacklight()->SetBrightness(0);
|
||||
} else {
|
||||
IoExpanderSetLevel(BSP_PWR_SYSTEM, 0);
|
||||
}
|
||||
});
|
||||
power_save_timer_->SetEnabled(true);
|
||||
}
|
||||
@@ -123,6 +131,13 @@ private:
|
||||
.priv = this,
|
||||
},
|
||||
};
|
||||
|
||||
//watcher 是通过长按滚轮进行开机的, 需要等待滚轮释放, 否则用户开机松手时可能会误触成单击
|
||||
ESP_LOGI(TAG, "waiting for knob button release");
|
||||
while(IoExpanderGetLevel(BSP_KNOB_BTN) == 0) {
|
||||
vTaskDelay(50 / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
btns = iot_button_create(&btn_config);
|
||||
iot_button_register_cb(btns, BUTTON_SINGLE_CLICK, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<SensecapWatcher*>(usr_data);
|
||||
@@ -193,7 +208,7 @@ private:
|
||||
esp_lcd_panel_init(panel_);
|
||||
esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
esp_lcd_panel_disp_on_off(panel_, true);
|
||||
|
||||
|
||||
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,
|
||||
{
|
||||
@@ -201,13 +216,30 @@ private:
|
||||
.icon_font = &font_awesome_30_4,
|
||||
.emoji_font = font_emoji_64_init(),
|
||||
});
|
||||
|
||||
// 使每次刷新的行数是4的倍数,防止花屏
|
||||
lv_display_add_event_cb(lv_display_get_default(), [](lv_event_t *e) {
|
||||
lv_area_t *area = (lv_area_t *)lv_event_get_param(e);
|
||||
uint16_t x1 = area->x1;
|
||||
uint16_t x2 = area->x2;
|
||||
// round the start of area down to the nearest 4N number
|
||||
area->x1 = (x1 >> 2) << 2;
|
||||
// round the start of area down to the nearest 4N number
|
||||
area->x1 = (x1 >> 2) << 2;
|
||||
|
||||
// round the end of area up to the nearest 4M+3 number
|
||||
area->x2 = ((x2 >> 2) << 2) + 3;
|
||||
// round the end of area up to the nearest 4M+3 number
|
||||
area->x2 = ((x2 >> 2) << 2) + 3;
|
||||
}, LV_EVENT_INVALIDATE_AREA, NULL);
|
||||
|
||||
}
|
||||
|
||||
// 物联网初始化,添加对 AI 可见设备
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -249,6 +281,14 @@ public:
|
||||
return &backlight;
|
||||
}
|
||||
|
||||
// 根据 https://github.com/Seeed-Studio/OSHW-SenseCAP-Watcher/blob/main/Hardware/SenseCAP_Watcher_v1.0_SCH.pdf
|
||||
// RGB LED型号为 ws2813 mini, 连接在GPIO 40,供电电压 3.3v, 没有连接 BIN 双信号线
|
||||
// 可以直接兼容SingleLED采用的ws2812
|
||||
virtual Led* GetLed() override {
|
||||
static SingleLed led(BUILTIN_LED_GPIO);
|
||||
return &led;
|
||||
}
|
||||
|
||||
virtual void SetPowerSaveMode(bool enabled) override {
|
||||
if (!enabled) {
|
||||
power_save_timer_->WakeUp();
|
||||
|
||||
@@ -198,7 +198,7 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
}
|
||||
void InitializeMute() {
|
||||
gpio_reset_pin(AUDIO_MUTE_PIN);
|
||||
|
||||
@@ -269,21 +269,16 @@ public:
|
||||
return display_;
|
||||
}
|
||||
|
||||
virtual bool GetBatteryLevel(int &level, bool& charging) override {
|
||||
static bool last_charging = false;
|
||||
virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override {
|
||||
static bool last_discharging = false;
|
||||
charging = pmic_->IsCharging();
|
||||
if (charging != last_charging) {
|
||||
power_save_timer_->WakeUp();
|
||||
last_charging = charging;
|
||||
discharging = pmic_->IsDischarging();
|
||||
if (discharging != last_discharging) {
|
||||
power_save_timer_->SetEnabled(discharging);
|
||||
last_discharging = discharging;
|
||||
}
|
||||
|
||||
level = pmic_->GetBatteryLevel();
|
||||
|
||||
if (pmic_->IsDischarging()) {
|
||||
power_save_timer_->SetEnabled(true);
|
||||
} else {
|
||||
power_save_timer_->SetEnabled(false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -215,8 +215,14 @@ public:
|
||||
return display_;
|
||||
}
|
||||
|
||||
virtual bool GetBatteryLevel(int& level, bool& charging) override {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -220,8 +220,14 @@ public:
|
||||
return display_;
|
||||
}
|
||||
|
||||
virtual bool GetBatteryLevel(int& level, bool& charging) override {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
thing_manager.AddThing(iot::CreateThing("Battery"));
|
||||
}
|
||||
|
||||
@@ -196,8 +196,14 @@ public:
|
||||
return &backlight;
|
||||
}
|
||||
|
||||
virtual bool GetBatteryLevel(int& level, bool& charging) override {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -167,6 +167,11 @@ public:
|
||||
return is_charging_;
|
||||
}
|
||||
|
||||
bool IsDischarging() {
|
||||
// 没有区分充电和放电,所以直接返回相反状态
|
||||
return !is_charging_;
|
||||
}
|
||||
|
||||
uint8_t GetBatteryLevel() {
|
||||
return battery_level_;
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ private:
|
||||
void InitializeIot() {
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Backlight"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
thing_manager.AddThing(iot::CreateThing("Battery"));
|
||||
}
|
||||
|
||||
@@ -199,8 +199,14 @@ public:
|
||||
return &backlight;
|
||||
}
|
||||
|
||||
virtual bool GetBatteryLevel(int& level, bool& charging) override {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
#define TAG "Display"
|
||||
|
||||
Display::Display() {
|
||||
// Load theme from settings
|
||||
Settings settings("display", false);
|
||||
current_theme_name_ = settings.GetString("theme", "light");
|
||||
|
||||
// Notification timer
|
||||
esp_timer_create_args_t notification_timer_args = {
|
||||
.callback = [](void *arg) {
|
||||
@@ -127,9 +131,9 @@ void Display::Update() {
|
||||
esp_pm_lock_acquire(pm_lock_);
|
||||
// 更新电池图标
|
||||
int battery_level;
|
||||
bool charging;
|
||||
bool charging, discharging;
|
||||
const char* icon = nullptr;
|
||||
if (board.GetBatteryLevel(battery_level, charging)) {
|
||||
if (board.GetBatteryLevel(battery_level, charging, discharging)) {
|
||||
if (charging) {
|
||||
icon = FONT_AWESOME_BATTERY_CHARGING;
|
||||
} else {
|
||||
@@ -150,15 +154,15 @@ void Display::Update() {
|
||||
}
|
||||
|
||||
if (low_battery_popup_ != nullptr) {
|
||||
if (strcmp(icon, FONT_AWESOME_BATTERY_EMPTY) == 0) {
|
||||
if (lv_obj_has_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN)) {
|
||||
if (strcmp(icon, FONT_AWESOME_BATTERY_EMPTY) == 0 && discharging) {
|
||||
if (lv_obj_has_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN)) { // 如果低电量提示框隐藏,则显示
|
||||
lv_obj_clear_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
|
||||
auto& app = Application::GetInstance();
|
||||
app.PlaySound(Lang::Sounds::P3_LOW_BATTERY);
|
||||
}
|
||||
} else {
|
||||
// Hide the low battery popup when the battery is not empty
|
||||
if (!lv_obj_has_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN)) {
|
||||
if (!lv_obj_has_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN)) { // 如果低电量提示框显示,则隐藏
|
||||
lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
@@ -249,3 +253,9 @@ void Display::SetChatMessage(const char* role, const char* content) {
|
||||
}
|
||||
lv_label_set_text(chat_message_label_, content);
|
||||
}
|
||||
|
||||
void Display::SetTheme(const std::string& theme_name) {
|
||||
current_theme_name_ = theme_name;
|
||||
Settings settings("display", true);
|
||||
settings.SetString("theme", theme_name);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ public:
|
||||
virtual void SetEmotion(const char* emotion);
|
||||
virtual void SetChatMessage(const char* role, const char* content);
|
||||
virtual void SetIcon(const char* icon);
|
||||
virtual void SetTheme(const std::string& theme_name);
|
||||
virtual std::string GetTheme() { return current_theme_name_; }
|
||||
|
||||
inline int width() const { return width_; }
|
||||
inline int height() const { return height_; }
|
||||
@@ -48,6 +50,7 @@ protected:
|
||||
const char* battery_icon_ = nullptr;
|
||||
const char* network_icon_ = nullptr;
|
||||
bool muted_ = false;
|
||||
std::string current_theme_name_;
|
||||
|
||||
esp_timer_handle_t notification_timer_ = nullptr;
|
||||
esp_timer_handle_t update_timer_ = nullptr;
|
||||
|
||||
@@ -6,11 +6,78 @@
|
||||
#include <esp_err.h>
|
||||
#include <esp_lvgl_port.h>
|
||||
#include "assets/lang_config.h"
|
||||
#include <cstring>
|
||||
#include "settings.h"
|
||||
|
||||
#include "board.h"
|
||||
|
||||
#define TAG "LcdDisplay"
|
||||
|
||||
// Color definitions for dark theme
|
||||
#define DARK_BACKGROUND_COLOR lv_color_hex(0x121212) // Dark background
|
||||
#define DARK_TEXT_COLOR lv_color_white() // White text
|
||||
#define DARK_CHAT_BACKGROUND_COLOR lv_color_hex(0x1E1E1E) // Slightly lighter than background
|
||||
#define DARK_USER_BUBBLE_COLOR lv_color_hex(0x1A6C37) // Dark green
|
||||
#define DARK_ASSISTANT_BUBBLE_COLOR lv_color_hex(0x333333) // Dark gray
|
||||
#define DARK_SYSTEM_BUBBLE_COLOR lv_color_hex(0x2A2A2A) // Medium gray
|
||||
#define DARK_SYSTEM_TEXT_COLOR lv_color_hex(0xAAAAAA) // Light gray text
|
||||
#define DARK_BORDER_COLOR lv_color_hex(0x333333) // Dark gray border
|
||||
#define DARK_LOW_BATTERY_COLOR lv_color_hex(0xFF0000) // Red for dark mode
|
||||
|
||||
// Color definitions for light theme
|
||||
#define LIGHT_BACKGROUND_COLOR lv_color_white() // White background
|
||||
#define LIGHT_TEXT_COLOR lv_color_black() // Black text
|
||||
#define LIGHT_CHAT_BACKGROUND_COLOR lv_color_hex(0xE0E0E0) // Light gray background
|
||||
#define LIGHT_USER_BUBBLE_COLOR lv_color_hex(0x95EC69) // WeChat green
|
||||
#define LIGHT_ASSISTANT_BUBBLE_COLOR lv_color_white() // White
|
||||
#define LIGHT_SYSTEM_BUBBLE_COLOR lv_color_hex(0xE0E0E0) // Light gray
|
||||
#define LIGHT_SYSTEM_TEXT_COLOR lv_color_hex(0x666666) // Dark gray text
|
||||
#define LIGHT_BORDER_COLOR lv_color_hex(0xE0E0E0) // Light gray border
|
||||
#define LIGHT_LOW_BATTERY_COLOR lv_color_black() // Black for light mode
|
||||
|
||||
// Theme color structure
|
||||
struct ThemeColors {
|
||||
lv_color_t background;
|
||||
lv_color_t text;
|
||||
lv_color_t chat_background;
|
||||
lv_color_t user_bubble;
|
||||
lv_color_t assistant_bubble;
|
||||
lv_color_t system_bubble;
|
||||
lv_color_t system_text;
|
||||
lv_color_t border;
|
||||
lv_color_t low_battery;
|
||||
};
|
||||
|
||||
// Define dark theme colors
|
||||
static const ThemeColors DARK_THEME = {
|
||||
.background = DARK_BACKGROUND_COLOR,
|
||||
.text = DARK_TEXT_COLOR,
|
||||
.chat_background = DARK_CHAT_BACKGROUND_COLOR,
|
||||
.user_bubble = DARK_USER_BUBBLE_COLOR,
|
||||
.assistant_bubble = DARK_ASSISTANT_BUBBLE_COLOR,
|
||||
.system_bubble = DARK_SYSTEM_BUBBLE_COLOR,
|
||||
.system_text = DARK_SYSTEM_TEXT_COLOR,
|
||||
.border = DARK_BORDER_COLOR,
|
||||
.low_battery = DARK_LOW_BATTERY_COLOR
|
||||
};
|
||||
|
||||
// Define light theme colors
|
||||
static const ThemeColors LIGHT_THEME = {
|
||||
.background = LIGHT_BACKGROUND_COLOR,
|
||||
.text = LIGHT_TEXT_COLOR,
|
||||
.chat_background = LIGHT_CHAT_BACKGROUND_COLOR,
|
||||
.user_bubble = LIGHT_USER_BUBBLE_COLOR,
|
||||
.assistant_bubble = LIGHT_ASSISTANT_BUBBLE_COLOR,
|
||||
.system_bubble = LIGHT_SYSTEM_BUBBLE_COLOR,
|
||||
.system_text = LIGHT_SYSTEM_TEXT_COLOR,
|
||||
.border = LIGHT_BORDER_COLOR,
|
||||
.low_battery = LIGHT_LOW_BATTERY_COLOR
|
||||
};
|
||||
|
||||
// Current theme - initialize based on default config
|
||||
static ThemeColors current_theme = LIGHT_THEME;
|
||||
|
||||
|
||||
LV_FONT_DECLARE(font_awesome_30_4);
|
||||
|
||||
SpiLcdDisplay::SpiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
|
||||
@@ -75,6 +142,13 @@ SpiLcdDisplay::SpiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_h
|
||||
lv_display_set_offset(display_, offset_x, offset_y);
|
||||
}
|
||||
|
||||
// Update the theme
|
||||
if (current_theme_name_ == "dark") {
|
||||
current_theme = DARK_THEME;
|
||||
} else if (current_theme_name_ == "light") {
|
||||
current_theme = LIGHT_THEME;
|
||||
}
|
||||
|
||||
SetupUI();
|
||||
}
|
||||
|
||||
@@ -139,6 +213,13 @@ RgbLcdDisplay::RgbLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_h
|
||||
lv_display_set_offset(display_, offset_x, offset_y);
|
||||
}
|
||||
|
||||
// Update the theme
|
||||
if (current_theme_name_ == "dark") {
|
||||
current_theme = DARK_THEME;
|
||||
} else if (current_theme_name_ == "light") {
|
||||
current_theme = LIGHT_THEME;
|
||||
}
|
||||
|
||||
SetupUI();
|
||||
}
|
||||
|
||||
@@ -176,12 +257,14 @@ void LcdDisplay::Unlock() {
|
||||
lvgl_port_unlock();
|
||||
}
|
||||
|
||||
#if CONFIG_USE_WECHAT_MESSAGE_STYLE
|
||||
void LcdDisplay::SetupUI() {
|
||||
DisplayLockGuard lock(this);
|
||||
|
||||
auto screen = lv_screen_active();
|
||||
lv_obj_set_style_text_font(screen, fonts_.text_font, 0);
|
||||
lv_obj_set_style_text_color(screen, lv_color_black(), 0);
|
||||
lv_obj_set_style_text_color(screen, current_theme.text, 0);
|
||||
lv_obj_set_style_bg_color(screen, current_theme.background, 0);
|
||||
|
||||
/* Container */
|
||||
container_ = lv_obj_create(screen);
|
||||
@@ -190,11 +273,293 @@ void LcdDisplay::SetupUI() {
|
||||
lv_obj_set_style_pad_all(container_, 0, 0);
|
||||
lv_obj_set_style_border_width(container_, 0, 0);
|
||||
lv_obj_set_style_pad_row(container_, 0, 0);
|
||||
lv_obj_set_style_bg_color(container_, current_theme.background, 0);
|
||||
lv_obj_set_style_border_color(container_, current_theme.border, 0);
|
||||
|
||||
/* Status bar */
|
||||
status_bar_ = lv_obj_create(container_);
|
||||
lv_obj_set_size(status_bar_, LV_HOR_RES, fonts_.emoji_font->line_height);
|
||||
lv_obj_set_style_radius(status_bar_, 0, 0);
|
||||
lv_obj_set_style_bg_color(status_bar_, current_theme.background, 0);
|
||||
lv_obj_set_style_text_color(status_bar_, current_theme.text, 0);
|
||||
|
||||
/* Content - Chat area */
|
||||
content_ = lv_obj_create(container_);
|
||||
lv_obj_set_style_radius(content_, 0, 0);
|
||||
lv_obj_set_width(content_, LV_HOR_RES);
|
||||
lv_obj_set_flex_grow(content_, 1);
|
||||
lv_obj_set_style_pad_all(content_, 5, 0);
|
||||
lv_obj_set_style_bg_color(content_, current_theme.chat_background, 0); // Background for chat area
|
||||
lv_obj_set_style_border_color(content_, current_theme.border, 0); // Border color for chat area
|
||||
|
||||
// Enable scrolling for chat content
|
||||
lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF);
|
||||
lv_obj_set_scroll_dir(content_, LV_DIR_VER);
|
||||
|
||||
// Create a flex container for chat messages
|
||||
lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_flex_align(content_, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START);
|
||||
lv_obj_set_style_pad_row(content_, 10, 0); // Space between messages
|
||||
|
||||
// We'll create chat messages dynamically in SetChatMessage
|
||||
chat_message_label_ = nullptr;
|
||||
|
||||
/* Status bar */
|
||||
lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_style_pad_all(status_bar_, 0, 0);
|
||||
lv_obj_set_style_border_width(status_bar_, 0, 0);
|
||||
lv_obj_set_style_pad_column(status_bar_, 0, 0);
|
||||
lv_obj_set_style_pad_left(status_bar_, 2, 0);
|
||||
lv_obj_set_style_pad_right(status_bar_, 2, 0);
|
||||
lv_obj_set_scrollbar_mode(status_bar_, LV_SCROLLBAR_MODE_OFF);
|
||||
// 设置状态栏的内容垂直居中
|
||||
lv_obj_set_flex_align(status_bar_, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
|
||||
// 创建emotion_label_在状态栏最左侧
|
||||
emotion_label_ = lv_label_create(status_bar_);
|
||||
lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_4, 0);
|
||||
lv_obj_set_style_text_color(emotion_label_, current_theme.text, 0);
|
||||
lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP);
|
||||
lv_obj_set_style_margin_right(emotion_label_, 5, 0); // 添加右边距,与后面的元素分隔
|
||||
|
||||
notification_label_ = lv_label_create(status_bar_);
|
||||
lv_obj_set_flex_grow(notification_label_, 1);
|
||||
lv_obj_set_style_text_align(notification_label_, LV_TEXT_ALIGN_CENTER, 0);
|
||||
lv_obj_set_style_text_color(notification_label_, current_theme.text, 0);
|
||||
lv_label_set_text(notification_label_, "");
|
||||
lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
|
||||
status_label_ = lv_label_create(status_bar_);
|
||||
lv_obj_set_flex_grow(status_label_, 1);
|
||||
lv_label_set_long_mode(status_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
|
||||
lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0);
|
||||
lv_obj_set_style_text_color(status_label_, current_theme.text, 0);
|
||||
lv_label_set_text(status_label_, Lang::Strings::INITIALIZING);
|
||||
|
||||
mute_label_ = lv_label_create(status_bar_);
|
||||
lv_label_set_text(mute_label_, "");
|
||||
lv_obj_set_style_text_font(mute_label_, fonts_.icon_font, 0);
|
||||
lv_obj_set_style_text_color(mute_label_, current_theme.text, 0);
|
||||
|
||||
network_label_ = lv_label_create(status_bar_);
|
||||
lv_label_set_text(network_label_, "");
|
||||
lv_obj_set_style_text_font(network_label_, fonts_.icon_font, 0);
|
||||
lv_obj_set_style_text_color(network_label_, current_theme.text, 0);
|
||||
lv_obj_set_style_margin_left(network_label_, 5, 0); // 添加左边距,与前面的元素分隔
|
||||
|
||||
battery_label_ = lv_label_create(status_bar_);
|
||||
lv_label_set_text(battery_label_, "");
|
||||
lv_obj_set_style_text_font(battery_label_, fonts_.icon_font, 0);
|
||||
lv_obj_set_style_text_color(battery_label_, current_theme.text, 0);
|
||||
lv_obj_set_style_margin_left(battery_label_, 5, 0); // 添加左边距,与前面的元素分隔
|
||||
|
||||
low_battery_popup_ = lv_obj_create(screen);
|
||||
lv_obj_set_scrollbar_mode(low_battery_popup_, LV_SCROLLBAR_MODE_OFF);
|
||||
lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, fonts_.text_font->line_height * 2);
|
||||
lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, 0);
|
||||
lv_obj_set_style_bg_color(low_battery_popup_, current_theme.low_battery, 0);
|
||||
lv_obj_set_style_radius(low_battery_popup_, 10, 0);
|
||||
lv_obj_t* low_battery_label = lv_label_create(low_battery_popup_);
|
||||
lv_label_set_text(low_battery_label, Lang::Strings::BATTERY_NEED_CHARGE);
|
||||
lv_obj_set_style_text_color(low_battery_label, lv_color_white(), 0);
|
||||
lv_obj_center(low_battery_label);
|
||||
lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
#define MAX_MESSAGES 50
|
||||
void LcdDisplay::SetChatMessage(const char* role, const char* content) {
|
||||
DisplayLockGuard lock(this);
|
||||
if (content_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
//避免出现空的消息框
|
||||
if(strlen(content) == 0) return;
|
||||
|
||||
// Create a message bubble
|
||||
lv_obj_t* msg_bubble = lv_obj_create(content_);
|
||||
lv_obj_set_style_radius(msg_bubble, 8, 0);
|
||||
lv_obj_set_scrollbar_mode(msg_bubble, LV_SCROLLBAR_MODE_OFF);
|
||||
lv_obj_set_style_border_width(msg_bubble, 1, 0);
|
||||
lv_obj_set_style_border_color(msg_bubble, current_theme.border, 0);
|
||||
lv_obj_set_style_pad_all(msg_bubble, 8, 0);
|
||||
|
||||
// Create the message text
|
||||
lv_obj_t* msg_text = lv_label_create(msg_bubble);
|
||||
lv_label_set_text(msg_text, content);
|
||||
|
||||
// 计算文本实际宽度
|
||||
lv_coord_t text_width = lv_txt_get_width(content, strlen(content), fonts_.text_font, 0);
|
||||
|
||||
// 计算气泡宽度
|
||||
lv_coord_t max_width = LV_HOR_RES * 85 / 100 - 16; // 屏幕宽度的85%
|
||||
lv_coord_t min_width = 20;
|
||||
lv_coord_t bubble_width;
|
||||
|
||||
// 确保文本宽度不小于最小宽度
|
||||
if (text_width < min_width) {
|
||||
text_width = min_width;
|
||||
}
|
||||
|
||||
// 如果文本宽度小于最大宽度,使用文本宽度
|
||||
if (text_width < max_width) {
|
||||
bubble_width = text_width;
|
||||
} else {
|
||||
bubble_width = max_width;
|
||||
}
|
||||
|
||||
// 设置消息文本的宽度
|
||||
lv_obj_set_width(msg_text, bubble_width); // 减去padding
|
||||
lv_label_set_long_mode(msg_text, LV_LABEL_LONG_WRAP);
|
||||
lv_obj_set_style_text_font(msg_text, fonts_.text_font, 0);
|
||||
|
||||
// 设置气泡宽度
|
||||
lv_obj_set_width(msg_bubble, bubble_width);
|
||||
lv_obj_set_height(msg_bubble, LV_SIZE_CONTENT);
|
||||
|
||||
// Set alignment and style based on message role
|
||||
if (strcmp(role, "user") == 0) {
|
||||
// User messages are right-aligned with green background
|
||||
lv_obj_set_style_bg_color(msg_bubble, current_theme.user_bubble, 0);
|
||||
// Set text color for contrast
|
||||
lv_obj_set_style_text_color(msg_text, current_theme.text, 0);
|
||||
|
||||
// 设置自定义属性标记气泡类型
|
||||
lv_obj_set_user_data(msg_bubble, (void*)"user");
|
||||
|
||||
// Set appropriate width for content
|
||||
lv_obj_set_width(msg_bubble, LV_SIZE_CONTENT);
|
||||
lv_obj_set_height(msg_bubble, LV_SIZE_CONTENT);
|
||||
|
||||
// Add some margin
|
||||
lv_obj_set_style_margin_right(msg_bubble, 10, 0);
|
||||
|
||||
// Don't grow
|
||||
lv_obj_set_style_flex_grow(msg_bubble, 0, 0);
|
||||
} else if (strcmp(role, "assistant") == 0) {
|
||||
// Assistant messages are left-aligned with white background
|
||||
lv_obj_set_style_bg_color(msg_bubble, current_theme.assistant_bubble, 0);
|
||||
// Set text color for contrast
|
||||
lv_obj_set_style_text_color(msg_text, current_theme.text, 0);
|
||||
|
||||
// 设置自定义属性标记气泡类型
|
||||
lv_obj_set_user_data(msg_bubble, (void*)"assistant");
|
||||
|
||||
// Set appropriate width for content
|
||||
lv_obj_set_width(msg_bubble, LV_SIZE_CONTENT);
|
||||
lv_obj_set_height(msg_bubble, LV_SIZE_CONTENT);
|
||||
|
||||
// Add some margin
|
||||
lv_obj_set_style_margin_left(msg_bubble, -4, 0);
|
||||
|
||||
// Don't grow
|
||||
lv_obj_set_style_flex_grow(msg_bubble, 0, 0);
|
||||
} else if (strcmp(role, "system") == 0) {
|
||||
// System messages are center-aligned with light gray background
|
||||
lv_obj_set_style_bg_color(msg_bubble, current_theme.system_bubble, 0);
|
||||
// Set text color for contrast
|
||||
lv_obj_set_style_text_color(msg_text, current_theme.system_text, 0);
|
||||
|
||||
// 设置自定义属性标记气泡类型
|
||||
lv_obj_set_user_data(msg_bubble, (void*)"system");
|
||||
|
||||
// Set appropriate width for content
|
||||
lv_obj_set_width(msg_bubble, LV_SIZE_CONTENT);
|
||||
lv_obj_set_height(msg_bubble, LV_SIZE_CONTENT);
|
||||
|
||||
// Don't grow
|
||||
lv_obj_set_style_flex_grow(msg_bubble, 0, 0);
|
||||
}
|
||||
|
||||
// Create a full-width container for user messages to ensure right alignment
|
||||
if (strcmp(role, "user") == 0) {
|
||||
// Create a full-width container
|
||||
lv_obj_t* container = lv_obj_create(content_);
|
||||
lv_obj_set_width(container, LV_HOR_RES);
|
||||
lv_obj_set_height(container, LV_SIZE_CONTENT);
|
||||
|
||||
// Make container transparent and borderless
|
||||
lv_obj_set_style_bg_opa(container, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_border_width(container, 0, 0);
|
||||
lv_obj_set_style_pad_all(container, 0, 0);
|
||||
|
||||
// Move the message bubble into this container
|
||||
lv_obj_set_parent(msg_bubble, container);
|
||||
|
||||
// Right align the bubble in the container
|
||||
lv_obj_align(msg_bubble, LV_ALIGN_RIGHT_MID, -10, 0);
|
||||
|
||||
// Auto-scroll to this container
|
||||
lv_obj_scroll_to_view_recursive(container, LV_ANIM_ON);
|
||||
} else if (strcmp(role, "system") == 0) {
|
||||
// 为系统消息创建全宽容器以确保居中对齐
|
||||
lv_obj_t* container = lv_obj_create(content_);
|
||||
lv_obj_set_width(container, LV_HOR_RES);
|
||||
lv_obj_set_height(container, LV_SIZE_CONTENT);
|
||||
|
||||
// 使容器透明且无边框
|
||||
lv_obj_set_style_bg_opa(container, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_border_width(container, 0, 0);
|
||||
lv_obj_set_style_pad_all(container, 0, 0);
|
||||
|
||||
// 将消息气泡移入此容器
|
||||
lv_obj_set_parent(msg_bubble, container);
|
||||
|
||||
// 将气泡居中对齐在容器中
|
||||
lv_obj_align(msg_bubble, LV_ALIGN_CENTER, 0, 0);
|
||||
|
||||
// 自动滚动底部
|
||||
lv_obj_scroll_to_view_recursive(container, LV_ANIM_ON);
|
||||
} else {
|
||||
// For assistant messages
|
||||
// Left align assistant messages
|
||||
lv_obj_align(msg_bubble, LV_ALIGN_LEFT_MID, 0, 0);
|
||||
|
||||
// Auto-scroll to the message bubble
|
||||
lv_obj_scroll_to_view_recursive(msg_bubble, LV_ANIM_ON);
|
||||
}
|
||||
|
||||
// Store reference to the latest message label
|
||||
chat_message_label_ = msg_text;
|
||||
|
||||
// 检查消息数量是否超过限制
|
||||
uint32_t msg_count = lv_obj_get_child_cnt(content_);
|
||||
while (msg_count >= MAX_MESSAGES) {
|
||||
// 删除最早的消息(第一个子节点)
|
||||
lv_obj_t* oldest_msg = lv_obj_get_child(content_, 0);
|
||||
if (oldest_msg != nullptr) {
|
||||
lv_obj_del(oldest_msg);
|
||||
msg_count--;
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
void LcdDisplay::SetupUI() {
|
||||
DisplayLockGuard lock(this);
|
||||
|
||||
auto screen = lv_screen_active();
|
||||
lv_obj_set_style_text_font(screen, fonts_.text_font, 0);
|
||||
lv_obj_set_style_text_color(screen, current_theme.text, 0);
|
||||
lv_obj_set_style_bg_color(screen, current_theme.background, 0);
|
||||
|
||||
/* Container */
|
||||
container_ = lv_obj_create(screen);
|
||||
lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES);
|
||||
lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_all(container_, 0, 0);
|
||||
lv_obj_set_style_border_width(container_, 0, 0);
|
||||
lv_obj_set_style_pad_row(container_, 0, 0);
|
||||
lv_obj_set_style_bg_color(container_, current_theme.background, 0);
|
||||
lv_obj_set_style_border_color(container_, current_theme.border, 0);
|
||||
|
||||
/* Status bar */
|
||||
status_bar_ = lv_obj_create(container_);
|
||||
lv_obj_set_size(status_bar_, LV_HOR_RES, fonts_.text_font->line_height);
|
||||
lv_obj_set_style_radius(status_bar_, 0, 0);
|
||||
lv_obj_set_style_bg_color(status_bar_, current_theme.background, 0);
|
||||
lv_obj_set_style_text_color(status_bar_, current_theme.text, 0);
|
||||
|
||||
/* Content */
|
||||
content_ = lv_obj_create(container_);
|
||||
@@ -202,12 +567,16 @@ void LcdDisplay::SetupUI() {
|
||||
lv_obj_set_style_radius(content_, 0, 0);
|
||||
lv_obj_set_width(content_, LV_HOR_RES);
|
||||
lv_obj_set_flex_grow(content_, 1);
|
||||
lv_obj_set_style_pad_all(content_, 5, 0);
|
||||
lv_obj_set_style_bg_color(content_, current_theme.chat_background, 0);
|
||||
lv_obj_set_style_border_color(content_, current_theme.border, 0); // Border color for content
|
||||
|
||||
lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_COLUMN); // 垂直布局(从上到下)
|
||||
lv_obj_set_flex_align(content_, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_SPACE_EVENLY); // 子对象居中对齐,等距分布
|
||||
|
||||
emotion_label_ = lv_label_create(content_);
|
||||
lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_4, 0);
|
||||
lv_obj_set_style_text_color(emotion_label_, current_theme.text, 0);
|
||||
lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP);
|
||||
|
||||
chat_message_label_ = lv_label_create(content_);
|
||||
@@ -215,6 +584,7 @@ void LcdDisplay::SetupUI() {
|
||||
lv_obj_set_width(chat_message_label_, LV_HOR_RES * 0.9); // 限制宽度为屏幕宽度的 90%
|
||||
lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_WRAP); // 设置为自动换行模式
|
||||
lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_CENTER, 0); // 设置文本居中对齐
|
||||
lv_obj_set_style_text_color(chat_message_label_, current_theme.text, 0);
|
||||
|
||||
/* Status bar */
|
||||
lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW);
|
||||
@@ -227,10 +597,12 @@ void LcdDisplay::SetupUI() {
|
||||
network_label_ = lv_label_create(status_bar_);
|
||||
lv_label_set_text(network_label_, "");
|
||||
lv_obj_set_style_text_font(network_label_, fonts_.icon_font, 0);
|
||||
lv_obj_set_style_text_color(network_label_, current_theme.text, 0);
|
||||
|
||||
notification_label_ = lv_label_create(status_bar_);
|
||||
lv_obj_set_flex_grow(notification_label_, 1);
|
||||
lv_obj_set_style_text_align(notification_label_, LV_TEXT_ALIGN_CENTER, 0);
|
||||
lv_obj_set_style_text_color(notification_label_, current_theme.text, 0);
|
||||
lv_label_set_text(notification_label_, "");
|
||||
lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
|
||||
@@ -238,20 +610,23 @@ void LcdDisplay::SetupUI() {
|
||||
lv_obj_set_flex_grow(status_label_, 1);
|
||||
lv_label_set_long_mode(status_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
|
||||
lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0);
|
||||
lv_obj_set_style_text_color(status_label_, current_theme.text, 0);
|
||||
lv_label_set_text(status_label_, Lang::Strings::INITIALIZING);
|
||||
mute_label_ = lv_label_create(status_bar_);
|
||||
lv_label_set_text(mute_label_, "");
|
||||
lv_obj_set_style_text_font(mute_label_, fonts_.icon_font, 0);
|
||||
lv_obj_set_style_text_color(mute_label_, current_theme.text, 0);
|
||||
|
||||
battery_label_ = lv_label_create(status_bar_);
|
||||
lv_label_set_text(battery_label_, "");
|
||||
lv_obj_set_style_text_font(battery_label_, fonts_.icon_font, 0);
|
||||
lv_obj_set_style_text_color(battery_label_, current_theme.text, 0);
|
||||
|
||||
low_battery_popup_ = lv_obj_create(screen);
|
||||
lv_obj_set_scrollbar_mode(low_battery_popup_, LV_SCROLLBAR_MODE_OFF);
|
||||
lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, fonts_.text_font->line_height * 2);
|
||||
lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, 0);
|
||||
lv_obj_set_style_bg_color(low_battery_popup_, lv_color_black(), 0);
|
||||
lv_obj_set_style_bg_color(low_battery_popup_, current_theme.low_battery, 0);
|
||||
lv_obj_set_style_radius(low_battery_popup_, 10, 0);
|
||||
lv_obj_t* low_battery_label = lv_label_create(low_battery_popup_);
|
||||
lv_label_set_text(low_battery_label, Lang::Strings::BATTERY_NEED_CHARGE);
|
||||
@@ -259,6 +634,7 @@ void LcdDisplay::SetupUI() {
|
||||
lv_obj_center(low_battery_label);
|
||||
lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
#endif
|
||||
|
||||
void LcdDisplay::SetEmotion(const char* emotion) {
|
||||
struct Emotion {
|
||||
@@ -317,3 +693,196 @@ void LcdDisplay::SetIcon(const char* icon) {
|
||||
lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_4, 0);
|
||||
lv_label_set_text(emotion_label_, icon);
|
||||
}
|
||||
|
||||
void LcdDisplay::SetTheme(const std::string& theme_name) {
|
||||
DisplayLockGuard lock(this);
|
||||
|
||||
if (theme_name == "dark" || theme_name == "DARK") {
|
||||
current_theme = DARK_THEME;
|
||||
} else if (theme_name == "light" || theme_name == "LIGHT") {
|
||||
current_theme = LIGHT_THEME;
|
||||
} else {
|
||||
// Invalid theme name, return false
|
||||
ESP_LOGE(TAG, "Invalid theme name: %s", theme_name.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the active screen
|
||||
lv_obj_t* screen = lv_screen_active();
|
||||
|
||||
// Update the screen colors
|
||||
lv_obj_set_style_bg_color(screen, current_theme.background, 0);
|
||||
lv_obj_set_style_text_color(screen, current_theme.text, 0);
|
||||
|
||||
// Update container colors
|
||||
if (container_ != nullptr) {
|
||||
lv_obj_set_style_bg_color(container_, current_theme.background, 0);
|
||||
lv_obj_set_style_border_color(container_, current_theme.border, 0);
|
||||
}
|
||||
|
||||
// Update status bar colors
|
||||
if (status_bar_ != nullptr) {
|
||||
lv_obj_set_style_bg_color(status_bar_, current_theme.background, 0);
|
||||
lv_obj_set_style_text_color(status_bar_, current_theme.text, 0);
|
||||
|
||||
// Update status bar elements
|
||||
if (network_label_ != nullptr) {
|
||||
lv_obj_set_style_text_color(network_label_, current_theme.text, 0);
|
||||
}
|
||||
if (status_label_ != nullptr) {
|
||||
lv_obj_set_style_text_color(status_label_, current_theme.text, 0);
|
||||
}
|
||||
if (notification_label_ != nullptr) {
|
||||
lv_obj_set_style_text_color(notification_label_, current_theme.text, 0);
|
||||
}
|
||||
if (mute_label_ != nullptr) {
|
||||
lv_obj_set_style_text_color(mute_label_, current_theme.text, 0);
|
||||
}
|
||||
if (battery_label_ != nullptr) {
|
||||
lv_obj_set_style_text_color(battery_label_, current_theme.text, 0);
|
||||
}
|
||||
if (emotion_label_ != nullptr) {
|
||||
lv_obj_set_style_text_color(emotion_label_, current_theme.text, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Update content area colors
|
||||
if (content_ != nullptr) {
|
||||
lv_obj_set_style_bg_color(content_, current_theme.chat_background, 0);
|
||||
lv_obj_set_style_border_color(content_, current_theme.border, 0);
|
||||
|
||||
// If we have the chat message style, update all message bubbles
|
||||
#if CONFIG_USE_WECHAT_MESSAGE_STYLE
|
||||
// Iterate through all children of content (message containers or bubbles)
|
||||
uint32_t child_count = lv_obj_get_child_cnt(content_);
|
||||
for (uint32_t i = 0; i < child_count; i++) {
|
||||
lv_obj_t* obj = lv_obj_get_child(content_, i);
|
||||
if (obj == nullptr) continue;
|
||||
|
||||
lv_obj_t* bubble = nullptr;
|
||||
|
||||
// 检查这个对象是容器还是气泡
|
||||
// 如果是容器(用户或系统消息),则获取其子对象作为气泡
|
||||
// 如果是气泡(助手消息),则直接使用
|
||||
if (lv_obj_get_child_cnt(obj) > 0) {
|
||||
// 可能是容器,检查它是否为用户或系统消息容器
|
||||
// 用户和系统消息容器是透明的
|
||||
lv_opa_t bg_opa = lv_obj_get_style_bg_opa(obj, 0);
|
||||
if (bg_opa == LV_OPA_TRANSP) {
|
||||
// 这是用户或系统消息的容器
|
||||
bubble = lv_obj_get_child(obj, 0);
|
||||
} else {
|
||||
// 这可能是助手消息的气泡自身
|
||||
bubble = obj;
|
||||
}
|
||||
} else {
|
||||
// 没有子元素,可能是其他UI元素,跳过
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bubble == nullptr) continue;
|
||||
|
||||
// 使用保存的用户数据来识别气泡类型
|
||||
void* bubble_type_ptr = lv_obj_get_user_data(bubble);
|
||||
if (bubble_type_ptr != nullptr) {
|
||||
const char* bubble_type = static_cast<const char*>(bubble_type_ptr);
|
||||
|
||||
// 根据气泡类型应用正确的颜色
|
||||
if (strcmp(bubble_type, "user") == 0) {
|
||||
lv_obj_set_style_bg_color(bubble, current_theme.user_bubble, 0);
|
||||
} else if (strcmp(bubble_type, "assistant") == 0) {
|
||||
lv_obj_set_style_bg_color(bubble, current_theme.assistant_bubble, 0);
|
||||
} else if (strcmp(bubble_type, "system") == 0) {
|
||||
lv_obj_set_style_bg_color(bubble, current_theme.system_bubble, 0);
|
||||
}
|
||||
|
||||
// Update border color
|
||||
lv_obj_set_style_border_color(bubble, current_theme.border, 0);
|
||||
|
||||
// Update text color for the message
|
||||
if (lv_obj_get_child_cnt(bubble) > 0) {
|
||||
lv_obj_t* text = lv_obj_get_child(bubble, 0);
|
||||
if (text != nullptr) {
|
||||
// 根据气泡类型设置文本颜色
|
||||
if (strcmp(bubble_type, "system") == 0) {
|
||||
lv_obj_set_style_text_color(text, current_theme.system_text, 0);
|
||||
} else {
|
||||
lv_obj_set_style_text_color(text, current_theme.text, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 如果没有标记,回退到之前的逻辑(颜色比较)
|
||||
// ...保留原有的回退逻辑...
|
||||
lv_color_t bg_color = lv_obj_get_style_bg_color(bubble, 0);
|
||||
|
||||
// 改进bubble类型检测逻辑,不仅使用颜色比较
|
||||
bool is_user_bubble = false;
|
||||
bool is_assistant_bubble = false;
|
||||
bool is_system_bubble = false;
|
||||
|
||||
// 检查用户bubble
|
||||
if (lv_color_eq(bg_color, DARK_USER_BUBBLE_COLOR) ||
|
||||
lv_color_eq(bg_color, LIGHT_USER_BUBBLE_COLOR) ||
|
||||
lv_color_eq(bg_color, current_theme.user_bubble)) {
|
||||
is_user_bubble = true;
|
||||
}
|
||||
// 检查系统bubble
|
||||
else if (lv_color_eq(bg_color, DARK_SYSTEM_BUBBLE_COLOR) ||
|
||||
lv_color_eq(bg_color, LIGHT_SYSTEM_BUBBLE_COLOR) ||
|
||||
lv_color_eq(bg_color, current_theme.system_bubble)) {
|
||||
is_system_bubble = true;
|
||||
}
|
||||
// 剩余的都当作助手bubble处理
|
||||
else {
|
||||
is_assistant_bubble = true;
|
||||
}
|
||||
|
||||
// 根据bubble类型应用正确的颜色
|
||||
if (is_user_bubble) {
|
||||
lv_obj_set_style_bg_color(bubble, current_theme.user_bubble, 0);
|
||||
} else if (is_assistant_bubble) {
|
||||
lv_obj_set_style_bg_color(bubble, current_theme.assistant_bubble, 0);
|
||||
} else if (is_system_bubble) {
|
||||
lv_obj_set_style_bg_color(bubble, current_theme.system_bubble, 0);
|
||||
}
|
||||
|
||||
// Update border color
|
||||
lv_obj_set_style_border_color(bubble, current_theme.border, 0);
|
||||
|
||||
// Update text color for the message
|
||||
if (lv_obj_get_child_cnt(bubble) > 0) {
|
||||
lv_obj_t* text = lv_obj_get_child(bubble, 0);
|
||||
if (text != nullptr) {
|
||||
// 回退到颜色检测逻辑
|
||||
if (lv_color_eq(bg_color, current_theme.system_bubble) ||
|
||||
lv_color_eq(bg_color, DARK_SYSTEM_BUBBLE_COLOR) ||
|
||||
lv_color_eq(bg_color, LIGHT_SYSTEM_BUBBLE_COLOR)) {
|
||||
lv_obj_set_style_text_color(text, current_theme.system_text, 0);
|
||||
} else {
|
||||
lv_obj_set_style_text_color(text, current_theme.text, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
// Simple UI mode - just update the main chat message
|
||||
if (chat_message_label_ != nullptr) {
|
||||
lv_obj_set_style_text_color(chat_message_label_, current_theme.text, 0);
|
||||
}
|
||||
|
||||
if (emotion_label_ != nullptr) {
|
||||
lv_obj_set_style_text_color(emotion_label_, current_theme.text, 0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Update low battery popup
|
||||
if (low_battery_popup_ != nullptr) {
|
||||
lv_obj_set_style_bg_color(low_battery_popup_, current_theme.low_battery, 0);
|
||||
}
|
||||
|
||||
// No errors occurred. Save theme to settings
|
||||
Display::SetTheme(theme_name);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,12 @@ public:
|
||||
~LcdDisplay();
|
||||
virtual void SetEmotion(const char* emotion) override;
|
||||
virtual void SetIcon(const char* icon) override;
|
||||
#if CONFIG_USE_WECHAT_MESSAGE_STYLE
|
||||
virtual void SetChatMessage(const char* role, const char* content) override;
|
||||
#endif
|
||||
|
||||
// Add theme switching function
|
||||
virtual void SetTheme(const std::string& theme_name) override;
|
||||
};
|
||||
|
||||
// RGB LCD显示器
|
||||
|
||||
@@ -12,13 +12,14 @@ class Battery : public Thing {
|
||||
private:
|
||||
int level_ = 0;
|
||||
bool charging_ = false;
|
||||
bool discharging_ = false;
|
||||
|
||||
public:
|
||||
Battery() : Thing("Battery", "电池管理") {
|
||||
// 定义设备的属性
|
||||
properties_.AddNumberProperty("level", "当前电量百分比", [this]() -> int {
|
||||
auto& board = Board::GetInstance();
|
||||
if (board.GetBatteryLevel(level_, charging_)) {
|
||||
if (board.GetBatteryLevel(level_, charging_, discharging_)) {
|
||||
return level_;
|
||||
}
|
||||
return 0;
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
#include "iot/thing.h"
|
||||
#include "board.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "settings.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "Backlight"
|
||||
|
||||
namespace iot {
|
||||
|
||||
// 这里仅定义 Backlight 的属性和方法,不包含具体的实现
|
||||
class Backlight : public Thing {
|
||||
public:
|
||||
Backlight() : Thing("Backlight", "屏幕背光") {
|
||||
// 定义设备的属性
|
||||
properties_.AddNumberProperty("brightness", "当前亮度百分比", [this]() -> int {
|
||||
// 这里可以添加获取当前亮度的逻辑
|
||||
auto backlight = Board::GetInstance().GetBacklight();
|
||||
return backlight ? backlight->brightness() : 0;
|
||||
});
|
||||
|
||||
// 定义设备可以被远程执行的指令
|
||||
methods_.AddMethod("SetBrightness", "设置亮度", ParameterList({
|
||||
Parameter("brightness", "0到100之间的整数", kValueTypeNumber, true)
|
||||
}), [this](const ParameterList& parameters) {
|
||||
uint8_t brightness = static_cast<uint8_t>(parameters["brightness"].number());
|
||||
auto backlight = Board::GetInstance().GetBacklight();
|
||||
if (backlight) {
|
||||
backlight->SetBrightness(brightness, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace iot
|
||||
|
||||
DECLARE_THING(Backlight);
|
||||
@@ -12,7 +12,11 @@ namespace iot {
|
||||
// 这里仅定义 Lamp 的属性和方法,不包含具体的实现
|
||||
class Lamp : public Thing {
|
||||
private:
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32
|
||||
gpio_num_t gpio_num_ = GPIO_NUM_12;
|
||||
#else
|
||||
gpio_num_t gpio_num_ = GPIO_NUM_18;
|
||||
#endif
|
||||
bool power_ = false;
|
||||
|
||||
void InitializeGpio() {
|
||||
|
||||
54
main/iot/things/screen.cc
Normal file
54
main/iot/things/screen.cc
Normal file
@@ -0,0 +1,54 @@
|
||||
#include "iot/thing.h"
|
||||
#include "board.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "settings.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <string>
|
||||
|
||||
#define TAG "Screen"
|
||||
|
||||
namespace iot {
|
||||
|
||||
// 这里仅定义 Screen 的属性和方法,不包含具体的实现
|
||||
class Screen : public Thing {
|
||||
public:
|
||||
Screen() : Thing("Screen", "这是一个屏幕,可设置主题和亮度") {
|
||||
// 定义设备的属性
|
||||
properties_.AddStringProperty("theme", "主题", [this]() -> std::string {
|
||||
auto theme = Board::GetInstance().GetDisplay()->GetTheme();
|
||||
return theme;
|
||||
});
|
||||
|
||||
properties_.AddNumberProperty("brightness", "当前亮度百分比", [this]() -> int {
|
||||
// 这里可以添加获取当前亮度的逻辑
|
||||
auto backlight = Board::GetInstance().GetBacklight();
|
||||
return backlight ? backlight->brightness() : 100;
|
||||
});
|
||||
|
||||
// 定义设备可以被远程执行的指令
|
||||
methods_.AddMethod("SetTheme", "设置屏幕主题", ParameterList({
|
||||
Parameter("theme_name", "主题模式, light 或 dark", kValueTypeString, true)
|
||||
}), [this](const ParameterList& parameters) {
|
||||
std::string theme_name = static_cast<std::string>(parameters["theme_name"].string());
|
||||
auto display = Board::GetInstance().GetDisplay();
|
||||
if (display) {
|
||||
display->SetTheme(theme_name);
|
||||
}
|
||||
});
|
||||
|
||||
methods_.AddMethod("SetBrightness", "设置亮度", ParameterList({
|
||||
Parameter("brightness", "0到100之间的整数", kValueTypeNumber, true)
|
||||
}), [this](const ParameterList& parameters) {
|
||||
uint8_t brightness = static_cast<uint8_t>(parameters["brightness"].number());
|
||||
auto backlight = Board::GetInstance().GetBacklight();
|
||||
if (backlight) {
|
||||
backlight->SetBrightness(brightness, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace iot
|
||||
|
||||
DECLARE_THING(Screen);
|
||||
@@ -4,10 +4,6 @@
|
||||
|
||||
#define TAG "CircularStrip"
|
||||
|
||||
#define DEFAULT_BRIGHTNESS 4
|
||||
#define HIGH_BRIGHTNESS 16
|
||||
#define LOW_BRIGHTNESS 1
|
||||
|
||||
#define BLINK_INFINITE -1
|
||||
|
||||
CircularStrip::CircularStrip(gpio_num_t gpio, uint8_t max_leds) : max_leds_(max_leds) {
|
||||
@@ -52,7 +48,7 @@ CircularStrip::~CircularStrip() {
|
||||
}
|
||||
|
||||
|
||||
void CircularStrip::StaticColor(StripColor color) {
|
||||
void CircularStrip::SetAllColor(StripColor color) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
esp_timer_stop(strip_timer_);
|
||||
for (int i = 0; i < max_leds_; i++) {
|
||||
@@ -62,6 +58,14 @@ void CircularStrip::StaticColor(StripColor color) {
|
||||
led_strip_refresh(led_strip_);
|
||||
}
|
||||
|
||||
void CircularStrip::SetSingleColor(uint8_t index, StripColor color) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
esp_timer_stop(strip_timer_);
|
||||
colors_[index] = color;
|
||||
led_strip_set_pixel(led_strip_, index, color.red, color.green, color.blue);
|
||||
led_strip_refresh(led_strip_);
|
||||
}
|
||||
|
||||
void CircularStrip::Blink(StripColor color, int interval_ms) {
|
||||
for (int i = 0; i < max_leds_; i++) {
|
||||
colors_[i] = color;
|
||||
@@ -172,6 +176,11 @@ void CircularStrip::StartStripTask(int interval_ms, std::function<void()> cb) {
|
||||
esp_timer_start_periodic(strip_timer_, interval_ms * 1000);
|
||||
}
|
||||
|
||||
void CircularStrip::SetBrightness(uint8_t default_brightness, uint8_t low_brightness) {
|
||||
default_brightness_ = default_brightness;
|
||||
low_brightness_ = low_brightness;
|
||||
OnStateChanged();
|
||||
}
|
||||
|
||||
void CircularStrip::OnStateChanged() {
|
||||
auto& app = Application::GetInstance();
|
||||
@@ -179,12 +188,12 @@ void CircularStrip::OnStateChanged() {
|
||||
switch (device_state) {
|
||||
case kDeviceStateStarting: {
|
||||
StripColor low = { 0, 0, 0 };
|
||||
StripColor high = { LOW_BRIGHTNESS, LOW_BRIGHTNESS, DEFAULT_BRIGHTNESS };
|
||||
StripColor high = { low_brightness_, low_brightness_, default_brightness_ };
|
||||
Scroll(low, high, 3, 100);
|
||||
break;
|
||||
}
|
||||
case kDeviceStateWifiConfiguring: {
|
||||
StripColor color = { LOW_BRIGHTNESS, LOW_BRIGHTNESS, DEFAULT_BRIGHTNESS };
|
||||
StripColor color = { low_brightness_, low_brightness_, default_brightness_ };
|
||||
Blink(color, 500);
|
||||
break;
|
||||
}
|
||||
@@ -192,27 +201,27 @@ void CircularStrip::OnStateChanged() {
|
||||
FadeOut(50);
|
||||
break;
|
||||
case kDeviceStateConnecting: {
|
||||
StripColor color = { LOW_BRIGHTNESS, LOW_BRIGHTNESS, DEFAULT_BRIGHTNESS };
|
||||
StaticColor(color);
|
||||
StripColor color = { low_brightness_, low_brightness_, default_brightness_ };
|
||||
SetAllColor(color);
|
||||
break;
|
||||
}
|
||||
case kDeviceStateListening: {
|
||||
StripColor color = { DEFAULT_BRIGHTNESS, LOW_BRIGHTNESS, LOW_BRIGHTNESS };
|
||||
StaticColor(color);
|
||||
StripColor color = { default_brightness_, low_brightness_, low_brightness_ };
|
||||
SetAllColor(color);
|
||||
break;
|
||||
}
|
||||
case kDeviceStateSpeaking: {
|
||||
StripColor color = { LOW_BRIGHTNESS, DEFAULT_BRIGHTNESS, LOW_BRIGHTNESS };
|
||||
StaticColor(color);
|
||||
StripColor color = { low_brightness_, default_brightness_, low_brightness_ };
|
||||
SetAllColor(color);
|
||||
break;
|
||||
}
|
||||
case kDeviceStateUpgrading: {
|
||||
StripColor color = { LOW_BRIGHTNESS, DEFAULT_BRIGHTNESS, LOW_BRIGHTNESS };
|
||||
StripColor color = { low_brightness_, default_brightness_, low_brightness_ };
|
||||
Blink(color, 100);
|
||||
break;
|
||||
}
|
||||
case kDeviceStateActivating: {
|
||||
StripColor color = { LOW_BRIGHTNESS, DEFAULT_BRIGHTNESS, LOW_BRIGHTNESS };
|
||||
StripColor color = { low_brightness_, default_brightness_, low_brightness_ };
|
||||
Blink(color, 500);
|
||||
break;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user