Merge main into remote-dev

This commit is contained in:
jyy
2026-04-24 11:21:34 +08:00
95 changed files with 3615 additions and 41513 deletions
+2 -1
View File
@@ -2,7 +2,8 @@
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
set(PROJECT_VER "2.1.0")
set(PROJECT_VER "1.2.4")
add_definitions(-DFIRMWARE_VERSION=\"${PROJECT_VER}\")
# Add this line to disable the specific warning
add_compile_options(-Wno-missing-field-initializers)
+1 -2
View File
@@ -1,4 +1,3 @@
# StackChan Firmware
## Build
@@ -10,7 +9,7 @@ python3 ./fetch_repos.py
### Tool Chains
[ESP-IDF v5.5.1](https://docs.espressif.com/projects/esp-idf/en/v5.5.1/esp32s3/index.html)
[ESP-IDF v5.5.4](https://docs.espressif.com/projects/esp-idf/en/v5.5.4/esp32s3/index.html)
### Build
File diff suppressed because it is too large Load Diff
+90 -16
View File
@@ -23,6 +23,7 @@ list(APPEND STACK_CHAN_SOURCES main.cpp)
# Define source files
set(SOURCES "audio/audio_codec.cc"
"audio/audio_service.cc"
"audio/demuxer/ogg_demuxer.cc"
"audio/codecs/no_audio_codec.cc"
"audio/codecs/box_audio_codec.cc"
"audio/codecs/es8311_audio_codec.cc"
@@ -38,7 +39,7 @@ set(SOURCES "audio/audio_codec.cc"
"display/lcd_display.cc"
"display/oled_display.cc"
"display/lvgl_display/lvgl_display.cc"
"display/emote_display.cc"
# "display/emote_display.cc"
"display/lvgl_display/emoji_collection.cc"
"display/lvgl_display/lvgl_theme.cc"
"display/lvgl_display/lvgl_font.cc"
@@ -63,15 +64,34 @@ set(SOURCES "audio/audio_codec.cc"
# Transform relative paths to absolute paths from xiaozhi-esp32/main
list(TRANSFORM SOURCES PREPEND "${XIAOZHI_MAIN_DIR}/")
set(INCLUDE_DIRS "." "display" "display/lvgl_display" "display/lvgl_display/jpg" "audio" "protocols")
set(INCLUDE_DIRS "." "display" "display/lvgl_display" "display/lvgl_display/jpg" "audio" "audio/demuxer" "protocols")
# Transform include dirs to absolute paths from xiaozhi-esp32/main
list(TRANSFORM INCLUDE_DIRS PREPEND "${XIAOZHI_MAIN_DIR}/")
# Add board common files
file(GLOB BOARD_COMMON_SOURCES ${XIAOZHI_MAIN_DIR}/boards/common/*.cc)
list(APPEND SOURCES ${BOARD_COMMON_SOURCES} ${STACK_CHAN_SOURCES})
list(APPEND SOURCES
"${XIAOZHI_MAIN_DIR}/boards/common/board.cc"
"${XIAOZHI_MAIN_DIR}/boards/common/wifi_board.cc"
"${XIAOZHI_MAIN_DIR}/boards/common/ml307_board.cc"
"${XIAOZHI_MAIN_DIR}/boards/common/nt26_board.cc"
"${XIAOZHI_MAIN_DIR}/boards/common/dual_network_board.cc"
"${XIAOZHI_MAIN_DIR}/boards/common/adc_battery_monitor.cc"
"${XIAOZHI_MAIN_DIR}/boards/common/afsk_demod.cc"
"${XIAOZHI_MAIN_DIR}/boards/common/axp2101.cc"
"${XIAOZHI_MAIN_DIR}/boards/common/backlight.cc"
"${XIAOZHI_MAIN_DIR}/boards/common/button.cc"
"${XIAOZHI_MAIN_DIR}/boards/common/i2c_device.cc"
"${XIAOZHI_MAIN_DIR}/boards/common/knob.cc"
"${XIAOZHI_MAIN_DIR}/boards/common/power_save_timer.cc"
"${XIAOZHI_MAIN_DIR}/boards/common/press_to_talk_mcp_tool.cc"
"${XIAOZHI_MAIN_DIR}/boards/common/sleep_timer.cc"
"${XIAOZHI_MAIN_DIR}/boards/common/sy6970.cc"
"${XIAOZHI_MAIN_DIR}/boards/common/system_reset.cc"
${STACK_CHAN_SOURCES}
)
list(APPEND INCLUDE_DIRS ${XIAOZHI_MAIN_DIR}/boards/common ${STACK_CHAN_INCLUDE_DIRS})
idf_build_get_property(build_components BUILD_COMPONENTS)
# Function to find component dynamically by pattern
function(find_component_by_pattern PATTERN COMPONENT_VAR PATH_VAR)
@@ -89,6 +109,8 @@ endfunction()
set(BUILTIN_TEXT_FONT font_puhui_14_1)
set(BUILTIN_ICON_FONT font_awesome_14_1)
set(EMOTE_RESOLUTION "320_240")
# Add board files according to BOARD_TYPE
# Set default assets if the board uses partition table V2
if(CONFIG_BOARD_TYPE_M5STACK_STACK_CHAN)
@@ -98,10 +120,17 @@ if(CONFIG_BOARD_TYPE_M5STACK_STACK_CHAN)
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
endif()
file(GLOB BOARD_SOURCES
${XIAOZHI_MAIN_DIR}/boards/${BOARD_TYPE}/*.cc
${XIAOZHI_MAIN_DIR}/boards/${BOARD_TYPE}/*.c
)
if(MANUFACTURER)
file(GLOB BOARD_SOURCES
${XIAOZHI_MAIN_DIR}/boards/${MANUFACTURER}/${BOARD_TYPE}/*.cc
${XIAOZHI_MAIN_DIR}/boards/${MANUFACTURER}/${BOARD_TYPE}/*.c
)
else()
file(GLOB BOARD_SOURCES
${XIAOZHI_MAIN_DIR}/boards/${BOARD_TYPE}/*.cc
${XIAOZHI_MAIN_DIR}/boards/${BOARD_TYPE}/*.c
)
endif()
list(APPEND SOURCES ${BOARD_SOURCES})
# Select audio processor according to Kconfig
@@ -119,7 +148,7 @@ endif()
# Auto Select Additional Sources
if (CONFIG_USE_ESP_BLUFI_WIFI_PROVISIONING)
list(APPEND SOURCES "${XIAOZHI_MAIN_DIR}/boards/common/blufi.cpp")
list(APPEND SOURCES "boards/common/blufi.cpp")
endif ()
# Select language directory according to Kconfig
if(CONFIG_LANGUAGE_ZH_CN)
@@ -228,7 +257,7 @@ if(NOT LANG_DIR STREQUAL "en-US")
endforeach()
endif()
# file(GLOB COMMON_SOUNDS ${XIAOZHI_MAIN_DIR}/assets/common/*.ogg)
# file(GLOB COMMON_SOUNDS ${CMAKE_CURRENT_SOURCE_DIR}/assets/common/*.ogg)
file(GLOB COMMON_SOUNDS
${XIAOZHI_MAIN_DIR}/assets/common/*.ogg
assets/sfx/*.ogg
@@ -240,16 +269,53 @@ if(CONFIG_IDF_TARGET_ESP32)
"audio/codecs/es8388_audio_codec.cc"
"audio/codecs/es8389_audio_codec.cc"
"led/gpio_led.cc"
"${XIAOZHI_MAIN_DIR}/boards/common/esp32_camera.cc"
"display/lvgl_display/jpg/image_to_jpeg.cpp"
"display/lvgl_display/jpg/jpeg_to_image.c"
"boards/common/nt26_board.cc"
"boards/common/ml307_board.cc"
"boards/common/dual_network_board.cc"
)
endif()
# Include EspVideo if target is ESP32S3 or ESP32P4
if(CONFIG_IDF_TARGET_ESP32S3 OR CONFIG_IDF_TARGET_ESP32P4)
list(APPEND SOURCES "${XIAOZHI_MAIN_DIR}/boards/common/esp_video.cc"
"${XIAOZHI_MAIN_DIR}/boards/common/rndis_board.cc"
)
endif()
# Include Esp32Camera if target is ESP32S3
if(CONFIG_IDF_TARGET_ESP32S3)
list(APPEND SOURCES "${XIAOZHI_MAIN_DIR}/boards/common/esp32_camera.cc")
endif()
idf_component_register(SRCS ${SOURCES}
EMBED_FILES ${LANG_SOUNDS} ${COMMON_SOUNDS}
INCLUDE_DIRS ${INCLUDE_DIRS}
WHOLE_ARCHIVE
PRIV_REQUIRES
esp_pm
esp_psram
esp_netif
esp_driver_gpio
esp_driver_uart
esp_driver_spi
esp_driver_i2c
esp_driver_i2s
esp_driver_jpeg
esp_driver_ppa
esp_app_format
app_update
spi_flash
console
efuse
bt
fatfs
ArduinoJson
esp-now
mooncake
mooncake_log
smooth_ui_toolkit
)
# Use target_compile_definitions to define BOARD_TYPE, BOARD_NAME
@@ -265,12 +331,12 @@ target_compile_definitions(${COMPONENT_LIB}
# Add generation rules
add_custom_command(
OUTPUT ${LANG_HEADER}
COMMAND python ${PROJECT_DIR}/xiaozhi-esp32/scripts/gen_lang.py
COMMAND python ${XIAOZHI_MAIN_DIR}/../scripts/gen_lang.py
--language "${LANG_DIR}"
--output "${LANG_HEADER}"
DEPENDS
${LANG_JSON}
${PROJECT_DIR}/xiaozhi-esp32/scripts/gen_lang.py
${XIAOZHI_MAIN_DIR}/../scripts/gen_lang.py
COMMENT "Generating ${LANG_DIR} language config"
)
@@ -328,7 +394,7 @@ endforeach()
endif()
set(DEFAULT_ASSETS_EXTRA_FILES "${CMAKE_CURRENT_SOURCE_DIR}/assets/assets_bin")
set(DEFAULT_ASSETS_EXTRA_FILES "${CMAKE_CURRENT_SOURCE_DIR}/assets/assets_bin")
# Function to build default assets based on configuration
function(build_default_assets_bin)
@@ -362,10 +428,10 @@ function(build_default_assets_bin)
# Create custom command to build assets
add_custom_command(
OUTPUT ${GENERATED_ASSETS_BIN}
COMMAND python ${PROJECT_DIR}/xiaozhi-esp32/scripts/build_default_assets.py ${BUILD_ARGS}
COMMAND python ${XIAOZHI_MAIN_DIR}/../scripts/build_default_assets.py ${BUILD_ARGS}
DEPENDS
${SDKCONFIG}
${PROJECT_DIR}/xiaozhi-esp32/scripts/build_default_assets.py
${XIAOZHI_MAIN_DIR}/../scripts/build_default_assets.py
COMMENT "Building default assets.bin based on configuration"
VERBATIM
)
@@ -452,6 +518,14 @@ if ("${size}" AND "${offset}")
get_assets_local_file("${CONFIG_CUSTOM_ASSETS_FILE}" ASSETS_LOCAL_FILE)
esptool_py_flash_to_partition(flash "assets" "${ASSETS_LOCAL_FILE}")
message(STATUS "Custom assets flash configured: ${ASSETS_LOCAL_FILE} -> assets partition")
elseif(CONFIG_FLASH_EXPRESSION_ASSETS)
set(ASSETS_NAME "expression_assets")
set(ASSETS_PARTITION "assets")
set(ASSETS_FILE "${CMAKE_BINARY_DIR}/${ASSETS_NAME}.bin")
build_speaker_assets_bin("${ASSETS_PARTITION}" ${EMOTE_RESOLUTION} ${ASSETS_FILE} ${CONFIG_MMAP_FILE_NAME_LENGTH})
message(STATUS "Generated emote assets: ${ASSETS_FILE} -> ${ASSETS_PARTITION} partition")
esptool_py_flash_to_partition(flash "${ASSETS_PARTITION}" "${ASSETS_FILE}")
elseif(CONFIG_FLASH_NONE_ASSETS)
message(STATUS "Assets flashing disabled (FLASH_NONE_ASSETS)")
endif()
+138 -43
View File
@@ -8,7 +8,8 @@ config OTA_URL
choice
prompt "Flash Assets"
default FLASH_DEFAULT_ASSETS
default FLASH_DEFAULT_ASSETS if !USE_EMOTE_MESSAGE_STYLE
default FLASH_EXPRESSION_ASSETS if USE_EMOTE_MESSAGE_STYLE
help
Select the assets to flash.
@@ -16,8 +17,12 @@ choice
bool "Do not flash assets"
config FLASH_DEFAULT_ASSETS
bool "Flash Default Assets"
depends on !USE_EMOTE_MESSAGE_STYLE
config FLASH_CUSTOM_ASSETS
bool "Flash Custom Assets"
config FLASH_EXPRESSION_ASSETS
bool "Flash Emote Assets"
depends on USE_EMOTE_MESSAGE_STYLE
endchoice
config CUSTOM_ASSETS_FILE
@@ -129,6 +134,9 @@ choice BOARD_TYPE
config BOARD_TYPE_BREAD_COMPACT_ML307
bool "Bread Compact ML307/EC801E (面包板 4G)"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_BREAD_COMPACT_NT26
bool "Bread Compact NT26 (面包板 4G)"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_BREAD_COMPACT_ESP32
bool "Bread Compact ESP32 DevKit (面包板)"
depends on IDF_TARGET_ESP32
@@ -147,9 +155,15 @@ choice BOARD_TYPE
config BOARD_TYPE_ESP_KORVO2_V3
bool "Espressif Korvo2 V3"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_ESP_KORVO2_V3_RNDIS
bool "Espressif Korvo2 V3 RNDIS"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_ESP_SPARKBOT
bool "Espressif SparkBot"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_ESP_SENSAIRSHUTTLE
bool "Espressif ESP-SensairShuttle"
depends on IDF_TARGET_ESP32C5
config BOARD_TYPE_ESP_SPOT_S3
bool "Espressif Spot-S3"
depends on IDF_TARGET_ESP32S3
@@ -201,6 +215,15 @@ choice BOARD_TYPE
config BOARD_TYPE_LICHUANG_DEV_C3
bool "立创·实战派 ESP32-C3"
depends on IDF_TARGET_ESP32C3
config BOARD_TYPE_EDA_TV_PRO
bool "EDA课程案例 EDA-TV-Pro"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_EDA_ROBOT_PRO
bool "EDA课程案例 EDA-Robot-Pro"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_EDA_SUPER_BEAR
bool "EDA课程案例 EDA-Super-Bear"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_DF_K10
bool "DFRobot 行空板 k10"
depends on IDF_TARGET_ESP32S3
@@ -240,77 +263,110 @@ choice BOARD_TYPE
config BOARD_TYPE_M5STACK_ATOM_ECHOS3R
bool "M5Stack AtomEchoS3R"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_M5STACK_CARDPUTER_ADV
bool "M5Stack Cardputer Adv"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_M5STACK_ATOM_MATRIX_ECHO_BASE
bool "M5Stack AtomMatrix + Echo Base"
depends on IDF_TARGET_ESP32
config BOARD_TYPE_WAVESHARE_S3_AUDIO_BOARD
config BOARD_TYPE_WAVESHARE_ESP32_TOUCH_LCD_3_5
bool "Waveshare ESP32-Touch-LCD-3.5"
depends on IDF_TARGET_ESP32
config BOARD_TYPE_WAVESHARE_ESP32_S3_AUDIO_BOARD
bool "Waveshare ESP32-S3-Audio-Board"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_WAVESHARE_S3_TOUCH_AMOLED_1_8
config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_8
bool "Waveshare ESP32-S3-Touch-AMOLED-1.8"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_WAVESHARE_S3_TOUCH_AMOLED_2_06
config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_2_06
bool "Waveshare ESP32-S3-Touch-AMOLED-2.06"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_WAVESHARE_C6_TOUCH_AMOLED_2_06
config BOARD_TYPE_WAVESHARE_ESP32_C6_TOUCH_AMOLED_2_06
bool "Waveshare ESP32-C6-Touch-AMOLED-2.06"
depends on IDF_TARGET_ESP32C6
config BOARD_TYPE_WAVESHARE_S3_TOUCH_AMOLED_1_75
config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_75
bool "Waveshare ESP32-S3-Touch-AMOLED-1.75"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_1_83
config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_75C
bool "Waveshare ESP32-S3-Touch-AMOLED-1.75C"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_1_83
bool "Waveshare ESP32-S3-Touch-LCD-1.83"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_4B
config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_4B
bool "Waveshare ESP32-S3-Touch-LCD-4B"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_1_85C
config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_4_3C
bool "Waveshare ESP32-S3-Touch-LCD-4.3C"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_1_85C
bool "Waveshare ESP32-S3-Touch-LCD-1.85C"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_1_85
config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_1_85
bool "Waveshare ESP32-S3-Touch-LCD-1.85"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_1_46
config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_1_46
bool "Waveshare ESP32-S3-Touch-LCD-1.46"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_WAVESHARE_C6_LCD_1_69
config BOARD_TYPE_WAVESHARE_ESP32_C6_LCD_1_69
bool "Waveshare ESP32-C6-LCD-1.69"
depends on IDF_TARGET_ESP32C6
config BOARD_TYPE_WAVESHARE_C6_TOUCH_LCD_1_83
config BOARD_TYPE_WAVESHARE_ESP32_C6_TOUCH_LCD_1_83
bool "Waveshare ESP32-C6-Touch-LCD-1.83"
depends on IDF_TARGET_ESP32C6
config BOARD_TYPE_WAVESHARE_C6_TOUCH_AMOLED_1_43
config BOARD_TYPE_WAVESHARE_ESP32_C6_TOUCH_AMOLED_1_43
bool "Waveshare ESP32-C6-Touch-AMOLOED-1.43"
depends on IDF_TARGET_ESP32C6
config BOARD_TYPE_WAVESHARE_C6_TOUCH_AMOLED_1_32
config BOARD_TYPE_WAVESHARE_ESP32_C6_TOUCH_AMOLED_1_32
bool "Waveshare ESP32-C6-Touch-AMOLOED-1.32"
depends on IDF_TARGET_ESP32C6
config BOARD_TYPE_WAVESHARE_S3_TOUCH_AMOLED_1_32
config BOARD_TYPE_WAVESHARE_ESP32_C6_TOUCH_AMOLED_1_8
bool "Waveshare ESP32-C6-Touch-AMOLED-1.8"
depends on IDF_TARGET_ESP32C6
config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_32
bool "Waveshare ESP32-S3-Touch-AMOLOED-1.32"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_3_49
config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_3_49
bool "Waveshare ESP32-S3-Touch-LCD-3.49"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_3_5
config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_3_5
bool "Waveshare ESP32-S3-Touch-LCD-3.5"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_WAVESHARE_S3_ePaper_1_54
bool "Waveshare ESP32-S3-ePaper-1.54"
config BOARD_TYPE_WAVESHARE_ESP32_S3_ePaper_1_54_v1
bool "Waveshare ESP32-S3-ePaper-1.54_v1"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_3_5B
config BOARD_TYPE_WAVESHARE_ESP32_S3_ePaper_1_54_v2
bool "Waveshare ESP32-S3-ePaper-1.54_v2"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_WAVESHARE_ESP32_S3_RLCD_4_2
bool "Waveshare ESP32-S3-RLCD-4.2"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_3_5B
bool "Waveshare ESP32-S3-Touch-LCD-3.5B"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_WAVESHARE_P4_NANO
config BOARD_TYPE_WAVESHARE_ESP32_P4_NANO
bool "Waveshare ESP32-P4-NANO"
depends on IDF_TARGET_ESP32P4
config BOARD_TYPE_WAVESHARE_P4_WIFI6_TOUCH_LCD_4B
config BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4B
bool "Waveshare ESP32-P4-WIFI6-Touch-LCD-4B"
depends on IDF_TARGET_ESP32P4
config BOARD_TYPE_WAVESHARE_P4_WIFI6_TOUCH_LCD_7B
config BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_7B
bool "Waveshare ESP32-P4-WIFI6-Touch-LCD-7B"
depends on IDF_TARGET_ESP32P4
config BOARD_TYPE_WAVESHARE_P4_WIFI6_TOUCH_LCD_XC
bool "Waveshare ESP32-P4-WIFI6-Touch-LCD-3.4C or ESP32-P4-WIFI6-Touch-LCD-4C"
config BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_3_4C
bool "Waveshare ESP32-P4-WIFI6-Touch-LCD-3.4C"
depends on IDF_TARGET_ESP32P4
config BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4C
bool "Waveshare ESP32-P4-WIFI6-Touch-LCD-4C"
depends on IDF_TARGET_ESP32P4
config BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_7
bool "Waveshare ESP32-P4-WIFI6-Touch-LCD-7"
depends on IDF_TARGET_ESP32P4
config BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_8
bool "Waveshare ESP32-P4-WIFI6-Touch-LCD-8"
depends on IDF_TARGET_ESP32P4
config BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_10_1
bool "Waveshare ESP32-P4-WIFI6-Touch-LCD-10.1"
depends on IDF_TARGET_ESP32P4
config BOARD_TYPE_TUDOUZI
bool "土豆子"
@@ -390,6 +446,9 @@ choice BOARD_TYPE
config BOARD_TYPE_XINGZHI_METAL_1_54_WIFI
bool "无名科技星智1.54 METAL(wifi)"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_XINGZHI_ABS_2_0
bool "无名科技星智ABS 2.0"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_SEEED_STUDIO_SENSECAP_WATCHER
bool "Seeed Studio SenseCAP Watcher"
depends on IDF_TARGET_ESP32S3
@@ -414,6 +473,12 @@ choice BOARD_TYPE
config BOARD_TYPE_ZHENGCHEN_1_54TFT_ML307
bool "征辰科技1.54(ML307)"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_ZHENGCHEN_CAM
bool "征辰科技AI Camera"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_ZHENGCHEN_CAM_ML307
bool "征辰科技AI Camera(ML307)"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_MINSI_K08_DUAL
bool "敏思科技K08(DUAL)"
depends on IDF_TARGET_ESP32S3
@@ -486,7 +551,7 @@ choice ESP_S3_LCD_EV_Board_Version_TYPE
endchoice
choice DISPLAY_OLED_TYPE
depends on BOARD_TYPE_BREAD_COMPACT_WIFI || BOARD_TYPE_BREAD_COMPACT_ML307 || BOARD_TYPE_BREAD_COMPACT_ESP32 || BOARD_TYPE_HU_087
depends on BOARD_TYPE_BREAD_COMPACT_WIFI || BOARD_TYPE_BREAD_COMPACT_ML307 || BOARD_TYPE_BREAD_COMPACT_NT26 || BOARD_TYPE_BREAD_COMPACT_ESP32 || BOARD_TYPE_HU_087
prompt "OLED Type"
default OLED_SSD1306_128X32
help
@@ -500,7 +565,7 @@ choice DISPLAY_OLED_TYPE
endchoice
choice DISPLAY_LCD_TYPE
depends on BOARD_TYPE_BREAD_COMPACT_WIFI_LCD || BOARD_TYPE_BREAD_COMPACT_ESP32_LCD || BOARD_TYPE_CGC || BOARD_TYPE_WAVESHARE_P4_NANO || BOARD_TYPE_WAVESHARE_P4_WIFI6_TOUCH_LCD_XC || BOARD_TYPE_BREAD_COMPACT_WIFI_CAM
depends on BOARD_TYPE_BREAD_COMPACT_WIFI_LCD || BOARD_TYPE_BREAD_COMPACT_ESP32_LCD || BOARD_TYPE_CGC || BOARD_TYPE_BREAD_COMPACT_WIFI_CAM
prompt "LCD Type"
default LCD_ST7789_240X320
help
@@ -535,20 +600,12 @@ choice DISPLAY_LCD_TYPE
bool "ILI9341 240*320, Non-IPS"
config LCD_GC9A01_240X240
bool "GC9A01 240*240 Circle"
config LCD_TYPE_800_1280_10_1_INCH
bool "Waveshare 101M-8001280-IPS-CT-K Display"
config LCD_TYPE_800_1280_10_1_INCH_A
bool "Waveshare 10.1-DSI-TOUCH-A Display"
config LCD_TYPE_800_800_3_4_INCH
bool "Waveshare ESP32-P4-WIFI6-Touch-LCD-3.4C with 800*800 3.4inch round display"
config LCD_TYPE_720_720_4_INCH
bool "Waveshare ESP32-P4-WIFI6-Touch-LCD-4C with 720*720 4inch round display"
config LCD_CUSTOM
bool "Custom LCD (自定义屏幕参数)"
endchoice
choice DISPLAY_ESP32S3_KORVO2_V3
depends on BOARD_TYPE_ESP_KORVO2_V3
depends on BOARD_TYPE_ESP_KORVO2_V3 || BOARD_TYPE_ESP_KORVO2_V3_RNDIS
prompt "ESP32S3_KORVO2_V3 LCD Type"
default ESP32S3_KORVO2_V3_LCD_ST7789
help
@@ -560,7 +617,7 @@ choice DISPLAY_ESP32S3_KORVO2_V3
endchoice
choice DISPLAY_ESP32S3_AUDIO_BOARD
depends on BOARD_TYPE_WAVESHARE_S3_AUDIO_BOARD
depends on BOARD_TYPE_WAVESHARE_ESP32_S3_AUDIO_BOARD
prompt "ESP32S3_AUDIO_BOARD LCD Type"
default AUDIO_BOARD_LCD_JD9853
help
@@ -571,6 +628,19 @@ choice DISPLAY_ESP32S3_AUDIO_BOARD
bool "ST7789 240*320"
endchoice
choice DISPLAY_ESP32S3_TOUCH_LCD_1_85C
depends on BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_1_85C
prompt "ESP32S3_TOUCH_LCD_1_85C version"
default VERSION_2_0
help
hardware version
config VERSION_1_0
bool "version 1.0"
config VERSION_2_0
bool "version 2.0"
endchoice
choice DISPLAY_STYLE
prompt "Select display style"
default USE_DEFAULT_MESSAGE_STYLE
@@ -585,9 +655,22 @@ choice DISPLAY_STYLE
config USE_EMOTE_MESSAGE_STYLE
bool "Emote animation style"
depends on BOARD_TYPE_ESP_BOX_3 || BOARD_TYPE_ECHOEAR || BOARD_TYPE_LICHUANG_DEV_S3
depends on BOARD_TYPE_ESP_BOX || BOARD_TYPE_ESP_BOX_3 \
|| BOARD_TYPE_ECHOEAR || BOARD_TYPE_LICHUANG_DEV_S3 \
|| BOARD_TYPE_ESP_SENSAIRSHUTTLE
endchoice
config USE_MULTILINE_CHAT_MESSAGE
bool "Use multiline chat message display (default mode only)"
depends on USE_DEFAULT_MESSAGE_STYLE
default n
help
When enabled, the chat message area in the default display mode shows
multiple wrapped lines that grow upward from the bottom of the screen,
with auto-adaptive height.
When disabled (default), a single-line horizontally scrolling label
is shown at the bottom of the screen.
choice WAKE_WORD_TYPE
prompt "Wake Word Implementation Type"
default USE_AFE_WAKE_WORD if (IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32P4) && SPIRAM
@@ -649,6 +732,16 @@ config SEND_WAKE_WORD_DATA
help
Send wake word data to the server as the first message of the conversation and wait for response
config WAKE_WORD_DETECTION_IN_LISTENING
bool "Enable Wake Word Detection in Listening Mode"
default n
depends on USE_AFE_WAKE_WORD || USE_CUSTOM_WAKE_WORD
help
Enable wake word detection while in listening mode.
When enabled, the device can detect wake word during listening,
which allows interrupting the current conversation.
When disabled (default), wake word detection is turned off during listening.
config USE_AUDIO_PROCESSOR
bool "Enable Audio Noise Reduction"
default y
@@ -660,10 +753,12 @@ config USE_DEVICE_AEC
bool "Enable Device-Side AEC"
default n
depends on USE_AUDIO_PROCESSOR && (BOARD_TYPE_ESP_BOX_3 || BOARD_TYPE_ESP_BOX || BOARD_TYPE_ESP_BOX_LITE \
|| BOARD_TYPE_LICHUANG_DEV_S3 || BOARD_TYPE_ESP_KORVO2_V3 || BOARD_TYPE_WAVESHARE_S3_TOUCH_AMOLED_1_75 || BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_1_83\
|| BOARD_TYPE_WAVESHARE_S3_TOUCH_AMOLED_2_06 || BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_4B || BOARD_TYPE_WAVESHARE_P4_WIFI6_TOUCH_LCD_4B || BOARD_TYPE_WAVESHARE_P4_WIFI6_TOUCH_LCD_7B \
|| BOARD_TYPE_WAVESHARE_P4_WIFI6_TOUCH_LCD_XC || BOARD_TYPE_ESP_S3_LCD_EV_Board_2 || BOARD_TYPE_YUNLIAO_S3 \
|| BOARD_TYPE_ECHOEAR || BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_3_49)
|| BOARD_TYPE_LICHUANG_DEV_S3 || BOARD_TYPE_ESP_KORVO2_V3 || BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_75|| BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_75C || BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_1_83\
|| BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_2_06 || BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_4B || BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4B || BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_7B \
|| BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_3_4C || BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4C || BOARD_TYPE_ESP_S3_LCD_EV_Board_2 || BOARD_TYPE_YUNLIAO_S3 \
|| BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_7 || BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_8 || BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_10_1 \
|| BOARD_TYPE_ECHOEAR || BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_3_49 || BOARD_TYPE_WAVESHARE_ESP32_S3_RLCD_4_2 || BOARD_TYPE_ZHENGCHEN_CAM || BOARD_TYPE_ZHENGCHEN_CAM_ML307 \
|| BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_4_3C)
help
To work properly, device-side AEC requires a clean output reference path from the speaker signal and physical acoustic isolation between the microphone and speaker.
@@ -20,7 +20,8 @@ AppAiAgent::AppAiAgent()
// Configure App name
setAppInfo().name = "AI.AGENT";
// Configure App icon
setAppInfo().icon = (void*)&icon_ai_agent;
static auto icon = assets::get_image("icon_ai_agent.bin");
setAppInfo().icon = (void*)&icon;
// Configure App theme color
static uint32_t theme_color = 0x33CC99;
setAppInfo().userData = (void*)&theme_color;
@@ -0,0 +1,125 @@
/*
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
#include "app_app_center.h"
#include <hal/hal.h>
#include <mooncake.h>
#include <mooncake_log.h>
#include <assets/assets.h>
#include <apps/common/common.h>
#include <memory>
using namespace mooncake;
using namespace smooth_ui_toolkit::lvgl_cpp;
AppAppCenter::AppAppCenter()
{
// 配置 App 名
setAppInfo().name = "APP.CENTER";
// 配置 App 图标
static auto icon = assets::get_image("icon_app_center.bin");
setAppInfo().icon = (void*)&icon;
// 配置 App 主题颜色
static uint32_t theme_color = 0xF4A354;
setAppInfo().userData = (void*)&theme_color;
}
void AppAppCenter::onCreate()
{
mclog::tagInfo(getAppInfo().name, "on create");
}
void AppAppCenter::onOpen()
{
mclog::tagInfo(getAppInfo().name, "on open");
// Create loading page
std::unique_ptr<view::LoadingPage> loading_page;
{
LvglLockGuard lock;
loading_page = std::make_unique<view::LoadingPage>(0xF4A354, 0x332609);
}
// Start network
GetHAL().startNetwork([&](std::string_view msg) {
LvglLockGuard lock;
loading_page->setMessage(msg);
});
// Fetch app list
{
LvglLockGuard lock;
loading_page->setMessage("Fetching app list...");
}
_app_list = GetHAL().fetchAppList();
LvglLockGuard lock;
// Destroy loading page
loading_page.reset();
_app_list_page = std::make_unique<view::AppListPage>(_app_list);
view::create_home_indicator([&]() { close(); }, 0xFFDF9A, 0x47330A);
view::create_status_bar(0xFFDF9A, 0x47330A);
}
void AppAppCenter::onRunning()
{
if (_launch_requested) {
GetHAL().launchApp(_app_list[_selected_index].firmwareUrl, [&](int percent) {
LvglLockGuard lock;
if (_app_install_page) {
_app_install_page->setProgress(percent);
}
});
}
LvglLockGuard lock;
if (_app_list_page) {
_selected_index = _app_list_page->isSelected();
if (_selected_index >= 0) {
mclog::tagInfo(getAppInfo().name, "selected index: {}", _selected_index);
_app_list_page.reset();
_app_detail_page = std::make_unique<view::AppDetailPage>(_app_list[_selected_index]);
}
}
if (_app_detail_page) {
if (_app_detail_page->checkBack()) {
mclog::tagInfo(getAppInfo().name, "back to app list");
_app_detail_page.reset();
_app_list_page = std::make_unique<view::AppListPage>(_app_list);
} else if (_app_detail_page->checkLaunch()) {
mclog::tagInfo(getAppInfo().name, "launch app");
_app_detail_page.reset();
_app_install_page = std::make_unique<view::AppInstallPage>(_app_list[_selected_index]);
_launch_requested = true;
}
}
view::update_home_indicator();
view::update_status_bar();
}
void AppAppCenter::onClose()
{
mclog::tagInfo(getAppInfo().name, "on close");
LvglLockGuard lock;
_app_list.clear();
_app_list_page.reset();
_app_detail_page.reset();
_app_install_page.reset();
_selected_index = -1;
_launch_requested = false;
view::destroy_home_indicator();
view::destroy_status_bar();
GetHAL().requestWarmReboot(3);
}
@@ -0,0 +1,31 @@
/*
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include "view/view.h"
#include <mooncake.h>
/**
* @brief 派生 App
*
*/
class AppAppCenter : public mooncake::AppAbility {
public:
AppAppCenter();
// 重写生命周期回调
void onCreate() override;
void onOpen() override;
void onRunning() override;
void onClose() override;
private:
std::vector<app_center::AppInfo_t> _app_list;
std::unique_ptr<view::AppListPage> _app_list_page;
std::unique_ptr<view::AppDetailPage> _app_detail_page;
std::unique_ptr<view::AppInstallPage> _app_install_page;
int _selected_index = -1;
bool _launch_requested = false;
};
@@ -0,0 +1,278 @@
/*
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <hal/hal.h>
#include <smooth_lvgl.hpp>
#include <uitk/short_namespace.hpp>
#include <assets/assets.h>
#include <mooncake_log.h>
#include <functional>
#include <vector>
#include <string>
#include <memory>
namespace view {
/**
* @brief
*
*/
class AppListPage {
public:
AppListPage(const app_center::AppInfoList_t& app_list)
{
_panel = std::make_unique<uitk::lvgl_cpp::Container>(lv_screen_active());
_panel->setSize(320, 240);
_panel->setAlign(LV_ALIGN_CENTER);
_panel->setBgColor(lv_color_hex(0xFFFAD6));
_panel->setFlexFlow(LV_FLEX_FLOW_COLUMN);
_panel->setFlexAlign(LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
_panel->setPadding(52, 72, 0, 0);
_panel->setPadRow(18);
_panel->setRadius(0);
_panel->setBorderWidth(0);
_panel->setScrollbarMode(LV_SCROLLBAR_MODE_ACTIVE);
_bg_image = assets::get_image("app_center_bg.png");
if (_bg_image.data_size != 0) {
lv_obj_set_style_bg_image_src(_panel->get(), &_bg_image, LV_PART_MAIN);
}
_title_panel = std::make_unique<uitk::lvgl_cpp::Container>(lv_screen_active());
_title_panel->setSize(320, 28);
_title_panel->align(LV_ALIGN_TOP_MID, 0, 0);
_title_panel->setBgColor(lv_color_hex(0xF4A354));
_title_panel->setPadding(0, 0, 0, 0);
_title_panel->setRadius(0);
_title_panel->setBorderWidth(0);
_title_panel->setScrollbarMode(LV_SCROLLBAR_MODE_ACTIVE);
_title = std::make_unique<uitk::lvgl_cpp::Label>(*_title_panel);
_title->setText("App Center");
_title->setTextFont(&lv_font_montserrat_16);
_title->setTextColor(lv_color_hex(0xFFF8C7));
_title->align(LV_ALIGN_CENTER, 0, 0);
int index = 0;
for (const auto& app : app_list) {
auto btn = std::make_unique<uitk::lvgl_cpp::Button>(*_panel);
btn->setSize(282, 52);
btn->setRadius(18);
btn->setBorderWidth(0);
btn->setShadowWidth(0);
btn->setBgColor(lv_color_hex(0xFFDF9A));
btn->label().setText(app.name);
btn->label().setTextFont(&lv_font_montserrat_20);
btn->label().setTextColor(lv_color_hex(0x47330A));
btn->label().setLongMode(LV_LABEL_LONG_SCROLL_CIRCULAR);
btn->label().setWidth(220);
btn->label().setTextAlign(LV_TEXT_ALIGN_CENTER);
btn->label().setAlign(LV_ALIGN_CENTER);
btn->onClick().connect([this, index]() { _clicked_index = index; });
_buttons.push_back(std::move(btn));
index++;
}
}
int isSelected()
{
int temp = _clicked_index;
_clicked_index = -1;
return temp;
}
private:
std::unique_ptr<uitk::lvgl_cpp::Container> _panel;
std::unique_ptr<uitk::lvgl_cpp::Container> _title_panel;
std::unique_ptr<uitk::lvgl_cpp::Label> _title;
std::vector<std::unique_ptr<uitk::lvgl_cpp::Button>> _buttons;
lv_image_dsc_t _bg_image;
int _clicked_index = -1;
};
/**
* @brief
*
*/
class AppDetailPage {
public:
AppDetailPage(const app_center::AppInfo_t& app_info)
{
_panel = std::make_unique<uitk::lvgl_cpp::Container>(lv_screen_active());
_panel->setBgColor(lv_color_hex(0xFFFAD6));
_panel->align(LV_ALIGN_CENTER, 0, 0);
_panel->setBorderWidth(0);
_panel->setSize(320, 240);
_panel->setRadius(0);
_panel->setFlexFlow(LV_FLEX_FLOW_COLUMN);
_panel->setFlexAlign(LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
_panel->setPadding(48, 72, 0, 0);
_panel->setPadRow(18);
_panel->setScrollbarMode(LV_SCROLLBAR_MODE_ACTIVE);
_bg_image = assets::get_image("app_center_bg.png");
if (_bg_image.data_size != 0) {
lv_obj_set_style_bg_image_src(_panel->get(), &_bg_image, LV_PART_MAIN);
}
_title = std::make_unique<uitk::lvgl_cpp::Label>(*_panel);
_title->setText(app_info.name);
_title->setTextFont(&lv_font_montserrat_20);
_title->setTextColor(lv_color_hex(0x47330A));
_title->setTextAlign(LV_TEXT_ALIGN_CENTER);
_title->setLongMode(LV_LABEL_LONG_SCROLL_CIRCULAR);
_title->setWidth(220);
_title->setHeight(28);
_desc_label = std::make_unique<uitk::lvgl_cpp::Label>(*_panel);
_desc_label->setText(app_info.description);
_desc_label->setTextFont(&lv_font_montserrat_20);
_desc_label->setTextColor(lv_color_hex(0x47330A));
_desc_label->setTextAlign(LV_TEXT_ALIGN_LEFT);
_desc_label->setWidth(260);
_desc_label->setLongMode(LV_LABEL_LONG_WRAP);
_btn_container = std::make_unique<uitk::lvgl_cpp::Container>(*_panel);
_btn_container->setSize(LV_PCT(100), 80);
_btn_container->setBgOpa(LV_OPA_TRANSP);
_btn_container->setBorderWidth(0);
_btn_container->setPadding(0, 0, 0, 0);
_btn_container->setPadColumn(28);
_btn_container->setFlexFlow(LV_FLEX_FLOW_ROW);
_btn_container->setFlexAlign(LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
_btn_back = std::make_unique<uitk::lvgl_cpp::Button>(*_btn_container);
_btn_back->setSize(112, 48);
_btn_back->setRadius(18);
_btn_back->setBgColor(lv_color_hex(0xFFDF9A));
_btn_back->setBorderWidth(0);
_btn_back->setShadowWidth(0);
_btn_back->label().setText("Back");
_btn_back->label().setTextFont(&lv_font_montserrat_20);
_btn_back->label().setTextColor(lv_color_hex(0x47330A));
_btn_back->onClick().connect([this]() { _is_back = true; });
_btn_launch = std::make_unique<uitk::lvgl_cpp::Button>(*_btn_container);
_btn_launch->setSize(112, 48);
_btn_launch->setRadius(18);
_btn_launch->setBgColor(lv_color_hex(0xFFAC6D));
_btn_launch->setBorderWidth(0);
_btn_launch->setShadowWidth(0);
_btn_launch->label().setText("Launch");
_btn_launch->label().setTextFont(&lv_font_montserrat_20);
_btn_launch->label().setTextColor(lv_color_hex(0x47330A));
_btn_launch->onClick().connect([this]() { _is_launch = true; });
}
bool checkBack()
{
if (_is_back) {
_is_back = false;
return true;
}
return false;
}
bool checkLaunch()
{
if (_is_launch) {
_is_launch = false;
return true;
}
return false;
}
private:
std::unique_ptr<uitk::lvgl_cpp::Container> _panel;
std::unique_ptr<uitk::lvgl_cpp::Label> _title;
std::unique_ptr<uitk::lvgl_cpp::Label> _desc_label;
std::unique_ptr<uitk::lvgl_cpp::Container> _btn_container;
std::unique_ptr<uitk::lvgl_cpp::Button> _btn_back;
std::unique_ptr<uitk::lvgl_cpp::Button> _btn_launch;
lv_image_dsc_t _bg_image;
bool _is_back = false;
bool _is_launch = false;
};
/**
* @brief
*
*/
class AppInstallPage {
public:
AppInstallPage(const app_center::AppInfo_t& app_info)
{
_panel = std::make_unique<uitk::lvgl_cpp::Container>(lv_screen_active());
_panel->setBgColor(lv_color_hex(0xFFFAD6));
_panel->align(LV_ALIGN_CENTER, 0, 0);
_panel->setBorderWidth(0);
_panel->setSize(320, 240);
_panel->setRadius(0);
_panel->setPadding(0, 0, 0, 0);
_bg_image = assets::get_image("app_center_bg.png");
if (_bg_image.data_size != 0) {
lv_obj_set_style_bg_image_src(_panel->get(), &_bg_image, LV_PART_MAIN);
}
_title = std::make_unique<uitk::lvgl_cpp::Label>(*_panel);
_title->setText(app_info.name);
_title->setTextFont(&lv_font_montserrat_20);
_title->setTextColor(lv_color_hex(0x47330A));
_title->setTextAlign(LV_TEXT_ALIGN_CENTER);
_title->align(LV_ALIGN_TOP_MID, 0, 15);
_title->setLongMode(LV_LABEL_LONG_SCROLL_CIRCULAR);
_title->setWidth(220);
_title->setHeight(28);
_tips = std::make_unique<uitk::lvgl_cpp::Label>(*_panel);
_tips->setText("Downloading...");
_tips->setTextFont(&lv_font_montserrat_16);
_tips->setTextColor(lv_color_hex(0xA36135));
_tips->setTextAlign(LV_TEXT_ALIGN_CENTER);
_tips->setLongMode(LV_LABEL_LONG_SCROLL_CIRCULAR);
_tips->align(LV_ALIGN_CENTER, 0, -40);
_progress_bar = std::make_unique<uitk::lvgl_cpp::Bar>(*_panel);
_progress_bar->setSize(260, 92);
_progress_bar->setRadius(18);
_progress_bar->setRadius(0, LV_PART_INDICATOR);
_progress_bar->align(LV_ALIGN_CENTER, 0, 23);
_progress_bar->setBgColor(lv_color_hex(0xFFDF9A));
_progress_bar->setBgColor(lv_color_hex(0xFF9E5D), LV_PART_INDICATOR);
_progress_bar->setBgOpa(LV_OPA_COVER);
_progress_bar->setRange(0, 100);
_progress_bar->setValue(0);
_progress = std::make_unique<uitk::lvgl_cpp::Label>(*_panel);
_progress->setTextFont(&lv_font_montserrat_24);
_progress->setTextColor(lv_color_hex(0x47330A));
_progress->align(LV_ALIGN_CENTER, 0, 23);
_progress->setText("");
}
void setProgress(int percent)
{
_progress->setText(fmt::format("{}%", percent));
_progress_bar->setValue(percent);
}
private:
std::unique_ptr<uitk::lvgl_cpp::Container> _panel;
std::unique_ptr<uitk::lvgl_cpp::Label> _title;
std::unique_ptr<uitk::lvgl_cpp::Label> _tips;
std::unique_ptr<uitk::lvgl_cpp::Label> _progress;
std::unique_ptr<uitk::lvgl_cpp::Bar> _progress_bar;
lv_image_dsc_t _bg_image;
};
} // namespace view
+2 -1
View File
@@ -47,7 +47,8 @@ AppAvatar::AppAvatar()
// 配置 App 名
setAppInfo().name = "AVATAR";
// 配置 App 图标
setAppInfo().icon = (void*)&icon_sentinel;
static auto icon = assets::get_image("icon_sentinel.bin");
setAppInfo().icon = (void*)&icon;
// 配置 App 主题颜色
static uint32_t theme_color = 0xFF6699;
setAppInfo().userData = (void*)&theme_color;
+155
View File
@@ -0,0 +1,155 @@
/*
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
#include "app_dance.h"
#include <hal/hal.h>
#include <mooncake.h>
#include <mooncake_log.h>
#include <stackchan/stackchan.h>
#include <apps/common/common.h>
#include <assets/assets.h>
using namespace mooncake;
using namespace stackchan;
AppDance::AppDance()
{
// 配置 App 名
setAppInfo().name = "DANCE";
// 配置 App 图标
static auto icon = assets::get_image("icon_dance.bin");
setAppInfo().icon = (void*)&icon;
// 配置 App 主题颜色
static uint32_t theme_color = 0xB77BFF;
setAppInfo().userData = (void*)&theme_color;
}
// App 被安装时会被调用
void AppDance::onCreate()
{
mclog::tagInfo(getAppInfo().name, "on create");
}
void AppDance::onOpen()
{
mclog::tagInfo(getAppInfo().name, "on open");
// Create loading page
std::unique_ptr<view::LoadingPage> loading_page;
{
LvglLockGuard lock;
loading_page = std::make_unique<view::LoadingPage>(0xB77BFF, 0x422268);
loading_page->setMessage("Starting\n BLE server...");
}
// Start BLE service
GetHAL().startBleServer();
LvglLockGuard lock;
// Destroy loading page
loading_page.reset();
// Create default avatar
auto avatar = std::make_unique<avatar::DefaultAvatar>();
avatar->init(lv_screen_active());
GetStackChan().attachAvatar(std::move(avatar));
/* ------------------------------- BLE events ------------------------------- */
GetHAL().onBleAvatarData.connect([&](const char* data) {
std::lock_guard<std::mutex> lock(_mutex);
if (_ble_avatar_data.update_flag) {
return;
}
_ble_avatar_data.update_flag = true;
_ble_avatar_data.data_ptr = (char*)data;
});
GetHAL().onBleMotionData.connect([&](const char* data) {
std::lock_guard<std::mutex> lock(_mutex);
if (_ble_motion_data.update_flag) {
return;
}
_ble_motion_data.update_flag = true;
_ble_motion_data.data_ptr = (char*)data;
});
GetHAL().onBleRgbData.connect([&](const char* data) {
std::lock_guard<std::mutex> lock(_mutex);
if (_ble_rgb_data.update_flag) {
return;
}
_ble_rgb_data.update_flag = true;
_ble_rgb_data.data_ptr = (char*)data;
});
/* ----------------------------- Common widgets ----------------------------- */
view::create_home_indicator([&]() { close(); }, 0xB77BFF, 0x422268);
view::create_status_bar(0xB77BFF, 0x422268);
}
void AppDance::onRunning()
{
std::lock_guard<std::mutex> lock(_mutex);
LvglLockGuard lvgl_lock;
if (_ble_avatar_data.update_flag) {
GetStackChan().updateAvatarFromJson(_ble_avatar_data.data_ptr);
_ble_avatar_data.update_flag = false;
_ble_avatar_data.data_ptr = nullptr;
}
if (_ble_motion_data.update_flag) {
check_auto_angle_sync_mode();
GetStackChan().updateMotionFromJson(_ble_motion_data.data_ptr);
_ble_motion_data.update_flag = false;
_ble_motion_data.data_ptr = nullptr;
}
if (_ble_rgb_data.update_flag) {
GetStackChan().updateNeonLightFromJson(_ble_rgb_data.data_ptr);
_ble_rgb_data.update_flag = false;
_ble_rgb_data.data_ptr = nullptr;
}
GetStackChan().update();
view::update_home_indicator();
view::update_status_bar();
}
void AppDance::onClose()
{
mclog::tagInfo(getAppInfo().name, "on close");
{
LvglLockGuard lock;
GetStackChan().resetAvatar();
GetHAL().onBleAvatarData.clear();
GetHAL().onBleMotionData.clear();
view::destroy_home_indicator();
view::destroy_status_bar();
}
GetHAL().requestWarmReboot(5);
}
void AppDance::check_auto_angle_sync_mode()
{
auto& motion = GetStackChan().motion();
// If far from last command, enable auto angle sync
if (GetHAL().millis() - _last_motion_cmd_tick > 2000) {
motion.setAutoAngleSyncEnabled(true);
} else {
motion.setAutoAngleSyncEnabled(false);
}
_last_motion_cmd_tick = GetHAL().millis();
}
+38
View File
@@ -0,0 +1,38 @@
/*
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <mooncake.h>
#include <memory>
#include <mutex>
/**
* @brief
*
*/
class AppDance : public mooncake::AppAbility {
public:
AppDance();
void onCreate() override;
void onOpen() override;
void onRunning() override;
void onClose() override;
private:
std::mutex _mutex;
struct BleHandlerData_t {
bool update_flag = false;
char* data_ptr = nullptr;
};
BleHandlerData_t _ble_avatar_data;
BleHandlerData_t _ble_motion_data;
BleHandlerData_t _ble_rgb_data;
uint32_t _last_motion_cmd_tick = 0;
void check_auto_angle_sync_mode();
};
@@ -26,7 +26,8 @@ AppEspnowControl::AppEspnowControl()
// 配置 App 名
setAppInfo().name = "ESPNOW.REMOTE";
// 配置 App 图标
setAppInfo().icon = (void*)&icon_controller;
static auto icon = assets::get_image("icon_controller.bin");
setAppInfo().icon = (void*)&icon;
// 配置 App 主题颜色
static uint32_t theme_color = 0x7ACE74;
setAppInfo().userData = (void*)&theme_color;
@@ -80,7 +80,7 @@ private:
* @param options
* @return int Selected option index
*/
static inline int create_page_selector_and_wait(std::string_view label, const std::vector<std::string> options)
static inline int create_page_selector_and_wait(std::string_view label, const std::vector<std::string>& options)
{
GetHAL().lvglLock();
auto page_selector = std::make_unique<view::PageSelector>(label, options);
@@ -0,0 +1,101 @@
/*
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
#include "app_ezdata.h"
#include <hal/hal.h>
#include <mooncake.h>
#include <mooncake_log.h>
#include <assets/assets.h>
#include <stackchan/stackchan.h>
using namespace mooncake;
using namespace stackchan;
AppEzdata::AppEzdata()
{
// 配置 App 名
setAppInfo().name = "EZDATA";
// 配置 App 图标
static auto icon = assets::get_image("icon_ezdata.bin");
setAppInfo().icon = (void*)&icon;
// 配置 App 主题颜色
static uint32_t theme_color = 0x60A5FA;
setAppInfo().userData = (void*)&theme_color;
}
void AppEzdata::onCreate()
{
mclog::tagInfo(getAppInfo().name, "on create");
}
void AppEzdata::onOpen()
{
mclog::tagInfo(getAppInfo().name, "on open");
{
LvglLockGuard lock;
// Create default avatar
auto avatar = std::make_unique<avatar::DefaultAvatar>();
avatar->init(lv_screen_active());
GetStackChan().attachAvatar(std::move(avatar));
// Create loading page
_loading_page = std::make_unique<view::LoadingPage>(0x60A5FA, 0x072448);
}
// Start ezdata service
GetHAL().startEzDataService([&](std::string_view msg) {
LvglLockGuard lock;
_loading_page->setMessage(msg);
});
LvglLockGuard lock;
/* --------------------------------- Events --------------------------------- */
GetHAL().onEzdataPairCode.connect([this](std::string_view code) {
LvglLockGuard lock;
// Destroy loading page
_loading_page.reset();
// Client connected
if (code.empty()) {
_ezdata_guide_page.reset();
return;
}
_ezdata_guide_page = std::make_unique<view::EzdataGuidePage>(code);
});
// _ezdata_guide_page = std::make_unique<view::EzdataGuidePage>("941004");
/* ----------------------------- Common widgets ----------------------------- */
view::create_home_indicator([&]() { close(); }, 0x93C3FE, 0x072448);
view::create_status_bar(0x93C3FE, 0x072448);
}
void AppEzdata::onRunning()
{
LvglLockGuard lock;
GetStackChan().update();
view::update_home_indicator();
view::update_status_bar();
}
void AppEzdata::onClose()
{
mclog::tagInfo(getAppInfo().name, "on close");
LvglLockGuard lock;
GetStackChan().resetAvatar();
view::destroy_home_indicator();
view::destroy_status_bar();
GetHAL().requestWarmReboot(4);
}
@@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include "view/view.h"
#include <mooncake.h>
#include <apps/common/common.h>
/**
* @brief 派生 App
*
*/
class AppEzdata : public mooncake::AppAbility {
public:
AppEzdata();
void onCreate() override;
void onOpen() override;
void onRunning() override;
void onClose() override;
private:
std::unique_ptr<view::LoadingPage> _loading_page;
std::unique_ptr<view::EzdataGuidePage> _ezdata_guide_page;
};
+84
View File
@@ -0,0 +1,84 @@
/*
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <hal/hal.h>
#include <smooth_lvgl.hpp>
#include <uitk/short_namespace.hpp>
#include <string_view>
#include <vector>
#include <string>
#include <memory>
namespace view {
/**
* @brief
*
*/
class EzdataGuidePage {
public:
EzdataGuidePage(std::string_view pairCode)
{
_panel = std::make_unique<uitk::lvgl_cpp::Container>(lv_screen_active());
_panel->setBgColor(lv_color_hex(0x60A5FA));
_panel->align(LV_ALIGN_CENTER, 0, 0);
_panel->setBorderWidth(0);
_panel->setSize(320, 240);
_panel->setRadius(0);
_panel->setPadding(0, 0, 0, 0);
_panel_url = std::make_unique<uitk::lvgl_cpp::Container>(*_panel);
_panel_url->setScrollbarMode(LV_SCROLLBAR_MODE_OFF);
_panel_url->setBgColor(lv_color_hex(0xFFFFFF));
_panel_url->align(LV_ALIGN_TOP_MID, 0, 42);
_panel_url->setBorderWidth(0);
_panel_url->setSize(296, 51);
_panel_url->setRadius(18);
_panel_pair_code = std::make_unique<uitk::lvgl_cpp::Container>(*_panel);
_panel_pair_code->setScrollbarMode(LV_SCROLLBAR_MODE_OFF);
_panel_pair_code->setBgColor(lv_color_hex(0xFFFFFF));
_panel_pair_code->align(LV_ALIGN_TOP_MID, 0, 138);
_panel_pair_code->setBorderWidth(0);
_panel_pair_code->setSize(296, 86);
_panel_pair_code->setRadius(18);
_title_url = std::make_unique<uitk::lvgl_cpp::Label>(*_panel);
_title_url->setText("Ezdata Web client:");
_title_url->setTextFont(&lv_font_montserrat_16);
_title_url->setTextColor(lv_color_hex(0x0E2648));
_title_url->align(LV_ALIGN_TOP_LEFT, 27, 16);
_msg_url = std::make_unique<uitk::lvgl_cpp::Label>(*_panel_url);
_msg_url->setText("https://my.m5stack.com/ezdata2");
_msg_url->setTextFont(&lv_font_montserrat_16);
_msg_url->setTextColor(lv_color_hex(0x0E2648));
_msg_url->align(LV_ALIGN_CENTER, 0, 0);
_title_pair_code = std::make_unique<uitk::lvgl_cpp::Label>(*_panel);
_title_pair_code->setText("Pair Code:");
_title_pair_code->setTextFont(&lv_font_montserrat_16);
_title_pair_code->setTextColor(lv_color_hex(0x0E2648));
_title_pair_code->align(LV_ALIGN_TOP_LEFT, 27, 113);
_msg_pair_code = std::make_unique<uitk::lvgl_cpp::Label>(*_panel_pair_code);
_msg_pair_code->setText(pairCode);
_msg_pair_code->setTextFont(&lv_font_montserrat_24);
_msg_pair_code->setTextColor(lv_color_hex(0x0E2648));
_msg_pair_code->align(LV_ALIGN_CENTER, 0, 0);
}
private:
std::unique_ptr<uitk::lvgl_cpp::Container> _panel;
std::unique_ptr<uitk::lvgl_cpp::Container> _panel_url;
std::unique_ptr<uitk::lvgl_cpp::Container> _panel_pair_code;
std::unique_ptr<uitk::lvgl_cpp::Label> _title_url;
std::unique_ptr<uitk::lvgl_cpp::Label> _msg_url;
std::unique_ptr<uitk::lvgl_cpp::Label> _title_pair_code;
std::unique_ptr<uitk::lvgl_cpp::Label> _msg_pair_code;
};
} // namespace view
@@ -394,6 +394,7 @@ void LauncherView::init(std::vector<mooncake::AppProps_t> appPorps)
_lr_indicator_panels.back()->onClick().connect([scroll_to_nearby_icon]() { scroll_to_nearby_icon(-1); });
_lr_indicators_images.push_back(std::make_unique<Image>(_lr_indicator_panels.back()->get()));
static auto icon_indicator_left = assets::get_image("icon_indicator_left.bin");
_lr_indicators_images.back()->setSrc(&icon_indicator_left);
_lr_indicators_images.back()->align(LV_ALIGN_CENTER, 0, 0);
@@ -409,6 +410,7 @@ void LauncherView::init(std::vector<mooncake::AppProps_t> appPorps)
_lr_indicator_panels.back()->onClick().connect([scroll_to_nearby_icon]() { scroll_to_nearby_icon(1); });
_lr_indicators_images.push_back(std::make_unique<Image>(_lr_indicator_panels.back()->get()));
static auto icon_indicator_right = assets::get_image("icon_indicator_right.bin");
_lr_indicators_images.back()->setSrc(&icon_indicator_right);
_lr_indicators_images.back()->align(LV_ALIGN_CENTER, 0, 0);
+79 -57
View File
@@ -20,7 +20,8 @@ AppSetup::AppSetup()
// 配置 App 名
setAppInfo().name = "SETUP";
// 配置 App 图标
setAppInfo().icon = (void*)&icon_setup;
static auto icon = assets::get_image("icon_setup.bin");
setAppInfo().icon = (void*)&icon;
// 配置 App 主题颜色
static uint32_t theme_color = 0xB3B3B3;
setAppInfo().userData = (void*)&theme_color;
@@ -41,61 +42,82 @@ void AppSetup::onOpen()
_need_warm_reset = false;
_magic_count = 0;
_menu_sections = {{
"Connectivity",
{{"Set Up Wi-Fi",
[&]() {
_destroy_menu = true;
_need_warm_reset = true;
_worker = std::make_unique<WifiSetupWorker>();
}}},
},
{
"Servo",
{{"Calibration",
[&]() {
_destroy_menu = true;
_worker = std::make_unique<ZeroCalibrationWorker>();
}},
{"LED Strips Test",
[&]() {
_destroy_menu = true;
_worker = std::make_unique<RgbTestWorker>();
}}},
},
{
"Display",
{{"Brightness",
[&]() {
_destroy_menu = true;
_worker = std::make_unique<BrightnessSetupWorker>();
}}},
},
{
"System",
{{"Timezone",
[&]() {
_destroy_menu = true;
_worker = std::make_unique<TimezoneWorker>();
}}},
},
{
"About",
{{fmt::format("FW Version: {}", common::FirmwareVersion),
[&]() {
_magic_count++;
if (_magic_count >= 10) {
_magic_count = 0;
_destroy_menu = true;
_worker = std::make_unique<FwVersionWorker>();
}
}},
{"Factory Reset",
[&]() {
_destroy_menu = true;
_worker = std::make_unique<FactoryResetWorker>();
}}},
}};
_menu_sections = {
{
"Wi-Fi",
{{"Change Wi-Fi",
[&]() {
_destroy_menu = true;
_need_warm_reset = true;
_worker = std::make_unique<WifiSetupWorker>();
}}},
},
{
"Device",
{{"Brightness",
[&]() {
_destroy_menu = true;
_worker = std::make_unique<BrightnessSetupWorker>();
}},
{"Volume",
[&]() {
_destroy_menu = true;
_worker = std::make_unique<VolumeSetupWorker>();
}},
{"Timezone",
[&]() {
_destroy_menu = true;
_worker = std::make_unique<TimezoneWorker>();
}}},
},
{
"Hardware Test",
{{"Servo",
[&]() {
_destroy_menu = true;
_worker = std::make_unique<ZeroCalibrationWorker>();
}},
{"RGB Strip",
[&]() {
_destroy_menu = true;
_worker = std::make_unique<RgbTestWorker>();
}}},
},
{
"Account",
{{"Unbind & Reset",
[&]() {
_destroy_menu = true;
_need_warm_reset = true;
_worker = std::make_unique<AccountWorker>();
}}},
},
{
"Firmware",
{
{fmt::format("Version: {}", common::FirmwareVersion),
[&]() {
_magic_count++;
if (_magic_count >= 10) {
_magic_count = 0;
_destroy_menu = true;
_worker = std::make_unique<FwVersionWorker>();
}
}},
{"Check for Updates",
[&]() {
_destroy_menu = true;
_need_warm_reset = true;
_worker = std::make_unique<SystemUpdateWorker>();
}},
// {"Factory Reset",
// [&]() {
// _destroy_menu = true;
// _worker = std::make_unique<FactoryResetWorker>();
// }}
},
},
};
LvglLockGuard lock;
@@ -146,6 +168,6 @@ void AppSetup::onClose()
view::destroy_status_bar();
if (_need_warm_reset) {
GetHAL().requestWarmReboot(3);
GetHAL().requestWarmReboot(6);
}
}
@@ -13,6 +13,7 @@
#include <hal/hal.h>
#include <functional>
#include <vector>
#include <apps/common/loading_page/loading_page.h>
using namespace uitk;
using namespace uitk::lvgl_cpp;
@@ -294,3 +295,35 @@ void FwVersionWorker::update()
_egg->update();
}
}
SystemUpdateWorker::SystemUpdateWorker()
{
auto loading_page = std::make_unique<view::LoadingPage>(0xF6F6F6, 0x26206A);
GetHAL().lvglUnlock();
// Start network
GetHAL().startNetwork([&](std::string_view msg) {
LvglLockGuard lock;
loading_page->setMessage(msg);
});
// Update Firmware
bool result = GetHAL().updateFirmware([&](std::string_view msg) {
LvglLockGuard lock;
loading_page->setMessage(msg);
});
// Hold the result for a while
GetHAL().delay(3000);
GetHAL().lvglLock();
_is_done = true;
}
SystemUpdateWorker::~SystemUpdateWorker()
{
}
void SystemUpdateWorker::update()
{
}

Some files were not shown because too many files have changed in this diff Show More