Compare commits

...

36 Commits

Author SHA1 Message Date
ZhouKe
e440aa725a -修复 st7796 参数 (#375)
-增加非IPS的ST7796屏
2025-03-20 13:50:28 +08:00
Terrence
26e47427b9 Bump to 1.5.0 2025-03-20 03:13:14 +08:00
Terrence
f76502cb59 Add screen theme switch to atk boards and atoms3 2025-03-20 03:13:04 +08:00
Terrence
6bb1ab7583 Add theme switch to all LCD boards 2025-03-20 03:00:07 +08:00
Terrence
71799ed85c Bump to 1.4.8 2025-03-20 02:03:55 +08:00
Terrence
f553459121 fix atk-dnesp32s3 backlight output invert 2025-03-20 01:26:39 +08:00
Chinsyo
39e9d49288 Add single-led support for SenseCAP Watcher (#373)
* add single-led support for SenseCAP Watcher

* Rename blaklight.cc to backlight.cc
2025-03-20 00:45:09 +08:00
ZhouKe
d84e27aae7 微调微信界面 (#370)
* 微调微信界面

* 调整气泡框左边距
2025-03-20 00:42:52 +08:00
mtdxc
4884122cc1 cherry-pick #357, fix: 解决esp32-box-lite麦克风收音太小的问题 (#372)
Co-authored-by: cqm <cqm@97kid.com>
2025-03-20 00:41:13 +08:00
Xiaoxia
d0ae468fac add ja-JP sounds 2025-03-18 21:21:30 +08:00
ZhouKe
61cc1a236b add iot command for theme switch (#364) 2025-03-18 21:00:54 +08:00
SunnyBoy-y
13fd170a89 Update atk_dnesp32s3_box.cc (#362)
atk_dnesp32s3_box适配微信界面
2025-03-18 20:55:36 +08:00
SunnyBoy-y
b2bcb96942 修复部分开发版在config中DISPLAY_BACKLIGHT_OUTPUT_INVERT调整失效问题并适配类微信界面 (#361)
* Update atk_dnesp32s3.cc

修复在config中DISPLAY_BACKLIGHT_OUTPUT_INVERT调整失效问题

* Update atk_dnesp32s3_box.cc

修复在config中调整DISPLAY_BACKLIGHT_OUTPUT_INVERT失效的问题

* Update df_k10_board.cc

修复config中DISPLAY_BACKLIGHT_OUTPUT_INVERT调整失效问题

* Update esp_box_board.cc

修复更改config中DISPLAY_BACKLIGHT_OUTPUT_INVERT失效问题

* Update esp_box3_board.cc

修复在config中DISPLAY_BACKLIGHT_OUTPUT_INVERT调整失效问题

* Update atk_dnesp32s3.cc

dnesp32s3适配微信界面
2025-03-18 20:55:03 +08:00
ZhouKe
2c8f2f7d42 add dark mode (#363) 2025-03-18 10:33:22 +08:00
Paul Xu
349267ef23 fix: 解决行空板df-k10麦克风收音太小的问题 (#357)
Fixes 78/xiaozhi-esp32#350
2025-03-18 00:51:23 +08:00
ZhouKe
423ddcb287 在LCD面板使用微信聊天样式。 (#356)
* 在LCD面板使用微信聊天样式。

* 调整换行
2025-03-18 00:47:35 +08:00
Kevincoooool
0e28cd8a54 降低pclk频率/增大背光PWM频率防止电感啸叫 (#355) 2025-03-17 09:45:03 +08:00
Lucinhu
c165d6f7e1 fix: 解决sensecap-watcher显示异常 (#348) 2025-03-14 23:40:13 +08:00
ooxxU
a7ed9fd0cf fix ESP32 IOT Pin Bug, Pin35 are used for input and cannot be used for output. changed to Pin12. (#349) 2025-03-14 23:31:54 +08:00
bruceOrange22
4a7ef0b995 feat:解决第一次编译,报错,找不到release路径的问题 (#342)
* 解决第一次编译,报错,找不到release路径的问题

* Update release.py

---------

Co-authored-by: guoxianwei <xianwei.guo@orioniot.cn>
Co-authored-by: Xiaoxia <terrence@tenclass.com>
2025-03-14 11:12:59 +08:00
Xiaoxia
a1d150a0b4 fix play_p3 time 2025-03-14 03:06:53 +08:00
dujianmin
e777287463 重新整理了代码规范。去掉了一部分多余代码。 (#340)
* 添加嘟嘟开发板CHATX电池检测功能

* 从新整理了代码的书写规范。去掉一部分多余的代码

* Update du-chatx-wifi.cc

---------

Co-authored-by: Xiaoxia <terrence@tenclass.com>
2025-03-14 03:00:29 +08:00
ooxxU
e29fde6fa3 fix esp32 lcd show bug: Pin conflict (#339) 2025-03-14 02:53:52 +08:00
香草味的纳西妲
d235ac4f52 add: 音频/P3批量转换工具 (#336)
Co-authored-by: NyaOH-Nahida <3051979160@qq.com>
2025-03-13 14:21:04 +08:00
香草味的纳西妲
0ef913635d fix: bug修复 (#335) 2025-03-13 13:11:45 +08:00
laride
ff7f396f9d feat: Audio loudness normalization for assets files (#332) 2025-03-13 12:14:17 +08:00
香草味的纳西妲
7eb710d404 add: GUI版P3文件简易播放器 (#334) 2025-03-13 08:11:58 +08:00
Xiaoxia
5a2e9ac87f add play_p3 and README 2025-03-12 23:28:20 +08:00
laride
45fa2ca389 feat: add AtomS3R CAM/M12 + Echo Base (#330)
* feat: add AtomS3R CAM/M12 + Echo Base

* Update README.md

fix typing

---------

Co-authored-by: Xiaoxia <terrence@tenclass.com>
2025-03-12 21:56:47 +08:00
ooxxU
d60446bc53 新增 ESP32 系列开发板 LCD 屏幕显示的支持 (#313)
1. 支持 ESP32 系列开发板: DevKitC / NodeMcu-32S / GoouuuESP32 / ESP32 DoIt / ESP-32S
2. 注意: 非ESP32-C3 / 非ESP32-S3

Co-authored-by: Xiaoxia <terrence@tenclass.com>
2025-03-12 02:30:45 +08:00
wdmomoxx
e23f6cf6d8 增加设备ESP32-CGC (#201)
* Update Kconfig.projbuild

增加设备ESP32-CGC

* Update CMakeLists.txt

新增设备EPS32-CGC

* Update idf_component.yml

添加ST7735显示驱动

* Update lamp.cc

与ESP32的屏幕GPIO引脚冲突修改

* Add files via upload

新增一个引脚控制风扇

* Update wifi_board.cc

编译提示错误没有这个函数,暂时注释掉
//wifi_ap.SetLanguage(Lang::CODE);

* Create README.md

创建新设备ESP32-CGC

* Add files via upload

创建新设备ESP32-CGC

* Add files via upload

ESP32-CGC设备引脚图

* Update esp32_cgc_board.cc

* Update idf_component.yml

* Delete main/iot/things/newfan.cc

* Update lamp.cc

* Update wifi_board.cc

* Update idf_component.yml

* Delete main/boards/esp32-cgc/esp32_cgc_board.cc

* Delete main/boards/esp32-cgc/config.h

* Delete main/boards/esp32-cgc/README.md

* Add files via upload

添加设备ESP32-CGC

* Update Kconfig.projbuild

添加ESP32-CGC多屏选择支持,增加一个ST7735-128x128分辨率

* Update config.json

* Delete docs/ESP32-CGC-PINMAP.png

* Update README.md

* Update README.md

* Update esp32_cgc_board.cc

* Update config.h

* Update esp32_cgc_board.cc

跟随主线修改

* Create README.md

加入了ESP32 CGC 144开发板

* Add files via upload

加入ESP32 CGC 144开发板

* Update esp32_cgc_144_board.cc

* Update config.h

* Update Kconfig.projbuild

增加ESP32 CGC 144

* Update CMakeLists.txt

增加ESP32 CGC 144

* Update config.json

* Update config.h

* Update esp32_cgc_144_board.cc

修改屏幕控制参数

* Add files via upload

* Update Kconfig.projbuild

删除重复定义

* Update esp32_cgc_144_board.cc

修改音量控制,增加语音配网指令

* Add files via upload

* Delete main/boards/esp32-cgc-144/esp32_cgc_144_lcd_display.h

* Delete main/boards/esp32-cgc-144/esp32_cgc_144_lcd_display.cc

* Update board_control.cc

修改背光控制逻辑

* Update esp32_cgc_144_board.cc

跟随主代码更新

* Update config.h

删除不使用的引脚定义

* Update esp32_cgc_board.cc

跟随主代码更新,去除多余引脚

* Update esp32_cgc_board.cc

* Update board_control.cc

* Update config.h

* Update esp32_cgc_144_board.cc

* Update Kconfig.projbuild

* Update CMakeLists.txt

* Update CMakeLists.txt

先移除144开发板

* Update Kconfig.projbuild

先移除144开发板

* Delete main/boards/esp32-cgc-144 directory

先移除144开发板

* Update esp32_cgc_board.cc

---------

Co-authored-by: Xiaoxia <terrence@tenclass.com>
2025-03-12 02:15:53 +08:00
Xiaoxia
2fdcda30ea fix esp-box-lite compiling error 2025-03-11 03:58:33 +08:00
hbs2001
4b46e4890d 添加了ESP32S3_KORVO2_V3官方lcd屏幕的支持 (#323)
* ESP32S3_KORVO2_V3添加官方开发板屏幕支持

* 修正笔误

* Update esp32s3_korvo2_v3_board.cc

---------

Co-authored-by: hbs2001 <wangyuchong@yuchong.wang>
Co-authored-by: Xiaoxia <terrence@tenclass.com>
2025-03-11 01:43:16 +08:00
virgil
236e21ffc2 Fix sensecap-watcher board bug (#322)
* fix: fixed the issue that the device enters re-network configuration mode by mistake when long pressing to start the device.

* fix: Do not shut down when connected to type-c.

* fix: fixed the issue that firmware verification failed during OTA for 32M flash.

* doc: update readme.

* fix: Solve the problem that the partition table configuration is not effective.
2025-03-11 01:38:42 +08:00
Terrence
9bc8be2f25 add IoT led control to kevin c3 2025-03-10 20:50:02 +08:00
Xiaoxia
dd1139b169 Add discharging status 2025-03-09 19:18:10 +08:00
111 changed files with 3507 additions and 270 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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>
## ファームウェア部分

View File

@@ -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

View File

@@ -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

View File

@@ -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

Binary file not shown.

BIN
main/assets/ja-JP/1.p3 Normal file

Binary file not shown.

BIN
main/assets/ja-JP/2.p3 Normal file

Binary file not shown.

BIN
main/assets/ja-JP/3.p3 Normal file

Binary file not shown.

BIN
main/assets/ja-JP/4.p3 Normal file

Binary file not shown.

BIN
main/assets/ja-JP/5.p3 Normal file

Binary file not shown.

BIN
main/assets/ja-JP/6.p3 Normal file

Binary file not shown.

BIN
main/assets/ja-JP/7.p3 Normal file

Binary file not shown.

BIN
main/assets/ja-JP/8.p3 Normal file

Binary file not shown.

BIN
main/assets/ja-JP/9.p3 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -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() {

View File

@@ -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) {

View File

@@ -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:

View File

@@ -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:

View File

@@ -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_

View File

@@ -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:

View 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 按钮重启。

View File

@@ -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);

View 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_

View 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\""
]
}
]
}

View File

@@ -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:

View 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_

View 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"
]
}
]
}

View 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);

View File

@@ -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:

View File

@@ -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

View File

@@ -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
};

View File

@@ -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;
}

View File

@@ -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;
};

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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);

View 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;
}
};

View File

@@ -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:

View File

@@ -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_));
}

View File

@@ -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:

View File

@@ -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:

View File

@@ -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"));
}

View 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
```

View 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_

View 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"
]
}

View 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);

View File

@@ -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;
}

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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;
}
};

View File

@@ -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 {

View 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);
});
}

View 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

View File

@@ -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:

View File

@@ -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:

View File

@@ -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, \

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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;
}

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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

View File

@@ -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"
]
}
]

View File

@@ -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();

View File

@@ -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);

View File

@@ -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;
}
};

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -167,6 +167,11 @@ public:
return is_charging_;
}
bool IsDischarging() {
// 没有区分充电和放电,所以直接返回相反状态
return !is_charging_;
}
uint8_t GetBatteryLevel() {
return battery_level_;
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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显示器

View File

@@ -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;

View File

@@ -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);

View File

@@ -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
View 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);

View File

@@ -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